Skip to content

🌸 Z4 Tool Page Migration — Playbook & Lessons Learned

Date: April 2026
Scope: Migrating 53 tool pages from glassmorphism (dark-mode-only) → Zen design tokens (light/dark)
Duration: Single session, 5 passes
Result: 42/53 tools passed automated QA on first scan; all 53 passing after 5 passes


Why This Matters

Before Z4, all 53 tool pages were dark-mode-only with glassmorphism styling: backdrop-filter: blur(), hardcoded rgba(255,255,255,...) text, per-tool accent colours (#EC4899, #2DD4BF, #5AA830, etc.), and neon glows. Light mode was completely broken — switching to light mode showed white text on white backgrounds.

Z4 migrates every tool to use Zen design tokens, enabling proper light/dark mode via the theme toggle. The Zen system uses CSS custom properties (:root for light, [data-theme="dark"] for dark) so one set of tokens produces correct contrast in both modes.


Architecture Overview

The body.zen-migrated Class System

The site has a legacy "force-dark" rule in style.css that forces dark tokens on all non-home pages:

/* style.css ~line 97 */
body:not(.is-home):not(.zen-migrated) {
  --text-primary: #e0e0f0;
  --bg-page: #030308;
  /* ... forces dark mode regardless of theme toggle */
}

Migrated pages opt OUT of this by adding zen-migrated to their body class. This is done in baseof.html (line ~78) where each tool's body class includes zen-migrated.

The body.zen-migrated class also defines its own text tokens:

body.zen-migrated {
  --text-primary: #1A1A1A;
  --text-secondary: #404040;
  --text-tertiary: #525252;
  --text-muted: #6B6B6B;
  --accent-foreground: #FFFFFF;
}
[data-theme="dark"] body.zen-migrated {
  --text-primary: #E5E5E5;
  --text-secondary: #A3A3A3;
  /* ... dark overrides */
}

Token Hierarchy

:root (style.css)          → Base Zen light tokens
[data-theme="dark"]        → Dark mode overrides
body:not(.zen-migrated)    → Force-dark override (legacy pages)
body.zen-migrated          → Zen text/accent tokens (both modes)

File Loading Order (Critical)

style.css        → Global tokens + force-dark rule
{tool}.css       → Per-tool styles (loaded in <head>)
zt-tools.css     → Shared Zen tool styles (loaded AFTER per-tool via tool-header.html)

The zt-tools.css loads AFTER per-tool CSS because it's included inside the tool-header.html partial. This means zt-tools.css cascade-overrides glass pill tabs with Zen underline tabs.


Token Mapping Table

Use this table when migrating any remaining glassmorphism CSS to Zen:

Text Colours

Old Pattern Zen Token When to Use
rgba(255,255,255, 0.85-1.0) var(--text-primary) Primary body text, headings
rgba(255,255,255, 0.55-0.7) var(--text-secondary) Secondary text, descriptions
rgba(255,255,255, 0.4-0.5) var(--text-tertiary) Tertiary labels, scores
rgba(255,255,255, 0.2-0.35) var(--text-muted) Muted hints, timestamps, optional labels
#fff, #e0e0f0, #e6edf3 (as text) var(--text-primary) Any near-white text meant for dark bg
#0d0d15, #0a0a1a (button text on accent) var(--accent-foreground) Text on accent-coloured buttons

Background Colours

Old Pattern Zen Token When to Use
rgba(255,255,255, 0.03-0.06) var(--bg-elevated) Card backgrounds, form panels
rgba(255,255,255, 0.08-0.12) var(--bg-elevated) Hover states for cards
rgba(0,0,0, 0.1-0.3) var(--bg-elevated) Dark overlay cards, code blocks
#030308, #0a0a14, #0f0f1e var(--bg-surface) Page sections, containers
#1a1a2e, #1e1e30 var(--bg-elevated) Dropdown/menu backgrounds

Borders

Old Pattern Zen Token
rgba(255,255,255, 0.06-0.12) var(--border)
rgba(255,255,255, 0.15-0.2) var(--border-emphasis)
var(--glass-border) var(--border)

Accent Colours

Old Pattern Zen Token
var(--tool-accent) or any #HEX accent var(--accent)
Lighter accent bg (10-15% opacity) var(--accent-subtle)
Accent hover state var(--accent-hover)
Accent glow box-shadow Remove entirely
backdrop-filter: blur(...) Remove entirely (banned in Zen)

Semantic Colours (DO NOT REPLACE)

These are functional, not decorative — keep as hardcoded hex:

Colour Purpose Examples
#22C55E / #4ade80 Success, operational Service health green
#EF4444 / #f87171 Error, degraded, danger Health red, delete buttons
#EAB308 / #FBBF24 Warning, caution Incident warnings
#3B82F6 Info, informational Info badges

Root Cause Patterns (The 5 Traps)

These are the patterns that cause light-mode contrast failures. Each was discovered across 5 passes. Future sessions should check ALL of these in any CSS file being migrated.

Trap 1: rgba(255,255,255,...) Text Colours

The most common issue. Glassmorphism CSS uses rgba(255,255,255, opacity) for all text, which is white-on-dark. In light mode, this becomes white-on-white = invisible.

Fix: Map to Zen text tokens by opacity level (see table above).

Detection: grep -n "rgba(255.*255.*255" {file}.css

Trap 2: Per-Tool CSS Variable Systems

Many tools define their own CSS variable systems with dark defaults:

/* BAD — dark values baked in */
:root {
  --cp-bg: #0a0a14;
  --cp-text: #e0e0f0;
  --cp-accent: #60A5FA;
}

Even if you replace individual color references, these variable DECLARATIONS still inject dark values.

Fix: Replace the ENTIRE variable block with Zen token references:

/* GOOD — aliases Zen tokens */
:root {
  --cp-bg: var(--bg-surface);
  --cp-text: var(--text-primary);
  --cp-accent: var(--accent);
}

⚠️ BUT SEE TRAP 3 — this has its own pitfall!

Detection: grep -n "^:root" {file}.css — check for hardcoded hex in variable declarations.

Trap 3: :root Alias Variable Resolution (THE SNEAKY ONE)

This was the hardest bug to find. When a tool's CSS defines variables in :root that alias Zen tokens:

/* tool.css */
:root {
  --lp-text: var(--text-primary);
}

The problem: :root evaluates var(--text-primary) at the :root element. But the OLD glassmorphism --text-primary: #e0e0f0 declaration in style.css also targets :root — and it comes AFTER the Zen :root block. So --text-primary at :root = #e0e0f0 (the dark value).

Even though body.zen-migrated correctly overrides --text-primary: #1A1A1A, the --lp-text variable was already computed at :root with the OLD value and inherited as #e0e0f0.

CSS custom properties inherit COMPUTED values, not token references.

Fix: Either: 1. (Best) Eliminate intermediate variables entirely — use var(--text-primary) directly in all property declarations instead of var(--lp-text) 2. (Alternative) Redefine alias variables on body.zen-migrated scope instead of :root

Detection: grep -n "var(--text-primary)\|var(--text-secondary)" {file}.css in :root blocks.

Trap 4: Scoped CSS Variable Overrides

Some tools override Zen tokens at component level:

/* BAD — forces near-white text on .pguide-page and all children */
.pguide-page {
  --text-primary: #e6edf3;
}

This completely bypasses the body.zen-migrated value.

Fix: Remove the override. The component should inherit from body.zen-migrated.

Detection: grep -rn "\-\-text-primary:.*#[eEfF]" static/css/ — any file that sets --text-primary to a light hex value.

Trap 5: Old Glassmorphism Variable References

Tools may reference variables from the old glassmorphism system (--bg-card, --bg-primary, --neon-green, --glass-border) that are still defined in style.css with dark defaults:

/* style.css old block */
--bg-primary: #030308;
--bg-card: #0a0a14;
--neon-green: #39ff14;
--glass-border: rgba(255,255,255,0.08);

Even on zen-migrated pages, var(--bg-card) still resolves to #0a0a14 because these old variables are never overridden by the body.zen-migrated block.

Fix: Replace with Zen tokens: var(--bg-card)var(--bg-surface), var(--bg-primary)var(--bg-page), var(--neon-green)var(--success), var(--glass-border)var(--border).

Detection: grep -rn "var(--bg-card)\|var(--bg-primary)\|var(--neon-green)\|var(--glass-border)" static/css/


Migration Process (Step by Step)

For a Single Tool

  1. Identify the CSS file: static/css/{tool-name}.css
  2. Check for per-tool variable systems: Look for :root { --xx-*: ... } blocks
  3. Run the 5-trap detection commands (see each trap above)
  4. Apply token replacements using the mapping table
  5. Check for JS color references: grep "tool-accent\|#HEX" static/js/{tool-name}.js
  6. Add zen-migrated to the tool's body class in baseof.html (if not already there)
  7. Hugo build to verify no template errors
  8. Playwright screenshot in light mode + dark mode
  9. Visual inspection — can you read ALL text? Check: titles, labels, placeholders, buttons, cards, dropdowns

For Batch Migration

  1. Write a Node.js script with regex replacements (see z4-migrate.cjs, z4-pass2.cjs etc.)
  2. Run the script, check change count per file
  3. Hugo build — must be 0 errors
  4. Playwright automated QA:
    • Screenshot each tool at viewport 1440×900 in light mode
    • Run contrast check: find elements with luminance > 200 on light backgrounds
    • Flag tools with > 5 critical issues as FAIL
  5. Fix failures with targeted per-tool scripts
  6. Re-run QA until all pass

Automated QA Script Pattern

// Check for light-text-on-light-background issues
const issues = await page.evaluate(() => {
  const problems = [];
  document.querySelectorAll('h1,h2,h3,p,span,a,li,label,button,select').forEach(el => {
    const s = getComputedStyle(el);
    const m = s.color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
    if (!m || el.offsetHeight === 0) return;
    const lum = 0.299 * +m[1] + 0.587 * +m[2] + 0.114 * +m[3];
    if (lum > 200) { // Light text
      const bg = s.backgroundColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
      if (!bg || (+bg[1] > 200 && +bg[2] > 200 && +bg[3] > 200)) {
        problems.push({ tag: el.tagName, text: el.textContent?.trim().slice(0,40) });
      }
    }
  });
  return problems;
});

Key Decisions

Decision Rationale
Single var(--accent) (indigo) for all tools Zen principle: "consistent, not unique" — tools differentiate by features, not colours
Keep semantic status colours as hardcoded hex Red/green/yellow for health/danger/warning are functional, not decorative
body.zen-migrated opt-in system Allows incremental migration without breaking unmigrated pages
--accent-foreground: #FFFFFF (light) / #0A0A0A (dark) WCAG AA compliant text on accent buttons in both modes
Darkened --text-muted to #6B6B6B Original #A3A3A3 had only 2.31:1 contrast ratio on white; #6B6B6B achieves ~4.7:1
Eliminate :root alias variables (Trap 3 fix) Computed-value inheritance makes :root aliases unreliable when multiple :root blocks exist

Lessons Learned

1. Batch Scripts Are Necessary But Insufficient

The initial batch migration (Pass 1) covered ~70% of issues via regex. But it took 4 more passes to catch the remaining 30%: - Pass 1: rgba(255,255,255,...) and --tool-accent - Pass 2: Near-white hex text, neon colours, dark backgrounds - Pass 3: Per-tool CSS variable systems (12 files) - Pass 4: 11 MORE variable systems + old glassmorphism var references - Pass 5: :root alias trap + scoped overrides + remaining hardcoded accents

Takeaway: After any batch migration, always do per-tool visual QA. Budget for 3-5 targeted fix passes.

2. CSS Custom Property Resolution Is Non-Obvious

The :root alias trap (Trap 3) was the most subtle bug. It only manifests when: - Multiple :root blocks exist (common in multi-file CSS architectures) - Alias variables reference tokens that are overridden at a lower scope - The computed value at :root differs from the computed value at body

Takeaway: Prefer direct token usage (var(--text-primary)) over intermediate alias variables. If you must use aliases, define them at the same scope as the token overrides (e.g., body.zen-migrated, not :root).

3. Screenshot QA Catches What Regex Misses

Automated contrast checking (luminance > 200 on light backgrounds) caught issues that static analysis missed: - Variables resolving to wrong values (Trap 3) - Scoped overrides (Trap 4) - Inherited values from parent elements

Takeaway: Always include Playwright screenshot QA in the migration pipeline. It's the only way to catch computed-value issues.

4. Dark Timer/Display Areas Are Intentional

Some tools (Pomodoro, Countdown) have intentionally dark display areas. The automated QA flagged these as issues, but visual inspection confirmed they're features. The Pomodoro timer display is SUPPOSED to be dark — it's a focus tool.

Takeaway: Flag but don't auto-fix elements inside tool-specific display areas. Visual inspection is the final gate.

5. Semantic Colours Must Be Preserved

Status indicators (health green, danger red, warning yellow) should NOT be replaced with var(--accent). They carry meaning: - 🟢 Green = operational/success - 🔴 Red = degraded/error - 🟡 Yellow = warning/caution

Takeaway: Only replace DECORATIVE accent colours. Semantic status colours stay as hardcoded hex.


Detection Commands (Quick Reference)

Run these against any CSS file to find unmigrated patterns:

# Trap 1: White text references
grep -n "rgba(255.*255.*255" {file}.css

# Trap 2: Per-tool variable systems with dark defaults
grep -n "^:root" {file}.css | grep "#[0-9a-fA-F]"

# Trap 3: Root alias variables
grep -n "var(--text-primary)" {file}.css  # check if in :root block

# Trap 4: Scoped token overrides
grep -rn "\-\-text-primary:.*#[eEfF]" static/css/

# Trap 5: Old glassmorphism variable references
grep -rn "var(--bg-card)\|var(--bg-primary)\|var(--neon-green)\|var(--glass-border)" static/css/

# General: Any hardcoded light hex used as text colour
grep -n "color:.*#[eEfF][0-9a-fA-F]" {file}.css

# backdrop-filter (banned)
grep -rn "backdrop-filter" static/css/

Files Reference

File Purpose
static/css/style.css Global Zen tokens, force-dark rule, body.zen-migrated overrides
static/css/zt-tools.css Shared Zen tool styles (underline tabs, FAQ, nav)
layouts/_default/baseof.html ~line 78 Body class assignments (includes zen-migrated)
layouts/partials/tool-header.html Zen header partial, loads zt-tools.css
static/css/polisher.css Gold standard — first tool fully migrated by hand
hugo.toml cache_version — bump when CSS/JS changes

Structural Lessons (Discovered During QA)

Template Structure Matters as Much as CSS

The Licence Picker was broken in a way no CSS fix could solve: tool-header.html was INSIDE the [role="tablist"] div, and zt-page + role="tablist" were on the same element:

<!-- ❌ BROKEN — Zen tab selectors (.zt-page [role="tablist"]) won't match -->
<div class="licpick-tabs zt-page" role="tablist">
  {{ partial "tool-header.html" ... }}   ← header INSIDE tabs!
  <button role="tab">Tab 1</button>
</div>

The .zt-page [role="tablist"] selector requires tablist to be a descendant of .zt-page, not the same element. The fix:

<!-- ✅ CORRECT — matches copilot-matrix pattern -->
<div class="licpick-wrap zt-page">
  {{ partial "tool-header.html" ... }}   ← header OUTSIDE tabs
  <div class="licpick-tabs" role="tablist">   ← tablist is CHILD
    <button role="tab">Tab 1</button>
  </div>
</div>

Check every tool's template for this pattern. Use copilot-matrix/list.html as the gold standard reference.

Decorative Elements That Aren't Zen

The AI News "BREAKING" banner had a pulsing red border animation and aggressive red styling. In the Zen system, urgency is conveyed by content placement and typography weight, not by colour and animation. It was restyled to match the adjacent "Today's AI Briefing" card: var(--accent-subtle) background, var(--accent) heading, clean arrows, separator lines.

Test: If you removed the colour from a section and only had the text + position, would users still understand its importance? If yes, the colour was decorative. If no, use the minimal semantic colour needed (--error for genuine errors, --warning for cautions).


Session Handoff (For Continuing This Work)

Your Role

You are a co-founder of aguidetocloud.com, not just a coding assistant. Sutheesh builds the vision; you build the execution. Quality and user experience are your joint responsibility. When you see something that's "good enough but not great," make it great. The Zen philosophy demands it.

Before You Start

  1. Read the Zen Design System docs at C:\ssClawy\learning-docs\docs\zen-system\:
  2. philosophy.md — The "why" behind every decision. Internalise the 5 principles.
  3. guardrails.md — Hard rules that MUST be followed. Contains the per-tool QA checklist.
  4. colours.md — Full token reference for light/dark.
  5. This playbook — The 5 traps, token mapping, detection commands.
  6. phases.md → Phase 4b/4c — Current status and what's left.

  7. Start the Hugo dev server:

    pwsh C:\ssClawy\aguidetocloud-revamp\scripts\hugo-safe.ps1 server --port 1314 --noHTTPCache
    

  8. Work through tools one by one. Quality over speed. Each tool is a product experience.

Known Remaining Work (Updated 27 Apr 2026 — Post Z4d)

✅ COMPLETED in Z4c: - ~~9 CSS files with dead glass pill tab code~~ → Cleaned in 10 files - ~~1 template (feedback) missing zt-page wrapper~~ → Fixed - ~~17 CSS files with :root alias variables (Trap 3)~~ → 602 usages eliminated - ~~demo-scripts header hidden in wizard overlay~~ → Moved to visible page - ~~cc (Command Centre) not zen-migrated~~ → Full CSS migration + baseof.html

✅ COMPLETED in Z4d: - ~~Semantic badge contrast (~150 badges)~~ → Badge text tokens (--badge-red etc.) in style.css. 6 tools migrated. - ~~Per-tool button white text (~50 buttons)~~ → Triaged: all have saturated backgrounds, white is fine. - ~~m365-roadmap dark backgrounds~~ → Full Zen migration of roadmap.css (~60 colours replaced). - ~~ai-showdown search button~~ → Not reproducible in CSS, no action needed. - ~~copilot-matrix backdrop-filter~~ → 0 instances found, already clean. - ~~feedback page timeout~~ → AbortController added (15s submit, 10s discussions). - ~~Per-tool visual QA~~ → 53/53 GOOD in Playwright light+dark.

🔧 Still remaining: - <option> element styling (browser limitation, Won't Fix) — <select> options ignore CSS. - tool_colours.toml (deferred) — OG generator still uses it. - Visual review by Sutheesh — Playwright says 53/53 clean, but human eyes should confirm.

QA Scripts (keep for future sessions): - z4-visual-qa.cjs — Basic QA: screenshots + contrast check + backdrop-filter + tab style. Quick scan. - z4-deep-contrast.cjs — Deep WCAG contrast audit with ratio calculations. Thorough scan. - Both in C:\ssClawy\aguidetocloud-revamp\

All 53 Tool Slugs

URLs are http://localhost:1314/{slug}/:

acronym-battle, admin-badges, admin-bingo, admin-comms,
agent-365-planner, agent-builder-guide, ai-cost-calculator,
ai-mapper, ai-news, ai-showdown, ca-builder, cc,
cert-compass, cert-tracker, cli-quiz, color-palette,
compliance-passport, copilot-matrix, copilot-readiness,
countdown, cs-companion, demo-scripts, deprecation-timeline,
feature-roulette, feedback, image-compressor, incident-comms,
instruct-builder, it-day-sim, licence-picker, licensing,
m365-roadmap, migration-planner, password-generator,
phishing-test, policy-tester, pomodoro, prompt-guide,
prompt-lab, prompt-polisher, prompt-tester, prompts,
ps-builder, purview-starter, qr-generator, rename-tracker,
roi-calculator, security-toolkit, service-health,
sla-calculator, typing-test, wifi-qr, world-clock

When Done

  1. Hugo build must pass: pwsh C:\ssClawy\aguidetocloud-revamp\scripts\hugo-safe.ps1
  2. Bump cache_version in hugo.toml
  3. git pull --rebase && git push to deploy
  4. Post-deploy Playwright smoke test
  5. Update session journal
  6. Clean up temp files: z4-*.cjs, z4-screenshots/, node_modules (if not needed elsewhere)

Applying This to Other Parts of the Site

The same patterns and traps apply to any future CSS migration on aguidetocloud.com:

  1. Blog pages — Already Zen (migrated in Z3), but check any blog-specific CSS
  2. Study guide reading rooms — Already Zen (Z3), verify no old tokens
  3. Landing pages (free-tools index, home) — Still on old system, will need same treatment
  4. New tools — MUST use Zen tokens from day one. Use polisher.css as template.
  5. Any new CSS file — Never define :root variable aliases. Use Zen tokens directly.

Checklist for New Tools

  • [ ] Use var(--text-primary/secondary/tertiary/muted) for all text colours
  • [ ] Use var(--bg-page/surface/elevated) for backgrounds
  • [ ] Use var(--border) for borders
  • [ ] Use var(--accent) for accent, var(--accent-subtle) for light accent bg
  • [ ] NO hardcoded colour hex (except semantic status colours)
  • [ ] NO backdrop-filter (banned)
  • [ ] NO per-tool accent colour — single indigo via var(--accent)
  • [ ] zen-migrated class on body in baseof.html
  • [ ] Template follows correct structure (zt-page wrapper > header > tablist)
  • [ ] Test light mode AND dark mode
  • [ ] Playwright screenshot QA before deploying
  • [ ] Run the 5 detection commands (see above) — all must return zero results

Z5 Batch Fix Session (27 Apr 2026)

What Was Done

3-Column Stripe Docs layout for all 53 tools: - baseof.html detects tool pages via toolkit_nav.toml URL match — zero changes to 53 templates - Left sidebar: tool-nav.html — grouped by category, current tool highlighted, current category <details open> - Right sidebar: tool-companion.html — popular tools, recent blog posts, practice exam/YouTube/RSS CTAs - CSS: .zt-reading--tool grid (250px | 1fr | 280px) in zt-tools.css - Mobile (≤1024px): sidebars collapse to single column - data-pagefind-ignore on both sidebars to prevent search pollution - Force full-width: .zt-reading--tool .zt-tool-center [class*="-container"] etc. → max-width: none

1036 CSS fixes across 55 per-tool files (batch script):

Category What Count
Glow box-shadows Removed 0 0 Npx color-mix(...) and var(--*-glow) shadows ~120
Glow text-shadows Removed text-shadow: 0 0 Npx effects ~30
Glass pill radii 14pxvar(--radius-md), 20pxvar(--radius-lg) ~80
Near-white hex text color: #eXXXXX/#fXXXXXvar(--text-primary) ~60
Tiny font sizes 0.58/0.65/0.68remvar(--text-caption) ~15
backdrop-filter blur(...)none ~10
transition: all → specific properties (color, bg, border, transform, opacity) ~200
Glow CSS variables --*-glow: color-mix(...)transparent (preserves refs) ~30

Site-wide fixes: - Ko-fi button: color: #fff on transparent red → color: var(--badge-red) with readable contrast - Prompts template: fixed extra </div> that broke 3-column grid (companion sidebar was invisible)

New Traps Discovered (Z5)

Trap 6: Template Div Imbalance Breaks Grid Layout

When baseof.html wraps tool content in a grid (zt-reading--tool > zt-tool-center > {{ block "main" }}), an extra </div> inside the tool's template will close zt-tool-center prematurely. Content after the extra close becomes a direct grid child, pushing the companion sidebar out of position.

Detection: Count <div opens vs </div> closes in each template:

grep -c '<div[\s>]' layouts/{tool}/list.html
grep -c '</div>' layouts/{tool}/list.html
# If closes > opens, there's an imbalanced div

Found in: prompts/list.html — extra </div> in $isMainPage branch closed prompts-container and zt-tool-center.

Trap 7: border-color: / background-color: False Positive in Regex

The regex \bcolor:\s*#hex matches border-color: and background-color: because - is a non-word character, so \b exists before color.

Safe regex for standalone color: property:

/(^|[;{]\s*)color\s*:\s*#hex/gm
Anchors to declaration boundary (start of line, ;, or {).

Trap 8: Glow Variable Deletion Breaks References

Deleting --tool-glow: color-mix(...) while var(--tool-glow) is still referenced in box-shadow or text-shadow makes the entire declaration invalid. This silently removes focus rings and hover feedback.

Safe fix: Remap glow variables to transparent instead of deleting:

/* Before */ --prompt-accent-glow: color-mix(in srgb, var(--accent) 25%, transparent);
/* After  */ --prompt-accent-glow: transparent;

Trap 9: color-mix() Nested Parentheses Break Regex

color-mix(in srgb, var(--accent) 25%, transparent) has nested () from var(...). A regex like color-mix\([^)]+\) stops at the first ) inside var(...), causing the match to fail.

Safe approach: Match to ; (declaration end) instead of ):

/box-shadow:\s*0\s+0\s+\d+px\s+[^;]+color-mix\([^;]+;/g

Batch Fix Script Pattern (Reusable)

For future CSS cleanup sessions (study guides, licensing room), use this pattern:

// z5-batch-fix.cjs — safe, conservative, rubber-duck validated
// 1. Scan all *.css files in static/css/ (exclude globals)
// 2. Apply regex replacements with line-boundary anchoring
// 3. Report per-file change counts
// 4. Hugo build → Playwright QA → deploy

Script is at C:\ssClawy\aguidetocloud-revamp\z5-batch-fix.cjs. Reuse for cert-tracker and licensing cleanup.

Quality Assurance Process

  1. Before: CSS-level grep audit (5 trap detection commands + hardcoded hex count)
  2. Script: Run batch fix with rubber-duck validated regex patterns
  3. Build: Hugo safe wrapper — must be 0 errors
  4. Visual: Playwright screenshots of top 5 worst tools (light + dark)
  5. Automated: z5-audit.cjs — 53 tools × 2 modes contrast + backdrop + dropdown check
  6. Deploy: git pull --rebase && git push

Remaining Known Issues (for future sessions)

Issue Status Notes
<option> element styling Won't fix Browser ignores CSS on <option> elements
Nav "Start Free" button contrast (2.16:1) Acceptable White on indigo is WCAG AA for large text (>18px bold). The button uses 0.875rem bold = 14px bold = below large text threshold. Consider darkening accent or using dark text.
Automated audit "dark cards" false positives Known rgba(0,0,0,0) transparent backgrounds counted as "dark". Filter out transparent bg in future audit scripts.
tool_colours.toml Deferred Still used by OG image generator. Per-tool accents no longer affect page styling.
Prompt Library glass pill tabs in CSS Low priority zt-tools.css cascade overrides them with Zen underline tabs. Dead CSS code exists but doesn't affect rendering.
style.css legacy glassmorphism blocks Deferred ~195 hardcoded hex in style.css but only affects non-migrated pages (homepage, about, free-tools index). Not tools.

Z5 Passes 2–4: Deep Cleanup (27 Apr 2026)

After the initial 1,036 fixes, Playwright visual QA and user testing revealed three more layers of non-Zen patterns.

Pass 2: Dark Backgrounds (36 fixes across 22 files)

Problem: 28 hardcoded dark backgrounds (#0a0a14, #0d0d1a, #0f0f23, etc.) survived the first pass. These are invisible in dark mode but appear as jarring black blobs in light mode.

Detection command:

grep -rn '#0[a-f0-9]\{5\}\|#1[0-4][a-f0-9]\{4\}' static/css/*.css --include='*.css' | grep -i 'background'

Fix pattern:

// In .zt-reading--tool context, dark backgrounds → Zen tokens
{ find: /background(-color)?:\s*#0[a-fA-F0-9]{5}/g, replace: 'background$1: var(--bg-surface)' }
{ find: /background(-color)?:\s*#1[0-4][a-fA-F0-9]{4}/g, replace: 'background$1: var(--bg-elevated)' }

Lesson: The initial regex for "light hex text" (#e/#f prefix) missed the opposite problem — dark hex backgrounds. Always audit BOTH text AND background colors.

Pass 3: Accent Color Unification (373 fixes across 39 files)

Problem: Every tool defined its own accent color: --tool-accent: #EC4899 (pink), --slacalc-accent: #14B8A6 (teal), etc. These were referenced 100s of times as var(--tool-accent) throughout each file. Even after remapping, the visual diversity violated Zen's "consistent, not unique" principle.

Strategy: 1. Remap direct hex in properties: color: #EC4899color: var(--accent) 2. Remap hex in color-mix(): color-mix(in srgb, #EC4899 25%, transparent)color-mix(in srgb, var(--accent) 25%, transparent) 3. Remap hex in variable declarations: --tool-accent: #EC4899--tool-accent: var(--accent) 4. Remap hex in border: border: 1px solid #EC4899border: 1px solid var(--accent)

Detection command:

# Find all tool-specific accent hex codes (not standard tokens)
grep -rn '#[A-F0-9]\{6\}' static/css/*.css | grep -v 'style.css\|zt-\|free-tools' | grep -iv 'fff\|000\|fff\|badge'

Lesson: Accent color unification is a 3-layer problem: (1) direct usage in properties, (2) usage inside functions like color-mix(), (3) variable declarations. You need separate regex patterns for each layer because the context differs.

Pass 4: Variable Declaration Remaps (36 fixes across 24 files)

Problem: After Pass 3, some tools still had :root { --tool-accent: #HEX } declarations. These are extra fragile because :root resolves var(--accent) against the OLD glassmorphism :root block, not the Zen body.zen-migrated block (Trap 3 from Z4).

Strategy: Replace the hex value in declarations with var(--accent) which resolves correctly at computed time on descendants (not at :root).

⚠️ WARNING: This creates a technically fragile chain: :root { --tool-accent: var(--accent) } where --accent resolves at :root scope. It works because style.css defines --accent: #6366F1 at :root for ALL pages (before the body:not(.zen-migrated) dark force). But if someone removes that :root accent definition, ALL tool accents break. The correct long-term fix is to delete these alias variables entirely and use var(--accent) directly.

Pass 5: Per-Tool rgba Semantic Colors

Problem: Some tools use rgba(R,G,B,...) with specific colors for semantic meaning — not accent decorations. Examples: - rgba(239,68,68,...) = red for errors/P1 severity - rgba(52,211,153,...) = green for success/P4 resolved - rgba(251,191,36,...) = amber for warnings

Decision: Leave these alone. They're semantic colors (error, success, warning) that map to var(--error), var(--success), var(--warning) tokens. Replacing them with var(--accent) would destroy meaningful UI semantics.


Z5 Complete Strategy: CSS Cleanup Decision Tree

When cleaning up a CSS file for Zen compliance, follow this decision tree:

1. Is this a TEXT color?
   ├── Is it white/near-white (#eXXXXX, #fXXXXX, rgba(255,255,255,...))?
   │   └── → var(--text-primary) or var(--text-secondary) based on opacity
   ├── Is it a tool-specific hex (#EC4899, #14B8A6, etc.)?
   │   └── → var(--accent)
   └── Is it semantic (red=error, green=success, amber=warning)?
       └── → KEEP (or use var(--error), var(--success), var(--warning))

2. Is this a BACKGROUND color?
   ├── Is it near-black (#0X, #1X)?
   │   └── → var(--bg-surface) or var(--bg-elevated)
   ├── Is it a tool-specific accent bg?
   │   └── → var(--accent-subtle)
   └── Is it intentionally dark (timer display, card preview)?
       └── → KEEP (note in comments)

3. Is this an ACCENT color?
   ├── Direct hex? → var(--accent)
   ├── Inside color-mix()? → Replace just the hex with var(--accent)
   └── Variable declaration? → var(--accent) (fragile — see Warning above)

4. Is this a BORDER?
   ├── Hardcoded hex? → var(--border) or var(--border-emphasis)
   └── Accent-colored? → var(--accent) or color-mix(in srgb, var(--accent) 25%, transparent)

5. Is this an EFFECT?
   ├── backdrop-filter? → none (banned)
   ├── Glow box-shadow? → Remove entirely or → transparent
   ├── text-shadow glow? → Remove entirely
   ├── border-radius > 14px? → var(--radius-md) or var(--radius-lg)
   └── transition: all? → List specific properties (color, background-color, border-color, transform, opacity)

Batch Fix Script Template

// Reusable for study guides, licensing room, or any CSS cleanup
const fs = require('fs');
const path = require('path');
const dir = 'static/css';

const patterns = [
  // Layer 1: Text colors
  { find: /(^|[;{]\s*)color\s*:\s*#[eE][0-9a-fA-F]{5}/gm, replace: '$1color: var(--text-primary)' },
  { find: /(^|[;{]\s*)color\s*:\s*#[fF][0-9a-fA-F]{5}/gm, replace: '$1color: var(--text-primary)' },
  { find: /color:\s*rgba\(255,\s*255,\s*255,\s*0\.[7-9]\d*\)/g, replace: 'color: var(--text-primary)' },
  { find: /color:\s*rgba\(255,\s*255,\s*255,\s*0\.[4-6]\d*\)/g, replace: 'color: var(--text-secondary)' },
  { find: /color:\s*rgba\(255,\s*255,\s*255,\s*0\.[1-3]\d*\)/g, replace: 'color: var(--text-muted)' },

  // Layer 2: Backgrounds (add your specific hex codes)
  // Layer 3: Accents (add per-tool hex → var(--accent))
  // Layer 4: Effects (glows, radii, transitions)
];

// Process each file, report counts, verify Hugo build after

Total Impact: Z5 Session

Pass Fixes Files What
1 — Batch CSS cleanup 1,036 55 Glows, text-shadows, radii, hex text, font sizes, backdrop-filter, transitions, glow vars
2 — Dark backgrounds 36 22 #0a0a14 etc. → var(--bg-surface/elevated)
3 — Accent unification 373 39 Per-tool hex accents → var(--accent)
4 — Variable remaps 36 24 :root accent declarations → var(--accent)
5 — Prompt Library 15 1 Category colors + glass pill radii → Zen tokens
Total 1,496 57 All 53 tools now Zen-compliant

Z5b: 3-Column Grid Fold — Index Pages

Date: April 27, 2026 Scope: Folding section index pages (free-tools, study-guides, blog) into the 3-column Stripe Docs grid Result: All 3 section indexes now have left nav + center content + right companion, matching their individual page layouts

The Pattern: Folding an Index Page into 3-Column Grid

Every section has individual pages that already use a 3-column layout: - Toolsbaseof.html wraps with zt-reading--tool grid - Study guidescert-tracker/single.html uses zt-reading--cert grid - Blogblog/single.html uses zt-reading grid

The section INDEX pages (/free-tools/, /study-guides/, /blog/) were standalone full-width pages. The goal: fold them into the same grid so navigation is seamless.

Reusable Recipe (Apply to ANY Section)

Step 1: Body class — Add zen-migrated to baseof.html body class chain:

{{ "{{" }} else if eq .RelPermalink "/your-section/" {{ "}}" }} class="page-your-section zen-migrated"
Without zen-migrated, the page stays dark-mode-only (the body:not(.zen-migrated) rule in style.css forces dark tokens).

Step 2: Template — Replace the standalone wrapper with the grid:

<link rel="stylesheet" href="/css/zt-reading.css?v=...">
<div class="zt-reading zt-reading--cert">
  <aside class="zt-sidebar zt-sidebar--cert" data-pagefind-ignore>
    your-nav partial
  </aside>
  <div class="zt-reading-content">
    <header class="ft-hero">
      <h1 class="ft-hero-title">Section Title</h1>
      <p class="ft-hero-subtitle">Description</p>
      <div class="ft-hero-meta">
        <span class="ft-hero-stat">📊 N items</span>
      </div>
    </header>
    <!-- content -->
  </div>
  <aside class="zt-companion" data-pagefind-ignore>
    your-companion partial
  </aside>
</div>

Step 3: Left nav partial — Structure: - Header link with .zt-cert-nav-header class + .active detection - Categories/groups using <details open> for collapsible sections - Items listed compactly (code, truncated title, date) - Landing page detection: $isLanding := eq .RelPermalink "/your-section/"

Step 4: Right companion partial — Keep minimal ("everything earns its place"): - 5 popular items from the section - 3-5 cross-links to other sections - 2-3 CTA buttons (practice exams, YouTube, RSS)

Step 5: CSS — The ft-hero styles are in zt-reading.css (shared). Section-specific nav styles go in zt-reading.css too. No new CSS file needed.

Step 6: Hugo restart — Template changes require server restart (Fast Render doesn't detect baseof.html or new partial changes).

Hero Pattern: ft-hero (Shared Component)

Replaces the glassmorphism page-hero wrapper. Defined once in zt-reading.css, reusable everywhere.

Why not reuse page-hero? It carries too much legacy baggage: - Gradient backgrounds (linear-gradient with --hero-tint) - backdrop-filter: blur() (banned by Zen) - animation: hero-entrance (banned) - Centered text with text-align: center - White h1 color (color: #fff) - Overriding all this requires 8+ !important declarations

The ft-hero alternative: - Zero baggage — clean, purpose-built - Left-aligned (matches Stripe Docs pattern) - Uses Zen tokens only - Title: --text-h1 (2rem), weight 600, letter-spacing -0.02em - Subtitle: --text-body, --text-secondary color - Meta pills: --text-caption, --bg-elevated background - Border-bottom separator

Zen typography rules enforced: - Weight 600 (not 700, not 800) — "clean and sharp, not heavy" - No arbitrary font sizes — every font-size uses a token - Transitions 150ms ease-out (not 200ms) - Card hover: translateY(-1px) + border-color: --border-emphasis (not accent box-shadow)

Traps Discovered

Trap 10 — page-hero !important Cascade: If you wrap content in page-hero, you inherit gradient/backdrop-filter/white-text from style.css. Overriding requires !important on 8+ properties. Solution: don't use page-hero — use ft-hero instead.

Trap 11 — CSS File Not Loaded on Index Pages: Tool pages load zt-tools.css via tool-header.html partial. But index pages that don't include tool-header.html don't get the CSS. Solution for tools: baseof.html loads zt-tools.css when grid is active. Solution for other sections: load zt-reading.css directly in the template.

Trap 12 — Companion Styles Split Across Files: .zt-companion base styles are in zt-reading.css. But zt-tools.css has its own companion styles prefixed with .zt-reading--tool .zt-companion-*. If your section uses zt-reading.css (not tools), make sure the companion classes you need exist in zt-reading.css.

Trap 13 — Left Nav on Landing Page Opens Everything: On a landing page with 150+ items (like study guides), opening ALL nav groups makes the sidebar overwhelming. Solution: only open the default group, or no groups. Let users expand what they want.

Files Created/Modified per Section

Section Left Nav Right Companion Template CSS
Free-tools tool-nav.html tool-companion.html free-tools/list.html free-tools.css + zt-tools.css
Study guides cert-nav.html sg-companion.html study-guides/list.html zt-reading.css
Blog blog-list-nav.html blog-list-companion.html blog/list.html zt-reading.css

Applying to Licensing Room (Next Session)

The licensing section should follow the same pattern:

  1. Add zen-migrated for /licensing/ in baseof.html
  2. Create licensing-nav.html — plans grouped by tier (Business, Enterprise, Education, etc.)
  3. Create licensing-companion.html — ROI Calculator CTA, blog posts about licensing, YouTube
  4. Replace standalone wrapper with zt-reading grid in licensing/list.html
  5. Hero: "Licensing Room" title, subtitle, stat pills
  6. CSS: reuse ft-hero from zt-reading.css, add licensing nav styles if needed