← JuicyLucy AdsCONTENT HISTORYWHAT CHANGED · RULE-BASED ANALYSIS
Update to JuicyLucy Ads
Snapshot Sep 30, 2026 · 23:16 UTC · version 0.22.1
Collection source: not recorded for this historical snapshot.
First saved snapshot
No earlier snapshot is available to establish a change.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"description": "Produce paid-media ad creative (Meta / Instagram / TikTok feed, Reels, Stories) — either a new ad from a brief or, more often, a variation of a reference creative that already works ('copy this ad, change X'). Use when the deliverable is an AD that will be uploaded to an ads manager and measured: performance creative, hook tests, before/after, offer ads, UGC-style spots. Creative is generated — the first frame through the image tool, the motion through the `juicy` command — never captured from a website; copy runs through a Meta-compliance gate; exports carry the Drive-to-Meta filename convention. For a brand launch film or product promo from a website URL use /product-launch-video instead. Formerly /ad-production.",
"included_files": [
{
"relative_path": "blueprints-index.md",
"size_in_bytes": 4824
},
{
"relative_path": "blueprints/background-video-text-overlay.md",
"size_in_bytes": 19462
},
{
"relative_path": "blueprints/clapping-reaction.md",
"size_in_bytes": 23026
},
{
"relative_path": "references/ad-copy.md",
"size_in_bytes": 11490
},
{
"relative_path": "references/ad-library.md",
"size_in_bytes": 10821
},
{
"relative_path": "references/capabilities.md",
"size_in_bytes": 11981
},
{
"relative_path": "references/defect-gate.md",
"size_in_bytes": 16049
},
{
"relative_path": "references/generation.md",
"size_in_bytes": 16952
},
{
"relative_path": "references/naming.md",
"size_in_bytes": 7928
},
{
"relative_path": "references/outro.md",
"size_in_bytes": 5280
},
{
"relative_path": "references/reference-iteration.md",
"size_in_bytes": 27141
},
{
"relative_path": "references/reference-manifest.md",
"size_in_bytes": 7481
},
{
"relative_path": "scripts/ad-library.mjs",
"size_in_bytes": 26343
},
{
"relative_path": "scripts/blueprint-styles.mjs",
"size_in_bytes": 632
},
{
"relative_path": "scripts/check-performance.mjs",
"size_in_bytes": 11698
},
{
"relative_path": "scripts/lint-blueprints.mjs",
"size_in_bytes": 6784
},
{
"relative_path": "scripts/reference-manifest.mjs",
"size_in_bytes": 14757
}
],
"name": "video-ad-production",
"skill_md_contents": "---\nname: video-ad-production\ndescription: \"Produce paid-media ad creative (Meta / Instagram / TikTok feed, Reels, Stories) — either a new ad from a brief or, more often, a variation of a reference creative that already works ('copy this ad, change X'). Use when the deliverable is an AD that will be uploaded to an ads manager and measured: performance creative, hook tests, before/after, offer ads, UGC-style spots. Creative is generated — the first frame through the image tool, the motion through the `juicy` command — never captured from a website; copy runs through a Meta-compliance gate; exports carry the Drive-to-Meta filename convention. For a brand launch film or product promo from a website URL use /product-launch-video instead. Formerly /ad-production.\"\n---\n\n> **JuicyLucy's own skill**, not upstream HyperFrames'. `README.md` § How upstream gets in covers\n> how this skill stays wired into the router across upstream syncs.\n\n# Video Ad Production\n\n> **This plugin ships the ad path only.** `/product-launch-video` is named below as where\n> non-ad work belongs; it is not installed here. Point the user at the HyperFrames plugin\n> rather than trying to load it.\n\n> **This runs in Codex on a Mac.** If there is no local shell here — the request came from\n> ChatGPT on the web or on a phone — say that ad production renders on the user's own Mac\n> inside Codex, point them there, and stop. Do not plan, write copy, or generate anything\n> from a surface that cannot render the result.\n\nTurn a product and an offer — or a reference creative and a list of changes — into paid-media ad\nvariants, cut to a platform spec and named so an ads-manager report can be traced back to the exact\nhook, blueprint, and generation seed that produced it.\n\n## What makes this different from a launch video\n\n`/product-launch-video` produces **one film**. This produces **measurable creative**.\n\n| Launch film | Paid-media ad |\n| ------------------------------ | ------------------------------------------------- |\n| One deliverable, approved once | Variants shipped together, judged by the platform |\n| Story arc across 30–90s | Hook on frame 0, payoff by 3s, 6–15s total |\n| Assets captured from the site | Assets **generated** — first frame, then video |\n| Sound design carries emotion | Read sound-off, **never shipped silent** |\n| Copy is brand voice | Copy passes a **Meta compliance gate** |\n| Filename is `video.mp4` | Filename is the ad's primary key |\n| Success = the user approves | Success = the next round beats the last |\n\nIf the ask is a brand film, a site tour, or a promo not going into paid distribution, stop and use\n`/product-launch-video`.\n\n## Before Step 0: is this machine set up?\n\nEverything this skill does runs through tools the `juicylucy-setup` skill installs — the renderer\nthat `hyperframes init` already needs at Step 0, the encoder, the `juicy` generation command — and\na machine that has never run setup has none of them. So **the first command of every run**, before\na reference is opened or a brief is asked about, is the setup skill's doctor in preflight mode:\n\n```bash\nsh <SKILL_DIR>/../juicylucy-setup/scripts/doctor.sh --preflight\n```\n\n`<SKILL_DIR>` is this skill's own directory, and the setup skill sits beside it in the plugin's\n`skills/` folder. (A local copy of this skill under `~/.agents/skills` has no such sibling; run the\nshipped doctor from the plugin cache, `~/.codex/plugins/cache/juicylucy/juicylucy-ads/*/skills/juicylucy-setup`.)\nThe doctor is read-only, takes a few seconds, reaches no network, and prints one line per\nrequirement, ending in a `preflight` line that is the verdict:\n\n- **`preflight ok`** (exit 0) — the toolchain is installed and reachable. Go on to § Pick the\n path first. If another line reads `missing` — `juicy-login`, a `config` or `network` line, a\n newer `juicy` — say so in one sentence so the user can have it fixed before Step 3 needs it, and\n continue; none of it blocks the brief.\n- **`preflight missing`** (a non-zero exit) — setup has not been run on this machine, or was not\n run to the end. **Do not start the ad.** Load the `juicylucy-setup` skill and run it from its\n Step 1 through to its restart: it diagnoses, explains what is missing in plain words, asks before\n installing, and ends on one quit-and-reopen of Codex. Tell the user the ad starts when they ask\n for it again after that restart — nothing from this run needs carrying over, because nothing has\n been written yet.\n\nRunning the doctor is not a question and not a stop (§ One gate, and nothing else asks). Do not\nsubstitute a check of your own — `which hyperframes`, whether `~/.juicylucy` exists — for it: the\ndoctor knows where setup puts things and which renderer version these skills were written against.\n\n## Pick the path first\n\nTwo shapes arrive, and they run differently:\n\n- **Variation from a reference** — \"copy this ad and change X\", a reference video plus edits. This\n is the common case. **Read `references/reference-iteration.md` before anything else**: read the\n reference through on a time axis and write `REFERENCE.md` before planning anything (§ Watch it\n before you plan it), everything the user did not name as a change is a thing to preserve —\n **the performance arc first** — the variant changes **one or two visual attributes and no more**\n (§ How close is close enough), and the reference's ending decides the outro. If the reference is a Meta Ads Library link rather than a file, get it onto disk first —\n `references/ad-library.md` — and register it in the reference manifest, which is Step 0's hard\n gate (`references/reference-manifest.md`). **A video ad is iterated from a video ad**: a\n screenshot, a poster frame, or the same advertiser's static ad is not a reference, and the gate\n rejects one.\n- **New ad from a brief** — no reference. Runs the full path below, and is the only case that gets a\n brand outro by default.\n\n## Audio is not optional\n\n**Every ad this skill ships carries a real soundtrack.** \"Works with sound off\" is a claim about the\n**overlay copy** — the argument has to land for a muted viewer — and it is never permission to\ndeliver a silent file. A muxed silent AAC track satisfies the container and fails the ad.\n\nWhere the audio comes from, in this order:\n\n1. **There is a reference → copy its audio, unmodified.** Both live blueprints are cut to a track,\n usually a recognisable song, and that track is part of why the reference works. Same track, same\n in-point, full duration — see `references/reference-iteration.md` § Reuse the reference's\n soundtrack.\n2. **The user asked for something else → do that.** \"Swap the song\", \"use an upbeat track\", \"no\n music at all\" all outrank the default. An explicit instruction is the only thing that changes an\n ad's audio.\n3. **No reference and no instruction → generate a music bed** for the full duration —\n `references/generation.md` § `cassetteai/music-generator`.\n\nA fade-out or a gain change on a **generated** bed is `/hyperframes-audio`'s job: it owns fades,\ntrack gain and ducking on audio already placed in the composition. A **preserved reference track is\nnever** ducked, re-timed or processed — `references/reference-iteration.md` § Reuse the reference's\nsoundtrack forbids it, and the audio skill's tools do not change that.\n\n**No TTS, no voiceover, no dubbing on either live blueprint.** Their audio is a song; the only\nspeech in it is song lyrics, and **lyrics stay in their original language** even when the ad is\nlocalised into a market that does not speak it. A Spanish text block over an English-language track\nis a correct localisation, not a half-finished one.\n\n## Workflow\n\nStep 3 is the one user gate. Nothing else is.\n\n```\nfirst preflight → doctor.sh --preflight; the setup skill first when it fails\nStep 0 brief → hyperframes init <campaign>, then AD_BRIEF.md\nStep 1 blueprint → the ad type is chosen and its file is read\nStep 2 copy → COPY.md, through the compliance gate\nStep 3 generate → .media/ + .media/manifest.jsonl\nStep 4 compose → index.html, one composition, variants as variables\nStep 5 validate → the defect gate, then the Studio preview handed to the user\nStep 6 name+render→ export-naming.json + variants.json + renders/\n```\n\n### One gate, and nothing else asks\n\nA stop is required only when the next step spends significant money (paid generation: motion, or a\nprovider image batch), significant time (a batch of many generations or localizations), or writes\noutside the workspace irreversibly. Everything else: decide, do, report what you did.\n\nThat one gate is the **whole** of what the user decides during a run. Everything else is you\nexecuting a plan they can see in the brief, the copy matrix and the editor, and it runs without\nchecking back. A gate is a review, never a permission prompt:\n\n| Gate | What they are looking at | The decision |\n| ---------- | ---------------------------- | ------------------------------------------------------------------- |\n| **Step 3** | The first frames, as one set | Whether the look is right — before any of it is paid for as motion. |\n\n**Do not stop anywhere else.** Specifically, none of these is a question:\n\n- **Running the setup doctor before Step 0.** Read-only, a few seconds, and its verdict alone\n decides whether the setup skill runs first (§ Before Step 0).\n- **Choosing the blueprint (Step 1).** Name it in one line and go on; the choice is visible in the\n brief and the copy matrix, and the user redirects at Step 2 if it is wrong.\n- **The copy matrix (Step 2).** Write it, run the compliance gate, record the verdict, and go on.\n Clean copy needs no approval; risky copy gets named in the closing reply with a compliant\n alternative; and the user changes any line in the editor after the ad is built, before it is\n localised. Copy is never what a run waits on.\n- **Sending a prompt or an image to the image tool, or running `juicy`.** The plan is the brief's and\n the review is Step 3's batch; a per-call confirmation re-asks a settled question and teaches the user to approve without\n reading. Generate the whole set, then show the whole set — that batch *is* the Step 3 gate.\n- **Moving to the next step with nothing to show.** A step that produced nothing for the user to\n look at has nothing to review. Go on and say what you did.\n- **Reading, listing, or probing anything inside the project** — a reference with `ffprobe`, the\n manifest, a snapshot, `.media/`. All reversible, all invisible in the output.\n- **Re-running a gate after a fix.** The fix loop is capped at 2 rounds precisely so it does not\n need supervising.\n- **Handing over the editor (Step 5) and rendering (Step 6).** The preview URL is the final\n presentation, not an approval: give it, render, and put both in the closing reply. Rendering costs\n minutes, not money, and what the user changes in the editor afterwards is a new render.\n\nIf the runtime raises its own approval prompt for one of these — a project folder that happens to\nlive inside Google Drive or iCloud, a command it wants confirmed — that is the sandbox asking, and\nthe answer is to approve it and continue. Do not forward it to the user as though it were a creative\ndecision, and do not stop the run on it.\n\nThe failure this prevents is the one that actually happened: a user asked to approve things they had\nno basis to judge, until the real gates were indistinguishable from the noise around them.\n\n**Offers ride inside the gate.** What the plugin can add beyond the blueprint's own recipe — a beat\ngrid, a sound mark under the offer, a draft on a shareable link — is listed in\n`references/capabilities.md`, one row per capability with the signal that earns it a mention and\nwhere it is offered. Every applicable offer is one or two lines folded into the Step 3 gate's ask,\nmade once and answered with the gate's reply; it is never a stop of its own. A capability that only\nmatters after the gate — a shareable link for someone who is not at this Mac — is named in the\nclosing reply, not asked. Write accepted ones to `AD_BRIEF.md` under `## Extras` as they are\naccepted.\n\n### The campaign directory IS the HyperFrames project\n\nCreate it with `hyperframes init` and put everything inside it:\n\n```\nvideos/ads/<campaign>/ ← hyperframes project root\n├── index.html the composition\n├── hyperframes.json\n├── .media/ frozen assets — INSIDE the root, deliberately\n├── AD_BRIEF.md · COPY.md\n├── variants.json one row per variant\n└── renders/\n```\n\n**Media must live under the project root.** Compositions are served with the\nproject root as their base URL, so every asset path is root-relative\n(`.media/video/hook-a.mp4`) and a path that climbs out with `../` fails\n`invalid_parent_traversal_in_asset_path` — plus `missing_local_asset` and\n`audio_src_not_found`, because the file genuinely is not in the project.\n\nA nested project (`<campaign>/hf/` beside `<campaign>/.media/`) puts the media\none level out of reach and is unfixable without moving one of them. `init`\nrefuses a directory that already has files in it, so **create the project first\nand write the brief into it** — not the other way round.\n\n## Never leave the framework\n\n**Every ad this skill ships is rendered by HyperFrames.** It is the only route,\nnot the preferred one, and there is no fallback path to a hand-assembled file.\n\nIf the render path is blocked — a composition that will not validate, a tool\nthat will not run, an asset the compiler rejects — **stop and report the\nblocker.** A run that ends at \"Step 4 is blocked because X, here is what I\ntried\" is a good outcome. Assembling the deliverable another way is not, even\nwhen the result looks right.\n\nAn ffmpeg pipeline that burns text and muxes audio can produce four plausible\nmp4s in a minute. What it cannot produce is anything the rest of this skill\noperates on: no composition to lint, nothing for `check` to open, no snapshots,\nno seeds joined to a copy row, no export naming. The gates do not fail — they\nhave nothing to inspect, so the ad ships untraceable and silently outside\nevery rule the blueprints encode. This has happened; it is why the rule is here.\n\nRun ffmpeg and ffprobe freely as **instruments** — probing a reference, dumping\na contact sheet, pulling a frame to edit, checking a render is not silent. Never\nas the renderer.\n\n---\n\n## Step 0: Brief\n\nCreate the project, then write the brief into it:\n\n```bash\ncd videos/ads && hyperframes init <campaign> \\\n --non-interactive --example blank --resolution portrait --skill video-ad-production\n```\n\nUse `--resolution portrait` for `9x16`, `square` for `1x1`; a `4x5` feed ad starts from\n`portrait` and sets its own `data-width` / `data-height`. Everything from here happens\ninside that directory.\n\n### Resolve the brand\n\n**This skill knows no brands.** It carries no product facts, no palette, no claims —\nthose belong to whichever brand the ad is for, and each one ships as its own skill,\nnamed `brand-<slug>`. Resolve it before writing the brief, because almost every field\nbelow depends on it:\n\n- **Exactly one `brand-*` skill available** → that is the brand. Load it and proceed,\n naming it in your reply so the choice is visible rather than assumed.\n- **Several** → list them and ask which.\n- **None** → say plainly that no brand is installed, then create one before going on:\n the `juicylucy-setup` skill's `extending.md` § Creating the first brand — ask for the\n product facts, write them into a `brand-<slug>` skill in the user's own skill folder\n from the template there, and read that skill for the rest of this run. Do not\n build from facts that live only in the conversation: the next ad would start from\n nothing again.\n\n**Never adopt the branding visible in a reference ad.** A reference is usually a\ncompetitor's, and its name, logo, offer and landing page belong to whoever made it —\nusing them is a legal problem, not a fallback (`references/reference-iteration.md`\n§ Build it as your own). \"I could not find a brand\" is a reason to ask, never a reason\nto become the reference.\n\nThe resolved brand owns product truth, the claims that may not be made, the palette and\ntype, and the end card. Read it before Step 2, not during.\n\nGet to a written `AD_BRIEF.md` before anything is generated. Generation costs money per attempt, so\nthe brief is the cheapest place to be wrong. Ask only what is missing.\n\n| Field | Meaning |\n| ------------------------ | ------------------------------------------------------------------------- |\n| `campaign` | kebab-case campaign id — the directory name |\n| `brand` / `product` | the resolved brand skill and what it sells — see § Resolve the brand |\n| `offer` | the commercial ask (discount, trial, bundle, none) |\n| `audience` | specific enough to change the copy |\n| `platform` / `placement` | `meta` / `tiktok`; `reels` / `feed` / `stories` |\n| `aspect` | derived from placement — Reels & Stories `9x16`, feed `4x5`, square `1x1` |\n| `duration` | seconds; 6–15 unless the brief argues otherwise |\n| `blueprint` | the ad type, or blank and decided at Step 1 |\n| `variants` | how many ship this round |\n| `reference` | path or URL of the reference creative, or `none` |\n| `claims` | what may and may not be said, verbatim |\n| `sound` | `reference` (default with a reference), `music`, or `vo` — never silent |\n\nFor a preservation brief, add two explicit lists under `## Changes` and `## Preserves`, and name\nwhich **one or two visual attributes** move — subject, wardrobe, setting, role read, framing,\npalette, props. Everything else on that list is preserved, whether or not the user mentioned it.\nSee `references/reference-iteration.md` § How close is close enough.\n\n**Gate:** every field filled or marked `n/a`; the claims constraint read back verbatim; for a\npreservation brief, both lists written down with **at most two visual attributes** in the change\nlist; a `REFERENCE.md` written for each reference, its `## Timeline` carrying timestamps and its\n`## Ad craft` naming the emotional arc or stating there is none (`references/reference-iteration.md`\n§ Watch it before you plan it); and every reference **registered and verified** — a library link is\nnot a reference until it is a playable local video with its source id recorded:\n\n```bash\nnode <SKILL_DIR>/scripts/reference-manifest.mjs verify --project . --min <concepts>\n```\n\nThat command must exit 0 before anything is generated. It fails on a citation with no file, a still\nstanding in for a video, a missing source id, an unplayable file, and on collated duplicates filling\nconcept slots. `references/reference-manifest.md` owns the rule; `references/ad-library.md` is how\nthe files arrive. A brief with `reference: none` skips it.\n\n---\n\n## Step 1: Choose the ad type\n\nRead `blueprints-index.md`, then read `blueprints/<id>.md` in full. The blueprint owns the shot\nstructure, the overlay contract, the generation prompts, and the variant axes — do not improvise\nthose here. With a reference, the reference selects the blueprint and usually pins several axes as\nheld constant.\n\n**Gate:** one blueprint id recorded in `AD_BRIEF.md`, and its file read. State the choice in one\nline and continue — this is not a stop.\n\n---\n\n## Step 2: Copy\n\nRead `references/ad-copy.md`, then write `COPY.md` — one row per variant filling the blueprint's\ncopy slots, plus a `rationale` column naming which hook shape each row tests. That column is what\nmakes the next round's results readable.\n\n**The compliance gate never stops the run** (`ad-copy.md` § When this gate blocks a line, and when\nit only reports one). When you are authoring the copy, a line that breaks a rule is fixed before it\nships and the fix recorded. When the user supplied the copy verbatim, it is built as given: that\nbranch outranks every rule, the brand's compliance overlay and its hard rules included. In both\ncases the closing reply names every remaining risk — the platform pattern, the brand rule, the\nproduct-truth conflict — with a compliant alternative for each. Never silently rewrite the user's\nwords; never silently ship a known-rejected pattern.\n\nThis is the last free step — after it, attempts cost money — but it is not a stop. Copy that is\nclean, already approved, or the user's own needs no sign-off, and any line can be changed in the\neditor after the ad is built and before it is localised.\n\n**Gate:** one complete row per variant, every slot within its character limit, compliance gate run\nand its verdict recorded — then continue.\n\n---\n\n## Step 3: Generate the creative\n\nRead `references/generation.md`. **First frame, then video** — never text-to-video directly. The\nfirst frames come from the image tool when it is available, the motion always from `juicy`\n(`generation.md` § Which tool makes which asset, and § Calling `juicy` for the commands).\n\nGenerate every variant's first frame without pausing, then **show the user the whole set at once and\nwait** — this is the Step 3 gate. Motion is the expensive half, so a look that is wrong is far\ncheaper to catch here than after it moves. One batch, one review: never a frame at a time, and never\na permission prompt per generation call.\n\n**On a variation, the first frame is an edit of the reference's own frame**, not a fresh render from\na description of it — `references/reference-iteration.md` § Start from the reference's own frame.\nThat is what holds the five attributes you are not changing.\n\n**A first frame has no text and no place for text.** Negative space is a quiet region of the scene,\nnever a drawn box waiting for copy: the plate and the copy are composition layers, laid over the\nfootage at Step 4 from the `COPY.md` variables. A frame that comes back carrying a blank plate, an\nempty banner or sign, a sticker outline, or any on-image text fails this gate before the user sees\nit — regenerate, and never show it with a promise that the approved copy will fill it later.\n`references/generation.md` § The footage carries no text and no place for text.\n\n`juicy` freezes every asset it makes under `.media/` and writes its `.media/manifest.jsonl` record\n— model, prompt, seed, hash — in the same call; an asset from the image tool is recorded by hand in\nthe same shape. Then run the gate, `juicy manifest verify --project . --require-video`, and fix what\nit names before going on. Generated clips are **mounted muted** — the default video model bakes\nambient sound into every clip, and it fights the ad's soundtrack. That is a rule about the **clip**, not\nabout the **ad**: the soundtrack goes in at Step 4, and it is never absent (§ Audio is not optional).\n\n**Demux the soundtrack to an audio file.** An `<audio>` element must point at an audio\nasset — hand it an `.mp4` and the render fails at compile with *\"composition asset(s) do\nnot match their authored media element type (expected: audio)\"*. The reference's track\nlives inside its video, so extract it once:\n\n```bash\nffmpeg -i .media/references/<ref>.mp4 -vn -c:a aac -b:a 160k .media/audio/reference.m4a\n```\n\nCopying the stream, not re-recording it: this is still the reference's own audio,\nunmodified, which is what § Reuse the reference's soundtrack requires.\n\n**Gate:** the first-frame set shown as a batch and approved by the user; every variant then has a\nfrozen first frame and video, each with a manifest record; nothing references a remote URL.\n\n---\n\n## Step 4: Compose\n\n**One composition — `index.html` — with the variants as variables.** Not one HTML file\nper variant: `check` and `snapshot` take a project directory and always open its\n`index.html`, so variants sitting in `compositions/` cannot be validated at all, and a\nblank root beside them fails lint as `blank_root_with_standalone_composition`.\n`compositions/` is for the *scenes* of one ad, mounted from `index.html` — not for\nsibling deliverables.\n\n**Replace the scaffold, do not build around it.** `init --example blank` writes a placeholder\n`<h1 id=\"title\" class=\"clip\">Title</h1>` spanning the root's first 10 seconds, and centres\n`#root` with flexbox. Delete the placeholder clip and restyle `#root` for the blueprint's layout.\nLeft in, \"Title\" renders over the ad in every variant, and `lint` does not flag it.\n\nDeclare the copy slots the blueprint names as variables on `<html>`, and bind them\ndeclaratively — no script needed:\n\n```html\n<html data-composition-variables='[\n {\"id\":\"hook\",\"type\":\"string\",\"label\":\"Hook\",\"default\":\"...\"}\n]'>\n ...\n <div class=\"clip\" data-var-text=\"hook\" ...>fallback text</div>\n```\n\n`data-var-text` substitutes an element's text, `data-var-src` its `src`, and every scalar\nalso lands as a `--{id}` CSS custom property. Keep a real fallback in the markup so\npreview works with no overrides. Details in `/hyperframes-core`\n§ Variables and Media.\n\nThis is why `COPY.md` is a table with one row per variant: that table becomes\n`variants.json` at Step 6 with no restructuring.\n\nBackground video plays as framework-owned media; the overlay is a separate track. Load\n`/hyperframes-core` for the composition contract and `/motion-doctrine` before authoring\nmotion. Ad text motion is deliberately restrained — read the blueprint's overlay section\nbefore reaching for the animation catalog.\n\nIf the ad ends on a brand card, read `references/outro.md` **now**, not after — the outro comes out\nof the ad's length, not on top of it.\n\n**Lay the soundtrack in last**, on its own track, spanning frame 0 to the final frame including any\noutro — the reference's own audio unless the user asked for something else (§ Audio is not\noptional).\n\n**Gate:** `hyperframes lint` is clean; the composition names its frozen media by\n**root-relative** paths (`.media/...`, never `../`); every copy slot the blueprint names is\na declared variable with a fallback; a full-duration audio track is present, pointing at an\naudio file; nothing of the scaffold's placeholder is left (no clip still reading `Title`).\n\n---\n\n## Step 5: Validate, then hand over the editor\n\nRead `references/defect-gate.md` and run it. It covers `lint`, `check`, snapshots, the ad-specific\ndefects those tools cannot name, and the four manual checks (frame 0 at thumbnail scale, one watch\nwith sound off, one watch with sound **on**, the final frame alone). The fix loop is **capped at 2\nrounds**.\n\n### Then open the editor — every time\n\nWhen the gate is clear, **open the Studio preview and give the user the URL**:\n\n```bash\nhyperframes preview --background\n```\n\n`--background` keeps the server alive after the command returns, which a plain `preview` does not\ndo from an agent shell. Fetch the URL once and make sure it answers before handing it over.\nHand over the project URL (`#project/<name>`) and say in one line what they are looking at and what\nthey can change directly in it. This is the final presentation, not an approval: go on to Step 6 in\nthe same run, and put the URL beside the filenames in the closing reply. What they change in the\neditor afterwards is a new render, not a blocked one.\n\nThis is not optional and it is not conditional on the ad looking difficult. The user reviews ads in\nthe editor, where they can drag a caption off a face and see the result — not by reading a\ndescription of the composition, and never by being handed commands to run themselves. A run that\nends with \"here's how to preview it\" has moved our work onto the person least equipped to do it.\n`/hyperframes-cli` § Two different preview surfaces covers the surface itself; the rule that it is\nalways reached is here.\n\nIf `preview` will not start, that is a blocker to report in plain words — not a reason to fall back\nto instructions. Say what failed, offer the snapshots you already have from the defect gate, and ask\nwhether to render anyway.\n\n**Gate:** nothing `detected`; manual checks done per variant; any `unknown` checks noted as caveats;\nthe preview URL handed over.\n\n---\n\n## Step 6: Name and render\n\nRead `references/naming.md`. Author the export naming record with the `ad-naming` skill's tool —\nnever a filename by hand. `<SKILLS_DIR>` is the directory holding the installed skills, the parent\nof this one:\n\n```bash\nnode <SKILLS_DIR>/ad-naming/scripts/naming.mjs get --project .\nnode <SKILLS_DIR>/ad-naming/scripts/naming.mjs set --project . \\\n --creative-name \"<name>\" --funnel <TOF|MOF|BOF> --source <source> --style <fb-style>\nnode <SKILLS_DIR>/ad-naming/scripts/naming.mjs expand --project . --ratio <ratio> --markets <codes>\n```\n\nYou author four fields; ratio, market token, and date are the system's. Resolve funnel / source /\nstyle from the reference's filename first, then the brief, then ask. The record is the project's\n`export-naming.json`.\n\n**Render every variant in one batch.** Write `variants.json` — a JSON array with one row\nper row of `COPY.md`, each key a declared variable — and hand it to the renderer:\n\n```bash\nhyperframes render --batch variants.json --strict-variables\n```\n\nOne output per row. `--strict-variables` is not optional: without it an undeclared or\nmisspelled key is a warning, so a variant renders carrying the composition's fallback copy\ninstead of its own and looks fine until someone reads it.\n\nBatch output names are the project's, not ours, so rename each file to its export name\nfrom the naming record afterwards — that mapping is what `renders/manifest.jsonl` records.\n\nThen append to `renders/manifest.jsonl` linking each file to its copy row, blueprint, and\ngeneration seeds.\n\n**Gate:** the record is `complete: true`; every variant rendered and recorded. The final reply lists\nthe filenames, states what varies between them, and names every compliance risk the copy gate\nrecorded, each with its compliant alternative.\n\n---\n\n## Reference map\n\n| Read | When |\n| ------------------------------------------------------------------------ | ---------------------------------------------------------------- |\n| the `juicylucy-setup` skill's `scripts/doctor.sh --preflight` | **First**, every run: whether this machine can make an ad at all; the setup skill runs first when it cannot. |\n\n| `[references/reference-iteration.md](references/reference-iteration.md)` | **First**, whenever there is a reference creative. |\n| `[references/reference-manifest.md](references/reference-manifest.md)` | Step 0: the gate — every reference on disk, playable, traceable. |\n| `[references/ad-library.md](references/ad-library.md)` | Step 0: the reference is an Ads Library link, not a file. |\n| `[blueprints-index.md](blueprints-index.md)` | Step 1: pick the ad type. |\n| `[references/ad-copy.md](references/ad-copy.md)` | Step 2: write copy; the compliance gate. |\n| `[references/generation.md](references/generation.md)` | Step 3: the image tool, the provider's models, prompts, freezing, seeds. |\n| the resolved `brand-<slug>` skill | **Step 0**: product truth, claims, palette, type, end card. |\n| `[references/outro.md](references/outro.md)` | Step 4: whether and how the ad ends on the brand card. |\n| `brand-<slug>` § `outro-card.md` | Step 4: fetch and verify the end-card. |\n| the `ad-platform` skill's `platform-specs.md` | Step 0 and 5: aspect, safe zones, duration caps. Shared platform truth, installed as a sibling skill. |\n| `[references/defect-gate.md](references/defect-gate.md)` | Step 5: the gate and the fix playbook. |\n| `/hyperframes-cli` | Step 5: `preview` — the Studio surface the user reviews in. |\n| `/hyperframes-audio` | Steps 4–6: fades and gain on a generated bed, never on a reference track.|\n| `[references/naming.md](references/naming.md)` | Step 6: the export naming record. |\n| `/hyperframes-core` · `/motion-doctrine` · `/media-use` | Step 4: composition, motion, media. |\n| `[references/capabilities.md](references/capabilities.md)` | Any gate: what may be offered there, and what must not be. |\n"
}SHA-256 of public snapshot: 7a1d7125d1b9dd249cc53bcbd9cb3e489cf2692211473045768baf4240bed154