Last active
June 28, 2026 16:57
-
-
Save JohnLBevan/8607af4d89b085ee1281b3b6a0e52dbe to your computer and use it in GitHub Desktop.
Tom Scott Leylines modernised by Gemini
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Modern Ley Line Locator</title> | |
| <!-- Leaflet CSS --> | |
| <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" /> | |
| <style> | |
| body { margin: 0; padding: 0; font-family: sans-serif; } | |
| #map { height: 100vh; width: 100vw; } | |
| #ui { | |
| position: absolute; | |
| top: 10px; left: 50px; | |
| z-index: 1000; | |
| background: white; | |
| padding: 15px; | |
| border-radius: 8px; | |
| box-shadow: 0 2px 5px rgba(0,0,0,0.3); | |
| max-width: 300px; | |
| } | |
| button { padding: 8px 12px; cursor: pointer; margin-top: 10px; width: 100%;} | |
| #status { margin-top: 10px; font-size: 0.9em; color: #555; } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="ui"> | |
| <h3>Ley Line Locator</h3> | |
| <p>Click anywhere on the map to set your "house", then click the button below to find ancient sites and intersecting ley lines.</p> | |
| <button id="findLinesBtn" disabled>Find Ley Lines</button> | |
| <div id="status">Waiting for location...</div> | |
| </div> | |
| <div id="map"></div> | |
| <!-- Leaflet JS --> | |
| <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> | |
| <script> | |
| const map = L.map('map').setView([51.505, -0.09], 11); // Default to London | |
| L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { | |
| attribution: '© OpenStreetMap contributors' | |
| }).addTo(map); | |
| let userMarker = null; | |
| let siteMarkers = L.layerGroup().addTo(map); | |
| let leyLines = L.layerGroup().addTo(map); | |
| // Handle map clicks to set the target location | |
| map.on('click', function(e) { | |
| if (!userMarker) { | |
| userMarker = L.marker(e.latlng, { icon: L.icon({ | |
| iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-red.png', | |
| iconSize: [25, 41], iconAnchor: [12, 41] | |
| })}).addTo(map); | |
| } else { | |
| userMarker.setLatLng(e.latlng); | |
| } | |
| document.getElementById('findLinesBtn').disabled = false; | |
| document.getElementById('status').innerText = "Location set. Ready to search."; | |
| }); | |
| document.getElementById('findLinesBtn').addEventListener('click', async () => { | |
| if (!userMarker) return; | |
| const lat = userMarker.getLatLng().lat; | |
| const lng = userMarker.getLatLng().lng; | |
| document.getElementById('status').innerText = "Querying OpenStreetMap for ancient sites..."; | |
| siteMarkers.clearLayers(); | |
| leyLines.clearLayers(); | |
| // Overpass API query: Find historic monuments, archaeological sites, or ruins within 10km | |
| const query = ` | |
| [out:json]; | |
| ( | |
| node["historic"="monument"](around:10000, ${lat}, ${lng}); | |
| node["historic"="archaeological_site"](around:10000, ${lat}, ${lng}); | |
| node["historic"="ruins"](around:10000, ${lat}, ${lng}); | |
| ); | |
| out body; | |
| `; | |
| try { | |
| const response = await fetch('https://overpass-api.de/api/interpreter', { | |
| method: 'POST', | |
| body: query | |
| }); | |
| const data = await response.json(); | |
| const sites = data.elements; | |
| document.getElementById('status').innerText = `Found ${sites.length} sites. Calculating lines...`; | |
| // Plot the sites | |
| sites.forEach(site => { | |
| L.circleMarker([site.lat, site.lon], { radius: 4, color: 'blue' }) | |
| .bindPopup(site.tags.name || "Unnamed ancient site") | |
| .addTo(siteMarkers); | |
| }); | |
| // Find lines that cross near the user's marker | |
| let lineCount = 0; | |
| const threshold = 0.001; // Roughly 100 meters in decimal degrees | |
| for (let i = 0; i < sites.length; i++) { | |
| for (let j = i + 1; j < sites.length; j++) { | |
| const dist = distanceToLine( | |
| lat, lng, | |
| sites[i].lat, sites[i].lon, | |
| sites[j].lat, sites[j].lon | |
| ); | |
| if (dist < threshold) { | |
| L.polyline([ | |
| [sites[i].lat, sites[i].lon], | |
| [sites[j].lat, sites[j].lon] | |
| ], { color: 'purple', weight: 2, opacity: 0.5 }).addTo(leyLines); | |
| lineCount++; | |
| } | |
| } | |
| } | |
| document.getElementById('status').innerText = `Drew ${lineCount} ley lines intersecting your location!`; | |
| } catch (error) { | |
| document.getElementById('status').innerText = "Error fetching data."; | |
| console.error(error); | |
| } | |
| }); | |
| // Calculate perpendicular distance from point (x0, y0) to line passing through (x1, y1) and (x2, y2) | |
| function distanceToLine(x0, y0, x1, y1, x2, y2) { | |
| const numerator = Math.abs((x2 - x1) * (y1 - y0) - (x1 - x0) * (y2 - y1)); | |
| const denominator = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)); | |
| return denominator === 0 ? 0 : numerator / denominator; | |
| } | |
| </script> | |
| </body> | |
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Static Ley Line Locator</title> | |
| <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" /> | |
| <style> | |
| body { margin: 0; padding: 0; font-family: sans-serif; } | |
| #map { height: 100vh; width: 100vw; } | |
| #ui { | |
| position: absolute; | |
| top: 10px; left: 50px; | |
| z-index: 1000; | |
| background: white; | |
| padding: 15px; | |
| border-radius: 8px; | |
| box-shadow: 0 2px 5px rgba(0,0,0,0.3); | |
| max-width: 320px; | |
| } | |
| button { padding: 10px; cursor: pointer; margin-top: 10px; width: 100%; font-weight: bold; } | |
| #status { margin-top: 10px; font-size: 0.9em; color: #333; } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="ui"> | |
| <h3>Static Ley Line Locator</h3> | |
| <p>Click the map to place your target, then calculate the mystical alignments!</p> | |
| <button id="findLinesBtn" disabled>Calculate Ley Lines</button> | |
| <div id="status">Waiting for location...</div> | |
| </div> | |
| <div id="map"></div> | |
| <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> | |
| <script> | |
| // Center on the UK | |
| const map = L.map('map').setView([52.5, -1.5], 7); | |
| L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { | |
| attribution: '© OpenStreetMap contributors' | |
| }).addTo(map); | |
| let userMarker = null; | |
| let siteMarkers = L.layerGroup().addTo(map); | |
| let leyLines = L.layerGroup().addTo(map); | |
| // 1. A static list of famous UK sites | |
| const staticSites = [ | |
| { name: "Stonehenge", lat: 51.1788, lng: -1.8262 }, | |
| { name: "Avebury Stone Circle", lat: 51.4286, lng: -1.8542 }, | |
| { name: "Glastonbury Tor", lat: 51.1449, lng: -2.6985 }, | |
| { name: "Castlerigg Stone Circle", lat: 54.6028, lng: -3.0984 }, | |
| { name: "Bryn Celli Ddu", lat: 53.2075, lng: -4.2355 }, | |
| { name: "Callanish Stones", lat: 58.1979, lng: -6.7452 }, | |
| { name: "Maeshowe", lat: 58.9967, lng: -3.1882 } | |
| ]; | |
| // 2. Simulate a massive database to make the math joke work | |
| // (Generates 500 "Minor Barrows/Stones" across the UK bounding box) | |
| for (let i = 0; i < 500; i++) { | |
| staticSites.push({ | |
| name: `Unrecorded Earthwork #${i+1}`, | |
| // UK approximate bounding box coordinates | |
| lat: 50 + Math.random() * 9, | |
| lng: -7 + Math.random() * 9 | |
| }); | |
| } | |
| // Plot all sites on load | |
| staticSites.forEach(site => { | |
| L.circleMarker([site.lat, site.lng], { radius: 3, color: 'blue', opacity: 0.5 }) | |
| .bindPopup(site.name) | |
| .addTo(siteMarkers); | |
| }); | |
| // Handle map clicks | |
| map.on('click', function(e) { | |
| if (!userMarker) { | |
| userMarker = L.marker(e.latlng, { icon: L.icon({ | |
| iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-red.png', | |
| iconSize: [25, 41], iconAnchor: [12, 41] | |
| })}).addTo(map); | |
| } else { | |
| userMarker.setLatLng(e.latlng); | |
| } | |
| document.getElementById('findLinesBtn').disabled = false; | |
| document.getElementById('status').innerText = "Location set. Ready to calculate."; | |
| }); | |
| // Calculate lines on button click | |
| document.getElementById('findLinesBtn').addEventListener('click', () => { | |
| if (!userMarker) return; | |
| const targetLat = userMarker.getLatLng().lat; | |
| const targetLng = userMarker.getLatLng().lng; | |
| document.getElementById('status').innerText = `Calculating lines between ${staticSites.length} sites...`; | |
| leyLines.clearLayers(); | |
| setTimeout(() => { | |
| let lineCount = 0; | |
| // Threshold is roughly 2km in decimal degrees to account for the mock data spread | |
| const threshold = 0.02; | |
| // Combinatorics: check every site against every other site | |
| for (let i = 0; i < staticSites.length; i++) { | |
| for (let j = i + 1; j < staticSites.length; j++) { | |
| const dist = distanceToLine( | |
| targetLat, targetLng, | |
| staticSites[i].lat, staticSites[i].lng, | |
| staticSites[j].lat, staticSites[j].lng | |
| ); | |
| if (dist < threshold) { | |
| L.polyline([ | |
| [staticSites[i].lat, staticSites[i].lng], | |
| [staticSites[j].lat, staticSites[j].lng] | |
| ], { color: 'purple', weight: 1, opacity: 0.3 }).addTo(leyLines); | |
| lineCount++; | |
| } | |
| } | |
| } | |
| document.getElementById('status').innerText = `Mathematical inevitability complete! Drew ${lineCount} ley lines intersecting your location.`; | |
| }, 50); // Small timeout to allow UI to update before heavy math blocks the main thread | |
| }); | |
| // Mathematical perpendicular distance formula | |
| function distanceToLine(x0, y0, x1, y1, x2, y2) { | |
| const numerator = Math.abs((x2 - x1) * (y1 - y0) - (x1 - x0) * (y2 - y1)); | |
| const denominator = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)); | |
| return denominator === 0 ? 0 : numerator / denominator; | |
| } | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment