# 5.1 — Theme System **Previous Step:** ← [Templates](../../04-file-organization/4.2-templates/4.2-templates.md) **Next Step:** → [Mobile Experience](../../06-mobile-experience/6.1-mobile-shell/6.1-mobile-shell.md) --- ## Page Metadata | Property | Value | |----------|-------| | **Scenario** | 05 — Theming | | **Page Number** | 5.1 | | **Platform** | Web (PWA), portable to macOS/Windows | | **Page Type** | Dropdown Panel + Settings Section | --- ## Overview **Page Purpose:** Enable users to personalize their workspace with preset themes and custom accent colors. Theming is a personality differentiator — especially the Matrix theme, which gives CalcText a unique identity in the calculator app market. **Success Criteria:** - Theme switches instantly (< 16ms, no flash of unstyled content) - Matrix theme makes users want to screenshot and share - Custom accent color gives ownership feeling - Theme persists across sessions --- ## Theme Architecture All theming works through CSS custom properties on `:root`. Switching themes = swapping property values. No component changes, no re-renders beyond what CSS handles natively. ```typescript interface ThemeDefinition { id: string name: string icon: string // emoji or svg tokens: { // Backgrounds bg: string bgSecondary: string codeBg: string // Text text: string textH: string // Borders border: string // Accent accent: string accentBg: string accentBorder: string // Semantic success: string warning: string error: string // Syntax highlighting syntaxVariable: string syntaxNumber: string syntaxOperator: string syntaxKeyword: string syntaxFunction: string syntaxCurrency: string syntaxComment: string syntaxHeading: string // Result colors resultNumber: string resultUnit: string resultCurrency: string resultDatetime: string resultBoolean: string // Stripes stripe: string // Special fontOverride?: string // for Matrix: force monospace everywhere specialEffect?: string // CSS class for effects like scanlines } } ``` --- ## Preset Themes ### Light | Token | Value | |-------|-------| | --bg | #ffffff | | --bg-secondary | #f8f9fa | | --text | #6b6375 | | --text-h | #08060d | | --border | #e5e4e7 | | --accent | #6366f1 | | --stripe | rgba(0, 0, 0, 0.02) | | Feel | Clean, airy, professional | ### Dark | Token | Value | |-------|-------| | --bg | #16171d | | --bg-secondary | #1a1b23 | | --text | #9ca3af | | --text-h | #f3f4f6 | | --border | #2e303a | | --accent | #818cf8 | | --stripe | rgba(255, 255, 255, 0.025) | | Feel | Calm, focused, modern | ### Matrix | Token | Value | |-------|-------| | --bg | #0a0a0a | | --bg-secondary | #0f1a0f | | --text | #00ff41 | | --text-h | #33ff66 | | --border | #003300 | | --accent | #00ff41 | | --stripe | rgba(0, 255, 65, 0.03) | | --syntax-* | Green spectrum (#00cc33 to #39ff14) | | --result-currency | #ffff00 (yellow — stands out in green) | | fontOverride | `'Courier New', 'Fira Code', monospace` | | specialEffect | `matrix-scanlines` | | Feel | Iconic, hackery, fun | #### Matrix Special Effects **Scanlines (optional, subtle):** ```css .matrix-scanlines::after { content: ''; position: fixed; inset: 0; pointer-events: none; background: repeating-linear-gradient( 0deg, transparent, transparent 2px, rgba(0, 0, 0, 0.08) 2px, rgba(0, 0, 0, 0.08) 4px ); z-index: 9999; } ``` **Cursor glow:** ```css .matrix-theme .cm-cursor { border-color: #00ff41; box-shadow: 0 0 4px #00ff41, 0 0 8px rgba(0, 255, 65, 0.3); } ``` **Text glow (subtle):** ```css .matrix-theme .result-value { text-shadow: 0 0 2px rgba(0, 255, 65, 0.3); } ``` ### Midnight | Token | Value | |-------|-------| | --bg | #0f172a | | --bg-secondary | #1e293b | | --text | #94a3b8 | | --text-h | #e2e8f0 | | --border | #334155 | | --accent | #38bdf8 (sky-400) | | --stripe | rgba(56, 189, 248, 0.03) | | Feel | Deep blue, serene, late-night coding | ### Warm | Token | Value | |-------|-------| | --bg | #fffbf5 | | --bg-secondary | #fef3e2 | | --text | #78716c | | --text-h | #1c1917 | | --border | #e7e5e4 | | --accent | #f97316 (orange-500) | | --stripe | rgba(249, 115, 22, 0.03) | | Feel | Paper-like, warm, comfortable for long sessions | --- ## Theme Picker Dropdown **OBJECT ID:** `theme-picker` | Property | Value | |----------|-------| | Trigger | Click theme icon in header or status bar, or Ctrl+Shift+T | | Position | Dropdown below header button, right-aligned | | Width | 240px | | Background | var(--bg) | | Border | 1px solid var(--border) | | Border radius | 8px | | Shadow | 0 4px 16px rgba(0, 0, 0, 0.15) | | Padding | space-xs (6px) | | Animation | Scale from 95% + fade, 150ms ease-out | | Dismiss | Click outside, Esc, or select theme | ### Theme Picker Layout ``` ┌──────────────────────────┐ │ Themes │ section header │ │ │ ☀️ Light ✓ │ active indicator │ 🌙 Dark │ │ 💻 Matrix │ │ 🌊 Midnight │ │ 📜 Warm │ │ │ │ ────────────────────── │ separator │ │ │ Accent Color │ section header │ [●][●][●][●][●][●][●] │ color swatches │ │ │ ────────────────────── │ │ ⚙️ System (auto) │ follows OS preference └──────────────────────────┘ ``` ### Theme Item | Property | Value | |----------|-------| | Height | 36px | | Padding | 8px 12px | | Layout | Icon + name + optional checkmark | | Hover | var(--accent-bg) background, border-radius 4px | | Active | Checkmark ✓ on right side, weight 500 | | Click | Apply theme instantly, close picker | | Preview | On hover, show a 4-color mini-swatch (bg, text, accent, secondary) | ### Color Swatch Preview (on hover) | Property | Value | |----------|-------| | Size | 4 circles, 12px each, 4px gap | | Colors | bg, bg-secondary, accent, text — of the hovered theme | | Position | Inline after theme name | --- ## Accent Color Picker **OBJECT ID:** `theme-accent-picker` | Property | Value | |----------|-------| | Location | Inside theme picker dropdown, below themes | | Presets | 7 color swatches in a row | | Swatch size | 20px circles, 6px gap | | Active swatch | 2px ring in var(--text-h) | | Custom | Last swatch is rainbow gradient → opens native color picker | | Behavior | Overrides --accent, --accent-bg, --accent-border for current theme | | Persistence | Stored as `accentColor` in localStorage | ### Preset Accent Colors | Name | Value | Hex | |------|-------|-----| | Indigo (default) | Indigo 500/400 | #6366f1 / #818cf8 | | Teal | Teal 500/400 | #14b8a6 / #2dd4bf | | Rose | Rose 500/400 | #f43f5e / #fb7185 | | Amber | Amber 500/400 | #f59e0b / #fbbf24 | | Emerald | Emerald 500/400 | #10b981 / #34d399 | | Sky | Sky 500/400 | #0ea5e9 / #38bdf8 | | Custom | User pick | Via `` | Each preset has light/dark variants. The correct variant is selected based on current base theme. --- ## System Theme Option **OBJECT ID:** `theme-system` | Property | Value | |----------|-------| | Behavior | Follows OS `prefers-color-scheme` | | Matches | Light ↔ Light theme, Dark ↔ Dark theme | | Override | Selecting any specific theme disables system following | | Re-enable | Select "System (auto)" in picker | | Label | "⚙️ System (auto)" with current resolved theme name | --- ## Theme Application Flow ``` 1. User clicks theme → store in localStorage 2. Apply CSS class to : `data-theme="matrix"` 3. CSS variables resolve from theme class 4. All components instantly update (CSS-only, no React re-render) 5. CodeMirror theme needs manual reconfiguration (dispatch theme compartment) 6. PWA theme-color meta tag updates for status bar color ``` ### CodeMirror Theme Sync | Step | Action | |------|--------| | 1 | Theme changes → dispatch new baseTheme to EditorView | | 2 | Syntax highlighting colors update via CSS variables (no extension swap) | | 3 | Stripe colors update via CSS | | 4 | Active line highlight updates via CSS | | 5 | Only the base theme extension needs reconfiguration | --- ## Responsive | Breakpoint | Theme Picker | |------------|-------------| | >= 768px | Dropdown panel below header button | | < 768px | Bottom sheet (slides up from bottom, 60vh max height) | ### Mobile Bottom Sheet | Property | Value | |----------|-------| | Width | 100vw | | Max height | 60vh | | Border radius | 12px 12px 0 0 (top corners) | | Drag handle | 32px × 4px pill, centered | | Backdrop | 50% black overlay | | Animation | Slide up 200ms ease-out | | Close | Swipe down, tap backdrop | --- ## Page States | State | When | Behavior | |-------|------|----------| | **First launch** | No theme preference | Follow OS (system). If OS is dark, use Dark. | | **Theme selected** | User picked a theme | Applied instantly, persisted. System mode disabled. | | **System mode** | User selected "System (auto)" | Listens to OS changes in real-time. | | **Custom accent** | User picked accent color | Overrides accent tokens. Works with any base theme. | | **Matrix active** | Matrix theme selected | Font override applied. Scanline effect enabled. Green cursor glow. | --- ## Technical Notes - **No Flash of Unstyled Content (FOUC):** Load theme from localStorage in `