1/**
2 * Sticky Section Nav
3 * Shows/hides based on scroll position and highlights current section.
4 */
5
6export function initSectionNav() {
7 const nav = document.getElementById('section-nav');
8 if (!nav) return;
9
10 const items = nav.querySelectorAll('.section-nav-item');
11 const sectionIds = Array.from(items).map(item => item.dataset.section);
12
13 // Show/hide nav based on scroll position
14 const hero = document.getElementById('hero');
15 const footer = document.querySelector('.site-footer');
16 if (!hero) return;
17
18 let ticking = false;
19
20 function updateNav() {
21 const scrollY = window.scrollY;
22 const heroBottom = hero.offsetTop + hero.offsetHeight - 100;
23 const footerTop = footer ? footer.offsetTop : Infinity;
24 const viewportBottom = scrollY + window.innerHeight;
25
26 // Show nav after hero, hide when footer is visible
27 if (scrollY > heroBottom && viewportBottom < footerTop + 60) {
28 nav.classList.add('is-visible');
29 } else {
30 nav.classList.remove('is-visible');
31 }
32
33 // Find current section
34 let currentSection = null;
35 const viewportMiddle = scrollY + window.innerHeight * 0.4;
36
37 for (let i = sectionIds.length - 1; i >= 0; i--) {
38 const section = document.getElementById(sectionIds[i]);
39 if (section && section.offsetTop <= viewportMiddle) {
40 currentSection = sectionIds[i];
41 break;
42 }
43 }
44
45 // If the current section shares its top row with siblings (e.g. side-by-side
46 // changelog + FAQ on desktop), treat all of them as active.
47 const activeSections = new Set();
48 if (currentSection) {
49 const currentEl = document.getElementById(currentSection);
50 const currentTop = currentEl?.offsetTop ?? 0;
51 sectionIds.forEach(id => {
52 const el = document.getElementById(id);
53 if (el && Math.abs(el.offsetTop - currentTop) < 4) {
54 activeSections.add(id);
55 }
56 });
57 }
58
59 // Update active state
60 items.forEach(item => {
61 if (activeSections.has(item.dataset.section)) {
62 item.classList.add('is-active');
63 } else {
64 item.classList.remove('is-active');
65 }
66 });
67
68 ticking = false;
69 }
70
71 window.addEventListener('scroll', () => {
72 if (!ticking) {
73 requestAnimationFrame(updateNav);
74 ticking = true;
75 }
76 }, { passive: true });
77
78 // Initial check
79 updateNav();
80}