chore: import upstream snapshot with attribution
regression / regression-shards (style-16-prod style-9-prod style-17-prod iframe-render-compat variables-prod mp4-h265-sdr, shard-4) (push) Has been cancelled
regression / regression-shards (style-4-prod style-11-prod style-2-prod animejs-adapter typegpu-adapter parallel-capture-regression, shard-5) (push) Has been cancelled
regression / regression-shards (style-7-prod style-8-prod style-10-prod css-spinner-render-compat webm-transparency mp4-h264-sdr webm-vp9, shard-3) (push) Has been cancelled
regression / regression-shards (sub-composition-video style-18-prod raf-ball-render-compat font-variant-numeric sub-comp-t0 sub-comp-id-selector, shard-7) (push) Has been cancelled
Windows render verification / Detect changes (push) Has been cancelled
Windows render verification / Preflight (lint + format) (push) Has been cancelled
Windows render verification / Render on windows-latest (push) Has been cancelled
Windows render verification / Tests on windows-latest (push) Has been cancelled
CI / Detect changes (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Fallow audit (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Producer: integration tests (push) Has been cancelled
CI / Producer: unit tests (push) Has been cancelled
CI / File size check (push) Has been cancelled
CI / Test: skills (push) Has been cancelled
CI / Skills: manifest in sync (push) Has been cancelled
CI / CLI: npx shim (macos-latest) (push) Has been cancelled
CI / CLI: npx shim (ubuntu-latest) (push) Has been cancelled
CI / CLI: npx shim (windows-latest) (push) Has been cancelled
CI / SDK: unit + contract + smoke (push) Has been cancelled
CI / Test: runtime contract (push) Has been cancelled
CI / Studio: load smoke (push) Has been cancelled
CI / Smoke: global install (push) Has been cancelled
CI / CLI smoke (required) (push) Has been cancelled
CI / Semantic PR title (push) Has been cancelled
Player perf / Detect changes (push) Has been cancelled
Player perf / Preflight (lint + format) (push) Has been cancelled
Player perf / player-perf (push) Has been cancelled
Player perf / Perf: drift (push) Has been cancelled
Player perf / Perf: fps (push) Has been cancelled
Player perf / Perf: parity (push) Has been cancelled
Player perf / Perf: scrub (push) Has been cancelled
Player perf / Perf: load (push) Has been cancelled
preview-regression / Detect changes (push) Has been cancelled
preview-regression / Preflight (lint + format) (push) Has been cancelled
preview-regression / Preview parity (push) Has been cancelled
preview-regression / preview-regression (push) Has been cancelled
regression / regression (push) Has been cancelled
regression / Detect changes (push) Has been cancelled
regression / Preflight (lint + format) (push) Has been cancelled
regression / regression-shards (hdr-regression style-5-prod style-3-prod mov-prores, shard-1) (push) Has been cancelled
regression / regression-shards (overlay-montage-prod style-12-prod chat missing-host-comp-id png-sequence portrait-edge-bleed, shard-6) (push) Has been cancelled
regression / regression-shards (style-13-prod style-6-prod vignelli-stacking gsap-letters-render-compat audio-mux-parity, shard-8) (push) Has been cancelled
regression / regression-shards (style-15-prod hdr-hlg-regression style-1-prod many-cuts vfr-screen-recording render-symlinked-assets, shard-2) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Docs / Validate docs (push) Has been cancelled
Sync skills to ClawHub / Publish changed skills (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:58:35 +08:00
commit 85453da49f
4031 changed files with 710987 additions and 0 deletions
@@ -0,0 +1,39 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "vfx-portal",
"type": "hyperframes:block",
"title": "Portal",
"description": "VFX composition block",
"stability": "experimental",
"dimensions": {
"width": 1920,
"height": 1080
},
"duration": 10,
"tags": ["html-in-canvas", "webgl"],
"files": [
{
"path": "vfx-portal.html",
"target": "compositions/vfx-portal.html",
"type": "hyperframes:composition"
}
],
"preview": {
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/vfx-portal.mp4",
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/vfx-portal.png"
},
"params": [
{
"key": "--bg-color",
"label": "Background",
"type": "color",
"default": "#000000"
},
{
"key": "--portal-color",
"label": "Portal color",
"type": "color",
"default": "#fafafa"
}
]
}
+863
View File
@@ -0,0 +1,863 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>HTML Portal Transition</title>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.147.0/build/three.min.js"></script>
<style>
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #000;
overflow: hidden;
font-family: "Inter", system-ui, sans-serif;
}
#root {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: #000;
}
.panel {
position: absolute;
top: 0;
left: 0;
width: 1920px;
height: 1080px;
overflow: hidden;
}
/* ── Shared Site Layout ──────────────────────────────── */
.site-wrap {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
}
.topbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 28px 64px;
}
.logo {
font-size: 24px;
font-weight: 800;
letter-spacing: -0.5px;
}
.nav {
display: flex;
gap: 36px;
}
.nav span {
font-size: 15px;
font-weight: 500;
}
.nav-cta {
font-size: 14px;
font-weight: 600;
padding: 10px 24px;
border-radius: 8px;
border: none;
cursor: pointer;
}
.hero {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
gap: 24px;
padding: 0 200px;
}
.hero-title {
font-size: 72px;
font-weight: 800;
letter-spacing: -2px;
line-height: 1.05;
}
.hero-sub {
font-size: 20px;
line-height: 1.6;
max-width: 640px;
}
.hero-btn {
display: inline-block;
padding: 16px 40px;
border-radius: 12px;
font-size: 17px;
font-weight: 700;
border: none;
cursor: pointer;
letter-spacing: -0.2px;
}
.cards-row {
display: flex;
gap: 24px;
padding: 0 64px 56px;
}
.feature-card {
flex: 1;
padding: 32px;
border-radius: 16px;
}
.card-icon {
width: 44px;
height: 44px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 22px;
margin-bottom: 16px;
}
.card-title {
font-size: 18px;
font-weight: 700;
margin-bottom: 6px;
}
.card-desc {
font-size: 14px;
line-height: 1.5;
}
/* ── Light Mode ──────────────────────────────────────── */
.light .site-wrap {
background: #fafafa;
color: #111;
}
.light .logo {
color: #111;
}
.light .nav span {
color: #555;
}
.light .nav-cta {
background: #7c3aed;
color: #fff;
}
.light .hero-title {
color: #111;
}
.light .hero-sub {
color: #666;
}
.light .hero-btn {
background: #7c3aed;
color: #fff;
}
.light .feature-card {
background: #fff;
border: 1px solid #e5e7eb;
}
.light .card-icon {
background: #f3f0ff;
color: #7c3aed;
}
.light .card-title {
color: #111;
}
.light .card-desc {
color: #777;
}
/* ── Dark Mode ───────────────────────────────────────── */
.dark .site-wrap {
background: #0a0a0f;
color: #f0f0f5;
}
.dark .logo {
background: linear-gradient(135deg, #a78bfa, #c084fc);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.dark .nav span {
color: rgba(255, 255, 255, 0.5);
}
.dark .nav-cta {
background: linear-gradient(135deg, #7c3aed, #a855f7);
color: #fff;
box-shadow: 0 0 24px rgba(124, 58, 237, 0.3);
}
.dark .hero-title {
color: #f0f0f5;
}
.dark .hero-sub {
color: rgba(255, 255, 255, 0.5);
}
.dark .hero-btn {
background: linear-gradient(135deg, #7c3aed, #a855f7);
color: #fff;
box-shadow: 0 0 32px rgba(124, 58, 237, 0.35);
}
.dark .feature-card {
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.08);
box-shadow: 0 0 20px rgba(124, 58, 237, 0.06);
}
.dark .card-icon {
background: rgba(124, 58, 237, 0.15);
color: #a78bfa;
}
.dark .card-title {
color: #f0f0f5;
}
.dark .card-desc {
color: rgba(255, 255, 255, 0.45);
}
#error-overlay {
position: absolute;
inset: 0;
background: #000;
display: none;
align-items: center;
justify-content: center;
z-index: 100;
flex-direction: column;
gap: 16px;
color: #fff;
}
#error-overlay.visible {
display: flex;
}
#error-overlay h2 {
font-size: 24px;
font-weight: 600;
}
#error-overlay p {
font-size: 14px;
color: rgba(255, 255, 255, 0.5);
max-width: 480px;
text-align: center;
line-height: 1.6;
}
#error-overlay code {
background: rgba(255, 255, 255, 0.1);
padding: 2px 8px;
border-radius: 4px;
font-size: 13px;
}
</style>
</head>
<body>
<div
id="root"
data-composition-id="portal-transition"
data-width="1920"
data-height="1080"
data-start="0"
data-duration="10"
data-root="true"
>
<!-- Light mode panel (front) -->
<canvas
id="cap-light"
layoutsubtree
width="1920"
height="1080"
style="
position: absolute;
top: 0;
left: 0;
width: 1920px;
height: 1080px;
z-index: -1;
pointer-events: none;
"
>
<section
class="panel light"
id="panel-light"
data-panel-start="0"
data-panel-duration="10"
style="width: 1920px; height: 1080px"
>
<div class="site-wrap">
<div class="topbar">
<div class="logo">HyperFrames</div>
<div class="nav">
<span>Docs</span>
<span>Catalog</span>
<span>GitHub</span>
<button class="nav-cta">Sign In</button>
</div>
</div>
<div class="hero">
<h1 class="hero-title">Write HTML<br />Render Video</h1>
<p class="hero-sub">
Ship faster with a platform that handles the hard parts. Analytics, infrastructure,
and developer tools — all in one place.
</p>
<button class="hero-btn">Start Creating</button>
</div>
<div class="cards-row">
<div class="feature-card">
<div class="card-icon">&#9889;</div>
<div class="card-title">Lightning Fast</div>
<div class="card-desc">
Sub-millisecond response times with edge deployment across 40+ regions worldwide.
</div>
</div>
<div class="feature-card">
<div class="card-icon">&#128274;</div>
<div class="card-title">Secure by Default</div>
<div class="card-desc">
SOC2 certified. End-to-end encryption. Role-based access out of the box.
</div>
</div>
<div class="feature-card">
<div class="card-icon">&#128268;</div>
<div class="card-title">Plug and Play</div>
<div class="card-desc">
200+ integrations. Connect your existing stack in minutes, not weeks.
</div>
</div>
<div class="feature-card">
<div class="card-icon">&#128200;</div>
<div class="card-title">Real-time Insights</div>
<div class="card-desc">
Live dashboards, anomaly detection, and AI-powered recommendations.
</div>
</div>
</div>
</div>
</section>
</canvas>
<!-- Dark mode panel (back) -->
<canvas
id="cap-dark"
layoutsubtree
width="1920"
height="1080"
style="
position: absolute;
top: 0;
left: 0;
width: 1920px;
height: 1080px;
z-index: -1;
pointer-events: none;
"
>
<section
class="panel dark"
id="panel-dark"
data-panel-start="0"
data-panel-duration="10"
style="width: 1920px; height: 1080px"
>
<div class="site-wrap">
<div class="topbar">
<div class="logo">HyperFrames</div>
<div class="nav">
<span>Docs</span>
<span>Catalog</span>
<span>GitHub</span>
<button class="nav-cta">Sign In</button>
</div>
</div>
<div class="hero">
<h1 class="hero-title">Write HTML<br />Render Video</h1>
<p class="hero-sub">
Ship faster with a platform that handles the hard parts. Analytics, infrastructure,
and developer tools — all in one place.
</p>
<button class="hero-btn">Start Creating</button>
</div>
<div class="cards-row">
<div class="feature-card">
<div class="card-icon">&#9889;</div>
<div class="card-title">Lightning Fast</div>
<div class="card-desc">
Sub-millisecond response times with edge deployment across 40+ regions worldwide.
</div>
</div>
<div class="feature-card">
<div class="card-icon">&#128274;</div>
<div class="card-title">Secure by Default</div>
<div class="card-desc">
SOC2 certified. End-to-end encryption. Role-based access out of the box.
</div>
</div>
<div class="feature-card">
<div class="card-icon">&#128268;</div>
<div class="card-title">Plug and Play</div>
<div class="card-desc">
200+ integrations. Connect your existing stack in minutes, not weeks.
</div>
</div>
<div class="feature-card">
<div class="card-icon">&#128200;</div>
<div class="card-title">Real-time Insights</div>
<div class="card-desc">
Live dashboards, anomaly detection, and AI-powered recommendations.
</div>
</div>
</div>
</div>
</section>
</canvas>
<!-- Three.js render target -->
<canvas
id="theater"
width="1920"
height="1080"
style="position: absolute; top: 0; left: 0; width: 1920px; height: 1080px"
></canvas>
<div id="error-overlay">
<h2>HTML-in-Canvas Required</h2>
<p>
This composition requires the experimental CanvasDrawElement API. Enable it in Chrome or
Brave:
</p>
<p><code>chrome://flags/#canvas-draw-element</code></p>
</div>
<!-- Driver clip for HyperFrames timeline sync -->
<div
id="driver"
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>
var theaterCanvas = document.getElementById("theater");
var W = 1920,
H = 1080;
// ── Feature Detection ──────────────────────────────────────────────
function isSupported() {
if (!("layoutSubtree" in document.createElement("canvas"))) return false;
var tc = document.createElement("canvas");
tc.setAttribute("layoutsubtree", "");
var ctx = tc.getContext("2d");
return ctx && typeof ctx.drawElementImage === "function";
}
if (!isSupported()) {
document.getElementById("error-overlay").classList.add("visible");
}
// ── Seeded PRNG (mulberry32) ───────────────────────────────────────
function mulberry32(seed) {
return function () {
seed |= 0;
seed = (seed + 0x6d2b79f5) | 0;
var t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
var rng = mulberry32(42);
// ── Canvas Capture ─────────────────────────────────────────────────
var capLight = document.getElementById("cap-light");
var capDark = document.getElementById("cap-dark");
var ctxLight = capLight.getContext("2d");
var ctxDark = capDark.getContext("2d");
var panelLight = document.getElementById("panel-light");
var panelDark = document.getElementById("panel-dark");
function capturePanels() {
ctxLight.clearRect(0, 0, W, H);
ctxLight.drawElementImage(panelLight, 0, 0, W, H);
ctxDark.clearRect(0, 0, W, H);
ctxDark.drawElementImage(panelDark, 0, 0, W, H);
lightTexture.needsUpdate = true;
darkTexture.needsUpdate = true;
}
// ── Three.js Scene ─────────────────────────────────────────────────
var renderer = new THREE.WebGLRenderer({
canvas: theaterCanvas,
antialias: false,
alpha: false,
powerPreference: "high-performance",
preserveDrawingBuffer: true,
});
renderer.setSize(W, H, false);
renderer.setPixelRatio(1);
renderer.setClearColor(0x000000, 1);
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(50, W / H, 0.1, 100);
camera.position.set(0, 0, 2.8);
camera.lookAt(0, 0, 0);
// Calculate quad dimensions to fill the camera frustum at z=0
var vFov = (camera.fov * Math.PI) / 180;
var quadH = 2 * Math.tan(vFov / 2) * camera.position.z;
var quadW = quadH * (W / H);
// ── Textures from Captured Canvases ────────────────────────────────
var lightTexture = new THREE.CanvasTexture(capLight);
lightTexture.minFilter = THREE.LinearFilter;
lightTexture.magFilter = THREE.LinearFilter;
lightTexture.generateMipmaps = false;
var darkTexture = new THREE.CanvasTexture(capDark);
darkTexture.minFilter = THREE.LinearFilter;
darkTexture.magFilter = THREE.LinearFilter;
darkTexture.generateMipmaps = false;
// ── Back Quad: Dark Mode (sits behind) ─────────────────────────────
var darkGeo = new THREE.PlaneGeometry(quadW, quadH);
var darkMat = new THREE.MeshBasicMaterial({ map: darkTexture, side: THREE.FrontSide });
var darkQuad = new THREE.Mesh(darkGeo, darkMat);
darkQuad.position.z = -0.01;
scene.add(darkQuad);
// ── Front Quad: Light Mode with Portal Shader ──────────────────────
var portalUniforms = {
tLight: { value: lightTexture },
portalRadius: { value: 0.0 },
portalCenter: { value: new THREE.Vector2(0.5, 0.5) },
edgeWidth: { value: 0.06 },
glowColor: { value: new THREE.Vector3(0.486, 0.227, 0.929) },
glowColor2: { value: new THREE.Vector3(0.29, 0.565, 1.0) },
chromaticStrength: { value: 0.025 },
distortionStrength: { value: 0.04 },
time: { value: 0.0 },
energyIntensity: { value: 1.0 },
};
var portalVertShader = [
"varying vec2 vUv;",
"void main() {",
" vUv = uv;",
" gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);",
"}",
].join("\n");
var portalFragShader = [
"uniform sampler2D tLight;",
"uniform float portalRadius;",
"uniform vec2 portalCenter;",
"uniform float edgeWidth;",
"uniform vec3 glowColor;",
"uniform vec3 glowColor2;",
"uniform float chromaticStrength;",
"uniform float distortionStrength;",
"uniform float time;",
"uniform float energyIntensity;",
"varying vec2 vUv;",
"",
"float hash(vec2 p) {",
" return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);",
"}",
"",
"float noise(vec2 p) {",
" vec2 i = floor(p);",
" vec2 f = fract(p);",
" f = f * f * (3.0 - 2.0 * f);",
" float a = hash(i);",
" float b = hash(i + vec2(1.0, 0.0));",
" float c = hash(i + vec2(0.0, 1.0));",
" float d = hash(i + vec2(1.0, 1.0));",
" return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);",
"}",
"",
"void main() {",
" vec2 uv = vUv;",
" vec2 aspect = vec2(" + (W / H).toFixed(4) + ", 1.0);",
" vec2 centered = (uv - portalCenter) * aspect;",
" float dist = length(centered);",
"",
" // Noise-modulated edge for organic tear look",
" float angle = atan(centered.y, centered.x);",
" float edgeNoise = noise(vec2(angle * 3.0, time * 2.0)) * 0.03 * energyIntensity;",
" float portalEdge = portalRadius + edgeNoise;",
"",
" // Distortion near portal edge: warp UVs radially",
" float edgeDist = abs(dist - portalEdge);",
" float distortMask = smoothstep(edgeWidth * 1.5, 0.0, edgeDist) * distortionStrength;",
" vec2 distortDir = normalize(centered + 0.0001);",
" vec2 distortedUv = uv + distortDir * distortMask * sin(angle * 8.0 + time * 5.0);",
"",
" // Chromatic aberration near portal edge",
" float chromaMask = smoothstep(edgeWidth * 2.0, 0.0, edgeDist) * chromaticStrength;",
" vec2 chromaOffset = distortDir * chromaMask;",
" float r = texture2D(tLight, distortedUv + chromaOffset).r;",
" float g = texture2D(tLight, distortedUv).g;",
" float b = texture2D(tLight, distortedUv - chromaOffset).b;",
" vec3 lightColor = vec3(r, g, b);",
"",
" // Portal mask: inside = transparent (show dark behind), outside = light",
" float mask = smoothstep(portalEdge - 0.008, portalEdge + 0.008, dist);",
"",
" // Glow ring at portal edge",
" float glowInner = smoothstep(edgeWidth, 0.0, edgeDist);",
" float glowOuter = smoothstep(edgeWidth * 2.5, edgeWidth * 0.5, edgeDist);",
" float glow = glowInner * glowOuter * energyIntensity;",
"",
" // Animated color cycling along the ring",
" float colorPhase = fract(angle / 6.2832 + time * 0.4);",
" vec3 ringColor = mix(glowColor, glowColor2, sin(colorPhase * 6.2832) * 0.5 + 0.5);",
"",
" // Secondary energy sparks",
" float sparks = noise(vec2(angle * 12.0, time * 8.0));",
" sparks = pow(sparks, 4.0) * glowInner * energyIntensity * 2.0;",
"",
" vec3 finalColor = lightColor * mask;",
" finalColor += ringColor * glow * 2.5;",
" finalColor += vec3(1.0, 0.9, 1.0) * sparks;",
"",
" // When portal is large enough, alpha fades out completely",
" float alpha = mask + glow * 0.5 + sparks * 0.3;",
" alpha = clamp(alpha, 0.0, 1.0);",
"",
" gl_FragColor = vec4(finalColor, alpha);",
"}",
].join("\n");
var frontGeo = new THREE.PlaneGeometry(quadW, quadH);
var frontMat = new THREE.ShaderMaterial({
uniforms: portalUniforms,
vertexShader: portalVertShader,
fragmentShader: portalFragShader,
transparent: true,
side: THREE.FrontSide,
depthWrite: false,
});
var frontQuad = new THREE.Mesh(frontGeo, frontMat);
frontQuad.position.z = 0.0;
scene.add(frontQuad);
// ── Animation State ────────────────────────────────────────────────
var animState = {
portalRadius: 0.0,
cameraZ: 2.8,
energyIntensity: 0.0,
chromaticStrength: 0.0,
distortionStrength: 0.0,
edgeWidth: 0.06,
time: 0.0,
};
// ── GSAP Timeline ──────────────────────────────────────────────────
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
// Phase 1: Hold on light mode (0 - 1.5s)
// Subtle energy buildup at center before portal opens
tl.to(
animState,
{
energyIntensity: 0.3,
duration: 1.2,
ease: "power2.in",
},
0.3,
);
// Phase 2: Portal tears open (1.5 - 5.0s)
tl.to(
animState,
{
portalRadius: 0.85,
duration: 3.5,
ease: "power2.inOut",
},
1.5,
);
tl.to(
animState,
{
energyIntensity: 1.0,
duration: 0.6,
ease: "power3.out",
},
1.5,
);
tl.to(
animState,
{
chromaticStrength: 0.035,
duration: 2.0,
ease: "power2.in",
},
1.5,
);
tl.to(
animState,
{
distortionStrength: 0.06,
duration: 2.5,
ease: "power2.inOut",
},
1.5,
);
tl.to(
animState,
{
edgeWidth: 0.08,
duration: 2.0,
ease: "sine.inOut",
},
2.0,
);
// Phase 3: Camera pushes through portal (3.5 - 6.5s)
tl.to(
animState,
{
cameraZ: 1.4,
duration: 3.0,
ease: "power2.inOut",
},
3.5,
);
// Phase 4: Portal overshoots to cover full screen (5.0 - 7.0s)
tl.to(
animState,
{
portalRadius: 1.8,
duration: 2.0,
ease: "power3.in",
},
5.0,
);
// Energy peaks then fades as we emerge
tl.to(
animState,
{
energyIntensity: 1.5,
duration: 0.8,
ease: "power2.in",
},
5.0,
);
tl.to(
animState,
{
energyIntensity: 0.0,
duration: 1.2,
ease: "power2.out",
},
6.2,
);
// Chromatic aberration peaks during push-through
tl.to(
animState,
{
chromaticStrength: 0.06,
duration: 1.0,
ease: "power3.in",
},
5.5,
);
tl.to(
animState,
{
chromaticStrength: 0.0,
duration: 1.0,
ease: "power2.out",
},
6.8,
);
// Distortion fades
tl.to(
animState,
{
distortionStrength: 0.0,
duration: 1.0,
ease: "power2.out",
},
6.5,
);
// Phase 5: Settle in dark mode (7.0 - 10s)
// Camera eases to final position
tl.to(
animState,
{
cameraZ: 2.8,
duration: 2.5,
ease: "power2.out",
},
7.0,
);
// ── Render Loop via Timeline ───────────────────────────────────────
tl.to(
{ v: 0 },
{
v: 1,
duration: 10,
ease: "none",
onUpdate: function () {
var t = tl.time();
animState.time = t;
// Update portal shader uniforms
portalUniforms.portalRadius.value = animState.portalRadius;
portalUniforms.edgeWidth.value = animState.edgeWidth;
portalUniforms.chromaticStrength.value = animState.chromaticStrength;
portalUniforms.distortionStrength.value = animState.distortionStrength;
portalUniforms.time.value = t;
portalUniforms.energyIntensity.value = animState.energyIntensity;
// Update camera Z for push-through effect
camera.position.z = animState.cameraZ;
// Recalculate quad scale to always fill the view at current camera Z
var currentVFov = (camera.fov * Math.PI) / 180;
var currentH = 2 * Math.tan(currentVFov / 2) * camera.position.z;
var currentW = currentH * (W / H);
frontQuad.scale.set(currentW / quadW, currentH / quadH, 1);
darkQuad.scale.set(currentW / quadW, currentH / quadH, 1);
renderer.render(scene, camera);
},
},
0,
);
window.__timelines["portal-transition"] = tl;
// ── Deferred Init ──────────────────────────────────────────────────
setTimeout(function () {
if (isSupported()) {
capturePanels();
}
tl.seek(0);
}, 0);
</script>
</body>
</html>