# Build the "Style and voice presets" pattern: `PresetPicker`

## Stack (edit this block to match your project)
- React 18+ with TypeScript: one function component, typed props, no `any`.
- Tailwind CSS, using your design system's tokens if you have them.
- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.
- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.

## What the pattern is for
Named, previewable presets that bundle many parameters into one recognisable choice.

Use it when: When output quality depends on combinations of settings users cannot intuit individually.

## What to build
A grid of named, versioned presets (visual styles, voices, tones) that each bundle several settings into one choice. Each preset shows a preview of its effect and the settings it applies, so a person can pick one without spending a run to find out what the name means. It renders in a generation settings panel or composer sidebar.

### Props
- `presets`: Array<{ id: string; name: string; version: string; description: string; thumbnailUrl?: string; sampleUrl?: string; settings: Array<{ label: string; value: string }>; tag?: string }>, shipped as data.
- `selectedId`: string and `onSelect(id: string)`, controlled selection.
- `previewMode`: "none" | "sample" | "live", whether previews are static samples or a cheap render of the person's own content.
- `previews?`: Record<string, { status: "loading" | "ready" | "error"; url?: string }>, low-cost previews of the person's content per preset, supplied by the parent.
- `onRequestPreview?(id: string)`: asks the parent for a free or low-cost preview of that preset.
- `runCost?`: string, the cost of a full run with the selected preset, e.g. "3 credits, about 20 s".
- `onApply(id: string, version: string)`: commits the choice; the version is passed so the saved artifact can be reproduced later. The component never generates anything itself.
- `savedPresetIds?`: string[], marks the person's own saved presets so they can be grouped separately.

### States it must render
- Default: preset tiles with name, version badge, one-line description and thumbnail; the selected tile is pressed and its settings list reads "Ink line v1 sets 5 things at once" with each setting as a chip.
- Previewing: the person's own content rendered in the selected preset, clearly labelled "Preview, free, low-res" so it is not mistaken for the final output.
- Preview loading: a placeholder at the final aspect ratio with a quiet indicator; selection stays responsive.
- Preview unavailable: the static sample is shown with "Example output, not your content".
- Applied: the chosen preset and version are shown as a summary line, e.g. "Generated with Ink line v1".

### Interactions
- Clicking a tile selects it, updates the settings list and calls `onRequestPreview` when `previewMode` is "live".
- Arrow keys move between tiles; Enter or Space selects.
- Apply calls `onApply(id, version)` and shows `runCost` on the button before it is pressed.
- Expanding "What this sets" on any tile shows its settings without selecting it.

### Edge cases
- A preset updated to a new version after an artifact was made with an older one: show "Made with v2, current is v3" rather than silently relabelling.
- A retired preset referenced by a saved artifact: keep its name and settings readable, and mark it "Retired".
- Presets with no thumbnail: show the settings chips prominently instead of an empty box.
- Many presets (more than 12): group into brand defaults and saved presets, keeping the selected one visible.

## Must avoid
Preset names that mean nothing without trying them, and no preview of the effect before spending a run.

## Engineering constraint
Ship presets as data with thumbnails; version them so a saved artifact can be reproduced later.

## Accessibility
- The grid is a `radiogroup` labelled "Style preset"; each tile is a `radio` whose accessible name includes name, version and description.
- Announce when a preview finishes loading, and label preview images with the preset name and "preview".

## House rules
- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live="polite"`; respect `prefers-reduced-motion`.
- All user-facing text comes from props (or a `labels` prop) so it can be translated.
- One consistent accent marks anything the AI made or is doing; amber means "needs a person", red means risky, green means done. Never rely on colour alone.
- Fits a 360 px wide container without horizontal scrolling.

## Acceptance checks
- [ ] Every state above can be rendered by passing props, with no internal fake data.
- [ ] Every interaction and edge case above behaves as described.
- [ ] Nothing in "Must avoid" can happen with this component.

## Deliver
1. The component file with its exported prop types.
2. A short usage example that renders each state.
