# 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 `<input type="color">` |

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 <html>: `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 `<script>` in `<head>` BEFORE React mounts. Set `data-theme` on `<html>` synchronously.
- **CSS structure:** Each theme is a `[data-theme="name"]` selector block overriding `:root` variables.
- **Matrix performance:** Scanline effect uses `pointer-events: none` and is pure CSS. No performance impact.
- **Cross-platform:** macOS/Windows use native appearance APIs. Theme names map to native equivalents. Matrix theme = custom dark palette on all platforms.
- **PWA:** Update `<meta name="theme-color">` dynamically when theme changes for native status bar color.

---

_Created using Whiteport Design Studio (WDS) methodology_
