Setting the file. One moment. Index · Music To Video · heygen-com/hyperframes · Skills DocsGsap Min
(opens in a new tab)
references/templates/typewriter-phrase-keyword-shuffle/index.html
HTML·238 lines·10 KB
12 {"id":"periodChar", "type":"string", "label":"Trailing accent char (empty = none)","default":"."}
13 ]'
14>
15 <head>
16 <meta charset="utf-8" />
17 <meta name="viewport" content="width=1920, height=1080" />
18 <title>Typewriter Phrase / Keyword Font-Shuffle — group_2 reference impl</title>
19 <script src="../../motion-primitives/assets/gsap.min.js"></script>
20 <style>
21 *,
22 *::before,
23 *::after {
24 margin: 0;
25 padding: 0;
26 box-sizing: border-box;
27 }
28 html,
29 body {
30 background: var(--bg, #000000);
31 overflow: hidden;
32 font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
33 }
34 #root {
35 position: relative;
36 width: 1920px;
37 height: 1080px;
38 overflow: hidden;
39 background: var(--bg, #000000);
40 }
41 .g2-overlay {
42 position: absolute;
43 inset: 0;
44 display: flex;
45 align-items: center;
46 justify-content: center;
47 pointer-events: none;
48 }
49 /* one centered line; when the keyword changes typeface the line re-centers,
50 exactly as the source does (the lead words shift slightly, that's faithful). */
51 .g2-line {
52 display: inline-flex;
53 align-items: baseline;
54 gap: 0.28em;
55 white-space: nowrap;
56 color: var(--text, #ffffff);
57 font-size: 112px;
58 font-weight: 800;
59 letter-spacing: -0.01em;
60 text-shadow:
61 0 0 18px rgba(255, 255, 255, 0.45),
62 0 0 44px rgba(255, 255, 255, 0.22);
63 }
64 .g2-word {
65 display: inline-block;
66 }
67 /* keyword + its trailing accent travel together so the dot hugs the word */
68 .g2-cword {
69 display: inline-flex;
70 align-items: baseline;
71 }
72 .g2-period {
73 display: inline-block;
74 color: var(--accent, #ff6a32);
75 font-weight: 800;
76 }
77 </style>
78 </head>
79 <body>
80 <!-- duration = 3.85s = the spell-out + shuffle scene (0 → just before the 3.855s drop) -->
81 <div
82 data-hf-id="hf-g2rt"
83 id="root"
84 data-composition-id="group-two-recreate"
85 data-width="1920"
86 data-height="1080"
87 data-start="0"
88 data-duration="3.85"
89 data-root="true"
90 >
91 <div data-hf-id="hf-g2ov" class="g2-overlay">
92 <div data-hf-id="hf-g2ln" class="g2-line" id="line"></div>
93 </div>
94 </div>
95
96 <script>
97 (function () {
98 // ════════════════════════════════════════════════════════════════
99 // TYPEWRITER PHRASE / KEYWORD FONT-SHUFFLE · structural_type: per_onset_sequential
100 // Words type in one-per-onset to spell a phrase; then ONE keyword cycles
101 // typefaces on the beat while everything else holds dead still.
102 // ════════════════════════════════════════════════════════════════
103
104 // ── 1. Content variables (what a user changes) ─────────────────────
105 var defaults = {
106 bgColor: "#000000",
107 textColor: "#ffffff",
108 accentColor: "#ff6a32",
109 lead1: "growth",
110 lead2: "through",
111 lead3: "",
112 keyword: "creativity",
113 periodChar: ".",
114 };
115 var vars = Object.assign(
116 {},
117 defaults,
118 window.__hyperframes && window.__hyperframes.getVariables
119 ? window.__hyperframes.getVariables()
120 : {},
121 );
122
123 // ── 2. FONT PRESET LIBRARY (preset many — the keyword cycles these) ─
124 // FONTS[0] is the BASE face, shared with the lead words. The keyword flips
125 // to FONTS[i] on each SHUFFLE step. Offline-safe generic stacks only (no
126 // remote fonts). Extend this list freely — more beats → more presets to
127 // cycle through. Each entry: { family, weight, style }.
128 var FONTS = [
129 {
130 family: '"Helvetica Neue", Helvetica, Arial, sans-serif',
131 weight: 800,
132 style: "normal",
133 }, // 0 base bold sans
134 { family: 'Georgia, "Times New Roman", Times, serif', weight: 500, style: "normal" }, // 1 upright serif
135 { family: "Georgia, serif", weight: 800, style: "normal" }, // 2 heavy serif
136 { family: 'Georgia, "Times New Roman", serif', weight: 500, style: "italic" }, // 3 italic serif
137 { family: '"Courier New", Courier, monospace', weight: 700, style: "normal" }, // 4 monospace
138 { family: '"Arial Narrow", "Helvetica Neue", sans-serif', weight: 700, style: "normal" }, // 5 condensed sans
139 { family: '"Helvetica Neue", Arial, sans-serif', weight: 300, style: "italic" }, // 6 light sans italic
140 { family: "Georgia, serif", weight: 700, style: "italic" }, // 7 bold serif italic
141 ];
142 function fontProps(i) {
143 var f = FONTS[((i % FONTS.length) + FONTS.length) % FONTS.length];
144 return { fontFamily: f.family, fontWeight: f.weight, fontStyle: f.style };
145 }
146
147 // ── 3. BEAT MAP (filled from <group>.audiomap.json — see README) ────
148 // group_2.audiomap.json: BPM 129.2, hi-hat onsets drive every move.
149 // one ENTRY onset per lead word (in order), then KEYWORD, then PERIOD.
150 var LEAD_ONSETS = [0.557, 0.906]; // grid.events[].t — one per non-empty lead word
151 var KEY_ONSET = 1.231; // keyword types in here
152 var PERIOD_ONSET = 1.44; // accent char punches in here
153 // keyword font-shuffle: each {t,font} flips the keyword on a beat/onset.
154 // t = a grid.beats_sec[] / events[].t after the phrase is set.
155 // font = index into FONTS[]. Add as many rows as the beat has flips — and
156 // re-use indices to repeat faces, or walk new ones for a longer shuffle.
157 var SHUFFLE = [
158 { t: 1.904, font: 1 }, // → upright serif
159 { t: 2.392, font: 2 }, // → heavy serif
160 { t: 2.624, font: 3 }, // → italic serif
161 { t: 3.088, font: 0 }, // → back to base, then HOLD to the end
162 ];
163 var DUR = 3.85; // scene end (just before the 3.855 drop/kick)
164
165 // ── 4. Build the line (1–3 lead words + keyword + optional accent) ──
166 var root = document.getElementById("root");
167 document.documentElement.style.setProperty("--bg", vars.bgColor);
168 document.documentElement.style.setProperty("--text", vars.textColor);
169 document.documentElement.style.setProperty("--accent", vars.accentColor);
170
171 var line = root.querySelector("#line");
172 var leadWords = [vars.lead1, vars.lead2, vars.lead3].filter(function (w) {
173 return w && String(w).trim() !== "";
174 });
175
176 var leadEls = leadWords.map(function (w) {
177 var s = document.createElement("span");
178 s.className = "g2-word";
179 s.textContent = w;
180 line.appendChild(s);
181 return s;
182 });
183
184 var cword = document.createElement("span");
185 cword.className = "g2-cword";
186 var keyEl = document.createElement("span");
187 keyEl.className = "g2-word";
188 keyEl.textContent = vars.keyword;
189 cword.appendChild(keyEl);
190 var periodEl = null;
191 if (vars.periodChar && String(vars.periodChar).trim() !== "") {
192 periodEl = document.createElement("span");
193 periodEl.className = "g2-period";
194 periodEl.textContent = vars.periodChar;
195 cword.appendChild(periodEl);
196 }
197 line.appendChild(cword);
198
199 // ── 5. Timeline — one paused GSAP timeline, absolute audio seconds ──
200 window.__timelines = window.__timelines || {};
201 var tl = gsap.timeline({ paused: true });
202
203 var allSpans = leadEls.concat([keyEl]);
204 if (periodEl) allSpans.push(periodEl);
205 gsap.set(allSpans, { autoAlpha: 0 });
206 gsap.set(keyEl, fontProps(0)); // keyword starts in the base face
207
208 var POP_IN = { autoAlpha: 0, scale: 0.92, y: 5 };
209 var POP_OUT = { autoAlpha: 1, scale: 1, y: 0, duration: 0.1, ease: "back.out(2)" };
210
211 // typewriter accumulation — each lead word pops on its onset (words stay)
212 leadEls.forEach(function (el, i) {
213 var t = i < LEAD_ONSETS.length ? LEAD_ONSETS[i] : KEY_ONSET;
214 tl.fromTo(el, POP_IN, POP_OUT, t);
215 });
216 // keyword types in (in the base face)
217 tl.fromTo(keyEl, POP_IN, POP_OUT, KEY_ONSET);
218 // trailing accent char punches in on its own onset
219 if (periodEl) {
220 tl.fromTo(
221 periodEl,
222 { autoAlpha: 0, scale: 0.3 },
223 { autoAlpha: 1, scale: 1, duration: 0.09, ease: "back.out(3)" },
224 PERIOD_ONSET,
225 );
226 }
227
228 // keyword font-shuffle — 0ms hard sets on the beat (the percussion IS the swap)
229 SHUFFLE.forEach(function (s) {
230 tl.set(keyEl, fontProps(s.font), s.t);
231 });
232
233 tl.seek(0);
234 window.__timelines["group-two-recreate"] = tl;
235 })();
236 </script>
237 </body>
238</html>