Skip to content

🚧 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-out only
  • 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
  • classDef definitions — banned
  • ❌ Emojis in ["🔐 Security"] — text only
  • ❌ Neon colours anywhere in diagrams
  • ✅ White nodes, #E5E5E5 borders, #C4C8D0 connectors, 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-filter on 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 with var(--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:

  1. Screenshot homepage + one tool page + one reading room
  2. Verify: nav renders correctly, no text leaks, sidebars hidden on mobile viewport
  3. 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_version bump

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

  1. Check if an existing component can be reused or composed
  2. Design it using only existing tokens
  3. Ensure it works in light + dark mode
  4. Add it to components.md with CSS
  5. It must look like it's always been part of the system

Adding a New Token

  1. Document why the existing scale doesn't cover the need
  2. The new value must fit the existing scale pattern (e.g., spacing follows 8px grid)
  3. Update the relevant doc (colours.md, typography.md, or spacing.md)
  4. Update style.css root variables

Adding a New Page Type

  1. Use the closest existing layout pattern
  2. If truly new, document the layout in spacing.md
  3. 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:

  1. Is the element visible?<option> elements are browser-controlled. Won't fix.
  2. Is the background saturated and fixed? → White text buttons on #FB923C, #84CC16 etc. are fine. Not an issue.
  3. Does the element exist in the DOM? → Some audit tools report elements hidden by display: none. Check visibility.
  4. 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

  1. Light mode screenshot — viewport 1440×900. Can you read ALL text?
  2. Dark mode screenshot — set data-theme="dark" on <html>. Still readable?
  3. Click every tab — screenshot each tab in both modes
  4. Check these specifics:
  5. [ ] Tabs are Zen underline style (border-bottom indicator, NOT pill/blob)
  6. [ ] No backdrop-filter glass effects visible
  7. [ ] No per-tool accent colour (everything indigo)
  8. [ ] Form inputs, selects, buttons have readable text
  9. [ ] Cards/tiles have proper contrast
  10. [ ] Placeholder text visible but muted
  11. [ ] 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.