diff --git a/Development/Simulation.md b/Development/Simulation.md index a4ce7f8..851de64 100644 --- a/Development/Simulation.md +++ b/Development/Simulation.md @@ -18,7 +18,6 @@ Requires Xcode and XcodeBuildMCP. The launcher builds the debug app and supplies - **Reset simulation** abandons only the in-memory run, including a stopped or uncertain outcome. - **Slow events** makes each event take two seconds; changing speed resets the session. - **Test channel** offers Stable, Release candidate and Edge (the default), and is local to the simulator. The line under it shows the Release channel menu as the app words it for this scenario: stable and rc have no Mac release, and the three channel scenarios change the test channel's state. It locks while preparing, authorizing, executing, and after execution has started. -- **Dark appearance** is on by default to match Omarchy. Turn it off to test light mode. - **Install / Authorize** on the simulation sheet uses a dummy credential. There are no real account fields. ## Scenario matrix diff --git a/Sources/OmarchyAppleInstallerApp/InstallerTheme.swift b/Sources/OmarchyAppleInstallerApp/InstallerTheme.swift index e6687f8..849d437 100644 --- a/Sources/OmarchyAppleInstallerApp/InstallerTheme.swift +++ b/Sources/OmarchyAppleInstallerApp/InstallerTheme.swift @@ -1,42 +1,39 @@ -import AppKit import SwiftUI /// Design tokens for the approved installer look: Try Omarchy's monospaced -/// type and buttons on omarchy.org's Tokyo Night colours (blue accent), with -/// light and dark palettes that follow the system appearance. +/// type and buttons on omarchy.org's Tokyo Night colours (blue accent). The +/// installer is always dark, whatever the system appearance. enum OmarchyTheme { // MARK: Palette - // Dark palette: omarchy.org's Tokyo Night tokens (background night/storm, - // foreground lavender, blue accent, red and yellow from the same scheme). - // Secondary text is the accent, slightly muted like Try Omarchy's but kept - // at 4.5:1 or better for its small sizes. Light palette: the Tokyo Night - // "Day" counterparts, darkened where small text needs the contrast. - static let window = dynamic(light: 0xE1_E2E7, dark: 0x1A_1B26) - static let card = dynamic(light: 0xFF_FFFF, dark: 0x24_283B) - static let text = dynamic(light: 0x37_60BF, dark: 0xC0_CAF5) - static let secondaryText = dynamic(light: 0x4A_5A9E, dark: 0x7A_A2F7, darkOpacity: 0.9) - static let separator = dynamic(light: 0xC4_C8DA, dark: 0x41_4868) - static let track = dynamic(light: 0xD0_D5E3, dark: 0x2F_334D) - static let accent = dynamic(light: 0x2E_7DE9, dark: 0x7A_A2F7) - static let accentText = dynamic(light: 0xFF_FFFF, dark: 0x1A_1B26) - static let success = dynamic(light: 0x48_5E30, dark: 0x9E_Cb6B) - static let danger = dynamic(light: 0xF5_2A65, dark: 0xF7_768E) - static let caution = dynamic(light: 0x8C_6C3E, dark: 0xE0_AF68) + // omarchy.org's Tokyo Night tokens (background night/storm, foreground + // lavender, blue accent, red and yellow from the same scheme). Secondary + // text is the accent, slightly muted like Try Omarchy's but kept at 4.5:1 or + // better for its small sizes. + static let window = rgb(0x1A_1B26) + static let card = rgb(0x24_283B) + static let text = rgb(0xC0_CAF5) + static let secondaryText = rgb(0x7A_A2F7, alpha: 0.9) + static let separator = rgb(0x41_4868) + static let track = rgb(0x2F_334D) + static let accent = rgb(0x7A_A2F7) + static let accentText = rgb(0x1A_1B26) + static let success = rgb(0x9E_Cb6B) + static let danger = rgb(0xF7_768E) + static let caution = rgb(0xE0_AF68) /// The disk divider handle: a firm blue that reads on the pale Omarchy - /// segment in both appearances. - static let handle = dynamic(light: 0x1F_5FD6, dark: 0x3D_74E8) + /// segment. + static let handle = rgb(0x3D_74E8) - // Try Omarchy's button feedback: in dark mode a primary button brightens to - // pale cyan under the pointer and turns cyan while pressed; in light mode it - // deepens instead, so its white label keeps its contrast. A secondary button - // takes an accent border and brighter text on hover and a tinted surface - // when pressed; every label stays at 4.5:1 or better. - static let buttonHover = dynamic(light: 0x2A_6FD6, dark: 0xB4_F9F8) - static let buttonPressed = dynamic(light: 0x00_7197, dark: 0x7D_CFFF) - static let buttonHoverText = dynamic(light: 0x25_63C9, dark: 0xB4_F9F8) - static let buttonPressedSurface = dynamic(light: 0xE6_E8EF, dark: 0x41_4868) + // Button feedback stays in the accent's blue: a primary button lightens + // under the pointer and deepens while pressed. A secondary button takes an + // accent border and lighter blue text on hover and a tinted surface when + // pressed; every label stays at 4.5:1 or better. + static let buttonHover = rgb(0x9A_B8FA) + static let buttonPressed = rgb(0x66_90EC) + static let buttonHoverText = rgb(0x9A_B8FA) + static let buttonPressedSurface = rgb(0x41_4868) // MARK: Metrics @@ -69,13 +66,13 @@ enum OmarchyTheme { .system(size: size, weight: weight, design: .monospaced) } - private static func dynamic(light: Int, dark: Int, darkOpacity: CGFloat = 1) -> Color { + private static func rgb(_ hex: Int, alpha: Double = 1) -> Color { Color( - nsColor: NSColor(name: nil) { appearance in - let isDark = - appearance.bestMatch(from: [.aqua, .darkAqua]) == .darkAqua - return isDark ? NSColor(hex: dark, alpha: darkOpacity) : NSColor(hex: light) - } + .sRGB, + red: Double((hex >> 16) & 0xFF) / 255, + green: Double((hex >> 8) & 0xFF) / 255, + blue: Double(hex & 0xFF) / 255, + opacity: alpha ) } } @@ -95,14 +92,3 @@ extension View { font(OmarchyTheme.body).fontDesign(.monospaced) } } - -extension NSColor { - fileprivate convenience init(hex: Int, alpha: CGFloat = 1) { - self.init( - srgbRed: CGFloat((hex >> 16) & 0xFF) / 255, - green: CGFloat((hex >> 8) & 0xFF) / 255, - blue: CGFloat(hex & 0xFF) / 255, - alpha: alpha - ) - } -} diff --git a/Sources/OmarchyAppleInstallerApp/OmarchyAppleInstallerApp.swift b/Sources/OmarchyAppleInstallerApp/OmarchyAppleInstallerApp.swift index 29bc600..ac746f6 100644 --- a/Sources/OmarchyAppleInstallerApp/OmarchyAppleInstallerApp.swift +++ b/Sources/OmarchyAppleInstallerApp/OmarchyAppleInstallerApp.swift @@ -18,6 +18,9 @@ private final class InstallerApplicationDelegate: NSObject, NSApplicationDelegat private var instanceLease: InstallerAppInstanceLease? func applicationWillFinishLaunching(_ notification: Notification) { + // Omarchy's look is dark only: windows, sheets, alerts and menus all take + // the dark appearance even when macOS is set to light. + NSApp.appearance = NSAppearance(named: .darkAqua) #if !DEBUG if ProcessInfo.processInfo.arguments.contains("--simulate") { fputs("Simulation requires a debug build. Refusing to launch the live installer.\n", stderr) @@ -71,7 +74,6 @@ struct OmarchyAppleInstallerApp: App { @State private var liveSession: InstallerSession? @State private var showsRemoval = false @State private var removalNeedsReview = false - @State private var simulationDark = true @State private var generation = UUID() private var isSimulation: Bool { @@ -86,8 +88,7 @@ struct OmarchyAppleInstallerApp: App { private var installerContent: some View { #if DEBUG if isSimulation { - SimulationDashboard( - onSessionAvailable: { liveSession = $0 }, onColorSchemeChange: { simulationDark = $0 }) + SimulationDashboard(onSessionAvailable: { liveSession = $0 }) } else { liveContent } @@ -149,7 +150,6 @@ struct OmarchyAppleInstallerApp: App { } }, onClose: { showsRemoval = false }, onRequiresReview: { removalNeedsReview = true }) } - .preferredColorScheme(isSimulation ? (simulationDark ? .dark : .light) : nil) .frame(minWidth: 640) .omarchyTypography() .tint(OmarchyTheme.accent) diff --git a/Sources/OmarchyAppleInstallerApp/OnePage/OmarchyWordmark.swift b/Sources/OmarchyAppleInstallerApp/OnePage/OmarchyWordmark.swift index 0d80ccc..087d49b 100644 --- a/Sources/OmarchyAppleInstallerApp/OnePage/OmarchyWordmark.swift +++ b/Sources/OmarchyAppleInstallerApp/OnePage/OmarchyWordmark.swift @@ -4,8 +4,6 @@ import SwiftUI /// at any size, coloured with the cyan-to-purple gradient of the site's hero /// art. No image file is involved. struct OmarchyWordmark: View { - @Environment(\.colorScheme) private var colorScheme - /// 81 × 19 cells, sampled from the site's OpenGraph image. static let grid: [String] = [ ".................###.............................................................", @@ -37,19 +35,11 @@ struct OmarchyWordmark: View { 0x71_56B3, 0x7C_4CAB, 0x8A_45A8, 0x9C_3EA6, 0xA8_3AA3, ].map(rgb) - /// The same ramp for light mode without its white and pale cyan top, which - /// vanish on the light window: light blue down to the same purple. - static let lightRowColors: [Color] = [ - 0x55_B5F2, 0x4D_ACEE, 0x46_A2E9, 0x42_99E5, 0x41_8FE1, 0x48_84DA, 0x4E_7BD4, - 0x55_72CC, 0x5C_6AC5, 0x64_62BE, 0x6B_5BB8, 0x72_55B2, 0x78_4FAE, 0x7F_4AAA, - 0x87_47A9, 0x90_43A7, 0x9A_3FA6, 0xA1_3CA5, 0xA8_3AA3, - ].map(rgb) - static let columns = grid[0].count static let rows = grid.count var body: some View { - let colors = colorScheme == .dark ? Self.rowColors : Self.lightRowColors + let colors = Self.rowColors Canvas { context, size in let columns = CGFloat(Self.columns) let rows = CGFloat(Self.rows) diff --git a/Sources/OmarchyAppleInstallerApp/SimulationDashboard.swift b/Sources/OmarchyAppleInstallerApp/SimulationDashboard.swift index 3d80f58..fcc3773 100644 --- a/Sources/OmarchyAppleInstallerApp/SimulationDashboard.swift +++ b/Sources/OmarchyAppleInstallerApp/SimulationDashboard.swift @@ -7,11 +7,9 @@ /// replace an in-memory environment; the live factory is never called. struct SimulationDashboard: View { var onSessionAvailable: ((InstallerSession) -> Void)? = nil - var onColorSchemeChange: ((Bool) -> Void)? = nil @State private var scenario = Self.launchScenario @State private var channel = ReleaseChannel.edge @State private var slow = false - @State private var dark = true @State private var generation = UUID() @State private var environment = InstallerSimulationEnvironment(scenario: Self.launchScenario) @State private var canChangeChannel = false @@ -37,7 +35,6 @@ } }.disabled(!canChangeChannel) Toggle("Slow playback", isOn: $slow) - Toggle("Dark mode", isOn: $dark) } // The Release channel menu's items, exactly as the app menu words // them, so the three channel states can be reviewed in the window. @@ -75,9 +72,7 @@ ) .id(generation) } - .preferredColorScheme(dark ? .dark : .light) .task { await continueAtLaunch() } - .onChange(of: dark) { _, value in onColorSchemeChange?(value) } .onChange(of: scenario) { _, _ in reset() } .onChange(of: slow) { _, _ in reset() } .onChange(of: channel) { _, _ in reset() }