fix: hero CTAs invisible until scroll, seek animation skip, layout overhaul

- Remove data-reveal from hero h1/subtitle/mascot/cta-group/version-badge
  (critical above-fold content must never start hidden)
- Change IntersectionObserver rootMargin -150px to -30px, threshold 0.1 to 0.05
  (CTAs at ~530px were past the observer zone on laptops)
- Hero layout: align-items flex-start + padding-top 7.5rem for content at top
- Cards 62cqw to 70cqw, -3% offsets for dramatic overlap cascade
- Film animation: replace stick-figure walker with cinematic night scene
  (7 twinkling stars, glowing moon with drift, parallax mountains, firefly)
- Seek now restarts all film child animations via .demo-reset class toggle
  (was incorrectly resetting parent only — no visible jump)
- Add .demo-seek-sweep overlay + brightness flash during seek
- Video select: placeholder option ('Choose a video tab...') until story picks
- RATE 8 to 1 for realtime playback
- Version badge moved below download buttons
- All 15 locale files: add DEMO_SELECT_PLACEHOLDER key
This commit is contained in:
KoalaDev
2026-07-03 10:42:09 +02:00
parent e5a4032710
commit 573847486b
18 changed files with 219 additions and 95 deletions
+28 -3
View File
@@ -29,8 +29,8 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
}, {
rootMargin: '0px 0px -150px 0px',
threshold: 0.1
rootMargin: '0px 0px -30px 0px',
threshold: 0.05
});
revealElements.forEach(el => revealObserver.observe(el));
@@ -433,7 +433,7 @@ document.addEventListener('DOMContentLoaded', () => {
if (!launcher || !chip || !cursor || !playBtn || !pauseBtn) return;
const EP_LEN = 2537; // fake 42:17 episode
const RATE = 8; // timelapse factor so the demo feels alive
const RATE = 1; // realtime: 1 wall-clock second = 1 video second
const START_T = 754; // 12:34
const tabs = {};
@@ -548,14 +548,35 @@ document.addEventListener('DOMContentLoaded', () => {
tabs[sourceKey].t = fraction * EP_LEN;
renderTab(tabs[sourceKey]);
showEvent('» ' + NAMES[sourceKey]);
// visible "cut" so the jump reads as a real seek, not a silent update
flashSeek(tabs[sourceKey].root);
setTimeout(() => {
tabs[peerKey].t = tabs[sourceKey].t;
renderTab(tabs[peerKey]);
flashSeek(tabs[peerKey].root);
pulse();
broadcasting = false;
}, 90);
};
// Restart the film animations mid-stride + trigger the sweep overlay
let seekFlashTimers = [];
const flashSeek = (root) => {
if (!root) return;
root.classList.remove('demo-seeking');
// Restart every animated layer from frame zero so the jump is visible
const film = root.querySelector('.demo-film');
if (film) {
film.classList.add('demo-reset');
void film.offsetWidth; // force reflow so the browser commits the reset
film.classList.remove('demo-reset');
}
void root.offsetWidth;
root.classList.add('demo-seeking');
const t = setTimeout(() => root.classList.remove('demo-seeking'), 360);
seekFlashTimers.push(t);
};
const setPopupOpen = (open) => {
scene.classList.toggle('popup-open', open);
launcher.setAttribute('aria-expanded', open ? 'true' : 'false');
@@ -651,6 +672,10 @@ document.addEventListener('DOMContentLoaded', () => {
const flashSelect = () => {
if (!videoSelect) return;
// Actually pick the Stranger Things tab so the placeholder reads as chosen
if (videoSelect.options.length > 1) {
videoSelect.selectedIndex = 1;
}
videoSelect.classList.remove('demo-attn');
void videoSelect.offsetWidth;
videoSelect.classList.add('demo-attn');