/* ============================================================================
   BG MÍDIA · DEVCODE OS · FASE 3 (ATELIER)
   Design tokens + componentes. Mobile-first. Zero dependência externa.
   Nenhum valor mágico: todo componente consome variáveis do :root.
   ========================================================================== */

/* ============================ 1 · DESIGN TOKENS ============================ */
:root {
  /* Tema escuro é o único do projeto desde 15/08. `color-scheme` faz o
     navegador desenhar barra de rolagem, campo de formulário e menu nativo na
     variante escura: sem isto, quem está com o sistema no claro vê a página
     escura com widgets brancos aparecendo no meio dela. */
  color-scheme: dark;

  /* --- COR ----------------------------------------------------------------
     Primária estimada visualmente do logo em PNG.
     [PENDENTE: validar hex com o logo em vetor]
     Contraste medido: #F4600E sobre #0D0B09 = 6.11:1 (AA em texto normal). */
  --color-primary:        #F4600E;   /* OKLCH L 0.672  C 0.197  H 42.3° */
  --color-primary-hover:  #FF7526;   /*      L 0.717  C 0.188  H 45.5° */
  --color-primary-ink:    #0D0B09;   /* texto SOBRE o laranja. 6.11:1 */
  --color-primary-soft:   rgba(244, 96, 14, 0.12);

  /* NEUTROS COM TEMPERATURA · corrigido em 15/08.
     Medidos em OKLCH, os seis neutros estavam todos em matiz 286°, que é
     azul-violeta. A marca está em 42°, laranja. A paleta inteira puxava para
     o lado oposto do próprio acento, e é isso que faz uma interface escura
     parecer genérica: cinza frio embaixo de acento quente lê como duas
     paletas coladas, não como uma.

     Todos foram levados para ~70°, o vizinho quente do laranja, sem virar
     bege. A LUMINÂNCIA DE CADA UM FOI PRESERVADA no terceiro decimal, então
     todos os pares de contraste continuam idênticos: a maior variação medida
     foi de 0,03:1, e nenhum par mudou de veredito.

     Croma entre 0.004 e 0.019: acima disso a superfície começa a competir com
     o acento; abaixo, o olho lê como cinza de renderização e não como
     escolha.                              L      C       H                  */
  --color-surface:        #0D0B09;  /* 0.151  0.0042   67.8°  base          */
  --color-surface-alt:    #161411;  /* 0.192  0.0040   78.2°  seção alterna */
  --color-surface-raised: #1E1B18;  /* 0.224  0.0077   67.4°  cartão elevado*/

  --color-text:           #FBFAF8;  /* 18.7:1 sobre a base. Branco neutro
                                       sobre superfície quente lê frio, então
                                       leva o mesmo croma mínimo dos fundos. */
  --color-text-muted:     #A5A19D;  /* 7.66:1 (era 7.64:1 em #A1A1A6)        */
  /* Decorativa: separa superfícies, não delimita controle, então não tem
     exigência de contraste (SC 1.4.11 vale para o -strong abaixo). Derivada
     do texto: 11% de #FAFAFA sobre a superfície base dá o mesmo #26262A de
     antes, e sobre a rua animada ou o painel de leitura ela acompanha o que
     está atrás. Cinza fixo sobre fundo que se move ora some, ora salta. */
  --color-border:         color-mix(in srgb, var(--color-text) 11%, transparent);
  /* Delimita controles (botão ghost, input, chip, ícone social).
     WCAG 2.2 SC 1.4.11 exige 3:1 contra o fundo REAL. Como esses controles
     aparecem sobre as três superfícies, o valor foi calculado contra a pior
     delas (--color-surface-raised). O #38383E anterior dava 1.69:1.
     #686874 dá 3.58 / 3.35 / 3.12:1 nas três. */
  --color-border-strong:  #71685E;   /* 3.60 / 3.37 / 3.14:1 nas três (era
                                        #686874, 3.58 / 3.35 / 3.12:1) */
  /* Contorno das caixas de conteúdo. O laranja da marca a 70% sobre as
     superfícies escuras dá 3.35:1, acima do mínimo de 3:1 do SC 1.4.11, e
     mesmo assim continua sendo fio de 1px: quem carrega o acento cheio é o
     botão, que é área pintada, e a diferença entre fio e área é o que mantém
     a ordem de leitura mesmo com a cor repetida na página inteira. */
  --color-border-accent:  color-mix(in srgb, var(--color-primary) 70%, transparent);

  --color-success:        #35C46B;
  --color-error:          #FF6B6B;

  /* --- TIPOGRAFIA ---------------------------------------------------------
     [PENDENTE: self-hospedar uma sans geométrica em WOFF2 para bater com o
     logo. Stack de sistema por enquanto: zero requisição, zero CLS de fonte.] */
  --font-display: "Segoe UI Variable Display", "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
  --font-body:    "Segoe UI Variable Text", "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;

  --text-xs:   clamp(0.75rem, 0.7rem + 0.2vw, 0.8125rem);
  --text-sm:   clamp(0.875rem, 0.85rem + 0.2vw, 0.9375rem);
  --text-base: clamp(1rem, 0.95rem + 0.25vw, 1.0625rem);
  --text-lg:   clamp(1.125rem, 1rem + 0.5vw, 1.375rem);
  --text-xl:   clamp(1.375rem, 1.2rem + 0.8vw, 1.75rem);
  --text-2xl:  clamp(1.75rem, 1.4rem + 1.5vw, 2.5rem);
  --text-3xl:  clamp(2.25rem, 1.6rem + 2.8vw, 4rem);

  --leading-tight: 1.12;
  --leading-body:  1.65;

  /* ENTRELETRA EM FUNÇÃO DO TAMANHO, não por elemento.
     A regra ótica: quanto maior o tipo, mais os brancos entre as letras
     parecem crescer, e mais precisa ser fechado. Um mesmo -0.02em num título
     de 64px e num de 22px deixa o grande solto e o pequeno apertado. Antes
     desta rodada h1, h2 e h3 compartilhavam -0.02em e havia mais oito valores
     soltos pelo arquivo, nenhum ligado ao tamanho.

     Caixa alta pequena vai para o outro lado: maiúscula não tem ascendente
     nem descendente para o olho se apoiar, então precisa de ar. */
  /* COMPENSAÇÃO DE DESCENDENTE. Recortar a palavra por overflow, que é o
     gesto de ela nascer de dentro da linha, corta o rabo do g, do p, do q e
     do y. O par padding/margem devolve o espaço sem mexer no layout. Em `em`
     porque a descendente é proporcional ao corpo do tipo.
     Estava 0,14em na camada de movimento e 0,16em na splash: mesmo problema,
     dois palpites, e ninguém saberia dizer qual dos dois estava certo. */
  --descida: 0.16em;

  --tracking-display: -0.03em;   /* --text-3xl · manchete do herói  */
  --tracking-title:   -0.02em;   /* --text-2xl · título de seção    */
  --tracking-sub:     -0.01em;   /* --text-xl  · título de cartão   */
  --tracking-flat:      0;       /* corpo: qualquer ajuste atrapalha */
  --tracking-small:  0.02em;     /* caixa baixa pequena             */
  --tracking-caps:   0.08em;     /* caixa alta pequena              */
  --tracking-wide:   0.14em;     /* caixa alta muito pequena        */

  /* --- ESPAÇAMENTO (base 4px) --- */
  --space-1: 0.25rem;  --space-2: 0.5rem;   --space-3: 0.75rem;
  --space-4: 1rem;     --space-5: 1.25rem;  --space-6: 1.5rem;
  --space-8: 2rem;     --space-10: 2.5rem;  --space-12: 3rem;
  --space-16: 4rem;    --space-24: 6rem;
  --section-y: clamp(3.5rem, 8vw, 7rem);

  /* --- FORMA E ELEVAÇÃO --- */
  --radius-sm:   8px;
  --radius-md:   14px;
  --radius-lg:   20px;
  --radius-full: 999px;

  /* ELEVAÇÃO · duas camadas, sempre. Sombra real tem contato curto e escuro
     logo abaixo (diz que o objeto está POUSADO) e difusão longa e fraca em
     volta (diz a que altura). Uma camada só entrega a difusão sem o contato,
     e o cartão fica com cara de adesivo colado. As duas eram de camada única.
     No escuro a sombra é apoio: quem comunica altura é a superfície ficando
     mais clara (surface → alt → raised). Por isso opacidades moderadas. */
  --shadow-sm:
    0 1px 2px rgba(0, 0, 0, 0.30),
    0 2px 6px rgba(0, 0, 0, 0.18);
  --shadow-md:
    0 2px 4px rgba(0, 0, 0, 0.28),
    0 12px 28px rgba(0, 0, 0, 0.30);
  --shadow-lg:
    0 4px 8px rgba(0, 0, 0, 0.30),
    0 24px 56px rgba(0, 0, 0, 0.38);

  /* BRILHO ≠ ELEVAÇÃO. Sombra situa no espaço; brilho é acento de marca.
     Misturar os dois num token só foi o que espalhou cinco literais laranja
     por três arquivos. */
  --glow-primary:      0 0 0 1px var(--color-primary), 0 0 24px rgba(244, 96, 14, 0.22);
  --glow-primary-soft: 0 0 12px rgba(244, 96, 14, 0.45);

  /* VÉU · escurecer o que está atrás para o texto claro passar em contraste.
     Eram quatro literais com quatro alfas (0.55, 0.58, 0.62 e 0.80) para o
     mesmo problema, em três lugares diferentes: véu do menu, chapa da badge
     do totem e botão de som. Quatro valores para uma decisão só é o sintoma
     clássico de valor arbitrário: ninguém escolheu 0.58 em vez de 0.55, foi
     digitado. Dois níveis dão conta, e a diferença entre eles é perceptível.
     Preto puro e não o neutro da marca: véu não é superfície, é ausência de
     luz, e qualquer croma aqui tinge a foto que está por baixo. */
  --veil:        rgba(0, 0, 0, 0.55);   /* camada sobre conteúdo: menu       */
  --veil-strong: rgba(0, 0, 0, 0.72);   /* chapa sobre foto ou vídeo         */

  /* --- LAYOUT E BREAKPOINTS ------------------------------------------------
     ESCALA ÚNICA DO PROJETO. As cinco camadas de CSS (styles, motion,
     assistant, footer, splash) usam exatamente estes valores. Antes havia
     480, 560, 600, 639, 640 e 900 espalhados pelos arquivos, o que criava
     faixas de largura em que uma camada já tinha trocado de layout e a
     outra ainda não.

       sm   480px   celular grande
       md   640px   tablet em retrato
       lg   900px   tablet em paisagem e notebook
       xl  1200px   desktop
       xxl 1600px   monitor grande e ultrawide

     Custom property dentro de @media ainda não é suportada, então os valores
     aparecem literalmente nas queries. Estes tokens são a referência: mudou
     aqui, mude nas queries.                                                 */
  --bp-sm:   480px;
  --bp-md:   640px;
  --bp-lg:   900px;
  --bp-xl:  1200px;
  --bp-xxl: 1600px;

  /* O container cresce por degraus (ver seção 16). Sem isso, num monitor de
     2560px sobravam 680px vazios de cada lado com o conteúdo espremido. */
  --container: 1200px;
  --container-narrow: 760px;
  --gutter: clamp(1rem, 4vw, 2rem);

  /* --- MOVIMENTO (baseline. O SOAP amplia, não substitui) --- */
  /* SISTEMA DE MOVIMENTO · três durações e duas curvas, e nada fora disso.
     Entrada usa --ease-out (out-expo): começa rápido e assenta devagar, que é
     como algo chegando se comporta. Saída usa --ease-in: começa devagar e
     acelera para fora, que é como algo indo embora se comporta. Usar a mesma
     curva nos dois sentidos é o erro que faz interface parecer borracha. */
  --duration-fast: 150ms;   /* resposta de toque e hover */
  --duration-base: 250ms;   /* padrão de transição de estado */
  --duration-slow: 400ms;   /* entrada de painel e revelação */
  /* Fora da faixa de interação de propósito e por isso declarado, não solto
     no meio de uma regra. A camada da rua não é um estado que responde a
     alguém: é atmosfera acendendo. Em 400ms ela pisca; em 1,4s ela chega. */
  --duration-ambient: 1400ms;
  --ease-out:    cubic-bezier(0.16, 1, 0.3, 1);   /* entrada */
  --ease-in:     cubic-bezier(0.4, 0, 1, 1);      /* saída */
  --ease-in-out: cubic-bezier(0.45, 0, 0.55, 1);  /* percurso fechado */

  /* --- Z-INDEX · ESCALA FECHADA (registro central, Bloco de Passagem §8) ----
     A escala parava no --z-modal, e as duas camadas acima disso resolveram
     sozinhas: assistente com 99999, splash com 2147483000, o maior inteiro
     que o navegador aceita. Funcionava e era beco sem saída. Agora as seis
     cabem na mesma régua, com folga de mil entre elas.                      */
  --z-base:    1;      /* conteúdo que só precisa ficar acima de um ::before  */
  --z-sticky:  100;    /* barra que gruda ao rolar                            */
  --z-header:  200;    /* cabeçalho fixo                                      */
  --z-overlay: 500;    /* véu do menu de tela cheia                           */
  --z-modal:   1000;   /* painel do menu sobre o véu                          */
  --z-chatbot: 2000;   /* assistente (CONCIERGE, FASE 5)                      */
  --z-splash:  3000;   /* abertura. Acima de tudo porque é temporária         */
}

/* A --color-primary do tema claro já passa em AA como texto (5.09:1), então
   .accent, .eyebrow e .step__num não precisam mais de uma cor à parte.
   Uma variável a menos para manter dessincronizada. */

/* ============================ 2 · RESET E BASE ============================ */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* A GOTEIRA DA BARRA DE ROLAGEM.
     Duas coisas travam a rolagem nesta página: a splash (splash.css) e o menu
     de tela cheia (§ do menu). As duas fazem overflow: hidden no <html>, o que
     em desktop com barra clássica FAZ A BARRA SUMIR e a página inteira saltar
     uns 15px para a direita. Na splash o salto acontece duas vezes: quando a
     abertura cobre a tela e de novo quando a cortina sobe, exatamente no
     instante em que a marca aparece.
     `stable` reserva a goteira o tempo todo, então esconder a barra não move
     mais nada. Navegador sem suporte ignora e volta ao comportamento de
     antes: não piora nada em lugar nenhum. */
  scrollbar-gutter: stable;
}

body {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-body);
  color: var(--color-text);
  background-color: var(--color-surface);
  -webkit-font-smoothing: antialiased;
  /* clip e não hidden: `hidden` cria um contexto de rolagem que quebra
     position: sticky do header. `clip` corta o excedente sem esse efeito. */
  overflow-x: clip;
  transition: background-color var(--duration-base) var(--ease-out),
              color var(--duration-base) var(--ease-out);
}
@supports not (overflow: clip) { body { overflow-x: hidden; } }

/* ---------------------------------------------------------------------------
   FUNDO AMBIENTE
   Brasa em movimento por trás de toda a página.

   z-index -1 funciona porque o <html> não declara background: o do <body>
   é propagado para a raiz e pintado antes de tudo. O ambiente fica entre
   essa cor de raiz e o conteúdo, sem precisar mexer em nenhum z-index de
   componente.

   DUAS CAMADAS, uma para cada capacidade:
     · o gradiente aqui embaixo é o piso. Sempre existe, custa quase nada e
       roda em qualquer aparelho.
     · o canvas por cima só é ligado pelo JS no desktop, em tema escuro e sem
       movimento reduzido. Se o WebGL2 faltar ou o shader não compilar, ele
       nunca fica visível e o gradiente segue sozinho.

   O TETO DE 0,14 NO ALFA DO SHADER NÃO É ESTÉTICA, É MEDIÇÃO. Acima disso a
   borda de controle (#686874, que precisa de 3:1) cai abaixo do mínimo. Está
   escrito no GLSL, não aqui, para o efeito nascer dentro do limite em vez de
   ser apagado por um véu depois.
   --------------------------------------------------------------------------- */
.ambient {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  overflow: hidden;

  /* Piso: três manchas quentes que derivam devagar. Puro gradiente, sem
     imagem e sem GPU dedicada. É o que o celular e o tema claro recebem. */
  background:
    radial-gradient(60% 45% at 18% 12%,
      color-mix(in srgb, var(--color-primary) 5%, transparent), transparent 70%),
    radial-gradient(50% 40% at 84% 34%, rgba(244, 96, 14, 0.035), transparent 72%),
    radial-gradient(70% 50% at 46% 88%, rgba(255, 117, 38, 0.03), transparent 74%);
  background-size: 180% 180%, 170% 170%, 200% 200%;
}

/* ⚠️ A DERIVA DO PISO SAIU, E O MOTIVO É MEDIDO.
   Aqui havia `animation: ambient-deriva 46s ease-in-out infinite alternate`,
   que anima `background-position` de três gradientes radiais num elemento
   `position: fixed; inset: 0`. Isso é repintura de viewport inteira a cada
   quadro, e era o item mais caro da página inteira:

     configuração                        1440×900 dpr2   3840×2160 dpr1
     como estava                          34,3ms 29fps    165,1ms  6fps
     sem a deriva                         19,3ms 52fps     40,1ms 25fps
     sem deriva, marquee e a rua          16,9ms 59fps     23,7ms 42fps

   Ou seja: ela sozinha custava 15ms por quadro em 1440 e 125ms em 4K, mais
   do que a cena da avenida inteira, que custa 2,8ms e 9,6ms. E estava quase
   toda escondida atrás do canvas, que roda com --rua em 1.

   O piso continua: são os mesmos três gradientes, parados. O que sumiu foi o
   deslocamento, que ninguém via e todo mundo pagava. */

/* ⚠️ A CENA NÃO É MAIS ATENUADA POR OPACIDADE, E ESTE É O LUGAR ONDE ISSO SE
   VÊ (01/09). Aqui havia `--rua: 0.78`, um `--rua: 0.62` dentro de um @media
   e a camada rodando a 62% de opacidade. Não há mais nada disso: o canvas
   compõe a 100% e o recuo acontece na PALETA, em ambient-street.js.

   Por que a troca. Opacidade sobre uma superfície quase preta é, de fato,
   redução de contraste — o argumento que sustentou o mecanismo em 31/08 está
   certo. O que ele não faz é distinguir: é um fator só, uniforme, que recua o
   farol e recua junto o asfalto, o meio-fio e a silhueta do caminhão, que não
   estavam competindo com nada. Para domar os painéis o número teve que descer
   a 0,62, e levou a cena inteira junto.

   A compressão de luminância distingue. Abaixo do joelho não toca em nada;
   acima dele satura suavemente num teto. O resultado medido é uma cena que
   recua MAIS onde competia e MENOS onde não competia — e o número que
   importa, a fração de tela acesa, caiu de 30,9% para 6,4% no monitor.

   ⚠️ E NÃO PONHA `filter` AQUI. Já foi tentado e foi medido: `filter:
   saturate() brightness()` neste elemento levava o quadro de 16,70ms para
   50,00ms em 1440×900 (de 60 para 20fps). Filtro numa camada de viewport
   inteira que muda todo quadro obriga o compositor a refazer a passagem 60
   vezes por segundo. A paleta faz a mesma coisa uma vez, no carregamento, de
   graça. */
.ambient__gl {
  display: block;
  width: 100%;
  height: 100%;

  /* A opacidade que sobrou é só a entrada da camada: nasce em 0 e o JS
     adiciona .is-on quando a cena está pronta. Não é mais um botão de
     intensidade — é uma transição de uma vez só. */
  opacity: 0;
  transition: opacity var(--duration-ambient) var(--ease-out);
}
.ambient__gl.is-on { opacity: 1; }

/* ---------------------------------------------------------------------------
   VÉU CINEMATOGRÁFICO
   Três camadas de gradiente escuro, ESTÁTICAS, por cima do canvas e por baixo
   de tudo mais. Um pseudo-elemento do .ambient, então herda o z-index -1 e o
   pointer-events: none do pai: é impossível ele encostar na interface.

   É estático de propósito. O comentário logo acima documenta que animar
   background-position num elemento fixed de viewport inteira custava 15ms por
   quadro em 1440 e 125ms em 4K. Gradiente parado pinta uma vez e vira textura
   de camada: custo de composição, não de repintura.

   Por que véu E filtro, e não só um dos dois: o filtro age igual em toda a
   tela, e o problema não é uniforme. O céu do alto é a região mais clara da
   cena e é exatamente onde o cabeçalho mora; o asfalto de baixo é onde os
   faróis de perto estouram; e os cantos são onde o olho entra na página. O
   véu escurece essas três zonas e deixa o miolo, que é o ponto de fuga da
   avenida, quase intacto. É lá que a cidade continua acontecendo.
   --------------------------------------------------------------------------- */
.ambient::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;

  /* --veu vem de CENA.veu, escrito pelo JS. As seis porcentagens abaixo são a
     COMPOSIÇÃO do véu, ou seja quanto cada zona pesa em relação às outras; a
     força do conjunto é este número só.

     ⚠️ Opacidade AQUI é legítima, e não contradiz a regra de não atenuar a
     cena por opacidade global. Esta regra não é a cena: é um pseudo-elemento
     de fundo, com z-index -1 e pointer-events: none herdados do .ambient, que
     não tem uma única linha de interface por cima. Atenuar a CENA por
     opacidade achataria o contraste do que se quer ver; atenuar o VÉU por
     opacidade é exatamente dosar um filtro escuro, que é o que ele é. */
  opacity: var(--veu, 1);

  background:
    /* 1 · vinheta. Centro em 45% da altura, que é a linha do horizonte da
       cena: a dissolução acompanha a geometria da avenida em vez de cortar
       um círculo por cima dela. */
    radial-gradient(126% 96% at 50% 45%,
      transparent 40%,
      color-mix(in srgb, var(--color-surface) 58%, transparent) 100%),
    /* 2 · o céu, que é a faixa mais clara da cena e fica atrás do cabeçalho */
    linear-gradient(to bottom,
      color-mix(in srgb, var(--color-surface) 62%, transparent) 0,
      transparent 24%),
    /* 3 · o asfalto de perto, onde os faróis chegam maiores */
    linear-gradient(to top,
      color-mix(in srgb, var(--color-surface) 48%, transparent) 0,
      transparent 32%);
}

/* A vinheta é uma elipse com raio em porcentagem do elemento. Num viewport
   estreito e alto ela deixa de ser moldura e vira tampa: a borda escura chega
   ao miolo e apaga justamente o ponto de fuga, que é a única parte da avenida
   que sobra no enquadramento do celular (ver o comentário do --rua acima).
   Abaixo de 900px a vinheta abre e recua, e as duas faixas horizontais, que
   dependem só da altura, ficam como estão. */
@media (max-width: 899px) {
  .ambient::after {
    background:
      radial-gradient(150% 108% at 50% 45%,
        transparent 54%,
        color-mix(in srgb, var(--color-surface) 42%, transparent) 100%),
      linear-gradient(to bottom,
        color-mix(in srgb, var(--color-surface) 62%, transparent) 0,
        transparent 24%),
      linear-gradient(to top,
        color-mix(in srgb, var(--color-surface) 48%, transparent) 0,
        transparent 32%);
  }
}


/* ---------------------------------------------------------------------------
   PAINEL DE LEITURA
   A rua roda a 100% por trás de tudo. O contraste do texto NÃO é resolvido
   apagando o efeito, e sim aqui: cada bloco de conteúdo ganha um painel de 85%
   da superfície, então atrás de texto passam 15% da cena.

   85% NÃO É GOSTO, É O MÍNIMO MEDIDO MAIS MARGEM.
   O número anterior era 93%, herdado de uma cena mais fraca e de um
   verificador que sobe a árvore do DOM e por isso não enxergava o canvas:
   ele media a superfície como se a rua não existisse.

   Medido em 20/08 com ferramentas/contraste-fundo.mjs no modo nu, que
   fotografa a cena CRUA atrás de cada linha de texto e resolve a opacidade
   por composição alfa: 2.219 alvos em seis larguras, e o pior deles pede
   81,2%. O pior é sempre texto em --color-text-muted em cima do núcleo
   branco de um farol, ou seja rgb(255,255,255) atrás de #A5A19D.

   85% dá 3,8 pontos de margem sobre o pior caso medido e deixa passar 15% da
   cena em vez de 7%: é o dobro de rua visível pelo mesmo texto legível.

   Como o .container tem no máximo 1200px, em monitor largo o shader fica a
   100% nas laterais: 83px de cada lado em 1366, 360px em 1920, 680px em 2560.
   Mais os vãos entre seções e o cabeçalho translúcido. O efeito aparece
   inteiro onde ninguém precisa ler, e recua onde precisa.

   A máscara horizontal dissolve as bordas do painel: sem ela o conteúdo
   pareceria uma caixa colada em cima do fundo.

   ⚠️ A MÁSCARA MEDE var(--gutter), NÃO UMA PORCENTAGEM, E ISSO IMPORTA.
   O texto começa exatamente na borda do padding, que é --gutter. Máscara em
   porcentagem não sabe disso: em 1440 o .container tem 1200 de largura e 32
   de padding, então 4% eram 48px de dissolução para 32px de folga, e os
   primeiros 16px da linha ficavam com o painel pela metade.

   Foi assim que o parágrafo .pacotes__note reprovou em 1440 com 4,05:1
   enquanto passava com 6,91:1 em 1280: não era a cena, era a máscara comendo
   o começo da linha. Casando a dissolução com o gutter, ela acaba onde o
   texto começa, em qualquer largura.

   ⚠️ ESTA REGRA JÁ ESTEVE DENTRO DE @media (min-width: 900px), E ERA BUG.
   Abaixo de 900px o texto ficava direto em cima da cena, sem painel nenhum.
   Passava despercebido porque a rua antiga era escura e rala; com a cena de
   20/08 deu 24 elementos reprovados em 360, 390 e 768px e zero em 1024, 1280
   e 1440. A fronteira do problema era exatamente a fronteira da media query.
   O painel cobre o BLOCO DE CONTEÚDO e não a seção inteira, então o respiro
   de cada seção continua transparente: é por ali que a rua aparece no
   celular.
   --------------------------------------------------------------------------- */
/* --painel-leitura vem de CENA.mascaraConteudo, escrito pelo JS. O padrão de
   85% é rede de segurança e é o mesmo valor: se o JS não rodar, a cena não
   aparece (ver .ambient__gl.is-on) e o painel é inofensivo de qualquer jeito. */
main > section > .container {
  background: linear-gradient(
    90deg,
    transparent 0,
    color-mix(in srgb, var(--color-surface) var(--painel-leitura, 85%), transparent) var(--gutter),
    color-mix(in srgb, var(--color-surface) var(--painel-leitura, 85%), transparent) calc(100% - var(--gutter)),
    transparent 100%
  );
}

/* O cabeçalho tem os dois botões contornados por --color-border-strong, que
   exigem 3:1. A 88% passavam 12% do shader e a borda caía para 2,67:1. */
@media (min-width: 900px) {
  .site-header {
    background: color-mix(in srgb, var(--color-surface) 94%, transparent);
  }
}

/* O canvas agora roda no celular também. O raymarcher anterior não podia:
   50 iterações por pixel deixavam Android mediano 2× acima do orçamento de
   GPU. Sprites copiados custam 0,08 GB/s e passam em qualquer aparelho, então
   o site deixou de ter duas experiências diferentes. */

@media (prefers-reduced-motion: reduce) {
  .ambient { animation: none; }
  .ambient__gl { display: none; }
}

/* ---------------------------------------------------------------------------
   GARANTIA ESTRUTURAL DE CONTENÇÃO
   O overflow-x: clip do body acima corta o excedente, mas corta no fim da
   pilha: qualquer coisa que estoure dentro de uma seção só é contida na borda
   da janela, e no caminho já empurrou o layout. Foi por isso que o corte
   lateral apareceu três vezes por causas diferentes: o brilho do totem, e
   depois as variantes horizontais do reveal.

   Contendo em CADA seção, o estouro morre onde nasce. Nenhum elemento, atual
   ou futuro, consegue vazar de uma seção para a horizontal da página. Isto é
   uma trava de arquitetura, não um remendo de sintoma.

   Por que `clip` e não `hidden`: `hidden` num eixo força o outro a `auto`,
   o que criaria um contexto de rolagem VERTICAL por seção. `clip` não cria
   contexto de rolagem nenhum e deixa o eixo Y em `visible`.
   Sem fallback @supports aqui de propósito: o fallback de `hidden` seria pior
   que o problema, e o clip do body já cobre navegador antigo.

   Nenhum `position: sticky` vive dentro de <main>, então nada aqui é afetado:
   o único sticky do projeto é o .site-header, que fica fora. Verificado. */
main > section,
.site-footer {
  overflow-x: clip;
}

/* Trava geral contra rolagem horizontal: nenhum filho de grid ou flex pode
   ultrapassar o pai por causa de conteúdo longo. É o `min-width: auto`
   implícito que causa 90% dos overflows em layouts de grid. */
.cards > *, .ft-top > *, .hero__inner > *, .sobre__inner > *,
/* ---------------------------------------------------------------------------
   MIN-WIDTH: 0 EM FILHO DE FLEX E GRID
   O valor inicial de min-width em item de flex e grid é `auto`, não `0`: o
   item se recusa a encolher abaixo do conteúdo, e uma palavra longa empurra
   a coluna inteira para fora. É a causa nº 1 de estouro lateral em layout
   moderno, e não aparece em teste até alguém escrever uma palavra grande.
   Aplicado em todo container que carrega texto.
   --------------------------------------------------------------------------- */
.decisao__inner > *,
.form__row > *,
.cards > *,
.hero__inner > *,
.sobre__inner > *,
.hero__fact > *,
.faq__q > *,
.plano__price > *,
.marquee__head > *,
.site-header__inner > *,
.contato-direto li > *,
.sobre__fatos > * { min-width: 0; }

/* ---------------------------------------------------------------------------
   FIGURA TABULAR
   Numeral proporcional dá larguras diferentes para cada dígito: o `1` é
   estreito, o `8` é largo. Numa lista de especificações isso desalinha a
   coluna da direita linha a linha, e num preço faz o valor tremer quando o
   dígito troca. Em tabela e em preço a figura tem que ser tabular, que é a
   variante de largura fixa que a fonte já traz e ninguém liga.

   Não vai no corpo do texto de propósito: lá o numeral proporcional é o certo,
   porque a leitura é sequencial e não vertical. Aplicar em tudo seria o mesmo
   erro na direção oposta.
   --------------------------------------------------------------------------- */
.specs,
.plano__price,
.step__num,
.hero__fact,
.contato-direto,
.sobre__fatos,
.ft-contact { font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------------------
   QUEBRA DE STRING LONGA
   `anywhere` só onde pode aparecer coisa sem espaço: número de telefone,
   endereço, URL colada pelo visitante no assistente. Nunca `break-all`, que
   corta palavra comum no meio e destrói a leitura.
   --------------------------------------------------------------------------- */
.ft-contato a,
.contato-direto__valor,
.site-nav__contact,
.field__error { overflow-wrap: anywhere; }

/* ---------------------------------------------------------------------------
   MEDIDA DE LINHA
   45 a 75 caracteres é a faixa em que o olho encontra o começo da linha
   seguinte sem se perder. Em `ch` e não em `rem` porque a medida é sobre
   CARACTERE: em rem ela se desloca sempre que a família tipográfica muda.
   --------------------------------------------------------------------------- */
.section__lead,
.stakes__text,
.sobre__text,
.sobre__bio,
.decisao__text,
.hero__subtitle,
.faq__a p { max-width: 65ch; }

h1, h2, h3 {
  font-family: var(--font-display);
  line-height: var(--leading-tight);
  /* Base do meio da escala. Quem sai dela é quem muda de tamanho, logo
     abaixo em cada componente, e não cada elemento por conta própria. */
  letter-spacing: var(--tracking-title);
  margin: 0;
  text-wrap: balance;
}

p  { margin: 0 0 var(--space-4); text-wrap: pretty; }
ul, ol, dl, dd { margin: 0; padding: 0; }
li { list-style: none; }
img, svg, figure { display: block; max-width: 100%; }
a { color: inherit; }

:focus-visible {
  outline: 3px solid var(--color-primary);
  outline-offset: 3px;
  border-radius: var(--radius-sm);
}

.skip-link {
  /* translate em vez de top: `top` é propriedade de layout e força reflow a
     cada quadro. O alvo é 60fps, e só transform e opacity compõem na GPU. */
  position: absolute; left: var(--space-4); top: var(--space-4);
  translate: 0 calc(-100% - var(--space-6));
  z-index: var(--z-modal);
  padding: var(--space-3) var(--space-5);
  background: var(--color-primary); color: var(--color-primary-ink);
  border-radius: var(--radius-full); font-weight: 700; text-decoration: none;
  transition: translate var(--duration-fast) var(--ease-out);
}
.skip-link:focus-visible,
.skip-link:focus { translate: 0 0; }

.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.container--narrow { max-width: var(--container-narrow); }

.section { padding-block: var(--section-y); }

.section__head { margin-bottom: var(--space-10); }
.section__head--narrow { max-width: 46rem; }
.section__title { font-size: var(--text-2xl); font-weight: 800; }
.section__lead {
  margin-top: var(--space-4);
  font-size: var(--text-lg);
  color: var(--color-text-muted);
  max-width: 44rem;
}

/* O rótulo de seção é rótulo: diz onde você está, não pede clique. Estava em
   --color-primary nas oito seções, e oito usos do acento em texto que ninguém
   toca é o maior gasto isolado da página. Caixa alta, 12px, peso 700 e
   entreletra larga já dão hierarquia de sobra sem consumir a cor de ação.
   7.66:1 sobre a superfície base. */
.eyebrow {
  margin: 0 0 var(--space-3);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.accent { color: var(--color-primary); }

/* ============================ 3 · BOTÕES ============================ */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--space-2);
  min-height: 48px;                 /* alvo de toque ≥ 44px */
  padding: var(--space-3) var(--space-6);
  border: 1px solid transparent;
  border-radius: var(--radius-full);
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--duration-fast) var(--ease-out),
              border-color var(--duration-fast) var(--ease-out),
              color var(--duration-fast) var(--ease-out),
              transform var(--duration-fast) var(--ease-out);
}
.btn:active { transform: translateY(1px); }

.btn--primary { background: var(--color-primary); color: var(--color-primary-ink); }
@media (hover: hover) and (pointer: fine) {
  .btn--primary:hover { background: var(--color-primary-hover); }
}

.btn--ghost {
  background: transparent;
  color: var(--color-text);
  border-color: var(--color-border-strong);
}
@media (hover: hover) and (pointer: fine) {
  .btn--ghost:hover { border-color: var(--color-primary); color: var(--color-primary); }
}

.btn--sm { min-height: 44px; padding: var(--space-2) var(--space-5); font-size: var(--text-xs); }
.btn--block { display: flex; width: 100%; }

/* CTA principal do hero: maior que os demais, para ser o ponto mais quente
   da primeira dobra sem depender só de cor. */
.btn--lg { min-height: 56px; padding: var(--space-4) var(--space-8); font-size: var(--text-base); }

/* Secundário de verdade: sem borda, para não competir com o primário.
   Dois botões com o mesmo peso visual viram uma decisão a mais, não a menos. */
.btn--quiet {
  background: transparent;
  color: var(--color-text-muted);
  border-color: transparent;
  padding-inline: var(--space-4);
}
@media (hover: hover) and (pointer: fine) {
  .btn--quiet:hover { color: var(--color-text); }
}
.btn__ico { width: 17px; height: 17px; fill: none; stroke: currentColor;
            stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }

@media (prefers-reduced-motion: reduce) { .hero__pulse::after { animation: none; } }

.btn__arrow { transition: transform var(--duration-fast) var(--ease-out); }

/* ---------------------------------------------------------------------------
   ESTADOS QUE FALTAVAM: DESABILITADO E ENVIANDO
   Todo controle precisa dos cinco estados. Existiam hover, focus-visible e
   active; faltavam estes dois, e sem eles um botão que não pode ser usado
   parece igual a um que pode, e um envio em curso não dá sinal nenhum de que
   está acontecendo.
   --------------------------------------------------------------------------- */
.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
  /* Sem transform no hover: controle desabilitado que ainda reage mente
     sobre a própria disponibilidade. */
  pointer-events: none;
}

/* O estado "enviando" e o anel girando saíram em 14/08: não existe mais
   espera no site. O único envio virou entrega direta no WhatsApp, que
   acontece no mesmo quadro do clique. Estado que nada dispara é peso morto
   que alguém vai jurar que funciona. Volta se entrar operação assíncrona. */
@media (hover: hover) and (pointer: fine) {
  .btn:hover .btn__arrow { transform: translateX(3px); }
}

/* .chip, .filters, .pontos__empty, .badge--free, .badge--full,
   .card--ponto.is-full e .card__disabled-note saíram em 19/08 junto com o
   filtro de disponibilidade e o dado de vaga que o alimentava. Eram 60 linhas
   de CSS para dois botões que devolviam o mesmo resultado e para três estados
   de cartão que nenhum cartão tem mais. Voltam no commit que devolver a
   disponibilidade real. */

/* ============================ 4 · HEADER ============================ */
.site-header {
  position: sticky; top: 0; z-index: var(--z-header);
  background: var(--color-surface); /* fallback para navegador sem color-mix */
  background: color-mix(in srgb, var(--color-surface) 88%, transparent);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--color-border);
  padding-top: env(safe-area-inset-top);
}
/* LAYOUT DO CABEÇALHO · dois grupos, não três coisas soltas.
   Era `justify-content: space-between` com três filhos, e com três filhos o
   space-between não centraliza nada: ele empurra o do meio para onde sobrar
   espaço depois que os dois das pontas se medirem. A navegação flutuava num
   ponto que dependia do comprimento do nome da marca, e mudar "Comunicação em
   LED" mexeria na posição dos links. Ninguém sabe dizer o que está errado
   nisso, mas todo mundo vê que o cabeçalho está torto.

   Agora são dois grupos com âncora explícita: a marca à esquerda, e navegação
   mais ação juntas à direita, empurradas pelo `margin-inline-start: auto` da
   .site-nav. O olho lê marca → (respiro) → onde ir → o que fazer, nessa ordem,
   e o respiro do meio é a maior área vazia da barra, que é o que faz um
   cabeçalho parecer calmo. */
.site-header__inner {
  display: flex; align-items: center;
  gap: var(--space-3);
  min-height: 68px;
  /* Sem transição em min-height: é propriedade de layout e animá-la obriga o
     navegador a refazer o fluxo a cada quadro, justamente durante a rolagem,
     que é o pior momento possível. A compactação passou a ser um degrau
     limpo; o que transiciona é o fundo e a sombra, que compõem na GPU. */
}

/* O header compacta depois da primeira dobra. Não é enfeite: no meio da
   página o cabeçalho já cumpriu o papel de apresentar a marca e passa a ser
   só navegação, então devolve 8px de altura ao conteúdo. A classe é escrita
   pelo main.js; sem JS o header fica na altura cheia e nada quebra. */
.site-header.is-scrolled .site-header__inner { min-height: 60px; }
.site-header.is-scrolled {
  background: color-mix(in srgb, var(--color-surface) 96%, transparent);
  box-shadow: var(--shadow-sm);
}
/* Exceção intencional à escala de breakpoints: 359px cobre só o aparelho de
   320px de largura. Acima disso o nome da marca cabe e deve aparecer. Sem
   esta regra, em 320px o logo empurrava os botões de tema e menu para fora. */
@media (max-width: 359px) { .brand__text { display: none; } }

/* min-height 44: a marca é o link de voltar ao topo e o único alvo do lado
   esquerdo do cabeçalho. A imagem tem 38px, então sem isto o alvo tinha 38 de
   altura. O cabeçalho já tem folga para os 44 e nada se move. */
.brand { display: flex; align-items: center; min-height: 44px;
         gap: var(--space-3); text-decoration: none; }
.brand__mark {
  width: 38px; height: 38px;
  border-radius: var(--radius-full);
  object-fit: cover;
  flex-shrink: 0;
}
.brand__text { display: flex; flex-direction: column; line-height: 1.2; }
.brand__text strong {
  font-family: var(--font-display);
  font-size: var(--text-base); font-weight: 800;
  letter-spacing: var(--tracking-sub);
}
/* Era 0.6875rem (11px), o menor texto do site inteiro, para uma linha que é a
   descrição do negócio. Subiu para o degrau da escala e ganhou 1px de folga
   do nome acima: duas linhas coladas viram uma mancha, e a assinatura da
   marca é justamente onde o acabamento é mais notado. */
.brand__text small {
  margin-top: 1px;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  letter-spacing: var(--tracking-small);
}

.site-nav { display: none; }
.site-nav__list { display: flex; gap: var(--space-6); }
.site-nav__list a {
  position: relative;
  display: inline-block;
  /* "Como funciona" quebrando em duas linhas dentro de um cabeçalho de 68px
     é a pior falha possível aqui: empurra a barra inteira e desalinha tudo.
     Sem quebra, o aperto vira estouro, que o @media abaixo resolve com espaço
     em vez de esconder o problema. */
  white-space: nowrap;
  padding-block: var(--space-1);
  font-size: var(--text-sm); font-weight: 600;
  color: var(--color-text-muted); text-decoration: none;
  /* A nav de topo só aparece a partir de 900px, largura em que quase sempre
     há mouse. Em tablet deitado, não: lá 1024px vem com dedo, e o link fechava
     em 33px de altura. O sublinhado é `bottom: -2px` no ::after, então crescer
     por min-height mantém o traço colado no texto. */
  min-height: 32px;
  transition: color var(--duration-fast) var(--ease-out);
}

/* Sublinhado que cresce do centro. Um pseudo-elemento com scaleX é mais
   honesto que animar `width`: escala não recalcula layout a cada quadro. */
.site-nav__list a::after {
  /* Espessura do sublinhado: uma decisão, usada quatro vezes. O raio é a
     própria espessura, que é o que dá a ponta arredondada. */
  --sublinhado: 2px;
  content: "";
  position: absolute;
  inset-inline: 0;
  bottom: calc(var(--sublinhado) * -1);
  height: var(--sublinhado);
  border-radius: var(--sublinhado);
  background: var(--color-primary);
  transform: scaleX(0);
  transition: transform var(--duration-base) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .site-nav__list a:hover { color: var(--color-text); }
}
@media (pointer: coarse) {
  .site-nav__list a { min-height: 44px; }
}
.site-nav__list a:focus-visible::after { transform: scaleX(1); }
@media (hover: hover) and (pointer: fine) {
  .site-nav__list a:hover::after { transform: scaleX(1); }
}

/* Seção ativa. O aria-current é escrito pelo main.js conforme a pessoa rola,
   então o indicador não é só visual: leitor de tela anuncia "página atual".
   Sem JS, nenhum link fica marcado e a nav funciona como sempre funcionou. */
.site-nav__list a[aria-current="true"] { color: var(--color-text); }
.site-nav__list a[aria-current="true"]::after { transform: scaleX(1); }

.site-header__actions { display: flex; align-items: center; gap: var(--space-2); }
.header-cta { display: none; }

/* Filete entre navegação e ação. Só existe acima de 900px, porque abaixo
   disso não há navegação visível para separar de nada. Altura menor que a do
   botão ao lado de propósito: divisor que encosta nas duas bordas vira grade,
   e grade num cabeçalho pesa. Herda a borda decorativa, que é 11% do texto,
   então ele é sentido antes de ser visto. */
.site-header__rule { display: none; }


/* ---------------------------------------------------------------------------
   BOTÃO DE MENU
   As três barras nascem do MESMO centro, em absoluto, e só o deslocamento as
   separa. Fechar é zerar o deslocamento e girar: o X é geometricamente
   garantido.

   A versão anterior usava `display: grid` com três linhas e `translateY(7px)`
   para juntá-las. A geometria dependia de como o grid distribuía as linhas
   dentro dos 44px, e o resultado na tela era um ">" em vez de um X. Posição
   explícita não tem esse problema: não há distribuição para dar errado.
   --------------------------------------------------------------------------- */
/* ⚠️ O botão nasce escondido e só aparece quando o <html> tem data-js="on",
   atributo escrito pelo script síncrono do <head>.

   Sem isto, abaixo de 900px e com o JavaScript morto, a .site-nav ficava
   display:none pela regra base e o botão ficava visível sem handler nenhum:
   um controle com aparência de funcional que não fazia nada. Quem tocasse
   não concluiria "o script falhou", concluiria "esse site é ruim".

   Não é o cenário raro de JS desligado: o main.js é um IIFE único com 7
   blocos e 2 try, então uma exceção no bloco do tema derruba o menu junto.
   Numa rede ruim, script truncado produz exatamente isso.

   Mesmo padrão do [data-motion="full"] da camada SOAP: o recurso só se
   anuncia quando quem o implementa está de pé. */
.nav-toggle { display: none; }

html[data-js="on"] .nav-toggle {
  /* GEOMETRIA DO HAMBÚRGUER · uma decisão, não seis literais.
     Largura e espessura da barra são os dois únicos números escolhidos aqui.
     Centralização, raio da ponta e afastamento entre as barras saem deles por
     conta, então mudar a barra de 18 para 20px continua com tudo alinhado. */
  --bar-w: 18px;
  --bar-h: 2px;
  --bar-gap: 5.5px;

  position: relative;
  display: block;
  width: 44px; height: 44px;
  padding: 0;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-full);
  background: transparent; cursor: pointer;
  /* O alvo é redondo e o dedo não some dentro dele: sem isto, um toque rápido
     em Android dispara o realce cinza retangular padrão, que sai por fora do
     círculo e desmancha o acabamento. */
  -webkit-tap-highlight-color: transparent;
  /* Um toque não pode virar seleção de texto nem menu de contexto de imagem
     quando o dedo escorrega. */
  user-select: none;
  /* O botão não é alvo de rolagem: entregar isso ao navegador tira o atraso
     de 300ms de espera por gesto em alguns Android. */
  touch-action: manipulation;
  transition: border-color var(--duration-fast) var(--ease-out),
              background-color var(--duration-fast) var(--ease-out),
              scale var(--duration-fast) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .nav-toggle:hover { border-color: var(--color-primary); }
}
/* FEEDBACK DE TOQUE. É a única coisa entre o dedo encostar e o painel chegar,
   e sem ela o botão parece morto nesse intervalo, que é justamente onde a
   pessoa decide se o site responde ou não.
   `scale` e não `background`: afunda de verdade em vez de piscar, compõe na
   GPU e não invalida nada de layout. 0,92 é o ponto em que se sente sem se
   ver como animação. */
.nav-toggle:active { scale: 0.92; }

/* Aberto, o botão deixa de ser neutro: vira o controle ativo da tela. */
.nav-toggle[aria-expanded="true"] {
  border-color: var(--color-primary);
  background: var(--color-primary-soft);
}

.nav-toggle__bar {
  position: absolute;
  top: 50%; left: 50%;
  width: var(--bar-w); height: var(--bar-h);
  /* o centro da barra no centro do botão, derivado das duas medidas acima */
  margin: calc(var(--bar-h) / -2) 0 0 calc(var(--bar-w) / -2);
  border-radius: var(--bar-h);        /* ponta arredondada = metade da espessura */
  background: var(--color-text);
  transition: transform var(--duration-base) var(--ease-out),
              opacity   var(--duration-fast) var(--ease-out);
}
.nav-toggle__bar:nth-child(1) { transform: translateY(calc(var(--bar-gap) * -1)); }
.nav-toggle__bar:nth-child(3) { transform: translateY(var(--bar-gap)); }

.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(1) { transform: rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(3) { transform: rotate(-45deg); }
/* A barra do meio some encolhendo para o centro, não apagando no lugar:
   o olho lê as outras duas se cruzando em cima dela. */
.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(2) {
  opacity: 0; transform: scaleX(0.15);
}

/* ---------------------------------------------------------------------------
   MENU MOBILE
   Estrutura: .site-nav é a camada que escurece a página (o véu), e
   .site-nav__panel é a folha branca/preta que desce do header.

   Sem `position: fixed`: o header já é sticky e serve de referência, então o
   véu é `absolute` a partir dele. A decisão travada nº 4 do projeto vale, o
   FAB do assistente continua sendo o único flutuante.
   --------------------------------------------------------------------------- */
.site-nav.is-open {
  display: block;
  position: absolute;
  inset-inline: 0;
  top: 100%;
  /* A escala do projeto reservava --z-overlay para o véu e --z-modal para o
     painel, e nenhum dos dois estava em uso: a ordem entre eles saía de ordem
     de pintura, que muda quando alguém acrescenta um `position` a qualquer um
     dos dois. Agora está declarada. Os dois valores são internos ao contexto
     de empilhamento do header, então nada disto passa por cima do assistente. */
  z-index: var(--z-overlay);
}

/* Véu: escurece o que está abaixo do painel até o fim da janela.
   Pseudo-elemento em vez de div: zero markup a mais, e o clique nele tem
   como alvo a própria .site-nav, que o JS usa para fechar. */
.site-nav.is-open::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  top: 100%;
  height: 100svh;
  background: var(--veil);
  /* 3px, não mais. O que o desfoque faz aqui é tirar a nitidez das letras que
     ficaram atrás do véu, e é isso que separa "tem uma camada escura por cima"
     de "o fundo saiu de foco". Acima de 4 ou 5px vira efeito, e o header desta
     mesma página já usa desfoque em 14px: dois valores muito diferentes na
     mesma barra leriam como dois materiais diferentes.
     Custa pouco porque a cena do canvas PARA enquanto o menu está aberto (ver
     ambient-street.js): o fundo desfocado é estático, então o navegador
     desfoca uma vez em vez de sessenta vezes por segundo. */
  backdrop-filter: blur(3px);
  animation: nav-veil var(--duration-base) var(--ease-out) both;
}
/* ⚠️ TUDO QUE CONSTRÓI A FOLHA MORA AQUI DENTRO, E ISSO NÃO É ORGANIZAÇÃO.
   Estas propriedades estavam na regra base, sem media query, e o bloco de
   900px desfazia uma por uma. Funcionou até alguém acrescentar uma linha à
   base e esquecer de acrescentar o desfazimento correspondente, que foi
   exatamente o que aconteceu com `overflow-y: auto`.

   O ESTRAGO: overflow-y sozinho não fica sozinho. Quando um eixo recebe valor
   diferente de `visible`, o outro deixa de poder ser `visible` e o navegador
   o computa como `auto`. O painel virou caixa de rolagem nos DOIS eixos, no
   desktop, com 33px de altura. E o sublinhado de cada link é um ::after em
   `bottom: -2px`, ou seja, fora da caixa de conteúdo: scrollHeight passou a
   ser maior que clientHeight e o Chrome desenhou a barra de rolagem nativa
   dela, com as setinhas, encostada nos links do cabeçalho.

   Medido em 1440, 1200, 1024 e 900: barra em todos. Em 899 para baixo não
   aparecia porque aí o painel só existe com o menu aberto.

   Com a folha escopada, o bloco de 900px não precisa desfazer nada, e a
   próxima propriedade que alguém acrescentar aqui não tem como vazar.
   --------------------------------------------------------------------------- */
@media (max-width: 899px) {
  .site-nav__panel {
    /* Acima do véu, que é ::after posicionado da .site-nav. Sem z-index os
       dois dependiam só de ordem de pintura, e a escala do projeto já tinha
       os dois degraus reservados para isto sem ninguém usar. */
    position: relative;
    z-index: var(--z-modal);
    /* Recorte lateral e inferior do aparelho. Em celular deitado com entalhe,
       o conteúdo do menu passava por baixo dele. max() para o vão nunca ficar
       menor que o do resto do site. */
    padding:
      var(--space-3)
      max(var(--gutter), env(safe-area-inset-right, 0px))
      max(var(--space-5), env(safe-area-inset-bottom, 0px))
      max(var(--gutter), env(safe-area-inset-left, 0px));
    background: var(--color-surface);
    box-shadow: var(--shadow-md);
    animation: nav-panel var(--duration-base) var(--ease-out) both;

    /* Canto arredondado embaixo e nenhuma borda: o painel deixa de ser uma
       gaveta e passa a ser uma folha que desceu do cabeçalho. */
    border: 0;
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);

    /* ⚠️ O PAINEL PRECISA ROLAR SOZINHO, SENÃO O FIM DELE FICA INALCANÇÁVEL.
       Com o menu aberto a página inteira trava em `overflow: hidden`. Se o
       painel passar da altura da janela, nada mais rola e o que sobrou
       embaixo não existe para quem toca.

       Medido em 20/08, com o menu aberto:

         janela      painel   sobra   telefone do rodapé do menu
         528×467      444px   -33px   cortado
         844×390      456px  -122px   cortado
         390×844      443px  +345px   inteiro
         360×640      467px  +117px   inteiro

       Ou seja: em celular deitado, que é a orientação de quem está no carro,
       o telefone da BG dentro do menu estava fora de alcance.

       --nav-topo é a distância do topo da janela até o painel, escrita pelo
       main.js na abertura e REESCRITA a cada giro de tela ou mudança de
       altura da janela, porque a barra de endereço do celular some e volta
       durante a leitura. O fallback de 4rem cobre o JS ausente. */
    max-height: calc(100svh - var(--nav-topo, 4rem));
    overflow-y: auto;
    /* Explícito, e não o `auto` que o navegador escolheria sozinho: nada aqui
       rola de lado, e barra horizontal dentro de menu é defeito puro. */
    overflow-x: clip;
    /* Sem isto, a rolagem que chega ao fim do painel tenta continuar na
       página por trás do véu. */
    overscroll-behavior: contain;
  }

  /* Saída: sem escalonamento e mais curta, todos juntos. Escopada junto com o
     resto da folha para que uma classe `is-saindo` deixada por engano em
     tamanho de desktop não tenha nenhuma animação para disparar. */
  .site-nav.is-saindo .site-nav__panel {
    animation: nav-panel-sai 180ms var(--ease-in) both;
  }
  .site-nav.is-saindo .site-nav__list a,
  .site-nav.is-saindo .site-nav__foot {
    animation: nav-item-sai 140ms var(--ease-in) both;
  }
}

.site-nav.is-open .site-nav__list {
  flex-direction: column;
  gap: 0;
}

/* Cada item é uma linha de 56px com fio separador e uma seta de afordância.
   A seta é feita de duas bordas giradas: nenhum SVG, nenhum ícone extra. */
.site-nav.is-open .site-nav__list li + li a { border-top: 1px solid var(--color-border); }

.site-nav.is-open .site-nav__list a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  min-height: 56px;
  padding: var(--space-3) 0;
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 700;
  color: var(--color-text);
  /* ⚠️ `transform` E NÃO `translate`, DE PROPÓSITO, E NÃO É PREFERÊNCIA.
     O deslocamento do hover era `padding-inline-start`, que é propriedade de
     layout: cada quadro da transição obrigava o navegador a refazer o fluxo
     do painel inteiro, no dedo, no celular.

     A troca óbvia seria `translate`, mas `translate` já é de quem entra: a
     animação nav-item tem fill `both` e mantém `translate: 0 0` aplicado para
     sempre, e animação vence transição na cascata. O item simplesmente não
     sairia do lugar. `transform` é propriedade SEPARADA de `translate`, e as
     duas se compõem na ordem translate → rotate → scale → transform. Então a
     entrada continua dona do `translate` e o hover fica com o `transform`,
     sem disputa e sem refluxo. */
  transition: color var(--duration-fast) var(--ease-out),
              transform var(--duration-fast) var(--ease-out);
  /* entra escalonado, item a item */
  animation: nav-item var(--duration-base) var(--ease-out) both;
}
/* ⚠️ O LEQUE SAIU. Os itens vinham de um arco de -68deg a +68deg com raio de
   74px, cada um de uma direção diferente, e o rodapé do painel de uma sétima.
   É o efeito que impressiona na primeira abertura e cansa na quinta, e ele
   estava brigando com o pedido de sobriedade: 74px de percurso por item é
   quatro vezes a altura da própria linha.

   Agora os seis chegam da MESMA direção, subindo 10px, escalonados. É a lista
   assentando, não seis peças voando. O escalonamento é o que dá vida; a
   direção única é o que dá compostura.

   Saiu junto a dependência de sin() e cos() em calc(), que existia só para
   desenhar o arco.

   PARA VOLTAR ATRÁS: o keyframe nav-item guarda a forma antiga em comentário. */
.site-nav.is-open .site-nav__list li:nth-child(1) a { animation-delay:  30ms; }
.site-nav.is-open .site-nav__list li:nth-child(2) a { animation-delay:  75ms; }
.site-nav.is-open .site-nav__list li:nth-child(3) a { animation-delay: 120ms; }
.site-nav.is-open .site-nav__list li:nth-child(4) a { animation-delay: 165ms; }
.site-nav.is-open .site-nav__list li:nth-child(5) a { animation-delay: 210ms; }

.site-nav.is-open .site-nav__list a::after {
  content: "";
  /* Reset explícito do sublinhado da nav desktop, que é o mesmo
     pseudo-elemento. Sem isto a seta herda position:absolute e inset-inline
     e some do fluxo do flex. */
  position: static;
  inset: auto;
  flex-shrink: 0;
  width: 8px; height: 8px;
  background: none;
  border-top: 2px solid currentColor;
  border-right: 2px solid currentColor;
  /* EXCEÇÃO DOCUMENTADA: 1px é a ponta da caneta, metade do traço de 2px que
     desenha a seta. Não é raio de controle e não pertence à escala de forma. */
  border-radius: 1px;
  transform: rotate(45deg);
  /* 0.40 e não 0.32: dá 3.69:1 no tema escuro em vez de 2.73:1, sem virar
     ruído visual. A seta é redundante, a linha inteira já é um link com
     texto, então SC 1.4.11 não se aplica. Margem por acabamento, não por
     obrigação. */
  opacity: 0.4;
  transition: opacity var(--duration-fast) var(--ease-out),
              translate var(--duration-fast) var(--ease-out);
}
.site-nav.is-open .site-nav__list a:focus-visible {
  color: var(--color-primary);
  transform: translateX(var(--space-2));
}
@media (hover: hover) and (pointer: fine) {
  .site-nav.is-open .site-nav__list a:hover {
    color: var(--color-primary);
    transform: translateX(var(--space-2));
  }
}
@media (hover: hover) and (pointer: fine) {
  .site-nav.is-open .site-nav__list a:hover::after {
    opacity: 1;
    translate: 3px 0;
  }
}

/* Rodapé do painel: o CTA que o mobile não tinha.
   O .header-cta é display:none abaixo de 900px, então até aqui quem abria o
   site no celular não encontrava nenhum botão de WhatsApp no cabeçalho. */
.site-nav__foot {
  margin-top: var(--space-5);
  padding-top: var(--space-5);
  border-top: 1px solid var(--color-border);
  animation: nav-item var(--duration-base) var(--ease-out) 240ms both;
}
.site-nav__contact {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  margin: var(--space-4) 0 0;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  text-align: center;
}
.site-nav__contact strong {
  font-weight: 700;
  color: var(--color-text);
  letter-spacing: var(--tracking-small);
}

@keyframes nav-panel {
  from { opacity: 0; translate: 0 -10px; }
  to   { opacity: 1; translate: 0 0; }
}
@keyframes nav-veil {
  from { opacity: 0; }
  to   { opacity: 1; }
}
/* ⚠️ UMA ANIMAÇÃO DE ENTRADA, NÃO DUAS.
   Por um tempo conviveram duas: esta, no <a>, e uma `nav-item-arco` no <li>,
   com escalonamentos diferentes. O item chegava duas vezes, e o olho lê isso
   como interface hesitante. O arco foi absorvido aqui: um keyframe, um
   escalonamento, um responsável.

   O leque de -68deg a +68deg é o que sobrou do circle menu do 21st.dev: os
   itens chegam em arco, saindo da direção do botão, em vez de subirem em
   coluna. sin e cos em CSS são de 2023, mesma safra do color-mix que este
   projeto já exige. Onde não houver, a declaração cai e o item entra só com
   fade: degrada para menos animação, nunca para menos menu. */
/* 10px e nenhum `scale`. Escala em texto obriga o navegador a reamostrar o
   glifo a cada quadro, e o que se vê no meio da animação é letra borrada: é
   isto, e não o percurso, que faz um menu parecer barato. Opacidade e
   deslocamento chegam limpos.
   A forma antiga, para quem quiser o arco de volta:
     translate: calc(sin(var(--ang)) * var(--raio))
                calc(cos(var(--ang)) * var(--raio) * -1);
   com --raio: 74px e --ang de -68deg a +68deg distribuídos nos seis itens. */
@keyframes nav-item {
  from { opacity: 0; translate: 0 10px; }
  to   { opacity: 1; translate: 0 0; }
}

/* Saída. Sem ela o menu abre com cuidado e fecha com um corte, que é o
   defeito de acabamento mais comum em menu mobile. Mais rápida que a
   entrada: sair tem que parecer decisão, não despedida. */
/* A saída é mais curta que a entrada e sem escalonamento: os seis somem
   juntos. Sair tem de parecer decisão, e decisão não tem cadência. */
@keyframes nav-item-sai {
  to { opacity: 0; translate: 0 -6px; }
}
@keyframes nav-panel-sai {
  to { opacity: 0; translate: 0 -8px; }
}

/* ⚠️ A TRAVA DE ROLAGEM FAZIA O CABEÇALHO INTEIRO DESAPARECER. LEIA ANTES DE
   MEXER, PORQUE A LINHA PARECE INOFENSIVA E NÃO É.

   Era `html[data-nav="open"], html[data-nav="open"] body { overflow: hidden }`.
   O segundo seletor é o problema.

   `position: sticky` gruda no scrollport ANCESTRAL MAIS PRÓXIMO. Enquanto o
   body tem overflow visível, esse scrollport é a janela, e o .site-header
   gruda no topo dela como deveria. No instante em que o body recebe
   `overflow: hidden`, ele vira um contêiner de overflow e passa a ser o
   scrollport mais próximo do header. Só que o body nunca rolou: quem rolou foi
   a janela. Então o deslocamento de rolagem do novo scrollport é zero, o
   sticky não tem de onde grudar e o header volta para a posição estática dele,
   que é o topo do DOCUMENTO.

   Na prática: quem está a 900px do topo aperta o hambúrguer e o cabeçalho
   inteiro, botão e painel juntos, salta 900px para cima, para fora da tela. O
   menu ABRE de verdade (a classe entra, o aria muda), mas ninguém vê. É o
   "clico e ele simplesmente some" relatado três vezes pelo cliente.

   POR QUE PASSOU EM TODA BATERIA DE TESTE ATÉ AQUI: todas abriam o menu com a
   página no topo. Com scrollY em 0, a posição estática do header e a posição
   grudada são a mesma, e o defeito é invisível. O teste estava errado, não o
   relato.

   Medido em 390x844 com scrollY 900:
     html + body hidden  → header em -900px, botão fora da tela
     só html hidden      → header em 0, botão no lugar
     só html clip        → header em 0, botão no lugar
     só body hidden      → header em 0, mas a rolagem salta para 90

   Ficou `html` sozinho: o overflow da raiz se propaga para a janela e trava a
   rolagem do mesmo jeito, sem criar um segundo scrollport no caminho do
   sticky. `clip` também resolveria, mas `hidden` na raiz é o caminho antigo e
   universalmente suportado, e aqui a diferença entre os dois é nenhuma.

   Só abaixo de 900px, que é onde o menu existe. */
@media (max-width: 899px) {
  html[data-nav="open"] { overflow: hidden; }
  /* A barra de rolagem clássica some junto com a trava e o conteúdo pula a
     largura dela. Em celular não acontece, porque a barra é sobreposta, mas a
     janela estreita de um desktop cai nesta faixa e lá acontece. Reservar a
     goteira antes evita o salto sem precisar medir a barra no JS. */
  html { scrollbar-gutter: stable; }
}

/* ============================ 5 · HERO ============================ */
.hero { padding-block: clamp(2.5rem, 7vw, 5.5rem) var(--section-y); }
.hero__inner { display: grid; gap: var(--space-10); }
.hero__title { font-size: var(--text-3xl); font-weight: 800; letter-spacing: var(--tracking-display); }
/* No celular o subtítulo é --text-base, não --text-lg. O texto do herói tem
   quatro parágrafos: em 18px ele empurrava o botão para 840px de rolagem, ou
   seja, o CTA nascia fora da primeira tela de um iPhone 14. Em 16px continua
   acima do piso de corpo de leitura e devolve o botão para dentro da dobra.
   De 640px para cima a coluna cabe o tamanho maior e ele volta. */
.hero__subtitle {
  margin-top: var(--space-4);
  font-size: var(--text-base);
  color: var(--color-text-muted);
  max-width: 34rem;
}
.hero__subtitle p { margin: 0; }
.hero__subtitle p + p { margin-top: var(--space-3); }
/* As duas linhas de fecho vêm no branco do texto, contra o cinza do resto:
   são o que a pessoa leva da seção, e destaque por COR pesa menos que por
   tamanho num bloco que já é o mais alto do herói. */
.hero__subtitle strong { color: var(--color-text); font-weight: 700; }
/* Em 320px os dois botões lado a lado ficariam com 8 caracteres cada.
   Largura total até 480px, e a partir daí voltam a dividir a linha. */
.hero__actions { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-6); }
.hero__actions .btn { flex: 1 1 100%; }

/* Pill de lugar com ponto de operação ativa. */
.hero__pill {
  display: inline-flex; align-items: center; gap: var(--space-3);
  margin: 0 0 var(--space-6);
  padding: var(--space-2) var(--space-4) var(--space-2) var(--space-3);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-full);
  font-size: var(--text-xs); font-weight: 600;
  letter-spacing: var(--tracking-small);
  color: var(--color-text-muted);
}
.hero__pulse {
  position: relative;
  width: 7px; height: 7px; flex-shrink: 0;
  border-radius: var(--radius-full);
  background: var(--color-primary);
}
.hero__pulse::after {
  content: "";
  position: absolute; inset: 0;
  border-radius: inherit;
  background: inherit;
  /* Três pulsos e para. Loop eterno ao lado do H1 rouba a atenção do que a
     pessoa veio ler; três batidas cumprem o papel de dizer "está no ar" e
     depois somem do caminho. */
  animation: hero-pulse 2.4s ease-out 3;
}
@keyframes hero-pulse {
  from { transform: scale(1);   opacity: 0.7; }
  to   { transform: scale(3.2); opacity: 0; }
}

/* Três fatos abaixo do CTA. Ancoragem de preço, escassez real e reversão de
   risco, na ordem em que a objeção aparece na cabeça de quem lê.

   A lista virou painel. Com uma borda de topo solta, os três fatos liam como
   sobra da seção anterior e flutuavam sobre o fundo do herói; fechados em
   caixa, ganham o peso de credencial que o herói cobra deles. O brilho quente
   no canto superior esquerdo é a única cor da peça e sai do laranja da marca,
   nunca de um degradê decorativo. */
.hero__facts {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-2);
  margin-top: var(--space-8);
  padding: var(--space-5);
  border: 1px solid var(--color-border-accent);
  border-radius: var(--radius-lg);
  background:
    radial-gradient(120% 150% at 0% 0%, var(--color-primary-soft), transparent 60%),
    linear-gradient(158deg, rgba(255, 255, 255, 0.06), rgba(255, 255, 255, 0.015));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.08),
    var(--shadow-md);
  backdrop-filter: blur(14px) saturate(1.25);
}
/* Enquanto o painel é estreito cada fato é uma linha, valor à esquerda e
   rótulo à direita. Empilhado em duas linhas cada, os três ocupavam 110px do
   herói num aparelho de 320px. Assim ocupam 72px e ficam mais fáceis de
   varrer. */
.hero__fact {
  position: relative;
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-3);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--color-border);
}
.hero__fact:last-child { border-bottom: 0; padding-bottom: 0; }
.hero__fact::after { content: ''; display: none; }
.hero__fact strong {
  font-family: var(--font-display);
  font-size: var(--text-lg); font-weight: 800;
  line-height: 1.15;
  color: var(--color-text);
}
/* `.hero__fact strong small` saiu: o único <small> do documento está no
   .brand__text do cabeçalho, então a regra não casava com nada desde que a
   copy dos três fatos foi reescrita em 14/08. Seletor morto é o tipo de
   sujeira que sobrevive a três refatorações porque ninguém sabe se pode ir. */
.hero__fact span { font-size: var(--text-xs); color: var(--color-text-muted); }
/* Esta regra existia com o comentário "o primeiro fato é o de escassez:
   recebe a cor da marca para ser o único destaque cromático da lista". A
   premissa venceu na atualização de copy de 14/08: o primeiro fato hoje é
   "8 anos de empresa", que é credencial, não escassez, e não há nada para
   isolar. O acento estava pintando um número por inércia de um argumento que
   já não existe.
   A regra saiu inteira. Os três fatos usam a mesma cor, que é o que a lista
   sempre quis dizer: são três credenciais do mesmo peso. */

/* ---------------------------------------------------------------------------
   TOTEM DE LED
   Mockup inteiro em CSS. Nenhuma imagem de moldura, nenhuma biblioteca.
   A altura é reservada por aspect-ratio: zero CLS enquanto o vídeo carrega.
   --------------------------------------------------------------------------- */
.totem {
  /* Escala fluida contínua: acompanha a largura sem depender de breakpoint.
     Piso de 230px para o vídeo continuar legível em 320px de viewport. */
  --totem-w: clamp(230px, 72vw, 320px);

  /* ---- MATERIAL ----------------------------------------------------------
     O totem é um objeto físico desenhado, não um controle da interface. Metal
     é frio: os neutros quentes da marca aqui leriam como plástico bege, e foi
     por isso que estes cinzas nasceram fora do :root. O erro não era a
     temperatura, era a quantidade: nove cinzas espalhados por quatro regras,
     dos quais #34343b e #2e2e35 são a mesma decisão digitada duas vezes.
     Cinco degraus nomeados cobrem o gabinete, o poste e a sapata inteiros, e
     ficam presos ao componente que os usa.                                  */
  --metal-hi:   #45454d;   /* aresta batida pela luz     */
  --metal-up:   #3a3a41;   /* topo do gabinete           */
  --metal-mid:  #2e2e35;   /* face frontal               */
  --metal-lo:   #1a1a1f;   /* face em sombra             */
  --metal-deep: #0e0e11;   /* vinco, vão e contato       */
  /* Vermelho de LED aceso. Não é --color-error: o selo AO VIVO não comunica
     falha, e usar o vermelho semântico aqui ensinaria a cor errada ao olho. */
  --led-live:   #FF4D4D;

  /* ---- GEOMETRIA DO GABINETE ---------------------------------------------
     Os dois únicos números escolhidos. O raio da tela sai deles pela regra do
     raio aninhado (interno = externo − padding), em vez de ser um terceiro
     valor que alguém precisa lembrar de recalcular. */
  --gabinete-r:   18px;
  --gabinete-pad: 10px;

  /* ---- EXCEÇÕES DOCUMENTADAS ---------------------------------------------
     1. O selo AO VIVO simula chrome desenhado DENTRO de um painel de LED, não
        texto da página. 10px é a medida que vende a ilusão de escala; na
        escala tipográfica (12 a 13px) o selo fica do tamanho de um rótulo da
        interface e o mockup deixa de parecer um objeto fotografado.
     2. A sombra da sapata precisa de contato mais escuro que --shadow-md
        (0.55 contra 0.28). A escala de elevação descreve cartão sobre
        superfície; isto é um objeto pousado no chão, e com o contato da
        escala o totem flutua dois centímetros acima do piso. */
  --selo-size:   0.625rem;
  --sombra-piso: 0 2px 3px rgba(0, 0, 0, 0.55), 0 16px 26px rgba(0, 0, 0, 0.42);
  --pixel-ink:   rgba(0, 0, 0, 0.32);
  --sombra-vao:  0 0 0 1px rgba(0, 0, 0, 0.9) inset;   /* vão da moldura */

  /* ⚠️ ORÇAMENTO VERTICAL. A linha acima dimensiona o totem só pela largura, e
     a altura vem de graça pelo aspect-ratio 9/16 da tela: altura ≈ largura ×
     1,78. Em tela baixa isso explode:

       tela                   totem  % da altura
       528×467 (seu teste)    633px         136%
       iPhone 14 deitado      633px         162%
       iPhone SE deitado      633px         169%

     Ou seja: o produto que a página existe para vender não cabia na tela de
     quem virasse o celular. 42svh é a largura máxima que mantém o totem
     inteiro abaixo de ~75% da altura, já que altura ≈ largura × 1,78.
     Em telas altas o clamp continua mandando e nada muda: em iPhone 14 em pé
     o min() escolhe os 281px do clamp, não os 354px do svh. */
  --totem-w: min(clamp(230px, 72vw, 320px), 42svh);
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-block: var(--space-6) var(--space-4);
}

/* Luz que o painel joga no ambiente. É o que separa "tela" de "painel aceso".
   ⚠️ A largura é limitada ao container de propósito. Antes era
   calc(var(--totem-w) * 2.4) puro, e como o elemento é absoluto e centralizado
   por translateX(-50%), ele estourava dos DOIS lados por igual:

     viewport  brilho  container  estouro por lado
        320px    553px      288px            132px
        414px    715px      381px            167px   ← pior caso
        528px    768px      486px            141px
        768px    768px      707px             31px

   Isso não aparecia como barra de rolagem porque o body tem overflow-x: clip,
   que corta em silêncio em vez de revelar. O min() com 100% prende o brilho ao
   container e mantém o efeito: em 528px ele ainda vaza 83px de cada lado do
   gabinete, que tem 320px.
   Não usei overflow no .totem porque isso decapitaria a box-shadow de 54px de
   desfoque do .totem__cabinet, que é o que dá volume ao mockup. */
.totem__glow {
  position: absolute;
  top: 0;
  left: 50%;
  /* estouro-ok: gradiente decorativo, pointer-events:none, que já é
     transparente a 68% do raio. Pode ser mais alto que a tela sem prejuízo:
     não empurra nada, não recebe clique e não tem conteúdo. */
  width: min(calc(var(--totem-w) * 2.4), 100%);
  aspect-ratio: 1;
  transform: translateX(-50%);
  background: radial-gradient(circle,
              color-mix(in srgb, var(--color-primary) 20%, transparent) 0%,
              color-mix(in srgb, var(--color-primary)  7%, transparent) 38%,
              transparent 68%);
  pointer-events: none;
  z-index: 0;
}
/* Gabinete: a caixa metálica que segura a tela. */
.totem__cabinet {
  position: relative;
  z-index: var(--z-base);
  width: var(--totem-w);
  padding: var(--gabinete-pad);
  border-radius: var(--gabinete-r);
  background: linear-gradient(160deg,
              var(--metal-up)   0%,
              var(--metal-lo)  26%,
              var(--metal-deep) 70%,
              var(--metal-mid) 100%);
  box-shadow:
    /* aresta superior iluminada e o vinco interno: rendering de material, sem
       equivalente na escala de elevação, que descreve altura e não superfície */
    0 0 0 1px color-mix(in srgb, var(--color-text) 7%, transparent) inset,
    0 2px 3px rgba(0, 0, 0, 0.5) inset,
    var(--shadow-lg);
}

.totem__screen {
  position: relative;
  overflow: hidden;
  /* RAIO ANINHADO. Regra: interno = externo − padding. Escrito como conta e
     não como resultado: assim mexer no gabinete não deixa a moldura com
     espessura desigual no canto sem ninguém saber dizer o porquê. Hoje dá
     8px, que é exatamente --radius-sm. */
  border-radius: calc(var(--gabinete-r) - var(--gabinete-pad));
  background: var(--metal-deep);
  aspect-ratio: 9 / 16;                 /* reserva a altura antes do vídeo chegar */
  box-shadow: var(--sombra-vao);
}

.totem__video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Painel de LED tem preto profundo e cor saturada. */
  filter: saturate(1.12) contrast(1.06);
}

/* Matriz de pixels: dois gradientes finos cruzados. ~40 bytes de CSS
   fazendo o trabalho que uma textura PNG faria com 30 KB. */
.totem__pixels {
  position: absolute;
  inset: 0;
  pointer-events: none;
  /* Tinta da matriz, não cor de interface: entra por mix-blend-mode: multiply
     para escurecer o vídeo em faixas de 1px, do jeito que o vão entre LEDs
     escurece. Preto puro porque multiply com qualquer croma tinge o vídeo. */
  background-image:
    repeating-linear-gradient(0deg,   var(--pixel-ink) 0 1px, transparent 1px 3px),
    repeating-linear-gradient(90deg,  var(--pixel-ink) 0 1px, transparent 1px 3px);
  mix-blend-mode: multiply;
  opacity: 0.55;
}

/* Reflexo do vidro. Diagonal única, bem sutil. */
.totem__sheen {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(115deg,
              color-mix(in srgb, var(--color-text) 10%, transparent) 0%,
              color-mix(in srgb, var(--color-text)  3%, transparent) 24%,
              transparent 46%);
}

.totem__live {
  position: absolute;
  top: var(--space-3); left: var(--space-3);
  display: flex; align-items: center; gap: var(--space-1);
  margin: 0;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-full);
  background: var(--veil-strong);
  backdrop-filter: blur(6px);
  font-size: var(--selo-size); font-weight: 700;
  letter-spacing: var(--tracking-wide); text-transform: uppercase;
  color: var(--color-text);
}
.totem__live span {
  width: 6px; height: 6px; border-radius: var(--radius-full);
  background: var(--led-live);
  /* Mais lento e mais sutil que antes: o selo AO VIVO precisa existir, não
     precisa piscar. */
  animation: totem-live 3.4s ease-in-out infinite;
}
@keyframes totem-live { 50% { opacity: 0.25; } }

.totem__sound {
  position: absolute;
  bottom: var(--space-3); right: var(--space-3);
  display: grid; place-items: center;
  width: 44px; height: 44px;
  /* 40% e não 22%: a borda é o único contorno do botão sobre vídeo, então
     responde ao SC 1.4.11. Mesma decisão já tomada no .splash__skip. */
  border: 1px solid color-mix(in srgb, var(--color-text) 40%, transparent);
  border-radius: var(--radius-full);
  background: var(--veil-strong);
  backdrop-filter: blur(6px);
  color: var(--color-text);
  cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease-out),
              color var(--duration-fast) var(--ease-out);
}
/* O hover mexe só na borda e no ícone. Escurecer mais a chapa era um terceiro
   valor de véu para dizer o que a borda laranja já diz sozinha. */
@media (hover: hover) and (pointer: fine) {
  .totem__sound:hover { border-color: var(--color-primary); color: var(--color-primary); }
}
.totem__ico {
  width: 19px; height: 19px; fill: none; stroke: currentColor;
  stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round;
}
.totem__ico--on { display: none; }
.totem__sound[aria-pressed="true"] .totem__ico--off { display: none; }
.totem__sound[aria-pressed="true"] .totem__ico--on  { display: block; }

/* Pedestal: poste e sapata. É o que faz o objeto virar totem de rua. */
.totem__base {
  position: relative; z-index: var(--z-base);
  display: flex; flex-direction: column; align-items: center;
  width: 100%;
}
.totem__pole {
  width: 15%;
  height: clamp(20px, 4vw, 32px);
  background: linear-gradient(90deg,
              var(--metal-deep)  0%,
              var(--metal-mid)  42%,
              var(--metal-hi)   52%,
              var(--metal-lo)  100%);
}
.totem__foot {
  width: 42%;
  height: 9px;
  /* Raio aninhado: o pé encosta no chão, então a aresta de baixo é mais
     macia que a de cima. Fica fora da escala de propósito, porque isto não é
     um controle, é um objeto físico desenhado. */
  border-radius: 3px 3px 5px 5px;
  background: linear-gradient(90deg,
              var(--metal-deep)  0%,
              var(--metal-mid)  50%,
              var(--metal-deep) 100%);
  /* Duas camadas, e aqui é onde mais importa: um objeto pousado no chão tem
     contato quase preto logo abaixo da base e difusão larga em volta. Só a
     difusão fazia o totem parecer flutuando dois centímetros acima do piso. */
  box-shadow: var(--sombra-piso);
}

.totem__tag {
  position: relative; z-index: var(--z-base);
  margin: var(--space-5) 0 0;
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-small);
  color: var(--color-text-muted);
  text-align: center;
}

/* Em 900px o hero passa de 1 para 2 colunas e o totem perde espaço.
   Um clamp contínuo cobre de 900px até ultrawide sem nenhum degrau: com
   breakpoints separados em 1200 e 1600 havia um salto de 18% na largura
   exatamente em 1600px, visível ao redimensionar a janela. */
@media (min-width: 900px) {
  .totem { --totem-w: clamp(292px, 26vw, 400px); }
}

@media (prefers-reduced-motion: reduce) {
  .totem__live span { animation: none; }
}

/* O placeholder de mídia saiu em 18/08: as fotos dos sete pontos chegaram e
   ele não tinha mais nenhum uso no markup. Componente sem markup é peso morto
   que sobrevive anos porque ninguém sabe se ainda é usado. Os três seletores
   entraram na lista F7 do teste, junto com os de depoimento. */

/* ============================ 6 · MARQUEE ============================
   Faixa de reconhecimento. O objetivo é o visitante se achar em 1 segundo,
   não decorar a lista. Por isso ícone + nome, e não só nome.
   ==================================================================== */
.marquee {
  position: relative;
  border-block: 1px solid var(--color-border);
  background:
    /* leve calor no centro: a faixa deixa de ser uma tarja cinza */
    radial-gradient(120% 200% at 50% 50%, var(--color-primary-soft), transparent 62%),
    var(--color-surface-alt);
  padding-block: var(--space-5) var(--space-6);
  overflow: hidden;
}

.marquee__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}
.marquee__label {
  margin: 0;
  font-size: var(--text-xs); font-weight: 700;
  letter-spacing: var(--tracking-wide); text-transform: uppercase;
  color: var(--color-text-muted);
}

/* Botão de pausa: exigência do WCAG 2.2 SC 2.2.2, não enfeite. */
.marquee__toggle {
  /* min-width junto do min-height: abaixo de 640px o rótulo "Pausar" some e
     sobra o ícone, e o botão fechava em 41px de largura por 44 de altura.
     Alvo quadrado de 44 nos dois eixos, sempre. */
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--space-2);
  min-width: 44px; min-height: 44px;
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--color-text-muted);
  font: inherit; font-size: var(--text-xs); font-weight: 700;
  letter-spacing: var(--tracking-small);
  cursor: pointer;
  transition: color var(--duration-fast) var(--ease-out),
              border-color var(--duration-fast) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .marquee__toggle:hover { color: var(--color-text); border-color: var(--color-text-muted); }
}
.marquee__ico { width: 15px; height: 15px; fill: none; stroke: currentColor;
                stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.marquee__ico--play { display: none; fill: currentColor; stroke: none; }
.marquee__toggle[aria-pressed="true"] .marquee__ico--pause { display: none; }
.marquee__toggle[aria-pressed="true"] .marquee__ico--play  { display: block; }

/* Fade nas bordas. Sem isso o texto é decepado na borda da tela e parece bug. */
/* Fade mais largo, 14% em vez de 7%: a 7% o texto ainda aparecia quase opaco
   na borda e depois sumia de uma vez, o que lê como corte. A 14% ele se
   dissolve, que é o que a faixa quer parecer. */
.marquee__viewport {
  display: flex;
  overflow: hidden;
  /* EXCEÇÃO DOCUMENTADA: #000 aqui não é cor, é o canal alfa da máscara.
     Opaco significa "mostra", transparente significa "esconde", e trocar por
     um token de cor da marca não muda nada além de esconder a intenção. */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 14%, #000 86%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 14%, #000 86%, transparent);
}

.marquee__track {
  display: flex; flex-shrink: 0; align-items: center;
  /* O respiro entre os itens e o respiro antes da repetição são a MESMA
     distância: se divergirem, a emenda do loop aparece como um soluço. */
  --marquee-gap: clamp(var(--space-6), 4vw, var(--space-12));
  gap: var(--marquee-gap);
  padding-right: var(--marquee-gap);
  margin: 0;
  white-space: nowrap;
  animation: marquee 38s linear infinite;
  will-change: transform;
}
/* A cópia é gerada por JS. Se o JS não rodar, a faixa não anima e vira uma
   lista estática legível: degradação correta, nada quebrado. */
.marquee:not([data-marquee-ready]) .marquee__track { animation: none; }

.marquee__item {
  display: inline-flex; align-items: center; gap: var(--space-3);
  font-family: var(--font-display);
  /* --text-lg e não --text-xl: em --text-xl a faixa competia com o H1 logo
     acima, e hierarquia invertida é o que faz um site parecer amador. Ela é
     apoio, não manchete. */
  font-size: var(--text-lg); font-weight: 700;
  letter-spacing: var(--tracking-sub);
  /* Texto cheio, não muted: a faixa é conteúdo, não legenda. */
  color: var(--color-text);
}

/* Ponto separador entre segmentos. Sem ele os itens viram uma frase só e o
   olho não sabe onde um termina. Com ele a faixa lê como enumeração. */
.marquee__item::after {
  content: "";
  width: 4px; height: 4px;
  margin-inline-start: var(--space-3);
  border-radius: var(--radius-full);
  background: var(--color-border-strong);
  flex-shrink: 0;
}
.marquee__glyph {
  width: 1.15em; height: 1.15em; flex-shrink: 0;
  fill: none; stroke: var(--color-primary);
  stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
}

@keyframes marquee { to { transform: translateX(-100%); } }

/* Pausa no hover e no foco. Complementa o botão, não substitui. */
@media (hover: hover) and (pointer: fine) {
  .marquee:hover .marquee__track { animation-play-state: paused; }
}
.marquee:focus-within .marquee__track { animation-play-state: paused; }
.marquee.is-paused .marquee__track   { animation-play-state: paused; }

@media (prefers-reduced-motion: reduce) {
  .marquee__track { animation: none; }
  .marquee__viewport { overflow-x: auto; -webkit-mask-image: none; mask-image: none; }
  .marquee__toggle { display: none; }   /* sem movimento, não há o que pausar */
}

@media (max-width: 479px) {
  .marquee__item { font-size: var(--text-lg); }
  .marquee__toggle-txt { position: absolute; width: 1px; height: 1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
  .marquee__toggle { padding-inline: var(--space-3); }
}

/* ============================ 7 · CARDS (base) ============================ */
.cards { display: grid; gap: var(--space-5); }

.card {
  display: flex; flex-direction: column;
  border: 1px solid var(--color-border-accent);
  border-radius: var(--radius-md);
  background: var(--color-surface-alt);
  transition: border-color var(--duration-base) var(--ease-out),
              transform var(--duration-base) var(--ease-out);
}
.card__title { font-size: var(--text-lg); font-weight: 700; }
.card__text  { margin: var(--space-3) 0 0; color: var(--color-text-muted); font-size: var(--text-sm); }

.card--outline { padding: var(--space-6); background: transparent; }
@media (hover: hover) and (pointer: fine) {
  .card--outline:hover { border-color: var(--color-primary); }
}

/* ============================ 8 · STAKES ============================ */
.stakes__text { max-width: 46rem; font-size: var(--text-lg); }
.stakes__cards { margin-top: var(--space-10); }
.stakes__bridge {
  margin: var(--space-10) 0 0;
  font-family: var(--font-display);
  font-size: var(--text-xl);
  letter-spacing: var(--tracking-sub);
  color: var(--color-text-muted);
}
.stakes__bridge strong { color: var(--color-text); }

/* ============================ 9 · PONTOS ============================ */
/* O CARTÃO DE PONTO EM QUATRO BLOCOS, NESTA ORDEM:
   foto com etiqueta · identificação · ficha técnica · valores · ação.
   Antes eram sete elementos soltos no mesmo nível, separados só por margem, e
   o olho tinha que descobrir sozinho onde uma informação acabava e a outra
   começava. Agrupar em blocos com fundo e moldura próprios é o que faz o
   cartão ser lido em quatro paradas em vez de sete. */

.card--ponto {
  overflow: hidden;
  /* A herança de .card só transita borda e transform. A sombra entra na conta
     aqui porque o cartão de ponto é o único que sobe no hover, e sombra que
     aparece de uma vez enquanto o cartão sobe em 200ms lê como piscada. */
  transition: border-color var(--duration-base) var(--ease-out),
              transform var(--duration-base) var(--ease-out),
              box-shadow var(--duration-base) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .card--ponto:hover {
    border-color: var(--color-border-strong);
    /* 2px e não 6: o cartão tem 500px de altura, e deslocamento grande num
       objeto grande lê como o layout tremendo, não como resposta ao mouse. */
    transform: translateY(-2px);
    /* Subir sem sombra é o cartão trocando de posição sem trocar de altura:
       o olho vê o deslocamento e não recebe a explicação. --shadow-md é a
       mesma sombra do cartão em destaque dos pacotes, então a página tem uma
       ideia só de "isto está à frente". */
    box-shadow: var(--shadow-md);
  }
  .card--ponto:hover .card__img { transform: scale(1.02); }
}
.card--ponto .card__body {
  display: flex; flex-direction: column; flex: 1;
  padding: var(--space-5);
}

/* FOTO DO PONTO
   Quadro 1:1 com a foto INTEIRA dentro, sem corte. As sete fotos originais
   têm proporções de 0,75 a 1,18; a mediana é 1,01, então o quadrado é o
   formato que menos sobra no conjunto. O que sobra não é faixa morta: o
   ferramentas/pontos.py preenche com a própria foto desfocada e escurecida,
   então a cena continua para fora do quadro em vez de terminar numa barra.

   `cover` continua aqui de propósito, mesmo com o arquivo já em 1:1: ele é a
   rede para o dia em que alguém trocar uma foto por uma fora de proporção.
   width e height no <img> reservam o espaço antes do byte chegar. */
.card__media {
  position: relative;
  margin: 0;
  overflow: hidden;
  background: var(--color-surface);
  border-radius: var(--radius-md) var(--radius-md) 0 0;
}
.card__img {
  display: block; width: 100%; height: auto;
  aspect-ratio: 1 / 1; object-fit: cover;
  transition: transform var(--duration-slow) var(--ease-out);
}

/* ⚠️ O TETO DE ALTURA SAIU DA IMAGEM E FOI PARA O QUADRO. ISSO CORRIGE CORTE.
   Havia aqui `max-height: 50vh` no próprio <img>, para janela baixa e larga
   (celular deitado, notebook antigo) não virar um cartão que é só foto.

   O efeito colateral era grave: com `width: 100%` e altura travada, o elemento
   deixava de ser quadrado e virava uma tira larga. Aí o `object-fit: cover`
   fazia o que ele faz, cortava topo e base para preencher. Em 528 por 467 o
   quadro dava 496 por 233, ou seja proporção 2,1:1 em cima de um arquivo 1:1:
   sumia mais da metade da altura da foto, painel incluído.

   Agora o teto é de LARGURA e vale para o quadro inteiro. O quadrado continua
   quadrado, encolhe e centraliza, e nenhum pixel da foto é cortado em nenhuma
   janela. O `object-fit: cover` fica como rede para o dia em que alguém trocar
   uma foto por uma fora de proporção. */
.card--ponto .card__media {
  /* ⚠️ width DEFINIDA, E align-self NO LUGAR DE margin-inline: auto.
     A primeira versão usava `max-width` mais `margin-inline: auto`. Parecia
     equivalente e não é: item de flex com margem automática no eixo cruzado
     PERDE o stretch e passa a se medir por fit-content. Como a foto é lazy,
     antes de ela chegar o fit-content é ZERO: o quadro nascia 0 por 0, o
     cartão nascia com 529px em vez de 892, e a seção dos pontos inteira
     crescia 1.090px no momento em que as fotos carregavam.

     O estrago não ficava na seção: tudo abaixo dela descia junto, e o clique
     em "Valores" no menu aterrissava 1.163px antes da seção de valores,
     porque o alvo tinha sido calculado com a página menor.

     Com `width` definida o navegador resolve o `width: 100%` do <img>, a
     aspect-ratio 1/1 reserva a altura antes do primeiro byte da foto, e
     `align-self: center` centraliza sem mexer na medida. */
  width: min(100%, 54vh);
  align-self: center;
  /* Quando o teto entra, o quadro descola das laterais do cartão. Arredondar
     os quatro cantos evita o canto reto solto no meio do fundo. */
  border-radius: var(--radius-md);
}
/* Véu do pé da foto: liga a imagem ao corpo do cartão e dá contraste para a
   etiqueta em qualquer foto, sem depender da cor que calhar de estar ali. */
.card__media::after {
  content: "";
  position: absolute; inset-inline: 0; bottom: 0;
  height: 38%;
  background: linear-gradient(to top,
              color-mix(in srgb, var(--color-surface-alt) 92%, transparent),
              transparent);
  pointer-events: none;
}

.card__title { letter-spacing: var(--tracking-sub); }
.card--ponto .card__title { font-size: var(--text-xl); }
.card__scene {
  margin: var(--space-2) 0 var(--space-5);
  color: var(--color-text-muted); font-size: var(--text-sm);
  text-wrap: pretty;
}
/* ALINHAMENTO INTERNO DA LINHA
   Os sete nomes e endereços têm comprimentos diferentes: "Painel Andrino" cabe
   numa linha, "Totem interno Shopping Unisul" ocupa duas, e o endereço da
   Fucap ocupa duas onde o do Andrino ocupa uma. Medido, isso empurrava a ficha
   técnica de um cartão até 84px abaixo da do vizinho na mesma linha, e o olho
   lia a linha como três cartões desencontrados.

   A folga vai para o vão livre entre o endereço e a ficha, não para o fim do
   cartão: `margin-bottom: auto` prende ficha, preço e botão no pé, onde ficam
   na mesma altura nos três cartões, e a diferença de altura do nome vira ar
   depois do endereço, que é onde ar não parece defeito.

   Alinhar por cima exigiria reservar duas linhas fixas para nome e endereço,
   e aí o cartão de nome curto ganharia duas linhas em branco no meio do texto,
   que lê como conteúdo faltando. */
.card--ponto .card__scene { margin-bottom: auto; }

/* ETIQUETA SOBRE A FOTO
   Saiu do corpo do cartão, onde ocupava uma linha inteira acima do título e
   repetia a mesma frase sete vezes em coluna, empurrando o conteúdo que
   diferencia um ponto do outro para baixo. Sobre a foto ela vira o que sempre
   foi, uma etiqueta, e devolve uma linha de altura a cada cartão. */
.badge {
  align-self: flex-start;
  margin: 0;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-full);
  font-size: var(--text-xs); font-weight: 700;
  letter-spacing: var(--tracking-caps); text-transform: uppercase;
}
.badge--check {
  position: absolute; top: var(--space-4); left: var(--space-4);
  /* Sem o teto, o bloco absoluto se estica do recuo da esquerda até a borda
     direita da foto: em cartão de 260px a etiqueta encostava na moldura e
     lia como faixa de cabeçalho, não como selo. O teto devolve o mesmo
     recuo dos dois lados quando o texto precisa de duas linhas. */
  max-width: calc(100% - var(--space-8));
  z-index: var(--z-base);
  display: inline-flex; align-items: center; gap: var(--space-2);
  background: var(--veil-strong);
  backdrop-filter: blur(8px);
  color: var(--color-text);
  border: 1px solid color-mix(in srgb, var(--color-text) 22%, transparent);
}
/* O ponto antes do texto: a etiqueta é status, e status sem indicador é só
   uma frase dentro de uma cápsula. */
.badge--check::before {
  content: "";
  width: 6px; height: 6px; flex-shrink: 0;
  border-radius: var(--radius-full);
  background: var(--color-primary);
}
.badge--featured {
  position: absolute; top: calc(var(--space-3) * -1); left: var(--space-5);
  background: var(--color-primary); color: var(--color-primary-ink); border: 0;
}

/* FICHA TÉCNICA
   Era um par de linhas com hairline em cima e embaixo, solto no fluxo do
   cartão. Agora é uma caixa com superfície própria, um degrau abaixo do
   cartão: o olho reconhece "aqui é especificação" antes de ler a primeira
   palavra, e a separação do bloco de preço deixa de depender só de margem. */
.specs {
  margin: var(--space-5) 0 var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
}
/* Rótulo em cima, valor embaixo, nas duas linhas.

   Em linha, com o valor à direita, o resultado dependia do comprimento do
   texto: "Resolução  960 × 384 px" cabia numa linha e "Formato  Painel
   horizontal · 5,00 × 2,00 m" não cabia, então uma linha ficava em par e a
   outra quebrada, dentro da mesma caixa. Parecia acidente, porque era.

   Empilhado, as duas linhas têm a mesma forma em qualquer largura, e o valor
   ganha a largura inteira da caixa antes de precisar quebrar, que é o que faz
   a altura da ficha variar menos entre os cartões da mesma linha. */
.specs__row {
  display: grid;
  gap: var(--space-1);
  padding: var(--space-2) 0;
  font-size: var(--text-sm);
}
.specs__row + .specs__row { border-top: 1px solid var(--color-border); }
.specs dt {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-caps); text-transform: uppercase;
}
.specs dd {
  margin: 0; font-weight: 600; min-width: 0;
  font-variant-numeric: tabular-nums;
  text-wrap: pretty;
}

/* VALORES
   Um prazo por linha, rótulo à esquerda e número à direita.

   Lado a lado não cabe, e a medição diz onde: na faixa de 900px a 1023px a
   grade já está em três colunas e o cartão tem 260px, sobrando 218px de
   conteúdo. Dois valores de 138px mais fio e vão pedem 309px. Como coluna
   `1fr` não encolhe abaixo do conteúdo e o cartão tem `overflow: hidden`, o
   segundo preço não estouraria a tela: sumiria, calado. Reduzir a fonte até
   caber resolveria o estouro entregando um preço de 18px logo acima de um
   botão de 16px, que é desistir da hierarquia para salvar o arranjo.

   Empilhado, os dois números ficam alinhados na mesma borda direita, que é a
   posição em que comparar 399 com 449 custa um movimento de olho vertical em
   vez de um salto lateral por cima de um fio. E cabe em 273px de janela.

   Sem caixa, ao contrário da ficha técnica logo acima: especificação é dado
   de consulta e fica recuada, preço é o que decide e fica na superfície do
   cartão. Nada de laranja aqui, o botão logo abaixo é laranja e acento
   repetido dentro da mesma caixa deixa de dirigir o olho para o clique. */
.preco {
  display: grid;
  margin: 0 0 var(--space-5);
}
.preco__col {
  display: flex; align-items: baseline;
  justify-content: space-between;
  gap: var(--space-1) var(--space-3);
  flex-wrap: wrap;                     /* rótulo e valor nunca se sobrepõem */
  padding: var(--space-2) 0;
}
.preco__col + .preco__col { border-top: 1px solid var(--color-border); }
.preco__prazo {
  font-size: var(--text-xs); font-weight: 700;
  letter-spacing: var(--tracking-caps); text-transform: uppercase;
  color: var(--color-text-muted);
  flex-shrink: 0;
}
.preco__valor {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-xl); font-weight: 800;
  letter-spacing: var(--tracking-sub);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  margin-inline-start: auto;           /* encosta na direita mesmo se quebrar */
}
.preco__unidade {
  font-family: var(--font-body); font-size: var(--text-sm); font-weight: 500;
  letter-spacing: var(--tracking-flat); color: var(--color-text-muted);
}
/* A faixa em que a grade já abriu três colunas mas a janela ainda não cresceu:
   cartão de 260px, 218px de conteúdo, e "SEMESTRAL" mais vão mais número
   pedem 223px. Faltam cinco, e o efeito não é estouro: é "SEMESTRAL" caindo
   de linha enquanto "ANUAL" fica onde estava, deixando duas formas diferentes
   dentro do mesmo bloco de preço.

   Diminuir o número até caber trocaria a hierarquia pelo arranjo: o preço
   ficaria menor que o do cartão vizinho na janela seguinte. Empilhar rótulo e
   valor nas duas linhas resolve sem tocar no corpo, e nesta faixa o preço
   passa a ter a mesma forma da ficha técnica logo acima, que é a única outra
   caixa do cartão. */
@media (min-width: 900px) and (max-width: 1023px) {
  .preco__col { display: grid; gap: var(--space-1); }
  .preco__valor { margin-inline-start: 0; }
}

/* Não repetir `margin-top: auto` no botão. Quem empurra o bloco de baixo para
   o pé do cartão é o `margin-bottom: auto` do endereço, e duas margens
   automáticas na mesma coluna flex dividem a sobra pela metade: metade do ar
   ficaria depois do endereço e metade acima do botão, e ficha e preço
   voltariam a flutuar em alturas diferentes. Medido: com as duas, o preço
   ainda desencontrava 42px na faixa de 900px. Com uma só, zero. */

.pontos__note {
  margin: var(--space-8) 0 0;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.pontos__note strong { color: var(--color-text); }

/* ============================ 10 · PROCESSO ============================ */
.card--step { padding: var(--space-6); }
/* Anel laranja e numeral no contraste máximo. Sem fundo: superfície dentro de
   superfície seria a terceira camada num elemento de 42px. */
.step__num {
  display: grid; place-items: center;
  width: 42px; height: 42px;
  margin: 0 0 var(--space-5);
  border: 2px solid var(--color-primary);
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--color-text);
  font-family: var(--font-display); font-weight: 800; font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
}
@media (hover: hover) and (pointer: fine) {
  .card--step:hover { border-color: var(--color-primary); }
}

/* ============================ 11 · PACOTES ============================ */
.card--plano { position: relative; padding: var(--space-6); }
/* Com a moldura laranja valendo para todos os cartões, borda no tom cheio
   deixou de separar o plano recomendado dos outros dois. A diferença passou a
   ser ESPESSURA, não cor: 2px contra 1px é degrau que sobrevive à repetição do
   acento. O padding compensa os 2px para as três colunas terminarem com o
   texto no mesmo eixo. */
.card--plano.is-featured {
  border-width: 2px;
  border-color: var(--color-primary);
  padding: calc(var(--space-6) - 1px);
  background: var(--color-surface-raised);
  box-shadow: var(--shadow-md);
}
.plano__price {
  margin: var(--space-4) 0 var(--space-8);
  display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--space-2);
}
.plano__value  {
  font-family: var(--font-display);
  font-size: var(--text-2xl); font-weight: 800;
  letter-spacing: var(--tracking-title);
}
/* ⚠️ AMBIGUIDADE CONHECIDA, não resolvida aqui de propósito.
   "no período" não diz QUAL período. O cabeçalho da seção diz "semestral ou
   anual" e a nota abaixo da grade repete, mas o preço em si não escolhe: quem
   olha o cartão não sabe se R$ 690 é o semestre ou o ano.

   Resolver exige seis valores (3 pacotes × 2 prazos) que o cliente ainda não
   passou. É o item 7 das pendências no MELHORIAS.md. Inventar o segundo valor
   para o cartão "ficar completo" seria criar dado, que é proibido pelo Kernel
   §12 e barrado pela trava de lançamento.

   Quando os seis chegarem, a estrutura é um <dl> por cartão com os dois
   prazos lado a lado, no lugar deste par único. Não antecipei o markup porque
   markup vazio esperando dado é markup que alguém preenche com estimativa. */
/* "a partir de" antes do valor, e não depois: o cartão do prazo mostra o piso
   da tabela, e piso sem aviso vira promessa de preço único. O prefixo é a
   ressalva chegando antes do número, no mesmo lugar em que a nota da seção
   abaixo abre a faixa inteira. */
.plano__prefix { color: var(--color-text-muted); font-size: var(--text-sm); }
.plano__period { color: var(--color-text-muted); font-size: var(--text-sm); }
/* RITMO INTERNO DO CARTÃO
   O cartão tem quatro zonas: quem é, quanto custa, o que inclui, o que fazer.
   O vão entre zonas era --space-6 (24px) e o vão entre itens da lista é
   --space-3 (12px): razão de 2 para 1. Abaixo de 3 para 1 o olho agrupa tudo
   como um bloco só, e o botão vira o quinto item da lista de inclusões em vez
   da conclusão do cartão. Com --space-10 (40px) a razão vai a 3,3 e as quatro
   zonas passam a ser lidas como quatro. */
.plano__features {
  display: grid; gap: var(--space-3);
  margin-bottom: var(--space-10);
  font-size: var(--text-sm);
}
.plano__features li { position: relative; padding-left: var(--space-6); color: var(--color-text-muted); }
/* O marcador era laranja, nos doze itens dos três cartões. Doze usos do
   acento como enfeite contra UM botão principal na mesma seção: o laranja
   deixava de significar "clique aqui" e passava a significar "tem uma bolinha
   aqui". Raridade é o que torna o acento irresistível, e ele estava sendo
   gasto em pontuação.

   Neutro forte, que é o token com 3:1 garantido contra as três superfícies.
   Dentro do cartão de plano o laranja agora aparece em dois lugares só, e os
   dois são decisão: o selo do plano recomendado e o botão. */
.plano__features li::before {
  content: ""; position: absolute; left: 0; top: 0.55em;
  width: 8px; height: 8px; border-radius: var(--radius-full);
  background: var(--color-border-strong);
}
.card--plano .btn { margin-top: auto; }
/* O QUE ESTÁ INCLUSO
   Fora dos cartões porque é igual nos dois prazos. Dentro, viraria duas listas
   idênticas que obrigam a comparação item a item para descobrir que a única
   diferença é o prazo. Reusa .plano__features: mesmo marcador, mesmo ritmo,
   nenhum componente novo para manter. */
.incluso { margin-top: var(--space-10); }
.incluso__label {
  margin: 0 0 var(--space-5);
  font-size: var(--text-xs); font-weight: 700;
  letter-spacing: var(--tracking-wide); text-transform: uppercase;
  color: var(--color-text-muted);
}
.incluso__list { margin-bottom: 0; }

.pacotes__note { margin: var(--space-8) 0 0; font-size: var(--text-sm); color: var(--color-text-muted); }

/* ============================ 12 · PROVA ============================ */
.prova__label {
  font-size: var(--text-xs); font-weight: 700;
  letter-spacing: var(--tracking-wide); text-transform: uppercase;
  color: var(--color-text-muted);
}
/* O rótulo voltou a ser alinhado à esquerda junto com o título quando a
   parede virou malha. Centrado, ele era a única linha centrada de uma seção
   inteira alinhada à esquerda, e isso lê como desalinhamento, não como
   composição. A malha preenche a largura do container, então não há mais
   motivo para centrar nada aqui. */
.prova__label { margin-bottom: var(--space-5); }
/* ---------------------------------------------------------------------------
   PAREDE DE MARCAS
   Trinta e duas marcas na cor original, sobre placa clara.

   A PLACA É CLARA POR NECESSIDADE, NÃO POR GOSTO.
   Dos 32 arquivos, 18 têm arte escura (feita para viver sobre branco) e 14
   têm arte clara sobre fundo colorido. Sobre placa escura, os 18 primeiros
   somem; sobre placa clara, os 14 outros continuam legíveis porque trazem o
   próprio fundo junto. Placa clara é o único fundo em que as 32 sobrevivem.
   O `ferramentas/marcas.py` decide marca a marca o que fazer com o fundo e dá
   o mesmo raio de canto aos 14 que o mantêm, para que a variação seja só de
   cor e a forma seja constante.

   MALHA DE FIO ÚNICO, E NÃO MARCAS SOLTAS NO VÃO.
   A primeira versão espalhava as 32 marcas em linhas centradas, e o resultado
   tinha três defeitos que só aparecem em tamanho real:
     · o intervalo entre marcas variava, porque a caixa era fixa e o desenho
       dentro dela não: Sicredi e Claro pareciam distantes, Ademicon e CDL
       pareciam colados;
     · as linhas não compartilhavam eixo, porque um logo de ícone-sobre-texto
       ocupa altura diferente de um logotipo em uma linha só;
     · a última fila ficava com quatro itens centrados e um vão assimétrico.
   A malha resolve os três de uma vez: cada marca mora numa célula idêntica, o
   fio de 1px declara a estrutura, e as colunas fecham exato porque 32 divide
   por 4 e por 8. Nenhuma célula vazia, nenhuma fila órfã, em nenhuma largura.

   O fio é feito de BORDA, não de fundo com gap. Fundo sólido nas células
   taparia a rua animada que passa atrás das seções; a borda desenha a mesma
   malha e deixa o fundo passar.
   --------------------------------------------------------------------------- */
.logos {
  display: grid;
  /* Três colunas no celular e não quatro: em 360px, quatro colunas dão célula
     de 82px, e nela o Nelson e o Silva viram borrão. Três dão 110px, que é
     onde eles voltam a ser legíveis.
     32 não divide por 3, então a PRIMEIRA célula ocupa duas colunas e a malha
     fecha em 11 linhas exatas. Ser a primeira e não a última não é detalhe: a
     célula dupla vira hierarquia (a marca de maior peso de prova abre a
     parede) em vez de sobra de conta. */
  grid-template-columns: repeat(3, 1fr);
  /* O FIO VEM DO FUNDO ATRAVÉS DO GAP, não de bordas somadas.
     Com borda em cada célula, a malha inteira fica presa a um retângulo de
     canto vivo: `border-radius` no container não recorta borda de filho, e a
     última coluna acaba com fio duplo. Pintando o container com a cor do fio
     e deixando 1px de gap, cada vão entre células vira o próprio fio, o
     contorno externo sai de graça e o `overflow: hidden` arredonda os quatro
     cantos da malha como um painel só. */
  /* O fio entre placas claras precisa ser ESCURO. `--color-border` é 11% do
     texto claro: sobre superfície escura ele desenha; entre duas placas
     claras ele desaparece. A superfície do site faz o papel do fio aqui. */
  gap: 1px;
  background-color: var(--color-surface);
  /* O contorno externo da parede é o único lugar em que a moldura laranja das
     outras seções encosta nas placas claras das marcas. Fica no container: em
     cada célula ele viraria fio duplo entre colunas. */
  border: 1px solid var(--color-border-accent);
  border-radius: var(--radius-md);
  overflow: hidden;
  margin-bottom: var(--space-10);
}
.logos__item {
  display: grid;
  place-items: center;
  /* A altura acompanha a PROPORÇÃO DA TELA das marcas (300×140), não um
     número escolhido no olho. Célula alta demais deixa a marca boiando num
     vão de 60px, e o olho lê a linha como desalinhada mesmo com tudo no
     centro. Em 141px de largura, 92 de altura é o que fecha o retângulo. */
  min-height: clamp(84px, 9.5vw, 106px);
  padding: var(--space-1);
  /* Off-white e não branco puro: a paleta do projeto é quente, e #FFF ao lado
     de #FBFAF8 lê como duas famílias. O `color-mix` com a superfície tira o
     estouro sem esfriar o tom. */
  background-color: color-mix(in srgb, var(--color-text) 93%, var(--color-surface));
  transition: background-color var(--duration-fast) var(--ease-out);
}
.logos__mark {
  /* ⚠️ LARGURA FIXA, E NÃO `max-width` COM `max-height`.
     As 32 imagens saem do ferramentas/marcas.py na MESMA tela de 300×140, com
     a área de tinta já equalizada dentro dela. Essa normalização só sobrevive
     se a tela inteira for exibida no mesmo tamanho.

     Com `max-width: 86%` mais `max-height: 72%`, cada imagem passava a ser
     reescalada pela própria proporção: a marca larga batia no limite de
     largura, a quadrada no de altura, e as duas voltavam a ter áreas
     diferentes. Era o trabalho do Python sendo desfeito pelo CSS.

     Agora toda imagem tem 86% da largura da célula. Como a tela é a mesma,
     a caixa é a mesma, e o que manda no tamanho aparente é o que foi medido
     na exportação. O `max-height` fica só como trava para célula baixa. */
  width: 92%;
  height: auto;
  max-height: 84%;
  object-fit: contain;
  /* Cor original em opacidade cheia. A contenção que antes vinha da opacidade
     agora vem da saturação: a 88% a parede assenta um degrau abaixo do laranja
     da marca sem que nenhuma cor pareça lavada, e volta ao original no hover. */
  filter: saturate(0.88);
  transition: filter var(--duration-fast) var(--ease-out);
}
/* A célula inteira acende, não só a imagem: o alvo do olho é o retângulo, e
   iluminar só o desenho dentro de uma moldura apagada lê como falha de
   renderização. */
@media (hover: hover) and (pointer: fine) {
  .logos__item:hover { background-color: var(--color-text); }
  .logos__item:hover .logos__mark { filter: saturate(1); }
}

.logos__item:first-child { grid-column: span 2; }
/* Na célula dupla a marca cresce, mas menos que a célula: 58% de duas colunas
   dá cerca de 1,4× o tamanho das outras, não 2×. É destaque, não outdoor. */
.logos__item:first-child .logos__mark { width: 60%; }

@media (min-width: 900px) {
  /* Oito colunas fecham 32 em quatro linhas exatas, então a célula dupla sai
     de cena e todas voltam a ter o mesmo peso. */
  .logos { grid-template-columns: repeat(8, 1fr); }
  .logos__item:first-child { grid-column: span 1; }
  .logos__item:first-child .logos__mark { width: 92%; }
}

/* ============================ 13 · SOBRE ============================ */
.sobre__inner { display: grid; gap: var(--space-10); align-items: center; }
.sobre__text {
  /* --space-4 no topo é o MESMO valor de `.section__lead`, que é o lead de
     todas as outras seções. O `.sobre__text` nunca teve o vão porque nasceu
     com classe própria, e o título encostava no primeiro parágrafo: era
     inconsistência do sistema, não escolha. Agora as duas famílias de lead
     respiram igual. */
  margin-top: var(--space-4);
  font-size: var(--text-lg);
  color: var(--color-text-muted);
  /* Caiu de --space-8 para --space-6 em 31/08: deixou de ser o último
     parágrafo da coluna. Vão de fim de bloco entre dois parágrafos do mesmo
     bloco lê como corte. */
  margin-bottom: var(--space-6);
}

/* Retrato do proprietário, com legenda sobreposta na base. */
.sobre__figure { position: relative; margin: 0; }
.sobre__img {
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 5;

  /* ⚠️ ORÇAMENTO VERTICAL. Retrato 4/5 em largura total fica mais alto que a
     tela sempre que a tela é baixa:

       tela                   retrato  % da altura
       528×467 (seu teste)      607px         130%
       iPhone SE deitado        767px         205%
       iPhone 14 deitado        975px         250%

     O max-height corta em vez de deformar, porque o object-fit abaixo já é
     cover e o object-position é center top: o que sai é o rodapé da foto, o
     rosto do Binho fica. Em celular em pé nada muda, o retrato lá dá 53% a
     64% da tela e o cap nem é atingido. */
  max-height: 70svh;

  object-fit: cover;
  object-position: center top;
  border-radius: var(--radius-lg);
  border: 1px solid var(--color-border);
}
.sobre__caption {
  position: absolute;
  left: var(--space-4);
  bottom: var(--space-4);
  right: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border-accent);
  border-radius: var(--radius-md);
  /* Fundo sólido, não translúcido: o contraste do texto não pode depender
     do que estiver na foto atrás. */
  background: var(--color-surface);
  font-size: var(--text-sm);
}
.sobre__caption strong { font-family: var(--font-display); font-weight: 800; }
.sobre__caption span { color: var(--color-text-muted); font-size: var(--text-xs); }
/* TERCEIRA LINHA DA LEGENDA: A CREDENCIAL.
   Cargo e formação são dois tipos de informação. No mesmo tamanho, na mesma
   cor e sem nada entre eles, o olho lê os dois como uma frase só quebrada em
   duas linhas. O fio custa 1px e resolve. `span + span` casa só o segundo:
   o primeiro filho da legenda é o <strong> do nome. */
.sobre__caption span + span {
  padding-top: var(--space-2);
  border-top: 1px solid var(--color-border);
}

/* ---------------------------------------------------------------------------
   BIOGRAFIA
   Dois parágrafos do mesmo tamanho e da mesma cor viram um bloco cinza único e
   o olho não sabe onde pousar. O primeiro é a promessa e fica no degrau `lg`;
   o segundo é o lastro e desce para o degrau base. A diferença de tamanho é o
   que diz qual dos dois é argumento e qual é prova, sem precisar de rótulo.
   --------------------------------------------------------------------------- */
.sobre__bio {
  color: var(--color-text-muted);
  margin-bottom: var(--space-8);
}

/* ---------------------------------------------------------------------------
   INVENTÁRIO · TRÊS FATOS CONFERÍVEIS
   Rótulo pequeno em cima, número grande embaixo: é a MESMA ordem do `.specs`
   do cartão de ponto. A página passa a ter um jeito só de mostrar ficha
   técnica, em vez de dois componentes com a mesma função e formas diferentes.

   ⚠️ O NUMERAL NÃO É LARANJA, E ISSO É DECISÃO, NÃO ESQUECIMENTO.
   O acento desta página significa "clique aqui" e está preso na allowlist de
   `teste-frontend.py` (_ACENTO_LEGITIMO). Três numerais de enfeite em laranja
   passariam despercebidos na revisão e cobrariam exatamente a força que o
   botão do fim desta seção precisa ter. O número já é o maior tipo do bloco:
   ele não precisa de cor para ser visto primeiro.

   ⚠️ O RÓTULO NÃO PODE QUEBRAR EM DUAS LINHAS, E É POR ISSO QUE OS TRÊS SÃO
   SUBSTANTIVOS DE UMA PALAVRA. Medido em 360px: a coluna dá 91px e "NO SHOPPING
   UNISUL" pedia 93px em caixa alta com --tracking-caps. Ele quebrava, empurrava
   o numeral do meio para baixo, e os três números deixavam de pousar na mesma
   linha, que é a única coisa que uma ficha de números precisa fazer. "Painéis",
   "Totem" e "Cidades" cabem com folga. A linha DE BAIXO pode quebrar à vontade:
   rodapé irregular não desalinha nada. O endereço do totem está na seção
   "pontos", que é onde ele se decide.

   auto-fit e não três colunas fixas: se um quarto fato entrar, ele se recoloca
   sozinho em vez de espremer os outros três. */
.sobre__fatos {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(5.5rem, 1fr));
  gap: var(--space-6) var(--space-5);
  margin: 0 0 var(--space-8);
  padding-top: var(--space-6);
  /* Fio de separação: acima é prosa, abaixo é ficha. O corte é o que autoriza
     o olho a parar de ler e começar a escanear. */
  border-top: 1px solid var(--color-border);
}
.sobre__fatos dt {
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--color-text-muted);
}
.sobre__fatos dd {
  margin: var(--space-2) 0 0;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.fato__num {
  display: block;
  margin-bottom: var(--space-1);
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  font-weight: 800;
  /* line-height 1 e não --leading-tight: numeral sozinho não tem descendente
     nem ascendente para acomodar, e 1.12 abriria um vão fantasma sob o dígito
     que faria o rótulo de baixo parecer desalinhado dos três. */
  line-height: 1;
  letter-spacing: var(--tracking-title);
  color: var(--color-text);
}

/* ============================ 14 · FAQ ============================ */
/* DUAS COLUNAS COM PROVA AO LADO
   A seção era uma coluna estreita com seis acordeões empilhados: seis caixas
   iguais, uma embaixo da outra, sem nada para o olho pousar. É a seção mais
   alta e mais monótona da página, e é justamente onde a pessoa está tirando a
   última objeção antes de falar com a BG.

   A foto ao lado resolve duas coisas de uma vez: dá um ponto de descanso na
   composição e coloca a prova do produto (um painel real, na rua, com peça de
   anunciante no ar) do lado das respostas sobre o produto.

   Ordem no código = ordem de leitura: título, foto, perguntas. No desktop a
   grade recoloca a foto na coluna 1 ocupando as duas linhas, sem que o leitor
   de tela ouça a legenda antes do <h2>. */
.faq__inner { display: grid; gap: var(--space-8); }
.faq__head { margin-bottom: 0; }

.faq__media {
  margin: 0;
  min-width: 0;
}
.faq__img {
  display: block;
  width: 100%; height: auto;
  aspect-ratio: 3 / 2;
  /* Teto de altura pelo ORÇAMENTO VERTICAL, não por gosto. Sem ele, em
     iPhone SE deitado (667×375) a foto ficava com 409px, ou seja 109% da
     tela: a pessoa rolaria uma tela inteira só de imagem antes de chegar na
     primeira pergunta. Com 60svh ela ocupa 225px ali e continua inteira nas
     telas em pé. O `cover` corta em vez de deformar. */
  max-height: 60svh;
  object-fit: cover;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  /* A referência usa cartão branco com sombra macia. Aqui a superfície é
     escura, então quem separa a foto do fundo é a borda de 1px, não a sombra:
     sombra escura sobre fundo escuro é borrão. A elevação vem do token, em
     duas camadas, e só o suficiente para a foto pousar. */
  box-shadow: var(--shadow-md);
}
.faq__caption {
  margin: var(--space-3) 0 0;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  text-wrap: pretty;
}

.faq__list { display: grid; gap: var(--space-3); align-content: start; }
.faq__item {
  border: 1px solid var(--color-border-accent);
  border-radius: var(--radius-md);
  background: var(--color-surface-alt);
  transition: border-color var(--duration-fast) var(--ease-out);
}
.faq__item[open] { border-color: var(--color-primary); }
.faq__q {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-4);
  min-height: 56px;
  padding: var(--space-4) var(--space-5);
  cursor: pointer;
  font-family: var(--font-display);
  font-size: var(--text-base); font-weight: 700;
  list-style: none;
}
.faq__q::-webkit-details-marker { display: none; }
.faq__q::after {
  content: "+";
  flex-shrink: 0;
  font-size: var(--text-xl); font-weight: 400; line-height: 1;
  color: var(--color-primary);
  transition: transform var(--duration-base) var(--ease-out);
}
.faq__item[open] .faq__q::after { transform: rotate(45deg); }
.faq__a { padding: 0 var(--space-5) var(--space-5); }
.faq__a p { margin: 0; color: var(--color-text-muted); }

/* ============================ 15 · DECISÃO E FORM ============================ */
.decisao__inner { display: grid; gap: var(--space-10); align-items: start; }
.decisao__text { font-size: var(--text-lg); color: var(--color-text-muted); }

/* OS TRÊS CONTATOS DIRETOS
   Eram três linhas soltas embaixo do texto, sem nada dizendo que duas delas
   são clicáveis e a do meio não. O trilho de 2px na esquerda resolve isso sem
   ícone e sem palavra: acende laranja onde há link, fica cinza no endereço.
   A regra de cima separa o bloco do argumento, que é outro assunto. */
.contato-direto {
  display: grid; gap: var(--space-5);
  margin-top: var(--space-8);
  padding-top: var(--space-8);
  border-top: 1px solid var(--color-border);
}
.contato-direto li {
  display: flex; flex-direction: column; gap: var(--space-1);
  padding-left: var(--space-4);
  border-left: 2px solid var(--color-border);
  transition: border-color var(--duration-base) var(--ease-out);
}
/* :has() falha inteiro se o navegador não conhece, e aí as três linhas ficam
   com o trilho cinza. Degradar para uniforme é degradar para o estado
   anterior, que já era aceitável: nada quebra, só se perde a distinção. */
.contato-direto li:has(a) {
  border-left-color: color-mix(in srgb, var(--color-primary) 38%, transparent);
}
.contato-direto li:focus-within { border-left-color: var(--color-primary); }
@media (hover: hover) and (pointer: fine) {
  .contato-direto li:hover { border-left-color: var(--color-primary); }
}
.contato-direto__label {
  font-size: var(--text-xs); font-weight: 700;
  letter-spacing: var(--tracking-wide); text-transform: uppercase;
  color: var(--color-text-muted);
}
/* WhatsApp e Instagram são os dois links de conversão desta seção, e tinham
   30px de altura de alvo. Mesma régua do rodapé: 32px de piso, 44 no dedo. */
.contato-direto a {
  display: inline-flex; align-items: center; min-height: 32px;
  font-family: var(--font-display); font-size: var(--text-lg); font-weight: 700;
  color: var(--color-primary); text-decoration: none;
}
@media (pointer: coarse) {
  .contato-direto a { min-height: 44px; }
}
@media (hover: hover) and (pointer: fine) {
  .contato-direto a:hover { text-decoration: underline; }
}

/* O CARTÃO DO FORMULÁRIO
   Antes: fundo chapado, borda de 1px, sombra média. Lê como caixa desenhada.
   Agora ele é um objeto sob a mesma luz da cena que corre atrás dele: a rampa
   curta clareia só o topo, o fio de luz marca a quina de cima, e a sombra
   longa afasta o cartão da página. Três camadas que dizem a mesma coisa, que
   é "isto está na frente", em vez de uma borda pedindo para ser notada.

   A rampa termina em 42%: gradiente de ponta a ponta pinta a caixa inteira de
   uma cor que não é token nenhum, e aí o cartão sai da paleta sem que ninguém
   perceba. Do meio para baixo é --color-surface-alt puro. */
.form-card {
  position: relative;
  padding: var(--space-6);
  border: 1px solid var(--color-border-accent);
  border-radius: var(--radius-lg);
  background: linear-gradient(
    to bottom,
    var(--color-surface-raised) 0%,
    var(--color-surface-alt) 42%
  );
  box-shadow: var(--shadow-lg);
  /* prende o fio de luz dentro do raio. Não corta o anel de foco: o padding
     do cartão é 24px e o anel gasta 6px. */
  overflow: hidden;
}
.form-card::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  top: 0;
  height: 1px;
  background: linear-gradient(
    90deg,
    transparent 0%,
    color-mix(in srgb, var(--color-primary) 52%, transparent) 50%,
    transparent 100%
  );
}

.form-card__title { font-size: var(--text-xl); font-weight: 800; letter-spacing: var(--tracking-sub); }
/* A linha embaixo do lead divide o cartão em cabeçalho e corpo. Sem ela o
   título, a promessa e o primeiro rótulo são três textos empilhados com pesos
   diferentes, e o olho não sabe onde o formulário começa. */
.form-card__lead {
  margin: var(--space-2) 0 var(--space-6);
  padding-bottom: var(--space-6);
  border-bottom: 1px solid var(--color-border);
  color: var(--color-text-muted); font-size: var(--text-sm);
}

/* O RITMO VINHA DE DOIS LUGARES E BRIGAVA CONSIGO MESMO.
   O espaço entre campos era `margin-bottom` no .field, e o espaço entre as
   colunas de uma linha era `gap` no .form__row. Duas fontes para a mesma
   medida: mexer numa desalinhava a outra. Agora o vão é do formulário, uma
   vez só, e o campo não tem opinião sobre o que vem depois dele. */
.form { display: grid; gap: var(--space-5); }
.form .field { margin-bottom: 0; }
/* align-items: end alinha os DOIS CAMPOS PELO FUNDO, e como o controle é o
   último filho do .field, é o controle que fica alinhado. Sem isso, rótulo de
   uma linha ao lado de rótulo de duas ("Como podemos te chamar?" quebra e
   "Qual seu WhatsApp?" não) desce um campo e deixa o outro em cima: a linha
   do formulário fica torta justamente onde o olho procura a régua. */
.form__row { display: grid; gap: var(--space-5); align-items: end; }

.field { display: flex; flex-direction: column; gap: var(--space-2); margin-bottom: var(--space-4); }
/* "Opcional" estava só no placeholder do campo de empresa, e placeholder some
   no primeiro caractere: a informação desaparecia justo para quem começou a
   digitar e ficou em dúvida se precisava terminar. No rótulo ela fica. */
.field__label { font-size: var(--text-sm); font-weight: 600; }
/* Inline e não item de flex. Como flex, o rótulo virava uma caixa que crescia
   e empurrava o selo para a borda direita do campo, a meia tela de distância
   da palavra que ele qualifica. Selo longe do que qualifica não qualifica
   nada. `white-space: nowrap` para "opcional" nunca quebrar sozinho. */
.field__opc {
  margin-inline-start: var(--space-2);
  font-size: var(--text-xs); font-weight: 600;
  letter-spacing: var(--tracking-caps); text-transform: uppercase;
  white-space: nowrap;
  color: var(--color-text-muted);
}
.field__input {
  width: 100%; min-height: 48px;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text);
  /* --text-base é clamp(1rem, ...), então o piso É 16px e o iOS não dá zoom.
     O literal garantia a regra e sonegava a escala. */
  font: inherit; font-size: var(--text-base);
  /* A sombra interna de 1px é o que faz a caixa parecer FUNDA em vez de
     desenhada. Campo escuro sobre cartão escuro, sem ela, é um retângulo de
     borda; com ela o olho lê um poço, e poço pede para ser preenchido. */
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.28);
  transition:
    border-color var(--duration-fast) var(--ease-out),
    background-color var(--duration-fast) var(--ease-out);
}
.field__input::placeholder { color: var(--color-text-muted); }
/* Sem hover o campo só reage depois do clique, e até lá nada na tela confirma
   que ele é um controle. `:not(:focus)` porque o foco já tem cor própria e
   duas regras disputando a mesma borda piscam na transição. */
@media (hover: hover) and (pointer: fine) {
  .field__input:hover:not(:focus) { border-color: var(--color-text-muted); }
}
.field__input:focus { border-color: var(--color-primary); }
.field__input--area {
  min-height: 132px; resize: vertical;
  line-height: var(--leading-body);
}
.field__input[aria-invalid="true"] { border-color: var(--color-error); }
.field__error { margin: 0; font-size: var(--text-sm); color: var(--color-error); }

/* A SETA DO SELECT
   A nativa é desenhada pelo sistema e não obedece a paleta: no print do
   cliente ela aparece clara e fria no meio de um cartão quente e escuro.
   Aqui ela é um quadrado de 8px com duas bordas, girado 45°, na cor do texto
   secundário, e acende junto com a borda quando o campo recebe foco.

   Fica no .field e não no select porque select não aceita pseudo-elemento de
   forma confiável. `pointer-events: none` para o clique atravessar a seta e
   abrir a lista, que é o que a pessoa está tentando fazer. */
.field--select { position: relative; }
.field--select .field__input {
  appearance: none; -webkit-appearance: none;
  padding-right: var(--space-10);
  /* "Ainda não sei, quero uma sugestão" não cabe na coluna estreita e o select
     corta no meio da palavra. Reticências dizem que continua; corte seco
     parece defeito de renderização. */
  text-overflow: ellipsis;
}
.field--select::after {
  content: "";
  position: absolute;
  right: var(--space-4);
  /* o rótulo ocupa o topo do .field e o select é o último filho, então o
     centro do controle está a 24px do fundo da caixa. Menos meia diagonal
     da seta, dá 18px. */
  bottom: 18px;
  width: 8px; height: 8px;
  border-right: 2px solid var(--color-text-muted);
  border-bottom: 2px solid var(--color-text-muted);
  rotate: 45deg;
  pointer-events: none;
  transition: border-color var(--duration-fast) var(--ease-out);
}
.field--select:focus-within::after { border-color: var(--color-primary); }

/* O AVISO ANTES DO BOTÃO
   Vinha como parágrafo solto e lia como letra miúda, que é exatamente o
   contrário do que ele é: a explicação de para onde o clique leva. A chapa
   de 4% o transforma em nota, custa nada e não mexe no contraste do texto,
   que continua em 6,7:1 sobre o cartão. */
.form__note {
  margin: 0;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--color-text) 4%, transparent);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}
.form__status { margin: 0; font-size: var(--text-sm); }

/* Honeypot. Precisa existir no DOM para o bot preencher, e não pode ser visto
   nem alcançado por gente. O left: -9999px clássico empurra a caixa de layout
   para fora do documento; em LTR o navegador ignora isso para rolagem, mas em
   RTL vira estouro de verdade, e em qualquer direção suja a área rolável.
   clip-path recorta sem tirar o elemento do lugar: zero efeito no layout. */
.form__hp {
  position: absolute;
  /* EXCEÇÃO DOCUMENTADA: 1px e -1px são o idioma consagrado de esconder da
     vista sem esconder do leitor de tela. Não são medidas de espaçamento. */
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
/* A nota subiu para ACIMA do botão e passou a dizer onde o clique leva. Por
   isso saiu de --text-xs (texto necessário não pode ser o menor da tela) e
   alinha à esquerda, na mesma borda dos campos. */
.form__note {
  margin: var(--space-5) 0 var(--space-3);
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--color-text-muted);
  text-wrap: pretty;
}
.form__status {
  margin: var(--space-4) 0 0; padding: var(--space-4);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  background: var(--color-primary-soft);
  border: 1px solid var(--color-primary);
}

/* ============================ 16 · BREAKPOINTS ============================
   Mobile-first. Tudo acima desta seção descreve 320px; daqui para baixo o
   layout só ganha espaço, nunca perde. Escala única: 480 / 640 / 900 / 1200 / 1600.
   ========================================================================= */

/* ---- sm · 480px · celular grande ---- */
@media (min-width: 480px) {
  .hero__actions .btn { flex: 0 1 auto; }   /* botões param de ocupar a linha toda */
}

/* Os fatos viram colunas só onde existe largura para isso: no celular grande e
   no tablet, enquanto o herói ainda é de uma coluna, e de 1200px para cima,
   quando a coluna de texto volta a passar de 500px. Entre 900px e 1199px o
   herói já se dividiu em dois e a coluna de texto fica estreita demais para
   três valores lado a lado, então o painel segue em linhas.
   As colunas têm largura de conteúdo, não fração igual: "Com fidelidade" pede
   mais espaço que "+380" e forçar terços iguais quebraria o valor mais longo
   em duas linhas de graça. O subgrid mantém traço, valor e rótulo alinhados
   entre as três colunas mesmo quando um valor ocupa duas linhas. */
@media (min-width: 480px) and (max-width: 899px), (min-width: 1200px) {
  .hero__facts {
    grid-template-columns: repeat(3, auto);
    grid-template-rows: auto 1fr auto;
    justify-content: space-between;
    gap: var(--space-4);
    padding: var(--space-6);
  }
  .hero__fact {
    display: grid;
    grid-row: span 3;
    grid-template-rows: subgrid;
    gap: var(--space-3);
    padding-bottom: 0;
    border-bottom: 0;
  }
  .hero__fact + .hero__fact { padding-left: var(--space-6); }
  .hero__fact + .hero__fact::before {
    content: '';
    position: absolute; left: 0; top: 0.2rem; bottom: 0.2rem;
    width: 1px;
    background: linear-gradient(
      180deg,
      color-mix(in srgb, var(--color-text) 0%, transparent),
      color-mix(in srgb, var(--color-text) 26%, transparent) 48%,
      color-mix(in srgb, var(--color-text) 0%, transparent)
    );
  }
  /* Traço curto de luz no topo de cada coluna. O halo é o mesmo laranja em
     volta, e não uma sombra cinza: a peça toda vende painel de LED aceso. */
  .hero__fact::after {
    display: block;
    grid-row: 1;
    width: 30px; height: 2px; border-radius: var(--radius-full);
    background: linear-gradient(
      90deg,
      var(--color-primary),
      color-mix(in srgb, var(--color-primary) 0%, transparent)
    );
    box-shadow: 0 0 10px color-mix(in srgb, var(--color-primary) 45%, transparent);
  }
  .hero__fact strong {
    grid-row: 2; align-self: end;
    font-size: clamp(1.5rem, 1rem + 1.4vw, 2.25rem);
    line-height: 1.06;
    letter-spacing: var(--tracking-title);
  }
  /* O terceiro valor não é número, é frase. No mesmo corpo dos dois números
     ele viraria o item mais pesado da lista por acidente de comprimento, e
     ainda seria o primeiro a quebrar em duas linhas. Um degrau abaixo devolve
     a hierarquia e libera os números para crescer. */
  .hero__fact:last-child strong {
    font-size: clamp(1.125rem, 0.85rem + 0.9vw, 1.625rem);
  }
  .hero__fact span {
    grid-row: 3;
    font-size: var(--text-sm);
    line-height: 1.4;
  }
}

/* ---- md · 640px · tablet em retrato ---- */
@media (min-width: 640px) {
  .hero__subtitle {
    margin-top: var(--space-5);
    font-size: var(--text-lg);
  }
  .hero__subtitle p + p { margin-top: var(--space-4); }
  .hero__actions { margin-top: var(--space-8); }

  .cards--3, .cards--4 { grid-template-columns: repeat(2, 1fr); }
  /* Os dois prazos são um par: nunca viram três colunas, então a regra entra
     aqui e não volta a mudar em 900px. */
  .cards--2 { grid-template-columns: repeat(2, 1fr); }
  .form__row { grid-template-columns: 1fr 1fr; }

  /* Havia aqui `.pontos__grid > li:last-child:nth-child(odd) { grid-column:
     span 1 }`, defendendo o sétimo cartão de "ficar esticado" em duas
     colunas. Item de grade não estica sozinho: `span 1` já é o padrão, então
     a regra nunca fez nada. Fazia uma coisa só, e ruim: com especificidade
     (0,3,1) ela vencia a regra de centralizar o órfão em três colunas, que
     tem (0,3,0), e o cartão continuava encostado na margem sem que ninguém
     entendesse por quê. Código defensivo contra um problema inexistente que
     acaba impedindo a correção do problema existente. */
}

/* ---- lg · 900px · tablet em paisagem e notebook ---- */
@media (min-width: 900px) {
  .site-nav { display: block; }
  /* A âncora do grupo da direita. É o que troca a distribuição acidental do
     space-between por uma decisão: todo o espaço que sobra vai para um único
     vão, entre a marca e a navegação. */
  .site-nav { margin-inline-start: auto; }
  /* Precisa casar a especificidade de html[data-js="on"] .nav-toggle (0,2,1),
     senão aquela regra vence esta mesmo vindo antes no arquivo, e o
     hambúrguer reaparece no desktop. */
  html[data-js="on"] .nav-toggle { display: none; }
  .header-cta { display: inline-flex; }

  /* A ação encosta na navegação, mas com respiro maior que o de entre links,
     senão o botão vira o sexto item do menu. --space-5 contra os --space-6
     internos da lista dá a proporção certa: perto o suficiente para o olho
     ler como um grupo, longe o suficiente para não virar mais um link. */
  .site-header__actions { gap: var(--space-5); }
  .site-header__rule {
    display: block;
    width: 1px;
    height: 20px;
    background: var(--color-border-strong);
    opacity: 0.55;
  }

  /* A FAIXA APERTADA · 900 a 1199px.
     É onde a navegação inline liga e o container ainda é estreito. Somando
     marca, cinco links, filete e botão, o conteúdo pede ~910px e o container
     em 900px oferece 836. Faltavam 76px, e o resultado era link comprimido
     com o vão do meio zerado: o cabeçalho parecia lotado justamente na
     largura mais comum de notebook pequeno.

     Isto já acontecia antes de 15/08, e com 52px a mais, porque o alternador
     de tema ocupava esse espaço. Ficava escondido pelo overflow-x: clip do
     body, que corta em silêncio em vez de mostrar barra de rolagem.

     Duas devoluções, nesta ordem de prioridade:
       · o vão entre links cai de 24 para 16px  → +32px
       · a assinatura da marca some              → +43px
     Some 75px, e o vão do meio volta a existir. A assinatura é a primeira a
     sair porque é a informação menos necessária da barra: quem está no site
     já sabe que a BG faz LED, e ela continua no rodapé e no herói. */
  @media (max-width: 1199px) {
    .site-nav__list { gap: var(--space-4); }
    .brand__text small { display: none; }
  }

  /* No desktop o menu não é um painel: a lista volta a ser inline no
     cabeçalho. Tudo que constrói a folha mobile é desligado aqui, inclusive
     o véu, o rodapé com CTA, as setas e o escalonamento de entrada. */
  .site-nav.is-open { position: static; }
  .site-nav.is-open::after { content: none; }
  /* O painel não precisa de desfazimento nenhum: a folha inteira está dentro
     de @media (max-width: 899px) e nunca chega aqui. Era daqui que faltava
     desfazer o overflow, e é por isso que a barra de rolagem aparecia. */
  .site-nav__foot { display: none; }
  .site-nav.is-open .site-nav__list { flex-direction: row; gap: var(--space-6); }
  .site-nav.is-open .site-nav__list li + li a { border-top: 0; }
  .site-nav.is-open .site-nav__list a {
    display: inline-block; min-height: 0; padding: 0; animation: none;
    padding-block: var(--space-1);
    font-family: var(--font-body); font-size: var(--text-sm); font-weight: 600;
    color: var(--color-text-muted);
  }
  /* De volta ao sublinhado da nav desktop: posição, tamanho e cor do base. */
  .site-nav.is-open .site-nav__list a::after {
    position: absolute; inset-inline: 0; inset-block-start: auto;
    bottom: calc(var(--sublinhado) * -1);
    width: auto; height: var(--sublinhado);
    border: 0; border-radius: var(--sublinhado);
    background: var(--color-primary);
    transform: scaleX(0);
  }
  /* FAQ em duas colunas. A foto vai para a coluna 1 ocupando as duas linhas;
     título e perguntas empilham na coluna 2. `align-items: start` impede a
     foto de esticar para acompanhar a altura do acordeão: imagem esticada
     para preencher grade é o erro que faz layout parecer automático. */
  .faq__inner {
    grid-template-columns: 0.85fr 1.15fr;
    grid-template-rows: auto 1fr;
    column-gap: var(--space-12);
    row-gap: var(--space-6);
    align-items: start;
  }
  .faq__media { grid-column: 1; grid-row: 1 / span 2; }
  .faq__head,
  .faq__list { grid-column: 2; }

  .site-nav.is-open .site-nav__list a[aria-current="true"]::after { transform: scaleX(1); }
  @media (hover: hover) and (pointer: fine) {
    .site-nav.is-open .site-nav__list a:hover::after { transform: scaleX(1); }
    /* Desfaz o deslocamento do item mobile. Era `padding-inline-start: 0`,
       que deixou de significar coisa alguma quando o hover passou de padding
       para transform: sem esta troca, um `is-open` que sobrasse em tamanho de
       desktop faria o link do cabeçalho escorregar 8px no hover. */
    .site-nav.is-open .site-nav__list a:hover { color: var(--color-text); transform: none; }
  }

  .cards--3 { grid-template-columns: repeat(3, 1fr); }
  /* .cards--4 NÃO abre em 4 colunas aqui: ver o @media de 1150px, no fim
     desta seção, e o motivo escrito lá. Entre 640px e 1149px a grade fica em
     2 colunas, que é o que a regra do @media de 640px já faz. */

  /* ÓRFÃO DA ÚLTIMA LINHA. (regra escrita com `li` para casar a
     especificidade do resto do arquivo, não por acaso)
     Sete pontos em três colunas deixam o sétimo sozinho na coluna 1, com dois
     terços da linha vazios e o cartão encostado na margem esquerda. Centrar
     fecha o buraco sem inventar um cartão "destaque" nem esticar um cartão
     para três colunas, que diria "este é mais importante" sem ser verdade.

     A conta é geral, não um remendo para o número 7: só casa quando o último
     item ABRE linha nova (nth-child(3n+1) e :last-child ao mesmo tempo).
     Com 8 pontos ela desliga sozinha, com 10 ela volta a valer. */
  .pontos__grid > li:last-child:nth-child(3n + 1) { grid-column: 2; }

  .hero__inner   { grid-template-columns: 1.05fr 0.95fr; align-items: center; }
  .sobre__inner  { grid-template-columns: 0.8fr 1.2fr; gap: var(--space-16); }
  .decisao__inner{ grid-template-columns: 1fr 1fr; gap: var(--space-16); }
  .form-card { padding: var(--space-8); }
  /* Toda a rolagem do formulário acontecia com o telefone fora da tela. Quem
     chega no último campo perdeu de vista o argumento e o WhatsApp, que é o
     caminho mais curto e o que a seção inteira defende. Com o bloco preso, os
     dois ficam à mão até o botão.
     min-height 760: preso e cortado é pior que solto. Em janela baixa a coluna
     não cabe inteira e a regra não entra.
     Nota para a linha "nenhum position: sticky vive dentro de <main>" lá em
     cima: agora vive este. `overflow-x: clip` no <section> não cria contexto
     de rolagem, então o sticky continua colando na janela. Verificado em
     1440x900 e 1280x800. */
  @media (min-height: 760px) {
    .decisao__pitch { position: sticky; top: calc(68px + var(--space-8)); }
  }
}

/* ---------------------------------------------------------------------------
   ⚠️ AS 4 COLUNAS COMEÇAM EM 1150px, NÃO EM 900px.
   Medido em 900px com o texto de 31/08: a coluna dá 200px, e descontado o
   padding de 24px de cada lado sobram 152px de texto, que em --text-sm são
   cerca de 20 caracteres por linha. "O / desenvolvimento / da arte está /
   incluso na / contratação." — uma palavra por linha, com rio branco no meio
   do parágrafo. Abaixo de ~45 caracteres o olho gasta mais tempo achando o
   começo da linha seguinte do que lendo.

   Não é o número de colunas que está errado: é que os quatro textos novos têm
   o dobro do comprimento dos antigos, e 4 colunas passaram a exigir mais
   largura de tela do que exigiam. Em 1150px a coluna dá ~265px, ou ~29
   caracteres, que ainda é estreito mas é texto de cartão e se lê.

   Entre 640px e 1149px ficam 2 colunas, que é o que o @media de 640px já faz.
   Se os textos voltarem a encurtar, este número volta para 900.
   --------------------------------------------------------------------------- */
@media (min-width: 1150px) {
  .cards--4 { grid-template-columns: repeat(4, 1fr); }
}

/* ---------------------------------------------------------------------------
   O FIO QUE LIGA OS QUATRO PASSOS
   Quatro cartões lado a lado são quatro cartões. O que dizia "isto é uma
   sequência, nesta ordem" eram só os numerais dentro dos círculos, e numeral
   é informação que precisa ser LIDA: quem escaneia a seção via quatro caixas
   iguais e seguia em frente. O fio passa na altura do centro dos círculos e
   transforma a leitura em uma varredura da esquerda para a direita, que é o
   gesto que a seção quer.

   Decorativo por completo: o <ol> já carrega a ordem para o leitor de tela e
   o `.step__num` é aria-hidden. Nada aqui muda o que é anunciado.

   O fio é --color-border-strong, não o laranja: o acento já está na moldura
   dos cartões e no anel dos numerais, e um terceiro traço laranja no vão
   entre eles empataria a ligação com a informação.

   Só existe em 4 colunas. Em 2 colunas ele apontaria para o fim da linha e
   morreria no ar depois do segundo cartão, e em 1 coluna não há vizinho ao
   lado para ligar. Por isso a regra vive no MESMO @media que cria as 4
   colunas: se um dos dois mudar de ponto de virada, o outro muda junto ou o
   fio aparece onde não há sequência para ligar.
   --------------------------------------------------------------------------- */

@media (min-width: 1150px) {
  .card--step { position: relative; }

  /* :not(:last-child) e não :last-child::before { display:none }: o fio nunca
     chega a existir no quarto cartão, em vez de existir e ser apagado. */
  .card--step:not(:last-child)::before {
    content: "";
    position: absolute;
    /* O bloco de posicionamento de um filho absoluto é a CAIXA DE PADDING do
       pai, então `left: 100%` já cai por dentro da borda de 1px. Os 2px de
       largura extra devolvem esse pixel de cada lado: o fio atravessa o vão
       inteiro e encosta nas duas bordas, sem sobra e sem falha. */
    left: 100%;
    width: calc(var(--space-5) + 2px);
    /* Centro do círculo: o padding do cartão mais metade dos 42px do
       `.step__num`. Escrito como conta e não como resultado, para o fio
       acompanhar se algum dos dois mudar. */
    top: calc(var(--space-6) + 21px);
    height: 1px;
    background: var(--color-border-strong);
  }
}

/* ---- xl · 1200px · desktop ----
   Nesta faixa o container já bateu no teto e o layout está no formato final.
   Só a mídia do hero ganha presença, porque a coluna cresceu. */
@media (min-width: 1200px) {
  .hero__inner { gap: var(--space-16); }
}

/* ---- xxl · 1600px · monitor grande e ultrawide ----
   Sem esta faixa, em 2560px sobravam 680px vazios de cada lado com o
   conteúdo espremido em 1200px. O container cresce, mas os blocos de texto
   mantêm o próprio max-width em rem: linha de leitura não acompanha a tela,
   senão a leitura piora em vez de melhorar. */
@media (min-width: 1600px) {
  :root {
    --container: 1360px;
    --section-y: clamp(4.5rem, 7vw, 8.5rem);
    /* A manchete do herói sobe junto, mas pelo TOKEN e não por uma regra de
       componente: assim ela continua sendo o degrau 3xl da escala em vez de
       virar um tamanho avulso que só existe acima de 1600px. */
    --text-3xl: clamp(3rem, 3.4vw, 4.4rem);
  }
}

@media (min-width: 2000px) {
  :root { --container: 1500px; }
  /* Trava de leitura: mesmo com o container maior, texto corrido não passa
     disso. Acima de ~75 caracteres por linha o olho perde a próxima linha. */
  .section__lead, .stakes__text, .sobre__text, .decisao__text { max-width: 52rem; }
}

/* ============================ 17 · MOVIMENTO REDUZIDO ============================ */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  /* EXCEÇÃO DOCUMENTADA: 0.01ms não é uma duração da escala, é o idioma de
     desligar animação sem usar `animation: none`, que cancelaria o estado
     final de quem anima com `forwards` e deixaria elementos invisíveis. */
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  /* A faixa "Atendemos" tem bloco próprio de reduced-motion na seção 6,
     mais completo que este. Não repetir aqui. */
}

/* ============================ 18 · IMPRESSÃO ============================ */
@media print {
  .site-header, .marquee { display: none; }
  /* EXCEÇÃO DOCUMENTADA: papel é branco. O tema escuro da tela impresso
     gastaria a folha inteira de tinta para entregar contraste pior. */
  body { background: #fff; color: #000; }
}

/* [ATELIER] Ponto de extensão: SOAP (FASE 4) adiciona tokens de movimento e
   classes de scroll reveal. Não sobrescrever os tokens acima, apenas estender.
   [ATELIER] Ponto de extensão: FOOTER (FASE 6) e CONCIERGE (FASE 5) trazem CSS
   próprio e escopado (.dc-bot-*). O z-index do chatbot já está reservado. */

/* ⚠️ AQUI HAVIA UM BORRÃO NAS BARRAS DO HAMBÚRGUER. ELE FAZIA O ÍCONE SUMIR.
   Portei o crossfade em `filter: blur(10px)` do circle menu do 21st.dev, onde
   ele acontece entre DOIS NÓS DE ÍCONE que se substituem. Aqui não há troca de
   nó: são três barras de 2px de altura que giram. Borrar 3px uma barra de 2px
   não desfoca a barra, APAGA a barra.

   O que aparecia na tela: ao tocar no menu, o ícone sumia por 160ms na
   abertura e por 280ms no fechamento, deixando a borda redonda vazia. Quem
   toca não conclui "está animando", conclui "o botão sumiu" e toca de novo,
   o que reabre e refecha o menu.

   Duas coisas saíram junto com o borrão:
     · o `filter`, que além de destrutivo era a única propriedade da página
       animada fora de transform e opacity;
     · o `transition` que eu tinha reescrito aqui. Ele listava `translate` e
       `rotate`, e por especificidade derrubava o `transform` da regra base
       (linha 843), que é a propriedade que o X realmente usa. As barras
       passaram a SALTAR para o X em vez de girar até ele.

   Do porte restou a sacudida do fechamento, logo abaixo: ela é aditiva, roda
   no botão e não encosta no ícone.

   Fechamento: o original sacode o gatilho e pulsa a escala enquanto os itens
   recolhem. Aqui é uma animação só, curta, e some sozinha. */
/* ⚠️ A SACUDIDA DO FECHAMENTO FOI REMOVIDA, E ISSO É CORREÇÃO, NÃO CORTE.
   Era `nav-toggle-fecha`: quatro quadros-chave levando o botão a translate
   ±2px e scale 1.12. Dois problemas, e o segundo é o que decide.

   Primeiro: 2px de translate é literalmente o botão pulando de lugar durante
   a própria animação. Segundo, e pior: quem fecha o menu ACABOU de apertar o
   botão, e o `:active` já respondeu com um afundamento. A sacudida chegava
   logo depois, então um toque produzia dois movimentos seguidos no mesmo
   controle, em direções opostas. O olho lê isso como recuo, não como resposta.

   O que confirma o fechamento é o X virando hambúrguer, que já acontece e é o
   sinal certo: fala do estado do menu, não do botão. Com a sacudida fora,
   saíram junto uma classe, um temporizador de 280ms no main.js, um
   quadro-chave e uma exceção de movimento reduzido. */

/* MOVIMENTO REDUZIDO · só o que a seção 17 não alcança.
   ⚠️ CORREÇÃO DE UMA AFIRMAÇÃO MINHA, ANTES DE ELA VIRAR VERDADE NO ARQUIVO.
   Eu tinha escrito aqui que quem pede menos movimento continuava recebendo o
   leque de -68deg a +68deg, porque o bloco antigo mirava em
   `.site-nav__list li` e a animação mudou para o <a> faz tempo. O seletor
   morto é real, mas a conclusão era falsa: a seção 17 zera `animation-duration`
   e `transition-duration` em `*` com !important, então o leque já não
   acontecia. O bloco antigo era código morto, não uma falha de acessibilidade.

   Sobrou o que duração não resolve, porque é ESTADO e não percurso: as duas
   propriedades abaixo aplicam de uma vez mesmo com a duração zerada, e aí um
   salto instantâneo de 8px é mais agressivo do que o deslize de 150ms que ele
   substitui. E a seção 17 evita `animation: none` de propósito, com motivo
   documentado lá, então nada aqui reintroduz isso.

   O toque continua respondendo: sem o afundamento, o botão troca de fundo. É
   feedback sem deslocamento, que é exatamente o que a preferência pede. */
@media (prefers-reduced-motion: reduce) {
  .site-nav.is-open .site-nav__list a:hover,
  .site-nav.is-open .site-nav__list a:focus-visible { transform: none; }
  .nav-toggle:active {
    scale: 1;
    background: var(--color-primary-soft);
    border-color: var(--color-primary);
  }
}

/* ---------------------------------------------------------------------------
   MENU MOBILE · CABER E ACABAMENTO

   ⚠️ EM TELA BAIXA O MENU NÃO CABIA, E ROLAR NÃO É RESPOSTA PARA SEIS ITENS.
   Cinco linhas de 56px mais o CTA mais o contato dão cerca de 444px. Num
   celular deitado sobram 334px abaixo do cabeçalho. A rolagem interna resolve
   o alcance, mas quem abre um menu de seis itens espera VER os seis.

   A saída é mudar a forma, não encolher a fonte: abaixo de 620px de altura a
   lista vira duas colunas. Cinco itens em duas colunas são três linhas, e o
   painel inteiro passa a caber com folga. A composição continua a mesma, o
   que muda é o arranjo, que é exatamente o que se espera de um layout
   responsivo de verdade.
   --------------------------------------------------------------------------- */
/* ⚠️ AS DUAS COLUNAS PRECISAM DE LARGURA, E A REGRA SÓ PEDIA ALTURA.
   A condição era `(max-width: 899px) and (max-height: 620px)`, escrita para
   celular deitado. Só que 620px de altura também é a de um celular pequeno EM
   PÉ: um 320x568 caía aqui e recebia duas colunas dentro de 288px de área
   útil. Cada coluna pedia 126px mais o vão, o conteúdo media 334px contra 320
   do painel, e "Os pontos" e "Valores" saíam 13px pela direita. Não aparecia
   barra de rolagem porque o painel corta, então o defeito era silencioso: o
   fim das duas palavras simplesmente não estava na tela.

   `min-width: 480px` é o piso de celular deitado. Abaixo disso a coluna única
   é a resposta certa em qualquer altura, e cabe: em 320x568 o painel fecha em
   440px contra 499px de janela disponível. */
@media (max-width: 899px) and (max-height: 620px) and (min-width: 480px) {
  .site-nav.is-open .site-nav__list {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: var(--space-4);
  }
  /* O fio separador some entre colunas e vira fio de linha: com border-top no
     `li + li` a segunda coluna ganharia um traço solto no topo. */
  .site-nav.is-open .site-nav__list li + li a { border-top: 0; }
  .site-nav.is-open .site-nav__list li a {
    border-bottom: 1px solid var(--color-border);
  }
  .site-nav.is-open .site-nav__list a { min-height: 46px; }
  .site-nav__foot { margin-top: var(--space-4); padding-top: var(--space-4); }
  .site-nav__contact { margin-top: var(--space-3); font-size: var(--text-xs); }
}

/* Barra de acento à esquerda da linha. Nasce em escala zero e cresce a partir
   do topo no foco, no hover e no item da seção atual. É o detalhe que faz a
   linha responder ao dedo em vez de só trocar de cor. */
.site-nav.is-open .site-nav__list a {
  position: relative;
  border-radius: var(--radius-sm);
}
.site-nav.is-open .site-nav__list a::before {
  content: "";
  position: absolute;
  inset-block: 18%;
  inset-inline-start: calc(var(--space-2) * -1);
  width: 2px;
  border-radius: var(--radius-full);
  background: var(--color-primary);
  scale: 1 0;
  transform-origin: top;
  transition: scale var(--duration-base) var(--ease-out);
}
.site-nav.is-open .site-nav__list a:focus-visible::before,
.site-nav.is-open .site-nav__list a:active::before,
.site-nav.is-open .site-nav__list a[aria-current="true"]::before { scale: 1 1; }
@media (hover: hover) and (pointer: fine) {
  .site-nav.is-open .site-nav__list a:hover::before { scale: 1 1; }
}

/* ⚠️ NO DEDO NÃO EXISTE HOVER, E ERA SÓ ELE QUE DAVA RESPOSTA À LINHA.
   Quem toca cobre a linha inteira com o dedo, então trocar a cor do texto não
   é feedback: o texto está embaixo da mão. A chapa atrás da linha aparece nas
   bordas, que é a parte que continua visível, e some junto com o menu.
   Entre o toque e o painel fechar passam quase 200ms de animação de saída, e
   sem nada aqui esses 200ms parecem o site ignorando o toque. */
.site-nav.is-open .site-nav__list a:active {
  background: color-mix(in srgb, var(--color-text) 7%, transparent);
}
.site-nav__foot .btn:active { scale: 0.985; }

/* O contato do rodapé do painel deixa de ser uma linha solta e vira bloco,
   com o telefone como o elemento clicável que ele sempre deveria ter sido. */
.site-nav__contact {
  flex-wrap: wrap;
  line-height: 1.5;
}

@media (prefers-reduced-motion: reduce) {
  .site-nav.is-saindo .site-nav__panel,
  .site-nav.is-saindo .site-nav__list a,
  .site-nav.is-saindo .site-nav__foot { animation: none; }
  .site-nav.is-open .site-nav__list a::before { transition: none; }
}
