feat(themes): Omarchy catalog, 12 light themes, Dark/Light Appearance picker

Expand the catalog from 11 themes (10 dark, 1 light) to 37 (24/13):
all 18 remaining Omarchy defaults (palettes from each theme's
colors.toml) plus 8 canonical light themes (Tokyo Night Day, Solarized
Light, One Light, Ayu Light, GitHub Light, Gruvbox Light, Everforest
Light, Kanagawa Lotus). Lupine leads the light list as the default
light theme; the app default stays tokyo-night.

Appearance panel: Dark/Light segmented filter (SSR-initialized from the
user's current theme), scrollable theme list, Bookshelf section pinned;
active row revealed on open. Light themes carry color-scheme: light and
the soft-shadow token set. Public-page theme selects get Dark/Light
optgroups. New moon/sun icons.

tailwind.config: safelist the new theme ids — Tailwind v3 purges custom
@layer base class selectors that appear in no scanned content file, so
an unsafelisted id silently ships without its CSS (found the hard way).

theme.ts: applyTheme now swaps only theme-* classes (changing theme
from the login select no longer wipes min-h-screen) and keeps the class
in sync on <html> for the new ThemeFlashGuard — a pre-paint inline
script on the four public pages that applies the saved theme before
first paint.

Kanagawa's UI accent uses crystalBlue (#7e9cd8) instead of Omarchy's
foreground-as-accent so links stay distinguishable from text.
This commit is contained in:
John O'Keefe committed 2026-10-18 12:08:07 -04:00
1 parent 4f913da0b1
commit d5bb874651
21 files changed
+1120 -289

No files matched your search

+292 -2
View File
@@ -93,6 +93,138 @@
--border: #37474f;
}
/* Omarchy dark themes, sorted by background luminance (darkest first).
Palettes: themes/<name>/colors.toml in basecamp/omarchy. Where the
upstream lighter_background matches the background (Last Horizon,
Solitude) the card tone is derived a step up instead. */
.theme-vantablack {
--bg-primary: #000000;
--bg-secondary: #1a1a1a;
--text-primary: #ffffff;
--text-secondary: #ececec;
--accent: #8d8d8d;
--border: #1a1a1a;
}
.theme-last-horizon {
--bg-primary: #0c0b0c;
--bg-secondary: #171516;
--text-primary: #fafcfb;
--text-secondary: #cfd3cd;
--accent: #b59790;
--border: #2b2729;
}
.theme-ethereal {
--bg-primary: #060b1e;
--bg-secondary: #131a3a;
--text-primary: #ffcead;
--text-secondary: #c9b8a6;
--accent: #7d82d9;
--border: #252e56;
}
.theme-hackerman {
--bg-primary: #0b0c16;
--bg-secondary: #151828;
--text-primary: #ddf7ff;
--text-secondary: #b5c5db;
--accent: #82fb9c;
--border: #1f253a;
}
.theme-matte-black {
--bg-primary: #121212;
--bg-secondary: #1e1e1e;
--text-primary: #bebebe;
--text-secondary: #8a8a8d;
--accent: #e68e0d;
--border: #2a2a2a;
}
.theme-solitude {
--bg-primary: #101315;
--bg-secondary: #171b1e;
--text-primary: #cacccc;
--text-secondary: #8a9199;
--accent: #798186;
--border: #262d31;
}
.theme-retro-82 {
--bg-primary: #05182e;
--bg-secondary: #0a2540;
--text-primary: #f6dcac;
--text-secondary: #a7c9c6;
--accent: #faa968;
--border: #134e5a;
}
.theme-osaka-jade {
--bg-primary: #111c18;
--bg-secondary: #23372b;
--text-primary: #c1c497;
--text-secondary: #81b8a8;
--accent: #509475;
--border: #32473b;
}
/* Omarchy maps Kanagawa's accent to its foreground; the UI uses
crystalBlue instead so links/buttons stay distinguishable from text. */
.theme-kanagawa {
--bg-primary: #1f1f28;
--bg-secondary: #223249;
--text-primary: #dcd7ba;
--text-secondary: #c8c093;
--accent: #7e9cd8;
--border: #363646;
}
.theme-miasma {
--bg-primary: #222222;
--bg-secondary: #2c2c2c;
--text-primary: #c2c2b0;
--text-secondary: #8a8a7e;
--accent: #78824b;
--border: #383838;
}
.theme-lumon {
--bg-primary: #16242d;
--bg-secondary: #1b2d40;
--text-primary: #d6e2ee;
--text-secondary: #4d86b0;
--accent: #8bc9eb;
--border: #243d56;
}
.theme-ristretto {
--bg-primary: #2c2525;
--bg-secondary: #3d2f2a;
--text-primary: #e6d9db;
--text-secondary: #c3b7b8;
--accent: #f38d70;
--border: #403e41;
}
.theme-gruvbox {
--bg-primary: #282828;
--bg-secondary: #3c3836;
--text-primary: #d4be98;
--text-secondary: #bdae93;
--accent: #7daea3;
--border: #504945;
}
.theme-everforest {
--bg-primary: #2d353b;
--bg-secondary: #343f44;
--text-primary: #d3c6aa;
--text-secondary: #9da9a0;
--accent: #7fbbb3;
--border: #3d484d;
}
.theme-catppuccin-mocha {
--bg-primary: #1e1e2e;
--bg-secondary: #181825;
@@ -131,6 +263,145 @@
--border: #dcdfe6;
}
/* Light themes. Each carries color-scheme: light so native form
controls, scrollbars and UA popups render light. Backgrounds follow
each theme's canonical palette; the card tone mirrors what the theme
itself does (elevated white for screen-like themes, a darker paper
tone for the paper ones — Solarized set that convention). */
.theme-tokyo-night-day {
color-scheme: light;
--bg-primary: #e1e2e7;
--bg-secondary: #ffffff;
--text-primary: #3760bf;
--text-secondary: #6172b0;
--accent: #2e7de9;
--border: #c4c8da;
}
.theme-solarized-light {
color-scheme: light;
--bg-primary: #fdf6e3;
--bg-secondary: #eee8d5;
--text-primary: #586e75;
--text-secondary: #93a1a1;
--accent: #268bd2;
--border: #ddd6c1;
}
.theme-one-light {
color-scheme: light;
--bg-primary: #fafafa;
--bg-secondary: #ffffff;
--text-primary: #383a42;
--text-secondary: #696c77;
--accent: #4078f2;
--border: #dfdfdf;
}
.theme-ayu-light {
color-scheme: light;
--bg-primary: #fafafa;
--bg-secondary: #f3f4f5;
--text-primary: #5c6166;
--text-secondary: #8a9199;
--accent: #ff9933;
--border: #e2e3e5;
}
.theme-github-light {
color-scheme: light;
--bg-primary: #ffffff;
--bg-secondary: #f6f8fa;
--text-primary: #24292f;
--text-secondary: #57606a;
--accent: #0969da;
--border: #d0d7de;
}
.theme-gruvbox-light {
color-scheme: light;
--bg-primary: #fbf1c7;
--bg-secondary: #f2e5bc;
--text-primary: #3c3836;
--text-secondary: #7c6f64;
--accent: #076678;
--border: #d5c4a1;
}
.theme-everforest-light {
color-scheme: light;
--bg-primary: #fdf6e3;
--bg-secondary: #f4f0d9;
--text-primary: #5c6a72;
--text-secondary: #708089;
--accent: #3a94c5;
--border: #d5cfaf;
}
.theme-kanagawa-lotus {
color-scheme: light;
--bg-primary: #f2ecbc;
--bg-secondary: #e5ddb0;
--text-primary: #545464;
--text-secondary: #716e61;
--accent: #4d699b;
--border: #d5cea3;
}
/* Omarchy light themes, sorted by background luminance (darkest first). */
.theme-rose-pine {
color-scheme: light;
--bg-primary: #faf4ed;
--bg-secondary: #fffaf3;
--text-primary: #575279;
--text-secondary: #797593;
--accent: #56949f;
--border: #dfdad9;
}
.theme-lupine {
color-scheme: light;
--bg-primary: #fafafa;
--bg-secondary: #ffffff;
--text-primary: #212121;
--text-secondary: #757575;
--accent: #3264eb;
--border: #d0d0d0;
}
.theme-flexoki-light {
color-scheme: light;
--bg-primary: #fffcf0;
--bg-secondary: #f2efe4;
--text-primary: #100f0f;
--text-secondary: #6f6e69;
--accent: #205ea6;
--border: #cecdc3;
}
/* Omarchy's pure monochrome theme: its accent is deliberately grey. */
.theme-white {
color-scheme: light;
--bg-primary: #ffffff;
--bg-secondary: #f5f5f5;
--text-primary: #000000;
--text-secondary: #808080;
--accent: #6e6e6e;
--border: #e8e8e8;
}
/*
* Derived tokens — computed once on <body> from the active theme vars so
* they adapt automatically to every theme without 11x repetition.
@@ -158,8 +429,21 @@
--shadow-bar: 0 1px 0 rgba(0, 0, 0, 0.18), 0 4px 16px rgba(0, 0, 0, 0.16);
}
/* Slightly softer shadows on the lone light theme */
.theme-catppuccin-latte {
/* Softer shadows on light themes — dark shadows tuned for dark themes
look harsh on paper backgrounds. */
.theme-catppuccin-latte,
.theme-tokyo-night-day,
.theme-solarized-light,
.theme-one-light,
.theme-ayu-light,
.theme-github-light,
.theme-gruvbox-light,
.theme-everforest-light,
.theme-kanagawa-lotus,
.theme-rose-pine,
.theme-lupine,
.theme-flexoki-light,
.theme-white {
--shadow-card: 0 1px 2px rgba(76, 79, 105, 0.08), 0 1px 3px rgba(76, 79, 105, 0.06);
--shadow-card-hover: 0 6px 16px rgba(76, 79, 105, 0.16), 0 2px 6px rgba(76, 79, 105, 0.08);
--shadow-pop: 0 12px 40px rgba(76, 79, 105, 0.22), 0 4px 12px rgba(76, 79, 105, 0.12);
@@ -237,6 +521,12 @@
padding: 0.375rem;
}
/* Active half of the Appearance panel's Dark/Light mode filter. */
.mode-toggle-active {
background-color: var(--accent-muted);
color: var(--text-primary);
}
/* Desktop: pin the sidebar, drop the backdrop. No unconditional body
offset here — the body:has(.app-sidebar) rule below is the only source
of the sidebar padding, so pages that never render the sidebar