mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-13 06:36:26 +03:00
- Restructure install section into a split primary card with the recommended path on the left and other install methods on the right - Drop the prefix toggle UI; bundle name is now baked into data-bundle - Balance the Stay updated step as paired Substack iframe + X follow card with matching height, border, and treatment - Pair Changelog and FAQ side-by-side on desktop with a centered divider and shared border-top; section nav highlights both when in view - Tone down the Visual Mode demo by removing side-tab borders, stacked card icons, and tiny body text so the overlay reads cleanly - Refine the Chrome extension callout: drop the pill badge, use an editorial eyebrow above the title, enlarge the thumbnail - Exclude private evals/ directory from version control
81 lines
2.2 KiB
JavaScript
81 lines
2.2 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;
|
|
|
|
function updateNav() {
|
|
const scrollY = window.scrollY;
|
|
const heroBottom = hero.offsetTop + hero.offsetHeight - 100;
|
|
const footerTop = footer ? footer.offsetTop : 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 && section.offsetTop <= 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?.offsetTop ?? 0;
|
|
sectionIds.forEach(id => {
|
|
const el = document.getElementById(id);
|
|
if (el && Math.abs(el.offsetTop - 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();
|
|
}
|