mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-01 19:42:03 +00:00
Restores the 208 catalog items reverted after their previews 404'd in production, this time on the payload mechanism rather than the .html files that caused the outage. The generator no longer writes a preview document to docs/public. That writer, and the machinery under it, existed only to produce files the docs host discards, so it is gone rather than bypassed. Items now embed the composition itself via a payload, which is what the previous change already does for the items that were already in the catalog. The variables explorer is parked, not restored: it drove its preview through the same unpublished .html path, so it would have shown an empty frame. Items that declare variables get the live player plus the static variables table, and reconnecting the explorer to payloads is a follow-up.
432 lines
17 KiB
HTML
Vendored
432 lines
17 KiB
HTML
Vendored
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=1920, height=1080" />
|
|
<title>Cosmic Orb</title>
|
|
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
|
<style>
|
|
*,
|
|
*::before,
|
|
*::after {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
body {
|
|
background: #000;
|
|
overflow: hidden;
|
|
}
|
|
#co-root {
|
|
position: relative;
|
|
width: 1920px;
|
|
height: 1080px;
|
|
overflow: hidden;
|
|
}
|
|
#co-backdrop {
|
|
position: absolute;
|
|
inset: 0;
|
|
background: #04040a;
|
|
}
|
|
#co-canvas {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 1920px;
|
|
height: 1080px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div
|
|
id="co-root"
|
|
data-composition-id="cosmic-orb"
|
|
data-root="true"
|
|
data-width="1920"
|
|
data-height="1080"
|
|
data-start="0"
|
|
data-duration="10"
|
|
data-composition-variables='[
|
|
{"id":"hue","type":"number","label":"Nebula hue","default":268,"min":0,"max":360,"step":1,"unit":"deg"},
|
|
{"id":"accent","type":"number","label":"Accent hue","default":196,"min":0,"max":360,"step":1,"unit":"deg"},
|
|
{"id":"spin","type":"number","label":"Spin rate","default":0.035,"min":0,"max":0.5,"step":0.005,"unit":"turns/s"},
|
|
{"id":"stars","type":"number","label":"Star density","default":1,"min":0,"max":2,"step":0.05},
|
|
{"id":"glow","type":"number","label":"Glow intensity","default":1,"min":0,"max":2,"step":0.05},
|
|
{"id":"size","type":"number","label":"Orb size","default":0.86,"min":0.2,"max":1.2,"step":0.01},
|
|
{"id":"pulse","type":"number","label":"Beat pulse","default":0,"min":0,"max":1,"step":0.01},
|
|
{"id":"pulseEnvelope","type":"string","label":"Beat pulse envelope (comma-separated 0-1 samples)","default":"","placeholder":"0,0.2,1,0.4,0"},
|
|
{"id":"backdrop","type":"color","label":"Backdrop","default":"#04040a"}
|
|
]'
|
|
>
|
|
<div id="co-backdrop"></div>
|
|
<canvas id="co-canvas" width="1920" height="1080"></canvas>
|
|
|
|
<!-- Driver clip: gives HyperFrames a timed element to own on track 0. -->
|
|
<div
|
|
id="co-drv"
|
|
class="clip"
|
|
data-start="0"
|
|
data-duration="10"
|
|
data-track-index="0"
|
|
style="position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none"
|
|
></div>
|
|
</div>
|
|
|
|
<script>
|
|
(function () {
|
|
var DUR = 10;
|
|
var W = 1920;
|
|
var H = 1080;
|
|
|
|
var ROOT = document.getElementById("co-root");
|
|
var CS = getComputedStyle(ROOT);
|
|
|
|
// `data-composition-variables` is the single owner of every declared
|
|
// default — parse it rather than repeating each default a second time
|
|
// in JS. `window.__hyperframes` is NOT guaranteed to exist yet when this
|
|
// inline script runs, so it can only ever be an override, never the
|
|
// source. Precedence: host CSS custom property, runtime variable bag,
|
|
// declared default.
|
|
var DECL = {};
|
|
JSON.parse(ROOT.getAttribute("data-composition-variables") || "[]").forEach(function (v) {
|
|
DECL[v.id] = v.default;
|
|
});
|
|
var HF = window.__hyperframes;
|
|
var V = (HF && HF.getVariables && HF.getVariables()) || {};
|
|
|
|
function raw(id) {
|
|
var css = CS.getPropertyValue("--" + id.toLowerCase()).trim();
|
|
if (css !== "") return css;
|
|
if (V[id] !== undefined && V[id] !== "") return V[id];
|
|
return DECL[id];
|
|
}
|
|
function num(id) {
|
|
var n = parseFloat(raw(id));
|
|
return isFinite(n) ? n : 0;
|
|
}
|
|
function str(id) {
|
|
var s = raw(id);
|
|
return typeof s === "string" ? s.trim() : "";
|
|
}
|
|
|
|
var HUE = num("hue");
|
|
var ACCENT = num("accent");
|
|
var SPIN = num("spin");
|
|
var STARS = num("stars");
|
|
var GLOW = num("glow");
|
|
var SIZE = num("size");
|
|
var PULSE = num("pulse");
|
|
var BACKDROP = str("backdrop");
|
|
|
|
document.getElementById("co-backdrop").style.background = BACKDROP;
|
|
|
|
// Beat hook. A precomputed envelope (comma-separated 0-1 samples spread
|
|
// evenly across the clip) is sampled closed-form at time t — no audio
|
|
// analysis, no accumulator. Empty envelope => the flat `pulse` value.
|
|
var ENV = str("pulseEnvelope")
|
|
.split(",")
|
|
.map(function (s) {
|
|
return parseFloat(s);
|
|
})
|
|
.filter(function (n) {
|
|
return isFinite(n);
|
|
});
|
|
|
|
function pulseAt(t) {
|
|
if (ENV.length === 0) return PULSE;
|
|
if (ENV.length === 1) return ENV[0];
|
|
var x = Math.min(1, Math.max(0, t / DUR)) * (ENV.length - 1);
|
|
var i = Math.floor(x);
|
|
var j = Math.min(i + 1, ENV.length - 1);
|
|
return ENV[i] + (ENV[j] - ENV[i]) * (x - i);
|
|
}
|
|
|
|
var canvas = document.getElementById("co-canvas");
|
|
var gl =
|
|
canvas.getContext("webgl", {
|
|
alpha: true,
|
|
antialias: false,
|
|
depth: false,
|
|
stencil: false,
|
|
preserveDrawingBuffer: true,
|
|
powerPreference: "high-performance",
|
|
}) ||
|
|
canvas.getContext("experimental-webgl", {
|
|
alpha: true,
|
|
preserveDrawingBuffer: true,
|
|
});
|
|
|
|
var VERT = [
|
|
"attribute vec2 aPos;",
|
|
"void main() { gl_Position = vec4(aPos, 0.0, 1.0); }",
|
|
].join("\n");
|
|
|
|
var FRAG = [
|
|
"precision highp float;",
|
|
"uniform vec2 uRes;",
|
|
"uniform float uTime;",
|
|
"uniform float uRadius;",
|
|
"uniform float uSpin;",
|
|
"uniform float uHue;",
|
|
"uniform float uAccent;",
|
|
"uniform float uStars;",
|
|
"uniform float uGlow;",
|
|
"uniform float uPulse;",
|
|
"const float TAU = 6.28318531;",
|
|
"",
|
|
"// One cheap hash for every random-looking thing in here.",
|
|
"float h1(float x) { return fract(sin(x * 127.1) * 43758.5453); }",
|
|
"float h2(vec2 p) { return h1(dot(p, vec2(1.0, 157.31))); }",
|
|
"",
|
|
"vec3 hue2rgb(float h) {",
|
|
" return clamp(abs(mod(h * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0);",
|
|
"}",
|
|
"",
|
|
"vec3 rotX(vec3 p, float a) { float c = cos(a), s = sin(a); return vec3(p.x, c * p.y - s * p.z, s * p.y + c * p.z); }",
|
|
"vec3 rotY(vec3 p, float a) { float c = cos(a), s = sin(a); return vec3(c * p.x + s * p.z, p.y, -s * p.x + c * p.z); }",
|
|
"vec3 rotZ(vec3 p, float a) { float c = cos(a), s = sin(a); return vec3(c * p.x - s * p.y, s * p.x + c * p.y, p.z); }",
|
|
"",
|
|
"vec3 safeDir(vec3 v) {",
|
|
" float l = dot(v, v);",
|
|
" if (l < 1e-6) return vec3(0.0, 0.0, -1.0);",
|
|
" return v * inversesqrt(l);",
|
|
"}",
|
|
"",
|
|
"// Fixed axial tilt, then spin about the tilted polar axis.",
|
|
"vec3 toBody(vec3 n, float spin) { return rotY(rotZ(rotX(n, -0.34), 0.42), -spin); }",
|
|
"vec2 lonlat(vec3 d) { return vec2(atan(d.x, d.z), asin(clamp(d.y, -1.0, 1.0))); }",
|
|
"",
|
|
"float dens(float base) { return clamp(1.0 - (1.0 - base) * uStars, 0.0, 1.0); }",
|
|
"",
|
|
"// One star size-class: cell grid, seeded jitter, soft round core.",
|
|
"float starField(vec2 q, float freq, float base, float rad) {",
|
|
" vec2 g = q * freq;",
|
|
" vec2 id = floor(g);",
|
|
" float h = h2(id);",
|
|
" if (h < dens(base)) return 0.0;",
|
|
" vec2 c = vec2(h2(id + vec2(3.7, 1.3)), h2(id + vec2(9.1, 5.5)));",
|
|
" float d = length(fract(g) - c);",
|
|
" float core = smoothstep(rad, 0.0, d);",
|
|
" // Twinkle: a per-star phase driven straight off uTime, no accumulator.",
|
|
" float tw = 0.62 + 0.38 * sin(uTime * 2.4 + h * 53.7);",
|
|
" return core * core * (0.45 + 0.55 * h1(h * 91.7)) * tw;",
|
|
"}",
|
|
"",
|
|
"// 4-point diffraction glare, only worth paying for on the brightest class.",
|
|
"float starGlare(vec2 q, float freq, float base) {",
|
|
" vec2 g = q * freq;",
|
|
" vec2 id = floor(g);",
|
|
" float h = h2(id);",
|
|
" if (h < dens(base)) return 0.0;",
|
|
" vec2 c = vec2(h2(id + vec2(3.7, 1.3)), h2(id + vec2(9.1, 5.5)));",
|
|
" vec2 dv = abs(fract(g) - c);",
|
|
" float hx = exp(-dv.x * 70.0) * exp(-dv.y * 9.0);",
|
|
" float hy = exp(-dv.y * 70.0) * exp(-dv.x * 9.0);",
|
|
" return max(hx, hy) * 0.45;",
|
|
"}",
|
|
"",
|
|
"// Three size-classes at ~6 / 11 / 19 cell frequency.",
|
|
"float starsAll(vec2 q, float gain) {",
|
|
" float s = starField(q, 6.0, 0.958, 0.20);",
|
|
" s += starGlare(q, 6.0, 0.958);",
|
|
" s += starField(q, 11.0, 0.920, 0.18) * 0.95;",
|
|
" s += starField(q, 19.0, 0.865, 0.17) * 0.75;",
|
|
" return s * gain;",
|
|
"}",
|
|
"",
|
|
"vec3 nebula(vec2 q) {",
|
|
" // Two sin-field turbulence layers.",
|
|
" float t1 = sin(q.x * 3.1 + sin(q.y * 2.7) * 1.9) * sin(q.y * 4.3 + sin(q.x * 1.7) * 2.3);",
|
|
" float t2 = sin(q.x * 7.9 + sin(q.y * 5.1) * 1.3) * sin(q.y * 9.7 + sin(q.x * 4.4) * 1.1);",
|
|
" float turb = t1 * 0.66 + t2 * 0.34;",
|
|
"",
|
|
" // Galaxy bands, warped by the turbulence.",
|
|
" float band = sin(q.y * 3.2 + turb * 1.7 + q.x * 0.55);",
|
|
" float bands = pow(max(0.0, band * 0.5 + 0.5), 3.2);",
|
|
"",
|
|
" // Dust lanes: broad darkened swathes, not a thin curve.",
|
|
" float lane = sin(q.y * 2.9 + q.x * 1.3 + t1 * 1.6);",
|
|
" float dust = 0.22 + 0.78 * smoothstep(-0.45, 0.75, lane);",
|
|
"",
|
|
" // Core bulge.",
|
|
" float dc = length(vec2((q.x - 0.55) * 0.5, (q.y + 0.12) * 1.1));",
|
|
" float core = exp(-dc * dc * 2.6);",
|
|
"",
|
|
" // Two pocket glows.",
|
|
" float dA = length(vec2((q.x + 1.95) * 0.62, (q.y - 0.52) * 1.45));",
|
|
" float dB = length(vec2((q.x - 2.35) * 0.55, (q.y + 0.78) * 1.30));",
|
|
" float pA = exp(-dA * dA * 4.0);",
|
|
" float pB = exp(-dB * dB * 5.0);",
|
|
"",
|
|
" vec3 cNeb = hue2rgb(uHue / 360.0);",
|
|
" vec3 cAcc = hue2rgb(uAccent / 360.0);",
|
|
" vec3 cWarm = hue2rgb(uHue / 360.0 + 0.07);",
|
|
"",
|
|
" vec3 col = mix(cNeb, cWarm, 0.5 + 0.5 * turb) * bands * dust * 0.17;",
|
|
" col += mix(cNeb, vec3(1.0), 0.50) * core * dust * 0.13;",
|
|
" col += mix(cAcc, vec3(1.0), 0.60) * pA * 0.30 * uGlow;",
|
|
" col += mix(cNeb, vec3(1.0), 0.50) * pB * 0.24 * uGlow;",
|
|
" return col;",
|
|
"}",
|
|
"",
|
|
"void main() {",
|
|
" vec2 p = (gl_FragCoord.xy - uRes * 0.5) / uRadius;",
|
|
" float r = length(p);",
|
|
" float aa = 1.4 / uRadius;",
|
|
" float spin = TAU * uSpin * uTime;",
|
|
"",
|
|
" vec3 cNeb = hue2rgb(uHue / 360.0);",
|
|
" vec3 cAcc = hue2rgb(uAccent / 360.0);",
|
|
"",
|
|
" float mask = smoothstep(1.0 + aa, 1.0 - aa, r);",
|
|
" float halo = exp(-max(0.0, r - 1.0) * 13.0) * (1.0 - mask);",
|
|
"",
|
|
" vec3 body = vec3(0.0);",
|
|
" if (mask > 0.001) {",
|
|
" float rc = min(r, 1.0);",
|
|
" // Analytic sphere: the normal falls straight out of z = sqrt(1 - r^2).",
|
|
" float z = sqrt(max(0.0, 1.0 - rc * rc));",
|
|
" vec3 N = vec3(p.x, p.y, z);",
|
|
" vec2 q = lonlat(toBody(safeDir(N), spin));",
|
|
"",
|
|
" float gain = 0.9 + 2.6 * uPulse;",
|
|
" body = nebula(q);",
|
|
" body += vec3(1.0) * starsAll(q, gain) * (0.95 + 0.15 * uGlow);",
|
|
"",
|
|
" // Limb darkening.",
|
|
" body *= mix(0.32, 1.0, pow(z, 0.5));",
|
|
"",
|
|
" // Refract-sampled back layer, split per channel: the chromatic",
|
|
" // aberration that reads as glass at the limb.",
|
|
" vec3 I = vec3(0.0, 0.0, -1.0);",
|
|
" vec3 dR = safeDir(refract(I, N, 1.0 / 1.09));",
|
|
" vec3 dG = safeDir(refract(I, N, 1.0 / 1.13));",
|
|
" vec3 dB = safeDir(refract(I, N, 1.0 / 1.17));",
|
|
" vec2 qR = lonlat(toBody(dR, spin));",
|
|
" vec2 qG = lonlat(toBody(dG, spin));",
|
|
" vec2 qB = lonlat(toBody(dB, spin));",
|
|
" vec3 back = vec3(starsAll(qR, gain), starsAll(qG, gain), starsAll(qB, gain));",
|
|
" float fres = pow(1.0 - z, 1.8);",
|
|
" body += back * fres * 2.2;",
|
|
" body += nebula(qG) * fres * 0.8;",
|
|
"",
|
|
" // Aurora sheen riding the glass shell.",
|
|
" float ang = atan(p.y, p.x);",
|
|
" float sheen = smoothstep(0.72, 0.99, r) * smoothstep(1.0, 0.90, r);",
|
|
" float w = 0.5 + 0.5 * sin(ang * 2.0 + spin * 1.6);",
|
|
" body += (mix(cNeb, cAcc, w) * 0.55 + vec3(0.16)) * sheen * 0.30 * uGlow;",
|
|
"",
|
|
" // Three speculars, fixed in screen space: the key light does not spin.",
|
|
" vec2 a1 = p - vec2(-0.34, 0.62);",
|
|
" vec2 a2 = p - vec2(0.58, 0.60);",
|
|
" vec2 a3 = p - vec2(0.24, -0.80);",
|
|
" float sp = exp(-dot(a1, a1) * 30.0) * 0.30;",
|
|
" sp += exp(-dot(a2, a2) * 150.0) * 0.80;",
|
|
" sp += exp(-dot(a3, a3) * 70.0) * 0.30;",
|
|
" body += vec3(1.0) * sp * (0.30 + 0.70 * pow(1.0 - z, 1.5)) * uGlow;",
|
|
"",
|
|
" // Fresnel void glow hugging the inside of the rim.",
|
|
" body += mix(cAcc, vec3(1.0), 0.4) * pow(1.0 - z, 6.0) * 0.30 * uGlow;",
|
|
" }",
|
|
"",
|
|
" vec3 haloCol = mix(cNeb, cAcc, 0.35);",
|
|
" vec3 outCol = body * mask + haloCol * halo * 0.22 * uGlow;",
|
|
" float outA = clamp(mask + halo * 0.16 * uGlow, 0.0, 1.0);",
|
|
" gl_FragColor = vec4(clamp(outCol, 0.0, 4.0), outA);",
|
|
"}",
|
|
].join("\n");
|
|
|
|
var uni = {};
|
|
var ready = false;
|
|
|
|
function compile(type, src) {
|
|
var sh = gl.createShader(type);
|
|
gl.shaderSource(sh, src);
|
|
gl.compileShader(sh);
|
|
if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
|
|
throw new Error("cosmic-orb shader: " + gl.getShaderInfoLog(sh));
|
|
}
|
|
return sh;
|
|
}
|
|
|
|
if (gl) {
|
|
var prog = gl.createProgram();
|
|
gl.attachShader(prog, compile(gl.VERTEX_SHADER, VERT));
|
|
gl.attachShader(prog, compile(gl.FRAGMENT_SHADER, FRAG));
|
|
gl.linkProgram(prog);
|
|
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
|
|
throw new Error("cosmic-orb link: " + gl.getProgramInfoLog(prog));
|
|
}
|
|
gl.useProgram(prog);
|
|
|
|
var buf = gl.createBuffer();
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
|
|
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
|
|
var loc = gl.getAttribLocation(prog, "aPos");
|
|
gl.enableVertexAttribArray(loc);
|
|
gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
|
|
|
|
[
|
|
"uRes",
|
|
"uTime",
|
|
"uRadius",
|
|
"uSpin",
|
|
"uHue",
|
|
"uAccent",
|
|
"uStars",
|
|
"uGlow",
|
|
"uPulse",
|
|
].forEach(function (n) {
|
|
uni[n] = gl.getUniformLocation(prog, n);
|
|
});
|
|
|
|
gl.viewport(0, 0, W, H);
|
|
gl.uniform2f(uni.uRes, W, H);
|
|
gl.uniform1f(uni.uRadius, (SIZE * H) / 2);
|
|
gl.uniform1f(uni.uSpin, SPIN);
|
|
gl.uniform1f(uni.uHue, HUE);
|
|
gl.uniform1f(uni.uAccent, ACCENT);
|
|
gl.uniform1f(uni.uStars, STARS);
|
|
gl.uniform1f(uni.uGlow, GLOW);
|
|
ready = true;
|
|
}
|
|
|
|
// Every frame is computed from t alone: spin is spin_rate * t, the
|
|
// pulse is sampled from the envelope at t. No accumulators, no clocks.
|
|
function draw(t) {
|
|
if (!ready) return;
|
|
gl.uniform1f(uni.uTime, t);
|
|
gl.uniform1f(uni.uPulse, pulseAt(t));
|
|
gl.clearColor(0, 0, 0, 0);
|
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
gl.flush();
|
|
}
|
|
|
|
window.__timelines = window.__timelines || {};
|
|
var tl = gsap.timeline({ paused: true });
|
|
|
|
// The canvas is repainted from a property SETTER, not from onUpdate:
|
|
// gsap's seek(t) suppresses events by default, so an onUpdate callback
|
|
// silently never fires on a scrub and the canvas freezes on frame 0.
|
|
// Tweened values are always written during render, suppressed or not,
|
|
// so this fires on every seek — and hands us the frame time directly.
|
|
var driver = { _t: 0 };
|
|
Object.defineProperty(driver, "t", {
|
|
get: function () {
|
|
return this._t;
|
|
},
|
|
set: function (v) {
|
|
this._t = v;
|
|
draw(v);
|
|
},
|
|
});
|
|
tl.to(driver, { t: DUR, duration: DUR, ease: "none" }, 0);
|
|
window.__timelines["cosmic-orb"] = tl;
|
|
|
|
draw(0);
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|