From cc341cc944d2796c0e1ef8be026dbc5721f30653 Mon Sep 17 00:00:00 2001 From: Mischa Date: Sat, 20 Jun 2026 11:28:12 +0200 Subject: [PATCH] fix(story): nav title cross-fades scroll-driven as hero content exits viewport MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced IntersectionObserver (discrete threshold) with a scroll RAF loop using getBoundingClientRect. Opacity is computed from the fraction of .story-hero__content still visible above the viewport top — so the nav title fades in gradually as the hero title slides off the top edge, reaching full opacity only when the element is completely gone. Removed CSS transition (no longer needed; per-frame JS update is smooth). Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr --- themes/intotheeast/css/style.css | 2 -- themes/intotheeast/templates/story.html.twig | 33 +++++++++++++++----- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/themes/intotheeast/css/style.css b/themes/intotheeast/css/style.css index e7526af..2bf5fc7 100644 --- a/themes/intotheeast/css/style.css +++ b/themes/intotheeast/css/style.css @@ -1210,10 +1210,8 @@ body::after { overflow: hidden; text-overflow: ellipsis; opacity: 0; - transition: opacity 0.35s ease; pointer-events: none; } -.story-nav-title.is-visible { opacity: 1; } @media (max-width: 640px) { .story-nav-title { display: none; } } /* ── Back to top pill ─────────────────────────────────────── */ diff --git a/themes/intotheeast/templates/story.html.twig b/themes/intotheeast/templates/story.html.twig index 271b011..06b5a43 100644 --- a/themes/intotheeast/templates/story.html.twig +++ b/themes/intotheeast/templates/story.html.twig @@ -88,20 +88,37 @@ update(); })(); -/* ── Story title in nav (fades in when hero title leaves viewport) ─────── */ +/* ── Story title in nav (cross-fades as hero content exits viewport top) ── */ (function () { var navTitle = document.getElementById('story-nav-title'); var heroContent = document.querySelector('.story-hero__content'); if (!navTitle || !heroContent) return; var hasBeenVisible = false; - new IntersectionObserver(function (entries) { - if (entries[0].isIntersecting) { - hasBeenVisible = true; - navTitle.classList.remove('is-visible'); - } else if (hasBeenVisible) { - navTitle.classList.add('is-visible'); + var ticking = false; + + function update() { + var rect = heroContent.getBoundingClientRect(); + var inView = rect.bottom > 0 && rect.top < window.innerHeight; + if (inView) hasBeenVisible = true; + + if (hasBeenVisible) { + var opacity; + if (rect.bottom <= 0) { + opacity = 1; // fully above viewport + } else if (rect.top <= 0) { + opacity = 1 - rect.bottom / rect.height; // partially exiting top + } else { + opacity = 0; // fully in viewport + } + navTitle.style.opacity = opacity.toFixed(3); } - }, { threshold: 0 }).observe(heroContent); + ticking = false; + } + + window.addEventListener('scroll', function () { + if (!ticking) { requestAnimationFrame(update); ticking = true; } + }, { passive: true }); + update(); })(); /* ── Back to top button ─────────────────────────────────── */