Setting the file. One moment. Template · Embedded Captions · heygen-com/hyperframes · Skills Docs14.43
Hershey Script Med
(opens in a new tab)
modes/cinematic/cinematic-cream/template.html
HTML·226 lines·10 KB
15 - Animation: 0.7s container fade-in (at g.in-0.01 via tl.set), per-word
16 reveal at w.start, group fade-out 0.5s before g.out
17
18 What's OPEN per group (agent decides per scene via `css` field):
19 - font-size, font-weight, font-style (italic or not), text-transform
20 (uppercase or not), letter-spacing, line-height
21 - Position: top / left / right / bottom / transform / text-align
22 - Layer: bg (embed via matte) | fg (announcement on top)
23 - Tone: soft | present (motion curve)
24 - scale: optional shortcut for size multiplication
25
26 LAYOUT — FIRST run the safe-zone precompute, THEN compose into PLANES:
27 - Run `node scripts/safe-zones.cjs <project>` and read `safe-zones.json`. From the
28 matte it gives subject coverage%, the clearer side, the largest CLEAN rectangles
29 (left/right/top), and a verdict:
30 · "fg" → subject fills the frame; NO room to embed behind it. Use
31 `caption_layer:"fg"` (screen-blend on top). Do NOT fight it with bg embeds —
32 that's the 9:16 case that costs many failed occlusion rounds.
33 · "embed"→ NARRATION planes go INSIDE the reported clean zones (prefer the clearer
34 side / top). The HERO is the EXCEPTION — see the next bullet. Don't guess
35 narration positions — use the zones safe-zones gives you.
36 - NARRATION: define 2-4 named planes in plan.json `planes {}`, each inside a clean zone
37 from safe-zones.json. Give each narration group a `plane`; groups STACK within it (a
38 plane is a flex column → lines accumulate, aligned + coherent). Narration must be CLEAR.
39 - THE HERO (the one promoted peak word) is the OPPOSITE of narration — BIG, and ON the
40 subject. It is the scarce, earned PEAK; it must feel like it BURSTS, not a polite label:
41 · BIG + dominant — **~0.22–0.34 × frame height**. Give it a WIDE plane (≈ centered,
42 spanning the subject), NEVER a narrow margin column. (fit-fonts won't enlarge — if
43 the hero's box is narrow it stays small; widen the box.)
44 · CENTERED on the subject (use safe-zones `heroAnchor` = the subject's center),
45 CROSSING it so the head/torso occludes the MIDDLE — **target ~30–55% occlusion**.
46 That occlusion IS the effect. A hero shoved into a clean margin (~10% graze) is
47 WRONG — it reads as a small floating word, not an embed behind the subject.
48 · Exactly ONE hero per clip (scarcity = impact). Mark it `"hero": true` in the plan.
49 - Do NOT hand-position every group with its own scattered absolute `top%` — that
50 produces the unbalanced, occluded layouts the gate rejects (many re-tries). Planes
51 first; per-group `css` only fine-tunes WITHIN a plane. (Position fields above apply
52 per-group only when a group is NOT in a plane — the exception, not the default.)
53
54 READING ORDER — on-screen position follows SPOKEN order; a promoted word appears once:
55 - For groups sharing a column/region, vertical (top→bottom) order MUST match spoken
56 order. A promoted hero must NOT sit below words that come after it.
57 (e.g. "judge us by the [ACTIONS] that we take" → ACTIONS reads BETWEEN "by the" and
58 "that we take", NEVER below "that we take".)
59 - The promoted word is lifted OUT of the narration (appears once, as the hero — never
60 also in a narration line). For the words AFTER it, two options, in order:
61 1. PREFERRED — flow them directly BELOW the hero (in reading order) or merge them
62 into the hero's group, IF that area is clean (check safe-zones). One continuous
63 read, no clearing.
64 2. FALLBACK — if below/merge would land on the subject (occluded), do the Standard
65 hand-off: the pre-hero narration CLEARS, then the post-hero words appear fresh
66 (page-flip); the hero may hold across the flip.
67 Prefer promoting a CLAUSE-FINAL word when you can — then nothing follows to place.
68
69 Philosophy:
70 - Number of groups = content decides (3? 7? 12? whatever the speech
71 warrants)
72 - Each group's typography = scene decides (italic for soft moments,
73 uppercase for punch, mixed case for narration — per group)
74 - No more "cap-1 must be italic 600" rules. Those were slots.
75 - The agent reads the scene + transcript and composes. The template
76 only guarantees the DNA feels coherent (Inter family, same motion
77 library, warm-cream or deep-warm palette, same shadow logic).
78
79 See `references/typographic-moves.md` for RECOMMENDED typography
80 combinations (NOT rules) — italic 600 for openers, uppercase 900 for
81 climaxes, etc. These are suggestions the agent can pick from or deviate.
82-->
83<html lang="en">
84 <head>
85 <meta charset="UTF-8" />
86 <meta name="viewport" content="width={{WIDTH}}, height={{HEIGHT}}" />
87 <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
88 <style>
89 :root { --font-scale: {{FONT_SCALE}}; --h: {{HEIGHT}}px; --w: {{WIDTH}}px; }
90
91 * { margin: 0; padding: 0; box-sizing: border-box; }
92 html, body {
93 margin: 0;
94 width: {{WIDTH}}px; height: {{HEIGHT}}px;
95 overflow: hidden;
96 background: #000;
97 font-family: "Inter", "Helvetica Neue", system-ui, sans-serif;
98 }
99 #a-roll { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
100 #stage {
101 position: absolute;
102 top: 0; left: 0;
103 width: {{WIDTH}}px; height: {{HEIGHT}}px;
104 pointer-events: none;
105 z-index: 2;
106 }
107
108 /* BASE — everything shared across groups */
109 .cap {
110 display: block;
111 position: absolute;
112 opacity: 0;
113 mix-blend-mode: {{BLEND_MODE}};
114 color: {{CAP_COLOR}};
115 text-shadow: {{TEXT_SHADOW}};
116 filter: {{TEXT_FILTER}};
117 line-height: 1.0;
118 will-change: transform, opacity;
119 /* Free-mode defaults (when cap is a direct child of #stage):
120 per-group `top:` positions the element's TOP edge from the
121 frame top. Agent writes top/left/right per group. */
122 top: 50%; left: 0; right: 0; text-align: center;
123 }
124 .cap .w { display: inline-block; opacity: 0; will-change: opacity, transform; }
125
126 /* Plane mode — when .cap is nested inside .plane, switch to flow
127 layout so the plane's flex/grid can own the stacking. Agent writes
128 typography only per group; positions are delegated to the plane.
129 This is the memory-wall / champion architecture: a shared parent
130 container handles spatial coherence, caps inside don't fight for
131 top% values. */
132 .plane { position: absolute; pointer-events: none; }
133 .plane > .cap {
134 position: relative;
135 top: auto; left: auto; right: auto; bottom: auto;
136 }
137
138 {{PLANES_CSS}}
139
140 /* Per-group typography (agent-authored). */
141 {{CUSTOM_CSS}}
142 </style>
143 </head>
144 <body>
145 <div
146 id="root"
147 data-composition-id="main"
148 data-start="0"
149 data-duration="{{DURATION}}"
150 data-width="{{WIDTH}}"
151 data-height="{{HEIGHT}}"
152 >
153 <video
154 id="a-roll"
155 src="source.mp4"
156 muted
157 playsinline
158 data-duration="{{DURATION}}"
159 data-track-index="0"
160 style="z-index: 1"
161 ></video>
162
163 <div id="stage">{{GROUPS_HTML}}</div>
164
165 <audio
166 id="a-roll-audio"
167 src="source.mp4"
168 data-start="0"
169 data-duration="{{DURATION}}"
170 data-track-index="3"
171 data-volume="1"
172 ></audio>
173 </div>
174
175 <script>
176 window.__timelines = window.__timelines || {};
177 const tl = gsap.timeline({ paused: true });
178 const DUR = {{DURATION}};
179 const GROUPS = {{GROUPS_JSON}};
180
181 /* Motion vocabulary — LOCKED.
182 soft: gentle fade with 8px y-drift, 0.45s power2.out
183 present: snappier pop with 6px y + 1.04 scale, 0.22s power3.out
184 Both target per-WORD spans. Container fade-in via tl.set at g.in-0.01. */
185 GROUPS.forEach(function (g) {
186 var sel = "#" + g.id;
187 var isSoft = g.tone === "soft";
188
189 // Enter: reveal opacity at g.in-0.01. The cap has been reserving flex
190 // space all along (opacity: 0 but display: block from CSS), so no
191 // layout reflow when it fades in — matches memory-wall canonical.
192 tl.set(sel, { opacity: 1 }, Math.max(0, g.in - 0.01));
193
194 g.words.forEach(function (w, i) {
195 var wsel = sel + " .w[data-i='" + i + "']";
196 if (isSoft) {
197 tl.fromTo(wsel,
198 { opacity: 0, y: 8 },
199 { opacity: 1, y: 0, duration: 0.45, ease: "power2.out", overwrite: "auto" },
200 w.start);
201 } else {
202 tl.fromTo(wsel,
203 { opacity: 0, y: 6, scale: 1.04 },
204 { opacity: 1, y: 0, scale: 1.0, duration: 0.22,
205 ease: "power3.out", overwrite: "auto", transformOrigin: "50% 50%" },
206 w.start);
207 }
208 });
209
210 if (g.out < DUR) {
211 tl.to(sel, { opacity: 0, duration: 0.5, ease: "power2.in", overwrite: "auto" },
212 g.out - 0.5);
213 }
214 // Exit: display:none releases flex space so the next sentence-block's
215 // caps can take the top slot. All caps in ONE block share the same
216 // `out` so this fires synchronously for the block — one clean reflow
217 // at the boundary, no per-cap jitter. Plan must share `out` within a
218 // block; staggered per-cap exits are forbidden in plane mode.
219 tl.set(sel, { opacity: 0, display: "none" }, g.out);
220 });
221
222 tl.seek(0);
223 window.__timelines["main"] = tl;
224 </script>
225 </body>
226</html>