Setting the file. One moment. Index · Music To Video · heygen-com/hyperframes · Skills DocsGsap Min
(opens in a new tab)
references/templates/roll-flipbook-word-cycle/index.html
HTML·267 lines·11 KB
]'
12>
13 <head>
14 <meta charset="utf-8" />
15 <meta name="viewport" content="width=1920, height=1080" />
16 <title>Roll-Driven Flipbook (Word Cycle → Optional Lock) — gruop_3 reference impl</title>
17 <script src="../../motion-primitives/assets/gsap.min.js"></script>
18 <style>
19 *,
20 *::before,
21 *::after {
22 margin: 0;
23 padding: 0;
24 box-sizing: border-box;
25 }
26 html,
27 body {
28 background: var(--bg, #000000);
29 overflow: hidden;
30 font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
31 }
32 #root {
33 position: relative;
34 width: 1920px;
35 height: 1080px;
36 overflow: hidden;
37 background: var(--bg, #000000);
38 }
39 .g3-overlay {
40 position: absolute;
41 inset: 0;
42 display: flex;
43 align-items: center;
44 justify-content: center;
45 pointer-events: none;
46 }
47 /* grid stack — every layer occupies the same centred cell, so scaling a
48 layer transforms around its own centre (no translate(-50%) to clobber). */
49 .g3-stack {
50 display: grid;
51 place-items: center;
52 }
53 .g3-stack > * {
54 grid-area: 1 / 1;
55 }
56 .g3-layer {
57 white-space: nowrap;
58 color: var(--text, #ffffff);
59 text-shadow:
60 0 0 18px rgba(255, 255, 255, 0.45),
61 0 0 46px rgba(255, 255, 255, 0.22);
62 }
63 .g3-flip {
64 font-size: 120px;
65 font-weight: 800;
66 letter-spacing: -0.01em;
67 }
68 .g3-resolve {
69 font-size: 116px;
70 font-weight: 800;
71 font-style: italic;
72 letter-spacing: -0.01em;
73 }
74 .g3-period {
75 color: var(--accent, #ff6a32);
76 font-weight: 800;
77 font-style: italic;
78 }
79 </style>
80 </head>
81 <body>
82 <!-- data-duration = the scene span. Set this to YOUR scene length; the script
83 reads it back so duration is single-sourced (no hard-coded value in JS).
84 Rebased to scene-relative time — content starts at ~0, no dead pre-roll. -->
85 <div
86 data-hf-id="hf-g3rt"
87 id="root"
88 data-composition-id="group-three-recreate"
89 data-width="1920"
90 data-height="1080"
91 data-start="0"
92 data-duration="3.8"
93 data-root="true"
94 >
95 <div data-hf-id="hf-g3ov" class="g3-overlay">
96 <div data-hf-id="hf-g3sk" class="g3-stack" id="stack"></div>
97 </div>
98 </div>
99
100 <script>
101 (function () {
102 // ════════════════════════════════════════════════════════════════
103 // ROLL-DRIVEN FLIPBOOK (Word Cycle → Optional Lock) · dense_multiply
104 // A hi-hat ROLL drives a centred word that flips every 16th-note (flipbook),
105 // cycling a word list to FILL the roll. Optionally, when a SURGE/phrase
106 // follows, the flicker resolves (scramble-decode) into a locked phrase.
107 // Background is pure black. Every move is a 0ms autoAlpha set on a pre-built
108 // node (no GSAP textContent-sets, no onUpdate — both fail under seek render).
109 // ════════════════════════════════════════════════════════════════
110
111 var root = document.getElementById("root");
112
113 // ── 1. BEAT MAP — the ONLY block you edit per track ─────────────────
114 // SCENE-RELATIVE time (starts at ~0 — no dead pre-roll). When reusing, map
115 // your audiomap roll [rollStart,rollEnd] onto [flipStart, flipStart+rollLen],
116 // i.e. subtract the roll's start offset. bpm stays your track's tempo.
117 var BEAT = {
118 bpm: 129.2, // tempo.bpm → 16th-note flip step = 60/bpm/4
119 flipStart: 0.1, // tiny entrance lead-in — NO dead air before the flicker
120 flipEnd: 2.19, // flicker length ≈ the source roll (~2.1s); auto-fills with words
121 resolveStart: 2.33, // lock phrase enters after a short clear (only if a phrase is set)
122 resolveLock: 3.33, // locks ~1s later (place just before a downbeat in your track)
123 };
124 // Scene length is single-sourced from data-duration (edit it in the HTML above).
125 var DUR = parseFloat(root.getAttribute("data-duration")) || 7.34;
126
127 // ── 2. Content variables ────────────────────────────────────────────
128 var defaults = {
129 bgColor: "#000000",
130 textColor: "#ffffff",
131 accentColor: "#ff6a32",
132 flipWords:
133 "Design,Strategy,Branding,Marketing,Production,Campaigns,Experiential,Communication,Event Management",
134 resolveText: "everything you need",
135 periodChar: ".",
136 };
137 var vars = Object.assign(
138 {},
139 defaults,
140 window.__hyperframes && window.__hyperframes.getVariables
141 ? window.__hyperframes.getVariables()
142 : {},
143 );
144
145 document.documentElement.style.setProperty("--bg", vars.bgColor);
146 document.documentElement.style.setProperty("--text", vars.textColor);
147 document.documentElement.style.setProperty("--accent", vars.accentColor);
148 var stack = root.querySelector("#stack");
149
150 var WORDS = String(vars.flipWords)
151 .split(",")
152 .map(function (w) {
153 return w.trim();
154 })
155 .filter(function (w) {
156 return w !== "";
157 });
158 var RESOLVE = String(vars.resolveText || "").trim(); // empty → pure flicker, no lock
159 var HAS_RESOLVE = RESOLVE !== "";
160 var PERIOD =
161 vars.periodChar && String(vars.periodChar).trim() !== "" ? vars.periodChar : "";
162
163 // ── 3. Derived timing (auto-fills the roll — adapts to any length / bpm) ─
164 var FLIP_STEP = 60 / BEAT.bpm / 4; // one word per 16th-note
165 var N_SWAPS = Math.max(1, Math.round((BEAT.flipEnd - BEAT.flipStart) / FLIP_STEP));
166
167 // ── 4. Build nodes ──────────────────────────────────────────────────
168 var flipNodes = {};
169 WORDS.forEach(function (w) {
170 if (!flipNodes[w]) {
171 var s = document.createElement("span");
172 s.className = "g3-layer g3-flip";
173 s.textContent = w;
174 stack.appendChild(s);
175 flipNodes[w] = s;
176 gsap.set(s, { autoAlpha: 0 });
177 }
178 });
179
180 var resolveNodes = [];
181 if (HAS_RESOLVE) {
182 var GLYPHS = "!<>-_/[]{}=+*^?#$%&01ABXYZ";
183 var decodeAt = function (str, p) {
184 var n = str.length,
185 out = "",
186 step = Math.floor(p * 48);
187 for (var k = 0; k < n; k++) {
188 var c = str.charAt(k);
189 if (c === " ") {
190 out += " ";
191 continue;
192 }
193 var lock = 0.4 + 0.6 * ((k + 1) / n); // lock left→right, in the back 60%
194 out += p >= lock ? c : GLYPHS.charAt((k * 7 + step * 3) % GLYPHS.length);
195 }
196 return out;
197 };
198 var DECODE_STEP = FLIP_STEP / 2; // scramble twice as fast as the flip
199 var N_STEPS = Math.max(
200 1,
201 Math.round((BEAT.resolveLock - BEAT.resolveStart) / DECODE_STEP),
202 );
203 for (var s2 = 0; s2 <= N_STEPS; s2++) {
204 var node = document.createElement("span");
205 node.className = "g3-layer g3-resolve";
206 if (s2 === N_STEPS) {
207 node.appendChild(document.createTextNode(RESOLVE));
208 if (PERIOD) {
209 var pd = document.createElement("span");
210 pd.className = "g3-period";
211 pd.textContent = PERIOD;
212 node.appendChild(pd);
213 }
214 } else {
215 node.textContent = decodeAt(RESOLVE, s2 / N_STEPS);
216 }
217 stack.appendChild(node);
218 gsap.set(node, { autoAlpha: 0 });
219 resolveNodes.push({ node: node, t: BEAT.resolveStart + s2 * DECODE_STEP });
220 }
221 }
222
223 // ── 5. Timeline — one paused GSAP timeline, absolute audio seconds ──
224 window.__timelines = window.__timelines || {};
225 var tl = gsap.timeline({ paused: true });
226
227 // PHASE A — flipbook word-cycle on the 16th grid, FILLING the roll window
228 var prev = null;
229 for (var i = 0; i < N_SWAPS; i++) {
230 var t = BEAT.flipStart + i * FLIP_STEP;
231 var node = flipNodes[WORDS[i % WORDS.length]];
232 if (prev && prev !== node) tl.set(prev, { autoAlpha: 0 }, t);
233 tl.set(node, { autoAlpha: 1 }, t);
234 prev = node;
235 }
236 // glitchy reveal of the first word as the roll kicks in (scale is safe here)
237 tl.fromTo(
238 flipNodes[WORDS[0]],
239 { scale: 0.8, filter: "blur(10px)" },
240 { scale: 1, filter: "blur(0px)", duration: 0.12, ease: "power2.out" },
241 BEAT.flipStart,
242 );
243
244 // PHASE B — resolve (OPTIONAL). With a lock phrase: clear, then scramble-decode
245 // and lock, holding to the end. Without one: the last word just holds (locks).
246 if (HAS_RESOLVE) {
247 if (prev) tl.set(prev, { autoAlpha: 0 }, BEAT.flipEnd); // clear the flicker
248 var prevR = null;
249 for (var r = 0; r < resolveNodes.length; r++) {
250 if (prevR) tl.set(prevR, { autoAlpha: 0 }, resolveNodes[r].t);
251 tl.set(resolveNodes[r].node, { autoAlpha: 1 }, resolveNodes[r].t);
252 prevR = resolveNodes[r].node;
253 }
254 }
255 // (if no resolve, `prev` — the last flip word — stays visible to the end)
256
257 // Extend the timeline to the full duration with a no-op so the held final
258 // state renders on every frame to DUR (a set AT the timeline's last instant
259 // gets dropped by the seek renderer).
260 tl.set(stack, { opacity: 1 }, DUR);
261
262 tl.seek(0);
263 window.__timelines["group-three-recreate"] = tl;
264 })();
265 </script>
266 </body>
267</html>