Setting the file. One moment. Index · Music To Video · heygen-com/hyperframes · Skills DocsGsap Min
(opens in a new tab)
references/templates/held-message-living-field/index.html
HTML·485 lines·17 KB
12 {"id":"tagText", "type":"string", "label":"Tagline", "default":"HTML · SHADERS · MP4"},
13 {"id":"flowSpeed", "type":"number", "label":"Flow speed", "default":1, "min":0.1, "max":3, "step":0.1}
14 ]'
15>
16 <head>
17 <meta charset="utf-8" />
18 <meta name="viewport" content="width=1920, height=1080" />
19 <title>WebGL Textures Playground — Template</title>
20 <link
21 href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap"
22 rel="stylesheet"
23 />
24 <script src="https://cdn.jsdelivr.net/npm/three@0.147.0/build/three.min.js"></script>
25 <script src="../../motion-primitives/assets/gsap.min.js"></script>
26 <style>
27 *,
28 *::before,
29 *::after {
30 margin: 0;
31 padding: 0;
32 box-sizing: border-box;
33 }
34 /* --bg / --mark-top / --mark-bottom / --line are set from variables in JS */
35 html,
36 body {
37 background: var(--bg, #050507);
38 overflow: hidden;
39 font-family: "Inter", system-ui, sans-serif;
40 }
41 #root {
42 position: relative;
43 width: 1920px;
44 height: 1080px;
45 overflow: hidden;
46 background: var(--bg, #050507);
47 }
48 .sf-stage {
49 position: absolute;
50 inset: 0;
51 }
52 .sf-canvas {
53 position: absolute;
54 inset: 0;
55 width: 1920px;
56 height: 1080px;
57 display: block;
58 }
59 .sf-overlay {
60 position: absolute;
61 inset: 0;
62 display: flex;
63 flex-direction: column;
64 align-items: center;
65 justify-content: center;
66 gap: 28px;
67 color: #fff;
68 text-align: center;
69 mix-blend-mode: screen;
70 pointer-events: none;
71 }
72 .sf-mark {
73 font-size: 280px;
74 font-weight: 800;
75 letter-spacing: -18px;
76 line-height: 1;
77 display: flex;
78 gap: 0;
79 opacity: 0;
80 filter: drop-shadow(0 0 60px rgba(180, 220, 255, 0.4));
81 }
82 .sf-mark-letter {
83 display: inline-block;
84 background: linear-gradient(
85 180deg,
86 var(--mark-top, #ffffff) 0%,
87 var(--mark-bottom, #8aa9ff) 100%
88 );
89 -webkit-background-clip: text;
90 background-clip: text;
91 -webkit-text-fill-color: transparent;
92 transform: translateY(0);
93 }
94 .sf-rule {
95 width: 0;
96 height: 2px;
97 background: linear-gradient(
98 90deg,
99 transparent 0%,
100 var(--line, rgba(255, 255, 255, 0.9)) 50%,
101 transparent 100%
102 );
103 opacity: 0;
104 }
105 .sf-title {
106 font-size: 64px;
107 font-weight: 700;
108 letter-spacing: 18px;
109 opacity: 0;
110 clip-path: inset(0 100% 0 0);
111 text-transform: uppercase;
112 }
113 .sf-tag {
114 font-size: 18px;
115 font-weight: 500;
116 letter-spacing: 8px;
117 color: rgba(255, 255, 255, 0.65);
118 opacity: 0;
119 text-transform: uppercase;
120 }
121 .sf-grain {
122 position: absolute;
123 inset: 0;
124 pointer-events: none;
125 opacity: 0.06;
126 mix-blend-mode: overlay;
127 background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='240' height='240'><filter id='n'><feTurbulence baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
128 }
129 </style>
130 </head>
131 <body>
132 <div
133 data-hf-id="hf-a0d4"
134 id="root"
135 data-composition-id="webgl-textures-playground"
136 data-width="1920"
137 data-height="1080"
138 data-start="0"
139 data-duration="12"
140 data-root="true"
141 >
142 <div data-hf-id="hf-clsq" class="sf-stage">
143 <canvas data-hf-id="hf-yk51" class="sf-canvas" width="1920" height="1080"></canvas>
144 <div data-hf-id="hf-7z0g" class="sf-overlay">
145 <div data-hf-id="hf-yo0z" class="sf-mark"><!-- letters injected from markText --></div>
146 <div data-hf-id="hf-nig2" class="sf-rule"></div>
147 <div data-hf-id="hf-gjx6" class="sf-title"></div>
148 <div data-hf-id="hf-e7y8" class="sf-tag"></div>
149 </div>
150 <div data-hf-id="hf-ku55" class="sf-grain"></div>
151 </div>
152 </div>
153
154 <script>
155 (function () {
156 // ── 1. Resolve variables (read ONCE during init) ──────────────────
157 var defaults = {
158 bgColor: "#050507",
159 colorLow: "#0a1430",
160 colorMid: "#6a6f9e",
161 colorHigh: "#e8f0ff",
162 accentColor: "#aee4ff",
163 markText: "HF",
164 titleText: "HYPERFRAMES",
165 tagText: "HTML · SHADERS · MP4",
166 flowSpeed: 1,
167 };
168 var vars = Object.assign(
169 {},
170 defaults,
171 window.__hyperframes && window.__hyperframes.getVariables
172 ? window.__hyperframes.getVariables()
173 : {},
174 );
175
176 // sRGB hex -> linear-light vec3 (the shader works in linear space and
177 // gamma-encodes at the end, so converting here makes the picked color
178 // land close to what a color picker shows).
179 function hexToLinear(hex) {
180 var h = String(hex).replace("#", "").trim();
181 if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
182 var r = parseInt(h.substr(0, 2), 16) / 255;
183 var g = parseInt(h.substr(2, 2), 16) / 255;
184 var b = parseInt(h.substr(4, 2), 16) / 255;
185 var lin = function (c) {
186 return Math.pow(c, 2.2);
187 };
188 return new THREE.Vector3(lin(r), lin(g), lin(b));
189 }
190
191 var root = document.getElementById("root");
192 var canvas = root.querySelector(".sf-canvas");
193 var W = 1920;
194 var H = 1080;
195
196 // ── 2. Apply text + CSS-driven colors ─────────────────────────────
197 var docEl = document.documentElement;
198 docEl.style.setProperty("--bg", vars.bgColor);
199 docEl.style.setProperty("--mark-top", vars.colorHigh);
200 docEl.style.setProperty("--mark-bottom", vars.colorMid);
201 docEl.style.setProperty("--line", vars.colorHigh);
202
203 var markEl = root.querySelector(".sf-mark");
204 markEl.innerHTML = "";
205 String(vars.markText)
206 .split("")
207 .forEach(function (ch) {
208 var span = document.createElement("span");
209 span.className = "sf-mark-letter";
210 span.textContent = ch;
211 markEl.appendChild(span);
212 });
213 root.querySelector(".sf-title").textContent = vars.titleText;
214 root.querySelector(".sf-tag").textContent = vars.tagText;
215
216 // ── 3. WebGL flow field ───────────────────────────────────────────
217 var renderer = new THREE.WebGLRenderer({
218 canvas: canvas,
219 antialias: false,
220 alpha: false,
221 preserveDrawingBuffer: true,
222 });
223 renderer.setPixelRatio(1);
224 renderer.setSize(W, H, false);
225
226 var scene = new THREE.Scene();
227 var camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
228
229 var uniforms = {
230 uTime: { value: 0.0 },
231 uPhase: { value: 0.0 },
232 uReveal: { value: 0.0 },
233 uChromAb: { value: 0.0 },
234 uVignette: { value: 0.0 },
235 uIntensity: { value: 0.0 },
236 uResolution: { value: new THREE.Vector2(W, H) },
237 uSpeed: { value: Number(vars.flowSpeed) || 1.0 },
238 // ── COLOR CONTROLS (driven by the composition variables) ──
239 uColLow: { value: hexToLinear(vars.colorLow) },
240 uColMid: { value: hexToLinear(vars.colorMid) },
241 uColHigh: { value: hexToLinear(vars.colorHigh) },
242 uAccent: { value: hexToLinear(vars.accentColor) },
243 };
244
245 var vert = [
246 "varying vec2 vUv;",
247 "void main(){",
248 " vUv = uv;",
249 " gl_Position = vec4(position.xy, 0.0, 1.0);",
250 "}",
251 ].join("\n");
252
253 var frag = [
254 "precision highp float;",
255 "varying vec2 vUv;",
256 "uniform float uTime;",
257 "uniform float uPhase;",
258 "uniform float uReveal;",
259 "uniform float uChromAb;",
260 "uniform float uVignette;",
261 "uniform float uIntensity;",
262 "uniform float uSpeed;",
263 "uniform vec2 uResolution;",
264 "uniform vec3 uColLow;",
265 "uniform vec3 uColMid;",
266 "uniform vec3 uColHigh;",
267 "uniform vec3 uAccent;",
268
269 "float hash21(vec2 p){",
270 " p = fract(p * vec2(123.34, 456.21));",
271 " p += dot(p, p + 45.32);",
272 " return fract(p.x * p.y);",
273 "}",
274
275 "float noise2(vec2 p){",
276 " vec2 i = floor(p);",
277 " vec2 f = fract(p);",
278 " vec2 u = f * f * (3.0 - 2.0 * f);",
279 " float a = hash21(i);",
280 " float b = hash21(i + vec2(1.0, 0.0));",
281 " float c = hash21(i + vec2(0.0, 1.0));",
282 " float d = hash21(i + vec2(1.0, 1.0));",
283 " return mix(mix(a,b,u.x), mix(c,d,u.x), u.y);",
284 "}",
285
286 "float fbm(vec2 p){",
287 " float v = 0.0;",
288 " float a = 0.5;",
289 " for(int i=0;i<5;i++){",
290 " v += a * noise2(p);",
291 " p *= 2.02;",
292 " a *= 0.5;",
293 " }",
294 " return v;",
295 "}",
296
297 "vec2 curl(vec2 p, float t){",
298 " float e = 0.6;",
299 " float n1 = fbm(p + vec2(0.0, e) + t*0.18);",
300 " float n2 = fbm(p + vec2(0.0, -e) + t*0.18);",
301 " float n3 = fbm(p + vec2( e, 0.0) + t*0.18);",
302 " float n4 = fbm(p + vec2(-e, 0.0) + t*0.18);",
303 " return vec2(n1 - n2, -(n3 - n4));",
304 "}",
305
306 // 3-stop gradient palette built from the color variables.
307 // t≈0 -> shadow, t≈0.5 -> mid, t≈1 -> highlight.
308 "vec3 palette(float t){",
309 " t = clamp(t, 0.0, 1.0);",
310 " vec3 lowMid = mix(uColLow, uColMid, smoothstep(0.0, 0.5, t));",
311 " return mix(lowMid, uColHigh, smoothstep(0.5, 1.0, t));",
312 "}",
313
314 "vec3 sampleField(vec2 uv, float t, float energy){",
315 " vec2 p = (uv - 0.5) * vec2(uResolution.x/uResolution.y, 1.0) * 2.4;",
316 " vec2 c = curl(p * 0.9, t);",
317 " vec2 q = p + c * (0.45 + 0.55 * energy);",
318
319 " float flow = fbm(q * 1.6 + vec2(t * 0.11, -t * 0.07));",
320 " float bands = sin(flow * 14.0 + t * 1.1);",
321 " bands = smoothstep(0.0, 1.0, 0.5 + 0.5 * bands);",
322
323 " float r = length(p);",
324 " float core = smoothstep(1.5, 0.05, r) * (0.6 + 0.4 * energy);",
325 " float glow = exp(-r * 1.6) * (0.55 + 0.45 * energy);",
326
327 " float k = clamp(flow * 0.85 + 0.15 * bands + 0.25 * core + 0.6 * glow, 0.0, 1.4);",
328 " vec3 col = palette(k * 0.9 + 0.1 * sin(t * 0.4));",
329 " col *= 0.7 + 0.6 * energy;",
330 " col += uAccent * core * 0.55;",
331 " return col;",
332 "}",
333
334 "void main(){",
335 " vec2 uv = vUv;",
336 " float t = uTime * uSpeed;",
337 " float e = uIntensity;",
338
339 " vec2 dir = normalize(uv - 0.5 + 1e-6);",
340 " float ca = uChromAb * (0.004 + 0.012 * length(uv - 0.5));",
341 " vec3 cR = sampleField(uv + dir * ca, t, e);",
342 " vec3 cG = sampleField(uv, t, e);",
343 " vec3 cB = sampleField(uv - dir * ca, t, e);",
344 " vec3 col = vec3(cR.r, cG.g, cB.b);",
345
346 " float lum = dot(col, vec3(0.299, 0.587, 0.114));",
347 " col = mix(vec3(lum), col, 1.05);",
348
349 " float rev = uReveal;",
350 " float sweep = smoothstep(0.0, 0.6, rev) * (1.0 - smoothstep(0.6, 1.0, rev));",
351 " col += uColHigh * sweep * 0.35;",
352
353 " vec3 phaseTint = mix(vec3(0.85, 0.92, 1.05), vec3(1.05, 0.95, 0.85), uPhase);",
354 " col *= phaseTint;",
355
356 " float v = length(uv - 0.5);",
357 " col *= 1.0 - smoothstep(0.45, 0.95, v) * uVignette;",
358
359 " col = col / (col + 1.0);",
360 " col = pow(col, vec3(1.0/2.2));",
361
362 " gl_FragColor = vec4(col, 1.0);",
363 "}",
364 ].join("\n");
365
366 var mat = new THREE.ShaderMaterial({
367 uniforms: uniforms,
368 vertexShader: vert,
369 fragmentShader: frag,
370 });
371 var geo = new THREE.PlaneGeometry(2, 2);
372 var mesh = new THREE.Mesh(geo, mat);
373 scene.add(mesh);
374
375 function renderFrame() {
376 renderer.render(scene, camera);
377 }
378 renderFrame();
379
380 // Continuous loop: ensures the WebGL backbuffer always has fresh
381 // pixels when puppeteer takes a screenshot. All visible state comes
382 // from uniforms driven by the GSAP timeline, so output is fully
383 // determined by the timeline position (deterministic across renders).
384 function loop() {
385 renderFrame();
386 window.requestAnimationFrame(loop);
387 }
388 window.requestAnimationFrame(loop);
389
390 // ── 4. Deterministic timeline (unchanged motion design) ───────────
391 window.__timelines = window.__timelines || {};
392 var tl = gsap.timeline({
393 paused: true,
394 onUpdate: renderFrame,
395 });
396
397 tl.to(uniforms.uTime, { value: 12.0, duration: 12, ease: "none" }, 0);
398
399 tl.to(uniforms.uIntensity, { value: 0.55, duration: 3.0, ease: "sine.inOut" }, 0);
400 tl.to(uniforms.uIntensity, { value: 1.0, duration: 1.2, ease: "power2.in" }, 3.0);
401 tl.to(uniforms.uIntensity, { value: 0.55, duration: 3.0, ease: "power2.out" }, 4.2);
402 tl.to(uniforms.uIntensity, { value: 0.3, duration: 4.8, ease: "sine.inOut" }, 7.2);
403
404 tl.fromTo(
405 uniforms.uChromAb,
406 { value: 0.0 },
407 { value: 0.4, duration: 3.0, ease: "sine.in" },
408 0,
409 );
410 tl.to(uniforms.uChromAb, { value: 1.6, duration: 1.0, ease: "power3.in" }, 3.0);
411 tl.to(uniforms.uChromAb, { value: 0.25, duration: 1.5, ease: "power2.out" }, 4.0);
412 tl.to(uniforms.uChromAb, { value: 0.18, duration: 6.5, ease: "sine.inOut" }, 5.5);
413
414 tl.fromTo(
415 uniforms.uReveal,
416 { value: 0.0 },
417 { value: 1.0, duration: 1.2, ease: "none" },
418 3.4,
419 );
420 tl.set(uniforms.uReveal, { value: 0.0 }, 4.6);
421
422 tl.to(uniforms.uPhase, { value: 0.6, duration: 5.0, ease: "sine.inOut" }, 1.5);
423 tl.to(uniforms.uPhase, { value: 0.15, duration: 5.5, ease: "sine.inOut" }, 6.5);
424
425 tl.to(uniforms.uVignette, { value: 0.85, duration: 3.0, ease: "power2.out" }, 4.2);
426
427 var mark = root.querySelector(".sf-mark");
428 var letters = root.querySelectorAll(".sf-mark-letter");
429 var rule = root.querySelector(".sf-rule");
430 var title = root.querySelector(".sf-title");
431 var tag = root.querySelector(".sf-tag");
432
433 tl.fromTo(
434 mark,
435 {
436 opacity: 0,
437 scale: 1.18,
438 filter: "blur(24px) drop-shadow(0 0 60px rgba(180,220,255,0.4))",
439 },
440 {
441 opacity: 1,
442 scale: 1.0,
443 filter: "blur(0px) drop-shadow(0 0 80px rgba(180,220,255,0.55))",
444 duration: 0.9,
445 ease: "power3.out",
446 },
447 3.6,
448 );
449 tl.fromTo(
450 letters,
451 { y: 80 },
452 { y: 0, duration: 1.0, ease: "expo.out", stagger: 0.08 },
453 3.6,
454 );
455
456 tl.fromTo(
457 rule,
458 { width: 0, opacity: 0 },
459 { width: 360, opacity: 0.85, duration: 0.8, ease: "power2.out" },
460 5.1,
461 );
462
463 tl.fromTo(
464 title,
465 { opacity: 0, clipPath: "inset(0 100% 0 0)" },
466 { opacity: 1, clipPath: "inset(0 0% 0 0)", duration: 0.95, ease: "power3.out" },
467 5.3,
468 );
469
470 tl.fromTo(
471 tag,
472 { opacity: 0, y: 12 },
473 { opacity: 0.7, y: 0, duration: 0.8, ease: "sine.out" },
474 6.0,
475 );
476
477 tl.to(mark, { scale: 1.04, duration: 5.0, ease: "sine.inOut" }, 7.0);
478
479 tl.to([mark, rule, title, tag], { opacity: 0, duration: 0.6, ease: "power1.in" }, 11.4);
480
481 window.__timelines["webgl-textures-playground"] = tl;
482 })();
483 </script>
484 </body>
485</html>