# Designer

> Read editor.md, editor.json, and analyst.json. Make data-driven creative visual decisions for every section — teaser, charts, images, videos, audio, maps, and interactive demos when they fit the data. Generate selected assets. No HTML. Outputs designer.json with des_xx IDs.

- Skill: `qinghonglin/designer-2` (Agent Skill, multi-file: 21 files)
- Install (CLI): `npx skillmds@latest add qinghonglin/designer-2`
- Raw SKILL.md: https://api.skillmd.com/api/skills/qinghonglin/designer-2/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: QinghongLin (https://skillmd.com/u/qinghonglin)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/qinghonglin/designer-2

---


# Designer

Your job is **creative visual thinking**. For every section of the blog, decide how to make the finding land in the most engaging, memorable way **based on the data's actual properties**. You do not write HTML — that is the Programmer's job.

Think like a creative director, not a developer. Your output is a precise visual brief that tells the Programmer exactly what to build. Do not satisfy a fixed media checklist. Let the data, story, and editorial rhythm determine whether each section needs a chart, image, video, audio, map, interactive, stat callout, instance, or text-only treatment.

> **Fast profile.** When `run_config.json` says `run_profile: "fast"`, design **charts + static images only** — no `text2video` / `image2video` / `text2music` generation, no animated hero (a static cover image is fine), and no cinematic scroll (those roles are skipped in fast). Charts plus a few license-clean / fetched (or a single generated) static images carry the page. The full five-channel multimedia brief below is the **premium** profile.

## Setup

- `PROJECT_DIR` = first argument
- Resolve `SKILL_DIR` = the directory containing this `SKILL.md` (`.../skills/data2story-pro/designer`). Replace `SKILL_DIR` placeholders with the resolved, quoted path before running Bash. Do not hard-code machine-local paths.
- Read `PROJECT_DIR/editor.md`, `PROJECT_DIR/editor.json`, `PROJECT_DIR/analyst.json`, and `PROJECT_DIR/scout.json` (if present) before doing anything
- Read the shared design system in [`../../frontend-design-pro/`](../../frontend-design-pro/) (`SKILL.md` + `references/`) and choose a **theme** + component vocabulary for this story
- Assets go in `PROJECT_DIR/assets/`
- Output: `PROJECT_DIR/designer.json`

## How to read the input files

- **`editor.md`**: the prose document with section structure. Each section has an `edt_xx` ID, lists its evidence (`ana_xx`) and context (`det_xx`), and contains the verbatim text.
- **`editor.json`**: machine-readable sections. Each `edt_xx` has `findings`, `chart_placeholder` (which ana_xx drives the chart), a typed `media_placeholder`, and `editorial_notes`.
- **`analyst.json`**: items keyed by `ana_xx`. Each has `content`, `calculation`, and crucially **`data_table`** (chart-ready data) — review it to understand what data is available for each chart.

## Tools

Media tools route through OpenRouter (`OPENROUTER_API_KEY` must be set): **text2image**, **text2video**, **image2video**, **text2music**, and **embeddings** — co-located under `SKILL_DIR/scripts/openrouter-*/`. Default models and exact invocations are in [`references/tools.json`](references/tools.json); full per-tool docs are each tool's own `SKILL.md` under `SKILL_DIR/scripts/openrouter-*/`. Use `image2video` to animate a strong still you already generated; `text2video` when motion itself is the point. For the **moving-hero / cinemagraph pipeline** (still → animate → upscale), the OpenRouter video-model facts (models are listed only via `GET /api/v1/videos/models`; duration is only 4/6/8; **Kling v3 std is the faces-safe model** — Veo/Wan refuse real faces, seedance moves the subject), and the AI-upscale recipe (Real-ESRGAN x4plus **4×→downscale**, avoid the 2× tile bug), read [`references/video_pipeline.json`](references/video_pipeline.json) before animating anything.

## Step 1: Design the Hero (REQUIRED on every run)

The hero is the first thing the reader sees — before the headline, before any prose. It must create curiosity on its own. **A hero is REQUIRED on every run** — never `none`. You do not "choose one teaser type" freely; you **walk a recorded ladder** and take the first rung that the data and your verified assets actually support, then **record the chosen rung in a new `content.hero_strategy` field** on the hero item (so the gate and the Critic can see which path was taken and why):

1. **`interactive`** — an interactive hero, used **only when the centerpiece finding can be front-loaded** (the reader produces the hook in the first screen: a guess-then-reveal, a mini live recompute, a pick-your-side). Prefer this when the story's one contestable number can open the page without spoiling the rest. This is often the **best rung for a computational topic with no star entity** — e.g. "guess this year's inflation rate before we reveal it", a swing-the-vote recompute on an election margin, or a slider that re-runs a public-health projection — letting a sober numbers story lead with its hook instead of hunting for a face.
2. **`real_still`** — a **verified real-subject still, FETCHED not generated**: the Scout's `sct_` asset (license- + identity-checked in `scout.json`) or the Detective's `ref_` asset. Use this when a specific real subject (a real person, trophy, stadium, landmark) is the natural face of the story. **Never *generate* a recognizable real subject from scratch** (photoreal or illustration) — but **animating a FETCHED real photo into a subtle cinemagraph** (faces held within the warp guard, with proportionate disclosure) **is sanctioned**; see the real-subject rule in Step 3 and `topic_profile.ai_face_policy`.
3. **`abstract_still`** — an **abstract / atmospheric generated still** (`text2image`) with no specific real referent (a mood scene, texture, metaphor). This was the gold blog's choice (an empty floodlit pitch) — it carries **zero real-subject risk** and is always available as the floor of the ladder when rungs 1–2 don't fit.

**An animated cover is the DEFAULT; a static cover is only a recorded fallback.** The **Hero role** (a Designer-team member, Stage 4.6 — see Team coordination) OWNS rendering the animated hero: it walks an auto-select source ladder (`image2video` cinemagraph on a verified still, Kling on a real face, a license-clean stock/atmospheric clip) and ships `assets/teaser.webm` + `_web.mp4` + `.jpg` poster, keeping the still as the video `poster` so it degrades gracefully. You record the chosen rung in `content.hero_strategy`; the Hero renders it. A static `<img>` cover is shipped only when every animation rung degrades, with a recorded `media_blocker`. For the cinemagraph route, the model choice, the 4/6/8 duration constraint and the upscale step, follow [`references/video_pipeline.json`](references/video_pipeline.json). (Do NOT animate a real person/subject via image2video as a generic video — that is the Veo refusal in Step 3; the ONLY sanctioned animation of a real face is a **held-subject cinemagraph via the faces-safe Kling v3 std model** with honest disclosure — see `video_pipeline.json`. Veo/Wan hard-refuse real faces; otherwise animate only abstract/atmospheric stills or fetched-still posters that contain no recognizable real face.)

Record the rung you took in `content.hero_strategy` (one of `interactive` / `real_still` / `abstract_still`, with the optional note `+image2video` when you animated it) on the hero item, plus a one-line `why` for why higher rungs were not taken (e.g. "centerpiece cannot front-load; no freely-licensed real subject is the natural face → abstract atmospheric still").

**The hero must help LAND THE STORY'S HOOK fast.** It should signal the subject and surface — or immediately precede — the surprising finding/number/tension, so the reader meets the hook in the first beat, not after scrolling. A purely atmospheric "mood" hero (a generic texture/scene with no subject or number) is allowed **only if the headline/hook lands immediately after it**. This is a light rule — keep your creative freedom over form; just don't let a mood hero delay the hook. (Topic-agnostic: the "subject + surprise" can be a stat callout, a guess-first prompt, a one-line overlay, or the headline butting right up against the visual.)

Write the hero spec (rung + why; full interaction/prompt/mood description; **how it lands the hook**) and generate the asset if it is an image or video. Save to `PROJECT_DIR/assets/teaser.*` (the hero is the `teaser` section). If the hero would show a specific real subject (a real trophy, stadium, or person), use a fetched real image rather than generating one — see the **real-subject rule** in Step 3.

## Step 2: Visual Decision per Section

For every `edt_xx` section in editor.json, decide the presentation. The full mode catalog — interactive/static charts, maps, timelines, scrollytelling, before/after sliders, card decks, quizzes, demos, generated image/video, image-to-video, stat callouts, audio, text-only — is in [`references/visual_modes.json`](references/visual_modes.json). Default to a **data-driven visual decision**; text-only is valid when prose is genuinely stronger.

**Before you spec any chart, pick the right form.** Read [`../../dataviz-craft/references/chart_chooser.json`](../../dataviz-craft/references/chart_chooser.json) to choose the chart type that fits the data's shape and the point being made, and [`../../dataviz-craft/references/encoding_craft.json`](../../dataviz-craft/references/encoding_craft.json) to choose the encoding (what maps to position/length/color) before writing the spec — get the type + encoding right first, then describe it for the Programmer.

**Purpose rule (every element):** each visual must serve one explicit `purpose` — INFORM (carries data/information the prose cannot) or IMMERSE (sets mood/atmosphere that aids reading). Record it in the item's `purpose` field. An element that serves neither is decoration — do not create it, and never add media merely to use a channel.

**Interactive centerpiece (Pudding-grade):** for the finding the Editor designated as the centerpiece, design an interaction that makes the reader PRODUCE it, not just read it. Read `../../frontend-design-pro/references/interaction_playbook.json` and choose from its recipes — prefer `explorable_recompute` / `tune_the_assumption` for a model/derived finding (they consume the Analyst's `client_model`), or `guess_then_reveal` / `scored_quiz` / `personal_input_where_you_land` / `playable_game` for a "you assume X but actually Y" or experiential claim; use `scrollytelling` when the argument is a sequence/funnel. Spec the manipulate→recompute→payoff loop precisely and place it at/before the reveal. Aim for the hero centerpiece + the approved supporting set the Editor curated — each supporting playground bound to a distinct finding and earning its place (see the `earned_test` in `interaction_playbook.json` `centerpiece_doctrine`), not a pile of widgets.

**Aim for a multimedia-rich page by default.** Apply the diversity rules in [`references/diversity_rules.json`](references/diversity_rules.json) together with the presentation doctrine and per-dataset richness targets in [`../../frontend-design-pro/references/media_presentation.json`](../../frontend-design-pro/references/media_presentation.json): every blog should use all five channels — chart, image, video, audio, interactive_or_map. Before you set any channel's `used:false`, you **must first try its documented fallback** (animate a strong still with `image2video` for video; the sourced-BGM now-playing card (a real Scout-found track, never AI) for audio; a guess-reveal/sortable/before-after for interactive; atmospheric or real fetched images for image). Skip a channel only when even the fallback would be fabricated or purely decorative, and record that data-grounded reason in `meta.media_decisions`. Avoid chart streaks and visual sameness across blogs.

Audio gets its own treatment — **BGM is expected on every blog**: the BGM is a **real track presented in a self-hosted, cover-art "now-playing" card** that starts on the reader's first click (the World Cup blog's form), **never an AI-composed track**, placed at the top of the article. (text2music is **SFX only** — an opt-in atmospheric `sound_design` bed for a genuinely un-findable sound — and is **never the front BGM**; see Step 3.) **Every blog opens with a fitting real-sourced track, including sober topics** — match the mood word to the tone: a sober / computational story (economics, elections, public-health stats, finance) gets a **pensive / ambient / minimal / orchestral / nocturne** track (a restrained real bed, NOT silence and NOT a generic upbeat loop), a celebratory / sport story gets **epic / anthem**, a somber story gets **elegy / adagio**. Judge the *flavour* off the shared classifier `../references/topic_profile.json` (`is_visual` / `tags`), then **walk the BGM ladder** (a ladder to climb down, NOT a menu to pick from) to source the fitting track — never drop audio for lack of effort. **FIT FIRST: pick the most recognizable best-FIT real track for the story; license-tier is a tiebreak only among comparably-fitting tracks.** So when the story HAS a signature anthem/track (the official anthem, the profiled artist's song), prefer that real best-fit track (rung 2 below — self-hosted, demo-gated) over a generic *unrelated* license-clean CC0 loop; only when no recognizable track fits the story do you reach for a clean-but-generic mood track. (License-tier still breaks ties between two tracks that fit equally well — a clean fitting track beats a gated equally-fitting one.) The rungs:
1. **`sourced_bgm` — license-clean real track (publishable):** a re-hostable CC0/CC-BY/PD/royalty-free track the Scout found that **fits the story's mood / place / era**, referenced by `media_ref` → its `sct_xx`, self-hosted with its cover art so the now-playing card has a square cover (a representative license-clean image or a designed CSS cover — **never** an AI cover). Take this rung when a clean track genuinely fits; do **not** settle for a generic unrelated CC0 loop in preference to a recognizable signature track the story is about (rung 2).
2. **Copyrighted best-fit real track — self-hosted for the DEMO, publish-gated (PREFERRED when the story has a signature track):** when the song the story is *about* (an official anthem, the profiled artist) is the most-fitting BGM, self-host that real track + its cover for the demo and register it as a **Designer `des_` audio asset with `publish_blocker: true`**, `license: "All Rights Reserved — demo-only"`, and a real `source_url`. A **`publish_note` is MANDATORY on this rung** (the swap target the Auditor/Programmer surface) — `validate.py` Section 8 hard-errors a gated asset with no swap target. Because it is a `des_` publish-blocker (NOT a clean `sct_`), it does **not** pass the `validate.py` license-allowlist gate as clean; the Auditor raises an advisory publish-blocker and the Programmer renders a "demo-only — must license or swap before publishing" credit line (the demo build is flagged, never blocked). This rung is the right default for a story with a recognizable signature track — fit beats license-tier; only fall to rung 1 when no signature track fits and a clean track does.
3. **Embed the official player:** if you can neither source a clean track nor self-host the copyrighted one, surface the real song as an `embed` (the official Spotify/YouTube player carries its own rights).
C. **Classical-recording floor (the GUARANTEED terminal rung):** when no topic-fitting real track (rung 1) and no signature track (rungs 2–3) land, the Scout sources a **license-clean classical RECORDING** (rung C — verify the *recording's* own license, not just the PD composition's) for the same top-of-article spinning-vinyl card. This rung **always succeeds**, so a clean BGM is always reachable.

There is **NO `audio.used:false` / no-BGM outcome on any topic** — BGM is mandatory on every blog, including sober / abstract / `privacy_sensitive` ones, because the rung-C classical-recording floor never fails. A sober economics / elections / public-health-stats / finance story gets a fitting restrained track (pensive / ambient / minimal / orchestral), which is the *right* BGM there, not "tonally-wrong filler"; a **privacy-sensitive** topic gets a quiet, non-intrusive classical recording (rung C), never silence. Whichever rung lands the track, **never autoplay** (start on the reader's first click/tap), and the page always works fully when muted. A copyrighted song the story references may **also** appear as an `embed` "listen ↗" link in context even when the BGM is a rung-1 track — that is separate from the BGM. Place the now-playing card at the very **top of the article** (set its section to the opening) so the reader can start it before reading. The card is the enriched `signature_media_card` — spec it with an **eyebrow** (e.g. "Official anthem") above the title and a **scroll-to-start** behaviour (it begins on the reader's first scroll/click, never autoplay), so the soundtrack reads as a deliberate signature element rather than a bare audio tag. The full ladder, the never-AI-BGM rule, the publish-gate, and the web-weight transcode are in [`references/audio_rules.json`](references/audio_rules.json).

Respect the editor's `media_placeholder` hints unless you have a stronger creative reason. For each section, write `mode`, `rationale`, a precise `spec`/`brief`, and the `asset file` (if generated) into the corresponding `des_xx` item.

> **Figure captions are the Copywriter's, not yours.** The Copywriter (Stage 3.5, just before you) writes the displayed `<figcaption>` for every `des_xx` into `copywriter.json` `items[des_xx].caption` (+ `subtitle`) as a takeaway-title (the chart's conclusion + metric subtitle; a photo's who/what/where + date, then why), and the Programmer renders that verbatim. So you don't author the on-page caption text — keep your `des_xx` `label`/`highlight`/`spec` as the visual's internal description (what to chart, what to annotate), and let the Copywriter's caption be the reader-facing line. Your annotation `spec` still drives the on-chart callout the caption's takeaway points at (pair with `dataviz-craft/references/annotation_layers.json`).

**Live-status, if any, is a compact dated badge — never a list.** If the Scout passed `live_status[]` (the "since the snapshot" freshness), present it as a **compact, dated summary** — e.g. a small "as of <date>" badge with a count + the **single latest result** — display-only, alongside the data it relates to. Do **not** render a long list of many specific forward-dated results: that reads like new data and confuses the dataset snapshot the story is built on. (Shared with the Editor work-stream; topic-agnostic.)

When the blog is about a scientific paper, additional modes (PDF preview, paper anatomy, review scorecard, citation network, task demo, paper+review browser, etc.) are available in [`references/visual_modes.json`](references/visual_modes.json) → `science_paper_modes`.

## Step 3: Generate Assets When Selected

Generated assets follow from the media decisions. **Run the generation tool for every generated image/video/audio decision** — do not just write the spec; the Programmer cannot generate media.

**Real-subject rule — fetch, don't fake.** NEVER AI-generate a specific, real, identifiable subject: a real trophy (e.g. the FIFA World Cup trophy), a named stadium or landmark, a real person, an official logo/crest. Generative models get recognizable details wrong (a fake trophy reads as obviously wrong). For any such subject use a REAL image — **prefer the Scout's verified `scout_*` assets** (already license- and identity-checked in `scout.json`), then the Detective's `ref_*`/flag/logo assets, or fetch one from Wikimedia/Commons via the Detective's helper (`../detective/scripts/fetch_images.py`). When you place a `scout_*` asset (or any asset that has an `sct_xx` record), set the item's `media_ref` to that `sct_xx` (see [`references/field_rules.json`](references/field_rules.json)) so the Programmer tags `data-sct` and the contract gate can check its license + identity. If no freely-licensed real image exists, pick a different real subject or an honestly-abstract treatment — do NOT fake the specific object. Reserve text2image / text2video / image2video for genuinely ABSTRACT or ATMOSPHERIC visuals with no specific real referent (mood scenes, textures, metaphors), and caption them as illustration.

**Generation fallbacks — the scripts degrade, never drop.** The media-generation scripts no longer fail hard on an API refusal/timeout: they retry, swap to a fallback model, and print `FALLBACK_USED=<rung>`. For `image2video`, if every rung fails they write a static `_poster` still instead of a video and print `VIDEO_UNUSED=1` + `POSTER_PATH=<path>` (rather than mislabel a still as `.mp4`). When you see these signals: reference the poster as the hero's `data-des` image (the hero is preserved as a still, not a video), set the video channel `used:false` with the reason, and transcribe any printed `media_blocker: {…}` line into `meta.media_blockers`. A fallback is a recorded outcome — never a silent missing asset.

**Never feed a recognizable real public figure to Veo/Wan.** `google/veo-3.1-fast` (image2video/text2video) and `alibaba/wan-2.7` **deterministically refuse** to animate a recognizable real public figure (refusal code 15236754 — not a retriable, retry-clearable refusal). A real person in the hero (or anywhere) must be a **FETCHED verified still** (Scout `sct_` / Detective `ref_`), **never a GENERATED face** (and never a faked one) — but **animating that fetched real photo IS sanctioned** when it goes through the faces-safe model: a **subtle cinemagraph via Kling v3 std** (face held enough to avoid warping, subtle natural motion allowed on top of moving atmosphere) **with proportionate disclosure**, plus a **warp-quality guard** (reject a distorting candidate → ffmpeg Ken-Burns on the same still). So motion on a real-person beat is fine via the held cinemagraph; what stays banned is routing the real face through *Veo/Wan* (they refuse) or *generating* the face. If **no usable fetched photo** of the person exists, do **not** generate the person — neither a photoreal fake **nor an illustration of the person** — take a **theme-first NON-PERSON cover** instead (real scene/object → data-as-cover → conceptual/typographic → abstract atmospheric, in that order; honor `topic_profile.ai_face_policy.no_photo_fallback` and `../hero/SKILL.md`). (See the project memory note `veo_celebrity_face_block`: Veo hard-blocks celebrity faces; the documented fallback when Kling is unavailable/distorts is ffmpeg Ken-Burns on the same fetched still, not generating the face.) Verify each file (`ls -la PROJECT_DIR/assets/`). Match the richness targets in [`../../frontend-design-pro/references/media_presentation.json`](../../frontend-design-pro/references/media_presentation.json) (`richness_targets`) — these are **soft targets, not quotas**: e.g. a visual/place/sport story should aim for **around 4-6 images** (prefer the Detective's real fetched photos over generic AI fills), **plus a video** (a `text2video` scene or an `image2video`-animated still — at least animate the teaser), **plus audio**, on top of charts and an interactive/map. Because the cinematic scroll background is mandatory, **every topic — including an abstract/computational one — sources a relevant real-image set** to back it: an economics / elections / public-health story is backed by **relevant real historical / archival / atmospheric photos** (real factory / loom / worker / steam-engine / trading-floor photos for an industrial-revolution piece; polling-station / ballot-box archival photos for an elections piece — abundant in the public domain and on Commons). What stays banned is **purely-decorative stock that says nothing** about the story (a random smiling-businessperson stock photo on an inflation piece) — the Critic penalizes decorative stock — and **AI-generating a specific real subject**. A relevant historical/archival real photo is NOT decorative: it is exactly the real-image set the cinematic needs. On such an abstract/computational topic, also read the positive playbook [`../../frontend-design-pro/references/abstract_excellence.json`](../../frontend-design-pro/references/abstract_excellence.json). Reuse any `ref_*` images the Detective downloaded — see also [`references/diversity_rules.json`](references/diversity_rules.json) (`asset_volume_by_dataset`, `before_generating`).

**Register every reused photo you intend for downstream use as a `des_` item — with a COMPLETE license + identity block.** Any Detective `ref_*` / `reference_media` photo (or any Scout `scout_*`) you want used downstream — as a **section image** OR as a **Cinematographer background** — MUST be registered as a `des_` item in `items`, with `type:"asset"` and `tool:"wikimedia_download"` (for a Detective `ref_`, copy its `source_url`/`filename` verbatim) or `tool:"scout_fetch"` + `media_ref` → its `sct_xx` (for a Scout asset). Registering it gives the photo a `des_` id, so the Programmer tags it `data-des` and it **resolves in the cinematic contract gate**.

**Image-richness target (cinematic) — MANDATORY on every topic (a SOFT count, not a quota).** The cinematic scroll background is a mandatory stage, so **every topic registers a real-image set** to back it — aim for **≥5 (around 5–6) registered, verified, license-clean landscape (cover-able) backgrounds** (`sct_`/`des_` items with complete license + identity blocks). Under-supply (1 ≤ cover-able count < 5) is **sent back to source ≥5 first** (the cinematic-supply gate, mirrored in `validate.py`), not accepted as a finished state — on **every** topic, including an abstract/computational one (which gets the relevant historical/archival real photos described above). Reuse every fitting Detective `ref_*` and Scout `scout_*` real photo (registered per the rule above) plus the Scout's atmospheric `fetch_stock.py` landscape shots, rather than leaning on one hero image. Counting these is the Designer team's job at the `cin_`→`des_` handoff (see Team coordination): if the Scout under-supplied, ask it for more cover-able shots before the Cinematographer runs. **Never scrape purely-decorative stock that says nothing about the story just to clear 5** — every background must pass the S2/S4 contract AND be **relevant** (a relevant historical/archival real photo backing the cinematic is not decorative). Only if relevant real images genuinely cannot be found does the Cinematographer fall back to generative/data-driven backgrounds — but source the real set hard first.

SHARED CONTRACT (S2 — the manifest block): every photo you route downstream carries a **complete, no-half-filled** provenance block — `{id, file, source_url, site, license:{spdx, permits_republication, requires_attribution, attribution_text}, identity:{method, verified, subject}}`. The `license.attribution_text` must be **non-empty** (the exact credit line that will be rendered in the References footer), and for any **real subject** (a real person/trophy/venue/landmark) `identity.verified` must be **`true`** with the `identity.subject` named. A photo whose license or identity block is incomplete (empty `attribution_text`, or a real subject with `identity.verified` unset/false) is **NOT eligible to be routed downstream** — finish the block or drop the photo. (For a Scout `sct_` asset this block already lives in `scout.json`; carry the `media_ref` → `sct_xx` so the Programmer/gate reads it from there rather than re-keying it.)

SHARED CONTRACT (S4 — registration-first): the Cinematographer picks backgrounds **only from registered `sct_`/`des_` ids** (`background.media_ref` must resolve to a Scout `sct_xx` or a Designer `des_xx`); a background with no resolvable verified source is a hard error there. **Every cinematic background must already be a registered `des_`/`sct_` item in `designer.json`/`scout.json` BEFORE the Cinematographer references it via `media_ref`.** The side `cinematic_imagery_manifest.json` is **only a scratchpad — it is NOT the registry**; resolving a background against that scratchpad instead of the role-JSON registry is the dangling-ref bug class that gave the gold blog 30 errors. So an unregistered raw `ref_` photo handed to the Cinematographer cannot resolve — your job is to **register every photo you route downstream BEFORE it is referenced** (don't leave a Detective `ref_` floating as a background with no `des_` record, and don't rely on the manifest scratchpad to stand in for registration).

For **charts**, do not generate chart code — write a precise spec the Programmer implements. For **interactive demos**, write a step-by-step interaction spec. Both spec shapes are in [`references/visual_modes.json`](references/visual_modes.json).

## Step 3.5: Make every asset web-weight (before designer.json)

Media handed to the Programmer must be **web-weight by default** — a heavy page kills both performance and reach (virality happens on mobile). Before you finalize `designer.json`, run the helper on your assets folder:

```
python3 SKILL_DIR/scripts/optimize_assets.py PROJECT_DIR/assets/
```

> **Needs `ffmpeg`** for audio/video — on PATH, or `pip install imageio-ffmpeg` (a self-contained wheel, no system install needed). Images need `Pillow`.

It transcodes/recompresses each asset in place to a streaming-friendly copy (suffix `_web`) and prints `original -> optimized` bytes; it uses `ffmpeg`/`Pillow` if available and **skips gracefully with a warning if a tool is missing** (it never crashes and never deletes the original). When an asset is optimized, **reference the optimized `_web` copy** from the item's `filename` (and from `media_ref` resolution) — not the heavy original. The helper does this repointing for you: when it writes a `_web` copy it **automatically rewrites the owning `scout.json`/`designer.json` item's `filename` to the `_web` copy** (so when the Auditor later removes the heavy original, validate.py's media gate still resolves). It prints which items it repointed; if you run it BEFORE writing `designer.json`, still set new designer items' `filename` to the `_web` copy yourself, and re-running after `designer.json` exists will repoint any item that still names an original.

**Budgets (general, shared with the Auditor weight-gate):** audio **< 3 MB**, single image **< 600 KB**, video **< 3 MB**. These are the defaults for any topic.
- A big Scout BGM (e.g. a multi-MB WAV/FLAC `scout_*` track) MUST be transcoded to ~128 kbps mp3/opus before you reference it — point the audio item's `filename` at the optimized copy (keep its `media_ref` → the Scout `sct_xx` for license/identity).
- Any image over the budget gets resized to display width and saved as webp/jpg.
- If the helper skipped a file because a codec was unavailable, record that the original is over budget (so the Auditor's weight-gate catches it) rather than silently shipping the heavy file.

This is a default, not a checklist tick: pick the smallest copy that still looks/sounds right for the page.

## Step 4: Page Visual Rhythm

Pick a concrete theme from [`../../frontend-design-pro/references/themes.json`](../../frontend-design-pro/references/themes.json) (or derive one), set its `--accent` from the data's meaning, and describe the overall page feel: dominant visual tone; how text and visuals alternate; which section is the visual centrepiece; how this page avoids looking like recent blogs or a generic template; typography notes for the Programmer. For the type, pick a concrete pairing from [`../../frontend-design-pro/references/typography.json`](../../frontend-design-pro/references/typography.json) (a `file://`-safe stack, not a default) and choose the finish details from [`../../frontend-design-pro/references/finish.json`](../../frontend-design-pro/references/finish.json). For motion intent — what should reveal, animate, or respond, and with what feel — read [`../../frontend-design-pro/references/motion.json`](../../frontend-design-pro/references/motion.json) and record the intended choreography for the Programmer. Record the chosen theme + accent + type pairing and these notes in `page_rhythm` so the Programmer applies them consistently.

Be aware of [`../../frontend-design-pro/references/quality_rubric.json`](../../frontend-design-pro/references/quality_rubric.json) throughout — it is the bar this design will be scored against, so make choices that clear it.

## Output

Write `PROJECT_DIR/designer.json` — the single output.

- **[`references/schema.json`](references/schema.json)** — the full structure (`meta.media_strategy`, `meta.media_decisions`, `meta.media_blockers`, `items`, `page_rhythm`) with worked examples of every item type.
- **[`references/field_rules.json`](references/field_rules.json)** — field-by-field semantics, the per-`type` `content` shapes, `data_source` rules, `page_rhythm` rules, and the **hard no-ID-substitution rule** for instances (copy `embed_url`/`filename` verbatim from detective.json).

**`scout.`-prefixed `data_source` grammar (gate-checked).** When an item's `data_source` points at a scouted source, only two forms resolve at the contract gate: `data_source: "scout.<sct_id>"` — the suffix (or `sct_` + the suffix) MUST name a registered `sct_` item in `scout.json` — or `data_source: "scout.live_status"`, valid only when `scout.json` carries a non-empty `live_status` list. Any other free-text `scout.<…>` now hard-errors (`des_data_source_scout_dangling`), so name a real `sct_` id (or use `scout.live_status`) rather than an ad-hoc string.

Done when a Programmer can read `designer.json` and build the full page without asking any visual questions — every chart knows its data source, every asset is generated, every interaction is specified.

## Team coordination — Designer team

You are the **lead of the Designer team**. Your members are the **Interaction Engineer** (the one narrative-bound centerpiece), the **Hero** (the animated cover), and the **Cinematographer** (the mandatory cinematic scroll background). You set the visual brief; they execute the centerpiece, the animated hero, and the cinematic layer against it; you integrate the result. You still record `content.hero_strategy` (the chosen rung + why), but the **Hero role OWNS rendering the animated hero** — you brief it, it builds it.

- **Member calls (mirrored from the orchestrator), in order, after your `designer.json` is complete:**
  - `Skill interaction PROJECT_DIR` — Stage 4.5, designs the centerpiece (consumes the Analyst `client_model`; outputs `interaction.json`, `int_` ids).
  - `Skill hero PROJECT_DIR` — Stage 4.6, renders the animated cover (walks the auto-select source ladder off your `hero_strategy`; outputs `hero.json` + `assets/teaser.webm`/`_web.mp4`/`.jpg` poster; reuses `data-des="des_hero_video"`). An animated cover is the DEFAULT; a static `<img>` is only its recorded fallback.
  - `Skill cinematographer PROJECT_DIR` — Stage 4.7, mandatory cinematic background (outputs `cinematographer.json`, `cin_` scenes from the topic's real-image set; consumes the hero as `cin_00`).
  All read `designer.json`, so finish and save it first. The Hero runs after the Interaction Engineer (it may render the backdrop behind an interactive teaser); the Cinematographer runs last (it reads `interaction.json` and stages the hero as `cin_00`).
- **The media brief.** In `designer.json` (its `page_rhythm` / `meta`), flag the two things your members need: (1) **which section is the centerpiece candidate** — the contestable finding the reader should *produce* — so the Interaction Engineer knows where to put the one explorable; and (2) **which verified `sct_`/`des_` assets** are available to stage as cinematic scene backgrounds (the cinematic is mandatory on every topic — including abstract/statistical ones, which are backed by the relevant historical/archival real photos), so the Cinematographer builds only from license-checked sources.
- **You OWN the `cin_`→`des_` registration handoff.** The Cinematographer picks backgrounds **only from registered `sct_`/`des_` ids**; an unregistered raw `ref_` photo cannot resolve in `validate.py` Section 6. So for **every photo a cinematic scene needs**, register it as a `des_` item (per "Register every reused photo…" in Step 3 — `type:"asset"`, with `tool:"wikimedia_download"` for a Detective `ref_`, or `tool:"scout_fetch"` + `media_ref` → its `sct_xx` for a Scout asset) so every `cin_*.background.media_ref` resolves and the Programmer can tag `data-des`/`data-sct`. Don't leave a background pointing at a floating photo with no `des_` record.
- **The media-purpose gate is the Designer team's exit gate.** Before the team hands off to the Programmer, every item (yours, plus anything your members rely on) must carry a `purpose` (INFORM or IMMERSE); decoration is cut. The orchestrator runs this gate at the team boundary.

This is coordination prose, not a new gate: your `des_xx` ids, the per-`type` `content` shapes, `data_source` rules, and the registration mechanics in Step 3 are all unchanged.

