import AppKit /// Borderless NSWindow subclass that can become key, allowing text field input. class KeyableWindow: NSWindow { override var canBecomeKey: Bool { true } override var canBecomeMain: Bool { true } } extension AppDelegate { func setupWindows() { for screen in NSScreen.screens { let window = KeyableWindow( contentRect: screen.frame, styleMask: .borderless, backing: .buffered, defer: false ) window.level = .screenSaver window.backgroundColor = .clear window.isOpaque = false window.hasShadow = false window.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary] window.ignoresMouseEvents = true window.isReleasedWhenClosed = false let gradientView = EdgeGradientView(frame: NSRect(origin: .zero, size: screen.frame.size)) gradientView.autoresizingMask = [.width, .height] window.contentView = gradientView window.alphaValue = 0.0 window.orderFrontRegardless() overlayWindows.append(window) let pillW: CGFloat = 200 let pillH: CGFloat = 80 let pillX = screen.frame.origin.x + (screen.frame.width - pillW) / 2 let pillY = screen.frame.origin.y + 40 let pillWindow = NSWindow( contentRect: NSRect(x: pillX, y: pillY, width: pillW, height: pillH), styleMask: .borderless, backing: .buffered, defer: false ) pillWindow.level = .screenSaver pillWindow.backgroundColor = .clear pillWindow.isOpaque = false pillWindow.hasShadow = false pillWindow.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary] pillWindow.ignoresMouseEvents = true pillWindow.isReleasedWhenClosed = false let pillView = NSView(frame: NSRect(x: 0, y: 0, width: pillW, height: pillH)) pillView.wantsLayer = true pillView.layer?.backgroundColor = NSColor.black.withAlphaComponent(0.6).cgColor pillView.layer?.cornerRadius = pillH / 2 let label = NSTextField(labelWithString: "00:10") label.font = NSFont.monospacedSystemFont(ofSize: 36, weight: .bold) label.textColor = Settings.shared.gradientColor label.alignment = .center label.frame = NSRect(x: 0, y: 14, width: pillW, height: 50) pillView.addSubview(label) pillWindow.contentView = pillView pillWindow.alphaValue = 0.0 pillWindow.orderFrontRegardless() countdownWindows.append(pillWindow) countdownLabels.append(label) } } func showCountdownPills() { for window in countdownWindows { if window.alphaValue < 1.0 { NSAnimationContext.runAnimationGroup { ctx in ctx.duration = 0.2 window.animator().alphaValue = 1.0 } } } } func hideCountdownPills() { for window in countdownWindows { window.alphaValue = 0.0 } } func updateCountdownLabels() { let seconds = max(remainingSeconds, 0) let text = String(format: "00:%02d", seconds) for label in countdownLabels { label.stringValue = text } } /// Single entry point for pulse schedule. Used by both work countdown and break. func applyPulseSchedule(remainingSeconds: Int, totalSeconds: Int) { if CountdownPulseSchedule.shouldBeSolid(remainingSeconds: remainingSeconds) { if !isSolid { makeSolid() } } else if CountdownPulseSchedule.shouldBlink(remainingSeconds: remainingSeconds) { triggerBlink(remainingSeconds: remainingSeconds, totalSeconds: totalSeconds) } } func triggerBlink(remainingSeconds: Int, totalSeconds: Int) { let settings = Settings.shared let scale = CGFloat(settings.intensityScale) let durationScale = settings.blinkDurationScale let countdownElapsed = Double(totalSeconds - remainingSeconds) let countdownTotal = Double(totalSeconds) let progress = min(max(countdownElapsed / countdownTotal, 0.0), 1.0) let baseIntensity = 0.15 + (0.60 * progress) // 0.15 at start, up to 0.75 at 0:00 let intensity = min(baseIntensity * Double(scale), 1.0) let d = CountdownPulseSchedule.blinkDurations(remainingSeconds: remainingSeconds, durationScale: durationScale) let fadeIn = d.fadeIn let hold = d.hold let fadeOut = d.fadeOut let color = settings.gradientColor for window in overlayWindows { guard let view = window.contentView as? EdgeGradientView else { continue } view.intensity = CGFloat(intensity) view.color = color view.needsDisplay = true NSAnimationContext.runAnimationGroup { ctx in ctx.duration = fadeIn window.animator().alphaValue = 1.0 } completionHandler: { DispatchQueue.main.asyncAfter(deadline: .now() + hold) { NSAnimationContext.runAnimationGroup({ ctx in ctx.duration = fadeOut window.animator().alphaValue = 0.0 }) } } } } func makeSolid() { isSolid = true let settings = Settings.shared let maxIntensity = min(0.75 * CGFloat(settings.intensityScale), 1.0) let color = settings.gradientColor for window in overlayWindows { guard let view = window.contentView as? EdgeGradientView else { continue } view.intensity = maxIntensity view.color = color view.needsDisplay = true NSAnimationContext.runAnimationGroup { ctx in ctx.duration = 0.3 window.animator().alphaValue = 1.0 } } } func resetOverlayToGradient() { for window in overlayWindows { window.ignoresMouseEvents = true let size = window.frame.size let gradientView = EdgeGradientView(frame: NSRect(origin: .zero, size: size)) gradientView.autoresizingMask = [.width, .height] window.contentView = gradientView window.alphaValue = 0.0 } } }