1/** 2 * Sitezen theme scripts. 3 * Mobile nav, submenu toggles, seamless ticker, sticky navbar shadow, 4 * scroll-reveal, reading progress, back-to-top with scroll ring. 5 */ 6(function () { 7 'use strict'; 8 9 var body = document.body; 10 11 /* ---------- Mobile nav toggle ---------- */ 12 var navToggle = document.querySelector('.nav-toggle'); 13 if (navToggle) { 14 navToggle.addEventListener('click', function () { 15 var open = body.classList.toggle('nav-open'); 16 navToggle.setAttribute('aria-expanded', open ? 'true' : 'false'); 17 }); 18 } 19 20 function closeNav() { 21 body.classList.remove('nav-open'); 22 if (navToggle) { 23 navToggle.setAttribute('aria-expanded', 'false'); 24 } 25 } 26 27 document.addEventListener('keyup', function (e) { 28 if (e.key === 'Escape') { 29 closeNav(); 30 } 31 }); 32 33 window.addEventListener('resize', function () { 34 if (window.innerWidth > 860) { 35 closeNav(); 36 } 37 }); 38 39 /* ---------- Submenu toggles (mobile) ---------- */ 40 var parents = document.querySelectorAll('.site-nav .menu li'); 41 parents.forEach(function (li) { 42 var sub = li.querySelector(':scope > ul'); 43 if (!sub) { 44 return; 45 } 46 li.classList.add('menu-item-has-children'); 47 var btn = document.createElement('button'); 48 btn.type = 'button'; 49 btn.className = 'sub-toggle'; 50 btn.setAttribute('aria-expanded', 'false'); 51 btn.innerHTML = '<span class="screen-reader-text">Open submenu</span>'; 52 li.insertBefore(btn, sub); 53 btn.addEventListener('click', function (e) { 54 e.preventDefault(); 55 var open = li.classList.toggle('open'); 56 btn.setAttribute('aria-expanded', open ? 'true' : 'false'); 57 }); 58 }); 59 60 /* ---------- Seamless breaking ticker ---------- */ 61 var move = document.querySelector('.ticker-move'); 62 if (move && move.children.length) { 63 move.innerHTML += move.innerHTML; // duplicate for -50% loop 64 var setSpeed = function () { 65 var half = move.scrollWidth / 2; 66 move.style.animationDuration = Math.max(18, Math.round(half / 85)) + 's'; 67 }; 68 setSpeed(); 69 window.addEventListener('resize', setSpeed); 70 } 71 72 /* ---------- Sticky navbar shadow ---------- */ 73 var navbar = document.getElementById('navbar'); 74 75 /* ---------- Back to top ---------- */ 76 var toTop = document.querySelector('.to-top'); 77 if (toTop) { 78 toTop.addEventListener('click', function () { 79 window.scrollTo({ top: 0, behavior: 'smooth' }); 80 }); 81 } 82 83 /* ---------- Visitor dark mode ---------- */ 84 var modeBtn = document.querySelector('.mode-toggle'); 85 if (modeBtn) { 86 var syncMode = function () { 87 modeBtn.setAttribute('aria-pressed', body.classList.contains('sz-night') ? 'true' : 'false'); 88 }; 89 modeBtn.addEventListener('click', function () { 90 var night = body.classList.toggle('sz-night'); 91 body.classList.toggle('sz-dark', night); 92 try { localStorage.setItem('sitezen-mode', night ? 'dark' : 'light'); } catch (e) {} 93 syncMode(); 94 }); 95 syncMode(); 96 } 97 98 /* ---------- Copy link (share row) ---------- */ 99 document.querySelectorAll('.sz-copy').forEach(function (btn) { 100 btn.addEventListener('click', function () { 101 var link = btn.getAttribute('data-link'); 102 var done = function () { 103 btn.classList.add('copied'); 104 setTimeout(function () { btn.classList.remove('copied'); }, 1600); 105 }; 106 if (navigator.clipboard && navigator.clipboard.writeText) { 107 navigator.clipboard.writeText(link).then(done); 108 } else { 109 var ta = document.createElement('textarea'); 110 ta.value = link; 111 document.body.appendChild(ta); 112 ta.select(); 113 document.execCommand('copy'); 114 document.body.removeChild(ta); 115 done(); 116 } 117 }); 118 }); 119 120 /* ---------- Reveal on scroll (respects reduced motion) ---------- */ 121 var reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; 122 if (!reduceMotion && 'IntersectionObserver' in window) { 123 body.classList.add('sz-anim'); 124 var revealables = document.querySelectorAll('.card, .hero-item, .feat-main, .widget, .home-section .mini-post'); 125 var io = new IntersectionObserver(function (entries) { 126 entries.forEach(function (en) { 127 if (en.isIntersecting) { 128 en.target.classList.add('sz-in'); 129 io.unobserve(en.target); 130 } 131 }); 132 }, { rootMargin: '0px 0px -8% 0px', threshold: 0.05 });
133 revealables.forEach(function (el, i) { 134 el.style.animationDelay = ((i % 5) * 60) + 'ms'; 135 io.observe(el); 136 }); 137 } 138 139 /* ---------- Reading progress (single posts) ---------- */ 140 var progress = document.querySelector('.read-progress span'); 141 142 var ticking = false; 143 function onScroll() { 144 var max = document.documentElement.scrollHeight - window.innerHeight; 145 var pct = max > 0 ? Math.min(100, (window.scrollY / max) * 100) : 0; 146 if (navbar) { 147 navbar.classList.toggle('scrolled', window.scrollY > 8); 148 } 149 if (toTop) { 150 toTop.classList.toggle('show', window.scrollY > 600); 151 toTop.style.setProperty('--p', pct.toFixed(1)); 152 } 153 if (progress) { 154 progress.style.width = pct.toFixed(2) + '%'; 155 } 156 ticking = false; 157 } 158 159 window.addEventListener('scroll', function () { 160 if (!ticking) { 161 window.requestAnimationFrame(onScroll); 162 ticking = true; 163 } 164 }); 165 onScroll(); 166})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.