🌸 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:
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¶
- Identify the CSS file:
static/css/{tool-name}.css - Check for per-tool variable systems: Look for
:root { --xx-*: ... }blocks - Run the 5-trap detection commands (see each trap above)
- Apply token replacements using the mapping table
- Check for JS color references:
grep "tool-accent\|#HEX" static/js/{tool-name}.js - Add
zen-migratedto the tool's body class inbaseof.html(if not already there) - Hugo build to verify no template errors
- Playwright screenshot in light mode + dark mode
- Visual inspection — can you read ALL text? Check: titles, labels, placeholders, buttons, cards, dropdowns
For Batch Migration¶
- Write a Node.js script with regex replacements (see
z4-migrate.cjs,z4-pass2.cjsetc.) - Run the script, check change count per file
- Hugo build — must be 0 errors
- 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
- Fix failures with targeted per-tool scripts
- 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¶
- Read the Zen Design System docs at
C:\ssClawy\learning-docs\docs\zen-system\: philosophy.md— The "why" behind every decision. Internalise the 5 principles.guardrails.md— Hard rules that MUST be followed. Contains the per-tool QA checklist.colours.md— Full token reference for light/dark.- This playbook — The 5 traps, token mapping, detection commands.
-
phases.md→ Phase 4b/4c — Current status and what's left. -
Start the Hugo dev server:
-
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¶
- Hugo build must pass:
pwsh C:\ssClawy\aguidetocloud-revamp\scripts\hugo-safe.ps1 - Bump
cache_versioninhugo.toml git pull --rebase && git pushto deploy- Post-deploy Playwright smoke test
- Update session journal
- 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:
- Blog pages — Already Zen (migrated in Z3), but check any blog-specific CSS
- Study guide reading rooms — Already Zen (Z3), verify no old tokens
- Landing pages (free-tools index, home) — Still on old system, will need same treatment
- New tools — MUST use Zen tokens from day one. Use
polisher.cssas template. - Any new CSS file — Never define
:rootvariable 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-migratedclass on body inbaseof.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 | 14px → var(--radius-md), 20px → var(--radius-lg) |
~80 |
| Near-white hex text | color: #eXXXXX/#fXXXXX → var(--text-primary) |
~60 |
| Tiny font sizes | 0.58/0.65/0.68rem → var(--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:
;, 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 ):
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¶
- Before: CSS-level grep audit (5 trap detection commands + hardcoded hex count)
- Script: Run batch fix with rubber-duck validated regex patterns
- Build: Hugo safe wrapper — must be 0 errors
- Visual: Playwright screenshots of top 5 worst tools (light + dark)
- Automated:
z5-audit.cjs— 53 tools × 2 modes contrast + backdrop + dropdown check - 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: #EC4899 → color: 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 #EC4899 → border: 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:
- Tools → baseof.html wraps with zt-reading--tool grid
- Study guides → cert-tracker/single.html uses zt-reading--cert grid
- Blog → blog/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"
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:
- Add
zen-migratedfor/licensing/in baseof.html - Create
licensing-nav.html— plans grouped by tier (Business, Enterprise, Education, etc.) - Create
licensing-companion.html— ROI Calculator CTA, blog posts about licensing, YouTube - Replace standalone wrapper with
zt-readinggrid inlicensing/list.html - Hero: "Licensing Room" title, subtitle, stat pills
- CSS: reuse
ft-herofromzt-reading.css, add licensing nav styles if needed