From da99b6391654e68db17197446f8139af1bac2d08 Mon Sep 17 00:00:00 2001 From: Falkan Date: Tue, 17 Mar 2026 11:28:42 -0400 Subject: [PATCH] feat: Dan Mode (third theme), Pico CSS notes, cycle theme toggle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dan Mode: pure white backgrounds, electric blue text (#0000ff), nuclear yellow accents (#ffff00), hot magenta borders (#ff00ff), red labels, glowing box-shadows. Maximum saturation. Hostile to human vision by design. Theme toggle now cycles light → dark → dan → light (icons: 🌙 😎 ☀️). PICO_CSS.md: documents [role=button] color clobber, palette scoping rules, and theme toggling pattern for future reference. --- PICO_CSS.md | 62 +++++++++++++++++++++++ src/app.css | 101 ++++++++++++++++++++++++++++++++++++++ src/routes/+layout.svelte | 21 ++++---- 3 files changed, 175 insertions(+), 9 deletions(-) create mode 100644 PICO_CSS.md diff --git a/PICO_CSS.md b/PICO_CSS.md new file mode 100644 index 0000000..f0f1332 --- /dev/null +++ b/PICO_CSS.md @@ -0,0 +1,62 @@ +# Pico CSS — Gotchas & Notes + +## [role=button] clobbers --pico-color + +Pico's stylesheet includes this rule: + +```css +[role=button], [type=button], button, ... { + --pico-color: var(--pico-primary-inverse); + ... +} +``` + +In light mode, `--pico-primary-inverse` is `#fff`. This means **any element with `role="button"` will have white text by default**, regardless of its background. Children inherit this, making text invisible on white/light card backgrounds. + +**Fix:** On the element itself, reset the variable and set an explicit color: + +```css +.your-element { + --pico-color: initial; + color: var(--pico-contrast); /* #181c25 light / #dfe3eb dark — always legible */ +} +``` + +**Alternative:** Remove `role="button"` and use `tabindex="0"` + keyboard handlers only, avoiding the Pico button rule entirely. Only do this if button semantics aren't needed for accessibility. + +## Palette overrides must be scoped to light mode + +Pico defines its palette twice: once for light (`:root:not([data-theme=dark])`) and once for dark (`@media (prefers-color-scheme: dark)` + `[data-theme=dark]`). If you override Pico vars at bare `:root`, you'll clobber the dark mode values. + +**Wrong:** +```css +:root { + --pico-color: #2C2C2C; /* kills dark mode */ +} +``` + +**Correct:** +```css +@media (prefers-color-scheme: light) { + :root:not([data-theme]) { --pico-color: #2C2C2C; } +} +[data-theme=light] { --pico-color: #2C2C2C; } +``` + +## Theme toggling pattern + +Set `data-theme="light"` or `data-theme="dark"` on ``. Pico reads this attribute for its palette selectors. Persist to `localStorage`. Read OS preference (`prefers-color-scheme`) on first visit when no stored value exists. + +## --pico-card-background-color in light mode + +In light mode, Pico sets: +``` +--pico-card-background-color: var(--pico-background-color) → #fff +``` + +If you want custom card backgrounds, override `--pico-card-background-color` directly (scoped to light mode only — see above). + +## CSS variables that Pico does NOT define + +- `--color-label` (app-defined) — must be set for both light and dark explicitly +- Any `--shadow-*` vars — define at `:root` (theme-neutral) diff --git a/src/app.css b/src/app.css index e7320a4..ba5e79b 100644 --- a/src/app.css +++ b/src/app.css @@ -54,6 +54,107 @@ } [data-theme=dark] { --color-label: #a0a09a; } +/* ── DAN MODE ──────────────────────────────────────────────────────────────── */ +/* Screaming bright colors. Offensive to the eyes by design. */ +[data-theme=dan] { + color-scheme: light; + + /* Base */ + --pico-background-color: #ffffff; + --pico-color: #0000ff; /* pure electric blue body text */ + --pico-muted-color: #ff6600; /* screaming orange for muted text */ + --pico-muted-border-color: #ff00ff; + + /* Cards */ + --pico-card-background-color: #ffffff; + --pico-card-border-color: #ff00ff; /* magenta borders */ + --pico-card-box-shadow: 0 0 12px 2px #ffff00, 0 0 4px 1px #ff00ff; + + /* Primary — nuclear yellow */ + --pico-primary: #cccc00; + --pico-primary-background: #ffff00; + --pico-primary-border: #cccc00; + --pico-primary-hover: #aaaa00; + --pico-primary-hover-background: #eeee00; + --pico-primary-hover-border: #aaaa00; + --pico-primary-focus: rgba(255, 255, 0, 0.6); + --pico-primary-inverse: #000000; /* black text on yellow buttons */ + + /* Secondary — hot magenta */ + --pico-secondary: #ff00ff; + --pico-secondary-background: #dd00dd; + --pico-secondary-border: #ff00ff; + --pico-secondary-hover: #ff44ff; + --pico-secondary-hover-background: #ff00ff; + --pico-secondary-inverse: #ffffff; + + /* Contrast */ + --pico-contrast: #000000; + --pico-contrast-background: #000000; + --pico-contrast-inverse: #ffff00; + + /* Form elements */ + --pico-form-element-background-color: #ffffcc; + --pico-form-element-border-color: #ff00ff; + --pico-form-element-color: #0000ff; + --pico-form-element-active-border-color: #ff0000; + --pico-form-element-focus-color: rgba(255, 0, 255, 0.5); + + /* App-specific */ + --color-label: #ff0000; /* red labels */ +} + +/* Dan Mode: result tile text — override the [role=button] white-text reset */ +[data-theme=dan] .result-tile { + --pico-color: initial; + color: #0000ff; + background-color: #ffffff; + border-color: #ff00ff; + box-shadow: 0 0 8px 1px #ffff00; +} + +[data-theme=dan] .result-tile:hover, +[data-theme=dan] .result-tile.highlighted { + background-color: #ffff00; + border-color: #ff0000; + color: #000000; + box-shadow: 0 0 16px 4px #ff00ff; +} + +[data-theme=dan] .result-label { + color: #ff0000; +} + +/* Dan Mode: highlight bar */ +[data-theme=dan] .highlighted-result { + background-color: #ffff00; + border-color: #ff0000 !important; + box-shadow: 0 0 20px 4px #ff00ff; +} + +[data-theme=dan] .highlighted-eq-from, +[data-theme=dan] .highlighted-eq-to { + color: #000000; +} + +[data-theme=dan] .highlighted-eq-sep { + color: #ff0000; +} + +/* Dan Mode: nav / header */ +[data-theme=dan] header { + background-color: #ffff00; + border-bottom: 3px solid #ff00ff; +} + +[data-theme=dan] header strong { + color: #ff0000; +} + +[data-theme=dan] header nav a { + color: #0000ff !important; +} + /* ── Results grid ──────────────────────────────────────────────────────────── */ .results-grid { display: grid; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 6342736..28d2bd3 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -5,25 +5,28 @@ let { children } = $props(); - // Resolve initial theme: stored pref > OS preference - function resolveInitialTheme(): 'light' | 'dark' { + type Theme = 'light' | 'dark' | 'dan'; + const CYCLE: Theme[] = ['light', 'dark', 'dan']; + const ICONS: Record = { light: '🌙', dark: '😎', dan: '☀️' }; + + function resolveInitialTheme(): Theme { if (!browser) return 'light'; const stored = localStorage.getItem('theme'); - if (stored === 'light' || stored === 'dark') return stored; + if (stored === 'light' || stored === 'dark' || stored === 'dan') return stored; return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } - let theme = $state<'light' | 'dark'>(resolveInitialTheme()); + let theme = $state(resolveInitialTheme()); - // Apply theme to and persist $effect(() => { if (!browser) return; document.documentElement.setAttribute('data-theme', theme); localStorage.setItem('theme', theme); }); - function toggleTheme() { - theme = theme === 'light' ? 'dark' : 'light'; + function cycleTheme() { + const next = CYCLE[(CYCLE.indexOf(theme) + 1) % CYCLE.length]; + theme = next; } @@ -37,8 +40,8 @@
  • About
  • Contact
  • -