🌸 Polish Phase 8 — CSS Consolidation & Legacy Cleanup Playbook¶
Date: 28 April 2026
Scope: Eliminate ALL remaining glass/neon/backdrop-filter fromstyle.css
Duration: Single session (~2 hours)
Result: 52 legacy refs → 0. Three deploys, zero regressions.
What This Phase Achieved¶
| Metric | Before | After |
|---|---|---|
var(--neon-*) refs |
22 | 0 |
var(--glass-*) refs |
10 | 0 |
backdrop-filter lines |
20 | 0 |
body.zen-migrated override blocks |
9 | 0 |
zt-tools.css (separate file) |
13.6KB | Deleted |
style.css line count |
3,222 | 3,174 (−48) |
Architecture: The Three-Layer Migration¶
Layer 1: File Consolidation (zt-tools.css → zt-reading.css)¶
Problem: Tool pages loaded zt-tools.css separately via a <link> in <body> (not <head>). It was loaded TWICE — once from baseof.html and once from tool-header.html.
Solution:
1. Moved $isToolPage detection to <head> (was in <body>)
2. Added $isToolPage to the zt-reading.css loading conditional
3. Appended all 544 lines of zt-tools.css into zt-reading.css
4. Removed both <link> references and deleted the file
Key gotcha — Cascade Order:
zt-tools.css loaded AFTER per-tool CSS to guarantee override. Moving it into zt-reading.css (loaded in <head>) changes the cascade. But the tab selectors use ARIA attributes ([role="tablist"] > [role="tab"]) giving specificity 0,3,0 — higher than per-tool class selectors (0,1,0). Specificity wins regardless of source order.
Layer 2: Fold Zen Overrides into Base¶
Pattern: Phase 1-4 created body.zen-migrated .component overrides that set Zen tokens and backdrop-filter: none. Now that ALL pages are zen-migrated, the override layer is dead code.
Migration pattern:
/* BEFORE: Two definitions */
.global-btt {
background: rgba(255,255,255,0.06);
backdrop-filter: blur(var(--glass-blur-subtle));
}
body.zen-migrated .global-btt {
background: var(--bg-surface);
backdrop-filter: none;
}
/* AFTER: Single definition with Zen tokens */
.global-btt {
background: var(--bg-surface);
}
Components folded: .global-btt, .feedback-fab, .bookmark-cta-*, .page-search-box, select elements.
Layer 3: Direct Token Replacement¶
For components without existing overrides, replace legacy tokens directly:
| Legacy Pattern | Zen Replacement |
|---|---|
var(--neon-cyan) |
var(--accent) |
var(--neon-magenta) |
var(--accent) or var(--warning) for action callouts |
var(--neon-green) |
var(--success) |
var(--neon-yellow) |
var(--warning) |
var(--glass-border) |
1px solid var(--border) |
var(--glass-radius-button) |
var(--radius-md, 10px) |
var(--glass-blur-subtle) |
Remove entirely |
backdrop-filter: blur(*) |
Remove entirely |
rgba(255,255,255,0.06) |
var(--bg-surface) |
rgba(255,255,255,0.12) |
var(--border) |
color: #fff |
var(--text-primary) |
text-shadow: 0 0 Npx color |
Remove entirely (Zen rule #7) |
box-shadow: 0 0 Npx neon-color |
Remove (glow) |
transition: all 0.3s |
Specific properties, ≤200ms ease-out |
Component-by-Component Decisions¶
Video Card Tags (semantic colours)¶
Tags use colour to convey category meaning. Mapped to Zen semantic tokens:
- .card-tag.ai → var(--accent) (indigo)
- .card-tag.cert → var(--success) (green)
- .card-tag.legacy → var(--warning) (amber)
- .card-tag.cloud → var(--accent) (same as AI)
Blog Headings¶
The "color-coded chapters" pattern (h2=magenta, h3=green) was a glass-era design. In Zen, headings use size/weight hierarchy, not colour:
- h2 → var(--accent) (still distinct, but within the system)
- h3 → var(--text-primary) (standard text colour)
Blockquote Callouts¶
Default blockquote: var(--accent) border + var(--accent-subtle) background.
Action callout: var(--warning) — "do this now" semantics suit amber.
Reference + Scenario callouts: kept existing hardcoded colours (#60A5FA, #14B8A6) — these need dedicated --info and --teal tokens in a future phase.
Global a:hover text-shadow¶
Removed. The neon-cyan text glow on every link hover was one of the most visible glass-era patterns. Zen rule #7 explicitly bans text-shadow glow. Replaced with color: var(--text-primary).
Progress Bar¶
var(--neon-cyan) → var(--accent). The transition: width 50ms linear is a documented exception to the animation guardrail (width animation IS the bar's purpose).
Box-shadow rgba(0,0,0,*)¶
Documented exception. Shadows always represent light absence — they're universally dark in both light and dark modes. No need for tokens.
QA Strategy¶
Playwright Test Pattern¶
// Helper: detect any neon color in a computed style value
function noNeon(s) {
return !s.includes('102, 255, 255') && // neon-cyan
!s.includes('255, 102, 255') && // neon-magenta
!s.includes('57, 255, 20') && // neon-green
!s.includes('255, 230, 0'); // neon-yellow
}
function noBlur(s) { return !s || s === 'none'; }
Pages Tested (both dark + light modes)¶
- Tool page — grid, tabs, BTT, FAB, pills
- Blog index — timeline search, dots, tags, CTA, pager
- Blog post — headings, blockquotes, progress bar
- Video section — card tags, page hero
- Study guide — back-nav link
- 404 page — cards, links
- Guided explore — reading room grid, no tool bleed
- Mobile 375px — tags, search, sidebar collapse
Pre-deploy Checklist¶
- [ ] Hugo build: zero errors
- [ ]
grep 'var(--neon-' style.cssreturns 0 matches - [ ]
grep 'var(--glass-' style.cssreturns 0 matches - [ ]
grep 'backdrop-filter' style.cssreturns 0 matches - [ ] Playwright: all pages × both modes × mobile
- [ ] Guided
zt-reading.csscopied and hashes match
Rubber-Duck Catches (Worth Remembering)¶
-
zt-reading.css didn't load on tool pages — the conditional in baseof.html only covered blog/study-guides/licensing/video. Adding
$isToolPagerequired moving the variable definition from<body>to<head>. -
/start/page not zen-migrated — folding overrides into base changes styling on non-migrated pages. Accepted as intentional (BTT/FAB improvement, not regression). -
:focus-visiblestill used neon-cyan — easy to miss since it only shows on keyboard navigation. Always grep for the token, don't rely on visual testing. -
Removing token definitions breaks downstream files — deleting
--border-glowand--shadow-neonfrom:rootcaused regressions insections.cssandreadiness.csswhich still referenced them. Always grep ALL CSS files for a token before removing its definition. -
Parallel sessions change class names — during this session, a parallel session renamed
.zt-reading--toolto.zt-reading--tool-detailin baseof.html. The 257 lines of CSS we merged from zt-tools.css became orphaned dead code targeting the old class. Always check git log for concurrent changes. -
Per-tool CSS is context-dependent — batch regex replacement of
color: #fffacross 50+ files would break buttons (text on colored bg), print stylesheets, and badge text. Each file needs manual review or agent-assisted migration with context rules.
Final State (After Full Session)¶
After the complete Phase 8 migration, the entire CSS layer is Zen-tokenised:
| Metric | Before | After |
|---|---|---|
var(--neon-*) consumers |
22 | 0 |
var(--glass-*) consumers |
10 | 0 |
backdrop-filter: blur() |
20 | 0 |
body.zen-migrated overrides |
9 blocks | 0 |
--neon-* token definitions |
4 | 0 (removed) |
--glass-* token definitions |
10 | 0 (removed) |
| Per-tool CSS legacy patterns | ~550 | 0 (523 migrated) |
| Semantic tokens | 3 (success/warning/error) | 5 (+info, +teal) |
zt-tools.css |
13.6KB | Deleted |
Guided .glass-* class names |
50+ refs | 0 (→ .surface-*) |
Tokens Added¶
--info:#60A5FA(light) /#93C5FD(dark) — references, docs, informational--teal:#14B8A6(light) /#5EEAD4(dark) — scenarios, instructions, processes
What Remains (Documented Exceptions)¶
rgba(0,0,0,*)in box-shadows — universally dark, no token needed- Hero-tint
color-mix()gradients — intentional per-section tinting system search-overrides.css— Pagefind third-party integration with!importantzt-reading.cssCSS fallback values (var(--token, #hex)) — these are progressive enhancement
Per-Tool CSS Migration Strategy¶
The 3-Tier Approach¶
Tier 1 — Shared files (do first, highest impact):
style.css, sections.css, zt-reading.css — loaded on every page. Fix these before anything else.
Tier 2 — Worst offenders (>20 issues):
guided.css, copilot-studio.css, start-here.css, demo-scripts.css, site-analytics.css — use a general-purpose agent with detailed rules per file.
Tier 3 — Remaining files (1-10 issues each): Batch in groups of 10-12 using parallel agents. Provide token reference + context rules. Each agent reads the file, applies changes, reports summary.
Agent Prompt Template for Per-Tool Migration¶
Migrate per-tool CSS files in [path] from legacy patterns to Zen tokens.
Token Reference: [list tokens]
Rules:
1. color: #fff → var(--text-primary) UNLESS text on colored bg → var(--accent-foreground)
2. color: #000 → var(--text-primary) UNLESS text on light bg → var(--bg-page)
3. rgba(255,255,255,0.0x) → var(--border) or var(--bg-elevated)
4. transition: all → specific properties ≤200ms ease-out
5. Hex matching tokens → use the token
6. rgba tints → color-mix(in srgb, var(--token) N%, transparent)
7. DO NOT touch rgba(0,0,0) in box-shadows
8. DO NOT touch CSS variable definitions
9. DO NOT touch print styles
Files: [list]
Key Lessons for Future Sessions¶
-
Audit first, migrate second. The explore agent audit found 52 refs in 30 seconds. Working without an audit risks missing refs in obscure selectors.
-
Rubber-duck before implementing. Caught the cascade order issue and
/start/protection gap before any code was written. -
Specificity > source order. When consolidating CSS files, increase specificity of override selectors instead of relying on file load order.
-
body.zen-migratedoverrides become dead code. Once all pages have the class, the scoped overrides can be folded into base rules. This is a one-way door — only do it when you're sure all pages are migrated. -
Test for absence, not presence. The
noNeon()helper catches legacy colors in ANY computed style. This is more robust than checking for specific Zen values (which might vary per mode). -
One Body, Two Organs. After Hugo changes, sync shared CSS to Guided immediately. Don't "do it later" — it creates drift.
-
Grep ALL consumers before removing a token definition. Removing
--border-glowfrom:rootsilently brokesections.cssandreadiness.css. Always:Select-String 'var(--token-name)' *.cssacross ALL CSS files before deleting a definition. -
Parallel agents for batch work. 3 agents processing 12 files each completed 139 changes in ~6 minutes. For repetitive pattern-matching migrations, this is 10x faster than sequential editing.
-
color-mix()replaces hardcodedrgba()tints. Instead ofrgba(239,68,68,0.1)usecolor-mix(in srgb, var(--error) 10%, transparent). This adapts to light/dark mode automatically.