🚧 Guardrails¶
"A garden without boundaries becomes a wilderness."
These rules are non-negotiable. They exist to prevent the site from drifting back into visual chaos. Every Copilot CLI session that touches aguidetocloud.com must respect these guardrails.
🔴 Hard Rules (NEVER Break)¶
1. No Hardcoded Colours¶
Every color, background, border-color, and box-shadow colour must use a CSS custom property from the Zen palette.
/* ❌ FORBIDDEN */
color: #3B82F6;
background: rgba(255, 102, 255, 0.7);
border: 1px solid rgba(255, 255, 255, 0.08);
/* ✅ CORRECT */
color: var(--accent);
background: var(--bg-surface);
border: 1px solid var(--border);
Exception: Third-party embeds (YouTube iframes, etc.) that we can't control.
2. No New Accent Colours¶
The site has one accent colour: indigo (--accent). No component, tool, or page gets its own unique colour.
If you think you need a new colour, you're wrong. Use:
- --accent for interactive/important elements
- --success, --warning, --error for semantic meaning
- Neutral tokens for everything else
To add a new semantic colour (e.g., --info): document the justification in this file and get explicit approval.
3. No Arbitrary Values¶
Every spacing, font-size, and border-radius must come from the design token scale.
/* ❌ FORBIDDEN */
padding: 23px;
font-size: 0.82rem;
border-radius: 18px;
margin-top: 1.3rem;
/* ✅ CORRECT */
padding: var(--space-6); /* 24px */
font-size: var(--text-sm); /* 14px */
border-radius: var(--radius-md); /* 10px */
margin-top: var(--space-8); /* 32px */
4. No Decorative Elements¶
Before adding ANY visual element, pass the Earns Its Place test:
"If I remove this, does the user lose functionality or essential information?"
- If no → don't add it.
- Decorative gradients, glows, watermarks, shimmer animations → all forbidden.
5. Both Modes Must Work¶
Every change must be tested in both light and dark mode. If it looks wrong in either, it's not done.
6. No backdrop-filter¶
Permanently banned. It's GPU-heavy, causes scroll jank, and is purely decorative.
7. No Neon or Glow¶
These specific patterns are permanently banned:
- text-shadow: 0 0 Npx <color> (text glow)
- box-shadow: 0 0 Npx <neon-color> (neon glow)
- Any colour from the old neon set: #66ffff, #ff66ff, #39ff14, #ffe600
8. Performance Budget¶
- Transitions: 150-200ms max,
ease-outonly - Only animate:
transform,opacity,color,background-color,border-color,box-shadow - Never animate:
width,height,padding,margin,top,left - No JS animation libraries (no GSAP, no Framer Motion)
- No scroll-hijacking
9. Diagrams Are Light Canvases¶
All Mermaid diagrams render as light islands on the dark page — white rounded nodes, dark grey text, thin grey connectors, on a #FAFAFA dot-grid canvas. No inline style/classDef in markdown — the theme controls everything. No emojis in node labels. See diagrams.md for full spec.
- ❌
style A fill:#... stroke:#... color:#...— banned - ❌
classDefdefinitions — banned - ❌ Emojis in
["🔐 Security"]— text only - ❌ Neon colours anywhere in diagrams
- ✅ White nodes,
#E5E5E5borders,#C4C8D0connectors, rounded corners
10. Blog Legacy Components Use Zen Overrides¶
Blog content uses legacy HTML classes (.living-doc-banner, .prompt-cards, .cowork-scenario, etc.) that were styled with glassmorphism/backdrop-filter/hardcoded rgba colours. These are overridden in zt-reading.css with Zen tokens. When adding new blog content classes, always override in zt-reading.css rather than modifying the legacy definitions in style.css (which tool pages may depend on).
- ❌
backdrop-filteron any blog reading element - ❌
rgba(255, 255, 255, 0.9)text colour (invisible in light mode) - ❌ Dark gradient backgrounds
linear-gradient(135deg, rgba(42, 26, 0, 0.7)...) - ✅
var(--bg-elevated)backgrounds withvar(--border)borders - ✅ Accent left-border for callouts, warning left-border for banners
11. CSS Custom Property Cascade¶
When using JS setProperty() to override a CSS variable, the variable must be defined on an ancestor, not on the element that consumes it. If the consuming element redefines the variable, its local definition wins over the inherited value.
/* ❌ BROKEN — JS sets --size on .parent, but .child redefines it */
.child { --size: 16px; font-size: var(--size); }
/* ✅ CORRECT — variable defined on parent, child just consumes it */
.parent { --size: 16px; }
.child { font-size: var(--size); }
12. 🔴 One Body, Two Organs — Cross-Platform Parity (Hugo ↔ Astro)¶
The main site (Hugo) and the Guided platform (Astro) are one product with one visual identity. A user navigating from a blog post to a practice exam must never feel they've left the site. This is a hard rule, not a nice-to-have.
Shared elements that MUST stay identical: - Nav bar — same layout, links, logo, theme toggle, mobile drawer - Footer — same structure, links, copyright - Font — Inter on both platforms - Colour tokens — same Zen palette values - Theme toggle behavior — same icons, same switching logic - Transitions — same ≤200ms ease-out timing
The rule:
Any visual change to a shared element on ONE platform MUST be applied to the OTHER platform in the SAME session. No exceptions. No "I'll sync it later."
Enforcement checklist (mandatory before push): 1. Does this change touch nav, footer, font, colours, or theme toggle? 2. If YES → make the identical change in the other platform's codebase 3. Both builds must pass (Hugo + Astro) 4. Commit messages must cross-reference: "Matches Guided commit X" / "Matches Hugo commit X"
Why this exists:
- 28 Apr 2026: Logo text + link spacing changed in Astro but not Hugo → user noticed the inconsistency immediately
- 28 Apr 2026: Phase 7 CSS cleanup removed .nav-drawer { display: none } → mobile nav links leaked onto all desktop pages
- The two codebases use different tech (Hugo templates + vanilla CSS vs Astro + Tailwind) but the output must be indistinguishable
13. 🔴 CSS Cleanup Sessions Need Playwright Gate¶
After any session that removes CSS rules (cleanup, dead code purge, consolidation), a mandatory Playwright check must run on desktop viewport (1440×900) BEFORE pushing:
- Screenshot homepage + one tool page + one reading room
- Verify: nav renders correctly, no text leaks, sidebars hidden on mobile viewport
- If any visual regression → revert and investigate
Why this exists:
- 28 Apr 2026: Phase 7 removed .nav-drawer { display: none } — nav links appeared as plain text on all desktop pages. A 30-second Playwright check would have caught it.
14. 🔴 cache_version Must Accompany CSS/JS Changes¶
Every commit that modifies CSS or JS files must also bump cache_version in hugo.toml. Browser cache serves stale content at the same URL — query string busting (?v=...) only works if the version changes.
- ❌ Commit CSS changes without bumping
cache_version - ✅ Same commit: CSS change +
cache_versionbump
15. No document.write() in Templates¶
Third-party scripts that use document.write() or document.writeln() are banned from templates. They inject HTML at parse time, which breaks DOM when inserting block elements inside inline elements (especially in minified HTML).
- ❌
<script>widget.draw()</script>(uses document.write internally) - ✅ Static HTML replica of the widget output with inline styles
Token name mapping (reference):
| Zen (Hugo) | Guided (Astro) |
|---|---|
--text-primary |
--color-text |
--text-secondary |
--color-text-body |
--text-tertiary |
--color-text-secondary |
--text-muted |
--color-text-muted |
--bg-page |
--color-base |
--bg-surface |
--color-surface |
--bg-elevated |
--color-surface-raised |
--border |
--color-border |
--border-emphasis |
--color-border-strong |
--accent |
--color-accent |
--accent-hover |
--color-accent-hover |
--accent-subtle |
--color-accent-subtle |
🟡 Soft Rules (Bend With Justification)¶
9. One Layout Per Page Type¶
All tool pages use the same layout. All blog posts use the same layout. All study guides use the same layout. Exceptions require documented justification.
10. Minimal JS¶
If CSS can do it, don't use JS. CSS :target, :checked, details/summary, scroll-snap — all preferred over JS equivalents.
Exception: Complex interactions like search, quiz logic, tool functionality.
11. Content Max Width¶
Reading content stays within --max-reading (720px). Tool content within --max-content (1080px). Exceptions for data-heavy tools (tables, dashboards) that genuinely need more width.
12. Tags Are Monochrome¶
All tags/badges use the same neutral style. No coloured tags to differentiate categories. Text differentiates, not colour.
Exception: Semantic status tags (Pass/Fail, Active/Expired) can use --success/--error.
🔵 Growth Rules (How to Extend the System)¶
Adding a New Component¶
- Check if an existing component can be reused or composed
- Design it using only existing tokens
- Ensure it works in light + dark mode
- Add it to
components.mdwith CSS - It must look like it's always been part of the system
Adding a New Token¶
- Document why the existing scale doesn't cover the need
- The new value must fit the existing scale pattern (e.g., spacing follows 8px grid)
- Update the relevant doc (colours.md, typography.md, or spacing.md)
- Update
style.cssroot variables
Adding a New Page Type¶
- Use the closest existing layout pattern
- If truly new, document the layout in
spacing.md - Use only existing components — no one-off styling
Pre-Commit Checklist¶
Before every git push that touches CSS, HTML templates, or JS:
- [ ] No hardcoded colours — grep for hex codes and rgba() in changed files
- [ ] No arbitrary spacing — grep for px/rem values not from the token scale
- [ ] Both modes work — test light AND dark mode visually
- [ ] Performance — no
backdrop-filter, no heavy animations, no layout-triggering transitions - [ ] Consistency — new elements look like existing elements
- [ ] Mobile — tested at 375px viewport minimum
- [ ] Cross-platform parity — if nav/footer/tokens changed, apply same change to BOTH Hugo and Astro
- [ ] Hugo build — zero errors
- [ ] Astro build — zero errors (if Guided touched)
Automated Checks (Future)¶
When CI/CD is set up, add: - CSS linting for hardcoded colour values - Lighthouse performance budget (>90 performance score) - Visual regression tests for light/dark mode
Session Guardrail Prompt¶
Paste this into any Copilot CLI session that will modify aguidetocloud.com:
⚠️ Zen Design System Active. This site follows the Zen Design System documented at learn.aguidetocloud.com → Playground → Zen Design System. All CSS must use design tokens (no hardcoded colours/spacing). One accent colour (indigo). No decorative elements. Both light and dark mode must work. No backdrop-filter. Transitions ≤200ms. See guardrails doc for full rules.
Violation Log¶
Track any guardrail violations here so patterns can be identified and addressed:
| Date | Violation | File | Resolution |
|---|---|---|---|
| 27 Apr 2026 | :root alias variables (--lp-text: var(--text-primary)) resolved against OLD glassmorphism tokens, not Zen tokens |
licence-picker.css | Eliminated all intermediate variables; use var(--text-primary) directly (Trap 3) |
| 27 Apr 2026 | Scoped --text-primary: #e6edf3 override forced near-white text |
prompt-guide.css | Removed scoped override (Trap 4) |
| 27 Apr 2026 | tool-header.html placed INSIDE [role="tablist"] div; zt-page + tablist on same element broke Zen tab selectors |
licence-picker/list.html | Restructured: header outside tabs, zt-page wrapper around everything |
| 27 Apr 2026 | 40+ rgba(255,255,255,...) text references survived 4 batch passes |
instruct-builder.css | Manual targeted fix in pass 5 (86 changes) |
| 27 Apr 2026 | AI News breaking banner used hardcoded red (#DC2626) with pulse animation | ainews.css | Restyled to match briefing card pattern (accent-subtle bg, Zen tokens) |
| 27 Apr 2026 | 17 CSS files had :root alias variables (Trap 3) — batch passes missed them because the variables LOOKED correct (var(--text-primary)) but resolved against legacy :root --text-primary: #e0e0f0 |
17 files (see Z4c findings) | Pass 7: eliminated 602 alias var usages, 58 declarations removed |
| 27 Apr 2026 | Missing --text-tertiary dark mode override in body.zen-migrated — caused 100+ dark mode contrast failures |
style.css | Added --text-tertiary: #A3A3A3 to [data-theme="dark"] body.zen-migrated |
| 27 Apr 2026 | Global floating elements (.global-btt, .feedback-fab, .bookmark-cta-inner) still used glassmorphism |
style.css | Added body.zen-migrated scoped overrides with Zen tokens |
| 27 Apr 2026 | <select> and <option> elements inherit browser defaults, not CSS tokens |
style.css | Added global body.zen-migrated select styling; <option> elements remain browser-controlled (limitation) |
| 27 Apr 2026 | .tool-eco-feedback shared component used rgba(255,255,255,...) text |
style.css | Replaced with var(--text-muted) / var(--text-tertiary) |
| 27 Apr 2026 | licensing.css had 9 instances of color: #f1f5f9 (Tailwind slate-100) |
licensing.css | Replaced with var(--text-primary) |
| 27 Apr 2026 | command-centre.css (cc) was never added to zen-migrated class list |
baseof.html + command-centre.css | Full Zen migration of CSS + added to baseof.html |
| 27 Apr 2026 | roadmap.css had ~60 hardcoded dark "Amber Command" palette colours in light mode | roadmap.css | Full Zen migration: all backgrounds, text, borders, hero replaced with tokens |
| 27 Apr 2026 | Badge text on tinted backgrounds used light/pastel colours (#FBBF24, #34D399, #f87171) failing WCAG in light mode | 6 tool CSS files | Created badge text tokens (--badge-red etc.) that flip between modes |
| 27 Apr 2026 | var(--text-primary) on JS-injected saturated backgrounds flips between modes but badge bg is fixed |
service-health.css | Use fixed #fff for opaque status backgrounds (safe on all status colours) |
| 27 Apr 2026 | feedback.js fetch calls had no timeout — page hung if API was down |
feedback.js | Added AbortController with 15s/10s timeouts |
🔬 Z4c Deep Contrast Audit Findings (27 Apr 2026)¶
Summary¶
After 7 passes of CSS migration (800+ changes in Z4b, 700+ changes in Z4c), a WCAG-based deep contrast audit was run using Playwright against all 53 tools. The audit checks every visible text element's contrast ratio against its effective background in both light and dark mode.
Trap 3 Was the Biggest Issue¶
17 CSS files had :root alias variables that looked correct but resolved wrong:
/* LOOKS correct — it references the Zen token! */
:root {
--cp-text: var(--text-primary);
}
/* BUT at :root level, --text-primary resolves to #e0e0f0 (legacy)
because the legacy :root block comes AFTER the Zen :root block.
body.zen-migrated overrides --text-primary to #1A1A1A,
but --cp-text was already computed at :root as #e0e0f0. */
Files fixed: ai-mapper, ca-builder, cert-tracker, color-palette, copilot-matrix, countdown, deptime, feedback, licence-picker, pomodoro, prompt-guide, ps-builder, service-health, showdown, world-clock
The rule: NEVER define alias variables at :root that reference Zen tokens. Use the Zen token directly: color: var(--text-primary), not color: var(--my-text).
Missing Dark Mode Token¶
The body.zen-migrated block was missing --text-tertiary for dark mode. Light mode set it to #525252 (good on white), but this value persisted into dark mode where it's invisible on dark backgrounds. Always ensure ALL text tokens have both light and dark mode values.
Remaining Known Issues (for future sessions)¶
| Category | Count | Status | Notes |
|---|---|---|---|
<option> element styling (dark mode) |
~488 | Won't fix | Browser ignores CSS on <option> elements. Users see platform-native dropdown styling, not the computed CSS. Exclude from future audits. |
| Semantic badge contrast (green-on-green, orange-on-orange) | ~150 | ✅ Fixed (Z4d) | Badge text tokens (--badge-red through --badge-purple) added to body.zen-migrated. Dark text in light mode, light text in dark mode. Applied to 6 tools. |
| Per-tool button white text | ~50 | ✅ Triaged (Z4d) | Investigated — all buttons have fixed saturated backgrounds. White text on #FB923C, #F39C12, #84CC16 etc. is contrast-safe. Not a real issue. |
| Dark backgrounds in light mode | ~45 | ✅ Fixed (Z4d) | roadmap.css fully migrated from "Amber Command" dark palette to Zen tokens. ~60 hardcoded colours replaced. |
Detection Commands (Updated for Z4c)¶
# Trap 3: root alias variables (THE MOST DANGEROUS)
# If ANY result appears, the file needs fixing
grep -n "var(--text-primary)\|var(--text-secondary)\|var(--bg-surface)" {tool}.css | grep "^\s*--"
# Trap 6 (NEW): missing dark mode token overrides
# Check body.zen-migrated has ALL text tokens in BOTH modes
grep -A10 "dark.*zen-migrated" style.css
# Original 5 traps still apply — see z4-migration-playbook.md
Quality Standard for Future QA¶
Run z4-deep-contrast.cjs after any CSS changes to tool pages. Thresholds:
- CRITICAL (ratio < 1.5): Must fix before deploy — text is invisible
- SERIOUS (ratio < 2.5): Should fix — text is barely readable
- MODERATE (ratio < WCAG AA): Nice to fix — below accessibility standard
- Exclude OPTION elements from count (browser limitation)
🔬 Z4d Polish Pass Learnings (27 Apr 2026)¶
Badge Text Token Pattern¶
Problem: Tinted badges (e.g. background: rgba(34,197,94,0.15); color: #34D399) use a single text colour for BOTH modes. Light pastel colours like #34D399, #FBBF24, #f87171 fail WCAG AA on light tinted backgrounds (~1.7–2.4:1 ratio).
Solution: Badge text tokens that flip between modes, defined in body.zen-migrated in style.css:
/* Light mode — dark text on light tinted backgrounds */
body.zen-migrated {
--badge-red: #B91C1C; /* dark red */
--badge-orange: #C2410C; /* dark orange */
--badge-amber: #92400E; /* dark amber */
--badge-green: #166534; /* dark green */
--badge-blue: #1D4ED8; /* dark blue */
--badge-purple: #6D28D9; /* dark purple */
}
/* Dark mode — light text on dark tinted backgrounds */
[data-theme="dark"] body.zen-migrated {
--badge-red: #FCA5A5;
--badge-orange: #FDBA74;
--badge-amber: #FDE68A;
--badge-green: #86EFAC;
--badge-blue: #93C5FD;
--badge-purple: #C4B5FD;
}
Usage:
/* Tinted background badges — use badge tokens */
.my-badge-error { background: rgba(239,68,68,0.15); color: var(--badge-red); }
.my-badge-warning { background: rgba(245,158,11,0.15); color: var(--badge-amber); }
.my-badge-success { background: rgba(34,197,94,0.15); color: var(--badge-green); }
/* Opaque/saturated background badges — use fixed #fff */
.my-status-badge { background: #EF4444; color: #fff; }
Rule: For tinted/transparent backgrounds → use var(--badge-*) tokens. For opaque saturated backgrounds (JS-injected status colours) → use fixed #fff.
When White Text (#fff) Is Safe¶
Safe: On fixed opaque saturated backgrounds where JS injects the colour:
- Roadmap status badges (style="background: #059669" via JS)
- Service health rank circles (background: var(--sh-red))
- Active product chips (background: var(--chip-c))
NOT safe: On Zen tokens that flip between modes. var(--text-primary) on a fixed green background is dark in light mode ✅ but light-on-green in dark mode ❌ for yellow/green hues.
Triage Approach for Audit Findings¶
Not all audit findings need fixing. Use this decision tree:
- Is the element visible? →
<option>elements are browser-controlled. Won't fix. - Is the background saturated and fixed? → White text buttons on
#FB923C,#84CC16etc. are fine. Not an issue. - Does the element exist in the DOM? → Some audit tools report elements hidden by
display: none. Check visibility. - Is it a CSS or JS issue? → Dynamic inline styles (chips, status badges) need JS fallback fixes, not CSS.
Z4d triaged 4 items as non-issues using this approach, saving significant time.
Full Zen Migration Checklist for Dark-Palette Tools¶
When a tool CSS file still has its original dark palette (like roadmap's "Amber Command"), follow this mapping:
| Pattern | Replace With |
|---|---|
| Dark backgrounds (#1C1924, #141118, etc.) | var(--bg-surface) |
| Dark borders (#2A2533, #3D3648) | var(--border) / var(--border-emphasis) |
| Hover dark backgrounds (#211E29) | var(--bg-elevated) |
| Light text (#E8E4ED) | var(--text-primary) |
| Muted text (#8B8594, #6B6575) | var(--text-muted) |
| Description text (#9B95A5) | var(--text-tertiary) |
| Secondary text (#D4D0DA) | var(--text-secondary) |
| Placeholder text (#504A59) | var(--text-muted) |
| Tool accent colour (#E5A00D etc.) | var(--accent) |
| Accent hover (#FBBF24 etc.) | var(--accent-hover) |
| Focus outline (hardcoded hex) | var(--accent) |
| Hero gradient → flat | var(--bg-surface) |
| Gradient text clip → plain | color: var(--text-primary) |
| BTT button text on accent | color: #fff |
| Skeleton shimmer | var(--bg-elevated) to var(--border) |
Also check the tool's JS file for fallback hex colours (e.g. color: '#888') — these need updating too.
🔧 Per-Tool Visual QA Process (Z4)¶
When doing visual QA on tool pages after CSS migration, follow this process for EACH tool:
Playwright QA Checklist¶
- Light mode screenshot — viewport 1440×900. Can you read ALL text?
- Dark mode screenshot — set
data-theme="dark"on<html>. Still readable? - Click every tab — screenshot each tab in both modes
- Check these specifics:
- [ ] Tabs are Zen underline style (border-bottom indicator, NOT pill/blob)
- [ ] No
backdrop-filterglass effects visible - [ ] No per-tool accent colour (everything indigo)
- [ ] Form inputs, selects, buttons have readable text
- [ ] Cards/tiles have proper contrast
- [ ] Placeholder text visible but muted
- [ ] Semantic colours (green/red/yellow for status) still work
Template Structure Check¶
Ensure every tool follows this pattern (use copilot-matrix as reference):
<div class="{tool}-container zt-page"> ← wrapper with zt-page
{{ partial "tool-header.html" (dict ...) }} ← header OUTSIDE tabs
<div class="{tool}-tabs" role="tablist"> ← tablist is CHILD of zt-page
<button role="tab">Tab 1</button>
</div>
<div role="tabpanel">...</div>
</div>
❌ WRONG: zt-page and role="tablist" on the SAME element — Zen tab selectors won't match.
The 5 Detection Commands¶
Run against each tool's CSS before considering it done:
grep -n "rgba(255.*255.*255" {tool}.css # Trap 1: white text
grep -n "^:root" {tool}.css # Trap 2: dark variable systems
grep -n "var(--text-primary)" {tool}.css # Trap 3: check if in :root block
grep -rn "\-\-text-primary:.*#[eEfF]" {tool}.css # Trap 4: scoped overrides
grep -n "var(--bg-card)\|var(--glass-border)" {tool}.css # Trap 5: old glassmorphism refs
grep -n "backdrop-filter" {tool}.css # Banned
grep -n "border-radius.*999" {tool}.css # Dead glass pill code
Quality Standard¶
Quality over speed. Measure twice, cut once.
Every tool page is a product experience. A user picking Microsoft 365 licences on the Licence Picker deserves the same visual polish as someone reading a blog post. If a tool looks "off" in light mode, it's not done — even if automated QA says it passes.
The Zen philosophy is "a cherry blossom tree, not a jungle." That means EVERY leaf matters.