Files
bookhoard/templates/theme_options.go
T
John O'Keefe d5bb874651 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.
2026-10-18 12:08:07 -04:00

108 lines
4.3 KiB
Go

package templates
// ThemeMode classifies a theme for the Appearance panel's Dark/Light filter.
type ThemeMode string
const (
ModeDark ThemeMode = "dark"
ModeLight ThemeMode = "light"
)
// ThemeOption describes a selectable color theme shown in the header picker.
type ThemeOption struct {
Name string // CSS theme suffix applied as class theme-<name>
Label string // Human-readable label
Color string // Hex swatch colour
Mode ThemeMode // dark or light — drives the panel's mode filter
}
// ThemeOptions is the full set of selectable colour themes, in display order.
//
// Dark themes come first, light themes after. Each mode's list keeps the
// same shape: the default first, then the long-standing classics, then the
// newer theme blocks sorted by background luminance (darkest → lightest),
// the treatment the Catppuccin run introduced. The light list mirrors the
// dark one: counterparts of the classics in the slots their dark siblings
// occupy, Catppuccin Latte after them, the Omarchy light block last.
var ThemeOptions = []ThemeOption{
// Dark: the original set, order preserved.
{"tokyo-night", "Tokyo Night", "#7aa2f7", ModeDark},
{"dracula", "Dracula", "#bd93f9", ModeDark},
{"nord", "Nord", "#88c0d0", ModeDark},
{"solarized-dark", "Solarized Dark", "#2aa198", ModeDark},
{"monokai", "Monokai", "#a6e22e", ModeDark},
{"one-dark-pro", "One Dark Pro", "#61dafb", ModeDark},
{"material-dark", "Material Dark", "#80cbc4", ModeDark},
{"catppuccin-mocha", "Catppuccin Mocha", "#f38ba8", ModeDark},
{"catppuccin-macchiato", "Catppuccin Macchiato", "#c6a0f6", ModeDark},
{"catppuccin-frappe", "Catppuccin Frappé", "#ca9ee6", ModeDark},
// Dark: Omarchy defaults, sorted by background luminance. Palettes come
// from each theme's colors.toml (github.com/basecamp/omarchy).
{"vantablack", "Vantablack", "#8d8d8d", ModeDark},
{"last-horizon", "Last Horizon", "#b59790", ModeDark},
{"ethereal", "Ethereal", "#7d82d9", ModeDark},
{"hackerman", "Hackerman", "#82fb9c", ModeDark},
{"matte-black", "Matte Black", "#e68e0d", ModeDark},
{"solitude", "Solitude", "#798186", ModeDark},
{"retro-82", "Retro 82", "#faa968", ModeDark},
{"osaka-jade", "Osaka Jade", "#509475", ModeDark},
{"kanagawa", "Kanagawa", "#7e9cd8", ModeDark},
{"miasma", "Miasma", "#78824b", ModeDark},
{"lumon", "Lumon", "#8bc9eb", ModeDark},
{"ristretto", "Ristretto", "#f38d70", ModeDark},
{"gruvbox", "Gruvbox", "#7daea3", ModeDark},
{"everforest", "Everforest", "#7fbbb3", ModeDark},
// Light themes. Lupine leads as the default light theme (the app's
// real default remains tokyo-night); then counterparts of the classics.
{"lupine", "Lupine", "#3264eb", ModeLight},
{"tokyo-night-day", "Tokyo Night Day", "#2e7de9", ModeLight},
{"solarized-light", "Solarized Light", "#268bd2", ModeLight},
{"one-light", "One Light", "#4078f2", ModeLight},
{"ayu-light", "Ayu Light", "#ff9933", ModeLight},
{"github-light", "GitHub Light", "#0969da", ModeLight},
{"gruvbox-light", "Gruvbox Light", "#076678", ModeLight},
{"everforest-light", "Everforest Light", "#3a94c5", ModeLight},
{"kanagawa-lotus", "Kanagawa Lotus", "#4d699b", ModeLight},
{"catppuccin-latte", "Catppuccin Latte", "#d20f39", ModeLight},
// Light: Omarchy defaults, sorted by background luminance.
{"rose-pine", "Rosé Pine", "#56949f", ModeLight},
{"flexoki-light", "Flexoki Light", "#205ea6", ModeLight},
{"white", "White", "#6e6e6e", ModeLight},
}
// ThemeOptionsFor returns the ordered subset of themes for one mode.
func ThemeOptionsFor(mode ThemeMode) []ThemeOption {
var out []ThemeOption
for _, opt := range ThemeOptions {
if opt.Mode == mode {
out = append(out, opt)
}
}
return out
}
// ThemeModeOf returns the mode of a theme name. Unknown or empty ids fall
// back to dark — the catalog's origin and home of the default theme.
func ThemeModeOf(name string) ThemeMode {
for _, opt := range ThemeOptions {
if opt.Name == name {
return opt.Mode
}
}
return ModeDark
}
// WoodOption describes a selectable bookshelf background texture.
type WoodOption struct {
Name string // value stored as data-wood and CSS bg-<name>
Label string
}
// WoodOptions is the full set of bookshelf background options.
var WoodOptions = []WoodOption{
{"none", "None"},
{"wood-light", "Wood Light"},
{"wood-dark", "Wood Dark"},
{"wood-mahogany", "Wood Mahogany"},
}