# XWUICaptureFrame

A titled frame around a real product capture. A header bar names what the
capture is and whether it exists; the capture sits under it; a caption below
carries the provenance. When no capture exists the frame reserves the slot and
names the missing artefact, instead of falling back to an illustration.

## When this and not XWUIImage, XWUICard or XWUIPanel

- `XWUIImage` is the picture, not the frame: its whole surface is the image.
  Its nearest chrome is `ribbon`, a corner label whose config carries a colour
  as a plain string (`XWUIImage.types.ts:56-60`), which is a literal on the
  consumer's side and a corner, not a bar.
- `XWUICard` renders its cover image **above** its header
  (`XWUICard.ts:187-196` before `:202`) and has no knob to reverse that, so the
  title can never sit on top of the capture.
- `XWUIPanel` has the right skeleton, header over body over footer, and paints
  nothing at all: `XWUIPanel.css` is structure only, no border, no background,
  no header height. Composing it would mean writing the whole frame in
  consumer CSS.

This component is the frame. The picture is still `XWUIImage`: hand it in as
`media`.

## Style dependencies

The stylesheet declares no value of its own except one aspect ratio, noted
below. It reads, and needs a host that declares:

- base: `styles/core/reset.css`, `styles/core/typography.css`
- style: `styles/style/modern/spacing.css` for `--spacing-xs`, `--spacing-sm`
  and `--spacing-lg`
- theme colours: `--bg-elevated`, `--border-color`, `--text-secondary`,
  `--accent-success`, and `--bg-sunken` for the frame ground. A theme without
  `--bg-sunken` falls back to `--bg-secondary`, which is the rung that token
  aliases.
- theme roundness: `--radius-lg`, `--radius-full`
- theme lines: `--border-width-thin`
- theme typography: `--font-family-mono`, `--font-size-xs`,
  `--font-weight-normal`, `--line-height-normal`, `--letter-spacing-wide`,
  `--letter-spacing-normal`

## Basic usage

```ts
new XWUICaptureFrame(container, {
  state: 'present',
  caption: 'capture . studio / flow canvas',
  note: 'studio-canvas.png, 3200 x 2000.',
  media: someImageElement
});

new XWUICaptureFrame(container, {
  state: 'missing',
  caption: 'capture . studio / list mode',
  artifact: 'no capture of list mode exists yet'
});
```

## Data

| Key | Type | Default | Meaning |
|---|---|---|---|
| `state` | `'present' \| 'missing'` | `'missing'` | Whether a real capture exists. |
| `caption` | string | `''` | What the capture is. Leading edge of the header bar. |
| `stateLabel` | string | per state | The state as words. `'Real capture'` when present, `'No capture'` when missing. |
| `artifact` | string | `''` | Name of the absent artefact. Read only in the missing state. |
| `note` | string | `''` | Provenance caption below the frame. |
| `media` | HTMLElement | none | The capture itself. Rendered only in the present state. |

Every field is optional and every default is the resting state: with no data at
all the frame draws an empty reserved box labelled `NO CAPTURE`, which is the
truth about a frame nobody has given a capture to.

## Configuration

| Key | Type | Default | Meaning |
|---|---|---|---|
| `className` | string | none | Extra class on the frame element. |

## The state is data, not paint

`state` decides three things at once, in `projectCaptureFrameView()`:

1. the dot colour, `--accent-success` when present and `--text-secondary`
   otherwise;
2. the state label text, which a screen reader reads;
3. **whether `media` is rendered at all**. In the missing state
   `projectCaptureFrameView()` returns `media: null`, so a capture handed to a
   frame that says it has none is dropped rather than shown.

That third one is the load-bearing rule. A frame whose header says `NO CAPTURE`
must not be able to paint a picture underneath it.

## Accessibility

The root is a `<figure>` and the provenance caption is its `<figcaption>`, so
the caption is bound to the picture rather than floating beside it.

The state reaches a screen reader as **text**, not as colour: the dot is
`aria-hidden` and the label next to it is real text. The same is true of the
missing case, where the reserved box carries the artefact name as text, so a
reader who cannot see the empty box still learns what is not there.

The header bar and the reserved box are written in sentence case and painted
uppercase with `text-transform`. The DOM therefore holds `Real capture`, not
`REAL CAPTURE`: screen readers that spell out short all-capital strings read the
word.

The picture's alternative text belongs to the element handed in as `media`. This
component does not invent one.

Nothing is interactive: no control, no tab stop, no handler.

## Comp values that lost to the token ladder

| Comp value | Written | Resolves | Why |
|---|---|---|---|
| header bar height 30px | nothing written | 27px | Derived from padding, border and line box, as every other bar in the library is. There is no height token, and one is proposed below. |
| header horizontal padding 10px | `--spacing-sm` | 8px | The spacing unit is 4; 8 is 2 away, 16 is 6 away. |
| header type 10 to 11px | `--font-size-xs` | 12px | The type ladder's floor. The step below it does not exist. |
| header colour `text.tertiary` | `--text-secondary` | `#9aa499` | `--text-tertiary` measures 4.06:1 on this ground and AA wants 4.5:1 at this size. |
| caption colour `text.tertiary` | `--text-secondary` | `#9aa499` | Same reason. |
| state dot 6px | `--spacing-sm` | 8px | 4 and 8 are equidistant. 8 keeps the dot-to-type ratio the comp drew: 6/10.5 is 0.57, 8/12 is 0.67, 4/12 is 0.33. |

## The one value that is not a token

`aspect-ratio: 16 / 10` on the reserved box. A ratio is neither a length nor a
colour, the token vocabulary has no ratio rung, and the box exists precisely to
hold the shape of the thing that is not there. Named as a proposed token below
rather than left unexplained.

## Proposed tokens, not added here

- a bar row height, about 28 to 32px. This is the same gap `XWUINodeChip.md`
  names for its chip row: every bar in the library derives its height from
  padding plus line box, so the same bar measures differently under different
  font axes.
- a set of media ratio tokens, so a reserved slot can name `16 / 10` the way it
  names a colour.
