Completa antes de enviar
[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]
En el sitio [server name] de mi servidor de hosting de Opus Growth, crea un sitio de anuncios estático y rápido para mi inmobiliaria [agency name] que genere todos los anuncios a partir de un único archivo JSON. Agencia: [city, the districts I work in, sale or rent]. Número de mi licencia de comercio inmobiliario (Taşınmaz Ticareti Yetki Belgesi): [licence no]. Anuncios: [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]. Contacto: [phone], [WhatsApp number], [email], [office address]. Dominio: [domain].
Páginas: inicio (un formulario de búsqueda por distrito, habitaciones y precio máximo, anuncios destacados, guía de distritos, preguntas frecuentes), anuncios (/ilanlar/: filtros, número de resultados, cuadrícula de anuncios, preguntas frecuentes sobre los anuncios), una página por anuncio (/ilanlar/[district-neighbourhood-for-sale-rooms-type]/), quiénes somos (/hakkimizda/: los pasos para comprar y la licencia), contacto (/iletisim/, sin formulario), privacidad (/gizlilik/) y 404.html.
Estilo: inmobiliario editorial y cálido. Fondo claro color arena, texto en tinta, un único acento oscuro como el verde pino (texto blanco en los botones), una serif de Fontshare para los títulos y los precios (por ejemplo Sentient 500) y una sans para el texto (por ejemplo Ranade 400). Tarjetas de anuncio con foto 4:3, un precio grande en serif y pequeñas etiquetas de habitaciones, metros cuadrados y planta. La foto de la tarjeta hace un ligero zoom al pasar el cursor. No se carga ninguna biblioteca de mapas, ni iframe de Google Maps, ni biblioteca de galería o slider.
Fuente única y generación:
1. Todos los datos de los anuncios viven en `assets/ilanlar.json`. Escribe un pequeño `ilan-build.mjs` y ejecútalo con site_run_command como `node ilan-build.mjs`: (a) genera `ilanlar/[slug]/index.html` para cada anuncio a partir de la plantilla `_src/ilan.tpl`, (b) estampa todas las tarjetas entre `<!-- ilanlar:start --><!-- ilanlar:end -->` en `ilanlar/index.html` y las destacadas entre `<!-- one-cikan:start --><!-- one-cikan:end -->` en la página de inicio, (c) escribe el schema ItemList de la página de anuncios entre `<!-- ilan-ld:start --><!-- ilan-ld:end -->`. Usa marcadores como `[[FIYAT]]` en la plantilla, no `{{...}}`. Orden: ilan-build, chrome-stamp, la compilación de Tailwind, seo-stamp. Añadir un anuncio o quitar uno vendido debe consistir en editar el JSON y volver a ejecutar este orden; cuando se elimine la página de un anuncio vendido, redirige su URL a /ilanlar/ con site_redirects.
2. Las tarjetas son HTML estático generado en el servidor, de modo que todos los anuncios se ven con JS desactivado y para los buscadores. Cada tarjeta lleva los atributos data-ilce, data-oda, data-fiyat, data-m2 y data-tarih para el filtro; los nombres de clase de los componentes no deben chocar con las utilidades de Tailwind (usa un prefijo como lcard).
Reglas del filtro:
1. El filtro es un formulario real con action="/ilanlar/" y method="get": selects con etiqueta para distrito, habitaciones, precio mínimo, precio máximo y orden, más un botón "Filtrar". Con JS desactivado el formulario rellena la URL pero la lista no cambia; dilo en una frase dentro de `<noscript>`.
2. `ilan-filtre.js` se carga con defer solo en /ilanlar/: al cargar lee ?ilce=&oda=&min=&max=&sira= con URLSearchParams (aceptando solo valores que existan en los selects), oculta y muestra tarjetas con hidden, reordena el DOM para ordenar y actualiza la URL con history.replaceState en cada cambio. La lista filtrada se puede compartir como enlace y el historial del botón Atrás no se llena.
3. Anuncia el número de resultados en una frase con role="status" ("2 de 6 anuncios coinciden con tus filtros"). Si no hay resultados, muestra un mensaje de estado vacío y un enlace "Borrar filtros". La caja de filtros está en la primera pantalla, así que JS no debe añadir ni ocultar nada ahí; solo cambian las tarjetas, o se produce un salto de diseño (CLS).
4. En la página de anuncios la foto de la primera tarjeta no es lazy y las demás sí; los títulos de las tarjetas son h2 en esta página y h3 bajo el h2 "Anuncios destacados" en la página de inicio.
Página de anuncio:
1. El primer párrafo resume el anuncio en una frase: barrio, distrito, habitaciones, metros cuadrados, año de construcción y precio. Después, la tira de fotos: figures que se desplazan en horizontal con CSS scroll-snap y sin JS, con miniaturas debajo que son enlaces internos a cada foto. `galeri.js` (solo en las páginas de anuncio) intercepta los clics en las miniaturas, desplaza únicamente la tira sin hacer saltar la página y marca con aria-current="true" la miniatura de la foto visible mediante un IntersectionObserver. La primera foto es el elemento LCP de la página: fetchpriority="high", no lazy.
2. Una caja de precio fija (sticky) en la columna derecha: el precio, una lista de datos (dl: precio, n.º de anuncio, habitaciones, metros cuadrados brutos y netos, planta, año de construcción, calefacción, gastos de comunidad, escritura, fecha del anuncio), un enlace de WhatsApp con un mensaje rellenado que incluya el número de anuncio, el teléfono y el número de licencia. La normativa turca exige el número de licencia en los anuncios inmobiliarios; ponlo también en el pie de página.
3. Ubicación: no publiques la dirección completa. Usa una imagen de mapa estático local que muestre la ubicación aproximada al nivel que haya acordado el propietario (normalmente el barrio) e indica debajo que la dirección completa se comunica en la visita. Si construyes el mapa con teselas de OpenStreetMap, respeta la política de uso de teselas (una vez por anuncio, pocas teselas, sin descargas masivas): obtén las teselas con site_fetch en `_src/harita/`, instala sharp con site_run_command y background=true (`npm i -D sharp`), y luego usa un pequeño script para unir las teselas, recortar alrededor del centro, marcar la zona aproximada con un círculo semitransparente y guardar archivos WebP de 480 y 768 píxeles. Si el recorte se sale del área de teselas aparece una franja negra en el borde; obtén al menos 5x3 teselas alrededor del centro. Escribe el crédito "© OpenStreetMap contributors" tanto dentro de la imagen como en un enlace debajo. El mapa se carga en diferido y ningún script de mapa externo entra en la página.
Schema: en la página de anuncio, RealEstateListing (url, name, description, datePosted, un array de image, offers con una Offer: price, priceCurrency "TRY", availability y el @id del negocio como seller) con un Apartment o SingleFamilyResidence como mainEntity (numberOfRooms, numberOfBedrooms, numberOfBathroomsTotal, floorSize como QuantitativeValue con unitCode "MTK", yearBuilt y una address solo con distrito, provincia y país). Google no muestra un resultado enriquecido específico para estos tipos; la idea es que los buscadores y los motores de IA lean correctamente el precio, el número de habitaciones y la ubicación. CollectionPage e ItemList en la página de anuncios, businessType "RealEstateAgent" en seo-stamp para la agencia, BreadcrumbList en todas las páginas interiores. Toma el precio, los metros cuadrados, los gastos de comunidad y los datos de la escritura únicamente de la declaración escrita del propietario; deja fuera, tanto del JSON como del schema, todo lo que no sepas.
Configuración y trampas conocidas (cada una costó puntos en las mediciones de la demo, no te saltes ninguna):
1. Si ya hay un sitio en producción en el servidor, avísame primero, porque la configuración inicial sobrescribe index.html y la carpeta assets. Previsualiza scaffold_site con confirm=false y llámalo con confirm=true solo después de mi aprobación. Luego lee site_design_guide y site_seo_guide. Antes de escribir código, pon la dirección de diseño (tipografía, colores, cuadrícula, elemento distintivo, lenguaje de movimiento y las bibliotecas que NO se cargarán) en un breve archivo ART.md fuera de assets y enséñamelo.
2. Compila Tailwind, no lo cargues desde el CDN. Instálalo con site_run_command y background=true (`npm i -D tailwindcss@3`) y sigue con site_command_status. Las rutas content de `tailwind.config.js` deben cubrir todos los archivos HTML, las plantillas de `_src` y cualquier archivo JS que escriba clases. `_src/tw.css` contiene la línea `@import "../assets/app.css";` más las tres líneas `@tailwind`; compílalo en un único archivo con `npx tailwindcss -c tailwind.config.js -i _src/tw.css -o assets/site.css --minify`. Quita el script cdn.tailwindcss.com y el tailwind.config en línea de cada página y enlaza solo `/assets/site.css`. Recompila después de cada cambio de HTML.
3. Aloja las fuentes tú mismo. Lee el CSS de la familia de Fontshare que hayas elegido, descarga por separado el woff2 de cada grosor con site_fetch en `assets/fonts/` y escribe las reglas @font-face con `font-display: swap` en `assets/app.css`, que es donde design-check busca una fuente personalizada. Comprueba el nombre font-family en el CSS descargado y confirma que la familia tiene las letras turcas ı, İ, ş y ğ (Tanker no tiene ı). Precarga TODOS los grosores usados en la primera pantalla con `<link rel="preload" as="font" type="font/woff2" crossorigin>`, colocado antes de la línea de la hoja de estilos; si son más de tres archivos, usa menos grosores. En la demo, un único grosor de texto sin precargar subió el LCP móvil a 2,6 segundos y bajó Performance a 97. La mayoría de las fuentes de Fontshare no tienen el signo ₺, así que escribe los precios con "TL".
4. Haz que el menú y el pie de página sean HTML estático. No cargues chrome.js, motion.js ni site.config.js en las páginas. Escribe un pequeño `chrome-stamp.mjs` que lea la lista de navegación de site.config.js y estampe `_src/header.tpl` y `_src/footer.tpl` entre los marcadores `<!-- nav:start --><!-- nav:end -->` y `<!-- footer:start --><!-- footer:end -->` en cada página, añadiendo aria-current="page" al enlace activo. Usa marcadores como `[[NAV]]` en lugar de `{{...}}` y no des a las plantillas la extensión .html, o design-check y seo-check las tratarán como páginas a medio hacer. Construye el menú móvil con un `<details>` sin JS. Mantén el nombre del negocio, la dirección y el teléfono como texto plano en el pie de página, y haz que los enlaces del pie y de las migas de pan midan al menos 44 píxeles de alto.
5. Convierte las imágenes al obtenerlas con site_fetch: escribe la misma imagen como archivos separados con resize="480x", "800x" y "1200x", image_format="webp", quality=60 (site_fetch no recorta, así que pide el origen en la proporción que necesites). Da a cada `<img>` srcset, sizes y width y height que coincidan con la proporción real. La imagen que es el elemento LCP de la página lleva fetchpriority="high" y no es lazy; cualquier otra imagen lleva loading="lazy" decoding="async". Si una imagen llena la primera pantalla móvil, sirve un recorte más pequeño al móvil con `<picture>`.
6. Añade `<link rel="icon" href="/favicon.svg" type="image/svg+xml">` a cada página y crea un apple-touch-icon de 180 píxeles a partir del logo con site_fetch y resize="180x180". Sin un enlace de favicon el navegador pide /favicon.ico, registra un 404 y Best Practices baja a 96.
7. Reglas del validador HTML: empieza cada documento con un `<!DOCTYPE html>` en mayúsculas; cada espacio en el texto visible de un enlace tel: debe ser ` `, incluido el que sigue a un prefijo como "Llamar: 0212..."; no uses style=""; da un type a cada botón; pon aria-label solo en landmarks, formularios o elementos interactivos (en un ul o en un div sin role era un error); nunca etiquetes dos landmarks con el mismo nombre; no dejes espacios en blanco al final de las líneas.
8. No saltes niveles de encabezado: en una página de lista, los títulos de tarjeta como h3 justo después del h1 bajaron Accessibility a 98. Cuando reutilices una tarjeta, fija su nivel de encabezado por página (h2 en una página de lista, h3 dentro de una sección h2).
9. Llama al atributo de movimiento data-reveal (design-check solo reconoce el movimiento con ese nombre). JS da el estado oculto inicial solo a los bloques que quedan por debajo de la pantalla cuando se abre la página, y nunca oculta el contenido de la primera pantalla; usa transform, porque desvanecer con opacity baja la auditoría de contraste. Pon el atributo en el bloque que envuelve un encabezado y su descripción, no solo en el encabezado; en la demo un encabezado en espera se deslizó sobre el párrafo de debajo. Con prefers-reduced-motion: reduce no se oculta nada y las animaciones están desactivadas.
SEO, AEO y GEO:
1. Usa URL de estilo directorio con barra final. Rellena el bloque seo de site.config.js y ejecuta `node seo-stamp.mjs`. Escribe el schema específico de cada página en bloques `<script type="application/ld+json">` separados, enlazados con el grafo base mediante @id. No anides BreadcrumbList dentro de @graph; mantenlo en su propio bloque, que es la única forma de que seo-check lo vea.
2. Títulos de como máximo 60 caracteres (seo-check marca como error todo lo que tenga menos de 25 o más de 65), descripciones de 130 a 158 caracteres, un h1 por página y al menos 150 palabras de contenido principal en cada página. Las páginas de contacto, privacidad y listados suelen quedarse cortas; añade información específica de la página, no relleno.
3. El primer párrafo de cada página responde directamente a la pregunta de la página. Escribe preguntas frecuentes específicas de cada página con `<details>` y márcalas como FAQPage. Muestra el editor y una fecha de "Última actualización" en un elemento `<time>`.
4. En robots.txt, escribe tu decisión con su motivo para GPTBot, OAI-SearchBot, ChatGPT-User, ClaudeBot, Claude-User, Claude-SearchBot, PerplexityBot, Perplexity-User, Google-Extended, Applebot-Extended, Bingbot y CCBot, y apunta al sitemap. sitemap.xml lleva fechas lastmod reales. llms.txt contiene un resumen exacto del sitio en dos frases y una lista de las páginas importantes.
5. Nada de invenciones: no escribas reseñas, valoraciones, premios, número de clientes, certificados, horarios, coordenadas ni precios sin preguntarme. Deja fuera del schema cualquier campo que no conozcas.
Puertas de control (en orden; no digas "hecho" hasta que pasen todas):
1. Con site_run_command ejecuta `node chrome-stamp.mjs`, la compilación de Tailwind, `node seo-stamp.mjs`, `node seo-check.mjs` (0 errores, 0 avisos), `node design-check.mjs` (0 errores; justifica cualquier aviso restante) y `npx html-validate "**/*.html"` (0 errores).
2. Revisa cada página con site_preview, view="mobile" y view="desktop". site_preview captura la página con el movimiento desactivado (reduced-motion); si falta contenido en esa captura, el estado estático está roto. En las capturas de página completa, los elementos fijos como una barra sticky pueden aparecer a mitad de página y las imágenes lazy más abajo pueden verse en blanco; no los cuentes como errores, pero comprueba sus rutas.
3. Ejecuta core_web_vitals para cada página con strategy="mobile" y strategy="desktop". Criterio de aceptación: en móvil, Performance de al menos 95 (objetivo 99 a 100) y Accessibility, Best Practices y SEO en 100; en escritorio, las cuatro en 100. Para una página que no llegue, usa resources=true para encontrar el archivo que la retrasa, corrígelo y mide de nuevo. Después ejecuta seo_audit en la página de inicio con strategy="mobile" y runs=2. No debe quedar en la lista de peticiones de red ninguna dirección fuera de tu propio dominio.
4. Sin desplazamiento horizontal a 360, 390, 768 y 1440 píxeles de ancho, áreas táctiles de al menos 44 píxeles y sin errores de consola.
Comprobación de la interacción: site_preview no puede operar el filtro. Dime cómo comprobar que al abrir /ilanlar/?ilce=[district]&oda=3%2B1 solo se muestran los anuncios que coinciden y se rellena el formulario, que al cambiar un select se actualiza la barra de direcciones, que el mensaje de estado vacío aparece cuando nada coincide, que todos los anuncios se muestran con JavaScript desactivado y que al hacer clic en una miniatura de una página de anuncio se desplaza solo la tira de fotos sin que la página salte.
Después de publicar: previsualiza la regla `/assets/* Cache-Control: public, max-age=31536000, immutable` con site_headers y aplícala cuando yo lo apruebe; a partir de entonces, versiona el nombre del archivo cada vez que cambien el CSS o el JS (site-v2.css y así sucesivamente). Envía sitemap.xml con gsc_submit_sitemap y comprueba la página de inicio con gsc_url_inspect. Al final entrégame: una tabla de puntuaciones por página (página, cuatro puntuaciones móviles, cuatro de escritorio, LCP, CLS), el resultado de cada puerta de control y la lista de correcciones que aplicaste.