storyboard-architect
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
Install
npx skills add https://github.com/whystrohm/shotkit/tree/main/skills/storyboard-architect
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install whystrohm-shotkit@llmmart
git clone https://github.com/whystrohm/shotkit.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole whystrohm/shotkit collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Storyboard Architect
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.
This is not a creative-writing exercise. The output is a spec.
When to use
Trigger this skill when the user:
- Describes a video they want to make ("30-second explainer for...", "TikTok ad about...")
- Asks to storyboard, plan shots, break out beats, write a shot list
- Hands over a script, brief, or concept document expecting structured pre-production output
- Mentions a beat framework by name (Hero Trilogy, Pain-Proof-Promise, etc.)
- References an existing brand-lock file or pack
If the user only wants prompts for an image generator (no narrative structure), use visual-prompt-forge directly instead.
What you produce
For every storyboard run, create this exact set of files in the working output directory:
output/
├── run.json # Run identity + every input pinned by content hash
├── storyboard.md # Human-readable, structured per shot
├── shots.json # Machine-readable, schema in templates/shots.schema.json
├── text-overlays.json # On-screen text + timing
└── brand-lock.snapshot.md # Frozen copy of the brand-lock used (audit trail)
run.json is what makes the rest of the tree auditable later. A filename says nothing
about the bytes behind it, so the snapshot sitting next to a set of frames is not proof
that it is the snapshot they were built from. The hashes in run.json are that proof.
Write it once, at the end of the run, and never edit it.
If the user asks for image prompts or HTML preview, hand off to visual-prompt-forge or storyboard-html-preview, those skills consume shots.json directly. Don't try to do their job here.
Inputs
You need these. If any are missing, ask before drafting.
| Input | Required? | Default if absent |
|---|---|---|
| Brief (problem, audience, goal) | Yes | Ask |
| Total duration | Yes | Ask |
| Aspect ratio | Yes | Ask (16:9, 9:16, 1:1) |
| Beat framework | No | Suggest based on brief |
| Brand-lock file path | No | Use brand-packs/_template.md and flag the gap |
| Voiceover style (VO present, on-screen only, captions) | No | Ask if unclear |
| Target generator(s) for downstream prompts | No | Note as "to be specified" |
Workflow
Follow this sequence. Don't skip steps even if the brief seems simple.
Step 1. Read the brand-lock
If a brand-lock file path is provided, read it first. Extract:
- Palette (hex)
- Typography
- Mood descriptors
- "Never" list (what this brand will never do visually)
- Motion language
- Voice tone
- Aspect-ratio preferences
If no brand-lock is provided, copy brand-packs/_template.md into the output as brand-lock.snapshot.md with a note: # UNCONFIGURED, using template defaults. Recommend providing a real brand-lock for production work.
Step 2. Pick the beat framework
Read references/beat-frameworks.md. Pick the one that matches the brief. Common cases:
- Pain-reframe-promise → conversion content
- Hero Trilogy → product hero films
- Founder Explainer → personal-brand content
- Content Spiral → kinetic typography / opinion pieces
If none fit cleanly, build a custom beat structure but document why in storyboard.md rationale section.
Step 3. Block out timing
Read references/timing-rules.md for the math. Default cadence:
- Hook beat: 0–2 seconds
- Pain/setup: 2–6 seconds (for 30s) or 2–10 seconds (for 60s)
- Proof/reframe: middle third
- Promise/CTA: final 4–6 seconds
Don't fight the framework. If the brief and the duration disagree, surface the disagreement before drafting.
Step 4. Draft the shot list
Read references/shot-grammar.md for controlled vocabulary. The field names below are
the schema's field names. templates/shots.schema.json sets additionalProperties: false, so a near-miss like environment instead of environment_ref is a validation
failure, not a synonym.
id, sequential, zero-padded (shot_01,shot_02...)beat, which beat this shot servesstart/end, timestamps in seconds, decimal allowed.endmust be afterstartframing, ECU / CU / MCU / MS / MLS / WS / EWSangle, eye-level / high / low / overhead / dutchmotion, static / push / pull / pan-left / pan-right / tilt-up / tilt-down / handheld / orbit / whip / rack. All eleven are legal; the schema enum is the authority andreferences/shot-grammar.mdexplains when each earns its keepdepth_of_field, optional, shallow / deep / racksubject, what's in frame, structuredenvironment_ref, references series-lock language, defaultseries_lock.environmentlighting_ref, references series-lock language, defaultseries_lock.lightingon_screen_text, null, one text-overlay id, OR an array of ids when a shot carries more than one overlayvo, voiceover line, or nullrationale, one sentence explaining why this shot at this moment
Note on rack: as a motion value it means the rack focus is the shot's movement; as a
depth_of_field value it means focus shifts mid-shot. Same word, two fields, two
meanings.
Step 5. Separate the text layer
Every piece of on-screen text becomes an entry in text-overlays.json. Never bake text into the visual description. Each overlay has:
id,text_01,text_02...shot_id, which shot this overlays oncontent, the actual textfont, references brand-lock typographyposition,center,lower-third,upper-third,left-third,right-third, or{x, y}percentagessize,display,headline,body,captionweight,regular,medium,bold,blackcolor, hex (must come from brand-lock palette)enter,{ at: seconds, animation: fade-in | slide-up | slide-down | type-on | hard-cut }exit,{ at: seconds, animation: fade-out | slide-up | slide-down | hard-cut }
Enter and exit have different animation vocabularies, and templates/text-overlays.schema.json
is the authority on both. A shot may carry more than one overlay; list every id in that
shot's on_screen_text array, or the extra overlays render nowhere.
Step 6. Lock the series
Define environment, lighting, and character anchors that apply across every shot. These go at the top of shots.json under series_lock. Without these, image generation will produce incoherent frames.
Step 7. Write rationale
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.
Step 8. Snapshot the brand-lock
Copy the brand-lock file (or template) into the output as brand-lock.snapshot.md. Add
these two comments at the very top, in this order:
<!-- snapshot taken: 2026-05-07T14:23:00Z -->
<!-- source: brand-packs/whystrohm.md -->
The timestamp is a full UTC instant, YYYY-MM-DDThh:mm:ssZ. A bare date cannot
distinguish two runs made on the same day, which is the case that matters. The source is
the path it was copied from, or the literal string template default for an
unconfigured run. Extra comments after these two are fine.
tools/validate_brand_lock.py --snapshot <path> checks both lines. Run it.
Step 9. Write run.json
Last step, after the other four files are final. Fill in
templates/run.schema.json: a run_id, the created_at instant, and the SHA-256 of
shots.json, text-overlays.json, and brand-lock.snapshot.md as written.
shasum -a 256 output/shots.json output/text-overlays.json output/brand-lock.snapshot.md
run_id is the compact UTC timestamp, a dash, then 8 hex characters, e.g.
20260730T142300Z-9f2c1ab4. The hex suffix is what keeps two operators starting a run
in the same second from colliding. Set brand_lock_configured: false when the snapshot
is an unfilled template.
Leave rounds empty. visual-prompt-forge appends a round entry when it writes
prompts.
Output formats
storyboard.md
Use the template at templates/storyboard.md.tpl. Read it before writing.
shots.json
Must validate against templates/shots.schema.json. Read it before writing. The structure is:
{
"version": "1.2",
"project": { "title": "...", "duration_s": 30, "aspect": "9:16", "framework": "..." },
"brand_lock_ref": "brand-lock.snapshot.md",
"series_lock": {
"character": "...",
"environment": "...",
"lighting": "...",
"color_grade": "..."
},
"shots": [
{
"id": "shot_01",
"beat": "hook",
"start": 0.0,
"end": 2.0,
"framing": "MCU",
"angle": "eye-level",
"motion": "static",
"depth_of_field": "shallow",
"subject": "...",
"environment_ref": "series_lock.environment",
"lighting_ref": "series_lock.lighting",
"on_screen_text": "text_01",
"vo": null,
"rationale": "..."
}
]
}
Write 1.2 for new storyboards. 1.0 and 1.1 files stay valid; the array form of
on_screen_text and the hashed assets block need 1.2.
text-overlays.json
Must validate against templates/text-overlays.schema.json. Read it before writing.
Quality bar
Run the validator. Do not eyeball this list.
python tools/validate_shots.py output/
python tools/validate_brand_lock.py --snapshot output/brand-lock.snapshot.md
python tools/validate_provenance.py output/
validate_shots.py checks every mechanical rule that used to live here as a checkbox,
because a checkbox is a rule enforced by remembering to look:
- shots.json and text-overlays.json validate against their schemas
endis afterstart, no duplicate ids, no gaps, no overlaps, and the covered span matchesproject.duration_swithin 0.1s- every
on_screen_textresolves to an overlay, everyoverlay.shot_idresolves to a shot, and every overlay is reachable from at least one shot - every overlay's timing sits inside its shot window, and exit is after enter
- every overlay color appears in the brand-lock palette
brand_lock_refresolves on disk
It warns, rather than fails, on judgement calls worth a second look: overlay copy repeated inside a shot subject, a raw hex in a subject, shot ids out of chronological order, an overlay font the brand-lock does not declare.
What the validator cannot check, and you still have to:
- Every rationale says why this shot at this moment, not what the shot contains
-
series_lockanchors are specific enough to reproduce (not "a person in a room") - The beat structure actually matches the brief's argument
-
run.jsonis written and its hashes are the files as shipped
If the validator fails, fix it before declaring done. A green validator plus an unread rationale is not a finished storyboard.
Reference files
Load these as needed:
references/beat-frameworks.md, the beat structuresreferences/shot-grammar.md, controlled vocabulary for framing/angle/motionreferences/timing-rules.md, pacing mathreferences/on-screen-text.md, when on-screen text earns its keep
Examples
examples/30s-pain-proof-promise/, full output set for a 30-second conversion adexamples/60s-founder-explainer/, full output set for a founder explainerexamples/shotkit-explainer/, the 90-second explainer, including a shot that carries two overlays
Read these to understand the expected output quality, especially the rationale fields.
All three validate clean under tools/validate_shots.py --examples, so they are also
the reference for what a passing file looks like.
For what the output tree looks like after generation and review, see
../visual-asset-critic/examples/worked-run/: two shots through two rounds, with real
hashes, per-round prompts and frames, and one critique per shot per round.
Handoff
After producing the five files, tell the user what's in output/ and offer the obvious next steps:
- "Want image prompts? I'll run
visual-prompt-forgeonshots.json." - "Want a shareable HTML preview? I'll run
storyboard-html-preview." - "Want to QA a generated image against this storyboard? I'll run
visual-asset-critic."
Don't run those on your own. The user picks.
Files (shotkit)
-
examples
-
30s-pain-proof-promise
-
brand-lock.snapshot.md 2.9 KB
<!-- snapshot taken: 2026-05-07T14:23:00Z --> <!-- source: brand-packs/whystrohm.md --> # Brand Lock: WhyStrohm ## Identity **Brand:** WhyStrohm **One-line description:** Managed content infrastructure for founder-led brands. Voice extraction, brand guardrails encoded in code, programmatic video, automated publishing. 30 minutes a week of founder time, 48-hour content cycles. **Archetype:** Operator **Voice posture:** Calm, considered, confident without shouting ## Palette | Role | Hex | Use | |---|---|---| | Background | `#F5F0E8` | Cream, primary canvas | | Ink | `#2A2A32` | Near-black, primary text and dark elements | | Accent (warm) | `#D94F3A` | Coral, emphasis, periods on signature beats | | Accent (cool) | `#C9BBE0` | Pale lavender, secondary highlights | | Muted | `#7A7580` | Secondary text, captions | | Rule | `#E8E1D4` | Subtle borders, dividers, card backgrounds | ## Typography **Display font:** `Inter Black 900`, headline weight, hooks, on-screen text **Body font:** `Inter Medium 500`, body copy, captions, UI **Mono font:** `JetBrains Mono Regular`, code, data, technical references ## Mood adjectives - operator (not creator) - considered (not reactive) - deterministic (not vibes-based) - confident (without volume) - defense-grade (not fragile) ## Never list - never use stock photo aesthetic - never use AI uncanny faces - never over-saturate the cream background - never use coral as a flood color (only as accent or signature periods) - never use em dashes in copy - never use emojis in body copy - never use bullet points in narrative copy - never use clip-art or generic icon sets - never use hype words ("game-changing", "revolutionary", "next-level", "comprehensive") - never use exclamation points in headlines - never default to dark mode, light mode hybrid is the brand - never animate text with bouncing or wobbling, type-on, fade, or hard cut only ## Aspect ratios - 9:16, primary for short-form social - 16:9, long-form, hero films, web embeds - 1:1, feed posts, podcast covers - 4:5. Instagram feed alternative ## Color grade direction Warm filmic, muted teal shadows. Slight grain. Cream highlights, deep navy/charcoal shadows, never crushed. Reminiscent of Kodak Portra 400 with a slight digital cleanup. ## Motion language Camera moves are minimal and deliberate. Default to static. When motion is used, slow push or slow pull only. Cuts on action, not on time. Type-on for emphasis, never bouncing or wobbling. Coral periods animate as a hard pop on signature beats. Transitions are hard cuts or 6-frame dissolves only. ## Voice rules - no em dashes - no emojis in body copy - no exclamation points in headlines - no hype words - prefer specific numbers over vague claims - prefer present-tense over future-tense - prefer "operator" over "creator", "infrastructure" over "agency" --- **Last updated:** 2026-05-07 **Owner:** Yuri Strohm **Version:** 1.0 -
preview.html 27.6 KB · in bundle
-
run.json 843 B
{ "version": "1.0", "run_id": "20260507T142300Z-4b8f5796", "created_at": "2026-05-07T14:23:00Z", "shotkit_version": "3.0.0", "operator": null, "project": { "title": "WhyStrohm. The Content Infrastructure Pitch", "duration_s": 30, "aspect": "9:16", "framework": "pain-reframe-promise" }, "inputs": { "shots_ref": "shots.json", "shots_sha256": "a94ec63c2dd16b887bebcbceeb7b0fe184543a7e913cfc8168a3dc8d21d4c5c2", "text_overlays_ref": "text-overlays.json", "text_overlays_sha256": "0ccd9ec3750129893680db615862a9c0c7280d6794d89cf5856bc0555a2c20a6", "brand_lock_ref": "brand-lock.snapshot.md", "brand_lock_sha256": "f364bc71f68d16187d0721fbc092450174545e247f2f563a4c45686b4a877f50", "brand_lock_source": "brand-packs/whystrohm.md", "brand_lock_configured": true }, "rounds": [] } -
shots.json 5 KB
{ "version": "1.0", "project": { "title": "WhyStrohm. The Content Infrastructure Pitch", "duration_s": 30, "aspect": "9:16", "framework": "pain-reframe-promise" }, "brand_lock_ref": "brand-lock.snapshot.md", "series_lock": { "character": "founder, mid-thirties, salt-and-pepper hair, navy crewneck, calm posture, working at laptop", "environment": "minimalist home office, white walls, oak desk, single houseplant, no decor clutter", "lighting": "soft natural side-light, large window camera-left, warm afternoon golden hour, gentle shadow rolloff", "color_grade": "warm filmic, muted teal shadows, slight grain, cream highlights, deep navy shadows never crushed" }, "shots": [ { "id": "shot_01", "beat": "hook", "start": 0.0, "end": 2.0, "framing": "MCU", "angle": "eye-level", "motion": "static", "depth_of_field": "shallow", "subject": "founder, mid-thirties, salt-and-pepper hair, navy crewneck, looking directly into camera, neutral expression, slightly tired", "environment_ref": "series_lock.environment", "lighting_ref": "series_lock.lighting", "on_screen_text": "text_01", "vo": null, "rationale": "Cold open, direct eye contact establishes parasocial trust. Static framing keeps the hook simple, text does the work." }, { "id": "shot_02", "beat": "pain", "start": 2.0, "end": 7.0, "framing": "MS", "angle": "high", "motion": "static", "depth_of_field": "deep", "subject": "founder at laptop, scrolling social feed, content tiles glowing on screen, slight slump in shoulders, mug of coffee mid-distance", "environment_ref": "series_lock.environment", "lighting_ref": "series_lock.lighting", "on_screen_text": "text_02", "vo": null, "rationale": "High angle subtly diminishes the subject, pain beat. Deep DOF keeps the messy social feed legible. Subject left-weighted, text reserves right two-thirds." }, { "id": "shot_03", "beat": "pain", "start": 7.0, "end": 11.0, "framing": "ECU", "angle": "eye-level", "motion": "static", "depth_of_field": "shallow", "subject": "close on founder's hand hovering over laptop trackpad, frozen, not moving", "environment_ref": "series_lock.environment", "lighting_ref": "series_lock.lighting", "on_screen_text": null, "vo": null, "rationale": "Hand frozen above trackpad is the visual symbol of decision paralysis. Wordless beat, let the image carry the pain before the reframe." }, { "id": "shot_04", "beat": "reframe", "start": 11.0, "end": 16.0, "framing": "MCU", "angle": "eye-level", "motion": "push", "depth_of_field": "shallow", "subject": "founder, same posture, looking at camera, expression shifts from tired to clear, small almost-smile", "environment_ref": "series_lock.environment", "lighting_ref": "series_lock.lighting", "on_screen_text": "text_03", "vo": null, "rationale": "Slow push as the reframe lands. Same character, same environment, only the expression changes. The change is the point." }, { "id": "shot_05", "beat": "proof", "start": 16.0, "end": 20.0, "framing": "MS", "angle": "eye-level", "motion": "static", "depth_of_field": "deep", "subject": "founder gestures toward laptop screen, kanban-style content pipeline visible, organized columns, clean structure", "environment_ref": "series_lock.environment", "lighting_ref": "series_lock.lighting", "on_screen_text": "text_04", "vo": null, "rationale": "Proof beat, show the system, not the result. The kanban metaphor is recognizable to operators. Subject right-weighted, text upper-third." }, { "id": "shot_06", "beat": "promise", "start": 20.0, "end": 26.0, "framing": "MCU", "angle": "eye-level", "motion": "static", "depth_of_field": "shallow", "subject": "founder, calm composed posture, fully present, slight smile, hands folded on desk", "environment_ref": "series_lock.environment", "lighting_ref": "series_lock.lighting", "on_screen_text": "text_05", "vo": null, "rationale": "Promise beat, the after-state is the same person, just calmer. Static frame holds the moment. Text carries the specific commitment." }, { "id": "shot_07", "beat": "cta", "start": 26.0, "end": 30.0, "framing": "MS", "angle": "eye-level", "motion": "static", "depth_of_field": "deep", "subject": "founder, neutral posture, room visible behind, calm presence, gentle eye contact with camera", "environment_ref": "series_lock.environment", "lighting_ref": "series_lock.lighting", "on_screen_text": "text_06", "vo": null, "rationale": "CTA beat. Pulled out to MS to give text the breathing room. Subject centered, text lower-third with URL. No motion, let it land." } ] } -
storyboard.md 4.9 KB
# WhyStrohm. The Content Infrastructure Pitch | | | |---|---| | **Duration** | 30s | | **Aspect** | 9:16 | | **Beat framework** | Pain-Reframe-Promise | | **Brand lock** | [`brand-lock.snapshot.md`](./brand-lock.snapshot.md) | | **Generated** | 2026-05-07 14:23 UTC | --- ## Brief A 30-second founder explainer aimed at founders running content marketing themselves. The audience knows their content output is uneven and they're tired of the treadmill. The reframe is the WhyStrohm thesis: it's not a content problem, it's an infrastructure problem. The promise is the offer, 30 min/week of founder time, 48-hour cycle. The CTA is the free /scan diagnostic. ## Beat framework: Pain-Reframe-Promise Standard PRP for conversion content. Three beats with hook and CTA bookending. - 0–2s. Hook - 2–11s. Pain (extended for 30s, gives the problem space to breathe) - 11–20s. Reframe + Proof (the WhyStrohm thesis lands, then is shown) - 20–26s. Promise (specific, time-bound) - 26–30s. CTA ## Series lock | | | |---|---| | **Character anchor** | founder, mid-thirties, salt-and-pepper hair, navy crewneck, calm posture, working at laptop | | **Environment** | minimalist home office, white walls, oak desk, single houseplant, no decor clutter | | **Lighting** | soft natural side-light, large window camera-left, warm afternoon golden hour, gentle shadow rolloff | | **Color grade** | warm filmic, muted teal shadows, slight grain, cream highlights, deep navy shadows never crushed | --- ## Shots ### shot_01 · 0.0–2.0s · MCU · static **Beat:** hook **Subject:** founder, mid-thirties, salt-and-pepper hair, navy crewneck, looking directly into camera, neutral expression, slightly tired **On-screen text:** "Your content feels random." (Inter Black 900, lower-third, hard cut at 0.4s) **Rationale:** Cold open, direct eye contact establishes parasocial trust. Static framing keeps the hook simple, text does the work. --- ### shot_02 · 2.0–7.0s · MS · static **Beat:** pain **Subject:** founder at laptop, scrolling social feed, content tiles glowing on screen, slight slump in shoulders, mug of coffee mid-distance **On-screen text:** "Posting more isn't fixing it." (Inter Black 900, right-third, fade-in at 2.4s) **Rationale:** High angle subtly diminishes the subject, pain beat. Deep DOF keeps the messy social feed legible. Subject left-weighted, text reserves right two-thirds. --- ### shot_03 · 7.0–11.0s · ECU · static **Beat:** pain **Subject:** close on founder's hand hovering over laptop trackpad, frozen, not moving **On-screen text:** none **Rationale:** Hand frozen above trackpad is the visual symbol of decision paralysis. Wordless beat, let the image carry the pain before the reframe. --- ### shot_04 · 11.0–16.0s · MCU · push **Beat:** reframe **Subject:** founder, same posture, looking at camera, expression shifts from tired to clear, small almost-smile **On-screen text:** "You don't have a content problem. / You have an infrastructure problem." (Inter Black 900, coral, center, type-on at 11.5s) **Rationale:** Slow push as the reframe lands. Same character, same environment, only the expression changes. The change is the point. --- ### shot_05 · 16.0–20.0s · MS · static **Beat:** proof **Subject:** founder gestures toward laptop screen, kanban-style content pipeline visible, organized columns, clean structure **On-screen text:** "Voice extracted. Brand locked. System runs." (Inter Medium 500, upper-third, slide-up at 16.4s) **Rationale:** Proof beat, show the system, not the result. The kanban metaphor is recognizable to operators. Subject right-weighted, text upper-third. --- ### shot_06 · 20.0–26.0s · MCU · static **Beat:** promise **Subject:** founder, calm composed posture, fully present, slight smile, hands folded on desk **On-screen text:** "30 minutes a week. / 48-hour content cycles." (Inter Black 900, lower-third, type-on at 20.5s) **Rationale:** Promise beat, the after-state is the same person, just calmer. Static frame holds the moment. Text carries the specific commitment. --- ### shot_07 · 26.0–30.0s · MS · static **Beat:** cta **Subject:** founder, neutral posture, room visible behind, calm presence, gentle eye contact with camera **On-screen text:** "whystrohm.com/scan" (Inter Black 900, coral, lower-third, type-on at 26.4s) **Rationale:** CTA beat. Pulled out to MS to give text the breathing room. Subject centered, text lower-third with URL. No motion, let it land. --- ## Handoff notes - **For image generation:** run `visual-prompt-forge` against `shots.json` - **For HTML preview:** run `storyboard-html-preview` against this folder - **For QA on generated frames:** run `visual-asset-critic` with the generated image and the shot ID ## Audit trail This storyboard was generated against `brand-lock.snapshot.md` (frozen at 2026-05-07T14:23:00Z, source `brand-packs/whystrohm.md`). If the brand-lock changes after this date, re-run to pick up the new state. -
text-overlays.json 2.2 KB
{ "version": "1.0", "overlays": [ { "id": "text_01", "shot_id": "shot_01", "content": "Your content feels random.", "font": "Inter Black 900", "size": "display", "weight": "black", "color": "#2A2A32", "position": "lower-third", "enter": { "at": 0.4, "animation": "hard-cut" }, "exit": { "at": 2.0, "animation": "hard-cut" } }, { "id": "text_02", "shot_id": "shot_02", "content": "Posting more isn't fixing it.", "font": "Inter Black 900", "size": "headline", "weight": "black", "color": "#2A2A32", "position": "right-third", "enter": { "at": 2.4, "animation": "fade-in" }, "exit": { "at": 6.8, "animation": "fade-out" } }, { "id": "text_03", "shot_id": "shot_04", "content": "You don't have a content problem.\nYou have an infrastructure problem.", "font": "Inter Black 900", "size": "headline", "weight": "black", "color": "#D94F3A", "position": "center", "enter": { "at": 11.5, "animation": "type-on" }, "exit": { "at": 15.8, "animation": "fade-out" } }, { "id": "text_04", "shot_id": "shot_05", "content": "Voice extracted. Brand locked. System runs.", "font": "Inter Medium 500", "size": "body", "weight": "medium", "color": "#2A2A32", "position": "upper-third", "enter": { "at": 16.4, "animation": "slide-up" }, "exit": { "at": 19.8, "animation": "fade-out" } }, { "id": "text_05", "shot_id": "shot_06", "content": "30 minutes a week.\n48-hour content cycles.", "font": "Inter Black 900", "size": "headline", "weight": "black", "color": "#2A2A32", "position": "lower-third", "enter": { "at": 20.5, "animation": "type-on" }, "exit": { "at": 25.8, "animation": "fade-out" } }, { "id": "text_06", "shot_id": "shot_07", "content": "whystrohm.com/scan", "font": "Inter Black 900", "size": "headline", "weight": "black", "color": "#D94F3A", "position": "lower-third", "enter": { "at": 26.4, "animation": "type-on" }, "exit": { "at": 29.8, "animation": "hard-cut" } } ] }
-
-
60s-founder-explainer
-
brand-lock.snapshot.md 2.9 KB
<!-- snapshot taken: 2026-05-07T14:23:00Z --> <!-- source: brand-packs/whystrohm.md --> # Brand Lock: WhyStrohm ## Identity **Brand:** WhyStrohm **One-line description:** Managed content infrastructure for founder-led brands. Voice extraction, brand guardrails encoded in code, programmatic video, automated publishing. 30 minutes a week of founder time, 48-hour content cycles. **Archetype:** Operator **Voice posture:** Calm, considered, confident without shouting ## Palette | Role | Hex | Use | |---|---|---| | Background | `#F5F0E8` | Cream, primary canvas | | Ink | `#2A2A32` | Near-black, primary text and dark elements | | Accent (warm) | `#D94F3A` | Coral, emphasis, periods on signature beats | | Accent (cool) | `#C9BBE0` | Pale lavender, secondary highlights | | Muted | `#7A7580` | Secondary text, captions | | Rule | `#E8E1D4` | Subtle borders, dividers, card backgrounds | ## Typography **Display font:** `Inter Black 900`, headline weight, hooks, on-screen text **Body font:** `Inter Medium 500`, body copy, captions, UI **Mono font:** `JetBrains Mono Regular`, code, data, technical references ## Mood adjectives - operator (not creator) - considered (not reactive) - deterministic (not vibes-based) - confident (without volume) - defense-grade (not fragile) ## Never list - never use stock photo aesthetic - never use AI uncanny faces - never over-saturate the cream background - never use coral as a flood color (only as accent or signature periods) - never use em dashes in copy - never use emojis in body copy - never use bullet points in narrative copy - never use clip-art or generic icon sets - never use hype words ("game-changing", "revolutionary", "next-level", "comprehensive") - never use exclamation points in headlines - never default to dark mode, light mode hybrid is the brand - never animate text with bouncing or wobbling, type-on, fade, or hard cut only ## Aspect ratios - 9:16, primary for short-form social - 16:9, long-form, hero films, web embeds - 1:1, feed posts, podcast covers - 4:5. Instagram feed alternative ## Color grade direction Warm filmic, muted teal shadows. Slight grain. Cream highlights, deep navy/charcoal shadows, never crushed. Reminiscent of Kodak Portra 400 with a slight digital cleanup. ## Motion language Camera moves are minimal and deliberate. Default to static. When motion is used, slow push or slow pull only. Cuts on action, not on time. Type-on for emphasis, never bouncing or wobbling. Coral periods animate as a hard pop on signature beats. Transitions are hard cuts or 6-frame dissolves only. ## Voice rules - no em dashes - no emojis in body copy - no exclamation points in headlines - no hype words - prefer specific numbers over vague claims - prefer present-tense over future-tense - prefer "operator" over "creator", "infrastructure" over "agency" --- **Last updated:** 2026-05-07 **Owner:** Yuri Strohm **Version:** 1.0 -
preview.html 29.1 KB · in bundle
-
run.json 837 B
{ "version": "1.0", "run_id": "20260507T142300Z-005daed8", "created_at": "2026-05-07T14:23:00Z", "shotkit_version": "3.0.0", "operator": null, "project": { "title": "WhyStrohm. How The System Actually Works", "duration_s": 60, "aspect": "9:16", "framework": "founder-explainer" }, "inputs": { "shots_ref": "shots.json", "shots_sha256": "23725f0696b0a614b6942fb04f9e70cbd20bdf68ccb7b95cf66b0ccdd82d2bef", "text_overlays_ref": "text-overlays.json", "text_overlays_sha256": "fb8bf44f326b1a8caf1d8b0273809d6c46d4155d32a2fe4883f790e04019f64e", "brand_lock_ref": "brand-lock.snapshot.md", "brand_lock_sha256": "f364bc71f68d16187d0721fbc092450174545e247f2f563a4c45686b4a877f50", "brand_lock_source": "brand-packs/whystrohm.md", "brand_lock_configured": true }, "rounds": [] } -
shots.json 5.7 KB
{ "version": "1.0", "project": { "title": "WhyStrohm. How The System Actually Works", "duration_s": 60, "aspect": "9:16", "framework": "founder-explainer" }, "brand_lock_ref": "brand-lock.snapshot.md", "series_lock": { "character": "founder, mid-thirties, salt-and-pepper hair, navy crewneck, speaking directly to camera, calm authoritative posture", "environment": "minimalist home office, white walls, oak desk, single houseplant, late afternoon", "lighting": "soft natural side-light, large window camera-left, warm afternoon golden hour, gentle shadow rolloff", "color_grade": "warm filmic, muted teal shadows, slight grain, cream highlights, deep navy shadows never crushed" }, "shots": [ { "id": "shot_01", "beat": "hook", "start": 0.0, "end": 2.0, "framing": "MCU", "angle": "eye-level", "motion": "static", "depth_of_field": "shallow", "subject": "founder, looking directly into camera, slight knowing smile, mid-statement", "environment_ref": "series_lock.environment", "lighting_ref": "series_lock.lighting", "on_screen_text": "text_01", "vo": "Most founders treat content like a hobby.", "rationale": "Hook is provocative but calm, voice carries the bite. Direct eye contact, MCU lock-in." }, { "id": "shot_02", "beat": "stakes", "start": 2.0, "end": 8.0, "framing": "MS", "angle": "eye-level", "motion": "static", "depth_of_field": "shallow", "subject": "founder, gesturing slightly with one hand, explaining, eye contact maintained", "environment_ref": "series_lock.environment", "lighting_ref": "series_lock.lighting", "on_screen_text": null, "vo": "Then they wonder why every post feels like starting over. New idea, new angle, no compounding.", "rationale": "Stakes beat. Pulled out to MS to let the gesture read. VO does the work, no on-screen text needed." }, { "id": "shot_03", "beat": "stakes", "start": 8.0, "end": 14.0, "framing": "ECU", "angle": "eye-level", "motion": "static", "depth_of_field": "shallow", "subject": "close on founder's eyes, considered expression, slight head tilt", "environment_ref": "series_lock.environment", "lighting_ref": "series_lock.lighting", "on_screen_text": "text_02", "vo": "It's not creativity. It's missing infrastructure.", "rationale": "ECU pulls the audience into the realization moment. Text reinforces the line that needs to land." }, { "id": "shot_04", "beat": "insight", "start": 14.0, "end": 24.0, "framing": "MCU", "angle": "eye-level", "motion": "push", "depth_of_field": "shallow", "subject": "founder, leaning slightly forward, more energy in delivery, hands resting on desk", "environment_ref": "series_lock.environment", "lighting_ref": "series_lock.lighting", "on_screen_text": null, "vo": "Every brand has a voice. Most founders never extract it. So every post starts from scratch.", "rationale": "Insight beat, slow push as the thesis builds. VO is the load-bearing element here." }, { "id": "shot_05", "beat": "insight", "start": 24.0, "end": 32.0, "framing": "MS", "angle": "eye-level", "motion": "static", "depth_of_field": "deep", "subject": "founder gestures toward laptop screen, kanban-style content pipeline visible, voice extraction interface, brand-lock file open in editor", "environment_ref": "series_lock.environment", "lighting_ref": "series_lock.lighting", "on_screen_text": "text_03", "vo": "Extract it once. Lock it in code. Now every post compounds.", "rationale": "Show the artifact. Text reinforces the three-step pattern. Deep DOF keeps the screen legible." }, { "id": "shot_06", "beat": "proof", "start": 32.0, "end": 42.0, "framing": "MCU", "angle": "eye-level", "motion": "static", "depth_of_field": "shallow", "subject": "founder, calm steady delivery, eye contact, hands folded", "environment_ref": "series_lock.environment", "lighting_ref": "series_lock.lighting", "on_screen_text": "text_04", "vo": "Hundreds of videos rendered from code. 48-hour content cycles. One operator.", "rationale": "Proof beat, concrete numbers. Stat callout in upper-third lets viewer absorb visually while VO confirms." }, { "id": "shot_07", "beat": "insight", "start": 42.0, "end": 50.0, "framing": "MS", "angle": "eye-level", "motion": "pull", "depth_of_field": "shallow", "subject": "founder, slight smile, settled into the explanation, room visible around him", "environment_ref": "series_lock.environment", "lighting_ref": "series_lock.lighting", "on_screen_text": null, "vo": "Not because we work harder. Because the system runs.", "rationale": "Slow pull releases tension. The thesis lands. VO carries, text would dilute." }, { "id": "shot_08", "beat": "cta", "start": 50.0, "end": 60.0, "framing": "MCU", "angle": "eye-level", "motion": "static", "depth_of_field": "shallow", "subject": "founder, calm, looks directly into camera one more time, almost-smile, settled", "environment_ref": "series_lock.environment", "lighting_ref": "series_lock.lighting", "on_screen_text": "text_05", "vo": "If this sounds like the version of content you actually want, run a free scan.", "rationale": "CTA beat. Static MCU brings it home. URL holds in lower-third for full read-twice duration." } ] } -
storyboard.md 4.8 KB
# WhyStrohm. How The System Actually Works | | | |---|---| | **Duration** | 60s | | **Aspect** | 9:16 | | **Beat framework** | Founder Explainer | | **Brand lock** | [`brand-lock.snapshot.md`](./brand-lock.snapshot.md) | | **Generated** | 2026-05-07 14:31 UTC | --- ## Brief A 60-second founder explainer for WhyStrohm. The audience is operators and founders who already know they need a content system but haven't seen the WhyStrohm thesis articulated. The narrative arc moves through five micro-beats: hook, stakes, insight, proof, and CTA. Voice carries the explanation; on-screen text reinforces the key turns. ## Beat framework: Founder Explainer Five micro-beats: - 0–2s. Hook (provocation) - 2–14s. Stakes (why it matters) - 14–32s. Insight (the actual point, extended for 60s to give the thesis room) - 32–42s. Proof (the numbers) - 42–60s. Insight wrap + CTA ## Series lock | | | |---|---| | **Character anchor** | founder, mid-thirties, salt-and-pepper hair, navy crewneck, speaking directly to camera, calm authoritative posture | | **Environment** | minimalist home office, white walls, oak desk, single houseplant, late afternoon | | **Lighting** | soft natural side-light, large window camera-left, warm afternoon golden hour, gentle shadow rolloff | | **Color grade** | warm filmic, muted teal shadows, slight grain, cream highlights, deep navy shadows never crushed | --- ## Shots ### shot_01 · 0.0–2.0s · MCU · static **Beat:** hook · **VO:** "Most founders treat content like a hobby." **Subject:** founder, looking directly into camera, slight knowing smile, mid-statement. **On-screen text:** "Most founders treat content like a hobby." (lower-third, hard-cut) **Rationale:** Hook is provocative but calm, voice carries the bite. Direct eye contact, MCU lock-in. ### shot_02 · 2.0–8.0s · MS · static **Beat:** stakes · **VO:** "Then they wonder why every post feels like starting over. New idea, new angle, no compounding." **Subject:** founder, gesturing slightly with one hand, explaining, eye contact maintained. **Rationale:** Stakes beat. Pulled out to MS to let the gesture read. VO does the work, no on-screen text needed. ### shot_03 · 8.0–14.0s · ECU · static **Beat:** stakes · **VO:** "It's not creativity. It's missing infrastructure." **Subject:** close on founder's eyes, considered expression, slight head tilt. **On-screen text:** "It's missing infrastructure." (coral, lower-third, type-on) **Rationale:** ECU pulls the audience into the realization moment. Text reinforces the line that needs to land. ### shot_04 · 14.0–24.0s · MCU · push **Beat:** insight · **VO:** "Every brand has a voice. Most founders never extract it. So every post starts from scratch." **Subject:** founder, leaning slightly forward, more energy in delivery, hands resting on desk. **Rationale:** Slow push as the thesis builds. VO is the load-bearing element here. ### shot_05 · 24.0–32.0s · MS · static **Beat:** insight · **VO:** "Extract it once. Lock it in code. Now every post compounds." **Subject:** founder gestures toward laptop screen, kanban content pipeline, voice extraction interface, brand-lock file open in editor. **On-screen text:** "Extract. Lock. Compound." (upper-third, slide-up) **Rationale:** Show the artifact. Text reinforces the three-step pattern. Deep DOF keeps the screen legible. ### shot_06 · 32.0–42.0s · MCU · static **Beat:** proof · **VO:** "Hundreds of videos rendered from code. 48-hour content cycles. One operator." **Subject:** founder, calm steady delivery, eye contact, hands folded. **On-screen text:** "hundreds of videos · 48hr cycles · 1 operator" (upper-third, fade) **Rationale:** Proof beat, concrete numbers. Stat callout lets viewer absorb visually while VO confirms. ### shot_07 · 42.0–50.0s · MS · pull **Beat:** insight · **VO:** "Not because we work harder. Because the system runs." **Subject:** founder, slight smile, settled into the explanation, room visible around him. **Rationale:** Slow pull releases tension. The thesis lands. VO carries, text would dilute. ### shot_08 · 50.0–60.0s · MCU · static **Beat:** cta · **VO:** "If this sounds like the version of content you actually want, run a free scan." **Subject:** founder, calm, looks directly into camera one more time, almost-smile, settled. **On-screen text:** "whystrohm.com/scan" (coral, lower-third, type-on) **Rationale:** CTA beat. Static MCU brings it home. URL holds in lower-third for full read-twice duration. --- ## Handoff notes - **For image generation:** run `visual-prompt-forge` against `shots.json` - **For HTML preview:** run `storyboard-html-preview` against this folder - **For QA:** run `visual-asset-critic` per shot ## Audit trail Generated against `brand-lock.snapshot.md` (frozen at 2026-05-07T14:31:00Z, source `brand-packs/whystrohm.md`). -
text-overlays.json 1.8 KB
{ "version": "1.0", "overlays": [ { "id": "text_01", "shot_id": "shot_01", "content": "Most founders treat content like a hobby.", "font": "Inter Black 900", "size": "headline", "weight": "black", "color": "#2A2A32", "position": "lower-third", "enter": { "at": 0.4, "animation": "hard-cut" }, "exit": { "at": 1.9, "animation": "hard-cut" } }, { "id": "text_02", "shot_id": "shot_03", "content": "It's missing infrastructure.", "font": "Inter Black 900", "size": "headline", "weight": "black", "color": "#D94F3A", "position": "lower-third", "enter": { "at": 9.0, "animation": "type-on" }, "exit": { "at": 13.8, "animation": "fade-out" } }, { "id": "text_03", "shot_id": "shot_05", "content": "Extract. Lock. Compound.", "font": "Inter Black 900", "size": "headline", "weight": "black", "color": "#2A2A32", "position": "upper-third", "enter": { "at": 24.4, "animation": "slide-up" }, "exit": { "at": 31.8, "animation": "fade-out" } }, { "id": "text_04", "shot_id": "shot_06", "content": "hundreds of videos · 48hr cycles · 1 operator", "font": "Inter Medium 500", "size": "body", "weight": "medium", "color": "#2A2A32", "position": "upper-third", "enter": { "at": 32.5, "animation": "fade-in" }, "exit": { "at": 41.8, "animation": "fade-out" } }, { "id": "text_05", "shot_id": "shot_08", "content": "whystrohm.com/scan", "font": "Inter Black 900", "size": "headline", "weight": "black", "color": "#D94F3A", "position": "lower-third", "enter": { "at": 52.5, "animation": "type-on" }, "exit": { "at": 59.8, "animation": "hard-cut" } } ] }
-
-
shotkit-explainer
-
brand-lock.snapshot.md 4 KB
<!-- snapshot taken: 2026-05-08T14:34:24Z --> <!-- source: brand-packs/whystrohm.md --> <!-- storyboard: shotkit-explainer --> # Brand Lock: WhyStrohm ## Identity **Brand:** WhyStrohm **One-line description:** Managed content infrastructure for founder-led brands. Voice extraction, brand guardrails encoded in code, programmatic video, automated publishing. 30 minutes a week of founder time, 48-hour content cycles. **Archetype:** Operator **Voice posture:** Calm, considered, confident without shouting ## Palette | Role | Hex | Use | |---|---|---| | Background | `#F5F0E8` | Cream, primary canvas | | Ink | `#2A2A32` | Near-black, primary text and dark elements | | Accent (warm) | `#D94F3A` | Coral, emphasis, periods on signature beats | | Accent (cool) | `#C9BBE0` | Pale lavender, secondary highlights | | Muted | `#7A7580` | Secondary text, captions | | Rule | `#E8E1D4` | Subtle borders, dividers, card backgrounds | ## Typography **Display font:** `Inter Black 900`, headline weight, hooks, on-screen text **Body font:** `Inter Medium 500`, body copy, captions, UI **Mono font:** `JetBrains Mono Regular`, code, data, technical references Two-font stack for production. Mono only for technical content. ## Mood adjectives - operator (not creator) - considered (not reactive) - deterministic (not vibes-based) - confident (without volume) - defense-grade (not fragile) ## Never list - never use stock photo aesthetic - never use AI uncanny faces - never over-saturate the cream background - never use coral as a flood color (only as accent or signature periods) - never use em dashes in copy (only periods, commas, semicolons) - never use emojis in body copy - never use bullet points in narrative copy - never use clip-art or generic icon sets - never use hype words ("game-changing", "revolutionary", "next-level", "comprehensive") - never use exclamation points in headlines - never default to dark mode, light mode hybrid is the brand - never animate text with bouncing or wobbling, type-on, fade, or hard cut only - never break the eye line between subject and the implied "operator perspective" ## Aspect ratios - 9:16, primary for short-form social - 16:9, long-form, hero films, web embeds - 1:1, feed posts, podcast covers - 4:5. Instagram feed alternative ## Color grade direction Warm filmic, muted teal shadows. Slight grain. Cream highlights, deep navy/charcoal shadows, never crushed. Reminiscent of Kodak Portra 400 with a slight digital cleanup. Skin tones warm but not orange. Greens kept slightly desaturated to push focus to subject. ## Motion language Camera moves are minimal and deliberate. Default to static. When motion is used, slow push or slow pull only. Cuts on action, not on time. Type-on for emphasis, never bouncing or wobbling. Coral periods animate as a hard pop on signature beats. Transitions are hard cuts or 6-frame dissolves only, no fancy wipes, no zooms. For kinetic typography content (the Content Spiral pattern): pacing accelerates inward, each zoom 1.5–2x faster than the previous. Snap-back to wide claim is a hard cut. ## Voice rules - no em dashes - no emojis in body copy - no exclamation points in headlines - no hype words ("game-changing", "revolutionary", "comprehensive", "next-level", "leverage") - no clichéd business metaphors ("move the needle", "drink the kool-aid", "low-hanging fruit") - no qualifiers in declarative copy ("perhaps", "maybe", "kind of") - prefer specific numbers over vague claims ("48 hours" not "fast", "30 minutes a week" not "a little time") - prefer present-tense over future-tense - prefer "operator" over "creator", "infrastructure" over "agency", "system" over "service" - coral period (`.`) on signature lines is a typography choice, not punctuation, apply at end of hero phrases only ## Reference materials - Site: https://whystrohm.com - Brand voice extractor: https://github.com/whystrohm/media-tsunami - Past hero films: see `/results` page - Content Spiral pattern: documented in WhyStrohm production bible v3 (internal) --- **Last updated:** 2026-05-07 **Owner:** Yuri Strohm **Version:** 1.0 -
preview.html 28.6 KB · in bundle
-
run.json 820 B
{ "version": "1.0", "run_id": "20260508T143424Z-a38d935c", "created_at": "2026-05-08T14:34:24Z", "shotkit_version": "3.0.0", "operator": null, "project": { "title": "shotkit. The Explainer.", "duration_s": 90, "aspect": "16:9", "framework": "founder-explainer" }, "inputs": { "shots_ref": "shots.json", "shots_sha256": "09f0e08a1623c033629bbc859cac198599f4823dba14e598d1ce3cdbba6aa21e", "text_overlays_ref": "text-overlays.json", "text_overlays_sha256": "d685bcebbca9d59565bf81c12fc0266084c3f7361c48635f5d7d87183bbe1002", "brand_lock_ref": "brand-lock.snapshot.md", "brand_lock_sha256": "dbb416f3cbac8e40982ad2f260e277487ebe4bf4c783c0aee75045260e4fbf14", "brand_lock_source": "brand-packs/whystrohm.md", "brand_lock_configured": true }, "rounds": [] } -
shots.json 6.2 KB
{ "version": "1.2", "project": { "title": "shotkit. The Explainer.", "duration_s": 90, "aspect": "16:9", "framework": "founder-explainer" }, "brand_lock_ref": "brand-lock.snapshot.md", "series_lock": { "character": "no human subject. typography-driven explainer. document-control aesthetic with cream canvas, ink type, coral accents on signature beats", "environment": "operator-grade dashboard. cream background, JetBrains Mono headers, Inter body, single coral accent per beat. no clutter, no decorative imagery", "lighting": "even diffuse. no shadow rolloff. flat operator-doc grading throughout", "color_grade": "cream highlights (#F5F0E8), ink shadows (#2A2A32), coral (#D94F3A) reserved for signature beats only. no saturation drift" }, "shots": [ { "id": "shot_01", "beat": "pain", "start": 0.0, "end": 12.0, "framing": "WS", "angle": "eye-level", "motion": "static", "depth_of_field": "deep", "subject": "vague founder brief types into the left panel in JetBrains Mono. cursor blinks. then a chaos cut shows four image generators producing slightly off-brand outputs in a 2x2 grid. each tile renders the same shot with a different palette, framing, and font choice. the misfire is the visual.", "environment_ref": "series_lock.environment", "lighting_ref": "series_lock.lighting", "on_screen_text": "text_01", "vo": null, "rationale": "Cold open names the failure mode every founder recognizes. Vague brief plus model-roulette equals brand drift. The 2x2 grid does the work text cannot. Static framing keeps the focus on the diagnosis." }, { "id": "shot_02", "beat": "reframe", "start": 12.0, "end": 30.0, "framing": "MS", "angle": "eye-level", "motion": "static", "depth_of_field": "deep", "subject": "five horizontal layers stack from top to bottom. each labeled in JetBrains Mono caps with 0.08em letter-spacing. brand lock, series lock, shot spec, text layer, generator adapter. each layer slides in from below on a 6-frame stagger. ink rules between. coral accent appears as a single dot on the brand lock layer when it lands.", "environment_ref": "series_lock.environment", "lighting_ref": "series_lock.lighting", "on_screen_text": "text_02", "vo": null, "rationale": "Architecture beat. The reader needs to see the layers exist before any claim about composability lands. Stack layout is the diagram doing the explanation. Coral accent on brand lock signals it as the load-bearing field." }, { "id": "shot_03", "beat": "proof-1", "start": 30.0, "end": 50.0, "framing": "MS", "angle": "eye-level", "motion": "push", "depth_of_field": "deep", "subject": "split panel. left BRIEF column shows typewriter brief slowly assembling. right OUTPUT column starts empty with a pulsing waiting dot, then file tree assembles row by row in JetBrains Mono Regular 16pt. storyboard.md, shots.json, text-overlays.json, brand-lock.snapshot.md, prompts directory with seven adapter files, preview.html. coral dot appears next to preview.html on completion.", "environment_ref": "series_lock.environment", "lighting_ref": "series_lock.lighting", "on_screen_text": "text_03", "vo": null, "rationale": "The build moment is the centerpiece. Brief in, structured files out. This is the shot that earns the methodology claim. Same animation language as the social demo so brand consistency reads across both surfaces." }, { "id": "shot_04", "beat": "proof-2", "start": 50.0, "end": 65.0, "framing": "MCU", "angle": "eye-level", "motion": "push", "depth_of_field": "shallow", "subject": "coral wipe expands radially from the preview.html dot. wipe reveals a rendered preview iframe scrolling vertically. the reader sees the actual shot cards from the 30-second example pass through frame, including framing, angle, motion, color-grade rows. subtle ten-percent vignette on edges.", "environment_ref": "series_lock.environment", "lighting_ref": "series_lock.lighting", "on_screen_text": "text_04", "vo": null, "rationale": "Reveal beat. The reader has now seen the methodology and the file outputs. The iframe shows the actual deliverable, not a mock. This is what separates the claim from the artifact." }, { "id": "shot_05", "beat": "proof-3", "start": 65.0, "end": 80.0, "framing": "MS", "angle": "eye-level", "motion": "static", "depth_of_field": "deep", "subject": "single shot description card sits center-frame. seven generator adapter logos and labels fan out radially around it in a 280-pixel radius. midjourney, flux, ideogram, gpt image, nano banana, seedream, runway sora. lines connect center card to each adapter label in 1px ink. each adapter shows its prompt syntax appearing as a typewriter pass, all running simultaneously.", "environment_ref": "series_lock.environment", "lighting_ref": "series_lock.lighting", "on_screen_text": "text_05", "vo": null, "rationale": "Cross-generator demonstration. Same shot description compiles to seven different prompt syntaxes simultaneously. The visual proves model-agnosticism is structural, not aspirational." }, { "id": "shot_06", "beat": "promise", "start": 80.0, "end": 90.0, "framing": "WS", "angle": "eye-level", "motion": "static", "depth_of_field": "deep", "subject": "full-frame typography. install command appears via type-on in JetBrains Mono Regular at 22pt, ink color, monospace block. below it the project tagline in Inter italic 500 18pt, color muted. coral period on the signature beat. door closes with the standard footer chrome holding.", "environment_ref": "series_lock.environment", "lighting_ref": "series_lock.lighting", "on_screen_text": ["text_06", "text_07"], "vo": null, "rationale": "Promise beat. The viewer has the diagnosis, the architecture, the artifact, and the proof. The CTA is one paste-into-terminal command. No friction surface, no signup, no email gate." } ] } -
storyboard.md 6.2 KB
# shotkit. The Explainer. **Project:** shotkit explainer film **Duration:** 90 seconds **Aspect:** 16:9 (1280x720 hero render, 1080x1920 vertical cut for socials) **Framework:** founder-explainer **Brand-lock:** [brand-lock.snapshot.md](./brand-lock.snapshot.md) snapshotted 2026-05-08T14:34:24Z **Generated:** 2026-05-08 > This storyboard is the record of the v0.1.0 explainer film as it was actually rendered, so > shot_05 still names seven adapters including Runway/Sora. Sora was discontinued and the > motion lane moved to Kling, Veo, Seedance, and Hailuo. The storyboard is deliberately left > matching the artifact it produced: editing it to describe a video that was never made is > exactly the kind of drift this repo exists to prevent. See `CHANGELOG.md` for the adapter > change. ## Brief 90-second explainer for shotkit. Targets founder-led brands at $500K to $5M ARR who run their own content and feel the prompt-engineering tax. Walks through the diagnosis (vague brief plus model-roulette equals brand drift), the methodology (five-layer prompt anatomy), the build moment (brief in, files out), the proof (rendered preview from a real shotkit run), and the install command. Lives at the top of the shotkit blog post on whystrohm.com. ## Series lock | Field | Value | |---|---| | **Character** | No human subject. Typography-driven explainer with document-control aesthetic. | | **Environment** | Operator-grade dashboard. Cream canvas, JetBrains Mono headers, Inter body, single coral accent per beat. | | **Lighting** | Even diffuse. No shadow rolloff. Flat operator-doc grading throughout. | | **Color grade** | Cream highlights (#F5F0E8), ink shadows (#2A2A32), coral (#D94F3A) reserved for signature beats. No saturation drift. | ## Shots ### shot_01: 0.0 to 12.0s. WS. eye-level. static. **Beat:** pain. **Subject:** vague founder brief types into the left panel in JetBrains Mono. Cursor blinks. Then a chaos cut shows four image generators producing slightly off-brand outputs in a 2x2 grid. Each tile renders the same shot with a different palette, framing, font. **On-screen text:** "You don't have a content problem. You have a pre-production problem." (Inter Black 900, center, fade) **Rationale:** Cold open names the failure mode every founder recognizes. Vague brief plus model-roulette equals brand drift. The 2x2 grid does the work text cannot. ### shot_02: 12.0 to 30.0s. MS. eye-level. static. **Beat:** reframe. **Subject:** Five horizontal layers stack from top to bottom. Each labeled in JetBrains Mono caps with 0.08em letter-spacing: brand lock, series lock, shot spec, text layer, generator adapter. Each layer slides in from below on a 6-frame stagger. Coral dot lands on brand lock as it appears. **On-screen text:** "Five layers. Locked top to bottom." (Inter Black 900, upper-third, type-on) **Rationale:** Architecture beat. The reader needs to see the layers exist before any claim about composability lands. Stack layout is the diagram doing the explanation. ### shot_03: 30.0 to 50.0s. MS. eye-level. push. **Beat:** proof-1. **Subject:** Split panel. Left BRIEF column types out a brief slowly. Right OUTPUT column starts empty with a pulsing waiting dot, then file tree assembles row by row in JetBrains Mono Regular 16pt. storyboard.md, shots.json, text-overlays.json, brand-lock.snapshot.md, prompts/ directory with seven adapter files, preview.html. Coral dot lands on preview.html on completion. **On-screen text:** "Brief in. Storyboard out. Audit trail included." (Inter Medium 500, upper-third, fade) **Rationale:** The build moment is the centerpiece. Brief in, structured files out. Same animation language as the social demo so brand consistency reads across both surfaces. ### shot_04: 50.0 to 65.0s. MCU. eye-level. push. **Beat:** proof-2. **Subject:** Coral wipe expands radially from the preview.html dot. Wipe reveals the rendered preview iframe scrolling vertically. The reader sees actual shot cards from the 30-second example pass through frame, including framing, angle, motion, color-grade rows. Subtle 10% vignette on edges. **On-screen text:** "Files. Not panels. Not a SaaS dashboard." (Inter Black 900, lower-third, type-on) **Rationale:** Reveal beat. The reader has now seen the methodology and the file outputs. The iframe shows the actual deliverable, not a mock. ### shot_05: 65.0 to 80.0s. MS. eye-level. static. **Beat:** proof-3. **Subject:** Single shot description card sits center-frame. Seven generator adapter labels fan out radially around it. Midjourney, Flux, Ideogram, GPT Image, Nano Banana, Seedream, Runway/Sora. Lines connect center card to each adapter in 1px ink. Each adapter shows its prompt syntax appearing as a typewriter pass running simultaneously. **On-screen text:** "One shot. Seven generators. One spec." (Inter Black 900, upper-third, fade) **Rationale:** Cross-generator demonstration. Same shot description compiles to seven different prompt syntaxes simultaneously. The visual proves model-agnosticism is structural, not aspirational. ### shot_06: 80.0 to 90.0s. WS. eye-level. static. **Beat:** promise. **Subject:** Full-frame typography. Install command appears via type-on in JetBrains Mono Regular at 22pt. Below it the project tagline in Inter italic 500 18pt, color muted. Coral period on the signature beat. Standard footer chrome holds. **On-screen text:** "git clone github.com/whystrohm/shotkit / cd shotkit && ./install.sh" (JetBrains Mono, center, type-on) plus tagline "Pre-production for founder-led video at scale." (Inter italic 500, lower-third, fade). **Rationale:** Promise beat. The viewer has the diagnosis, the architecture, the artifact, and the proof. The CTA is one paste-into-terminal command. No friction surface. ## Audit trail - Brief, this document, scoped from the shotkit blog launch sequence. - Brand-lock snapshotted from `brand-packs/whystrohm.md` on 2026-05-08. - Shot list designed to compose with the existing ShotkitDemo composition (same chrome, complementary content, longer arc). - Render shipped at [whystrohm.com/blog/you-dont-have-a-content-problem](https://whystrohm.com/blog/you-dont-have-a-content-problem) as the "we used shotkit to make this video" dogfood reveal. - Landscape MP4 served from the blog: `/media/shotkit-explainer/shotkit-explainer.mp4`. -
text-overlays.json 2.7 KB
{ "version": "1.0", "overlays": [ { "id": "text_01", "shot_id": "shot_01", "content": "You don't have a content problem.\nYou have a pre-production problem.", "font": "Inter Black 900", "size": "display", "weight": "black", "color": "#2A2A32", "position": "center", "enter": { "at": 6.0, "animation": "fade-in" }, "exit": { "at": 11.5, "animation": "fade-out" } }, { "id": "text_02", "shot_id": "shot_02", "content": "Five layers. Locked top to bottom.", "font": "Inter Black 900", "size": "display", "weight": "black", "color": "#2A2A32", "position": "upper-third", "enter": { "at": 12.5, "animation": "type-on" }, "exit": { "at": 29.5, "animation": "fade-out" } }, { "id": "text_03", "shot_id": "shot_03", "content": "Brief in. Storyboard out. Audit trail included.", "font": "Inter Medium 500", "size": "body", "weight": "medium", "color": "#2A2A32", "position": "upper-third", "enter": { "at": 30.5, "animation": "fade-in" }, "exit": { "at": 49.5, "animation": "fade-out" } }, { "id": "text_04", "shot_id": "shot_04", "content": "Files. Not panels. Not a SaaS dashboard.", "font": "Inter Black 900", "size": "display", "weight": "black", "color": "#2A2A32", "position": "lower-third", "enter": { "at": 51.0, "animation": "type-on" }, "exit": { "at": 64.5, "animation": "fade-out" } }, { "id": "text_05", "shot_id": "shot_05", "content": "One shot. Seven generators. One spec.", "font": "Inter Black 900", "size": "display", "weight": "black", "color": "#2A2A32", "position": "upper-third", "enter": { "at": 65.5, "animation": "fade-in" }, "exit": { "at": 79.5, "animation": "fade-out" } }, { "id": "text_06", "shot_id": "shot_06", "content": "git clone github.com/whystrohm/shotkit\ncd shotkit && ./install.sh", "font": "JetBrains Mono Regular", "size": "body", "weight": "regular", "color": "#2A2A32", "position": "center", "enter": { "at": 80.5, "animation": "type-on" }, "exit": { "at": 90.0, "animation": "hard-cut" } }, { "id": "text_07", "shot_id": "shot_06", "content": "Pre-production for founder-led video at scale.", "font": "Inter Medium 500", "size": "caption", "weight": "medium", "color": "#7A7580", "position": "lower-third", "enter": { "at": 86.0, "animation": "fade-in" }, "exit": { "at": 90.0, "animation": "hard-cut" } } ] }
-
-
-
references
-
beat-frameworks.md 2.7 KB
# Beat Frameworks Pick one. Don't invent a new one unless the brief genuinely doesn't fit. Document the choice in `storyboard.md`. ## Pain-Reframe-Promise (PRP) Default for conversion content. Three beats: 1. **Pain**, name the audience's actual problem in their actual language. Concrete, not abstract. "Your content feels random" not "marketing inefficiency." 2. **Reframe**, flip the problem on its head. The point is *not* what they thought it was. "You don't have a content problem. You have an infrastructure problem." 3. **Promise**, what life looks like on the other side. Specific, verifiable, time-bound when possible. Timing for 30s: 0–8 / 8–22 / 22–30. Timing for 60s: 0–15 / 15–45 / 45–60. Use when: ad creative, landing-page hero video, conversion-focused social. ## Hero Trilogy Default for product films and brand films. Three beats: 1. **World**, set the context. Who lives in this world. What's broken about it. 2. **Hero**, the product/founder/methodology arrives. Show what it does, not what it is. 3. **Transformation**, the world changes. Show before/after at scale. Timing for 60s: 0–20 / 20–40 / 40–60. Timing for 90s: 0–30 / 30–60 / 60–90. Use when: product launches, brand films, anchor pieces. ## Founder Explainer Default for personal-brand video where a person speaks to camera. Five micro-beats: 1. **Hook**, a one-line provocation (0–2s) 2. **Stakes**, why this matters (2–8s) 3. **Insight**, the actual point (8–20s for 30s, 8–40s for 60s) 4. **Proof**, one concrete example or data point 5. **CTA**, what to do next, narrow and specific Use when: founder content, thought leadership, personal-brand pieces. ## Content Spiral Default for kinetic typography or opinion content. The structure is recursive, each beat zooms in tighter: 1. **Wide claim**, the headline take 2. **Zoom 1**, one layer of nuance 3. **Zoom 2**, the layer underneath that 4. **Snap-back**, return to the wide claim, now reframed by the zooms Each zoom is 1.5–2x faster than the previous. Pacing accelerates inward. Use when: opinion videos, kinetic-type pieces, social-native commentary. ## Educational Demo Default for how-to content. Four beats: 1. **Problem state**, what someone is stuck on 2. **Reveal**, the technique or trick 3. **Walk-through**, apply it step by step 4. **Result**, the after-state, side-by-side with before Use when: tutorials, demo videos, training content. ## Custom If the brief doesn't fit, build a custom beat structure. Document in `storyboard.md`: ```markdown ## Beat framework: Custom **Why custom:** [one sentence, what made the standard frameworks insufficient] **Beats:** 1. [name], [purpose], [duration] 2. ... ``` Custom is a last resort, not a first move. The standard frameworks exist because they work. -
on-screen-text.md 3.8 KB
# On-Screen Text Text on screen is a load-bearing decision. Most storyboards over-text. The default should be: **does this shot need text to land?** If the visual carries the meaning, text dilutes. ## When on-screen text earns its keep 1. **Auto-play-mute environments.** Social feeds. Text replaces VO. 2. **Concept compression.** A short phrase lands harder than 4 seconds of narration. 3. **Stat or proof point.** Numbers stick visually in a way they don't audibly. 4. **Beat punctuation.** A single word or phrase that lands on a music hit. 5. **CTA.** The action you want the viewer to take. ## When on-screen text doesn't earn its keep 1. **Restating the VO.** If the voice says it, the text is noise. 2. **Decorative copy.** "Moments matter" floating over b-roll. Cut it. 3. **Brand vibing.** Product names everywhere. Logo lockup in the CTA covers this. 4. **Filler beats.** If the shot doesn't need text, leave it clean. ## Composition rules These flow into the `position` field of each text overlay. ### Negative-space-aware composition When a shot has on-screen text, the **shot subject must reserve space for it**. This is enforced at storyboard time, not generation time. If text is in the right third → subject composition leaves the right third clear. If text is lower third → subject upper two-thirds. If text is centered → subject framed to negative space around center. This goes into the shot's rationale: "MCU left-third subject, reserves right-third for `text_03`." ### Positions | Position | Use when | |---|---| | `center` | Display headline, single concept, hard cut in/out | | `lower-third` | Caption / VO substitute, persistent across shots | | `upper-third` | Stat callout, secondary information | | `left-third` | Subject is right-weighted in frame | | `right-third` | Subject is left-weighted in frame | | `{x: %, y: %}` | Custom, only when the standard positions don't fit | ### Sizes | Size | Pixel approx (1080p) | Use for | |---|---|---| | `display` | 96–144 px | Hero/hook beats | | `headline` | 56–80 px | Reframe beats | | `body` | 32–48 px | Caption-style, persistent | | `caption` | 20–28 px | Disclaimers, attribution | ### Animation Default to clean, not flashy: - Hook beats → `hard-cut` in, `hard-cut` out (no fade) - Reframe beats → `fade-in` 0.2s, `fade-out` 0.2s - Persistent captions → `slide-up` in, `slide-down` out - CTA → `type-on` for a typewriter effect, or `slide-up` Avoid stacking animations. Pick one per overlay. ## Color rules The `color` field of every text overlay must be a hex value that exists in the brand-lock palette. If you find yourself wanting a color that's not in the palette, the answer is not to add it, the answer is to pick a different overlay style or shot composition. ## Typography rules Same: every `font` field must reference a font defined in brand-lock typography. Two fonts max per project (display + body). More than that and the brand stops being recognizable. ## The "read twice" rule Already covered in timing-rules.md but worth repeating: text needs to be on screen long enough for someone to read it twice. Calculate, then verify. Don't eyeball. ## Stacking text across shots Text can persist across consecutive shots. This is useful when: - The text takes longer to read than a single shot's duration - You want text to feel anchored while visuals change underneath To stack, both shots reference the same `on_screen_text` ID. The overlay's `enter.at` aligns to the first shot's start, `exit.at` aligns to the second shot's end. In `shots.json`: ```json { "id": "shot_03", "on_screen_text": "text_02", ... }, { "id": "shot_04", "on_screen_text": "text_02", ... } ``` In `text-overlays.json`: ```json { "id": "text_02", "shot_id": ["shot_03", "shot_04"], ... } ``` Document this in rationale: "text persists across shot 3-4 to give 4.8s read-time for 16-word reframe." -
shot-grammar.md 3.5 KB
# Shot Grammar Controlled vocabulary. Use these exact terms in `shots.json`. Generators interpret loose language inconsistently, locked vocabulary survives translation. ## Framing (subject size in frame) | Code | Name | What it shows | |---|---|---| | `ECU` | Extreme close-up | Eyes, hands, single detail | | `CU` | Close-up | Head, or full hand-on-object | | `MCU` | Medium close-up | Head and shoulders | | `MS` | Medium shot | Waist up | | `MLS` | Medium long shot | Full body, environment minimal | | `WS` | Wide shot | Full body, environment present | | `EWS` | Extreme wide shot | Subject small in environment | Default to MCU and MS for talking-head founder content. ECU and CU for product detail and emotion. WS and EWS for context-setting. ## Angle (camera vertical position) | Code | Effect | |---|---| | `eye-level` | Neutral, default | | `high` | Subject feels smaller, vulnerable | | `low` | Subject feels powerful, dominant | | `overhead` | Detached, schematic, instructional | | `dutch` | Tilted, tension, unease | Default to eye-level. Use the others deliberately, not for variety. ## Motion (camera movement) | Code | Effect | Use when | |---|---|---| | `static` | No movement | Default. Static is not boring. | | `push` | Camera moves toward subject | Building intensity, revealing | | `pull` | Camera moves away | Releasing, contextualizing | | `pan-left` / `pan-right` | Camera rotates horizontally | Surveying environment | | `tilt-up` / `tilt-down` | Camera rotates vertically | Reveal scale or detail | | `handheld` | Subtle organic shake | Documentary feel, urgency | | `orbit` | Camera circles subject | Hero shots, product reveal | | `whip` | Fast pan as transition | Beat-cuts in fast-paced content | For AI-generated still frames, motion is mostly intent for the editor. For motion-video prompts (Kling, Veo, Seedance, Hailuo), motion translates directly. ## Depth of field | Code | Effect | |---|---| | `shallow` | Subject sharp, background blurred | | `deep` | Everything in focus | | `rack` | Focus shifts mid-shot | Default to shallow for talking-head, deep for environmental and schematic. ## Lighting style (referenced from series_lock) Don't redefine per shot. Define once in `series_lock.lighting`. Examples: - `soft natural side-light, large window left, warm afternoon` - `hard top-light, single source, deep shadows, studio black` - `practical mixed sources, neon accents, urban night` The series_lock string flows into every prompt. ## Subject description Structured, not poetic. Pattern: ``` [who/what], [doing what], [emotional or compositional note] ``` Good: ``` "Founder, mid-thirties, leaning forward at laptop, face partially turned to window light" ``` Bad: ``` "A determined entrepreneur conquering the digital frontier with passion" ``` Generators reward precision. Adjectives describing emotion ("determined", "passionate") produce stock-photo aesthetics. Describe what the camera actually sees. ## What goes in `subject` vs `series_lock` vs `brand_lock` This trips people up. The rule: - **brand_lock**, locked across the entire project. Palette, type, "never" list. - **series_lock**, locked across this storyboard. Character anchor, environment, lighting style, color grade. - **subject (per shot)**, what's different about *this* shot. Action, expression, framing-specific composition notes. If you find yourself repeating the same lighting description across shots, it belongs in series_lock. If you find yourself repeating the same color description across storyboards, it belongs in brand_lock. -
timing-rules.md 3.2 KB
# Timing Rules Pacing is math, not feel. Use these as defaults. Override only with reason documented in rationale. ## Shot duration baselines | Content type | Default shot duration | Range | |---|---|---| | Kinetic typography / opinion | 1.0–1.5s | 0.5–2.0s | | Founder talking-head | 2.5–4.0s | 2.0–6.0s | | Product/lifestyle b-roll | 1.5–2.5s | 1.0–3.5s | | Demo / how-to | 3.0–5.0s | 2.0–7.0s | | Cinematic brand film | 3.5–6.0s | 2.0–10.0s | If a single shot is longer than 7 seconds in a 30-second piece, justify it in rationale. Long shots are not bad. Unjustified long shots are. ## Hook timing (the first beat) The hook is 0–2s. Always. There are no exceptions in short-form content. Specifically: - 9:16 social: first frame must telegraph the topic. Auto-play on mute means the first half-second is fighting a swipe. - 16:9 in-feed: first 2s decides watch-time. The hook shot framing should be high-contrast against the shots that follow. If shot 2 is MS, shot 1 should not be MS. Visual contrast = retention. ## CTA timing (the final beat) The CTA is the final 4–6s for 30s content, 6–10s for 60s content. Specifically: - Last shot should hold long enough for someone to read the CTA text and act - Don't put motion on the CTA shot, let the text breathe - If there's a logo lockup, it lives in the final 2s, not earlier ## On-screen text duration math A text overlay needs to be on screen long enough to be **read twice**. Not once, twice. Why: viewers are skimming, eyes don't always lock on first frame. Reading speed reference (assume average viewer): - 1 short word (≤5 chars): 0.6s minimum read - Short phrase (≤6 words): 1.2s - Sentence (≤14 words): 2.4s - Long sentence (15–25 words): 4.0s Multiply by 2 for the "read twice" rule. So a 6-word phrase needs **2.4s on screen minimum**. If a shot is 2 seconds and you need a 14-word sentence, you have a problem. Either: 1. Shorten the copy 2. Carry the text across two consecutive shots (text persists during cut) 3. Lengthen the shot Do not under-time text. It's the most common storyboard failure. ## Pacing curve For 30-second content, the natural energy curve: ``` 0s ─────────────────────────────────── 30s Energy ▲ ╱╲ │ ╱ ╲___ │ ╱ ╲___ │ ╱ ╲___ │ ╱ ╲___ └─────────────────────────► hook build apex release CTA 0-2 2-12 12-20 20-26 26-30 ``` The apex is two-thirds in, not at the end. The CTA is a release, not a peak. For 60-second content, scale the same curve. Apex around 0:40, CTA from 0:50. ## Validating timing Before declaring done, check: 1. Sum of `(end - start)` across all shots equals project duration ±0.1s 2. No shot has `start >= end` 3. No two shots overlap 4. First shot starts at 0.0 5. Last shot ends at project duration 6. Every text overlay's `enter.at` is ≥ its shot's `start` 7. Every text overlay's `exit.at` is ≤ its shot's `end` (or carries to a flagged successor shot) 8. Every text overlay's on-screen duration ≥ read-twice threshold
-
-
templates
-
run.schema.json 6.8 KB
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://whystrohm.com/schemas/shotkit/run.schema.json", "title": "Run", "description": "Written once at the start of a storyboard run and never edited. Pins every input by content hash, so six months later you can prove which brand-lock, shot list, and text spec a set of frames was produced against, rather than trusting that the files sitting next to them are the originals. Everything downstream (prompts, frames, critiques) is addressed by round and shot id under the output root, so no two operators write the same path. Validated by tools/validate_provenance.py.", "type": "object", "required": ["version", "run_id", "created_at", "shotkit_version", "project", "inputs"], "additionalProperties": false, "properties": { "version": { "type": "string", "const": "1.0" }, "meta": { "type": "object", "description": "Forward-compat passthrough. Ignored by core skills.", "additionalProperties": true }, "run_id": { "type": "string", "pattern": "^[0-9]{8}T[0-9]{6}Z-[0-9a-f]{8}$", "description": "UTC compact timestamp, a dash, then 8 hex characters. Example: 20260730T142300Z-9f2c1ab4. The hex suffix distinguishes two runs started in the same second by different operators." }, "created_at": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$", "description": "UTC ISO-8601 instant the run started. Must agree with the timestamp embedded in run_id." }, "shotkit_version": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$", "description": "Version of shotkit that produced the run." }, "operator": { "oneOf": [ { "type": "null" }, { "type": "string", "minLength": 1 } ], "description": "Who started the run. Free text: a name, a handle, or a CI job id. Null when nobody recorded it." }, "project": { "type": "object", "required": ["title", "duration_s", "aspect"], "additionalProperties": false, "properties": { "title": { "type": "string", "minLength": 1 }, "duration_s": { "type": "number", "exclusiveMinimum": 0 }, "aspect": { "type": "string", "enum": ["16:9", "9:16", "1:1", "4:5", "21:9"] }, "framework": { "type": "string", "minLength": 1 } } }, "inputs": { "type": "object", "description": "Every file the run was built from, by path and by hash. Paths are relative to the output directory root.", "required": [ "shots_ref", "shots_sha256", "text_overlays_ref", "text_overlays_sha256", "brand_lock_ref", "brand_lock_sha256", "brand_lock_source" ], "additionalProperties": false, "properties": { "shots_ref": { "type": "string", "minLength": 1 }, "shots_sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, "text_overlays_ref": { "oneOf": [ { "type": "null" }, { "type": "string", "minLength": 1 } ], "description": "Null for a storyboard with no on-screen text." }, "text_overlays_sha256": { "oneOf": [ { "type": "null" }, { "type": "string", "pattern": "^[0-9a-f]{64}$" } ] }, "brand_lock_ref": { "type": "string", "minLength": 1 }, "brand_lock_sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, "brand_lock_source": { "type": "string", "minLength": 1, "description": "Where the snapshot was copied from, e.g. 'brand-packs/whystrohm.md'. The literal string 'template default' records an UNCONFIGURED run built from brand-packs/_template.md." }, "brand_lock_configured": { "type": "boolean", "description": "False when the snapshot is an unfilled template. tools/validate_brand_lock.py --require-configured is what decides this." } } }, "generators": { "type": "array", "description": "Generators targeted by this run, with the model version current at run time. The capability matrix records what is current now; this records what was current then.", "items": { "type": "object", "required": ["id", "model_version"], "additionalProperties": false, "properties": { "id": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]*$" }, "model_version": { "type": "string", "minLength": 1 }, "capabilities_sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$", "description": "SHA-256 of _capabilities.json as read, so a later matrix edit is detectable." } } } }, "rounds": { "type": "array", "description": "Appended once per revision round. Each round records the prompt files it produced. Frames and critiques are found on disk under frames/round-N/ and critiques/round-N/, not listed here, so two operators working the same round never write the same JSON.", "items": { "type": "object", "required": ["round", "started_at", "prompts"], "additionalProperties": false, "properties": { "round": { "type": "integer", "minimum": 1 }, "started_at": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$" }, "reason": { "type": "string", "minLength": 1, "description": "Why this round exists. Round 1 is 'initial'. Later rounds cite the critiques that triggered them." }, "prompts": { "type": "array", "minItems": 1, "items": { "type": "object", "required": ["generator", "ref", "sha256"], "additionalProperties": false, "properties": { "generator": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]*$" }, "ref": { "type": "string", "minLength": 1 }, "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, "shot_ids": { "type": "array", "items": { "type": "string", "pattern": "^shot_[0-9]{2,3}$" }, "description": "Which shots this file covers. A full pass lists every shot; a revision file lists only the shots it re-emitted." } } } }, "post_only_shots": { "type": "array", "items": { "type": "string", "pattern": "^shot_[0-9]{2,3}$" }, "description": "Shots whose only outstanding issues are post-level. They get no new prompt, so without this list the compositing obligation exists nowhere on disk." } } } } } } -
shots.schema.json 8.7 KB
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://whystrohm.com/schemas/shotkit/shots.schema.json", "title": "Shots", "description": "Structured shot list for a storyboard. Versioned, audit-friendly, generator-agnostic. Write-once: the QA loop never mutates this file, so its hash in run.json stays valid for the life of the project. Cross-field rules this schema cannot express (end greater than start, shots tiling project.duration_s, on_screen_text resolving into text-overlays.json, overlay colors coming from the brand-lock palette) are enforced by tools/validate_shots.py.", "type": "object", "required": ["version", "project", "brand_lock_ref", "series_lock", "shots"], "additionalProperties": false, "properties": { "version": { "type": "string", "enum": ["1.0", "1.1", "1.2"], "description": "1.1 added optional shot.assets and top-level meta. 1.2 added content hashes and critique back-references to shot.assets.generated, and allows on_screen_text to be an array so a shot can carry more than one overlay. Backward compatible: every valid 1.0 file is a valid 1.2 file." }, "meta": { "type": "object", "description": "Forward-compat passthrough for tooling that needs to annotate a storyboard without a schema bump. Ignored by core skills.", "additionalProperties": true }, "project": { "type": "object", "required": ["title", "duration_s", "aspect"], "additionalProperties": false, "properties": { "title": { "type": "string", "minLength": 1 }, "duration_s": { "type": "number", "exclusiveMinimum": 0 }, "aspect": { "type": "string", "enum": ["16:9", "9:16", "1:1", "4:5", "21:9"] }, "framework": { "type": "string", "description": "Beat framework name. e.g. 'pain-reframe-promise', 'hero-trilogy', 'founder-explainer', 'content-spiral', 'educational-demo', 'custom'" } } }, "brand_lock_ref": { "type": "string", "minLength": 1, "description": "Path to brand-lock.snapshot.md, relative to the output directory root. The content hash of that file lives in run.json:inputs.brand_lock_sha256, which is what actually pins the brand state. This field alone is a name, not a proof." }, "series_lock": { "type": "object", "required": ["character", "environment", "lighting", "color_grade"], "additionalProperties": false, "properties": { "character": { "type": "string", "minLength": 1 }, "environment": { "type": "string", "minLength": 1 }, "lighting": { "type": "string", "minLength": 1 }, "color_grade": { "type": "string", "minLength": 1 } } }, "shots": { "type": "array", "minItems": 1, "items": { "type": "object", "required": [ "id", "beat", "start", "end", "framing", "angle", "motion", "subject", "rationale" ], "additionalProperties": false, "properties": { "id": { "type": "string", "pattern": "^shot_[0-9]{2,3}$" }, "beat": { "type": "string", "minLength": 1 }, "start": { "type": "number", "minimum": 0 }, "end": { "type": "number", "exclusiveMinimum": 0, "description": "Must be greater than start. JSON Schema cannot compare two sibling properties, so tools/validate_shots.py enforces it." }, "framing": { "type": "string", "enum": ["ECU", "CU", "MCU", "MS", "MLS", "WS", "EWS"] }, "angle": { "type": "string", "enum": ["eye-level", "high", "low", "overhead", "dutch"] }, "motion": { "type": "string", "enum": [ "static", "push", "pull", "pan-left", "pan-right", "tilt-up", "tilt-down", "handheld", "orbit", "whip", "rack" ], "description": "Camera movement. 'rack' here means a rack focus used as the shot's motion. The identically named depth_of_field value describes the focus state instead." }, "depth_of_field": { "type": "string", "enum": ["shallow", "deep", "rack"], "description": "Focus behaviour. 'rack' means focus shifts mid-shot." }, "subject": { "type": "string", "minLength": 1 }, "environment_ref": { "type": "string", "description": "Default 'series_lock.environment'. Override only with reason." }, "lighting_ref": { "type": "string", "description": "Default 'series_lock.lighting'. Override only with reason." }, "on_screen_text": { "oneOf": [ { "type": "null" }, { "type": "string", "pattern": "^text_[0-9]{2,3}$" }, { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "type": "string", "pattern": "^text_[0-9]{2,3}$" } } ], "description": "Overlay id, array of overlay ids for a shot carrying several, or null. Added the array form in 1.2: text-overlays.json has always allowed many overlays per shot, and a single string could not represent that." }, "vo": { "oneOf": [ { "type": "null" }, { "type": "string", "minLength": 1 } ] }, "rationale": { "type": "string", "minLength": 1 }, "assets": { "type": "object", "description": "Optional. Frames associated with this shot. Read by tools/shots-to-html.py and tools/validate_provenance.py; both fall back to the frames/round-N/{shot_id}.{ext} path convention when this block is absent. Added in 1.1, given content hashes and critique back-references in 1.2.", "additionalProperties": false, "properties": { "source": { "type": "array", "description": "User-supplied images for this shot (skip generation). Paths relative to the output directory root.", "items": { "oneOf": [ { "type": "string", "minLength": 1 }, { "type": "object", "required": ["path"], "additionalProperties": false, "properties": { "path": { "type": "string", "minLength": 1 }, "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, "note": { "type": "string", "minLength": 1 } } } ] } }, "generated": { "type": "array", "items": { "type": "object", "required": ["path"], "additionalProperties": false, "properties": { "path": { "type": "string", "minLength": 1, "description": "Relative to the output directory root." }, "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$", "description": "SHA-256 of the frame. Without this, 'accepted' is an assertion about a filename." }, "round": { "type": "integer", "minimum": 1 }, "generator": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]*$", "description": "Must match an id in visual-prompt-forge/adapters/_capabilities.json." }, "model_version": { "type": "string", "minLength": 1 }, "seed": { "oneOf": [ { "type": "null" }, { "type": "integer" }, { "type": "string", "minLength": 1 } ] }, "prompt_ref": { "type": "string", "minLength": 1 }, "prompt_sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, "critique_ref": { "type": "string", "minLength": 1, "description": "Path to the critique.json that produced 'accepted'. An accepted frame with no critique_ref is an unsourced approval." }, "accepted": { "type": "boolean" } } } } } } } } } } } -
storyboard.md.tpl 1.5 KB · in bundle
-
text-overlays.schema.json 3.3 KB
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://whystrohm.com/schemas/shotkit/text-overlays.schema.json", "title": "Text Overlays", "description": "On-screen text elements that get composited over generated images. Never baked into prompts.", "type": "object", "required": ["version", "overlays"], "additionalProperties": false, "properties": { "version": { "type": "string", "const": "1.0" }, "overlays": { "type": "array", "items": { "type": "object", "required": [ "id", "shot_id", "content", "font", "size", "weight", "color", "position", "enter", "exit" ], "additionalProperties": false, "properties": { "id": { "type": "string", "pattern": "^text_[0-9]{2,3}$" }, "shot_id": { "oneOf": [ { "type": "string", "pattern": "^shot_[0-9]{2,3}$" }, { "type": "array", "minItems": 2, "items": { "type": "string", "pattern": "^shot_[0-9]{2,3}$" } } ], "description": "Single shot ID, or array of consecutive shot IDs for persistent overlays." }, "content": { "type": "string", "minLength": 1 }, "font": { "type": "string", "description": "Must reference a font defined in brand-lock typography." }, "size": { "type": "string", "enum": ["display", "headline", "body", "caption"] }, "weight": { "type": "string", "enum": ["regular", "medium", "bold", "black"] }, "color": { "type": "string", "pattern": "^#[0-9A-Fa-f]{6}$", "description": "Hex value. Must come from brand-lock palette." }, "position": { "oneOf": [ { "type": "string", "enum": [ "center", "lower-third", "upper-third", "left-third", "right-third" ] }, { "type": "object", "required": ["x", "y"], "additionalProperties": false, "properties": { "x": { "type": "number", "minimum": 0, "maximum": 100 }, "y": { "type": "number", "minimum": 0, "maximum": 100 } } } ] }, "enter": { "type": "object", "required": ["at", "animation"], "additionalProperties": false, "properties": { "at": { "type": "number", "minimum": 0 }, "animation": { "type": "string", "enum": ["fade-in", "slide-up", "slide-down", "type-on", "hard-cut"] } } }, "exit": { "type": "object", "required": ["at", "animation"], "additionalProperties": false, "properties": { "at": { "type": "number", "minimum": 0 }, "animation": { "type": "string", "enum": ["fade-out", "slide-up", "slide-down", "hard-cut"] } } } } } } } }
-
-
SKILL.md 12.5 KB
--- name: storyboard-architect description: 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. --- # Storyboard Architect 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. This is not a creative-writing exercise. The output is a spec. ## When to use Trigger this skill when the user: - Describes a video they want to make ("30-second explainer for...", "TikTok ad about...") - Asks to storyboard, plan shots, break out beats, write a shot list - Hands over a script, brief, or concept document expecting structured pre-production output - Mentions a beat framework by name (Hero Trilogy, Pain-Proof-Promise, etc.) - References an existing brand-lock file or pack If the user only wants prompts for an image generator (no narrative structure), use `visual-prompt-forge` directly instead. ## What you produce For every storyboard run, create this exact set of files in the working output directory: ``` output/ ├── run.json # Run identity + every input pinned by content hash ├── storyboard.md # Human-readable, structured per shot ├── shots.json # Machine-readable, schema in templates/shots.schema.json ├── text-overlays.json # On-screen text + timing └── brand-lock.snapshot.md # Frozen copy of the brand-lock used (audit trail) ``` `run.json` is what makes the rest of the tree auditable later. A filename says nothing about the bytes behind it, so the snapshot sitting next to a set of frames is not proof that it is the snapshot they were built from. The hashes in `run.json` are that proof. Write it once, at the end of the run, and never edit it. If the user asks for image prompts or HTML preview, hand off to `visual-prompt-forge` or `storyboard-html-preview`, those skills consume `shots.json` directly. Don't try to do their job here. ## Inputs You need these. If any are missing, ask before drafting. | Input | Required? | Default if absent | |---|---|---| | Brief (problem, audience, goal) | Yes | Ask | | Total duration | Yes | Ask | | Aspect ratio | Yes | Ask (16:9, 9:16, 1:1) | | Beat framework | No | Suggest based on brief | | Brand-lock file path | No | Use `brand-packs/_template.md` and flag the gap | | Voiceover style (VO present, on-screen only, captions) | No | Ask if unclear | | Target generator(s) for downstream prompts | No | Note as "to be specified" | ## Workflow Follow this sequence. Don't skip steps even if the brief seems simple. ### Step 1. Read the brand-lock If a brand-lock file path is provided, read it first. Extract: - Palette (hex) - Typography - Mood descriptors - "Never" list (what this brand will never do visually) - Motion language - Voice tone - Aspect-ratio preferences If no brand-lock is provided, copy `brand-packs/_template.md` into the output as `brand-lock.snapshot.md` with a note: `# UNCONFIGURED, using template defaults. Recommend providing a real brand-lock for production work.` ### Step 2. Pick the beat framework Read `references/beat-frameworks.md`. Pick the one that matches the brief. Common cases: - Pain-reframe-promise → conversion content - Hero Trilogy → product hero films - Founder Explainer → personal-brand content - Content Spiral → kinetic typography / opinion pieces If none fit cleanly, build a custom beat structure but document why in `storyboard.md` rationale section. ### Step 3. Block out timing Read `references/timing-rules.md` for the math. Default cadence: - Hook beat: 0–2 seconds - Pain/setup: 2–6 seconds (for 30s) or 2–10 seconds (for 60s) - Proof/reframe: middle third - Promise/CTA: final 4–6 seconds Don't fight the framework. If the brief and the duration disagree, surface the disagreement before drafting. ### Step 4. Draft the shot list Read `references/shot-grammar.md` for controlled vocabulary. The field names below are the schema's field names. `templates/shots.schema.json` sets `additionalProperties: false`, so a near-miss like `environment` instead of `environment_ref` is a validation failure, not a synonym. - `id`, sequential, zero-padded (`shot_01`, `shot_02`...) - `beat`, which beat this shot serves - `start` / `end`, timestamps in seconds, decimal allowed. `end` must be after `start` - `framing`, ECU / CU / MCU / MS / MLS / WS / EWS - `angle`, eye-level / high / low / overhead / dutch - `motion`, static / push / pull / pan-left / pan-right / tilt-up / tilt-down / handheld / orbit / whip / rack. All eleven are legal; the schema enum is the authority and `references/shot-grammar.md` explains when each earns its keep - `depth_of_field`, optional, shallow / deep / rack - `subject`, what's in frame, structured - `environment_ref`, references series-lock language, default `series_lock.environment` - `lighting_ref`, references series-lock language, default `series_lock.lighting` - `on_screen_text`, null, one text-overlay id, OR an array of ids when a shot carries more than one overlay - `vo`, voiceover line, or null - `rationale`, one sentence explaining *why this shot at this moment* Note on `rack`: as a `motion` value it means the rack focus is the shot's movement; as a `depth_of_field` value it means focus shifts mid-shot. Same word, two fields, two meanings. ### Step 5. Separate the text layer Every piece of on-screen text becomes an entry in `text-overlays.json`. Never bake text into the visual description. Each overlay has: - `id`, `text_01`, `text_02`... - `shot_id`, which shot this overlays on - `content`, the actual text - `font`, references brand-lock typography - `position`, `center`, `lower-third`, `upper-third`, `left-third`, `right-third`, or `{x, y}` percentages - `size`, `display`, `headline`, `body`, `caption` - `weight`, `regular`, `medium`, `bold`, `black` - `color`, hex (must come from brand-lock palette) - `enter`, `{ at: seconds, animation: fade-in | slide-up | slide-down | type-on | hard-cut }` - `exit`, `{ at: seconds, animation: fade-out | slide-up | slide-down | hard-cut }` Enter and exit have different animation vocabularies, and `templates/text-overlays.schema.json` is the authority on both. A shot may carry more than one overlay; list every id in that shot's `on_screen_text` array, or the extra overlays render nowhere. ### Step 6. Lock the series Define environment, lighting, and character anchors that apply across every shot. These go at the top of `shots.json` under `series_lock`. Without these, image generation will produce incoherent frames. ### Step 7. Write rationale 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. ### Step 8. Snapshot the brand-lock Copy the brand-lock file (or template) into the output as `brand-lock.snapshot.md`. Add these two comments at the very top, in this order: ``` <!-- snapshot taken: 2026-05-07T14:23:00Z --> <!-- source: brand-packs/whystrohm.md --> ``` The timestamp is a full UTC instant, `YYYY-MM-DDThh:mm:ssZ`. A bare date cannot distinguish two runs made on the same day, which is the case that matters. The source is the path it was copied from, or the literal string `template default` for an unconfigured run. Extra comments after these two are fine. `tools/validate_brand_lock.py --snapshot <path>` checks both lines. Run it. ### Step 9. Write run.json Last step, after the other four files are final. Fill in `templates/run.schema.json`: a `run_id`, the `created_at` instant, and the SHA-256 of `shots.json`, `text-overlays.json`, and `brand-lock.snapshot.md` as written. ```bash shasum -a 256 output/shots.json output/text-overlays.json output/brand-lock.snapshot.md ``` `run_id` is the compact UTC timestamp, a dash, then 8 hex characters, e.g. `20260730T142300Z-9f2c1ab4`. The hex suffix is what keeps two operators starting a run in the same second from colliding. Set `brand_lock_configured: false` when the snapshot is an unfilled template. Leave `rounds` empty. `visual-prompt-forge` appends a round entry when it writes prompts. ## Output formats ### `storyboard.md` Use the template at `templates/storyboard.md.tpl`. Read it before writing. ### `shots.json` Must validate against `templates/shots.schema.json`. Read it before writing. The structure is: ```json { "version": "1.2", "project": { "title": "...", "duration_s": 30, "aspect": "9:16", "framework": "..." }, "brand_lock_ref": "brand-lock.snapshot.md", "series_lock": { "character": "...", "environment": "...", "lighting": "...", "color_grade": "..." }, "shots": [ { "id": "shot_01", "beat": "hook", "start": 0.0, "end": 2.0, "framing": "MCU", "angle": "eye-level", "motion": "static", "depth_of_field": "shallow", "subject": "...", "environment_ref": "series_lock.environment", "lighting_ref": "series_lock.lighting", "on_screen_text": "text_01", "vo": null, "rationale": "..." } ] } ``` Write `1.2` for new storyboards. `1.0` and `1.1` files stay valid; the array form of `on_screen_text` and the hashed `assets` block need `1.2`. ### `text-overlays.json` Must validate against `templates/text-overlays.schema.json`. Read it before writing. ## Quality bar Run the validator. Do not eyeball this list. ```bash python tools/validate_shots.py output/ python tools/validate_brand_lock.py --snapshot output/brand-lock.snapshot.md python tools/validate_provenance.py output/ ``` `validate_shots.py` checks every mechanical rule that used to live here as a checkbox, because a checkbox is a rule enforced by remembering to look: - shots.json and text-overlays.json validate against their schemas - `end` is after `start`, no duplicate ids, no gaps, no overlaps, and the covered span matches `project.duration_s` within 0.1s - every `on_screen_text` resolves to an overlay, every `overlay.shot_id` resolves to a shot, and every overlay is reachable from at least one shot - every overlay's timing sits inside its shot window, and exit is after enter - every overlay color appears in the brand-lock palette - `brand_lock_ref` resolves on disk It warns, rather than fails, on judgement calls worth a second look: overlay copy repeated inside a shot subject, a raw hex in a subject, shot ids out of chronological order, an overlay font the brand-lock does not declare. What the validator cannot check, and you still have to: - [ ] Every rationale says *why this shot at this moment*, not what the shot contains - [ ] `series_lock` anchors are specific enough to reproduce (not "a person in a room") - [ ] The beat structure actually matches the brief's argument - [ ] `run.json` is written and its hashes are the files as shipped If the validator fails, fix it before declaring done. A green validator plus an unread rationale is not a finished storyboard. ## Reference files Load these as needed: - `references/beat-frameworks.md`, the beat structures - `references/shot-grammar.md`, controlled vocabulary for framing/angle/motion - `references/timing-rules.md`, pacing math - `references/on-screen-text.md`, when on-screen text earns its keep ## Examples - `examples/30s-pain-proof-promise/`, full output set for a 30-second conversion ad - `examples/60s-founder-explainer/`, full output set for a founder explainer - `examples/shotkit-explainer/`, the 90-second explainer, including a shot that carries two overlays Read these to understand the expected output quality, especially the rationale fields. All three validate clean under `tools/validate_shots.py --examples`, so they are also the reference for what a passing file looks like. For what the output tree looks like after generation and review, see `../visual-asset-critic/examples/worked-run/`: two shots through two rounds, with real hashes, per-round prompts and frames, and one critique per shot per round. ## Handoff After producing the five files, tell the user what's in `output/` and offer the obvious next steps: - "Want image prompts? I'll run `visual-prompt-forge` on `shots.json`." - "Want a shareable HTML preview? I'll run `storyboard-html-preview`." - "Want to QA a generated image against this storyboard? I'll run `visual-asset-critic`." Don't run those on your own. The user picks.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.