import AppKit extension AppDelegate { func showFullOverlay() { countdownTimer?.invalidate() currentSuggestion = SuggestionManager.shared.next() statusItem.button?.title = "🍅 Time's Up!" for window in countdownWindows { window.orderOut(nil) } breakCountdownLabels.removeAll() breakRemainingSeconds = 300 startBreakTimer() for (i, window) in overlayWindows.enumerated() { window.ignoresMouseEvents = false buildSuggestionScreen(window: window, isPrimary: i == 0) NSAnimationContext.runAnimationGroup { ctx in ctx.duration = 0.3 window.animator().alphaValue = 1.0 } } } func startBreakTimer() { breakTimer?.invalidate() breakTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] timer in guard let self = self else { timer.invalidate(); return } self.breakRemainingSeconds -= 1 self.updateBreakCountdownLabels() if self.breakRemainingSeconds <= 0 { timer.invalidate() } } } func updateBreakCountdownLabels() { let minutes = breakRemainingSeconds / 60 let seconds = breakRemainingSeconds % 60 let text = String(format: "%02d:%02d", minutes, seconds) for label in breakCountdownLabels { label.stringValue = text } } private var breakScreenColor: NSColor { Settings.shared.gradientColor.withAlphaComponent(0.85) } /// Replaces overlay with suggestion UI. Called when break countdown reaches 0 (same pulse schedule as work). func showSuggestionScreen() { for window in countdownWindows { window.orderOut(nil) } for (i, window) in overlayWindows.enumerated() { window.ignoresMouseEvents = false buildSuggestionScreen(window: window, isPrimary: i == 0) NSAnimationContext.runAnimationGroup { ctx in ctx.duration = 0.3 window.animator().alphaValue = 1.0 } } } func buildSuggestionScreen(window: NSWindow, isPrimary: Bool) { let size = window.frame.size let view = NSView(frame: NSRect(origin: .zero, size: size)) view.wantsLayer = true view.layer?.backgroundColor = breakScreenColor.cgColor let countdownLabel = NSTextField(labelWithString: "05:00") countdownLabel.font = NSFont.monospacedSystemFont(ofSize: 72, weight: .bold) countdownLabel.textColor = .white countdownLabel.alignment = .center countdownLabel.sizeToFit() countdownLabel.frame.origin = CGPoint( x: (size.width - countdownLabel.frame.width) / 2, y: (size.height / 2) + 80 ) view.addSubview(countdownLabel) breakCountdownLabels.append(countdownLabel) let suggestion = NSTextField(labelWithString: currentSuggestion) suggestion.font = NSFont.systemFont(ofSize: 48, weight: .medium) suggestion.textColor = NSColor.white.withAlphaComponent(0.9) suggestion.alignment = .center suggestion.sizeToFit() suggestion.frame.origin = CGPoint( x: (size.width - suggestion.frame.width) / 2, y: (size.height / 2) - 10 ) view.addSubview(suggestion) if isPrimary { let closeBtn = NSButton(frame: NSRect(x: size.width - 140, y: size.height - 60, width: 120, height: 36)) closeBtn.title = "Close" closeBtn.bezelStyle = .rounded closeBtn.font = NSFont.systemFont(ofSize: 14, weight: .medium) closeBtn.target = self closeBtn.action = #selector(showWorkLog) view.addSubview(closeBtn) let continueWorkingBtn = NSButton(frame: NSRect(x: (size.width - 200) / 2, y: size.height / 2 - 50, width: 200, height: 50)) continueWorkingBtn.title = "Continue Working" continueWorkingBtn.bezelStyle = .rounded continueWorkingBtn.font = NSFont.systemFont(ofSize: 18, weight: .semibold) continueWorkingBtn.target = self continueWorkingBtn.action = #selector(resumePommedoro) view.addSubview(continueWorkingBtn) let btnY = size.height / 2 - 110 let btnW: CGFloat = 160 let btnH: CGFloat = 44 let gap: CGFloat = 16 let totalW = btnW * 3 + gap * 2 let startX = (size.width - totalW) / 2 let successBtn = NSButton(frame: NSRect(x: startX, y: btnY, width: btnW, height: btnH)) successBtn.title = "Success!" successBtn.bezelStyle = .rounded successBtn.font = NSFont.systemFont(ofSize: 16, weight: .semibold) successBtn.target = self successBtn.action = #selector(didSuccess) view.addSubview(successBtn) let nextTimeBtn = NSButton(frame: NSRect(x: startX + btnW + gap, y: btnY, width: btnW, height: btnH)) nextTimeBtn.title = "Next Time" nextTimeBtn.bezelStyle = .rounded nextTimeBtn.font = NSFont.systemFont(ofSize: 16, weight: .semibold) nextTimeBtn.target = self nextTimeBtn.action = #selector(skipSuggestion) view.addSubview(nextTimeBtn) let dontSuggestBtn = NSButton(frame: NSRect(x: startX + (btnW + gap) * 2, y: btnY, width: btnW, height: btnH)) dontSuggestBtn.title = "Don't Suggest" dontSuggestBtn.bezelStyle = .rounded dontSuggestBtn.font = NSFont.systemFont(ofSize: 16, weight: .semibold) dontSuggestBtn.target = self dontSuggestBtn.action = #selector(dismissSuggestion) view.addSubview(dontSuggestBtn) } window.contentView = view } @objc func skipSuggestion() { currentSuggestionFeedback = .skipped resumePommedoro() } @objc func didSuccess() { currentSuggestionFeedback = .didIt breakCountdownLabels.removeAll() let bgColor = breakScreenColor for (i, window) in overlayWindows.enumerated() { let size = window.frame.size let view = NSView(frame: NSRect(origin: .zero, size: size)) view.wantsLayer = true view.layer?.backgroundColor = bgColor.cgColor let countdownLabel = NSTextField(labelWithString: String(format: "%02d:%02d", breakRemainingSeconds / 60, breakRemainingSeconds % 60)) countdownLabel.font = NSFont.monospacedSystemFont(ofSize: 36, weight: .bold) countdownLabel.textColor = NSColor.white.withAlphaComponent(0.7) countdownLabel.alignment = .center countdownLabel.sizeToFit() countdownLabel.frame.origin = CGPoint( x: (size.width - countdownLabel.frame.width) / 2, y: size.height - 80 ) view.addSubview(countdownLabel) breakCountdownLabels.append(countdownLabel) let congrats = NSTextField(labelWithString: "Nice work!") congrats.font = NSFont.systemFont(ofSize: 72, weight: .bold) congrats.textColor = .white congrats.alignment = .center congrats.sizeToFit() congrats.frame.origin = CGPoint( x: (size.width - congrats.frame.width) / 2, y: (size.height / 2) + 80 ) view.addSubview(congrats) let prompt = NSTextField(labelWithString: "How did that impact your readiness for the day?") prompt.font = NSFont.systemFont(ofSize: 36, weight: .medium) prompt.textColor = NSColor.white.withAlphaComponent(0.9) prompt.alignment = .center prompt.sizeToFit() prompt.frame.origin = CGPoint( x: (size.width - prompt.frame.width) / 2, y: (size.height / 2) + 10 ) view.addSubview(prompt) if i == 0 { let closeBtn = NSButton(frame: NSRect(x: size.width - 140, y: size.height - 60, width: 120, height: 36)) closeBtn.title = "Close" closeBtn.bezelStyle = .rounded closeBtn.font = NSFont.systemFont(ofSize: 14, weight: .medium) closeBtn.target = self closeBtn.action = #selector(showWorkLog) view.addSubview(closeBtn) let reflY = size.height / 2 - 60 let btnW: CGFloat = 180 let btnH: CGFloat = 44 let gap: CGFloat = 16 let totalW = btnW * 3 + gap * 2 let startX = (size.width - totalW) / 2 let greatBtn = NSButton(frame: NSRect(x: startX, y: reflY, width: btnW, height: btnH)) greatBtn.title = "Feeling Great" greatBtn.bezelStyle = .rounded greatBtn.font = NSFont.systemFont(ofSize: 15, weight: .medium) greatBtn.target = self greatBtn.action = #selector(reflectionGreat) view.addSubview(greatBtn) let okBtn = NSButton(frame: NSRect(x: startX + btnW + gap, y: reflY, width: btnW, height: btnH)) okBtn.title = "About the Same" okBtn.bezelStyle = .rounded okBtn.font = NSFont.systemFont(ofSize: 15, weight: .medium) okBtn.target = self okBtn.action = #selector(reflectionSame) view.addSubview(okBtn) let notBtn = NSButton(frame: NSRect(x: startX + (btnW + gap) * 2, y: reflY, width: btnW, height: btnH)) notBtn.title = "Not Really" notBtn.bezelStyle = .rounded notBtn.font = NSFont.systemFont(ofSize: 15, weight: .medium) notBtn.target = self notBtn.action = #selector(reflectionNotReally) view.addSubview(notBtn) } window.contentView = view } } @objc func reflectionGreat() { currentWellnessReflection = .great; reflectionDone() } @objc func reflectionSame() { currentWellnessReflection = .same; reflectionDone() } @objc func reflectionNotReally() { currentWellnessReflection = .notReally; reflectionDone() } @objc func reflectionDone() { breakCountdownLabels.removeAll() reflectionField = nil intentField = nil let bgColor = breakScreenColor for (i, window) in overlayWindows.enumerated() { let size = window.frame.size let view = NSView(frame: NSRect(origin: .zero, size: size)) view.wantsLayer = true view.layer?.backgroundColor = bgColor.cgColor let countdownLabel = NSTextField(labelWithString: String(format: "%02d:%02d", breakRemainingSeconds / 60, breakRemainingSeconds % 60)) countdownLabel.font = NSFont.monospacedSystemFont(ofSize: 36, weight: .bold) countdownLabel.textColor = NSColor.white.withAlphaComponent(0.7) countdownLabel.alignment = .center countdownLabel.sizeToFit() countdownLabel.frame.origin = CGPoint( x: (size.width - countdownLabel.frame.width) / 2, y: size.height - 80 ) view.addSubview(countdownLabel) breakCountdownLabels.append(countdownLabel) let msg = NSTextField(labelWithString: "Ready when you are") msg.font = NSFont.systemFont(ofSize: 56, weight: .bold) msg.textColor = .white msg.alignment = .center msg.sizeToFit() msg.frame.origin = CGPoint( x: (size.width - msg.frame.width) / 2, y: (size.height / 2) + 120 ) view.addSubview(msg) if i == 0 { let closeBtn = NSButton(frame: NSRect(x: size.width - 140, y: size.height - 60, width: 120, height: 36)) closeBtn.title = "Close" closeBtn.bezelStyle = .rounded closeBtn.font = NSFont.systemFont(ofSize: 14, weight: .medium) closeBtn.target = self closeBtn.action = #selector(showWorkLog) view.addSubview(closeBtn) // Reflection text field let fieldW: CGFloat = 560 let fieldH: CGFloat = 48 let reflLabel = NSTextField(labelWithString: "What did you accomplish this interval?") reflLabel.font = NSFont.systemFont(ofSize: 18, weight: .medium) reflLabel.textColor = NSColor.white.withAlphaComponent(0.9) reflLabel.alignment = .center reflLabel.sizeToFit() reflLabel.frame.origin = CGPoint( x: (size.width - reflLabel.frame.width) / 2, y: (size.height / 2) + 76 ) view.addSubview(reflLabel) let reflField = NSTextField(frame: NSRect( x: (size.width - fieldW) / 2, y: (size.height / 2) + 24, width: fieldW, height: fieldH )) reflField.placeholderString = "Optional — jot down what you worked on" reflField.font = NSFont.systemFont(ofSize: 20) reflField.alignment = .center reflField.bezelStyle = .roundedBezel reflField.isBordered = true reflField.drawsBackground = true reflField.backgroundColor = NSColor.textBackgroundColor reflField.textColor = NSColor.labelColor reflField.wantsLayer = true reflField.layer?.cornerRadius = 10 reflField.focusRingType = .default reflField.isEditable = true reflField.isSelectable = true view.addSubview(reflField) self.reflectionField = reflField // Intent text field let intentLabel = NSTextField(labelWithString: "What do you intend to work on next?") intentLabel.font = NSFont.systemFont(ofSize: 18, weight: .medium) intentLabel.textColor = NSColor.white.withAlphaComponent(0.9) intentLabel.alignment = .center intentLabel.sizeToFit() intentLabel.frame.origin = CGPoint( x: (size.width - intentLabel.frame.width) / 2, y: (size.height / 2) - 10 ) view.addSubview(intentLabel) let intField = NSTextField(frame: NSRect( x: (size.width - fieldW) / 2, y: (size.height / 2) - 62, width: fieldW, height: fieldH )) intField.placeholderString = "Optional — set your intention for the next interval" intField.font = NSFont.systemFont(ofSize: 20) intField.alignment = .center intField.bezelStyle = .roundedBezel intField.isBordered = true intField.drawsBackground = true intField.backgroundColor = NSColor.textBackgroundColor intField.textColor = NSColor.labelColor intField.wantsLayer = true intField.layer?.cornerRadius = 10 intField.focusRingType = .default intField.isEditable = true intField.isSelectable = true view.addSubview(intField) self.intentField = intField // Pre-fill with the current or most recent intent from today if let intent = self.currentIntent, !intent.isEmpty { intField.stringValue = intent } else if let lastIntent = WorkLogStore.shared.lastTodayIntent() { intField.stringValue = lastIntent } // Buttons row: Pause | Finish Day | Continue let btnW: CGFloat = 160 let btnH: CGFloat = 50 let gap: CGFloat = 20 let btnCount: CGFloat = 3 let totalW = btnW * btnCount + gap * (btnCount - 1) let startX = (size.width - totalW) / 2 let btnY = size.height / 2 - 140 let pauseBtn = NSButton(frame: NSRect(x: startX, y: btnY, width: btnW, height: btnH)) pauseBtn.title = "Pause" pauseBtn.bezelStyle = .rounded pauseBtn.font = NSFont.systemFont(ofSize: 18, weight: .semibold) pauseBtn.target = self pauseBtn.action = #selector(pauseFromOverlay) view.addSubview(pauseBtn) let finishBtn = NSButton(frame: NSRect(x: startX + btnW + gap, y: btnY, width: btnW, height: btnH)) finishBtn.title = "Finish Day" finishBtn.bezelStyle = .rounded finishBtn.font = NSFont.systemFont(ofSize: 18, weight: .semibold) finishBtn.target = self finishBtn.action = #selector(showWorkLog) view.addSubview(finishBtn) let continueBtn = NSButton(frame: NSRect(x: startX + (btnW + gap) * 2, y: btnY, width: btnW, height: btnH)) continueBtn.title = "Continue" continueBtn.bezelStyle = .rounded continueBtn.font = NSFont.systemFont(ofSize: 18, weight: .semibold) continueBtn.target = self continueBtn.action = #selector(resumePommedoro) view.addSubview(continueBtn) } window.contentView = view if i == 0 { window.makeKeyAndOrderFront(nil) if let field = self.reflectionField { window.makeFirstResponder(field) } } } } }