// ── Alarm Overlay ────────────────────────────────────────────── // Full-screen overlay for CRITICAL urgency firing timers // Requires confirm-to-dismiss import SwiftUI struct AlarmOverlay: View { let timer: CMTimer @EnvironmentObject var store: TimerStore @State private var showConfirmDismiss = false @State private var pulseScale: CGFloat = 1.0 var body: some View { ZStack { // Background CMColors.bg.opacity(0.95) .ignoresSafeArea() VStack(spacing: CMSpacing.xxl) { Spacer() // Pulsing urgency ring ZStack { Circle() .stroke(CMColors.critical.opacity(0.2), lineWidth: 4) .frame(width: 200, height: 200) .scaleEffect(pulseScale) .animation( .easeInOut(duration: 1.0).repeatForever(autoreverses: true), value: pulseScale ) Circle() .stroke(CMColors.critical, lineWidth: 3) .frame(width: 160, height: 160) VStack(spacing: CMSpacing.sm) { Image(systemName: "bell.fill") .font(.system(size: 36)) .foregroundStyle(CMColors.critical) Text("NOW") .font(CMFonts.mono(size: 24, weight: .bold)) .foregroundStyle(CMColors.critical) } } // Timer info VStack(spacing: CMSpacing.sm) { UrgencyBadge(urgency: .critical) Text(timer.label) .font(CMFonts.display(size: 28)) .foregroundStyle(CMColors.text) .multilineTextAlignment(.center) if let desc = timer.description, !desc.isEmpty { Text(desc) .font(CMFonts.body(size: 16)) .foregroundStyle(CMColors.textSecondary) .multilineTextAlignment(.center) } if timer.snoozeCount > 0 { Text("Snoozed \(timer.snoozeCount) time\(timer.snoozeCount == 1 ? "" : "s")") .font(CMFonts.body(size: 14)) .foregroundStyle(CMColors.textMuted) } } Spacer() // Snooze buttons HStack(spacing: CMSpacing.lg) { Button { HapticEngine.tap() store.snooze(timer.id, minutes: 5) } label: { VStack(spacing: CMSpacing.xs) { Image(systemName: "moon.zzz") .font(.title3) Text("5 min") .font(CMFonts.body(size: 13, weight: .medium)) } .foregroundStyle(CMColors.text) .frame(maxWidth: .infinity) .padding(.vertical, CMSpacing.lg) .background(CMColors.surface) .clipShape(RoundedRectangle(cornerRadius: CMRadius.md)) .overlay( RoundedRectangle(cornerRadius: CMRadius.md) .stroke(CMColors.border, lineWidth: 1) ) } Button { HapticEngine.tap() store.snooze(timer.id, minutes: 15) } label: { VStack(spacing: CMSpacing.xs) { Image(systemName: "moon.zzz.fill") .font(.title3) Text("15 min") .font(CMFonts.body(size: 13, weight: .medium)) } .foregroundStyle(CMColors.text) .frame(maxWidth: .infinity) .padding(.vertical, CMSpacing.lg) .background(CMColors.surface) .clipShape(RoundedRectangle(cornerRadius: CMRadius.md)) .overlay( RoundedRectangle(cornerRadius: CMRadius.md) .stroke(CMColors.border, lineWidth: 1) ) } } .padding(.horizontal, CMSpacing.xl) // Dismiss button (requires confirmation for Critical) Button { HapticEngine.tap() showConfirmDismiss = true } label: { Text("Dismiss") .font(CMFonts.body(size: 18, weight: .bold)) .foregroundStyle(.white) .frame(maxWidth: .infinity) .padding(.vertical, CMSpacing.lg) .background(CMColors.critical) .clipShape(RoundedRectangle(cornerRadius: CMRadius.md)) } .padding(.horizontal, CMSpacing.xl) .padding(.bottom, CMSpacing.xxl) } } .onAppear { pulseScale = 1.15 } .alert("Dismiss Critical Timer?", isPresented: $showConfirmDismiss) { Button("Cancel", role: .cancel) {} Button("Dismiss", role: .destructive) { store.dismiss(timer.id) } } message: { Text("Are you sure you want to dismiss \"\(timer.label)\"? This is a critical timer.") } } }