1// Force light theme (dark mode removed) 2localStorage.removeItem("theme"); 3localStorage.removeItem("ncea-theme"); 4// ============================================= 5// SHARED COMPONENTS JS 6// ============================================= 7 8// Theme toggle 9 10// Nav hide on scroll 11(function() { 12 let lastScrollY = window.scrollY; 13 const topNav = document.getElementById('topNav'); 14 if (!topNav) return; 15 window.addEventListener('scroll', () => { 16 const currentScrollY = window.scrollY; 17 if (currentScrollY > 80) { 18 topNav.classList.add('scrolled'); 19 if (currentScrollY > lastScrollY && currentScrollY > 200) { 20 topNav.classList.add('hidden'); 21 } else { 22 topNav.classList.remove('hidden'); 23 } 24 } else { 25 topNav.classList.remove('scrolled'); 26 topNav.classList.remove('hidden'); 27 } 28 lastScrollY = currentScrollY; 29 }); 30})(); 31 32// Contact drawer 33function openContactDrawer() { 34 const d = document.getElementById('contactDrawer'); 35 if (d) d.classList.add('open'); 36} 37function closeContactDrawer() { 38 const d = document.getElementById('contactDrawer'); 39 if (d) d.classList.remove('open'); 40} 41 // ============================================= 42 // ISLAND NAV (scoped to avoid global const conflicts) 43 // ============================================= 44{ 45 const islandNav = document.getElementById('islandNav'); 46 const islandHam = document.getElementById('islandHam'); 47 const megaContent = document.getElementById('megaContent'); 48 const megaClose = document.getElementById('megaClose'); 49 const navOverlay = document.getElementById('navOverlay'); 50 const navLinks = document.querySelectorAll('.mega-nav-link'); 51 const contentPanels = document.querySelectorAll('.mega-content-panel'); 52 53 let isOpen = false; 54 let currentPanel = 'scholars'; 55 56 function openMega() { 57 if (isOpen) return; 58 isOpen = true; 59 navOverlay.classList.add('is-visible'); 60 islandNav.classList.add('is-open'); 61 gsap.to(megaContent, { opacity: 1, duration: 0.4, delay: 0.3, ease: 'power2.out' }); 62 } 63 64 function closeMega() { 65 if (!isOpen) return; 66 isOpen = false; 67 megaMain.classList.remove('search-active'); 68 gsap.to(megaContent, { opacity: 0, duration: 0.2, ease: 'power2.in' }); 69 setTimeout(() => { 70 islandNav.classList.remove('is-open'); 71 navOverlay.classList.remove('is-visible'); 72 }, 180); 73 } 74 75 const megaMain = document.querySelector('.mega-main'); 76 77 function switchPanel(panelName) { 78 if (panelName === currentPanel) return; 79 currentPanel = panelName; 80 megaMain.classList.toggle('search-active', panelName === 'search'); 81 navLinks.forEach(link => link.classList.toggle('is-active', link.dataset.panel === panelName)); 82 contentPanels.forEach(panel => { 83 if (panel.dataset.panel === panelName) { 84 gsap.to(panel, { opacity: 1, duration: 0.25, ease: 'power2.out', onStart: () => panel.classList.add('is-active') }); 85 } else if (panel.classList.contains('is-active')) { 86 gsap.to(panel, { opacity: 0, duration: 0.15, ease: 'power2.in', onComplete: () => panel.classList.remove('is-active') }); 87 } 88 }); 89 } 90 91 // Open mega with specific panel from top nav 92 window.openMegaWithPanel = function(panelName) { 93 currentPanel = panelName; 94 megaMain.classList.toggle('search-active', panelName === 'search'); 95 navLinks.forEach(link => link.classList.toggle('is-active', link.dataset.panel === panelName)); 96 contentPanels.forEach(panel => { 97 panel.classList.toggle('is-active', panel.dataset.panel === panelName); 98 panel.style.opacity = panel.dataset.panel === panelName ? '1' : '0'; 99 }); 100 openMega(); 101 } 102 103 if(megaClose) megaClose.addEventListener('click', () => { 104 closeMega(); 105 megaOpenedViaHover = false; 106 navOverlay.style.pointerEvents = ''; 107 document.querySelectorAll('.nav-item[data-mega-panel]').forEach(item => item.classList.remove('mega-active')); 108 }); 109 if(navOverlay) navOverlay.addEventListener('click', () => { 110 closeMega(); 111 megaOpenedViaHover = false;
112 navOverlay.style.pointerEvents = ''; 113 document.querySelectorAll('.nav-item[data-mega-panel]').forEach(item => item.classList.remove('mega-active')); 114 }); 115 document.addEventListener('keydown', e => { 116 if (e.key === 'Escape' && isOpen) { 117 closeMega(); 118 megaOpenedViaHover = false; 119 navOverlay.style.pointerEvents = ''; 120 document.querySelectorAll('.nav-item[data-mega-panel]').forEach(item => item.classList.remove('mega-active')); 121 } 122 }); 123 124 navLinks.forEach(link => { 125 link.addEventListener('mouseenter', () => switchPanel(link.dataset.panel)); 126 link.addEventListener('click', e => { e.preventDefault(); switchPanel(link.dataset.panel); }); 127 }); 128 129 // Top nav items: click to open mega menu (removed hover for better UX) 130 document.querySelectorAll('.nav-item[data-mega-panel]').forEach(item => { 131 item.addEventListener('click', (e) => { 132 e.preventDefault(); 133 const panelName = item.dataset.megaPanel; 134 135 if (isOpen) { 136 // If clicking same panel, close. If different, switch panel. 137 const currentActive = document.querySelector('.nav-item.mega-active'); 138 if (currentActive && currentActive.dataset.megaPanel === panelName) { 139 closeMega(); 140 document.querySelectorAll('.nav-item[data-mega-panel]').forEach(i => i.classList.remove('mega-active')); 141 } else { 142 switchPanel(panelName); 143 document.querySelectorAll('.nav-item[data-mega-panel]').forEach(i => i.classList.remove('mega-active')); 144 item.classList.add('mega-active'); 145 } 146 } else { 147 openMegaWithPanel(panelName); 148 document.querySelectorAll('.nav-item[data-mega-panel]').forEach(i => i.classList.remove('mega-active')); 149 item.classList.add('mega-active'); 150 } 151 }); 152 }); 153 154 // Click overlay or outside to close mega 155 if (navOverlay) { 156 if(navOverlay) navOverlay.addEventListener('click', () => { 157 closeMega(); 158 document.querySelectorAll('.nav-item[data-mega-panel]').forEach(item => item.classList.remove('mega-active')); 159 }); 160 } 161 162 // Island hamburger toggle 163 const origIslandClick = () => { 164 if (isOpen) { 165 closeMega(); 166 document.querySelectorAll('.nav-item[data-mega-panel]').forEach(item => item.classList.remove('mega-active')); 167 } else { 168 openMega(); 169 } 170 }; 171 if(islandHam) islandHam.addEventListener('click', origIslandClick); 172 173 // ============================================= 174 // GRID ROW EXPANSION HOVER EFFECT 175 // ============================================= 176 function setupRowExpansion(gridSelector, cardSelector) { 177 const grid = document.querySelector(gridSelector); 178 if (!grid) return; 179 180 const cards = grid.querySelectorAll(cardSelector); 181 182 cards.forEach(card => { 183 card.addEventListener('mouseenter', () => { 184 const row = card.dataset.row; 185 grid.classList.remove('row-1-hover', 'row-2-hover', 'row-3-hover'); 186 if (row) { 187 grid.classList.add(`row-${row}-hover`); 188 } 189 }); 190 191 card.addEventListener('mouseleave', () => { 192 // Small delay before removing class to prevent flicker 193 setTimeout(() => { 194 if (!grid.querySelector(':hover')) { 195 grid.classList.remove('row-1-hover', 'row-2-hover', 'row-3-hover'); 196 } 197 }, 50); 198 }); 199 }); 200 201 grid.addEventListener('mouseleave', () => { 202 grid.classList.remove('row-1-hover', 'row-2-hover', 'row-3-hover'); 203 }); 204 } 205 206 // Setup row expansion for all grids 207 setupRowExpansion('#scholarsGridNav', '.scholar-nav-card'); 208 setupRowExpansion('#researchPanelGrid', '.panel-card'); 209 setupRowExpansion('#topicsPanelGrid', '.panel-card'); 210 211 // ============================================= 212 // SITE SEARCH (INSIDE MEGA NAV) 213 // ============================================= 214 const megaSearchInput = document.getElementById('megaSearchInput'); 215 const megaSearchSubmit = document.getElementById('megaSearchSubmit'); 216 const megaSearchResults = document.getElementById('megaSearchResults'); 217 const megaSearchTags = document.querySelectorAll('.mega-search-tag'); 218 const megaSearchBar = document.getElementById('megaSearchBar'); 219 const islandSearchBtn = document.getElementById('islandSearchBtn'); 220 221 // Site-wide search index â media metadata, research, events, scholars 222 const siteIndex = [ 223 // === RESEARCH / PUBLICATIONS === 224 { title: 'Grid Reliability Under Stress: 2026 Winter Outlook', type: 'study', date: 'Jan 15, 2026', scholar: 'Mark P. Mills', topics: ['grid','policy'], outlet: 'NCEA Research', excerpt: 'Comprehensive analysis of grid vulnerability during extreme cold events, with regional risk assessments.', url: 'research-library.html' }, 225 { title: 'The Case for Nuclear: Land, Labor & Reliability', type: 'study', date: 'Jan 8, 2026', scholar: 'Jonathan Lesser, PhD', topics: ['nuclear','grid'], outlet: 'NCEA Research', excerpt: 'Comparative analysis showing nuclear requires 300Ã less land than solar for equivalent output.', url: 'research-library.html' }, 226 { title: 'AI Data Centers: The Coming Energy Surge', type: 'brief', date: 'Dec 28, 2025', scholar: 'Mark P. Mills', topics: ['ai','grid'], outlet: 'NCEA Issue Brief', excerpt: 'How AI infrastru
226cture growth will drive unprecedented electricity demand, adding 15% to national consumption by 2030.', url: 'research-library.html' }, 227 { title: 'Mark Mills on CNBC: Energy Transition Realities', type: 'media', date: 'Dec 15, 2025', scholar: 'Mark P. Mills', topics: ['net-zero'], outlet: 'CNBC', excerpt: 'Television appearance discussing the practical challenges and timeline of achieving net-zero emissions targets.', url: 'research-library.html' }, 228 { title: 'State Energy Affordability Index 2025', type: 'study', date: 'Dec 1, 2025', scholar: 'Jonathan Lesser, PhD', topics: ['affordability','policy'], outlet: 'NCEA Research', excerpt: 'Comprehensive ranking of all 50 states on energy affordability metrics.', url: 'research-library.html' }, 229 { title: 'Advanced Reactors: Regulatory Barriers and Solutions', type: 'brief', date: 'Nov 22, 2025', scholar: 'Mark P. Mills', topics: ['nuclear','policy'], outlet: 'NCEA Issue Brief', excerpt: 'Analysis of NRC licensing bottlenecks preventing deployment of next-generation nuclear technologies.', url: 'research-library.html' }, 230 { title: 'Transmission Infrastructure: The Missing Link', type: 'study', date: 'Nov 5, 2025', scholar: 'Jonathan Lesser, PhD', topics: ['grid'], outlet: 'NCEA Research', excerpt: 'Research on transmission grid expansion required to accommodate renewable energy growth.', url: 'research-library.html' }, 231 { title: 'Congressional Testimony: Energy Security Priorities', type: 'testimony', date: 'Oct 28, 2025', scholar: 'Mark P. Mills', topics: ['policy','grid'], outlet: 'House Energy Committee', excerpt: 'Full testimony on energy security implications of current policy trajectories.', url: 'research-library.html' }, 232 { title: 'Electric Vehicle Mandates: Grid Impact Assessment', type: 'brief', date: 'Oct 15, 2025', scholar: 'Jonathan Lesser, PhD', topics: ['grid','policy'], outlet: 'NCEA Issue Brief', excerpt: 'Analysis of the strain EV adoption mandates will place on local distribution grids.', url: 'research-library.html' }, 233 { title: 'Mining for the Energy Transition: Supply Chain Risks', type: 'study', date: 'Oct 1, 2025', scholar: 'Mark P. Mills', topics: ['net-zero'], outlet: 'NCEA Research', excerpt: 'Critical mineral dependencies and supply chain vulnerabilities in renewable energy manufacturing.', url: 'research-library.html' }, 234 // === EVENTS === 235 { title: 'Grid Reliability in a Changing Climate: 2026 Outlook', type: 'webinar', date: 'Feb 7, 2026', scholar: 'Mark Mills', topics: ['grid'], outlet: 'NCEA Webinar', excerpt: 'Deep dive into regional grid reliability challenges as extreme weather events become more frequent.', url: 'events.html' }, 236 { title: 'CERAWeek 2026: The Path to Energy Security', type: 'conference', date: 'Mar 10-14, 2026', scholar: 'Sarah Chen', topics: ['policy'], outlet: 'CERAWeek', excerpt: 'NCEA scholars at the premier annual energy conference in Houston.', url: 'events.html' }, 237 { title: 'The Energy Gang: Nuclear Renaissance?', type: 'podcast', date: 'Feb 12, 2026', scholar: 'David Williams', topics: ['nuclear'], outlet: 'The Energy Gang Podcast', excerpt: 'Discussion on the prospects and challenges of nuclear energy expansion.', url: 'events.html' }, 238 { title: 'Senate ENR: Permitting Reform Hearing', type: 'testimony', date: 'Feb 20, 2026', scholar: 'Mark Mills', topics: ['policy'], outlet: 'U.S. Senate', excerpt: 'Testimony before the Senate Energy & Natural Resources Committee on permitting reform.', url: 'events.html' }, 239 { title: 'Capacity Markets 101: A Policy Primer', type: 'webinar', date: 'Feb 25, 2026', scholar: 'Jennifer Park', topics: ['policy','grid'], outlet: 'NCEA Webinar', excerpt: 'Introduction to capacity market structures and their role in grid reliability.', url: 'events.html' }, 240 { title: 'ERCOT Grid Summit: Lessons from Winter Storm Elliott', type: 'conference', date: 'Mar 5, 2026', scholar: 'Robert Martinez', topics: ['grid'], outlet: 'ERCOT Grid Summit', excerpt: 'Post-mortem analysis of Winter Storm Elliott grid impacts in Texas.', url: 'events.html' }, 241 { title: 'Leading Energy Indicators Launch Event', type: 'webinar', date: 'Jan 15, 2026', scholar: 'Mark Mills', topics: ['grid','policy'], outlet: 'NCEA Webinar', excerpt: 'Launch of the NCEA Leading Energy Indicators dashboard.', url: 'events.html' }, 242 { title: 'House Energy Committee: Grid Modernization', type: 'testimony', date: 'Dec 10, 2025', scholar: 'Sarah Chen', topics: ['grid','policy'], outlet: 'U.S. House of Representatives', excerpt: 'Testimony on grid modernization priorities and infrastru
242cture investment needs.', url: 'events.html' }, 243 { title: 'Odd Lots: The Coming Power Crunch', type: 'podcast', date: 'Nov 28, 2025', scholar: 'Mark Mills', topics: ['grid'], outlet: 'Bloomberg Odd Lots', excerpt: 'Discussion on looming electricity supply shortfalls and policy implications.', url: 'events.html' }, 244 { title: 'EIA Energy Conference 2025', type: 'conference', date: 'Oct 22, 2025', scholar: 'David Williams', topics: ['policy'], outlet: 'Energy Information Administration', excerpt: 'NCEA scholars presenting at the annual EIA conference.', url: 'events.html' }, 245 { title: 'State of the Grid: Q3 2025 Assessment', type: 'webinar', date: 'Oct 1, 2025', scholar: 'Jennifer Park', topics: ['grid'], outlet: 'NCEA Webinar', excerpt: 'Quarterly assessment of national grid performance and reliability metrics.', url: 'events.html' }, 246 { title: 'FERC Technical Conference on Capacity', type: 'testimony', date: 'Sep 15, 2025', scholar: 'Robert Martinez', topics: ['grid','policy'], outlet: 'FERC', excerpt: 'Technical testimony on capacity market design and resource adequacy.', url: 'events.html' }, 247 // === SCHOLARS === 248 { title: 'Mark P. Mills', type: 'scholar', date: '', scholar: 'Mark P. Mills', topics: ['grid','nuclear','policy','net-zero','ai'], outlet: 'Executive Director, NCEA', excerpt: 'Contributing editor at City Journal, faculty fellow at Northwestern, co-founding partner in Montrose Lane.', url: '#' }, 249 { title: 'David G. Victor, PhD', type: 'scholar', date: '', scholar: 'David G. Victor', topics: ['policy','grid'], outlet: 'Senior Scholar, NCEA', excerpt: 'Professor of Innovation and Public Policy at UC San Diego, energy governance expert.', url: '#' }, 250 { title: 'Julio M. Ottino, PhD', type: 'scholar', date: '', scholar: 'Julio M. Ottino', topics: ['grid','nuclear'], outlet: 'Senior Scholar, NCEA', excerpt: 'Dean of the McCormick School of Engineering at Northwestern University.', url: '#' }, 251 { title: 'Jonathan Lesser, PhD', type: 'scholar', date: '', scholar: 'Jonathan Lesser', topics: ['affordability','grid','policy'], outlet: 'Senior Fellow, NCEA', excerpt: 'Energy economist and expert on electricity market regulation and affordability.', url: '#' }, 252 { title: 'Sarah Chen', type: 'scholar', date: '', scholar: 'Sarah Chen', topics: ['grid','policy'], outlet: 'Research Fellow, NCEA', excerpt: 'Grid modernization specialist with focus on infrastructure resilience.', url: '#' }, 253 { title: 'Jennifer Park', type: 'scholar', date: '', scholar: 'Jennifer Park', topics: ['grid','policy'], outlet: 'Policy Analyst, NCEA', excerpt: 'Capacity market and electricity policy researcher.', url: '#' }, 254 // === DASHBOARD === 255 { title: 'Index of Leading Energy Indicators', type: 'dashboard', date: 'Updated daily', scholar: '', topics: ['grid','affordability','policy'], outlet: 'NCEA Data Platform', excerpt: 'Real-time indices across security, reliability, cost, and environmental footprint for all 50 states.', url: '#' }, 256 // === MEDIA / IN THE NEWS === 257 { title: 'The Hidden Cost of Green Energy Mandates', type: 'media', date: 'Feb 8, 2026', scholar: 'Mark P. Mills', topics: ['policy','affordability'], outlet: 'Wall Street Journal', excerpt: 'State renewable portfolio standards are driving up electricity costs far beyond official estimates.', url: '#' }, 258 { title: 'Grid Reliability Concerns Ahead of Winter Storm', type: 'media', date: 'Feb 6, 2026', scholar: 'Mark P. Mills', topics: ['grid'], outlet: 'CNBC', excerpt: 'NCEA Executive Director discusses regional grid vulnerabilities as arctic blast approaches.', url: '#' }, 259 { title: 'The Coming Power Crunch', type: 'podcast', date: 'Feb 4, 2026', scholar: 'Mark P. Mills', topics: ['grid','policy'], outlet: 'Bloomberg Odd L
259ots', excerpt: 'A deep dive into why electricity demand is outpacing supply growth for the first time in decades.', url: '#' }, 260 { title: 'Nuclear Energy\'s Second Act', type: 'media', date: 'Feb 2, 2026', scholar: 'David G. Victor', topics: ['nuclear','ai'], outlet: 'New York Times', excerpt: 'Why major tech companies are betting billions on nuclear power for their data centers.', url: '#' }, 261 { title: 'NCEA Study Cited in Senate Hearing on Permitting Reform', type: 'media', date: 'Jan 30, 2026', scholar: '', topics: ['policy'], outlet: 'E&E News', excerpt: 'Senators reference NCEA research on infrastructure bottlenecks during Energy Committee markup.', url: '#' }, 262 { title: 'The IEA\'s Flawed Forecasts Are Hurting Investment', type: 'media', date: 'Jan 28, 2026', scholar: 'Jonathan Lesser', topics: ['policy','net-zero'], outlet: 'Financial Times', excerpt: 'International Energy Agency projections have consistently underestimated oil and gas demand.', url: '#' }, 263 { title: 'Winter Storm Tests Texas Grid Again', type: 'media', date: 'Jan 25, 2026', scholar: 'Mark P. Mills', topics: ['grid'], outlet: 'Fox Business', excerpt: 'Analysis of ERCOT performance during extreme cold and lessons from previous winter storms.', url: '#' }, 264 { title: 'Electricity Affordability Crisis', type: 'podcast', date: 'Jan 22, 2026', scholar: 'Jonathan Lesser', topics: ['affordability','policy'], outlet: 'The Energy Gang', excerpt: 'Why electricity bills are rising faster than inflation and what policymakers can do about it.', url: '#' }, 265 { title: 'AI\'s Insatiable Appetite for Power', type: 'media', date: 'Jan 18, 2026', scholar: 'Mark P. Mills', topics: ['ai','grid'], outlet: 'Wired', excerpt: 'Data centers could consume 15% of U.S. electricity by 2030, creating unprecedented grid challenges.', url: '#' } 266 ]; 267 268 // SVG icons per content type 269 const typeIcons = { 270 study: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>', 271 brief: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>', 272 media: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="7" width="20" height="15" rx="2" ry="2"/><polyline points="17 2 12 7 7 2"/></svg>', 273 webinar: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2" ry="2"/></svg>', 274 conference: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>', 275 podcast: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/></svg>', 276 testimony: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/><line x1="4" y1="22" x2="4" y2="15"/></svg>', 277 scholar: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>', 278 dashboard: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="9" y1="21" x2="9" y2="9"/></svg>' 279 }; 280 281 const typeLabels = { 282 study: 'Study', brief: 'Issue Brief', media: 'Media', webinar: 'Webinar', 283 conference: 'Conference', podcast: 'Podcast', testimony: 'Testimony', 284 scholar: 'Scholar', dashboard: 'Dashboard' 285 }; 286 287 function highlightMatch(text, query) { 288 if (!query) return text; 289 const words = query.split(/\s+/).filter(w => w.length > 1); 290 let result = text; 291 words.forEach(word => { 292 const regex = new RegExp(`(${word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi'); 293 result = result.replace(regex, '<mark>$1</mark>'); 294 }); 295 return result; 296 } 297 298 function searchSite(query) { 299 if (!query || query.length < 2) { 300 megaSearchResults.innerHTML = ''; 301 return; 302 } 303 const q = query.toLowerCase().trim(); 304 const terms = q.split(/\s+/).filter(t => t.length > 1); 305 306 const scored = siteIndex.map(item => { 307 let score = 0; 308 const searchable = [ 309 item.title, 310 item.scholar, 311 item.outlet, 312 item.type, 313 item.date, 314 item.excerpt,
315 ...(item.topics || []) 316 ].join(' ').toLowerCase(); 317 318 terms.forEach(term => { 319 if (item.title.toLowerCase().includes(term)) score += 10; 320 if (item.scholar.toLowerCase().includes(term)) score += 8; 321 if (item.outlet.toLowerCase().includes(term)) score += 6; 322 if (item.type.toLowerCase().includes(term)) score += 5; 323 if ((item.topics || []).some(t => t.includes(term))) score += 4; 324 if (item.date.toLowerCase().includes(term)) score += 3; 325 if (item.excerpt.toLowerCase().includes(term)) score += 2; 326 }); 327 328 return { ...item, score }; 329 }).filter(item => item.score > 0) 330 .sort((a, b) => b.score - a.score); 331 332 if (scored.length === 0) { 333 megaSearchResults.innerHTML = ` 334 <div class="search-empty-state"> 335 <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> 336 <p><strong>No results for "${query}"</strong></p> 337 <p>Try different keywords â search by title, scholar name, outlet, or topic.</p> 338 </div>`; 339 return; 340 } 341 342 let html = `<div class="mega-search-results-count">${scored.length} result${scored.length !== 1 ? 's' : ''}</div>`; 343 scored.slice(0, 15).forEach(item => { 344 const icon = typeIcons[item.type] || typeIcons.study; 345 const label = typeLabels[item.type] || item.type; 346 const metaParts = [label]; 347 if (item.scholar) metaParts.push(item.scholar); 348 if (item.outlet && item.outlet !== 'NCEA Research' && item.outlet !== 'NCEA Issue Brief') metaParts.push(item.outlet); 349 if (item.date) metaParts.push(item.date); 350 351 html += ` 352 <a href="${item.url}" class="search-result-item"> 353 <div class="search-result-icon type-${item.type}">${icon}</div> 354 <div class="search-result-body"> 355 <div class="search-result-title">${highlightMatch(item.title, query)}</div> 356 <div class="search-result-meta">${metaParts.join(' <span class="search-result-meta-sep">·</span> ')}</div> 357 ${item.excerpt ? `<div class="search-result-excerpt">${item.excerpt}</div>` : ''} 358 </div> 359 </a>`; 360 }); 361 362 megaSearchResults.innerHTML = html; 363 } 364 365 function openSearchInMega() { 366 openMegaWithPanel('search'); 367 setTimeout(() => megaSearchInput && megaSearchInput.focus(), 400); 368 } 369 370 function exitSearchMode() { 371 // Go back to scholars panel (default) 372 const megaMain = document.querySelector('.mega-main'); 373 megaMain.classList.remove('search-active'); 374 375 // Show scholars panel 376 document.querySelectorAll('.mega-content-panel').forEach(p => p.classList.remove('is-active')); 377 document.querySelectorAll('.mega-nav-link').forEach(l => l.classList.remove('is-active')); 378 379 const scholarsPanel = document.querySelector('.mega-content-panel[data-panel="scholars"]'); 380 const scholarsLink = document.querySelector('.mega-nav-link[data-panel="scholars"]'); 381 if (scholarsPanel) scholarsPanel.classList.add('is-active'); 382 if (scholarsLink) scholarsLink.classList.add('is-active'); 383 } 384 385 function submitSiteSearch() { 386 const query = megaSearchInput.value.trim(); 387 searchSite(query); 388 } 389 390 // Mega search bar (bottom of mega) opens search modal 391 if (megaSearchBar) { 392 megaSearchBar.addEventListener('click', () => { 393 closeMega(); 394 if (typeof window.openSearchModal === 'function') { window.openSearchModal(); } 395 }); 396 } 397 398 // Island search button opens global search modal 399 if (islandSearchBtn) { 400 islandSearchBtn.addEventListener('click', openSearchModal); 401 } 402 403 // Top nav search button 404 window.openAISearch = openSearchModal; 405 window.exitSearchMode = exitSearchMode; 406 407 // Search submit 408 if (megaSearchSubmit) { 409 megaSearchSubmit.addEventListener('click', submitSiteSearch); 410 } 411 if (megaSearchInput) { 412 megaSearchInput.addEventListener('keydown', e => { if (e.key === 'Enter') submitSiteSearch(); }); 413 // Live search as you type (debounced) 414 let searchTimeout; 415 megaSearchInput.addEventListener('input', () => { 416 clearTimeout(searchTimeout); 417 searchTimeout = setTimeout(submitSiteSearch, 200); 418 }); 419 } 420 421 // Search suggestion tags
422 megaSearchTags.forEach(tag => { 423 tag.addEventListener('click', () => { 424 megaSearchInput.value = tag.dataset.query; 425 submitSiteSearch(); 426 }); 427 }); 428 429 // Cmd+K shortcut to open search modal 430 document.addEventListener('keydown', e => { 431 if ((e.metaKey || e.ctrlKey) && e.key === 'k') { 432 e.preventDefault(); 433 if (isOpen) closeMega(); 434 openSearchModal(); 435 } 436 }); 437 438 // Expose needed functions globally 439 window.exitSearchMode = exitSearchMode; 440} // end island nav scope 441 442// Search modal stub â redirects to search page on non-homepage 443function openSearchModal(q) { 444 var modal = document.getElementById('globalSearchModal'); 445 if (modal) { 446 modal.classList.add('active'); 447 return; 448 } 449 // Check if homepage modal exists 450 var hpModal = document.getElementById('searchModalOverlay'); 451 if (hpModal) { 452 hpModal.classList.add('is-open'); 453 var inp = hpModal.querySelector('.search-modal__input'); 454 if (inp) { inp.value = q || ''; inp.focus(); } 455 return; 456 } 457 // Inject lightweight search modal 458 var overlay = document.createElement('div'); 459 overlay.id = 'searchModalInjected'; 460 overlay.innerHTML = 461 '<div class="srch-overlay">' + 462 '<div class="srch-modal">' + 463 '<div class="srch-header">' + 464 '<svg class="srch-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>
464' + 465 '<input class="srch-input" type="text" placeholder="Search publications, scholars, topics\u2026" autofocus>' + 466 '<span class="srch-kbd">ESC</span>' + 467 '<button class="srch-close" aria-label="Close"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button>' + 468 '</div>' + 469 '<div class="srch-body">' + 470 '<div class="srch-hint">Press Enter to search</div>' + 471 '<div class="srch-links">' + 472 '<a href="research-library.html">Research Library</a>' + 473 '<a href="advisory-board.html">Scholars</a>' + 474 '<a href="topics.html">Topics</a>' + 475 '<a href="events.html">Events</a>' + 476 '</div>' + 477 '</div>' + 478 '</div>' + 479 '</div>'; 480 var style = document.createElement('style'); 481 style.textContent = 482 '.srch-overlay{position:fixed;inset:0;z-index:10000;background:rgba(0,0,0,0.7);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);display:flex;align-items:flex-start;justify-content:center;padding-top:12vh;opacity:0;transition:opacity 0.25s cubic-bezier(0.16,1,0.3,1)}' + 483 '.srch-overlay.is-open{opacity:1}' + 484 '.srch-modal{width:min(620px,92vw);background:#0f0f11;border-radius:16px;overflow:hidden;transform:translateY(16px);transition:transform 0.3s cubic-bezier(0.16,1,0.3,1);box-shadow:0 0 0 1px rgba(255,255,255,0.08),0 32px 64px rgba(0,0,0,0.5)}' + 485 '.srch-overlay.is-open .srch-modal{transform:translateY(0)}' + 486 '.srch-header{display:flex;align-items:center;gap:12px;padding:18px 20px;border-bottom:1px solid rgba(255,255,255,0.08)}' + 487 '.srch-icon{width:20px;height:20px;color:rgba(255,255,255,0.35);flex-shrink:0}' + 488 '.srch-input{flex:1;background:none;border:none;font-size:16px;color:#fff;font-family:var(--font-body,sans-serif);outline:none}' + 489 '.srch-input::placeholder{color:rgba(255,255,255,0.3)}' + 490 '.srch-kbd{font-size:11px;color:rgba(255,255,255,0.25);background:rgba(255,255,255,0.06);padding:3px 8px;border-radius:5px;font-family:var(--font-body,sans-serif)}' + 491 '.srch-close{width:32px;height:32px;border-radius:8px;background:rgba(255,255,255,0.06);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background 0.2s}' + 492 '.srch-close:hover{background:rgba(255,255,255,0.12)}' + 493 '.srch-close svg{width:14px;height:14px;color:rgba(255,255,255,0.5)}' + 494 '.srch-body{padding:20px}' + 495 '.srch-hint{font-size:13px;color:rgba(255,255,255,0.25);margin-bottom:20px}' + 496 '.srch-links{display:flex;gap:8px;flex-wrap:wrap}' + 497 '.srch-links a{font-size:13px;color:rgba(255,255,255,0.5);padding:6px 14px;border-radius:100px;border:1px solid rgba(255,255,255,0.1);transition:all 0.2s;text-decoration:none}' + 498 '.srch-links a:hover{color:#fff;border-color:rgba(255,255,255,0.3)}'; 499 document.head.appendChild(style); 500 document.body.appendChild(overlay); 501 var ov = overlay.querySelector('.srch-overlay'); 502 var inp = overlay.querySelector('.srch-input'); 503 var cls = overlay.querySelector('.srch-close'); 504 requestAnimationFrame(function() { requestAnimationFrame(function() { 505 ov.classList.add('is-open'); 506 inp.focus(); 507 if (q) inp.value = q; 508 }); }); 509 function closeIt() { 510 ov.classList.remove('is-open'); 511 setTimeout(function() { overlay.remove(); }, 300); 512 } 513 ov.addEventListener('click', function(e) { if (e.target === ov) closeIt(); }); 514 cls.addEventListener('click', closeIt); 515 inp.addEventListener('keydown', function(e) { 516 if (e.key === 'Escape') closeIt(); 517 if (e.key === 'Enter' && inp.value.trim()) { 518 if (typeof window.openSearchModal === 'function') { closeIt(); window.openSearchModal(inp.value.trim()); } 519 } 520 }); 521 document.addEventListener('keydown', function handler(e) { 522 if (e.key === 'Escape') { closeIt(); document.removeEventListener('keydown', handler); } 523 }); 524} 525 526// Index dashboard stub â redirects to energy-index page 527function openIndexDashboard() { 528 const popup = document.getElementById('indexPopup'); 529 if (popup) { 530 popup.classList.toggle('active'); 531 } else { 532 window.location.href = 'energy-index.html'; 533 } 534}
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.