Phase 0+1: Scaffold, Datenmodell, Karte und Liste

Reines HTML5/CSS3/JavaScript ohne Build-Tooling, lauffaehig direkt per
Doppelklick auf index.html (file://, kein Server noetig):

- Datenmodell fuer Ausflugsziele mit allen Feldern aus der Anforderung,
  15 feste Kategorien inkl. Farbe/Icon
- Zentraler Pub/Sub-Store mit LocalStorage-Persistenz und
  Migrations-Grundgeruest
- Interaktive Karte (Leaflet + Leaflet.markercluster, lokal vendored)
  mit kategoriefarbenen Markern, Clustering, Popups, Geolocation-Button
- Kartengrundlage ueber CARTO-Basemaps statt tile.openstreetmap.org, da
  dessen Referer-Pflicht file://-Aufrufe blockiert
- Seed-Daten als eingebettetes Script (data/seed.js) statt JSON-Datei,
  da fetch() auf lokale Dateien unter file:// nicht zuverlaessig
  funktioniert
- Ergebnisliste mit 10 Beispiel-Ausflugszielen, sicher gerendert ueber
  textContent/DOM-APIs statt innerHTML (Vorbereitung fuer spaeteren
  KI-JSON-Import mit Fremddaten)
- Responsives Layout (Desktop nebeneinander, Mobile gestapelt)
This commit is contained in:
2026-07-24 11:24:47 +02:00
parent 6b0d10c4fb
commit ee55fb1cf9
32 changed files with 2557 additions and 0 deletions
+47
View File
@@ -0,0 +1,47 @@
// Feste Liste der Ausflugsziel-Kategorien (siehe anforderung.md).
// Jede Kategorie hat eine stabile id (slug), ein Label, eine Farbe (Hex)
// für Marker/Labels und ein Emoji-Icon als Glyph.
(function () {
const CATEGORIES = [
{ id: 'naturpark', label: 'Naturpark', color: '#2e7d32', icon: '🌳' },
{ id: 'wald-wanderweg', label: 'Wald/Wanderweg', color: '#1b5e20', icon: '🌲' },
{ id: 'see-badestelle', label: 'See/Badestelle', color: '#0288d1', icon: '🏖️' },
{ id: 'erlebnispark', label: 'Erlebnispark', color: '#f57c00', icon: '🎢' },
{ id: 'freizeitpark', label: 'Freizeitpark', color: '#e64a19', icon: '🎡' },
{ id: 'tierpark-zoo', label: 'Tierpark/Zoo', color: '#ff8f00', icon: '🦁' },
{ id: 'spielplatz', label: 'Spielplatz', color: '#d81b60', icon: '🛝' },
{ id: 'museum-kinder', label: 'Museum für Kinder', color: '#7b1fa2', icon: '🏛️' },
{ id: 'indoor-spielplatz', label: 'Indoor-Spielplatz', color: '#5e35b1', icon: '🧸' },
{ id: 'bauernhof', label: 'Bauernhof', color: '#6d4c41', icon: '🐄' },
{ id: 'kletterpark', label: 'Kletterpark', color: '#00897b', icon: '🧗' },
{ id: 'aussichtspunkt', label: 'Aussichtspunkt', color: '#455a64', icon: '🔭' },
{ id: 'cafe-restaurant', label: 'Familienfreundliches Café/Restaurant', color: '#c62828', icon: '☕' },
{ id: 'sonstige-aktivitaet', label: 'Sonstige kinderfreundliche Aktivität', color: '#00acc1', icon: '🎈' },
{ id: 'sonstige', label: 'Sonstige', color: '#616161', icon: '📍' },
];
const CATEGORY_MAP = new Map(CATEGORIES.map((c) => [c.id, c]));
const CATEGORY_LABEL_MAP = new Map(
CATEGORIES.map((c) => [c.label.toLowerCase(), c])
);
function getCategoryById(id) {
return CATEGORY_MAP.get(id) ?? CATEGORY_MAP.get('sonstige');
}
/**
* Findet eine Kategorie anhand ihres Labels (z.B. aus KI-Import-JSON).
* Fällt auf "Sonstige" zurück, falls kein Treffer gefunden wird.
*/
function getCategoryByLabel(label) {
if (typeof label !== 'string') {
return CATEGORY_MAP.get('sonstige');
}
return CATEGORY_LABEL_MAP.get(label.toLowerCase()) ?? CATEGORY_MAP.get('sonstige');
}
EventMap.config.CATEGORIES = CATEGORIES;
EventMap.config.getCategoryById = getCategoryById;
EventMap.config.getCategoryByLabel = getCategoryByLabel;
})();