Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Development/Simulation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
80 changes: 33 additions & 47 deletions Sources/OmarchyAppleInstallerApp/InstallerTheme.swift
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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
)
}
}
Expand All @@ -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
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand All @@ -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
}
Expand Down Expand Up @@ -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)
Expand Down
12 changes: 1 addition & 11 deletions Sources/OmarchyAppleInstallerApp/OnePage/OmarchyWordmark.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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] = [
".................###.............................................................",
Expand Down Expand Up @@ -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)
Expand Down
5 changes: 0 additions & 5 deletions Sources/OmarchyAppleInstallerApp/SimulationDashboard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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() }
Expand Down
Loading