Update human-evolution.html
· 1 year ago
6ade467279434e22cb9fd3685b563e2fe4d83930
Parent:
099aea0ca
1 file changed +225 −288
- human-evolution.html +225 −288
Diff
--- a/human-evolution.html +++ b/human-evolution.html @@ -3,7 +3,7 @@ <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <title>Human Evolution: Journey Through Time (Concise) - AWESOME Edition v3</title> + <title>Human Evolution: Journey Through Time (Concise) - AWESOME Edition v4 (Fossil Markers)</title> <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.min.css" /> <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" /> @@ -32,7 +32,7 @@ --text-muted-color: #90909A; --text-heading-color: #FFFFFF; - --timeline-period-color: #FFC107; + --timeline-period-color: #FFC107; /* Amber for current period, also used for fossil markers */ /* Typography */ --font-primary: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; @@ -309,7 +309,6 @@ box-shadow: 0 4px 12px rgba(var(--danger-accent-rgb), 0.5); } - /* Period Navigator Select Styling */ #period-filter-select { background-color: var(--dark-bg); color: var(--text-color); @@ -422,7 +421,7 @@ background-color: rgba(var(--warning-accent-rgb), 0.05); padding: 0.5rem; border-radius: 4px; - border-top: none; /* Removed original dashed top border as it's a box now */ + border-top: none; } .data-limitation-note i { margin-right: 0.35rem; @@ -578,6 +577,17 @@ border-bottom: 1px solid var(--light-bg); padding-bottom: 0.4rem; } + .leaflet-popup-content h5 i { /* Icon styling within popup title */ + margin-right: 0.3em; + color: inherit; /* Inherit color from h5, or set specifically */ + } + .popup-hr { /* Styling for HR in popups */ + margin: 0.3rem 0; + border-color: var(--light-bg); + border-top-width: 1px; + opacity: 0.5; + } + .leaflet-container a.leaflet-popup-close-button { color: var(--text-muted-color); padding: 8px 8px 0 0; @@ -593,7 +603,7 @@ margin-left: auto; margin-right: auto; border: 1px solid var(--light-bg); - border-radius: var(--border-radius-standard); /* Match other radii */ + border-radius: var(--border-radius-standard); } .popup-learn-more { color: var(--primary-accent-static); @@ -615,7 +625,7 @@ outline-offset: 2px; } #species-filter-list .form-check-input:focus-visible, - #period-filter-select:focus-visible { /* Add period filter to custom focus */ + #period-filter-select:focus-visible { box-shadow: 0 0 0 2px var(--dark-bg), 0 0 0 4px var(--focus-ring-color); } .tooltip-inner { @@ -634,67 +644,40 @@ /* Responsive Adjustments */ @media (max-width: 992px) { - #main-content { - flex-direction: column; - } + #main-content { flex-direction: column; } #controls-sidebar { width: 100%; - max-height: 45vh; /* Original */ + max-height: 45vh; border-right: none; border-bottom: 1px solid var(--light-bg); } - #map-container { - flex-grow: 1; - height: auto; - } + #map-container { flex-grow: 1; height: auto; } } @media (max-width: 767.98px) { - :root { - /* --sidebar-width: 100%; Already handled by .controls-sidebar in this query */ - } - header.app-header h1 { font-size: 1.3rem; } header.app-header h1 i { margin-right: 0.5rem; } - #controls-sidebar { - padding: 0.75rem; - gap: 0.75rem; - max-height: 50vh; - } - + #controls-sidebar { padding: 0.75rem; gap: 0.75rem; max-height: 50vh; } .control-card .card-header { font-size: 0.8rem; padding: 0.5rem 0.75rem; } .control-card .card-body { padding: 0.6rem 0.75rem; } - #current-period-display { font-size: 1.1rem; } #timeline-value-display { font-size: 0.7rem; margin-bottom: 0.5rem; } #timeline-scrubber { margin: 0.5rem 0; } #timeline-scrubber::-webkit-slider-thumb { width: 18px; height: 18px; } #timeline-scrubber::-moz-range-thumb { width: 18px; height: 18px; } - - - .timeline-buttons .btn, - .quick-filter-buttons .btn, - #reset-button { padding: 0.3rem 0.75rem; font-size: 0.75rem; } + .timeline-buttons .btn, .quick-filter-buttons .btn, #reset-button { padding: 0.3rem 0.75rem; font-size: 0.75rem; } .timeline-buttons .btn i, #reset-button i { margin-right: 0.25rem; } - #period-filter-select { font-size: 0.75rem; padding-top: 0.3rem; padding-bottom: 0.3rem; } - #species-filter-list .form-check-label { font-size: 0.75rem; } #species-filter-list .form-check-input { width: 1em; height: 1em; } #species-filter-list .form-check-input:checked::before { font-size: 0.7em; } .species-color-indicator { width: 10px; height: 10px; } - - - #narrative-panel .card-body, - #narrative-panel .card-body p#narrative-text-element, - .data-limitation-note { font-size: 0.75rem; } - + #narrative-panel .card-body, #narrative-panel .card-body p#narrative-text-element, .data-limitation-note { font-size: 0.75rem; } .legend { padding: 0.5rem 0.75rem; max-height: 150px; } .legend h5 { font-size: 0.8rem; } .legend-item { font-size: 0.7rem; } .legend-color-box { width: 10px; height: 10px; } - .leaflet-popup-content { padding: 0.6rem 0.8rem; font-size: 0.75rem; } .leaflet-popup-content h5 { font-size: 1em; } } @@ -703,41 +686,22 @@ header.app-header { padding: 0 1rem; } header.app-header h1 { font-size: 1.15rem; } header.app-header h1 i { font-size: 0.9em; } - - #controls-sidebar { padding: 0.5rem; gap: 0.5rem; } - .control-card .card-header { font-size: 0.75rem; padding: 0.4rem 0.6rem; } .control-card .card-header i { font-size: 0.85em; } .control-card .card-body { padding: 0.5rem 0.6rem; } .control-card .card-header + .card-body { padding-top: 0.75rem; } - - #current-period-display { font-size: 1rem; } - .timeline-buttons { flex-wrap: wrap; gap: 0.25rem; } - .timeline-buttons .btn, - .timeline-buttons .btn-group, /* Target group directly for basis */ - #reset-button { /* Play, Group, Reset */ - flex-grow: 1; /* Allow them to grow */ - flex-basis: auto; /* Reset basis or set to a meaningful min-content */ - } - /* Make all three items (Play button, Speed group, Reset button) share space */ .d-flex.justify-content-between.align-items-center.mt-2.timeline-buttons > * { - flex-basis: calc(33.333% - 0.2rem); /* Approx 1/3 width, accounting for gap */ - min-width: 80px; /* Prevent them from becoming too small */ + flex-basis: calc(33.333% - 0.2rem); + min-width: 70px; /* Further reduced min-width */ } .timeline-buttons .btn-group { display: flex; } .timeline-buttons .btn-group .btn { flex-grow: 1; } - - .quick-filter-buttons .btn { font-size: 0.7rem; padding: 0.25rem 0.5rem; } - #species-filter-list { max-height: 120px; } - - #narrative-panel .card-body, - #narrative-panel .card-body p#narrative-text-element, - .data-limitation-note { font-size: 0.7rem; } + #narrative-panel .card-body, #narrative-panel .card-body p#narrative-text-element, .data-limitation-note { font-size: 0.7rem; } .data-limitation-note i { display: none; } } @@ -803,14 +767,12 @@ </div> </div> - <!-- NEW: Period Navigation Card --> <div class="control-card card"> <div class="card-header"><i class="bi bi-collection-play"></i> Period Navigator</div> <div class="card-body"> <label for="period-filter-select" class="form-label visually-hidden">Select a Time Period</label> <select class="form-select" id="period-filter-select" data-bs-toggle="tooltip" title="Jump to a specific period"> <option selected disabled value="">Select a period...</option> - <!-- Options will be populated by JavaScript --> </select> </div> </div> @@ -893,6 +855,7 @@ let iceSheetLayerGroup = L.layerGroup(); let speciesGeoJsonLayerGroup = L.layerGroup().addTo(map); + let fossilSiteLayerGroup = L.layerGroup().addTo(map); // Layer for fossil markers let currentPeriodIndex = 0; let selectedSpecies = {}; @@ -917,14 +880,14 @@ loadingScreen: null, controlsContentDiv: null, climateToggle: null, - periodFilterSelect: null // Added for Period Navigator + periodFilterSelect: null }; function populateDomElements() { const idMap = { controlsContentDiv: 'controls-content', climateToggle: 'climate-toggle-switch', - periodFilterSelect: 'period-filter-select' // Added + periodFilterSelect: 'period-filter-select' }; for (const key in domElements) { if (key === 'playPauseIcon' || key === 'playPauseText') continue; @@ -937,8 +900,6 @@ if (domElements.playPauseButton) { domElements.playPauseIcon = domElements.playPauseButton.querySelector('i'); domElements.playPauseText = domElements.playPauseButton.querySelector('span'); - if (!domElements.playPauseIcon) console.warn("Play/Pause button icon (<i>) not found."); - if (!domElements.playPauseText) console.warn("Play/Pause button text (<span>) not found."); } else { console.error("playPauseButton element not found, cannot derive icon and text elements."); } @@ -952,26 +913,27 @@ } const [evolutionResponse, iceSheetResponse] = await Promise.all([ - fetch('https://cheatsheets.davidveksler.com/evolution_data.json'), - fetch('https://cheatsheets.davidveksler.com/iceSheetGeoJson.json') + fetch('https://cheatsheets.davidveksler.com/evolution_data.json'), // Ensure this path is correct + fetch('https://cheatsheets.davidveksler.com/iceSheetGeoJson.json') // Ensure this path is correct ]); - if (!evolutionResponse.ok) { - throw new Error(`HTTP error! status: ${evolutionResponse.status} for evolution data`); - } - if (!iceSheetResponse.ok) { - throw new Error(`HTTP error! status: ${iceSheetResponse.status} for ice sheet data`); - } + if (!evolutionResponse.ok) throw new Error(`HTTP error! status: ${evolutionResponse.status} for evolution data`); + if (!iceSheetResponse.ok) throw new Error(`HTTP error! status: ${iceSheetResponse.status} for ice sheet data`); timePeriodsData = await evolutionResponse.json(); fetchedIceSheetGeoJson = await iceSheetResponse.json(); - if (!Array.isArray(timePeriodsData) || timePeriodsData.length === 0) { - throw new Error("Fetched evolution data is not a valid array or is empty."); - } - if (typeof fetchedIceSheetGeoJson !== 'object' || fetchedIceSheetGeoJson === null) { - throw new Error("Fetched ice sheet data is not a valid object."); - } + if (!Array.isArray(timePeriodsData) || timePeriodsData.length === 0) throw new Error("Fetched evolution data is not a valid array or is empty."); + if (typeof fetchedIceSheetGeoJson !== 'object' || fetchedIceSheetGeoJson === null) throw new Error("Fetched ice sheet data is not a valid object."); + + // Sort timePeriodsData chronologically + timePeriodsData.sort((a, b) => { + if (a.numericStartYear == null || b.numericStartYear == null) { + console.warn("Data integrity: Found period(s) without numericStartYear. Sorting may be affected."); + return 0; + } + return a.numericStartYear - b.numericStartYear; + }); initializeApplication(); } catch (error) { @@ -982,6 +944,7 @@ domElements.loadingScreen.innerHTML = `<div class='p-3 text-center'><i class='bi bi-exclamation-triangle-fill text-danger h1'></i><p class="text-light">Failed to load required data.<br><small>${error.message}</small></p></div>`; } } + // The redundant try...catch block that was here has been removed. } function populatePeriodFilter() { @@ -990,13 +953,12 @@ return; } domElements.periodFilterSelect.disabled = false; - domElements.periodFilterSelect.innerHTML = ''; // Clear existing options + domElements.periodFilterSelect.innerHTML = ''; const defaultOption = document.createElement('option'); defaultOption.textContent = 'Select a period...'; defaultOption.value = ""; defaultOption.disabled = true; - // defaultOption.selected = true; // Will be handled by updateUIForCurrentPeriod setting the correct index domElements.periodFilterSelect.appendChild(defaultOption); timePeriodsData.forEach((period, index) => { @@ -1006,35 +968,31 @@ domElements.periodFilterSelect.appendChild(option); }); } - function initializeApplication() { if (!domElements.loadingScreen || !domElements.controlsContentDiv) { - console.error("Core UI elements (loadingScreen or controlsContentDiv) not found. Cannot initialize application properly."); - document.body.innerHTML = `<div class='p-5 text-center text-danger'><h1>Application Error</h1><p>Essential UI components are missing. Please check the HTML structure and element IDs.</p></div>`; + document.body.innerHTML = `<div class='p-5 text-center text-danger'><h1>Application Error</h1><p>Essential UI components are missing.</p></div>`; return; } domElements.loadingScreen.style.display = 'none'; domElements.controlsContentDiv.classList.remove('d-none'); - Object.values(domElements).forEach(el => { - if (el && el.id !== 'period-filter-select' && typeof el.disabled === 'boolean') { // Don't enable period filter select here + Object.keys(domElements).forEach(key => { + const el = domElements[key]; + if (el && el.id !== 'period-filter-select' && typeof el.disabled === 'boolean') { el.disabled = false; } }); - if (domElements.timelineScrubber) { - domElements.timelineScrubber.max = timePeriodsData.length - 1; - } + if (domElements.timelineScrubber) domElements.timelineScrubber.max = timePeriodsData.length - 1; populateAllSpeciesList(); populateSpeciesFilter(); - populatePeriodFilter(); // Populate period filter + populatePeriodFilter(); currentPeriodIndex = 0; updateSpeedButtonsActiveState(); updateQuickFilterActiveState('filter-all'); - updateUIForCurrentPeriod(); // This will now also set periodFilterSelect - + updateUIForCurrentPeriod(); const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); tooltipTriggerList.map(function (tooltipTriggerEl) { @@ -1045,13 +1003,11 @@ function populateAllSpeciesList() { const speciesSet = new Map(); timePeriodsData.forEach(period => { - if (period.species && Array.isArray(period.species)) { - period.species.forEach(s => { - if (s && s.name && !speciesSet.has(s.name)) { - speciesSet.set(s.name, { color: s.color || '#cccccc' }); - } - }); - } + (period.species || []).forEach(s => { + if (s && s.name && !speciesSet.has(s.name)) { + speciesSet.set(s.name, { color: s.color || '#cccccc' }); + } + }); }); allSpeciesList = Array.from(speciesSet, ([name, data]) => ({ name, ...data })); allSpeciesList.sort((a, b) => a.name.localeCompare(b.name)); @@ -1060,12 +1016,11 @@ function populateSpeciesFilter() { domElements.speciesFilterList.innerHTML = ''; if (allSpeciesList.length === 0) { - domElements.speciesFilterList.innerHTML = '<p class="text-muted p-2 small">No species found in data.</p>'; return; + domElements.speciesFilterList.innerHTML = '<p class="text-muted p-2 small">No species data.</p>'; return; } allSpeciesList.forEach(s => { selectedSpecies[s.name] = true; - const itemDiv = document.createElement('div'); - itemDiv.className = 'form-check'; + const itemDiv = document.createElement('div'); itemDiv.className = 'form-check'; const input = document.createElement('input'); input.type = 'checkbox'; input.className = 'form-check-input'; input.id = `filter-${s.name.replace(/\W/g, '_')}`; input.value = s.name; input.checked = true; @@ -1075,47 +1030,33 @@ updateQuickFilterActiveState(null); }); const colorIndicator = document.createElement('span'); - colorIndicator.className = 'species-color-indicator'; - colorIndicator.style.backgroundColor = s.color; - + colorIndicator.className = 'species-color-indicator'; colorIndicator.style.backgroundColor = s.color; const label = document.createElement('label'); - label.className = 'form-check-label'; label.htmlFor = input.id; - label.textContent = s.name; - - itemDiv.appendChild(input); - itemDiv.appendChild(colorIndicator); - itemDiv.appendChild(label); + label.className = 'form-check-label'; label.htmlFor = input.id; label.textContent = s.name; + itemDiv.appendChild(input); itemDiv.appendChild(colorIndicator); itemDiv.appendChild(label); domElements.speciesFilterList.appendChild(itemDiv); }); } function updateUIForCurrentPeriod() { - speciesGeoJsonLayerGroup.clearLayers(); + speciesGeoJsonLayerGroup.clearLayers(); + fossilSiteLayerGroup.clearLayers(); if (!timePeriodsData || timePeriodsData.length === 0 || currentPeriodIndex < 0 || currentPeriodIndex >= timePeriodsData.length) { if(domElements.currentPeriodDisplay) domElements.currentPeriodDisplay.textContent = `Timeline End`; - if(domElements.timelineValueDisplay) domElements.timelineValueDisplay.textContent = "(Drag scrubber or Reset)"; - if(domElements.narrativeTextElement) { - domElements.narrativeTextElement.style.color = 'var(--text-color)'; - domElements.narrativeTextElement.textContent = "End of timeline data."; - } - if(domElements.legendItemsContainer) domElements.legendItemsContainer.innerHTML = '<small class="text-muted">No active period.</small>'; - if(domElements.periodFilterSelect) domElements.periodFilterSelect.value = ""; // Select placeholder + if(domElements.timelineValueDisplay) domElements.timelineValueDisplay.textContent = "(Drag or Reset)"; + if(domElements.narrativeTextElement) domElements.narrativeTextElement.textContent = "End of timeline."; + if(domElements.legendItemsContainer) domElements.legendItemsContainer.innerHTML = '<small class="text-muted">No period.</small>'; + if(domElements.periodFilterSelect) domElements.periodFilterSelect.value = ""; return; } const period = timePeriodsData[currentPeriodIndex]; - if(domElements.currentPeriodDisplay) domElements.currentPeriodDisplay.textContent = period.periodName || "Unknown Period"; + if(domElements.currentPeriodDisplay) domElements.currentPeriodDisplay.textContent = period.periodName || "Unknown"; if(domElements.timelineValueDisplay) domElements.timelineValueDisplay.textContent = period.timeRange || `Period ${currentPeriodIndex + 1}`; if (domElements.timelineScrubber) domElements.timelineScrubber.value = currentPeriodIndex; - if (domElements.periodFilterSelect) { // Set period filter select - domElements.periodFilterSelect.value = currentPeriodIndex; - } - - if(domElements.narrativeTextElement) { - domElements.narrativeTextElement.style.color = 'var(--text-color)'; - domElements.narrativeTextElement.innerHTML = period.narrative || "No narrative available for this period."; - } + if (domElements.periodFilterSelect) domElements.periodFilterSelect.value = currentPeriodIndex; + if(domElements.narrativeTextElement) domElements.narrativeTextElement.innerHTML = period.narrative || "No narrative."; let speciesInCurrentPeriod = period.species || []; let visibleSpeciesInLegend = []; @@ -1124,51 +1065,54 @@ if (s && s.name && selectedSpecies[s.name]) { visibleSpeciesInLegend.push(s); - // ENHANCED POPUP CONTENT - let learnMoreLink = s.learnMoreUrl ? `<p class="mb-1 mt-1 small"><a href="${s.learnMoreUrl}" target="_blank" rel="noopener noreferrer" class="popup-learn-more">Learn More <i class="bi bi-box-arrow-up-right"></i></a></p>` : ''; let speciesImage = s.imageUrl ? `<img src="${s.imageUrl}" alt="${s.name}" class="img-fluid rounded mb-2 popup-species-image">` : ''; - - let popupContent = `<h5>${s.name}</h5> - ${speciesImage} - <p class="mb-1 small"><strong>Timeline:</strong> ${s.timeline || 'N/A'}</p> - <p class="mb-0 small">${s.notes || 'No specific notes.'}</p> - ${learnMoreLink}`; + let learnMoreLink = s.learnMoreUrl ? `<p class="mb-1 mt-1 small"><a href="${s.learnMoreUrl}" target="_blank" rel="noopener noreferrer" class="popup-learn-more">Learn More <i class="bi bi-box-arrow-up-right"></i></a></p>` : ''; + let speciesRangePopupContent = `<h5>${s.name}</h5>${speciesImage}<p class="mb-1 small"><strong>Timeline:</strong> ${s.timeline||'N/A'}</p><p class="mb-0 small">${s.notes||'No notes.'}</p>${learnMoreLink}`; if (s.estimatedRangeGeoJson && Object.keys(s.estimatedRangeGeoJson).length > 0) { try { - L.geoJSON(s.estimatedRangeGeoJson, { - style: { - fillColor: s.color || '#888888', - weight: 1.5, - opacity: 1, - color: 'rgba(255,255,255,0.3)', - fillOpacity: 0.55, - } - }).bindPopup(popupContent).addTo(speciesGeoJsonLayerGroup); + L.geoJSON(s.estimatedRangeGeoJson, { style: { fillColor: s.color||'#888', weight:1.5, opacity:1, color:'rgba(255,255,255,0.3)', fillOpacity:0.55 }}) + .bindPopup(speciesRangePopupContent).addTo(speciesGeoJsonLayerGroup); } catch (e) { console.warn("Error drawing range for", s.name, e); } } if (s.migrationPaths && Array.isArray(s.migrationPaths)) { s.migrationPaths.forEach(path => { if (path && Object.keys(path).length > 0) { - try { - L.geoJSON(path, { - style: { - color: s.migrationColor || s.color || '#888888', - weight: 3, - opacity: 0.75, - dashArray: '8, 6', - lineCap: 'round' - } - }).bindPopup(popupContent).addTo(speciesGeoJsonLayerGroup); - } catch (e) { console.warn("Error drawing migration for", s.name, e); } + try { L.geoJSON(path, { style: { color:s.migrationColor||s.color||'#888', weight:3, opacity:0.75, dashArray:'8, 6', lineCap:'round' }}) + .bindPopup(speciesRangePopupContent).addTo(speciesGeoJsonLayerGroup); } catch (e) { console.warn("Error drawing migration for", s.name, e); } + } + }); + } + + // Plot Fossil Site Markers + if (s.locations && Array.isArray(s.locations)) { + s.locations.forEach(loc => { + if (loc.latitude != null && loc.longitude != null) { + const markerCertainty = loc.certainty || 0.7; + const markerOptions = { + radius: 5, + fillColor: "var(--timeline-period-color)", // Amber + color: s.color || "var(--light-bg)", + weight: 1.5, + opacity: 1, + fillOpacity: 0.6 + (markerCertainty * 0.3) + }; + let fossilPopupContent = `<h5><i class="bi bi-compass"></i> ${loc.siteName}</h5> + <p class="mb-1 small">${loc.details}</p> + <hr class="popup-hr"> + <p class="mb-0 small"><strong>Found:</strong> ${s.name}</p> + ${loc.certainty ? `<p class="mb-0 small"><strong>Discovery Certainty:</strong> ${(markerCertainty * 100).toFixed(0)}%</p>` : ''}`; + L.circleMarker([loc.latitude, loc.longitude], markerOptions) + .bindPopup(fossilPopupContent) + .addTo(fossilSiteLayerGroup); } }); } } }); updateLegend(visibleSpeciesInLegend); - updateClimateOverlay(period.timeRange); + updateClimateOverlay(period); } function updateLegend(visibleSpecies) { @@ -1192,49 +1136,84 @@ if (!rangeString) return { start: null, end: null }; const cleaned = rangeString.toLowerCase().replace(/c\.|ago|years|million|kya|mya/g, '').trim(); const parts = cleaned.split(/\s*-\s*|\s+to\s+/); - function parsePart(part) { - part = part.trim(); - let num = parseFloat(part.replace(/,/g, '')); - if (rangeString.toLowerCase().includes("million") || rangeString.toLowerCase().includes("mya")) { - num *= 1000000; - } else if (rangeString.toLowerCase().includes("kya")) { - num *= 1000; - } + part = part.trim(); let num = parseFloat(part.replace(/,/g, '')); + if (rangeString.toLowerCase().includes("million") || rangeString.toLowerCase().includes("mya")) num *= 1000000; + else if (rangeString.toLowerCase().includes("kya")) num *= 1000; return isNaN(num) ? null : -Math.abs(num); } - let startYear = parsePart(parts[0]); let endYear = parts.length > 1 ? parsePart(parts[1]) : startYear; - - if (startYear && endYear === null) endYear = startYear; - if (endYear && startYear === null) startYear = endYear; - - if (startYear && endYear && startYear > endYear) { - [startYear, endYear] = [endYear, startYear]; - } + if (startYear && endYear === null) endYear = startYear; if (endYear && startYear === null) startYear = endYear; + if (startYear && endYear && startYear > endYear) [startYear, endYear] = [endYear, startYear]; return { start: startYear, end: endYear }; } - function updateClimateOverlay(periodTimeRange) { + function updateClimateOverlay(period) { // Accepts the full period object iceSheetLayerGroup.clearLayers(); - if (domElements.climateToggle && domElements.climateToggle.checked && periodTimeRange && fetchedIceSheetGeoJson) { - const { start, end } = parseYearFromRange(periodTimeRange); - const lgmStart = -26500; - const lgmEnd = -19000; - - if (start && end && Math.max(start, lgmStart) <= Math.min(end, lgmEnd)) { + // --- DEBUGGING START --- + console.log("Attempting to update climate overlay."); + if (domElements.climateToggle) { + console.log("Climate Toggle Exists. Checked:", domElements.climateToggle.checked); + } else { + console.log("Climate Toggle DOM element NOT FOUND."); + // Fallback or early exit if essential controls are missing + return; + } + + if (period) { + console.log("Current Period:", period.periodName, + "Numeric Start:", period.numericStartYear, + "Numeric End:", period.numericEndYear); + } else { + console.log("Current Period object is null or undefined."); + } + + if (!fetchedIceSheetGeoJson) { + console.log("fetchedIceSheetGeoJson is null or not loaded."); + } + // --- DEBUGGING END --- + + if (domElements.climateToggle.checked && period && + period.numericStartYear != null && period.numericEndYear != null && // Ensure numeric years are present + fetchedIceSheetGeoJson) { + + const currentPeriodStart = period.numericStartYear; + const currentPeriodEnd = period.numericEndYear; + + const lgmStart = -26500; // Last Glacial Maximum start ~26,500 years ago + const lgmEnd = -19000; // Last Glacial Maximum end ~19,000 years ago + + // --- DEBUGGING START --- + console.log(`Checking LGM overlap: Period (${currentPeriodStart} to ${currentPeriodEnd}) vs LGM (${lgmStart} to ${lgmEnd})`); + // --- DEBUGGING END --- + + // Check for overlap: The LGM period must overlap with the current hominin period + const isOverlap = Math.max(currentPeriodStart, lgmStart) <= Math.min(currentPeriodEnd, lgmEnd); + + // --- DEBUGGING START --- + console.log("Is LGM overlapping with current period?", isOverlap); + // --- DEBUGGING END --- + + if (isOverlap) { + // --- DEBUGGING START --- + console.log("Overlap with LGM detected. Adding ice sheets to layer group."); + let sheetsAddedCount = 0; + // --- DEBUGGING END --- Object.values(fetchedIceSheetGeoJson).forEach(sheet => { - if (sheet && sheet.type && sheet.coordinates) { - try { + if (sheet && sheet.type && sheet.coordinates) { + try { L.geoJSON(sheet, { - style: { - color: 'rgba(173, 216, 230, 0.5)', - fillColor: '#A2D1E6', + style: { + color: 'rgba(173,216,230,0.5)', + fillColor: '#A2D1E6', fillOpacity: 0.35, weight: 1 } }).addTo(iceSheetLayerGroup); + // --- DEBUGGING START --- + sheetsAddedCount++; + // --- DEBUGGING END --- } catch (e) { console.warn("Error drawing ice sheet GeoJSON feature:", e, sheet); } @@ -1242,27 +1221,40 @@ console.warn("Invalid GeoJSON feature structure in ice sheet data:", sheet); } }); + // --- DEBUGGING START --- + console.log(sheetsAddedCount + " ice sheet features processed."); + // --- DEBUGGING END --- } + } else { + // --- DEBUGGING START --- + let conditionsNotMetReason = "Conditions for displaying ice sheets not met:"; + if (!domElements.climateToggle.checked) conditionsNotMetReason += " Toggle not checked."; + if (!period) conditionsNotMetReason += " Period undefined."; + else if (period.numericStartYear == null || period.numericEndYear == null) conditionsNotMetReason += " Numeric years missing in period."; + if (!fetchedIceSheetGeoJson) conditionsNotMetReason += " Ice sheet data not fetched."; + console.log(conditionsNotMetReason); + // --- DEBUGGING END --- } - if (domElements.climateToggle) { - if (!map.hasLayer(iceSheetLayerGroup) && domElements.climateToggle.checked && iceSheetLayerGroup.getLayers().length > 0) { + + // Add or remove the layer group from the map based on its content and toggle state + if (domElements.climateToggle.checked && iceSheetLayerGroup.getLayers().length > 0) { + if (!map.hasLayer(iceSheetLayerGroup)) { map.addLayer(iceSheetLayerGroup); - } else if (map.hasLayer(iceSheetLayerGroup) && (!domElements.climateToggle.checked || iceSheetLayerGroup.getLayers().length === 0)) { + console.log("iceSheetLayerGroup added to map."); + } + } else { + if (map.hasLayer(iceSheetLayerGroup)) { map.removeLayer(iceSheetLayerGroup); + console.log("iceSheetLayerGroup removed from map."); } } } function animationLoop(timestamp) { if (!isPlaying) return; - if (timestamp - lastFrameTime >= (baseAnimationInterval / playbackSpeed)) { - lastFrameTime = timestamp; - currentPeriodIndex++; - if (currentPeriodIndex >= timePeriodsData.length) { - currentPeriodIndex = timePeriodsData.length - 1; - pauseAnimation(); - } + lastFrameTime = timestamp; currentPeriodIndex++; + if (currentPeriodIndex >= timePeriodsData.length) { currentPeriodIndex = timePeriodsData.length - 1; pauseAnimation(); } updateUIForCurrentPeriod(); } animationFrameId = requestAnimationFrame(animationLoop); @@ -1274,9 +1266,7 @@ if (domElements.playPauseIcon) domElements.playPauseIcon.className = 'bi bi-pause-fill'; if (domElements.playPauseText) domElements.playPauseText.textContent = "Pause"; lastFrameTime = performance.now(); - if (currentPeriodIndex >= timePeriodsData.length - 1 && timePeriodsData.length > 0) { - currentPeriodIndex = 0; - } + if (currentPeriodIndex >= timePeriodsData.length - 1 && timePeriodsData.length > 0) currentPeriodIndex = 0; updateUIForCurrentPeriod(); animationFrameId = requestAnimationFrame(animationLoop); } @@ -1285,132 +1275,79 @@ isPlaying = false; if (domElements.playPauseIcon) domElements.playPauseIcon.className = 'bi bi-play-fill'; if (domElements.playPauseText) domElements.playPauseText.textContent = "Play"; - if (animationFrameId) { - cancelAnimationFrame(animationFrameId); - } + if (animationFrameId) cancelAnimationFrame(animationFrameId); } - function togglePlayPause() { - if (isPlaying) { - pauseAnimation(); - } else { - playAnimation(); - } - } + function togglePlayPause() { if (isPlaying) pauseAnimation(); else playAnimation(); } function resetAnimation() { - pauseAnimation(); - currentPeriodIndex = 0; + pauseAnimation(); currentPeriodIndex = 0; if (domElements.timelineScrubber) domElements.timelineScrubber.value = 0; - // periodFilterSelect will be updated by updateUIForCurrentPeriod - playbackSpeed = 1; - updateSpeedButtonsActiveState(); + playbackSpeed = 1; updateSpeedButtonsActiveState(); if (allSpeciesList && allSpeciesList.length > 0) { - Object.keys(selectedSpecies).forEach(sName => { - selectedSpecies[sName] = true; - }); - document.querySelectorAll('#species-filter-list input[type="checkbox"]').forEach(cb => { - cb.checked = true; - }); + Object.keys(selectedSpecies).forEach(sName => { selectedSpecies[sName] = true; }); + document.querySelectorAll('#species-filter-list input[type="checkbox"]').forEach(cb => { cb.checked = true; }); } updateQuickFilterActiveState('filter-all'); - updateUIForCurrentPeriod(); // This will sync periodFilterSelect + updateUIForCurrentPeriod(); } function updateSpeedButtonsActiveState() { document.querySelectorAll('.speed-btn').forEach(btn => { - btn.classList.remove('active'); - btn.classList.add('btn-outline-light'); - if (parseInt(btn.dataset.speed) === playbackSpeed) { - btn.classList.add('active'); - btn.classList.remove('btn-outline-light'); - } + btn.classList.remove('active'); btn.classList.add('btn-outline-light'); + if (parseInt(btn.dataset.speed) === playbackSpeed) { btn.classList.add('active'); btn.classList.remove('btn-outline-light'); } }); } function updateQuickFilterActiveState(activeButtonId) { document.querySelectorAll('.quick-filter-buttons .btn').forEach(btn => { - btn.classList.remove('active'); - btn.classList.add('btn-outline-light'); + btn.classList.remove('active'); btn.classList.add('btn-outline-light'); }); if (activeButtonId) { const activeButton = document.getElementById(activeButtonId); - if (activeButton) { - activeButton.classList.add('active'); - activeButton.classList.remove('btn-outline-light'); - } + if (activeButton) { activeButton.classList.add('active'); activeButton.classList.remove('btn-outline-light'); } } } function addEventListeners() { - if (!domElements.timelineScrubber || !domElements.playPauseButton || !domElements.resetButton) { - console.error("Cannot add event listeners: Core DOM elements for controls not found."); - return; - } + if (!domElements.timelineScrubber || !domElements.playPauseButton || !domElements.resetButton) return; domElements.timelineScrubber.addEventListener('input', (event) => { - currentPeriodIndex = parseInt(event.target.value); - pauseAnimation(); - updateUIForCurrentPeriod(); + currentPeriodIndex = parseInt(event.target.value); pauseAnimation(); updateUIForCurrentPeriod(); }); domElements.playPauseButton.addEventListener('click', togglePlayPause); domElements.resetButton.addEventListener('click', resetAnimation); document.querySelectorAll('.speed-btn').forEach(button => { - button.addEventListener('click', () => { - playbackSpeed = parseInt(button.dataset.speed); - updateSpeedButtonsActiveState(); - }); - }); - const filterAllBtn = document.getElementById('filter-all'); - if(filterAllBtn) filterAllBtn.addEventListener('click', () => { - allSpeciesList.forEach(s => selectedSpecies[s.name] = true); - document.querySelectorAll('#species-filter-list input[type="checkbox"]').forEach(cb => cb.checked = true); - updateUIForCurrentPeriod(); - updateQuickFilterActiveState('filter-all'); + button.addEventListener('click', () => { playbackSpeed = parseInt(button.dataset.speed); updateSpeedButtonsActiveState(); }); }); - const filterNoneBtn = document.getElementById('filter-none'); - if(filterNoneBtn) filterNoneBtn.addEventListener('click', () => { - allSpeciesList.forEach(s => selectedSpecies[s.name] = false); - document.querySelectorAll('#species-filter-list input[type="checkbox"]').forEach(cb => cb.checked = false); - updateUIForCurrentPeriod(); - updateQuickFilterActiveState('filter-none'); - }); - const filterHomoBtn = document.getElementById('filter-homo'); - if(filterHomoBtn) filterHomoBtn.addEventListener('click', () => { - allSpeciesList.forEach(s => { - const isHomo = s.name.toLowerCase().includes('homo'); - selectedSpecies[s.name] = isHomo; - const cb = document.getElementById(`filter-${s.name.replace(/\W/g, '_')}`); - if(cb) cb.checked = isHomo; + ['filter-all', 'filter-none', 'filter-homo'].forEach(id => { + const btn = document.getElementById(id); + if (btn) btn.addEventListener('click', () => { + if (id === 'filter-all') allSpeciesList.forEach(s => selectedSpecies[s.name] = true); + else if (id === 'filter-none') allSpeciesList.forEach(s => selectedSpecies[s.name] = false); + else if (id === 'filter-homo') allSpeciesList.forEach(s => selectedSpecies[s.name] = s.name.toLowerCase().includes('homo')); + + document.querySelectorAll('#species-filter-list input[type="checkbox"]').forEach(cb => { + cb.checked = selectedSpecies[cb.value]; + }); + updateUIForCurrentPeriod(); updateQuickFilterActiveState(id); }); - updateUIForCurrentPeriod(); - updateQuickFilterActiveState('filter-homo'); }); - if(domElements.climateToggle) { - domElements.climateToggle.addEventListener('change', () => { - if (timePeriodsData.length > 0 && fetchedIceSheetGeoJson) { - updateUIForCurrentPeriod(); - } - }); - } + if(domElements.climateToggle) domElements.climateToggle.addEventListener('change', () => { + if (timePeriodsData.length > 0 && fetchedIceSheetGeoJson) updateUIForCurrentPeriod(); + }); - if (domElements.periodFilterSelect) { // Event listener for Period Navigator - domElements.periodFilterSelect.addEventListener('change', (event) => { - const selectedIndex = parseInt(event.target.value); - if (!isNaN(selectedIndex) && selectedIndex >= 0 && selectedIndex < timePeriodsData.length) { - currentPeriodIndex = selectedIndex; - pauseAnimation(); - updateUIForCurrentPeriod(); - } - }); - } + if (domElements.periodFilterSelect) domElements.periodFilterSelect.addEventListener('change', (event) => { + const selectedIndex = parseInt(event.target.value); + if (!isNaN(selectedIndex) && selectedIndex >= 0 && selectedIndex < timePeriodsData.length) { + currentPeriodIndex = selectedIndex; pauseAnimation(); updateUIForCurrentPeriod(); + } + }); } document.addEventListener('DOMContentLoaded', () => { - populateDomElements(); - addEventListeners(); - fetchAllRequiredData(); + populateDomElements(); addEventListeners(); fetchAllRequiredData(); }); </script> </body>