/** * Interactive Table of Contents Controller * - Perfectly Centered Post Layout (No 70/30 split) * - Margin-Docked Auto-Hide / Minimizable Desktop Rail (Dropbox Paper style) * - Mobile / Tablet Floating Action Badge & Slide-Up Bottom Sheet */ document.addEventListener('DOMContentLoaded', () => { const sideToc = document.getElementById('side-toc'); const minimizeBtn = document.getElementById('side-toc-minimize'); const floatingBadge = document.getElementById('floating-toc-badge'); const sheet = document.getElementById('mobile-toc-sheet'); const overlay = document.getElementById('mobile-toc-overlay'); const sheetCloseBtn = document.getElementById('mobile-sheet-close'); // Check user preference for minimized TOC in localStorage let isMinimized = false; try { isMinimized = localStorage.getItem('pengzhan_toc_minimized') === 'true'; } catch (_) {} function applyDesktopState() { if (!sideToc) return; if (window.innerWidth >= 1280) { if (isMinimized) { sideToc.classList.add('is-minimized'); if (floatingBadge) floatingBadge.classList.add('is-visible'); } else { sideToc.classList.remove('is-minimized'); if (floatingBadge) floatingBadge.classList.remove('is-visible'); } } else { // On screens < 1280px, desktop side rail is hidden via CSS, floating badge is available if (floatingBadge) floatingBadge.classList.add('is-visible'); } } if (minimizeBtn) { minimizeBtn.addEventListener('click', () => { isMinimized = true; try { localStorage.setItem('pengzhan_toc_minimized', 'true'); } catch (_) {} applyDesktopState(); }); } // Floating badge click if (floatingBadge) { floatingBadge.addEventListener('click', () => { if (window.innerWidth >= 1280) { // Expand desktop side TOC isMinimized = false; try { localStorage.setItem('pengzhan_toc_minimized', 'false'); } catch (_) {} applyDesktopState(); } else { // Open mobile bottom sheet openMobileSheet(); } }); } function openMobileSheet() { if (sheet && overlay) { sheet.classList.add('is-open'); overlay.classList.add('is-open'); document.body.style.overflow = 'hidden'; } } function closeMobileSheet() { if (sheet && overlay) { sheet.classList.remove('is-open'); overlay.classList.remove('is-open'); document.body.style.overflow = ''; } } if (sheetCloseBtn) sheetCloseBtn.addEventListener('click', closeMobileSheet); if (overlay) overlay.addEventListener('click', closeMobileSheet); // Close mobile sheet on anchor click if (sheet) { sheet.querySelectorAll('.mobile-sheet-link').forEach(link => { link.addEventListener('click', () => { closeMobileSheet(); }); }); } window.addEventListener('resize', applyDesktopState, { passive: true }); applyDesktopState(); // Scroll Spy for Desktop Rail and Mobile List const sideLinks = Array.from(document.querySelectorAll('.side-toc-link')); if (sideLinks.length === 0) return; const headings = sideLinks .map(link => { const id = link.getAttribute('data-id'); const target = document.getElementById(id); return target ? { id, target, link } : null; }) .filter(Boolean); if (headings.length === 0) return; function updateActiveHeading() { const scrollPos = window.scrollY + 140; let currentActive = headings[0]; for (let i = 0; i < headings.length; i++) { const top = headings[i].target.getBoundingClientRect().top + window.scrollY; if (top <= scrollPos) { currentActive = headings[i]; } else { break; } } headings.forEach(item => { if (item === currentActive) { item.link.classList.add('toc-active'); } else { item.link.classList.remove('toc-active'); } }); } window.addEventListener('scroll', updateActiveHeading, { passive: true }); updateActiveHeading(); });