Install
$ agentstack add skill-qinghonglin-data2story-skill-hero ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Hero
> Premium-profile stage. The orchestrator runs the Hero only in the premium profile; in fast the 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 ` loop on every rung but the last; a static ` 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](../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](references/hero_recipes.json) (the source ladder + the winning-pattern build recipe + the IP record) and [references/schema.json](references/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](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 `
:kind:"image",assets.videowebm/mp4 = null, with a recordedmediablocker. The ONLY sanctioned static-cover path — reachable only when every animation rung degraded (image2videoprintedFALLBACKUSED=staticposter/VIDEOUNUSED=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](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](../detective/SKILL.md) and [../references/topic_profile.json](../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 from [references/hero_recipes.json](references/hero_recipes.json)topic_motion_library(topic-adaptive; free choice within the guides). Keep the source still as the `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 `keeps theloop` attribute. (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:(or aon the rung-5 fallback), **wrapped in.cin-stage** — cinematic-ON it is thecin_00layer of the live cinematic stage; cinematic-OFF a degeneratethat still paints the poster as backdrop. Either way the existingisDecorative()(which excludesel.closest('.cin-stage')`) auto-excludes the cover from Verify. - A reduced-motion `
fallback (the poster) is always in the DOM; underprefers-reduced-motion/ JS-off it shows and autoplay never fires. Thekeepsautoplay 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](references/hero_recipes.json) build_recipe.
Output — hero.json
Full schema in [references/schema.json](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: / 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.json](../cinematographer/references/cinematic_recipes.json)hero_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 `/+ 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 aloop` marker, and the page wires an autoplay-retry** (the last two WARN by default, ERROR on a visual topic).
References
- [
references/schema.json](references/schema.json) — the fullhero.jsonshape + field notes. - [
references/hero_recipes.json](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](../cinematographer/references/cinematic_recipes.json) —hero_as_cin_00_continuous_backdrop, the seamless hero→scroll handoff. - [
../designer/references/video_pipeline.json](../designer/references/video_pipeline.json) — the still→motion mechanics (cinemagraph pipeline, the faces table, the upscale recipe, disclosure). - [
../references/topic_profile.json](../references/topic_profile.json) — the
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: QinghongLin
- Source: QinghongLin/data2story-skill
- License: MIT
- Homepage: https://data2story.github.io/
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.