feat(route): nav script — arrows, fades, progress dots, initial scroll
Vanilla TS script at the bottom of RoadmapRoute.astro. No library. - Arrows scrollBy ±72% of the scroll-container's clientWidth, smooth behaviour. Disabled at scroll start/end. - Edge fades (.rr-fade-left / -right) flip opacity 0↔1 at scroll start / end so the affordance disappears when there's nowhere further to go. - Progress dots track scrollLeft/(scrollWidth-clientWidth) percentage, bucketing into dots.length slots. Active dot gets .active (themed in CSS as --on-surface). - On mount, the script reads section.data-initial-x — the SVG x position of the most recent shipping milestone (computed server-side from the layout helper) — and scrolls so that x sits ~25% from the left edge of the viewport. Clamped to [0, scrollWidth-clientWidth]. Member opens /roadmap and immediately sees one shipped item + several ahead-of-them items, not the very start of history. - setTimeout(update, 50) re-measures after first paint settles (especially relevant when SVG fonts or other late-arriving assets shift the trackWidth by a couple of px). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7bd4902b9d
commit
d49882b3f9
1 changed files with 52 additions and 0 deletions
|
|
@ -159,6 +159,58 @@ const initialShippingX = lastShippingIndex >= 0 ? itemXByIndex[lastShippingIndex
|
|||
</ol>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
// Vanilla nav for the desktop horizontal route:
|
||||
// - arrows scrollBy 72% of the viewport per click
|
||||
// - edge fades flip on at-start / at-end
|
||||
// - progress dots track scroll position
|
||||
// - on mount, scroll the 'you are here' milestone roughly 25% from left
|
||||
document.querySelectorAll<HTMLElement>('.route').forEach((section) => {
|
||||
const scroll = section.querySelector<HTMLElement>('#rr-scroll');
|
||||
const prev = section.querySelector<HTMLButtonElement>('#rr-prev');
|
||||
const next = section.querySelector<HTMLButtonElement>('#rr-next');
|
||||
const fadeL = section.querySelector<HTMLElement>('#rr-fade-l');
|
||||
const fadeR = section.querySelector<HTMLElement>('#rr-fade-r');
|
||||
const dots = Array.from(section.querySelectorAll<HTMLElement>('#rr-progress .rr-progress-dot'));
|
||||
if (!scroll) return;
|
||||
|
||||
const step = () => scroll.clientWidth * 0.72;
|
||||
|
||||
prev?.addEventListener('click', () => scroll.scrollBy({ left: -step(), behavior: 'smooth' }));
|
||||
next?.addEventListener('click', () => scroll.scrollBy({ left: step(), behavior: 'smooth' }));
|
||||
|
||||
function update() {
|
||||
const max = scroll!.scrollWidth - scroll!.clientWidth;
|
||||
const atStart = scroll!.scrollLeft <= 2;
|
||||
const atEnd = scroll!.scrollLeft >= max - 2;
|
||||
if (prev) prev.disabled = atStart;
|
||||
if (next) next.disabled = atEnd;
|
||||
if (fadeL) fadeL.style.opacity = atStart ? '0' : '1';
|
||||
if (fadeR) fadeR.style.opacity = atEnd ? '0' : '1';
|
||||
if (dots.length > 0) {
|
||||
const pct = max > 0 ? scroll!.scrollLeft / max : 0;
|
||||
const activeIdx = Math.min(dots.length - 1, Math.floor(pct * dots.length));
|
||||
dots.forEach((d, i) => d.classList.toggle('active', i === activeIdx));
|
||||
}
|
||||
}
|
||||
|
||||
scroll.addEventListener('scroll', update, { passive: true });
|
||||
window.addEventListener('resize', update);
|
||||
|
||||
// Initial scroll: park the most recent shipping item ~25% from the left.
|
||||
const initialX = Number(section.dataset.initialX ?? 0);
|
||||
if (initialX > 0) {
|
||||
const max = scroll.scrollWidth - scroll.clientWidth;
|
||||
const target = Math.max(0, Math.min(max, initialX - scroll.clientWidth * 0.25));
|
||||
scroll.scrollLeft = target;
|
||||
}
|
||||
|
||||
// First paint may happen before layout settles — re-measure shortly after.
|
||||
setTimeout(update, 50);
|
||||
update();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* ── Section header ─────────────────────────────────────────────── */
|
||||
.route-header {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue