mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-13 15:49:53 +00:00
486 lines
17 KiB
HTML
486 lines
17 KiB
HTML
<!doctype html>
|
|
<html
|
|
lang="en"
|
|
data-composition-variables='[
|
|
{"id":"bgColor", "type":"color", "label":"Page background", "default":"#050507"},
|
|
{"id":"colorLow", "type":"color", "label":"Flow — shadow tone", "default":"#0a1430"},
|
|
{"id":"colorMid", "type":"color", "label":"Flow — mid tone", "default":"#6a6f9e"},
|
|
{"id":"colorHigh", "type":"color", "label":"Flow — highlight tone", "default":"#e8f0ff"},
|
|
{"id":"accentColor","type":"color", "label":"Core / glow accent", "default":"#aee4ff"},
|
|
{"id":"markText", "type":"string", "label":"Monogram", "default":"HF"},
|
|
{"id":"titleText", "type":"string", "label":"Title", "default":"HYPERFRAMES"},
|
|
{"id":"tagText", "type":"string", "label":"Tagline", "default":"HTML · SHADERS · MP4"},
|
|
{"id":"flowSpeed", "type":"number", "label":"Flow speed", "default":1, "min":0.1, "max":3, "step":0.1}
|
|
]'
|
|
>
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=1920, height=1080" />
|
|
<title>WebGL Textures Playground — Template</title>
|
|
<link
|
|
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap"
|
|
rel="stylesheet"
|
|
/>
|
|
<script src="https://cdn.jsdelivr.net/npm/three@0.147.0/build/three.min.js"></script>
|
|
<script src="../../motion-primitives/assets/gsap.min.js"></script>
|
|
<style>
|
|
*,
|
|
*::before,
|
|
*::after {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
/* --bg / --mark-top / --mark-bottom / --line are set from variables in JS */
|
|
html,
|
|
body {
|
|
background: var(--bg, #050507);
|
|
overflow: hidden;
|
|
font-family: "Inter", system-ui, sans-serif;
|
|
}
|
|
#root {
|
|
position: relative;
|
|
width: 1920px;
|
|
height: 1080px;
|
|
overflow: hidden;
|
|
background: var(--bg, #050507);
|
|
}
|
|
.sf-stage {
|
|
position: absolute;
|
|
inset: 0;
|
|
}
|
|
.sf-canvas {
|
|
position: absolute;
|
|
inset: 0;
|
|
width: 1920px;
|
|
height: 1080px;
|
|
display: block;
|
|
}
|
|
.sf-overlay {
|
|
position: absolute;
|
|
inset: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 28px;
|
|
color: #fff;
|
|
text-align: center;
|
|
mix-blend-mode: screen;
|
|
pointer-events: none;
|
|
}
|
|
.sf-mark {
|
|
font-size: 280px;
|
|
font-weight: 800;
|
|
letter-spacing: -18px;
|
|
line-height: 1;
|
|
display: flex;
|
|
gap: 0;
|
|
opacity: 0;
|
|
filter: drop-shadow(0 0 60px rgba(180, 220, 255, 0.4));
|
|
}
|
|
.sf-mark-letter {
|
|
display: inline-block;
|
|
background: linear-gradient(
|
|
180deg,
|
|
var(--mark-top, #ffffff) 0%,
|
|
var(--mark-bottom, #8aa9ff) 100%
|
|
);
|
|
-webkit-background-clip: text;
|
|
background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
transform: translateY(0);
|
|
}
|
|
.sf-rule {
|
|
width: 0;
|
|
height: 2px;
|
|
background: linear-gradient(
|
|
90deg,
|
|
transparent 0%,
|
|
var(--line, rgba(255, 255, 255, 0.9)) 50%,
|
|
transparent 100%
|
|
);
|
|
opacity: 0;
|
|
}
|
|
.sf-title {
|
|
font-size: 64px;
|
|
font-weight: 700;
|
|
letter-spacing: 18px;
|
|
opacity: 0;
|
|
clip-path: inset(0 100% 0 0);
|
|
text-transform: uppercase;
|
|
}
|
|
.sf-tag {
|
|
font-size: 18px;
|
|
font-weight: 500;
|
|
letter-spacing: 8px;
|
|
color: rgba(255, 255, 255, 0.65);
|
|
opacity: 0;
|
|
text-transform: uppercase;
|
|
}
|
|
.sf-grain {
|
|
position: absolute;
|
|
inset: 0;
|
|
pointer-events: none;
|
|
opacity: 0.06;
|
|
mix-blend-mode: overlay;
|
|
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>");
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div
|
|
data-hf-id="hf-a0d4"
|
|
id="root"
|
|
data-composition-id="webgl-textures-playground"
|
|
data-width="1920"
|
|
data-height="1080"
|
|
data-start="0"
|
|
data-duration="12"
|
|
data-root="true"
|
|
>
|
|
<div data-hf-id="hf-clsq" class="sf-stage">
|
|
<canvas data-hf-id="hf-yk51" class="sf-canvas" width="1920" height="1080"></canvas>
|
|
<div data-hf-id="hf-7z0g" class="sf-overlay">
|
|
<div data-hf-id="hf-yo0z" class="sf-mark"><!-- letters injected from markText --></div>
|
|
<div data-hf-id="hf-nig2" class="sf-rule"></div>
|
|
<div data-hf-id="hf-gjx6" class="sf-title"></div>
|
|
<div data-hf-id="hf-e7y8" class="sf-tag"></div>
|
|
</div>
|
|
<div data-hf-id="hf-ku55" class="sf-grain"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
(function () {
|
|
// ── 1. Resolve variables (read ONCE during init) ──────────────────
|
|
var defaults = {
|
|
bgColor: "#050507",
|
|
colorLow: "#0a1430",
|
|
colorMid: "#6a6f9e",
|
|
colorHigh: "#e8f0ff",
|
|
accentColor: "#aee4ff",
|
|
markText: "HF",
|
|
titleText: "HYPERFRAMES",
|
|
tagText: "HTML · SHADERS · MP4",
|
|
flowSpeed: 1,
|
|
};
|
|
var vars = Object.assign(
|
|
{},
|
|
defaults,
|
|
window.__hyperframes && window.__hyperframes.getVariables
|
|
? window.__hyperframes.getVariables()
|
|
: {},
|
|
);
|
|
|
|
// sRGB hex -> linear-light vec3 (the shader works in linear space and
|
|
// gamma-encodes at the end, so converting here makes the picked color
|
|
// land close to what a color picker shows).
|
|
function hexToLinear(hex) {
|
|
var h = String(hex).replace("#", "").trim();
|
|
if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
|
|
var r = parseInt(h.substr(0, 2), 16) / 255;
|
|
var g = parseInt(h.substr(2, 2), 16) / 255;
|
|
var b = parseInt(h.substr(4, 2), 16) / 255;
|
|
var lin = function (c) {
|
|
return Math.pow(c, 2.2);
|
|
};
|
|
return new THREE.Vector3(lin(r), lin(g), lin(b));
|
|
}
|
|
|
|
var root = document.getElementById("root");
|
|
var canvas = root.querySelector(".sf-canvas");
|
|
var W = 1920;
|
|
var H = 1080;
|
|
|
|
// ── 2. Apply text + CSS-driven colors ─────────────────────────────
|
|
var docEl = document.documentElement;
|
|
docEl.style.setProperty("--bg", vars.bgColor);
|
|
docEl.style.setProperty("--mark-top", vars.colorHigh);
|
|
docEl.style.setProperty("--mark-bottom", vars.colorMid);
|
|
docEl.style.setProperty("--line", vars.colorHigh);
|
|
|
|
var markEl = root.querySelector(".sf-mark");
|
|
markEl.innerHTML = "";
|
|
String(vars.markText)
|
|
.split("")
|
|
.forEach(function (ch) {
|
|
var span = document.createElement("span");
|
|
span.className = "sf-mark-letter";
|
|
span.textContent = ch;
|
|
markEl.appendChild(span);
|
|
});
|
|
root.querySelector(".sf-title").textContent = vars.titleText;
|
|
root.querySelector(".sf-tag").textContent = vars.tagText;
|
|
|
|
// ── 3. WebGL flow field ───────────────────────────────────────────
|
|
var renderer = new THREE.WebGLRenderer({
|
|
canvas: canvas,
|
|
antialias: false,
|
|
alpha: false,
|
|
preserveDrawingBuffer: true,
|
|
});
|
|
renderer.setPixelRatio(1);
|
|
renderer.setSize(W, H, false);
|
|
|
|
var scene = new THREE.Scene();
|
|
var camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
|
|
|
|
var uniforms = {
|
|
uTime: { value: 0.0 },
|
|
uPhase: { value: 0.0 },
|
|
uReveal: { value: 0.0 },
|
|
uChromAb: { value: 0.0 },
|
|
uVignette: { value: 0.0 },
|
|
uIntensity: { value: 0.0 },
|
|
uResolution: { value: new THREE.Vector2(W, H) },
|
|
uSpeed: { value: Number(vars.flowSpeed) || 1.0 },
|
|
// ── COLOR CONTROLS (driven by the composition variables) ──
|
|
uColLow: { value: hexToLinear(vars.colorLow) },
|
|
uColMid: { value: hexToLinear(vars.colorMid) },
|
|
uColHigh: { value: hexToLinear(vars.colorHigh) },
|
|
uAccent: { value: hexToLinear(vars.accentColor) },
|
|
};
|
|
|
|
var vert = [
|
|
"varying vec2 vUv;",
|
|
"void main(){",
|
|
" vUv = uv;",
|
|
" gl_Position = vec4(position.xy, 0.0, 1.0);",
|
|
"}",
|
|
].join("\n");
|
|
|
|
var frag = [
|
|
"precision highp float;",
|
|
"varying vec2 vUv;",
|
|
"uniform float uTime;",
|
|
"uniform float uPhase;",
|
|
"uniform float uReveal;",
|
|
"uniform float uChromAb;",
|
|
"uniform float uVignette;",
|
|
"uniform float uIntensity;",
|
|
"uniform float uSpeed;",
|
|
"uniform vec2 uResolution;",
|
|
"uniform vec3 uColLow;",
|
|
"uniform vec3 uColMid;",
|
|
"uniform vec3 uColHigh;",
|
|
"uniform vec3 uAccent;",
|
|
|
|
"float hash21(vec2 p){",
|
|
" p = fract(p * vec2(123.34, 456.21));",
|
|
" p += dot(p, p + 45.32);",
|
|
" return fract(p.x * p.y);",
|
|
"}",
|
|
|
|
"float noise2(vec2 p){",
|
|
" vec2 i = floor(p);",
|
|
" vec2 f = fract(p);",
|
|
" vec2 u = f * f * (3.0 - 2.0 * f);",
|
|
" float a = hash21(i);",
|
|
" float b = hash21(i + vec2(1.0, 0.0));",
|
|
" float c = hash21(i + vec2(0.0, 1.0));",
|
|
" float d = hash21(i + vec2(1.0, 1.0));",
|
|
" return mix(mix(a,b,u.x), mix(c,d,u.x), u.y);",
|
|
"}",
|
|
|
|
"float fbm(vec2 p){",
|
|
" float v = 0.0;",
|
|
" float a = 0.5;",
|
|
" for(int i=0;i<5;i++){",
|
|
" v += a * noise2(p);",
|
|
" p *= 2.02;",
|
|
" a *= 0.5;",
|
|
" }",
|
|
" return v;",
|
|
"}",
|
|
|
|
"vec2 curl(vec2 p, float t){",
|
|
" float e = 0.6;",
|
|
" float n1 = fbm(p + vec2(0.0, e) + t*0.18);",
|
|
" float n2 = fbm(p + vec2(0.0, -e) + t*0.18);",
|
|
" float n3 = fbm(p + vec2( e, 0.0) + t*0.18);",
|
|
" float n4 = fbm(p + vec2(-e, 0.0) + t*0.18);",
|
|
" return vec2(n1 - n2, -(n3 - n4));",
|
|
"}",
|
|
|
|
// 3-stop gradient palette built from the color variables.
|
|
// t≈0 -> shadow, t≈0.5 -> mid, t≈1 -> highlight.
|
|
"vec3 palette(float t){",
|
|
" t = clamp(t, 0.0, 1.0);",
|
|
" vec3 lowMid = mix(uColLow, uColMid, smoothstep(0.0, 0.5, t));",
|
|
" return mix(lowMid, uColHigh, smoothstep(0.5, 1.0, t));",
|
|
"}",
|
|
|
|
"vec3 sampleField(vec2 uv, float t, float energy){",
|
|
" vec2 p = (uv - 0.5) * vec2(uResolution.x/uResolution.y, 1.0) * 2.4;",
|
|
" vec2 c = curl(p * 0.9, t);",
|
|
" vec2 q = p + c * (0.45 + 0.55 * energy);",
|
|
|
|
" float flow = fbm(q * 1.6 + vec2(t * 0.11, -t * 0.07));",
|
|
" float bands = sin(flow * 14.0 + t * 1.1);",
|
|
" bands = smoothstep(0.0, 1.0, 0.5 + 0.5 * bands);",
|
|
|
|
" float r = length(p);",
|
|
" float core = smoothstep(1.5, 0.05, r) * (0.6 + 0.4 * energy);",
|
|
" float glow = exp(-r * 1.6) * (0.55 + 0.45 * energy);",
|
|
|
|
" float k = clamp(flow * 0.85 + 0.15 * bands + 0.25 * core + 0.6 * glow, 0.0, 1.4);",
|
|
" vec3 col = palette(k * 0.9 + 0.1 * sin(t * 0.4));",
|
|
" col *= 0.7 + 0.6 * energy;",
|
|
" col += uAccent * core * 0.55;",
|
|
" return col;",
|
|
"}",
|
|
|
|
"void main(){",
|
|
" vec2 uv = vUv;",
|
|
" float t = uTime * uSpeed;",
|
|
" float e = uIntensity;",
|
|
|
|
" vec2 dir = normalize(uv - 0.5 + 1e-6);",
|
|
" float ca = uChromAb * (0.004 + 0.012 * length(uv - 0.5));",
|
|
" vec3 cR = sampleField(uv + dir * ca, t, e);",
|
|
" vec3 cG = sampleField(uv, t, e);",
|
|
" vec3 cB = sampleField(uv - dir * ca, t, e);",
|
|
" vec3 col = vec3(cR.r, cG.g, cB.b);",
|
|
|
|
" float lum = dot(col, vec3(0.299, 0.587, 0.114));",
|
|
" col = mix(vec3(lum), col, 1.05);",
|
|
|
|
" float rev = uReveal;",
|
|
" float sweep = smoothstep(0.0, 0.6, rev) * (1.0 - smoothstep(0.6, 1.0, rev));",
|
|
" col += uColHigh * sweep * 0.35;",
|
|
|
|
" vec3 phaseTint = mix(vec3(0.85, 0.92, 1.05), vec3(1.05, 0.95, 0.85), uPhase);",
|
|
" col *= phaseTint;",
|
|
|
|
" float v = length(uv - 0.5);",
|
|
" col *= 1.0 - smoothstep(0.45, 0.95, v) * uVignette;",
|
|
|
|
" col = col / (col + 1.0);",
|
|
" col = pow(col, vec3(1.0/2.2));",
|
|
|
|
" gl_FragColor = vec4(col, 1.0);",
|
|
"}",
|
|
].join("\n");
|
|
|
|
var mat = new THREE.ShaderMaterial({
|
|
uniforms: uniforms,
|
|
vertexShader: vert,
|
|
fragmentShader: frag,
|
|
});
|
|
var geo = new THREE.PlaneGeometry(2, 2);
|
|
var mesh = new THREE.Mesh(geo, mat);
|
|
scene.add(mesh);
|
|
|
|
function renderFrame() {
|
|
renderer.render(scene, camera);
|
|
}
|
|
renderFrame();
|
|
|
|
// Continuous loop: ensures the WebGL backbuffer always has fresh
|
|
// pixels when puppeteer takes a screenshot. All visible state comes
|
|
// from uniforms driven by the GSAP timeline, so output is fully
|
|
// determined by the timeline position (deterministic across renders).
|
|
function loop() {
|
|
renderFrame();
|
|
window.requestAnimationFrame(loop);
|
|
}
|
|
window.requestAnimationFrame(loop);
|
|
|
|
// ── 4. Deterministic timeline (unchanged motion design) ───────────
|
|
window.__timelines = window.__timelines || {};
|
|
var tl = gsap.timeline({
|
|
paused: true,
|
|
onUpdate: renderFrame,
|
|
});
|
|
|
|
tl.to(uniforms.uTime, { value: 12.0, duration: 12, ease: "none" }, 0);
|
|
|
|
tl.to(uniforms.uIntensity, { value: 0.55, duration: 3.0, ease: "sine.inOut" }, 0);
|
|
tl.to(uniforms.uIntensity, { value: 1.0, duration: 1.2, ease: "power2.in" }, 3.0);
|
|
tl.to(uniforms.uIntensity, { value: 0.55, duration: 3.0, ease: "power2.out" }, 4.2);
|
|
tl.to(uniforms.uIntensity, { value: 0.3, duration: 4.8, ease: "sine.inOut" }, 7.2);
|
|
|
|
tl.fromTo(
|
|
uniforms.uChromAb,
|
|
{ value: 0.0 },
|
|
{ value: 0.4, duration: 3.0, ease: "sine.in" },
|
|
0,
|
|
);
|
|
tl.to(uniforms.uChromAb, { value: 1.6, duration: 1.0, ease: "power3.in" }, 3.0);
|
|
tl.to(uniforms.uChromAb, { value: 0.25, duration: 1.5, ease: "power2.out" }, 4.0);
|
|
tl.to(uniforms.uChromAb, { value: 0.18, duration: 6.5, ease: "sine.inOut" }, 5.5);
|
|
|
|
tl.fromTo(
|
|
uniforms.uReveal,
|
|
{ value: 0.0 },
|
|
{ value: 1.0, duration: 1.2, ease: "none" },
|
|
3.4,
|
|
);
|
|
tl.set(uniforms.uReveal, { value: 0.0 }, 4.6);
|
|
|
|
tl.to(uniforms.uPhase, { value: 0.6, duration: 5.0, ease: "sine.inOut" }, 1.5);
|
|
tl.to(uniforms.uPhase, { value: 0.15, duration: 5.5, ease: "sine.inOut" }, 6.5);
|
|
|
|
tl.to(uniforms.uVignette, { value: 0.85, duration: 3.0, ease: "power2.out" }, 4.2);
|
|
|
|
var mark = root.querySelector(".sf-mark");
|
|
var letters = root.querySelectorAll(".sf-mark-letter");
|
|
var rule = root.querySelector(".sf-rule");
|
|
var title = root.querySelector(".sf-title");
|
|
var tag = root.querySelector(".sf-tag");
|
|
|
|
tl.fromTo(
|
|
mark,
|
|
{
|
|
opacity: 0,
|
|
scale: 1.18,
|
|
filter: "blur(24px) drop-shadow(0 0 60px rgba(180,220,255,0.4))",
|
|
},
|
|
{
|
|
opacity: 1,
|
|
scale: 1.0,
|
|
filter: "blur(0px) drop-shadow(0 0 80px rgba(180,220,255,0.55))",
|
|
duration: 0.9,
|
|
ease: "power3.out",
|
|
},
|
|
3.6,
|
|
);
|
|
tl.fromTo(
|
|
letters,
|
|
{ y: 80 },
|
|
{ y: 0, duration: 1.0, ease: "expo.out", stagger: 0.08 },
|
|
3.6,
|
|
);
|
|
|
|
tl.fromTo(
|
|
rule,
|
|
{ width: 0, opacity: 0 },
|
|
{ width: 360, opacity: 0.85, duration: 0.8, ease: "power2.out" },
|
|
5.1,
|
|
);
|
|
|
|
tl.fromTo(
|
|
title,
|
|
{ opacity: 0, clipPath: "inset(0 100% 0 0)" },
|
|
{ opacity: 1, clipPath: "inset(0 0% 0 0)", duration: 0.95, ease: "power3.out" },
|
|
5.3,
|
|
);
|
|
|
|
tl.fromTo(
|
|
tag,
|
|
{ opacity: 0, y: 12 },
|
|
{ opacity: 0.7, y: 0, duration: 0.8, ease: "sine.out" },
|
|
6.0,
|
|
);
|
|
|
|
tl.to(mark, { scale: 1.04, duration: 5.0, ease: "sine.inOut" }, 7.0);
|
|
|
|
tl.to([mark, rule, title, tag], { opacity: 0, duration: 0.6, ease: "power1.in" }, 11.4);
|
|
|
|
window.__timelines["webgl-textures-playground"] = tl;
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|