Skip to content
Merged
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
27 changes: 27 additions & 0 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,33 @@ Rules:
- Avoid single-hue UIs. Theme presets can tint the app, but surfaces should
still read as neutral operational UI.

### Tinted-neutral gray base

The nine shadcn-baseColor families (clay, stone, olive, sage, steel, slate,
zinc, mauve, plum) carry `--theme-chroma: 0.05` — the open band between pure
neutral (`0`) and full color (`0.16`). When that band is active the root gets a
`.tone-tinted` class that shifts the gray base in opposite directions per mode,
so the subtle tint reads without washing surfaces out:

| Mode | Effect on the gray base |
| ----- | ------------------------------------------------------ |
| Light | **Deeper** gray surfaces (more contrast against white) |
| Dark | **Lighter** gray surfaces (lifts the near-black base) |

Implementation: the `.light.tone-tinted` / `.dark.tone-tinted` surface-lightness
overrides live in `globals.css`; the class is toggled at runtime by
`redux/listener.ts` and pre-hydration (no FOUC) by the byte-identical bootstrap
IIFE in `renderer/index.html` and `renderer/settings/index.html`. Pure-neutral
(`0`) and full-color (`0.16`) presets keep the crisp default ramp untouched, so
the default neutral-dark appearance never changes.

Rules:

- Apply the tinted gray base only inside the open `(0, 0.16)` chroma band; never
to pure neutral or full color.
- Hold dark tinted `--secondary` / `--muted` lightness at or below `0.27` so the
10px ThemeSelector family labels keep AA 4.5:1 contrast on `hover:bg-muted`.

## Typography

| Use | Typeface | Size guidance | Notes |
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Skills Desktop provides a GUI to manage and monitor skills installed via [`npx s
- **68 AI Agents Supported** - Auto-detects Claude Code, Cursor, Codex, Gemini CLI, and more
- **Symlink Status Visualization** - Valid (✓), Broken (◐), Inaccessible (!), Missing (○) indicators
- **Customizable Dashboard** - Widget-based home view with skill stats, symlink health, agent coverage, bookmarks, and quick actions — drag, resize, and arrange across multiple pages
- **44 Themes** - 34 OKLCH color themes (17 hues × light/dark) + 2 pure neutral + 8 tinted neutral
- **54 Themes** - 34 OKLCH color themes (17 hues × light/dark) + 2 pure neutral + 18 tinted neutral
- **Auto Update** - Automatic updates via GitHub Releases

## Supported Agents
Expand Down
31 changes: 18 additions & 13 deletions SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,15 +307,15 @@ Based on Terminal Minimal style with OKLCH dynamic theming.

### Theme System

44 visual themes total (27 presets): 34 OKLCH color themes + 2 pure neutral themes + 8 shadcn-baseColor-style tinted neutral themes.
54 visual themes total (37 presets): 34 OKLCH color themes + 2 pure neutral themes + 18 shadcn-baseColor-style tinted neutral themes.

**Theme Types:**

| Type | Description | Count |
| -------------- | ------------------------------------------------------------ | ------------------------ |
| Color | OKLCH hue-based dynamic colors (all UI elements tinted) | 34 (17 hues × 2 modes) |
| Pure Neutral | shadcn/ui default gray palette (chroma = 0) | 2 (Dark + Light) |
| Tinted Neutral | shadcn baseColor lookalikes (subtle hue tint, chroma = 0.05) | 8 (4 families × 2 modes) |
| Type | Description | Count |
| -------------- | ------------------------------------------------------------ | ------------------------- |
| Color | OKLCH hue-based dynamic colors (all UI elements tinted) | 34 (17 hues × 2 modes) |
| Pure Neutral | shadcn/ui default gray palette (chroma = 0) | 2 (Dark + Light) |
| Tinted Neutral | shadcn baseColor lookalikes (subtle hue tint, chroma = 0.05) | 18 (9 families × 2 modes) |

**Color Theme Hues (17):**

Expand All @@ -339,20 +339,25 @@ Based on Terminal Minimal style with OKLCH dynamic theming.
| Fuchsia | 325 | `oklch(0.7 0.16 325)` |
| Magenta | 340 | `oklch(0.7 0.16 340)` |

**Tinted Neutral Families (4):**
**Tinted Neutral Families (9):**

shadcn-baseColor lookalikes — subtle hue tint at `chroma = 0.05` puts the
background at ~`chroma 0.0055` (matches shadcn's `oklch(0.141 0.005 285.823)`
zinc value) while accents at L=0.7 read as "subtly tinted gray." Useful
when you want the shadcn baseColor look without committing to a fully
saturated theme.

| Family | Hue | Character |
| ------ | --- | ---------------- |
| Zinc | 265 | Cool purple-gray |
| Slate | 240 | Blue-gray |
| Stone | 60 | Warm sand-gray |
| Mauve | 320 | Purple-pink-gray |
| Family | Hue | Character |
| ------ | --- | -------------------- |
| Clay | 20 | Warm terracotta-gray |
| Stone | 60 | Warm sand-gray |
| Olive | 105 | Yellow-green gray |
| Sage | 150 | Green gray |
| Steel | 200 | Cool cyan-blue gray |
| Slate | 240 | Blue-gray |
| Zinc | 265 | Cool purple-gray |
| Mauve | 320 | Purple-pink-gray |
| Plum | 345 | Pink-purple gray |

Each family ships with a `-dark` and `-light` preset (e.g. `zinc-dark`,
`zinc-light`) that bakes in the mode, mirroring the shape of the existing
Expand Down
17 changes: 17 additions & 0 deletions src/renderer/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,19 @@
if (typeof t.hue === 'number') {
html.style.setProperty('--theme-hue', String(t.hue))
}
// Resolve chroma from v1 (`t.chroma`) or the legacy v0 `presetType`
// so `chromaVal` can also decide the .tone-tinted gray base below.
var chromaVal = null
if (typeof t.chroma === 'number') {
chromaVal = t.chroma
html.style.setProperty('--theme-chroma', String(t.chroma))
} else if (t.presetType === 'color') {
// v0 payload — async migration runs ~100ms later; without this
// branch the user sees a neutral-dark flash until hydration.
chromaVal = 0.16
html.style.setProperty('--theme-chroma', '0.16')
} else if (t.presetType === 'neutral') {
chromaVal = 0
html.style.setProperty('--theme-chroma', '0')
}
if (t.mode === 'light') {
Expand All @@ -42,6 +48,17 @@
html.classList.add('dark')
html.classList.remove('light')
}
// Tinted-neutral presets (0 < chroma < 0.16 = COLOR_PRESET_CHROMA)
// soften their gray base via .tone-tinted in globals.css. Apply it
// pre-hydration so a persisted tinted theme doesn't flash the crisp
// base ramp before React mounts. Pure-neutral (the default) and
// full-color presets never get the class. v0 storage had no tinted
// presets, so the legacy branches above always leave it off.
if (chromaVal !== null && chromaVal > 0 && chromaVal < 0.16) {
html.classList.add('tone-tinted')
} else {
html.classList.remove('tone-tinted')
}
} catch (_err) {
// Malformed storage — fall back to default .dark
}
Expand Down
19 changes: 19 additions & 0 deletions src/renderer/settings/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,19 @@
if (typeof t.hue === 'number') {
html.style.setProperty('--theme-hue', String(t.hue))
}
// Resolve chroma from v1 (`t.chroma`) or the legacy v0 `presetType`
// so `chromaVal` can also decide the .tone-tinted gray base below.
var chromaVal = null
if (typeof t.chroma === 'number') {
chromaVal = t.chroma
html.style.setProperty('--theme-chroma', String(t.chroma))
} else if (t.presetType === 'color') {
// v0 payload — async migration runs ~100ms later; without this
// branch the user sees a neutral-dark flash until hydration.
chromaVal = 0.16
html.style.setProperty('--theme-chroma', '0.16')
} else if (t.presetType === 'neutral') {
chromaVal = 0
html.style.setProperty('--theme-chroma', '0')
}
if (t.mode === 'light') {
Expand All @@ -41,6 +49,17 @@
html.classList.add('dark')
html.classList.remove('light')
}
// Tinted-neutral presets (0 < chroma < 0.16 = COLOR_PRESET_CHROMA)
// soften their gray base via .tone-tinted in globals.css. Apply it
// pre-hydration so a persisted tinted theme doesn't flash the crisp
// base ramp before React mounts. Pure-neutral (the default) and
// full-color presets never get the class. v0 storage had no tinted
// presets, so the legacy branches above always leave it off.
if (chromaVal !== null && chromaVal > 0 && chromaVal < 0.16) {
html.classList.add('tone-tinted')
} else {
html.classList.remove('tone-tinted')
}
} catch (_err) {
// Malformed storage — fall back to default .dark
}
Expand Down
Loading
Loading