|
16 | 16 | let saved = localStorage.getItem(THEME_KEY) || (prefersDark ? 'dark' : 'light'); |
17 | 17 | applyTheme(saved); |
18 | 18 |
|
19 | | - // Wait for DOM |
20 | | - document.addEventListener('DOMContentLoaded', () => { |
21 | | - // Build fallback nav if markup missing |
22 | | - const navs = document.querySelectorAll('nav.navbar'); |
| 19 | + // Init routine |
| 20 | + function initNavAndTheme() { |
| 21 | + try { |
| 22 | + console.debug('nav-theme: init start'); |
| 23 | + // Build fallback nav if markup missing |
| 24 | + const navs = document.querySelectorAll('nav.navbar'); |
23 | 25 | navs.forEach((oldNav) => { |
24 | 26 | // If .site-nav already present, skip |
25 | 27 | if (oldNav.classList.contains('site-nav')) return; |
|
77 | 79 | oldNav.replaceWith(newNav); |
78 | 80 | }); |
79 | 81 |
|
80 | | - // Wire up interactions |
81 | | - const navToggle = document.getElementById('nav-toggle'); |
82 | | - const menu = document.getElementById('primary-menu'); |
83 | | - const themeBtn = document.getElementById('theme-toggle'); |
84 | | - |
85 | | - if (navToggle && menu) { |
86 | | - navToggle.addEventListener('click', () => { |
87 | | - const open = navToggle.getAttribute('aria-expanded') === 'true'; |
88 | | - navToggle.setAttribute('aria-expanded', String(!open)); |
89 | | - if (open) { |
90 | | - menu.hidden = true; |
| 82 | + // Wire up interactions per nav (support multiple navs) |
| 83 | + const siteNavs = document.querySelectorAll('nav.site-nav'); |
| 84 | + siteNavs.forEach((nav) => { |
| 85 | + const navToggle = nav.querySelector('#nav-toggle'); |
| 86 | + const menu = nav.querySelector('#primary-menu'); |
| 87 | + const themeBtn = nav.querySelector('#theme-toggle'); |
| 88 | + |
| 89 | + // Ensure menu visibility based on viewport |
| 90 | + const innerLinks = menu ? menu.querySelector('.nav-links') : null; |
| 91 | + function syncForViewport() { |
| 92 | + if (window.innerWidth >= 720) { |
| 93 | + // show menu on desktop |
| 94 | + if (menu) menu.hidden = false; |
| 95 | + if (innerLinks) innerLinks.classList.remove('active'); |
| 96 | + if (navToggle) navToggle.setAttribute('aria-expanded', 'false'); |
91 | 97 | } else { |
92 | | - menu.hidden = false; |
93 | | - const first = menu.querySelector('a,button,[tabindex]:not([tabindex="-1"])'); |
94 | | - if (first) first.focus(); |
| 98 | + // mobile: hide menu by default |
| 99 | + if (menu) menu.hidden = true; |
| 100 | + if (innerLinks) innerLinks.classList.remove('active'); |
| 101 | + if (navToggle) navToggle.setAttribute('aria-expanded', 'false'); |
95 | 102 | } |
96 | | - }); |
| 103 | + } |
97 | 104 |
|
98 | | - // close on outside click |
99 | | - document.addEventListener('click', (e) => { |
100 | | - if (!menu.contains(e.target) && !navToggle.contains(e.target) && !menu.hidden) { |
101 | | - menu.hidden = true; |
102 | | - navToggle.setAttribute('aria-expanded', 'false'); |
103 | | - } |
104 | | - }); |
105 | | - |
106 | | - // Esc to close |
107 | | - document.addEventListener('keydown', (e) => { |
108 | | - if (e.key === 'Escape' && !menu.hidden) { |
109 | | - menu.hidden = true; |
110 | | - navToggle.setAttribute('aria-expanded', 'false'); |
111 | | - navToggle.focus(); |
112 | | - } |
113 | | - }); |
| 105 | + syncForViewport(); |
| 106 | + window.addEventListener('resize', syncForViewport); |
| 107 | + |
| 108 | + if (navToggle && menu) { |
| 109 | + navToggle.addEventListener('click', (e) => { |
| 110 | + e.stopPropagation(); |
| 111 | + console.debug('nav-theme: navToggle click, innerWidth=', window.innerWidth, 'menu.hidden=', menu ? menu.hidden : 'no-menu'); |
| 112 | + const open = navToggle.getAttribute('aria-expanded') === 'true'; |
| 113 | + navToggle.setAttribute('aria-expanded', String(!open)); |
| 114 | + if (open) { |
| 115 | + menu.hidden = true; |
| 116 | + if (innerLinks) innerLinks.classList.remove('active'); |
| 117 | + } else { |
| 118 | + menu.hidden = false; |
| 119 | + if (innerLinks) innerLinks.classList.add('active'); |
| 120 | + const first = menu.querySelector('a,button,[tabindex]:not([tabindex="-1"])'); |
| 121 | + if (first) first.focus(); |
| 122 | + } |
| 123 | + }); |
| 124 | + |
| 125 | + // close on outside click |
| 126 | + document.addEventListener('click', (e) => { |
| 127 | + if (!menu.contains(e.target) && !navToggle.contains(e.target) && !menu.hidden && window.innerWidth < 720) { |
| 128 | + menu.hidden = true; |
| 129 | + if (innerLinks) innerLinks.classList.remove('active'); |
| 130 | + navToggle.setAttribute('aria-expanded', 'false'); |
| 131 | + } |
| 132 | + }); |
| 133 | + |
| 134 | + // Esc to close |
| 135 | + document.addEventListener('keydown', (e) => { |
| 136 | + if (e.key === 'Escape' && !menu.hidden) { |
| 137 | + menu.hidden = true; |
| 138 | + navToggle.setAttribute('aria-expanded', 'false'); |
| 139 | + navToggle.focus(); |
| 140 | + } |
| 141 | + }); |
| 142 | + } |
| 143 | + |
| 144 | + // Theme toggle wiring |
| 145 | + if (themeBtn) { |
| 146 | + themeBtn.setAttribute('aria-pressed', document.documentElement.getAttribute('data-theme') === 'dark'); |
| 147 | + themeBtn.addEventListener('click', (e) => { |
| 148 | + e.stopPropagation(); |
| 149 | + console.debug('nav-theme: themeBtn click, before theme=', document.documentElement.getAttribute('data-theme')); |
| 150 | + const cur = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light'; |
| 151 | + const next = cur === 'dark' ? 'light' : 'dark'; |
| 152 | + applyTheme(next); |
| 153 | + localStorage.setItem(THEME_KEY, next); |
| 154 | + themeBtn.setAttribute('aria-pressed', next === 'dark'); |
| 155 | + console.debug('nav-theme: themeBtn click, after theme=', document.documentElement.getAttribute('data-theme')); |
| 156 | + }); |
| 157 | + } |
| 158 | + }); |
| 159 | + console.debug('nav-theme: init done, navs=', siteNavs.length); |
| 160 | + } catch (err) { |
| 161 | + console.error('nav-theme: init error', err); |
114 | 162 | } |
| 163 | + } |
115 | 164 |
|
116 | | - // Theme toggle wiring |
117 | | - if (themeBtn) { |
118 | | - // reflect saved |
119 | | - themeBtn.setAttribute('aria-pressed', document.documentElement.getAttribute('data-theme') === 'dark'); |
120 | | - themeBtn.addEventListener('click', () => { |
121 | | - const cur = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light'; |
122 | | - const next = cur === 'dark' ? 'light' : 'dark'; |
123 | | - applyTheme(next); |
124 | | - localStorage.setItem(THEME_KEY, next); |
125 | | - themeBtn.setAttribute('aria-pressed', next === 'dark'); |
126 | | - }); |
| 165 | + if (document.readyState === 'loading') { |
| 166 | + document.addEventListener('DOMContentLoaded', initNavAndTheme); |
| 167 | + } else { |
| 168 | + // DOM already ready |
| 169 | + initNavAndTheme(); |
| 170 | + } |
| 171 | + |
| 172 | + // Delegated handler as a fallback: catch clicks on any .burger button |
| 173 | + document.addEventListener('click', (e) => { |
| 174 | + const btn = e.target.closest && e.target.closest('.burger'); |
| 175 | + if (!btn) return; |
| 176 | + const nav = btn.closest && btn.closest('nav.site-nav'); |
| 177 | + if (!nav) return; |
| 178 | + const navToggle = btn; |
| 179 | + const menu = nav.querySelector('#primary-menu'); |
| 180 | + const innerLinks = menu ? menu.querySelector('.nav-links') : null; |
| 181 | + console.debug('nav-theme: delegated burger click, innerWidth=', window.innerWidth, 'menu.hidden=', menu ? menu.hidden : 'no-menu'); |
| 182 | + const open = navToggle.getAttribute('aria-expanded') === 'true'; |
| 183 | + navToggle.setAttribute('aria-expanded', String(!open)); |
| 184 | + if (open) { |
| 185 | + if (menu) menu.hidden = true; |
| 186 | + if (innerLinks) innerLinks.classList.remove('active'); |
| 187 | + } else { |
| 188 | + if (menu) menu.hidden = false; |
| 189 | + if (innerLinks) innerLinks.classList.add('active'); |
| 190 | + const first = menu ? menu.querySelector('a,button,[tabindex]:not([tabindex="-1"])') : null; |
| 191 | + if (first) first.focus(); |
127 | 192 | } |
128 | 193 | }); |
129 | 194 | })(); |
0 commit comments