/* TOD Documentation Website — Shared JavaScript */ (function () { 'use strict'; /* -------------------------------------------------- */ /* 1. Scroll Spy (docs page) */ /* -------------------------------------------------- */ function initScrollSpy() { const sidebar = document.querySelector('.sidebar-nav'); if (!sidebar) return; const links = Array.from(document.querySelectorAll('.sidebar-link')); const sections = links .map(function (link) { var id = link.getAttribute('data-section'); return id ? document.getElementById(id) : null; }) .filter(Boolean); if (!sections.length) return; var current = null; var observer = new IntersectionObserver( function (entries) { entries.forEach(function (entry) { if (entry.isIntersecting) { var id = entry.target.id; if (id !== current) { current = id; links.forEach(function (l) { l.classList.toggle( 'sidebar-link--active', l.getAttribute('data-section') === id ); }); /* keep active link visible in sidebar */ var active = sidebar.querySelector('.sidebar-link--active'); if (active) { active.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); } } } }); }, { rootMargin: '-80px 0px -60% 0px', threshold: 0 } ); sections.forEach(function (s) { observer.observe(s); }); } /* -------------------------------------------------- */ /* 2. Docs Search */ /* -------------------------------------------------- */ function initDocsSearch() { var input = document.getElementById('docs-search-input'); if (!input) return; var content = document.getElementById('docs-content'); if (!content) return; var sections = Array.from(content.querySelectorAll('section[id]')); var links = Array.from(document.querySelectorAll('.sidebar-link')); input.addEventListener('input', function () { var q = input.value.trim().toLowerCase(); if (!q) { sections.forEach(function (s) { s.style.display = ''; }); links.forEach(function (l) { l.style.display = ''; }); return; } sections.forEach(function (s) { var text = s.textContent.toLowerCase(); var match = text.indexOf(q) !== -1; s.style.display = match ? '' : 'none'; }); links.forEach(function (l) { var sectionId = l.getAttribute('data-section'); var section = sectionId ? document.getElementById(sectionId) : null; if (section) { l.style.display = section.style.display; } }); }); } /* -------------------------------------------------- */ /* 3. Back to Top */ /* -------------------------------------------------- */ function initBackToTop() { var btn = document.getElementById('back-to-top'); if (!btn) return; window.addEventListener('scroll', function () { btn.classList.toggle('back-to-top--visible', window.scrollY > 400); }, { passive: true }); } window.scrollToTop = function () { window.scrollTo({ top: 0, behavior: 'smooth' }); }; /* -------------------------------------------------- */ /* 4. Sidebar Toggle (mobile) */ /* -------------------------------------------------- */ window.toggleSidebar = function () { var sidebar = document.getElementById('docs-sidebar'); if (!sidebar) return; sidebar.classList.toggle('docs-sidebar--open'); }; function initSidebarClose() { var sidebar = document.getElementById('docs-sidebar'); if (!sidebar) return; /* close on outside click */ document.addEventListener('click', function (e) { if ( sidebar.classList.contains('docs-sidebar--open') && !sidebar.contains(e.target) && !e.target.closest('.docs-hamburger') ) { sidebar.classList.remove('docs-sidebar--open'); } }); /* close on link click (mobile) */ sidebar.addEventListener('click', function (e) { if (e.target.closest('.sidebar-link')) { sidebar.classList.remove('docs-sidebar--open'); } }); } /* -------------------------------------------------- */ /* 5. Reveal Animation (landing page) */ /* -------------------------------------------------- */ function initReveal() { var els = document.querySelectorAll('.reveal'); if (!els.length) return; var observer = new IntersectionObserver( function (entries) { entries.forEach(function (entry) { if (entry.isIntersecting) { entry.target.classList.add('reveal--visible'); observer.unobserve(entry.target); } }); }, { threshold: 0.1 } ); els.forEach(function (el) { observer.observe(el); }); } /* -------------------------------------------------- */ /* 6. Smooth scroll for anchor links */ /* -------------------------------------------------- */ function initSmoothScroll() { document.addEventListener('click', function (e) { var link = e.target.closest('a[href^="#"]'); if (!link) return; var id = link.getAttribute('href').slice(1); var target = document.getElementById(id); if (!target) return; e.preventDefault(); var offset = 80; var top = target.getBoundingClientRect().top + window.pageYOffset - offset; window.scrollTo({ top: top, behavior: 'smooth' }); /* update URL without jump */ history.pushState(null, '', '#' + id); }); } /* -------------------------------------------------- */ /* 7. Active nav link (landing page) */ /* -------------------------------------------------- */ function initActiveNav() { var navLinks = document.querySelectorAll('.nav-link[href^="#"]'); if (!navLinks.length) return; /* only run on landing page */ if (document.body.classList.contains('docs-page')) return; var sectionIds = []; navLinks.forEach(function (l) { var hash = l.getAttribute('href').split('#')[1]; if (hash) sectionIds.push({ id: hash, link: l }); }); if (!sectionIds.length) return; var observer = new IntersectionObserver( function (entries) { entries.forEach(function (entry) { if (entry.isIntersecting) { sectionIds.forEach(function (item) { item.link.classList.toggle( 'nav-link--active', item.id === entry.target.id ); }); } }); }, { rootMargin: '-80px 0px -60% 0px', threshold: 0 } ); sectionIds.forEach(function (item) { var el = document.getElementById(item.id); if (el) observer.observe(el); }); } /* -------------------------------------------------- */ /* 8. Bullet throb — one at a time per list */ /* -------------------------------------------------- */ function initBulletThrob() { var lists = document.querySelectorAll('.content-list'); lists.forEach(function (list) { var items = list.querySelectorAll('li'); var count = items.length; if (!count) return; /* each bullet gets 0.8s of the cycle; total = count * 0.8s */ var step = 0.8; var total = count * step; /* * The keyframe runs 0→50%→100% as throb. * We shrink the active window so only ~1/count of the cycle is the throb. * Rewrite: 0-X% is the throb, X-100% is idle. */ var pct = (1 / count) * 100; var halfPct = pct / 2; /* inject a scoped keyframe for this list size */ var name = 'bt-' + count; if (!document.querySelector('[data-bt="' + name + '"]')) { var style = document.createElement('style'); style.setAttribute('data-bt', name); style.textContent = '@keyframes ' + name + ' {' + '0% { transform: scale(1); box-shadow: 0 0 0 0 rgba(42,157,143,0); }' + halfPct.toFixed(1) + '% { transform: scale(1.5); box-shadow: 0 0 8px 2px rgba(42,157,143,0.3); }' + pct.toFixed(1) + '% { transform: scale(1); box-shadow: 0 0 0 0 rgba(42,157,143,0); }' + '100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(42,157,143,0); }' + '}'; document.head.appendChild(style); } items.forEach(function (li, i) { li.style.setProperty('--bt-animation', name + ' ' + total + 's ease-in-out infinite ' + (i * step) + 's'); }); }); /* apply via a single rule */ var rule = document.createElement('style'); rule.textContent = '.content-list li::before { animation: var(--bt-animation, none); }'; document.head.appendChild(rule); } /* -------------------------------------------------- */ /* Init on DOMContentLoaded */ /* -------------------------------------------------- */ document.addEventListener('DOMContentLoaded', function () { initScrollSpy(); initDocsSearch(); initBackToTop(); initSidebarClose(); initReveal(); initSmoothScroll(); initActiveNav(); initBulletThrob(); }); })();