feat(web): implement complete workspace with themes, tabs, sidebar, and mobile
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>
This commit is contained in:
@@ -0,0 +1,371 @@
|
||||
# 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_
|
||||
Reference in New Issue
Block a user