Adopt Oxide's color scheme and a square design
The palette now follows Oxide's design system (docs.oxide.computer): a cool neutral scale at hue 260 with almost no chroma, opaque strokes, and Oxide's green as the only accent. The accent-color option is gone: selection, the cursor row, focus rings and the active-panel line all read green. The file panel's header strip now steps toward the list, one shade per row (session strip, path bar, column headers), derived by color-mix so every theme, AMOLED included, follows on its own. Everything is square: every rounded utility and the radius tokens are gone, and checkboxes, radios, selects, number spinners and scrollbars are restyled to match (square scrollbars are always visible on macOS, the cost of styling them). Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+38
-32
@@ -49,8 +49,7 @@ before any page script, plus the persisted theme attributes and interface
|
||||
scale so the first paint is already right. `ui/src/main.ts` picks the root
|
||||
component from `APP_MODE`; all four roots use selector `app-root` (index.html
|
||||
hosts only that tag). `chromeOptions()` is the ONLY place the native frame is
|
||||
decided, spread by
|
||||
the main window and `openSecondaryWindow`: macOS `hiddenInset` +
|
||||
decided, spread by the main window and `openSecondaryWindow`: macOS `hiddenInset` +
|
||||
`trafficLightOffset {x:12,y:13}` (the UI renders an `h-10 pl-24` drag-region
|
||||
header via `IS_MAC_CHROME`); other platforms keep the native titlebar and a
|
||||
compact `h-9` non-drag header.
|
||||
@@ -233,7 +232,7 @@ component; tokens → `ui/src/styles.css`.
|
||||
enqueue and runs the collision flow (same-path auto-skip, type-mismatch
|
||||
skip, overwrite dialog with all/none batch modes).
|
||||
- `settings.service.ts` — one signal per setting; **imperative apply pattern**
|
||||
(`applyTheme/DarkTone/Accent` set `data-*` attributes on `<html>`;
|
||||
(`applyTheme/DarkTone` set `data-*` attributes on `<html>`;
|
||||
`applyUiScale` sets its inline `font-size`); `applyAll` also serves the
|
||||
`settingsChanged` broadcast, so every window follows a save made in any
|
||||
other. Every window RPC serves `saveSettings` (editor and manager windows
|
||||
@@ -281,7 +280,7 @@ component; tokens → `ui/src/styles.css`.
|
||||
pointer; the keyboard path never consults it — so `pick()` and the arrow-key
|
||||
walker check the flag themselves.
|
||||
- `file-icons.ts` — extension → category map driving glyphs, colors
|
||||
(fixed `--icon-*` tokens, never accent-tinted) and the Kind column.
|
||||
(fixed `--icon-*` tokens from Oxide's hue scales) and the Kind column.
|
||||
- `icons.ts` — the one icon registry (TDesign, `@ng-icons/tdesign-icons`), passed to
|
||||
`provideIcons` in `app.config.ts`. Icon names are plain strings in templates and
|
||||
the build never checks them: an unregistered name renders nothing (console error
|
||||
@@ -301,35 +300,42 @@ component; tokens → `ui/src/styles.css`.
|
||||
|
||||
## Theming
|
||||
|
||||
`ui/src/styles.css`: shadcn-style OKLCH tokens; dark values are duplicated in
|
||||
`:root[data-theme='dark']` AND a `prefers-color-scheme` media block (system
|
||||
mode) — every dark token change goes in BOTH. Light is the zinc family; the
|
||||
dark neutrals are a **warm gray family (hue 107)** anchored on `#1a1a19`
|
||||
(`--background`) — retoning one dark neutral means retoning them all, or warm
|
||||
and cool surfaces clash. **Surface layers**, each its own shade: `--chrome`
|
||||
(frame — tab/transfer/status bars, editor/manager header+footer strips, the
|
||||
manager's connection sidebar), `--toolbar` (each file panel's header strip —
|
||||
session strip, path bar, column headers — and the editor's search bar; both
|
||||
panels share one neutral strip, and the ACTIVE panel is marked by a 0.125rem
|
||||
`--primary` line along its top edge, an inset box-shadow on the session strip so
|
||||
switching panels never shifts layout), `--background` (file lists, editor
|
||||
text, forms; `--row-alt` stripes odd file rows, skipped on the cursor/selected
|
||||
row so two `bg-*` utilities never fight) and `--card` (popovers, dialogs,
|
||||
toasts). Dark climbs frame 0.165 < list 0.218 < header 0.265 < card 0.285;
|
||||
light steps greys out from white content: frame 0.935 < header 0.965 < list =
|
||||
card 1. **`--accent` is translucent** (6% zinc-900 light, 9% white dark):
|
||||
hover/selected states sit on every layer, and an opaque hover vanishes on a
|
||||
surface of its own lightness. The "darker" tone is true-black AMOLED: chrome,
|
||||
background, toolbar and row-alt are all `#000` (the layers collapse
|
||||
on purpose), elevation survives only via `--card`/`--accent` (opaque there),
|
||||
and `--border`/`--input` go to 16%/18% white because those lines then carry the
|
||||
whole layout. Accent tones: one
|
||||
`--tone-h/--tone-c` line per tone + three formula blocks derive `--primary`,
|
||||
`--ring`, `--cursor-tint`; graphite = attribute removed = pristine zinc.
|
||||
`ui/src/styles.css`: OKLCH tokens adapted from **Oxide's design system**
|
||||
(docs.oxide.computer: a cool neutral scale at hue 260 with almost no chroma,
|
||||
one green accent, opaque strokes; the comments carry Oxide's step names
|
||||
n0…n1300 / green-800). Dark values are duplicated in `:root[data-theme='dark']`
|
||||
AND a `prefers-color-scheme` media block (system mode) — every dark token
|
||||
change goes in BOTH; retone neutrals along the one scale, or warm and cool
|
||||
surfaces clash. **There is no accent setting**: `--primary` is Oxide green per
|
||||
theme (green-800 dark, green-500 light), so selection (`bg-primary/15`), the
|
||||
cursor row (`--cursor-tint`, a `color-mix` of `--primary`), focus rings
|
||||
(green-600) and `--success` all read green. **Surface layers**, each its own
|
||||
shade: `--chrome` (frame — tab/transfer/status bars, the editor/manager/settings
|
||||
header+footer strips and sidebars), the file panel's header strip **stepping
|
||||
toward the list** — session strip `--toolbar`, path bar `--toolbar-2`, column
|
||||
headers `--toolbar-3` (`color-mix` steps declared once in `:root`, so every
|
||||
theme follows) — then `--background` (file lists, editor text, forms;
|
||||
`--row-alt` stripes odd file rows, skipped on the cursor/selected row so two
|
||||
`bg-*` utilities never fight) and `--card` (popovers, dialogs, toasts). The
|
||||
ACTIVE panel is marked by a 0.125rem `--primary` line along its top edge (an
|
||||
inset box-shadow on the session strip, so switching panels never shifts
|
||||
layout). Dark: frame 0.162 < list 0.195 < header 0.275 → 0.248 → 0.221 < card
|
||||
0.29; light steps greys out from near-white content: frame 0.925 < header 0.95
|
||||
→ 0.965 → 0.98 < list = card 0.995. **`--accent` is translucent** (n0 at 5%
|
||||
light, white 7% dark): hover/selected states sit on every layer, and an opaque
|
||||
hover vanishes on a surface of its own lightness. Strokes are opaque
|
||||
(`--border` n300 dark / n900 light, `--input` one step stronger). The
|
||||
"darker" tone is true-black AMOLED: chrome, background, toolbar (+ its steps)
|
||||
and row-alt are all `#000` (the layers collapse on purpose); elevation
|
||||
survives only via `--card`/`--muted`/`--accent` (opaque there). **Square
|
||||
design**: no `rounded-*` utilities and no radius tokens; an `@layer base`
|
||||
block restyles native controls to match — checkboxes/radios (`appearance:
|
||||
none`, a drawn check / square dot), selects (a `currentColor` caret; use
|
||||
`pl-*`, never `px-*`, on a select), hidden number spinners, and square
|
||||
`::-webkit-scrollbar`s (which make macOS scrollbars always visible).
|
||||
`@theme inline` maps tokens to Tailwind utilities (`bg-primary`,
|
||||
`text-icon-folder`, `bg-cursor-tint`). Selected rows are `bg-primary/15`
|
||||
(follow the accent); the cursor row uses `--cursor-tint` so graphite stays
|
||||
pixel-identical to the old neutral hover. **Interface scale** (`settings.uiScale`,
|
||||
`text-icon-folder`, `bg-toolbar-2`, `bg-cursor-tint`). File-type icon colors
|
||||
come from Oxide's hue scales (800 steps dark, 600 light). **Interface scale** (`settings.uiScale`,
|
||||
0.5–4 in 0.1 steps): one root font-size lever — `applyUiScale` sets
|
||||
`html { font-size: 16px * scale }`, and since EVERY size in the app is rem
|
||||
(panel tokens like `--row-h`, all Tailwind utilities, ng-icon sizes), the
|
||||
|
||||
+13
-8
@@ -60,17 +60,22 @@ touching the adjacent code.
|
||||
|
||||
- **A new surface token needs an AMOLED line, or AMOLED stops being
|
||||
all-black.** The two `[data-dark-tone='darker']` blocks re-declare every
|
||||
surface (`--chrome`, `--toolbar`, `--row-alt`, …) as `#000`; a token added
|
||||
only to the dark blocks shows through in AMOLED.
|
||||
- **Never declare one token in both the AMOLED blocks and the accent dark
|
||||
formula blocks.** Both are (0,3,0), so source order decides and the later
|
||||
accent block wins. A token that must follow the accent belongs in the base
|
||||
theme blocks instead — a `var(--primary)` there still resolves per accent on
|
||||
`:root`.
|
||||
surface (`--chrome`, `--toolbar`, `--background`, `--row-alt`, …) as `#000`;
|
||||
a token added only to the dark blocks shows through in AMOLED. Tokens derived
|
||||
in `:root` by `color-mix` (`--toolbar-2/-3`) follow on their own.
|
||||
- **Hover/selected color is a translucent overlay (`--accent`).** An opaque
|
||||
hover vanishes on a surface of its own lightness — light's old 0.967 hover
|
||||
on the 0.965 header strip did exactly that. Keep `--accent` alpha-based when
|
||||
retoning surfaces.
|
||||
- **A native control ignores `border-radius` and background until
|
||||
`appearance: none`.** WebKit draws checkboxes, radios and selects itself;
|
||||
the square restyle in the `@layer base` block depends on it. Put `pl-*`,
|
||||
never `px-*`, on a `<select>`: utilities outrank the base layer, so a `px`
|
||||
utility would erase the padding-right the drawn caret lives in.
|
||||
- **A custom `::-webkit-scrollbar` turns off macOS overlay scrollbars.** The
|
||||
moment the pseudo-element is styled, WKWebView draws classic always-visible
|
||||
scrollbars that take layout width. Deliberate here (square design); don't
|
||||
reach for it to "tint" scrollbars without accepting that.
|
||||
|
||||
## The interface scale (root font-size lever)
|
||||
|
||||
@@ -144,7 +149,7 @@ touching the adjacent code.
|
||||
- **Window-open flash prevention is three-layer** — remove any one and the
|
||||
white/inverted blink returns: (1) windows are created `hidden: true` and
|
||||
revealed by `showWhenReady` on the webview's first `dom-ready` (800 ms
|
||||
fallback), (2) the bun preload bakes the persisted `data-theme`/tone/accent
|
||||
fallback), (2) the bun preload bakes the persisted `data-theme`/tone
|
||||
attributes and the interface scale before first paint (`buildPreload`),
|
||||
(3) `ui/src/index.html` carries an inline `<style>` with the html background
|
||||
per theme — its oklch values MUST stay in sync with the `--background` tokens
|
||||
|
||||
@@ -9,6 +9,7 @@ macOS is the primary target; Windows 11+ x64 and Linux x64 are **beta** targets
|
||||
| Concern | File | mac | win | linux |
|
||||
|---|---|---|---|---|
|
||||
| Window chrome | `src/bun/windows.ts chromeOptions` | hiddenInset + traffic-light offset, UI drag header | native titlebar, compact header | same as win |
|
||||
| Scrollbars | `ui/src/styles.css` (`::-webkit-scrollbar`) | custom square, always visible (styling them turns off overlay scrollbars) | custom square | custom square |
|
||||
| External editor launch | `src/bun/edits.ts launchEditor` | `open [-a app]` | `cmd /c start "" [app] file` | app directly / `xdg-open` |
|
||||
| Editor app picker | `rpc-handlers.ts pickEditorApp` | /Applications, `.app` | %ProgramFiles%, `.exe` | home, any |
|
||||
| Owner/group names | `fs/local.ts resolveId` | `id -nu` + `dscacheutil` | undefined → "—" | `id -nu` + `getent` |
|
||||
|
||||
Reference in New Issue
Block a user