/* =========================================================================
   Zeus Net — folha de estilo do site público
   Mobile first: tudo aqui nasce no celular; breakpoints só ampliam.
   As cores da marca são os tokens --c-* declarados no :root abaixo — nunca
   escreva hex de marca fora dali.
   FONTE: usamos a stack do sistema para não baixar webfont em 4G.
   Se o cliente aprovar uma fonte de marca (ex.: Archivo/Anton), auto-hospede
   em /public/fonts e troque --font-display abaixo.

   TESE DE MOVIMENTO: baralho. Cada escolha do site é uma carta puxada de um
   monte único. O deck de planos é o protagonista — é o único elemento com
   profundidade Z, rotação e arrasto. Todo o resto se move pouco, em Y.
   REGRA DE LAYOUT: nenhum módulo rola por dentro. Se a informação não cabe
   numa tela, ela vira carta do deck em vez de virar scroll.
   ========================================================================= */

/* Montserrat auto-hospedado (subset latin, arquivo variável 700–900).
   É a fonte das artes que o cliente publica: itálico pesado geométrico.
   Só o display usa webfont — o corpo continua na stack do sistema, então
   nada de texto invisível em 4G: `swap` + ~40KB por arquivo, e só o
   itálico entra no caminho crítico (preload no <head>). */
@font-face {
  font-family: "Montserrat";
  font-style: italic;
  font-weight: 700 900;
  font-display: swap;
  src: url("/fonts/montserrat-latin-italic-var.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+2000-206F, U+20AC, U+2122;
}
@font-face {
  font-family: "Montserrat";
  font-style: normal;
  font-weight: 700 900;
  font-display: swap;
  src: url("/fonts/montserrat-latin-var.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+2000-206F, U+20AC, U+2122;
}

:root {
  --font-display: "Montserrat", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-body: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

  /* CORES DA MARCA. Moravam em /theme.css, gerado pelo preset sazonal ativo no
     painel; a sazonalidade saiu do projeto em 2026-08-16 (ver CLAUDE.md) e com
     ela a rota, então os tokens vivem aqui — uma requisição a menos no caminho
     crítico. Continua valendo: nunca escreva hex de marca fora deste bloco,
     use as variáveis --c-*. */
  --c-blue: #023E7A;
  --c-blue-deep: #012C58;
  --c-blue-soft: #0A4F94;
  --c-yellow: #FEC504;
  --c-yellow-deep: #E0A800;
  --c-white: #FDFDFD;
  --c-accent: #FEC504;
  --c-accent-ink: #012C58;
  --hero-glow: rgba(254, 197, 4, .28);

  --ink: #10243c;
  --ink-soft: #4a5c72;
  --bg: #ffffff;
  --bg-soft: #f2f6fb;
  --line: #dde6f1;
  --wa: #25d366;
  --wa-dark: #1da851;
  /* verde legível como TEXTO sobre fundo claro (o #25d366 puro dá ~1.9:1 em
     branco). É a cor do WhatsApp em peso secundário — ver .btn-wa-soft. */
  --wa-deep: #0a6e35;

  --r-sm: 8px;
  --r-md: 14px;
  --r-lg: 22px;
  --r-pill: 999px;

  --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px;
  --sp-5: 24px; --sp-6: 32px; --sp-7: 48px; --sp-8: 64px; --sp-9: 88px;

  --shadow-sm: 0 2px 8px rgba(2, 62, 122, .08);
  --shadow-md: 0 10px 30px rgba(2, 62, 122, .12);
  --shadow-lg: 0 24px 60px rgba(2, 62, 122, .18);
  --shadow-card: 0 30px 70px -20px rgba(1, 44, 88, .45);

  --wrap: 1180px;
  --header-h: 60px;
  /* ease-out FORTE. O valor antigo (.22,.61,.36,1) era o `ease-out` nativo do
     CSS com outro nome: sai devagar demais no primeiro terço, justo o trecho
     que o olho está olhando, e por isso toda transição parecia "morna".
     Esta curva arranca e freia longo — é o que dá a impressão de que o site
     respondeu na hora. Se algum movimento ficar seco demais, é esta linha
     única que volta: cubic-bezier(.22, .61, .36, 1). */
  --ease: cubic-bezier(.23, 1, .32, 1);

  /* escala de movimento — todo o site cabe nestes 4 tempos + 2 curvas.
     entrada desacelera (--ease-out), saída acelera e é mais curta (--ease-in). */
  --dur-micro: 160ms;    /* hover, press, toggle */
  --dur-ui: 220ms;       /* tabs, city-switch, dots, faq */
  --dur-overlay: 420ms;  /* carta do deck assentando, menu mobile */
  --dur-section: 560ms;  /* scroll reveal */
  --ease-out: var(--ease);
  --ease-in: cubic-bezier(.64, 0, .78, .39);

  /* geometria do baralho — um token por eixo, sobrescrito no desktop */
  --deck-x: 13px;    /* deslocamento lateral por carta atrás */
  --deck-y: 11px;    /* afundamento por carta atrás */
  --deck-s: .055;    /* redução de escala por carta atrás */
  --deck-r: 2.2;     /* graus de rotação por carta atrás */
  --deck-w: min(88vw, 340px);

  /* altura útil de um módulo: a tela menos o cabeçalho fixo */
  --screen: calc(100svh - var(--header-h));

  /* CARD DE PLANO — tokens da arte do cliente.
     O laranja não é cor nova de marca: é o amarelo do tema puxado para o
     tom quente que aparece impresso nos posts. Derivado de --c-accent para
     que os presets sazonais continuem mandando na cor do card. */
  --c-plan-accent: color-mix(in srgb, var(--c-accent) 45%, #F1740A);
  --plan-frame-line: rgba(255, 255, 255, .78);
  --plan-ink-soft: rgba(255, 255, 255, .72);
  /* NÃO existe verde dentro do card. Verde é a cor de uma coisa só no site
     inteiro — abrir o WhatsApp (§ lei de cor, acima de .btn). O "até o
     vencimento" e o selo de simetria eram verdes e moravam a poucos pixels
     do .btn-wa: três verdes no mesmo card, um só clicável. Verde repetido
     em texto é verde gasto, e aí o botão deixa de significar ação.
     O destaque agora vem de peso + branco cheio; o acento fica no ícone,
     no laranja que o card já usa. */
  --plan-due: rgba(255, 255, 255, .94);
}

/* Propriedades animáveis: registradas para poderem transicionar/animar.
   `inherits: true` é obrigatório — quem lê estes valores são os
   pseudo-elementos e o filho .edge-light, não o próprio elemento. */
@property --cursor-angle { syntax: "<angle>"; inherits: true; initial-value: 45deg; }
@property --edge-proximity { syntax: "<number>"; inherits: true; initial-value: 0; }
/* POSIÇÃO do ponteiro dentro do botão, em %, e não mais um ângulo.
   O ângulo (--sb-angle) era o defeito visível do filete especular: numa
   pílula larga e baixa, a projeção ângulo→perímetro é grosseiramente
   desigual — as bordas longas engolem ~150° e as duas pontas arredondadas
   espremem os ~100° restantes num punhado de pixels. Resultado: do lado o
   brilho virava um borrão duro e curto ("o gradiente é curto e dá pra ver a
   sobra"), e no topo virava um lavado sem direção. Posição não distorce:
   o ponto de luz caminha na mesma velocidade em qualquer trecho da borda,
   em botão largo, estreito, grande ou pequeno. */
@property --sb-x { syntax: "<length-percentage>"; inherits: true; initial-value: 50%; }
@property --sb-y { syntax: "<length-percentage>"; inherits: true; initial-value: 0%; }

/* ------------------------------------------------------------------ reset */

*, *::before, *::after { box-sizing: border-box; }
html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: var(--header-h);
  /* SEM scroll-snap no documento. Existia um encaixe `y proximity` com um
     único alvo (o muro de #apps). Qualquer mudança de layout — trocar a aba
     Lite/X, por exemplo — faz o navegador reencaixar, e com um alvo só ele
     "teleportava" a página para o muro, muitas seções à frente. Um efeito
     de tela cheia não vale um salto de rolagem no meio da leitura. */
  /* `clip` e não `hidden`: corta o halo do card e as cartas de trás do
     baralho sem transformar o <html> em contêiner de rolagem (o que
     quebraria o header sticky e o scroll-behavior). */
  overflow-x: clip;
}
body {
  margin: 0;
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.55;
  color: var(--ink);
  background: var(--bg);
  /* clip (e não hidden): corta o estouro horizontal sem criar um container
     de scroll novo — o que quebraria o scroll-snap vertical do documento. */
  overflow-x: clip;
}
img, picture, svg { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }
button { font: inherit; cursor: pointer; }
h1, h2, h3 { font-family: var(--font-display); line-height: 1.1; letter-spacing: -.025em; margin: 0 0 var(--sp-3); font-weight: 800; }
p { margin: 0 0 var(--sp-3); }
ul { margin: 0; padding: 0; list-style: none; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 999;
  background: var(--c-yellow); color: var(--c-blue-deep); padding: 10px 16px; font-weight: 700;
}
.skip-link:focus { left: 8px; top: 8px; border-radius: var(--r-sm); }

:focus-visible { outline: 3px solid var(--c-accent); outline-offset: 2px; border-radius: 4px; }

.wrap { width: 100%; max-width: var(--wrap); margin-inline: auto; padding-inline: var(--sp-4); }
.wrap.narrow { max-width: 760px; }
.muted { color: var(--ink-soft); font-size: .92rem; }
.center-cta { text-align: center; margin-top: var(--sp-5); }

/* seção de tela cheia: sem snap, rolagem livre e natural */
.screen { padding-block: var(--sp-7); }

/* ---------------------------------------------------------------- botões

   LEI DE COR DOS BOTÕES (vale para o site inteiro — ver CLAUDE.md
   § "Coerência de CTA e de cidade"):

     verde  (.btn-wa / .btn-wa-soft) ⇔ abre o WhatsApp. SEMPRE, e SÓ.
     amarelo (.btn-primary) .......... navegação dentro do site.
     contorno/fantasma ............... ação secundária, sem sair do site.

   A lei é bidirecional: não existe ação de WhatsApp que não seja verde, e
   não existe botão verde que não abra o WhatsApp. Era ela que estava
   quebrada — a mesma ação (abrir conversa) aparecia amarela no hero e nas
   faixas de fechamento, e verde nos cards.

   PESO dentro do verde — no máximo UM verde sólido por seção:
     .btn-wa ....... o CTA principal daquela seção
     .btn-wa-soft .. repetição/apoio (a oferta já foi feita perto dali) */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 13px 22px; border: 2px solid transparent; border-radius: var(--r-pill);
  font-weight: 800; font-size: .95rem; letter-spacing: .01em; text-align: center;
  transition: transform var(--dur-micro) var(--ease-out), box-shadow var(--dur-micro) var(--ease-out),
              background-color var(--dur-micro) var(--ease-out), color var(--dur-micro) var(--ease-out),
              /* amortecimento do ponto de luz: a cada quadro o JS reescreve a
                 posição e a transição recomeça do valor corrente — isso é uma
                 suavização exponencial, o mesmo "peso" de uma mola, de graça.
                 `linear` porque a curva de cada quadro não deve ter curva
                 própria; e 90ms, não 220ms: mais que isso e a luz visivelmente
                 corre atrás do cursor em vez de acompanhá-lo.
                 O amortecimento só liga DEPOIS de aceso (--sb-damp): na
                 chegada do ponteiro a luz tem que já nascer no lugar certo,
                 e não deslizar da posição do hover anterior. */
              --sb-x var(--sb-damp, 0ms) linear, --sb-y var(--sb-damp, 0ms) linear;
}
.btn.is-lit { --sb-damp: 90ms; }
.btn .icon { width: 20px; height: 20px; flex: 0 0 auto; }
/* o levantar é resposta a PONTEIRO. No toque, `:hover` cola depois do tap e o
   botão fica pairando sozinho até o próximo toque em outro lugar. */
@media (hover: hover) and (pointer: fine) {
  .btn:hover { transform: translateY(-2px); }
}
.btn:active { transform: translateY(0) scale(.97); }

.btn-primary { background: var(--c-accent); color: var(--c-accent-ink); box-shadow: 0 8px 24px rgba(0, 0, 0, .18); }
.btn-primary:hover { background: var(--c-yellow-deep); }
.btn-wa { background: var(--wa); color: #062b17; }
.btn-wa:hover { background: var(--wa-dark); color: #fff; }
/* mesma ação, meio peso: continua verde (a lei manda), mas não disputa a
   atenção com o CTA principal da seção. Usado quando a oferta de WhatsApp
   já foi feita a uma ou duas dobras dali. */
.btn-wa-soft {
  background: color-mix(in srgb, var(--wa) 12%, transparent);
  border-color: color-mix(in srgb, var(--wa) 55%, transparent);
  color: var(--wa-deep);
}
.btn-wa-soft:hover { background: var(--wa); border-color: var(--wa); color: #062b17; }
/* sobre fundo escuro o texto verde-escuro some: aí o contorno vira o branco
   esverdeado e o preenchimento acende no hover. */
.section--dark .btn-wa-soft, .cta-band .btn-wa-soft, .hero .btn-wa-soft {
  color: #b8f5cf; border-color: color-mix(in srgb, var(--wa) 70%, transparent);
}
.btn-outline { border-color: currentColor; color: var(--c-blue); background: transparent; }
.btn-outline:hover { background: var(--c-blue); color: var(--c-white); border-color: var(--c-blue); }
.btn-ghost { border-color: rgba(255, 255, 255, .55); color: #fff; background: rgba(255, 255, 255, .08); backdrop-filter: blur(4px); }
.btn-ghost:hover { background: rgba(255, 255, 255, .18); }
.btn-lg { padding: 15px 26px; font-size: 1rem; }
.btn-sm { padding: 9px 16px; font-size: .85rem; }
.btn-block { width: 100%; }
.btn-link { color: var(--c-blue); font-weight: 800; font-size: .9rem; text-decoration: underline; text-underline-offset: 4px; }

/* ------------------------------------------------------- botão especular
   Porte do react-bits SpecularButton (JS+CSS) sem React e sem WebGL: o
   original desenha o brilho num shader com `ogl`, o que aqui custaria
   ~50kB de JS + um contexto GL por botão — inviável no 4G do interior.
   Leitura em CSS: ::after é o filete de luz nítido na borda, ::before é o
   mesmo filete borrado vazando pra fora, e a POSIÇÃO da luz (--sb-x/--sb-y)
   vem do ponteiro via site.js. Duas lombadas opostas porque, como no shader
   (`abs(dot)`), as duas bordas no eixo da luz refletem — mas a segunda é
   mais fraca, senão o botão fica com dois faróis do mesmo tamanho e some a
   informação de onde está o cursor.

   POR QUE POSIÇÃO E NÃO ÂNGULO — ver o comentário de @property --sb-x lá em
   cima. Resumo: gradiente cônico numa pílula distribui luz por ângulo, e
   ângulo não é distância no perímetro de um retângulo achatado.

   COR — a luz é da SUPERFÍCIE, não branca por padrão. Um halo branco em
   volta do amarelo do CTA lia como sujeira/fumaça sobre o azul do hero (a
   "cor errada"), e em volta do verde do WhatsApp descolorava o verde. Cada
   variante mistura branco COM a própria cor: continua sendo brilho
   especular (branco domina) mas pertence ao botão.
     --sb-line ..... o filete nítido, encostado na borda
     --sb-bloom .... o vazamento borrado pra fora (sempre mais fraco) */
.btn:not(.btn-link) { position: relative; isolation: isolate; }
/* :where() = especificidade ZERO. Sem isso o padrão (`.btn:not(.btn-link)`,
   0-2-0) venceria toda variante de cor (`.btn-primary`, 0-1-0) e todo botão
   voltaria a brilhar branco — o defeito que este bloco existe pra corrigir. */
:where(.btn) {
  --sb-line: rgba(255, 255, 255, .82);
  --sb-bloom: rgba(255, 255, 255, .5);
  /* raio do ponto de luz. Fixo em px (não em %) de propósito: em % o raio
     seria relativo à caixa e a mancha esticaria junto com o botão — a mesma
     distorção de aspecto que o ângulo trazia, só que de volta pela porta
     dos fundos. */
  --sb-size: 78px;
}
.btn-primary { --sb-line: color-mix(in srgb, #fff 62%, var(--c-accent)); --sb-bloom: color-mix(in srgb, var(--c-accent) 62%, transparent); }
.btn-wa      { --sb-line: color-mix(in srgb, #fff 66%, var(--wa)); --sb-bloom: color-mix(in srgb, var(--wa) 58%, transparent); }
.btn-outline { --sb-line: color-mix(in srgb, var(--c-blue) 72%, transparent); --sb-bloom: color-mix(in srgb, var(--c-blue) 34%, transparent); }
.btn-wa-soft { --sb-line: color-mix(in srgb, var(--wa) 78%, transparent); --sb-bloom: color-mix(in srgb, var(--wa) 40%, transparent); }
.btn-ghost   { --sb-bloom: rgba(255, 255, 255, .34); }
.btn-lg { --sb-size: 96px; }
.btn-sm { --sb-size: 58px; }

.btn:not(.btn-link)::before,
.btn:not(.btn-link)::after {
  content: ""; position: absolute; border-radius: inherit;
  pointer-events: none; opacity: 0;
  /* acender é convite (o ponteiro acabou de chegar), apagar é o sistema
     respondendo a uma decisão já tomada — sai mais rápido do que entra. */
  transition: opacity 190ms var(--ease-out);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
}
.btn:not(.btn-link):not(.is-lit)::before,
.btn:not(.btn-link):not(.is-lit)::after { transition-duration: 130ms; transition-timing-function: var(--ease-in); }

/* filete nítido, colado na borda. inset: -2px porque o elemento absoluto
   se ancora na padding box — sem isso o filete nasceria por dentro dos
   2px de borda do .btn, parecendo um anel interno em vez do contorno.
   A mancha principal segue o ponteiro; a de trás (posição espelhada) é o
   segundo reflexo, a 45% da força. */
.btn:not(.btn-link)::after {
  inset: -2px; padding: 1.4px;
  background:
    radial-gradient(var(--sb-size) var(--sb-size) at var(--sb-x) var(--sb-y),
      var(--sb-line) 0%, color-mix(in srgb, var(--sb-line) 45%, transparent) 46%, transparent 78%),
    radial-gradient(calc(var(--sb-size) * .82) calc(var(--sb-size) * .82) at calc(100% - var(--sb-x)) calc(100% - var(--sb-y)),
      color-mix(in srgb, var(--sb-line) 45%, transparent) 0%, transparent 74%);
}
/* vazamento de luz pra fora (o "rim glow" do shader).
   inset/padding maiores que o blur: a máscara tem borda dura, e borrar 6px
   um anel de 7px deixava o quadrado do próprio anel aparecendo nas quinas —
   era literalmente "a sobra" visível. Com 10px de folga o borrão morre
   dentro da própria caixa e o que sobra é só luz. */
.btn:not(.btn-link)::before {
  inset: -10px; padding: 11px; z-index: -1; filter: blur(9px);
  background:
    radial-gradient(calc(var(--sb-size) * 1.15) calc(var(--sb-size) * 1.15) at var(--sb-x) var(--sb-y),
      var(--sb-bloom) 0%, transparent 76%),
    radial-gradient(var(--sb-size) var(--sb-size) at calc(100% - var(--sb-x)) calc(100% - var(--sb-y)),
      color-mix(in srgb, var(--sb-bloom) 40%, transparent) 0%, transparent 74%);
}

.btn.is-lit::after { opacity: 1; }
.btn.is-lit::before { opacity: .62; }
/* SEM PONTEIRO, SEM ESPECULAR — e sem as camadas dele (2026-08-20). Quem
   acende `.is-lit` é o pointerenter, que ignora toque: no celular esses dois
   pseudos ficam eternamente em opacity 0. Só que não são caixas vazias — são
   duas camadas mascaradas (mask-composite) por botão, uma delas com
   `filter: blur(9px)`, e todo card de plano carrega um botão. No arrasto do
   baralho isso é peso de composição por uma luz que aquele aparelho nunca vai
   mostrar. */
@media (hover: none) {
  .btn:not(.btn-link)::before, .btn:not(.btn-link)::after { display: none; }
}
/* TECLADO NÃO GANHA ESPECULAR. O foco já tem o contorno de 3px do
   :focus-visible global, e um brilho posicional sem ponteiro fica parado
   num canto arbitrário — leitura de defeito, não de foco. */

/* O raio decorativo (`.bolt`) e sua animação `bolt-flash` saíram em
   2026-08-15, a pedido do cliente: o símbolo já está na logo, e repetido em
   cada eyebrow de cada página virava ruído piscando. Não recriar. */

/* ícones inline (lucide, sem dependência — ver views/partials/icon-*.ejs).
   Tamanho por padrão acompanha o texto ao redor; contextos com selo/pílula
   fixam um px explícito porque o texto ali já tem tamanho próprio. */
.icon { width: 1em; height: 1em; flex: 0 0 auto; vertical-align: -0.125em; display: inline-block; }

/* ----------------------------------------------------------------- ribbon */

.ribbon {
  background: linear-gradient(90deg, var(--c-accent), var(--c-yellow-deep));
  color: var(--c-blue-deep); text-align: center; font-weight: 700; font-size: .82rem;
}
.ribbon p { margin: 0; padding: 6px var(--sp-4); }

/* ----------------------------------------------------------------- header */

/* STICKY, e não `relative`: o site vende por WhatsApp e o botão de WhatsApp
   mora aqui. Cabeçalho que sai da tela é CTA que sai da tela — no celular,
   entre o hero e o card de plano, não sobrava nenhum caminho de conversão.

   ESCALA DE CAMADAS DO SITE (respeitar ao criar qualquer z-index novo):
     999  skip-link
     100  cabeçalho sticky
      90  WhatsApp flutuante
    ≤ 30  conteúdo que se empilha internamente (cartas do baralho, em JS)
   Empate de z-index é resolvido por ordem de DOM: uma carta em 100 GANHA do
   cabeçalho. Conteúdo nunca chega perto de 90. */
.site-header {
  position: sticky; top: 0; z-index: 100;
  background: var(--c-blue-deep);
  border-bottom: 1px solid rgba(255, 255, 255, .1);
  color: var(--c-white);
}
.header-inner { display: flex; align-items: center; gap: var(--sp-3); min-height: var(--header-h); }
/* a logo nunca encolhe: com o cabeçalho apertado o flex espremia a marca até 0px */
.brand { display: flex; align-items: center; margin-right: auto; flex-shrink: 0; }
.brand-logo { height: 38px; width: auto; }

/* Cidade + WhatsApp + hambúrguer: irmãos, sempre visíveis, fora do menu.
   Nenhum dos três pode morar dentro do hambúrguer — com o menu fechado (o
   estado normal no celular) o cliente não veria nem para qual cidade está
   vendo preço, nem por onde falar com a loja. */
.header-actions { display: flex; align-items: center; gap: var(--sp-2); }

/* UM ponto de WhatsApp PERSISTENTE por viewport (ver .wa-float logo abaixo).
   No celular quem cumpre esse papel é o flutuante, na zona do polegar; aqui
   em cima o botão só duplicaria o mesmo link a dois toques de distância e
   ainda roubaria a largura de que a pílula de cidade precisa. */
.header-wa { display: none; padding: 9px 13px; }
.header-wa .icon { width: 18px; height: 18px; }
.header-wa-text { display: inline; }

/* CENTRAL DO CLIENTE, mesma lei de "um por viewport": no celular o item vive
   dentro do menu (é serviço de quem já é cliente, não a conversão — por isso
   pode ficar no hambúrguer, ao contrário do WhatsApp), no computador vira
   botão de contorno no cabeçalho. Nunca os dois ao mesmo tempo. */
/* contorno claro: `.btn-outline` nasce com texto azul, para fundo claro — em
   cima do cabeçalho azul ele fica ilegível (mesmo motivo do override da
   faixa de cobertura). */
.header-portal {
  display: none; padding: 9px 13px;
  color: var(--c-white); border-color: rgba(255, 255, 255, .5);
}
.header-portal:hover { background: var(--c-white); color: var(--c-blue-deep); border-color: var(--c-white); }

.nav-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; background: rgba(255, 255, 255, .1);
  border: 0; border-radius: var(--r-sm); color: var(--c-white);
  transition: background-color var(--dur-micro) var(--ease-out), transform var(--dur-micro) var(--ease-out);
}
.nav-toggle:active { transform: scale(.94); }
.nav-toggle-bars, .nav-toggle-bars::before, .nav-toggle-bars::after {
  display: block; width: 20px; height: 2px; background: currentColor; border-radius: 2px;
  transition: transform var(--dur-micro) var(--ease-out), opacity var(--dur-micro) var(--ease-out),
              background-color var(--dur-micro) var(--ease-out);
}
.nav-toggle-bars { position: relative; }
.nav-toggle-bars::before, .nav-toggle-bars::after { content: ""; position: absolute; left: 0; }
.nav-toggle-bars::before { top: -6px; }
.nav-toggle-bars::after { top: 6px; }
/* a barra do meio some por COR (background-color entrou na lista de
   transições acima), nunca por opacity: as outras duas barras são
   pseudo-elementos DELA, e opacity no pai apagaria o X inteiro. Antes a cor
   não transicionava e ela sumia de um quadro pro outro — era a única parte
   do gesto sem continuidade. */
.nav-toggle[aria-expanded="true"] .nav-toggle-bars { background: transparent; }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars::before { transform: translateY(6px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars::after { transform: translateY(-6px) rotate(-45deg); }

.nav {
  position: absolute; left: 0; right: 0; top: 100%;
  background: var(--c-blue-deep); border-bottom: 3px solid var(--c-accent);
  padding: var(--sp-4); display: none;
  opacity: 0; transform: translateY(-8px);
  /* saída: 180ms. Fechar o menu é o sistema respondendo a uma decisão já
     tomada — o cliente quer a página de volta, não a despedida do painel.
     Estava em 290ms e a entrada em 420ms; nenhum menu de navegação precisa
     de quase meio segundo (a faixa útil de um dropdown é 150–250ms). */
  transition: opacity 180ms var(--ease-in), transform 180ms var(--ease-in), display 180ms allow-discrete;
}
.nav.is-open {
  display: block; opacity: 1; transform: none;
  transition: opacity 260ms var(--ease-out), transform 260ms var(--ease-out), display 260ms allow-discrete;
}
@starting-style {
  .nav.is-open { opacity: 0; transform: translateY(-8px); }
}

.nav-list { display: flex; flex-direction: column; gap: 2px; margin-bottom: var(--sp-4); }
.nav-list a {
  display: block; padding: 12px 10px; border-radius: var(--r-sm); font-weight: 700;
  color: rgba(255, 255, 255, .92);
}
.nav-list a:hover, .nav-list a.is-current { background: rgba(255, 255, 255, .1); color: var(--c-accent); }

/* -------------------------------------------------- WhatsApp flutuante
   Requisito do briefing §5. É o CTA de 1 toque em qualquer ponto de
   qualquer página — NO CELULAR. No desktop quem faz esse papel é o botão
   do cabeçalho fixo, que já está permanentemente à vista e no alcance do
   ponteiro; os dois juntos eram o mesmo link duas vezes na mesma tela.
   Regra: exatamente um WhatsApp persistente por viewport.
   z-index 90: abaixo do cabeçalho (100), acima de todo conteúdo.
   Some quando a faixa de CTA final entra na tela (site.js, .is-hidden):
   dois botões verdes empilhados no fim da página é ruído. */

.wa-float {
  position: fixed; z-index: 90;
  right: var(--sp-4); bottom: max(var(--sp-4), calc(env(safe-area-inset-bottom) + var(--sp-2)));
  display: inline-flex; align-items: center; gap: 10px;
  padding: 14px; border-radius: var(--r-pill);
  background: var(--wa); color: #062b17;
  font-weight: 800; font-size: .92rem; line-height: 1;
  box-shadow: 0 14px 34px rgba(0, 0, 0, .3);
  transition: transform var(--dur-ui) var(--ease-out), opacity var(--dur-ui) var(--ease-out),
              background-color var(--dur-micro) var(--ease-out), color var(--dur-micro) var(--ease-out);
}
.wa-float .icon { width: 26px; height: 26px; }
.wa-float-text { display: none; }
/* o flutuante só existe abaixo de 900px, ou seja: quase sempre num aparelho
   de toque. `:hover` ali gruda depois do tap e o botão fica pairando. */
@media (hover: hover) and (pointer: fine) {
  .wa-float:hover { background: var(--wa-dark); color: #fff; transform: translateY(-2px); }
}
.wa-float:active { transform: scale(.96); }
.wa-float.is-hidden { opacity: 0; transform: translateY(14px) scale(.9); pointer-events: none; }

/* ------------------------------------------- cidade no cabeçalho (indicador)

   ONDE A CIDADE APARECE (ver CLAUDE.md § "Coerência de CTA e de cidade"):
     cabeçalho ...... INDICADOR. Diz em que cidade você está vendo o site e,
                      só se pedirem, troca. Peso terciário, sem amarelo.
     home §planos e /planos ... SELETOR explícito (.city-switch), colado no
                      preço — o único lugar em que trocar de cidade muda o
                      que está na tela.
     /cobertura, /diferenciais, /contato ... nenhum seletor: nada ali depende
                      da cidade escolhida.

   O par de pílulas amarelas que morava aqui era o terceiro bloco a disputar
   a mesma barra (menu + cidade + WhatsApp), usava o amarelo reservado a CTA
   e, no celular, ficava escondido dentro do hambúrguer. O indicador é uma
   pílula só e não cresce quando a operação chegar a 12 praças. */

.city-pin { position: relative; }
/* Maior e com borda clara desde 2026-09-14, a pedido do cliente: o botão diz
   "Sua localização" e precisa se destacar. Continua SEM amarelo e sem verde —
   amarelo é ação de conversão e verde é WhatsApp (lei de cor). 44px de altura
   = mesma do botão do menu ao lado, e alvo de toque confortável. */
.city-pin-btn, .city-pin-static {
  display: inline-flex; align-items: center; gap: 8px;
  min-height: 44px; padding: 10px 16px; border-radius: var(--r-pill);
  font: inherit; font-weight: 800; font-size: .92rem; line-height: 1;
  color: #fff; background: rgba(255, 255, 255, .14);
  border: 1.5px solid rgba(255, 255, 255, .42);
  transition: background-color var(--dur-micro) var(--ease-out), color var(--dur-micro) var(--ease-out),
              transform var(--dur-micro) var(--ease-out);
}
.city-pin-btn { cursor: pointer; }
.city-pin-btn:hover { background: rgba(255, 255, 255, .22); border-color: rgba(255, 255, 255, .7); color: #fff; }
.city-pin-btn:active { transform: scale(.97); }
.city-pin-btn .icon { width: 18px; height: 18px; color: var(--c-accent); }
.city-pin-static .icon { width: 18px; height: 18px; color: var(--c-accent); }
.city-pin-name { max-width: 12ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* texto fixo: nunca reticências ("Sua localiza…") */
.city-pin-label { white-space: nowrap; }
.city-pin-caret {
  width: 0; height: 0; margin-left: 1px;
  border-left: 4px solid transparent; border-right: 4px solid transparent;
  border-top: 5px solid currentColor; opacity: .7;
  transition: transform var(--dur-micro) var(--ease-out);
}
.city-pin-btn[aria-expanded="true"] .city-pin-caret { transform: rotate(180deg); }

/* popover ancorado no gatilho: nasce da pílula (transform-origin no canto
   de cima à direita), não do centro da tela. */
.city-pin-menu {
  position: absolute; top: calc(100% + 8px); right: 0; z-index: 5;
  min-width: 190px; max-height: 62svh; overflow-y: auto;
  padding: var(--sp-2); border-radius: var(--r-md);
  background: #fff; color: var(--ink); box-shadow: var(--shadow-lg);
  transform-origin: top right;
  opacity: 1; transform: none;
  transition: opacity var(--dur-micro) var(--ease-out), transform var(--dur-micro) var(--ease-out),
              display var(--dur-micro) allow-discrete;
}
/* saída mais curta que a entrada: ao abrir o cliente está esperando o menu,
   ao fechar ele já decidiu e quer a tela de volta. */
.city-pin-menu[hidden] {
  display: none; opacity: 0; transform: scale(.96) translateY(-4px);
  transition-duration: 120ms; transition-timing-function: var(--ease-in);
}
@starting-style {
  .city-pin-menu { opacity: 0; transform: scale(.96) translateY(-4px); }
}
.city-pin-title {
  margin: 0; padding: 6px 10px 8px;
  font-size: .7rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-soft);
}
.city-pin-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.city-pin-list a {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  padding: 9px 10px; border-radius: var(--r-sm);
  font-weight: 700; font-size: .88rem; color: var(--c-blue);
  transition: background-color var(--dur-micro) var(--ease-out);
}
.city-pin-list a:hover { background: var(--bg-soft); }
.city-pin-list a[aria-current="true"] { background: rgba(2, 62, 122, .08); }
.city-pin-list a[aria-current="true"]::after { content: "✓"; color: var(--wa-deep); }
.city-pin-uf { font-size: .72rem; font-weight: 700; color: var(--ink-soft); }

/* ------------------------------------------------------- seletor de cidade */

.city-switch { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.city-switch-label { font-weight: 700; font-size: .9rem; }
.city-switch-options {
  display: inline-flex; padding: 4px; gap: 4px;
  background: rgba(255, 255, 255, .12); border-radius: var(--r-pill);
}
.city-option {
  padding: 8px 16px; border-radius: var(--r-pill); font-weight: 700; font-size: .86rem;
  color: inherit; white-space: nowrap;
  transition: background-color var(--dur-ui) var(--ease-out), color var(--dur-ui) var(--ease-out),
              transform var(--dur-micro) var(--ease-out);
}
.city-option:active { transform: scale(.96); }
.city-option.is-active { background: var(--c-accent); color: var(--c-accent-ink); }

/* formato LISTA — entra quando a operação passa de poucas praças (o painel
   decide em Configurações). Em pílulas, 12 cidades viram três linhas de botão
   no celular e empurram o hero para fora da tela. */
.city-switch--list { gap: var(--sp-2); }
.city-select {
  font: inherit; font-weight: 700; font-size: .88rem; color: var(--c-blue);
  padding: 8px 12px; max-width: 60vw;
  background: #fff; border: 2px solid transparent; border-radius: var(--r-pill);
  transition: border-color var(--dur-ui) var(--ease-out);
}
.city-select:focus-visible { border-color: var(--c-accent); outline: none; }
.city-switch-go {
  font: inherit; font-weight: 700; font-size: .82rem; cursor: pointer;
  padding: 8px 14px; color: var(--c-accent-ink); background: var(--c-accent);
  border: 0; border-radius: var(--r-pill);
  transition: background-color var(--dur-micro) var(--ease-out), transform var(--dur-micro) var(--ease-out);
}
.city-switch-go:active { transform: scale(.96); }

/* fora do header (fundo claro) */
.section-head .city-switch, .plans-copy .city-switch { margin-top: var(--sp-4); }
.section-head .city-switch { justify-content: center; }
.section-head .city-switch-options,
.plans-copy .city-switch-options { background: rgba(2, 62, 122, .1); }
.section-head .city-option:not(.is-active),
.plans-copy .city-option:not(.is-active) { color: var(--c-blue); }
/* page-hero fundo escuro — pílula clara p/ contraste, texto herda branco */
.page-hero .city-switch-options { background: rgba(255, 255, 255, .12); }

/* =========================================================================
   BARALHO (deck)
   Progressive enhancement: sem JS o markup é uma faixa rolável nativa —
   funciona, é acessível e não esconde nada. Com JS o container ganha
   .is-live e as cartas viram um monte único empilhado.
   ========================================================================= */

.deck { position: relative; }

/* O baralho é gesto, não texto. `user-select: none` só no card não bastava:
   o Chrome deixa uma seleção COMEÇADA fora (na folga do monte) invadir o
   card mesmo assim, e o arrasto pintava blocos azuis por cima das cartas.
   Então o bloqueio sobe pro deck inteiro e o ::selection transparente
   apaga qualquer resto que ainda escape. O JS ainda cancela `selectstart`
   e `dragstart` no monte — CSS sozinho não impede o gesto de virar
   seleção em todos os navegadores. */
.deck, .deck-stack, .deck-card {
  user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;
}
.deck ::selection, .deck::selection { background: transparent; }
.deck ::-moz-selection { background: transparent; }

/* --- estado base (sem JS): faixa rolável com snap nativo --- */
.deck-stack {
  display: flex; gap: var(--sp-4);
  overflow-x: auto; scroll-snap-type: x mandatory;
  scrollbar-width: none; -ms-overflow-style: none;
  padding-block: var(--sp-2) var(--sp-4);
}
.deck-stack::-webkit-scrollbar { display: none; }
.deck-card { flex: 0 0 auto; width: var(--deck-w); scroll-snap-align: center; display: flex; }
.deck-card > * { flex: 1 1 auto; min-width: 0; }

/* --- estado ativo (com JS): monte empilhado --- */
.deck.is-live .deck-stack {
  display: block; position: relative;
  /* a altura precisa cobrir também o afundamento das cartas de trás,
     senão a barra de controle senta em cima do monte */
  height: calc(var(--deck-h, 460px) + var(--deck-y) * 3 + 22px);
  overflow: visible;
  scroll-snap-type: none;
  padding: 0;
  touch-action: pan-y;               /* o eixo X é nosso; o Y continua do usuário */
}
@media (hover: hover) and (pointer: fine) {
  .deck.is-live .deck-stack { cursor: grab; }
  .deck.is-live .deck-stack.is-dragging { cursor: grabbing; }
}

.deck.is-live .deck-card {
  position: absolute; top: 0; left: 50%; width: var(--deck-w);
  /* TODA carta tem exatamente a altura da maior — `height`, não `min-height`.
     Com mínimo, cada plano ficava com a sua própria altura e o card mudava
     de tamanho a cada arrasto e a cada troca de linha. O JS zera --deck-h
     antes de medir, então isto não se realimenta. */
  height: var(--deck-h, auto);
  margin: 0;
  /* pivô na base: as cartas giram como um leque apoiado na mesa, e a pilha
     não invade o espaço da barra de controle logo abaixo */
  transform-origin: 50% 100%;
  transform:
    translate3d(calc(-50% + var(--o) * var(--deck-x) + var(--dx, 0) * 1px), calc(var(--a) * var(--deck-y)), 0)
    rotate(calc((var(--o) * var(--deck-r) + var(--rot, 0)) * 1deg))
    scale(calc(1 - var(--a) * var(--deck-s)));
  opacity: calc(1 - var(--a) * .26);
  /* `filter` SAIU da lista de transições. Filtro não compõe na GPU: animar a
     dessaturação das cartas de trás repinta três cartas inteiras (fundo em
     SVG, gradientes, sombra) a cada quadro do gesto, e era isso que travava a
     rolagem no celular. O véu chapado (::after, logo abaixo) já faz a
     passagem em opacidade — a saturação pode virar de um quadro pro outro por
     baixo dele que ninguém vê. */
  transition:
    transform var(--dur-overlay) var(--ease-out),
    opacity var(--dur-ui) var(--ease-out);
}
/* cartas atrás: sem foco, sem clique, sem leitor de tela (via `inert` no JS) */
.deck.is-live .deck-card[data-state="behind"] { pointer-events: none; }
/* dessaturar as cartas de trás é DESKTOP-ONLY (2026-08-20). `filter` num
   elemento que se transforma a cada quadro do arrasto obriga o navegador a
   manter uma superfície de filtro por carta e refiltrar a composição — e são
   até três cartas atrás mais a da frente, no meio do gesto. Quem separa o
   monte da carta ativa no celular é o véu chapado (::after, logo abaixo), que
   é uma cor sólida por cima: mesma leitura de "papel empilhado", custo de
   pintura de um retângulo. */
@media (hover: hover) and (pointer: fine) {
  .deck.is-live .deck-card[data-state="behind"] { filter: saturate(.55); }
}

/* véu sobre as cartas de trás: um monte tem que ler como PAPEL empilhado, e
   não como três CTAs verdes espiando por baixo do da frente — era exatamente
   isso que aparecia, uma escadinha de fatias verdes no canto inferior.
   Dessaturar sozinho não bastava: o verde do .btn-wa é forte demais.
   Véu chapado em vez de blur — o monte inteiro se transforma a cada quadro
   do arrasto, e blur aí custa caro (Safari principalmente).
   O véu mora em TODA carta com opacidade 0 e só acende no estado "behind":
   assim ele transiciona quando a carta assume a frente, em vez de sumir de
   um quadro pro outro quando o seletor deixa de casar. */
.deck.is-live .deck-card::after {
  content: ""; position: absolute; inset: 0; border-radius: var(--r-lg);
  background: color-mix(in srgb, var(--c-blue-deep) 58%, transparent);
  opacity: 0; pointer-events: none;
  transition: opacity var(--dur-overlay) var(--ease-out);
}
.deck.is-live .deck-card[data-state="behind"]::after { opacity: 1; }
/* cartas já passadas saem pela esquerda — o monte anda, não some */
.deck.is-live .deck-card[data-state="past"] {
  pointer-events: none; opacity: 0;
  transform: translate3d(calc(-50% - 120%), 0, 0) rotate(-14deg) scale(.9);
}
/* fora do alcance visual do monte */
.deck.is-live .deck-card[data-state="hidden"] { pointer-events: none; opacity: 0; }
/* durante o arrasto a carta segue o dedo em tempo real: nada de transição */
.deck.is-live .deck-stack.is-dragging .deck-card { transition: none; }

/* barra de controle: setas + pontinhos, sempre ABAIXO do monte
   (nada de seta sobreposta à carta, que era o que atrapalhava no celular) */
.deck-ui {
  display: flex; align-items: center; justify-content: center; gap: var(--sp-4);
  margin-top: var(--sp-4);
}
.deck-nav {
  width: 44px; height: 44px; flex: 0 0 auto; border: 0; border-radius: 50%;
  background: #fff; color: var(--c-blue); box-shadow: var(--shadow-md);
  display: grid; place-items: center;
  transition: transform var(--dur-micro) var(--ease-out), opacity var(--dur-micro) var(--ease-out), background-color var(--dur-micro) var(--ease-out);
}
.deck-nav::before {
  content: ""; width: 10px; height: 10px;
  border-right: 3px solid currentColor; border-bottom: 3px solid currentColor;
}
.deck-nav--prev::before { transform: rotate(135deg) translate(-2px, -2px); }
.deck-nav--next::before { transform: rotate(-45deg) translate(-2px, -2px); }
@media (hover: hover) and (pointer: fine) {
  .deck-nav:hover:not(:disabled) { background: var(--c-accent); color: var(--c-accent-ink); transform: scale(1.06); }
}
/* a seta precisa responder ao dedo: no celular não existe hover, e sem o
   afundar do press o botão parece que não recebeu o toque */
.deck-nav:active:not(:disabled) { transform: scale(.94); }
.deck-nav:disabled { opacity: .28; cursor: default; box-shadow: none; }

/* a seta "próximo" pisca enquanto ninguém mexeu no baralho: é o convite.
   Para no primeiro uso (.is-used) e não existe para quem pediu menos motion. */
.deck.is-live:not(.is-used) .deck-nav--next:not(:disabled) { animation: deck-call 2.6s var(--ease-out) infinite; }
/* animação ganha de transição no mesmo transform: com o convite rodando, o
   hover e o press da seta não apareciam. Quem já está com a mão no botão não
   precisa mais de convite — o convite sai. */
.deck-nav--next:hover, .deck-nav--next:active { animation: none; }
@keyframes deck-call {
  0%, 62%, 100% { transform: none; box-shadow: var(--shadow-md); }
  72% { transform: translateX(3px); box-shadow: var(--shadow-lg); }
  86% { transform: translateX(0); }
}

/* pontinhos do baralho: mesma receita dos do carrossel (ver .carousel-dot) —
   botão de 24×24 como área de toque, ponto de 8px desenhado no ::before */
.deck-dots { display: flex; align-items: center; gap: 0; }
.deck-dot {
  position: relative;
  width: 24px; height: 24px; margin-block: -8px; padding: 0; border: 0;
  background: none;
  transition: transform var(--dur-micro) var(--ease-out);
}
.deck-dot::before {
  content: ""; position: absolute; top: 50%; left: 50%;
  width: 8px; height: 8px; border-radius: 50%;
  background: rgba(2, 62, 122, .24);
  transform: translate(-50%, -50%);
  transition: width var(--dur-ui) var(--ease-out), background-color var(--dur-ui) var(--ease-out);
}
.deck-dot.is-active::before { width: 24px; border-radius: var(--r-pill); background: var(--c-accent); }
.deck-dot:active { transform: scale(.86); }
.deck--on-dark .deck-dot::before { background: rgba(255, 255, 255, .32); }
.deck--on-dark .deck-dot.is-active::before { background: var(--c-accent); }

/* dica escrita: diz o gesto que o desenho não consegue dizer ("dá pra
   arrastar"). Some no primeiro uso — instrução repetida vira ruído.
   A altura fica reservada: sumir não pode mexer no layout do módulo. */
.deck-hint {
  margin: var(--sp-2) 0 0; min-height: 1.05rem;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  font-size: .76rem; font-weight: 700; color: var(--ink-soft); text-align: center;
  transition: opacity var(--dur-ui) var(--ease-out);
}
.deck.is-used .deck-hint { opacity: 0; }
.deck--on-dark .deck-hint { color: rgba(255, 255, 255, .68); }
/* mãozinha que corre de um lado ao outro: no celular é ela que ensina, não o texto */
.deck-hint::before {
  content: ""; flex: 0 0 auto; width: 22px; height: 10px; border-radius: var(--r-pill);
  background: currentColor; opacity: .3;
  animation: deck-swipe 2.6s var(--ease-out) infinite;
}
@keyframes deck-swipe {
  0%, 100% { transform: translateX(5px) scaleX(.75); opacity: .18; }
  30% { transform: translateX(-6px) scaleX(1); opacity: .42; }
  70% { transform: translateX(5px) scaleX(.75); opacity: .18; }
}
/* (o bloco global de prefers-reduced-motion, no fim do arquivo, já mata as
   duas animações acima — não repetir a regra aqui) */

/* --------------------------------------------------------------- carrossel
   Usado onde o conteúdo é uma faixa de imagens (logos, fotos): o baralho
   não serve para item pequeno e repetido. Rolagem nativa + snap. */

.carousel { position: relative; }
.carousel-track {
  display: flex; gap: var(--sp-4);
  overflow-x: auto; scroll-snap-type: x mandatory;
  scrollbar-width: none; -ms-overflow-style: none;
  scroll-behavior: smooth;
}
.carousel-track::-webkit-scrollbar { display: none; }
@media (hover: hover) and (pointer: fine) {
  .carousel-track { cursor: grab; }
  .carousel-track.is-dragging { cursor: grabbing; scroll-snap-type: none; scroll-behavior: auto; }
}
.carousel-slide { scroll-snap-align: center; flex: 0 0 auto; }

.carousel-arrow {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 44px; height: 44px; border-radius: 50%;
  /* borda + sombra forte: a seta passa por cima de cards brancos (logos),
     e sem separação ela sumia dentro do card */
  border: 1px solid var(--line);
  background: #fff; color: var(--c-blue); box-shadow: var(--shadow-md);
  display: none; z-index: 3;
  transition: opacity var(--dur-micro) var(--ease-out), background-color var(--dur-micro) var(--ease-out),
              transform var(--dur-micro) var(--ease-out);
}
/* mesma resposta ao dedo da seta do baralho — as duas fazem a mesma coisa e
   estavam respondendo de jeitos diferentes. */
@media (hover: hover) and (pointer: fine) {
  .carousel-arrow:hover:not(:disabled) { transform: translateY(-50%) scale(1.06); }
}
.carousel-arrow:active:not(:disabled) { transform: translateY(-50%) scale(.94); }
.carousel-arrow::before {
  content: ""; display: block; width: 10px; height: 10px; margin: auto;
  border-right: 3px solid currentColor; border-bottom: 3px solid currentColor;
}
.carousel-arrow--prev { left: 8px; }
.carousel-arrow--prev::before { transform: rotate(135deg) translate(-2px, -2px); }
.carousel-arrow--next { right: 8px; }
.carousel-arrow--next::before { transform: rotate(-45deg) translate(-2px, -2px); }
.carousel-arrow:disabled { opacity: .25; cursor: default; }
/* o atributo hidden do UA perde para `display: grid` do breakpoint;
   sem este reforço a seta continuava visível quando não havia o que rolar */
.carousel-arrow[hidden], .deck-nav[hidden] { display: none; }

/* PONTINHOS: o ponto que se vê continua com 8px, mas o botão — a área de
   toque — tem 24×24, o mínimo de alvo da WCAG 2.2 (2.5.8). Com o botão do
   tamanho do ponto, acertar 8×8 com o polegar era sorte (medido na varredura
   de 2026-09-10). O desenho mora no ::before; o botão é só a área. A margem
   vertical negativa devolve à linha a altura de antes — a área cresce sem
   empurrar o layout — e o gap zerou porque o próprio botão já separa os
   pontos. Mesma receita nos pontinhos do baralho (.deck-dot). */
.carousel-dots { display: flex; justify-content: center; gap: 0; margin-top: var(--sp-4); }
.carousel-dot {
  position: relative;
  width: 24px; height: 24px; margin-block: -8px; padding: 0; border: 0;
  background: none;
}
.carousel-dot::before {
  content: ""; position: absolute; top: 50%; left: 50%;
  width: 8px; height: 8px; border-radius: 50%;
  background: rgba(2, 62, 122, .24);
  transform: translate(-50%, -50%);
  transition: width var(--dur-ui) var(--ease-out), background-color var(--dur-ui) var(--ease-out);
}
.carousel-dot.is-active::before { width: 24px; border-radius: var(--r-pill); background: var(--c-accent); }

/* --------------------------------------------------------------- hero */

/* O hero é uma coluna: o carrossel ocupa a sobra e a barra de confiança
   fica encostada na base. Assim o bloco inteiro cabe em UMA tela — banner,
   promessa, CTA e prova social sem nenhuma rolagem. */
/* A ALTURA DO TOPO SEGUE A ARTE, limitada a uma tela.
   Era `min-height: var(--screen)` puro, e isso só passava despercebido enquanto
   o banner era placeholder: uma tela de celular é ~9:19, a arte publicada é
   900×1100 (~9:11), então o `object-fit: cover` do `.hero-media` comia mais de
   um terço da largura — no primeiro banner real do cliente a manchete e o preço
   saíram cortados dos dois lados. O corte não é escolha do painel: a medida da
   peça está no gabarito de `/admin/banners`, e o topo tem que respeitá-la.
   Por isso a altura é o MENOR entre uma tela e a altura que a arte pede na
   largura atual — a proporção de cada breakpoint é a do `<source>` do
   `<picture>` no hero (celular 900×1100, ≥768px 1920×900).
   Continua sendo `min-height`: banner com texto longo cresce por dentro. */
.hero {
  background: var(--c-blue-deep); color: var(--c-white); position: relative;
  display: flex; flex-direction: column;
  min-height: min(var(--screen), calc(100vw * 1100 / 900));
}
@media (min-width: 768px) {
  .hero { min-height: min(var(--screen), calc(100vw * 900 / 1920)); }
}
.carousel--hero { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.carousel--hero .carousel-track { gap: 0; flex: 1 1 auto; min-height: 0; align-items: stretch; }
/* UM GESTO = UM BANNER. O snap sozinho só diz ONDE parar, não QUANTOS pular:
   no celular o impulso do dedo atravessa dois ou três slides de uma vez e o
   banner do meio nunca chega a ser visto. `scroll-snap-stop: always` obriga o
   navegador a encostar no próximo ponto de parada antes de aceitar o gesto
   seguinte. Só no hero: em faixa de itens pequenos (logos) isso deixaria a
   rolagem dura, porque lá vários itens cabem na tela ao mesmo tempo. */
.carousel--hero .carousel-slide { scroll-snap-stop: always; }
.hero-slide {
  position: relative; width: 100%;
  min-height: min(420px, 62svh);
  display: flex; flex-direction: column;
}
.hero-media { position: absolute; inset: 0; }
.hero-media img { width: 100%; height: 100%; object-fit: cover; }
/* Só azul sob a foto — raio nunca vira névoa em cima da imagem, isso lá em
   baixo à esquerda ficava um borrão amarelo-esverdeado sobre a cena. Escurece
   de cima pra baixo só o necessário pra ler o texto.

   ESCURECIMENTO É ESCOLHA DO PAINEL (2026-08-29). Antes o véu forte caía em
   TODO banner, sempre — o que destruía arte que já chega pronta e clara do
   designer. Agora são quatro estados, e a view resolve "automático" antes de
   escrever a classe: com texto (ou com o botão 2, que é translúcido) vira
   forte; só a arte, ou a arte com o botão 1 sólido, vira nenhum. */
.hero-slide--ov-forte::before {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(180deg, rgba(1, 32, 63, .35) 0%, rgba(1, 32, 63, .78) 55%, rgba(1, 32, 63, .96) 100%);
}
.hero-slide--ov-leve::before {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(180deg, rgba(1, 32, 63, .1) 0%, rgba(1, 32, 63, .34) 60%, rgba(1, 32, 63, .5) 100%);
}
/* `--ov-nenhum` não desenha véu nenhum: é o banner só de imagem, com a arte
   fechada pelo designer. */
/* Cantos em colchete: a mesma moldura das artes que o cliente publica
   (ver card_exemplo.jpeg) e do stage-frame de /cobertura — o raio some da
   névoa e reaparece aqui, nítido, como assinatura em vez de mancha. */
.hero-slide::after {
  content: ""; position: absolute; inset: 16px; z-index: 3; pointer-events: none;
  border: 2px solid color-mix(in srgb, var(--c-accent) 85%, transparent);
  border-radius: 16px;
  -webkit-mask:
    linear-gradient(#000 0 0) top left / 40px 40px no-repeat,
    linear-gradient(#000 0 0) top right / 40px 40px no-repeat,
    linear-gradient(#000 0 0) bottom left / 40px 40px no-repeat,
    linear-gradient(#000 0 0) bottom right / 40px 40px no-repeat;
  mask:
    linear-gradient(#000 0 0) top left / 40px 40px no-repeat,
    linear-gradient(#000 0 0) top right / 40px 40px no-repeat,
    linear-gradient(#000 0 0) bottom left / 40px 40px no-repeat,
    linear-gradient(#000 0 0) bottom right / 40px 40px no-repeat;
}
/* A moldura de colchete vira liga/desliga no painel: arte que já vem com
   moldura própria ficava com duas. */
.hero-slide--sem-moldura::after { content: none; }

/* A ARTE INTEIRA CLICÁVEL. O link cobre o slide; o bloco de texto deixa o
   toque passar por baixo, mas os botões continuam por cima — nunca os dois
   disputando o mesmo dedo. O carrossel é arrastado com o mesmo dedo, então o
   JS cancela o clique quando a faixa acabou de rolar (public/js/site.js). */
.hero-link { position: absolute; inset: 0; z-index: 2; }
.hero-slide--clicavel .hero-content { pointer-events: none; }
.hero-slide--clicavel .hero-content .btn { pointer-events: auto; }

.hero-content {
  position: relative; z-index: 3; flex: 1 1 auto;
  display: flex; flex-direction: column; justify-content: center;
  padding-block: var(--sp-6) var(--sp-7);
}
/* POSIÇÃO DO BLOCO — grade 3×3 escolhida no painel, sempre por classe: a CSP
   do site bloqueia `style=""` e há teste guardando.
   A vertical vale em qualquer tela. A HORIZONTAL só a partir de 768px: no
   retrato do celular (900×1100) um bloco encostado à direita fica com quatro
   palavras por linha e some. Lá o texto ocupa a largura toda, de propósito. */
.hero-content--y-topo { justify-content: flex-start; }
.hero-content--y-meio { justify-content: center; }
.hero-content--y-base { justify-content: flex-end; }
.hero-box { max-width: 46ch; }
/* Chapa atrás do texto: faz qualquer arte funcionar sem escurecer a foto
   inteira. É o que o cliente tinha nos temas de WordPress. Desligada por
   padrão. */
.hero-box--plate {
  background: rgba(1, 32, 63, .68);
  border-radius: var(--r-md);
  padding: var(--sp-4) var(--sp-4);
  backdrop-filter: blur(2px);
}
.hero-logo { width: 110px; height: auto; margin-bottom: var(--sp-3); }
.hero-eyebrow {
  display: inline-flex; align-items: center; align-self: flex-start; font-weight: 800; font-size: .76rem;
  letter-spacing: .14em; text-transform: uppercase; color: var(--c-accent);
  margin-bottom: var(--sp-3);
}
.hero-title { font-size: clamp(2rem, 8vw, 3.4rem); text-wrap: balance; margin-bottom: var(--sp-3); }
.hero-sub { font-size: 1.02rem; max-width: 42ch; color: rgba(255, 255, 255, .9); margin-bottom: 0; }
.hero-actions { display: flex; flex-wrap: wrap; gap: var(--sp-3); margin-top: var(--sp-5); }
/* `.hero-foot` (a linha "A partir de R$ …/mês") saiu em 2026-08-29 com o
   pedido de tirar preço do banner. Preço da home nasce no card. */
/* atalho para a pergunta nº 1 de quem procura provedor, ainda no hero */
.hero-check { margin: var(--sp-2) 0 0; font-size: .86rem; }
.hero-check a {
  color: var(--c-white); font-weight: 700;
  border-bottom: 1px solid rgba(255, 255, 255, .45); padding-bottom: 1px;
}
.hero-check a:hover { color: var(--c-accent); border-bottom-color: var(--c-accent); }

/* A posição HORIZONTAL do bloco só existe a partir daqui. No celular a arte é
   retrato (900×1100) e o texto ocupa a largura toda — bloco encostado num
   lado, ali, vira coluna de quatro palavras. Limitação deliberada, escrita
   também no painel junto do controle. */
@media (min-width: 768px) {
  .hero-content--x-esquerda { align-items: flex-start; }
  .hero-content--x-centro { align-items: center; text-align: center; }
  .hero-content--x-direita { align-items: flex-end; text-align: right; }
  .hero-content--x-centro .hero-eyebrow { align-self: center; }
  .hero-content--x-direita .hero-eyebrow { align-self: flex-end; }
  .hero-content--x-centro .hero-actions { justify-content: center; }
  .hero-content--x-direita .hero-actions { justify-content: flex-end; }
  .hero-content--x-centro .hero-sub,
  .hero-content--x-direita .hero-sub { margin-inline: auto; }
}

/* ------------------------------------------------- faixa de cobertura
   2ª seção da home: responde "vocês atendem meu endereço?" antes de o
   cliente gastar quatro telas de preço. É FAIXA, não seção cheia — o mapa e
   a lista bairro a bairro continuam em /cobertura. */

/* o filete amarelo não é enfeite: a faixa nasce logo depois do hero, que
   termina no mesmo azul profundo — sem ele os dois blocos viram um só */
.strip {
  background: var(--c-blue-deep); color: var(--c-white);
  border-top: 3px solid var(--c-accent);
  padding-block: var(--sp-6);
}
.strip-inner { display: grid; gap: var(--sp-4); }
.strip .eyebrow { color: var(--c-accent); margin-bottom: 6px; }
.strip-title { font-size: clamp(1.25rem, 5vw, 1.9rem); margin-bottom: var(--sp-2); }
.strip-sub { margin: 0; color: rgba(255, 255, 255, .84); font-size: .94rem; }
.strip-sub strong { color: var(--c-accent); }

.strip-facts { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-3); }
.strip-fact {
  display: grid; gap: 1px; padding: 8px 14px;
  background: rgba(255, 255, 255, .08); border: 1px solid rgba(255, 255, 255, .14);
  border-radius: var(--r-md);
}
.strip-fact strong { font-family: var(--font-display); font-style: italic; font-size: 1.02rem; }
.strip-fact span { font-size: .74rem; color: rgba(255, 255, 255, .68); }
.strip-actions { display: flex; flex-wrap: wrap; gap: var(--sp-3); }
.strip .btn-outline { color: var(--c-white); }
.strip .btn-outline:hover { background: var(--c-white); color: var(--c-blue-deep); border-color: var(--c-white); }
.carousel--hero .carousel-dots { position: absolute; bottom: 14px; left: 0; right: 0; margin: 0; z-index: 4; }
.carousel--hero .carousel-dot::before { background: rgba(255, 255, 255, .38); }
.carousel--hero .carousel-dot.is-active::before { background: var(--c-accent); }

/* --------------------------------------------------------------- ícones */
.why-ico {
  flex: 0 0 auto; width: 34px; height: 34px; border-radius: 10px;
  background: rgba(254, 197, 4, .16); position: relative;
}
.why-ico::after {
  content: ""; position: absolute; inset: 8px; background: var(--c-accent);
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-size: contain; mask-size: contain;
}
/* Jogo do lucide (arrow-up-down, house-wifi, wifi, headset), hospedado como
   SVG local em vez de `npm i lucide-react`: não há React nem build aqui, e
   o pacote só serviria para gerar exatamente estes quatro paths. Traço em
   #000 porque, como máscara, o que conta é o alfa — a cor vem do
   `background: var(--c-accent)` de quem usa. */
.ico-wifi::after { -webkit-mask-image: url("/img/icons/lucide-wifi.svg"); mask-image: url("/img/icons/lucide-wifi.svg"); }
.ico-sym::after { -webkit-mask-image: url("/img/icons/lucide-arrow-up-down.svg"); mask-image: url("/img/icons/lucide-arrow-up-down.svg"); }
.ico-play::after { -webkit-mask-image: url("/img/icons/play.svg"); mask-image: url("/img/icons/play.svg"); }
.ico-extend::after { -webkit-mask-image: url("/img/icons/lucide-house-wifi.svg"); mask-image: url("/img/icons/lucide-house-wifi.svg"); }
.ico-support::after { -webkit-mask-image: url("/img/icons/lucide-headset.svg"); mask-image: url("/img/icons/lucide-headset.svg"); }

/* ---------------------------------------------------------------- seções */

.section { padding-block: var(--sp-8); }
/* módulo de uma tela paga um respiro menor: o espaço é do conteúdo */
.section.screen { padding-block: var(--sp-6); }
.section--alt { background: var(--bg-soft); }
.section--plans { background: var(--bg-soft); }
.section--dark { background: var(--c-blue-deep); color: var(--c-white); }
.section--dark .section-sub, .section--dark .muted { color: rgba(255, 255, 255, .78); }
.section--dark .eyebrow { color: var(--c-accent); }

.section-head { text-align: center; max-width: 720px; margin-inline: auto; margin-bottom: var(--sp-5); }
.section-title { font-size: clamp(1.5rem, 5.4vw, 2.4rem); text-wrap: balance; }
.section-sub { color: var(--ink-soft); margin-bottom: 0; }
.section-sub--install { font-weight: 800; color: var(--c-blue); }
.eyebrow {
  font-weight: 800; font-size: .74rem; letter-spacing: .16em; text-transform: uppercase;
  color: var(--c-blue-soft); margin-bottom: var(--sp-2);
}

.page-hero {
  background: linear-gradient(160deg, var(--c-blue-deep), var(--c-blue) 70%);
  color: var(--c-white); padding-block: var(--sp-7) var(--sp-8);
}
.page-hero .eyebrow { color: var(--c-accent); }
.page-title { font-size: clamp(1.9rem, 7vw, 3rem); }
.page-sub { max-width: 60ch; color: rgba(255, 255, 255, .88); }
.page-hero .btn-lg { margin-top: var(--sp-4); }
.error-page { min-height: 60vh; display: flex; align-items: center; }

/* ------------------------------------------------- páginas jurídicas
   Termos e Política são texto corrido longo lido, na maioria das vezes, no
   celular e com pressa. Prioridade aqui é medida de linha e respiro entre
   blocos — nada de animação, nada de card: quem abre esta página quer achar
   um parágrafo específico, não ser encantado. */
.legal { line-height: 1.75; }
.legal h2 {
  font-size: 1.15rem; margin-top: var(--sp-6); margin-bottom: var(--sp-3);
  color: var(--c-blue-deep); scroll-margin-top: 90px;
}
.legal h3 { font-size: 1rem; margin-top: var(--sp-5); margin-bottom: var(--sp-2); }
.legal p { margin-bottom: var(--sp-3); max-width: 68ch; }
.legal ul { margin: 0 0 var(--sp-4) 1.15rem; max-width: 68ch; list-style: disc; }
.legal li { margin-bottom: 8px; }
.legal a { color: var(--c-blue); text-decoration: underline; }
.legal a:hover { color: var(--c-blue-deep); }
.legal code {
  font-size: .88em; padding: 1px 5px; border-radius: 4px;
  background: rgba(2, 62, 122, .08); word-break: break-word;
}
.legal-meta {
  font-size: .85rem; color: #5b6b7d;
  padding-bottom: var(--sp-3); border-bottom: 1px solid rgba(2, 62, 122, .12);
}

/* ------------------------------------------------------------------ tabs */

.tabs-list {
  display: flex; gap: 6px; padding: 5px;
  background: rgba(2, 62, 122, .08); border-radius: var(--r-pill); width: fit-content; max-width: 100%;
}
.tab {
  border: 0; background: transparent; padding: 10px 20px; border-radius: var(--r-pill);
  font-weight: 800; color: var(--c-blue); white-space: nowrap;
  transition: background-color var(--dur-ui) var(--ease-out), color var(--dur-ui) var(--ease-out),
              transform var(--dur-micro) var(--ease-out);
}
/* trocar de linha é a interação mais repetida da seção de planos: sem o
   afundar, o toque no celular parece não ter chegado até o painel trocar. */
.tab:active { transform: scale(.96); }
@media (hover: hover) and (pointer: fine) {
  .tab:not(.is-active):hover { background: rgba(2, 62, 122, .08); }
}
.tab.is-active { background: var(--c-blue); color: var(--c-white); box-shadow: var(--shadow-sm); }
.tab-panel { display: none; }
.tab-panel.is-active { display: block; }
.line-tagline { color: var(--ink-soft); font-size: .92rem; margin: var(--sp-3) 0 0; }

/* ------------------------------------------------ seção de planos (layout)
   Desktop: copy à esquerda, baralho à direita — preenche a tela sem
   espalhar card nenhum. Mobile: copy em cima, baralho embaixo. */

.plans-layout { display: grid; gap: var(--sp-5); align-items: center; }
.plans-copy { text-align: center; }
.plans-copy .section-title { margin-bottom: var(--sp-2); }
.plans-copy .section-sub { margin-bottom: 0; }
.plans-copy .city-switch { justify-content: center; }
.plans-deck .tabs-list { margin-inline: auto; margin-bottom: var(--sp-4); }
/* link "comparar todos" removido do baralho da home: o menu já leva a /planos.
   As setas/pontinhos FICAM. Ficaram escondidas por um tempo apostando que o
   arrasto se explicava sozinho — não se explica: quem não tenta arrastar vê
   um card só e conclui que existe um plano só. Affordance invisível é
   affordance que não existe. */
.plans-deck .center-cta { display: none; }

/* Os três argumentos de venda TAMBÉM no celular. Eram desktop-only, ou seja:
   o público minoritário recebia o argumento e a maioria (que decide no
   telefone) não. A altura se paga em tipografia e respiro, não removendo
   conteúdo — especificidade acima de .check-list, declarada mais adiante. */
.plans-copy .plans-usp {
  display: grid; gap: 7px; margin-top: var(--sp-4); margin-bottom: 0;
  max-width: 42ch; margin-inline: auto; text-align: left;
}
.plans-copy .plans-usp li { font-size: .86rem; color: var(--ink-soft); }
.plans-copy .plans-usp li::before { width: 17px; height: 17px; margin-top: 1px; }

/* A diferença entre Lite e X em uma frase, acima das abas: era a dúvida que
   fazia o cliente achar o X caro e comprar o Lite — ou não comprar nada. */
.plans-lines {
  margin: var(--sp-3) 0 0; font-size: .9rem; color: var(--ink-soft);
  max-width: 52ch; margin-inline: auto;
}
.plans-lines strong { color: var(--c-blue); font-weight: 800; }
.plans-lines--hero { color: rgba(255, 255, 255, .9); max-width: 62ch; margin-inline: 0; }
.plans-lines--hero strong { color: var(--c-accent); }

/* baralho de depoimentos: carta mais larga, texto respira */
.deck--quotes { --deck-w: min(90vw, 480px); }

/* ------------------------------------------------------------ recomendador
   Três perguntas → um plano. Rádios de verdade (funciona sem JS, é
   navegável por teclado e é lido por leitor de tela como o que é: um
   seletor). Sem JS, a lista de resultado mostra TODOS os planos — nunca
   sobra um bloco vazio esperando script.

   COM JS, UMA PERGUNTA POR VEZ. A primeira versão empilhava os três blocos de
   rádio mais uma caixa de resultado vazia embaixo e passava de duas telas de
   altura: o cliente via o tamanho do trabalho antes de começar. Agora só
   existe o passo atual, e o módulo cabe numa tela. */

.rec-form { display: grid; gap: var(--sp-5); }
.rec-q { border: 0; margin: 0; padding: 0; min-width: 0; }
.rec-q legend {
  display: flex; align-items: center; gap: 10px; padding: 0;
  font-weight: 800; font-size: .95rem; margin-bottom: var(--sp-3);
}
.rec-n {
  display: grid; place-items: center; flex: 0 0 auto;
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--c-accent); color: var(--c-accent-ink);
  font-family: var(--font-display); font-style: italic; font-size: .8rem; font-weight: 900;
}
.rec-opts { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.rec-opt { position: relative; }
/* o input continua no fluxo de foco e cobre o rótulo inteiro: alvo grande no
   toque, foco visível pelo :focus-visible do irmão */
.rec-opt input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
.rec-opt span {
  display: block; padding: 10px 16px; background: #fff;
  border: 2px solid var(--line); border-radius: var(--r-pill);
  font-weight: 700; font-size: .88rem; color: var(--ink);
  transition: background-color var(--dur-ui) var(--ease-out), border-color var(--dur-ui) var(--ease-out), color var(--dur-ui) var(--ease-out);
}
.rec-opt input:hover + span { border-color: var(--c-blue-soft); }
.rec-opt input:checked + span { background: var(--c-blue); border-color: var(--c-blue); color: var(--c-white); }
.rec-opt input:focus-visible + span { outline: 3px solid var(--c-accent); outline-offset: 2px; }

.rec-result { display: grid; gap: var(--sp-6); }
.rec-result:focus { outline: none; }
.rec-option { display: grid; gap: var(--sp-3); justify-items: center; }
.rec-card { width: min(100%, 340px); }
.rec-verdict { margin: 0; text-align: center; font-size: 1.02rem; }
.rec-verdict strong { color: var(--c-blue); }
.rec-foot { margin: var(--sp-5) 0 0; text-align: center; font-size: .9rem; color: var(--ink-soft); }

/* barra de controle do diálogo — só existe com JS: sem ela, responder a 2ª
   pergunta seria caminho sem volta */
.rec-bar { display: none; }
.section--rec.is-live .rec-bar {
  display: flex; align-items: center; justify-content: center;
  gap: var(--sp-4); margin-top: var(--sp-5); min-height: 34px;
}
.rec-progress { margin: 0; font-size: .82rem; color: var(--ink-soft); letter-spacing: .02em; }
.rec-back, .rec-redo {
  border: 0; background: none; padding: 6px 10px; cursor: pointer;
  font: inherit; font-size: .85rem; font-weight: 700; color: var(--c-blue);
  border-radius: var(--r-pill);
  transition: background-color var(--dur-micro) var(--ease-out), opacity var(--dur-micro) var(--ease-out);
}
.rec-back[disabled] { opacity: 0; pointer-events: none; }
.rec-back:focus-visible, .rec-redo:focus-visible { outline: 3px solid var(--c-accent); outline-offset: 2px; }

/* com JS: uma pergunta por vez, e a lista de planos vira resultado único */
.section--rec.is-live .rec-q { display: none; }
.section--rec.is-live .rec-q.is-current { display: block; animation: rec-in var(--dur-ui) var(--ease-out) both; }
.section--rec.is-live.has-answer .rec-form { display: none; }
.section--rec.is-live .rec-option { display: none; }
.section--rec.is-live .rec-option.is-chosen { display: grid; animation: rec-in var(--dur-overlay) var(--ease-out) both; }
@keyframes rec-in {
  from { opacity: 0; transform: translate3d(0, 10px, 0); }
  to { opacity: 1; transform: none; }
}

/* ------------------------------------------------------------ card de plano */

.plan-card {
  position: relative; height: 100%;
  display: flex; flex-direction: column;
  /* fundo da arte: azul royal com clarão no canto superior esquerdo e
     malha de rede em SVG (data URI — CSP permite, inline style não). */
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='320' height='320'%3E%3Cg fill='none' stroke='%23ffffff' stroke-width='1' opacity='.1'%3E%3Cpath d='M-10 62L54 18L128 74L196 26L262 86L330 44'/%3E%3Cpath d='M54 18L38 118L128 74L150 178L196 26'/%3E%3Cpath d='M38 118L-6 196L92 236L150 178L242 220L262 86'/%3E%3Cpath d='M92 236L120 320M242 220L296 300M330 176L262 86'/%3E%3C/g%3E%3Cg fill='%23ffffff' opacity='.22'%3E%3Ccircle cx='54' cy='18' r='2.2'/%3E%3Ccircle cx='128' cy='74' r='2'/%3E%3Ccircle cx='196' cy='26' r='1.8'/%3E%3Ccircle cx='38' cy='118' r='2'/%3E%3Ccircle cx='150' cy='178' r='2.4'/%3E%3Ccircle cx='262' cy='86' r='2'/%3E%3Ccircle cx='92' cy='236' r='2.2'/%3E%3Ccircle cx='242' cy='220' r='1.8'/%3E%3C/g%3E%3C/svg%3E") 0 0 / 320px 320px repeat,
    radial-gradient(120% 90% at 12% 0%, color-mix(in srgb, var(--c-blue-soft) 88%, #fff) 0%, transparent 62%),
    linear-gradient(165deg, var(--c-blue) 0%, var(--c-blue-deep) 78%);
  color: var(--c-white);
  border: 1px solid rgba(255, 255, 255, .16); border-radius: var(--r-lg);
  padding: var(--sp-5) var(--sp-4) var(--sp-4);
  text-align: center;
  box-shadow: var(--shadow-card);
  transition: border-color var(--dur-ui) var(--ease-out), box-shadow var(--dur-ui) var(--ease-out);
  /* carta de arrasto, não texto: selecionar em cima trava o gesto e fica
     feio no desktop (blocos azuis de seleção). Só o CTA continua clicável. */
  user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;
  /* o brilho de borda mora dentro deste contexto: sem isto, a camada
     .edge-light (z-index -1) escaparia pra trás da seção inteira */
  isolation: isolate;
  --edge-sensitivity: 2;    /* abaixo disto o halo externo nem aparece */
  --color-sensitivity: 6;   /* abaixo disto a borda colorida nem aparece */
  --cone-spread: 22;        /* % do círculo aceso de cada lado do cursor */
  --cone-fade: 16;          /* rampa de apagamento depois do cone */
  --glow-span: 44;          /* proximidade que já vale brilho MÁXIMO */
  --glow-pad: 48px;         /* quanto o halo vaza pra fora do card */
  --glow-radius: var(--r-lg);
  --glow-color: var(--c-plan-accent);
}
.plan-card.is-highlight {
  border-color: var(--c-plan-accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--c-plan-accent) 38%, transparent), var(--shadow-card);
}
/* o selo (.plan-badge) fica de fora deste fluxo — folga extra no topo
   pra ele pousar sobre a borda do card sem colar na moldura de velocidade */
.plan-card:has(.plan-badge) { padding-top: calc(var(--sp-5) + 14px); }

/* corpo em coluna: único filho que cresce (flex: 1) e único que empurra
   altura — o selo é irmão dele, fora do fluxo, então nunca conta pra
   altura que o baralho sincroniza entre os cards (ver site.js, --deck-h).
   `position: relative` só para vencer a lâmina de luz (.plan-foil, logo
   abaixo) na ordem de pintura: dois elementos posicionados com z-index auto
   se resolvem por ordem no DOM, e o corpo vem depois. Sem isto o texto
   passaria POR BAIXO do reflexo. */
.plan-body { position: relative; display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
.plan-body .btn { margin-top: auto; }

/* ---- LÂMINA DE LUZ: uma varredura diagonal atravessa a carta quando ela
   assume a frente do baralho. É a única coisa puramente decorativa do card, e
   ela se paga porque acontece RARO — uma vez por carta virada, não em hover,
   não em scroll. O elemento existe sempre no DOM (custo zero enquanto
   `opacity: 0`); quem o acende é a regra do estado ativo, lá no fim do bloco. */
.plan-foil {
  position: absolute; inset: 0; border-radius: inherit;
  overflow: hidden; pointer-events: none; opacity: 0;
}
.plan-foil::before {
  content: ""; position: absolute; top: -30%; bottom: -30%; left: 0; width: 42%;
  background: linear-gradient(100deg,
    transparent 0%,
    rgba(255, 255, 255, .10) 34%,
    rgba(255, 255, 255, .34) 50%,
    rgba(255, 255, 255, .10) 66%,
    transparent 100%);
  transform: translate3d(-160%, 0, 0) skewX(-14deg);
}

/* =====================================================  BorderGlow
   Porte do react-bits BorderGlow (JS+CSS) para CSS puro — sem React e sem
   build. O site.js escreve dois valores no card:
     --cursor-angle    direção do cursor a partir do centro
     --edge-proximity  0 no centro, 100 na borda
   Tudo o mais é máscara cônica: a luz só acende no setor pra onde o cursor
   aponta, e só ganha força quando ele chega perto da borda.
   Em tela sem cursor o mesmo efeito roda sozinho, uma varredura quando a
   carta vira a da frente do baralho (keyframes mais abaixo).
   Adaptações à marca: gradiente amarelo/azul em vez do mesh roxo, e o halo
   externo em blending normal — `plus-lighter` do original sumiria no branco.

   Serve dois donos: o card de plano (baralho) e os quadrados do "por que".
   O que muda entre eles são só tokens — --glow-color, --glow-radius e o
   quanto o halo vaza (--glow-pad) —, então a matemática de máscara mora em
   um lugar só. Quem quiser um terceiro card com brilho declara os tokens,
   põe <span class="edge-light"> dentro e entra na lista de seletores. */

/* A RAMPA DE INTENSIDADE — era aqui que o neon se perdia.
   A fórmula antiga mapeava proximidade→opacidade em linha reta até 100, e
   proximidade 100 é EXATAMENTE cima da borda: um ponto onde o ponteiro
   quase nunca está. Passeando pelo card a proximidade típica é 40–60, então
   o brilho vivia entre 25% e 50% da força — "mal dá pra ver".
   Agora a rampa satura em --glow-span (44) e existe um piso de hover: assim
   que o ponteiro entra no card a luz já está acesa, e aproximar-se da borda
   só reforça. O que o cursor controla é a DIREÇÃO; a presença é do hover. */
@media (hover: hover) and (pointer: fine) {
  .plan-card:hover, .why-card:hover { --glow-floor: .5; }
}

/* borda colorida, acesa só no setor do cursor */
.plan-card::before,
.why-card::before {
  content: ""; position: absolute; inset: -1px; border-radius: inherit;
  padding: 2.6px; pointer-events: none;
  /* card escuro: a borda acende em branco/laranja — azul sobre azul sumia.
     A base sólida subiu de .7 para .92 porque os quatro radiais só cobrem os
     cantos: o MEIO de cada aresta — justamente onde o cursor passa mais —
     era servido só pela base, e era ela que estava pálida. */
  background:
    radial-gradient(at 12% 8%, var(--glow-color) 0, transparent 62%),
    radial-gradient(at 88% 18%, #fff 0, transparent 62%),
    radial-gradient(at 78% 92%, var(--glow-color) 0, transparent 62%),
    radial-gradient(at 22% 88%, #fff 0, transparent 62%),
    linear-gradient(rgba(255, 255, 255, .92) 0 100%);
  /* 3 camadas: o cone do cursor ∩ (moldura = tudo XOR miolo) */
  -webkit-mask:
    conic-gradient(from var(--cursor-angle) at 50% 50%,
      #000 calc(var(--cone-spread) * 1%),
      transparent calc((var(--cone-spread) + var(--cone-fade)) * 1%),
      transparent calc((100 - var(--cone-spread) - var(--cone-fade)) * 1%),
      #000 calc((100 - var(--cone-spread)) * 1%)),
    linear-gradient(#000 0 0) content-box,
    linear-gradient(#000 0 0);
  mask:
    conic-gradient(from var(--cursor-angle) at 50% 50%,
      #000 calc(var(--cone-spread) * 1%),
      transparent calc((var(--cone-spread) + var(--cone-fade)) * 1%),
      transparent calc((100 - var(--cone-spread) - var(--cone-fade)) * 1%),
      #000 calc((100 - var(--cone-spread)) * 1%)),
    linear-gradient(#000 0 0) content-box,
    linear-gradient(#000 0 0);
  -webkit-mask-composite: source-in, xor;
  mask-composite: intersect, exclude;
  /* NADA de `filter: drop-shadow` aqui pra tentar fazer o filete sangrar:
     na ordem de renderização o filtro vem ANTES da máscara, então o borrão
     seria recortado pela mesma máscara que desenha o anel e o custo sairia
     sem efeito nenhum. Quem faz a luz vazar é o .edge-light, que não tem
     máscara de moldura — só a do cone. */
  opacity: clamp(0, max(var(--glow-floor, 0),
    (var(--edge-proximity) - var(--color-sensitivity)) / var(--glow-span)), 1);
  transition: opacity var(--dur-ui) var(--ease-out);
}

/* halo que vaza pra fora, no MESMO cone do anel colorido (::before, acima).
   Tinha um cone próprio bem mais estreito (2.5%/10% fixo) contra o cone
   largo do anel: o halo apagava bem antes da borda colorida, e essa costura
   entre "tem glow" e "não tem" aparecia como um corte reto contra o fundo
   claro da página. Mesma fórmula = um efeito só. */
.plan-card > .edge-light,
.why-card > .edge-light {
  position: absolute; inset: calc(var(--glow-pad) * -1);
  border-radius: calc(var(--glow-radius) + var(--glow-pad));
  pointer-events: none; z-index: -1;
  -webkit-mask-image: conic-gradient(from var(--cursor-angle) at 50% 50%,
    #000 calc(var(--cone-spread) * 1%),
    transparent calc((var(--cone-spread) + var(--cone-fade)) * 1%),
    transparent calc((100 - var(--cone-spread) - var(--cone-fade)) * 1%),
    #000 calc((100 - var(--cone-spread)) * 1%));
  mask-image: conic-gradient(from var(--cursor-angle) at 50% 50%,
    #000 calc(var(--cone-spread) * 1%),
    transparent calc((var(--cone-spread) + var(--cone-fade)) * 1%),
    transparent calc((100 - var(--cone-spread) - var(--cone-fade)) * 1%),
    #000 calc((100 - var(--cone-spread)) * 1%));
  /* o halo entra um pouco atrás do filete (piso menor): primeiro acende o
     traço, depois o ar em volta dele. Acender os dois juntos achata o efeito
     numa mancha só e tira a leitura de "tubo aceso". */
  opacity: clamp(0, max(calc(var(--glow-floor, 0) * .82),
    (var(--edge-proximity) - var(--edge-sensitivity)) / var(--glow-span)), 1);
  transition: opacity var(--dur-ui) var(--ease-out);
}
.plan-card > .edge-light::before,
.why-card > .edge-light::before {
  content: ""; position: absolute; inset: var(--glow-pad); border-radius: var(--glow-radius);
  box-shadow:
    0 0 0 1px var(--glow-color),
    0 0 4px 0 color-mix(in srgb, var(--glow-color) 88%, transparent),
    0 0 11px 0 color-mix(in srgb, var(--glow-color) 72%, transparent),
    0 0 24px 2px color-mix(in srgb, var(--glow-color) 55%, transparent),
    0 0 44px 5px color-mix(in srgb, var(--glow-color) 38%, transparent),
    0 0 76px 10px color-mix(in srgb, var(--c-blue-soft) 32%, transparent);
}

/* amortecimento do ponteiro: sem isto o ângulo salta de quadro em quadro e a
   luz "pisca" de posição em movimentos rápidos. 110ms linear = média móvel.
   Só no desktop — no celular quem escreve o ângulo é a varredura em
   keyframes, e transição brigando com animação no mesmo valor dá tranco.
   Só o ÂNGULO é amortecido: amortecer também a proximidade empilharia com a
   transição de opacidade das camadas e o brilho passaria a chegar atrasado.
   Classe repetida = especificidade 0-2-0, para vencer o `transition:`
   (shorthand) que .why-card declara mais abaixo no arquivo. */
@media (hover: hover) and (pointer: fine) {
  .plan-card.plan-card, .why-card.why-card {
    transition: border-color var(--dur-ui) var(--ease-out), background-color var(--dur-ui) var(--ease-out),
                box-shadow var(--dur-ui) var(--ease-out), --cursor-angle 110ms linear;
  }
}

/* SEM CURSOR (CELULAR): O BRILHO INTEIRO SAIU. Duas rodadas de queixa do
   mesmo defeito, e a segunda fechou o assunto:

   2026-08-17 — saiu a varredura infinita, que animava --cursor-angle e
   --edge-proximity para sempre. Ficou no lugar o filete aceso parado.
   2026-08-20 — o cliente voltou dizendo que AINDA trava ao passar as cartas,
   e pediu o brilho todo fora do celular.

   Por que o filete parado continuava caro mesmo sem relógio: ele é uma
   camada mascarada (mask-composite, moldura = tudo XOR miolo) desenhada por
   cima de um card que se transforma a cada quadro do arrasto. Camada com
   máscara não é textura barata de recompor, e são até quatro cartas
   compostas ao mesmo tempo. A lâmina (.plan-foil) roda UMA vez por virada,
   mas ela roda EXATAMENTE durante a virada — o quadro em que o dedo está
   arrastando o monte inteiro. O enfeite disputa quadro com o gesto, e no
   celular o gesto ganha sempre.

   No desktop nada muda: cursor, cone, halo e lâmina continuam. Isto aqui é
   `hover: none`, e no toque a carta é chapada — cor, número, preço, CTA. */
@media (hover: none) {
  .plan-card::before, .why-card::before { display: none; }
  .plan-card > .edge-light, .why-card > .edge-light { display: none; }
  .plan-foil { display: none; }
}

/* selo fora do fluxo, pousado sobre a borda superior do PRÓPRIO card —
   nunca sobre o vizinho: é filho absoluto de .plan-card (isolation: isolate
   já delimita a camada) e, por vir depois de .plan-body no DOM, pinta por
   cima sem precisar de z-index. Como não é in-flow, também não conta pra
   altura que o baralho sincroniza entre cards (--deck-h), então o selo
   nunca empurra ou desalinha a moldura de velocidade de card nenhum. */
.plan-badge {
  position: absolute; top: 0; left: 50%; translate: -50% -50%;
  display: inline-flex; align-items: center; gap: 5px;
  background: var(--c-plan-accent); color: #2A1500;
  font-size: .72rem; font-weight: 900; letter-spacing: .07em; white-space: nowrap;
  padding: 7px 15px 7px 12px; border-radius: var(--r-pill);
  border: 1.5px solid rgba(255, 255, 255, .55);
  box-shadow: 0 6px 16px rgba(0, 0, 0, .32);
}
.plan-badge .icon { width: 12px; height: 12px; }

/* ---- quadro de cantos em colchete (a moldura das artes do cliente)
   A borda é contínua e o vão do topo é aberto por máscara: a primeira
   camada é uma faixa de 3px no topo com um buraco no meio (onde entra o
   rótulo), a segunda é o resto do quadro inteiro. Máscaras se somam por
   padrão, então não precisa de mask-composite. */
.plan-frame {
  position: relative;
  border-radius: 18px;
  padding: var(--sp-4) var(--sp-3) var(--sp-3);
}
/* a moldura é pseudo-elemento: máscara no próprio .plan-frame cortaria
   também o rótulo, que é desenhado por cima da linha do topo. */
.plan-frame::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  border: 2px solid var(--plan-frame-line);
  border-radius: inherit;
  -webkit-mask:
    linear-gradient(to right, #000 0 18%, transparent 18% 82%, #000 82% 100%) top / 100% 3px no-repeat,
    linear-gradient(#000 0 0) bottom / 100% calc(100% - 3px) no-repeat;
  mask:
    linear-gradient(to right, #000 0 18%, transparent 18% 82%, #000 82% 100%) top / 100% 3px no-repeat,
    linear-gradient(#000 0 0) bottom / 100% calc(100% - 3px) no-repeat;
}
/* o rótulo mora dentro do vão da borda — e não pode passar dele. O vão é
   fixo (18%–82% da largura, ou seja 64%); o rótulo vinha com `max-width: 78%`
   e `nowrap`, então bastava o Felipe cadastrar um nome comprido no /admin
   ("Family Gamer Premium") pra letra atravessar a linha do quadro. O teto
   agora é menor que o vão e a reticência é o modo de falhar: nome cortado é
   feio, nome cruzando a moldura é quebrado. */
.plan-frame > .plan-name {
  position: absolute; top: 0; left: 50%; translate: -50% -52%;
  margin: 0; max-width: 58%; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
  font-family: var(--font-display); font-style: italic; font-weight: 800;
  font-size: clamp(.9rem, 4.2vw, 1.05rem); letter-spacing: .01em;
  text-transform: uppercase; color: var(--c-white);
}

/* ---- UMA moldura por card.
   O colchete é a assinatura da arte e pertence à VELOCIDADE — é o que muda
   de carta pra carta no baralho. O preço tinha um quadro idêntico, mesma
   linha de 2px, e dois pesos iguais empatam: o olho pinga entre os dois e o
   card fica sem foco. Pior, o quadro do preço herdava o vão do topo sem ter
   rótulo pra pôr lá — um talho na borda sem função nenhuma.
   Agora o preço se distingue por FORMA (painel preenchido, sem borda), não
   por outra borda de mesmo peso. */
.plan-frame--price {
  background: rgba(0, 0, 0, .26);
  padding: var(--sp-3) var(--sp-3) var(--sp-2);
}
.plan-frame--price::before { display: none; }

/* nada aqui pode estourar a largura da carta: sem `min-width: 0` o tamanho
   mínimo automático de flex/grid deixa a linha de selos empurrar o card
   para fora da tela (e aí a página inteira ganha rolagem horizontal). */
.plan-card, .plan-body, .plan-head, .plan-flags, .plan-price, .price-terms, .plan-frame, .price-main, .plan-delta { min-width: 0; }
.plan-card { max-width: 100%; }
.plan-flags, .plan-benefits, .plan-delta { overflow-wrap: anywhere; }

.plan-head { display: grid; gap: var(--sp-3); }

.plan-frame--speed { padding-top: var(--sp-5); }
.plan-speed {
  margin: 0; font-family: var(--font-display); font-style: italic; font-weight: 900;
  font-size: clamp(2.4rem, 12.5vw, 3.3rem); line-height: .96; letter-spacing: -.02em;
  text-transform: uppercase; color: var(--c-white);
  display: flex; align-items: baseline; justify-content: center;
}
.speed-unit { color: var(--c-plan-accent); margin-left: .12em; }

/* número que conta de 0 até a velocidade quando a carta vira a da frente.
   As duas camadas ocupam A MESMA célula de grade: a de baixo (fantasma,
   invisível) tem o texto final e é ela quem define a largura; a de cima
   recebe os números intermediários. Sem esse par, contar até 800 empurraria
   o "MEGA" laranja de lado três vezes durante a animação — e a linha inteira
   ficaria dançando. Alinhado à direita porque é de lá que o número cresce. */
.speed-num.is-countable { display: inline-grid; justify-items: end; }
.speed-num.is-countable > * { grid-area: 1 / 1; }
.speed-ghost { visibility: hidden; }
.speed-live { font-variant-numeric: tabular-nums; }

/* ---- SELOS: UMA FORMA, UMA COR.
   Havia três tratamentos para a mesma classe de informação — apps em laranja
   sem pílula, simetria em texto puro com check laranja, Wi-Fi 6 em pílula
   escura — mais um realce âmbar no selo que o plano estreava. O efeito
   colateral matou a leitura: o MESMO selo aparecia laranja numa carta e
   branco na seguinte (Wi-Fi 6 no Gamer Lite vs. Zeus Lite), e cor que muda
   sem o significado mudar é ruído puro. Pior, ela roubava o único acento
   quente do card, que pertence ao número da velocidade e ao preço.
   Selo é rótulo de característica: mesma pílula, mesmo branco, em todo plano
   e nas duas linhas. Comparação entre planos é trabalho da faixa abaixo, que
   diz por escrito o que muda — e diz em Mega e em reais.
   Ícones reais (lucide) no lugar do wifi desenhado em CSS puro: o traço fino
   em fundo escuro ficava quebrado em telas pequenas. */
.plan-flags {
  display: flex; flex-wrap: wrap; justify-content: center; align-items: center;
  gap: 7px; margin: 0; font-size: .76rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: .04em;
}
.flag {
  display: inline-flex; align-items: center; gap: 6px;
  color: var(--c-white); background: rgba(0, 0, 0, .28);
  border: 1px solid rgba(255, 255, 255, .28); border-radius: var(--r-pill);
  padding: 4px 11px 4px 9px;
}
.flag .icon { width: 13px; height: 13px; flex: none; color: var(--c-white); }
/* o "+" da arte impressa do cliente: fica maior que o texto de propósito,
   mas na mesma cor — é pontuação, não destaque. */
.flag-plus {
  font-family: var(--font-display); font-weight: 900; font-size: 1.2rem;
  line-height: 1; color: var(--c-white);
}

/* ================================================ A FAIXA DO DEGRAU
   O card respondia "o que este plano é" — e a resposta era praticamente igual
   nas quatro cartas, o que provava ao cliente que os planos só diferiam no
   preço. Esta faixa responde a outra pergunta, a que ele realmente tem na
   mão: O QUE MUDA daqui pro anterior, e quanto custa esse a mais.
   Texto derivado (src/lib/plan-ladder.js), nunca digitado: mexer numa
   velocidade ou num preço no /admin reescreve a faixa sozinha.
   Amarelo aqui não viola a lei de cor — a lei governa BOTÕES; isto é texto
   sobre placa, sem destino, e o único verde da carta continua sendo o CTA. */
/* Duas fileiras, e o valor do degrau desceu para a segunda.
   Na primeira versão o custo dividia a linha do título ("+300 Mega" | "+R$20"),
   o que funcionava com título curto e desmontava com título longo: "Upload
   igual ao download" caiu em três linhas espremidas contra a coluna do preço.
   O título agora tem a largura inteira e o rodapé da faixa carrega o par
   "com quem comparo" / "quanto custa" — que é justamente o par que se lê
   junto. */
.plan-delta {
  /* a distância mínima até o preço mora aqui, e não no `.plan-price`, porque
     lá em cima o `margin-top` virou `auto` (repartição da folga do baralho).
     Em flex as margens vizinhas somam, não colapsam: este `--sp-3` é o chão
     do respiro e o `auto` só acrescenta o que sobrar. */
  margin: var(--sp-3) 0;
  display: grid; grid-template-columns: 1fr auto; align-items: baseline;
  gap: 3px var(--sp-2);
  padding: 10px var(--sp-3) 11px;
  border-radius: 14px;
  border-left: 3px solid var(--c-plan-accent);
  background: linear-gradient(100deg,
    color-mix(in srgb, var(--c-plan-accent) 22%, transparent) 0%,
    rgba(255, 255, 255, .05) 62%,
    transparent 100%);
  text-align: left;
}
.delta-lead {
  grid-column: 1 / -1;
  display: flex; align-items: baseline; gap: 7px; min-width: 0;
  font-family: var(--font-display); font-style: italic; font-weight: 900;
  font-size: clamp(1.02rem, 4.6vw, 1.2rem); line-height: 1.15;
  letter-spacing: -.01em; color: var(--c-white);
  text-wrap: balance;
}
.delta-vs {
  font-size: .84rem; font-weight: 600; color: var(--plan-ink-soft); line-height: 1.3;
}
.delta-cost {
  justify-self: end; white-space: nowrap;
  font-size: .84rem; font-weight: 900; color: var(--c-plan-accent);
}
/* plano do chão: não há degrau anterior, então a faixa não compara — ela
   apresenta. Mesma caixa, sem o acento amarelo, para não gritar o que é o
   ponto de partida. */
.plan-delta--entry {
  border-left-color: rgba(255, 255, 255, .38);
  background: rgba(255, 255, 255, .06);
}
.plan-delta--entry .delta-lead { font-size: 1.02rem; color: var(--plan-due); }

/* ---- preço: "Por R$" empilhado à esquerda, ",90 / MÊS" à direita.
   A FOLGA DO BARALHO SE DIVIDE AQUI. Todas as cartas têm a altura da maior
   (o JS sincroniza em --deck-h), então as menores sobram espaço — e com a
   lista de benefícios nascendo vazia, essa sobra virou um buraco único entre
   a placa de preço e o CTA, porque só o botão puxava `margin-top: auto`.
   Dois `auto` no mesmo eixo repartem o espaço livre igualmente: um antes do
   preço e um antes do botão. A carta baixa fica respirando em dois lugares em
   vez de ter um vão no meio, e a carta cheia não muda em nada (sobra zero,
   auto vira zero). O `--sp-3` de baixo é o piso: sem ele, folga zero colaria
   a placa no botão. */
.plan-price { margin: auto 0 var(--sp-3); }
.price-main {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  margin: 0; color: var(--c-white); font-family: var(--font-display); font-style: italic;
}
.price-pre, .price-tail { display: grid; line-height: 1.05; text-align: left; align-self: center; }
/* "Por R$" senta na base do número, como na arte impressa — centralizado ele
   flutuava no meio de um dígito de 4rem e a leitura perdia a linha de base. */
.price-pre { align-self: end; padding-bottom: .14em; }
.price-tail { margin-left: 2px; }
.price-pre { justify-items: start; }
.price-por { font-size: 1rem; font-weight: 700; }
.price-currency { font-size: 1.15rem; font-weight: 900; }
.price-int {
  font-size: clamp(3rem, 16vw, 4rem); font-weight: 900; letter-spacing: -.03em; line-height: .9;
}
.price-dec { font-size: 1.3rem; font-weight: 900; }
.price-period { font-size: .95rem; font-weight: 800; letter-spacing: .02em; }
.price-main--empty { font-size: 1.5rem; font-weight: 900; text-transform: uppercase; }

/* os dois preços (em dia / em atraso) numa LINHA SÓ, dentro da placa.
   A regra do briefing é que os dois fiquem sempre visíveis — e "visível" não
   é 0,78rem em cinza logo abaixo da placa, que é onde o valor após o
   vencimento morava: fora do bloco de preço ele lia como rodapé, exatamente
   a letra miúda que a regra existe para proibir. Agora os dois moram na mesma
   placa, separados por um filete, com corpo de texto de verdade. A hierarquia
   continua: o valor em dia é o número gigante acima; aqui embaixo os dois
   rótulos têm o mesmo direito de serem lidos. */
/* GRADE de duas colunas, nunca `flex-wrap`. Com flex, o rótulo comprido
   ("ATÉ O VENCIMENTO") empurrava o segundo preço pra linha de baixo e o
   filete separador — que era um ::before do próprio texto — reaparecia lá
   como um risco solto no começo da linha. Em grade, cada preço tem a sua
   coluna e o filete é a BORDA da coluna da direita: se o rótulo da esquerda
   precisar de duas linhas, ele quebra dentro da própria coluna e o separador
   continua sendo um traço inteiro entre os dois. */
.price-terms {
  margin: var(--sp-2) 0 0; padding-top: 9px;
  border-top: 1px solid rgba(255, 255, 255, .16);
  display: grid; grid-template-columns: auto auto;
  justify-content: center; align-items: center; column-gap: 11px;
}
.price-terms:has(.price-note--empty) { grid-template-columns: 1fr; }
.price-note {
  margin: 0; display: inline-flex; align-items: center; gap: 5px;
  font-size: .76rem; font-weight: 800; color: var(--plan-due);
  text-transform: uppercase; letter-spacing: .04em; line-height: 1.25;
}
.price-note .icon { width: 12px; height: 12px; flex: none; color: var(--c-plan-accent); }
.price-note--empty { color: var(--plan-ink-soft); text-transform: none; letter-spacing: 0; }
.price-late {
  margin: 0; padding-left: 11px; font-size: .84rem; line-height: 1.25;
  color: var(--plan-ink-soft);
  border-left: 1px solid rgba(255, 255, 255, .22);
}
.price-late strong { color: var(--c-white); font-weight: 900; font-size: .94rem; }

/* o texto continua à esquerda (lista se lê à esquerda), mas o BLOCO centra:
   ocupando a largura toda num card centrado, a borda direita irregular da
   lista puxava a massa visual pra um lado só.
   Corpo subiu de .82 para .92rem: três linhas é pouca massa de texto, e era
   justamente aqui — no único lugar da carta que descreve a VIDA do cliente
   com o plano — que a letra estava menor que a de um rodapé. */
.plan-benefits {
  display: grid; gap: 8px; margin-bottom: var(--sp-4); font-size: .92rem;
  line-height: 1.3; color: rgba(255, 255, 255, .92); text-align: left;
  width: fit-content; max-width: 100%; margin-inline: auto;
}
.plan-benefits li { display: flex; align-items: flex-start; gap: 9px; }
.plan-benefits .icon { width: 14px; height: 14px; margin-top: .18em; flex: none; color: var(--c-plan-accent); }

/* telas curtas: o card encolhe em vez de exigir scroll */
@media (max-height: 760px) {
  .plan-card { padding: var(--sp-4) var(--sp-3) var(--sp-3); }
  .plan-head { gap: var(--sp-2); }
  .plan-price { margin: auto 0 var(--sp-2); }
  .plan-delta { margin-block: var(--sp-2); padding: 8px var(--sp-3) 9px; }
  .plan-benefits { gap: 5px; font-size: .86rem; margin-bottom: var(--sp-3); }
  .plan-speed { font-size: clamp(2.1rem, 11vw, 2.8rem); }
  .price-int { font-size: clamp(2.6rem, 14vw, 3.4rem); }
}

/* ==========================================================================
   ENTRADA DA CARTA — a coreografia de virar um degrau.

   Por que animar aqui e não em toda parte: o baralho é o ÚNICO gesto do site
   em que o usuário troca de conteúdo de propósito, e ele faz isso poucas
   vezes por visita. É o caso raro em que a animação pode ser generosa — não é
   um hover que ele vai ver duzentas vezes, é a virada de carta que ele faz
   três ou quatro vezes e que precisa parecer que valeu.

   A ordem conta uma frase: o número sobe (quanto), a faixa abre (o que muda
   e quanto custa mudar), a placa de preço assenta (quanto sai), os benefícios
   entram (o que vem junto). O CTA está FORA da coreografia de propósito —
   botão de conversão nunca aparece depois; ele já está lá quando a carta
   chega.

   Tudo em transform/opacity/clip-path. Nada de altura, nada de layout — a
   altura da carta é medida pelo JS (--deck-h) e um único `height` animado
   aqui faria o baralho inteiro tremer a cada virada.

   Só na carta ATIVA de um baralho vivo: sem JS, e para as cartas de trás, o
   estado final é o estado natural do CSS. E tudo dentro de
   `prefers-reduced-motion: no-preference` — quem pediu menos movimento recebe
   a carta pronta, com a informação toda no lugar. */
@media (prefers-reduced-motion: no-preference) {
  .deck.is-live.is-seen .deck-card[data-state="active"] .plan-foil {
    animation: plan-foil 1100ms var(--ease-out) 60ms 1;
  }
  .deck.is-live.is-seen .deck-card[data-state="active"] .plan-delta {
    animation: delta-open 380ms var(--ease-out) 120ms backwards;
  }
  .deck.is-live.is-seen .deck-card[data-state="active"] .plan-frame--price {
    animation: price-settle 300ms var(--ease-out) 140ms backwards;
  }
  .deck.is-live.is-seen .deck-card[data-state="active"] .plan-benefits li {
    animation: benefit-in 260ms var(--ease-out) 260ms backwards;
  }
  .deck.is-live.is-seen .deck-card[data-state="active"] .plan-benefits li:nth-child(2) { animation-delay: 310ms; }
  .deck.is-live.is-seen .deck-card[data-state="active"] .plan-benefits li:nth-child(n + 3) { animation-delay: 360ms; }
}

/* a lâmina percorre a carta uma vez e some. `linear` porque luz que varre tem
   velocidade constante — uma curva faria o reflexo correr e frear no meio do
   card, que é o único lugar onde ele está totalmente visível. */
@keyframes plan-foil {
  0%   { opacity: 0; }
  12%  { opacity: 1; }
  88%  { opacity: 1; }
  100% { opacity: 0; }
}
@media (prefers-reduced-motion: no-preference) {
  .deck.is-live.is-seen .deck-card[data-state="active"] .plan-foil::before {
    animation: plan-foil-run 1100ms linear 60ms 1;
  }
}
@keyframes plan-foil-run {
  from { transform: translate3d(-160%, 0, 0) skewX(-14deg); }
  to   { transform: translate3d(420%, 0, 0) skewX(-14deg); }
}

/* a faixa se abre como uma persiana da esquerda pra direita (clip-path, não
   width: nenhum pixel de layout se move) e só o conteúdo desliza um pouco. */
@keyframes delta-open {
  from { clip-path: inset(0 100% 0 0); opacity: .4; }
  to   { clip-path: inset(0 0 0 0); opacity: 1; }
}

/* preço não "entra": ele ASSENTA. Escala vindo de .96 — nada no mundo real
   aparece do nada, e um preço que cresce de zero parece promoção de banner.
   E não parte de opacidade ZERO: o preço é a informação que a pessoa veio
   buscar, e um card que fica com o buraco do preço vazio por um terço de
   segundo a cada virada de carta parece quebrado, não elegante. Parte de .4 —
   já legível no primeiro quadro, só assentando. Mesma razão dos atrasos
   curtos: a coreografia inteira termina antes de 620ms, e nada essencial
   passa mais de ~140ms fora de vista. */
@keyframes price-settle {
  from { transform: scale(.96); opacity: .4; }
  to   { transform: none; opacity: 1; }
}

@keyframes benefit-in {
  from { transform: translate3d(0, 7px, 0); opacity: 0; }
  to   { transform: none; opacity: 1; }
}

/* -------------------------------------------------------------- por que
   O módulo é um circuito. Duas coisas acontecem aqui e as duas são scroll,
   não tempo — quem controla é o dedo do usuário:

   1) ENCAIXE (transição entre módulos). O envelope .why é claro, do mesmo
      --bg-soft dos vizinhos (planos antes, apps depois). O painel escuro
      entra como placa arredondada e recuada, trava full-bleed durante a
      leitura e se recolhe na saída. Assim o azul profundo não bate de
      frente no módulo claro: ele chega e sai, em vez de existir de repente.
   2) CORRENTE (o gancho de leitura). Um pulso amarelo percorre o trilho e
      cada card só acende quando a corrente passa por ele. Quem quiser ler
      acompanha o raio — que é literalmente a marca. É a ÚNICA seção do site
      com revelação sequenciada: ela paga esse custo sendo a mais curta.

   Sem `view()` (ou com reduced-motion) nada disso roda e a seção nasce
   inteira, acesa e legível — a mesma regra do resto do arquivo. */

/* O envelope é o "chão" que aparece nas beiradas enquanto a placa está
   recuada. Ele não tem uma cor só porque os vizinhos não têm: em cima está o
   módulo de planos (claro) e embaixo o muro de cartazes (quase preto). Metade
   e metade, com a virada no miolo — onde a placa está encaixada e o chão
   nunca aparece. Assim a placa parece sair de um módulo e entrar no outro,
   em vez de flutuar sobre uma faixa de cor que não pertence a lugar nenhum. */
/* A ORDEM DA HOME MUDOU e o chão acompanhou: hoje o muro de cartazes vem
   ANTES (quase preto, em cima) e a contratação depois (claro, embaixo). Se o
   gradiente ficasse como estava, a placa escura sairia de um chão claro para
   um chão escuro — exatamente ao contrário dos vizinhos, e a costura
   apareceria como duas faixas de cor erradas. */
.why {
  --why-inset: 4%;
  --why-round: 22px;
  --why-floor-prev: #050D1A;  /* = --cine-bg da seção de cartazes, acima */
  background: linear-gradient(180deg, var(--why-floor-prev) 0 42%, var(--bg-soft) 58%);
}

/* Só os quadrados: nada de trilho, derivação ou fio ligando os blocos. A
   sequência não precisa ser desenhada — ela é o próprio movimento das
   cartas chegando uma depois da outra. */
.why-grid {
  position: relative;
  /* coluna única no celular: com o gancho na frente, dois cards lado a lado
     dariam 8 linhas de texto em 160px de largura. */
  display: grid; grid-template-columns: 1fr; gap: var(--sp-3);
}

.why-card {
  position: relative; isolation: isolate;
  background: rgba(255, 255, 255, .05);
  border: 1px solid rgba(255, 255, 255, .12); border-radius: var(--r-md);
  padding: var(--sp-5) var(--sp-4);
  /* pivô na base, igual às cartas do baralho de planos: elas giram apoiadas
     na mesa, e é essa a física que o card herda ao ser distribuído. */
  transform-origin: 50% 100%;
  /* sem levantar no hover: o card não é clicável, e "tudo levanta um pouco"
     é justamente o hover genérico que não comunica nada. Quem responde ao
     cursor aqui é o BorderGlow. */
  transition: border-color var(--dur-ui) var(--ease-out), background-color var(--dur-ui) var(--ease-out);
  /* BorderGlow: mesmos tokens do card de plano, com o halo mais curto —
     são quatro lado a lado, e 48px de vazamento encostariam um no outro. */
  --glow-radius: var(--r-md);
  --glow-color: var(--c-accent);
  --edge-sensitivity: 2;
  --color-sensitivity: 6;
  --cone-spread: 22;
  --cone-fade: 16;
  --glow-span: 44;
  --glow-pad: 26px;
}
/* a borda NÃO acende inteira no hover: quem responde ao cursor é o
   BorderGlow, que acende só o setor pra onde ele aponta. Acender a moldura
   toda por baixo apagava justamente a direção — o efeito virava um retângulo
   amarelo. Sobra o clareamento do fundo, que só diz "é este aqui". */
.why-card:hover { background: rgba(255, 255, 255, .09); }

/* O gancho vem antes do argumento técnico, e é uma cena — não um recurso e
   não um problema. Ninguém compra "velocidade simétrica": compra a live que
   sobe no meio da festa. Cada frase descreve a casa DEPOIS da Zeus, no
   presente, como se sempre tivesse sido assim — a venda é a normalidade.
   Itálico pesado da Montserrat: é a voz das artes que a Zeus publica. */
.why-hook {
  font-family: var(--font-display); font-style: italic; font-weight: 800;
  font-size: 1.02rem; line-height: 1.25; letter-spacing: -.02em;
  color: var(--c-accent); text-wrap: balance;
  /* sem filete embaixo: a mudança de cor, peso e itálico já separa o gancho
     do resto. O traço só somava mais uma linha num bloco que pediu menos —
     mas aí o respiro tem que fazer o trabalho dele. Ritmo do card: folga
     maior entre gancho e título (papéis diferentes), menor entre título e
     corpo (mesmo papel). Sem isso os dois primeiros viram um bloco só. */
  margin: 0 0 var(--sp-4);
}

/* ÍCONE: glifo solto, sem pastilha. Fica sozinho na primeira linha do card
   em vez de dentro do h3 — assim ícone, gancho, título e texto começam
   todos no MESMO x, e o título de duas linhas não precisa contornar nada.
   Uma coluna só de alinhamento é o que faz o bloco descansar o olho. */
.why-ico {
  display: block; width: 22px; height: 22px;
  background: none; border-radius: 0;
  margin-bottom: var(--sp-4);
}
.why-ico::after { inset: 0; background: var(--c-accent); }

.why-card h3 { font-size: 1rem; line-height: 1.25; margin-bottom: var(--sp-2); }
/* :not(.why-hook) não é preciosismo — o gancho TAMBÉM é um <p> dentro do
   card, e `.why-card p` (0,1,1) ganha de `.why-hook` (0,1,0) por
   especificidade, em qualquer ordem de arquivo. Sem esta exclusão o gancho
   herda o corpo do texto: tamanho, entrelinha e margem zerada. */
.why-card p:not(.why-hook) { color: rgba(255, 255, 255, .76); font-size: .86rem; line-height: 1.5; margin: 0; }


/* ------------------------------------------------- apps · muro de conteúdo
   O módulo anterior fecha o argumento técnico; aqui a luz apaga e entra a
   parte que o cliente reconhece de olho: a arte do que ele já assiste.
   Por isso não é fileira de logo pequeno — é cartaz 2:3, grande, sangrando
   de borda a borda, em DUAS faixas correndo em direções opostas. O sentido
   contrário é o que cria profundidade: as duas faixas nunca formam bloco.
   Vitrine passiva: sem seta, sem ponto, nada para arrastar — o cliente só
   olha, e o CTA embaixo recolhe a atenção.
   Movimento contínuo pede `linear` (mesma lógica de marquee/progress bar) e
   duração longa (46s/58s): a faixa de trás mais lenta = paralaxe barata.
   Cada faixa sai 3x no HTML (2 cópias `aria-hidden`) e anda -33.3333% da
   própria largura = exatamente um set: a costura fecha e, com 3 sets, o
   trilho continua mais largo que a tela mesmo num monitor 4K.
   `prefers-reduced-motion` já zera a duração da animação globalmente (regra
   no fim do arquivo); aqui só tiramos as cópias e a inclinação. */

/* UMA TELA, SEM ROLAR POR DENTRO. O cartaz é medido em `svh`: cresce com a
   tela em vez de estourar a dobra, e o `clamp` é o cinto nas duas pontas.
   `.section.section--cinema` e não `.section--cinema` porque a compactação
   mobile lá embaixo zera o `min-height` de `.screen` — aqui a tela cheia é
   justamente o ponto.

   O MURO É O FUNDO, NÃO UMA FILA NO MEIO DA PÁGINA.
   O cartaz precisa ser grande para parar o dedo, e o módulo precisa continuar
   com uma tela de altura — as duas coisas só convivem se o muro sair do fluxo
   e passar POR TRÁS do título e do botão. O que cresce é a imagem, nunca o
   contêiner. Três camadas:
     0 · o muro (absoluto, sangra dos quatro lados)
     1 · o véu, que devolve contraste ao texto sobre a arte
     2 · título e CTA.
   E o módulo pede a tela inteira ao chegar (scroll-snap): meia seção da
   vizinha aparecendo aqui em cima estraga exatamente o que o muro construiu.
   `scroll-margin-top` negativo cancela o `scroll-padding-top` do <html> (que
   existe para âncoras) — sem ele o snap pararia com a seção anterior à
   mostra na altura do cabeçalho. */
.section.section--cinema {
  --cine-bg: #050D1A;
  --wall-tilt: -1.6deg;
  /* no celular o limite não é a altura, é a largura: acima de ~50% da tela o
     cartaz deixa de parecer esteira e vira slide parado. */
  --poster-h: clamp(200px, 34svh, 360px);
  /* a faixa livre é a tela MENOS o cabeçalho sticky — com `100svh` o módulo
     nascia mais alto que o espaço que sobra e o ímã encaixava o topo da seção
     atrás do cabeçalho: o título entrava cortado ao meio e a base do módulo
     ficava fora da dobra, jogando a regra e o CTA em cima dos cartazes em vez
     da faixa escura do véu, que mora nas pontas. */
  min-height: var(--screen);
  display: flex; flex-direction: column; justify-content: space-between;
  position: relative; isolation: isolate; overflow: hidden;
  background: var(--cine-bg); color: var(--c-white);
  /* O encaixe do documento saiu (ver <html>, ele teleportava a página) e virou
     ímã em JS — `data-magnet` no HTML, `site.js`.
     O `scroll-margin-top` negativo que existia aqui saiu junto com o
     cabeçalho não-fixo: ele cancelava o `scroll-padding-top` do <html>
     porque nada cobria o topo da tela. Agora o cabeçalho é sticky e ocupa
     essa faixa de verdade — cancelar a folga colocaria o título do módulo
     debaixo dele em todo salto para `/#apps`. */
}
/* véu: escuro onde o texto mora (topo e base), quase limpo no miolo, que é
   onde a arte tem que aparecer inteira. */
.section--cinema::after {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background:
    radial-gradient(64% 30% at 50% 8%, rgba(4, 10, 20, .96), rgba(4, 10, 20, .5) 62%, transparent 84%),
    radial-gradient(72% 26% at 50% 94%, rgba(4, 10, 20, .96), rgba(4, 10, 20, .5) 62%, transparent 84%),
    /* as duas faixas de texto são chão, não transparência: o miolo (30–66%),
       onde o cartaz tem que aparecer inteiro, continua quase limpo. */
    linear-gradient(to bottom,
      rgba(4, 10, 20, .9) 0,
      rgba(4, 10, 20, .66) 16%,
      rgba(4, 10, 20, .26) 32%,
      rgba(4, 10, 20, .26) 64%,
      rgba(4, 10, 20, .7) 84%,
      rgba(4, 10, 20, .92) 100%);
}
.section--cinema > .wrap { position: relative; z-index: 2; }
.section--cinema .section-sub { color: rgba(255, 255, 255, .82); }
.section--cinema .eyebrow { color: var(--c-accent); }
.section--cinema .section-head { margin-bottom: 0; }
.section--cinema .center-cta { margin-top: 0; }
/* mesma razão da .wall-rule: o texto mora sobre arte que muda a cada segundo,
   então ele carrega o próprio contraste em vez de depender do que passa atrás */
.section--cinema .section-title, .section--cinema .eyebrow {
  text-shadow: 0 2px 16px rgba(4, 10, 20, .95), 0 0 5px rgba(4, 10, 20, .85);
}

/* `inset` vertical negativo: a caixa da máscara precisa passar da seção, senão
   ela corta na diagonal o canto dos cartazes girados dentro do campo de visão.
   Quem corta de fato é o `overflow: hidden` da seção, na borda certa. */
.wall {
  position: absolute; inset: -60px 0; z-index: 0;
  display: flex; flex-direction: column; justify-content: center;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
}
/* três caixas, três papéis, e é de propósito:
   .wall       — máscara nas pontas e scroll reveal (reveal-in escreve
                 `transform`, então a inclinação não pode morar aqui);
   .wall-inner — a inclinação, uma vez só. Girar cada faixa cortaria os
                 cantos dos cartazes contra o próprio overflow:hidden dela;
                 girando o conjunto, cada faixa segue cortando na horizontal
                 (que é o que a esteira precisa) e o corte diagonal acontece
                 fora da tela, por isso ela nasce mais larga e sangra;
   .wall-row   — a janela da esteira. */
.wall-inner {
  display: grid; gap: var(--sp-3);
  width: 116%; margin-inline-start: -8%;
  transform: rotate(var(--wall-tilt));
}
/* CLIPA SÓ NA HORIZONTAL. `overflow: hidden` cortava nos quatro lados, e o
   cartaz em destaque (scale 1.06) estourava a altura da própria faixa: a base
   do cartaz de cima e o topo do de baixo apareciam decepados numa linha reta —
   a "linha invisível" entre as duas esteiras. O corte que a esteira precisa é
   lateral; na vertical ela tem que vazar. `overflow-x: clip` + `overflow-y:
   visible` é o único par que faz isso (com `hidden` o navegador promove o
   outro eixo para `auto` e volta a cortar, ainda por cima com scroll).
   Quem segura a lateral de fato continua sendo a máscara de `.wall` e o
   `overflow: hidden` da seção.
   `position: relative` existe para o par de z-index abaixo: a faixa B vem
   depois no HTML, então pintava por cima da A; sem subir a faixa sob o cursor,
   vazar na vertical só trocaria o corte por sobreposição.

   E o `overflow-y: visible` sozinho NÃO bastou: o `.wall-track` é camada
   composta (`will-change: transform`), e camada composta é recortada pela
   caixa de clipe do ancestral nos DOIS eixos — a "linha invisível" voltava
   pela porta dos fundos, comendo a base do cartaz da faixa de cima e o topo
   do da faixa de baixo. A correção que não depende de o navegador respeitar o
   par clip/visible é dar FOLGA DENTRO da caixa: `padding-block` empurra a
   borda de clipe (que é a caixa de padding) para longe do cartaz, e o
   `margin-block` negativo devolve exatamente a mesma medida, então a faixa
   ocupa a mesma altura de antes na grade e nada se mexe no repouso.
   A folga tem que cobrir o destaque inteiro: metade do crescimento
   (scale 1.07 sobre --poster-h) mais os 8px que ele sobe. */
.wall-row {
  --poster-air: 36px;
  overflow-x: clip; overflow-y: visible; position: relative; z-index: 0;
  padding-block: var(--poster-air);
  margin-block: calc(var(--poster-air) * -1);
  /* consequência da folga: as duas faixas agora se sobrepõem 36px cada, e a
     faixa B (posterior no HTML) engoliria o hover da base dos cartazes da
     faixa A com uma área vazia. Quem recebe ponteiro é o cartaz, mais nada —
     o `:hover` das faixas e do muro continua chegando pela linha de ancestrais
     do cartaz sob o cursor. */
  pointer-events: none;
}
.poster { pointer-events: auto; }
@media (hover: hover) and (pointer: fine) {
  .wall-row:hover { z-index: 1; }
}
.wall-track {
  display: flex; width: max-content; gap: var(--sp-3);
  animation: wall-run 46s linear infinite;
  will-change: transform;
}
.wall-row--b .wall-track { animation-name: wall-run-back; animation-duration: 58s; }
.wall:focus-within .wall-track { animation-play-state: paused; }
@media (hover: hover) and (pointer: fine) {
  .wall:hover .wall-track { animation-play-state: paused; }
}

@keyframes wall-run {
  from { transform: translate3d(0, 0, 0); }
  to { transform: translate3d(-33.3333%, 0, 0); }
}
@keyframes wall-run-back {
  from { transform: translate3d(-33.3333%, 0, 0); }
  to { transform: translate3d(0, 0, 0); }
}

.poster {
  position: relative; flex: 0 0 auto; margin: 0;
  height: var(--poster-h); width: calc(var(--poster-h) / 1.5);  /* 2:3 pela altura */
  border-radius: var(--r-md); overflow: hidden; background: #0B1B2E;
  box-shadow: 0 18px 38px -16px rgba(0, 0, 0, .85);
  transition: transform var(--dur-ui) var(--ease-out),
              opacity var(--dur-ui) var(--ease-out),
              filter var(--dur-ui) var(--ease-out),
              box-shadow var(--dur-ui) var(--ease-out);
}
/* a arte tem zoom PRÓPRIO, por dentro da moldura: no destaque o cartaz cresce
   1.07 e a imagem cresce mais 1.06 dentro dele. São duas velocidades no mesmo
   gesto — é isso que dá profundidade em vez de "figurinha esticada". */
.poster-art {
  display: block; width: 100%; height: 100%; object-fit: cover;
  transition: transform var(--dur-overlay) var(--ease-out);
}
/* brilho especular: uma faixa de luz atravessa o cartaz em diagonal quando ele
   acende, e some fora dele. Fica presa pelo `overflow: hidden` da moldura, então
   não tem como vazar para a faixa vizinha. Só entra no hover; parada, é
   invisível (translate fora + opacity 0), sem custo de pintura. */
.poster::after {
  content: ""; position: absolute; inset: -40% -60%; pointer-events: none;
  background: linear-gradient(74deg,
    transparent 38%, rgba(255, 255, 255, .16) 47%,
    rgba(255, 255, 255, .34) 50%, rgba(255, 255, 255, .16) 53%, transparent 62%);
  opacity: 0; transform: translate3d(-62%, 0, 0);
}
/* NADA por cima do cartaz. O scrim, o chip de pacote (.poster .tier) e a
   pílula com o logo (.poster-tag) saíram daqui: a arte oficial do app já
   carrega a marca, e o rótulo por cima era o mesmo nome escrito duas vezes.
   Não recolocar — o muro é vitrine de imagem, o texto mora no título da
   seção e a lista por pacote em /planos e /diferenciais. */
/* destacar um cartaz só faz sentido onde existe cursor: no toque isso
   viraria estado preso depois do tap.

   O DESTAQUE ACENDE, NÃO SÓ DESAPAGA. `opacity: 1` devolve o cartaz ao normal,
   mas "normal" aqui é debaixo do véu da seção (`.section--cinema::after`,
   z-index 1) — e o muro é z-index 0, então nenhum cartaz sobe acima dele sem
   levar a arte por cima do título. Por isso o ganho é luz própria: brilho +
   saturação compensam o véu, e o cartaz sob o cursor fica mais claro que a
   linha de base, não apenas menos apagado que os vizinhos. A sombra ganha
   alcance junto porque o cartaz agora flutua sobre a faixa vizinha.

   O gesto tem quatro camadas em tempos diferentes, e é a diferença de tempo que
   faz parecer caro: moldura sobe e cresce (220ms), arte dá zoom por dentro
   (420ms), brilho especular varre (520ms), vizinhos apagam e desfocam (220ms).
   Só `transform`, `opacity`, `filter` — nada que force layout.
   O contorno é `box-shadow: inset`, não `outline` nem borda: contorno por
   dentro não muda a caixa e não pode ser cortado por vizinho nenhum. */
@media (hover: hover) and (pointer: fine) {
  .wall:hover .poster {
    opacity: .3;
    filter: brightness(.82) saturate(.72) blur(1.1px);
  }
  .wall .poster:hover {
    opacity: 1; z-index: 1;
    transform: scale(1.07) translate3d(0, -8px, 0);
    filter: brightness(1.42) saturate(1.16) contrast(1.04);
    box-shadow: 0 34px 60px -18px rgba(0, 0, 0, .92),
                inset 0 0 0 2px rgba(254, 197, 4, .92);
  }
  .wall .poster:hover .poster-art { transform: scale(1.06); }
  .wall .poster:hover::after {
    opacity: 1; transform: translate3d(62%, 0, 0);
    transition: transform 520ms var(--ease-out), opacity 200ms linear;
  }
}

/* Sem movimento o muro vira cenário parado: some a inclinação e as cópias,
   e ele NÃO vira rolagem lateral — é uma camada atrás do texto, transformar
   em área rolável só criaria um alvo de scroll escondido sob o título. */
@media (prefers-reduced-motion: reduce) {
  .wall-inner { width: 100%; margin-inline-start: 0; transform: none; }
  .wall-track .is-duplicate { display: none; }
  /* o destaque continua existindo, só que sem deslocamento: luz e foco sim,
     salto e varredura não (a regra global já zerou a duração, então o que
     sobraria de movimento seria um pulo seco). */
  .wall .poster:hover { transform: none; }
  .wall .poster:hover .poster-art { transform: none; }
  .poster::after { display: none; }
  /* E OS VIZINHOS NÃO APAGAM. Apagar o resto a 30% só se lê enquanto a esteira
     corre: a arte passa, o cartaz sob o cursor se separa do fluxo, e um segundo
     depois é outro. Com a esteira parada o ponteiro fica em cima do módulo, e o
     apagar deixa de ser destaque para virar estado: o muro inteiro fica a 30%,
     borrado, parecendo apagado ou quebrado. O destaque continua — só que pelo
     que o cartaz sob o cursor GANHA (brilho + saturação + contorno), não pelo
     que os outros perdem. `:not(:hover)` é o que preserva esse ganho. */
  .wall:hover .poster:not(:hover) { opacity: 1; filter: none; }
}

.tier {
  font-size: .62rem; font-weight: 900; letter-spacing: .1em; text-transform: uppercase;
  padding: 3px 9px; border-radius: var(--r-pill);
}
.tier--standard { background: rgba(2, 62, 122, .12); color: var(--c-blue); }
.tier--top { background: rgba(254, 197, 4, .22); color: #7a5c00; }
.tier--premium { background: var(--c-blue); color: var(--c-accent); }

/* A regra que o cliente não entendia: o cartaz mostra o que TEM no app, mas
   ninguém dizia que é ele quem escolhe os apps dentro de cada categoria. */
.wall-rule {
  margin: 0 auto var(--sp-3); max-width: 46ch; text-align: center; font-size: .88rem;
  color: rgba(255, 255, 255, .92);
  /* a linha cai no miolo do módulo, onde o véu é quase limpo para a arte
     aparecer: sem a sombra ela briga com o cartaz que está atrás */
  text-shadow: 0 2px 14px rgba(4, 10, 20, .95), 0 0 4px rgba(4, 10, 20, .8);
}

/* =================================================== apps por categoria

   Eram três caixas azuis em colunas de altura igual. Mas a quantidade de app
   por categoria é desigual POR NATUREZA (hoje Standard tem 1 e Top tem 3), e
   colunas de grid esticam todas na altura da mais cheia: a categoria curta
   virava um retângulo azul vazio do tamanho da cheia. Junto disso, o logo
   espremido num tile de 92px ficava ilegível e o nome do app aparecia duas
   vezes (dentro da arte e embaixo dela).

   Agora é UM painel azul dividido em TRILHAS horizontais. Trilha tem a altura
   do próprio conteúdo: 1 app ocupa uma linha, 12 apps ocupam quatro — não
   sobra buraco em nenhum dos dois casos, e cadastrar o próximo app no painel
   não exige tocar em CSS (regra da seção "crescer é operação de painel").

   O logo vai sobre PLACA CLARA porque é assim que a marca oficial é entregue
   (colorida ou escura, fundo transparente): sobre azul royal, logo preto
   some. É a mesma instrução que /admin/midia já dá para o Felipe. */
.tier-lanes {
  position: relative;
  margin-top: var(--sp-6);
  padding: clamp(12px, 3.4vw, 26px) clamp(14px, 3.4vw, 30px);
  border-radius: var(--r-lg);
  background: linear-gradient(168deg, var(--c-blue) 0%, var(--c-blue-deep) 82%);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}
/* malha de rede da arte do cliente, reduzida a pontos e apagando na diagonal:
   dá textura ao painel sem competir com as placas brancas */
.tier-lanes::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background-image: radial-gradient(circle at center, rgba(255, 255, 255, .18) 1px, transparent 1.6px);
  background-size: 26px 26px;
  -webkit-mask-image: radial-gradient(120% 84% at 8% 0%, #000 0%, transparent 70%);
  mask-image: radial-gradient(120% 84% at 8% 0%, #000 0%, transparent 70%);
}

.lane { position: relative; display: grid; gap: var(--sp-3); padding: var(--sp-4) 0; }
.lane + .lane { border-top: 1px solid rgba(255, 255, 255, .14); }

.lane-rail { display: flex; align-items: baseline; gap: var(--sp-3); }
.lane-tag {
  position: relative; margin: 0; padding-left: 14px;
  font-family: var(--font-display); font-style: italic; font-weight: 800;
  font-size: clamp(1rem, 3.8vw, 1.24rem); letter-spacing: .02em;
  text-transform: uppercase; color: #fff;
}
/* o raio da marca reduzido a uma barra: marca a trilha sem virar mais um selo */
.lane-tag::before {
  content: ""; position: absolute; left: 0; top: .1em; bottom: .1em; width: 4px;
  border-radius: 2px; background: var(--c-accent);
}
/* escalada entre categorias — Standard é branco, Top acende, Premium acende
   mais forte. A hierarquia do produto vira hierarquia visual de graça. */
.lane--top .lane-tag,
.lane--premium .lane-tag { color: var(--c-accent); }
.lane--premium .lane-tag::before {
  width: 6px; box-shadow: 0 0 14px 2px color-mix(in srgb, var(--c-accent) 55%, transparent);
}
.lane-count {
  margin: 0; font-size: .72rem; font-weight: 700; letter-spacing: .14em;
  text-transform: uppercase; color: rgba(255, 255, 255, .55);
}
/* Fecho das trilhas: "tem mais apps, consulte". Fica DEPOIS da última trilha e
   não dentro de nenhuma, porque vale para as três. Alinha o texto e o botão na
   mesma linha no desktop e empilha no celular, onde a frase sozinha já ocupa
   duas linhas. */
.lanes-more {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3);
  margin: var(--sp-5) 0 0; padding-top: var(--sp-4);
  border-top: 1px solid rgba(255, 255, 255, .14);
  font-size: .95rem; color: rgba(255, 255, 255, .78);
}
.lanes-more > span { flex: 1 1 260px; }

/* "com anúncios" na trilha Top. Fica do lado do nome, não junto da contagem:
   é qualificação da OFERTA — é o que diferencia o Disney+ da Top do mesmo
   Disney+ da Premium —, não estatística da trilha. */
.lane-note {
  margin: 0; font-size: .74rem; font-weight: 600; font-style: italic;
  color: rgba(255, 255, 255, .72);
}

.lane-apps { display: flex; flex-wrap: wrap; gap: var(--sp-3); margin: 0; padding: 0; list-style: none; }
/* SEM CHAPA BRANCA. A chapa existia porque o logo do app era um arquivo solto
   de marca (colorida ou escura) que precisava de um fundo claro para não sumir.
   Desde 2026-09-01 o que entra aqui é o ÍCONE do app, recortado da arte do
   próprio cliente: ele já vem com o fundo da marca e o canto arredondado. A
   chapa virou moldura branca em volta de um tile que já é um tile — e o formato
   2/1 morreu na prática, porque o ícone é quadrado e empurrava a altura (a
   placa media 176×168, quase quadrada, com branco sobrando dos dois lados).

   Então: quadrado, sem fundo, sem respiro, o ícone ocupando a caixa inteira.
   O raio acompanha o do próprio ícone (22%) porque quem usa a caixa é o anel
   do hover — se o raio divergir, o anel corta a quina da arte.

   NÃO tentar alargar o ícone para um retângulo: já foi tentado por fundo
   borrado e por faixa esticada, e as duas emendam visivelmente em HBO Max e
   Disney+, que têm variação horizontal no fundo. Alargar ícone quadrado é
   inventar arte da marca, e aparece. Retângulo de verdade só com peça
   retangular vinda do cliente. */
.app-plate {
  flex: 0 0 auto;
  width: clamp(112px, 32vw, 148px);
  aspect-ratio: 1 / 1;
  display: grid; place-items: center;
  border-radius: 22%;
  transition: transform var(--dur-micro) var(--ease-out), box-shadow var(--dur-micro) var(--ease-out);
}
.app-plate-logo { width: 100%; height: 100%; object-fit: contain; }
/* app sem ícone cadastrado cai no letreiro. Sem a chapa
   branca ele ficaria texto azul sobre fundo azul: ganha caixa própria, discreta,
   no lugar exato onde o ícone entraria quando a arte chegar. */
.app-plate-word {
  display: grid; place-items: center;
  width: 100%; height: 100%; padding: 8px;
  border-radius: inherit;
  background: rgba(255, 255, 255, .10);
  border: 1px solid rgba(255, 255, 255, .18);
  font-family: var(--font-display); font-weight: 800; font-size: .95rem;
  text-align: center; color: var(--c-white);
}
@media (hover: hover) and (pointer: fine) {
  .app-plate:hover {
    transform: translateY(-3px);
    box-shadow:
      0 14px 26px -8px rgba(2, 20, 44, .6),
      0 0 0 2px color-mix(in srgb, var(--c-accent) 75%, transparent);
  }
}
@media (prefers-reduced-motion: reduce) { .app-plate { transition: none; } }

/* sobra largura: o nome da categoria sai de cima e vira trilho à esquerda,
   as placas correm ao lado. Menos altura, mesma leitura. */
@media (min-width: 760px) {
  .lane {
    grid-template-columns: 176px minmax(0, 1fr);
    align-items: center; gap: var(--sp-5); padding: var(--sp-5) 0;
  }
  .lane-rail { flex-direction: column; align-items: flex-start; gap: 4px; }
  .lane-apps { gap: var(--sp-4); }
}


/* ------------------------------------------------------- feature / extend */

.feature-grid { display: grid; gap: var(--sp-5); align-items: center; }
.feature-copy p { color: var(--ink-soft); }
/* zera a margem de 40px que o navegador dá a `figure` por padrão: no celular
   ela recuava a arte para dentro da coluna de texto, deixando a figura mais
   estreita que o parágrafo logo acima. `.coverage-map` já fazia o mesmo. */
.feature-media { margin: 0; }
.feature-media img {
  width: 100%; height: auto; border-radius: var(--r-lg); box-shadow: var(--shadow-md);
}
.check-list { display: grid; gap: 10px; margin-bottom: var(--sp-4); }
.check-list li { display: flex; align-items: flex-start; gap: 10px; font-size: .95rem; }
.check-list li::before {
  content: ""; flex: 0 0 auto; width: 20px; height: 20px; margin-top: 2px; border-radius: 50%;
  background: var(--c-accent);
  -webkit-mask: url("/img/icons/check.svg") center / 12px no-repeat, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask: url("/img/icons/check.svg") center / 12px no-repeat, linear-gradient(#000 0 0);
  mask-composite: exclude;
}

/* ------------------------------------------------------------- cobertura
   O mapa agora tem proporção fixa e teto de altura: nunca mais estica a
   seção nem abre buraco branco enquanto a imagem não decodifica. */

.coverage-grid { display: grid; gap: var(--sp-5); align-items: center; }
.coverage-map { margin: 0; }
.coverage-map img {
  width: 100%;
  aspect-ratio: 16 / 10;
  max-height: min(46svh, 420px);
  object-fit: cover;
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-md);
  background: var(--bg-soft);
}
.coverage-map figcaption { margin-top: var(--sp-2); text-align: center; font-size: .82rem; }
/* As praças viram LISTA, não cartões: a operação está expandindo e o painel
   cadastra cidade nova — em cartão, cada praça custava ~120px de altura e o
   módulo estourava a tela na quarta cidade. A lista rola dentro do bloco. */
.coverage-cities { display: grid; gap: var(--sp-3); align-content: center; }
.coverage-list-title { font-size: 1.05rem; }
/* com a expansão, as praças saem agrupadas por região (campo do painel).
   O bloco inteiro é que rola — não cada grupo por sua conta. */
.coverage-groups { display: grid; gap: var(--sp-3); }
.coverage-region {
  margin: 0; font-family: var(--font-body); font-size: .74rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: .08em; color: var(--ink-soft);
}
.coverage-list {
  display: grid; gap: 2px; list-style: none; margin: 0; padding: 0;
  background: var(--line); border: 1px solid var(--line);
  border-radius: var(--r-md); overflow: hidden;
}
.coverage-item {
  display: grid; gap: 2px var(--sp-3); align-items: center;
  padding: var(--sp-3) var(--sp-4);
  background: #fff;
  border-left: 4px solid var(--c-accent);
}
.coverage-item.is-soon { border-left-color: #c9d4e2; }
.coverage-item-city { display: flex; align-items: center; gap: 8px; font-size: 1rem; }
.coverage-item-uf {
  font-size: .68rem; font-weight: 700; letter-spacing: .06em;
  color: var(--c-blue); background: rgba(2, 62, 122, .08);
  padding: 2px 6px; border-radius: 999px;
}
.coverage-item-badge {
  font-size: .66rem; font-weight: 700; text-transform: uppercase; letter-spacing: .06em;
  color: #7a5c00; background: rgba(254, 197, 4, .22);
  padding: 2px 8px; border-radius: 999px;
}
.coverage-item-note { color: var(--ink-soft); font-size: .84rem; }
/* Duas linhas com a mesma forma e destinos diferentes: cidade que já vende
   leva para /planos (navegação interna, azul) e praça "em breve" abre o
   WhatsApp da lista de espera (verde). A lei de cor vale também em peso de
   link — era a mesma cor para as duas. */
.coverage-item-cta {
  justify-self: start; font-weight: 700; font-size: .86rem; color: var(--c-blue);
  text-decoration: none; padding: 2px 0;
  border-bottom: 2px solid transparent;
  transition: border-color var(--dur-micro) var(--ease-out);
}
.coverage-item-cta::after { content: " →"; }
.coverage-item-cta:hover, .coverage-item-cta:focus-visible { border-bottom-color: var(--c-accent); }
.coverage-item-cta--wa { color: var(--wa-deep); }
.coverage-item-cta--wa:hover, .coverage-item-cta--wa:focus-visible { border-bottom-color: var(--wa); }
.coverage-list-foot { font-size: .82rem; }
/* enquanto o mapa for arte genérica ele é elemento secundário: mapa vago
   comunica "não sabemos onde atendemos". Quem responde é a lista de bairros. */
.coverage-map--aside img { max-height: min(34svh, 300px); }

/* NÃO EXISTE MAIS lista/busca de bairro (`.areas`, `.area-*`): o site publica
   cobertura em CIDADE e a consulta de endereço acontece no WhatsApp
   (CLAUDE.md § "Dados de localização"). Não reintroduzir. */

/* ---------------------------------------------------------- palco de fotos
   ("Zeus Net por dentro" / "Nossa operação")

   Foto de equipe é o argumento de confiança mais caro da página, e a faixa
   com snap tratava cada uma como miniatura de logo. Aqui é palco: UMA foto
   por vez, quadro cheio, e a troca é uma CORTINA — o próximo slide entra por
   clip-path enquanto a imagem assenta de 1.06 para 1. Cortina e não fade
   porque duas fotos sobrepostas em crossfade viram borrão; cortina lê como
   corte de cinema.

   Sem rotação e sem Z: profundidade continua sendo exclusividade do baralho
   de planos (ver TESE DE MOVIMENTO no topo). O que dá ousadia aqui é escala,
   corte e tipografia — o numerão em Montserrat itálico é a mesma voz das
   artes que o cliente publica.

   Só transform/opacity/clip-path animam, e por TRANSIÇÃO (não keyframes):
   quem metralha a seta interrompe a cortina no meio em vez de reiniciar.

   `.is-live` é escrito pelo JS. Sem JS não há palco: as fotos ficam em coluna,
   cada uma com sua legenda, e a seção continua inteira — mesma convenção do
   baralho de planos (`.deck.is-live`). */

.stage { --stage-dur: 560ms; position: relative; display: grid; gap: var(--sp-3); }
.stage:not(.is-live) .stage-count,
.stage:not(.is-live) .stage-caption,
.stage:not(.is-live) .stage-bar,
.stage:not(.is-live) .stage-rail { display: none; }

.stage-frame {
  position: relative; overflow: hidden;
  border-radius: var(--r-lg);
  background: var(--c-blue-deep);
  box-shadow: var(--shadow-card);
}
.stage.is-live .stage-frame {
  aspect-ratio: 3 / 2; max-height: min(56svh, 520px);
  touch-action: pan-y;   /* arrasto horizontal é nosso; vertical continua rolando a página */
}
/* cantos em colchete: a mesma moldura das artes do cliente, agora recortada
   por máscara em quatro quadrados — só as quinas da borda sobrevivem. */
.stage-frame::after {
  content: ""; position: absolute; inset: 10px; z-index: 3; pointer-events: none;
  border: 2px solid rgba(255, 255, 255, .55); border-radius: 14px;
  -webkit-mask:
    linear-gradient(#000 0 0) top left / 34px 34px no-repeat,
    linear-gradient(#000 0 0) top right / 34px 34px no-repeat,
    linear-gradient(#000 0 0) bottom left / 34px 34px no-repeat,
    linear-gradient(#000 0 0) bottom right / 34px 34px no-repeat;
  mask:
    linear-gradient(#000 0 0) top left / 34px 34px no-repeat,
    linear-gradient(#000 0 0) top right / 34px 34px no-repeat,
    linear-gradient(#000 0 0) bottom left / 34px 34px no-repeat,
    linear-gradient(#000 0 0) bottom right / 34px 34px no-repeat;
}

.stage-track { display: grid; gap: 2px; }
.stage.is-live .stage-track { position: absolute; inset: 0; display: block; gap: 0; }

.stage-slide { position: relative; margin: 0; overflow: hidden; }
.stage-slide-cap {
  padding: var(--sp-2) var(--sp-3) var(--sp-3);
  color: var(--c-white); font-family: var(--font-display); font-style: italic;
  font-weight: 800; text-transform: uppercase; font-size: 1rem;
}
.stage.is-live .stage-slide-cap { display: none; }

/* estado de repouso = fora do quadro pelo lado de onde a foto vai ENTRAR.
   O sentido é dado pelo data-dir, escrito pelo JS antes de trocar a classe. */
.stage.is-live .stage-slide {
  position: absolute; inset: 0;
  clip-path: inset(0 0 0 100%);
  transition: clip-path var(--stage-dur) var(--ease-out), transform 320ms var(--ease-out);
}
.stage.is-live[data-dir="prev"] .stage-slide { clip-path: inset(0 100% 0 0); }
/* a que está saindo fica inteira embaixo: é ela que a cortina cobre */
.stage.is-live .stage-slide.is-prev { clip-path: inset(0); z-index: 1; transition: none; }
.stage.is-live .stage-slide.is-active { clip-path: inset(0); z-index: 2; }
/* durante o arrasto o dedo manda: transição de transform sairia do caminho
   do dedo e chegaria atrasada. Ela volta na hora de soltar. */
.stage.is-dragging .stage-slide.is-active { transition: none; }

.stage-img {
  width: 100%; aspect-ratio: 3 / 2; object-fit: cover; display: block;
  user-select: none; -webkit-user-drag: none;
}
.stage.is-live .stage-img {
  height: 100%; aspect-ratio: auto;
  transform: scale(1.06);
  /* mais lento que a cortina de propósito: a foto continua assentando depois
     que o corte terminou, e é isso que dá sensação de câmera, não de slide */
  transition: transform 1200ms var(--ease-out);
}
.stage.is-live .stage-slide.is-active .stage-img { transform: scale(1); }
.stage.is-live .stage-slide.is-prev .stage-img { transform: scale(1.03); }

/* ---- numerão: 01/03 em itálico pesado, ativo sólido e total em contorno */
/* UMA FOTO SÓ: o JS marca contador e trilha com `hidden` (controle que não
   controla nada é ruído — public/js/site.js, ramo `single`), mas `display:flex`
   abaixo tem especificidade maior que a regra do atributo e o `hidden` não
   pegava. Ficava um "01" e uma miniatura solitária no quadro. Não apareceu por
   meses porque a galeria sempre teve quatro fotos; apareceu em 2026-09-09, ao
   suspender as três maquetes. A barra já obedecia; as SETAS, só no celular:
   no bloco do desktop `.stage.is-live .stage-arrow { display: grid }` vencia o
   `hidden` do mesmo jeito, e duas setas mortas ficaram no palco de uma foto só
   até 2026-09-10 — por isso lá a regra é `:not([hidden])`. */
.stage-count[hidden],
.stage-rail[hidden] { display: none; }

.stage-count {
  position: absolute; z-index: 4; margin: 0;
  top: clamp(12px, 3.5vw, 22px); left: clamp(16px, 4.5vw, 28px);
  display: flex; align-items: baseline;
  font-family: var(--font-display); font-style: italic; font-weight: 900;
  font-size: clamp(2rem, 8.5vw, 3.6rem); line-height: .9; letter-spacing: -.03em;
}
.stage-count-now {
  color: var(--c-accent);
  transition: transform 170ms var(--ease-out), opacity 170ms var(--ease-out), filter 170ms var(--ease-out);
}
.stage-count-all {
  font-size: .44em; color: transparent;
  -webkit-text-stroke: 1.5px rgba(255, 255, 255, .6);
}
/* o blur existe para colar as duas metades da troca: sem ele o olho vê dois
   números distintos passando, com ele vê UM número virando outro.
   Saída rápida e acelerando (--ease-in), entrada mais longa e desacelerando:
   o sistema responde na hora, a informação nova chega com calma. */
.stage-count-now.is-out {
  transform: translateY(-.3em); opacity: 0; filter: blur(5px);
  transition-duration: 180ms; transition-timing-function: var(--ease-in);
}
.stage-count-now.is-in { transform: translateY(.3em); opacity: 0; filter: blur(5px); transition: none; }

/* ---- legenda */
.stage-caption {
  position: absolute; z-index: 4; left: 0; right: 0; bottom: 0; pointer-events: none;
  padding: clamp(34px, 10vw, 64px) clamp(18px, 5vw, 30px) clamp(16px, 4vw, 24px);
  background: linear-gradient(180deg, transparent, rgba(1, 32, 63, .88) 64%);
}
.stage-caption-text {
  margin: 0; color: var(--c-white);
  font-family: var(--font-display); font-style: italic; font-weight: 800;
  font-size: clamp(1.02rem, 4.4vw, 1.55rem); line-height: 1.15;
  text-transform: uppercase; text-wrap: balance;
  transition: transform 300ms var(--ease-out), opacity 300ms var(--ease-out), filter 300ms var(--ease-out);
}
.stage-caption-text.is-out {
  transform: translateY(10px); opacity: 0; filter: blur(6px);
  transition-duration: 180ms; transition-timing-function: var(--ease-in);
}
.stage-caption-text.is-in { transform: translateY(-10px); opacity: 0; filter: blur(6px); transition: none; }

/* ---- barra do autoplay: é o relógio do carrossel, não enfeite. O JS troca
   de slide no transitionend dela, então pausar a barra pausa o carrossel. */
.stage-bar {
  position: absolute; z-index: 5; left: 0; right: 0; bottom: 0; height: 3px;
  background: var(--c-accent); transform-origin: left center;
  /* o progresso mora numa variável (e não em style.transform inline) porque
     ao pausar o JS precisa CONGELAR onde a barra está e depois retomar dali:
     valor inline venceria a regra .is-running e a retomada saltaria pro zero */
  transform: scaleX(var(--stage-p, 0));
}
.stage-bar.is-running { transform: scaleX(1); transition: transform var(--stage-auto, 6000ms) linear; }
.stage-bar[hidden] { display: none; }

/* ---- setas: só no ponteiro fino. No toque o arrasto e as miniaturas dão
   conta, e seta grande em cima de foto pequena come a foto. */
.stage-arrow {
  position: absolute; z-index: 5; top: 50%; translate: 0 -50%;
  width: 46px; height: 46px; display: none; place-items: center;
  border: 1px solid rgba(255, 255, 255, .34); border-radius: 12px;
  background: rgba(1, 32, 63, .45); backdrop-filter: blur(8px); color: #fff;
  transition: transform var(--dur-micro) var(--ease-out),
              background-color var(--dur-micro) var(--ease-out),
              opacity var(--dur-micro) var(--ease-out);
}
.stage-arrow::before {
  content: ""; width: 10px; height: 10px;
  border-right: 3px solid currentColor; border-bottom: 3px solid currentColor;
}
.stage-arrow--prev { left: clamp(12px, 2vw, 20px); }
.stage-arrow--prev::before { transform: rotate(135deg) translate(-2px, -2px); }
.stage-arrow--next { right: clamp(12px, 2vw, 20px); }
.stage-arrow--next::before { transform: rotate(-45deg) translate(-2px, -2px); }
.stage-arrow:active { transform: scale(.94); }
.stage-arrow[hidden] { display: none; }

/* ---- trilho de miniaturas: para foto, escolher pelo que se vê vale mais
   que pontinho anônimo. Some junto com a seta quando só há uma foto. */
.stage-rail { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--sp-2); }
.stage-thumb {
  position: relative; padding: 0; border: 0; background: none; cursor: pointer;
  width: clamp(62px, 19vw, 104px); aspect-ratio: 3 / 2;
  border-radius: 10px; overflow: hidden; opacity: .46;
  transition: opacity var(--dur-ui) var(--ease-out), transform var(--dur-ui) var(--ease-out);
}
.stage-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.stage-thumb::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  border: 2px solid var(--c-accent); opacity: 0;
  transition: opacity var(--dur-ui) var(--ease-out);
}
.stage-thumb.is-active { opacity: 1; transform: translateY(-3px); }
.stage-thumb.is-active::after { opacity: 1; }
.stage-thumb:active { transform: scale(.97); }
.stage-thumb.is-active:active { transform: translateY(-3px) scale(.97); }
@media (hover: hover) and (pointer: fine) {
  .stage-thumb:hover { opacity: .82; }
}

/* Movimento reduzido: a informação continua (a foto troca), o movimento sai.
   Vira corte seco em opacidade, sem cortina, sem deriva de câmera, sem blur. */
@media (prefers-reduced-motion: reduce) {
  .stage.is-live .stage-slide,
  .stage.is-live[data-dir="prev"] .stage-slide {
    clip-path: none; opacity: 0; transition: opacity var(--dur-ui) linear;
  }
  .stage.is-live .stage-slide.is-active, .stage.is-live .stage-slide.is-prev { opacity: 1; }
  .stage.is-live .stage-img,
  .stage.is-live .stage-slide.is-active .stage-img,
  .stage.is-live .stage-slide.is-prev .stage-img { transform: none; transition: none; }
  .stage-caption-text, .stage-count-now { transition: opacity var(--dur-ui) linear; }
  .stage-caption-text.is-out, .stage-caption-text.is-in,
  .stage-count-now.is-out, .stage-count-now.is-in { transform: none; filter: none; }
}

/* ------------------------------------------------------------ depoimentos */

.quote {
  height: 100%; margin: 0; background: #fff; border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: var(--sp-5); box-shadow: var(--shadow-card);
  display: flex; flex-direction: column; gap: var(--sp-3);
}
.stars { display: flex; gap: 3px; color: var(--c-accent); margin: 0; }
.stars .icon { width: 17px; height: 17px; }
.quote-text { font-size: 1rem; margin: 0; }
.quote-author { display: flex; align-items: center; gap: var(--sp-3); margin-top: auto; font-size: .86rem; color: var(--ink-soft); }
.quote-author img { width: 44px; height: 44px; border-radius: 50%; object-fit: cover; }

/* A tabela comparativa de planos saiu em 2026-08-12 (o card já mostra
   velocidade, benefícios, os dois preços e o CTA — a tabela era o mesmo
   plano renderizado uma terceira vez na mesma página). Estilos removidos
   junto: .table-scroll, .plans-table, .td-price, .td-late, .pill,
   .table-note. */

/* ---------------------------------------------------------------- FAQ */

.faq { border: 1px solid var(--line); border-radius: var(--r-md); background: #fff; margin-bottom: var(--sp-3); }
.faq summary {
  cursor: pointer; padding: var(--sp-4); font-weight: 800; list-style: none;
  display: flex; justify-content: space-between; align-items: center; gap: var(--sp-3);
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: ""; width: 10px; height: 10px; flex: 0 0 auto;
  border-right: 2px solid var(--c-blue); border-bottom: 2px solid var(--c-blue);
  transform: rotate(45deg); transition: transform var(--dur-ui) var(--ease-out);
}
.faq[open] summary::after { transform: rotate(-135deg); }
.faq p { padding: 0 var(--sp-4) var(--sp-4); margin: 0; color: var(--ink-soft); }

/* -------------------------------------------------------------- contato */

.contact-grid { display: grid; gap: var(--sp-4); }
.contact-card { background: #fff; border: 1px solid var(--line); border-radius: var(--r-lg); padding: var(--sp-5); box-shadow: var(--shadow-sm); }
.contact-card--wa { border-color: var(--wa); box-shadow: 0 0 0 3px rgba(37, 211, 102, .18); }
.contact-card h2 { font-size: 1.1rem; }
.contact-value { font-size: 1.3rem; font-weight: 900; color: var(--c-blue); margin-bottom: var(--sp-2); }
.contact-card p { color: var(--ink-soft); font-size: .92rem; }
.contact-links { display: flex; gap: var(--sp-2); flex-wrap: wrap; }

/* ------------------------------------------------------ passos (1→2→3→4)
   Quatro números soltos não são uma sequência: sem nada ligando um ao outro
   o bloco lê como quatro avisos independentes. O TRILHO é quem diz que há
   ordem — vertical no celular (a leitura desce), horizontal no desktop (a
   leitura anda). Ele nasce de cada passo e morre antes do número seguinte,
   então não depende da cor de fundo da seção: `.steps` aparece em
   section--alt na home e em fundo branco em /cobertura e /contato. */

.steps { display: grid; gap: var(--sp-4); }
.step { position: relative; display: flex; gap: var(--sp-3); align-items: flex-start; }
.step-n {
  flex: 0 0 auto; width: 38px; height: 38px; border-radius: 50%;
  background: var(--c-accent); color: var(--c-accent-ink);
  display: grid; place-items: center; font-weight: 900;
  box-shadow: 0 2px 10px rgba(254, 197, 4, .45);
}
/* uma coluna: o trilho desce do número até o próximo, parando 8px antes */
.step:not(:last-child)::after {
  content: ''; position: absolute; left: 18px; top: 46px;
  bottom: calc(var(--sp-4) * -1 + 8px); width: 2px;
  background: var(--line); border-radius: 2px;
}
/* o <br> das views vira quebra de bloco: título e descrição passam a ter
   peso, corpo e entrelinha próprios sem mexer nas três páginas que usam
   este markup (home, /contato, /cobertura). */
.step p { margin: 0; font-size: .92rem; line-height: 1.55; color: var(--ink-soft); }
.step p br { display: none; }
.step strong { display: block; margin-bottom: 3px; font-size: 1.12rem; line-height: 1.25; color: var(--ink); }

/* --------------------------------------------------- fatos da contratação
   Custo de entrada, prazo, fidelidade, roteador, pagamento, documentos e
   troca de plano — o conteúdo que não existia em página nenhuma e que era a
   maior causa de "só uma dúvida" no WhatsApp. Vem de `settings`: campo vazio
   não vira item, porque preço inventado custa mais caro que ausência.

   POR QUE FLEX E NÃO GRID: a quantidade de fatos é decisão de painel, não de
   layout — hoje é 1, amanhã pode ser 5 ou os 7. A versão em grade de 2/3
   colunas pintava o fundo da própria lista de cinza e deixava os gaps
   aparecerem: com a linha incompleta (1, 2, 4, 5 ou 7 fatos) sobrava uma
   faixa cinza morta ao lado do único cartão preenchido. Cartão que se
   dimensiona sozinho e centraliza fica certo em QUALQUER contagem. Se um dia
   voltar a virar grade, ela quebra de novo no primeiro campo que o Felipe
   deixar em branco. */

.facts {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: var(--sp-3); margin: var(--sp-6) 0 0; padding: 0;
}
.fact {
  flex: 0 1 268px; min-width: min(100%, 232px);
  display: grid; gap: 3px;
  background: #fff; padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--line); border-left: 3px solid var(--c-accent);
  border-radius: var(--r-md); box-shadow: var(--shadow-sm);
}
.fact dt { font-size: .74rem; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; color: var(--c-blue-soft); }
.fact dd { margin: 0; font-size: 1rem; font-weight: 800; color: var(--ink); }
.facts-empty { margin: var(--sp-5) auto 0; max-width: 62ch; text-align: center; }

.contact-hours { margin: var(--sp-2) 0 0; font-size: .86rem; font-weight: 700; color: var(--c-blue); }
.contact-card--store { border-color: var(--c-accent); box-shadow: 0 0 0 3px rgba(254, 197, 4, .2); }

/* ------------------------------------------------------------- CTA band */

/* A faixa é larga e baixa (~5:1). Num box desse formato um gradiente a 135deg
   vira uma diagonal escancarada de canto a canto, e o clarão AMARELO que
   ficava por cima do azul escuro fechava em verde-oliva no canto de cima —
   era o "gradiente estranho". Aqui vale a mesma receita do card de plano:
   clarão radial ancorado no topo + linear quase vertical. E a rampa termina
   em --c-blue-deep, que é exatamente a cor do rodapé logo abaixo: a faixa
   solda no rodapé em vez de emendar claro contra escuro. */

.cta-band {
  background:
    radial-gradient(120% 140% at 50% -25%, color-mix(in srgb, var(--c-blue-soft) 92%, #fff) 0%, transparent 62%),
    linear-gradient(180deg, var(--c-blue) 0%, var(--c-blue-deep) 100%);
  color: var(--c-white); text-align: center; position: relative; overflow: hidden;
}
/* filete amarelo no topo: separa da seção branca acima e é a única dose de
   amarelo da faixa — amarelo espalhado em clarão é que sujava o azul. */
.cta-band::before {
  content: ""; position: absolute; inset: 0 0 auto; height: 3px;
  background: var(--c-accent); pointer-events: none;
}
.cta-inner {
  position: relative; padding-block: var(--sp-8);
  display: grid; justify-items: center; gap: var(--sp-3);
}
.cta-inner h2 { font-size: clamp(1.5rem, 6vw, 2.3rem); max-width: 20ch; }
.cta-inner p { color: rgba(255, 255, 255, .88); max-width: 52ch; }
.cta-inner .btn { margin-top: var(--sp-2); }
.cta-note {
  margin-top: var(--sp-3);
  font-size: .78rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--c-accent);
}

/* ---------------------------------------------------------------- rodapé */

.site-footer {
  background: var(--c-blue-deep); color: rgba(255, 255, 255, .82);
  padding-top: var(--sp-7);
  /* reserva a faixa onde o botão flutuante do WhatsApp mora, para que ele
     nunca cubra o texto legal do rodapé (era o bug do print 5) */
  padding-bottom: calc(72px + env(safe-area-inset-bottom));
  scroll-snap-align: none;
}
.footer-grid { display: grid; gap: var(--sp-5); padding-bottom: var(--sp-5); }
.footer-brand img { height: 42px; width: auto; }
.slogan { margin-top: var(--sp-3); font-weight: 700; color: var(--c-white); }
.hashtag { color: var(--c-accent); font-weight: 900; letter-spacing: .06em; }
.footer-col h2 { font-size: .78rem; letter-spacing: .16em; text-transform: uppercase; color: var(--c-accent); margin-bottom: var(--sp-3); }
.footer-col li { margin-bottom: 7px; font-size: .9rem; }
.footer-col a:hover { color: var(--c-accent); text-decoration: underline; }
.footer-note { font-size: .8rem; color: rgba(255, 255, 255, .6); margin-top: var(--sp-3); }
.footer-bottom {
  border-top: 1px solid rgba(255, 255, 255, .12); padding-top: var(--sp-4);
  font-size: .78rem; color: rgba(255, 255, 255, .55);
}
.footer-bottom p { margin: 0 0 4px; }
/* Termos e Política: exigência legal, não decoração — ficam legíveis (contraste
   maior que o resto do rodapé) e com alvo de toque de 44px no celular. */
.footer-legal { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: var(--sp-3) 0 6px !important; }
.footer-legal a { color: rgba(255, 255, 255, .82); text-decoration: underline; padding: 6px 0; }
.footer-legal a:hover { color: var(--c-accent); }
.footer-legal span { opacity: .4; }
.footer-credit a { display: inline-flex; align-items: center; gap: 6px; color: inherit; text-decoration: none; padding: 6px 0; }
.footer-credit a:hover { color: var(--c-accent); text-decoration: underline; }
.footer-credit img { display: block; opacity: .82; }

/* O raio que atravessava .btn-primary/.btn-wa no hover saiu daqui: virou o
   filete especular lá em cima (seção "botão especular"). */

/* ------------------------------------------------------- scroll reveal
   100% CSS, ligado ao progresso do scroll. Sem JS envolvido: se o navegador
   não suporta `view()`, nada é aplicado e o conteúdo nasce visível — nunca
   existe opacity:0 órfão esperando script.
   Hierarquia por FÍSICA, não por delay: o protagonista percorre mais
   distância e num alcance maior; o coadjuvante quase não se mexe. */

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .reveal {
      animation-name: reveal-in;
      animation-fill-mode: both;
      animation-timing-function: linear;  /* o scroll é o tempo */
      animation-timeline: view();
      /* o alcance termina dentro da fase `entry`: assim que o elemento está
         inteiro na tela, o reveal acabou. Com módulos de tela cheia + snap,
         um alcance que invadisse `cover` deixaria o último item de baixo
         parado no meio da animação depois do snap. */
      animation-range: entry 8% entry 92%;
    }
    .reveal--lead { --reveal-y: 40px; --reveal-blur: 5px; animation-range: entry 0% entry 82%; }
    .reveal--detail { --reveal-y: 8px; animation-range: entry 22% entry 100%; }
  }
}
@keyframes reveal-in {
  from { opacity: 0; transform: translate3d(0, var(--reveal-y, 20px), 0); filter: blur(var(--reveal-blur, 0px)); }
  to { opacity: 1; transform: none; filter: blur(0); }
}

/* -------------------------------------------- distribuição do "por que"
   Toda a coreografia da seção mora aqui, atrás do mesmo par de guardas do
   scroll reveal. `timeline-scope` publica a timeline da grade para o painel
   e para os cards: assim os quatro compartilham UMA régua e o escalonamento
   vira `animation-range`, não `animation-delay` — no desktop os cards estão
   na mesma linha e teriam progresso idêntico se cada um lesse a si mesmo.
   Como o scroll é o relógio, arrastar a barra pra trás desfaz a distribuição
   na mesma ordem: nada sai de fase. */

@supports (animation-timeline: view()) and (timeline-scope: --x) {
  @media (prefers-reduced-motion: no-preference) {
    .why { timeline-scope: --why-view; }
    .why-grid { view-timeline-name: --why-view; view-timeline-axis: block; }

    /* 1) encaixe do painel: recuado e arredondado nas pontas do percurso,
       cravado no meio. O range é `cover` porque a transição precisa cobrir
       entrada E saída do módulo — é o vínculo com planos e com apps. */
    .why-panel {
      animation: why-dock linear both;
      animation-timeline: view();
      animation-range: cover 0% cover 100%;
    }

    /* 2) a distribuição: cada card é uma carta saindo do monte, com a mesma
       física do baralho de planos logo acima (mesmos --deck-*, mesmo pivô na
       base). O scroll descendo é quem distribui.
       CELULAR: coluna única muito mais alta que a tela — cada card tem sua
       própria régua e assenta ao surgir por baixo. Independe da altura da
       tela, então não precisa de percentual calibrado à mão. */
    .why-card {
      animation: why-deal linear both;
      animation-timeline: view();
      animation-range: entry 4% entry 42%;
    }

    /* DESKTOP: os quatro cabem numa tela só, então a régua é a da grade e a
       distribuição é sequenciada à mão.
       `cover` em vez de `entry`: a fileira tem ~380px de altura, e `entry`
       dura a altura do próprio elemento — a sequência inteira caberia em
       190px de rolagem, ou seja, acabaria antes do usuário olhar.

       ONDE A SEQUÊNCIA TERMINA. `cover` mede tela + elemento, então a grade
       está exatamente no MEIO da tela em cover 50%. Esse é o teto: tudo
       precisa ter acontecido antes de 50%, senão o cliente chega ao centro
       do módulo com o quarto card ainda a caminho e a animação vira estorvo
       em vez de convite. Roda entre 15% e 46%; no centro a fileira já está
       inteira assentada, parada, pronta pra ler. */
    @media (min-width: 900px) {
      .why-card {
        animation-timeline: --why-view;
        animation-range: cover 15% cover 25%;
      }
      .why-card:nth-child(2) { animation-range: cover 22% cover 32%; }
      .why-card:nth-child(3) { animation-range: cover 29% cover 39%; }
      .why-card:nth-child(4) { animation-range: cover 36% cover 46%; }
    }
  }
}

/* 20%/80% e não 10%/90%: com `cover` num painel de ~700px numa tela de 900,
   cada 1% vale ~16px de rolagem. Encaixar em 10% seria travar a placa com
   só 160px dela à vista — o usuário nunca veria a chegada. */
@keyframes why-dock {
  0%   { clip-path: inset(0 var(--why-inset) round var(--why-round)); transform: translate3d(0, 26px, 0); }
  20%  { clip-path: inset(0 0 round 0); transform: none; }
  80%  { clip-path: inset(0 0 round 0); transform: none; }
  100% { clip-path: inset(0 var(--why-inset) round var(--why-round)); transform: none; }
}
/* A CARTA SENDO DISTRIBUÍDA — mesma física do baralho de planos, de
   propósito: o card parte da posição que uma carta atrás ocupa no monte
   (`--o: 2` / `--a: 2` lá em .deck.is-live .deck-card) e assenta em zero.
   Reusar os tokens --deck-* em vez de números soltos faz o movimento
   continuar coerente se o baralho for recalibrado, e mantém a mesma leitura
   nos dois módulos: no de cima o dedo puxa a carta, aqui o scroll distribui.
   Nada de mexer em --edge-proximity aqui: uma animação com `fill: both`
   ganha do que o JS escreve em style, e o BorderGlow do cursor pararia de
   responder assim que a carta assentasse. */
@keyframes why-deal {
  from {
    opacity: 0;
    transform:
      translate3d(calc(var(--deck-x) * 2), calc(var(--deck-y) * 2.4), 0)
      rotate(calc(var(--deck-r) * 2deg))
      scale(calc(1 - var(--deck-s) * 2));
  }
  to { opacity: 1; transform: none; }
}

/* ------------------------------------------------------ compactação mobile
   No celular o orçamento é a tela inteira menos o cabeçalho. Aqui a gente
   corta o que é redundante (o card já diz o preço e a cidade) e aperta os
   respiros, para que o módulo caiba sem rolagem interna. */

@media (max-width: 999px) {
  /* full-screen snap é coisa de desktop: em celular, seções curtas
     (por-que, apps, cobertura) centralizadas em min-height:100svh
     sobravam metade da tela em branco. No mobile o conteúdo manda a altura. */
  .screen { min-height: 0; display: block; }
  .section.screen { padding-block: var(--sp-5); }
  .deck-ui { margin-top: var(--sp-2); }
  .deck-hint { margin-top: 6px; font-size: .72rem; min-height: 1rem; }

  .plans-layout { gap: var(--sp-4); }
  /* NADA de `display: none` aqui. O que o celular perdia — subtítulo dos
     preços, argumentos de venda, público-alvo e o 3º benefício do card X —
     era exatamente argumento de venda, na tela em que a maioria decide.
     O orçamento de altura se paga apertando tipografia e respiro. */
  .plans-copy .section-sub { font-size: .86rem; }
  .plans-copy .plans-lines { font-size: .84rem; margin-top: var(--sp-2); }
  .plans-copy .plans-usp { margin-top: var(--sp-3); gap: 5px; }
  .plans-copy .plans-usp li { font-size: .82rem; }
  .plans-copy .section-title { font-size: clamp(1.35rem, 5.8vw, 1.8rem); }
  .plans-copy .city-switch { margin-top: var(--sp-3); }
  .plans-copy .city-switch-label { display: none; }
  .plans-deck .tabs-list { margin-bottom: var(--sp-3); }
  /* pilha menor e mais junta: o offset padrão (até 39px em 3 cartas) estourava
     a margem da .wrap (16px) e cortava a carta de trás rente na borda da tela,
     um corte seco que lia como quebrado. Aqui a carta de trás cabe inteira.
     Vale para TODO baralho de planos (home e /planos): antes só a home tinha
     esta regra e o card mudava de tamanho de uma página para a outra. */
  .deck:not(.deck--quotes) { --deck-w: min(82vw, 300px); --deck-x: 8px; --deck-y: 8px; --deck-s: .045; --deck-r: 1.6; }

  .plan-card { padding: var(--sp-4) var(--sp-3) var(--sp-4); }
  .plan-head { gap: var(--sp-2); }
  .plan-frame { padding: var(--sp-3) var(--sp-2) var(--sp-2); }
  .plan-frame--speed { padding-top: var(--sp-4); }
  .plan-speed { font-size: clamp(2rem, 10vw, 2.6rem); }
  .price-int { font-size: clamp(2.4rem, 13vw, 3.2rem); }
  .plan-price { margin: var(--sp-3) 0 var(--sp-2); }
  .plan-flags { font-size: .7rem; gap: 5px 8px; }
  /* o card da linha X carrega duas linhas a mais (selos de simetria e
     streaming) e é o mais difícil de justificar — era justamente ele que
     perdia a frase de público e o terceiro benefício no celular. Agora todo
     card mostra tudo: a altura vem de tipografia menor e de uma linha só de
     público, não da remoção do argumento. */
  .plan-benefits { gap: 5px; font-size: .78rem; margin-bottom: var(--sp-3); }
  .plan-audience { -webkit-line-clamp: 1; font-size: .76rem; }

  .section-head { margin-bottom: var(--sp-4); }
  .coverage-grid { gap: var(--sp-4); }
  .coverage-map img { max-height: min(30svh, 260px); aspect-ratio: 16 / 9; }
  .coverage-item { padding: var(--sp-3); }
  .coverage-item-note { font-size: .8rem; }
  /* no celular a lista rola dentro do módulo: 4+ praças não empurram o CTA
     do WhatsApp para fora da tela */
  .coverage-groups { max-height: min(46svh, 340px); overflow-y: auto; }

  .tier-lanes { margin-top: var(--sp-5); }
  .lane { padding: var(--sp-3) 0; }
  .why-card { padding: var(--sp-4); }
  .why-card p:not(.why-hook) { font-size: .86rem; }
  .why-hook { font-size: 1.06rem; }
  .why-ico { margin-bottom: var(--sp-3); }
}

/* (aqui morava a versão empilhada da tabela comparativa no celular: cada
   linha virava um cartão com o rótulo vindo do `data-label`. Saiu junto com
   a tabela — eram cartões de plano logo abaixo dos cartões de plano.) */

/* ------------------------------------------------------------ breakpoints */

@media (min-width: 600px) {
  /* ainda é o flutuante que manda (o cabeçalho só assume aos 900px), mas
     aqui já sobra largura para o rótulo: ícone sozinho parece bolha de chat
     genérica, com rótulo é oferta. */
  .wa-float { padding: 14px 22px; }
  .wa-float-text { display: inline; }

  .contact-grid { grid-template-columns: repeat(2, 1fr); }
  .steps { grid-template-columns: repeat(2, 1fr); }
  .steps--3 { grid-template-columns: repeat(3, 1fr); }

  /* PASSO EM COLUNA a partir daqui: em coluna estreita o número ao lado do
     texto rouba largura e esfarrapa a descrição em três linhas curtas. Com o
     número em cima, o texto usa a coluna inteira. */
  .step { flex-direction: column; gap: var(--sp-3); }
  /* trilho horizontal só onde os passos formam UMA linha — em duas linhas
     ele sairia pelo lado direito da primeira e apontaria para o vazio.
     Aqui isso vale para o trio (/cobertura); o quarteto espera os 900px. */
  .step:not(:last-child)::after { display: none; }
  .steps--3 .step:not(:last-child)::after,
  /* duas colunas: a ordem de leitura é 1,2 / 3,4 — então o trilho liga
     dentro de cada linha (só o ímpar puxa a linha para o vizinho). */
  .steps:not(.steps--3) .step:nth-child(odd):not(:last-child)::after {
    display: block; top: 18px; left: 46px; right: -8px;
    bottom: auto; width: auto; height: 2px;
  }
  .logo-slide { width: 170px; }
  .section.section--cinema { --poster-h: clamp(240px, 42svh, 480px); }
}

@media (min-width: 900px) {
  /* ESCALA TIPOGRÁFICA DE DESKTOP (+15%, 2026-08-15).
     O cliente reclamou de "letrinhas miúdas" e de eyebrow amarela pequena
     demais para enxergar. A causa não é a fonte — o corpo usa a stack do
     sistema; é a escala, calibrada no celular e nunca reescalada quando a
     tela dobra de tamanho.

     Por que aqui e não em cada regra: TODA medida tipográfica do arquivo é
     rem e TODA medida de layout é px (--sp-*, --wrap, --header-h, raios) —
     conferido: só sobram dois rem não-tipográficos, ambos inofensivos. Então
     mexer na raiz aumenta letra e mais nada. Uma linha, sem 60 overrides
     para sair de sincronia depois.

     O CELULAR NÃO ENTRA: abaixo de 900px a escala continua a de antes, que
     é a que o cliente aprovou. Se um dia isto subir para o celular, medir
     antes o card de plano — lá a largura é fixa (--deck-w). */
  html { font-size: 115%; }

  :root {
    --header-h: 72px;
    --deck-x: 22px;
    --deck-y: 14px;
    --deck-s: .05;
    --deck-r: 2;
    /* +15% junto com a letra: a carta é arte de largura fixa, e só o texto
       crescer dentro dela quebraria a linha do preço e a moldura de
       velocidade. Card e tipografia escalam como uma peça só. */
    --deck-w: 437px;
  }

  /* a eyebrow amarela leva um degrau a mais: era .74rem em caixa alta com
     .16em de tracking — a combinação que o cliente apontou como ilegível.
     Menos tracking + mais corpo; a mesma pílula, agora legível de longe. */
  .eyebrow { font-size: .84rem; letter-spacing: .12em; }
  .hero-eyebrow { font-size: .86rem; letter-spacing: .11em; }

  .brand-logo { height: 48px; }

  .nav-toggle { display: none; }
  .nav {
    position: static; display: flex; align-items: center; gap: var(--sp-5);
    background: transparent; border: 0; padding: 0;
    opacity: 1; transform: none; transition: none;
  }
  .nav-list { flex-direction: row; gap: var(--sp-1); margin: 0; }
  .nav-list a { padding: 8px 12px; font-size: .9rem; }
  /* no DOM as ações vêm antes do <nav> (para ficarem fora do hambúrguer no
     celular); no desktop elas voltam para o fim da linha, depois do menu */
  .nav { order: 2; }
  .header-actions { order: 3; margin-left: var(--sp-3); }

  /* A TROCA DE TURNO DO WHATSAPP PERSISTENTE.
     Daqui pra cima o cabeçalho fixo está sempre à vista e o ponteiro chega
     nele sem custo: o botão do topo assume, e o flutuante sai. Abaixo de
     900px é o contrário. Nunca os dois — é o mesmo link. */
  .header-wa { display: inline-flex; padding: 10px 18px; }
  .wa-float { display: none; }
  /* a Central troca de turno junto: botão no cabeçalho, item fora do menu */
  .header-portal { display: inline-flex; }
  .nav-portal { display: none; }

  .city-pin-btn, .city-pin-static { font-size: .95rem; min-height: 48px; padding: 11px 18px; }
  .city-pin-name { max-width: 18ch; }

  .strip { padding-block: var(--sp-7); }
  .strip-inner { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: var(--sp-5); }
  .strip-lead { flex: 1 1 320px; }
  .strip-actions { flex: 0 0 auto; flex-direction: column; align-items: stretch; }

  .hero-sub { font-size: 1.12rem; }
  .carousel-arrow { display: grid; }
  /* seta do palco entra junto com a do carrossel: daqui pra cima existe
     ponteiro fino e sobra foto para a seta não cobrir */
  .stage.is-live .stage-arrow:not([hidden]) { display: grid; }
  .stage-arrow:hover { background: rgba(1, 32, 63, .68); transform: scale(1.06); }
  .stage-arrow:active { transform: scale(.94); }
  .stage.is-live .stage-frame { max-height: min(62svh, 560px); }
  .carousel--hero .carousel-arrow { background: rgba(255, 255, 255, .18); color: #fff; border-color: rgba(255, 255, 255, .3); backdrop-filter: blur(6px); }
  .carousel--hero .carousel-arrow--prev { left: 20px; }
  .carousel--hero .carousel-arrow--next { right: 20px; }

  /* no desktop o módulo tem tela sobrando: os blocos ganham presença em vez
     de flutuar minúsculos no meio de um mar de espaço vazio */
  .section-head { margin-bottom: var(--sp-6); }

  /* no desktop os quatro blocos ficam lado a lado e as cartas são
     distribuídas da esquerda pra direita, na direção da leitura */
  .why { --why-inset: 6%; --why-round: 32px; }
  .why-grid { grid-template-columns: repeat(4, 1fr); gap: var(--sp-4); }
  .why-card { padding: var(--sp-5); }
  .why-ico { width: 24px; height: 24px; }
  /* a cena pesa mais que a resposta: é ela que precisa parar o olho */
  .why-hook { font-size: 1.22rem; }
  .why-card h3 { font-size: 1rem; }
  .why-card p:not(.why-hook) { font-size: .9rem; }
  /* reserva maior: título/CTA crescem e a folga da máscara dobra (giro maior).
     Seletor duplo para bater a especificidade da regra base. */
  /* sobra largura: o cartaz pode passar da altura da tela e sangrar */
  .section.section--cinema { --poster-h: clamp(300px, 52svh, 640px); --wall-tilt: -2.4deg; }
  .wall-inner { gap: var(--sp-4); }
  .wall-track { gap: var(--sp-4); }
  /* 230px e não 200: a coluna do rótulo é px e a letra dentro dela cresceu 15% */
  .lane { grid-template-columns: 230px minmax(0, 1fr); }
  .feature-grid { grid-template-columns: 1.05fr 1fr; gap: var(--sp-7); }
  .feature-grid--reverse .feature-copy { order: 2; }
  .coverage-grid { grid-template-columns: 1.25fr 1fr; }
  /* a lista acompanha a altura do mapa ao lado; passou disso, rola */
  .coverage-groups { max-height: min(52svh, 430px); overflow-y: auto; }
  .footer-grid { grid-template-columns: 1.4fr 1fr 1.2fr 1fr; }
  .steps { grid-template-columns: repeat(4, 1fr); }
  .steps--3 { grid-template-columns: repeat(3, 1fr); }
  /* os quatro passos cabem numa linha só: o trilho pode ligá-los */
  .steps .step:not(:last-child)::after {
    display: block; top: 18px; left: 46px; right: -8px;
    bottom: auto; width: auto; height: 2px;
  }
  .contact-grid { grid-template-columns: repeat(3, 1fr); }
  .section { padding-block: var(--sp-9); }
}

/* CABEÇALHO ENTRE 900 E 1179px (2026-09-14). O botão "Sua localização" é
   ~100px mais largo que a pílula com o nome da cidade, e nessa faixa (tablet
   deitado, notebook com escala de 125%) logo + menu + cidade + Central do
   Cliente + WhatsApp não cabiam: a logo era espremida até 0px e o WhatsApp saía
   cortado. Nada some, só aperta: o WhatsApp vira só o ícone (o texto fica para
   leitor de tela), menu e cidade descem um degrau e a Central não quebra linha.
   Medido com a Central ligada, que é o pior caso. De 1180px para cima cabe tudo
   no tamanho cheio. */
@media (min-width: 900px) and (max-width: 1179px) {
  .header-inner { gap: 8px; }
  .header-actions { margin-left: 4px; gap: 6px; }
  .nav-list a { padding: 8px 7px; font-size: .84rem; }
  .city-pin-btn { min-height: 44px; padding: 10px 12px; font-size: .86rem; }
  .header-wa { padding: 10px 13px; }
  .header-wa-text {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip: rect(0 0 0 0); white-space: nowrap;
  }
  .header-portal { white-space: nowrap; padding: 9px 10px; }
}
/* 900–959px: mais um degrau, senão ainda faltam ~47px */
@media (min-width: 900px) and (max-width: 959px) {
  .brand-logo { height: 40px; }
  .header-inner { gap: 6px; }
  .header-actions { margin-left: 0; gap: 5px; }
  .nav-list a { padding: 8px 5px; font-size: .8rem; }
  .city-pin-btn { padding: 10px; font-size: .82rem; }
  .header-portal { padding: 9px 8px; }
}

@media (min-width: 1000px) {
  /* copy à esquerda, baralho à direita — o módulo inteiro cabe numa tela.
     A coluna acompanha o --deck-w de desktop (437px), que subiu 15% junto
     com a tipografia: apertar a carta aqui a faria vazar da coluna. */
  .plans-layout { grid-template-columns: 1fr minmax(437px, 520px); gap: var(--sp-8); }
  .plans-copy { text-align: left; }
  .plans-copy .city-switch { justify-content: flex-start; }
  .plans-copy .plans-usp { margin-top: var(--sp-5); margin-inline: 0; }
  .plans-copy .plans-usp li { font-size: .92rem; }
  .plans-lines { margin-inline: 0; }
  .deck-ui { justify-content: center; }
}

/* ------------------------------------------------------- acessibilidade */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; scroll-snap-type: none; }
  *, *::before, *::after {
    animation-duration: .001ms !important; animation-iteration-count: 1 !important;
    transition-duration: .001ms !important; scroll-behavior: auto !important;
  }
}

@media print {
  .site-header, .carousel-arrow, .carousel-dots, .deck-ui, .deck-hint, .cta-band, .wa-float,
  .stage-arrow, .stage-bar, .stage-rail { display: none !important; }
  /* no papel não há troca de slide: a foto ativa vira foto única */
  .stage-slide { clip-path: none !important; }
  .stage-slide:not(.is-active) { display: none !important; }
  .stage-img { transform: none !important; }
  body { color: #000; }
  .deck.is-live .deck-stack { height: auto !important; }
  .deck.is-live .deck-card { position: static !important; transform: none !important; opacity: 1 !important; width: 100% !important; }
  /* no papel não existe carta "de trás": todas viram lista, então o véu sai */
  .deck.is-live .deck-card::after { display: none !important; }
}
