7.2 KiB
Dark Mode & Visual Polish Design Spec
For agentic workers: REQUIRED SUB-SKILL: Use
superpowers:subagent-driven-development(recommended) orsuperpowers:executing-plansto implement this plan task-by-task.
Goal: Replace the existing warm-paper light theme with a warm-dark "notebook/sketchbook at night" aesthetic — dark-only, no toggle, no system preference detection. Add paper grain texture, switch to dark terrain map tiles, and tighten typography.
Architecture: All changes are CSS and one Twig template update. Color tokens live in tokens.css (swap values, keep names). Grain texture is a pure-CSS SVG noise layer on body::after. Map tiles swap in map.html.twig. No new dependencies, no JS changes.
Approach chosen: B — color token swap + paper grain + typography refinements. Card/hero treatment (Approach C) deferred to a future visual polish pass.
Tech Stack: CSS custom properties, inline SVG data URI for grain, Stadia Maps tile CDN for dark terrain.
Global Constraints
- Dark-only — no light mode, no
prefers-color-schememedia query, no toggle - All changes in
user/— commit withgit -C user - No new npm/JS dependencies
- Existing token names (
--color-paper,--color-ink, etc.) must not change — only values - Teal accent
#1F6B5Alightens to#2A8C73for dark-background contrast - Map tile provider: Stadia Maps Alidade Smooth Dark (free tier; API key needed for production — see Task 2)
make test-uimust pass after implementation (25/25 or pre-existing P2 exception)
1. Color System
Replace all values in user/themes/intotheeast/css/tokens.css. Token names are unchanged.
Dark palette
| Token | Old value | New value | Role |
|---|---|---|---|
--color-paper |
#F7F5F2 |
#1A1814 |
Page background — warm near-black |
--color-canvas |
#FFFFFF |
#22201B |
Card surfaces, form backgrounds |
--color-ink |
#17171A |
#EDE8DF |
Primary text — warm cream |
--color-ink-2 |
#4A4850 |
#B8B0A4 |
Body text — muted warm |
--color-ink-muted |
#9896A0 |
#7A7268 |
Labels, timestamps, captions |
--color-border |
#E8E6E3 |
#2E2B25 |
Standard dividers |
--color-border-soft |
#F0EDEA |
#252219 |
Subtle dividers |
--color-accent |
#1F6B5A |
#2A8C73 |
Teal — lightened for dark contrast |
--color-accent-hover |
#185647 |
#236655 |
Hover/pressed teal |
--color-accent-light |
#EBF5F2 |
#1A2E29 |
Pale teal tint backgrounds |
--color-accent-on |
#FFFFFF |
#FFFFFF |
Text on accent surfaces (unchanged) |
Additional dark-only tokens (add to tokens.css)
--color-surface-raised: #2A2720; /* elevated surfaces: tooltips, hover states */
--color-ink-inverse: #17171A; /* text on accent-colored buttons */
2. Paper Grain Texture
Add to style.css, in the body section:
body::after {
content: '';
position: fixed;
inset: 0;
pointer-events: none;
z-index: 9998;
opacity: 0.035;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23noise)' opacity='1'/%3E%3C/svg%3E");
background-repeat: repeat;
background-size: 200px 200px;
}
This overlays a fixed noise texture across the entire viewport. pointer-events: none ensures it never blocks clicks. z-index: 9998 keeps it below any modals or dropdowns (which should use z-index 9999+). Opacity 3.5% — subtle enough to feel like paper texture without being distracting on photography.
3. Map Tiles — Stadia Alidade Smooth Dark
Replace the tile layer in user/themes/intotheeast/templates/map.html.twig.
Old:
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
New:
L.tileLayer('https://tiles.stadiamaps.com/tiles/alidade_smooth_dark/{z}/{x}/{y}{r}.png', {
maxZoom: 20,
attribution: '© <a href="https://stadiamaps.com/">Stadia Maps</a> © <a href="https://openmaptiles.org/">OpenMapTiles</a> © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
Production note: Stadia Maps requires a free API key for production domains. Add the key as a query param when ready: ?api_key=YOUR_KEY. During development on localhost no key is needed. Add a <!-- TODO: add Stadia API key before launch --> comment above the tile layer call so it's not forgotten.
Also update the mini-map tile layer in dailies.html.twig (same swap — same tile URL, same attribution).
4. Typography Refinements
Targeted improvements to style.css — not a full type system rewrite.
4a. Entry body readability
The entry body text (--text-md / 1.125rem) already uses --leading-normal (1.65) which is good. Increase the paragraph bottom margin slightly for breathing room:
/* current */
.entry-body p { margin-bottom: 1.1em; ... }
/* new */
.entry-body p { margin-bottom: 1.4em; ... }
4b. Heading tracking
DM Serif Display at large sizes benefits from slightly tighter tracking. Find heading rules that currently have letter-spacing: -0.01em and tighten to -0.02em. Only apply to h1 and h2 — smaller headings keep current tracking.
4c. Login form dark surface
The login form currently hardcodes background: #f0f0f0; color: #333 on the secondary button (line ~497 in style.css). Replace with tokens:
/* current */
.login-form .button.secondary { background: #f0f0f0; color: #333; ... }
/* new */
.login-form .button.secondary { background: var(--color-canvas); color: var(--color-ink); ... }
4d. Stats numbers
On the stats page, numeric values should feel deliberate. Add font-variant-numeric: tabular-nums to the stat value elements so columns of numbers align cleanly.
5. Incidental dark-mode fixes
Some existing styles use hardcoded light colors that will look wrong in dark mode. Audit and fix these in style.css:
- Any
background: #ffforbackground: white→var(--color-canvas) - Any
color: #333or similar hardcoded dark text →var(--color-ink)orvar(--color-ink-2) - Any
border: 1px solid #eeeor similar →var(--color-border) - Focus outline: currently likely a light-mode color — ensure
outline-colorusesvar(--color-accent)
Run a grep for literal hex values after implementation: grep -n '#[0-9a-fA-F]\{3,6\}' user/themes/intotheeast/css/style.css — every hit is a candidate to tokenize.
Verification
After implementation:
make test-ui— all tests pass- Visual check at
http://localhost:8081/trips/japan-korea-2026/dailies— warm dark background, cream text, teal accents visible, subtle grain - Visual check at
http://localhost:8081/trips/japan-korea-2026/map— dark terrain tiles load, GPX polyline visible, entry pins visible - Check the post form at
/post— form fields readable on dark canvas, no white-on-white or black-on-black surfaces - Run the hardcoded-hex grep and confirm any remaining literals are intentional