spb/zyquo-mlx Public MIT
The local MLX foundry for your Mac — run, fine-tune, quantize, and ship models. Nothing leaves your machine.
Swift 93.4%
Python 3.8%
Makefile 2.2%
Shell 0.5%
1//2// ZyquoTheme.swift3// Zyquo MLX4//5// Author: Simon-Pierre Boucher6// Mail: contact@spboucher.ai7//89import SwiftUI1011/// The Zyquo MLX design system — single source of truth for every color,12/// font, spacing, radius, and motion value in the app (charter Phase 4).13/// Identity: molten copper on slate — a foundry, precise and Apple-clean.14/// Light theme is flagship; dark is the "deep slate forge" derivation.15enum ZyquoTheme {1617 // MARK: - Colors (light / dark pairs)1819 /// Canvas behind everything.20 static let background = adaptive(light: 0xFAFAF9, dark: 0x161A22)21 /// Cards, panels.22 static let surface = adaptive(light: 0xFFFFFF, dark: 0x1E2430)23 /// Hover states, code/log blocks.24 static let surfaceSecondary = adaptive(light: 0xF3F2F0, dark: 0x252C3A)2526 /// Molten copper — primary actions, run/train buttons, active states.27 static let accent = adaptive(light: 0xC2410C, dark: 0xEA6A2B)28 /// Bright end of the copper gradient.29 static let accentBright = adaptive(light: 0xEA6A2B, dark: 0xF0813F)30 /// Copper pair: slate for secondary emphasis.31 static let slate = adaptive(light: 0x334155, dark: 0x94A3B8)32 /// Selected rows, active-run tint.33 static let accentSubtle = adaptive(light: 0xFCEDE4, dark: 0x3A2A20)3435 static let textPrimary = adaptive(light: 0x1A1A1C, dark: 0xF2F2F4)36 static let textSecondary = adaptive(light: 0x6B6B72, dark: 0xA5A8B0)37 static let textTertiary = adaptive(light: 0x9E9EA6, dark: 0x71747E)38 static let border = adaptive(light: 0xE6E4E1, dark: 0x323A48)3940 static let success = adaptive(light: 0x2FA36B, dark: 0x3FBF81)41 static let warning = adaptive(light: 0xD9822B, dark: 0xE2953F)42 static let danger = adaptive(light: 0xD64545, dark: 0xE25A5A)4344 /// Chart tokens: copper = train loss, slate = val loss, teal = tokens/sec.45 static let chartTrain = accent46 static let chartVal = slate47 static let chartThroughput = adaptive(light: 0x0F8B8D, dark: 0x2BB3B6)4849 /// The copper gradient for hero actions.50 static var copperGradient: LinearGradient {51 LinearGradient(52 colors: [Color(hex: 0xC2410C), Color(hex: 0xEA6A2B)],53 startPoint: .top, endPoint: .bottom)54 }5556 // MARK: - Typography5758 static let titleFont = Font.system(size: 20, weight: .semibold)59 static let headlineFont = Font.system(size: 14, weight: .semibold)60 static let bodyFont = Font.system(size: 13)61 static let captionFont = Font.system(size: 11)62 static let captionMediumFont = Font.system(size: 11, weight: .medium)63 /// Logs, metrics, code — always SF Mono.64 static let monoFont = Font.system(size: 12, design: .monospaced)65 static let monoSmallFont = Font.system(size: 11, design: .monospaced)6667 // MARK: - Spacing (4pt grid)6869 static let spacing2: CGFloat = 270 static let spacing4: CGFloat = 471 static let spacing8: CGFloat = 872 static let spacing12: CGFloat = 1273 static let spacing16: CGFloat = 1674 static let spacing20: CGFloat = 2075 static let spacing24: CGFloat = 2476 static let spacing32: CGFloat = 327778 // MARK: - Radii & lines7980 static let radiusSmall: CGFloat = 681 static let radiusMedium: CGFloat = 1082 static let radiusLarge: CGFloat = 1483 /// Hairlines are always 0.5pt (family rule).84 static let hairline: CGFloat = 0.58586 // MARK: - Layout constants (charter §4.2)8788 static let sidebarWidth: CGFloat = 24089 static let headerHeight: CGFloat = 5290 static let defaultWindowSize = CGSize(width: 1360, height: 880)91 static let minWindowSize = CGSize(width: 1080, height: 700)9293 // MARK: - Motion9495 static let quickAnimation = Animation.easeOut(duration: 0.15)96 static let standardAnimation = Animation.easeInOut(duration: 0.25)9798 // MARK: - Helpers99100 private static func adaptive(light: UInt32, dark: UInt32) -> Color {101 Color(102 nsColor: NSColor(name: nil) { appearance in103 let isDark = appearance.bestMatch(from: [.aqua, .darkAqua]) == .darkAqua104 return NSColor(Color(hex: isDark ? dark : light))105 })106 }107}108109extension Color {110 /// Token-internal hex initializer — views never use raw hex directly.111 init(hex: UInt32) {112 self.init(113 .sRGB,114 red: Double((hex >> 16) & 0xFF) / 255.0,115 green: Double((hex >> 8) & 0xFF) / 255.0,116 blue: Double(hex & 0xFF) / 255.0)117 }118}119120// MARK: - Shared components121122/// A 0.5pt hairline-bordered card surface.123struct ZyquoCard: ViewModifier {124 func body(content: Content) -> some View {125 content126 .background(ZyquoTheme.surface)127 .clipShape(RoundedRectangle(cornerRadius: ZyquoTheme.radiusMedium))128 .overlay(129 RoundedRectangle(cornerRadius: ZyquoTheme.radiusMedium)130 .stroke(ZyquoTheme.border, lineWidth: ZyquoTheme.hairline))131 }132}133134extension View {135 func zyquoCard() -> some View { modifier(ZyquoCard()) }136}137138/// Status pill for jobs and runs.139struct StatusPill: View {140 let text: String141 let color: Color142143 var body: some View {144 Text(text)145 .font(ZyquoTheme.captionMediumFont)146 .foregroundStyle(color)147 .padding(.horizontal, ZyquoTheme.spacing8)148 .padding(.vertical, ZyquoTheme.spacing2)149 .background(color.opacity(0.12))150 .clipShape(Capsule())151 }152}153154/// Model-type badge (LLM/VLM/Embed/Speech/Image).155struct TypeBadge: View {156 let type: ModelType157158 var body: some View {159 Text(type.displayName)160 .font(ZyquoTheme.captionMediumFont)161 .foregroundStyle(ZyquoTheme.slate)162 .padding(.horizontal, ZyquoTheme.spacing8)163 .padding(.vertical, ZyquoTheme.spacing2)164 .background(ZyquoTheme.surfaceSecondary)165 .clipShape(RoundedRectangle(cornerRadius: ZyquoTheme.radiusSmall))166 }167}168169/// Memory-verdict badge driven by MemoryAdvisor.170struct VerdictBadge: View {171 let verdict: MemoryVerdict172173 private var color: Color {174 switch verdict {175 case .comfortable: ZyquoTheme.success176 case .tight: ZyquoTheme.warning177 case .wontFit: ZyquoTheme.danger178 }179 }180181 var body: some View {182 StatusPill(text: verdict.displayName, color: color)183 }184}185186/// Instructive, intentional empty state (charter: App-Store-front-page quality).187struct EmptyStateView: View {188 let icon: String189 let title: String190 let message: String191 var actionLabel: String?192 var action: (() -> Void)?193194 var body: some View {195 VStack(spacing: ZyquoTheme.spacing16) {196 ZStack {197 Circle()198 .fill(ZyquoTheme.accentSubtle)199 .frame(width: 88, height: 88)200 Image(systemName: icon)201 .font(.system(size: 36, weight: .light))202 .foregroundStyle(ZyquoTheme.accent)203 }204 VStack(spacing: ZyquoTheme.spacing8) {205 Text(title)206 .font(ZyquoTheme.titleFont)207 .foregroundStyle(ZyquoTheme.textPrimary)208 Text(message)209 .font(ZyquoTheme.bodyFont)210 .foregroundStyle(ZyquoTheme.textSecondary)211 .multilineTextAlignment(.center)212 .frame(maxWidth: 420)213 }214 if let actionLabel, let action {215 Button(action: action) {216 Text(actionLabel)217 .font(ZyquoTheme.headlineFont)218 .foregroundStyle(.white)219 .padding(.horizontal, ZyquoTheme.spacing20)220 .padding(.vertical, ZyquoTheme.spacing8)221 .background(ZyquoTheme.copperGradient)222 .clipShape(RoundedRectangle(cornerRadius: ZyquoTheme.radiusSmall))223 }224 .buttonStyle(.plain)225 }226 }227 .frame(maxWidth: .infinity, maxHeight: .infinity)228 .background(ZyquoTheme.background)229 }230}231