# Adopting the theme

## Minimal integration

Copy `theme.css` into the project's static asset directory and link it before
application-specific CSS:

```html
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500;600;700&family=Fira+Sans:wght@400;500;600;700&family=Space+Grotesk:wght@400;500;600;700&display=swap">
<link rel="stylesheet" href="/static/theme.css">
<link rel="stylesheet" href="/static/application.css">
```

The second stylesheet may define product layout, but should consume shared
tokens and components rather than restyling them.

## Optional behavior

Copy `theme.js` only when the project needs the included light/dark/system
preference, example tabs, or dialog helpers. Applications with existing state
management should implement those behaviors themselves.

All implementations must follow [`MODALS.md`](./MODALS.md). In particular,
application-managed confirmations must be descendants of a permanent native
settings dialog; a sibling `z-index` cannot escape the browser top layer.

Theme controls use:

```html
<button data-theme-value="system">System</button>
<button data-theme-value="dark">Dark</button>
<button data-theme-value="light">Light</button>
```

The preference is stored under `xore-theme`. A second, independent axis
selects the named theme family (Claude, Slate, Sage, …). Its controls set
`data-hp-theme-value`, the chosen family lands as `data-hp-theme` on
`<html>`, and the selection is stored under `xore-hp-theme`; the default
family stores nothing. Mode and family compose: the family owns the token
surface, the mode picks which half of it renders.

## Before first paint

`theme.js` resolves the saved preference when it runs — normally after the
stylesheet has already painted default tokens — so a returning visitor
whose saved state differs from the default sees a flash of the wrong theme
on every navigation. Closing that flash takes an inline script in the
`<head>`, placed *above* the `theme.css` link, that re-applies just enough
state for the cascade:

```html
<script>
  (function () {
    try {
      var mode = localStorage.getItem('xore-theme');
      if (mode === 'dark' || mode === 'light') {
        document.documentElement.setAttribute('data-theme', mode);
      }
      var family = localStorage.getItem('xore-hp-theme');
      if (family && family !== 'claude') {
        document.documentElement.setAttribute('data-hp-theme', family);
      }
    } catch (_) { /* storage blocked: defaults render */ }
  })();
</script>
```

Four rules keep it safe:

- **Validate before applying.** Only exact expected values reach
  `setAttribute`. Anything else — empty, unknown, tampered — falls through
  to the defaults, which is precisely how `theme.js` treats it on load.
  Never blit storage into an attribute verbatim.
- **Absence is the default.** The snippet writes nothing for system or for
  the default family, so the attribute and localStorage cannot disagree.
- **Document order decides.** An inline script below the `theme.css` link
  runs after the browser has it; above the link is the only placement that
  works.
- **CSP:** a strict `script-src` without `'unsafe-inline'` needs a nonce or
  hash for this script — see [`CSP.md`](./CSP.md) for how that policy
  interacts with per-element values.

Permanent settings surfaces use:

```html
<dialog class="modal modal--permanent" data-permanent-dialog>
  <!-- sidebar, content, and every nested confirmation -->
</dialog>
```

`theme.js` opens these dialogs on load, prevents Escape from closing them,
restores focus after nested dialogs, synchronizes backdrop `inert` and
`aria-hidden` state, and keeps only one `.action-menu` open at a time.

## Vendoring

For repositories that embed static assets:

1. Copy `theme.css` into the embedded asset directory.
2. Add a comment containing the source repository and commit SHA.
3. Add a CI check that the CSS parses and expected selectors exist.
4. Keep a `theme/` snapshot only when maintainers explicitly want the complete
   examples and migration documentation available offline.

## Application-specific CSS

Keep selectors in the application when they encode product data or behavior,
for example audit-table column widths, map sizing, or authentication form
layout. Move a selector into the shared theme only when its semantic contract
is reusable.

## Content Security Policy

If the application enforces a strict `style-src`/`script-src` (a per-request
nonce, no `'unsafe-inline'`), read [`CSP.md`](./CSP.md) before building
anything that sets a per-element value at render time — a chart bar's
height, a progress fill, a positioned tooltip. The obvious inline
`style="..."` attribute is silently dropped under that policy in every
CSP-enforcing browser; `CSP.md` has the pattern that actually works. The policy
must also allow `fonts.googleapis.com` in `style-src` and `fonts.gstatic.com` in
`font-src` for the theme's web typography.

The font stylesheet is a `<link>` in your page's `<head>`, not an `@import`
inside `theme.css`. That is deliberate: an `@import` at the top of the main
stylesheet forces the browser to fetch and parse `theme.css` before it even
discovers the font request, then fetch the font CSS, then the font files —
three serial round trips blocking first paint, on every page load. As a
`<link>` the font CSS is discovered immediately and resolves in parallel.
The `--font-*` stacks carry platform-native fallbacks, so a blocked or
offline font host degrades rather than breaks; omit the two lines entirely
and the theme still renders in the fallback faces.

## Validation checklist

- Review all example pages at 1440×900, 1024×768, and 390×844.
- Review dark, light, and system modes.
- Reload with a non-default preference saved and confirm no wrong-theme
  flash (pre-paint boot script in place and above the stylesheet link).
- Navigate every control with a keyboard.
- Check visible focus and modal Escape behavior.
- Verify Save and Enter open the same visible configuration warning.
- Verify nested confirmation backdrops live inside permanent native dialogs.
- Verify confirmation runs once, Cancel runs nothing, and focus is restored.
- Confirm no horizontal page scroll at mobile widths.
- Confirm `prefers-reduced-motion` disables non-essential movement.
- Run an HTML validator and a CSS parser.
