// // AppearanceSettingsTab.swift // Zyquo Agent // // Author: Simon-Pierre Boucher // Mail: contact@spboucher.ai // // Settings › Appearance — theme mode (Light/Dark/System), the five accent // choices with swatches (violet flagship + graphite, sky, emerald, amber), // and the chat font size slider (12–18pt) with live preview. // import SwiftUI struct AppearanceSettingsTab: View { @EnvironmentObject private var appearance: AppearanceStore var body: some View { Form { Picker("Theme", selection: $appearance.themeMode) { ForEach(ThemeMode.allCases) { mode in Text(mode.displayName).tag(mode) } } .pickerStyle(.segmented) VStack(alignment: .leading, spacing: ZyquoSpacing.xs) { Text("Accent") HStack(spacing: ZyquoSpacing.sm) { ForEach(AccentChoice.allCases) { choice in accentSwatch(choice) } Spacer(minLength: 0) } } VStack(alignment: .leading, spacing: ZyquoSpacing.xs) { HStack { Text("Chat font size") Spacer() Text(String(format: "%.1f pt", appearance.chatFontSize)) .font(ZyquoFont.caption) .foregroundStyle(ZyquoColor.textSecondary) .monospacedDigit() } Slider(value: $appearance.chatFontSize, in: 12...18, step: 0.5) VStack(alignment: .leading, spacing: ZyquoSpacing.xxs) { Text("Preview") .font(ZyquoFont.caption) .foregroundStyle(ZyquoColor.textTertiary) Text("The quick brown fox jumps over the lazy dog — Zyquo Agent renders task text at this size, with generous 1.45 line height for readability.") .font(ZyquoFont.body(size: appearance.chatFontSize)) .lineSpacing(appearance.chatFontSize * ZyquoFont.bodyLineSpacingFactor) .foregroundStyle(ZyquoColor.textPrimary) .padding(ZyquoSpacing.sm) .frame(maxWidth: .infinity, alignment: .leading) .background( RoundedRectangle(cornerRadius: ZyquoRadius.medium, style: .continuous) .fill(ZyquoColor.surface) .overlay( RoundedRectangle(cornerRadius: ZyquoRadius.medium, style: .continuous) .strokeBorder(ZyquoColor.border, lineWidth: ZyquoMetrics.hairline) ) ) } } } .formStyle(.grouped) } private func accentSwatch(_ choice: AccentChoice) -> some View { let isSelected = appearance.accent == choice return Button { withAnimation(ZyquoMotion.appear) { appearance.accent = choice } } label: { VStack(spacing: ZyquoSpacing.xxs) { Circle() .fill(swatchColor(choice)) .frame(width: 26, height: 26) .overlay( Circle().strokeBorder( isSelected ? ZyquoColor.textPrimary : ZyquoColor.border, lineWidth: isSelected ? 2 : ZyquoMetrics.hairline ) ) .overlay { if isSelected { Image(systemName: "checkmark") .font(.system(size: 10, weight: .bold)) .foregroundStyle(.white) } } Text(choice.displayName) .font(ZyquoFont.caption) .foregroundStyle(isSelected ? ZyquoColor.textPrimary : ZyquoColor.textSecondary) } } .buttonStyle(PressableButtonStyle()) .help("\(choice.displayName) accent") } /// Appearance-resolved swatch color for one accent choice. private func swatchColor(_ choice: AccentChoice) -> Color { let (light, dark) = choice.accentHex return Color(nsColor: NSColor(name: nil) { appearance in let hex = appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua ? dark : light return NSColor(hex: hex) }) } }