function createMobilePlanTodayNavigation(options) { const buttons = options.buttons || {}; const targets = options.targets || {}; const root = options.root; const listeners = new Map(); const prefersReducedMotion = options.prefersReducedMotion || (() => false); const now = options.now || (() => Date.now()); const targetNames = new Map(Object.entries(targets).map(([name, target]) => [target, name])); let observer = null; let navigationLockUntil = 0; function select(name) { Object.entries(buttons).forEach(([key, button]) => { if (!button) return; if (key === name) button.setAttribute('aria-current', 'location'); else button.removeAttribute('aria-current'); }); } function navigate(name) { const target = targets[name]; if (!root || !target) return false; root.scrollTo({ top: Math.max(0, target.offsetTop - root.offsetTop), behavior: prefersReducedMotion() ? 'auto' : 'smooth', }); navigationLockUntil = now() + 500; select(name); return true; } function reset() { select('fit'); } return { start() { Object.entries(buttons).forEach(([name, button]) => { if (!button || listeners.has(button)) return; const listener = event => { event.preventDefault(); navigate(name); }; listeners.set(button, listener); button.addEventListener('click', listener); }); reset(); const observe = options.observe || ((handler, observedTargets, observedRoot) => { if (typeof IntersectionObserver === 'undefined') return null; const instance = new IntersectionObserver(handler, { root: observedRoot, rootMargin: '-20% 0px -60% 0px', threshold: [0, 0.25, 0.5, 0.75, 1], }); observedTargets.forEach(target => instance.observe(target)); return instance; }); observer = observe(entries => { if (now() < navigationLockUntil) return; const visible = entries .filter(entry => entry.isIntersecting && targetNames.has(entry.target)) .sort((left, right) => right.intersectionRatio - left.intersectionRatio)[0]; if (visible) select(targetNames.get(visible.target)); }, Array.from(targetNames.keys()).filter(Boolean), root); }, stop() { listeners.forEach((listener, button) => button.removeEventListener('click', listener)); listeners.clear(); if (observer) observer.disconnect(); observer = null; }, navigate, reset, select, }; } function attachMobilePlanTodayNavigation({document, window}) { const bySection = name => document.querySelector('[data-plan-today-section="' + name + '"]'); const sheet = document.getElementById('plan-today-sheet'); const panel = document.querySelector('.plan-today-panel'); const navigation = createMobilePlanTodayNavigation({ root: panel, buttons: { fit: bySection('fit'), today: bySection('today'), available: bySection('available'), }, targets: { fit: document.getElementById('plan-today-fit'), today: document.getElementById('plan-today-selected'), available: document.getElementById('plan-today-available-work'), }, prefersReducedMotion: () => window.matchMedia('(prefers-reduced-motion: reduce)').matches, }); navigation.start(); new MutationObserver(() => { if (!sheet.hidden) navigation.reset(); }).observe(sheet, {attributes: true, attributeFilter: ['hidden']}); return navigation; } if (typeof module !== 'undefined') module.exports = createMobilePlanTodayNavigation; else attachMobilePlanTodayNavigation({document, window});