# SEO Theme Support Update

**Date:** 2026-03-21
**Agent:** seo-specialist
**Task:** Update SEO metadata for theme support

---

## Summary of Changes

### 1. `src/layouts/BaseLayout.astro` — `theme-color` meta tags

**Before:**
```html
<meta name="theme-color" content="#0891b2" />
```

**After:**
```html
<!-- OS-level dual media query approach -->
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#0891b2" />
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0f172a" />
```

The dual `media` attribute approach lets browsers natively use the correct browser-chrome color based on OS theme — even before JavaScript runs. This is important for users who haven't yet interacted with the site.

The inline theme-init script (already present for FOUC prevention) was extended to also sync these meta tags when a user has a **stored explicit preference** in localStorage that overrides their OS setting.

---

### 2. `src/components/ThemeToggle.astro` — Meta tag sync on toggle

**Before:** `applyTheme()` used `querySelector` (selects only the first meta), and the system-preference change listener did not update theme-color at all.

**After:**
- `applyTheme()` uses `querySelectorAll('meta[name="theme-color"]').forEach(...)` to update **all** theme-color metas simultaneously
- System preference change listener (`matchMedia` change event) now also syncs all theme-color metas when no stored preference exists

---

## What Was NOT Changed (and Why)

### Structured Data (JSON-LD)
All schema.org structured data in `BaseLayout.astro` and `SEO.astro` is **theme-agnostic by design**. JSON-LD describes content/entities, not visual presentation. Search engines and AI crawlers have no concept of visual theme when consuming structured data. No changes needed.

### Open Graph / Twitter Card Images
Social preview images (`og-image.jpg`) are static assets served to social crawlers and link-unfurling bots. These crawlers:
- Do not respect OS `prefers-color-scheme`
- Do not execute JavaScript
- Do not honor `media` query attributes

Providing separate dark/light OG images would require serving different `<meta property="og:image">` values based on the server-side request context, which adds complexity without meaningful SEO benefit. The existing `og-image.jpg` works universally.

### Other Meta Tags
All other meta tags (title, description, canonical, robots, OG/Twitter properties) are theme-agnostic and correct as-is.

---

## Color Values Used

| Theme | `theme-color` value | Token |
|-------|-------------------|-------|
| Light | `#0891b2` | `--palette-primary-500` (brand cyan) |
| Dark  | `#0f172a` | `--color-surface` dark (deep navy) |

These values match the design tokens in `src/styles/design-tokens.css`.

---

## How It Works End-to-End

1. **Page load (no JS yet):** Browser reads both `theme-color` meta tags and picks the one matching OS preference via the `media` attribute — instant, no flash.
2. **Inline init script fires:** If user has a stored preference that differs from OS, the script updates both meta tag `content` values to the stored preference color.
3. **User clicks ThemeToggle:** `applyTheme()` toggles the `.dark` class AND updates all `theme-color` metas in one synchronous call.
4. **User changes OS theme (no stored preference):** `matchMedia` change listener updates `.dark` class AND syncs all theme-color metas.

---

## Testing

Verify correct behavior by:
1. Opening DevTools → Elements → search for `theme-color` meta tags
2. Toggle between light/dark using the site toggle — both meta `content` values should update to the same color
3. Remove localStorage `theme` key, change OS theme — meta tags should track OS preference
4. Check browser tab/address bar color updates on Chrome/Edge (Android) after toggle
