// router.js - Simple SPA router import { renderHomePage, getHomePageSidebar } from '../pages/HomePage.js'; import { renderAreaPage, getAreaPageSidebar } from '../pages/AreaPage.js'; import { renderResourcesPage, getResourcesPageSidebar } from '../pages/ResourcesPage.js'; import { scrollToSection } from './dom.js'; class Router { constructor() { this.routes = { '/': 'home', '/home': 'home', '/efficiency': 'efficiency', '/personal': 'personal', '/rights': 'rights', '/ecosystems': 'ecosystems', '/about': 'resources' }; this.currentPage = null; this.init(); } init() { // Handle initial page load console.log('Initializing router'); this.loadPage(window.location.pathname); // Handle browser back/forward window.addEventListener('popstate', (e) => { const path = window.location.pathname; const hash = window.location.hash; const fullUrl = path + hash; // Use navigateToUrl to ensure proper scroll behavior this.navigateToUrl(fullUrl); }); // Handle hash changes for navigation window.addEventListener('hashchange', (e) => { const hash = window.location.hash.substring(1); if (hash) { // Scroll to the section after a short delay to ensure content is loaded setTimeout(() => scrollToSection(hash), 100); } }); // Handle initial hash if present const initialHash = window.location.hash.substring(1); if (initialHash) { setTimeout(() => scrollToSection(initialHash), 200); } // Initialize unified navigation handling this.initializeNavigation(); } async loadPage(path) { const route = this.routes[path] || 'home'; if (this.currentPage === route) { // Same page, no need to reload return; } this.currentPage = route; console.log('Loading page:', route); // Handle background for all pages await this.setPageBackground(route); console.log('Background set for:', route); try { switch (route) { case 'home': await this.loadHomePage(); break; case 'efficiency': case 'personal': case 'rights': case 'ecosystems': await this.loadAreaPage(route); break; case 'resources': await this.loadResourcesPage(); break; default: await this.loadHomePage(); } } catch (error) { console.error('Error loading page:', error); // Fallback to home page await this.loadHomePage(); } console.log('Page loaded:', route); return Promise.resolve(); } async setPageBackground(route) { console.log('Setting background for route:', route); // Clear any existing background this.clearBackground(); let backgroundImage, attribution, sourceUrl; if (route === 'home' || route === 'resources') { const { homeBackgroundImage } = await import('../data/areas.js'); backgroundImage = homeBackgroundImage.image; attribution = homeBackgroundImage.attribution; sourceUrl = homeBackgroundImage.sourceUrl; } else { // Area pages const { areasData } = await import('../data/areas.js'); const area = areasData[route]; backgroundImage = area.image; attribution = area.imageAttribution; sourceUrl = area.imageSourceUrl; } // Create background element at document level const backgroundDiv = document.createElement('div'); backgroundDiv.id = 'page-background'; backgroundDiv.className = 'fixed opacity-40 z-0'; // The positioning logic is now in updatePageBackgroundPosition, call it after adding to DOM document.body.insertAdjacentElement('afterbegin', backgroundDiv); updatePageBackgroundPosition(); // Set initial position backgroundDiv.innerHTML = `
${attribution}
`; // Add hover functionality for attribution this.initializeBackgroundAttribution(); } clearBackground() { const existingBg = document.getElementById('page-background'); if (existingBg) { existingBg.remove(); } } initializeBackgroundAttribution() { const backgroundContainer = document.getElementById('page-background'); const attribution = document.getElementById('bg-attribution'); if (!backgroundContainer || !attribution) return; backgroundContainer.addEventListener('mouseenter', () => { attribution.style.opacity = '1'; }); backgroundContainer.addEventListener('mouseleave', () => { attribution.style.opacity = '0'; }); } async loadHomePage() { const mainContent = document.getElementById('main-content'); const leftSidebar = document.getElementById('left-sidebar'); if (!mainContent) { return; } try { // Get page content and sidebar const homePage = renderHomePage(); // Update content const contentContainer = mainContent.querySelector('.max-w-4xl') || mainContent; contentContainer.innerHTML = homePage.content; // Update sidebar if it exists if (leftSidebar) { leftSidebar.innerHTML = getHomePageSidebar(); } // Initialize page if (homePage.init) { homePage.init(); } // Initialize left sidebar attribution after content is loaded initializeLeftSidebarAttribution(); // Update navigation state this.updateNavigation('home'); } catch (error) { // Fallback to error content const contentContainer = mainContent.querySelector('.max-w-4xl') || mainContent; contentContainer.innerHTML = `

Error Loading Page

Sorry, there was an error loading the home page.

`; } } async loadAreaPage(area) { const mainContent = document.getElementById('main-content'); const leftSidebar = document.getElementById('left-sidebar'); if (!mainContent) { return; } try { // Get area page content and sidebar const areaPage = renderAreaPage(area); // Update content const contentContainer = mainContent.querySelector('.max-w-4xl') || mainContent; contentContainer.innerHTML = areaPage.content; // Update sidebar if (leftSidebar) { leftSidebar.innerHTML = getAreaPageSidebar(area); } // Initialize page if (areaPage.init) { areaPage.init(); } // Initialize left sidebar attribution after content is loaded initializeLeftSidebarAttribution(); } catch (error) { console.error(`Error loading ${area} page:`, error); // Fallback content const contentContainer = mainContent.querySelector('.max-w-4xl') || mainContent; contentContainer.innerHTML = `

Error Loading Page

Sorry, there was an error loading the ${area} page.

`; } this.updateNavigation(area); } async loadResourcesPage() { const mainContent = document.getElementById('main-content'); const leftSidebar = document.getElementById('left-sidebar'); if (!mainContent) return; try { // Get resources page content and sidebar const resourcesPage = renderResourcesPage(); // Update content const contentContainer = mainContent.querySelector('.max-w-4xl') || mainContent; contentContainer.innerHTML = resourcesPage.content; // Update sidebar if (leftSidebar) { leftSidebar.innerHTML = getResourcesPageSidebar(); } // Initialize page if (resourcesPage.init) { resourcesPage.init(); } // Initialize left sidebar attribution after content is loaded initializeLeftSidebarAttribution(); } catch (error) { // Fallback content const contentContainer = mainContent.querySelector('.max-w-4xl') || mainContent; contentContainer.innerHTML = `

Error Loading Page

Sorry, there was an error loading the resources page.

`; } this.updateNavigation('resources'); } updateNavigation(currentPage) { // Update header navigation active states const navLinks = document.querySelectorAll('header nav a'); navLinks.forEach(link => { link.classList.remove('text-blue-600', 'bg-blue-50'); link.classList.add('text-gray-700'); const href = link.getAttribute('href'); if ((currentPage === 'home' && (href === '/' || href === '/home')) || (currentPage === 'resources' && href === '/about') || (currentPage !== 'home' && currentPage !== 'resources' && href === `/${currentPage}`)) { link.classList.remove('text-gray-700'); link.classList.add('text-blue-600', 'bg-blue-50'); } }); } initializeNavigation() { // Use event delegation to handle all navigation links document.addEventListener('click', (e) => { const link = e.target.closest('a'); if (!link) return; const href = link.getAttribute('href'); if (!href) return; // Handle different types of links if (href.startsWith('/') && !href.startsWith('/js/') && !href.startsWith('/css/') && !href.startsWith('/images/')) { // Check if this is a known SPA route const path = href.split('#')[0]; if (this.routes[path]) { e.preventDefault(); this.navigateToUrl(href); } } else if (href.startsWith('#')) { // Same-page hash navigation e.preventDefault(); scrollToSection(href.substring(1)); } // External links are handled normally by browser }); } navigateToUrl(fullUrl) { // Parse URL into path and hash const [path, hash] = fullUrl.split('#'); // Update browser URL window.history.pushState({}, '', fullUrl); // Load page and then handle hash this.loadPage(path).then(() => { if (hash) { setTimeout(() => scrollToSection(hash), 100); } else { // No hash - scroll to top of page with a delay to ensure content is rendered setTimeout(() => { window.scrollTo({ top: 0, behavior: 'smooth' }); }, 100); } }); } } // Export router instance export const router = new Router(); // New function to update the position and size of the page background function updatePageBackgroundPosition() { const backgroundDiv = document.getElementById('page-background'); if (!backgroundDiv) return; const headerHeight = getComputedStyle(document.documentElement).getPropertyValue('--header-height'); const leftSidebar = document.getElementById('left-sidebar'); const searchSidebar = document.getElementById('search-sidebar'); const leftSidebarWidth = leftSidebar.classList.contains('-translate-x-full') ? 0 : leftSidebar.offsetWidth; const rightSidebarWidth = searchSidebar.classList.contains('translate-x-full') ? 0 : searchSidebar.offsetWidth; backgroundDiv.style.cssText = ` top: ${headerHeight}; left: ${leftSidebarWidth}px; right: ${rightSidebarWidth}px; bottom: 0; width: calc(100% - ${leftSidebarWidth + rightSidebarWidth}px); height: calc(100vh - ${headerHeight}); transition: left 0.3s ease-in-out, width 0.3s ease-in-out, right 0.3s ease-in-out; /* Add transition for smooth movement */ `; } // Export the new function so main.js can call it export { updatePageBackgroundPosition }; // Function to initialize the attribution hover for the left sidebar function initializeLeftSidebarAttribution() { const leftSidebarContainer = document.getElementById('left-sidebar'); const attribution = document.getElementById('left-sidebar-attribution'); if (!leftSidebarContainer || !attribution) return; leftSidebarContainer.addEventListener('mouseenter', () => { attribution.style.opacity = '1'; }); leftSidebarContainer.addEventListener('mouseleave', () => { attribution.style.opacity = '0'; }); } // Export the new function as well export { initializeLeftSidebarAttribution };