(() => {
const content = document.querySelector('[data-doc-content]');
const progress = document.querySelector('[data-doc-progress]');
const backToTop = document.querySelector('[data-doc-back-to-top]');
const tocLinks = Array.from(document.querySelectorAll('[data-doc-toc-link], .page-toc .toc-link'));
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const headings = tocLinks
.map(link => {
const hash = link.getAttribute('href');
if (!hash?.startsWith('#')) return null;
try {
const heading = document.getElementById(decodeURIComponent(hash.slice(1)));
return heading ? { link, heading } : null;
} catch {
return null;
}
})
.filter(Boolean);
let ticking = false;
const updateReaderState = () => {
ticking = false;
const scrollTop = window.scrollY || document.documentElement.scrollTop;
if (progress && content) {
const contentTop = scrollTop + content.getBoundingClientRect().top;
const available = Math.max(1, content.scrollHeight - window.innerHeight * 0.55);
const percentage = Math.min(100, Math.max(0, ((scrollTop - contentTop + 120) / available) * 100));
progress.style.width = `${percentage}%`;
}
backToTop?.classList.toggle('is-visible', scrollTop > 480);
if (headings.length > 0) {
let active = headings[0];
for (const item of headings) {
if (item.heading.getBoundingClientRect().top <= 150) active = item;
else break;
}
headings.forEach(item => {
const isActive = item === active;
item.link.classList.toggle('is-active', isActive);
if (isActive) item.link.setAttribute('aria-current', 'location');
else item.link.removeAttribute('aria-current');
});
}
};
const requestUpdate = () => {
if (ticking) return;
ticking = true;
window.requestAnimationFrame(updateReaderState);
};
window.addEventListener('scroll', requestUpdate, { passive: true });
window.addEventListener('resize', requestUpdate, { passive: true });
window.addEventListener('load', requestUpdate, { once: true });
backToTop?.addEventListener('click', () => {
window.scrollTo({ top: 0, behavior: reduceMotion ? 'auto' : 'smooth' });
});
updateReaderState();
})();
(() => {
const content = document.querySelector('[data-doc-content]');
const progress = document.querySelector('[data-doc-progress]');
const backToTop = document.querySelector('[data-doc-back-to-top]');
const tocLinks = Array.from(document.querySelectorAll('[data-doc-toc-link], .page-toc .toc-link'));
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const headings = tocLinks
.map(link => {
const hash = link.getAttribute('href');
if (!hash?.startsWith('#')) return null;
try {
const heading = document.getElementById(decodeURIComponent(hash.slice(1)));
return heading ? { link, heading } : null;
} catch {
return null;
}
})
.filter(Boolean);
let ticking = false;
const updateReaderState = () => {
ticking = false;
const scrollTop = window.scrollY || document.documentElement.scrollTop;
if (progress && content) {
const contentTop = scrollTop + content.getBoundingClientRect().top;
const available = Math.max(1, content.scrollHeight - window.innerHeight * 0.55);
const percentage = Math.min(100, Math.max(0, ((scrollTop - contentTop + 120) / available) * 100));
progress.style.width = `${percentage}%`;
}
backToTop?.classList.toggle('is-visible', scrollTop > 480);
if (headings.length > 0) {
let active = headings[0];
for (const item of headings) {
if (item.heading.getBoundingClientRect().top <= 150) active = item;
else break;
}
headings.forEach(item => {
const isActive = item === active;
item.link.classList.toggle('is-active', isActive);
if (isActive) item.link.setAttribute('aria-current', 'location');
else item.link.removeAttribute('aria-current');
});
}
};
const requestUpdate = () => {
if (ticking) return;
ticking = true;
window.requestAnimationFrame(updateReaderState);
};
window.addEventListener('scroll', requestUpdate, { passive: true });
window.addEventListener('resize', requestUpdate, { passive: true });
window.addEventListener('load', requestUpdate, { once: true });
backToTop?.addEventListener('click', () => {
window.scrollTo({ top: 0, behavior: reduceMotion ? 'auto' : 'smooth' });
});
updateReaderState();
})();