diff --git a/docs/design.md b/docs/design.md index 57ae140..a12f1d7 100644 --- a/docs/design.md +++ b/docs/design.md @@ -31,7 +31,7 @@ Hoist's chrome borrows directly from: The Hoist designs (in `design.pen`) lay out **five shells** (Three-pane · Sidebar-only · Tabs · Harness grid · CLI-first) and pick the Three-pane as the recommended shell. They document **three real surfaces** (Library · Harness Detail · Install Wizard) with the Library as the canonical harness-management view. The document also defines a **six-component grammar** (install status filled · install status outlined · action buttons · ⌘K command bar · harness table · terminal output) and a **four-card spec** (where each shell wins · one color one state · six tokens three status additions · posture rules). -What we **don't** borrow: Light themes (Hoist is dark-only for now), decorative gradients, sidebars that are themselves apps (cf. 1Password's sidebar-of-vaults is *the* model). +What we **don't** borrow: decorative gradients or sidebars that are themselves apps (cf. 1Password's sidebar-of-vaults is *the* model). Hoist follows the system appearance with graphite dark surfaces and warm drafting-paper light surfaces. --- @@ -50,7 +50,7 @@ All values live in `src/renderer/styles/tokens.css`. Components consume them via --surface-4 #43434f Selected row / focused surface ``` -Dark-only. Depth comes from surface ladder, never from drop shadows. Every chrome element is hairline-bordered against the surface below. The `--surface-recessed` token is reserved for the icon-only nav rail and the terminal code block — surfaces that should feel "behind" the active canvas. +Both appearances use the same depth ladder. Dark mode uses graphite surfaces; light mode maps the ladder to warm paper and off-white panels. Depth comes from the ladder, never from drop shadows. Every chrome element is hairline-bordered against the surface below. The `--surface-recessed` token is reserved for the icon-only nav rail and the terminal code block, surfaces that should feel "behind" the active canvas. ### Rail + sidebar widths @@ -409,7 +409,7 @@ These are open questions, not commitments: - **Auto-lock the vault** after idle (1Password's Quick Access `Cmd+Shift+Space` re-prompts for master password; we should mirror that for the renderer when keys are read). - **Discovered `.env` import** (1Password Watchtower pattern) — scan the user's filesystem for plaintext secrets, offer to vault them. -- **Theme picker** (Linear onboarding step 2). Currently dark-only; "Hoist Dark" is the only theme. A "Hoist Light" with the same surface ladder is straightforward to add since we use CSS variables. +- **Theme picker** (Linear onboarding step 2). Hoist currently follows the system light or dark appearance. A manual system/light/dark override can use the same token ladder. - **Multi-window support** — currently a single `BrowserWindow`. The IPC handler is already window-agnostic; the renderer would just need a per-window `App.tsx` state slice. - **Detail rail actions** — wire the buttons to actual IPC (Probe now → `probe.run`, Edit → opens a form, Delete → `vault.delete`). - **Settings panel** — surface-level settings (theme, auto-lock timeout, keytar backend toggle, GitHub repo for issue tracking) in a modal. Mirrors Claude Code's `/config` tabbed interface. diff --git a/src/main/index.ts b/src/main/index.ts index 340bf12..99e5a4d 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -19,7 +19,7 @@ async function applyLiquidGlass(handle: Buffer): Promise { if (process.platform !== 'darwin') return try { const { default: liquidGlass } = await import('electron-liquid-glass') - liquidGlass.addView(handle, { cornerRadius: 12, tintColor: '#12121459' }) + liquidGlass.addView(handle, { cornerRadius: 12 }) } catch { /* glass unsupported on this macOS version — window stays transparent */ } } diff --git a/src/renderer/styles/components.css b/src/renderer/styles/components.css index 331dfbe..7f0b006 100644 --- a/src/renderer/styles/components.css +++ b/src/renderer/styles/components.css @@ -166,16 +166,16 @@ vertical-align: middle; flex: 0 0 auto; } -.badge-ok { background: var(--status-ok); color: var(--text-on-accent); } +.badge-ok { background: var(--status-ok); color: var(--text-on-status); } .badge-ok-faded { background: var(--status-ok-soft); color: var(--status-ok); } -.badge-info { background: var(--status-info); color: var(--text-on-accent); } +.badge-info { background: var(--status-info); color: var(--text-on-status); } .badge-info-faded { background: var(--status-info-soft); color: var(--status-info); } -.badge-accent { background: var(--status-accent); color: var(--surface-canvas); } +.badge-accent { background: var(--status-accent); color: var(--text-on-status); } .badge-accent-faded { background: var(--status-accent-soft); color: var(--status-accent); } -.badge-merged { background: var(--status-merged); color: var(--text-on-accent); } +.badge-merged { background: var(--status-merged); color: var(--text-on-status); } .badge-merged-faded { background: var(--status-merged-soft); color: var(--status-merged); } .badge-warn { background: var(--status-warn-soft); color: var(--status-warn); } -.badge-bad { background: var(--status-bad); color: var(--text-on-accent); } +.badge-bad { background: var(--status-bad); color: var(--text-on-status); } .badge-bad-faded { background: var(--status-bad-soft); color: var(--status-bad); } .divider { height: 1px; background: var(--border); border: 0; margin: 0; } diff --git a/src/renderer/styles/layout.css b/src/renderer/styles/layout.css index f7fc96b..5f230aa 100644 --- a/src/renderer/styles/layout.css +++ b/src/renderer/styles/layout.css @@ -620,15 +620,15 @@ body.is-col-resizing * { } .hoist-stat-card .badge-ok { background: var(--status-ok-soft); - color: #3dd68c; + color: var(--status-ok); } .hoist-stat-card .badge-bad { background: var(--status-bad-soft); - color: #ff7b72; + color: var(--status-bad); } .hoist-stat-card .badge-warn { background: var(--status-warn-soft); - color: #f0a000; + color: var(--status-warn); } .hoist-stat-sub { font-size: 12px; @@ -684,7 +684,7 @@ body.is-col-resizing * { .hoist-catalogue-tile:hover { background: var(--surface-3); border-color: var(--border-strong); } .hoist-catalogue-tile.is-featured { background: var(--accent-soft); - border-color: rgba(21, 123, 243, 0.4); + border-color: color-mix(in srgb, var(--accent) 40%, transparent); } .hoist-catalogue-tile-icon { width: 40px; height: 40px; @@ -1340,16 +1340,16 @@ body.is-col-resizing * { } .hoist-doctor-pill.is-error.is-hot { background: var(--status-bad-soft); - border-color: rgba(207, 34, 46, 0.35); - color: #ff7b72; + border-color: color-mix(in srgb, var(--status-bad) 35%, transparent); + color: var(--status-bad); } -.hoist-doctor-pill.is-error.is-hot .hoist-doctor-pill-n { color: #ff7b72; } +.hoist-doctor-pill.is-error.is-hot .hoist-doctor-pill-n { color: var(--status-bad); } .hoist-doctor-pill.is-warn.is-hot { background: var(--status-warn-soft); - border-color: rgba(240, 160, 0, 0.35); - color: #f0a000; + border-color: color-mix(in srgb, var(--status-warn) 35%, transparent); + color: var(--status-warn); } -.hoist-doctor-pill.is-warn.is-hot .hoist-doctor-pill-n { color: #f0a000; } +.hoist-doctor-pill.is-warn.is-hot .hoist-doctor-pill-n { color: var(--status-warn); } .hoist-doctor-pill.is-ok .hoist-doctor-pill-n { color: var(--status-ok); } .hoist-doctor-list { @@ -1364,9 +1364,9 @@ body.is-col-resizing * { border-radius: var(--radius-card); overflow: hidden; } -.hoist-doctor-card.is-error { border-color: rgba(207, 34, 46, 0.35); } -.hoist-doctor-card.is-warn { border-color: rgba(240, 160, 0, 0.28); } -.hoist-doctor-card.is-ok { border-color: rgba(45, 164, 78, 0.28); } +.hoist-doctor-card.is-error { border-color: color-mix(in srgb, var(--status-bad) 35%, transparent); } +.hoist-doctor-card.is-warn { border-color: color-mix(in srgb, var(--status-warn) 28%, transparent); } +.hoist-doctor-card.is-ok { border-color: color-mix(in srgb, var(--status-ok) 28%, transparent); } .hoist-doctor-card-head { display: grid; @@ -1644,7 +1644,7 @@ body.is-col-resizing * { } .hoist-form-error { margin: 0; - color: #ff7b72; + color: var(--status-bad); font-size: 12px; } @@ -1681,7 +1681,7 @@ body.is-col-resizing * { min-width: 0; } .hoist-path-item.is-winner { - border-color: rgba(45, 164, 78, 0.45); + border-color: color-mix(in srgb, var(--status-ok) 45%, transparent); background: var(--status-ok-soft); } .hoist-path-item.is-current:not(.is-winner) { @@ -1867,8 +1867,8 @@ select.input { } .hoist-sev-ok { color: var(--status-ok); font-weight: 600; } -.hoist-sev-warn { color: #f0a000; font-weight: 600; } -.hoist-sev-bad { color: #ff7b72; font-weight: 600; } +.hoist-sev-warn { color: var(--status-warn); font-weight: 600; } +.hoist-sev-bad { color: var(--status-bad); font-weight: 600; } /* ─── Harness configure panel ──────────────────────────────────── */ @@ -2011,8 +2011,8 @@ select.input { /* macOS liquid glass — panes go translucent so the window vibrancy reads through */ html[data-platform='darwin'] .hoist, -html[data-platform='darwin'] .hoist-main { background: transparent; } -html[data-platform='darwin'] .hoist-topbar { background: rgba(18, 18, 20, 0.55); } +html[data-platform='darwin'] .hoist-main { background: var(--glass-canvas); } +html[data-platform='darwin'] .hoist-topbar { background: var(--glass-topbar); } html[data-platform='darwin'] .hoist-sidebar, html[data-platform='darwin'] .hoist-rail, -html[data-platform='darwin'] .hoist-rail.hoist-rail { background: rgba(26, 26, 29, 0.42); } +html[data-platform='darwin'] .hoist-rail.hoist-rail { background: var(--glass-panel); } diff --git a/src/renderer/styles/tokens.css b/src/renderer/styles/tokens.css index 5008a91..94c1c9f 100644 --- a/src/renderer/styles/tokens.css +++ b/src/renderer/styles/tokens.css @@ -5,6 +5,8 @@ */ :root { + color-scheme: dark; + /* Surface ladder — neutral graphite, no blue cast */ --surface-canvas: #121214; /* page background */ --surface-recessed: #0c0c0e; /* deeper than canvas — rail backdrop, terminal canvas */ @@ -28,6 +30,12 @@ --text-muted: #b0aca4; --text-subtle: #7d7a73; --text-on-accent: #121214; + --text-on-status: #121214; + + /* Translucent macOS pane fills */ + --glass-canvas: rgba(18, 18, 20, 0.48); + --glass-topbar: rgba(18, 18, 20, 0.72); + --glass-panel: rgba(26, 26, 29, 0.68); /* Safety signal — reserved for actions and focus */ --accent: #f06a3c; @@ -74,6 +82,51 @@ --space-8: 40px; } +@media (prefers-color-scheme: light) { + :root { + color-scheme: light; + + /* Warm drafting paper, not a clinical white inversion */ + --surface-canvas: #f3f0e8; + --surface-recessed: #e6e1d6; + --surface-1: #fbf9f3; + --surface-2: #eeebe3; + --surface-3: #e4dfd4; + --surface-4: #d8d1c5; + + --border: #d7d0c4; + --border-strong: #b9b0a3; + --border-soft: rgba(18, 18, 20, 0.06); + + --text: #1b1b1d; + --text-muted: #625e57; + --text-subtle: #716c64; + --text-on-accent: #121214; + --text-on-status: #fffdf6; + + --accent: #e45f34; + --accent-soft: rgba(228, 95, 52, 0.13); + --accent-strong: #cf4f29; + + --status-ok: #1f7a62; + --status-ok-soft: rgba(31, 122, 98, 0.12); + --status-info: #3268a8; + --status-info-soft: rgba(50, 104, 168, 0.12); + --status-accent: #a65a00; + --status-accent-soft: rgba(166, 90, 0, 0.12); + --status-merged: #6f4eb8; + --status-merged-soft: rgba(111, 78, 184, 0.12); + --status-warn: #986100; + --status-warn-soft: rgba(152, 97, 0, 0.12); + --status-bad: #b72e36; + --status-bad-soft: rgba(183, 46, 54, 0.12); + + --glass-canvas: rgba(243, 240, 232, 0.76); + --glass-topbar: rgba(251, 249, 243, 0.84); + --glass-panel: rgba(251, 249, 243, 0.78); + } +} + /* Colorway exploration — accent swaps from the icon colorway matrix. Picked via data-theme on (dev theme picker); default stays Rigging Yard orange. */ [data-theme='signal'] { --accent: #ff7a1a; --accent-strong: #ff9440; --accent-soft: rgba(255, 122, 26, 0.14); --text-on-accent: #121214; }