Live MCP for Immobilien Eichmann listings (Konstanz). Tools: search_listings, get_listing.
One address for any MCP client. No sign-in needed on this server.
4 read1 act
Grouped by tool name. At most: only reads or looks up information.
Odel charges nothing per call. The publisher may charge for its own service.
/var/lib/eichmann/listings.db (not in git, not in the web root).immowelt (inbound sync only) and eigen (own listings via unified /admin/#eigen).origin=immowelt and never deletes origin=eigen.data/listings.json is an export/backup with "sot": "sqlite", not the SoT.node scripts/publish-from-db.mjs writes JSON export + HTML from the DB./admin/api/ (localhost Node). No GitHub PAT.better-sqlite3. App tooling: /var/lib/eichmann/app. Admin API unit: eichmann-admin-api.service.docs/STABSCHEF-admin-unified-2026-09-25.mdStatische Website für Immobilien Eichmann / Helmut Eichmann in Konstanz.
ChristianHohlfeld/eichmannimmobilienmaindp-forms.service auf forms.digitalisierungsplanung.deStand dieser Dokumentation: 20.09.2026.
Die öffentliche Website selbst besteht aus statischen HTML-, CSS-, JavaScript-, JSON- und Bilddateien auf GitHub Pages. Kontakt- und Exposé-Anfragen gehen an einen eigenen, technisch getrennten Formular-Gateway-Dienst unter forms.digitalisierungsplanung.de; weitere dynamische Abläufe laufen im Browser, über GitHub API / GitHub Actions oder über klar benannte Dienste wie Immowelt, Amazon SES und – nach Consent – Google Analytics.
Besucher
|
v
GitHub Pages / immobilieneichmann.de
|
+--> HTML + CSS + Vanilla JavaScript
|
+--> Kontakt-/Exposé-Anfrage
| Browser -> forms.digitalisierungsplanung.de -> Amazon SES -> info@immobilien-eichmann.com
|
+--> Analytics nur nach Consent
| Browser -> Google Analytics 4
|
+--> Externe Links
-> Immowelt / Google Maps
Admin-Browser
|
+--> GitHub REST API mit Sitzungs-PAT
| -> data/listings.json
| -> assets/listings/*
|
+--> repository_dispatch / workflow_dispatch
-> GitHub Actions
-> Renderer
-> generierte HTML-Seiten / Sitemap
-> Commit auf main
-> GitHub Pages Deployment
GitHub Actions Cron
|
+--> Playwright -> öffentliches Immowelt-Profil
-> Merge in data/listings.json
-> Bilder lokal spiegeln
-> HTML / Sitemap neu rendern
Die Website-Dateien selbst laufen weiterhin ohne Application Server auf GitHub Pages. Für Formulare existiert bewusst ein kleiner separater Node-Dienst auf dem vorhandenen digitalisierungsplanung.de-VPS. Er hat eigene Runtime/Env, keine gemeinsame Produkt-DB oder Session und speichert keine Formularinhalte in einer eigenen Datenbank. GitHub-Actions-Runner bleiben kurzlebige Build-/Automationsmaschinen.
Die Website ist bewusst ohne Frontend-Framework gebaut.
| Ebene | Technik |
|---|---|
| Markup | HTML5 |
| Styling | eine eigene CSS-Codebasis in css/styles.css |
| Browserlogik | Vanilla JavaScript |
| Build-Bundler | keiner |
| React/Vue/Angular | keiner |
| jQuery | keiner |
| CSS-Framework | keines |
| Webfonts | keine externen Google Fonts; System-Font-Stacks |
| Bilder | JPG, WebP, PNG, SVG |
| SEO | statische Meta-Tags, Open Graph, Twitter Cards, JSON-LD, Sitemap |
| Deployment | GitHub Pages aus main |
Die Browserlogik liegt hauptsächlich in:
js/main.js – Navigation, Kontakt-/Exposé-Formulare, Flyer, Gallery, Lightbox, URL-Prefillsjs/cookie-consent.js – Consent-Speicherung und kontrolliertes Laden von Analyticsjs/analytics.js – Google Analytics 4 / gtag.js, Measurement ID G-QVRRBPYNVMFür Objektseiten unter /objekt/ wird über window.__eichmannJsBase sichergestellt, dass die gemeinsamen JavaScript-Dateien mit dem korrekten relativen Pfad geladen werden.
Dieses Repository benötigt Node lokal und in GitHub Actions für Generierung, Scraping, Bildverarbeitung und Tests. Der separate Formular-Gateway läuft serverseitig als Node-Dienst im Repository ChristianHohlfeld/digitalisierungsplanung.de; er ist nicht Teil des statischen GitHub-Pages-Deployments dieses Repositories.
package.json verlangt Node >= 20.
Direkte Dependencies:
| Paket | Verwendung | aktuell aufgelöst |
|---|---|---|
playwright | Browser-Automation: Immowelt-Import und Browser-Regressionsprüfungen | 1.63.0 |
sharp | Bildkonvertierung, Größenanpassung, Social-Share-Bilder | 0.33.5 |
Wichtige Befehle:
npm ci
npm run sync-immowelt
npm run sync-immowelt:render
npm run test:legal
npm run test:expose-form
npm run test:layout
npm run test:logo
npm run test:share
npm run test:jsonld
npm test
Für Playwright lokal:
npx playwright install chromium
Für die statische Website nein; für Formulare ja.
GitHub Pages liefert die Website aus. Kontakt- und Exposé-POSTs gehen an den vorhandenen VPS unter forms.digitalisierungsplanung.de. Dort terminiert Nginx TLS und routet ausschließlich die Formularpfade an dp-forms.service auf Loopback-Port 8791. Es gibt weiterhin keine eigene Formular-Datenbank.
Die Dateien liegen im GitHub-Repository und werden von GitHub Pages statisch ausgeliefert.
Die Datei:
CNAME
enthält:
immobilieneichmann.de
Damit ist die Custom Domain der GitHub-Pages-Site zugeordnet.
Die DNS-Konfiguration selbst liegt außerhalb dieses Repositories beim jeweiligen DNS-Provider. Das Repository enthält keine Zugangsdaten zum DNS-Provider.
immobilieneichmann.de auf GitHub Pages auf.cookie-consent.js und main.js aus.forms.digitalisierungsplanung.de aufgerufen./
├── .github/workflows/ GitHub-Actions-Automation
├── admin/ statisches Exposé-Admin
├── assets/
│ ├── flyer/ Flyerbilder
│ ├── listings/ lokal gespeicherte Objektbilder
│ ├── logo* Marken-/Header-Assets
│ └── share-card* Social-Preview-Bilder
├── css/styles.css gesamtes Public Styling
├── data/listings.json generierter lokaler Spiegel der Immowelt-Objektdaten
├── js/
│ ├── main.js
│ ├── cookie-consent.js
│ └── analytics.js
├── objekt/*.html generierte Objekt-/Exposé-Seiten
├── partials/ generierte HTML-Fragmente
├── scripts/ Generatoren, Importer und Tests
├── index.html Startseite
├── kontakt.html allgemeines Kontaktformular
├── impressum.html
├── datenschutz.html
├── widerrufsbelehrung.html
├── vertrag-widerrufen.html
├── sitemap.xml
├── robots.txt
├── llms.txt
├── CNAME
├── .nojekyll
├── package.json
└── package-lock.json
Die fachliche Single Source of Truth ist Immowelt. Helmut pflegt dort Titel, Preis, Status, Referenznummern und Objektdaten.
data/listings.json ist ausschließlich der lokale, generierte Spiegel für Rendering und Website-Betrieb. Als lokale Abweichung ist nur site_hidden erlaubt: damit kann ein Immowelt-Objekt auf der eigenen Website ausgeblendet werden, ohne Immowelt zu verändern.
Top-Level-Struktur:
{
"sot": "local",
"source": "...",
"immowelt_profile": "...",
"scraped_at": "...",
"listing_count": 13,
"listings": []
}
Typische Felder eines Listings:
idsluglocal_urltitlepricelocationroomsliving_areaplot_areatypestatusshort_descriptiondescriptionfactsexpose_urlmain_image_urlimagesfloor_plansimage_basegallery_basesfloor_plan_basessourceimmowelt_idsync_policymissing_on_immoweltactive – steuert, ob ein Objekt öffentlich erscheint; false behält es intern, veröffentlicht es aber nichtdetail_page – false erlaubt einen öffentlichen Kontakt-Teaser ohne erfundene Exposé-Inhalte; nur true erzeugt Detailseite und Sitemap-Eintragmanual_overridesDie HTML-Seiten unter objekt/, die Karten auf Start-/Projektseite und die Objekt-URLs in sitemap.xml werden daraus erzeugt.
Immowelt ist nur eine externe Inbound-Quelle.
Das Immowelt-Konto wird von dieser Codebasis niemals beschrieben.
Quelle:
https://www.immowelt.de/profil/3b18336c6a2e401da38e9cc20268270d
Importer:
scripts/sync-immowelt.mjs
Workflow:
.github/workflows/sync-immowelt.yml
data/listings.json gemerged.main committed.Der Workflow läuft regulär:
0 */3 * * *
also alle drei Stunden nach UTC.
Zusätzlich kann er manuell gestartet werden.
Wenn scripts/sync-immowelt.mjs oder der Workflow selbst geändert wird, wird auf Push nur aus dem vorhandenen kanonischen JSON gerendert, damit Templateänderungen ohne erneuten Live-Scrape sofort auf alle Objektseiten kommen.
Immowelt kann automatisierte Browser mit DataDome/Captcha blockieren.
Darum ist der Import bewusst als Soft-Fail gebaut:
Standard:
sync_policy = independent
Bedeutung:
manual_overrides[field] = true schützt einen Wert vor Immowelt-Überschreibensource: local ist lokal autoritativmissing_on_immowelt: truesync_policy: mirror erlaubt Auto-Entfernung eines verknüpften, dort verschwundenen ObjektsObjektbilder liegen persistent im Git-Repository:
assets/listings/
Der Importer erzeugt in der Regel:
Sharp übernimmt die Konvertierung.
Nicht mehr referenzierte, vom System verwaltete Objektbilder werden beim Rendern/Sync als Orphans erkannt und aufgeräumt.
Admin-Uploads werden zuerst per GitHub Contents API als Datei committed und danach in data/listings.json referenziert. Das ist ein zweistufiger Vorgang, keine Datenbanktransaktion.
Der öffentliche Exposé-Flow von:
https://www.bi-bodenseeimmo.de/
wurde am 20.09.2026 gegen aktuelle Objektseiten geprüft.
Die sichtbaren Personen-/Adressfelder des Eichmann-Exposé-Flows entsprechen diesem Referenzflow:
Auf Eichmann-Exposé-Seiten gibt es bewusst kein zusätzliches Freitext-Nachrichtenfeld.
Die bi-bodenseeimmo-Seite zeigt zusätzlich eine Datenschutz-Einwilligungscheckbox. Diese wird hier bewusst nicht kopiert: die Eichmann-Site verwendet für die notwendige Anfragebearbeitung einen Datenschutzhinweis mit Link statt einer zusätzlichen Pflicht-Einwilligung.
Generiert wird der Flow zentral in:
scripts/sync-immowelt.mjs
Dadurch gelten Änderungen automatisch für alle erzeugten Objektseiten.
Zusätzlich zu den sichtbaren Feldern werden Objektkontext und Mail-Metadaten mitgegeben:
anliegen = Exposé-Anfrageobjektobjekt_urlbotcheckMail-Betreff und Empfänger werden serverseitig festgelegt; der Browser darf sie nicht frei bestimmen.
js/main.js:
required-/E-Mail-Regelnform.checkValidity()reportValidity()Damit ist eine komplett leere Exposé-Anfrage technisch nicht mehr möglich.
Regressionstest:
npm run test:expose-form
Der Test startet Chromium, lädt eine generierte Exposé-Seite, prüft Feldsatz, Reihenfolge, Required-Status, Payload und Success-UI und mockt ausschließlich die Netzwerkgrenze zum eigenen Formular-Gateway.
Zusätzlich zur Browservalidierung validiert der Gateway die Pflichtfelder nochmals serverseitig. Er erzwingt Tenant, erlaubte Origins, feste Empfänger, feste Exposé-URL-Domain, Requestgrößen und Rate-Limits. Erfolg wird erst gemeldet, nachdem Amazon SES den Versand angenommen hat.
kontakt.html ist vom Exposé-Flow getrennt.
Pflichtfelder:
Optional:
Auch hier:
Produktiver Pfad:
Browser auf immobilieneichmann.de
-> HTTPS JSON POST
-> https://forms.digitalisierungsplanung.de/v1/immobilieneichmann/{contact|expose}
-> Nginx
-> dp-forms.service (127.0.0.1:8791)
-> Amazon SES
-> info@immobilien-eichmann.com
Der Gateway ist vom Hauptprodukt digitalisierungsplanung.de getrennt: eigener systemd-Dienst, eigener Linux-User, eigene Environment-Datei, eigener Loopback-Port, keine gemeinsame Account-Datenbank und keine Produktsession.
Bei Erfolg antwortet er mit:
{ "success": true, "requestId": "…" }
Bei Fehler zeigt die Website die bestehende Fehlerbox und den Mailto-Fallback. Die HTML-Formulare besitzen zusätzlich ein normales action auf denselben Gateway und method="POST" als einfachen No-JavaScript-Fallback.
Kontakt-/Exposé-Eingaben werden nicht in dieses GitHub-Repository, Local Storage, Session Storage oder eine eigene Formular-Datenbank geschrieben. Der Gateway verarbeitet sie im Arbeitsspeicher für Validierung und Mailversand. Seine technischen Logs enthalten Request-ID, Tenant, Flow, Status und Dauer, aber keine Formularinhalte oder Kontaktdaten. Danach liegt die Anfrage als E-Mail im Zielpostfach info@immobilien-eichmann.com, abhängig von dessen Aufbewahrungseinstellungen.
immobilien-eichmann.com wird weiterhin nicht in diesem Repository konfiguriert.Consent-Code:
js/cookie-consent.js
Browser-Key:
eichmann_cookie_consent_v1
Gespeichert wird in localStorage:
{
"necessary": true,
"analytics": false,
"ts": 0
}
Es wird also nur die Consent-Entscheidung lokal im Browser gespeichert.
Analytics wird nicht beim ersten Seitenaufruf automatisch geladen.
Nur wenn der Nutzer „Alle akzeptieren“ wählt:
cookie-consent.js lädt js/analytics.jsanalytics.js lädt Googles gtag.jsG-QVRRBPYNVMOhne Analytics-Consent wird googletagmanager.com von unserer Analytics-Logik nicht geladen.
Keine externen Google Fonts.
Die Website verwendet lokale/System-Font-Stacks. Dadurch entsteht beim normalen Rendern kein Fonts-Request an Google.
Es gibt keinen automatisch geladenen Maps-Iframe.
Auf der Kontaktseite gibt es nur einen externen Link. Erst wenn der Nutzer ihn anklickt, wird Google Maps geöffnet.
URL:
https://immobilieneichmann.de/admin/
Unified UI (tabs: Immowelt | Eigen-Inserate). /admin/eigen/ redirects to /admin/#eigen.
Details: admin/README.md and docs/STABSCHEF-admin-unified-2026-09-25.md.
admin/config.json:
auth_mode = password_session
Login benötigt:
Kein GitHub-PAT. Der Browser ruft POST /admin/api/login auf; nginx proxyt auf 127.0.0.1:3847.
Die API prüft Allowlist + password_sha256 und setzt ein HttpOnly Secure SameSite=Strict Cookie.
Browser → /admin/api/{eigen|visibility|publish}
→ eichmann-admin-api (systemd)
→ SQLite SoT → publish-from-db → /var/www/…
Immowelt-Konto bleibt read-only. Immowelt-Import weiterhin über sync-immowelt.yml.
sessionStorage Tokenadmin-eigen-save.yml (repository_dispatch → SSH upsert)Deprecated 2026-09-25: Day-to-day CRUD uses
/admin/api/on the droplet. The flows below describe the old PAT + Contents API path and may still apply to leftoveradmin-save.ymltooling.
Primärer Pfad:
Admin-Browser
-> GET data/listings.json + SHA über GitHub Contents API
-> Benutzer ändert Felder
-> PUT data/listings.json mit bisherigem SHA
-> GitHub Commit
-> repository_dispatch admin_apply_render
-> GitHub Action rendert
-> Commit generierte Outputs
-> GitHub Pages Deployment
Die Verwendung des aktuellen File-SHA ist eine einfache Form von Optimistic Concurrency: ein offensichtlich veralteter Stand kann von GitHub mit Konflikt abgewiesen werden.
Wenn der direkte Contents-API-Save fehlschlägt:
repository_dispatch: admin_save_listings
Dann schreibt admin-save.yml die komplette vom Browser gesendete JSON-Datei und rendert anschließend.
Dieser Fallback ist funktional, aber keine transaktionale Multi-User-Datenbank. Gleichzeitiges Bearbeiten durch mehrere Admins sollte vermieden werden.
Das Admin erzeugt:
source: localsync_policy: independentmanual_overrides für manuell gepflegte FelderEine optionale Immowelt-URL/UUID kann als Inbound-Verknüpfung hinterlegt werden.
Löschen entfernt den Datensatz aus data/listings.json.
Der anschließende Render entfernt:
objekt/{slug}.htmlassets/listings/.data/listings.json wird committed.Maximale vom Admin akzeptierte Uploadgröße: ungefähr 4,5 MB.
Erlaubte Dateitypen im aktuellen Adminpfad:
Wird ein Feld im Admin manuell verändert, schreibt das Admin:
"manual_overrides": {
"title": true,
"price": true,
"updated_at": "..."
}
Der Immowelt-Importer respektiert diese Locks.
Geschützte Feldgruppen umfassen unter anderem:
So bleibt lokal gepflegter Inhalt autoritativ.
sync-immowelt.ymlAufgabe:
Schreibziel:
data/listings.jsonassets/listings/partials/listings-grid.htmlindex.htmlprojekte.htmlobjekt/sitemap.xmladmin-save.ymlAufgabe:
repository_dispatch empfangenEvents:
admin_save_listingsadmin_apply_renderadmin_trigger_syncapply-legal-baseline.ymlWird ausgeführt, wenn die Legal-Baseline selbst geändert wird.
Ablauf:
layout-regression.ymlHard Gate für relevante Frontendänderungen.
Prüft:
Screenshots werden als GitHub-Actions-Artefakt hochgeladen.
logo-consistency.ymlPrüft Logo-Dateien, Versionierung und bekannte Fehlerbilder.
Der Logo-Test selbst läuft aktuell standardmäßig soft; die Header-Logo-Sicherheitsversion ist:
assets/logo-header.svg?v=header-safe-v1
Der zusätzliche rechte SVG-Viewport verhindert, dass das letzte N im Header wieder abgeschnitten wird.
share-card.ymlAuf jedem Push/PR:
npm run test:share
Prüft unter anderem:
refresh-share-card.ymlNur bei Änderungen am Refresh-Script/Workflow.
Lädt das freigegebene Immobilienbild, erzeugt 1200 × 630 und schreibt:
share-card-source-plain-v2.jpgshare-card-plain-v2.jpgEs werden kein Text und kein Logo-Overlay hinzugefügt.
Das Pages-Build-/Deploy ist ein GitHub-eigener Workflow und liegt deshalb nicht zwingend als eigene YAML-Datei in diesem Verzeichnis.
Jeder relevante Commit auf main wird anschließend über GitHub Pages veröffentlicht.
npm test
npm run test:share
npm run test:logo
npm run test:jsonld
npm run test:legal
npm run test:layout
npm run test:expose-form
scripts/test-legal-baseline.mjs prüft unter anderem:
scripts/test-expose-form.mjs prüft mit echtem Chromium unseren kompletten Browserflow und mockt kontrolliert nur die Netzwerkgrenze zum eigenen Formular-Gateway. Feldsatz, Validierung, Payload und Success-UI bleiben dadurch deterministisch testbar.
Root-Seiten verwenden:
https://immobilieneichmann.de/assets/share-card-plain-v2.jpg
Maße:
1200 × 630
Das Bild ist bewusst plain, ohne zusätzlichen Text und ohne zusätzliches Logo.
Generierte Objektseiten verwenden nach Möglichkeit das jeweilige Immobilienbild als Social Preview und fallen sonst auf die allgemeine Share-Card zurück.
Vorhanden:
robots.txtsitemap.xmlllms.txtrobots.txt erlaubt die öffentliche Site und disallowt /admin/ für Crawler.
Wichtig: robots.txt ist keine Zugriffskontrolle.
| Dienst | Wann aufgerufen | Zweck | Welche Daten |
|---|---|---|---|
| GitHub Pages | jeder Seitenaufruf | statisches Hosting/CDN | normale HTTP-Verbindungsdaten |
| GitHub REST API | nur Admin-Nutzung | Listings/Bilder lesen und schreiben, Workflows dispatchen | Repo-Daten + PAT im Request |
| GitHub Actions | Cron, Push, Admin-Dispatch | Scrape, Render, Tests, Bildverarbeitung | Repo-/Build-Daten |
eigenes Formular-Gateway (forms.digitalisierungsplanung.de) | Kontakt-/Exposé-Submit | serverseitige Validierung und Mail-Übergabe | vom Nutzer eingegebene Formulardaten |
| Amazon SES | nach erfolgreicher Gateway-Validierung | transaktionaler E-Mail-Versand | Formulardaten im Mailinhalt + Empfänger/Reply-To |
| Immowelt | automatischer Import / externe Links | öffentliche Objektquelle | öffentliche Objektdaten |
| Google Analytics 4 | nur nach Analytics-Consent | Statistik | Analytics-/Browserdaten nach Google-Konfiguration |
| Google Maps | nur nach Nutzer-Klick | externe Kartenansicht | erst nach Öffnen von Google |
| npm Registry | nur Build/Install | Playwright/Sharp installieren | Build-Metadaten |
Nicht vorhanden:
| Daten | Speicherort |
|---|---|
| statische Website | GitHub Repository + GitHub Pages |
| Immobilien-Stammdaten | data/listings.json im Git-Repo |
| Objektbilder | assets/listings/ im Git-Repo |
| generierte Exposés | objekt/*.html im Git-Repo |
| Kontakt-/Exposé-Eingaben | nicht im Repo/Browser-Speicher; flüchtige Verarbeitung im eigenen Formular-Gateway, danach E-Mail-Versand über Amazon SES |
| Formular-Gateway-Logs | nur technische Metadaten ohne Formularinhalte/Kontaktdaten |
| zugestellte Anfragen | Ziel-Mailbox info@immobilien-eichmann.com |
| Analytics-Daten | Google Analytics, nur nach Consent |
| Consent-Auswahl | Browser-localStorage |
| Admin-PAT | Browser-sessionStorage |
| Admin-Passwort | nicht persistent im Browser; nur Hash liegt öffentlich in Config |
| Git-Historie | GitHub |
| CI-Logs/Artefakte | GitHub Actions |
Nicht der HTML-Login allein.
Da das Admin vollständig statisch ist, kann jeder Besucher seinen JavaScript-Code lesen.
Der tatsächliche Schreibschutz ist GitHub:
Es gibt keine transaktionale Datenbank und kein eigenes Locking-System.
Der normale Contents-API-Save nutzt den letzten bekannten SHA und erkennt dadurch viele Stale-Write-Situationen.
Der Fallback admin_save_listings kann jedoch die komplette JSON aus dem Browserzustand schreiben. Daher sollte das Admin nicht von mehreren Personen gleichzeitig am selben Objekt benutzt werden.
Die Browservalidierung verhindert normale leere Submits.
Da Client-Code grundsätzlich manipulierbar ist, validiert dp-forms.service dieselben Pflichtfelder serverseitig. Der Gateway erzwingt feste Tenant-/Origin-/Empfängerregeln, Body- und Rate-Limits und akzeptiert für Exposés nur Objekt-URLs der Eichmann-Domain.
git clone <repo>
cd eichmannimmobilien
npm ci
npx playwright install chromium
Einfacher Static Server:
npx serve .
Danach zum Beispiel:
http://localhost:3000/
http://localhost:3000/admin/
Nur rendern, ohne Immowelt live zu lesen:
node scripts/sync-immowelt.mjs --render-only
Live-Import:
node scripts/sync-immowelt.mjs
Seed aus anderer JSON:
node scripts/sync-immowelt.mjs --from-json /pfad/datei.json
Bevorzugt über /admin/.
Resultat:
Admin -> GitHub JSON -> Render Action -> Commit -> Pages
Immowelt-Workflow manuell starten oder nächsten 3-Stunden-Lauf abwarten.
Im Admin „+ Neues Objekt“.
scripts/sync-immowelt.mjs ändern und pushen.
Der Push-Trigger rendert alle Objektseiten aus data/listings.json neu.
js/analytics.js und gegebenenfalls datenschutz.html anpassen.
Mindestens prüfen/anpassen:
js/main.jsaction in kontakt.htmlscripts/sync-immowelt.mjsdatenschutz.htmlscripts/test-expose-form.mjsscripts/test-legal-baseline.mjsHeader verwendet die crop-sichere Datei assets/logo-header.svg.
Logoänderungen immer gemeinsam mit scripts/test-logo-consistency.mjs prüfen.
data/listings.json ist die Single Source of Truth.dp-forms-Gateway.Exakte Objektadressen werden öffentlich nicht ausgegeben. Angebotskarten, Exposé-Fakten, Beschreibungen und die öffentliche data/listings.json werden auf Stadtteil/Ort reduziert. Die Geschäftsadresse von Immobilien Eichmann in Footer/Impressum bleibt davon unberührt.