9a61f55cb1
- Design-System (assets/css/design-system.css) mit 21 Komponenten, iPad-Responsive-Breakpoints, Touch-Ziele 36px, Music-Player, Glossar-Tooltips - Templates (sims/template.html + student/teacher-Dashboard) - Docs: module-interface.md (inkl. 4a Sprachregel, 4b Leichte Sprache, 4c iPad), content-architecture.md, crash-recovery.md, music-registry.md - Admin-Infrastruktur: admin-modules.html + api/admin-modules.php (Titel, Emoji, Bild, Status, Dauer, Alter pro Modul) - Inbox-System: _inbox/README.md + _status.md fuer Atlas + Briefings an Klima, Glossar, Lehrplan, Fluss - Zentrale SFX-Pipeline (scripts/generate-sounds.py) - DALL-E-Bilder: 8 Badges + 5 Glossar-Repraesentationsbilder (Querformat) - Logo + Inter-Font lokal - PHP-APIs: admin, glossar, levels, licenses, progress, waypoints, assignments, profile, tickets - Spielsprache entfernt (admin-modules, admin-levels, schueler) - Landing-Page-Bearbeitungen (Boote sichtbarer, Button-Hintergrund) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
455 lines
34 KiB
HTML
455 lines
34 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>GeoGraSim — Helikopter-Navigation</title>
|
||
<link rel="icon" type="image/png" href="favicon-96x96.png">
|
||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
|
||
<style>
|
||
*{margin:0;padding:0;box-sizing:border-box}
|
||
body{font-family:'Inter',system-ui,sans-serif;overflow:hidden;height:100vh;background:#e8e4d8}
|
||
#map{width:100%;height:100vh}
|
||
|
||
/* Aktive Basen: groß, farbig */
|
||
.heli-active{font-size:32px;text-align:center;line-height:1;filter:drop-shadow(0 2px 4px rgba(0,0,0,.4))}
|
||
.heli-active.oeamtc{filter:drop-shadow(0 2px 4px rgba(0,0,0,.3)) brightness(1.3) saturate(3) hue-rotate(-30deg) sepia(.3)}
|
||
.heli-active.ara{filter:drop-shadow(0 2px 4px rgba(0,0,0,.3)) hue-rotate(320deg) saturate(2)}
|
||
.heli-active.police{filter:drop-shadow(0 2px 4px rgba(0,0,0,.3)) hue-rotate(180deg) saturate(1.5) brightness(1.1)}
|
||
.heli-active.bh{filter:drop-shadow(0 2px 4px rgba(0,0,0,.3)) hue-rotate(90deg) saturate(2)}
|
||
|
||
/* Inaktive Basen: klein, deutlich sichtbar aber entsättigt */
|
||
.heli-inactive{font-size:18px;text-align:center;line-height:1;filter:drop-shadow(0 1px 3px rgba(0,0,0,.3)) grayscale(.7) brightness(.7) contrast(1.2)}
|
||
|
||
/* Labels/Pills */
|
||
.heli-pill{font-family:'Inter',system-ui,sans-serif;font-weight:600;font-size:11px;white-space:nowrap;padding:3px 8px;border-radius:5px;background:rgba(255,255,255,.92);box-shadow:0 1px 4px rgba(0,0,0,.15);line-height:1.3;max-width:180px}
|
||
.heli-pill .pill-name{font-weight:800;display:block}
|
||
.heli-pill .pill-org{font-size:8px;opacity:.7;display:block}
|
||
.heli-pill.oeamtc{border-left:4px solid #d4a017;color:#7a5f00}
|
||
.heli-pill.ara{border-left:4px solid #cc3333;color:#882222}
|
||
.heli-pill.police{border-left:4px solid #2255aa;color:#1a3a77}
|
||
.heli-pill.bh{border-left:4px solid #228833;color:#145522}
|
||
|
||
.heli-pill-sm{font-family:'Inter',system-ui,sans-serif;font-weight:700;font-size:10px;white-space:nowrap;padding:2px 6px;border-radius:3px;background:rgba(255,255,255,.88);box-shadow:0 1px 3px rgba(0,0,0,.15);color:#555;border-left:3px solid #888}
|
||
|
||
/* Marker: weiße Kreise mit Rahmen */
|
||
.marker-circle{width:26px;height:26px;background:#fff;border:2.5px solid #1f4e5a;border-radius:50%;box-shadow:0 2px 6px rgba(0,0,0,.3);display:flex;align-items:center;justify-content:center;font-size:13px;line-height:1}
|
||
.marker-circle.capital{border-color:#e8833a;width:30px;height:30px;font-size:15px}
|
||
.marker-circle.peak{border-color:#5a8a5e;width:24px;height:24px;font-size:12px}
|
||
.marker-circle.heli-m{border-color:#d4a017;width:30px;height:30px;font-size:16px}
|
||
.marker-circle.heli-m.inactive{width:22px;height:22px;font-size:11px;border-color:#888;opacity:.7}
|
||
|
||
/* Stadt-Pills */
|
||
.city-pill{font-family:'Inter',system-ui,sans-serif;font-weight:700;font-size:11px;white-space:nowrap;padding:4px 10px;border-radius:5px;background:rgba(218,232,236,.94);box-shadow:0 1px 4px rgba(0,0,0,.15);color:#1f4e5a;border-bottom:2.5px solid #4a7c8a;display:flex;align-items:center;height:22px}
|
||
.city-pill.capital{color:#e8833a;background:rgba(232,200,170,.92);border-bottom-color:#e8833a}
|
||
.city-pill .city-pop{font-size:8px;font-weight:500;color:#8a8a8a;margin-left:4px}
|
||
|
||
/* Berg-Pills */
|
||
.peak-pill{font-family:'Inter',system-ui,sans-serif;font-weight:700;font-size:10px;white-space:nowrap;padding:4px 9px;border-radius:5px;background:rgba(220,234,221,.94);box-shadow:0 1px 4px rgba(0,0,0,.12);color:#3a6b3e;border-bottom:2.5px solid #5a8a5e;display:flex;align-items:center;height:20px}
|
||
.peak-pill .peak-ele{font-size:8px;font-weight:500;color:#5a8a5e;margin-left:4px}
|
||
|
||
/* Quartett-Popup */
|
||
.heli-popup .leaflet-popup-content{margin:0;padding:0}
|
||
.heli-popup .leaflet-popup-content-wrapper{border-radius:12px;padding:0;overflow:hidden;box-shadow:0 4px 20px rgba(0,0,0,.15)}
|
||
.quartett{width:260px;font-family:'Inter',system-ui,sans-serif}
|
||
.quartett-img{height:140px;overflow:hidden;background:#f7f6f3;display:flex;align-items:center;justify-content:center}
|
||
.quartett-img img{width:100%;height:100%;object-fit:cover}
|
||
.quartett-body{padding:10px 12px 12px}
|
||
.quartett-name{font-size:13px;font-weight:800;color:#1f4e5a;margin-bottom:2px}
|
||
.quartett-org{font-size:10px;color:#8a8a8a;margin-bottom:8px}
|
||
.quartett-stats{display:grid;grid-template-columns:1fr 1fr;gap:4px}
|
||
.quartett-stat{background:#f7f6f3;border-radius:5px;padding:4px 6px}
|
||
.quartett-stat .qs-label{font-size:8px;color:#8a8a8a;font-weight:500}
|
||
.quartett-stat .qs-val{font-size:12px;font-weight:800;color:#1f4e5a}
|
||
.quartett-fact{margin-top:8px;font-size:10px;color:#4a4a4a;font-style:italic;line-height:1.4;border-top:1px solid #eee;padding-top:6px}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="map"></div>
|
||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||
<script>
|
||
var bounds = L.latLngBounds(L.latLng(46.3, 9.4), L.latLng(49.1, 17.2));
|
||
|
||
var map = L.map('map', {
|
||
zoomControl: false,
|
||
maxBounds: bounds.pad(0.05),
|
||
maxBoundsViscosity: 1.0,
|
||
minZoom: 7,
|
||
maxZoom: 15
|
||
}).setView([47.4, 13.2], 8);
|
||
|
||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
||
maxZoom: 18
|
||
}).addTo(map);
|
||
|
||
L.control.zoom({position: 'topright'}).addTo(map);
|
||
L.control.scale({position: 'bottomleft', metric: true, imperial: false, maxWidth: 200}).addTo(map);
|
||
|
||
// === HUBSCHRAUBER-TYPEN (Quartett-Daten) ===
|
||
var HELI_TYPES = {
|
||
'EC135': {name:'Eurocopter EC135', img:'sims/heli/assets/heli-ec135.png', engines:2, crew:'1 Pilot + 1 Notarzt + 1 Sanitäter', maxSpeed:254, range:635, ceiling:6096, weight:2910, seats:6, role:'Rettung & Notfall', fact:'Der EC135 ist der meistgenutzte Rettungshubschrauber in Europa.', wiki:'https://de.wikipedia.org/wiki/Eurocopter_EC_135'},
|
||
'H145': {name:'Airbus H145', img:'sims/heli/assets/heli-h145.png', engines:2, crew:'1–2 Piloten + Rettungsteam', maxSpeed:268, range:680, ceiling:5485, weight:3700, seats:8, role:'Rettung & Intensivtransport', fact:'Der H145 kann auch bei Nacht und schlechtem Wetter fliegen (IFR).', wiki:'https://de.wikipedia.org/wiki/Airbus_Helicopters_H145'},
|
||
'AW169': {name:'Leonardo AW169', img:'sims/heli/assets/heli-aw169.png', engines:2, crew:'1–2 Piloten + Besatzung', maxSpeed:280, range:820, ceiling:4572, weight:4600, seats:10, role:'Polizei & Überwachung', fact:'Die Flugpolizei nennt ihre Hubschrauber „Libelle".', wiki:'https://de.wikipedia.org/wiki/Leonardo_AW169'},
|
||
'AB212': {name:'Agusta-Bell AB212', img:'sims/heli/assets/heli-ab212.png', engines:2, crew:'2 Piloten + Besatzung', maxSpeed:240, range:450, ceiling:4630, weight:5070, seats:14, role:'Militär & Katastrophenhilfe', fact:'Das Bundesheer setzt den AB212 seit den 1980er-Jahren ein.', wiki:'https://de.wikipedia.org/wiki/Agusta-Bell_AB_212'},
|
||
};
|
||
|
||
// === ALLE HUBSCHRAUBER-BASEN ÖSTERREICH ===
|
||
var bases = [
|
||
// ÖAMTC Christophorus
|
||
{name:'Christophorus 1', code:'C1', org:'ÖAMTC', orgClass:'oeamtc', lat:47.2600, lon:11.3439, active:true, heli:'EC135', region:'Innsbruck / Nordtirol'},
|
||
{name:'Christophorus 2', code:'C2', org:'ÖAMTC', orgClass:'oeamtc', lat:48.4306, lon:15.6100, active:false, heli:'EC135', region:'Gneixendorf / Waldviertel'},
|
||
{name:'Christophorus 3', code:'C3', org:'ÖAMTC', orgClass:'oeamtc', lat:47.8419, lon:16.2331, active:false, heli:'EC135', region:'Wiener Neustadt'},
|
||
{name:'Christophorus 4', code:'C4', org:'ÖAMTC', orgClass:'oeamtc', lat:47.3801, lon:12.3580, active:true, heli:'H145', region:'Reith bei Kitzbühel'},
|
||
{name:'Christophorus 5', code:'C5', org:'ÖAMTC', orgClass:'oeamtc', lat:47.1597, lon:10.5933, active:true, heli:'H145', region:'Zams / Oberinntal'},
|
||
{name:'Christophorus 6', code:'C6', org:'ÖAMTC', orgClass:'oeamtc', lat:47.7950, lon:13.0044, active:false, heli:'H145', region:'Salzburg'},
|
||
{name:'Christophorus 7', code:'C7', org:'ÖAMTC', orgClass:'oeamtc', lat:46.8297, lon:12.7647, active:true, heli:'EC135', region:'Lienz / Osttirol'},
|
||
{name:'Christophorus 8', code:'C8', org:'ÖAMTC', orgClass:'oeamtc', lat:47.1828, lon:9.7072, active:true, heli:'H145', region:'Nenzing / Vorarlberg'},
|
||
{name:'Christophorus 9', code:'C9', org:'ÖAMTC', orgClass:'oeamtc', lat:48.1172, lon:16.5025, active:false, heli:'EC135', region:'Wien'},
|
||
{name:'Christophorus 10', code:'C10', org:'ÖAMTC', orgClass:'oeamtc', lat:48.2331, lon:14.1875, active:false, heli:'EC135', region:'Linz'},
|
||
{name:'Christophorus 11', code:'C11', org:'ÖAMTC', orgClass:'oeamtc', lat:46.6503, lon:14.3372, active:false, heli:'EC135', region:'Klagenfurt'},
|
||
{name:'Christophorus 12', code:'C12', org:'ÖAMTC', orgClass:'oeamtc', lat:47.0094, lon:15.4106, active:false, heli:'EC135', region:'Graz'},
|
||
{name:'Christophorus 14', code:'C14', org:'ÖAMTC', orgClass:'oeamtc', lat:47.4756, lon:14.0042, active:false, heli:'H145', region:'Niederöblarn'},
|
||
{name:'Christophorus 15', code:'C15', org:'ÖAMTC', orgClass:'oeamtc', lat:47.9483, lon:14.8894, active:false, heli:'EC135', region:'Ybbsitz'},
|
||
{name:'Christophorus 16', code:'C16', org:'ÖAMTC', orgClass:'oeamtc', lat:47.2886, lon:16.2069, active:false, heli:'EC135', region:'Oberwart'},
|
||
{name:'Christophorus 17', code:'C17', org:'ÖAMTC', orgClass:'oeamtc', lat:47.3500, lon:11.7000, active:false, heli:'EC135', region:'Vomp / Inntal'},
|
||
|
||
// ARA Flugrettung
|
||
{name:'Alpin 1 Reutte', code:'RK1', org:'ARA Flugrettung', orgClass:'ara', lat:47.4858, lon:10.7181, active:true, heli:'H145', region:'Reutte / Außerfern'},
|
||
{name:'Alpin 2 Fresach', code:'RK2', org:'ARA Flugrettung', orgClass:'ara', lat:46.7250, lon:13.6825, active:false, heli:'H145', region:'Fresach / Kärnten'},
|
||
{name:'Alpin 3 Ybbsitz', code:'RK3', org:'ARA Flugrettung', orgClass:'ara', lat:47.9500, lon:14.8900, active:false, heli:'EC135', region:'Ybbsitz / Mostviertel'},
|
||
{name:'Alpin 5 Salzburg', code:'RK5', org:'ARA Flugrettung', orgClass:'ara', lat:47.7933, lon:13.0100, active:false, heli:'H145', region:'Salzburg'},
|
||
{name:'Alpin 6 Linz', code:'RK6', org:'ARA Flugrettung', orgClass:'ara', lat:48.2333, lon:14.2000, active:false, heli:'EC135', region:'Linz'},
|
||
|
||
// Polizei/BMI Flugpolizei
|
||
{name:'Libelle Innsbruck', code:'LIB-I', org:'Flugpolizei BMI', orgClass:'police', lat:47.2583, lon:11.3556, active:true, heli:'AW169', region:'Innsbruck'},
|
||
{name:'Hohenems (Flugplatz)', code:'LIB-H', org:'Flugpolizei BMI', orgClass:'police', lat:47.38154, lon:9.69851, active:true, heli:'AW169', region:'Hohenems / Rheintal'},
|
||
{name:'Libelle Wien', code:'LIB-W', org:'Flugpolizei BMI', orgClass:'police', lat:48.1103, lon:16.5697, active:false, heli:'AW169', region:'Wien'},
|
||
{name:'Libelle Salzburg', code:'LIB-S', org:'Flugpolizei BMI', orgClass:'police', lat:47.7933, lon:13.0033, active:false, heli:'AW169', region:'Salzburg'},
|
||
{name:'Libelle Klagenfurt', code:'LIB-K', org:'Flugpolizei BMI', orgClass:'police', lat:46.6428, lon:14.3375, active:false, heli:'AW169', region:'Klagenfurt'},
|
||
{name:'Libelle Linz', code:'LIB-L', org:'Flugpolizei BMI', orgClass:'police', lat:48.2400, lon:14.1867, active:false, heli:'AW169', region:'Linz'},
|
||
{name:'Libelle Graz', code:'LIB-G', org:'Flugpolizei BMI', orgClass:'police', lat:47.0094, lon:15.4400, active:false, heli:'AW169', region:'Graz'},
|
||
|
||
// Bundesheer
|
||
{name:'Aigen im Ennstal', code:'BH-A', org:'Bundesheer', orgClass:'bh', lat:47.5236, lon:14.1350, active:false, heli:'AB212', region:'Aigen / Ennstal'},
|
||
{name:'Höttinger Au', code:'BH-I', org:'Bundesheer', orgClass:'bh', lat:47.2722, lon:11.3753, active:true, heli:'AB212', region:'Innsbruck'},
|
||
{name:'Langenlebarn', code:'BH-L', org:'Bundesheer', orgClass:'bh', lat:48.3239, lon:15.9836, active:false, heli:'AB212', region:'Langenlebarn / Tullnerfeld'},
|
||
|
||
// Krankenhaus-Landeplätze
|
||
{name:'LKH Innsbruck', code:'KH', org:'Krankenhaus', orgClass:'oeamtc', lat:47.2636, lon:11.3833, active:false},
|
||
{name:'LKH Feldkirch', code:'KH', org:'Krankenhaus', orgClass:'oeamtc', lat:47.2400, lon:9.5956, active:false},
|
||
{name:'AKH Wien', code:'KH', org:'Krankenhaus', orgClass:'oeamtc', lat:48.2206, lon:16.3453, active:false},
|
||
{name:'LKH Salzburg', code:'KH', org:'Krankenhaus', orgClass:'oeamtc', lat:47.7989, lon:13.0333, active:false},
|
||
{name:'LKH Graz', code:'KH', org:'Krankenhaus', orgClass:'oeamtc', lat:47.0808, lon:15.4678, active:false},
|
||
{name:'KH Kufstein', code:'KH', org:'Krankenhaus', orgClass:'oeamtc', lat:47.5836, lon:12.1647, active:false},
|
||
{name:'KH St. Johann', code:'KH', org:'Krankenhaus', orgClass:'oeamtc', lat:47.3528, lon:12.4247, active:false},
|
||
{name:'KH Zams', code:'KH', org:'Krankenhaus', orgClass:'oeamtc', lat:47.1575, lon:10.5917, active:false},
|
||
{name:'BKH Lienz', code:'KH', org:'Krankenhaus', orgClass:'oeamtc', lat:46.8300, lon:12.7700, active:false},
|
||
{name:'LKH Bregenz', code:'KH', org:'Krankenhaus', orgClass:'oeamtc', lat:47.4997, lon:9.7431, active:false},
|
||
];
|
||
|
||
function makeHeliPopup(b) {
|
||
var t = HELI_TYPES[b.heli];
|
||
if (!t) return '<strong>' + b.name + '</strong><br>' + b.org;
|
||
var bp = location.pathname.substring(0, location.pathname.lastIndexOf('/') + 1);
|
||
return '<div class="quartett">'
|
||
+ '<div class="quartett-img"><img src="' + bp + t.img + '" alt="' + t.name + '"></div>'
|
||
+ '<div class="quartett-body">'
|
||
+ '<div class="quartett-name">' + b.code + ' — ' + b.name + '</div>'
|
||
+ '<div class="quartett-org">' + b.org + ' · ' + (b.region||'') + '</div>'
|
||
+ '<div class="quartett-stats">'
|
||
+ '<div class="quartett-stat"><div class="qs-label">Typ</div><div class="qs-val">' + t.name + '</div></div>'
|
||
+ '<div class="quartett-stat"><div class="qs-label">Max. Speed</div><div class="qs-val">' + t.maxSpeed + ' km/h</div></div>'
|
||
+ '<div class="quartett-stat"><div class="qs-label">Reichweite</div><div class="qs-val">' + t.range + ' km</div></div>'
|
||
+ '<div class="quartett-stat"><div class="qs-label">Gipfelhöhe</div><div class="qs-val">' + t.ceiling + ' m</div></div>'
|
||
+ '<div class="quartett-stat"><div class="qs-label">Triebwerke</div><div class="qs-val">' + t.engines + '</div></div>'
|
||
+ '<div class="quartett-stat"><div class="qs-label">Besatzung</div><div class="qs-val">' + t.crew + '</div></div>'
|
||
+ '</div>'
|
||
+ '<div class="quartett-fact">💡 ' + t.fact + '</div>'
|
||
+ (t.wiki ? '<div style="text-align:center;margin-top:6px"><a href="'+t.wiki+'" target="_blank" style="font-size:10px;color:#4a7c8a;text-decoration:none;font-weight:600">📖 Wikipedia →</a></div>' : '')
|
||
+ '</div></div>';
|
||
}
|
||
|
||
bases.forEach(function(b) {
|
||
var size = b.active ? 30 : 22;
|
||
var circleClass = b.active ? 'marker-circle heli-m' : 'marker-circle heli-m inactive';
|
||
|
||
L.marker([b.lat, b.lon], {
|
||
icon: L.divIcon({
|
||
className: '',
|
||
html: '<div class="' + circleClass + '">🚁</div>',
|
||
iconSize: [size, size],
|
||
iconAnchor: [size/2, size/2]
|
||
}),
|
||
title: b.name + ' (' + b.org + ')',
|
||
zIndexOffset: b.active ? 1000 : 0
|
||
}).addTo(map).bindPopup(makeHeliPopup(b), {maxWidth:280, className:'heli-popup'});
|
||
|
||
// Pill-Label
|
||
if (b.active) {
|
||
L.marker([b.lat, b.lon], {
|
||
icon: L.divIcon({
|
||
className: 'heli-pill ' + b.orgClass,
|
||
html: '<span class="pill-name">' + b.code + ' ' + b.name + '</span><span class="pill-org">' + b.org + '</span>',
|
||
iconSize: [160, 28],
|
||
iconAnchor: [-20, 14]
|
||
}),
|
||
interactive: false,
|
||
zIndexOffset: 900
|
||
}).addTo(map);
|
||
} else {
|
||
L.marker([b.lat, b.lon], {
|
||
icon: L.divIcon({
|
||
className: 'heli-pill-sm',
|
||
html: b.code,
|
||
iconSize: [40, 14],
|
||
iconAnchor: [-10, 7]
|
||
}),
|
||
interactive: false
|
||
}).addTo(map);
|
||
}
|
||
});
|
||
|
||
// === STÄDTE >30.000 EW + Bregenz ===
|
||
var cities = [
|
||
{name:'Wien', lat:48.2082, lon:16.3738, pop:1982097, capital:true, land:'Wien', fun:'Hat mehr Einwohner*innen als alle anderen Landeshauptstädte zusammen!', fact:'Wurde 5× zur lebenswertesten Stadt der Welt gewählt.', wiki:'https://de.wikipedia.org/wiki/Wien', img:'sims/heli/assets/cities/wien.png'},
|
||
{name:'Graz', lat:47.0707, lon:15.4395, pop:295424, capital:true, land:'Steiermark', fun:'Die Altstadt ist UNESCO-Weltkulturerbe.', fact:'Zweitgrößte Stadt Österreichs, Universitätsstadt mit über 60.000 Studierenden.', wiki:'https://de.wikipedia.org/wiki/Graz', img:'sims/heli/assets/cities/graz.png'},
|
||
{name:'Linz', lat:48.3069, lon:14.2858, pop:210118, capital:true, land:'Oberösterreich', fun:'War 2009 Europäische Kulturhauptstadt.', fact:'Wichtigster Industriestandort Österreichs (Stahl, Chemie).', wiki:'https://de.wikipedia.org/wiki/Linz', img:'sims/heli/assets/cities/linz.png'},
|
||
{name:'Salzburg', lat:47.8095, lon:13.0550, pop:157745, capital:true, land:'Salzburg', fun:'Geburtsstadt von Wolfgang Amadeus Mozart (1756).', fact:'Die Altstadt gehört zum UNESCO-Weltkulturerbe.', wiki:'https://de.wikipedia.org/wiki/Salzburg', img:'sims/heli/assets/cities/salzburg.png'},
|
||
{name:'Innsbruck', lat:47.2654, lon:11.3928, pop:130585, capital:true, land:'Tirol', fun:'Hat schon 3× Olympische Winterspiele ausgerichtet!', fact:'Liegt auf 574 m im Inntal, umgeben von Bergen bis 2.300 m.', wiki:'https://de.wikipedia.org/wiki/Innsbruck', img:'sims/heli/assets/cities/innsbruck.png'},
|
||
{name:'Klagenfurt', lat:46.6247, lon:14.3050, pop:103671, capital:true, land:'Kärnten', fun:'Am Wörthersee — einem der wärmsten Badeseen der Alpen.', fact:'Südlichste Landeshauptstadt Österreichs.', wiki:'https://de.wikipedia.org/wiki/Klagenfurt_am_Wörthersee', img:'sims/heli/assets/cities/klagenfurt.png'},
|
||
{name:'Villach', lat:46.6111, lon:13.8558, pop:65127, capital:false, land:'Kärnten', fun:'Hat einen eigenen Fasching — den Villacher Fasching!', fact:'Zweitgrößte Stadt Kärntens, wichtiger Verkehrsknotenpunkt Richtung Italien/Slowenien.', wiki:'https://de.wikipedia.org/wiki/Villach', img:'sims/heli/assets/cities/villach.png'},
|
||
{name:'Wels', lat:48.1575, lon:14.0289, pop:63539, capital:false, land:'Oberösterreich', fun:'Hier findet die größte Agrarmesse Österreichs statt.', fact:'Zweitgrößte Stadt Oberösterreichs.', wiki:'https://de.wikipedia.org/wiki/Wels_(Stadt)', img:'sims/heli/assets/cities/wels.png'},
|
||
{name:'St. Pölten', lat:48.2047, lon:15.6256, pop:57169, capital:true, land:'Niederösterreich', fun:'Jüngste Landeshauptstadt — erst seit 1986!', fact:'Vorher war Wien auch Hauptstadt von Niederösterreich.', wiki:'https://de.wikipedia.org/wiki/St._Pölten', img:'sims/heli/assets/cities/stpoelten.png'},
|
||
{name:'Dornbirn', lat:47.4136, lon:9.7424, pop:51222, capital:false, land:'Vorarlberg', fun:'Größte Stadt Vorarlbergs — nicht Bregenz!', fact:'Wichtiger Messestandort und Wirtschaftszentrum im Rheintal.', wiki:'https://de.wikipedia.org/wiki/Dornbirn', img:'sims/heli/assets/cities/dornbirn.png'},
|
||
{name:'Wiener Neustadt', lat:47.8133, lon:16.2467, pop:48519, capital:false, land:'Niederösterreich', fun:'Hier wurde die erste Militärakademie der Welt gegründet (1752).', fact:'Wichtiger Standort des Bundesheers.', wiki:'https://de.wikipedia.org/wiki/Wiener_Neustadt', img:'sims/heli/assets/cities/wiener_neustadt.png'},
|
||
{name:'Steyr', lat:48.0386, lon:14.4214, pop:38572, capital:false, land:'Oberösterreich', fun:'Die Altstadt sieht aus wie ein Märchen — mit Flüssen und mittelalterlichen Häusern.', fact:'Berühmt für Stahl und Waffen seit dem Mittelalter.', wiki:'https://de.wikipedia.org/wiki/Steyr', img:'sims/heli/assets/cities/steyr.png'},
|
||
{name:'Feldkirch', lat:47.2376, lon:9.5982, pop:35793, capital:false, land:'Vorarlberg', fun:'Hat eine echte mittelalterliche Burg — die Schattenburg.', fact:'Grenzstadt zur Schweiz und Liechtenstein.', wiki:'https://de.wikipedia.org/wiki/Feldkirch', img:'sims/heli/assets/cities/feldkirch.png'},
|
||
{name:'Bregenz', lat:47.5026, lon:9.7473, pop:29620, capital:true, land:'Vorarlberg', fun:'Die Seebühne auf dem Bodensee ist die größte Freilichtbühne der Welt!', fact:'Kleinste Landeshauptstadt Österreichs.', wiki:'https://de.wikipedia.org/wiki/Bregenz', img:'sims/heli/assets/cities/bregenz.png'},
|
||
{name:'Leoben', lat:47.3765, lon:15.0910, pop:32768, capital:false, land:'Steiermark', fun:'Hier wird an der Montanuniversität Bergbau studiert.', fact:'War jahrhundertelang das Zentrum des steirischen Erzbergbaus.', wiki:'https://de.wikipedia.org/wiki/Leoben', img:'sims/heli/assets/cities/leoben.png'},
|
||
{name:'Eisenstadt', lat:47.8456, lon:16.5189, pop:15073, capital:true, land:'Burgenland', fun:'Hier lebte der Komponist Joseph Haydn über 30 Jahre.', fact:'Kleinste Landeshauptstadt nach Einwohnern. Schloss Esterházy ist das Wahrzeichen.', wiki:'https://de.wikipedia.org/wiki/Eisenstadt', img:'sims/heli/assets/cities/eisenstadt.png'},
|
||
];
|
||
|
||
cities.forEach(function(c) {
|
||
var dotSize = c.capital ? 30 : 26;
|
||
var cls = '';
|
||
|
||
// Punkt
|
||
var capitalBadge = c.capital ? '<div style="display:inline-block;background:#e8833a;color:#fff;font-size:9px;font-weight:700;padding:2px 8px;border-radius:99px;margin-bottom:6px">★ Landeshauptstadt' + (c.land ? ' von ' + c.land : '') + '</div>' : (c.land ? '<div style="font-size:10px;color:#8a8a8a;margin-bottom:4px">' + c.land + '</div>' : '');
|
||
L.marker([c.lat, c.lon], {
|
||
icon: L.divIcon({
|
||
className: cls,
|
||
html: '<div class="marker-circle' + (c.capital ? ' capital' : '') + '">🏙️</div>',
|
||
iconSize: [dotSize, dotSize],
|
||
iconAnchor: [dotSize/2, dotSize/2]
|
||
}),
|
||
zIndexOffset: c.capital ? 500 : 100
|
||
}).addTo(map).bindPopup(
|
||
'<div style="font-family:Inter,sans-serif;width:260px;padding:0">'
|
||
+ (c.img ? '<div style="height:130px;overflow:hidden;border-radius:8px 8px 0 0;margin:-12px -12px 8px"><img src="'+c.img+'" style="width:100%;height:100%;object-fit:cover"></div>' : '')
|
||
+ '<div style="text-align:center">'
|
||
+ capitalBadge
|
||
+ '<div style="font-size:16px;font-weight:900;color:#1f4e5a;margin-bottom:2px">' + c.name + '</div>'
|
||
+ '<div style="font-size:22px;font-weight:900;color:#4a7c8a">' + c.pop.toLocaleString() + '</div>'
|
||
+ '<div style="font-size:9px;color:#8a8a8a;margin-bottom:8px">Einwohner*innen</div>'
|
||
+ '</div>'
|
||
+ (c.fun ? '<div style="font-size:11px;color:#2d5434;background:rgba(90,138,94,.08);border-radius:6px;padding:6px 8px;margin-bottom:6px">🎯 ' + c.fun + '</div>' : '')
|
||
+ (c.fact ? '<div style="font-size:11px;color:#4a4a4a;line-height:1.4;margin-bottom:6px">📚 ' + c.fact + '</div>' : '')
|
||
+ (c.wiki ? '<div style="text-align:center"><a href="'+c.wiki+'" target="_blank" style="font-size:10px;color:#4a7c8a;text-decoration:none;font-weight:600">📖 Wikipedia →</a></div>' : '')
|
||
+ '</div>'
|
||
, {maxWidth:260});
|
||
|
||
// Pill
|
||
var popStr = c.pop >= 1000000 ? (c.pop/1000000).toFixed(1) + ' Mio' : Math.round(c.pop/1000) + 'k';
|
||
var star = c.capital ? ' ★' : '';
|
||
L.marker([c.lat, c.lon], {
|
||
icon: L.divIcon({
|
||
className: 'city-pill' + (c.capital ? ' capital' : ''),
|
||
html: c.name + star + '<span class="city-pop">' + popStr + '</span>',
|
||
iconSize: [140, 22],
|
||
iconAnchor: [-16, 11]
|
||
}),
|
||
interactive: false,
|
||
zIndexOffset: c.capital ? 400 : 50
|
||
}).addTo(map);
|
||
});
|
||
|
||
// === BERGGIPFEL ===
|
||
var peaks = [
|
||
{name:'Großglockner', lat:47.0742, lon:12.6947, ele:3798, fact:'Höchster Berg Österreichs! Die berühmte Großglockner Hochalpenstraße führt bis auf 2.504 m.', wiki:'https://de.wikipedia.org/wiki/Großglockner'},
|
||
{name:'Wildspitze', lat:46.8853, lon:10.8672, ele:3768, fact:'Höchster Berg Tirols und zweithöchster Österreichs. Hat den größten Gletscher der Ostalpen.', wiki:'https://de.wikipedia.org/wiki/Wildspitze'},
|
||
{name:'Weißkugel', lat:46.8439, lon:10.7833, ele:3738, fact:'Dritthöchster Berg Österreichs, direkt an der Grenze zu Italien.', wiki:'https://de.wikipedia.org/wiki/Weißkugel'},
|
||
{name:'Großvenediger', lat:47.1092, lon:12.3464, ele:3657, fact:'Wird „Weltalte Majestät" genannt. Einer der schönsten Gletscherberge der Alpen.', wiki:'https://de.wikipedia.org/wiki/Großvenediger'},
|
||
{name:'Similaun', lat:46.7578, lon:10.8853, ele:3599, fact:'Hier wurde 1991 die Gletschermumie „Ötzi" gefunden — 5.300 Jahre alt!', wiki:'https://de.wikipedia.org/wiki/Similaun'},
|
||
{name:'Zuckerhütl', lat:46.9833, lon:11.1167, ele:3505, fact:'Höchster Berg der Stubaier Alpen. Beliebtes Ziel für Skitouren.', wiki:'https://de.wikipedia.org/wiki/Zuckerhütl'},
|
||
{name:'Piz Buin', lat:46.8444, lon:10.1186, ele:3312, fact:'Höchster Berg Vorarlbergs. Die Sonnencreme-Marke wurde nach ihm benannt!', wiki:'https://de.wikipedia.org/wiki/Piz_Buin'},
|
||
{name:'Zugspitze', lat:47.4211, lon:10.9856, ele:2962, fact:'Höchster Berg Deutschlands, direkt an der Grenze zu Österreich. Seilbahn fährt bis zum Gipfel.', wiki:'https://de.wikipedia.org/wiki/Zugspitze'},
|
||
{name:'Dachstein', lat:47.4753, lon:13.6061, ele:2995, fact:'Höchster Berg der Steiermark. Hat eine Eishöhle und den berühmten Skywalk.', wiki:'https://de.wikipedia.org/wiki/Hoher_Dachstein'},
|
||
{name:'Valluga', lat:47.1567, lon:10.2117, ele:2809, fact:'Hausberg von St. Anton am Arlberg, eines der berühmtesten Skigebiete der Welt.', wiki:'https://de.wikipedia.org/wiki/Valluga'},
|
||
{name:'Parseierspitze', lat:47.0825, lon:10.5086, ele:3036, fact:'Höchster Berg der Lechtaler Alpen. Nur für erfahrene Bergsteiger*innen!', wiki:'https://de.wikipedia.org/wiki/Parseierspitze'},
|
||
{name:'Brenner', lat:47.0422, lon:11.5067, ele:1370, fact:'Der wichtigste Alpenpass zwischen Österreich und Italien. Schon die Römer nutzten ihn.', wiki:'https://de.wikipedia.org/wiki/Brennerpass'},
|
||
{name:'Patscherkofel', lat:47.2083, lon:11.4611, ele:2246, fact:'Hausberg von Innsbruck. Hier fanden Olympische Skirennen statt.', wiki:'https://de.wikipedia.org/wiki/Patscherkofel'},
|
||
{name:'Arlberg', lat:47.1297, lon:10.2119, ele:1793, fact:'Grenze zwischen Tirol und Vorarlberg. Wiege des alpinen Skilaufs.', wiki:'https://de.wikipedia.org/wiki/Arlberg'},
|
||
];
|
||
|
||
peaks.forEach(function(p) {
|
||
L.marker([p.lat, p.lon], {
|
||
icon: L.divIcon({
|
||
className: '',
|
||
html: '<div class="marker-circle peak">🏔️</div>',
|
||
iconSize: [24, 24],
|
||
iconAnchor: [12, 12]
|
||
})
|
||
}).addTo(map).bindPopup(
|
||
'<div style="font-family:Inter,sans-serif;width:220px;padding:2px">'
|
||
+ '<div style="text-align:center">'
|
||
+ '<div style="font-size:2.5rem">🏔️</div>'
|
||
+ '<div style="font-size:15px;font-weight:900;color:#3a6b3e">' + p.name + '</div>'
|
||
+ '<div style="font-size:24px;font-weight:900;color:#1f4e5a">' + p.ele.toLocaleString() + ' m</div>'
|
||
+ '</div>'
|
||
+ (p.fact ? '<div style="font-size:11px;color:#4a4a4a;line-height:1.4;margin:8px 0 4px;background:rgba(90,138,94,.06);border-radius:6px;padding:6px 8px">🌿 ' + p.fact + '</div>' : '')
|
||
+ (p.wiki ? '<div style="text-align:center"><a href="'+p.wiki+'" target="_blank" style="font-size:10px;color:#4a7c8a;text-decoration:none;font-weight:600">📖 Wikipedia →</a></div>' : '')
|
||
+ '</div>'
|
||
, {maxWidth:240});
|
||
|
||
L.marker([p.lat, p.lon], {
|
||
icon: L.divIcon({
|
||
className: 'peak-pill',
|
||
html: p.name + '<span class="peak-ele">' + p.ele.toLocaleString() + ' m</span>',
|
||
iconSize: [140, 20],
|
||
iconAnchor: [-14, 10]
|
||
}),
|
||
interactive: false
|
||
}).addTo(map);
|
||
});
|
||
|
||
// === DB-SYNC: Koordinaten aus der Datenbank laden und Marker aktualisieren ===
|
||
(async function() {
|
||
try {
|
||
var bp = location.pathname.replace(/\/[^\/]*$/, '');
|
||
var resp = await fetch(bp + '/api/waypoints');
|
||
var dbWaypoints = await resp.json();
|
||
if (!Array.isArray(dbWaypoints)) return;
|
||
|
||
// Index nach wp_key
|
||
var dbMap = {};
|
||
dbWaypoints.forEach(function(w) { dbMap[w.wp_key] = {lat: parseFloat(w.lat), lon: parseFloat(w.lon), name: w.name}; });
|
||
|
||
// Alle Leaflet-Marker durchgehen und Positionen updaten
|
||
map.eachLayer(function(layer) {
|
||
if (layer.getLatLng && layer.options && layer.options.title) {
|
||
// Versuche den Marker anhand des Titels zu identifizieren
|
||
// (Heli-Basen haben title gesetzt)
|
||
}
|
||
});
|
||
|
||
// Einfacher: Seite neu rendern wenn DB-Daten signifikant abweichen
|
||
// Für jetzt: Konsolenwarnung bei Abweichungen
|
||
var fixes = 0;
|
||
bases.forEach(function(b) {
|
||
var key = b.name.toLowerCase().replace(/[^a-z0-9]/g,'');
|
||
// Versuche verschiedene Key-Matches
|
||
for (var dbKey in dbMap) {
|
||
if (dbMap[dbKey].name === b.name || dbMap[dbKey].name.indexOf(b.code) !== -1) {
|
||
var dLat = Math.abs(b.lat - dbMap[dbKey].lat);
|
||
var dLon = Math.abs(b.lon - dbMap[dbKey].lon);
|
||
if (dLat > 0.001 || dLon > 0.001) {
|
||
console.warn('DB-Korrektur: ' + b.name + ' (' + b.lat + ',' + b.lon + ') → (' + dbMap[dbKey].lat + ',' + dbMap[dbKey].lon + ')');
|
||
fixes++;
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
});
|
||
// Marker-Positionen korrigieren: alle Layer entfernen und neu setzen
|
||
if (Object.keys(dbMap).length > 0) {
|
||
// Alle bestehenden Marker entfernen
|
||
var layersToRemove = [];
|
||
map.eachLayer(function(layer) {
|
||
if (layer.getLatLng && !(layer instanceof L.TileLayer)) {
|
||
layersToRemove.push(layer);
|
||
}
|
||
});
|
||
layersToRemove.forEach(function(l) { map.removeLayer(l); });
|
||
|
||
// Basen neu setzen mit DB-Koordinaten
|
||
bases.forEach(function(b) {
|
||
// Finde passenden DB-Eintrag
|
||
for (var dbKey in dbMap) {
|
||
if (dbMap[dbKey].name.indexOf(b.code) !== -1 || dbMap[dbKey].name === b.name) {
|
||
b.lat = dbMap[dbKey].lat;
|
||
b.lon = dbMap[dbKey].lon;
|
||
break;
|
||
}
|
||
}
|
||
var size = b.active ? 30 : 22;
|
||
var circleClass = b.active ? 'marker-circle heli-m' : 'marker-circle heli-m inactive';
|
||
L.marker([b.lat, b.lon], {
|
||
icon: L.divIcon({className:'', html:'<div class="'+circleClass+'">🚁</div>', iconSize:[size,size], iconAnchor:[size/2,size/2]}),
|
||
title: b.name+' ('+b.org+')', zIndexOffset: b.active ? 1000 : 0
|
||
}).addTo(map).bindPopup(makeHeliPopup(b), {maxWidth:280, className:'heli-popup'});
|
||
if (b.active) {
|
||
L.marker([b.lat, b.lon], {
|
||
icon: L.divIcon({className:'heli-pill '+b.orgClass, html:'<span class="pill-name">'+b.code+' '+b.name+'</span><span class="pill-org">'+b.org+'</span>', iconSize:[160,28], iconAnchor:[-20,14]}),
|
||
interactive:false, zIndexOffset:900
|
||
}).addTo(map);
|
||
}
|
||
});
|
||
|
||
// Städte neu setzen
|
||
cities.forEach(function(c) {
|
||
var dbKey = c.name.toLowerCase().replace(/[^a-zäöüß]/g,'');
|
||
for (var k in dbMap) {
|
||
if (dbMap[k].name.toLowerCase().replace(/[^a-zäöüß]/g,'') === dbKey) {
|
||
c.lat = dbMap[k].lat; c.lon = dbMap[k].lon; break;
|
||
}
|
||
}
|
||
var dotSize = c.capital ? 30 : 26;
|
||
var capitalBadge = c.capital ? '<div style="display:inline-block;background:#e8833a;color:#fff;font-size:9px;font-weight:700;padding:2px 8px;border-radius:99px;margin-bottom:6px">★ Landeshauptstadt'+(c.land?' von '+c.land:'')+'</div>' : (c.land?'<div style="font-size:10px;color:#8a8a8a;margin-bottom:4px">'+c.land+'</div>':'');
|
||
L.marker([c.lat, c.lon], {
|
||
icon: L.divIcon({className:'', html:'<div class="marker-circle'+(c.capital?' capital':'')+'">🏙️</div>', iconSize:[dotSize,dotSize], iconAnchor:[dotSize/2,dotSize/2]}),
|
||
zIndexOffset: c.capital ? 500 : 100
|
||
}).addTo(map).bindPopup(
|
||
'<div style="font-family:Inter,sans-serif;width:260px;padding:0">'
|
||
+(c.img?'<div style="height:130px;overflow:hidden;border-radius:8px 8px 0 0;margin:-12px -12px 8px"><img src="'+c.img+'" style="width:100%;height:100%;object-fit:cover"></div>':'')
|
||
+'<div style="text-align:center">'+capitalBadge
|
||
+'<div style="font-size:16px;font-weight:900;color:#1f4e5a;margin-bottom:2px">'+c.name+'</div>'
|
||
+'<div style="font-size:22px;font-weight:900;color:#4a7c8a">'+c.pop.toLocaleString()+'</div>'
|
||
+'<div style="font-size:9px;color:#8a8a8a;margin-bottom:8px">Einwohner*innen</div></div>'
|
||
+(c.fun?'<div style="font-size:11px;color:#2d5434;background:rgba(90,138,94,.08);border-radius:6px;padding:6px 8px;margin-bottom:6px">🎯 '+c.fun+'</div>':'')
|
||
+(c.fact?'<div style="font-size:11px;color:#4a4a4a;line-height:1.4;margin-bottom:6px">📚 '+c.fact+'</div>':'')
|
||
+(c.wiki?'<div style="text-align:center"><a href="'+c.wiki+'" target="_blank" style="font-size:10px;color:#4a7c8a;text-decoration:none;font-weight:600">📖 Wikipedia →</a></div>':'')
|
||
+'</div>', {maxWidth:260});
|
||
var star = c.capital ? ' ★' : '';
|
||
var popStr = c.pop >= 1000000 ? (c.pop/1000000).toFixed(1)+' Mio' : Math.round(c.pop/1000)+'k';
|
||
L.marker([c.lat, c.lon], {
|
||
icon: L.divIcon({className:'city-pill'+(c.capital?' capital':''), html:c.name+star+'<span class="city-pop">'+popStr+'</span>', iconSize:[140,22], iconAnchor:[-16,11]}),
|
||
interactive:false, zIndexOffset: c.capital ? 400 : 50
|
||
}).addTo(map);
|
||
});
|
||
|
||
// Berge neu setzen
|
||
peaks.forEach(function(p) {
|
||
L.marker([p.lat, p.lon], {
|
||
icon: L.divIcon({className:'', html:'<div class="marker-circle peak">🏔️</div>', iconSize:[24,24], iconAnchor:[12,12]})
|
||
}).addTo(map).bindPopup(
|
||
'<div style="font-family:Inter,sans-serif;width:220px;padding:2px"><div style="text-align:center">'
|
||
+'<div style="font-size:2.5rem">🏔️</div><div style="font-size:15px;font-weight:900;color:#3a6b3e">'+p.name+'</div>'
|
||
+'<div style="font-size:24px;font-weight:900;color:#1f4e5a">'+p.ele.toLocaleString()+' m</div></div>'
|
||
+(p.fact?'<div style="font-size:11px;color:#4a4a4a;line-height:1.4;margin:8px 0 4px;background:rgba(90,138,94,.06);border-radius:6px;padding:6px 8px">🌿 '+p.fact+'</div>':'')
|
||
+(p.wiki?'<div style="text-align:center"><a href="'+p.wiki+'" target="_blank" style="font-size:10px;color:#4a7c8a;text-decoration:none;font-weight:600">📖 Wikipedia →</a></div>':'')
|
||
+'</div>', {maxWidth:240});
|
||
L.marker([p.lat, p.lon], {
|
||
icon: L.divIcon({className:'peak-pill', html:p.name+'<span class="peak-ele">'+p.ele.toLocaleString()+' m</span>', iconSize:[140,20], iconAnchor:[-14,10]}),
|
||
interactive:false
|
||
}).addTo(map);
|
||
});
|
||
|
||
console.log('Karte mit DB-Koordinaten aktualisiert (' + Object.keys(dbMap).length + ' Waypoints)');
|
||
}
|
||
} catch(e) { console.warn('DB-Sync fehlgeschlagen:', e); }
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|