A Pen by Konstantin (Konsti) on CodePen.
Created
December 25, 2025 20:03
-
-
Save thekonsti1/f8ae1bd210afb0d79f0daa9061bf55b5 to your computer and use it in GitHub Desktop.
Untitled
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="de"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Global Price Tracker</title> | |
| <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" /> | |
| <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> | |
| <style> | |
| :root { --primary: #1a73e8; --bg: #f8f9fa; } | |
| body { margin: 0; font-family: 'Segoe UI', sans-serif; display: flex; height: 100vh; overflow: hidden; } | |
| /* Seitenleiste */ | |
| #sidebar { | |
| width: 300px; background: white; border-right: 1px solid #ddd; | |
| display: flex; flex-direction: column; z-index: 2000; | |
| } | |
| #sidebar-header { padding: 20px; background: var(--primary); color: white; } | |
| #price-list { flex: 1; overflow-y: auto; padding: 10px; } | |
| .price-item { | |
| padding: 12px; border-bottom: 1px solid #eee; cursor: pointer; | |
| transition: background 0.2s; border-radius: 8px; margin-bottom: 5px; | |
| } | |
| .price-item:hover { background: #eef2ff; } | |
| .price-item b { color: #d93025; font-size: 1.1em; } | |
| .price-item small { display: block; color: #666; margin-top: 4px; } | |
| /* Karte & Suche */ | |
| #main { flex: 1; position: relative; } | |
| #map { height: 100%; width: 100%; cursor: crosshair; } | |
| #search-ui { | |
| position: absolute; top: 15px; left: 15px; z-index: 1000; | |
| background: white; padding: 10px; border-radius: 10px; | |
| box-shadow: 0 4px 15px rgba(0,0,0,0.2); width: 280px; display: flex; gap: 5px; | |
| } | |
| input { flex: 1; padding: 10px; border: 1px solid #ccc; border-radius: 6px; outline: none; } | |
| button { padding: 10px; background: var(--primary); color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: bold; } | |
| @media (max-width: 600px) { | |
| body { flex-direction: column; } | |
| #sidebar { width: 100%; height: 30%; border-right: none; border-top: 1px solid #ddd; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="sidebar"> | |
| <div id="sidebar-header"> | |
| <h3 style="margin:0">đź’° Alle Preise</h3> | |
| </div> | |
| <div id="price-list"> | |
| </div> | |
| </div> | |
| <div id="main"> | |
| <div id="search-ui"> | |
| <input type="text" id="addrInp" placeholder="Adresse suchen..."> | |
| <button onclick="searchAddr()">Finden</button> | |
| </div> | |
| <div id="map"></div> | |
| </div> | |
| <script> | |
| const bounds = L.latLngBounds(L.latLng(-85, -180), L.latLng(85, 180)); | |
| const map = L.map('map', { | |
| maxBounds: bounds, | |
| maxBoundsViscosity: 1.0, | |
| minZoom: 2, | |
| worldCopyJump: false | |
| }).setView([51.16, 10.45], 5); | |
| L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { | |
| noWrap: true, bounds: bounds | |
| }).addTo(map); | |
| let storageKey = 'price_app_shared_v3'; | |
| let data = JSON.parse(localStorage.getItem(storageKey) || '[]'); | |
| function updateUI() { | |
| const listContainer = document.getElementById('price-list'); | |
| listContainer.innerHTML = ''; | |
| // Marker auf Karte löschen und neu zeichnen | |
| map.eachLayer((layer) => { | |
| if (layer instanceof L.Marker) map.removeLayer(layer); | |
| }); | |
| data.forEach((item, index) => { | |
| // Marker zur Karte hinzufĂĽgen | |
| const marker = L.marker([item.lat, item.lng]).addTo(map) | |
| .bindPopup(`<b style="font-size:1.4em; color:#d93025;">${item.price} €</b>`); | |
| // Element zur Liste hinzufĂĽgen | |
| const div = document.createElement('div'); | |
| div.className = 'price-item'; | |
| div.innerHTML = `<b>${item.price} €</b> <small>${item.address || 'Gespeicherter Ort'}</small>`; | |
| div.onclick = () => { | |
| map.setView([item.lat, item.lng], 15); | |
| marker.openPopup(); | |
| }; | |
| listContainer.prepend(div); // Neueste oben | |
| }); | |
| } | |
| map.on('click', async function(e) { | |
| if (e.originalEvent.target.classList.contains('leaflet-marker-icon')) return; | |
| const price = prompt("Welchen Preis möchtest du eintragen? (in €)"); | |
| if (!price) return; | |
| // Optional: Adresse fĂĽr die Liste abrufen | |
| let addrName = "Ort auf Karte"; | |
| try { | |
| const res = await fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${e.latlng.lat}&lon=${e.latlng.lng}`); | |
| const resData = await res.json(); | |
| addrName = resData.display_name.split(',')[0] + ", " + (resData.address.city || resData.address.town || ""); | |
| } catch(err) {} | |
| const entry = { lat: e.latlng.lat, lng: e.latlng.lng, price: price, address: addrName }; | |
| data.push(entry); | |
| localStorage.setItem(storageKey, JSON.stringify(data)); | |
| updateUI(); | |
| }); | |
| async function searchAddr() { | |
| const query = document.getElementById('addrInp').value; | |
| const res = await fetch(`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(query)}`); | |
| const results = await res.json(); | |
| if (results.length > 0) { | |
| map.setView([results[0].lat, results[0].lon], 16); | |
| } | |
| } | |
| // Initiales Laden | |
| updateUI(); | |
| </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
| body { | |
| font-family: system-ui; | |
| background: #f06d06; | |
| color: white; | |
| text-align: center; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment