1// ============================================= 2// GLOBAL NAV â Injected nav + drawer + all logic 3// ============================================= 4 5(function() { 6 'use strict'; 7 8 // Check if nav already exists 9 const alreadyExists = !!document.getElementById('topNav'); 10 11 // Determine if this is an inner page (not homepage) 12 const isInner = !document.body.classList.contains('is-homepage'); 13 const navClass = isInner ? 'top-nav top-nav--inner scrolled' : 'top-nav'; 14 15 // ---- SVG icon fragments ---- 16 const svgArrow = '<svg class="sd-link-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>'; 17 const svgSearch = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>'; 18 const svgSun = '<svg class="sun-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>'; 19 const svgMoon = '<svg class="moon-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>';
20 const svgChevron = '<svg class="nav-index-arrow" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 4l4 4-4 4"/></svg>'; 21 const svgSmallArrow = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>'; 22 23 // ---- NAV HTML ---- 24 const navHTML = ` 25 <nav class="${navClass}" id="topNav"> 26 <div class="nav-left"> 27 <a class="nav-logo" href="index.html"> 28 <img src="assets/energy_analytics_logomark_2.svg" alt="NCEA"> 29 </a> 30 <div class="nav-index" id="navIndexBtn"> 31 <span class="nav-index-dot"></span> 32 <span>Leading Energy Indicators: 72.4</span> 33 ${svgChevron} 34 </div> 35 </div> 36 <div class="nav-links"> 37 <a class="nav-item" data-drawer="research" href="research-library.html">Research<sup>01</sup></a> 38 <a class="nav-item" data-drawer="events" href="events.html">Events<sup>02</sup></a> 39 <a class="nav-item" data-drawer="experts" href="advisory-board.html">Experts<sup>03</sup></a> 40 <a class="nav-item" data-drawer="about" href="about.html">About<sup>
4004</sup></a> 41 <a class="nav-text-link" href="subscribe.html">Subscribe</a> 42 <a class="nav-text-link" href="get-involved.html">Get Involved</a> 43 <div class="nav-divider"></div> 44 <div class="nav-icons"> 45 <button class="nav-icon-btn" onclick="if(typeof openSearchModal==='function')openSearchModal()" aria-label="Search"> 46 ${svgSearch} 47 </button> 48 <button class="nav-hamburger" id="navHamburger" aria-label="Menu"> 49 <img class="ham-dark" src="assets/hamburger-dark.svg" alt=""> 50 <img class="ham-white" src="assets/hamburger-white.svg" alt=""> 51 <svg class="ham-x" viewBox="0 0 20 20" fill="none"><line x1="4" y1="4" x2="16" y2="16"/><line x1="16" y1="4" x2="4" y2="16"/></svg> 52 </button> 53 </div> 54 </div> 55 </nav> 56 `; 57 58 // ---- DRAWER HTML ---- 59 const drawerHTML = ` 60 <div class="drawer-scrim" id="drawerScrim"></div> 61 <aside class="side-drawer" id="sideDrawer" data-active-panel="research"> 62 <div class="side-drawer-spacer"></div> 63 <div class="side-drawer-body"> 64 65 <!-- TABS â panel switching lives inside drawer --> 66 <div class="sd-tabs"> 67 <button class="sd-tab is-active" data-panel-target="research">Research</button> 68 <button class="sd-tab" data-panel-target="events">Events</button> 69 <button class="sd-tab" data-panel-target="experts">Experts</button> 70 <button class="sd-tab" data-panel-target="about">About</button> 71 </div> 72 73 <div class="sd-content"> 74 75 <!-- RESEARCH --> 76 <div class="side-drawer-panel is-active" data-panel="research"> 77 <div class="sd-label">Research</div> 78 <div class="sd-links"> 79 <a href="esi-report.html" class="sd-link" style="font-weight:500">Energy Security Index<span class="sd-link-count">New</span>${svgArrow}</a> 80 <a href="research-library.html" class="sd-link">All Publications${svgArrow}</a> 81 <a href="research-library.html?type=studies" class="sd-link">Studies${svgArrow}</a> 82 <a href="research-library.html?type=reports" class="sd-link">Reports${svgArrow}</a> 83 <a href="research-library.html?type=analysis" class="sd-link">Analysis${svgArrow}</a> 84 <a href="research-library.html?type=testimony" class="sd-link">Testimony${svgArrow}</a> 85 <a href="topics.html" class="sd-link">Topics${svgArrow}</a> 86 </div> 87 </div> 88 89 <!-- EVENTS --> 90 <div class="side-drawer-panel" data-panel="events"> 91 <div class="sd-label">Events</div> 92 <div class="sd-links"> 93 <a href="events.html" class="sd-link">All Events${svgArrow}</a> 94 <a href="events.html#upcoming" class="sd-link">Upcoming${svgArrow}</a> 95 <a href="events.html#past" class="sd-link">Past Events${svgArrow}</a> 96 <a href="events.html#webinars" class="sd-link">Webinars${svgArrow}</a> 97 </div> 98 </div> 99 100 <!-- SCHOLARS --> 101 <div class="side-drawer-panel" data-panel="experts"> 102 <div class="sd-label">Experts</div> 103 <div class="sd-links"> 104 <a href="advisory-board.html" class="sd-link">Full Advisory Board${svgArrow}</a> 105 <a href="scholar-detail.html?slug=mark-p-mills" class="sd-link">Mark P. Mills${svgArrow}</a> 106 <a href="advisory-board.html#fellows" class="sd-link">Research Fellows${svgArrow}</a> 107 <a href="press-kit.html#scholars" class="sd-link">Expert Directory${svgArrow}</a> 108 <a href="media-requests.html" class="sd-link">Media Requests${svgArrow}</a> 109 <a href="get-involved.html" class="sd-link">Become a Scholar${svgArrow}</a> 110 </div> 111 </div> 112 113 <!-- ABOUT --> 114 <div class="side-drawer-panel" data-panel="about"> 115 <div class="sd-label">About</div> 116 <div class="sd-links"> 117 <a href="about.html" class="sd-link">Our Mission${svgArrow}</a> 118 <a href="about.html#approach" class="sd-link">Approach${svgArrow}</a> 119 <a href="about.html#team" class="sd-link">Leadership${svgArrow}</a> 120 <a href="contact.html" class="sd-link">Contact${svgArrow}</a> 121 <a href="request-briefing.html" class="sd-link">Request a Briefing${svgArrow}</a> 122 <a href="energy-index.html" class="sd-link">Energy Security Index${svgArrow}</a> 123 <a href="subscribe.html" class="sd-link">Subscribe${svgArrow}</a> 124 </div> 125 </div> 126 127 </div><!-- end sd-content --> 128 </div> 129 </aside> 130 `; 131 132 // ---- INJECT (only if not already present) ---- 133 if (!alreadyExists) { 134 // Remove old nav if present 135 const oldNav = document.getElementById('topNav'); 136 if (oldNav) oldNav.remove(); 137 138 // Insert at very start of body 139 document.body.insertAdjacentHTML('afterbegin', drawerHTML); 140 document.body.insertAdjacentHTML('afterbegin', navHTML); 141 } 142 143 // ---- SCROLL BEHAVIOR ---- 144 const topNav = document.getElementById('topNav'); 145 if (topNav && !isInner) { 146 let lastY = 0; 147 window.addEventListener('scroll', () => { 148 const y = window.scrollY; 149 topNav.classList.toggle('scrolled', y > 80); 150 if (y > 100 && y > lastY + 2) topNav.classList.add('hidden'); 151 else if (y < lastY - 2 || y < 60) topNav.classList.remove('hidden'); 152 lastY = y; 153 }); 154 } else if (topNav && isInner) { 155 // Inner pages: always scrolled look, hide/show on scroll 156 topNav.classList.add('scrolled'); 157 let lastY = 0; 158 window.addEventListener('scroll', () => { 159 const y = window.scrollY; 160 if (y > 100 && y > lastY + 2) topNav.classList.add('hidden'); 161 else if (y < lastY - 2 || y < 60) topNav.classList.remove('hidden'); 162 lastY = y; 163 }); 164 } 165 166 // ---- DRAWER LOGIC ---- 167 const drawer = document.getElementById('sideDrawer'); 168 const scrim = document.getElementById('drawerScrim'); 169 const hamburger = document.getElementById('navHamburger'); 170 const panels = document.querySelectorAll('.side-drawer-panel'); 171 const drawerTabs = document.querySelectorAll('.sd-tab[data-panel-target]'); 172 const navItems = document.querySelectorAll('.nav-item[data-drawer]'); 173 174 if (!drawer || !hamburger) return; 175 176 let isOpen = false;
177 let hoverOpenTimeout = null; 178 let currentPanel = 'research'; 179 180 function switchPanel(name) { 181 if (name === currentPanel && isOpen) return; 182 currentPanel = name; 183 panels.forEach(p => p.classList.toggle('is-active', p.dataset.panel === name)); 184 drawerTabs.forEach(t => t.classList.toggle('is-active', t.dataset.panelTarget === name)); 185 navItems.forEach(i => i.classList.toggle('is-drawer-active', i.dataset.drawer === name)); 186 drawer.setAttribute('data-active-panel', name); 187 const panel = document.querySelector(`.side-drawer-panel[data-panel="${name}"]`); 188 if (panel && isOpen) animatePanel(panel); 189 } 190 191 function animatePanel(panel) { 192 if (typeof gsap === 'undefined') return; 193 const links = panel.querySelectorAll('.sd-link'); 194 const labels = panel.querySelectorAll('.sd-label'); 195 const tl = gsap.timeline({ defaults: { ease: 'power2.out' } }); 196 if (labels.length) tl.fromTo(labels, { opacity: 0, x: -8 }, { opacity: 1, x: 0, duration: 0.25 }, 0); 197 if (links.length) tl.fromTo(links, { opacity: 0, x: -10 }, { opacity: 1, x: 0, duration: 0.3, stagger: 0.035 }, 0.04); 198 } 199 200 function openDrawer(panelName) { 201 if (isOpen) { if (panelName) switchPanel(panelName); return; } 202 isOpen = true; 203 if (panelName) switchPanel(panelName); 204 drawer.classList.add('is-open'); 205 scrim.classList.add('is-active'); 206 document.body.classList.add('drawer-open'); 207 document.body.style.overflow = 'hidden'; 208 requestAnimationFrame(() => { 209 const active = document.querySelector('.side-drawer-panel.is-active'); 210 if (active) animatePanel(active); 211 }); 212 } 213 214 function closeDrawer() { 215 if (!isOpen) return; 216 isOpen = false; 217 drawer.classList.remove('is-open'); 218 scrim.classList.remove('is-active'); 219 document.body.classList.remove('drawer-open'); 220 document.body.style.overflow = ''; 221 navItems.forEach(i => i.classList.remove('is-drawer-active')); 222 } 223 224 // Hamburger toggles drawer 225 hamburger.addEventListener('click', () => { 226 isOpen ? closeDrawer() : openDrawer('research'); 227 }); 228 229 // Top nav items: hover opens/switches on desktop, click navigates 230 navItems.forEach(item => { 231 item.addEventListener('mouseenter', () => { 232 const panel = item.dataset.drawer; 233 if (isOpen) { switchPanel(panel); } 234 else { hoverOpenTimeout = setTimeout(() => openDrawer(panel), 120); } 235 }); 236 item.addEventListener('mouseleave', () => clearTimeout(hoverOpenTimeout)); 237 item.addEventListener('click', (e) => { 238 if (isOpen) { e.preventDefault(); closeDrawer(); } 239 }); 240 }); 241 242 // Tabs inside drawer for mobile (hidden on desktop via CSS) 243 drawerTabs.forEach(tab => { 244 tab.addEventListener('click', () => { 245 switchPanel(tab.dataset.panelTarget); 246 }); 247 }); 248 249 scrim.addEventListener('click', closeDrawer); 250 document.addEventListener('keydown', e => { if (e.key === 'Escape' && isOpen) closeDrawer(); }); 251 252 // ---- LEADING ENERGY INDICATORS MODAL ---- 253 (function() { 254 // Inject modal HTML 255 var modalHTML = '<div class="lei-modal-overlay" id="leiOverlay">' + 256 '<div class="lei-modal">' + 257 '<div class="lei-modal__header">' + 258 '<span class="lei-modal__title">Leading Energy Indicators</span>' + 259 '<button class="lei-modal__close" id="leiClose" aria-label="Close">' + 260 '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>' + 261 '</button>' + 262 '</div>' + 263 '<iframe class="lei-modal__iframe" id="leiIframe" src="about:blank" frameborder="0" allow="fullscreen"></iframe>' + 264 '</div>' + 265 '</div>'; 266 document.body.insertAdjacentHTML('beforeend', modalHTML); 267 268 // Inject modal CSS 269 var modalCSS = document.createElement('style'); 270 modalCSS.textContent = '' + 271 '.lei-modal-overlay{position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,0.6);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;opacity:0;pointer-events:none;transition:opacity 0.3s cubic-bezier(0.16,1,0.3,1)}' + 272 '.lei-modal-overlay.is-open{opacity:1;pointer-events:auto}' + 273 '.lei-modal{width:calc(100vw - 48px);height:calc(100vh - 48px);max-width:1600px;max-height:1000px;background:var(--bg,#f8f5e7);border-radius:8px;overflow:hidden;display:flex;flex-direction:column;transform:scale(0.96) translateY(12px);transition:transform 0.35s cubic-bezier(0.16,1,0.3,1);box-shadow:0 24px 80px rgba(0,0,0,0.25)}' + 274 '.lei-modal-overlay.is-open .lei-modal{transform:scale(1) translateY(0)}' + 275 '.lei-modal__header{display:flex;align-items:center;justify-content:space-between;padding:14px 20px;border-bottom:1px solid var(--border,rgba(26,26,26,0.1));flex-shrink:0}' + 276 '.lei-modal__title{font-size:13px;font-weight:500;letter-spacing:0.02em;color:var(--text,#1a1a1a);font-family:var(--font-body,-apple-system,sans-serif)}' + 277 '.lei-modal__close{width:32px;height:32px;border-radius:50%;border:none;background:var(--tag-bg,rgba(26,26,26,0.05));cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background 0.2s}' + 278 '.lei-modal__close:hover{background:rgba(26,26,26,0.12)}' + 279 '.lei-modal__close svg{width:16px;height:16px;color:var(--text,#1a1a1a)}' + 280 '.lei-modal__iframe{flex:1;width:100%;border:none}' + 281 '@media(max-width:600px){.lei-modal{width:100vw;height:100vh;max-width:none;max-height:none;border-radius:0}.lei-modal-overlay{padding:0}}'; 282 document.head.appendChild(modalCSS); 283 284 // Click handler 285 var navIndexBtn = document.getElementById('navIndexBtn'); 286 var leiOverlay = document.getElementById('leiOverlay'); 287 var leiClose = document.getElementById('leiClose'); 288 var leiIframe = document.getElementById('leiIframe'); 289 290 function openLEI() {
291 leiIframe.src = 'https://fluffy-nougat-1420dd.netlify.app/'; 292 leiOverlay.classList.add('is-open'); 293 document.body.style.overflow = 'hidden'; 294 } 295 function closeLEI() { 296 leiOverlay.classList.remove('is-open'); 297 document.body.style.overflow = ''; 298 setTimeout(function() { leiIframe.src = 'about:blank'; }, 350); 299 } 300 301 if (navIndexBtn) navIndexBtn.addEventListener('click', openLEI); 302 if (leiClose) leiClose.addEventListener('click', closeLEI); 303 if (leiOverlay) leiOverlay.addEventListener('click', function(e) { 304 if (e.target === leiOverlay) closeLEI(); 305 }); 306 document.addEventListener('keydown', function(e) { 307 if (e.key === 'Escape' && leiOverlay && leiOverlay.classList.contains('is-open')) closeLEI(); 308 }); 309 })(); 310 311 // ============================================= 312 // GLOBAL SEARCH MODAL â inject on all pages 313 // ============================================= 314 (function() { 315 if (document.getElementById('searchModalOverlay')) return; 316 317 var searchHTML = '<div class="search-modal-overlay" id="searchModalOverlay">' + 318 '<div class="search-modal" id="searchModal">' + 319 '<div class="search-modal__header">' + 320 '<div class="search-modal__input-wrap">' + 321 '<svg class="search-modal__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>
321' + 322 '<input type="text" class="search-modal__input" id="searchModalInput" placeholder="Search research, experts, events..." autocomplete="off">' + 323 '</div>' + 324 '<span class="search-modal__kbd">ESC</span>' + 325 '<button class="search-modal__close" id="searchModalClose"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6L6 18M6 6l12 12"/></svg></button>' + 326 '</div>' + 327 '<div class="search-modal__toggle">' + 328 '<button class="search-modal__toggle-btn active" data-view="results"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 21l-4.35-4.35M11 19a8 8 0 100-16 8 8 0 000 16z"/></svg> Search Results</button>' + 329 '<button class="search-modal__toggle-btn" data-view="ai"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/></svg> AI Answer <span class="search-modal__toggle-badge">Beta</span></button>' + 330 '</div>' + 331 '<div class="search-modal__body">' + 332 '<div class="search-view active" id="searchResultsView"><div class="search-empty" id="searchEmptyState"><svg class="search-empty__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg><p class="search-empty__title">Search NCEA</p><p class="search-empty__hint">Find research, experts, events, and media</p><div class="search-quick-tags"><span class="search-quick-tag" data-query="grid reliability">Grid reliability</span><span class="search-quick-tag" data-query="nuclear">Nuclear</span><span class="search-quick-tag" data-query="renewable costs">Renewable costs</span></div></div><div id="searchResultsContainer" style="display:none"></div></div>' + 333 '<div class="search-view" id="aiAnswerView"><div class="search-empty" id="aiEmptyState"><svg class="search-empty__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/></svg><p class="search-empty__title">Ask a question</p><p class="search-empty__hint">Get an AI-powered answer grounded in NCEA research</p></div><div id="aiAnswerContainer" style="display:none"></div></div>' + 334 '</div></div></div>'; 335 336 document.body.insertAdjacentHTML('beforeend', searchHTML); 337 338 var overlay = document.getElementById('searchModalOverlay'); 339 var input = document.getElementById('searchModalInput'); 340 var emptyState = document.getElementById('searchEmptyState'); 341 var resultsContainer = document.getElementById('searchResultsContainer'); 342 var aiEmpty = document.getElementById('aiEmptyState'); 343 var aiContainer = document.getElementById('aiAnswerContainer'); 344 var resultsView = document.getElementById('searchResultsView'); 345 var aiView = document.getElementById('aiAnswerView'); 346 347 window.openSearchModal = function(q) { 348 overlay.classList.add('is-open'); 349 document.body.style.overflow = 'hidden'; 350 setTimeout(function() { input.focus(); if (q) { input.value = q; doSearch(q); } }, 100); 351 }; 352 window.closeSearchModal = function() { 353 overlay.classList.remove('is-open'); 354 document.body.style.overflow = ''; 355 setTimeout(function() { input.value = ''; emptyState.style.display='block'; resultsContainer.style.display='none'; resultsContainer.innerHTML=''; aiEmpty.style.display='block'; aiContainer.style.display='none'; aiContainer.innerHTML=''; }, 300); 356 }; 357 358 overlay.addEventListener('click', function(e) { if (e.target === overlay) window.closeSearchModal(); }); 359 document.getElementById('searchModalClose').addEventListener('click', window.closeSearchModal); 360 361 document.querySelectorAll('.search-modal__toggle-btn').forEach(function(btn) { 362 btn.addEventListener('click', function() { 363 document.querySelectorAll('.search-modal__toggle-btn').forEach(function(b){b.classList.remove('active');}); 364 btn.classList.add('active'); 365 if (btn.dataset.view === 'results') { resultsView.classList.add('active'); aiView.classList.remove('active'); } 366 else { resultsView.classList.remove('active'); aiView.classList.add('active'); } 367 }); 368 }); 369 370 document.querySelectorAll('.search-quick-tag').forEach(function(tag) { 371 tag.addEventListener('click', function() { input.value = tag.dataset.query; input.focus(); doSearch(tag.dataset.query); }); 372 }); 373 374 document.addEventListener('keydown', function(e) { 375 if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault(); overlay.classList.contains('is-open') ? window.closeSearchModal() : window.openSearchModal(); } 376 if (e.key === 'Escape' && overlay.classList.contains('is-open')) window.closeSearchModal(); 377 }); 378 379 // Search data + logic (works on all pages) 380 var searchItems = [ 381 {cat:'Research',title:'The Real Cost of Renewable Mandates',meta:'Study \u2022 Mark P. Mills \u2022 Jan 2026',href:'article.html'}, 382 {cat:'Research',title:'Grid Reliability Under High Renewable Penetration',meta:'Study \u2022 Neil Atkinson \u2022 Dec 2025',href:'article.html'}, 383 {cat:'Research',title:'Nuclear Energy Economics: A Fresh Look',meta:'Issue Brief \u2022 Lars Schernikau \u2022 Nov 2025',href:'article.html'}, 384 {cat:'Research',title:'U.S. Energy Security Index',meta:'Flagship Report \u2022 Q4 2025',href:'esi-report.html'}, 385 {cat:'Research',title:'The AI Energy Surge: Quantifying Compute Demand',meta:'Study \u2022 Mark P. Mills \u2022 2025',href:'article.html'}, 386 {cat:'Experts',title:'Mark P. Mills',meta:'Executive Director \u2022 Energy Policy',href:'scholar-detail.html?slug=mark-p-mills'}, 387 {cat:'Experts',title:'Neil Atkinson',meta:'Visiting Fellow \u2022 Oil Markets',href:'scholar-detail.html?slug=neil-atkinson'},
388 {cat:'Experts',title:'Paul H. Tice',meta:'Advisory Board \u2022 Energy Finance',href:'scholar-detail.html?slug=paul-tice'}, 389 {cat:'Experts',title:'Brent Bennett, PhD',meta:'Senior Fellow \u2022 Grid Policy',href:'scholar-detail.html?slug=brent-bennett'}, 390 {cat:'Experts',title:'Steven Koonin',meta:'Advisory Board \u2022 University Professor, NYU',href:'scholar-detail.html?slug=steven-koonin'}, 391 {cat:'Events',title:'Energy Outlook 2026: What\u2019s Ahead',meta:'Webinar \u2022 Mar 2026',href:'event-detail.html'}, 392 {cat:'Events',title:'Congressional Testimony on Grid Reliability',meta:'Testimony \u2022 Feb 2026',href:'events.html'}, 393 {cat:'Media',title:'CNBC: The Hidden Costs of the Energy Transition',meta:'CNBC \u2022 Feb 2026',href:'media.html'}, 394 {cat:'Media',title:'WSJ Op-Ed: Why Energy Policy Needs Better Data',meta:'Wall Street Journal \u2022 2026',href:'media.html'} 395 ]; 396 397 function doSearch(q) { 398 if (!q || !q.trim()) { 399 emptyState.style.display = 'block'; 400 resultsContainer.style.display = 'none'; 401 resultsContainer.innerHTML = ''; 402 return; 403 } 404 var lower = q.toLowerCase(); 405 var matches = searchItems.filter(function(item) { 406 return item.title.toLowerCase().indexOf(lower) !== -1 || item.meta.toLowerCase().indexOf(lower) !== -1 || item.cat.toLowerCase().indexOf(lower) !== -1; 407 }); 408 emptyState.style.display = 'none'; 409 resultsContainer.style.display = 'block'; 410 if (!matches.length) { 411 resultsContainer.innerHTML = '<p style="padding:40px 0;text-align:center;color:rgba(26,26,26,0.4);font-size:14px">No results found for \u201c' + q + '\u201d</p>'; 412 return; 413 } 414 var groups = {}; 415 matches.forEach(function(m) { if (!groups[m.cat]) groups[m.cat] = []; groups[m.cat].push(m); }); 416 var html = ''; 417 Object.keys(groups).forEach(function(cat) { 418 html += '<div style="margin-bottom:24px"><div style="display:flex;justify-content:space-between;padding:8px 0;border-bottom:1px solid rgba(26,26,26,0.08);margin-bottom:8px"><span style="font-size:11px;font-weight:600;letter-spacing:0.1em;text-transform:uppercase;color:rgba(26,26,26,0.35)">' + cat + '</span><span style="font-size:12px;color:rgba(26,26,26,0.3)">' + groups[cat].length + '</span></div>'; 419 groups[cat].forEach(function(item) { 420 html += '<a href="' + item.href + '" style="display:block;padding:12px 0;border-bottom:1px solid rgba(26,26,26,0.04);text-decoration:none;color:inherit"><div style="font-size:15px;font-weight:500;margin-bottom:2px">' + item.title + '</div><div style="font-size:12px;color:rgba(26,26,26,0.4)">' + item.meta + '</div></a>'; 421 }); 422 html += '</div>'; 423 }); 424 resultsContainer.innerHTML = html; 425 } 426 427 var searchTimeout; 428 input.addEventListener('input', function() { 429 clearTimeout(searchTimeout); 430 searchTimeout = setTimeout(function() { doSearch(input.value); }, 150); 431 }); 432 input.addEventListener('keydown', function(e) { 433 if (e.key === 'Enter') { e.preventDefault(); doSearch(input.value); } 434 }); 435 })(); 436 437 438 439 440 // ================================================ 441 // PAGE TRANSITION â olive panel + logomark 442 // ================================================ 443 (function(){ 444 var css = document.createElement('style'); 445 css.textContent = '.ptr{position:fixed;inset:0;z-index:99999;pointer-events:none;overflow:hidden}.ptr__panel{position:absolute;inset:0;background:#817828;will-change:transform}.ptr__logo{position:absolute;top:50%;left:50%;width:112px;height:112px;margin:-56px 0 0 -56px;opacity:0;will-change:opacity}'; 446 document.head.appendChild(css); 447 448 var el = document.createElement('div'); 449 el.className = 'ptr'; 450 el.innerHTML = '<div class="ptr__panel"><img class="ptr__logo" src="assets/logomark.svg" alt=""></div>'; 451 document.body.appendChild(el); 452 453 var panel = el.querySelector('.ptr__panel'); 454 var logo = el.querySelector('.ptr__logo'); 455 var busy = false; 456 457 // REVEAL â arriving from transition 458 if (sessionStorage.getItem('ptr_active')) { 459 sessionStorage.removeItem('ptr_active'); 460 document.documentElement.classList.remove('ptr-cover'); 461 if (typeof gsap !== 'undefined') { 462 gsap.set(panel, { y: '0%' }); 463 gsap.set(logo, { opacity: 1 }); 464 gsap.to(logo, { opacity: 0, duration: 0.2, delay: 0.05, ease: 'none' }); 465 gsap.to(panel, { y: '-100%', duration: 0.85, delay: 0.12, ease: 'power4.inOut' }); 466 } 467 // Hard fallback 468 setTimeout(function() { panel.style.transform = 'translateY(-100%)'; logo.style.opacity = '0'; }, 1500); 469 } else { 470 panel.style.transform = 'translateY(-100%)'; 471 } 472 473 // COVER + NAVIGATE on link click 474 document.addEventListener('click', function(e) { 475 var link = e.target.closest('a'); 476 if (!link || busy) return; 477 var href = link.getAttribute('href'); 478 if (!href) return; 479 if (/^(http|#|javascript|mailto|tel)/.test(href)) return; 480 if (e.metaKey || e.ctrlKey || e.shiftKey || link.target === '_blank') return; 481 482 e.preventDefault(); 483 busy = true; 484 sessionStorage.setItem('ptr_active', '1'); 485 486 if (typeof gsap !== 'undefined') { 487 gsap.set(panel, { y: '100%' }); 488 gsap.set(logo, { opacity: 0 }); 489 gsap.to(panel, { y: '0%', duration: 0.8, ease: 'power4.inOut' }); 490 gsap.to(logo, { opacity: 1, duration: 0.4, delay: 0.5, ease: 'power2.out' }); 491 } 492 // Navigate after animation (hard timeout, not onComplete) 493 setTimeout(function() { window.location.href = href; }, 1200); 494 }); 495 496 // Browser back/forward â bfcache restore 497 window.addEventListener('pageshow', function(e) { 498 if (e.persisted) { 499 busy = false;
500 sessionStorage.removeItem('ptr_active'); 501 document.documentElement.classList.remove('ptr-cover'); 502 panel.style.transform = 'translateY(-100%)'; 503 logo.style.opacity = '0'; 504 } 505 }); 506 })(); 507 508})(); 509 510// Fallback â ensure nav exists after DOM is ready 511document.addEventListener('DOMContentLoaded', function() { 512 if (!document.getElementById('topNav')) { 513 console.warn('NCEA: topNav missing after DOMContentLoaded, page may need hard refresh'); 514 } 515});
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.