Theme System === Compass has a per-user theme system with 10 built-in presets and support for AI-generated custom themes. Users can switch themes instantly without page reload, and each user's preference persists independently. The system lives in `src/lib/theme/` with four files: types, presets, apply, and fonts. Why oklch --- Every color in the theme system is defined in oklch format: `oklch(0.6671 0.0935 170.4436)`. The choice of oklch over hex or hsl is deliberate. oklch is a perceptually uniform color space, which means that two colors with the same lightness value actually look equally bright to the human eye. In hsl, "50% lightness" for blue looks dramatically different from "50% lightness" for yellow. This matters when you're defining 32 color keys that need to feel visually consistent across different hues. oklch has three components: - **L** (0-1): perceptual lightness - **C** (0-0.4ish): chroma (color intensity) - **H** (0-360): hue angle This makes it straightforward to create coherent dark/light mode pairs - you adjust the lightness channel while keeping hue and chroma consistent. Color map --- Each theme defines 32 color keys, once for light mode and once for dark. The `ThemeColorKey` type in `src/lib/theme/types.ts` enumerates all of them: **Core UI colors** (16 keys): - `background`, `foreground` - page background and default text - `card`, `card-foreground` - card surfaces - `popover`, `popover-foreground` - dropdown/dialog surfaces - `primary`, `primary-foreground` - primary action color - `secondary`, `secondary-foreground` - secondary actions - `muted`, `muted-foreground` - subdued elements - `accent`, `accent-foreground` - accent highlights - `destructive`, `destructive-foreground` - danger/error states **Utility colors** (3 keys): - `border` - borders and dividers - `input` - form input borders - `ring` - focus ring color **Chart colors** (5 keys): - `chart-1` through `chart-5` - used by Recharts visualizations **Sidebar colors** (8 keys): - `sidebar`, `sidebar-foreground`, `sidebar-primary`, `sidebar-primary-foreground`, `sidebar-accent`, `sidebar-accent-foreground`, `sidebar-border`, `sidebar-ring` The sidebar has its own color set because it's often visually distinct from the main content area. The native-compass preset, for example, uses a teal sidebar against a warm off-white background. The type is defined as: ```typescript export type ColorMap = Readonly> ``` Readonly because theme colors should never be mutated after creation. Fonts --- Each theme specifies three font stacks: ```typescript export interface ThemeFonts { readonly sans: string readonly serif: string readonly mono: string } ``` These map to CSS variables `--font-sans`, `--font-serif`, and `--font-mono` that Tailwind v4 picks up. Themes can also declare Google Fonts to load dynamically: ```typescript fontSources: { googleFonts: ["Oxanium", "Source Code Pro"] } ``` The `loadGoogleFonts()` function in `src/lib/theme/fonts.ts` handles this. It maintains a `Set` of already-loaded fonts to avoid duplicate requests, constructs the Google Fonts CSS URL with weights 300-700, and injects a `` element into the document head. ```typescript const families = toLoad .map((f) => `family=${f.replace(/ /g, "+")}:wght@300;400;500;600;700`) .join("&") const href = `https://fonts.googleapis.com/css2?${families}&display=swap` ``` The `display=swap` parameter ensures text remains visible while the font loads. Design tokens --- Beyond colors and fonts, each theme defines spatial and shadow tokens: ```typescript export interface ThemeTokens { readonly radius: string // border radius (e.g., "1.575rem") readonly spacing: string // base spacing unit (e.g., "0.3rem") readonly trackingNormal: string // letter spacing readonly shadowColor: string readonly shadowOpacity: string readonly shadowBlur: string readonly shadowSpread: string readonly shadowOffsetX: string readonly shadowOffsetY: string } ``` Themes also define a full shadow scale from `2xs` to `2xl`, separately for light and dark modes. This allows themes to have fundamentally different shadow characters - doom-64 uses hard directional shadows while bubblegum uses pop-art style drop shadows. How applyTheme() works --- The core of the theme system is `applyTheme()` in `src/lib/theme/apply.ts`. It works by injecting a `