scroll.js

 1// Instant anchor scroll - no smooth scrolling for better UX on long pages
 2export function initAnchorScroll() {
 3	document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
 4		anchor.addEventListener("click", (e) => {
 5			e.preventDefault();
 6			const target = document.querySelector(anchor.getAttribute("href"));
 7			if (target) {
 8				// Instant jump with small offset for visual breathing room
 9				const offset = 40;
10				const targetPosition = target.getBoundingClientRect().top + window.scrollY - offset;
11				window.scrollTo({ top: targetPosition, behavior: 'auto' });
12			}
13		});
14	});
15}
16
17export function initHashTracking() {
18	const sections = document.querySelectorAll('section[id]');
19	if (!sections.length) return;
20
21	let currentHash = window.location.hash.slice(1) || '';
22	let ticking = false;
23
24	function updateHash() {
25		// Don't override command deep links while user is in the commands section
26		if (currentHash.startsWith('cmd-')) {
27			const cmdEl = document.getElementById(currentHash);
28			if (cmdEl) {
29				const rect = cmdEl.getBoundingClientRect();
30				// Only clear the cmd hash if user scrolled well away from commands section
31				if (rect.top > window.innerHeight * 2 || rect.bottom < -window.innerHeight) {
32					currentHash = '';
33				} else {
34					ticking = false;
35					return;
36				}
37			}
38		}
39
40		const scrollY = window.scrollY;
41		const viewportHeight = window.innerHeight;
42		const triggerPoint = scrollY + viewportHeight * 0.3;
43
44		let activeSection = '';
45
46		sections.forEach(section => {
47			const rect = section.getBoundingClientRect();
48			const sectionTop = scrollY + rect.top;
49			const sectionBottom = sectionTop + rect.height;
50
51			if (triggerPoint >= sectionTop && triggerPoint < sectionBottom) {
52				activeSection = section.id;
53			}
54		});
55
56		// Don't set #hero — it's the default state, no hash needed
57		if (activeSection === 'hero') activeSection = '';
58
59		if (activeSection !== currentHash) {
60			currentHash = activeSection;
61			if (activeSection) {
62				history.replaceState(null, '', `#${activeSection}`);
63			} else {
64				history.replaceState(null, '', window.location.pathname);
65			}
66		}
67
68		ticking = false;
69	}
70
71	window.addEventListener('scroll', () => {
72		if (!ticking) {
73			requestAnimationFrame(updateHash);
74			ticking = true;
75		}
76	}, { passive: true });
77
78	// Handle initial hash on page load - instant jump
79	if (window.location.hash) {
80		const hash = window.location.hash.slice(1);
81		const target = document.getElementById(hash);
82		if (target) {
83			currentHash = hash;
84			setTimeout(() => {
85				const offset = 40;
86				const targetPosition = target.getBoundingClientRect().top + window.scrollY - offset;
87				window.scrollTo({ top: targetPosition, behavior: 'auto' });
88
89				// If it's a command deep link, activate it
90				if (hash.startsWith('cmd-') && target.classList.contains('manual-entry')) {
91					target.click();
92				}
93			}, 100);
94		}
95	} else {
96		// No hash — don't set one on initial load
97	}
98}
99