Vor dem Senden ausfüllen
[server name][agency name][city, the districts I work in, sale or rent][licence no][phone][WhatsApp number][email][office address][domain][district-neighbourhood-for-sale-rooms-type][slug][[FIYAT]
Baue auf der Website [server name] auf meinem Opus Growth Hosting-Server eine statische, schnelle Inseratswebsite für mein Maklerbüro [agency name], die jedes Inserat aus einer einzigen JSON-Datei generiert. Büro: [city, the districts I work in, sale or rent]. Meine Gewerbeerlaubnis für den Immobilienhandel (Taşınmaz Ticareti Yetki Belgesi): [licence no]. Inserate: [for each listing the listing no, title, district, neighbourhood, rooms, gross and net square metres, floor, year built, heating, service charge, title deed status, price, listing date, a two-paragraph description, four key features, photo links and the location detail the owner has agreed to show]. Kontakt: [phone], [WhatsApp number], [email], [office address]. Domain: [domain].
Seiten: Startseite (Suchformular für Stadtteil, Zimmerzahl und Höchstpreis, Top-Inserate, Stadtteilführer, FAQ), Inserate (/ilanlar/: Filter, Trefferzahl, Inseratsraster, Inserats-FAQ), eine Seite pro Inserat (/ilanlar/[district-neighbourhood-for-sale-rooms-type]/), Über uns (/hakkimizda/: die Schritte des Immobilienkaufs und die Gewerbeerlaubnis), Kontakt (/iletisim/, ohne Formular), Datenschutz (/gizlilik/) und 404.html.
Stil: warmer, redaktioneller Immobilienstil. Heller Sandton als Hintergrund, Tintentext, ein einziger dunkler Akzent wie Kiefergrün (weißer Text auf Buttons), eine Fontshare-Serifenschrift für Überschriften und Preise (zum Beispiel Sentient 500) und eine Sans für den Fließtext (zum Beispiel Ranade 400). Inseratskarten mit 4:3-Foto, großem Serifen-Preis und kleinen Tags für Zimmer, Quadratmeter und Etage. Das Kartenfoto zoomt beim Hover leicht. Es werden keine Kartenbibliothek, kein Google-Maps-iframe und keine Galerie- oder Slider-Bibliothek geladen.
Eine Quelle und Generierung:
1. Alle Inseratsdaten liegen in `assets/ilanlar.json`. Schreibe ein kleines `ilan-build.mjs` und führe es mit site_run_command als `node ilan-build.mjs` aus: (a) es erzeugt für jedes Inserat `ilanlar/[slug]/index.html` aus der Vorlage `_src/ilan.tpl`, (b) es setzt alle Karten zwischen `<!-- ilanlar:start --><!-- ilanlar:end -->` in `ilanlar/index.html` und die Top-Inserate zwischen `<!-- one-cikan:start --><!-- one-cikan:end -->` auf der Startseite ein, (c) es schreibt das ItemList-Schema der Inseratsseite zwischen `<!-- ilan-ld:start --><!-- ilan-ld:end -->`. Verwende in der Vorlage Platzhalter wie `[[FIYAT]]`, nicht `{{...}}`. Reihenfolge: ilan-build, chrome-stamp, Tailwind-Build, seo-stamp. Ein neues Inserat hinzuzufügen oder ein verkauftes zu entfernen soll bedeuten: JSON bearbeiten und diese Reihenfolge erneut ausführen; wird die Seite eines verkauften Inserats gelöscht, leite ihre URL mit site_redirects auf /ilanlar/ um.
2. Die Karten sind auf dem Server generiertes statisches HTML, sodass jedes Inserat ohne JS und für Suchmaschinen sichtbar ist. Jede Karte trägt die Attribute data-ilce, data-oda, data-fiyat, data-m2 und data-tarih für den Filter; Komponenten-Klassennamen dürfen nicht mit Tailwind-Utilities kollidieren (verwende ein Präfix wie lcard).
Filterregeln:
1. Der Filter ist ein echtes Formular mit action="/ilanlar/" und method="get": beschriftete Selects für Stadtteil, Zimmerzahl, Mindestpreis, Höchstpreis und Sortierung sowie ein Button "Filtern". Ohne JS füllt das Formular die URL, aber die Liste ändert sich nicht; sage das in einem Satz innerhalb von `<noscript>`.
2. `ilan-filtre.js` wird mit defer nur auf /ilanlar/ geladen: Beim Laden liest es ?ilce=&oda=&min=&max=&sira= mit URLSearchParams (und akzeptiert nur Werte, die es in den Selects gibt), blendet Karten mit hidden aus und ein, ordnet das DOM für die Sortierung neu und aktualisiert bei jeder Änderung die URL mit history.replaceState. Die gefilterte Liste lässt sich als Link teilen, und der Zurück-Verlauf füllt sich nicht.
3. Gib die Trefferzahl in einem Satz mit role="status" aus ("2 von 6 Inseraten entsprechen Ihren Filtern"). Bei null Treffern zeige einen Leerzustand und einen Link "Filter zurücksetzen". Der Filterkasten steht im ersten Bildschirm, daher darf JS dort nichts hinzufügen oder ausblenden; nur die Karten ändern sich, sonst entsteht ein Layout-Shift (CLS).
4. Auf der Inseratsseite ist das erste Kartenfoto nicht lazy, die übrigen sind es; Kartentitel sind auf dieser Seite h2, auf der Startseite h3 unter der h2 "Top-Inserate".
Inseratsseite:
1. Der erste Absatz fasst das Inserat in einem Satz zusammen: Viertel, Stadtteil, Zimmer, Quadratmeter, Baujahr und Preis. Danach der Fotostreifen: figures, die per CSS scroll-snap ohne JS seitlich scrollen, darunter Vorschaubilder, die Sprunglinks zu den einzelnen Fotos auf der Seite sind. `galeri.js` (nur auf Inseratsseiten) fängt Klicks auf Vorschaubilder ab, scrollt nur den Streifen, ohne dass die Seite springt, und markiert das Vorschaubild des sichtbaren Fotos über einen IntersectionObserver mit aria-current="true". Das erste Foto ist das LCP-Element der Seite: fetchpriority="high", nicht lazy.
2. Eine mitlaufende (sticky) Preisbox in der rechten Spalte: der Preis, eine Faktenliste (dl: Preis, Inseratsnummer, Zimmer, Brutto- und Nettoquadratmeter, Etage, Baujahr, Heizung, Hausgeld/Nebenkosten, Grundbuch-/Titelstatus, Inseratsdatum), ein WhatsApp-Link mit vorausgefüllter Nachricht, die die Inseratsnummer enthält, das Telefon und die Erlaubnisnummer. Die türkischen Vorschriften verlangen die Erlaubnisnummer bei Immobilieninseraten; setze sie auch in den Footer.
3. Lage: Veröffentliche nicht die vollständige Adresse. Verwende ein lokales statisches Kartenbild, das die ungefähre Lage in der vom Eigentümer erlaubten Genauigkeit zeigt (meist das Viertel), und weise darunter darauf hin, dass die vollständige Adresse bei der Besichtigung mitgeteilt wird. Wenn du die Karte aus OpenStreetMap-Kacheln baust, halte dich an die Nutzungsrichtlinie für Kacheln (einmal pro Inserat, wenige Kacheln, kein Massen-Download): Lade die Kacheln mit site_fetch nach `_src/harita/`, installiere sharp mit site_run_command und background=true (`npm i -D sharp`), füge die Kacheln dann mit einem kleinen Skript zusammen, schneide um den Mittelpunkt zu, markiere den ungefähren Bereich mit einem halbtransparenten Kreis und speichere WebP-Dateien mit 480 und 768 Pixeln. Läuft der Zuschnitt über den Kachelbereich hinaus, erscheint am Rand ein schwarzer Streifen; lade mindestens 5x3 Kacheln rund um den Mittelpunkt. Schreibe den Hinweis "© OpenStreetMap contributors" sowohl ins Bild als auch als Link darunter. Die Karte lädt lazy, und kein externes Kartenskript gelangt auf die Seite.
Schema: Auf der Inseratsseite RealEstateListing (url, name, description, datePosted, ein image-Array, offers mit einem Offer: price, priceCurrency "TRY", availability und die @id des Unternehmens als seller) mit einem Apartment oder SingleFamilyResidence als mainEntity (numberOfRooms, numberOfBedrooms, numberOfBathroomsTotal, floorSize als QuantitativeValue mit unitCode "MTK", yearBuilt und eine address nur mit Stadtteil, Provinz und Land). Google zeigt für diese Typen kein eigenes Rich Result; entscheidend ist, dass Such- und KI-Engines Preis, Zimmerzahl und Lage korrekt lesen. CollectionPage und ItemList auf der Inseratsseite, businessType "RealEstateAgent" in seo-stamp für das Büro, BreadcrumbList auf jeder Unterseite. Übernimm Preis, Quadratmeter, Nebenkosten und Grundbuchangaben nur aus der schriftlichen Angabe des Eigentümers; lass alles, was du nicht weißt, sowohl in der JSON als auch im Schema weg.
Einrichtung und bekannte Stolperfallen (jede hat in den Demo-Messungen Punkte gekostet, keine auslassen):
1. Ist auf dem Server bereits eine Website live, sag es mir zuerst, denn die Einrichtung überschreibt index.html und den Ordner assets. Zeige eine Vorschau von scaffold_site mit confirm=false und rufe es mit confirm=true erst nach meiner Freigabe auf. Lies dann site_design_guide und site_seo_guide. Halte vor dem Schreiben von Code die Designrichtung (Typografie, Farben, Raster, Signature-Element, Bewegungssprache und die Bibliotheken, die NICHT geladen werden) in einer kurzen Datei ART.md außerhalb von assets fest und zeige sie mir.
2. Kompiliere Tailwind, lade es nicht vom CDN. Installiere es mit site_run_command und background=true (`npm i -D tailwindcss@3`) und prüfe danach mit site_command_status. Die content-Pfade in `tailwind.config.js` müssen alle HTML-Dateien, die Vorlagen in `_src` und jede JS-Datei abdecken, die Klassen schreibt. `_src/tw.css` enthält die Zeile `@import "../assets/app.css";` plus die drei `@tailwind`-Zeilen; kompiliere sie mit `npx tailwindcss -c tailwind.config.js -i _src/tw.css -o assets/site.css --minify` in eine einzige Datei. Entferne das Skript cdn.tailwindcss.com und die Inline-tailwind.config von jeder Seite und verlinke nur `/assets/site.css`. Kompiliere nach jeder HTML-Änderung neu.
3. Hoste die Schriften selbst. Lies das CSS der gewählten Fontshare-Familie, lade die woff2-Datei jedes Schnitts einzeln mit site_fetch nach `assets/fonts/` und schreibe die @font-face-Regeln mit `font-display: swap` in `assets/app.css`, denn dort sucht design-check nach einer eigenen Schrift. Prüfe den font-family-Namen im heruntergeladenen CSS und stelle sicher, dass die Familie die türkischen Buchstaben ı, İ, ş und ğ enthält (Tanker hat kein ı). Lade JEDEN im ersten Bildschirm verwendeten Schnitt mit `<link rel="preload" as="font" type="font/woff2" crossorigin>` vor, platziert vor der Stylesheet-Zeile; sind es mehr als drei Dateien, nimm weniger Schnitte. In der Demo hat ein einziger nicht vorgeladener Body-Schnitt das mobile LCP auf 2,6 Sekunden und Performance auf 97 gedrückt. Den meisten Fontshare-Schriften fehlt das ₺-Zeichen, schreibe Preise daher mit "TL".
4. Mache Menü und Footer zu statischem HTML. Lade chrome.js, motion.js oder site.config.js nicht auf den Seiten. Schreibe ein kleines `chrome-stamp.mjs`, das die Navigationsliste aus site.config.js liest und `_src/header.tpl` und `_src/footer.tpl` zwischen den Markern `<!-- nav:start --><!-- nav:end -->` und `<!-- footer:start --><!-- footer:end -->` auf jeder Seite einsetzt und dem aktiven Link aria-current="page" gibt. Verwende Platzhalter wie `[[NAV]]` statt `{{...}}` und gib den Vorlagen keine .html-Endung, sonst behandeln design-check und seo-check sie als unfertige Seiten. Baue das mobile Menü mit einem JS-freien `<details>`. Lass Firmenname, Adresse und Telefon im Footer als reinen Text stehen und mache Footer- und Breadcrumb-Links mindestens 44 Pixel hoch.
5. Wandle Bilder beim Abruf mit site_fetch um: Schreibe dasselbe Bild als getrennte Dateien mit resize="480x", "800x" und "1200x", image_format="webp", quality=60 (site_fetch schneidet nicht zu, fordere die Quelle also im benötigten Seitenverhältnis an). Gib jedem `<img>` srcset, sizes sowie width und height passend zum echten Seitenverhältnis. Das Bild, das das LCP-Element der Seite ist, bekommt fetchpriority="high" und ist nicht lazy; jedes andere Bild bekommt loading="lazy" decoding="async". Füllt ein Bild den ersten mobilen Bildschirm, liefere mit `<picture>` einen kleineren Zuschnitt für Mobilgeräte.
6. Füge jeder Seite `<link rel="icon" href="/favicon.svg" type="image/svg+xml">` hinzu und erzeuge mit site_fetch und resize="180x180" aus dem Logo ein apple-touch-icon mit 180 Pixeln. Ohne Favicon-Link fordert der Browser /favicon.ico an, protokolliert einen 404, und Best Practices fällt auf 96.
7. Regeln des HTML-Validators: Beginne jedes Dokument mit einem großgeschriebenen `<!DOCTYPE html>`; jedes Leerzeichen im sichtbaren Text eines tel:-Links muss ein ` ` sein, auch das nach einem Präfix wie "Anrufen: 0212..."; verwende kein style=""; gib jedem Button ein type; setze aria-label nur auf Landmarks, Formulare oder interaktive Elemente (auf einer ul oder einem div ohne Rolle war es ein Fehler); beschrifte nie zwei Landmarks mit demselben Namen; lass keine Leerzeichen am Zeilenende.
8. Überspringe keine Überschriftsebenen: Auf einer Listenseite haben Kartentitel als h3 direkt nach dem h1 Accessibility auf 98 gedrückt. Wenn du eine Karte wiederverwendest, setze ihre Überschriftsebene pro Seite (h2 auf einer Listenseite, h3 innerhalb eines h2-Abschnitts).
9. Nenne das Bewegungsattribut data-reveal (design-check erkennt Bewegung nur unter diesem Namen). JS versetzt nur Blöcke in den versteckten Ausgangszustand, die beim Öffnen der Seite unterhalb des Bildschirms liegen, und blendet nie Inhalte des ersten Bildschirms aus; verwende transform, denn Überblenden mit opacity senkt das Kontrast-Audit. Setze das Attribut auf den Block, der eine Überschrift und ihre Beschreibung umschließt, nicht nur auf die Überschrift; in der Demo ist eine wartende Überschrift über den Absatz darunter gerutscht. Bei prefers-reduced-motion: reduce wird nichts versteckt und Animationen sind aus.
SEO, AEO und GEO:
1. Verwende Verzeichnis-URLs mit abschließendem Schrägstrich. Fülle den seo-Block von site.config.js und führe `node seo-stamp.mjs` aus. Schreibe seitenspezifisches Schema in eigene `<script type="application/ld+json">`-Blöcke, die über @id mit dem Basis-Graphen verknüpft sind. Verschachtle BreadcrumbList nicht in @graph; halte sie in einem eigenen Block, nur so erkennt seo-check sie.
2. Titel höchstens 60 Zeichen (seo-check meldet alles unter 25 oder über 65 als Fehler), Beschreibungen 130 bis 158 Zeichen, ein h1 pro Seite und mindestens 150 Wörter Hauptinhalt auf jeder Seite. Kontakt-, Datenschutz- und Listenseiten bleiben oft darunter; ergänze seitenspezifische Informationen, keine Füllwörter.
3. Der erste Absatz jeder Seite beantwortet die Frage der Seite direkt. Schreibe seitenspezifische FAQs mit `<details>` und zeichne sie als FAQPage aus. Zeige Herausgeber und ein Datum "Zuletzt aktualisiert" in einem `<time>`-Element.
4. Halte in robots.txt deine Entscheidung mit Begründung für GPTBot, OAI-SearchBot, ChatGPT-User, ClaudeBot, Claude-User, Claude-SearchBot, PerplexityBot, Perplexity-User, Google-Extended, Applebot-Extended, Bingbot und CCBot fest und verweise auf die Sitemap. sitemap.xml enthält echte lastmod-Daten. llms.txt enthält eine zutreffende Zusammenfassung der Website in zwei Sätzen und eine Liste der wichtigen Seiten.
5. Keine Erfindungen: Schreibe keine Bewertungen, Ratings, Auszeichnungen, Kundenzahlen, Zertifikate, Öffnungszeiten, Koordinaten oder Preise, ohne mich zu fragen. Lass jedes Feld, das du nicht kennst, komplett aus dem Schema weg.
Prüfschritte (der Reihe nach; sag nicht "fertig", bevor alle bestanden sind):
1. Führe mit site_run_command `node chrome-stamp.mjs`, den Tailwind-Build, `node seo-stamp.mjs`, `node seo-check.mjs` (0 Fehler, 0 Warnungen), `node design-check.mjs` (0 Fehler; begründe jede verbleibende Warnung) und `npx html-validate "**/*.html"` (0 Fehler) aus.
2. Sieh dir jede Seite mit site_preview an, mit view="mobile" und view="desktop". site_preview erfasst die Seite ohne Bewegung (reduced-motion); fehlt in dieser Aufnahme Inhalt, ist der statische Zustand kaputt. In Ganzseiten-Aufnahmen können feste Elemente wie eine Sticky-Leiste mitten auf der Seite erscheinen und weiter unten liegende Lazy-Bilder leer sein; zähle das nicht als Fehler, prüfe aber ihre Pfade.
3. Führe für jede Seite core_web_vitals mit strategy="mobile" und strategy="desktop" aus. Abnahmekriterium: mobil Performance mindestens 95 (Ziel 99 bis 100) und Accessibility, Best Practices und SEO jeweils 100; Desktop alle vier bei 100. Finde bei einer Seite, die das verfehlt, mit resources=true die Datei, die sie ausbremst, behebe sie und miss erneut. Führe dann seo_audit für die Startseite mit strategy="mobile" und runs=2 aus. In der Liste der Netzwerkanfragen darf keine Adresse außerhalb deiner eigenen Domain übrig bleiben.
4. Kein horizontales Scrollen bei 360, 390, 768 und 1440 Pixeln Breite, Touch-Ziele mindestens 44 Pixel, keine Konsolenfehler.
Interaktionsprüfung: site_preview kann den Filter nicht bedienen. Sag mir, wie ich prüfe, dass /ilanlar/?ilce=[district]&oda=3%2B1 nur passende Inserate zeigt und das Formular füllt, dass eine Änderung an einem Select die Adressleiste aktualisiert, dass der Leerzustand erscheint, wenn nichts passt, dass bei ausgeschaltetem JavaScript jedes Inserat angezeigt wird und dass ein Klick auf ein Vorschaubild auf einer Inseratsseite nur den Fotostreifen scrollt, ohne dass die Seite springt.
Nach der Veröffentlichung: Zeige mit site_headers die Regel `/assets/* Cache-Control: public, max-age=31536000, immutable` in der Vorschau und wende sie nach meiner Freigabe an; versioniere ab dann den Dateinamen, sobald sich CSS oder JS ändern (site-v2.css usw.). Reiche sitemap.xml mit gsc_submit_sitemap ein und prüfe die Startseite mit gsc_url_inspect. Gib mir am Ende: eine Punktetabelle pro Seite (Seite, vier mobile Werte, vier Desktop-Werte, LCP, CLS), das Ergebnis jedes Prüfschritts und die Liste der angewendeten Korrekturen.