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)
8.3 KiB
EventMap – Gesamtplan
Dieses Dokument fasst den vom architect-Agenten erarbeiteten Plan zusammen und wird phasenweise fortgeschrieben. Aktuell umgesetzt: Phase 0 (Scaffold) und Phase 1 (Datenmodell + Karte + Liste, read-only mit Beispieldaten).
Tech-Stack
- Vanilla HTML5/CSS3/JavaScript, klassische Scripts (kein
type="module"); Module-Ersatz über einen globalen Namespacewindow.EventMap+ IIFE-Kapselung pro Datei, siehedocs/adr/0001-vanilla-esm.md. Grund: die App wird per Doppelklick direkt ausfile://geöffnet, native ES-Module würden dort per CORS-Policy blockiert. - Kein Build-Tool, kein Bundler, kein Framework
- Karte: Leaflet 1.9.4 + Leaflet.markercluster 1.5.3, lokal vendored unter
vendor/ - Kartengrundlage: OpenStreetMap-Daten, ausgeliefert über CARTO-Basemap-Tiles
(
basemaps.cartocdn.com, Stil "light_all") statt des offiziellentile.openstreetmap.org. Grund: dessen Tile Usage Policy verlangt seit 2024 einen gültigen HTTP-Referer, denfile://-Seiten (Doppelklick-Start) grundsätzlich nicht mitsenden können ("Access blocked: Referer is required"). CARTO liefert dieselben OSM-Daten kartographisch auf, ohne diese Einschränkung; Attribution zeigt weiterhin OpenStreetMap + CARTO. - Datenhaltung: LocalStorage (Offline-first), vorbereitet für spätere Migration auf Backend/API
Ordnerstruktur
index.html
css/ reset, variables (Design-Tokens), layout, components, responsive
js/config/ constants.js, categories.js
js/store/ store.js (Pub/Sub-State), storage.js (LocalStorage-IO), migrations.js
js/models/ place.js (Normalisierung), schema.js (Feldstruktur/Defaults)
js/map/ mapController.js, markers.js, geolocation.js
js/ui/ list.js (Ergebnisliste; weitere UI-Module folgen in späteren Phasen)
js/utils/ dom.js, geo.js, id.js
data/seed.js Beispieldaten (10 Ausflugsziele, wird nur beim allerersten Start geladen)
vendor/ lokal vendorte Leaflet-Bibliotheken (JS/CSS/Bildassets)
docs/ dieser Plan, ADRs
Datenmodell (Ausflugsziel / Place)
Das interne Place-Objekt orientiert sich eng am verbindlichen KI-Import-JSON-Format
aus anforderung.md (siehe dortiger Abschnitt "Vorgabe für das KI-JSON"):
- Basisfelder:
name,category,shortDescription,description,latitude,longitude,address,city,region ageRecommendation: { min, max }cost: { type: 'free'|'paid'|'mixed'|'unknown', description }openingHours,durationMinutesfacilities: { strollerAccessible, wheelchairAccessible, toilets, parking, restaurant }environment: 'indoor'|'outdoor'|'mixed'website,image,rating,tags[],source,sourceVerified- Intern generiert:
id(UUID),createdAt,updatedAt(ISO-Strings)
js/models/place.js normalisiert rohe Objekte (aus data/seed.js oder späteren
Importen) zu diesem vollständigen internen Format; die Kategorie wird dabei
immer auf eine gültige Kategorie-id (Slug, siehe js/config/categories.js)
aufgelöst – entweder direkt (falls schon ein Slug vorliegt) oder über das
Label (z.B. "Spielplatz" → spielplatz), wie es die KI liefern würde.
Kategorien
14 Kategorien aus der Anforderung + "Sonstige" als generischer Fallback
(insgesamt 15), jeweils mit id (Slug), label, color (Hex) und icon
(Emoji-Glyph) – siehe js/config/categories.js.
Store-Architektur
js/store/store.js ist ein zentraler Pub/Sub-Store:
state = { places, filters: null, userLocation: null, selectedPlaceId: null }
subscribe(fn)registriert Listener,setState(patch)merged den State und benachrichtigt alle Subscriber.initStore()lädt beim Start persistierte Daten aus LocalStorage (js/store/storage.js). Ist noch nichts gespeichert, wirddata/seed.jsgeladen, normalisiert und einmalig persistiert.- Einstellungen (
lastFilters,lastMapView,userConsentGeo,defaultCenter) werden getrennt übergetSettings()/updateSettings()verwaltet und im Keyeventmap.settings.v1persistiert.
LocalStorage-Schema
eventmap.data.v1→{ schemaVersion: 1, places: [...], updatedAt }eventmap.settings.v1→{ lastFilters, lastMapView, userConsentGeo, defaultCenter }
js/store/migrations.js stellt eine schlanke, sequenziell anwendbare
Migrations-Infrastruktur bereit (migrate(data)). Aktuell existiert nur
Schema-Version 1, das Migrationsarray ist entsprechend leer.
defaultCenter (frei wählbares Fallback-Kartenzentrum) ist als Datenfeld
vorbereitet, aber in Phase 1 noch nicht über die UI einstellbar. Solange kein
defaultCenter gesetzt ist, verwendet die Karte den hartcodierten Default aus
js/config/constants.js (geografische Mitte Deutschlands, lat 51.1657,
lng 10.4515, zoom 6).
Karte
js/map/mapController.js: initialisiert Leaflet-Karte + OSM-Tile-Layer (inkl. korrekter Attribution), verwaltet einL.markerClusterGroup, reagiert persubscribe()auf Änderungen anstate.places(Marker neu rendern) undstate.selectedPlaceId(Karte zentrieren + Popup öffnen).fitToPlaces()passt die Ansicht auf alle vorhandenen Places an, mit Fallback auf das Default-Center falls keine Places mit gültigen Koordinaten vorhanden sind.js/map/markers.js: erzeugt je Kategorie einL.divIcon(Farbe + Emoji, kein PNG-Sprite nötig) und den Popup-Inhalt (Name, Kategorie, Kurzbeschreibung) als DOM-Node (nicht als HTML-String, siehe Sicherheitshinweis unten).js/map/geolocation.js: fragt den Standort nur auf explizite Nutzeraktion (Button "Meinen Standort verwenden") ab, setzt bei ErfolguserLocationim Store und zentriert die Karte; bei Ablehnung/Fehler bleibt das Default-Center erhalten und ein Hinweis wird angezeigt.
Ergebnisliste
js/ui/list.js rendert state.places als Cards (Name, Kategorie mit
Farbe/Icon, Kurzbeschreibung). Ein Klick auf eine Card setzt
selectedPlaceId im Store; mapController.js reagiert darauf und
zentriert die Karte auf den zugehörigen Marker. Filterung (Phase 4) ist
hier noch nicht implementiert – aktuell werden immer alle Places gezeigt.
Sicherheitshinweis: Da künftig Fremddaten per KI-Import in die App
gelangen, wird an keiner Stelle innerHTML mit Place-Daten befüllt.
js/utils/dom.js stellt dafür el() (DOM-Erzeugung über textContent/
Properties) sowie escapeHtml() als zusätzliches Sicherheitsnetz bereit.
Layout
- Header mit Titel "EventMap".
- Mobile/Tablet (< 1024px): Karte oben (fixe Höhe ca. 50vh), Liste darunter, normal scrollbar.
- Desktop (≥ 1024px): Grid mit Karte (~65%) und Liste-Sidebar (~35%)
nebeneinander, siehe
css/responsive.css.
Kein Dev-Server nötig
Die App verwendet klassische <script>-Tags statt ES-Module (siehe
docs/adr/0001-vanilla-esm.md) und lässt sich daher per Doppelklick auf
index.html direkt aus dem lokal synchronisierten Ordner öffnen
(file://...) – ein lokaler HTTP-Server ist dafür nicht mehr nötig.
Die Beispieldaten liegen in data/seed.js als eingebettetes
<script>-Objekt (EventMap.data.SEED_PLACES) statt als .json-Datei,
da fetch() auf lokale Dateien über file:// in manchen Browsern
(insbesondere Chrome/Chromium) blockiert wird. initStore() liest die
Seed-Daten dadurch synchron direkt aus dem globalen Namespace, ganz ohne
fetch().
Phasenübersicht
| Phase | Inhalt | Status |
|---|---|---|
| 0 | Scaffold (Ordnerstruktur, vendored Leaflet, Grundgerüst) | ✅ umgesetzt |
| 1 | Datenmodell, Karte, Liste (read-only, Seed-Daten) | ✅ umgesetzt |
| 2 | Detailansicht (Titelbild, vollständige Beschreibung, Mini-Karte, Navigation-Button, Bearbeiten/Löschen) | offen |
| 3 | Formular zum manuellen Anlegen/Bearbeiten (Validierung, Koordinatenprüfung) | offen |
| 4 | Filter- und Suchfunktion (Kategorie, Entfernung, Alter, Kosten, Indoor/Outdoor, Ausstattung, Bewertung, Region) inkl. Anwendung auf Karte + Liste | offen |
| 5 | Route/Navigation-Integration (z.B. Link zu externem Kartendienst) | offen |
| 6 | KI-Prompt-Generator (Eingabeformular, Promptgenerierung, Copy/Download) | offen |
| 7 | KI-JSON-Import (Validierung, Vorschau, Übernahme ins LocalStorage) | offen |
Nicht Teil von Phase 0/1 und daher bewusst noch nicht angelegt: Filter-Modul, Detail-Modul, Formular-Modul, Prompt-Generator-Modul, Import-Modul.