Skip to content

🌸 Polish Phase 8 — CSS Consolidation & Legacy Cleanup Playbook

Date: 28 April 2026
Scope: Eliminate ALL remaining glass/neon/backdrop-filter from style.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.aivar(--accent) (indigo) - .card-tag.certvar(--success) (green)
- .card-tag.legacyvar(--warning) (amber) - .card-tag.cloudvar(--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: - h2var(--accent) (still distinct, but within the system) - h3var(--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)

  1. Tool page — grid, tabs, BTT, FAB, pills
  2. Blog index — timeline search, dots, tags, CTA, pager
  3. Blog post — headings, blockquotes, progress bar
  4. Video section — card tags, page hero
  5. Study guide — back-nav link
  6. 404 page — cards, links
  7. Guided explore — reading room grid, no tool bleed
  8. Mobile 375px — tags, search, sidebar collapse

Pre-deploy Checklist

  • [ ] Hugo build: zero errors
  • [ ] grep 'var(--neon-' style.css returns 0 matches
  • [ ] grep 'var(--glass-' style.css returns 0 matches
  • [ ] grep 'backdrop-filter' style.css returns 0 matches
  • [ ] Playwright: all pages × both modes × mobile
  • [ ] Guided zt-reading.css copied and hashes match

Rubber-Duck Catches (Worth Remembering)

  1. zt-reading.css didn't load on tool pages — the conditional in baseof.html only covered blog/study-guides/licensing/video. Adding $isToolPage required moving the variable definition from <body> to <head>.

  2. /start/ page not zen-migrated — folding overrides into base changes styling on non-migrated pages. Accepted as intentional (BTT/FAB improvement, not regression).

  3. :focus-visible still used neon-cyan — easy to miss since it only shows on keyboard navigation. Always grep for the token, don't rely on visual testing.

  4. Removing token definitions breaks downstream files — deleting --border-glow and --shadow-neon from :root caused regressions in sections.css and readiness.css which still referenced them. Always grep ALL CSS files for a token before removing its definition.

  5. Parallel sessions change class names — during this session, a parallel session renamed .zt-reading--tool to .zt-reading--tool-detail in 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.

  6. Per-tool CSS is context-dependent — batch regex replacement of color: #fff across 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 !important
  • zt-reading.css CSS 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

  1. Audit first, migrate second. The explore agent audit found 52 refs in 30 seconds. Working without an audit risks missing refs in obscure selectors.

  2. Rubber-duck before implementing. Caught the cascade order issue and /start/ protection gap before any code was written.

  3. Specificity > source order. When consolidating CSS files, increase specificity of override selectors instead of relying on file load order.

  4. body.zen-migrated overrides 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.

  5. 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).

  6. One Body, Two Organs. After Hugo changes, sync shared CSS to Guided immediately. Don't "do it later" — it creates drift.

  7. Grep ALL consumers before removing a token definition. Removing --border-glow from :root silently broke sections.css and readiness.css. Always: Select-String 'var(--token-name)' *.css across ALL CSS files before deleting a definition.

  8. 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.

  9. color-mix() replaces hardcoded rgba() tints. Instead of rgba(239,68,68,0.1) use color-mix(in srgb, var(--error) 10%, transparent). This adapts to light/dark mode automatically.