mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 22:26:38 +03:00
The changelog and FAQ sections are inside a positioned .changelog-faq-row wrapper, so their offsetTop was 0 (relative to parent) instead of their actual document position. This broke current-section detection and caused both pills to appear permanently active. Use getBoundingClientRect() instead, which returns correct absolute positions regardless of nesting. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
87 lines
2.4 KiB
JavaScript
87 lines
2.4 KiB
JavaScript
/**
|
|
* Sticky Section Nav
|
|
* Shows/hides based on scroll position and highlights current section.
|
|
*/
|
|
|
|
export function initSectionNav() {
|
|
const nav = document.getElementById('section-nav');
|
|
if (!nav) return;
|
|
|
|
const items = nav.querySelectorAll('.section-nav-item');
|
|
const sectionIds = Array.from(items).map(item => item.dataset.section);
|
|
|
|
// Show/hide nav based on scroll position
|
|
const hero = document.getElementById('hero');
|
|
const footer = document.querySelector('.site-footer');
|
|
if (!hero) return;
|
|
|
|
let ticking = false;
|
|
|
|
// Returns the element's top position relative to the document,
|
|
// which works even when the element is inside a positioned parent.
|
|
function docTop(el) {
|
|
return el.getBoundingClientRect().top + window.scrollY;
|
|
}
|
|
|
|
function updateNav() {
|
|
const scrollY = window.scrollY;
|
|
const heroBottom = hero.offsetTop + hero.offsetHeight - 100;
|
|
const footerTop = footer ? docTop(footer) : Infinity;
|
|
const viewportBottom = scrollY + window.innerHeight;
|
|
|
|
// Show nav after hero, hide when footer is visible
|
|
if (scrollY > heroBottom && viewportBottom < footerTop + 60) {
|
|
nav.classList.add('is-visible');
|
|
} else {
|
|
nav.classList.remove('is-visible');
|
|
}
|
|
|
|
// Find current section
|
|
let currentSection = null;
|
|
const viewportMiddle = scrollY + window.innerHeight * 0.4;
|
|
|
|
for (let i = sectionIds.length - 1; i >= 0; i--) {
|
|
const section = document.getElementById(sectionIds[i]);
|
|
if (section && docTop(section) <= viewportMiddle) {
|
|
currentSection = sectionIds[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If the current section shares its top row with siblings (e.g. side-by-side
|
|
// changelog + FAQ on desktop), treat all of them as active.
|
|
const activeSections = new Set();
|
|
if (currentSection) {
|
|
const currentEl = document.getElementById(currentSection);
|
|
const currentTop = currentEl ? docTop(currentEl) : 0;
|
|
sectionIds.forEach(id => {
|
|
const el = document.getElementById(id);
|
|
if (el && Math.abs(docTop(el) - currentTop) < 4) {
|
|
activeSections.add(id);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Update active state
|
|
items.forEach(item => {
|
|
if (activeSections.has(item.dataset.section)) {
|
|
item.classList.add('is-active');
|
|
} else {
|
|
item.classList.remove('is-active');
|
|
}
|
|
});
|
|
|
|
ticking = false;
|
|
}
|
|
|
|
window.addEventListener('scroll', () => {
|
|
if (!ticking) {
|
|
requestAnimationFrame(updateNav);
|
|
ticking = true;
|
|
}
|
|
}, { passive: true });
|
|
|
|
// Initial check
|
|
updateNav();
|
|
}
|