Preencha antes de enviar
[nome do servidor][nome da agência][cidade, os bairros em que atuo, venda ou aluguel][número da licença][telefone][número do WhatsApp][e-mail][endereço do escritório][domínio][bairro-regiao-a-venda-quartos-tipo][slug][[PREÇO]
Use minha conexão com o Opus Growth para isso. Se você não encontrar a ferramenta de que precisa, procure entre as ferramentas do Opus Growth (com search_tools, se estiver na sua lista); se não conseguir acessar as ferramentas de forma alguma, não adivinhe, me avise.
No site [nome do servidor] do meu servidor de hospedagem Opus Growth, crie um site de anúncios estático e rápido para minha imobiliária [nome da agência], que gere todos os anúncios a partir de um único arquivo JSON. Imobiliária: [cidade, os bairros em que atuo, venda ou aluguel]. Número do meu registro profissional de corretagem de imóveis (Taşınmaz Ticareti Yetki Belgesi): [número da licença]. Anúncios: [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]. Contato: [telefone], [número do WhatsApp], [e-mail], [endereço do escritório]. Domínio: [domínio].
Páginas: início (um formulário de busca por bairro/região, quartos e preço máximo, anúncios em destaque, um guia das regiões, FAQ), anúncios (/ilanlar/: filtros, contagem de resultados, grade de anúncios, FAQ dos anúncios), uma página por anúncio (/ilanlar/[bairro-regiao-a-venda-quartos-tipo]/), sobre (/hakkimizda/: as etapas da compra e o registro profissional), contato (/iletisim/, sem formulário), privacidade (/gizlilik/) e 404.html.
Estilo: imobiliário editorial e acolhedor. Fundo areia claro, texto cor de tinta, um único tom de destaque escuro, como verde-pinho (texto branco nos botões), uma fonte serifada da Fontshare para títulos e preços (por exemplo, Sentient 500) e uma sem serifa para o texto corrido (por exemplo, Ranade 400). Cards de anúncio com foto 4:3, preço grande em serifa e pequenas tags para quartos, metros quadrados e andar. A foto do card dá um leve zoom ao passar o mouse. Nenhuma biblioteca de mapa, nenhum iframe do Google Maps e nenhuma biblioteca de galeria ou slider são carregados.
Fonte única e geração:
1. Todos os dados dos anúncios ficam em `assets/ilanlar.json`. Escreva um pequeno `ilan-build.mjs` e execute-o com site_run_command como `node ilan-build.mjs`: (a) ele gera `ilanlar/[slug]/index.html` para cada anúncio a partir do template `_src/ilan.tpl`, (b) ele preenche todos os cards entre `<!-- ilanlar:start --><!-- ilanlar:end -->` em `ilanlar/index.html` e os destacados entre `<!-- one-cikan:start --><!-- one-cikan:end -->` na página inicial, (c) ele escreve o schema ItemList da página de anúncios entre `<!-- ilan-ld:start --><!-- ilan-ld:end -->`. Use placeholders como `[[PREÇO]]` no template, não `{{...}}`. Ordem: ilan-build, chrome-stamp, build do Tailwind, seo-stamp. Adicionar um anúncio ou remover um já vendido deve significar editar o JSON e executar essa ordem novamente; quando a página de um anúncio vendido for excluída, redirecione a URL dela para /ilanlar/ com site_redirects.
2. Os cards são HTML estático gerado no servidor, então todo anúncio fica visível com JS desativado e para os mecanismos de busca. Cada card tem os atributos data-ilce, data-oda, data-fiyat, data-m2 e data-tarih para o filtro; os nomes de classe dos componentes não devem conflitar com utilitários do Tailwind (use um prefixo como lcard).
Regras do filtro:
1. O filtro é um formulário de verdade com action="/ilanlar/" e method="get": selects com rótulos para bairro/região, quartos, preço mínimo, preço máximo e ordenação, mais um botão "Filtrar". Com JS desativado, o formulário preenche a URL, mas a lista não muda; diga isso em uma frase dentro de `<noscript>`.
2. O `ilan-filtre.js` é carregado com defer somente em /ilanlar/: ao carregar, ele lê ?ilce=&oda=&min=&max=&sira= com URLSearchParams (aceitando só valores que existam nos selects), oculta e mostra cards com hidden, reordena o DOM para a ordenação e atualiza a URL com history.replaceState a cada mudança. A lista filtrada pode ser compartilhada como link e o histórico do botão voltar não fica cheio.
3. Anuncie a contagem de resultados em uma frase com role="status" ("2 de 6 anúncios correspondem aos seus filtros"). Sem resultados, mostre uma mensagem de estado vazio e um link "Limpar filtros". A caixa de filtro fica na primeira tela, então o JS não deve adicionar nem ocultar nada ali; só os cards mudam, caso contrário ocorre mudança de layout (CLS).
4. Na página de anúncios, a foto do primeiro card não é lazy e as demais são; os títulos dos cards são h2 nessa página e h3 sob o h2 "Anúncios em destaque" na página inicial.
Página do anúncio:
1. O primeiro parágrafo resume o anúncio em uma frase: bairro, região, quartos, metros quadrados, ano de construção e preço. Depois vem a faixa de fotos: figures que rolam na horizontal com CSS scroll-snap e sem JS, com miniaturas abaixo que são links internos para cada foto. O `galeri.js` (somente nas páginas de anúncio) intercepta os cliques nas miniaturas, rola apenas a faixa sem fazer a página pular e marca a miniatura da foto visível com aria-current="true" por meio de um IntersectionObserver. A primeira foto é o elemento LCP da página: fetchpriority="high", sem lazy.
2. Uma caixa de preço fixa (sticky) na coluna da direita: o preço, uma lista de dados (dl: preço, nº do anúncio, quartos, metros quadrados brutos e líquidos, andar, ano de construção, aquecimento, taxa de condomínio, escritura, data do anúncio), um link de WhatsApp com mensagem preenchida que inclui o número do anúncio, o telefone e o número do registro profissional. As regras turcas exigem o número do registro nos anúncios de imóveis; coloque-o também no rodapé.
3. Localização: não publique o endereço completo. Use uma imagem de mapa estática local que mostre a localização aproximada no nível que o proprietário autorizou (geralmente o bairro) e diga abaixo dela que o endereço completo é informado na visita. Se você montar o mapa com tiles do OpenStreetMap, siga a política de uso de tiles (uma vez por anúncio, poucos tiles, sem downloads em massa): busque os tiles com site_fetch em `_src/harita/`, instale o sharp com site_run_command e background=true (`npm i -D sharp`), depois use um pequeno script para unir os tiles, recortar em torno do centro, marcar a área aproximada com um círculo semitransparente e salvar arquivos WebP de 480 e 768 pixels. Se o recorte passar da área dos tiles, aparece uma faixa preta na borda; busque pelo menos 5x3 tiles em torno do centro. Escreva o crédito "© OpenStreetMap contributors" tanto dentro da imagem quanto como link embaixo dela. O mapa carrega de forma lazy e nenhum script de mapa externo entra na página.
Schema: na página do anúncio, RealEstateListing (url, name, description, datePosted, um array de imagens, offers com um Offer: price, priceCurrency "TRY", availability e o @id do negócio como seller) com um Apartment ou SingleFamilyResidence como mainEntity (numberOfRooms, numberOfBedrooms, numberOfBathroomsTotal, floorSize como QuantitativeValue com unitCode "MTK", yearBuilt e um address apenas com região, província e país). O Google não mostra um rich result específico para esses tipos; o objetivo é que os mecanismos de busca e de IA leiam corretamente o preço, o número de quartos e a localização. CollectionPage e ItemList na página de anúncios, businessType "RealEstateAgent" no seo-stamp para a imobiliária, BreadcrumbList em todas as páginas internas. Use preço, metros quadrados, taxa de condomínio e dados da escritura somente a partir da declaração escrita do proprietário; deixe de fora, tanto do JSON quanto do schema, qualquer coisa que você não saiba.
Configuração e armadilhas conhecidas (cada uma custou pontos nas medições da demo, não pule nenhuma):
1. Se já houver um site no ar no servidor, avise-me primeiro, porque a configuração sobrescreve o index.html e a pasta assets. Faça a prévia do scaffold_site com confirm=false e chame-o com confirm=true somente depois da minha aprovação. Depois leia site_design_guide e site_seo_guide. Antes de escrever código, coloque a direção de design (tipografia, cores, grade, elemento marcante, linguagem de movimento e as bibliotecas que NÃO serão carregadas) em um pequeno arquivo ART.md fora de assets e mostre para mim.
2. Compile o Tailwind, não o carregue pela CDN. Instale-o com site_run_command e background=true (`npm i -D tailwindcss@3`) e acompanhe com site_command_status. Os caminhos de content em `tailwind.config.js` devem cobrir todos os arquivos HTML, os templates de `_src` e qualquer arquivo JS que escreva classes. O `_src/tw.css` contém a linha `@import "../assets/app.css";` mais as três linhas `@tailwind`; compile-o em um único arquivo com `npx tailwindcss -c tailwind.config.js -i _src/tw.css -o assets/site.css --minify`. Remova o script cdn.tailwindcss.com e o tailwind.config inline de todas as páginas e vincule apenas `/assets/site.css`. Recompile após cada alteração no HTML.
3. Hospede as fontes você mesmo. Leia o CSS da família Fontshare escolhida, baixe o woff2 de cada peso separadamente com site_fetch em `assets/fonts/` e escreva as regras @font-face com `font-display: swap` em `assets/app.css`, que é onde o design-check procura uma fonte personalizada. Confira o nome do font-family no CSS baixado e confirme que a família tem as letras turcas ı, İ, ş e ğ (a Tanker não tem ı). Faça preload de TODOS os pesos usados na primeira tela com `<link rel="preload" as="font" type="font/woff2" crossorigin>`, colocado antes da linha do stylesheet; se forem mais de três arquivos, use menos pesos. Na demo, um único peso de texto sem preload levou o LCP mobile a 2,6 segundos e a Performance a 97. A maioria das fontes da Fontshare não tem o símbolo ₺, então escreva os preços com "TL".
4. Faça o menu e o rodapé em HTML estático. Não carregue chrome.js, motion.js nem site.config.js nas páginas. Escreva um pequeno `chrome-stamp.mjs` que leia a lista de navegação de site.config.js e preencha `_src/header.tpl` e `_src/footer.tpl` entre os marcadores `<!-- nav:start --><!-- nav:end -->` e `<!-- footer:start --><!-- footer:end -->` em todas as páginas, adicionando aria-current="page" ao link ativo. Use placeholders como `[[NAV]]` em vez de `{{...}}` e não dê aos templates a extensão .html, senão o design-check e o seo-check os tratam como páginas inacabadas. Construa o menu mobile com um `<details>` sem JS. Mantenha o nome da empresa, o endereço e o telefone como texto simples no rodapé e faça os links do rodapé e do breadcrumb terem pelo menos 44 pixels de altura.
5. Converta as imagens ao buscá-las com site_fetch: grave a mesma imagem em arquivos separados com resize="480x", "800x" e "1200x", image_format="webp", quality=60 (o site_fetch não recorta, então peça a origem na proporção de que você precisa). Dê a cada `<img>` srcset, sizes e width e height compatíveis com a proporção real. A imagem que é o elemento LCP da página recebe fetchpriority="high" e não é lazy; todas as outras imagens recebem loading="lazy" decoding="async". Se uma imagem preencher a primeira tela do mobile, sirva um recorte menor para mobile com `<picture>`.
6. Adicione `<link rel="icon" href="/favicon.svg" type="image/svg+xml">` a todas as páginas e crie um apple-touch-icon de 180 pixels a partir do logo com site_fetch e resize="180x180". Sem o link do favicon, o navegador solicita /favicon.ico, registra um 404 e as Boas Práticas caem para 96.
7. Regras do validador de HTML: comece cada documento com `<!DOCTYPE html>` em maiúsculas; todo espaço no texto visível de um link tel: deve ser ` `, inclusive o que vem depois de um prefixo como "Ligue: 0212..."; não use style=""; dê um type a todo botão; coloque aria-label apenas em landmarks, formulários ou elementos interativos (em um ul ou em uma div sem role foi erro); nunca dê o mesmo nome a dois landmarks; não deixe espaços em branco no fim das linhas.
8. Não pule níveis de título: em uma página de lista, títulos de card como h3 logo depois do h1 derrubaram a Acessibilidade para 98. Ao reutilizar um card, defina o nível do título por página (h2 em uma página de lista, h3 dentro de uma seção h2).
9. Dê ao atributo de movimento o nome data-reveal (o design-check só reconhece movimento por esse nome). O JS aplica o estado inicial oculto apenas aos blocos que ficam abaixo da tela quando a página abre e nunca oculta o conteúdo da primeira tela; use transform, porque desvanecer com opacity reduz a auditoria de contraste. Coloque o atributo no bloco que envolve um título e sua descrição, não apenas no título; na demo, um título em espera deslizou por cima do parágrafo abaixo. Com prefers-reduced-motion: reduce nada é ocultado e as animações ficam desativadas.
SEO, AEO e GEO:
1. Use URLs em estilo de diretório com barra final. Preencha o bloco seo de site.config.js e execute `node seo-stamp.mjs`. Escreva o schema específico de cada página em blocos `<script type="application/ld+json">` separados, ligados ao grafo base por @id. Não aninhe o BreadcrumbList dentro do @graph; mantenha-o em um bloco próprio, que é a única forma de o seo-check enxergá-lo.
2. Títulos com no máximo 60 caracteres (o seo-check marca como erro qualquer um com menos de 25 ou mais de 65), descrições de 130 a 158 caracteres, um h1 por página e pelo menos 150 palavras de conteúdo principal em cada página. As páginas de contato, privacidade e lista tendem a ficar curtas; adicione informações específicas da página, não enchimento.
3. O primeiro parágrafo de cada página responde diretamente à pergunta da página. Escreva FAQs específicas de cada página com `<details>` e marque-as como FAQPage. Mostre o publisher e uma data de "Última atualização" em um elemento `<time>`.
4. No robots.txt, escreva sua decisão, com o motivo, para GPTBot, OAI-SearchBot, ChatGPT-User, ClaudeBot, Claude-User, Claude-SearchBot, PerplexityBot, Perplexity-User, Google-Extended, Applebot-Extended, Bingbot e CCBot, e aponte para o sitemap. O sitemap.xml traz datas lastmod reais. O llms.txt contém um resumo preciso do site em duas frases e uma lista das páginas importantes.
5. Sem invenções: não escreva avaliações, notas, prêmios, números de clientes, certificados, horários de funcionamento, coordenadas nem preços sem me perguntar. Deixe qualquer campo que você não conheça totalmente fora do schema.
Portões de qualidade (em ordem; não diga "pronto" até que todos passem):
1. Com site_run_command, execute `node chrome-stamp.mjs`, o build do Tailwind, `node seo-stamp.mjs`, `node seo-check.mjs` (0 erros, 0 avisos), `node design-check.mjs` (0 erros; justifique qualquer aviso restante) e `npx html-validate "**/*.html"` (0 erros).
2. Veja cada página com site_preview, view="mobile" e view="desktop". O site_preview captura a página com movimento desativado (reduced-motion); se faltar conteúdo nessa captura, o estado estático está quebrado. Em capturas de página inteira, elementos fixos como uma barra sticky podem aparecer no meio da página e imagens lazy mais abaixo podem aparecer em branco; não conte isso como erro, mas confira os caminhos delas.
3. Execute core_web_vitals para cada página com strategy="mobile" e strategy="desktop". Critério de aceitação: no mobile, Performance de pelo menos 95 (meta de 99 a 100) e Acessibilidade, Boas Práticas e SEO em 100; no desktop, as quatro em 100. Para uma página que não atingir, use resources=true para encontrar o arquivo que a atrasa, corrija e meça de novo. Depois execute seo_audit na página inicial com strategy="mobile" e runs=2. Nenhum endereço fora do seu próprio domínio pode restar na lista de requisições de rede.
4. Sem rolagem horizontal em 360, 390, 768 e 1440 pixels de largura, alvos de toque de pelo menos 44 pixels e nenhum erro no console.
Verificação de interação: o site_preview não consegue operar o filtro. Diga-me como verificar que abrir /ilanlar/?ilce=[bairro]&oda=3%2B1 mostra apenas os anúncios correspondentes e preenche o formulário, que alterar um select atualiza a barra de endereço, que a mensagem de estado vazio aparece quando nada corresponde, que todos os anúncios são exibidos com JavaScript desativado e que clicar em uma miniatura na página de um anúncio rola apenas a faixa de fotos sem fazer a página pular.
Depois de publicar: faça a prévia da regra `/assets/* Cache-Control: public, max-age=31536000, immutable` com site_headers e aplique-a quando eu aprovar; a partir daí, versione o nome do arquivo sempre que o CSS ou o JS mudar (site-v2.css e assim por diante). Envie o sitemap.xml com gsc_submit_sitemap e verifique a página inicial com gsc_url_inspect. No final, entregue-me: uma tabela de notas por página (página, as quatro notas mobile, as quatro notas desktop, LCP, CLS), o resultado de cada portão e a lista das correções aplicadas.