Transform CalcText from a single-document calculator into a full workspace application with multi-document support, theming, and responsive mobile experience. - Theme system: 5 presets (Light, Dark, Matrix, Midnight, Warm) + accent colors - Document model with localStorage persistence and auto-save - Tab bar with keyboard shortcuts (Ctrl+N/W/Tab/1-9), rename, close - Sidebar with search, recent, favorites, folders, templates, drag-and-drop - 5 templates: Budget, Invoice, Unit Converter, Trip Planner, Loan Calculator - Status bar with cursor position, engine status, dedication to Igor Cassel - Results panel: type-specific colors, click-to-copy, error hints - Format toolbar: H, B, I, //, color labels with live preview toggle - Syntax highlighting using theme CSS variables - Error hover tooltips - Mobile: bottom results tray, sidebar drawer, touch targets, safe areas - Docker multi-stage build (Rust WASM + Vite + Nginx) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
372 lines
10 KiB
Markdown
372 lines
10 KiB
Markdown
# 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_
|