fix(registry): derive airbnb-deck slide entrances from composition time

Scene entrances and problem fragments fired wall-clock anime.animate() calls
from the visibility controller (mirroring the GSAP original's gsap.from()).
Autoplaying animations run on anime's own rAF engine, which does not advance
under the seek-driven render pipeline, so slide content rendered stuck at
opacity 0 or mid-fade for the entire scene: the ADOPTION STRATEGY slide was
completely empty at t=63.7s and the MARKET SIZE footnote missing at t=41s,
while the GSAP baseline only survived because GSAP's ticker happened to keep
ticking between captures.

Compute entrance and fragment progress as a pure function of composition
time inside updateVisibility (same authored motion: 400ms/70ms-stagger
outCubic entrances, 350ms fragment slides) -- deterministic under seeking in
both directions, and the interactive slideshow path behaves the same.

Verified by snapshot at t=9.72/41.03/63.7 vs the pinned baseline: all slides
now render their full content; the only residual difference is fragments
mid-authored-fade where the baseline shows them prematurely settled.
This commit is contained in:
Miguel Angel Simon Sierra
2026-07-10 13:24:06 -04:00
parent c67c7ba6ef
commit 6bf2010889
+39 -40
View File
@@ -1294,31 +1294,43 @@
{ time: 9.3, id: "prob-item1" },
{ time: 9.6, id: "prob-item2" }
];
var probRevealed = [false, false];
var lastActiveId = null;
function fireEntrance(sceneEl) {
var animEls = sceneEl.querySelectorAll("[data-anim]");
if (!animEls.length) return;
anime.animate(animEls, {
opacity: [0, 1],
translateY: [28, 0],
duration: 400,
delay: anime.stagger(70),
ease: "outCubic"
});
// Entrances are computed as a pure function of composition time.
// The port originally fired wall-clock anime.animate() calls on scene
// entry (mirroring the GSAP original's gsap.from()), but autoplaying
// animations run on anime's own rAF engine, which does not advance
// under the seek-driven render pipeline -- slides rendered with their
// content stuck at opacity 0 (or mid-fade), while the GSAP baseline
// happened to complete because GSAP's ticker still ticked during
// capture. Deriving progress from t keeps the exact same motion
// (400ms, 70ms stagger, outCubic; fragments 350ms) and is
// deterministic under seeking in both directions.
function outCubic(p) {
return 1 - Math.pow(1 - p, 3);
}
function clamp01(v) {
return v < 0 ? 0 : v > 1 ? 1 : v;
}
function revealProblem(id) {
var el = document.getElementById(id);
if (!el) return;
anime.animate(el, {
opacity: [0, 1],
translateX: [-24, 0],
duration: 350,
ease: "outCubic"
});
function applyEntrance(sceneEl, dt) {
var animEls = sceneEl.querySelectorAll("[data-anim]");
for (var i = 0; i < animEls.length; i++) {
var e = outCubic(clamp01((dt - i * 0.07) / 0.4));
animEls[i].style.opacity = e;
animEls[i].style.transform = "translateY(" + 28 * (1 - e) + "px)";
}
}
function applyProblemFragments(t) {
for (var f = 0; f < probFragments.length; f++) {
var pEl = document.getElementById(probFragments[f].id);
if (!pEl) continue;
var e = outCubic(clamp01((t - probFragments[f].time) / 0.35));
pEl.style.opacity = e;
pEl.style.transform = "translateX(" + -24 * (1 - e) + "px)";
}
}
function updateVisibility(t) {
@@ -1330,30 +1342,17 @@
el.style.opacity = active ? "1" : "0";
el.style.pointerEvents = active ? "auto" : "none";
if (active && lastActiveId !== s.id) {
// Update Three.js mood
if (window.__threeApplyMood) window.__threeApplyMood(s.sceneKey);
if (s.id === "scene-problem") {
// Reset problem items on re-entry
probRevealed = [false, false];
probFragments.forEach(function (f) {
var pEl = document.getElementById(f.id);
if (pEl) anime.utils.set(pEl, { opacity: 0, translateX: 0 });
});
if (active) {
if (lastActiveId !== s.id) {
// Update Three.js mood
if (window.__threeApplyMood) window.__threeApplyMood(s.sceneKey);
lastActiveId = s.id;
}
lastActiveId = s.id;
fireEntrance(el);
applyEntrance(el, t - s.start);
}
}
// Reveal problem fragments
for (var f = 0; f < probFragments.length; f++) {
if (!probRevealed[f] && t >= probFragments[f].time) {
probRevealed[f] = true;
revealProblem(probFragments[f].id);
}
}
applyProblemFragments(t);
}
window.__hfSetTime = updateVisibility;