/* ===========================================================================
   EFEITOS DA LANDING — os controles do movimento e o CSS de quem se move.

   CARREGA DEPOIS de `site.css`, e isso é deliberado: os quatro controles da
   parede nascem em `site.css` e aqui só entram os que são NOVOS ou os que
   mudam por largura. Redeclarar tudo criaria duas fontes de verdade para o
   mesmo número, que é como se perde um ajuste.

   ZERO HEX. Todo valor de cor sai de `tokens.css` por `var()` ou
   `color-mix()`. `deploy-landing.sh` reprova o deploy se aparecer hex aqui.
=========================================================================== */

/* ---------------------------------------------------------------------------
   1. OS CONTROLES DA PAREDE que este arquivo acrescenta.

   `--parede-celula` é a mudança estrutural desta rodada, e ela existe por um
   número: em 390px a célula media 18,85 x 110,11px — proporção 1:5,8. A grade
   era fixa em 24x14 (a resolução do arquivo de relevo) enquanto o herói passa
   de proporção 2,06 no desktop para 0,29 no celular. Grade fixa em caixa que
   muda de proporção 7x = célula deformada, e "parede de blocos" com célula 6x
   mais alta que larga não lê como relevo, lê como PERSIANA. Era a queixa nº1
   do dono no celular.

   Agora a grade sai da proporção da caixa mirando este lado em px, e a célula
   sai quadrada em qualquer largura. Os valores:

     celular  46px  ->  10 x 34 = 340 células de 45,2 x 45,4px
     desktop  62px  ->  27 x 13 = 351 células de 61,9 x 62,3px

   Ou seja: mesma ordem de contagem que as 336 de antes (o orçamento de quadro
   não muda), e a célula deixa de ser fatia.

   POR QUE 46 NO CELULAR e não 30 ou 60: medido a olho contra os dois. Em 30px
   dá 15 colunas numa tela de 390 e volta a ler como textura em vez de bloco.
   Em 60px sobram 8 colunas e o relevo perde as manchas do mapa de calor — a
   grade fica grossa demais para o campo ter forma. 46 é onde as duas coisas
   coexistem.
--------------------------------------------------------------------------- */
.heroi {
  --parede-celula: 46;

  /* A ONDA AUTÔNOMA, e ela só existe onde não há cursor (ver o bloco de
     `hover: none` abaixo). Amplitude como FRAÇÃO de `--parede-volume`, para
     "mais volume" continuar sendo uma linha só. Período em SEGUNDOS porque é
     o que se percebe e o que se pede; converter velocidade na cabeça é onde se
     erra. */
  --parede-onda-amp: 0;
  --parede-onda-seg: 9;
  --parede-deriva-seg: 13;

  /* 0 = sem teto (o navegador manda). O teto só entra no celular. */
  --parede-fps: 0;
}

@media (min-width: 62rem) {
  /* Célula maior no desktop: a tela é 3,7x mais larga, e manter 46px ali
     daria 36 colunas — confete na direção oposta. */
  .heroi { --parede-celula: 62; }
}

/* ---------------------------------------------------------------------------
   SEM PONTEIRO FINO: A PAREDE PASSA A SE MOVER SOZINHA.

   ISTO É O CONSERTO DA QUEIXA Nº1, e o número que a define: medido com
   `logs/proto-efeitos/medir-vida.mjs`, a amplitude de luminância no herói em
   390px era 0,00 em 9 de 9 pontos ao longo de 1,7s. Imagem parada. Sem cursor
   o laço nunca iniciava e sobrava um quadro estático — a versão de
   acessibilidade entregue por acidente à maioria do tráfego.

   `hover: none` e não `max-width`: quem decide é a existência de ponteiro, não
   a largura. Um notebook com janela estreita tem cursor e deve reagir a ele;
   um tablet de 1024px não tem e precisa da onda.

   O FPS FICA EM 30, e é economia sem perda: a onda tem período de 9s e a
   deriva da luz 13s. Nessa velocidade 30 quadros são indistinguíveis de 60 a
   olho, e são metade da rasterização — que é o item caro num canvas de
   390x1330 (518.700 pixels internos por quadro).
--------------------------------------------------------------------------- */
@media (hover: none) {
  .heroi {
    --parede-onda-amp: 0.30;
    --parede-fps: 30;
    /* Raio maior: o herói no celular tem 1330px de altura, e um facho de 260px
       nele é uma bolha isolada num campo escuro em vez de iluminação. */
    --parede-luz-raio: 420;
  }
}

/* MOVIMENTO REDUZIDO: a onda para, a parede fica. Quem pede menos movimento
   perde o movimento, não o conteúdo — o relevo continua desenhado num quadro
   estático, com a luz sobre o painel. */
@media (prefers-reduced-motion: reduce) {
  .heroi { --parede-onda-amp: 0; }
}


/* ---------------------------------------------------------------------------
   2. A BARRA QUE CAI DE "MÉDIA BRUTA" PARA "AJUSTADO".

   A página inteira argumenta que a média engana. A barra "Ajustado" que
   simplesmente cresce até 1,8 AFIRMA o número; a que cresce até 2,1, para, e
   DESCE até 1,8 DEMONSTRA a frase — a pessoa vê o desconto acontecer.

   DUAS DURAÇÕES DIFERENTES, e é a regra da casa: o que entra se apresenta
   (`--dur-reveal`, 320ms), o que sai só sai (`--dur-base`, 160ms). A subida
   até o pico é apresentação; a queda é correção, e correção rápida lê como
   correção — lenta leria como uma segunda animação decorativa.

   `transform` e nada mais. A barra já é `scaleX(var(--frac))` no `site.css`;
   aqui só se troca a duração conforme a fase. `transition: all` é proibido na
   casa e seria o jeito fácil de fazer isto errado. */
.barra__preenche[data-caindo="sobe"] {
  transition: transform var(--dur-reveal) var(--ease-out);
}
.barra__preenche[data-caindo="cai"] {
  transition: transform var(--dur-base) var(--ease-out);
}
/* O valor numérico acompanha, e ele NÃO pode empurrar o layout ao trocar de
   dígito: `tabular-nums` dá a todo dígito a mesma largura, então "2,1" e "1,8"
   ocupam exatamente o mesmo espaço. Sem isto a barra inteira dança na troca. */
.barra__valor {
  font-variant-numeric: tabular-nums;
}

/* Número que conta: mesma razão, e vale para todo `[data-contar]`. Sem largura
   tabular, contar de 0 a 1,8 muda a largura do texto uma dúzia de vezes por
   segundo e empurra o vizinho a cada quadro. */
[data-contar] {
  font-variant-numeric: tabular-nums;
}


/* ---------------------------------------------------------------------------
   3. BORDA QUE ILUMINA — e o substituto no celular, que é o ponto.

   O card ganha um halo que segue o cursor. NO CELULAR não há cursor, e a
   resposta fácil ("então o efeito não existe") é a que deixa a página morta
   para a maioria do tráfego: lá, o card que está no CENTRO DA JANELA fica
   aceso, então rolar acende um card depois do outro. Mesmo efeito, dirigido
   pelo único gesto que todo mundo faz no celular — e com uma função a mais do
   que decorar: guia a leitura numa lista longa.

   POR QUE DOIS PSEUDO-ELEMENTOS e não um.

   `::before` é a BORDA acesa. Ele existe porque não há como fazer um gradiente
   percorrer só a borda de um elemento: a saída é um retângulo com o gradiente
   e uma máscara que só deixa passar a moldura de 1px
   (`padding-box`/`border-box` em `mask-composite: exclude`). É o único jeito
   sem SVG e sem imagem.

   `::after` é o CLARÃO INTERNO, um radial fraco que dá volume à superfície.
   Sem ele a borda acesa lê como contorno de foco de teclado, não como luz.

   `--rx`/`--ry` chegam do JS em px, e o padrão `-100%` deixa a luz FORA do
   card: enquanto o JS não escreveu (ou se ele não carregou), o card é um card
   normal, sem halo pendurado num canto arbitrário.
--------------------------------------------------------------------------- */
[data-realce] {
  position: relative;
  isolation: isolate;
}
[data-realce]::before,
[data-realce]::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  /* A SAÍDA É MAIS CURTA QUE A ENTRADA. Acender é o evento; apagar é só
     desfazer, e apagar devagar deixa um rastro que lê como atraso. */
  transition: opacity var(--dur-base) var(--ease-out);
}
[data-realce][data-aceso]::before,
[data-realce][data-aceso]::after {
  opacity: 1;
  transition-duration: var(--dur-enter);
}

[data-realce]::before {
  z-index: 1;
  padding: 1px;
  /* OCRE E VERDE, NAO AMBAR. A casa reserva o ambar (`--p-lamp`) para UM foco
     por tela, e esse foco e o CTA. Uma borda ambar em 11 cards seria o
     contrario disso: onze focos, ou seja nenhum. */
  background: radial-gradient(
    circle 14rem at var(--rx, -100%) var(--ry, -100%),
    color-mix(in srgb, var(--p-cartola) 58%, transparent) 0%,
    color-mix(in srgb, var(--p-signal) 30%, transparent) 42%,
    transparent 74%
  );
  /* `black` E NAO UM TOKEN, e isto e deliberado: numa mascara, preto e branco
     nao sao cor — sao canal de opacidade. Preto esconde, branco mostra, e o
     matiz e irrelevante. A palavra-chave deixa a intencao explicita e impede
     que a proxima pessoa "corrija" isto para `var(--brand-...)`, o que
     quebraria a mascara sem quebrar o build. */
  -webkit-mask: linear-gradient(black 0 0) content-box, linear-gradient(black 0 0);
  mask: linear-gradient(black 0 0) content-box, linear-gradient(black 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
}

[data-realce]::after {
  z-index: 0;
  background: radial-gradient(
    circle 13rem at var(--rx, -100%) var(--ry, -100%),
    /* 5% e não 12%: este clarão fica ACIMA da superfície do card, ou seja
       entra na conta de contraste do texto que está nele. A parede do herói
       pode ser forte porque vive ABAIXO do véu; este não tem véu nenhum por
       cima. A regra da casa: efeito acima do véu paga a própria conta de
       contraste. */
    color-mix(in srgb, var(--p-cartola) 5%, transparent) 0%,
    color-mix(in srgb, var(--p-cartola) 2%, transparent) 45%,
    transparent 100%
  );
}

/* O conteúdo do card sobe acima dos dois. Sem isto o `::before` (que é
   posicionado) pintaria por cima do texto — pseudo-elemento posicionado pinta
   depois do conteúdo em fluxo. */
[data-realce] > * {
  position: relative;
  z-index: 2;
}

@media (prefers-reduced-motion: reduce) {
  /* Nada acende e nada anima. O card continua inteiro e legível: o halo nunca
     carregou informação nenhuma, ele só apontava onde o dedo/cursor estava. */
  [data-realce]::before,
  [data-realce]::after { display: none; }
}


/* ---------------------------------------------------------------------------
   4. O SÍMBOLO LÍQUIDO — o hospedeiro, e nada mais.

   O desenho é do JS (deformação de bitmap em Canvas 2D). O CSS só garante que
   o canvas tem de onde se posicionar e expõe os dois controles.

   CONTRATO COM O AGENTE DE MARCA: qualquer `<svg>` inline ou `<img>` dentro de
   `[data-liquido]` serve. Combinado pelo contrato e não pelo arquivo, porque o
   símbolo está sendo desenhado em paralelo e combinar por caminho seria
   combinar pelo que ainda não existe.
--------------------------------------------------------------------------- */
[data-liquido] {
  position: relative;
  display: inline-block;
  --liquido-amp: 3;
  --liquido-seg: 6;
}
@media (prefers-reduced-motion: reduce) {
  /* O JS já desenha um quadro parado neste caso; a amplitude a zero é cinto e
     suspensório para o caso de alguém ligar o laço por outro caminho. */
  [data-liquido] { --liquido-amp: 0; }
}


/* ---------------------------------------------------------------------------
   5. VIDRO — a aproximação honesta, e o que ela NÃO é.

   O dono gostou da estética de vidro/líquido (`liquid-glass-js`). A leitura do
   repositório está em `docs/lp-efeitos.md`; o resumo é que o efeito de lá é um
   shader WebGL2 que fotografa a página com `html2canvas` (194,8 KB minificado)
   e reamostra o bitmap. O caminho CSS+SVG que a comunidade convergiu
   (`backdrop-filter: url(#filtroSVG)` com `feDisplacementMap`) só funciona no
   Chrome — Firefox e Safari não aplicam filtro SVG em `backdrop-filter`
   (WebKit bug 245510, aberto). Ou seja: a refração de verdade custa 195 KB ou
   funciona em um navegador só.

   O QUE ENTREGA A ESTÉTICA DENTRO DO ORÇAMENTO é o que o design system já tem
   opinião sobre: FIO INTERNO DE 1px + SOMBRA INTERNA. O fio interno é o
   detalhe que faz a diferença entre "vidro" e "div translúcida" — ele é a
   quina do vidro pegando luz, e é por isso que ele é mais claro no TOPO e some
   embaixo. Os dois são pintura ESTÁTICA: zero trabalho por quadro, nada de
   camada nova no compositor.

   ---------------------------------------------------------------------------
   O DESFOQUE NÃO ENTRA NO PADRÃO, E O MOTIVO É QUE EU NÃO CONSEGUI MEDI-LO.

   Isto é o oposto de uma desculpa, é a regra da casa aplicada contra a minha
   própria vontade. Tentei duas vezes:

     1. Intervalo entre `requestAnimationFrame` rolando 12 painéis desfocados
        (`logs/proto-efeitos/medir-proto.mjs`): custo de 0,10ms de p95. Número
        falso por construção — `backdrop-filter` não roda na thread principal,
        roda no compositor, e o medidor olha a thread principal.
     2. Quadros APRESENTADOS via `Page.startScreencast`
        (`logs/proto-efeitos/medir-apresentados.mjs`), que é o único que vê o
        compositor. Quatro leituras em 1440px: -2,4 / +9,1 / +4,4 / +5,1 fps.
        Média +4,1 fps, espalhamento 11,5. Em 390px: +1,0 / -5,0 / +1,4 /
        -6,2 / -0,5. O piso de ruído deste equipamento é de cerca de ±6 fps,
        ou seja MAIOR que o efeito que eu quero medir.

   Conclusão honesta: não tenho número para o desfoque, e Chromium sem cabeça
   com composição por software não é GPU de celular — qualquer número que eu
   arrancasse dali seria fraco de transferir. Quando não se pode medir, não se
   gasta: o padrão fica sem desfoque, e quem quiser o desfoque pede por opção
   explícita (`data-vidro="desfoque"`), que existe logo abaixo e vem com o
   aviso preso a ela.
--------------------------------------------------------------------------- */
[data-vidro] {
  --vidro-desfoque: 14px;
  position: relative;
  isolation: isolate;
  /* Superfície OPACA no padrão. Translúcido sem desfoque não é estilo, é
     defeito de contraste: o texto do fundo aparece legível atrás do texto da
     frente. Translucidez só entra junto com o desfoque, na variante. */
  background: var(--p-surface);
  border-radius: var(--radius-card);
  /* A SOMBRA INTERNA é a espessura do vidro. Duas: uma clara no topo (a quina
     pegando luz) e uma escura embaixo (a massa do vidro). Uma só lê como
     borda mal desenhada. */
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--p-cartola) 14%, transparent),
    inset 0 -1px 0 color-mix(in srgb, var(--p-sunken) 40%, transparent),
    var(--shadow-card);
}
/* O FIO INTERNO, e ele é um pseudo-elemento e não `border` por um motivo: uma
   `border` é uniforme, e a quina de um vidro real não é — ela acende em cima e
   apaga embaixo. Só um gradiente mascarado faz isso. */
[data-vidro]::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: inherit;
  padding: 1px;
  pointer-events: none;
  background: linear-gradient(
    to bottom,
    color-mix(in srgb, var(--p-cartola) 26%, transparent) 0%,
    color-mix(in srgb, var(--p-border-strong) 40%, transparent) 38%,
    transparent 100%
  );
  -webkit-mask: linear-gradient(black 0 0) content-box, linear-gradient(black 0 0);
  mask: linear-gradient(black 0 0) content-box, linear-gradient(black 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
}
[data-vidro] > * { position: relative; z-index: 2; }

/* A VARIANTE COM DESFOQUE — opção explícita, e sob três condições.

   `data-vidro="desfoque"` e não um padrão, porque o custo dela não foi medido
   (ver o bloco acima). Quem a liga está gastando orçamento que ninguém
   contou, e o atributo deixa isso visível na marcação em vez de escondido no
   CSS.

   1. SÓ ONDE HÁ PONTEIRO. `hover: hover` não é proxy de "desktop": é proxy de
      "máquina com ventilador". Celular fica de fora até haver medição em
      aparelho de verdade.
   2. SÓ COM SUPORTE, e a translucidez vem JUNTO com o desfoque, nunca antes:
      sem o desfoque, translúcido é defeito de contraste.
   3. `@supports` e não teste de JS, porque o navegador é quem sabe. */
@media (hover: hover) and (pointer: fine) {
  @supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    [data-vidro="desfoque"] {
      background: color-mix(in srgb, var(--p-surface) 62%, transparent);
      -webkit-backdrop-filter: blur(var(--vidro-desfoque)) saturate(1.4);
      backdrop-filter: blur(var(--vidro-desfoque)) saturate(1.4);
    }
  }
}


/* ---------------------------------------------------------------------------
   6. GRADIENTE ANIMADO — NÃO ENTROU, e a medição é que decidiu.

   O dono mandou avaliar o ShaderGradient. A biblioteca está fora por peso
   (324 KB comprimidos somando react + react-dom + three + @react-three/fiber
   + o próprio pacote), o que não surpreende. A pergunta que interessava era a
   outra: **eu faço o mesmo efeito à mão dentro do orçamento?**

   Montei os dois caminhos em `logs/proto-efeitos/p2-gradiente.html` e medi por
   quadros APRESENTADOS (`medir-apresentados.mjs`, CPU 4x, 1440px, rolando):

     A — CSS: `@property --giro` girando um `conic-gradient`, com
         `filter: blur(58px)`             ->  16,6 e 19,8 fps
     B — Canvas 2D: gradiente de 3 paradas numa tela fora de tela, e cada
         linha de varredura deslocada por duas senóides                
                                          ->  38,7 e 53,0 fps
         (custo de JS por dentro: 0,5ms p50 / 1,0ms p95)

   O CSS é 2 a 3 vezes MAIS CARO, e o culpado não é `@property` — é o
   `blur(58px)`. E o desfoque não é opcional: sem ele o cone aparece como um
   leque de raios, que é exatamente o marcador de template que esta página
   evita. Ou seja o caminho CSS precisa da parte cara para ficar bom.

   ENTÃO NADA ENTROU AQUI, e não por falta de caminho: porque o herói JÁ TEM um
   efeito de fundo animado (a parede), e um segundo gradiente animado na mesma
   página seria gastar 8ms de orçamento para dizer duas vezes a mesma coisa. O
   protótipo B fica guardado e medido para o dia em que houver uma faixa que
   precise dele — a receita está lá, em ~40 linhas.

   POR QUE ESTE COMENTÁRIO CONTINUA NO ARQUIVO mesmo sem código: para a próxima
   pessoa não gastar a tarde refazendo o teste A. A recusa medida vale tanto
   quanto a escolha.
--------------------------------------------------------------------------- */


/* ---------------------------------------------------------------------------
   7. REVELAÇÃO EM MÁSCARA, AMARRADA AO PROGRESSO DE ROLAGEM. Sem JavaScript.

   POR QUE ELA NÃO É REDUNDANTE COM `.revelar` (que já existe em `site.js`):
   as duas são coisas diferentes, e é a diferença que justifica a segunda.

     `.revelar`   é DISPARADA pela rolagem: o `IntersectionObserver` acende uma
                  vez, e daí a animação corre no tempo dela. Se a pessoa parar
                  de rolar no meio, a animação termina sozinha.
     `[data-rolar]` é AMARRADA à rolagem: o progresso da máscara É a posição do
                  elemento na janela. Parar de rolar para a revelação no meio;
                  rolar de volta a desfaz. O movimento passa a ser resposta ao
                  gesto em vez de resposta a um gatilho.

   É por isso que ela é a certa para uma faixa larga de conteúdo — a pessoa
   sente que está revelando, não assistindo. E é por isso que ela NÃO deve ser
   aplicada onde `.revelar` já está: dois sistemas no mesmo elemento brigam
   pelo mesmo `opacity`.

   MÁSCARA E NÃO `opacity` + `translateY`, que é o que `.revelar` faz: se as
   duas fizerem a mesma coisa, a segunda não vale o código. A máscara varre o
   bloco de baixo para cima, o que combina com o sentido da rolagem e lê como
   revelar em vez de aparecer.

   `mask-image` AQUI É BARATO E NO CANVAS ERA CARO, e a diferença importa
   porque nesta frente já se mediu o caso ruim: máscara sobre canvas fez o p95
   saltar de 8,2 para 31,2ms, porque o compositor tinha de refazer a máscara de
   uma camada que muda a cada quadro. Aqui o conteúdo mascarado é ESTÁTICO
   (texto e imagem que não mudam), e só a POSIÇÃO da máscara anda — que é
   exatamente o que o compositor sabe fazer de graça.

   DEGRADAÇÃO: onde `animation-timeline: view()` não existe (Firefox e Safari,
   set/2026), o `@supports` não se aplica, `mask-image` nunca é escrito e o
   bloco fica inteiro e visível. Ninguém perde conteúdo por falta de recurso —
   o oposto de esconder por JS e depender de JS para mostrar.
   Aferido em Chrome 151: `CSS.supports("animation-timeline: view()") === true`.

   `animation-range` de `cover 6%` a `cover 46%`: a varredura termina bem antes
   de o bloco chegar ao meio da tela. Amarrada ao fim do percurso, ela ainda
   estaria acontecendo quando a pessoa já está lendo, e aí o movimento compete
   com o texto em vez de apresentá-lo.
--------------------------------------------------------------------------- */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    [data-rolar] {
      animation: varrer linear both;
      animation-timeline: view();
      animation-range: cover 6% cover 46%;
    }
    /* `black` e `transparent` são canal de opacidade, não cor — ver a nota na
       máscara do realce. A borda suave de 18% é o que separa "revelação" de
       "cortina descendo": limite duro lê como recorte. */
    @keyframes varrer {
      from {
        -webkit-mask-image: linear-gradient(to top, black 0%, transparent 18%);
        mask-image: linear-gradient(to top, black 0%, transparent 18%);
      }
      to {
        -webkit-mask-image: linear-gradient(to top, black 100%, transparent 118%);
        mask-image: linear-gradient(to top, black 100%, transparent 118%);
      }
    }
  }
}


/* ---------------------------------------------------------------------------
   8. O ERRO ACONTECENDO — o par de números e o fio.

   Especificado em `docs/lp-conteudo.md` 6.1. O CSS aqui faz três coisas, e só
   três: a hierarquia entre o valor errado e o certo, o fio da linha de base, e
   a trilha do celular.

   A HIERARQUIA USA TRÊS CANAIS e não um. Medi `--p-faint` sobre todas as
   superfícies do sistema, nos dois temas: ele passa o piso de 4,5:1 em todas
   (de 4,81 a 6,73), mas o pior caso tem 0,31 de folga — e folga dessa ordem
   desaparece na primeira vez que alguém mexer num degrau de superfície. Então
   a diferença entre "errado" e "certo" NÃO fica só na cor:

     cor      A em `--p-faint`, B em `--p-ink`
     tamanho  A em `--text-figure-sm`, B em `--text-figure`
     direção  o fio, que aponta de A para B

   Se um canal falhar, os outros dois sustentam a leitura — que é a seção
   inteira. Isto é resposta direta ao pedido do agente de conteúdo de medir
   antes de apoiar a distinção em opacidade.
--------------------------------------------------------------------------- */
[data-par] { position: relative; }

.par__medidas {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
}
/* `tabular-nums` nos DOIS e não só no que conta: se A e B tiverem largura de
   dígito diferente, a seta entre eles fica torta e o fio embaixo não alinha
   com nenhum dos dois. */
.par__a,
.par__b {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--track-tnum);
  margin: 0;
}
.par__a {
  font-size: var(--text-figure-sm);
  font-weight: 400;
  color: var(--p-faint);
}
.par__b {
  font-size: var(--text-figure);
  font-weight: 600;
  color: var(--p-ink);
}
/* CARTÃO SEM NÚMERO — `data-par="lugar"`, e ele é conserto de um defeito que o
   print pegou na hora: o cartão 04 compara dois LUGARES do campo ("de fora da
   área" contra "dentro da pequena área"), e com a escala de cifra herdada essas
   palavras saíam em `--text-figure` e quebravam em quatro linhas, empurrando o
   cartão para o dobro da altura dos vizinhos.

   A hierarquia numérica não serve para palavra: onde o número usa TAMANHO para
   dizer "este é o certo", o texto usa PESO e cor. O fio e a seta continuam
   iguais nos quatro cartões — é o que mantém os quatro lendo como a mesma
   ideia. */
[data-par="lugar"] .par__a,
[data-par="lugar"] .par__b {
  font-family: var(--font-body);
  /* `--text-lead` (18px) e nao `--text-body` (16px): o par de PALAVRAS tem de
     ter o mesmo peso visual que o par de NUMEROS dos outros tres cartoes, e
     numero em `--text-figure` (36px) contra palavra em 16px fazia o cartao 04
     ler como nota de pe de pagina ao lado dos vizinhos. 18px e o maior degrau
     em que "bola nas costas" ainda cabe numa linha na coluna de 110px do
     desktop — medido, nao escolhido. */
  font-size: var(--text-lead);
  line-height: var(--leading-dense);
  letter-spacing: normal;
}
[data-par="lugar"] .par__a { color: var(--p-faint); }
[data-par="lugar"] .par__b { font-weight: 700; }

/* A SETA é `aria-hidden` no HTML e decorativa aqui: ela repete em símbolo o
   que o fio diz em geometria. Redundância deliberada — é o terceiro canal. */
.par__seta {
  color: var(--p-signal);
  font-family: var(--font-mono);
  font-size: var(--text-meta);
}

/* O FIO DA LINHA DE BASE. Ele NASCE CHEIO, e isso não é descuido: cheio é o
   estado correto sem JavaScript e em movimento reduzido. O script zera, espera
   dois quadros e manda para cheio — se ele nascesse vazio, uma falha de script
   deixaria um fio invisível e a ligação entre os dois números desapareceria. */
.par__fio {
  display: block;
  height: 1px;
  margin-top: 0.5rem;
  background: linear-gradient(to right, var(--p-faint), var(--p-signal));
  transform: scaleX(1);
  transform-origin: left;
}
[data-par-fio="vazio"] {
  transform: scaleX(0);
  /* Sem transição no zero: este é o estado de partida, e transicionar PARA ele
     desenharia o fio encolhendo antes de crescer. */
  transition: none;
}
[data-par-fio="cheio"] {
  transform: scaleX(1);
  transition: transform 420ms var(--ease-out);
}
@media (prefers-reduced-motion: reduce) {
  .par__fio { transition: none; transform: scaleX(1); }
}

/* A TRILHA DO CELULAR. Rolável pelo dedo, sem autoavanço e sem botão de play —
   é uma lista, não um carrossel. `scroll-snap` faz o cartão parar inteiro na
   vista em vez de meio cortado, que é o que separa "trilha" de "área que
   escorrega".

   86vw e não 100vw de propósito: com o cartão do tamanho exato da janela nada
   indica que existe um próximo. A fatia de 14% do vizinho é o que convida. */
@media (max-width: 48rem) {
  .par-trilha {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 86vw;
    gap: 0.75rem;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    /* `scroll-padding` e não `padding` no primeiro filho: com padding, o snap
       ancora no lugar errado e o primeiro cartão para fora do alinhamento. */
    scroll-padding-inline: var(--gutter-page);
    padding-inline: var(--gutter-page);
    padding-bottom: 0.75rem;
    /* Sem barra visível no desktop-em-janela-estreita, mas rolável por dedo e
       por teclado (o contêiner recebe `tabindex="0"` no HTML). */
    scrollbar-width: thin;
    overscroll-behavior-x: contain;
  }
  .par-trilha > [data-par] { scroll-snap-align: start; }
}


/* ---------------------------------------------------------------------------
   9. O SELO — o controle de três passos, e a régua que É a base.

   Especificado em `docs/lp-conteudo.md` 6.2. Teto de 260ms na troca de estado,
   e cada duração abaixo é a que a especificação pediu.

   A RÉGUA ANIMA `transform: scaleX()` E NUNCA `width`. Não é preferência de
   estilo: `width` recalcula layout a cada quadro e arrasta tudo que está
   embaixo na mesma conta; `transform` roda no compositor. É a mesma regra que
   as barras do painel do herói já seguem.
--------------------------------------------------------------------------- */

/* NO CELULAR OS TRÊS BOTÕES EMPILHAM, e o motivo é medida: os rótulos são
   "a temporada", "só em casa" e "só contra os quatro primeiros" — o terceiro
   sozinho não cabe em 390px ao lado de nada. E alvo de 44px porque abaixo
   disso o dedo erra, que é o piso de toque da casa. */

/* PISO DE ALVO EM TODA LARGURA, e nao so no celular. A versao anterior punha
   `min-height: 44px` dentro do media query de <=48rem, e no desktop os botoes
   ficavam com ~36px — abaixo do piso de 40px da casa. Alvo pequeno nao e so
   questao de dedo: ele tambem e mais dificil de acertar com mouse, e muito
   mais para quem tem tremor. */

/* O ATIVO: fio de sinal e peso, nao preenchimento chapado. Preenchimento
   verde cheio faria o botao competir com a cifra, que e o protagonista do
   cartao — e o verde aqui e o token de POSITIVO, entao um passo "ativo" em
   verde cheio sugeriria que aquele filtro e o bom. Nao e: o terceiro passo
   ativo e justamente o ruim. */
/* E quando o passo ativo E o de risco, o fio acompanha: o controle passa a
   dizer, sozinho, que aquele estado e o problema. */

/* A CIFRA TROCANDO: esmaece e sobe 4px, o texto é trocado no escuro, e volta.
   `-4px` e só na cifra — a 6px e no bloco todo o cartão lê como recarregando,
   e isso já foi ajustado uma vez no alternador de preço. */

/* NO PASSO DE RISCO a régua troca de cor junto com a borda do cartão: a
   largura sozinha (3,8%) é pequena o bastante para passar batida, e é
   justamente ela a conclusão da seção. */

/* A ENTRADA É MAIS LONGA QUE A SAÍDA (160ms contra 120ms), que é a regra da
   casa: o aviso se apresenta, e depois só sai. */

/* ---------------------------------------------------------------------------
   A FRASE SENDO ESCRITA — `data-escrever`.

   DE ONDE VEIO: o dono olhou as frases que fecham cada análise ("Fez 37 gols
   criando chance para 33. Menos exagerado que o Palmeiras, mas na mesma
   direção") e pediu *"dar a impressão de que ele está sendo gerado por IA
   temporal... com aquele brilhinho que a IA gera, para dar o efeito de que é
   justamente a IA que está analisando e gerando cada situação ali"*.

   E É HONESTO, o que aqui não é detalhe: essas frases são geradas pelo modelo
   do produto de verdade — é a `/ofensiva` e a `/defensiva` que escrevem
   "Contra o Remo, em casa, o Bahia tem tudo pra criar o suficiente pra mais de
   um gol" a cada rodada, não um redator. A animação DRAMATIZA um
   comportamento real do produto. Se as frases fossem escritas à mão, este
   efeito seria mentira e não entraria.

   O QUE ELE NÃO FAZ, e cada linha é uma regra da casa:
     · não digita letra por letra. Máquina de escrever de verdade obriga o
       texto a nascer com largura zero, e aí o parágrafo cresce e EMPURRA o
       resto do cartão — layout shift no elemento que fecha o argumento. Aqui
       as PALAVRAS já ocupam o lugar delas desde o primeiro quadro; o que
       anima é opacidade, desfoque e 2px de subida;
     · não roda em loop. Uma vez, quando o bloco entra na tela, e para. Acima
       de cinco segundos de movimento a WCAG 2.2.2 exigiria mecanismo de
       pausa, e a frase mais longa desta página fecha em ~1,3s;
     · não esconde nada sem JavaScript. As palavras só ficam invisíveis
       enquanto o atributo `data-escrevendo` existe, e quem põe o atributo é o
       script. Sem script, ou em movimento reduzido, o texto está inteiro ali.
--------------------------------------------------------------------------- */
[data-escrever] { position: relative; }

/* A PALAVRA. `display: inline-block` para o `transform` valer (transform em
   inline puro é ignorado), e `white-space: pre` no espaço que a segue para o
   espaçamento não colapsar com o inline-block. */
[data-escrever] .pal {
  display: inline-block;
  will-change: opacity, filter, transform;
}
[data-escrevendo] .pal {
  opacity: 0;
  filter: blur(5px);
  transform: translateY(2px);
  animation: escrevendo 340ms var(--ease-out) forwards;
  /* 34ms por palavra: mais lento que isso lê como preguiça, e mais rápido lê
     como fade do bloco inteiro em vez de escrita. */
  animation-delay: calc(var(--i, 0) * 34ms);
}
@keyframes escrevendo {
  to { opacity: 1; filter: blur(0); transform: none; }
}

/* O BRILHO QUE VARRE. É o "brilhinho" pedido, e ele é uma faixa de luz de
   ~35% da largura passando uma vez, por cima do texto, com
   `pointer-events: none`.

   Sem `mix-blend-mode`: no tema claro `screen` deixaria a faixa invisível e no
   escuro ela estouraria. Cor de token com alfa funciona nos dois. */
[data-escrever]::after {
  content: "";
  position: absolute;
  inset: -0.25rem -0.5rem;
  border-radius: var(--radius-control);
  pointer-events: none;
  opacity: 0;
  background: linear-gradient(
    100deg,
    transparent 34%,
    color-mix(in srgb, var(--p-signal) 14%, transparent) 46%,
    color-mix(in srgb, var(--p-ink) 8%, transparent) 50%,
    color-mix(in srgb, var(--p-signal) 14%, transparent) 54%,
    transparent 66%
  );
  background-size: 320% 100%;
  background-position: 100% 0;
}
[data-escrevendo]::after {
  animation: varrer-brilho var(--dur-brilho, 900ms) var(--ease-out) forwards;
}
@keyframes varrer-brilho {
  0%   { opacity: 0;    background-position: 130% 0; }
  18%  { opacity: 1; }
  82%  { opacity: 1; }
  100% { opacity: 0;    background-position: -30% 0; }
}

/* O CURSOR. Ele é o que faz a coisa ler como "está sendo escrito agora" e não
   como "apareceu com fade". Vive num `<i>` que o script cria e remove, então
   não existe no HTML e não sobra se o script falhar no meio.

   E ELE É ABSOLUTO, não inline, por duas razões que apontam para o mesmo
   lugar. A primeira apareceu na captura: um cursor no FIM do parágrafo fica
   parado num espaço vazio enquanto o texto surge três linhas acima — lê como
   "tem um retângulo verde ali", não como "está escrevendo". Ele precisa
   ACOMPANHAR a palavra. A segunda é consequência: um cursor inline que anda
   entre as palavras empurra tudo o que vem depois dele a cada passo, ou seja
   reintroduz o layout shift que este efeito existe para não ter. Absoluto,
   ele anda por cima sem mexer em nada.

   As coordenadas vêm de `offsetLeft`/`offsetTop` da palavra, e é por isso que
   `[data-escrever]` é `position: relative` — o `offsetParent` da palavra tem
   de ser o parágrafo, e não o corpo da página. */
[data-escrever] .cursor {
  position: absolute;
  left: 0;
  top: 0;
  width: 2px;
  height: 1.05em;
  background: var(--p-signal);
  border-radius: 1px;
  pointer-events: none;
  /* Salto CURTO entre palavras: sem transição o cursor teleporta e lê como
     piscada; com mais de 90ms ele fica atrás da escrita. */
  transition: left 70ms linear, top 70ms linear;
  /* SEIS PISCADAS E PARA, e nao `infinite`. O portao de celular reprovou o
     `infinite` citando a WCAG 2.2.2 (movimento acima de 5s precisa de
     mecanismo de pausa) e ele esta certo por um motivo que eu nao tinha
     pensado: na pratica o script remove o cursor em ~1,3s, mas se o script
     morrer no meio o `infinite` pisca para sempre. Seis voltas de 620ms sao
     3,7s — abaixo do teto com folga, e mais que o dobro da frase mais longa
     desta pagina. O pior caso deixou de depender de o JavaScript funcionar. */
  animation: pulsar-cursor 620ms steps(2, end) 6;
}
@keyframes pulsar-cursor {
  50% { opacity: 0.15; }
}

/* MOVIMENTO REDUZIDO: nada disso. E não é "a animação sem transição" — é a
   ausência dela: o script nem embrulha as palavras, então não há estado
   intermediário nenhum para dar errado. Estas regras são a rede, para o caso
   de o atributo escapar. */
@media (prefers-reduced-motion: reduce) {
  [data-escrevendo] .pal {
    opacity: 1;
    filter: none;
    transform: none;
    animation: none;
  }
  [data-escrevendo]::after { animation: none; opacity: 0; }
  [data-escrever] .cursor { display: none; }
}
