Created
February 15, 2026 00:05
-
-
Save miguel-heygen/903ca831098f27f2005ead018917f278 to your computer and use it in GitHub Desktop.
Temporary HyperFrames sandbox interceptor CDN test
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (function() { | |
| var _capturedTimeline = null; | |
| var _isPlaying = false; | |
| var _rafId = null; | |
| var _currentTime = 0; | |
| var _deterministicAdapters = []; | |
| // ── 1. Initialize __timelines registry ────────────────────────────────── | |
| window.__timelines = window.__timelines || {}; | |
| // ── 2. Layout management ───────────────────────────────────────────── | |
| function parseDimensionPx(value) { | |
| if (value == null || value === '') return null; | |
| var num = parseFloat(value); | |
| if (!isFinite(num) || num <= 0) return null; | |
| return num + 'px'; | |
| } | |
| function parseStartAttr(el) { | |
| var raw = el && el.getAttribute ? el.getAttribute('data-start') : null; | |
| var num = parseFloat(raw || '0'); | |
| return isNaN(num) ? 0 : num; | |
| } | |
| function createCssAdapter() { | |
| var entries = []; | |
| return { | |
| name: 'css', | |
| discover: function() { | |
| entries = []; | |
| var all = document.querySelectorAll('*'); | |
| for (var i = 0; i < all.length; i++) { | |
| var el = all[i]; | |
| var style = window.getComputedStyle(el); | |
| var hasAnimation = style.animationName && style.animationName !== 'none'; | |
| if (!hasAnimation) continue; | |
| entries.push({ | |
| el: el, | |
| baseDelay: el.style.animationDelay || '', | |
| basePlayState: el.style.animationPlayState || '', | |
| }); | |
| } | |
| }, | |
| seek: function(ctx) { | |
| var time = Number(ctx && ctx.time) || 0; | |
| for (var i = 0; i < entries.length; i++) { | |
| var entry = entries[i]; | |
| var el = entry.el; | |
| if (!el || !el.isConnected) continue; | |
| var start = parseStartAttr(el); | |
| var localTime = Math.max(0, time - start); | |
| el.style.animationPlayState = 'paused'; | |
| el.style.animationDelay = '-' + localTime.toFixed(3) + 's'; | |
| } | |
| }, | |
| pause: function() { | |
| for (var i = 0; i < entries.length; i++) { | |
| var entry = entries[i]; | |
| var el = entry.el; | |
| if (!el || !el.isConnected) continue; | |
| el.style.animationPlayState = entry.basePlayState || 'paused'; | |
| if (entry.baseDelay) el.style.animationDelay = entry.baseDelay; | |
| } | |
| }, | |
| }; | |
| } | |
| function createWaapiAdapter() { | |
| return { | |
| name: 'waapi', | |
| discover: function() {}, | |
| seek: function(ctx) { | |
| if (!document.getAnimations) return; | |
| var timeMs = Math.max(0, (Number(ctx && ctx.time) || 0) * 1000); | |
| var animations = document.getAnimations(); | |
| for (var i = 0; i < animations.length; i++) { | |
| var animation = animations[i]; | |
| if (!animation) continue; | |
| try { | |
| animation.pause(); | |
| animation.currentTime = timeMs; | |
| } catch (e) {} | |
| } | |
| }, | |
| pause: function() { | |
| if (!document.getAnimations) return; | |
| var animations = document.getAnimations(); | |
| for (var i = 0; i < animations.length; i++) { | |
| try { animations[i].pause(); } catch (e) {} | |
| } | |
| }, | |
| }; | |
| } | |
| function createGsapAdapter() { | |
| return { | |
| name: 'gsap', | |
| discover: function() {}, | |
| seek: function(ctx) { | |
| var tl = _capturedTimeline || (window.__player && window.__player._timeline); | |
| if (!tl) return; | |
| tl.pause(); | |
| // Keep GSAP callbacks active during scrubbing so tl.call(...) updates fire. | |
| tl.seek(Number(ctx && ctx.time) || 0, false); | |
| }, | |
| pause: function() { | |
| var tl = _capturedTimeline || (window.__player && window.__player._timeline); | |
| if (!tl) return; | |
| tl.pause(); | |
| }, | |
| }; | |
| } | |
| function createThreeAdapter() { | |
| var patched = false; | |
| var originalClockGetElapsedTime = null; | |
| var originalClockGetDelta = null; | |
| var originalMixerUpdate = null; | |
| var forcedTime = null; | |
| var lastForcedTime = 0; | |
| function patchClock(THREE) { | |
| if (!THREE || !THREE.Clock || !THREE.Clock.prototype || originalClockGetElapsedTime) return; | |
| originalClockGetElapsedTime = THREE.Clock.prototype.getElapsedTime; | |
| originalClockGetDelta = THREE.Clock.prototype.getDelta; | |
| THREE.Clock.prototype.getElapsedTime = function() { | |
| if (typeof forcedTime === 'number') { | |
| this.elapsedTime = forcedTime; | |
| this.oldTime = this.startTime + forcedTime * 1000; | |
| return forcedTime; | |
| } | |
| return originalClockGetElapsedTime.apply(this, arguments); | |
| }; | |
| THREE.Clock.prototype.getDelta = function() { | |
| if (typeof forcedTime === 'number') { | |
| var delta = Math.max(0, forcedTime - lastForcedTime); | |
| this.elapsedTime = forcedTime; | |
| this.oldTime = this.startTime + forcedTime * 1000; | |
| return delta; | |
| } | |
| return originalClockGetDelta.apply(this, arguments); | |
| }; | |
| } | |
| function patchAnimationMixer(THREE) { | |
| if (!THREE || !THREE.AnimationMixer || !THREE.AnimationMixer.prototype || originalMixerUpdate) return; | |
| originalMixerUpdate = THREE.AnimationMixer.prototype.update; | |
| THREE.AnimationMixer.prototype.update = function(deltaTime) { | |
| if (typeof forcedTime === 'number' && typeof this.setTime === 'function') { | |
| try { | |
| this.setTime(forcedTime); | |
| return this; | |
| } catch (e) {} | |
| } | |
| return originalMixerUpdate.call(this, deltaTime); | |
| }; | |
| } | |
| return { | |
| name: 'three', | |
| discover: function() { | |
| if (!window.THREE) return; | |
| patchClock(window.THREE); | |
| patchAnimationMixer(window.THREE); | |
| patched = true; | |
| }, | |
| seek: function(ctx) { | |
| if (!patched && window.THREE) { | |
| patchClock(window.THREE); | |
| patchAnimationMixer(window.THREE); | |
| patched = true; | |
| } | |
| forcedTime = Math.max(0, Number(ctx && ctx.time) || 0); | |
| lastForcedTime = forcedTime; | |
| window.__magicEditThreeTime = forcedTime; | |
| try { | |
| window.dispatchEvent(new CustomEvent('magic-edit-seek', { detail: { time: forcedTime } })); | |
| } catch (e) {} | |
| }, | |
| pause: function() { | |
| if (typeof forcedTime !== 'number') { | |
| forcedTime = Math.max(0, _capturedTimeline ? _capturedTimeline.time() : 0); | |
| lastForcedTime = forcedTime; | |
| } | |
| }, | |
| play: function() { | |
| forcedTime = null; | |
| }, | |
| }; | |
| } | |
| function setupDeterministicAdapters() { | |
| // CSS first (strict), then WAAPI, GSAP and Three.js. | |
| _deterministicAdapters = [ | |
| createCssAdapter(), | |
| createWaapiAdapter(), | |
| createGsapAdapter(), | |
| createThreeAdapter(), | |
| ]; | |
| for (var i = 0; i < _deterministicAdapters.length; i++) { | |
| try { _deterministicAdapters[i].discover(); } catch (e) {} | |
| } | |
| } | |
| function runDeterministicSeek(time) { | |
| var ctx = { time: Number(time) || 0 }; | |
| for (var i = 0; i < _deterministicAdapters.length; i++) { | |
| try { _deterministicAdapters[i].seek(ctx); } catch (e) {} | |
| } | |
| } | |
| function runDeterministicPause() { | |
| for (var i = 0; i < _deterministicAdapters.length; i++) { | |
| try { _deterministicAdapters[i].pause(); } catch (e) {} | |
| } | |
| } | |
| function runDeterministicPlay() { | |
| for (var i = 0; i < _deterministicAdapters.length; i++) { | |
| if (!_deterministicAdapters[i] || !_deterministicAdapters[i].play) continue; | |
| try { _deterministicAdapters[i].play(); } catch (e) {} | |
| } | |
| } | |
| function applyClipLayout(rootEl) { | |
| rootEl.style.position = rootEl.style.position || 'relative'; | |
| rootEl.style.overflow = 'hidden'; | |
| var rootW = parseDimensionPx(rootEl.getAttribute('data-width')); | |
| var rootH = parseDimensionPx(rootEl.getAttribute('data-height')); | |
| if (rootW) rootEl.style.width = rootW; | |
| if (rootH) rootEl.style.height = rootH; | |
| var children = rootEl.children; | |
| var count = 0; | |
| for (var i = 0; i < children.length; i++) { | |
| var el = children[i]; | |
| var tag = el.tagName.toLowerCase(); | |
| if (tag === 'script' || tag === 'style' || tag === 'link') continue; | |
| if (!el.hasAttribute('data-start')) continue; | |
| el.style.position = 'absolute'; | |
| el.style.top = '0'; | |
| el.style.left = '0'; | |
| if (tag !== 'audio') { | |
| var forcedW = parseDimensionPx(el.getAttribute('data-width')); | |
| var forcedH = parseDimensionPx(el.getAttribute('data-height')); | |
| el.style.width = forcedW || '100%'; | |
| el.style.height = forcedH || '100%'; | |
| } | |
| if (tag === 'video' && !el.style.objectFit) { | |
| el.style.objectFit = 'cover'; | |
| } | |
| var track = parseInt(el.getAttribute('data-track') || '0', 10); | |
| if (!isNaN(track)) el.style.zIndex = String(track); | |
| count++; | |
| } | |
| console.log('[Sandbox] Applied clip layout for ' + count + ' elements'); | |
| } | |
| // ── 3. Visibility management ────────────────────────────────────────── | |
| function addVisibilityBookends(tl) { | |
| if (!tl || !window.gsap) return; | |
| var elements = document.querySelectorAll('[data-start]'); | |
| for (var i = 0; i < elements.length; i++) { | |
| var el = elements[i]; | |
| if (!el.id) continue; | |
| var start = parseFloat(el.dataset.start); | |
| var duration = parseFloat(el.dataset.duration); | |
| if (isNaN(start)) continue; | |
| // Infer duration from media's actual duration for video/audio | |
| if (isNaN(duration) && (el.tagName === 'VIDEO' || el.tagName === 'AUDIO')) { | |
| var mediaDur = el.duration; | |
| if (mediaDur && isFinite(mediaDur) && mediaDur > 0) { | |
| var pbStart = parseFloat(el.dataset.playbackStart) || 0; | |
| duration = mediaDur - pbStart; | |
| } | |
| } | |
| // Infer duration from GSAP timeline for compositions | |
| var compId = el.getAttribute('data-composition-id'); | |
| if (isNaN(duration) && compId && window.__timelines[compId]) { | |
| var compDur = window.__timelines[compId].duration(); | |
| if (compDur > 0) duration = compDur; | |
| } | |
| tl.set('#' + el.id, { visibility: 'hidden' }, 0); | |
| tl.set('#' + el.id, { visibility: 'visible' }, start); | |
| if (!isNaN(duration)) { | |
| tl.set('#' + el.id, { visibility: 'hidden' }, start + duration); | |
| } | |
| } | |
| console.log('[Sandbox] Added visibility bookends for', elements.length, 'elements'); | |
| } | |
| // ── 4. Media sync ───────────────────────────────────────────────────── | |
| function syncMedia(time, playing) { | |
| var mediaEls = document.querySelectorAll('video[data-start], audio[data-start]'); | |
| for (var i = 0; i < mediaEls.length; i++) { | |
| var el = mediaEls[i]; | |
| var start = parseFloat(el.dataset.start); | |
| var dur = parseFloat(el.dataset.duration); | |
| var volume = parseFloat(el.dataset.volume); | |
| if (isNaN(start)) continue; | |
| // Infer duration from media's actual duration | |
| if (isNaN(dur) && el.duration && isFinite(el.duration) && el.duration > 0) { | |
| var pbOff = parseFloat(el.dataset.playbackStart) || 0; | |
| dur = el.duration - pbOff; | |
| } | |
| var end = !isNaN(dur) ? start + dur : Infinity; | |
| var mediaStart = parseFloat(el.dataset.playbackStart) || 0; | |
| var relTime = time - start + mediaStart; | |
| if (time >= start && time < end && relTime >= 0) { | |
| // Element should be active | |
| if (!isNaN(volume)) el.volume = volume; | |
| // Sync currentTime if drifted | |
| if (Math.abs((el.currentTime || 0) - relTime) > 0.3) { | |
| try { el.currentTime = relTime; } catch(e) {} | |
| } | |
| if (playing && el.paused) { | |
| el.play().catch(function() {}); | |
| } else if (!playing && !el.paused) { | |
| el.pause(); | |
| } | |
| } else { | |
| // Element should be inactive | |
| if (!el.paused) el.pause(); | |
| } | |
| } | |
| } | |
| // ── 5. RAF loop ─────────────────────────────────────────────────────── | |
| function tick() { | |
| if (!_capturedTimeline) { _rafId = requestAnimationFrame(tick); return; } | |
| _currentTime = _capturedTimeline.time(); | |
| syncMedia(_currentTime, _isPlaying); | |
| // Check if reached end | |
| if (_isPlaying && _currentTime >= _capturedTimeline.duration()) { | |
| _isPlaying = false; | |
| _capturedTimeline.pause(); | |
| syncMedia(_currentTime, false); | |
| } | |
| _rafId = requestAnimationFrame(tick); | |
| } | |
| // ── 6. Public API: window.__player ──────────────────────────────────── | |
| window.__player = { | |
| _timeline: null, // set once captured | |
| play: function() { | |
| if (!_capturedTimeline) return; | |
| if (_capturedTimeline.time() >= _capturedTimeline.duration()) { | |
| runDeterministicSeek(0); | |
| } | |
| _capturedTimeline.play(); | |
| runDeterministicPlay(); | |
| _isPlaying = true; | |
| }, | |
| pause: function() { | |
| if (!_capturedTimeline) return; | |
| _capturedTimeline.pause(); | |
| runDeterministicSeek(_capturedTimeline.time()); | |
| runDeterministicPause(); | |
| _isPlaying = false; | |
| syncMedia(_capturedTimeline.time(), false); | |
| }, | |
| seek: function(time) { | |
| if (!_capturedTimeline) return; | |
| _capturedTimeline.pause(); | |
| var safeTime = Math.max(0, Number(time) || 0); | |
| // Keep GSAP callbacks active during scrubbing so tl.call(...) updates fire. | |
| _capturedTimeline.seek(safeTime, false); | |
| runDeterministicSeek(safeTime); | |
| _isPlaying = false; | |
| _currentTime = safeTime; | |
| syncMedia(safeTime, false); | |
| }, | |
| getTime: function() { | |
| return _capturedTimeline ? _capturedTimeline.time() : 0; | |
| }, | |
| getDuration: function() { | |
| return _capturedTimeline ? _capturedTimeline.duration() : 0; | |
| }, | |
| isPlaying: function() { | |
| return _isPlaying; | |
| } | |
| }; | |
| // ── 7. Load external compositions from data-composition-src ───────── | |
| function loadExternalCompositions(callback) { | |
| var compEls = document.querySelectorAll('[data-composition-src]'); | |
| if (compEls.length === 0) { callback(); return; } | |
| var pending = compEls.length; | |
| console.log('[Sandbox] Loading ' + pending + ' external composition(s)'); | |
| function done() { | |
| pending--; | |
| if (pending <= 0) callback(); | |
| } | |
| for (var i = 0; i < compEls.length; i++) { | |
| (function(el) { | |
| var src = el.getAttribute('data-composition-src'); | |
| console.log('[Sandbox] Fetching composition: ' + src); | |
| fetch(src).then(function(res) { | |
| if (!res.ok) throw new Error('HTTP ' + res.status); | |
| return res.text(); | |
| }).then(function(html) { | |
| var parser = new DOMParser(); | |
| var doc = parser.parseFromString(html, 'text/html'); | |
| var template = doc.querySelector('template'); | |
| var sourceNode = template ? template.content : doc.body; | |
| // If the template/body has a root div with data-composition-id, unwrap it | |
| // to avoid nesting duplicate composition wrappers | |
| var compId = el.getAttribute('data-composition-id'); | |
| var innerRoot = sourceNode.querySelector('[data-composition-id="' + compId + '"]'); | |
| var contentNode = innerRoot || sourceNode; | |
| // Inject styles into document head | |
| var styles = contentNode.querySelectorAll('style'); | |
| for (var s = 0; s < styles.length; s++) { | |
| document.head.appendChild(styles[s].cloneNode(true)); | |
| } | |
| // Collect scripts before importing (they won't execute via appendChild from a template) | |
| var scripts = contentNode.querySelectorAll('script'); | |
| var scriptTexts = []; | |
| for (var s = 0; s < scripts.length; s++) { | |
| scriptTexts.push(scripts[s].textContent); | |
| scripts[s].parentNode.removeChild(scripts[s]); | |
| } | |
| // Remove styles from content (already injected to head) | |
| var remainingStyles = contentNode.querySelectorAll('style'); | |
| for (var s = 0; s < remainingStyles.length; s++) { | |
| remainingStyles[s].parentNode.removeChild(remainingStyles[s]); | |
| } | |
| // Import content into the container element | |
| if (innerRoot) { | |
| // Keep the inner root as a positioned sub-container. | |
| // data-composition-id stays on both outer and inner — the CSS double-match | |
| // is harmless (outer uses position:absolute from interceptor, inner uses | |
| // position:relative, and flex/layout CSS on the inner correctly positions content). | |
| var imported = document.importNode(innerRoot, true); | |
| var innerWRaw = innerRoot.getAttribute('data-width'); | |
| var innerHRaw = innerRoot.getAttribute('data-height'); | |
| var innerW = parseDimensionPx(innerWRaw); | |
| var innerH = parseDimensionPx(innerHRaw); | |
| imported.style.position = 'relative'; | |
| imported.style.width = innerW || '100%'; | |
| imported.style.height = innerH || '100%'; | |
| if (innerWRaw) el.setAttribute('data-width', innerWRaw); | |
| if (innerHRaw) el.setAttribute('data-height', innerHRaw); | |
| if (innerW) el.style.width = innerW; | |
| if (innerH) el.style.height = innerH; | |
| el.appendChild(imported); | |
| } else if (template) { | |
| var imported = document.importNode(contentNode, true); | |
| el.appendChild(imported); | |
| } else { | |
| el.innerHTML = contentNode.innerHTML; | |
| } | |
| // Execute scripts in order (wrapped in IIFE for isolated scope) | |
| for (var s = 0; s < scriptTexts.length; s++) { | |
| var newScript = document.createElement('script'); | |
| newScript.textContent = '(function(){' + scriptTexts[s] + '})();'; | |
| document.body.appendChild(newScript); | |
| } | |
| console.log('[Sandbox] Loaded composition "' + (compId || src) + '" from ' + src); | |
| done(); | |
| }).catch(function(err) { | |
| console.warn('[Sandbox] Failed to load composition ' + src + ':', err); | |
| done(); | |
| }); | |
| })(compEls[i]); | |
| } | |
| } | |
| // ── 8. Init on DOMContentLoaded ─────────────────────────────────────── | |
| function init() { | |
| var rootEl = document.querySelector('[data-composition-id]'); | |
| if (!rootEl) { | |
| console.warn('[Sandbox] No element with data-composition-id found'); | |
| _rafId = requestAnimationFrame(tick); | |
| return; | |
| } | |
| var rootId = rootEl.getAttribute('data-composition-id'); | |
| // Apply absolute positioning and sizing to clip elements (the framework layout contract) | |
| applyClipLayout(rootEl); | |
| // Load external compositions first, then proceed with timeline setup | |
| loadExternalCompositions(function() { | |
| initTimelines(rootEl, rootId); | |
| }); | |
| _rafId = requestAnimationFrame(tick); | |
| } | |
| function initTimelines(rootEl, rootId) { | |
| var childEls = rootEl.querySelectorAll('[data-composition-id]'); | |
| var seen = {}; | |
| var children = []; | |
| for (var i = 0; i < childEls.length; i++) { | |
| var childId = childEls[i].getAttribute('data-composition-id'); | |
| if (childId === rootId) continue; | |
| if (seen[childId]) continue; | |
| seen[childId] = true; | |
| var start = parseFloat(childEls[i].getAttribute('data-start') || '0'); | |
| children.push({ id: childId, start: isNaN(start) ? 0 : start }); | |
| } | |
| console.log('[Sandbox] Root composition: "' + rootId + '", children:', children.map(function(c) { return c.id + '@' + c.start; })); | |
| function addChildren(masterTL) { | |
| for (var i = 0; i < children.length; i++) { | |
| var childTL = window.__timelines[children[i].id]; | |
| if (childTL) { | |
| childTL.paused(false); | |
| masterTL.add(childTL, children[i].start); | |
| console.log('[Sandbox] Added "' + children[i].id + '" at ' + children[i].start + 's'); | |
| } | |
| } | |
| } | |
| function finalize(masterTL, wasExisting) { | |
| masterTL.pause(); | |
| if (!wasExisting) { | |
| masterTL.set({}, {}, masterTL.duration() + 0.1); | |
| } | |
| _capturedTimeline = masterTL; | |
| window.__player._timeline = masterTL; | |
| window.__timelines[rootId] = masterTL; | |
| setupDeterministicAdapters(); | |
| addVisibilityBookends(masterTL); | |
| // Signal render readiness (used by producer's frameCapture.ts) | |
| window.__playerReady = true; | |
| console.log('[Sandbox] Player ready' + (wasExisting ? ' (existing root timeline)' : ' (created fallback)') + ', duration:', masterTL.duration()); | |
| } | |
| // Poll for the HTML-authored root timeline, then append children | |
| var attempts = 0; | |
| var maxAttempts = 300; // 30s at 100ms | |
| var iv = setInterval(function() { | |
| attempts++; | |
| var existingMaster = window.__timelines[rootId]; | |
| var allChildrenReady = true; | |
| for (var i = 0; i < children.length; i++) { | |
| if (!window.__timelines[children[i].id]) { allChildrenReady = false; break; } | |
| } | |
| // Found existing root timeline and children are ready | |
| if (existingMaster && allChildrenReady) { | |
| clearInterval(iv); | |
| console.log('[Sandbox] Found existing root timeline "' + rootId + '"'); | |
| addChildren(existingMaster); | |
| finalize(existingMaster, true); | |
| return; | |
| } | |
| // 30s timeout — no existing root timeline found, create our own | |
| if (attempts >= maxAttempts) { | |
| clearInterval(iv); | |
| if (existingMaster) { | |
| var missing = children.filter(function(c) { return !window.__timelines[c.id]; }).map(function(c) { return c.id; }); | |
| if (missing.length) console.warn('[Sandbox] Timed out waiting for child timelines after 30s:', missing); | |
| addChildren(existingMaster); | |
| finalize(existingMaster, true); | |
| } else { | |
| console.warn('[Sandbox] Root timeline "' + rootId + '" not found after 30s, creating fallback'); | |
| var masterTL = window.gsap.timeline({ paused: true }); | |
| addChildren(masterTL); | |
| finalize(masterTL, false); | |
| } | |
| } | |
| }, 100); | |
| } | |
| if (document.readyState === 'loading') { | |
| document.addEventListener('DOMContentLoaded', init); | |
| } else { | |
| init(); | |
| } | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment