Replace SVG map with interactive Leaflet map

The commit focuses on replacing a static SVG biblical map with an interactive Leaflet map implementation. This enables better zooming, panning, and location interactions.
This commit is contained in:
2025-05-30 14:57:31 -04:00
parent fd99b37ce1
commit 46b021c1b4
+143 -225
View File
@@ -9,6 +9,8 @@
{% block og_title %}Biblical Maps - Important Bible Locations - KJV Study{% endblock %}
{% block head %}
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
<style>
.maps-container {
max-width: 1200px;
@@ -120,53 +122,63 @@
border: 2px solid var(--border-color);
border-radius: 8px;
background: white;
z-index: 1;
}
.location-point {
.leaflet-popup-content-wrapper {
background: var(--card-background);
color: var(--text-color);
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.leaflet-popup-content {
margin: 12px 16px;
line-height: 1.5;
font-family: 'Inter', sans-serif;
}
.leaflet-popup h3 {
margin: 0 0 8px 0;
color: var(--primary-color);
font-family: 'Crimson Text', serif;
font-size: 1.1rem;
}
.leaflet-popup p {
margin: 0 0 8px 0;
font-size: 0.9rem;
color: var(--text-muted);
font-style: italic;
}
.leaflet-popup-tip {
background: var(--card-background);
}
.location-marker {
border-radius: 50%;
border: 2px solid white;
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
cursor: pointer;
transition: all 0.2s ease;
stroke: white;
stroke-width: 2;
}
.location-point:hover {
r: 12;
stroke-width: 3;
filter: drop-shadow(0 0 8px rgba(0,0,0,0.3));
.location-marker:hover {
transform: scale(1.2);
box-shadow: 0 4px 12px rgba(0,0,0,0.4);
}
.location-label {
font-size: 10px;
font-weight: 600;
fill: #333;
font-family: 'Inter', sans-serif;
pointer-events: none;
.ot-marker {
background: #8b4513;
}
.location-label.small {
font-size: 8px;
.nt-marker {
background: #4169e1;
}
.legend-title {
font-size: 12px;
font-weight: 600;
fill: #333;
font-family: 'Inter', sans-serif;
}
.legend-text {
font-size: 10px;
fill: #666;
font-family: 'Inter', sans-serif;
}
.map-tooltip {
pointer-events: none;
}
.tooltip-text {
font-size: 11px;
font-family: 'Inter', sans-serif;
.journey-marker {
background: #ff6b35;
}
.testament-section {
@@ -438,164 +450,7 @@
🗺️ Interactive Biblical Map
</h2>
<div class="map-wrapper">
<svg viewBox="0 0 800 600" class="biblical-map" id="biblical-map">
<!-- Background -->
<rect width="800" height="600" fill="#e6f3ff"/>
<!-- Mediterranean Sea -->
<path d="M0 150 Q80 130 160 135 Q240 140 320 145 Q400 150 480 148 Q560 146 640 150 Q720 154 800 158 L800 0 L0 0 Z" fill="#4a90e2" opacity="0.6"/>
<!-- Red Sea -->
<path d="M470 480 Q480 450 490 420 Q500 390 510 360 Q520 330 530 300 L545 300 Q535 330 525 360 Q515 390 505 420 Q495 450 485 480 Z" fill="#4a90e2" opacity="0.6"/>
<!-- Land masses -->
<path d="M0 200 Q100 185 200 188 Q300 192 400 195 Q500 198 600 202 Q700 206 800 210 L800 600 L0 600 Z" fill="#d4c5a0" opacity="0.7"/>
<!-- Desert regions -->
<circle cx="450" cy="480" r="60" fill="#e6d4a6" opacity="0.4"/>
<circle cx="650" cy="420" r="80" fill="#e6d4a6" opacity="0.4"/>
<!-- Mountain ranges -->
<path d="M350 280 L360 270 L370 275 L380 265 L390 270 L400 260 L410 265 L420 255 L430 260 L440 250 L450 255 L460 245 L470 250 L480 240" stroke="#8b7355" stroke-width="2" fill="none"/>
<path d="M180 300 L190 290 L200 295 L210 285 L220 290 L230 280 L240 285 L250 275" stroke="#8b7355" stroke-width="2" fill="none"/>
<!-- River Jordan -->
<path d="M410 320 Q415 340 420 360 Q418 380 416 400 Q414 420 412 440" stroke="#4a90e2" stroke-width="3" fill="none" opacity="0.7"/>
<text x="430" y="355" class="location-label small" fill="#4a90e2">Jordan River</text>
<!-- Biblical Locations -->
<!-- Old Testament -->
<g class="ot-locations">
<!-- Garden of Eden -->
<circle cx="580" cy="350" r="8" fill="#8b4513" class="location-point" data-location="garden-of-eden" data-tooltip="Garden of Eden"/>
<text x="580" y="375" text-anchor="middle" class="location-label">Eden</text>
<!-- Mount Ararat -->
<circle cx="480" cy="280" r="8" fill="#8b4513" class="location-point" data-location="mount-ararat" data-tooltip="Mount Ararat"/>
<text x="480" y="305" text-anchor="middle" class="location-label">Ararat</text>
<!-- Ur of the Chaldees -->
<circle cx="650" cy="420" r="8" fill="#8b4513" class="location-point" data-location="ur" data-tooltip="Ur of the Chaldees"/>
<text x="650" y="445" text-anchor="middle" class="location-label">Ur</text>
<!-- Jerusalem -->
<circle cx="400" cy="380" r="10" fill="#d4af37" stroke="#8b4513" stroke-width="2" class="location-point" data-location="jerusalem" data-tooltip="Jerusalem - The Holy City"/>
<text x="400" y="405" text-anchor="middle" class="location-label" style="font-weight: bold;">Jerusalem</text>
<!-- Egypt -->
<circle cx="350" cy="500" r="8" fill="#8b4513" class="location-point" data-location="egypt" data-tooltip="Egypt"/>
<text x="350" y="525" text-anchor="middle" class="location-label">Egypt</text>
<!-- Mount Sinai -->
<circle cx="420" cy="480" r="8" fill="#8b4513" class="location-point" data-location="mount-sinai" data-tooltip="Mount Sinai"/>
<text x="420" y="505" text-anchor="middle" class="location-label">Sinai</text>
<!-- Babylon -->
<circle cx="570" cy="380" r="8" fill="#8b4513" class="location-point" data-location="babylon" data-tooltip="Babylon"/>
<text x="570" y="405" text-anchor="middle" class="location-label">Babylon</text>
<!-- Bethel -->
<circle cx="395" cy="365" r="6" fill="#8b4513" class="location-point" data-location="bethel" data-tooltip="Bethel"/>
<text x="395" y="385" text-anchor="middle" class="location-label small">Bethel</text>
<!-- Hebron -->
<circle cx="395" cy="395" r="6" fill="#8b4513" class="location-point" data-location="hebron" data-tooltip="Hebron"/>
<text x="395" y="415" text-anchor="middle" class="location-label small">Hebron</text>
<!-- Jericho -->
<circle cx="415" cy="375" r="6" fill="#8b4513" class="location-point" data-location="jericho" data-tooltip="Jericho"/>
<text x="415" y="395" text-anchor="middle" class="location-label small">Jericho</text>
</g>
<!-- New Testament -->
<g class="nt-locations">
<!-- Bethlehem -->
<circle cx="398" cy="388" r="8" fill="#4169e1" class="location-point" data-location="bethlehem" data-tooltip="Bethlehem - Birthplace of Jesus"/>
<text x="398" y="413" text-anchor="middle" class="location-label">Bethlehem</text>
<!-- Nazareth -->
<circle cx="385" cy="340" r="8" fill="#4169e1" class="location-point" data-location="nazareth" data-tooltip="Nazareth"/>
<text x="385" y="365" text-anchor="middle" class="location-label">Nazareth</text>
<!-- Sea of Galilee -->
<ellipse cx="410" cy="330" rx="12" ry="8" fill="#4a90e2" opacity="0.6" class="location-point" data-location="galilee" data-tooltip="Sea of Galilee"/>
<text x="410" y="355" text-anchor="middle" class="location-label">Sea of Galilee</text>
<!-- Antioch -->
<circle cx="440" cy="280" r="8" fill="#4169e1" class="location-point" data-location="antioch" data-tooltip="Antioch"/>
<text x="440" y="305" text-anchor="middle" class="location-label">Antioch</text>
<!-- Damascus -->
<circle cx="480" cy="320" r="8" fill="#4169e1" class="location-point" data-location="damascus" data-tooltip="Damascus"/>
<text x="480" y="345" text-anchor="middle" class="location-label">Damascus</text>
<!-- Calvary -->
<circle cx="402" cy="378" r="6" fill="#8B0000" class="location-point" data-location="calvary" data-tooltip="Calvary - Place of Crucifixion"/>
<text x="402" y="398" text-anchor="middle" class="location-label small" style="fill: #8b0000;">Calvary</text>
</g>
<!-- Paul's Journey locations -->
<g class="journey-locations">
<!-- Cyprus -->
<circle cx="380" cy="220" r="6" fill="#ff6b35" class="location-point" data-location="cyprus" data-tooltip="Cyprus"/>
<text x="380" y="240" text-anchor="middle" class="location-label small">Cyprus</text>
<!-- Ephesus -->
<circle cx="280" cy="250" r="6" fill="#ff6b35" class="location-point" data-location="ephesus" data-tooltip="Ephesus"/>
<text x="280" y="270" text-anchor="middle" class="location-label small">Ephesus</text>
<!-- Corinth -->
<circle cx="250" cy="280" r="6" fill="#ff6b35" class="location-point" data-location="corinth" data-tooltip="Corinth"/>
<text x="250" y="300" text-anchor="middle" class="location-label small">Corinth</text>
<!-- Athens -->
<circle cx="240" cy="290" r="6" fill="#ff6b35" class="location-point" data-location="athens" data-tooltip="Athens"/>
<text x="240" y="310" text-anchor="middle" class="location-label small">Athens</text>
<!-- Philippi -->
<circle cx="260" cy="260" r="6" fill="#ff6b35" class="location-point" data-location="philippi" data-tooltip="Philippi"/>
<text x="260" y="280" text-anchor="middle" class="location-label small">Philippi</text>
<!-- Rome -->
<circle cx="140" cy="280" r="8" fill="#ff6b35" class="location-point" data-location="rome" data-tooltip="Rome - Capital of the Empire"/>
<text x="140" y="305" text-anchor="middle" class="location-label">Rome</text>
<!-- Malta -->
<circle cx="180" cy="350" r="6" fill="#ff6b35" class="location-point" data-location="malta" data-tooltip="Malta"/>
<text x="180" y="370" text-anchor="middle" class="location-label small">Malta</text>
<!-- Patmos -->
<circle cx="300" cy="320" r="6" fill="#ff6b35" class="location-point" data-location="patmos" data-tooltip="Patmos"/>
<text x="300" y="340" text-anchor="middle" class="location-label small">Patmos</text>
<!-- Journey Routes -->
<path d="M440 280 Q400 250 380 220" stroke="#ff6b35" stroke-width="2" fill="none" opacity="0.5" stroke-dasharray="5,5"/>
<path d="M380 220 Q330 235 280 250" stroke="#ff6b35" stroke-width="2" fill="none" opacity="0.5" stroke-dasharray="5,5"/>
<path d="M280 250 Q265 265 250 280" stroke="#ff6b35" stroke-width="2" fill="none" opacity="0.5" stroke-dasharray="5,5"/>
<path d="M250 280 Q195 280 140 280" stroke="#ff6b35" stroke-width="2" fill="none" opacity="0.5" stroke-dasharray="5,5"/>
</g>
<!-- Map Legend -->
<g class="map-legend" transform="translate(20, 20)">
<rect x="0" y="0" width="180" height="90" fill="white" stroke="#ccc" rx="5" opacity="0.9"/>
<text x="10" y="20" class="legend-title">Biblical Locations</text>
<circle cx="20" cy="35" r="4" fill="#8b4513"/>
<text x="30" y="40" class="legend-text">Old Testament</text>
<circle cx="20" cy="55" r="4" fill="#4169e1"/>
<text x="30" y="60" class="legend-text">New Testament</text>
<circle cx="20" cy="75" r="4" fill="#ff6b35"/>
<text x="30" y="80" class="legend-text">Paul's Journeys</text>
</g>
<!-- Tooltip -->
<g id="map-tooltip" class="map-tooltip" style="display: none; pointer-events: none;">
<rect x="0" y="0" width="120" height="30" fill="black" opacity="0.8" rx="3"/>
<text x="60" y="20" text-anchor="middle" fill="white" class="tooltip-text"></text>
</g>
</svg>
<div id="biblical-map" class="biblical-map"></div>
</div>
<div class="map-controls">
@@ -731,31 +586,86 @@ function updateLocationCount(count) {
}
function initializeMap() {
const locationPoints = document.querySelectorAll('.location-point');
const tooltip = document.getElementById('map-tooltip');
const tooltipText = tooltip.querySelector('.tooltip-text');
locationPoints.forEach(point => {
point.addEventListener('mouseenter', function(e) {
const locationName = this.getAttribute('data-tooltip');
tooltipText.textContent = locationName;
tooltip.style.display = 'block';
const x = this.getAttribute('cx') - 60;
const y = this.getAttribute('cy') - 40;
tooltip.setAttribute('transform', `translate(${x}, ${y})`);
});
// Biblical locations with coordinates
const biblicalLocations = {
'garden-of-eden': { name: 'Garden of Eden', coords: [33.0, 44.0], type: 'ot', description: 'The original home of mankind' },
'mount-ararat': { name: 'Mount Ararat', coords: [39.7, 44.3], type: 'ot', description: 'Where Noah\'s ark came to rest' },
'ur': { name: 'Ur of the Chaldees', coords: [30.96, 46.1], type: 'ot', description: 'Abraham\'s birthplace' },
'jerusalem': { name: 'Jerusalem', coords: [31.7857, 35.2007], type: 'ot', description: 'The holy city, city of David' },
'egypt': { name: 'Egypt', coords: [26.8206, 30.8025], type: 'ot', description: 'Land of bondage and deliverance' },
'mount-sinai': { name: 'Mount Sinai', coords: [28.5, 33.9], type: 'ot', description: 'Where Moses received the Ten Commandments' },
'babylon': { name: 'Babylon', coords: [32.5355, 44.4275], type: 'ot', description: 'Place of exile for the Jewish people' },
'bethel': { name: 'Bethel', coords: [31.93, 35.22], type: 'ot', description: 'Where Jacob saw the ladder to heaven' },
'hebron': { name: 'Hebron', coords: [31.53, 35.1], type: 'ot', description: 'Where Abraham, Isaac, and Jacob are buried' },
'jericho': { name: 'Jericho', coords: [31.87, 35.44], type: 'ot', description: 'First city conquered in the Promised Land' },
point.addEventListener('mouseleave', function() {
tooltip.style.display = 'none';
});
'bethlehem': { name: 'Bethlehem', coords: [31.7054, 35.2024], type: 'nt', description: 'Birthplace of Jesus Christ' },
'nazareth': { name: 'Nazareth', coords: [32.7, 35.3], type: 'nt', description: 'Where Jesus grew up' },
'galilee': { name: 'Sea of Galilee', coords: [32.8, 35.6], type: 'nt', description: 'Where Jesus called disciples and performed miracles' },
'antioch': { name: 'Antioch', coords: [36.16, 36.2], type: 'nt', description: 'Where believers were first called Christians' },
'damascus': { name: 'Damascus', coords: [33.5, 36.3], type: 'nt', description: 'Where Paul was converted on the road' },
'calvary': { name: 'Calvary', coords: [31.7784, 35.2066], type: 'nt', description: 'The place where Jesus was crucified' },
point.addEventListener('click', function() {
const locationId = this.getAttribute('data-location');
highlightLocationCard(locationId);
scrollToLocationCard(locationId);
'cyprus': { name: 'Cyprus', coords: [35.1, 33.4], type: 'journey', description: 'First stop on Paul\'s first missionary journey' },
'ephesus': { name: 'Ephesus', coords: [37.95, 27.37], type: 'journey', description: 'Important center of early Christianity' },
'corinth': { name: 'Corinth', coords: [37.9, 22.9], type: 'journey', description: 'Major city where Paul established a church' },
'athens': { name: 'Athens', coords: [37.98, 23.73], type: 'journey', description: 'Where Paul preached at the Areopagus' },
'philippi': { name: 'Philippi', coords: [41.01, 24.28], type: 'journey', description: 'Where Paul and Silas were imprisoned' },
'rome': { name: 'Rome', coords: [41.9, 12.5], type: 'journey', description: 'Capital of the empire, destination of Paul\'s final journey' },
'malta': { name: 'Malta', coords: [35.9, 14.5], type: 'journey', description: 'Island where Paul was shipwrecked' },
'patmos': { name: 'Patmos', coords: [37.3, 26.55], type: 'journey', description: 'Island where John received the Revelation' }
};
// Initialize the map
const map = L.map('biblical-map').setView([31.5, 35.0], 6);
// Add OpenStreetMap tiles
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
maxZoom: 18,
}).addTo(map);
// Create layer groups for different location types
const otLayer = L.layerGroup().addTo(map);
const ntLayer = L.layerGroup().addTo(map);
const journeyLayer = L.layerGroup().addTo(map);
// Add markers for each location
Object.entries(biblicalLocations).forEach(([id, location]) => {
const marker = L.circleMarker(location.coords, {
radius: location.type === 'ot' && location.name === 'Jerusalem' ? 10 : 8,
fillColor: location.type === 'ot' ? '#8b4513' : location.type === 'nt' ? '#4169e1' : '#ff6b35',
color: 'white',
weight: 2,
opacity: 1,
fillOpacity: 0.8
});
// Add popup with location info
marker.bindPopup(`
<h3>${location.name}</h3>
<p>${location.description}</p>
`);
// Add click handler to highlight location cards
marker.on('click', function() {
highlightLocationCard(id);
scrollToLocationCard(id);
});
// Add to appropriate layer
if (location.type === 'ot') {
marker.addTo(otLayer);
} else if (location.type === 'nt') {
marker.addTo(ntLayer);
} else {
marker.addTo(journeyLayer);
}
});
// Store layers globally for toggle functionality
window.mapLayers = { otLayer, ntLayer, journeyLayer };
window.biblicalMap = map;
}
function highlightLocationCard(locationId) {
@@ -817,18 +727,26 @@ function getLocationNameFromId(locationId) {
return locationMap[locationId] || locationId;
}
function toggleMapLayer(layerClass) {
const layer = document.querySelector(`.${layerClass}`);
const button = document.getElementById(layerClass.replace('-locations', '-toggle'));
function toggleMapLayer(layerType) {
const button = document.getElementById(layerType.replace('-locations', '-toggle'));
const layerMap = {
'ot-locations': 'otLayer',
'nt-locations': 'ntLayer',
'journey-locations': 'journeyLayer'
};
if (layer.style.display === 'none') {
layer.style.display = 'block';
button.classList.add('active');
button.style.opacity = '1';
} else {
layer.style.display = 'none';
button.classList.remove('active');
button.style.opacity = '0.5';
if (window.mapLayers && window.biblicalMap) {
const layer = window.mapLayers[layerMap[layerType]];
if (window.biblicalMap.hasLayer(layer)) {
window.biblicalMap.removeLayer(layer);
button.classList.remove('active');
button.style.opacity = '0.5';
} else {
window.biblicalMap.addLayer(layer);
button.classList.add('active');
button.style.opacity = '1';
}
}
}