Files
OTSSigns-Website/theme/assets/js/video-editor-animator.js

111 lines
3.5 KiB
JavaScript
Raw Permalink Normal View History

/**
* Video Editor Timeline Animator
* Animates playhead scrubbing across the timeline and video preview crossfades.
* Mirrors the structure and conventions of dashboard-animator.js.
*/
(function () {
'use strict';
var DURATION = 10000; // ms for one full playhead sweep (left → right, then loop)
var X_MIN = 104; // leftmost playhead centre (SVG units)
var X_MAX = 504; // rightmost playhead centre (end of clip area)
function makeState(svg) {
return {
svg: svg,
playheadLine: svg.querySelector('#ve-playhead-line'),
playheadHead: svg.querySelector('#ve-playhead-head'),
scene1: svg.querySelector('#ve-scene-1'),
scene2: svg.querySelector('#ve-scene-2'),
scene3: svg.querySelector('#ve-scene-3'),
innerScreen: svg.querySelector('#ve-inner-screen'),
timecode: svg.querySelector('#ve-timecode'),
scrubPct: 0,
lastTime: performance.now(),
paused: false
};
}
function lerp(a, b, t) { return a + (b - a) * t; }
function updatePlayhead(st) {
var xC = lerp(X_MIN, X_MAX, st.scrubPct);
if (st.playheadLine) st.playheadLine.setAttribute('x', (xC - 1).toFixed(1));
if (st.playheadHead) st.playheadHead.setAttribute('transform', 'translate(' + xC.toFixed(1) + ',234)');
if (st.timecode) {
var s = Math.floor(st.scrubPct * 10);
st.timecode.textContent = '0:' + (s < 10 ? '0' + s : s);
}
}
function updateScenes(st) {
var p = st.scrubPct;
var o1, o2, o3, fade;
if (p < 0.30) {
o1 = 1; o2 = 0; o3 = 0;
} else if (p < 0.40) {
fade = (p - 0.30) / 0.10; o1 = 1 - fade; o2 = fade; o3 = 0;
} else if (p < 0.65) {
o1 = 0; o2 = 1; o3 = 0;
} else if (p < 0.75) {
fade = (p - 0.65) / 0.10; o1 = 0; o2 = 1 - fade; o3 = fade;
} else {
o1 = 0; o2 = 0; o3 = 1;
}
if (st.scene1) st.scene1.setAttribute('opacity', o1.toFixed(3));
if (st.scene2) st.scene2.setAttribute('opacity', o2.toFixed(3));
if (st.scene3) st.scene3.setAttribute('opacity', o3.toFixed(3));
if (st.innerScreen) {
st.innerScreen.setAttribute('fill',
p < 0.38 ? 'url(#ve-scr-warm)' :
p < 0.72 ? 'url(#ve-scr-cold)' :
'url(#ve-scr-go)'
);
}
}
function loop(st, now) {
if (!st.paused) {
var delta = now - st.lastTime;
st.lastTime = now;
st.scrubPct += delta / DURATION;
if (st.scrubPct >= 1) st.scrubPct -= 1;
updatePlayhead(st);
updateScenes(st);
} else {
st.lastTime = now; // keep fresh so there is no jump on resume
}
requestAnimationFrame(function (t) { loop(st, t); });
}
function observe(st) {
if (!('IntersectionObserver' in window)) return;
new IntersectionObserver(function (entries) {
for (var i = 0; i < entries.length; i++) {
st.paused = !entries[i].isIntersecting;
}
}, { rootMargin: '200px', threshold: 0.05 }).observe(st.svg);
}
function boot() {
var svgs = document.querySelectorAll('.ve-svg');
if (!svgs.length) return;
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
for (var i = 0; i < svgs.length; i++) {
(function (svg) {
if (svg._veAnim) return;
var st = makeState(svg);
svg._veAnim = st;
observe(st);
requestAnimationFrame(function (t) { loop(st, t); });
})(svgs[i]);
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', boot);
} else {
boot();
}
})();