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:
1 parent
4f913da0b1
commit
d5bb874651
21 files changed
+1120
-289
No files matched your search
+42
-14
@@ -84,7 +84,7 @@ templ Header(user User, currentPath string) {
|
||||
</nav>
|
||||
<!-- Bottom: theme picker + user -->
|
||||
<div
|
||||
x-data="{ openPanel: window.location.pathname.startsWith('/admin') ? 'admin' : null }"
|
||||
x-data={ "{ openPanel: window.location.pathname.startsWith('/admin') ? 'admin' : null, themeMode: '" + string(ThemeModeOf(user.Theme)) + "' }" }
|
||||
class="shrink-0 px-3 py-3 space-y-2 border-t"
|
||||
style="border-color: color-mix(in srgb, var(--text-primary) 7%, transparent);"
|
||||
>
|
||||
@@ -97,7 +97,7 @@ templ Header(user User, currentPath string) {
|
||||
<div class="sidebar-panel">
|
||||
<button
|
||||
type="button"
|
||||
@click="openPanel = openPanel === 'appearance' ? null : 'appearance'"
|
||||
@click={ "openPanel = openPanel === 'appearance' ? null : 'appearance'; if (openPanel === 'appearance') $nextTick(() => revealCurrentTheme(themeMode))" }
|
||||
class="w-full flex items-center gap-3 px-3 py-2 rounded-lg text-sm font-medium transition-colors text-content-muted hover:text-content hover:bg-surface-hover"
|
||||
>
|
||||
@Icon("palette", "h-5 w-5 shrink-0")
|
||||
@@ -106,22 +106,50 @@ templ Header(user User, currentPath string) {
|
||||
@Icon("chevron-down", "h-4 w-4")
|
||||
</span>
|
||||
</button>
|
||||
<div x-show="openPanel === 'appearance'" x-cloak x-transition class="mt-1 space-y-0.5 pl-1">
|
||||
for _, opt := range ThemeOptions {
|
||||
<div x-show="openPanel === 'appearance'" x-cloak x-transition class="mt-1 pl-1">
|
||||
<!-- Dark / Light filter -->
|
||||
<div class="grid grid-cols-2 gap-1 px-2 pt-1">
|
||||
<button
|
||||
type="button"
|
||||
@click={ "changeTheme('" + opt.Name + "'); openPanel = null" }
|
||||
class="theme-btn w-full flex items-center gap-2.5 px-3 py-1.5 rounded-lg text-sm transition-colors hover:bg-surface-hover"
|
||||
data-theme={ opt.Name }
|
||||
style="color: var(--text-secondary);"
|
||||
@click="themeMode = 'dark'; $nextTick(() => revealCurrentTheme('dark'))"
|
||||
class="flex items-center justify-center gap-1.5 py-1.5 rounded-md text-xs font-medium transition-colors"
|
||||
:class={ "themeMode === 'dark' ? 'mode-toggle-active' : 'text-content-muted hover:text-content'" }
|
||||
>
|
||||
<span class="inline-block w-3.5 h-3.5 rounded-full shrink-0 ring-1 ring-black/10" style={ "background-color: " + opt.Color }></span>
|
||||
<span class="flex-1 text-left">{ opt.Label }</span>
|
||||
<span class={ "theme-check shrink-0" + themeCheckClass(opt.Name, user.Theme) }>
|
||||
@Icon("check", "h-4 w-4")
|
||||
</span>
|
||||
@Icon("moon", "h-3.5 w-3.5 shrink-0")
|
||||
<span>Dark</span>
|
||||
<span class="tabular-nums opacity-50">{ len(ThemeOptionsFor(ModeDark)) }</span>
|
||||
</button>
|
||||
}
|
||||
<button
|
||||
type="button"
|
||||
@click="themeMode = 'light'; $nextTick(() => revealCurrentTheme('light'))"
|
||||
class="flex items-center justify-center gap-1.5 py-1.5 rounded-md text-xs font-medium transition-colors"
|
||||
:class={ "themeMode === 'light' ? 'mode-toggle-active' : 'text-content-muted hover:text-content'" }
|
||||
>
|
||||
@Icon("sun", "h-3.5 w-3.5 shrink-0")
|
||||
<span>Light</span>
|
||||
<span class="tabular-nums opacity-50">{ len(ThemeOptionsFor(ModeLight)) }</span>
|
||||
</button>
|
||||
</div>
|
||||
<!-- Scrollable theme list (filtered by the mode above) -->
|
||||
<div class="mt-1 max-h-72 overflow-y-auto pr-1 space-y-0.5">
|
||||
for _, opt := range ThemeOptions {
|
||||
<button
|
||||
type="button"
|
||||
@click={ "changeTheme('" + opt.Name + "'); openPanel = null" }
|
||||
class="theme-btn w-full flex items-center gap-2.5 px-3 py-1.5 rounded-lg text-sm transition-colors hover:bg-surface-hover"
|
||||
x-show={ "themeMode === '" + string(opt.Mode) + "'" }
|
||||
data-theme={ opt.Name }
|
||||
data-mode={ string(opt.Mode) }
|
||||
style="color: var(--text-secondary);"
|
||||
>
|
||||
<span class="inline-block w-3.5 h-3.5 rounded-full shrink-0 ring-1 ring-black/10" style={ "background-color: " + opt.Color }></span>
|
||||
<span class="flex-1 text-left">{ opt.Label }</span>
|
||||
<span class={ "theme-check shrink-0" + themeCheckClass(opt.Name, user.Theme) }>
|
||||
@Icon("check", "h-4 w-4")
|
||||
</span>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
<div class="my-1.5 mx-3 border-t" style="border-color: color-mix(in srgb, var(--text-primary) 7%, transparent);"></div>
|
||||
<p class="px-3 pb-1 text-xs font-medium uppercase tracking-wide" style="color: var(--text-secondary);">Bookshelf</p>
|
||||
for _, w := range WoodOptions {
|
||||
|
||||
Reference in new issue
Block a user