tune overview anchors + stack-title-bar placement

- SCENE_ANCHOR_OFFSET replaced with getSceneAnchorOffset() so the
  bifrost scene can compute its offset per-viewport instead of using
  a fixed px count. bifrost lands at offsetTop + 0.85 * vh so the arc
  and sub-headline are already drawn in; stack-scene drops from 2100
  to 1800 so the anchor lands mid-stack rather than on the 4th card's
  final beat.
- .stack-title-bar top drops from clamp(3.75rem, 7vh, 5.25rem) to
  clamp(1.25rem, 2.8vh, 1.85rem) so the title floats at the same
  vertical baseline as the fixed .site-mark wordmark in the top-left,
  instead of sitting below it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Arlind Ukshini 2026-04-24 09:53:40 +02:00
parent 9b2c166b6c
commit 5f466e68a9
2 changed files with 29 additions and 23 deletions

View file

@ -1232,24 +1232,30 @@
* see sceneOrder[] inside init().
* Special value "hero" scrolls to top (0).
*/
// Per-scene scroll offsets in pixels. Added to the scene's offsetTop
// when a dot-nav button anchors to it, so the reader lands AFTER the
// scene's initial reveal rather than at an empty frame where the
// scrub hasn't advanced yet.
// Per-scene scroll offsets. Added to the scene's offsetTop when a
// dot-nav button anchors to it, so the reader lands AFTER the scene's
// initial reveal rather than at an empty pre-scrub frame.
//
// stack-scene — the pin is 5000px long; Phase A (cards falling in)
// completes at ~0.42 of that (~2100px). Landing at +2100 puts
// the reader on the fully stacked state, just before the grid
// rearrange begins in Phase B.
// stack-scene — pin is 5000px long; Phase A (cards falling in) is
// complete at ~0.42 of that (~2100px). 1800 lands just before the
// 4th card fully settles, so the reveal still has one tick to go.
//
// hero, bifrost, bifrost-join — short reveal tweens; offsetTop is
// already the correct landing spot so offset is 0.
const SCENE_ANCHOR_OFFSET = {
'hero': 0,
'stack-scene': 2100,
'bifrost': 0,
'bifrost-join': 0,
};
// bifrost — section is 200vh with a scrubbed reveal that runs from
// top-top to bottom-bottom (100vh scroll range). The sub-headline
// fades in at ~0.83 of that. Offset is computed per viewport as
// 85% of vh so the reader arrives on the fully-drawn arc +
// wordmark, regardless of display size.
//
// hero, bifrost-join — short reveal tweens; offsetTop is already
// the correct landing spot so offset is 0.
function getSceneAnchorOffset(sceneId) {
const vh = window.innerHeight;
switch (sceneId) {
case 'stack-scene': return 1800;
case 'bifrost': return Math.round(vh * 0.85);
default: return 0;
}
}
function scrollTo(sceneId) {
if (!scrollerEl) return; // init() hasn't run yet — ignore
@ -1260,7 +1266,7 @@
// the scene element directly works in most cases but produces a tiny
// non-zero offset (padding / border) — hard-code 0 for hero.
const base = sceneId === 'hero' ? 0 : target.offsetTop;
const scrollY = base + (SCENE_ANCHOR_OFFSET[sceneId] || 0);
const scrollY = base + getSceneAnchorOffset(sceneId);
if (lenisInstance && typeof lenisInstance.scrollTo === 'function') {
// Lenis does the smooth animation. `immediate: false` uses the

View file

@ -1076,13 +1076,13 @@ html {
.sc-current token is updated from bifrost.js's ScrollTrigger onUpdate
— 1/4 → 4/4 — one tick per landing card.
Vertical offset clears the fixed .site-mark (top:28px, width:118px)
so the title sits below the wordmark instead of crashing into it.
Horizontal padding on the left is bumped to push past the wordmark's
right edge; the right side uses the same edge token as the theatre. */
Vertical top is aligned with the fixed .site-mark (top:28px) so
the title rides beside the wordmark at the very top of the scene,
floating horizontally at the same baseline. Horizontal padding on
the left is sized to clear the wordmark's right edge (~146px). */
.stack-title-bar {
position: absolute;
top: clamp(3.75rem, 7vh, 5.25rem);
top: clamp(1.25rem, 2.8vh, 1.85rem);
left: 0;
right: 0;
z-index: 20;
@ -1090,7 +1090,7 @@ html {
justify-content: space-between;
align-items: baseline;
gap: 1.5rem;
padding-left: clamp(10.5rem, 12vw, 12.5rem);
padding-left: clamp(10rem, 12vw, 12rem);
padding-right: clamp(0.75rem, 2vw, 2.25rem);
pointer-events: none;
}