# CSS Components

CSS-only component library on semantic HTML, with a flat, geometric look: cream paper, ink 2px borders, 2px radius, no shadows, uppercase mono UI, sky/yellow boxed section bands.

## Layout
- `fonts/` is not vendored: the flake imports `urbanist` and builds the site package (`nix build .#site`) with its web woff2 files in `fonts/`. Open `result/index.html`, or run `nix run` to serve it.
- `css/tokens.css`, `css/base.css` — foundations (shared, do not edit from a component branch)
- `css/components/<group>.css` — one file per group; `css/style.css` imports them all
- `pages/<group>.html` — demo page per group (every component, every variant)
- `nix run .#demo-server` — serve the demos at http://localhost:8000 (`nix run .#demo-server -- 3000` or `PORT=3000` to change the port); `nix flake check` runs the static and layout (overflow) checks
- `scripts/shoot.js` — (`npm install`, then `CHROME=… npm run shoot`) puppeteer screenshots of every page (light/dark, desktop/mobile) plus a horizontal-overflow check

## Conventions
- **No JavaScript, no build step.** Native `details`, `dialog`, `popover`, `:target`, `:has()` for interactivity.
- **Semantic element selectors first** (`button`, `article`, `details`, `nav`...). Use ARIA attributes (`[aria-current]`, `[role=tablist]`) and native state pseudo-classes (`:user-invalid`, `:checked`, `:disabled`) — never classes for validity and `data-*` attributes for variants (`data-variant="secondary"`, `data-size="sm"`, `data-tone="sky"`). Avoid class names except the layout helpers in `base.css`.
- **Scope selectors** so they cannot leak into unrelated markup, e.g. `a[role=button]`, `nav[aria-label=Breadcrumb]`, `article[data-card]`.
- **Only use tokens** from `tokens.css` (`var(--border)`, `var(--radius)`, `var(--c-sky)`...). No raw hex in component files.
- Square-ish and flat: 2px radius, 2px ink borders, no box-shadows. Borders are symmetrical (all four sides or none; no single-side accent bars, no open-bottom tabs, no thick keycap bottoms). No circles or pills: avatars, switches, markers and spinners are squares. The one exception is the radio button, which stays round so it reads as single-choice next to square checkboxes. Hairline dividers between rows (tables, lists, stat cells) are fine. Hover/active states via colour inversion or background change.
- **Motion is fast and decelerating** (see https://animations.dev/learn/animation-theory/the-easing-blueprint): hover changes to colour, background, border and opacity use `var(--ease)` (120ms `ease`); anything that moves (transform, translate, position) uses `var(--ease-move)` (160ms ease-out-expo). Never `ease-in`; `linear` only for constant motion (the indeterminate progress stripes); the square spinner uses `steps(4)` so it never tilts. `prefers-reduced-motion` disables all of it.
- **Header dropdowns:** give every `details[data-dropdown]` in a header the same `name` (e.g. `name="site-nav"`) so opening one closes the others. This is native HTML, no JS. Clicking outside does not close a dropdown (that needs JS); click the summary again or press Esc where supported.
- Must work at 360px wide (checked by `scripts/shoot.js`), in light and dark themes, with visible `:focus-visible`.
- Every component appears in its group's demo page with all variants and states, using valid semantic HTML.
- **Tabs:** `[role=tab]` buttons are styling only; add your own roving-tabindex/arrow-key handling if you use them as real tabs. The no-JS `[data-tabs]` radio variant supports up to 6 tabs and needs the tablist as the first `div` child.
- **Tooltips:** `[data-tooltip]` is a visual hint on a focusable host (button, link, or `tabindex=0`). Pair it with `aria-describedby` or put the same text in the element's accessible name.
