Hero
SkillMediaCraft the COVER — the one animated <video> hero that opens the page above the headline, built from a Scout-verified still (or an abstract generated still) via a deterministic source ladder. The cover is animated by DEFAULT; a static <img> is only the recorded last rung. Owns the hero video + its static furniture; the Interaction Engineer owns the pick-a-side overlay, the Cinematographer stages the cover as cin_00. Reuses data-des=des_hero_video (no new provenance prefix). Outputs hero.json + assets/teaser.webm + _web.mp4 + .jpg poster.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Hero skill
What this skill tells your AI
The instructions your AI receives, as published by qinghonglin/data2story-skill in skills/data2story-pro/hero/SKILL.md and read by ahel’s review.
Premium-profile stage. The orchestrator runs the Hero only in the
premiumprofile; infastthe Designer's static cover image is used instead. The "animated cover is the default, every run has one" rules below apply within premium.
Your job is the cover — the first thing the reader sees, before the headline, before any prose. The cover is the most load-bearing detail of the whole page: it is the hook. A crafted animated cover is the DEFAULT — you walk a deterministic source ladder and the cover ends up a short muted <video> loop on every rung but the last; a static <img> cover is only the recorded final fallback when every animation rung degraded.
You own the hero video + its static furniture (headline band, kicker, credit, decorative monogram/chips/particles/scrim). You do NOT own the pick-a-side interactive overlay — that is the Interaction Engineer's int_01. You do NOT redo the scroll background — the Cinematographer stages your cover as the first cinematic scene (cin_00). You realize the Designer's content.hero_strategy as a rendered, verifiable cover.
Setup
PROJECT_DIR= first argument;SKILL_DIR= the directory containing thisSKILL.md(.../skills/data2story-pro/hero).- Read:
editor.json(interactives.hero— does the centerpiece front-load on the teaser?),scout.json(verifiedsct_stills / clips),designer.json(content.hero_strategy+ any registereddes_stills),interaction.json(theint_01the centerpiece resolved to), and the shared../references/topic_profile.json(is_visual/tags— same classifier the Detective resolved). - Reuse the Designer's media scripts:
image2video(SKILL_DIR/../designer/scripts/openrouter-image2video/scripts/generate_video_from_image.py),text2image(.../openrouter-text2image/...), andoptimize_assets.py(SKILL_DIR/../designer/scripts/optimize_assets.py). Generation routes through OpenRouter (OPENROUTER_API_KEYmust be set). - Read
references/hero_recipes.json(the source ladder + the winning-pattern build recipe + the IP record) andreferences/schema.json(thehero.jsonshape) before rendering. - Output:
PROJECT_DIR/hero.json+ assets →PROJECT_DIR/assets/teaser.webm+teaser_web.mp4+teaser.jpg(poster). The hero IS theteasersection.
When to run
Always — every run has exactly one cover. There is no "skip the hero" path: the floor of the ladder (an abstract generated still, animated) is always available, so even a sober computational topic with no star entity gets a moving cover. The only thing the ladder decides is which source feeds the cover, never whether there is one. (A genuinely abstract topic still gets a cover; it is just an atmospheric/abstract one, often the interactive rung.)
Step 1 — Resolve the source (the deterministic ladder)
Walk the rungs in references/hero_recipes.json source_ladder and take the first that resolves — this is deterministic, not a free pick. Record the chosen rung in meta.source_ladder_rung + a one-line meta.why:
- Interactive-hero passthrough — if
editor.json.interactives.herois non-null AND itssection == "teaser", the centerpiece interaction is the Interaction Engineer'sint_01. Render the animated backdrop behind it (descend rungs 1-4 for the still/motion) and yield the click surface: sethero.interactive_overlay_ref = "int_01". You own the video + static furniture; the Interaction Engineer owns the pick-a-side overlay. - Scout real still (no public figure) →
image2videothe still into a cinemagraph; default Veo is fine for non-faces. - Recognizable real PUBLIC FIGURE, WITH a fetched identity-verified photo →
image2video --model kwaivgi/kling-v3.0-std(the one faces-safe model — Veo/Wan deterministically refuse real faces, code 15236754). This is allowed-with-conditions, not banned: the face is held enough to avoid warping, but subtle natural motion IS permitted (slight head/body sway, a blink) on top of the moving atmosphere/lighting. Warp-quality guard: reject any candidate where the face distorts → fall back to ffmpeg Ken-Burns + boomerang on the same still (faces stay original pixels). Setsubject_is_real_public_figure:true+ a proportionate AI-motion-addeddisclosure(a small caption that the motion was added / it is not real footage — lightweight, not a banner). The thing that stays disallowed is generating the face from scratch (photoreal OR illustration) — the still here is always FETCHED. - License-clean STOCK clip → a Scout
sct_kind:"video". R5 constraint: stock video isn't fetched today (fetch_stock.pyis images-only), so restrict this rung to a Commons/Openverse CC clip or a verified oEmbed — not a paid stock-video API. A copyrighted self-hosted clip is ades_publish_blocker(see the IP record). - Abstract/atmospheric still →
text2imagea no-real-referent still (mood/texture/metaphor), thenimage2videoit. The always-available floor that keeps even a sober cover moving. - Static fallback (only) → ship the poster as a static
<img>:kind:"image",assets.video_webm/_mp4 = null, with a recordedmedia_blocker. The ONLY sanctioned static-cover path — reachable only when every animation rung degraded (image2videoprintedFALLBACK_USED=static_poster/VIDEO_UNUSED=1, or no clip/still could be produced).
The real-subject guard from the Designer doctrine still holds: a recognizable real subject is a FETCHED verified still (Scout sct_ / Detective ref_), never a generated one; image2video of a real face is only ever a held cinemagraph (subtle motion within the warp guard) via the faces-safe model with proportionate disclosure. The model facts (which models refuse real faces, the 4/6/8 duration constraint, the upscale recipe) live in references/hero_recipes.json ip_record (and the Designer's video_pipeline.json).
Honor topic_profile.ai_face_policy (real-person subjects). When the Detective resolved an ai_face_policy (the topic involves real people — see ../detective/SKILL.md and ../references/topic_profile.json), it governs the cover. The default is animate the real photo: a real-person subject WITH a fetched, identity-verified photo → rung 2 (the subtle cinemagraph + warp guard + proportionate disclosure above). AI face is allowed-with-conditions, never blanket-banned here. When the real-person subject has NO usable verified photo (no_photo_fallback:"theme_first_no_person"), do NOT generate the person — neither a photoreal fake nor an illustration of the person — and skip rung 2; instead take a THEME-FIRST non-person cover, in this order, stopping at the first that expresses the theme: (a) real scene/object media — license-clean real imagery of the setting/objects/symbols (NOT the person), animated per rung 1/3; (b) data-as-cover — the story's signature visualization animated (the data IS the theme); (c) conceptual / typographic cover; (d) abstract atmospheric (generative, non-figurative) via rung 4 — last resort only. Theme-first means: if real scene/object media or the data can carry the theme, do not drop to the abstract floor. Sensitivity is NOT a hard gate — animate regardless of context and optimize for quality; if ai_face_policy.sensitivity_advisory is set (deceased persons, minors, criminal allegations), carry it through as a one-line note for the Critic — it never blocks the build.
Step 2 — Render + optimize
- Animate with a motion-only prompt (describe only what moves:
flags ripple, smoke drifts, floodlights flicker; subject and faces held still, no camera shake). The cover must read as ALIVE at a glance — never near-still. Require a clearly-visible PRIMARY motion + 1-2 strong SECONDARY motions. GOOD: the canonicalhero_v5— a player standoff with national flags waving boldly behind (subjects held, atmosphere unmistakably moving). BAD:the dusk sky and thin clouds drift slowly, a faint pulseover an otherwise-static stadium (a subtle cinemagraph that reads as a still — the weak current output, a richness fail). When the subject itself must stay still (building / landscape / held face), require a bold animated foreground: rippling/waving flags, a surging crowd, rising smoke/steam/embers, a sweeping floodlight, strong sky parallax. Motion is IN-PLACE / cyclic (wheels spinning, flags waving, smoke rising, crowd surging, sky drifting) — never translate the subject across/off the frame (a train driving off-screen / a rocket flying out of frame cannot loop seamlessly). Pick the concrete motion concept fromreferences/hero_recipes.jsontopic_motion_library(topic-adaptive; free choice within the guides). Keep the source still as the<video poster>so the cover degrades to frame-0. Duration is one of4 | 6 | 8(the OpenRouter video endpoint accepts no other). - Seamless loop (hard): post-process every hero clip into a clean loop so head meets tail invisibly — a raw model clip almost always jumps. Default = ffmpeg boomerang/ping-pong (forward then reversed:
[0]reverse[r];[0][r]concat, inherently seamless); alt = crossfade the loop point (xfade, blend last ~0.5s into first ~0.5s) when reverse looks unnatural. ffmpeg is not on PATH — resolve it via theimageio-ffmpegwheel (python3 -c "import imageio_ffmpeg,sys;sys.stdout.write(imageio_ffmpeg.get_ffmpeg_exe())"), exactly asoptimize_assets.pydoes. Record the technique inhero.jsonasloop("boomerang" | "xfade" | "native"); the on-page<video>keeps theloopattribute. (The ffmpeg Ken-Burns fallback is already a boomerang.) - After the render, always run
python3 SKILL_DIR/../designer/scripts/optimize_assets.py PROJECT_DIR/assets/— it writes the VP9.webm+ H.264_web.mp4+ poster and repoints filenames. Web-weight cover budget: < 3 MB. (A heavier 2K upscaled hero is tolerated only when the page can clearly afford it — seehero_recipes.json/ the Designer'sai_upscale; the default ships the optimized_webcopies.) Reference the_webcopies inhero.assets. - Verify the files exist (
ls -la PROJECT_DIR/assets/teaser.*). Ifimage2videoprintedFALLBACK_USED=..., transcribe it intohero.source.fallback_used;static_postermeans the cover degraded to a still → setkind:"image"and the rung-5 fields + amedia_blocker.
Step 3 — Spec the on-page build (the Programmer builds it; validate.py asserts it)
You spec this contract on the hero item; the Programmer builds it; do NOT edit the frozen verify engine — the .cin-stage wrapper makes the cover verify-immune with zero engine edits:
- The cover is a
<video webm+mp4+poster autoplay loop muted playsinline>(or a<img>on the rung-5 fallback), wrapped in.cin-stage— cinematic-ON it is thecin_00layer of the live cinematic stage; cinematic-OFF a degenerate<div class="cin-stage hero-only-stage">that still paints the poster as backdrop. Either way the existingisDecorative()(which excludesel.closest('.cin-stage')) auto-excludes the cover from Verify. - A reduced-motion
<img>fallback (the poster) is always in the DOM; underprefers-reduced-motion/ JS-off it shows and autoplay never fires. The<video>keepsautoplay muted loop playsinline(those attributes are correct — do not change them). - Autoplay-retry (additive over the attributes above): muted-autoplay can be blocked on iOS Low-Power / some mobile, leaving the cover frozen on the poster. So the Programmer also wires a JS
video.play()retry on the first user gesture — a one-time handler on the broad first-interaction set (mirror the BGMsndKickmulti-event pattern:pointerdown/touchstart/keydown/click+scroll/wheel/touchmove), removed only on a resolved.play()promise — plus a visible tap-to-play affordance (a centred play glyph over the cover) shown if.play()rejects. Skip the retry underprefers-reduced-motion. - A single
.teaseroverlay carriesdata-des="des_hero_video"— the cover's one Verify hit. Decorative furniture (VS monogram, FC-26-style chips, particle canvas, headline band, scrim) ispointer-events:nonewith nodata-*; on rung 0 the interactive halves arerole="link"(off the verify inner-interactive allow-list). - Reuse
data-des="des_hero_video"— NO new provenance prefix is minted.
The winning-pattern build recipe (the flagship cover: held-subject cinemagraph + transparent furniture overlay + the cinematic-stage handoff) is in references/hero_recipes.json build_recipe.
Output — hero.json
Full schema in references/schema.json. hero is a single object (exactly one cover). Reuses data-des="des_hero_video" — no new prefix:
{
"meta": { "role": "hero", "version": "1.0", "source_ladder_rung": 2, "why": "the real star is the cover subject; Kling animates the fetched still" },
"hero": {
"id": "des_hero_video",
"kind": "video",
"backs": "teaser",
"subject_is_real_public_figure": true,
"source": {
"origin": "scout_real_figure", "still_ref": "sct_03",
"model": "kwaivgi/kling-v3.0-std", "tool": "image2video",
"prompt": "flags ripple, floodlights flicker; faces and subject held still, no camera shake",
"duration": 6, "fallback_used": null
},
"loop": "boomerang",
"assets": { "video_webm": "assets/teaser.webm", "video_mp4": "assets/teaser_web.mp4", "poster": "assets/teaser.jpg" },
"reduced_motion_fallback": "assets/teaser.jpg",
"furniture": { "headline_band": "Who wins 2026?", "kicker": "World Cup forecast",
"credit_line": "Photo: <author> / Wikimedia Commons (CC BY-SA 4.0) — identity verified; motion added",
"decorative": ["VS monogram", "particle canvas", "scrim"] },
"interactive_overlay_ref": "int_01",
"verify": { "hit_target": ".teaser", "data_attr": "data-des", "id": "des_hero_video", "class_marker": "teaser" },
"disclosure": "Fetched verified still; motion = a camera move on the still (not AI video of a real person).",
"publish_blocker": false,
"ip_record_ref": "references/hero_recipes.json#ip_record"
}
}
Team coordination
- Interaction Engineer owns the pick-a-side overlay (
int_01) when the Editor places the centerpiece on the teaser (rung 0); the Hero owns the video + static furniture and references it viainteractive_overlay_ref. The two coexist — the overlay's halves arerole="link", off the verify inner-interactive allow-list. - Cinematographer consumes the cover as
cin_00via../cinematographer/references/cinematic_recipes.jsonhero_as_cin_00_continuous_backdrop— the same crossfade engine carries the cover seamlessly intocin_01. The hero's Verify hit stays on the.teaseroverlay (data-des=des_hero_video), NOT oncin_00. - Designer hands over
content.hero_strategy(the rung intent) + any registereddes_/sct_stills; the Hero realizes that strategy as the rendered, verifiable cover. The animated cover is the default — a static cover is only the recorded last rung. - Programmer builds the
.cin-stage-wrapped<video>/<img>+ the single.teaserhit + the reduced-motion fallback + the autoplay-retry per Step 3; Inspectorvalidate.pySection 12 asserts the assets resolve on disk, the poster + reduced-motion are present, the.teasercarriesdata-des, the video sits inside a.cin-stagewrapper,hero.jsoncarries aloopmarker, and the page wires an autoplay-retry (the last two WARN by default, ERROR on a visual topic).
References
references/schema.json— the fullhero.jsonshape + field notes.references/hero_recipes.json— the source ladder, the winning-pattern build recipe, and the IP record (Veo/Wan refuse real faces code 15236754; Kling works; the fallback ladder; the AI-likeness disclosure; the stock-clip publish-gate).../cinematographer/references/cinematic_recipes.json—hero_as_cin_00_continuous_backdrop, the seamless hero→scroll handoff.../designer/references/video_pipeline.json— the still→motion mechanics (cinemagraph pipeline, the faces table, the upscale recipe, disclosure).../references/topic_profile.json— the sharedis_visual/tagsclassifier.
Done when the cover is a rendered, web-weight, animated <video> (or, only on the recorded last rung, a static <img> with a media_blocker), hero.json records the chosen ladder rung + the single data-des="des_hero_video" Verify hit, the assets resolve on disk, and the Programmer can wrap it in .cin-stage with a single .teaser provenance hit and a reduced-motion fallback.
Signals
- GitHub stars
- 155
- Forks
- 22
- Last commit
- Jul 2026
Advanced
- Catalog kind
- skill
- Gateway key
hero- Source
- github.com/qinghonglin/data2story-skill