# Frame embed contract

Protocol between an embedding page (e.g. `/resources`) and a frame-conformant
chart loaded in an iframe. Replaces the ~100 lines of DOM-surgery in
`resources.html` for any chart that opts in.

## Why

A page wrapping a chart in an iframe needs to:

- Switch the chart's theme when the user toggles the page-level theme.
- Read the chart's title/subtitle/source for its own modal header.
- Hide the chart's own theme toggle so it doesn't double up.

The legacy approach reaches into `iframeDoc` and writes inline styles to
hardcoded element IDs (`#outer`, `#card`, `#title`, `#sourceText`, `#logoWhite`,
`#logoDark`, …). That's fragile: rename one ID, forget one element, and you
get white-on-white text in the wrong theme. The chart and the page have a
brittle, undocumented coupling.

The contract replaces that with messages.

## Detection

A frame-conformant chart exposes:

```js
window.__frame = {
  version: 1,
  chartType: 'multiLine' | 'horizontalBar' | …,
  slug: 'europe-vc-cumulative-monthly',
  inbound: ['setTheme', 'getMetadata', 'replay'],
  outbound: ['metadata', 'ready'],
};
```

The embedding page checks for this object on iframe `load` and skips legacy
surgery when it's present.

## Inbound messages (parent → chart)

```js
chartIframe.contentWindow.postMessage({ type: 'setTheme', theme: 'dark' | 'light' }, '*');
chartIframe.contentWindow.postMessage({ type: 'getMetadata' }, '*');
chartIframe.contentWindow.postMessage({ type: 'replay' }, '*');
```

| Type | Effect |
|------|--------|
| `setTheme` | Flips `data-theme` on `<html>`. Frame's CSS variables + `__onThemeChange` callback re-tint everything (canvas lines, plugin redraws, logo swap) atomically. |
| `getMetadata` | Chart posts back `{type:'metadata', headline, subtitle, source, slug, chartType}`. Avoids `iframeDoc.querySelector` reach-in. |
| `replay` | Re-runs the chart's entry animation if it has one (calls `chart.update()`). |

## Outbound messages (chart → parent)

```js
window.parent.postMessage({ type: 'ready', slug }, '*');
window.parent.postMessage({ type: 'metadata', headline, subtitle, source, slug, chartType }, '*');
```

| Type | When |
|------|------|
| `ready` | Once, after first paint. The page can use this to apply theme/scale before animations land. |
| `metadata` | In response to `getMetadata`. |

## Theme contract

Frame charts MUST:

1. Define theme-bearing colors as CSS variables under `[data-theme="dark"]` / `[data-theme="light"]` rules.
2. React to `data-theme` flips automatically (CSS variables) plus an optional `window.__onThemeChange(isDark)` callback for imperative work (Chart.js dataset re-tint, custom plugin redraws).
3. Provide both logo variants via `#logoWhite` (white-ink for dark bg) and `#logoDark` (dark-ink for light bg). The page does NOT need to know which is shown — the chart's `applyTheme` flips them.

## Element IDs (used by the title/metadata reader)

The page reads metadata via DOM as a fallback for the no-postMessage case. Frame charts already conform:

- `#title` — headline `<h1>`
- `#subtitle` — subtitle `<p>`
- `#sourceText` — source string `<span>`

These IDs are the **only** elements the page touches via DOM lookup. All
behavior changes go through postMessage.

## Migration path for legacy charts

Phase 1 (current): page detects `window.__frame` and uses contract for those; legacy charts still get the DOM surgery.

Phase 2 (later): retrofit enough charts that the legacy block is rarely run. Audit — if all charts in production conform, delete the block.

Phase 3 (eventual): drop the iframe entirely and render charts inline; the contract becomes a no-op.
