{"slug":"storyboard-architect","title":"storyboard-architect","summary":"Turn a creative brief into a production-grade storyboard with shot specs, timing, on-screen text, and per-shot rationale. Use when the user describes a video brief, plans a video, references shots or beats, scripts a social video, or hands over a creative concept to break into sc","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-09-24T15:43:09.99087Z","repo":{"url":"https://github.com/whystrohm/shotkit","stars":21,"forks":5,"license":"Apache-2.0","updatedAt":"2026-09-24T12:25:06Z"},"bodyHtml":"<hr>\n<h2>name: storyboard-architect\ndescription: Turn a creative brief into a production-grade storyboard with shot specs, timing, on-screen text, and per-shot rationale. Use when the user describes a video brief, plans a video, references shots or beats, scripts a social video, or hands over a creative concept to break into scenes. Produces run.json, storyboard.md, shots.json, text-overlays.json, and brand-lock.snapshot.md. Pairs with visual-prompt-forge, visual-asset-critic, storyboard-html-preview.</h2>\n<h1>Storyboard Architect</h1>\n<p>You are turning a creative brief into a deterministic storyboard. The output is a set of files an editor, agency, or developer can act on without asking follow-up questions.</p>\n<p>This is not a creative-writing exercise. The output is a spec.</p>\n<h2>When to use</h2>\n<p>Trigger this skill when the user:</p>\n<ul>\n<li>Describes a video they want to make (\"30-second explainer for...\", \"TikTok ad about...\")</li>\n<li>Asks to storyboard, plan shots, break out beats, write a shot list</li>\n<li>Hands over a script, brief, or concept document expecting structured pre-production output</li>\n<li>Mentions a beat framework by name (Hero Trilogy, Pain-Proof-Promise, etc.)</li>\n<li>References an existing brand-lock file or pack</li>\n</ul>\n<p>If the user only wants prompts for an image generator (no narrative structure), use <code>visual-prompt-forge</code> directly instead.</p>\n<h2>What you produce</h2>\n<p>For every storyboard run, create this exact set of files in the working output directory:</p>\n<pre><code>output/\n├── run.json                   # Run identity + every input pinned by content hash\n├── storyboard.md              # Human-readable, structured per shot\n├── shots.json                 # Machine-readable, schema in templates/shots.schema.json\n├── text-overlays.json         # On-screen text + timing\n└── brand-lock.snapshot.md     # Frozen copy of the brand-lock used (audit trail)\n</code></pre>\n<p><code>run.json</code> is what makes the rest of the tree auditable later. A filename says nothing\nabout the bytes behind it, so the snapshot sitting next to a set of frames is not proof\nthat it is the snapshot they were built from. The hashes in <code>run.json</code> are that proof.\nWrite it once, at the end of the run, and never edit it.</p>\n<p>If the user asks for image prompts or HTML preview, hand off to <code>visual-prompt-forge</code> or <code>storyboard-html-preview</code>, those skills consume <code>shots.json</code> directly. Don't try to do their job here.</p>\n<h2>Inputs</h2>\n<p>You need these. If any are missing, ask before drafting.</p>\n<table>\n<thead>\n<tr>\n<th>Input</th>\n<th>Required?</th>\n<th>Default if absent</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Brief (problem, audience, goal)</td>\n<td>Yes</td>\n<td>Ask</td>\n</tr>\n<tr>\n<td>Total duration</td>\n<td>Yes</td>\n<td>Ask</td>\n</tr>\n<tr>\n<td>Aspect ratio</td>\n<td>Yes</td>\n<td>Ask (16:9, 9:16, 1:1)</td>\n</tr>\n<tr>\n<td>Beat framework</td>\n<td>No</td>\n<td>Suggest based on brief</td>\n</tr>\n<tr>\n<td>Brand-lock file path</td>\n<td>No</td>\n<td>Use <code>brand-packs/_template.md</code> and flag the gap</td>\n</tr>\n<tr>\n<td>Voiceover style (VO present, on-screen only, captions)</td>\n<td>No</td>\n<td>Ask if unclear</td>\n</tr>\n<tr>\n<td>Target generator(s) for downstream prompts</td>\n<td>No</td>\n<td>Note as \"to be specified\"</td>\n</tr>\n</tbody>\n</table>\n<h2>Workflow</h2>\n<p>Follow this sequence. Don't skip steps even if the brief seems simple.</p>\n<h3>Step 1. Read the brand-lock</h3>\n<p>If a brand-lock file path is provided, read it first. Extract:</p>\n<ul>\n<li>Palette (hex)</li>\n<li>Typography</li>\n<li>Mood descriptors</li>\n<li>\"Never\" list (what this brand will never do visually)</li>\n<li>Motion language</li>\n<li>Voice tone</li>\n<li>Aspect-ratio preferences</li>\n</ul>\n<p>If no brand-lock is provided, copy <code>brand-packs/_template.md</code> into the output as <code>brand-lock.snapshot.md</code> with a note: <code># UNCONFIGURED, using template defaults. Recommend providing a real brand-lock for production work.</code></p>\n<h3>Step 2. Pick the beat framework</h3>\n<p>Read <code>references/beat-frameworks.md</code>. Pick the one that matches the brief. Common cases:</p>\n<ul>\n<li>Pain-reframe-promise → conversion content</li>\n<li>Hero Trilogy → product hero films</li>\n<li>Founder Explainer → personal-brand content</li>\n<li>Content Spiral → kinetic typography / opinion pieces</li>\n</ul>\n<p>If none fit cleanly, build a custom beat structure but document why in <code>storyboard.md</code> rationale section.</p>\n<h3>Step 3. Block out timing</h3>\n<p>Read <code>references/timing-rules.md</code> for the math. Default cadence:</p>\n<ul>\n<li>Hook beat: 0–2 seconds</li>\n<li>Pain/setup: 2–6 seconds (for 30s) or 2–10 seconds (for 60s)</li>\n<li>Proof/reframe: middle third</li>\n<li>Promise/CTA: final 4–6 seconds</li>\n</ul>\n<p>Don't fight the framework. If the brief and the duration disagree, surface the disagreement before drafting.</p>\n<h3>Step 4. Draft the shot list</h3>\n<p>Read <code>references/shot-grammar.md</code> for controlled vocabulary. The field names below are\nthe schema's field names. <code>templates/shots.schema.json</code> sets <code>additionalProperties: false</code>, so a near-miss like <code>environment</code> instead of <code>environment_ref</code> is a validation\nfailure, not a synonym.</p>\n<ul>\n<li><code>id</code>, sequential, zero-padded (<code>shot_01</code>, <code>shot_02</code>...)</li>\n<li><code>beat</code>, which beat this shot serves</li>\n<li><code>start</code> / <code>end</code>, timestamps in seconds, decimal allowed. <code>end</code> must be after <code>start</code></li>\n<li><code>framing</code>, ECU / CU / MCU / MS / MLS / WS / EWS</li>\n<li><code>angle</code>, eye-level / high / low / overhead / dutch</li>\n<li><code>motion</code>, static / push / pull / pan-left / pan-right / tilt-up / tilt-down /\nhandheld / orbit / whip / rack. All eleven are legal; the schema enum is the\nauthority and <code>references/shot-grammar.md</code> explains when each earns its keep</li>\n<li><code>depth_of_field</code>, optional, shallow / deep / rack</li>\n<li><code>subject</code>, what's in frame, structured</li>\n<li><code>environment_ref</code>, references series-lock language, default <code>series_lock.environment</code></li>\n<li><code>lighting_ref</code>, references series-lock language, default <code>series_lock.lighting</code></li>\n<li><code>on_screen_text</code>, null, one text-overlay id, OR an array of ids when a shot carries\nmore than one overlay</li>\n<li><code>vo</code>, voiceover line, or null</li>\n<li><code>rationale</code>, one sentence explaining <em>why this shot at this moment</em></li>\n</ul>\n<p>Note on <code>rack</code>: as a <code>motion</code> value it means the rack focus is the shot's movement; as a\n<code>depth_of_field</code> value it means focus shifts mid-shot. Same word, two fields, two\nmeanings.</p>\n<h3>Step 5. Separate the text layer</h3>\n<p>Every piece of on-screen text becomes an entry in <code>text-overlays.json</code>. Never bake text into the visual description. Each overlay has:</p>\n<ul>\n<li><code>id</code>, <code>text_01</code>, <code>text_02</code>...</li>\n<li><code>shot_id</code>, which shot this overlays on</li>\n<li><code>content</code>, the actual text</li>\n<li><code>font</code>, references brand-lock typography</li>\n<li><code>position</code>, <code>center</code>, <code>lower-third</code>, <code>upper-third</code>, <code>left-third</code>, <code>right-third</code>, or <code>{x, y}</code> percentages</li>\n<li><code>size</code>, <code>display</code>, <code>headline</code>, <code>body</code>, <code>caption</code></li>\n<li><code>weight</code>, <code>regular</code>, <code>medium</code>, <code>bold</code>, <code>black</code></li>\n<li><code>color</code>, hex (must come from brand-lock palette)</li>\n<li><code>enter</code>, <code>{ at: seconds, animation: fade-in | slide-up | slide-down | type-on | hard-cut }</code></li>\n<li><code>exit</code>, <code>{ at: seconds, animation: fade-out | slide-up | slide-down | hard-cut }</code></li>\n</ul>\n<p>Enter and exit have different animation vocabularies, and <code>templates/text-overlays.schema.json</code>\nis the authority on both. A shot may carry more than one overlay; list every id in that\nshot's <code>on_screen_text</code> array, or the extra overlays render nowhere.</p>\n<h3>Step 6. Lock the series</h3>\n<p>Define environment, lighting, and character anchors that apply across every shot. These go at the top of <code>shots.json</code> under <code>series_lock</code>. Without these, image generation will produce incoherent frames.</p>\n<h3>Step 7. Write rationale</h3>\n<p>Every shot has a one-sentence rationale. Why this beat. Why this framing. Why this on-screen text. This is the audit trail. Do not skip it.</p>\n<h3>Step 8. Snapshot the brand-lock</h3>\n<p>Copy the brand-lock file (or template) into the output as <code>brand-lock.snapshot.md</code>. Add\nthese two comments at the very top, in this order:</p>\n<pre><code>&lt;!-- snapshot taken: 2026-05-07T14:23:00Z --&gt;\n&lt;!-- source: brand-packs/whystrohm.md --&gt;\n</code></pre>\n<p>The timestamp is a full UTC instant, <code>YYYY-MM-DDThh:mm:ssZ</code>. A bare date cannot\ndistinguish two runs made on the same day, which is the case that matters. The source is\nthe path it was copied from, or the literal string <code>template default</code> for an\nunconfigured run. Extra comments after these two are fine.</p>\n<p><code>tools/validate_brand_lock.py --snapshot &lt;path&gt;</code> checks both lines. Run it.</p>\n<h3>Step 9. Write run.json</h3>\n<p>Last step, after the other four files are final. Fill in\n<code>templates/run.schema.json</code>: a <code>run_id</code>, the <code>created_at</code> instant, and the SHA-256 of\n<code>shots.json</code>, <code>text-overlays.json</code>, and <code>brand-lock.snapshot.md</code> as written.</p>\n<pre><code>shasum -a 256 output/shots.json output/text-overlays.json output/brand-lock.snapshot.md\n</code></pre>\n<p><code>run_id</code> is the compact UTC timestamp, a dash, then 8 hex characters, e.g.\n<code>20260730T142300Z-9f2c1ab4</code>. The hex suffix is what keeps two operators starting a run\nin the same second from colliding. Set <code>brand_lock_configured: false</code> when the snapshot\nis an unfilled template.</p>\n<p>Leave <code>rounds</code> empty. <code>visual-prompt-forge</code> appends a round entry when it writes\nprompts.</p>\n<h2>Output formats</h2>\n<h3><code>storyboard.md</code></h3>\n<p>Use the template at <code>templates/storyboard.md.tpl</code>. Read it before writing.</p>\n<h3><code>shots.json</code></h3>\n<p>Must validate against <code>templates/shots.schema.json</code>. Read it before writing. The structure is:</p>\n<pre><code>{\n  \"version\": \"1.2\",\n  \"project\": { \"title\": \"...\", \"duration_s\": 30, \"aspect\": \"9:16\", \"framework\": \"...\" },\n  \"brand_lock_ref\": \"brand-lock.snapshot.md\",\n  \"series_lock\": {\n    \"character\": \"...\",\n    \"environment\": \"...\",\n    \"lighting\": \"...\",\n    \"color_grade\": \"...\"\n  },\n  \"shots\": [\n    {\n      \"id\": \"shot_01\",\n      \"beat\": \"hook\",\n      \"start\": 0.0,\n      \"end\": 2.0,\n      \"framing\": \"MCU\",\n      \"angle\": \"eye-level\",\n      \"motion\": \"static\",\n      \"depth_of_field\": \"shallow\",\n      \"subject\": \"...\",\n      \"environment_ref\": \"series_lock.environment\",\n      \"lighting_ref\": \"series_lock.lighting\",\n      \"on_screen_text\": \"text_01\",\n      \"vo\": null,\n      \"rationale\": \"...\"\n    }\n  ]\n}\n</code></pre>\n<p>Write <code>1.2</code> for new storyboards. <code>1.0</code> and <code>1.1</code> files stay valid; the array form of\n<code>on_screen_text</code> and the hashed <code>assets</code> block need <code>1.2</code>.</p>\n<h3><code>text-overlays.json</code></h3>\n<p>Must validate against <code>templates/text-overlays.schema.json</code>. Read it before writing.</p>\n<h2>Quality bar</h2>\n<p>Run the validator. Do not eyeball this list.</p>\n<pre><code>python tools/validate_shots.py output/\npython tools/validate_brand_lock.py --snapshot output/brand-lock.snapshot.md\npython tools/validate_provenance.py output/\n</code></pre>\n<p><code>validate_shots.py</code> checks every mechanical rule that used to live here as a checkbox,\nbecause a checkbox is a rule enforced by remembering to look:</p>\n<ul>\n<li>shots.json and text-overlays.json validate against their schemas</li>\n<li><code>end</code> is after <code>start</code>, no duplicate ids, no gaps, no overlaps, and the covered span\nmatches <code>project.duration_s</code> within 0.1s</li>\n<li>every <code>on_screen_text</code> resolves to an overlay, every <code>overlay.shot_id</code> resolves to a\nshot, and every overlay is reachable from at least one shot</li>\n<li>every overlay's timing sits inside its shot window, and exit is after enter</li>\n<li>every overlay color appears in the brand-lock palette</li>\n<li><code>brand_lock_ref</code> resolves on disk</li>\n</ul>\n<p>It warns, rather than fails, on judgement calls worth a second look: overlay copy\nrepeated inside a shot subject, a raw hex in a subject, shot ids out of chronological\norder, an overlay font the brand-lock does not declare.</p>\n<p>What the validator cannot check, and you still have to:</p>\n<ul>\n<li><input disabled=\"disabled\" type=\"checkbox\"> Every rationale says <em>why this shot at this moment</em>, not what the shot contains</li>\n<li><input disabled=\"disabled\" type=\"checkbox\"> <code>series_lock</code> anchors are specific enough to reproduce (not \"a person in a room\")</li>\n<li><input disabled=\"disabled\" type=\"checkbox\"> The beat structure actually matches the brief's argument</li>\n<li><input disabled=\"disabled\" type=\"checkbox\"> <code>run.json</code> is written and its hashes are the files as shipped</li>\n</ul>\n<p>If the validator fails, fix it before declaring done. A green validator plus an unread\nrationale is not a finished storyboard.</p>\n<h2>Reference files</h2>\n<p>Load these as needed:</p>\n<ul>\n<li><code>references/beat-frameworks.md</code>, the beat structures</li>\n<li><code>references/shot-grammar.md</code>, controlled vocabulary for framing/angle/motion</li>\n<li><code>references/timing-rules.md</code>, pacing math</li>\n<li><code>references/on-screen-text.md</code>, when on-screen text earns its keep</li>\n</ul>\n<h2>Examples</h2>\n<ul>\n<li><code>examples/30s-pain-proof-promise/</code>, full output set for a 30-second conversion ad</li>\n<li><code>examples/60s-founder-explainer/</code>, full output set for a founder explainer</li>\n<li><code>examples/shotkit-explainer/</code>, the 90-second explainer, including a shot that carries\ntwo overlays</li>\n</ul>\n<p>Read these to understand the expected output quality, especially the rationale fields.\nAll three validate clean under <code>tools/validate_shots.py --examples</code>, so they are also\nthe reference for what a passing file looks like.</p>\n<p>For what the output tree looks like after generation and review, see\n<code>../visual-asset-critic/examples/worked-run/</code>: two shots through two rounds, with real\nhashes, per-round prompts and frames, and one critique per shot per round.</p>\n<h2>Handoff</h2>\n<p>After producing the five files, tell the user what's in <code>output/</code> and offer the obvious next steps:</p>\n<ul>\n<li>\"Want image prompts? I'll run <code>visual-prompt-forge</code> on <code>shots.json</code>.\"</li>\n<li>\"Want a shareable HTML preview? I'll run <code>storyboard-html-preview</code>.\"</li>\n<li>\"Want to QA a generated image against this storyboard? I'll run <code>visual-asset-critic</code>.\"</li>\n</ul>\n<p>Don't run those on your own. The user picks.</p>\n","files":[{"path":"examples/30s-pain-proof-promise/brand-lock.snapshot.md","sizeBytes":2923,"isText":true},{"path":"examples/30s-pain-proof-promise/preview.html","sizeBytes":28274,"isText":false},{"path":"examples/30s-pain-proof-promise/run.json","sizeBytes":843,"isText":true},{"path":"examples/30s-pain-proof-promise/shots.json","sizeBytes":5168,"isText":true},{"path":"examples/30s-pain-proof-promise/storyboard.md","sizeBytes":5027,"isText":true},{"path":"examples/30s-pain-proof-promise/text-overlays.json","sizeBytes":2284,"isText":true},{"path":"examples/60s-founder-explainer/brand-lock.snapshot.md","sizeBytes":2923,"isText":true},{"path":"examples/60s-founder-explainer/preview.html","sizeBytes":29782,"isText":false},{"path":"examples/60s-founder-explainer/run.json","sizeBytes":837,"isText":true},{"path":"examples/60s-founder-explainer/shots.json","sizeBytes":5860,"isText":true},{"path":"examples/60s-founder-explainer/storyboard.md","sizeBytes":4892,"isText":true},{"path":"examples/60s-founder-explainer/text-overlays.json","sizeBytes":1883,"isText":true},{"path":"examples/shotkit-explainer/brand-lock.snapshot.md","sizeBytes":4124,"isText":true},{"path":"examples/shotkit-explainer/preview.html","sizeBytes":29331,"isText":false},{"path":"examples/shotkit-explainer/run.json","sizeBytes":820,"isText":true},{"path":"examples/shotkit-explainer/shots.json","sizeBytes":6345,"isText":true},{"path":"examples/shotkit-explainer/storyboard.md","sizeBytes":6337,"isText":true},{"path":"examples/shotkit-explainer/text-overlays.json","sizeBytes":2730,"isText":true},{"path":"references/beat-frameworks.md","sizeBytes":2815,"isText":true},{"path":"references/on-screen-text.md","sizeBytes":3920,"isText":true},{"path":"references/shot-grammar.md","sizeBytes":3581,"isText":true},{"path":"references/timing-rules.md","sizeBytes":3227,"isText":true},{"path":"SKILL.md","sizeBytes":12800,"isText":true},{"path":"templates/run.schema.json","sizeBytes":6933,"isText":true},{"path":"templates/shots.schema.json","sizeBytes":8957,"isText":true},{"path":"templates/storyboard.md.tpl","sizeBytes":1561,"isText":false},{"path":"templates/text-overlays.schema.json","sizeBytes":3428,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-09-24T15:44:12.764797Z","sha256":"D5FFD4B3B062AF58785D95B7B57A07BD0D97AD602FE7C5F2EBA493ABD54AC4DD","sizeBytes":61978},"review":null,"source":{"repositoryUrl":"https://github.com/whystrohm/shotkit","path":"skills/storyboard-architect","license":"Apache-2.0","commit":"1b56831c035b11f24278365962ad11d2fb2f5de4","subtreeSha":"C9E872A3003C881F470F1EFE51510EFF8BA3816BA21D9F133C63AD9B8B34AFB6","lastSyncedAt":"2026-09-24T15:43:09.838799Z"},"reviewedAt":"2026-09-24T15:47:18.235908Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/whystrohm/shotkit/tree/main/skills/storyboard-architect"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install whystrohm-shotkit@llmmart"},{"target":"git","command":"git clone https://github.com/whystrohm/shotkit.git"}]}