/* Rede de Parceiros — tema base. Sem framework, sem build.
 *
 * CLARO é o padrão: o corretor usa isto de dia, na rua, no celular, e tela
 * escura sob sol é o pior cenário de leitura. O escuro fica como opção, no
 * botão do cabeçalho, e é lembrado no `localStorage`.
 *
 * Toda cor do produto passa por estas variáveis — trocar de tema é trocar o
 * bloco, e nenhuma regra abaixo precisa saber em qual tema está. As duas
 * exceções (texto sobre botão e sobre o pill vermelho) também são variáveis,
 * porque texto escuro sobre verde-escuro fica ilegível no claro.
 */
:root {
  --bg: #f4f6f8;         /* fundo da página */
  --bg2: #ffffff;        /* cartões */
  --bg3: #eceff3;        /* campos, KPIs, barras */
  --line: #dbe1e8;
  --fg: #12181f;
  --muted: #5a6673;      /* 4.9:1 sobre o cartão branco */
  --teal: #0f766e;       /* acento de texto: preço, link */
  --teal-dark: #0f766e;  /* fundo de botão */
  --btn-fg: #ffffff;     /* texto sobre botão */
  --btn-hover: #115e59;
  --amber: #b45309;
  --red: #dc2626;
  --hot-fg: #ffffff;     /* texto sobre o pill vermelho cheio */
  --green: #047857;
  --radius: 10px;
  --radius-sm: 8px;
  /* Escala de espaçamento. Nasceu com a home densa: até aqui só havia tokens de
     cor, e todo respiro era um número solto no CSS ou um `style=` no JS (eram
     137 deles). Sem escala, "compacto" vira opinião de cada bloco e a tela
     perde o ritmo. Os degraus são os que já apareciam de fato no arquivo. */
  --esp-1: 4px;
  --esp-2: 8px;
  --esp-3: 14px;
  --esp-4: 18px;
  --esp-5: 24px;
  /* Altura da faixa da simulação "ver como corretor". Vira token porque QUATRO
     regras precisam concordar com ela: o `padding-top` do body, o `top` do
     cabeçalho sticky, o `scroll-margin-top` da grade e o `top` do painel do
     mapa. Enquanto o 44 vivia escrito num lugar só, as outras três não sabiam
     que a faixa existia — foi exatamente o KI-056.

     ⚠️ Este valor é só o PONTO DE PARTIDA, e ele está errado de propósito na
     folha: a faixa mede 54px (`padding: 9px` + `button.small{min-height:36px}`)
     e chega a ~100px no celular, quando o texto quebra e o botão desce. Nenhum
     número escrito à mão sobrevive a isso.

     Quem manda é `medirFaixa()` em `web/app.js`: ele lê a altura real e
     sobrescreve este token no `:root`, com `ResizeObserver` para acompanhar giro
     de tela e mudança de fonte. A primeira correção do KI-056 chutou 44 aqui, e
     o guarda do CSS não podia pegar — ele verifica que as regras USAM o token,
     nunca que o VALOR confere. Achado pela auditoria independente de 07/08. */
  --faixa-alt: 44px;
  /* Distância do topo para o conteúdo que precisa parar ABAIXO do cabeçalho
     sticky (~64px + respiro). Ganha a faixa quando a simulação está ligada. */
  --topo-fixo: 84px;
  /* Sombra também é cor, e por isso é token: no claro ela precisa ser discreta,
     no escuro precisa ser mais funda para o cartão descolar do fundo. */
  --sombra-1: 0 1px 3px rgba(0, 0, 0, .06);
  --sombra-rolagem: rgba(0, 0, 0, .12);
  /* ── sobre a FOTO (o card do Descobrir, 14/08) ───────────────────────────
     Estes três NÃO trocam entre claro e escuro, e a exceção é deliberada: o que
     está atrás deles não é o tema, é uma FOTOGRAFIA. Um véu que clareasse no
     tema claro deixaria o preço ilegível sobre foto de fachada branca, que é
     boa parte do estoque.

     São tokens mesmo assim, porque a razão da regra do topo continua valendo:
     quem for ajustar o contraste do card precisa achar o número num lugar só.
     Não terem par no bloco do escuro é o que os declara iguais nos dois temas —
     de propósito, e não por esquecimento. */
  --veu-forte: rgba(6, 10, 15, .88);
  /* ⚠️ 0.62 é MEDIDO, não escolhido. É o véu na altura do PREÇO, e ele precisa
     aguentar o pior fundo real do estoque: fachada branca. Sobre branco puro
     ele compõe ~rgb(103,105,108) e dá **5,6:1** com texto branco — acima do 4,5
     que a WCAG AA pede.

     A primeira versão usava 0.55 num degradê cujo efeito NA ALTURA DO PREÇO era
     de apenas 0.205 de véu: **1,62:1**, ou seja, o preço praticamente sumia
     sobre foto clara. Foi o achado ALTO da auditoria de promoção de 14/08 — e
     era um defeito pior que os 40% de branco que este card veio corrigir. */
  --veu-meio: rgba(6, 10, 15, .62);
  --veu-nulo: rgba(6, 10, 15, 0);
  --sobre-foto: #ffffff;
  --sobre-foto-fraco: rgba(255, 255, 255, .82);
  /* A barrinha APAGADA do carrossel do baralho — a acesa é `--sobre-foto`.
     Sem par no bloco do escuro pelo mesmo motivo dos véus acima, e não por
     esquecimento: ela vive sobre FOTO, e foto não tem tema. */
  --sobre-foto-apagado: rgba(255, 255, 255, .38);
  color-scheme: light;   /* select, scrollbar e caret nativos acompanham */
}

:root[data-tema="escuro"] {
  --bg: #0e1116;
  --bg2: #161b22;
  --bg3: #1c232c;
  --line: #2a323d;
  --fg: #e6edf3;
  --muted: #8b949e;
  --teal: #2dd4bf;
  --teal-dark: #0d9488;
  --btn-fg: #04231f;
  --btn-hover: #2dd4bf;
  --amber: #f59e0b;
  --red: #f87171;
  --hot-fg: #2b0a0a;
  --green: #34d399;
  --sombra-1: 0 1px 3px rgba(0, 0, 0, .5);
  /* mais forte porque `--bg2` já é quase preto: a 12% não se enxerga */
  --sombra-rolagem: rgba(0, 0, 0, .45);
  color-scheme: dark;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font: 15px/1.55 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

a { color: var(--teal); }

header.top {
  display: flex; align-items: center; gap: 12px;
  padding: 14px 20px;
  border-bottom: 1px solid var(--line);
  background: var(--bg2);
  position: sticky; top: 0; z-index: 10;
}
header.top h1 { font-size: 17px; margin: 0; font-weight: 600; letter-spacing: .2px; }
header.top .grow { flex: 1; }
header.top .who { color: var(--muted); font-size: 13px; }

main { max-width: 980px; margin: 0 auto; padding: 24px 20px 64px; }

.card {
  background: var(--bg2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 22px;
  margin-bottom: 18px;
}
.card h2 { margin: 0 0 6px; font-size: 18px; }
.card .sub { color: var(--muted); margin: 0 0 18px; font-size: 14px; }

.muted { color: var(--muted); }
.right { text-align: right; }

label { display: block; margin: 14px 0 6px; font-size: 13px; color: var(--muted); }

input[type=text], input[type=tel], select, textarea {
  width: 100%;
  padding: 10px 12px;
  background: var(--bg3);
  border: 1px solid var(--line);
  border-radius: 8px;
  color: var(--fg);
  /* 16px no mobile mata o auto-zoom do iOS ao focar o campo */
  font-size: 16px;
  font-family: inherit;
}
input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--teal-dark);
}

.row { display: flex; gap: 14px; flex-wrap: wrap; }
.row > * { flex: 1 1 220px; }

.check {
  display: flex; gap: 10px; align-items: flex-start;
  margin: 20px 0 4px; font-size: 14px; color: var(--fg);
}
.check input { margin-top: 3px; width: 18px; height: 18px; flex: none; }

button {
  background: var(--teal-dark);
  color: var(--btn-fg);
  border: 0;
  border-radius: 8px;
  padding: 11px 18px;
  font-size: 15px; font-weight: 600;
  cursor: pointer;
  /* alvo de toque ≥44px */
  min-height: 44px;
}
button:hover { background: var(--btn-hover); }
button:disabled { background: var(--line); color: var(--muted); cursor: not-allowed; }
button.ghost { background: transparent; border: 1px solid var(--line); color: var(--fg); }
button.ghost:hover { border-color: var(--teal-dark); }
button.small { padding: 7px 12px; font-size: 13px; min-height: 36px; }
button.danger { background: transparent; border: 1px solid var(--red); color: var(--red); }

.pill {
  display: inline-block; padding: 3px 10px; border-radius: 999px;
  font-size: 12px; font-weight: 600; letter-spacing: .2px;
}
.pill.warn { background: rgba(245,158,11,.15); color: var(--amber); }
.pill.ok { background: rgba(52,211,153,.15); color: var(--green); }
.pill.off { background: rgba(139,148,158,.15); color: var(--muted); }
.pill.bad { background: rgba(248,113,113,.15); color: var(--red); }
/* Situação comercial: o peso visual cresce com o funil, porque é o que decide
   se o corretor corre atrás. "negociação avançada" tem que saltar da grade. */
.pill.info { background: rgba(45,212,191,.15); color: var(--teal); }
.pill.hot  { background: var(--red); color: var(--hot-fg); }
.pill.big  { font-size: 14px; padding: 6px 14px; }

.tabs { display: flex; gap: 6px; margin-bottom: 18px; flex-wrap: wrap; }
.tabs button { background: transparent; border: 1px solid var(--line); color: var(--muted); }
.tabs button.on { background: var(--bg3); color: var(--fg); border-color: var(--teal-dark); }

/* ── ícones ─────────────────────────────────────────────────────────────────
   Um sprite `<symbol>` inline no index.html; aqui só a forma. `currentColor`
   no traço é o que faz o mesmo ícone servir aos dois temas, ao botão ligado e
   ao texto apagado sem nenhuma segunda cópia. */
.ico {
  width: 16px; height: 16px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.8;
  stroke-linecap: round; stroke-linejoin: round;
  vertical-align: -.15em;
}
/* Ícone MAIS rótulo. O ícone é reconhecimento; o texto é a garantia. */
.tabs.menu button { display: inline-flex; align-items: center; gap: 6px; }

/* Rótulo só para leitor de tela — o campo de busca tem lupa e placeholder, mas
   placeholder não é rótulo: ele some ao digitar e nem todo leitor o anuncia. */
.sr {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; border: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

/* ── busca e filtros da vitrine ─────────────────────────────────────────────
   A busca sobe para a linha inteira: é o que o corretor mais usa e estava
   espremida na quarta coluna de uma `.row`. A lupa e o campo vivem dentro da
   mesma moldura, e o foco acende a moldura toda. */
.busca-linha {
  display: flex; align-items: center; gap: 8px;
  background: var(--bg3); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 0 12px; margin-bottom: 12px;
}
.busca-linha .ico { width: 18px; height: 18px; color: var(--muted); }
.busca-linha input[type=text] {
  border: 0; background: transparent; padding: 12px 0; border-radius: 0;
}
.busca-linha input[type=text]:focus { border-color: transparent; }
.busca-linha:focus-within { border-color: var(--teal-dark); }

/* Faixa da simulação "ver como corretor". Fixa no topo e sem como perder de
   vista: um modo esquecido ligado faria a Total concluir que perdeu acesso ao
   próprio sistema. O `padding-top` no body evita que ela cubra a primeira aba. */
.faixa-simulacao {
  position: fixed; inset: 0 0 auto 0; z-index: 50;
  display: flex; gap: var(--esp-3); align-items: center; justify-content: center;
  flex-wrap: wrap; padding: 9px var(--esp-3);
  background: var(--teal-dark); color: #fff; font-size: 13px;
  box-shadow: var(--sombra-1);
}
.faixa-simulacao button { background: transparent; color: #fff; border-color: #fff; }
.faixa-simulacao button:hover { background: rgba(255, 255, 255, .14); }
body.simulando { padding-top: var(--faixa-alt); }

/* KI-056 — o cabeçalho precisa saber que a faixa existe.
   `header.top` é `position: sticky; top: 0`, e o `top` do sticky é medido contra
   a viewport, não contra o `padding-top` do body. Sem esta regra o padding só
   empurra o conteúdo enquanto a página está no início: assim que rola ~44px o
   cabeçalho gruda em 0, POR BAIXO da faixa (que é `fixed`, z-index 50), e leva
   junto o botão de tema e o "Sair" — no modo simulação, os dois controles que
   fazem a Total voltar a ser ela mesma. */
body.simulando header.top { top: var(--faixa-alt); }
body.simulando #grade { scroll-margin-top: calc(var(--topo-fixo) + var(--faixa-alt)); }
body.simulando .resultados.com-mapa .mapa-painel { top: calc(var(--topo-fixo) + var(--faixa-alt)); }

/* "O que seu cliente procura?" — o campo em linguagem natural da home.
   No celular o botão desce e ocupa a linha inteira: um botão de 90px ao lado de
   um campo de 200px espreme os dois e o corretor usa isto na rua. */
.busca-cliente { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.busca-cliente input[type=text] { flex: 1 1 260px; min-width: 0; }
.busca-cliente button { flex: 0 0 auto; }
/* As alternativas ("cedendo uma coisa"): empilhadas, e não em linha. Cada uma é
   uma decisão sobre o dinheiro do cliente, e lado a lado elas viram um menu que
   se lê de relance. */
.bc-opcoes { display: flex; flex-direction: column; align-items: flex-start;
             gap: var(--esp-2); margin-top: var(--esp-2); }
.bc-opcoes button { text-align: left; }

/* Linha do resumo de filtros: o <details> à esquerda, "Limpar filtros" à
   direita — só aparece com algum filtro ativo (o botão nasce `hidden`).
   `align-items: flex-start` mantém o botão colado na linha do resumo mesmo
   quando o <details> abre e cresce por baixo dele. */
.filtros-linha {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 12px; flex-wrap: wrap;
}
.filtros-linha .filtros { flex: 1 1 auto; min-width: 160px; }

/* O resto dos filtros num <details> nativo: sem JS de animação, e teclado e
   leitor de tela funcionam de graça. O <summary> conta quantos estão em vigor,
   porque filtro escondido que muda o resultado em silêncio é pior que filtro
   feio — o corretor concluiria que a rede tem menos imóvel do que tem. */
.filtros > summary {
  cursor: pointer; list-style: none;
  display: flex; align-items: center; gap: 8px;
  color: var(--muted); font-size: 14px; padding: 6px 0;
}
.filtros > summary::-webkit-details-marker { display: none; }
.filtros > summary::before {
  content: "›"; font-size: 18px; line-height: 1;
  transition: transform .12s; display: inline-block;
}
.filtros[open] > summary::before { transform: rotate(90deg); }
.filtros > summary:focus-visible { outline: 2px solid var(--teal-dark); outline-offset: 2px; }
/* Dentro do painel os campos já vêm rotulados; a margem de topo do <label>
   dobraria o respiro logo abaixo do resumo. */
.filtros .row label:first-child { margin-top: 0; }

.tbl-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
table { width: 100%; border-collapse: collapse; font-size: 14px; }
th, td { text-align: left; padding: 10px 12px; border-bottom: 1px solid var(--line); white-space: nowrap; }
th { color: var(--muted); font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: .4px; }

.msg {
  position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%);
  background: var(--bg3); border: 1px solid var(--teal-dark);
  padding: 10px 18px; border-radius: 8px; font-size: 14px;
  max-width: 90vw; z-index: 50;
}

.empty { text-align: center; color: var(--muted); padding: 40px 12px; }

/* Aviso que precisa ser LIDO, não decorado: "marcar interesse não reserva o
   imóvel" é a regra que evita a briga de que ninguém quer participar. */
.aviso {
  margin: 18px 0 0;
  padding: 10px 14px;
  border-left: 3px solid var(--amber);
  background: rgba(245,158,11,.08);
  border-radius: 0 8px 8px 0;
  font-size: 13px; color: var(--fg);
}

/* ── linha do tempo do imóvel ───────────────────────────────────────────── */
.timeline { list-style: none; margin: 16px 0 0; padding: 0; }
.timeline .tl {
  padding: 10px 0 10px 14px;
  border-left: 2px solid var(--line);
  font-size: 14px;
}
.timeline .tl:hover { border-left-color: var(--teal-dark); }
.timeline .tl > span, .timeline .tl > strong { margin-right: 8px; }
.timeline .tl .muted { font-size: 12px; }
.tl-nota { color: var(--muted); font-size: 13px; margin-top: 4px; white-space: pre-wrap; }

@media (max-width: 600px) {
  main { padding: 16px 14px 48px; }
  .card { padding: 16px; }
  header.top h1 { font-size: 15px; }
  header.top .who { display: none; }
}

/* ── painel de início ───────────────────────────────────────────────────── */
/* O componente `.kpi*` viveu aqui e saiu em 07/08, junto com a faxina que fechou
   o KI-052: `kpi()` tinha sido apagada do JS e o CSS ficou sem consumidor. O que
   ocupa este lugar hoje é `.tile*`, mais abaixo. */

/* Uma linha clicável de lista — mais densa que um card, para o painel caber. */
.linha {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 10px 0; border-bottom: 1px solid var(--line);
  text-decoration: none; color: inherit; font-size: 14px;
}
.linha:last-child { border-bottom: 0; }
.linha:hover .linha-tit { color: var(--teal); }
.linha-tit { font-weight: 600; flex: 1 1 200px; }
.linha-preco { font-weight: 600; color: var(--teal); }

.barra { display: block; height: 8px; background: var(--bg3); border-radius: 4px; overflow: hidden; }
.barra > span { display: block; height: 100%; background: var(--teal-dark); }

/* ── home densa ─────────────────────────────────────────────────────────────
   A home era sete cartões de largura inteira, 5 a 8 alturas de tela. Cinco
   deles dependiam de atividade que ainda não existe (0 interesses, 0 negócios),
   então quem entrasse via uma coluna de estados vazios e, no fim, uma tabela de
   900px.

   A regra que este bloco implementa: **espaço proporcional ao conteúdo**. Bloco
   cheio ganha área; bloco vazio encolhe para uma linha com convite (`.vazio`),
   nunca um cartão inteiro para dizer "nada aqui". */

/* `align-items: start` é o que impede o bloco curto de esticar até a altura do
   vizinho alto. Sem ele, "Entrou na rede" ao lado de uma lista de 7 rascunhos
   ganhava 260px de cartão vazio embaixo do último item — um buraco que parece
   defeito de carregamento. */
.grade-home { display: grid; grid-template-columns: repeat(12, 1fr);
              gap: var(--esp-4); align-items: start; }
.grade-home > .col-4 { grid-column: span 4; }
.grade-home > .col-5 { grid-column: span 5; }
.grade-home > .col-6 { grid-column: span 6; }
.grade-home > .col-7 { grid-column: span 7; }
.grade-home > .col-8 { grid-column: span 8; }
.grade-home > .col-12 { grid-column: span 12; }
/* 760px e não 600: abaixo disso duas colunas de ~340px espremem a lista de
   imóveis (título + bairro + pill não cabem numa linha e cada item vira três). */
@media (max-width: 760px) {
  .grade-home { gap: var(--esp-3); }
  .grade-home > [class*="col-"] { grid-column: span 12; }
}

/* Container de célula da grade: é o `.card`, sem a margem de baixo — quem separa
   agora é o `gap`. Somar os dois deixaria buraco desigual entre linhas. */
.bloco {
  background: var(--bg2); border: 1px solid var(--line);
  border-radius: var(--radius); padding: var(--esp-4);
  display: flex; flex-direction: column;
}

/* Cabeçalho de seção: título à esquerda, ação à direita. Substitui o `<h2>` +
   `.sub` do cartão quando a seção não precisa de um parágrafo de explicação. */
.secao { display: flex; align-items: baseline; gap: var(--esp-3);
         margin-bottom: var(--esp-3); }
.secao h2 { margin: 0; font-size: 16px; flex: 1; }
.secao .conta { color: var(--muted); font-weight: 400; font-size: 14px; }
.secao .acao { font-size: 13px; white-space: nowrap; }
/* O subtítulo pertence ao cabeçalho, não ao corpo. Sem isto ele herda o respiro
   de `.secao` MAIS o próprio, e a frase que explica o bloco flutua no meio do
   caminho entre o título que ela explica e o conteúdo que ela introduz. */
.secao + .sub { margin-top: -10px; }

/* Tiles de métrica. Menores que o `.kpi` que eles substituíram (26px, removido
   do CSS em 07/08 por não ter mais consumidor) de propósito: na home nova são
   cinco na mesma faixa, e 26px cinco vezes vira um painel de aeroporto. */
.tiles { display: grid; gap: var(--esp-2);
         grid-template-columns: repeat(auto-fit, minmax(118px, 1fr)); }
.tile { background: var(--bg3); border: 1px solid var(--line);
        border-radius: var(--radius-sm); padding: var(--esp-2) var(--esp-3); }
.tile-n { font-size: 21px; font-weight: 700; color: var(--teal); line-height: 1.15; }
/* Valor longo (dinheiro) num tile estreito. Sem isto, "R$ 435,2 mi" quebrava em
   três linhas e aquele tile ficava mais alto que a faixa inteira — o print
   pegou, nenhum teste pegaria. Quem decide é `componentes.js`, pelo COMPRIMENTO
   do texto e não pelo tipo do dado: assim qualquer valor longo futuro já nasce
   coberto sem ninguém lembrar de passar uma classe. */
.tile-n.longo { font-size: 15px; }
/* Número ausente não é zero. `—` em cinza diz "não sei"; um 0 em teal diz
   "medi e deu zero", e as duas coisas não podem ter a mesma cara. */
.tile-n.vazia { color: var(--muted); font-weight: 600; }
.tile-r { font-size: 12px; margin-top: 1px; }
.tile-nota { font-size: 11px; color: var(--muted); margin-top: var(--esp-1); }

/* Card-tarefa: contagem + o que fazer + seta. Some quando a contagem é zero —
   uma fila vazia não é uma tarefa, e mostrar "0 aguardando" todo dia ensina o
   olho a pular a faixa inteira. */
.taskcards { display: grid; gap: var(--esp-2);
             grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
.taskcard {
  display: block; text-align: left; width: 100%;
  background: var(--bg3); color: inherit; text-decoration: none;
  border: 1px solid var(--line); border-left: 3px solid var(--teal-dark);
  border-radius: var(--radius-sm); padding: var(--esp-3);
  font: inherit; cursor: pointer; min-height: 0;
}
.taskcard:hover { border-color: var(--teal-dark); background: var(--bg2); }
.taskcard.urgente { border-left-color: var(--amber); }
.taskcard-n { font-size: 21px; font-weight: 700; line-height: 1.15; }
.taskcard-r { font-size: 13px; margin-top: 2px; }
.taskcard-ir { font-size: 12px; color: var(--teal); margin-top: var(--esp-2); }

/* Linha de lista ainda mais apertada que a `.linha`, para caber numa meia
   coluna sem virar rolagem. */
.linha.densa { padding: 7px 0; font-size: 13px; gap: var(--esp-2); }
.linha.densa .linha-tit { flex: 1 1 140px; }

/* Estado vazio de UMA LINHA, com o convite junto. É o coração da regra: hoje o
   funil, os rascunhos e os "quentes" voltam vazios de verdade, e três cartões
   inteiros dizendo "nada aqui" era metade da home. */
.vazio { font-size: 13px; color: var(--muted); margin: 0; }
.vazio a, .vazio .link { color: var(--teal); }

/* ── feed de acontecimentos ─────────────────────────────────────────────────
   Fatos agrupados, nunca um item por imóvel: a importação da Total foi de 732
   de uma vez, e um post por imóvel viraria uma parede que esconde o resto. */
.feed { display: flex; flex-direction: column; }
.feed-item {
  display: grid; grid-template-columns: 20px 1fr auto; gap: var(--esp-2);
  align-items: baseline;
  padding: var(--esp-2) 0; border-bottom: 1px solid var(--line);
  font-size: 13px; text-decoration: none; color: inherit;
}
.feed-item:last-child { border-bottom: 0; }
.feed-item:hover .feed-txt { color: var(--teal); }
.feed-ico { color: var(--muted); justify-self: center; }
.feed-quando { color: var(--muted); font-size: 11px; white-space: nowrap; }
.feed-txt strong { font-weight: 600; }
/* "Para você" é o único bloco do feed que fala do corretor, então é o único que
   ganha fundo. Sem isso ele se perde no meio dos fatos da rede. */
.feed-voce { background: var(--bg3); border: 1px solid var(--line);
             border-radius: var(--radius-sm); padding: var(--esp-3);
             margin-top: var(--esp-3); font-size: 13px; }

/* ── oportunidades ("vale a pena olhar hoje") ───────────────────────────────
   Card compacto e SEM capa: aqui o argumento é o número, não a foto — e 733 dos
   735 publicados não têm foto nenhuma. A capa entraria como ilustração de tipo
   repetida quatro vezes, que é ruído com cara de conteúdo. */
.oport-grade { display: grid; gap: var(--esp-2);
               grid-template-columns: repeat(auto-fit, minmax(148px, 1fr)); }
.oport {
  display: flex; flex-direction: column; gap: 2px;
  background: var(--bg3); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: var(--esp-3);
  text-decoration: none; color: inherit;
}
.oport:hover { border-color: var(--teal-dark); }
.oport:focus-visible { outline: 2px solid var(--teal-dark); outline-offset: 2px; }
/* Sobrou para o "-22% abaixo" da faixa de oportunidades, que é percentual e não
   quantia — as quantias passaram todas para `.oport-q` no KI-049. */
.oport-dif { font-size: 17px; font-weight: 700; color: var(--green); line-height: 1.1; }
.oport-loc { font-size: 12px; color: var(--muted); }
/* Uma quantia com NOME (KI-049): rótulo em cima, valor embaixo, proporção
   depois. Sem os rótulos o card mostrava duas quantias empilhadas e ninguém
   sabia qual era a comissão e qual o preço — nem se era mil ou milhão. */
.oport-q { display: block; margin-bottom: var(--esp-2); }
.oport-rot {
  display: block; font-size: 10px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted); font-weight: 600; line-height: 1.4;
}
/* ⚠️ `nowrap` no VALOR. Sem isto "R$ 7 milhões" quebrava entre o número e a
   unidade num card de 150px, e a linha de baixo ficava só com "milhões" — a
   mesma dúvida "mil ou milhão" que os rótulos vieram resolver. */
.oport-val { display: block; font-size: 15px; font-weight: 600; line-height: 1.2;
             white-space: nowrap; }
/* `forte` é TAMANHO, nunca cor: quem manda na faixa fica maior. */
.oport-q.forte .oport-val { font-size: 18px; font-weight: 700; }
/* O verde é reservado ao dinheiro DO CORRETOR. Pintar um preço de verde faria
   a tela prometer que aquilo é dele. */
.oport-q.seu .oport-val { color: var(--green); }
/* "metade da comissão" — a proporção, fora do número. Antes vinha colada
   ("R$ 210.000 (metade)") e no celular quebrava para a linha de baixo,
   parecendo uma segunda quantia. */
.oport-nota { font-size: 11px; color: var(--muted); line-height: 1.3; }
.oport-parte { font-size: 12px; color: var(--muted); margin-top: var(--esp-1);
               border-top: 1px solid var(--line); padding-top: var(--esp-1); }

/* ── saúde do estoque ("onde atacar") ───────────────────────────────────────
   Uma linha por lacuna, ordenada pela magnitude no JS — não por uma ordem fixa
   aqui. Depois do sync do CRM 49, "sem foto" deixa de ser o maior buraco, e a
   lista precisa parar de gritar foto sozinha. */
.saude { display: flex; flex-direction: column; }
.saude-linha {
  display: grid; grid-template-columns: minmax(96px, 1.1fr) 2fr auto;
  gap: var(--esp-3); align-items: center;
  padding: var(--esp-2) 0; border-bottom: 1px solid var(--line);
  font-size: 13px; text-decoration: none; color: inherit;
}
.saude-linha:last-child { border-bottom: 0; }
.saude-linha:hover .saude-rot { color: var(--teal); }
.saude-n { font-weight: 600; white-space: nowrap; }
.saude-linha .barra > span { background: var(--amber); }
.saude-linha.leve .barra > span { background: var(--muted); }

/* O `th` do produto é caixa-alta, e isso está certo para CABEÇALHO de coluna.
   Mas o panorama usa `th` também como cabeçalho de LINHA — é o nome da cidade —
   e ali a caixa-alta grita ("CORONEL FABRICIANO") e ainda por cima é uma
   armadilha de smoke: `innerText` devolve o texto renderizado, então a asserção
   passa a comparar com maiúscula de verdade. */
.pan-linha th { text-transform: none; font-size: 13px; color: var(--fg); }

/* ── gráficos à mão (SVG inline, sem biblioteca) ────────────────────────────
   `CLAUDE.md §4` proíbe build e npm, e um histograma não paga quebrar isso. O
   produto já desenha barra (`.barra`) e faixa de preço (`.faixa-barra`); esta é
   a terceira peça da mesma família.

   A cor vem de `currentColor` no SVG, não de `corToken()` como no mapa: aqui o
   desenho está DENTRO do documento e herda a cor por CSS, então o tema escuro
   acompanha sozinho. O mapa precisa do JS porque o Leaflet desenha em canvas,
   fora do alcance da cascata. */
/* A ALTURA PRECISA SER FIXA. Com `height: auto` e `preserveAspectRatio="none"`
   o navegador deduz a altura da razão do `viewBox` (100:36), e num container de
   920px isso deu um gráfico de 331px — três vezes a tabela que ele ilustra.
   O `viewBox` aqui é sistema de coordenadas, não proporção de tela. */
.spark { display: block; width: 100%; height: 56px; color: var(--teal-dark); }
.spark rect { fill: currentColor; }
.spark rect.fraca { fill: var(--line); }
.spark-rot { display: flex; justify-content: space-between; gap: var(--esp-2);
             font-size: 11px; color: var(--muted); margin-top: var(--esp-1); }

/* ── faixa de preço da ficha ────────────────────────────────────────────── */
.faixa-barra {
  display: block; position: relative; height: 8px; border-radius: 4px;
  background: linear-gradient(90deg, var(--bg3), var(--teal-dark), var(--bg3));
}
.faixa-marca {
  position: absolute; top: -4px; width: 3px; height: 16px;
  background: var(--fg); border-radius: 2px; transform: translateX(-1px);
}
.faixa-rot { display: flex; justify-content: space-between; gap: 10px;
             font-size: 12px; margin-top: 8px; flex-wrap: wrap; }

/* ── cadastro por texto livre ───────────────────────────────────────────── */
.duas-colunas { display: grid; gap: 18px; grid-template-columns: 0.85fr 1.15fr; align-items: start; }
@media (max-width: 900px) { .duas-colunas { grid-template-columns: 1fr; } }
/* Dentro da coluna estreita, o piso de 220px do `.row` daria uma linha por
   campo e a ficha viraria uma tira de 2.500px. Quartos/suítes/vagas cabem
   lado a lado. */
.duas-colunas .row > * { flex: 1 1 120px; }

.conversa { display: flex; flex-direction: column; gap: 10px; margin-bottom: 18px; }
.balao {
  padding: 10px 14px; border-radius: 12px; font-size: 14px; max-width: 90%;
  white-space: pre-wrap;
}
.balao.eu { align-self: flex-end; background: var(--teal-dark); color: var(--btn-fg); border-bottom-right-radius: 3px; }
.balao.sistema { align-self: flex-start; background: var(--bg3); border: 1px solid var(--line);
                 border-bottom-left-radius: 3px; }

/* Amarelo = o sistema não tem certeza. Vermelho = falta para publicar.
   As duas cores dizem coisas diferentes, e misturá-las tiraria o sentido. */
.campo.incerto { border-color: var(--amber); }
.campo.falta { border-color: var(--red); }
/* Chip que a extração ligou mas sobre o qual ela tem dúvida ("semi mobiliado").
   Some no primeiro clique — encostou, conferiu. */
.attr.incerto { border-color: var(--amber); }

/* O eco embaixo do campo de dinheiro: o valor de volta, escrito como dinheiro.
   Fica ENCOSTADO no input (margem de 4px) porque ele é sobre aquele campo — a
   mesma razão que trouxe o aviso de pendências para junto dos campos. Sem
   altura reservada: vazio, o parágrafo não ocupa linha e o formulário não
   ganha buraco em todo campo de preço. */
.eco-dinheiro { margin: 4px 0 0; font-size: 13px; color: var(--muted); }
.eco-dinheiro:empty { display: none; }

/* Cada pendência do aviso é um atalho para o campo vermelho correspondente — o
   corretor via as duas coisas na tela sem nada ligando uma à outra. Botão, não
   âncora: `#` no href mexeria no hash, que aqui é a rota. */
.link-pendencia {
  background: none; border: 0; padding: 0; cursor: pointer; font: inherit;
  color: var(--red); text-decoration: underline;
}

/* ── grade da vitrine ───────────────────────────────────────────────────────
   O card é foto-first, como o do portal que o corretor já usa: imagem no topo,
   preço logo abaixo, faixa de ícones, e a comissão num rodapé separado.
   A ordem importa — antes o preço era o penúltimo elemento, e preço é a
   primeira pergunta de quem varre uma vitrine. */
.cards {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
  margin-bottom: 18px;
}
/* Sem padding no card: a capa precisa encostar nas bordas. O respiro é do
   corpo e do rodapé, que é o que permite a linha do rodapé atravessar o card
   de ponta a ponta. `overflow:hidden` faz o raio recortar a foto.

   ── por que o card deixou de ser um <a> ──
   A tag "Impulsionar" é um BOTÃO, e botão dentro de link quebra teclado e
   leitor de tela — era por isso que todo controle vivia fora do card. A saída
   sem aninhar nada é o LINK ESTICADO: `<article>` posicionado, um `<a>` que se
   espalha por um `::after` absoluto, e o botão acima dele no eixo Z. O card
   segue clicável por inteiro, e agora o Tab visita os dois em vez de um só. */
.imovel {
  position: relative;
  display: flex; flex-direction: column;
  color: inherit;
  background: var(--bg2); border: 1px solid var(--line);
  border-radius: var(--radius); overflow: hidden;
  transition: border-color .12s;
}
.imovel:hover { border-color: var(--teal-dark); }
.imovel-link { text-decoration: none; color: inherit; }
.imovel-link::after { content: ""; position: absolute; inset: 0; z-index: 0; }
/* O anel de foco desenha no `::after`, que já cobre o card inteiro. Fazer isso
   com `:has()` no pai seria mais direto e depende de um seletor que navegador
   velho não tem — este funciona em qualquer um, e é o mesmo desenho. */
.imovel-link:focus-visible { outline: none; }
.imovel-link:focus-visible::after {
  outline: 2px solid var(--teal-dark); outline-offset: -2px;
  border-radius: var(--radius);
}

/* A tag de marketing, no canto inferior direito da capa.
   `pointer-events: auto` porque a faixa que a hospeda tem `none` — lá o clique
   é do card; aqui, não. `z-index` acima do link esticado, senão o `::after`
   engole o clique e o botão vira decoração. */
.impulsionar {
  position: relative; z-index: 1; pointer-events: auto;
  /* `flex: none` na PRÓPRIA pílula, não só no `.lado` que a hospeda: `.lado`
     também é flex, e sem isto a tag encolhe lá dentro (medido: 105px de
     conteúdo espremidos numa caixa de 94px). O rótulo era cortado por dentro,
     sem sangrar pela borda do card — invisível para quem só olhasse a borda. */
  flex: none;
  display: inline-flex; align-items: center; gap: 5px; white-space: nowrap;
  padding: 5px 10px; font-size: 12px; font-weight: 600; line-height: 1.2;
  color: #fff; background: rgba(17, 24, 28, .72);
  border: 1px solid rgba(255, 255, 255, .4); border-radius: 999px;
  cursor: pointer; backdrop-filter: blur(2px);
}
.impulsionar:hover { background: var(--teal-dark); border-color: var(--teal-dark); }
.impulsionar .ico { width: 14px; height: 14px; }
/* No celular a tag ENCOLHE, e o alvo de toque cresce por baixo.

   Medido no piso de 360px: são dois cards por linha, a capa fica com ~150px, e
   a pílula com o padding do desktop somada ao selo do código passava da borda
   — o print mostrava "Impulsion…". É o mesmo problema que o selo de estágio já
   teve nesta mesma faixa, e a regra da casa é a mesma: **encolhe o selo em vez
   de cortar o texto**, porque "Impulsion…" não é informação nenhuma.

   O `::before` invisível devolve o alvo de ~44px sem engordar o desenho. Só no
   celular: no desktop o alvo do mouse já basta e uma área grande roubaria
   clique da capa. */
@media (max-width: 600px) {
  .impulsionar { font-size: 11px; padding: 4px 8px; gap: 4px; }
  .impulsionar .ico { width: 12px; height: 12px; }
  .impulsionar::before {
    content: ""; position: absolute; inset: -11px; border-radius: 999px;
  }
  /* Sem espaço, a tag DESCE em vez de ser espremida. O `flex: none` é o que
     faz a diferença: sem ele o flex encolhe a pílula (medido: 105px de
     conteúdo numa caixa de 94px) e o rótulo é cortado por dentro, sem sangrar
     pela borda — invisível para uma medição que só olhe a borda do card. */
  .capa-selos.base { flex-wrap: wrap; }
  .capa-selos .lado-acao { flex: none; }
}
.imovel-corpo { padding: 12px 14px 14px; flex: 1; }
.imovel-preco { font-size: 19px; font-weight: 600; color: var(--teal); line-height: 1.2; }
.imovel-tit { font-weight: 600; font-size: 14px; margin-top: 6px;
              display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
              overflow: hidden; }
.imovel-local { color: var(--muted); font-size: 13px; margin-top: 2px; }
/* A faixa de ícones. `flex-wrap` porque num card estreito quatro itens não
   cabem numa linha — e quebrar é melhor que espremer até o número colar no
   ícone seguinte. */
.imovel-icones {
  display: flex; flex-wrap: wrap; gap: 6px 12px;
  color: var(--muted); font-size: 13px; margin-top: 10px;
}
.imovel-icones .ic { display: inline-flex; align-items: center; gap: 4px; }
.imovel-icones small { font-size: 11px; }
/* Quanto ESTE corretor ganha neste imóvel — o que decide se ele corre atrás.
   Ganhou rodapé próprio, separado por uma linha: antes se confundia com a
   linha de características. Segue menor que o preço e sem cor de acento — o
   preço é do imóvel, a comissão é dele. */
.imovel-comissao {
  font-size: 13px; color: var(--muted);
  padding: 9px 14px; border-top: 1px solid var(--line); background: var(--bg);
}
@media (max-width: 600px) {
  .cards { grid-template-columns: 1fr 1fr; gap: 10px; }
  .imovel-corpo { padding: 10px; }
  .imovel-preco { font-size: 16px; }
  .imovel-icones { gap: 4px 8px; font-size: 12px; }
  .imovel-comissao { font-size: 12px; padding: 8px 10px; }
  /* Dois cards por linha deixam a capa com ~170px. "negociação avançada · 3"
     no corpo de 12px ocupava duas linhas e metade da foto. Encolhe o selo em
     vez de cortar o texto: o rótulo do estágio é a informação, e "negociação
     avanç…" não é informação nenhuma. */
  .capa-selos { padding: 6px; gap: 4px; }
  .capa-selos .pill { font-size: 10px; padding: 2px 7px; }
  .selo { font-size: 10px; padding: 1px 6px; }
}

/* ── tela de Parceiros ──────────────────────────────────────────────────────
   Sete colunas de largura livre não cabem nos 980px de `main`, e como
   `th, td` tem `white-space: nowrap` GLOBAL (as outras 12 tabelas do sistema
   dependem disso), a tabela rolava para o lado — escondendo a última coluna,
   que é justamente a dos botões.

   O conserto é local nos dois eixos: só esta tabela libera a quebra, e só esta
   tela alarga. Afrouxar a regra global mudaria a forma de 13 tabelas sem
   ninguém ter pedido. */
#app.largo { max-width: 1200px; }

.tbl-parceiros td.longo { white-space: normal; min-width: 170px; }
.tbl-parceiros td.longo .muted { word-break: break-word; }

/* Os botões cresceram de 1 para até 3 por linha. Sem contêiner eles são texto
   inline separado por espaço em branco: sem alinhamento e sem ponto de quebra.

   ⚠️ ESCOPADO em `.tbl-parceiros` de propósito. Um `.acoes` solto atinge o
   `.foto-admin .acoes` da galeria de fotos: aquele seletor é mais específico e
   ganha em tudo o que DECLARA, mas ele não declara `flex-wrap` — então o `wrap`
   daqui vazaria para lá, e a barra de 4 ícones de uma foto estreita passaria a
   quebrar em duas linhas numa tela que esta entrega não tocou. Era o mesmo erro
   que o comentário acima adverte, um seletor adiante. */
.tbl-parceiros .acoes { display: flex; gap: 6px; justify-content: flex-end; flex-wrap: wrap; }

/* A rolagem lateral existe e é a saída certa quando ainda falta espaço — mas
   com barra sobreposta (o padrão no mac) ela é invisível, e a coluna cortada
   parece defeito. A sombra na borda direita é o aviso de que há mais.

   A cor vem de `--sombra-rolagem`, e não de um `rgba` fixo: esta sombra assenta
   sobre `--bg2`, que muda de branco para `#161b22` entre os temas. Um preto a
   12% some no escuro — e some justamente o aviso que este bloco existe para
   dar. (`--sombra-1` não serve aqui: ele é um `box-shadow` inteiro, não uma
   cor, e gradiente precisa de cor.)

   ESCOPADA em `.tbl-wrap-sombra`, e não em `.tbl-wrap` puro, pelo mesmo motivo
   do bloco acima: `.tbl-wrap` é o wrapper de rolagem das 13 tabelas do sistema
   (Integrações, Importar, Negócios, Início, Vitrine, Convites — mais esta), e
   as outras 12 vivem sem esta sombra desde sempre. Ligar para todas de uma vez
   é mudança visual em cinco telas e dois temas que ninguém pediu e que esta
   entrega não verificou — só a tela de Parceiros carrega a segunda classe
   (`web/index.html`, função `parceiros()`). */
.tbl-wrap-sombra { background:
  linear-gradient(to right, var(--bg2) 30%, transparent) left / 24px 100% no-repeat,
  linear-gradient(to left, var(--bg2) 30%, transparent) right / 24px 100% no-repeat,
  radial-gradient(farthest-side at 0 50%, var(--sombra-rolagem), transparent) left / 12px 100% no-repeat,
  radial-gradient(farthest-side at 100% 50%, var(--sombra-rolagem), transparent) right / 12px 100% no-repeat;
  background-attachment: local, local, scroll, scroll;
}

/* A linha do formulário de edição: some sem ocupar espaço, e quando aparece não
   herda o `nowrap` das células normais. */
.tbl-parceiros tr.edicao > td { white-space: normal; background: var(--bg3); }
.tbl-parceiros tr.edicao[hidden] { display: none; }

/* ── fotos ──────────────────────────────────────────────────────────────────
   A caixa da capa é desenhada MESMO sem foto, com aspect-ratio fixo. Enquanto o
   estoque da Total não tiver imagem, um card com foto ao lado de um sem — com
   alturas diferentes — leria como layout quebrado. */
.imovel-foto {
  position: relative;
  aspect-ratio: 4 / 3; background: var(--bg3);
  overflow: hidden; display: grid; place-items: center;
}
.imovel-foto img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* A ilustração de categoria preenche a caixa como uma foto preencheria: o
   ponto dela é o espaço da imagem existir mesmo sem foto. */
.imovel-foto img.ilustracao, .galeria-principal img.ilustracao {
  width: 100%; height: 100%; object-fit: cover; display: block;
}

/* ── selos sobre a capa ─────────────────────────────────────────────────────
   Texto sobre foto é onde este padrão costuma quebrar: um selo translúcido
   some numa foto clara e some de novo, por outro motivo, numa escura. Por isso
   todo selo daqui recebe PLACA OPACA (`--bg2`, o mesmo fundo do card) e a cor
   semântica fica no texto. Funciona sobre qualquer foto e nos dois temas, sem
   uma segunda paleta. */
.capa-selos {
  position: absolute; left: 0; right: 0;
  display: flex; justify-content: space-between; align-items: flex-start;
  gap: 6px; padding: 8px;
  pointer-events: none;   /* o clique é do <a> inteiro */
}
.capa-selos.topo { top: 0; }
.capa-selos.base { bottom: 0; align-items: flex-end; }
.capa-selos .lado { display: flex; gap: 6px; flex-wrap: wrap; }
/* O FIO DE CONTORNO não é enfeite. A placa opaca resolve foto escura, mas some
   sobre foto BRANCA — parede branca, azulejo, céu estourado, que é metade das
   fotos de imóvel. Foi visto renderizando um card com capa #ffffff: a pílula
   ficava sem limite nenhum, segurada só pela sombra. A sombra some no branco;
   o fio não. */
.capa-selos .pill, .selo {
  border: 1px solid var(--line);
  box-shadow: 0 1px 4px rgba(0,0,0,.28);
}
.capa-selos .pill { background: var(--bg2); }
.capa-selos .pill.hot { background: var(--red); border-color: var(--red); }
.selo {
  font-size: 11px; line-height: 1.6; color: var(--muted);
  background: var(--bg2); border-radius: 999px; padding: 1px 8px;
}

.galeria-principal {
  aspect-ratio: 3 / 2; background: var(--bg3); border-radius: var(--radius);
  overflow: hidden; display: grid; place-items: center;
}
.galeria-principal img { width: 100%; height: 100%; object-fit: cover; display: block; }
.galeria-principal img:focus-visible { outline: 2px solid var(--teal-dark); outline-offset: -2px; }
.galeria-tiras { display: flex; gap: 8px; overflow-x: auto; margin-top: 8px;
                 -webkit-overflow-scrolling: touch; padding-bottom: 4px; }
.galeria-tiras img {
  width: 84px; height: 64px; object-fit: cover; border-radius: 6px;
  background: var(--bg3); border: 2px solid transparent; cursor: pointer; flex: 0 0 auto;
}
.galeria-tiras img.on { border-color: var(--teal-dark); }

.foto-admin { display: grid; gap: 10px; margin-top: 10px;
              grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); }
.foto-admin figure { position: relative; margin: 0; }
.foto-admin img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover;
                  border-radius: 6px; background: var(--bg3); display: block; }
.foto-admin .acoes {
  position: absolute; inset: auto 0 0 0; display: flex; justify-content: center; gap: 2px;
  background: var(--bg2); border: 1px solid var(--line); border-radius: 0 0 6px 6px;
}
.foto-admin .acoes button {
  background: none; border: 0; color: var(--fg); cursor: pointer;
  font-size: 13px; padding: 4px 6px; line-height: 1;
}
.foto-admin .acoes button:disabled { color: var(--muted); cursor: default; }
.foto-capa-selo {
  position: absolute; top: 6px; left: 6px; background: var(--teal-dark);
  color: var(--btn-fg); font-size: 11px; padding: 2px 6px; border-radius: 4px;
}

/* ── modal de gerar anúncio ─────────────────────────────────────────────────
   Sem biblioteca de modal: overlay + caixa, Esc e clique no fundo fecham. */
.modal { position: fixed; inset: 0; z-index: 50; display: none;
         background: rgba(8, 12, 18, .62); padding: 20px;
         overflow-y: auto; -webkit-overflow-scrolling: touch; }
.modal.on { display: block; }
.modal-caixa { background: var(--bg2); border: 1px solid var(--line);
               border-radius: var(--radius); max-width: 560px; margin: 24px auto;
               padding: 20px; }
.modal-topo { display: flex; justify-content: space-between; align-items: center;
              margin-bottom: 12px; }
.modal-x { background: none; border: 0; color: var(--muted); cursor: pointer;
           font-size: 18px; line-height: 1; padding: 4px 8px; }
.modal-x:hover { color: var(--fg); }

/* A prévia do "Mandar pro Radar" — o texto EXATO que vai para o Telegram.
   `pre` e não `p` porque a mensagem é montada por blocos separados por quebra
   de linha (`app/radar.py::montar_mensagem`), e um `p` colapsaria todas elas:
   quem lê a prévia veria um parágrafo corrido e o canal receberia outra coisa.
   `pre-wrap` preserva as quebras e ainda quebra linha comprida no celular. */
.radar-previa {
  margin: 10px 0 0; padding: 12px 14px;
  background: var(--bg3); border: 1px solid var(--line);
  border-radius: 8px;
  white-space: pre-wrap; overflow-wrap: anywhere;
  font-family: inherit; font-size: 13px; line-height: 1.5; color: var(--fg);
}

.anuncio-secao { margin: 16px 0; }
.anuncio-rotulo { display: flex; justify-content: space-between; align-items: center;
                  font-size: 13px; font-weight: 600; margin-bottom: 8px; }
.segmentado { display: inline-flex; border: 1px solid var(--line);
              border-radius: 999px; overflow: hidden; }
.segmentado button { background: var(--bg2); border: 0; color: var(--muted);
                     cursor: pointer; font-size: 12px; padding: 5px 12px; }
.segmentado button.on { background: var(--teal-dark); color: var(--btn-fg); }

.anuncio-templates { display: grid; gap: 8px;
                     grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.anuncio-template { text-align: left; background: var(--bg3); cursor: pointer;
                    border: 2px solid transparent; border-radius: 8px; padding: 10px; }
.anuncio-template.on { border-color: var(--teal-dark); }
.anuncio-template-nome { display: block; font-weight: 600; font-size: 14px;
                         color: var(--fg); }
.anuncio-template-desc { display: block; font-size: 12px; color: var(--muted);
                         margin-top: 3px; }

.anuncio-fotos { display: flex; gap: 8px; overflow-x: auto; padding-bottom: 4px; }
.anuncio-foto { flex: 0 0 auto; width: 84px; height: 64px; padding: 0;
                border: 2px solid transparent; border-radius: 6px; overflow: hidden;
                background: var(--bg3); cursor: pointer; }
.anuncio-foto.on { border-color: var(--teal-dark); }
.anuncio-foto img { width: 100%; height: 100%; object-fit: cover; display: block; }
.anuncio-ilustracao { color: var(--muted); font-size: 11px; line-height: 1.25; }

.anuncio-previa { background: var(--bg3); border-radius: 8px; padding: 12px;
                  display: grid; place-items: center; }
.anuncio-previa img { max-width: 320px; width: 100%; height: auto; display: block;
                      border-radius: 4px; }
.anuncio-copy { margin: 14px 0; }
.anuncio-copy textarea { width: 100%; background: var(--bg3); color: var(--fg);
                         border: 1px solid var(--line); border-radius: 6px;
                         padding: 8px; font: inherit; font-size: 13px; resize: vertical; }
/* As ações quebram em vez de espremer: no celular são quatro botões e o
   principal ("Compartilhar") não pode virar duas letras e reticências. */
.anuncio-acoes { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 0; }

@media (max-width: 600px) {
  .modal { padding: 0; }
  .modal-caixa { margin: 0; border-radius: 0; border: 0; min-height: 100vh; }
  /* O botão principal ocupa a linha inteira: é o que a pessoa veio fazer. */
  .anuncio-acoes > button:first-child { flex: 1 0 100%; }
}

/* ── central de marketing: a grade de peças ──────────────────────────────────

   ⚠️ ERA UMA LISTA, e a regra escrita aqui dizia: *"cada peça precisa dizer DE
   QUAL CASA é, e nome de imóvel não cabe embaixo de uma miniatura de 84px"*.
   Ela estava certa sobre a miniatura de 84px e errada sobre o que a miniatura
   mostrava: era a CAPA DO IMÓVEL, não a peça. Três artes do mesmo imóvel com
   fotos diferentes saíam com a mesma imagem, o mesmo título, o mesmo modelo e a
   mesma data — três linhas idênticas na tela, sobre três arquivos diferentes.

   Com a arte de verdade, a imagem passa a ser o que identifica (é o objeto que
   se veio ver), e o texto vira confirmação. Aí a grade cabe: 150px de arte já
   distingue duas peças, e o nome do imóvel continua embaixo, em duas linhas.

   `auto-fill` e não `auto-fit`: com 2 peças, `auto-fit` esticaria cada cartão
   até 470px e a arte apareceria gigante ao lado de um vazio — que é o estado de
   hoje (a base tem 5 peças). `auto-fill` mantém o tamanho do cartão e deixa a
   linha incompleta, que é a leitura honesta de "você tem duas". */
.pecas { display: grid; gap: var(--esp-3);
         grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); }
/* DUAS colunas no celular, e não uma.
   Com 180px de mínimo, a 360px cabia um cartão por linha: a arte ficava com
   ~280px de altura e três peças viravam quase mil pixels de rolagem para ver
   três coisas. Reconhecer qual peça é — que é para isso que a miniatura serve —
   não precisa de 280px.

   ⚠️ `min(46%, 140px)` e não `140px` seco. A largura que sobra não é a da tela:
   o `.card` tem 22px de padding de cada lado, então a 360px o espaço útil é
   ~276px e dois cartões de 140 + gap (288) NÃO cabiam — a media query entrava e
   a grade continuava com uma coluna só. Medido no print, depois de a primeira
   tentativa não mudar nada. Com a porcentagem, duas colunas são garantidas por
   construção, sem esta regra precisar saber quanto padding o cartão tem hoje. */
@media (max-width: 560px) {
  .pecas { grid-template-columns: repeat(auto-fill, minmax(min(46%, 140px), 1fr));
           gap: var(--esp-2); }
}
.peca {
  display: flex; flex-direction: column; gap: var(--esp-2); width: 100%;
  padding: var(--esp-2); text-align: left; cursor: pointer;
  background: var(--bg2); border: 1px solid var(--line); border-radius: 8px;
  color: inherit; font: inherit;
}
.peca:hover { border-color: var(--teal-dark); }

/* `contain`, e a caixa QUADRADA para todo formato.

   Duas decisões, e as duas nasceram de olhar a tela:

   **`contain` e não `cover`** — uma peça `stories` é 9:16. Recortada para
   preencher, ela sai com a cabeça e o rodapé cortados, e o rodapé é onde estão
   o nome, o telefone e o CRECI de quem assina. Cortar o rodapé de um material
   de divulgação na tela que existe para conferi-lo é o pior corte possível.

   **A caixa é 1:1 para todos, e não a proporção de cada formato.** A primeira
   versão declarava `aspect-ratio` por formato — honesto no papel e quebrado na
   tela: numa linha com uma stories e duas feed, o cartão alto empurrava a
   legenda dele 120px abaixo das vizinhas, e a grade lia como layout defeituoso.
   Com a caixa igual, as legendas alinham; a stories continua aparecendo
   INTEIRA, só que menor e com moldura — e o formato continua escrito embaixo,
   em palavras.

   1:1 e não 4:5 porque `feed` é o formato dominante (é o que o Radar publica e
   o padrão do modal): ele preenche a caixa exatamente, sem sobra. */
.peca-arte {
  display: block; width: 100%; aspect-ratio: 1 / 1;
  border-radius: 6px; overflow: hidden;
  /* A sobra de uma stories numa caixa quadrada precisa parecer MOLDURA, não
     imagem que não carregou. */
  background: var(--bg3);
}
.peca-arte img { width: 100%; height: 100%; object-fit: contain; display: block; }

.peca-corpo { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.peca-tit {
  font-weight: 600; font-size: 14px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.peca-meta { color: var(--muted); font-size: 12px; }
.pecas-mais { margin: var(--esp-3) 0 0; }
/* O selo só aparece na peça que o robô compôs. Na gaveta "Publicadas pelo
   Radar" todas têm — redundante e de propósito: a gaveta pode ser aberta por
   link direto, e um selo por peça continua verdadeiro fora do contexto. */
.peca-origem {
  font-size: 11px; color: var(--teal); margin-top: 2px;
  letter-spacing: .02em;
}

/* ── criar peça a partir da central: o seletor de imóvel ─────────────────────
   Uma lista curta (8), e não uma grade: aqui o imóvel é ESCOLHIDO, não
   navegado. Quem precisa navegar tem a vitrine, com o mapa e a paginação — duas
   grades para o mesmo dado é a família de defeito que este produto já viu seis
   vezes. Por isso `.achado` é linha e `.peca` é cartão: objetos diferentes,
   leituras diferentes. */
/* O painel de escolher tem fundo e borda PRÓPRIOS.
   Sem isso ele encostava na grade de peças e as duas coisas liam como uma lista
   só — "o que estou escolhendo" e "o que eu já tenho" com a mesma cara, na
   mesma tela, uma embaixo da outra. Medido olhando o print. */
#peca_escolher:not(:empty) {
  background: var(--bg3); border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: var(--esp-3); margin-bottom: var(--esp-4);
}
.busca-peca { display: flex; gap: var(--esp-2); margin: 0 0 var(--esp-3); }
.busca-peca input { flex: 1; min-width: 0; }
/* Último filho sem margem: com resultados na tela, a sobra dobrava o respiro
   que o painel já tem. */
#peca_escolher > *:last-child > *:last-child { margin-bottom: 0; }

.achados { display: flex; flex-direction: column; gap: var(--esp-2); }
.achado {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center; gap: var(--esp-3); width: 100%;
  padding: var(--esp-2); text-align: left; cursor: pointer;
  background: var(--bg2); border: 1px solid var(--line);
  border-radius: var(--radius-sm); color: inherit; font: inherit;
}
.achado:hover { border-color: var(--teal-dark); }
.achado-capa {
  width: 56px; height: 42px; border-radius: 6px; overflow: hidden;
  background: var(--bg3);
}
.achado-capa img { width: 100%; height: 100%; object-fit: cover; display: block; }
.achado-corpo { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.achado-tit {
  font-weight: 600; font-size: 14px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* O rótulo da ação diz o que o clique FAZ. "Impulsionar" é o vocabulário que o
   corretor já conhece do card da vitrine e da ficha — a mesma palavra para a
   mesma coisa, nas três portas de entrada. */
.achado-ir { font-size: 13px; color: var(--teal); white-space: nowrap; }

@media (max-width: 560px) {
  /* No celular a ação desce para baixo do título e a linha vira duas: com três
     colunas, "Impulsionar →" espremia o nome do imóvel até a reticência. */
  .achado { grid-template-columns: auto minmax(0, 1fr); gap: var(--esp-2) var(--esp-3); }
  .achado-ir { grid-column: 2; justify-self: start; }
  .busca-peca { flex-wrap: wrap; }
  .busca-peca input { flex-basis: 100%; }
}

/* A nota de rodapé de um bloco (retenção, dedupe, o que o número significa).
   Existia como `style="font-size:13px;margin:14px 0 0"` repetido em quatro
   lugares desta tela — os mesmos `style=` inline que a home densa tinha matado
   e que voltaram por aqui. */
.nota-rodape { font-size: 13px; margin: var(--esp-3) 0 0; }

/* ── um bloco que se recolhe (configuração, relatório) ───────────────────────

   `<details>` puro, sem JS, como `.filtros` da vitrine e `.anuncio-ajustes` do
   modal. Nasceu para a agenda do Radar: ela ficava entre o título do card e o
   histórico, empurrando para baixo justamente o que se vem ver, sendo
   CONFIGURAÇÃO que se consulta de vez em quando.

   ⚠️ Não confundir com `.bloco-config`, que é o cartão-link da tela de
   Configurações (`config.js`) e é `display:flex`. Nomes parecidos, coisas
   diferentes — este é o único aviso que impede a próxima pessoa de fundir os
   dois e quebrar a outra tela. */
.recolhivel {
  margin: var(--esp-3) 0 0; border: 1px solid var(--line);
  border-radius: var(--radius-sm); background: var(--bg2);
}
.recolhivel > summary {
  display: flex; align-items: baseline; gap: var(--esp-2); flex-wrap: wrap;
  padding: 10px 12px; cursor: pointer; font-size: 13px;
  /* Alvo de toque de 24px no celular: o `<summary>` é a única alça para abrir,
     e a régua de 360px reprova qualquer alvo menor. */
  min-height: 24px;
}
.recolhivel-rot { font-weight: 600; }
.recolhivel > summary::marker { color: var(--muted); }
.recolhivel > .radar-agenda, .recolhivel > pre { margin: 0; border: 0; }
.recolhivel > .radar-agenda { border-top: 1px solid var(--line); border-radius: 0; }

/* ── Radar: o histórico da automação, dentro de Marketing ────────────────────
   Grade e não flex: as quatro colunas (quando · imóvel · categoria · cliques)
   precisam ALINHAR entre as linhas, senão a leitura vertical — que é a única
   que importa aqui, *"qual oferta rendeu mais"* — some. O `minmax(0,1fr)` do
   meio é o que faz o título truncar em vez de empurrar as colunas da direita.
   A 560px vira duas linhas, com data e cliques descendo para baixo do título:
   é o que cabe num celular de 360px sem rolagem horizontal. */
/* O aviso de campo faltando. Âmbar e não vermelho: nada está quebrado — a
   arte sai, o canal fala. O que falta é um dado que só uma pessoa pode dar. */
.aviso-tel {
  margin: 12px 0 0; padding: 10px 12px; border-radius: 8px; font-size: 13px;
  background: rgba(245, 158, 11, .12); border: 1px solid rgba(245, 158, 11, .35);
}
.radar-hist { display: flex; flex-direction: column; gap: 8px; }
.radar-linha {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto;
  align-items: center; gap: 12px;
  padding: 10px; text-decoration: none; color: inherit;
  background: var(--bg2); border: 1px solid var(--line); border-radius: 8px;
}
.radar-linha:hover { border-color: var(--teal-dark); }
.radar-quando { color: var(--muted); font-size: 12px; white-space: nowrap; }
.radar-corpo { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.radar-tit {
  font-weight: 600; font-size: 14px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.radar-cat {
  font-size: 12px; color: var(--muted); white-space: nowrap;
  padding: 2px 8px; border-radius: 999px; background: var(--bg3);
}
/* O que saiu por dedo humano tem contorno; o automático não. É a única
   diferença visual entre as duas origens na lista, e ela é discreta de
   propósito — quem quiser separar de verdade usa o filtro. Contorno e não cor
   cheia: a etiqueta diz o MOTIVO da oferta ("abaixo do mercado"), e pintá-la
   competiria com a coluna de cliques, que é o que a linha existe para comparar. */
.radar-cat.manual { border: 1px solid var(--line); background: transparent; }

/* Zero clique continua sendo um NÚMERO (o "—" leria como "não medimos"), mas
   em cinza. Com dez linhas de "0 abriram" em negrito o olho aprende a pular a
   coluna inteira — e no dia em que aparecer um 3 de verdade, ninguém vê. */
.radar-cliques {
  font-size: 12px; font-weight: 600; white-space: nowrap; color: var(--muted);
}
.radar-cliques.rendeu { color: var(--teal); }

/* A contagem dentro do filtro do canal. Existe para o botão dizer quanta coisa
   ele esconde ANTES do clique: "Manual 3" e "Manual 0" pedem decisões
   diferentes, e sem o número os dois têm exatamente a mesma cara. */
.segmentado-n { opacity: .7; font-variant-numeric: tabular-nums; }

/* No celular a linha vira duas, e o TÍTULO fica com a largura inteira.
   ⚠️ A primeira versão punha a etiqueta ao lado do título na linha de cima, e a
   360px isso truncava "Apartamento · 3q" em "Apartamento…" — comendo os
   quartos, que é metade do que a linha diz sobre o imóvel. Medido no print, não
   deduzido: a etiqueta é larga ("escolhido à mão") e disputava a mesma linha.
   Descendo a etiqueta para junto da data, o título passa a caber inteiro. */
@media (max-width: 560px) {
  .radar-linha { grid-template-columns: auto auto 1fr; gap: 6px 10px; }
  .radar-corpo   { grid-column: 1 / -1; grid-row: 1; }
  .radar-quando  { grid-column: 1; grid-row: 2; }
  .radar-cat     { grid-column: 2; grid-row: 2; justify-self: start; }
  .radar-cliques { grid-column: 3; grid-row: 2; justify-self: end; }
}

/* ── Radar: a agenda editável ────────────────────────────────────────────────
   `flex-wrap` e não grade: o número de horários é decidido pela Total e pode
   ser 0 ou 8. Uma grade de N colunas teria de escolher N, e escolheria errado
   metade das vezes. */
.radar-agenda {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  margin: 14px 0; padding: 12px; border-radius: 8px;
  background: var(--bg2); border: 1px solid var(--line);
}
/* `.radar-agenda-rot` (o rótulo "HORÁRIOS") saiu em 11/08 junto com o rótulo:
   a agenda passou a viver dentro de um `<details>` cujo `<summary>` já diz o
   que ela é, e uma legenda em caixa alta logo abaixo do título repetia a mesma
   palavra duas vezes em duas tipografias. Regra sem consumidor não fica. */
.radar-horas { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.radar-hora {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 3px 6px 3px 10px; border-radius: 999px;
  background: var(--bg3); font-size: 13px; font-variant-numeric: tabular-nums;
}
.radar-hora-x {
  /* Alvo de toque, não só um glifo. Medido no harness a 360px: com `padding`
     sozinho o ✕ saía com menos de 24px e virava o menor alvo da tela — o mais
     fácil de errar com o polegar, e o único cujo erro APAGA um horário. O
     mínimo confortável na rua é ~32px; 28 aqui + 32 no celular, abaixo. */
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 28px; min-height: 28px;
  border: 0; background: none; color: var(--muted); cursor: pointer;
  font-size: 13px; line-height: 1; border-radius: 999px;
}
.radar-hora-x:hover { color: var(--red); background: var(--bg2); }
.radar-hora-x:disabled { opacity: .4; cursor: default; }
.radar-agenda-acoes { display: flex; gap: 6px; align-items: center; margin-left: auto; }
/* A saída do disparo é texto de terminal: monoespaçado, rolável, e com teto de
   altura — o relatório tem ~25 linhas e não pode empurrar o histórico para
   fora da tela. */
.radar-saida {
  margin: 10px 0 0; padding: 10px; max-height: 260px; overflow: auto;
  background: var(--bg3); border-radius: 6px;
  font-size: 12px; line-height: 1.5; white-space: pre-wrap; word-break: break-word;
}
@media (max-width: 560px) {
  /* Num celular a linha vira duas: os horários em cima, as ações embaixo e
     ocupando a largura — botão de 40px de alvo, não de 24. */
  .radar-agenda-acoes { margin-left: 0; flex-basis: 100%; }
  .radar-agenda-acoes select, .radar-agenda-acoes button { flex: 1; }
  /* E o ✕ cresce: na rua, com o celular numa mão só, 28px ainda erra. */
  .radar-hora-x { min-width: 32px; min-height: 32px; }
  .radar-hora { padding: 4px 4px 4px 12px; }
}

/* ── comissão e gerência na ficha ───────────────────────────────────────── */
.comissao-ficha {
  display: flex; flex-direction: column; gap: 4px;
  margin: 18px 0 0; padding: 12px 14px;
  background: var(--bg3); border: 1px solid var(--line); border-radius: 8px;
  font-size: 15px;
}

/* Cada ação de gerência é uma linha: à esquerda o que é e o que acontece, à
   direita o botão. Empilha no celular, onde o corretor vive. */
.gerenciar-linha {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px; padding: 14px 0; border-bottom: 1px solid var(--line);
}
.gerenciar-linha:last-child { border-bottom: 0; }
.gerenciar-botoes { display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
@media (max-width: 600px) {
  .gerenciar-linha { flex-direction: column; align-items: stretch; }
  .gerenciar-botoes { justify-content: flex-start; }
}

/* ── mapa ───────────────────────────────────────────────────────────────────
   O Leaflet traz o CSS dele por CDN; aqui fica só o que é do produto: a altura
   da caixa (o Leaflet exige altura explícita, senão desenha 0px e some sem
   erro), a moldura e o acerto do tema escuro. */
.mapa {
  height: 320px; width: 100%;
  border: 1px solid var(--line); border-radius: 8px;
  background: var(--bg3);          /* enquanto os ladrilhos não chegam */
}
@media (max-width: 600px) { .mapa { height: 260px; } }

/* Os ladrilhos do OpenStreetMap só existem em versão clara. Sem isto, o mapa
   vira um retângulo branco ofuscante no meio do tema escuro. Inverter só o
   painel de ladrilhos preserva as cores dos nossos círculos, que ficam por
   cima, em outro painel. */
:root[data-tema="escuro"] .leaflet-tile-pane {
  filter: invert(1) hue-rotate(180deg) brightness(0.92) contrast(0.9);
}
.leaflet-container { font: inherit; background: var(--bg3); }
.leaflet-control-attribution {
  font-size: 11px; background: var(--bg2) !important; color: var(--muted);
}
.leaflet-control-attribution a { color: var(--teal); }

/* Mapa da busca: mais alto que o da ficha — é um painel ao lado (desktop) ou
   acima (celular) da lista, não mais a tela inteira, mas ainda precisa de
   espaço pra desenhar uma área com precisão. */
.mapa-busca { height: min(70vh, 620px); }
@media (max-width: 600px) { .mapa-busca { height: 60vh; } }

/* Painel do mapa da busca: empilha acima da lista por padrão (celular, onde
   o corretor usa isto na rua) e vira coluna fixa ao lado dela a partir de
   900px — mesma proporção e o mesmo ponto de quebra que `.duas-colunas`
   (linha ~283) já usa em `novo.js`, pra não inventar uma segunda régua de
   layout pro mesmo tipo de grid. Sem `.com-mapa`, `#resultados` nem vira
   grid — a lista ocupa a largura cheia, exatamente como hoje. */
/* Mesmo deslocamento do painel sticky (84px) — sem isto, o scroll automático
   de "Procurar nesta área" no celular esconde o aviso atrás do cabeçalho
   fixo (header.top é sticky, ~64px). */
#grade { scroll-margin-top: var(--topo-fixo); }

@media (min-width: 900px) {
  .resultados.com-mapa {
    display: grid;
    grid-template-columns: 0.85fr 1.15fr;
    gap: 18px;
    align-items: start;
  }
  .resultados.com-mapa .mapa-painel {
    position: sticky;
    top: var(--topo-fixo);   /* header.top sticky (~64px) + respiro */
  }
  .resultados.com-mapa .cards {
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  }
}

.mapa-topo {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-bottom: 12px; font-size: 13px;
}

/* A contagem dentro do círculo do grupo — desfaz a caixinha branca padrão do
   tooltip do Leaflet, que aqui esconderia o próprio círculo.

   ⚠️ O seletor precisa das DUAS classes: o CSS do Leaflet é injetado por CDN
   DEPOIS deste arquivo, então `.mapa-conta` sozinha perde o desempate por ordem
   e a caixinha branca volta. Foi o que o primeiro screenshot mostrou. */
.leaflet-tooltip.mapa-conta {
  background: none; border: 0; box-shadow: none; padding: 0;
  color: #fff; font-weight: 700; font-size: 12px;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .5);   /* legível sobre qualquer ladrilho */
}
.leaflet-tooltip.mapa-conta::before { display: none; }   /* a setinha do balão */
/* ── o card do pino (substituiu o balão do Leaflet) ──────────────────────────

   Aqui morava o bloco `.leaflet-popup*`, que existia para vencer o CSS do
   Leaflet no desempate por ordem (KI-038). Ele saiu junto com o `bindPopup`:
   clicar num pino agora preenche este slot, que é DOM nosso, irmão do
   `.leaflet-container` e não filho dele.

   O ganho não é estético. Fora do `.leaflet-container` não há `overflow:hidden`
   nativo recortando, não há `maxHeight`/`autoPan` para acertar, e não há
   nenhuma regra do CDN para desempatar — as três coisas que produziram KI-038,
   KI-039 e o balão vazio que nunca reproduziu. Por isso estas regras usam UMA
   classe só e isso é seguro: `.mapa-slot` não colide com nada do Leaflet. */
.mapa-slot {
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--bg3); padding: 10px 12px; margin-bottom: 12px;
  /* O teto existe por causa do caso COMUM em produção: a carteira empilha 657
     imóveis em ~105 pontos, então um ponto de 34 é rotina. Sem teto, o slot
     empurraria o mapa inteiro para fora da tela — exatamente o problema que fez
     os controles subirem para o `.mapa-topo` na v0.2.4. Com teto, o slot rola e
     o corretor continua vendo TODOS (o balão cortava em 8 + "e mais N"). */
  max-height: min(40vh, 320px); overflow-y: auto;
}
.mapa-slot[hidden] { display: none; }

/* O CABEÇALHO GRUDA NO TOPO ENQUANTO A LISTA ROLA.
   Com 34 imóveis o corretor rola até o fim e, sem isto, perde de vista as duas
   coisas que não podem sumir: o "×" para fechar e a ressalva "posição aproximada
   do bairro". Uma lista de endereços sem a ressalva à vista é precisão
   inventada — a mesma regra do KI-037. O fundo repete o do slot para o texto não
   passar por baixo. */
.slot-cabeca {
  position: sticky; top: -10px; z-index: 1;
  background: var(--bg3); padding: 10px 0 4px; margin: -10px 0 0;
}
.slot-topo {
  display: flex; align-items: baseline; gap: 8px;
  font-size: 14px; margin-bottom: 2px;
}
.slot-topo strong { flex: 1; }
.slot-x {
  background: none; border: 0; padding: 0 2px; cursor: pointer;
  color: var(--muted); font-size: 18px; line-height: 1;
}
.slot-x:hover { color: var(--fg); }
.slot-conta { font-size: 12px; color: var(--muted); }

/* A nota (quartos · área · comissão) ocupa a linha inteira e desce sozinha: o
   `.linha` já é flex com `flex-wrap`, então `flex: 1 0 100%` a manda para a
   segunda linha inteira. Aninhada no título, ela dividia a horizontal com o
   preço e quebrava no meio — o screenshot do caso G mostrou "(metade)" sozinho
   numa terceira linha. */
.slot-nota { flex: 1 0 100%; font-weight: 400; font-size: 12px; color: var(--muted); }

/* ── o que existe em volta do imóvel ────────────────────────────────────── */
.entorno-grupos {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 14px;
}
.entorno-grupo {
  background: var(--bg3); border: 1px solid var(--line);
  border-radius: 8px; padding: 12px 14px; font-size: 14px;
}
.entorno-titulo {
  display: flex; justify-content: space-between; align-items: baseline;
  font-weight: 600; margin-bottom: 8px; font-size: 13px;
}
/* Nome à esquerda, distância à direita, e o nome comprido encolhe em vez de
   empurrar a distância para fora do cartão. */
.entorno-item {
  display: flex; justify-content: space-between; gap: 10px; padding: 3px 0;
}
.entorno-item > span:first-child {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.entorno-item > span:last-child { flex: none; font-variant-numeric: tabular-nums; }

/* Mini-mapa do painel: só situa, não é para explorar. */
.mapa-mini { height: 200px; margin-bottom: 14px; }

/* A quebra do funil, logo abaixo do título de "Você está trabalhando". */
.funil {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 0 0 14px; font-size: 13px;
}

/* Linha de cidade do panorama: clicar leva à vitrine filtrada. */
.pan-linha { cursor: pointer; }
.pan-linha:hover { background: var(--bg3); }

/* ── Configurações ───────────────────────────────────────────────────────────
   Blocos de gerenciamento. Sem cor nova: usa os mesmos tokens do resto. */
.grade-config { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.bloco-config { display: flex; flex-direction: column; gap: 6px; text-decoration: none; color: inherit; transition: border-color .12s; }
.bloco-config:hover { border-color: var(--teal-dark); }

/* ── ajustes da peça (Fase 2 do marketing) ───────────────────────────────────
   Fechado por padrão: o caminho comum é clicar em "Gerar anúncio" sem tocar em
   nada, e um formulário aberto transformaria dois cliques em oito. */
.anuncio-ajustes {
  border: 1px solid var(--line); border-radius: 8px;
  padding: 10px 12px; margin: 16px 0;
}
.anuncio-ajustes > summary {
  cursor: pointer; font-weight: 600; font-size: 14px; list-style: none;
}
.anuncio-ajustes > summary::-webkit-details-marker { display: none; }
.anuncio-ajustes > summary::before { content: "▸ "; color: var(--muted); }
.anuncio-ajustes[open] > summary::before { content: "▾ "; }
.anuncio-ajustes input[type=text] { margin-top: 8px; font-size: 14px; }
.anuncio-interruptores { display: grid; gap: 10px; margin-top: 10px; }
/* Alvo de toque da linha inteira, não da caixinha de 18px.
   O `<input>` mora dentro de um `<label>`, então quem recebe o dedo é a linha —
   e ela media 24px de altura, abaixo do piso de 28 do produto. Achado em 11/08
   pela régua de 360px, na primeira vez que ela foi apontada para este harness.
   Os interruptores decidem o que aparece na peça que vai ao cliente, e são
   mexidos no celular, na rua. */
.anuncio-interruptores .check { min-height: 28px; align-items: center; }
/* O erro mora colado NO CAMPO que ele reprova. Uma tela de erro genérica
   mandaria o corretor adivinhar qual das quatro frases o servidor recusou. */
.anuncio-erro {
  margin: 6px 0 0; font-size: 13px; color: var(--red);
  border-left: 3px solid var(--red); padding-left: 8px;
}

/* A miniatura do modelo, desenhada a partir do MESMO dado que desenha a peça
   (`forma`, em templates.py). Sem ela, "Clássico / Ficha / Destaque" eram três
   nomes sem diferença visível — foi o que o Icaro relatou depois de usar. */
.mini {
  display: block; width: 54px; height: auto; margin-bottom: 8px;
  border-radius: 3px; border: 1px solid var(--line);
}

/* ── recorte e carrossel (Fase 3 do marketing) ───────────────────────────────
   A prévia do recorte mostra a foto INTEIRA, não a peça recortada: o que o
   corretor precisa ver para decidir é justamente o que vai ficar DE FORA. */
.anuncio-recorte { margin-top: 12px; }
.recorte-caixa {
  position: relative; display: block; cursor: crosshair;
  border: 1px solid var(--line); border-radius: 8px; overflow: hidden;
  background: var(--bg3); max-width: 320px;
}
.recorte-caixa img { display: block; width: 100%; height: auto; }
/* O alvo é um anel, não um ponto cheio: sobre foto clara um ponto branco some e
   sobre foto escura some um preto. O anel tem as duas cores. */
.recorte-alvo {
  position: absolute; width: 22px; height: 22px; margin: -11px 0 0 -11px;
  border: 2px solid #fff; border-radius: 50%;
  box-shadow: 0 0 0 2px rgba(0, 0, 0, .55), inset 0 0 0 2px rgba(0, 0, 0, .55);
  pointer-events: none;
}
/* A ordem da foto no carrossel — é o que faz o corretor subir na sequência. */
.anuncio-ordem {
  position: absolute; top: 2px; left: 2px;
  min-width: 16px; height: 16px; padding: 0 3px;
  font-size: 10px; font-weight: 700; line-height: 16px; text-align: center;
  color: #fff; background: var(--teal-dark); border-radius: 4px;
}
.anuncio-foto { position: relative; }
.anuncio-previa figure { margin: 0; }
.anuncio-previa figcaption {
  margin-top: 4px; text-align: center; font-size: 12px; color: var(--muted);
}
/* Duas ou três peças lado a lado, roláveis: elas são para serem COMPARADAS. */
.anuncio-previa.carrossel {
  display: flex; gap: 10px; overflow-x: auto; place-items: start;
}
.anuncio-previa.carrossel img { max-width: 190px; }

/* ═══════════════════════════════════════════════════════════════════════════
   DESCOBRIR — a tela do comprador (13/08)
   ═══════════════════════════════════════════════════════════════════════════

   Uma superfície à parte: quem chega aqui não fez login, não tem aba e não é
   corretor. O cabeçalho do app é escondido por `descobrir.js`.

   ⚠️ Nenhuma cor fixa — tudo sai dos tokens de `:root`, senão um dos dois temas
   quebra sem avisar (`CLAUDE.md §4`). O piso é 360px, a régua deste repo.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Enquanto o Descobrir está na tela, o cabeçalho do corretor não existe.
   Declarativo de propósito: um `style.display` inline vale só para o elemento
   daquele instante e volta no primeiro re-render — a marca sobrevive. Mesmo
   padrão do `data-tema`, e pela mesma razão. */
:root[data-descobrir] header.top { display: none; }

/* Quem é da casa ganha o link "voltar ao sistema" fixo no topo (KI-103) — e a
   tela precisa DESCER, senão ele fica em cima de "Meus favoritos" e um toque na
   metade de cima do botão tira a pessoa do Descobrir em vez de abrir a lista.
   Medido na auditoria de 14/08. Para o comprador a marca não existe e a tela
   não desce: ele não vê o link. */
:root[data-descobrir-saida] .desc-tela { padding-top: 56px; }

.desc-tela {
  max-width: 460px;
  margin: 0 auto;
  padding: var(--esp-4) var(--esp-3);
  /* `dvh` e não `vh`: no celular a barra do navegador some e volta, e `vh`
     mede a tela SEM ela — o rodapé de ações ficaria fora do alcance do dedo.
     Mesma correção que o widget da Maysa já carrega. */
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  gap: var(--esp-3);
  box-sizing: border-box;
  /* Entalhe e barra inferior do iPhone. Sem isto o botão ♥ fica embaixo do
     indicador de home e vira intocável. */
  padding-bottom: calc(var(--esp-4) + env(safe-area-inset-bottom, 0px));
}

.desc-card { padding: var(--esp-5) var(--esp-4); }
.desc-card h2 { margin: 0 0 var(--esp-2); }

.desc-rot {
  display: block;
  font-size: 13px;
  color: var(--muted);
  margin: var(--esp-3) 0 var(--esp-1);
}

.desc-campo {
  width: 100%;
  box-sizing: border-box;
  padding: 12px var(--esp-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--bg3);
  color: var(--fg);
  /* 16px é o mínimo que impede o Safari do iPhone de dar zoom ao focar o
     campo — e o zoom desalinha a tela inteira, sem caminho de volta óbvio. */
  font-size: 16px;
  min-height: 44px;
}
.desc-campo:focus { outline: 2px solid var(--teal); outline-offset: 1px; }

.desc-botao {
  width: 100%;
  margin-top: var(--esp-4);
  padding: 14px;
  border: 0;
  border-radius: var(--radius-sm);
  background: var(--teal-dark);
  color: var(--btn-fg);
  font-size: 16px;
  font-weight: 600;
  min-height: 48px;
  cursor: pointer;
}
.desc-botao:hover:not(:disabled) { background: var(--btn-hover); }
.desc-botao:disabled { opacity: .6; cursor: default; }

.desc-erro { color: var(--red); font-size: 14px; margin: var(--esp-2) 0 0; }
.desc-nota { color: var(--muted); font-size: 13px; margin: var(--esp-3) 0 0; }

/* O "Agora não" do pedido de WhatsApp. Mesmo TAMANHO do botão principal, de
   propósito: a decisão de 14/08 é que o telefone é opcional, e uma saída em
   letra miúda ao lado de um botão cheio seria a barreira de volta, disfarçada.
   O que distingue os dois é o peso visual, não o alcance do dedo. */
.desc-secundario {
  margin-top: var(--esp-2);
  background: transparent;
  color: var(--muted);
  border: 1px solid var(--line);
  font-weight: 500;
}
.desc-secundario:hover:not(:disabled) { background: var(--bg3); }

/* A camada do pedido de WhatsApp. Sobrepõe em vez de trocar a tela — os
   handlers do baralho vivem nos nós de baixo e não sobrevivem a um
   `innerHTML` (ver `pedirTelefone`). */
.desc-modal {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: var(--veu-forte);
  display: grid;
  place-items: center;
  padding: var(--esp-3);
  padding-bottom: calc(var(--esp-3) + env(safe-area-inset-bottom, 0px));
  overflow-y: auto;
}
.desc-modal-caixa { width: 100%; max-width: 420px; }

/* A saída de quem é da casa e abriu o link do comprador por engano. Discreta de
   propósito: para o comprador — que é 99% de quem chega — ela não aparece, e
   para quem é da casa basta ser encontrável, não chamativa.

   ⚠️ NO TOPO, e à direita. Embaixo ela cairia em cima dos botões ✕ e ♥, que
   moram no rodapé do baralho e são a área de toque mais importante da tela. */
.desc-saida {
  position: fixed;
  right: var(--esp-2);
  top: calc(var(--esp-2) + env(safe-area-inset-top, 0px));
  z-index: 50;
  padding: 10px var(--esp-3);
  min-height: 44px;
  display: flex;
  align-items: center;
  border-radius: 999px;
  background: var(--bg2);
  border: 1px solid var(--line);
  color: var(--muted);
  font-size: 13px;
  text-decoration: none;
  box-shadow: var(--sombra-1);
}

/* ── as fichas de preferência ─────────────────────────────────────────────── */

.desc-grupo { margin-top: var(--esp-4); }
.desc-fichas { display: flex; flex-wrap: wrap; gap: var(--esp-2); }

.desc-ficha {
  padding: 10px var(--esp-3);
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--bg2);
  color: var(--fg);
  font-size: 14px;
  /* 44px é o alvo de toque mínimo. O número está aqui e não num token porque
     é o piso da plataforma, não uma medida desta tela. */
  min-height: 44px;
  cursor: pointer;
}
.desc-ficha.on {
  background: var(--teal-dark);
  border-color: var(--teal-dark);
  color: var(--btn-fg);
}

/* ── a pilha ──────────────────────────────────────────────────────────────── */

.desc-tela-baralho { justify-content: space-between; }

.desc-topo {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--esp-2);
}

.desc-link {
  background: none;
  border: 0;
  color: var(--teal);
  font-size: 14px;
  padding: 10px var(--esp-1);
  min-height: 44px;
  cursor: pointer;
}

.desc-pilha {
  position: relative;
  flex: 1;
  min-height: 380px;
  display: grid;
}

/* Os três cards ocupam a MESMA célula do grid — é o que os empilha sem
   `position: absolute` e sem altura fixa. O primeiro do DOM fica por cima. */
.desc-card-imovel {
  grid-area: 1 / 1;
  overflow: hidden;
  padding: 0;
  /* ⚠️ `relative`, e não mais `flex column`. Ver o bloco de `.desc-foto`. */
  position: relative;
  /* `touch-action: pan-y` deixa a rolagem vertical da página funcionar e
     entrega o gesto horizontal ao Pointer Events. Sem isso o navegador
     "rouba" o arrasto e o card nunca se move. */
  touch-action: pan-y;
  user-select: none;
  will-change: transform;
}
.desc-card-imovel:nth-child(1) { z-index: 3; }
.desc-card-imovel:nth-child(2) { z-index: 2; transform: scale(.97) translateY(8px); }
.desc-card-imovel:nth-child(3) { z-index: 1; transform: scale(.94) translateY(16px); }

/* ⚠️ A FOTO PREENCHE O CARD — e é a correção do defeito que o dono viu na tela.

   Como era: `.desc-pilha` tem `flex: 1` e estica até o fim da coluna; o card,
   sendo `grid-area: 1/1`, estica junto; mas a foto estava travada em
   `aspect-ratio: 4/3` e o texto ocupava o que ocupava. **Todo o resto sobrava
   como branco** — medido na tela do dono a 360px: foto de ~300px, texto de
   ~90px, e ~300px de nada embaixo. Perto de 40% da tela sem função.

   Como é: a foto ocupa o card inteiro (`inset: 0`) e a informação flutua por
   cima, no rodapé, sobre um degradê. Não sobra vazio porque não há mais nada
   para sobrar — a foto é o card. É também o que se espera de um baralho: a
   pessoa decide por ela, e o preço confirma.

   `background-size: cover` recorta em vez de deformar. Foto retrato e paisagem
   convivem sem o card mudar de altura. */
.desc-foto {
  position: absolute;
  inset: 0;
  background: var(--bg3) center / cover no-repeat;
  display: grid;
  place-items: center;
}
.desc-sem-foto { color: var(--muted); font-size: 13px; }

/* A informação, flutuando sobre o rodapé da foto.
   O degradê é o que garante a leitura: sem ele, preço branco sobre fachada
   clara some. Ele vai de transparente (topo) a quase opaco (base), então o
   texto senta sempre sobre a parte escura — inclusive quando NÃO há foto e o
   fundo é o `--bg3` claro do tema. */
.desc-corpo {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  /* ⚠️ UM número, duas regras. `--desc-fade` é a zona de esmaecimento: ele é o
     `padding-top` (nenhum texto entra ali) E o comprimento do degradê, logo
     abaixo. Escrever 72 duas vezes é convite para as duas se separarem — e
     quando se separam o texto volta a sentar na parte clara, que é exatamente o
     defeito que este bloco existe para não ter. */
  --desc-fade: 72px;
  padding: var(--desc-fade) var(--esp-3) var(--esp-3);
  display: grid;
  gap: 3px;
  /* ⚠️ As paradas são em PIXELS, e não em porcentagem, e é isso que torna o
     contraste previsível.
     Com `55%` o degradê ainda estava clareando bem no meio do texto — e onde
     ele clareava era justamente na primeira linha, o preço. Em porcentagem a
     posição do texto dentro do degradê depende da ALTURA do bloco, que muda com
     o tamanho do nome do bairro e com a quantidade de selos: o contraste virava
     função do conteúdo.
     Agora o esmaecimento acontece inteiro nos 72px de padding do topo, e todo
     texto senta em `--veu-meio` ou mais escuro. */
  background: linear-gradient(to top,
              var(--veu-forte) 0,
              var(--veu-meio) calc(100% - var(--desc-fade)),
              var(--veu-nulo) 100%);
  color: var(--sobre-foto);
  /* O gesto é do CARD. Sem isto, começar o arrasto em cima do texto não
     deslizaria — e o texto é metade da área tocável do card. */
  pointer-events: none;
}
.desc-preco {
  margin: 0;
  font-size: 26px;
  font-weight: 700;
  color: var(--sobre-foto);
  line-height: 1.15;
}
.desc-onde { margin: 0; font-weight: 600; color: var(--sobre-foto); }
.desc-linha { margin: 0; color: var(--sobre-foto-fraco); font-size: 14px; }
.desc-regiao { margin: 2px 0 0; color: var(--sobre-foto-fraco); font-size: 12px; }

.desc-selos { margin: var(--esp-1) 0 0; display: flex; flex-wrap: wrap; gap: var(--esp-1); }
.desc-selo {
  font-size: 12px;
  padding: 3px var(--esp-2);
  border-radius: 999px;
  /* Sobre a foto, o selo precisa do próprio véu — o degradê já acabou nesta
     altura. */
  background: var(--veu-meio);
  color: var(--sobre-foto-fraco);
}

.desc-fim { text-align: center; padding: var(--esp-5) var(--esp-4); }
.desc-fim h3 { margin: 0 0 var(--esp-2); }

/* ── ✕ e ♥ ────────────────────────────────────────────────────────────────── */

.desc-acoes { display: flex; justify-content: center; gap: var(--esp-5); }

.desc-acao {
  width: 64px;
  height: 64px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--bg2);
  font-size: 26px;
  line-height: 1;
  cursor: pointer;
  box-shadow: var(--sombra-1);
}
.desc-acao:disabled { opacity: .4; cursor: default; }
.desc-nao { color: var(--muted); }
.desc-sim { color: var(--red); }

/* ══ AS BARRINHAS DO CARROSSEL ═══════════════════════════════════════════════

   Uma por foto, no topo, no formato que todo mundo já leu em Stories. Elas são
   o ÚNICO aviso de que o card tem mais de uma foto — sem elas, o toque na borda
   é um gesto secreto, e gesto secreto é funcionalidade que não existe para quem
   não o descobriu (o mesmo argumento que mantém os botões ✕/♥ sempre visíveis).

   ⚠️ `pointer-events: none` é obrigatório. Quem decide se houve toque é o
   `pointerup` no card, olhando quanto o dedo andou; qualquer elemento por cima
   que capturasse o `pointerdown` roubaria o começo do ARRASTO, e o card deixaria
   de deslizar na faixa de cima. É a mesma razão pela qual não existem `<div>` de
   zona esquerda/direita neste card.

   O degradê atrás existe porque a barra apagada é branca translúcida e o pior
   fundo real do estoque é fachada branca — sem véu, ela sumiria justamente no
   imóvel em que o comprador mais precisa saber que há mais foto. Reusa os
   mesmos tokens do rodapé, na direção contrária. */
.desc-barras {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 2;
  display: flex;
  gap: 4px;
  padding: var(--esp-2) var(--esp-2) var(--esp-4);
  pointer-events: none;
  background: linear-gradient(to bottom, var(--veu-meio), var(--veu-nulo));
}
.desc-barra {
  flex: 1;
  height: 3px;
  border-radius: 999px;
  background: var(--sobre-foto-apagado);
}
.desc-barra.on { background: var(--sobre-foto); }
/* Trocar de foto não é animação de entretenimento, mas quem pediu menos
   movimento leva a fé: a barra acende sem transição. */
@media (prefers-reduced-motion: no-preference) {
  .desc-barra { transition: background .15s ease; }
}

/* ══ A LISTA DE FAVORITOS NÃO USA O CARD SOBREPOSTO ═══════════════════════════

   ⚠️ Aqui o card volta a ser **foto em cima, informação embaixo** — o layout
   que ele sempre teve. A sobreposição é uma escolha do BARALHO, e não do
   produto: lá o card ocupa a tela inteira, a foto é a decisão e o texto é a
   confirmação. Numa LISTA, onde o card é pequeno e são vários, sobrepor
   informação à foto piora as duas.

   E havia prova, não só gosto. A auditoria de promoção mediu: com o card da
   lista em 4/3 (226px a 360px) e o corpo em 260px, **o corpo era MAIOR que o
   card** (1,15×). Como o card tem `overflow: hidden` e o corpo é ancorado no
   rodapé, o que sobrava saía por cima — e o que saía era justamente a ponta
   transparente do degradê. Resultado: o véu cobria **100% da foto** e a última
   linha era cortada no meio. Na tela onde o comprador vê o próprio trabalho, e
   na entrega que veio pôr a foto no centro do card.

   Um `padding-top` menor só aliviava o sintoma: o texto continuaria comendo a
   maior parte de um card de 226px. Empilhar resolve a causa — o card passa a
   ter a altura do que carrega, a foto tem os 4/3 dela inteiros, e o texto senta
   sobre o fundo do tema (contraste do tema, sem véu nenhum).                  */

.desc-favoritos { display: grid; gap: var(--esp-3); margin-top: var(--esp-2); }

.desc-favoritos .desc-card-imovel {
  grid-area: auto;
  transform: none;
  position: relative;
  /* Sem `aspect-ratio`: a altura volta a ser a do conteúdo, porque a foto e o
     corpo voltaram ao fluxo normal (abaixo). */
}
.desc-favoritos .desc-foto {
  position: relative;
  inset: auto;
  aspect-ratio: 4 / 3;
}
.desc-favoritos .desc-corpo {
  position: static;
  padding: var(--esp-3);
  background: none;              /* sem véu: não há foto atrás */
  color: var(--fg);
  pointer-events: auto;          /* aqui não há gesto de arrastar para proteger */
}
/* As cores do tema voltam a valer — sobre o cartão, e não sobre a fotografia. */
.desc-favoritos .desc-preco { color: var(--teal); font-size: 20px; }
.desc-favoritos .desc-onde { color: var(--fg); }
.desc-favoritos .desc-linha,
.desc-favoritos .desc-regiao { color: var(--muted); }
.desc-favoritos .desc-selo { background: var(--bg3); color: var(--muted); }

/* ── 360px, o piso ────────────────────────────────────────────────────────── */

@media (max-width: 380px) {
  .desc-tela { padding: var(--esp-3) var(--esp-2); }
  .desc-acao { width: 56px; height: 56px; font-size: 22px; }
  .desc-acoes { gap: var(--esp-4); }
  .desc-preco { font-size: 20px; }
}

/* Quem prefere menos movimento não recebe o giro do card — a decisão continua
   inteira pelos botões. */
@media (prefers-reduced-motion: reduce) {
  .desc-card-imovel { transition: none !important; }
}

/* ── COMPRADORES: a fila da Total e "os meus" do corretor (13/08) ─────────── */

.compr-topo {
  display: flex; justify-content: space-between; align-items: center;
  flex-wrap: wrap; gap: var(--esp-2);
  margin-bottom: var(--esp-3);
}
.compr-filtro { display: flex; align-items: center; gap: var(--esp-1);
                color: var(--muted); font-size: 14px; min-height: 44px; }

.compr-lista { display: grid; gap: var(--esp-3); }
.compr-linha { padding: var(--esp-3); display: grid; gap: var(--esp-2); }

.compr-cab { display: flex; justify-content: space-between; align-items: baseline;
             gap: var(--esp-2); flex-wrap: wrap; }
.compr-tel { color: var(--teal); font-variant-numeric: tabular-nums; }
/* "sem contato" — possível desde a migration 024. Âmbar e não vermelho: não é
   erro do sistema, é uma pessoa que preferiu não deixar o número. Quem opera
   precisa distinguir isso à distância para não abrir a linha à toa. */
.compr-sem-tel { color: var(--amber); font-size: 14px; }
.compr-procura { margin: 0; color: var(--muted); font-size: 14px; }

.compr-rodape { display: flex; justify-content: space-between; gap: var(--esp-2);
                flex-wrap: wrap; font-size: 14px; }
.compr-curtidas { color: var(--green); font-weight: 600; }

.compr-acoes { display: flex; justify-content: space-between; align-items: center;
               gap: var(--esp-2); flex-wrap: wrap; }
/* A fila é o assunto da tela, então ela é a única coisa colorida da linha. */
.compr-fila { color: var(--amber); font-weight: 600; font-size: 14px; }
.compr-com { color: var(--muted); font-size: 14px; }

.compr-aviso {
  font-size: 12px; padding: 2px var(--esp-2); border-radius: 999px;
  background: var(--bg3); color: var(--amber); margin-left: var(--esp-2);
}

.compr-ficha { margin-top: var(--esp-3); }
.compr-tel-grande { font-size: 20px; margin: var(--esp-1) 0; }
.compr-tel-grande a { color: var(--teal); }

.compr-distribuir {
  margin-top: var(--esp-4); padding-top: var(--esp-3);
  border-top: 1px solid var(--line);
  display: flex; flex-wrap: wrap; gap: var(--esp-2); align-items: center;
}
.compr-rot { color: var(--muted); font-size: 13px; width: 100%; }
.compr-select {
  flex: 1; min-width: 180px; min-height: 44px;
  padding: 10px var(--esp-2);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--bg3); color: var(--fg); font-size: 15px;
}

.compr-titulo { margin: var(--esp-5) 0 var(--esp-2); }

/* ══ AS VISITAS ══════════════════════════════════════════════════════════════

   A lista de idas do comprador ao Descobrir, dentro da ficha dele. Lista simples
   de propósito: cada linha é uma data e dois números, e o trabalho dela é levar
   à tela da visita — não competir com a grade de curtidos logo acima. */
.compr-visitas {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--esp-1);
}
.compr-visitas li {
  display: flex;
  flex-wrap: wrap;          /* a 360px a data e os números empilham em vez de espremer */
  align-items: baseline;
  gap: var(--esp-2);
  padding: var(--esp-2) 0;
  border-bottom: 1px solid var(--line);
}
.compr-visitas a { font-weight: 600; }

/* "curtiu há 2 horas" na linha da fila. Verde e não âmbar: âmbar já significa
   AVISO nesta tela (sem contato, telefone repetido), e um segundo significado
   para a mesma cor faz as duas perderem. Este número diz o contrário de aviso —
   é a pessoa que vale ligar agora. */
.compr-quente { color: var(--green); font-size: 14px; font-weight: 600; }

/* O imóvel RECUSADO, na tela da visita.

   Esmaecido, e não escondido nem vermelho: ele é metade do sinal — saber o que
   a pessoa não quer encurta a conversa tanto quanto saber o que ela quer —, mas
   não é o que o corretor vai oferecer. `.72` ainda passa em contraste sobre o
   fundo dos dois temas; abaixo disso o texto do card começa a sair da régua.

   ⚠️ Sem `filter: grayscale`: a foto esmaecida já diz o suficiente, e tirar a
   cor de fachada de imóvel confunde quem está tentando lembrar de qual era. */
.imovel-recusado { opacity: .72; }
.imovel-recusado:hover,
.imovel-recusado:focus-within { opacity: 1; }

/* Os imóveis curtidos usam `.cards` + `.imovel` — a MESMA grade e o mesmo card
   da vitrine. `.compr-curtidos`, `.compr-curtido` e `.compr-preco` viviam aqui
   e saíram junto com a lista crua que descreviam (13/08): eram três linhas de
   texto por imóvel, sem foto, e o corretor não tinha como saber o que ia
   oferecer sem abrir cada um e voltar para a lista.

   A leitura dos ❤️ resume a grade em uma linha. Tipografia miúda e cor de
   metadado de propósito: ela orienta a leitura dos cards, não compete com
   eles. */
.compr-leitura {
  margin: 0 0 var(--esp-2);
  color: var(--muted); font-size: 13px;
}
/* A contradição entre o que ele PEDIU e o que ele curtiu é o pedaço que muda a
   conversa do corretor — então é o único que ganha cor. */
.compr-contra { color: var(--teal); }

@media (max-width: 380px) {
  .compr-cab { flex-direction: column; gap: var(--esp-1); }
  .compr-distribuir { flex-direction: column; align-items: stretch; }
}

/* "ou" entre as duas portas do Descobrir, e o botão do Google. */
.desc-ou {
  display: flex; align-items: center; gap: var(--esp-2);
  color: var(--muted); font-size: 13px; margin: var(--esp-3) 0 0;
}
.desc-ou::before, .desc-ou::after {
  content: ""; flex: 1; height: 1px; background: var(--line);
}
.desc-google {
  margin-top: var(--esp-2);
  background: var(--bg2); color: var(--fg);
  border: 1px solid var(--line);
}
.desc-google:hover:not(:disabled) { background: var(--bg3); }

/* ── o painel de Atalhos ────────────────────────────────────────────────────
   Vive dentro do `.modal` que o anúncio e o Radar já usam — inclusive a regra
   de tela cheia no celular, que é justamente onde uma lista de 13 endereços
   precisaria dela. Aqui só o conteúdo. */
.atalho-secao { margin-top: var(--esp-5); }
.atalho-secao:first-child { margin-top: var(--esp-3); }
.atalho-secao h3 {
  margin: 0; font-size: 12px; font-weight: 700;
  letter-spacing: .6px; text-transform: uppercase; color: var(--muted);
}
.atalho-secao .sub { margin: var(--esp-1) 0 var(--esp-2); font-size: 13px; }

/* Linha = destino (cresce) + copiar (não encolhe). O `gap` é o que impede o
   botão de encostar no texto quando a nota ocupa duas linhas no celular. */
.atalho {
  display: flex; align-items: center; gap: var(--esp-2);
  border-top: 1px solid var(--line); padding: var(--esp-2) 0;
}
.atalho-ir {
  flex: 1; min-width: 0;                 /* min-width:0 deixa o texto truncar */
  display: flex; flex-direction: column; gap: 2px;
  text-decoration: none; color: var(--fg);
  padding: var(--esp-1) var(--esp-2);
  margin: calc(var(--esp-1) * -1) calc(var(--esp-2) * -1);
  border-radius: var(--radius-sm);
}
.atalho-ir:hover { background: var(--bg3); }
.atalho-ir:focus-visible { outline: 2px solid var(--teal-dark); outline-offset: 1px; }
.atalho-rotulo { font-size: 14px; font-weight: 600; }
.atalho-nota { color: var(--muted); font-size: 12.5px; line-height: 1.45; }
/* A setinha de "sai deste app". Discreta porque a informação já está na nota da
   seção; ela serve a quem varre a lista sem ler. */
.atalho-fora { color: var(--muted); font-size: 11px; }
.atalho-copiar { flex: none; }

/* O convite ao comprador abre o painel porque é a única ação aqui que alguém
   faz VÁRIAS vezes por dia — as outras linhas são navegação. */
.atalho-convite {
  display: flex; align-items: center; gap: var(--esp-3); flex-wrap: wrap;
  background: var(--bg3); border: 1px solid var(--line);
  border-radius: var(--radius); padding: var(--esp-3);
}
.atalho-convite > div:first-child { flex: 1 1 240px; min-width: 0; }
.atalho-convite strong { font-size: 14px; }
.atalho-convite .sub { margin: 4px 0 0; font-size: 12.5px; }
.atalho-convite-acoes { display: flex; gap: var(--esp-2); flex-wrap: wrap; }

/* Na tela de Compradores o mesmo bloco respira acima da lista. */
.compr-convite { margin-bottom: var(--esp-3); }

@media (max-width: 480px) {
  /* Empilha: com o botão "Copiar" ao lado, uma nota de duas linhas espremia o
     rótulo em três. O alvo de toque continua o mesmo — o botão só muda de
     lugar, e vai para a direita, onde o polegar já está. */
  .atalho { flex-direction: column; align-items: stretch; gap: var(--esp-1); }
  .atalho-copiar { align-self: flex-end; }
  .atalho-convite-acoes { width: 100%; }
  .atalho-convite-acoes button { flex: 1; }
}
