diff --git a/Gaze/AppDelegate.swift b/Gaze/AppDelegate.swift index b37fb7b..8a66cf7 100644 --- a/Gaze/AppDelegate.swift +++ b/Gaze/AppDelegate.swift @@ -191,4 +191,9 @@ class AppDelegate: NSObject, NSApplicationDelegate { reminderWindowController?.close() reminderWindowController = nil } + + // Public method to get menubar icon position for animations + func getMenuBarIconPosition() -> NSRect? { + return statusItem?.button?.window?.frame + } } diff --git a/Gaze/Services/SettingsManager.swift b/Gaze/Services/SettingsManager.swift index 74c9049..93602f5 100644 --- a/Gaze/Services/SettingsManager.swift +++ b/Gaze/Services/SettingsManager.swift @@ -22,6 +22,10 @@ class SettingsManager: ObservableObject { private let settingsKey = "gazeAppSettings" private init() { + #if DEBUG + // Clear settings on every development build + UserDefaults.standard.removeObject(forKey: "gazeAppSettings") + #endif self.settings = Self.loadSettings() } diff --git a/Gaze/Services/TimerEngine.swift b/Gaze/Services/TimerEngine.swift index 410dcd1..b68ab51 100644 --- a/Gaze/Services/TimerEngine.swift +++ b/Gaze/Services/TimerEngine.swift @@ -16,7 +16,7 @@ class TimerEngine: ObservableObject { private var timerSubscription: AnyCancellable? private let settingsManager: SettingsManager - nonisolated init(settingsManager: SettingsManager = .shared) { + init(settingsManager: SettingsManager) { self.settingsManager = settingsManager } diff --git a/Gaze/Views/MenuBar/MenuBarContentView.swift b/Gaze/Views/MenuBar/MenuBarContentView.swift index 8af6194..47e0d7f 100644 --- a/Gaze/Views/MenuBar/MenuBarContentView.swift +++ b/Gaze/Views/MenuBar/MenuBarContentView.swift @@ -7,6 +7,39 @@ import SwiftUI +// Hover button style for menubar items +struct MenuBarButtonStyle: ButtonStyle { + func makeBody(configuration: Configuration) -> some View { + configuration.label + .background( + RoundedRectangle(cornerRadius: 6) + .fill(configuration.isPressed ? Color.blue.opacity(0.2) : Color.gray.opacity(0.1)) + .opacity(configuration.isPressed ? 1 : 0) + ) + .contentShape(Rectangle()) + .animation(.easeInOut(duration: 0.1), value: configuration.isPressed) + } +} + +struct MenuBarHoverButtonStyle: ButtonStyle { + @State private var isHovered = false + + func makeBody(configuration: Configuration) -> some View { + configuration.label + .background( + RoundedRectangle(cornerRadius: 6) + .fill(isHovered ? Color.gray.opacity(0.15) : Color.clear) + ) + .contentShape(Rectangle()) + .onHover { hovering in + isHovered = hovering + } + .scaleEffect(configuration.isPressed ? 0.98 : 1.0) + .animation(.easeInOut(duration: 0.1), value: isHovered) + .animation(.easeInOut(duration: 0.05), value: configuration.isPressed) + } +} + struct MenuBarContentView: View { @ObservedObject var timerEngine: TimerEngine @ObservedObject var settingsManager: SettingsManager @@ -54,7 +87,7 @@ struct MenuBarContentView: View { } // Controls - VStack(spacing: 8) { + VStack(spacing: 4) { Button(action: { if timerEngine.timerStates.values.first?.isPaused == true { timerEngine.resume() @@ -67,10 +100,10 @@ struct MenuBarContentView: View { Text(isPaused ? "Resume All Timers" : "Pause All Timers") Spacer() } - .contentShape(Rectangle()) + .padding(.horizontal, 8) + .padding(.vertical, 6) } - .buttonStyle(.plain) - .padding(.horizontal) + .buttonStyle(MenuBarHoverButtonStyle()) Button(action: { // TODO: Open settings window @@ -80,12 +113,13 @@ struct MenuBarContentView: View { Text("Settings...") Spacer() } - .contentShape(Rectangle()) + .padding(.horizontal, 8) + .padding(.vertical, 6) } - .buttonStyle(.plain) - .padding(.horizontal) + .buttonStyle(MenuBarHoverButtonStyle()) } .padding(.vertical, 8) + .padding(.horizontal, 8) Divider() @@ -93,13 +127,16 @@ struct MenuBarContentView: View { Button(action: onQuit) { HStack { Image(systemName: "power") + .foregroundColor(.red) Text("Quit Gaze") Spacer() } - .contentShape(Rectangle()) + .padding(.horizontal, 8) + .padding(.vertical, 6) } - .buttonStyle(.plain) - .padding() + .buttonStyle(MenuBarHoverButtonStyle()) + .padding(.horizontal, 8) + .padding(.vertical, 8) } .frame(width: 300) } @@ -113,6 +150,7 @@ struct TimerStatusRow: View { let type: TimerType let state: TimerState var onSkip: () -> Void + @State private var isHovered = false var body: some View { HStack { @@ -136,9 +174,17 @@ struct TimerStatusRow: View { Image(systemName: "forward.fill") .font(.caption) .foregroundColor(.blue) + .padding(6) + .background( + Circle() + .fill(isHovered ? Color.blue.opacity(0.1) : Color.clear) + ) } .buttonStyle(.plain) .help("Skip to next \(type.displayName) reminder") + .onHover { hovering in + isHovered = hovering + } } .padding(.horizontal) .padding(.vertical, 4) @@ -170,9 +216,11 @@ struct TimerStatusRow: View { } #Preview { + let settingsManager = SettingsManager.shared + let timerEngine = TimerEngine(settingsManager: settingsManager) MenuBarContentView( - timerEngine: TimerEngine(settingsManager: .shared), - settingsManager: .shared, + timerEngine: timerEngine, + settingsManager: settingsManager, onQuit: {} ) } diff --git a/Gaze/Views/Onboarding/BlinkSetupView.swift b/Gaze/Views/Onboarding/BlinkSetupView.swift index 779cc24..b44ea82 100644 --- a/Gaze/Views/Onboarding/BlinkSetupView.swift +++ b/Gaze/Views/Onboarding/BlinkSetupView.swift @@ -11,6 +11,7 @@ struct BlinkSetupView: View { @Binding var enabled: Bool @Binding var intervalMinutes: Int var onContinue: () -> Void + var onBack: (() -> Void)? var body: some View { VStack(spacing: 30) { @@ -49,27 +50,41 @@ struct BlinkSetupView: View { } } .padding() - .background(Color.gray.opacity(0.1)) - .cornerRadius(12) + .glassEffect(in: .rect(cornerRadius: 12)) InfoBox(text: "We blink much less when focusing on screens. Regular blink reminders help prevent dry eyes") Spacer() - Button(action: onContinue) { - Text("Continue") - .font(.headline) - .frame(maxWidth: .infinity) - .padding() - .background(Color.blue) - .foregroundColor(.white) - .cornerRadius(12) + HStack(spacing: 12) { + if let onBack = onBack { + Button(action: onBack) { + HStack { + Image(systemName: "chevron.left") + Text("Back") + } + .font(.headline) + .frame(maxWidth: .infinity) + .padding() + } + .buttonStyle(.plain) + .glassEffect(.regular.interactive()) + } + + Button(action: onContinue) { + Text("Continue") + .font(.headline) + .frame(maxWidth: .infinity) + .padding() + } + .buttonStyle(.plain) + .glassEffect(.regular.tint(.blue).interactive()) } - .buttonStyle(.plain) .padding(.horizontal, 40) } .frame(width: 600, height: 500) .padding() + .background(.clear) } } @@ -77,6 +92,7 @@ struct BlinkSetupView: View { BlinkSetupView( enabled: .constant(true), intervalMinutes: .constant(5), - onContinue: {} + onContinue: {}, + onBack: {} ) } diff --git a/Gaze/Views/Onboarding/CompletionView.swift b/Gaze/Views/Onboarding/CompletionView.swift index 9f26d4f..0908d46 100644 --- a/Gaze/Views/Onboarding/CompletionView.swift +++ b/Gaze/Views/Onboarding/CompletionView.swift @@ -9,6 +9,7 @@ import SwiftUI struct CompletionView: View { var onComplete: () -> Void + var onBack: (() -> Void)? var body: some View { VStack(spacing: 30) { @@ -60,28 +61,42 @@ struct CompletionView: View { .padding(.horizontal) } .padding() - .background(Color.gray.opacity(0.1)) - .cornerRadius(12) + .glassEffect(in: .rect(cornerRadius: 12)) Spacer() - Button(action: onComplete) { - Text("Get Started") - .font(.headline) - .frame(maxWidth: .infinity) - .padding() - .background(Color.green) - .foregroundColor(.white) - .cornerRadius(12) + HStack(spacing: 12) { + if let onBack = onBack { + Button(action: onBack) { + HStack { + Image(systemName: "chevron.left") + Text("Back") + } + .font(.headline) + .frame(maxWidth: .infinity) + .padding() + } + .buttonStyle(.plain) + .glassEffect(.regular.interactive()) + } + + Button(action: onComplete) { + Text("Get Started") + .font(.headline) + .frame(maxWidth: .infinity) + .padding() + } + .buttonStyle(.plain) + .glassEffect(.regular.tint(.green).interactive()) } - .buttonStyle(.plain) .padding(.horizontal, 40) } .frame(width: 600, height: 500) .padding() + .background(.clear) } } #Preview { - CompletionView(onComplete: {}) + CompletionView(onComplete: {}, onBack: {}) } diff --git a/Gaze/Views/Onboarding/LookAwaySetupView.swift b/Gaze/Views/Onboarding/LookAwaySetupView.swift index b7e6df4..983f3c2 100644 --- a/Gaze/Views/Onboarding/LookAwaySetupView.swift +++ b/Gaze/Views/Onboarding/LookAwaySetupView.swift @@ -66,27 +66,41 @@ struct LookAwaySetupView: View { } } .padding() - .background(Color.gray.opacity(0.1)) - .cornerRadius(12) + .glassEffect(in: .rect(cornerRadius: 12)) - InfoBox(text: "Every 20 minutes, look at something 20 feet away for 20 seconds to reduce eye strain") + InfoBox(text: "Every \(intervalMinutes) minutes, look in the distance for \(countdownSeconds) seconds to reduce eye strain") Spacer() - Button(action: onContinue) { - Text("Continue") - .font(.headline) - .frame(maxWidth: .infinity) - .padding() - .background(Color.blue) - .foregroundColor(.white) - .cornerRadius(12) + HStack(spacing: 12) { + if let onBack = onBack { + Button(action: onBack) { + HStack { + Image(systemName: "chevron.left") + Text("Back") + } + .font(.headline) + .frame(maxWidth: .infinity) + .padding() + } + .buttonStyle(.plain) + .glassEffect(.regular.interactive()) + } + + Button(action: onContinue) { + Text("Continue") + .font(.headline) + .frame(maxWidth: .infinity) + .padding() + } + .buttonStyle(.plain) + .glassEffect(.regular.tint(.blue).interactive()) } - .buttonStyle(.plain) .padding(.horizontal, 40) } .frame(width: 600, height: 500) .padding() + .background(.clear) } } @@ -102,8 +116,7 @@ struct InfoBox: View { .foregroundColor(.secondary) } .padding() - .background(Color.blue.opacity(0.1)) - .cornerRadius(8) + .glassEffect(.regular.tint(.blue), in: .rect(cornerRadius: 8)) } } @@ -112,6 +125,7 @@ struct InfoBox: View { enabled: .constant(true), intervalMinutes: .constant(20), countdownSeconds: .constant(20), - onContinue: {} + onContinue: {}, + onBack: {} ) } diff --git a/Gaze/Views/Onboarding/OnboardingContainerView.swift b/Gaze/Views/Onboarding/OnboardingContainerView.swift index 4458809..375ff08 100644 --- a/Gaze/Views/Onboarding/OnboardingContainerView.swift +++ b/Gaze/Views/Onboarding/OnboardingContainerView.swift @@ -6,6 +6,26 @@ // import SwiftUI +import AppKit + +// NSVisualEffectView wrapper for SwiftUI +struct VisualEffectView: NSViewRepresentable { + let material: NSVisualEffectView.Material + let blendingMode: NSVisualEffectView.BlendingMode + + func makeNSView(context: Context) -> NSVisualEffectView { + let view = NSVisualEffectView() + view.material = material + view.blendingMode = blendingMode + view.state = .active + return view + } + + func updateNSView(_ nsView: NSVisualEffectView, context: Context) { + nsView.material = material + nsView.blendingMode = blendingMode + } +} struct OnboardingContainerView: View { @ObservedObject var settingsManager: SettingsManager @@ -17,54 +37,90 @@ struct OnboardingContainerView: View { @State private var blinkIntervalMinutes = 5 @State private var postureEnabled = true @State private var postureIntervalMinutes = 30 + @State private var launchAtLogin = false + @State private var isAnimatingOut = false + @Environment(\.dismiss) private var dismiss var body: some View { - VStack(spacing: 0) { - TabView(selection: $currentPage) { - WelcomeView(onContinue: { currentPage = 1 }) - .tag(0) - - LookAwaySetupView( - enabled: $lookAwayEnabled, - intervalMinutes: $lookAwayIntervalMinutes, - countdownSeconds: $lookAwayCountdownSeconds, - onContinue: { currentPage = 2 } - ) - .tag(1) - - BlinkSetupView( - enabled: $blinkEnabled, - intervalMinutes: $blinkIntervalMinutes, - onContinue: { currentPage = 3 } - ) - .tag(2) - - PostureSetupView( - enabled: $postureEnabled, - intervalMinutes: $postureIntervalMinutes, - onContinue: { currentPage = 4 } - ) - .tag(3) - - CompletionView( - onComplete: { - completeOnboarding() - } - ) - .tag(4) - } - .tabViewStyle(.automatic) + ZStack { + // Semi-transparent background with blur + VisualEffectView(material: .hudWindow, blendingMode: .behindWindow) + .ignoresSafeArea() - // Page indicator - Text("\(currentPage + 1)/5") - .font(.subheadline) - .foregroundColor(.secondary) - .padding(.top, 8) - .padding(.bottom, 20) + VStack(spacing: 0) { + TabView(selection: $currentPage) { + WelcomeView( + onContinue: { currentPage = 1 } + ) + .tag(0) + .tabItem { + Image(systemName: "hand.wave.fill") + } + + LookAwaySetupView( + enabled: $lookAwayEnabled, + intervalMinutes: $lookAwayIntervalMinutes, + countdownSeconds: $lookAwayCountdownSeconds, + onContinue: { currentPage = 2 }, + onBack: { currentPage = 0 } + ) + .tag(1) + .tabItem { + Image(systemName: "eye.fill") + } + + BlinkSetupView( + enabled: $blinkEnabled, + intervalMinutes: $blinkIntervalMinutes, + onContinue: { currentPage = 3 }, + onBack: { currentPage = 1 } + ) + .tag(2) + .tabItem { + Image(systemName: "eye.circle.fill") + } + + PostureSetupView( + enabled: $postureEnabled, + intervalMinutes: $postureIntervalMinutes, + onContinue: { currentPage = 4 }, + onBack: { currentPage = 2 } + ) + .tag(3) + .tabItem { + Image(systemName: "figure.stand") + } + + SettingsOnboardingView( + launchAtLogin: $launchAtLogin, + onContinue: { currentPage = 5 }, + onBack: { currentPage = 3 } + ) + .tag(4) + .tabItem { + Image(systemName: "gearshape.fill") + } + + CompletionView( + onComplete: { + completeOnboarding() + }, + onBack: { currentPage = 4 } + ) + .tag(5) + .tabItem { + Image(systemName: "checkmark.circle.fill") + } + } + .tabViewStyle(.automatic) + } } + .opacity(isAnimatingOut ? 0 : 1) + .scaleEffect(isAnimatingOut ? 0.3 : 1.0) } private func completeOnboarding() { + // Save settings settingsManager.settings.lookAwayTimer = TimerConfiguration( enabled: lookAwayEnabled, intervalSeconds: lookAwayIntervalMinutes * 60 @@ -81,7 +137,73 @@ struct OnboardingContainerView: View { intervalSeconds: postureIntervalMinutes * 60 ) + settingsManager.settings.launchAtLogin = launchAtLogin settingsManager.settings.hasCompletedOnboarding = true + + // Apply launch at login setting + do { + if launchAtLogin { + try LaunchAtLoginManager.enable() + } else { + try LaunchAtLoginManager.disable() + } + } catch { + print("Failed to set launch at login: \(error)") + } + + // Perform vacuum animation + performVacuumAnimation() + } + + private func performVacuumAnimation() { + // Get the NSWindow reference + guard let window = NSApplication.shared.windows.first(where: { $0.isVisible && $0.contentView != nil }) else { + // Fallback: just dismiss without animation + dismiss() + return + } + + // Get menubar icon position from AppDelegate + let appDelegate = NSApplication.shared.delegate as? AppDelegate + let targetFrame = appDelegate?.getMenuBarIconPosition() + + // Calculate target position (menubar icon or top-center as fallback) + let targetRect: NSRect + if let menuBarFrame = targetFrame { + // Use menubar icon position + targetRect = NSRect( + x: menuBarFrame.midX, + y: menuBarFrame.midY, + width: 0, + height: 0 + ) + } else { + // Fallback to top-center of screen + let screen = NSScreen.main?.frame ?? .zero + targetRect = NSRect( + x: screen.midX, + y: screen.maxY, + width: 0, + height: 0 + ) + } + + // Start SwiftUI animation for visual effects + withAnimation(.easeInOut(duration: 0.7)) { + isAnimatingOut = true + } + + // Animate window frame using AppKit + NSAnimationContext.runAnimationGroup({ context in + context.duration = 0.7 + context.timingFunction = CAMediaTimingFunction(name: .easeIn) + window.animator().setFrame(targetRect, display: true) + window.animator().alphaValue = 0 + }, completionHandler: { + // Close window after animation completes + self.dismiss() + window.close() + }) } } diff --git a/Gaze/Views/Onboarding/PostureSetupView.swift b/Gaze/Views/Onboarding/PostureSetupView.swift index b5b8f1e..3e36176 100644 --- a/Gaze/Views/Onboarding/PostureSetupView.swift +++ b/Gaze/Views/Onboarding/PostureSetupView.swift @@ -11,6 +11,7 @@ struct PostureSetupView: View { @Binding var enabled: Bool @Binding var intervalMinutes: Int var onContinue: () -> Void + var onBack: (() -> Void)? var body: some View { VStack(spacing: 30) { @@ -49,27 +50,41 @@ struct PostureSetupView: View { } } .padding() - .background(Color.gray.opacity(0.1)) - .cornerRadius(12) + .glassEffect(in: .rect(cornerRadius: 12)) InfoBox(text: "Regular posture checks help prevent back and neck pain from prolonged sitting") Spacer() - Button(action: onContinue) { - Text("Continue") - .font(.headline) - .frame(maxWidth: .infinity) - .padding() - .background(Color.blue) - .foregroundColor(.white) - .cornerRadius(12) + HStack(spacing: 12) { + if let onBack = onBack { + Button(action: onBack) { + HStack { + Image(systemName: "chevron.left") + Text("Back") + } + .font(.headline) + .frame(maxWidth: .infinity) + .padding() + } + .buttonStyle(.plain) + .glassEffect(.regular.interactive()) + } + + Button(action: onContinue) { + Text("Continue") + .font(.headline) + .frame(maxWidth: .infinity) + .padding() + } + .buttonStyle(.plain) + .glassEffect(.regular.tint(.blue).interactive()) } - .buttonStyle(.plain) .padding(.horizontal, 40) } .frame(width: 600, height: 500) .padding() + .background(.clear) } } @@ -77,6 +92,7 @@ struct PostureSetupView: View { PostureSetupView( enabled: .constant(true), intervalMinutes: .constant(30), - onContinue: {} + onContinue: {}, + onBack: {} ) } diff --git a/Gaze/Views/Onboarding/SettingsOnboardingView.swift b/Gaze/Views/Onboarding/SettingsOnboardingView.swift new file mode 100644 index 0000000..62ffaea --- /dev/null +++ b/Gaze/Views/Onboarding/SettingsOnboardingView.swift @@ -0,0 +1,170 @@ +// +// SettingsOnboardingView.swift +// Gaze +// +// Created by Mike Freno on 1/8/26. +// + +import SwiftUI + +struct SettingsOnboardingView: View { + @Binding var launchAtLogin: Bool + var onContinue: () -> Void + var onBack: (() -> Void)? + + var body: some View { + VStack(spacing: 30) { + Spacer() + + Image(systemName: "gearshape.fill") + .font(.system(size: 80)) + .foregroundColor(.blue) + + Text("Final Settings") + .font(.system(size: 36, weight: .bold)) + + Text("Configure app preferences and support the project") + .font(.title3) + .foregroundColor(.secondary) + .multilineTextAlignment(.center) + .padding(.horizontal, 40) + + VStack(spacing: 20) { + // Launch at Login Toggle + HStack { + VStack(alignment: .leading, spacing: 4) { + Text("Launch at Login") + .font(.headline) + Text("Start Gaze automatically when you log in") + .font(.caption) + .foregroundColor(.secondary) + } + Spacer() + Toggle("", isOn: $launchAtLogin) + .labelsHidden() + .onChange(of: launchAtLogin) { oldValue, newValue in + applyLaunchAtLoginSetting(enabled: newValue) + } + } + .padding() + .glassEffect(in: .rect(cornerRadius: 12)) + + // Links Section + VStack(spacing: 12) { + Text("Support & Contribute") + .font(.headline) + .frame(maxWidth: .infinity, alignment: .leading) + + // GitHub Link + Button(action: { + if let url = URL(string: "https://github.com/mikefreno/Gaze") { + NSWorkspace.shared.open(url) + } + }) { + HStack { + Image(systemName: "chevron.left.forwardslash.chevron.right") + .font(.title3) + VStack(alignment: .leading, spacing: 2) { + Text("View on GitHub") + .font(.subheadline) + .fontWeight(.semibold) + Text("Star the repo, report issues, contribute") + .font(.caption) + .foregroundColor(.secondary) + } + Spacer() + Image(systemName: "arrow.up.right") + .font(.caption) + } + .padding() + .frame(maxWidth: .infinity) + } + .buttonStyle(.plain) + .glassEffect(.regular.interactive(), in: .rect(cornerRadius: 10)) + + // Buy Me a Coffee + Button(action: { + if let url = URL(string: "https://buymeacoffee.com/placeholder") { + NSWorkspace.shared.open(url) + } + }) { + HStack { + Image(systemName: "cup.and.saucer.fill") + .font(.title3) + .foregroundColor(.orange) + VStack(alignment: .leading, spacing: 2) { + Text("Buy Me a Coffee") + .font(.subheadline) + .fontWeight(.semibold) + Text("Support development of Gaze") + .font(.caption) + .foregroundColor(.secondary) + } + Spacer() + Image(systemName: "arrow.up.right") + .font(.caption) + } + .padding() + .frame(maxWidth: .infinity) + .background(Color.orange.opacity(0.1)) + .cornerRadius(10) + } + .buttonStyle(.plain) + .glassEffect(.regular.tint(.orange).interactive(), in: .rect(cornerRadius: 10)) + } + .padding() + } + + Spacer() + + HStack(spacing: 12) { + if let onBack = onBack { + Button(action: onBack) { + HStack { + Image(systemName: "chevron.left") + Text("Back") + } + .font(.headline) + .frame(maxWidth: .infinity) + .padding() + } + .buttonStyle(.plain) + .glassEffect(.regular.interactive()) + } + + Button(action: onContinue) { + Text("Continue") + .font(.headline) + .frame(maxWidth: .infinity) + .padding() + } + .buttonStyle(.plain) + .glassEffect(.regular.tint(.blue).interactive()) + } + .padding(.horizontal, 40) + } + .frame(width: 600, height: 500) + .padding() + .background(.clear) + } + + private func applyLaunchAtLoginSetting(enabled: Bool) { + do { + if enabled { + try LaunchAtLoginManager.enable() + } else { + try LaunchAtLoginManager.disable() + } + } catch { + print("Failed to set launch at login: \(error)") + } + } +} + +#Preview { + SettingsOnboardingView( + launchAtLogin: .constant(false), + onContinue: {}, + onBack: {} + ) +} diff --git a/Gaze/Views/Onboarding/WelcomeView.swift b/Gaze/Views/Onboarding/WelcomeView.swift index 8ad4ed4..3ee670c 100644 --- a/Gaze/Views/Onboarding/WelcomeView.swift +++ b/Gaze/Views/Onboarding/WelcomeView.swift @@ -31,6 +31,7 @@ struct WelcomeView: View { FeatureRow(icon: "figure.stand", title: "Maintain Good Posture", description: "Gentle reminders to sit up straight") } .padding() + .glassEffect(in: .rect(cornerRadius: 16)) Spacer() @@ -39,15 +40,14 @@ struct WelcomeView: View { .font(.headline) .frame(maxWidth: .infinity) .padding() - .background(Color.blue) - .foregroundColor(.white) - .cornerRadius(12) } .buttonStyle(.plain) + .glassEffect(.regular.tint(.blue).interactive()) .padding(.horizontal, 40) } .frame(width: 600, height: 500) .padding() + .background(.clear) } }