/* Grupo Dls/ - página de case
 *
 * Um sistema de blocos, não uma página. Cada `.cs--*` sabe se posicionar
 * sozinho, não conhece os vizinhos, e cai para uma versão estática quando
 * não há GSAP ou quando há preferência por menos movimento. É o mesmo
 * contrato das cenas da home.
 *
 * O QUE MUDA NESTA VERSÃO, E POR QUÊ
 * O diagnóstico foi de sufoco: texto colado em imagem, e squircle
 * comendo o conteúdo das molduras. As duas causas são de sistema, não de
 * bloco isolado, então as duas viraram token:
 *
 *   --r-moldura   16px em tudo o que é moldura de mídia. O squircle
 *                 proporcional (20,8% do lado menor) é desenho de
 *                 objeto, não de janela: numa foto de 760px ele arredonda
 *                 158px de canto e come o que está na borda da imagem.
 *   --ar-legenda  a distância de qualquer legenda à mídia que ela
 *                 descreve. Estava em três valores diferentes pela
 *                 folha, e o menor deles (sp-3) era o aperto.
 */

/* ---------- o tom que atravessa a página ----------
   Registrado com `@property` para o navegador saber que isto é cor, e não
   texto: sem a declaração de sintaxe, a custom property é string e nada
   interpola - nem transição de CSS, nem tween.

   O VALOR INICIAL VIROU #f1f1f1 EM 26/08. Era branco, porque a página
   abria em branco e a rolagem a levava até o papel mais escuro e de volta.
   Agora ela abre - e fica - no mesmo chão das páginas de frente: um papel
   só, do começo ao fim. O porquê está na seção `.cs`, logo abaixo.

   O hexadecimal está escrito à mão, e não como `var(--c-offwhite)`, porque
   `initial-value` precisa ser computacionalmente independente: uma
   referência a token é recusada e a regra inteira cai, em silêncio. É a
   ÚNICA cópia de `#f1f1f1` fora de `tokens.css`, e se aquele token mudar,
   esta linha é a que precisa ser mudada junto.

   A mecânica continua inteira: quem troca o valor é a rolagem, em
   src/scripts/case/cor.js, quando houver tom declarado para trocar. */
@property --case-tint {
  syntax: "<color>";
  inherits: true;
  initial-value: #f1f1f1;
}

/* ---------- o chão da página ----------
   `data-theme="light"` no html já troca `--fg`, `--fg-muted` e `--line`
   por tokens.css. Aqui só entra o que é próprio do case. */
/* Pintado na raiz e no corpo. O fundo do `body` é propagado para a tela
   quando o `html` é transparente, e nessa propagação o valor deixa de ser
   consultável: `getComputedStyle(body).backgroundColor` devolve branco até
   com uma cor literal aplicada inline. Pintar também o `html` não muda um
   pixel do resultado e devolve a cor à medição - o que é a diferença entre
   afirmar que a troca funciona e conseguir provar. */
:root:has(> .case-page),
.case-page {
  background: var(--case-tint);
}

/* O anel de foco amarelo do sistema mede 1.07:1 sobre branco - invisível.
   Em página clara ele vira preto, com um segundo anel branco por fora
   para não sumir quando o alvo estiver sobre imagem. */
.case-page :focus-visible {
  outline: 2px solid var(--c-black);
  outline-offset: 3px;
  box-shadow: 0 0 0 5px var(--c-white);
}

/* ---------- o header sobre chão claro ----------
   Na home o header nasce com degradê preto porque atrás dele há vídeo, e
   sobre imagem em movimento a subtração fica à mercê do quadro. Aqui atrás
   dele há uma cor chapada, então a subtração é determinística desde o
   pixel zero: branco sobre #f1f1f1 devolve #0e0e0e, e o mesmo header fica
   preto na parte clara e volta a branco sozinho quando o bloco preto do
   fechamento passa por baixo. Nada a coreografar. */
.case-page .header {
  background: none;
  mix-blend-mode: difference;
}

/* ---------- os dois tokens da folha ---------- */
:root {
  --r-moldura: 16px;
  --ar-legenda: var(--sp-5);
}

/* ---------- a seção ---------- */
/* UM PAPEL SÓ (26/08). O tom de cada capítulo vinha de `--cs-tint`, no
   HTML, e alternava entre os dois papéis do site - `#ffffff` e `#f1f1f1`.
   Antes disso eram cinco lavagens pastel, cada uma com 6 a 10% de uma cor
   que o sistema não tem, e a redução para dois já tinha sido um corte.

   O corte final é para zero: a página de case passa a ter o mesmo chão
   das páginas de frente, `--c-offwhite`, sem troca nenhuma. A decisão é
   de coerência entre páginas, não de módulo - uma frente e um case são o
   mesmo site, e o leitor que sai de /comunicacao para um case não deveria
   sentir o papel mudar de cor no caminho.

   O QUE ISTO NÃO É: não é a remoção do mecanismo. `--cs-tint` continua
   sendo lido pelo CSS aqui embaixo e por `case/cor.js`, e o fallback é
   `transparent` justamente para este caso - sem tom declarado, a seção
   deixa o chão da página aparecer, que é o que agora se quer. O dia em
   que um case pedir capítulos de cores diferentes, é uma declaração de
   `--cs-tint` na seção, e tudo volta a andar sozinho. O que saiu foram os
   DADOS, não a máquina. */
.cs {
  position: relative;
  /* Eram clamp(96px, 11vh, 192px): 216px de branco entre dois módulos
     numa tela de 982, e 300px antes de uma citação. Alto demais para o
     que é só uma troca de capítulo - o leitor chegava a achar que a
     página tinha acabado e voltava. O respiro continua sendo o maior do
     site, só que agora ele separa em vez de encerrar. */
  padding-block: clamp(var(--sp-8), 8.5vh, var(--sp-10));
  /* Sem JS cada capítulo pinta o próprio tom: bordas duras entre eles, e
     a página inteira legível. Com JS o tom sai daqui e vai para o corpo,
     onde a rolagem consegue interpolá-lo. Ver `.tinta-viva`. */
  background: var(--cs-tint, transparent);
}

.tinta-viva .cs {
  background: transparent;
}

.cs--flush {
  padding-block: 0;
}

/* ---------- de onde vem a cor de detalhe ----------
   Não vem do case. Cada capítulo declarava um acento próprio - magenta,
   teal, roxo, azul, verde - e o resultado era uma página que trocava de
   identidade a cada rolagem e não se parecia com o site em trecho
   nenhum. Cinco cores que não são do Grupo Dls/ nem do cliente: são de
   ninguém.

   A regra passa a ser a mesma que a home já segue. Lá os anéis são
   cinza porque a cor pertence ao squircle; aqui o chrome - filete,
   olho, numeral, barra de progresso, rótulo de prêmio - é preto sobre
   papel, e a cor da página vem do trabalho mostrado: as fotos, o filme,
   as peças. Uma página de case que se pinta sozinha compete com aquilo
   que ela existe para exibir.

   Continua sendo token, e não `var(--fg)` escrito em oito lugares: o dia
   em que um case quiser puxar a cor da frente que o assinou, é esta
   linha, e não uma caçada por `--cs-accent` espalhado pelo HTML. */
.cs {
  --acento: var(--fg);
}

.cs__olho {
  color: var(--acento);
}

/* Filete de abertura de capítulo. Cresce quando a seção entra. */
.cs__regua {
  height: 2px;
  background: var(--acento);
  transform-origin: left;
  margin-bottom: var(--sp-6);
}

.js .cs__regua {
  transform: scaleX(0);
}

.cs__regua.is-tracada {
  transform: scaleX(1);
  transition: transform 900ms var(--ease-out);
}

@media (prefers-reduced-motion: reduce) {
  .js .cs__regua {
    transform: scaleX(1);
  }
}

/* ============================================================
   cs--hero
   Só a ficha: olho, manchete, subtítulo e os campos do projeto.
   Nenhuma imagem.

   A imagem saiu daqui porque amarrava duas decisões que não têm
   por que andar juntas. Um case cuja abertura é um filme, ou uma
   foto vertical, ou nada, ficava obrigado a ter uma folha
   horizontal em sangria logo abaixo do título - e um case sem
   arquivo bom para essa posição abria com um buraco. Quem quer
   abrir com imagem usa `.cs--capa` na sequência; quem não quer,
   não usa. Dois blocos, duas decisões.
   ============================================================ */
.cs--hero {
  padding-block: clamp(var(--sp-9), 16vh, var(--sp-10))
    clamp(var(--sp-8), 8vh, var(--sp-9));
}

.case-hero__topo {
  display: grid;
  gap: var(--sp-5);
}

/* Sem medida imposta aqui. `ch` resolve contra a fonte do PRÓPRIO
   elemento, e uma medida escrita no container (17px) esmagaria um título
   de 80px: 20ch viravam 167px, e "Um passo para o futuro" caía em quatro
   linhas. Solto, ele mede 1312px e cabe numa linha dentro do shell de
   1440. Quem quebra a manchete é a largura da tela, como deve ser. */
.case-hero__titulo {
  font-weight: 300;
  font-size: var(--fs-display-l);
  line-height: 1.02;
  letter-spacing: var(--tr-display);
}

.case-hero__sub {
  font-size: var(--fs-body-l);
  line-height: 1.5;
  max-width: 46ch;
  color: var(--fg-muted);
}

/* Ficha técnica. Grade de quatro, e não uma linha de itens separados por
   ponto: são quatro campos com rótulo, e rótulo em lista é o que permite
   ler só o que interessa. */
.case-hero__ficha {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--sp-6) var(--sp-5);
  margin-top: clamp(var(--sp-8), 8vh, var(--sp-9));
  padding-top: var(--sp-5);
  border-top: 1px solid var(--line);
}

.case-hero__ficha dt {
  font-size: var(--fs-label);
  font-weight: 500;
  line-height: 1;
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--fg-muted);
  margin-bottom: var(--sp-3);
}

.case-hero__ficha dd {
  margin: 0;
  font-size: var(--fs-small);
  line-height: 1.45;
}

/* Serviços como etiquetas, e não como texto separado por ponto: são seis,
   e seis pontos numa linha viram ruído. */
.case-tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

.case-tags span {
  padding: 0.3em 0.75em;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  font-size: var(--fs-label);
  letter-spacing: 0.04em;
  white-space: nowrap;
}

/* ============================================================
   cs--capa
   A imagem que abre, na largura da janela menos uma margem
   pequena - o mesmo respiro da referência. Ela não é margem de
   conteúdo: a do texto (`--margin`) vai de 20 a 64px e serve
   para a leitura ter onde começar. Esta é da ordem de 16px e
   serve para outra coisa - dizer que a imagem tem borda. Sem
   ela, a foto encosta no cromo do navegador e passa a ser fundo
   de janela; com ela, é uma peça pousada na página, e é isso que
   faz o raio de 16px ter sentido no canto.

   `100%`, e não `100vw` com margem negativa. O truque da sangria
   existe para escapar de um container com margem, e aqui não há:
   a figura é filha direta da seção, que já é da largura do
   documento. Com `100vw` o bloco fica mais largo que o documento
   exatamente pela largura da barra de rolagem - medido, 8px de
   rolagem horizontal num viewport de 1280 - porque `vw` conta a
   barra e o documento não.

   CONTRATO DO BLOCO: `.cs__capa` é filha direta de `.cs--capa`.
   Dentro de um `.shell` ela vira uma imagem com margem de texto,
   que é outro bloco.
   ============================================================ */
.cs--capa {
  --margem-capa: clamp(12px, 1.2vw, 20px);
  padding-block: 0;
  padding-inline: var(--margem-capa);
}

.cs__capa {
  position: relative;
  width: 100%;
  overflow: clip;
  border-radius: var(--r-moldura);
}

.cs__capa img,
.cs__capa video {
  display: block;
  width: 100%;
  height: auto;
}

/* O vídeo da capa é uma CAMADA POR CIMA do poster, nunca no lugar dele.
   O porquê está em `partials/modulos.php`, na abertura do módulo; aqui
   ficam as três consequências de desenho.

   `inset: 0` com `height: 100%`, e não o `height: auto` da regra acima:
   quem manda no tamanho da seção é a imagem debaixo. Um vídeo de
   proporção diferente é recortado pelo `cover` em vez de esticar a
   página — é o motivo do aviso de proporção no `help` do campo.

   Nasce em `opacity: 0` e só acende com `.is-ready`, que `case/capa.js`
   põe DEPOIS de `play()` resolver. Sem isso o primeiro quadro apareceria
   antes de o vídeo andar, e o corte de foto para vídeo parado é mais
   visível que a troca inteira.

   `pointer-events: none` porque não há o que clicar: o vídeo não tem
   controles, e uma camada transparente sobre a imagem inteira é o tipo
   de coisa que só se descobre quando alguém tenta arrastar a foto. */
.cs__capa .cs__capa-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.6s ease;
}

.cs__capa .cs__capa-video.is-ready {
  opacity: 1;
}

/* Legenda de capa recua até a margem do texto, e não fica alinhada à
   borda da imagem: a imagem é quase da largura da tela, o texto não. O
   desconto é da margem da capa, para a soma dar exatamente `--margin`. */
.cs__capa-legenda {
  margin-top: var(--ar-legenda);
  padding-inline: calc(var(--margin) - var(--margem-capa));
  font-size: var(--fs-small);
  color: var(--fg-muted);
}

/* A capa quando encontra a si mesma.

   `padding-block: 0` delega o respiro ao vizinho, e isso vale contra
   todo vizinho menos outra capa: zero mais zero são as duas fotos
   encostadas. Não é falta de ar - é o argumento da margem lateral se
   perdendo. Ela existe para a foto ser uma peça pousada na página em
   vez de fundo de janela, e é ela que faz o raio de 16px ter sentido
   no canto. Coladas, os cantos de baixo de uma encontram os de cima da
   outra e o par vira uma faixa só, com um beliscão no meio.

   Por isso o vão é `--margem-capa`, o MESMO da lateral, e não um valor
   do ritmo do `.cs`: com ele a capa fica com moldura par nos quatro
   lados e a sequência se lê como pratos empilhados. Com os 64px de
   troca de capítulo elas virariam três assuntos diferentes, que é o
   contrário do que uma sequência de fotos diz.

   O caso da legenda é outro. Ela pertence à imagem de cima, e 16px
   depois dela já é a imagem de baixo - a legenda passa a flutuar entre
   as duas e deixa de ter dono. Aí o vão sobe para o respiro entre
   peças de mídia irmãs, o mesmo do `.cs--midia--duo`. */
.cs--capa + .cs--capa {
  padding-block-start: var(--margem-capa);
}

.cs--capa:has(.cs__capa-legenda) + .cs--capa {
  padding-block-start: var(--sp-6);
}

/* ============================================================
   cs--capa--estatica
   A mesma capa, sem efeito nenhum. Existe como módulo próprio, e
   não como "a capa quando você esquece o `.cs__zoom`", porque a
   escolha é de conteúdo: um still de campanha, um layout de peça
   gráfica ou um retrato posado não ganham nada assentando de
   1.24 para 1.0 - o zoom conta que há algo entrando em cena, e
   quando não há, ele vira maneirismo.

   Regra prática para quem monta a página: o zoom é da PRIMEIRA
   mídia, uma vez. Da segunda em diante, esta aqui.
   ============================================================ */
.cs--capa--estatica .cs__capa img,
.cs--capa--estatica .cs__capa video {
  /* Sem `will-change` e sem transform: nada aqui é animado, e uma
     camada de composição por imagem parada é memória de vídeo gasta
     para segurar um pixel que não se move. */
  transform: none;
}

/* ============================================================
   cs--numeros
   A prova antes do argumento. Quem chega numa página de case
   decide em segundos se vale ler.
   ============================================================ */
.cs__numeros {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--sp-7) var(--sp-5);
}

/* Sem filete em cima. O numeral já é a peça mais alta e mais clara da
   página; uma linha por cima de cada um transformava quatro números numa
   tabela, e o que a seção quer é que eles leiam como quatro fatos
   soltos. O que separa uma coluna da outra passa a ser só o vão. */
.cs__numero {
  display: grid;
  gap: var(--sp-3);
  align-content: start;
}

/* Light, e não Hairline. O numeral da home é branco sobre preto, onde
   o traço de 100 se sustenta; aqui ele é preto sobre papel, e a mesma
   espessura que lá é elegante, aqui é apagada - a 74px o traço fica com
   menos de 2px e o número lê como marca d'água ao lado da legenda cinza.
   300 é o degrau seguinte que a família tem (não há 200) e é o mesmo
   peso que `.cs__kpi-valor` já usa nesta folha, também sobre claro. */
.cs__numero b {
  font-weight: 300;
  font-size: var(--fs-numeral);
  line-height: var(--lh-numeral);
  letter-spacing: -0.04em;
  font-variant-numeric: tabular-nums;
  color: var(--acento);
}

.cs__numero span {
  font-size: var(--fs-small);
  line-height: 1.45;
  color: var(--fg-muted);
  max-width: 32ch;
}

/* ============================================================
   cs--texto
   Manchete e parágrafo empilhados, numa medida só. Nada de
   manchete à esquerda com parágrafo solto no canto direito: são
   duas peças do mesmo pensamento e leem-se em sequência.
   ============================================================ */
/* ---------- onde a medida mora ----------
   REGRA DA PÁGINA, e vale para todo `ch` daqui para baixo: a unidade
   resolve contra a fonte do PRÓPRIO elemento. Uma medida em `ch` escrita
   num container de 17px não mede o título de 80px que ele guarda - mede
   17px, e esmaga o título.

   Aconteceu três vezes nesta folha antes de virar regra: 20ch no bloco do
   hero deram 167px e quebraram a manchete em quatro linhas; 22ch na
   figura da citação deram 230px e quebraram a frase em nove.

   Então: `ch` só no elemento que carrega a fonte. Container que precisa
   de teto usa pixel. */
.cs__texto {
  display: grid;
  gap: var(--sp-5);
}

.cs__texto p + p {
  margin-top: var(--sp-4);
}

/* ---------- a medida do texto corrido ----------
   55ch, e o número foi aferido, não escolhido. O parâmetro é o módulo
   sticky: neste valor a primeira linha do parágrafo quebra depois de
   "continuar". A banda que satisfaz isso vai de 54ch a 57ch (665px a
   702px em 1440); 55ch senta dentro dela com 13px de folga para baixo e
   24px para cima, que é o que absorve a variação do `clamp` da fonte
   entre uma largura de tela e outra.

   Eram 62ch. Em 1440 esse valor nem chegava a valer no sticky - a
   coluna de 7fr cortava antes, em 727px - e a linha carregava uma
   palavra a mais do que devia. */
.cs__texto p {
  font-size: var(--fs-body-l);
  line-height: 1.6;
  max-width: 55ch;
}

.cs--texto--duas-colunas .cs__texto {
  max-width: none;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: var(--sp-8);
  align-items: start;
}

.cs--texto--duas-colunas .cs__texto > :first-child {
  position: sticky;
  top: 120px;
}

/* ---------- variante: rótulo à esquerda, texto à direita ----------
   Não é a variante de cima com outro nome. Lá quem fica à esquerda é a
   manchete, e o desequilíbrio é justamente o defeito: título grande de
   um lado, parágrafo pequeno do outro. Aqui quem fica à esquerda é um
   rótulo - o mesmo olho em caixa alta que o resto do site usa -, e a
   hierarquia se inverte: o peso da página é o texto, e o rótulo só
   nomeia o capítulo e sai da frente.

   As colunas são iguais porque o rótulo não precisa de largura, precisa
   de posição: ele mora no início da folha e o texto começa na metade. O
   vazio entre os dois é o que a composição tem a oferecer - uma coluna
   estreita à esquerda encostaria o texto no rótulo e o desfaria.

   Nada gruda ao rolar. Rótulo de duas palavras não corre risco de sumir
   antes do texto acabar, e sticky aqui só faria uma palavra perseguir a
   leitura. Quem precisa de manchete que acompanha usa
   `.cs--texto--duas-colunas`.

   O rótulo é `h2`, e não `span`: ele É o título da seção. Sair do
   sumário da página por ser pequeno seria trocar estrutura por
   aparência. */
.cs--texto--rotulo .cs__texto {
  max-width: none;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--sp-7);
  align-items: start;
}

/* Teto próprio, porque a medida da folha não alcança aqui: a coluna da
   direita é metade da página (696px em 1512) e corta antes dos 55ch. Sem
   este valor, o rótulo seria o único módulo de texto que não encolheu.

   Eram 48ch (591px). Duas coisas ao mesmo tempo: a linha carregava mais
   palavra do que a leitura confortável pede, e - por ser quase toda a
   coluna - encostava o texto na borda direita da folha, sem respiro
   nenhum contra a margem. 44ch resolve as duas com o mesmo movimento:
   542px de medida (a mesma que `.fr-conexao__textos` usa nas frentes,
   para o site ter UMA largura de leitura) e 154px de vão à direita, que
   antes eram 105px. */
.cs--texto--rotulo .cs__texto p {
  max-width: 44ch;
}

/* O rótulo senta na linha de base da primeira linha do texto, e não no
   topo da caixa dela. Alinhar caixas aqui não serve: a caixa do corpo
   começa meia entrelinha antes da letra, e o rótulo, com entrelinha 1,
   começa colado nela - encostados pelo topo, ficavam 7px fora de
   registro, mais da metade da altura do próprio rótulo.

   A conta é a distância do topo de cada caixa até a base da primeira
   linha: no corpo, meia entrelinha mais o ascendente; no rótulo, só o
   ascendente. Nesta fonte, 1.05 do corpo menos 0.75 do rótulo. Os dois
   são tokens, então a conta acompanha o clamp do corpo sem ser refeita.

   Se a família de fonte do site mudar, esta é a linha para reconferir. */
.cs--texto--rotulo .cs__texto > :first-child {
  margin-block-start: calc(1.05 * var(--fs-body-l) - 0.75 * var(--fs-label));
}

/* ============================================================
   cs--flutuante
   Texto correndo com imagens flutuando em profundidades
   diferentes. O deslocamento é de milímetros: profundidade, não
   passeio.
   ============================================================ */
/* Duas boias, e não três. Com três, a terceira era sempre a que sobrava:
   pequena (14%), encostada no topo direito, e a primeira a ser escondida
   no celular - um elemento que só existe onde há espaço não é parte da
   composição, é enfeite. Com duas, cada uma tem lado, tamanho e
   profundidade próprios, e as duas cabem em qualquer largura.

   O texto passa a ser alinhado à esquerda. Centralizado, ele disputava
   eixo com as imagens dos dois lados e não tinha margem de onde começar;
   à esquerda, ele tem uma régua, e as boias passam a orbitar um texto que
   está firme em vez de flutuarem em volta de outra coisa que também
   flutua. */
.cs--flutuante {
  overflow: clip;
}

.cs__flutuante {
  position: relative;
  display: grid;
  justify-items: start;
}

/* Pixel, e não `ch`: este teto é do container, e o container tem 17px de
   fonte enquanto a manchete dentro dele tem 48. Ver a regra da medida,
   acima. */
/* ---------- as três pistas ----------
   O bloco é dividido em três faixas horizontais que não se tocam, e cada
   peça mora numa delas:

     texto    0 → 50%
     boia A  54 → 76%
     boia B  81 → 100%

   Assim escrito, "não sobrepõe" deixa de ser uma coincidência de altura
   e vira geometria: as faixas são disjuntas na horizontal, e o parallax
   só mexe na vertical. Não existe posição de rolagem, altura de texto ou
   largura de tela em que duas peças se encontrem.

   Era isso que faltava. Antes, A ocupava 67,6→92% e B 80→100%: as duas
   se cruzavam em 12 pontos de largura, e bastava o bloco ser mais baixo
   que a soma das alturas para elas se empilharem - medido, elas se
   cobriam entre 146px e 230px de altura.

   O texto perdeu 12 pontos (62% → 50%) para as pistas caberem. É a troca
   que o pedido autoriza, e é a certa: texto mais estreito continua
   legível, imagem por cima de imagem não tem conserto de leitura.

   `min()` com teto em pixel porque num shell de 1440 a linha passaria de
   75 caracteres, e aí quem limita deixa de ser a composição e passa a
   ser a leitura. */
.cs__flutuante .cs__texto {
  /* Encolhido junto com os outros dois: 760px e 50% eram a medida
     antiga; 710px e 47% são os mesmos 6,7% a menos. O teto em pixel
     manda nas telas largas, a porcentagem nas estreitas. */
  max-width: min(710px, 47%);
  text-align: left;
  justify-items: start;
  margin-inline-start: 0;
}

.cs__flutuante .cs__texto p {
  max-width: none;
}

.cs__boia {
  position: absolute;
  width: 22%;
  max-width: 300px;
  will-change: transform;
}

.cs__boia img {
  width: 100%;
  height: auto;
  border-radius: var(--r-moldura);
}

/* A de cima na pista do meio, a de baixo na pista da borda: as duas do
   mesmo lado do texto, em alturas e tamanhos diferentes. Uma de cada lado
   emparedaria o parágrafo.

   `right` é o que define a pista, e é por isso que ele não pode ser
   mexido sozinho: 24% + 22% de largura fecha A em 54→76%, e 0 + 19%
   fecha B em 81→100%. Os cinco pontos entre uma e outra são a folga que
   garante que nem o arredondamento de porcentagem as encosta. */
.cs__boia--a {
  right: 24%;
  top: -2%;
}

.cs__boia--b {
  right: 0;
  bottom: -6%;
  width: 19%;
}

/* ============================================================
   cs--citacao
   Uma frase, sozinha. Sem imagem ao lado e sem parágrafo abaixo.
   ============================================================ */
/* 56px fixos em cima e embaixo, medida dada pela CEO.

   Duas coisas mudam de natureza aqui, e as duas são de propósito.

   Sai o `clamp`, então a citação para de crescer com a altura da
   tela: era `clamp(96px, 16vh, 128px)` e batia no teto de 128px em
   qualquer tela a partir de 800px de altura. É o valor que ela
   apontou.

   E a citação passa a respirar MENOS que uma seção comum, não mais:
   a base `.cs` é `clamp(64px, 8.5vh, 128px)`, que numa tela de 800px
   dá 68px. A citação era o módulo mais arejado da página e agora é o
   mais apertado. Inversão deliberada, registrada aqui para ninguém
   "consertar" depois achando que foi engano.

   56px não está na escala (--sp-7 é 48, --sp-8 é 64). Fica literal:
   inventar um token para um valor de um lugar só seria pior. */
.cs--citacao {
  padding-block: 56px;
}

.cs__citacao {
  margin: 0;
}

/* A medida está aqui, no elemento de 80px, e não na figura de 17px.

   O `text-indent: -0.42em` saiu. Ele existia para puxar a aspa de
   abertura para fora da margem - refinamento tipográfico correto em
   princípio, errado como foi feito: o recuo é aplicado à primeira linha
   do parágrafo INTEIRO, tenha ele aspa ou não, e o valor é fixo enquanto
   a largura da aspa muda com o corpo da fonte. Resultado: a frase nascia
   34px à esquerda da legenda logo abaixo, e de todo o resto da página. O
   desalinhamento que se via era esse.

   O jeito certo de pendurar a aspa é `hanging-punctuation: first`, que o
   navegador calcula pela glifo real. Safari entende; Chrome e Firefox
   ignoram, e ignorar significa cair no alinhamento normal - que é
   exatamente o que se quer como padrão. Nenhum navegador fica pior que
   antes, e um fica melhor. */
.cs__citacao p {
  font-weight: 300;
  font-size: var(--fs-display-l);
  line-height: 1.06;
  letter-spacing: var(--tr-display);
  hanging-punctuation: first;
  max-width: 18ch;
}

/* ---------- variante: a frase longa ----------
   O módulo é feito para uma sentença curta - 18ch em corpo de display,
   que é a medida do mote. Uma frase quatro vezes mais longa nessa medida
   vira uma parede de sete linhas de 80px, e o que era destaque passa a
   ser um bloco de texto grande.

   A variante troca o corpo por display-m e abre a medida para 26ch:
   continua sendo a peça grande do capítulo, e resolve em três linhas.
   Ela não dispensa o critério - frase que precisa de mais que isto não é
   citação, é parágrafo, e o lugar dela é num módulo de texto. */
.cs--citacao--longa .cs__citacao p {
  font-size: var(--fs-display-m);
  line-height: 1.12;
  max-width: 26ch;
}

.cs__citacao figcaption {
  margin-top: var(--sp-6);
  font-size: var(--fs-small);
  color: var(--fg-muted);
}

/* ============================================================
   cs--depoimento
   A fala longa, em dois corpos.
   ------------------------------------------------------------
   A CITAÇÃO E O DEPOIMENTO NÃO SÃO O MESMO MÓDULO COM TAMANHOS
   DIFERENTES. A citação é uma linha que carrega o case e por isso vive
   em corpo de display: 18ch, ou 26ch na variante longa. O depoimento é
   alguém falando por meio minuto, e meio minuto de fala em corpo de
   display é uma parede.

   A saída não é encolher a tipografia, é partir a fala: a abertura fica
   em display, porque é ela que para o olho, e o resto cai no corpo de
   leitura do site. Medido a 1440px com o depoimento do case da
   Datainfo, que tem 566 caracteres: 968px como citação longa, 635px
   como depoimento. Um terço a menos, sem perder uma palavra, e o que
   era destaque continua sendo destaque — agora só na parte que merece.
   ============================================================ */
.cs--depoimento .cs__depoimento {
  margin: 0;
}

/* A MEDIDA É UMA SÓ, E MORA NO `<blockquote>`, não nos dois parágrafos.

   A régua da direita é o que faz os dois corpos lerem como uma fala e
   não como dois blocos empilhados, e ela só é a mesma se for a MESMA
   conta. `ch` mede o "0" da fonte do PRÓPRIO elemento: `24ch` em
   display-m e `55ch` em body-l dariam 696px e 678px — 18px de degrau,
   medido, e o degrau mudaria de tamanho a cada largura de tela, porque
   os dois `clamp` de fonte não sobem juntos.

   Pondo `font-size: var(--fs-body-l)` no `<blockquote>` e a medida
   nele, os `ch` resolvem uma vez só. A abertura em display herda a
   caixa, não a conta. As duas colunas passam a ser idênticas em
   qualquer viewport, por construção e não por coincidência.

   55ch não é medida nova: é a do texto corrido do case, aferida em
   `.cs__texto p`. Um depoimento é texto para ler — a mesma régua. */
.cs__depoimento-fala {
  margin: 0;
  font-size: var(--fs-body-l);
  max-width: 55ch;
}

.cs__depoimento-abertura {
  font-weight: 300;
  font-size: var(--fs-display-m);
  line-height: 1.12;
  letter-spacing: var(--tr-display);
  hanging-punctuation: first;
}

.cs__depoimento-corpo {
  margin-top: var(--sp-5);
  font-size: var(--fs-body-l);
  line-height: var(--lh-body);
}

.cs__depoimento-corpo + .cs__depoimento-corpo {
  margin-top: var(--sp-4);
}

/* O retrato é `alt=""` na marcação, e não por descuido: o nome de quem
   falou está no texto ao lado, dentro da mesma legenda. Descrever a foto
   ali seria anunciar a mesma pessoa duas vezes seguidas — a foto é
   decorativa PORQUE a informação que ela carrega já está escrita. */
.cs__depoimento-autor {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  margin-top: var(--sp-6);
}

.cs__depoimento-retrato {
  flex: 0 0 auto;
  width: 64px;
  height: 64px;
  border-radius: 50%;
  object-fit: cover;
}

.cs__depoimento-quem {
  display: grid;
}

.cs__depoimento-nome {
  font-size: var(--fs-body);
  font-weight: 500;
}

.cs__depoimento-cargo {
  font-size: var(--fs-small);
  color: var(--fg-muted);
}

/* ============================================================
   cs--galeria
   Faixa lateral com rolagem nativa. Sem pin e sem scroll lock: é
   um bloco no meio de um documento, e prender a página inteira
   para mostrar seis imagens pequenas cobraria caro por pouco.
   O Lenis só sequestra o eixo vertical, então não há briga.
   ============================================================ */
/* Cartões grandes, no mínimo cinco, e uma barra de progresso de um pixel
   embaixo. Os 246px de antes eram miniatura: uma faixa de seis
   miniaturas não é um capítulo, é uma tira de contato. Em 72vw o cartão
   passa a ser a peça, e é por isso que o mínimo de cinco importa - com
   três, a faixa acaba antes de virar faixa e o gesto não se justifica.

   Com GSAP a faixa avança na horizontal conduzida pela rolagem vertical
   (ver lab/case.js). Sem GSAP ela continua sendo o que sempre foi: uma
   caixa de rolagem nativa, operável por dedo, roda e teclado. Nenhuma
   das duas depende da outra. */
.cs__rail {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: min(72vw, 860px);
  gap: clamp(var(--sp-5), 3vw, var(--sp-7));
  overflow-x: auto;
  scroll-snap-type: x proximity;
  padding-block: var(--sp-2) var(--sp-6);
  padding-inline: var(--margin);
  margin-inline: calc(var(--margin) * -1);
  scrollbar-width: thin;
  scrollbar-color: var(--acento) transparent;
}

/* O respiro entre a chamada e os cartões.

   O `padding-block` de 8px no topo da faixa é folga para o anel de foco,
   não espaçamento de composição - e era só ele que separava o fim do
   parágrafo do topo da primeira foto. Oito pixels, num bloco onde a foto
   e a legenda dela têm 24 e a legenda e a barra têm 56: o texto ficava
   grudado na imagem que ele anuncia.

   A regra é do segundo `.shell` da seção, e não da faixa, porque é isso
   que ela diz: quando houver um bloco antes, respire dele. Galeria que
   começa direto nos cartões não ganha margem nenhuma. */
.cs--galeria .shell + .shell,
.cs--galeria--grande .shell + .shell {
  margin-top: clamp(var(--sp-7), 5vh, var(--sp-8));
}

/* Sob condução da rolagem a caixa não rola mais sozinha: quem move é o
   tween, e uma barra de rolagem nativa por baixo disso são dois
   controles para o mesmo eixo. */
.cs__rail.is-conduzida {
  overflow-x: hidden;
  scroll-snap-type: none;
  scrollbar-width: none;
}

.cs--galeria--grande .cs__rail {
  grid-auto-columns: min(86vw, 1040px);
}

.cs__rail figure {
  margin: 0;
  scroll-snap-align: start;
}

.cs__rail img {
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  border-radius: var(--r-moldura);
}

.cs__rail figcaption {
  margin-top: var(--ar-legenda);
  font-size: var(--fs-small);
  color: var(--fg-muted);
  max-width: 46ch;
}

/* A barra de progresso. Um pixel, largura do shell, e o preenchimento
   com o acento do capítulo. Ela é a única indicação de que ainda há
   cartão adiante quando a barra de rolagem nativa não está visível. */
.cs__rail-progresso {
  position: relative;
  height: 1px;
  background: var(--line);
  margin-top: var(--sp-5);
}

.cs__rail-progresso i {
  position: absolute;
  inset: 0 auto 0 0;
  display: block;
  width: 0;
  background: var(--acento);
}

/* Rolagem por teclado precisa de alvo focável, e alvo focável precisa se
   anunciar. Sem isso a faixa é conteúdo que só existe para o mouse. */
.cs__rail:focus-visible {
  outline-offset: 6px;
}

/* ============================================================
   cs--zoom
   A primeira mídia depois da ficha. Nasce ampliada dentro de uma
   moldura que a recorta e assenta em 1.0 conforme sobe a tela: o
   quadro fica parado e a imagem acomoda dentro dele.

   Serve para foto e para vídeo sem mudar nada - quem é ampliado é
   o filho da moldura, seja `img`, `video` ou `picture`.

   Sem JS ou com movimento reduzido a mídia fica em 1.0 e o bloco
   é uma figura comum. É por isso que a escala inicial mora no JS
   e não no CSS: declarada aqui, ela ficaria travada em 1.24 se o
   script não rodasse, e a página abriria com a imagem cortada.
   ============================================================ */
.cs__zoom {
  position: relative;
  overflow: clip;
  border-radius: var(--r-moldura);
}

.cs__zoom > img,
.cs__zoom > video,
.cs__zoom > picture {
  display: block;
  width: 100%;
  height: auto;
  will-change: transform;
}

.cs__zoom-legenda {
  margin-top: var(--ar-legenda);
  font-size: var(--fs-small);
  color: var(--fg-muted);
  max-width: 52ch;
}

/* ============================================================
   cs--grid-fotos
   O arranjo do site atual: fotos em 4:3, duas ou três colunas, e
   um vão largo entre elas. É a única grade da folha em que o vão
   é maior que o respiro padrão de seção, e de propósito - o que
   segura uma grade de fotos como conjunto é o branco entre elas,
   não a borda de cada uma.

   Dois botões: `--colunas` (2 ou 3) e `--proporcao`. Abaixo de 900px
   vira 2 colunas, e abaixo de 560 vira 1, em qualquer caso — a
   proporção não muda com a largura, porque o que ela decide é o
   ENQUADRAMENTO da foto, e enquadramento não é assunto de viewport.
   ============================================================ */
.cs__grid-fotos {
  display: grid;
  grid-template-columns: repeat(var(--colunas, 3), minmax(0, 1fr));
  gap: clamp(var(--sp-6), 4vw, var(--sp-8));
}

.cs__grid-fotos figure {
  margin: 0;
}

/* `height: auto` NÃO É REDUNDANTE, e a ausência dela era um defeito
   silencioso (26/08). Sem esta linha o `aspect-ratio` acima nunca valeu:
   os atributos `width`/`height` do `<img>` entram como DICA DE
   APRESENTAÇÃO, e a dica de `height` vence justamente porque a regra de
   autor só declarava `width`. Com as duas dimensões definidas, o
   navegador não tem o que calcular e a proporção é ignorada.

   Medido nesta página, numa coluna de 353px: a foto saía com 562px de
   altura - o valor do atributo -, e não com os 265px que 4:3 pede. A
   grade inteira ficava 396px mais alta do que o módulo promete, e
   mudava de altura conforme o tamanho do arquivo que alguém subisse.

   O `object-fit: cover` continua fazendo o que sempre fez: recortar a
   foto para dentro da caixa. O que muda é que agora a caixa tem a
   proporção que o módulo pediu, de verdade.

   A PROPORÇÃO É DO MÓDULO, e não de cada foto. O que segura uma grade
   como conjunto é a régua embaixo: seis fotos na mesma altura têm seis
   legendas na mesma linha, e é isso que se lê como mostruário. Por isso
   `--proporcao` mora no contêiner e não no `<figure>` — uma proporção
   por foto daria alturas discordantes dentro da mesma fileira, que é
   outro módulo. Ele existe, sob o nome `livre`, para quem quiser
   exatamente isso; o que não existe é cair nele por acidente.

   A RESERVA `4 / 3` NÃO É ENFEITE DE SINTAXE. É ela que faz um case
   montado antes deste campo existir continuar desenhando igual: sem
   `--proporcao` no `style`, o `var()` cai nela. Trocar a reserva por
   `auto` reenquadraria em silêncio toda grade já publicada.

   E NUNCA CHEGA `auto` AQUI, nem na opção que se chama "livre". O
   `--proporcao` da grade é herdado, e no modo livre cada `<img>` traz o
   seu próprio, com a dimensão real do arquivo. `aspect-ratio: auto`
   escrito por autor apaga a proporção que o navegador deduz dos
   atributos `width`/`height`, e sem ela a caixa vale 353×0 até a foto
   baixar — o salto de layout que esses atributos existem para impedir.
   Medido, e não suposto. */
.cs__grid-fotos img {
  width: 100%;
  height: auto;
  aspect-ratio: var(--proporcao, 4 / 3);
  object-fit: cover;
  border-radius: var(--r-moldura);
}

.cs__grid-fotos figcaption {
  margin-top: var(--ar-legenda);
  font-size: var(--fs-small);
  color: var(--fg-muted);
}

/* ============================================================
   cs--midia
   ============================================================ */
.cs__midia img {
  width: 100%;
  height: auto;
  border-radius: var(--r-moldura);
}

.cs--midia--sangria .cs__midia img {
  border-radius: 0;
}

.cs__midia figcaption {
  margin-top: var(--ar-legenda);
  font-size: var(--fs-small);
  color: var(--fg-muted);
  max-width: 52ch;
}

.cs--midia--duo .cs__midia {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--sp-6);
  align-items: end;
}

/* Alturas diferentes: duas imagens do mesmo tamanho lado a lado é uma
   imagem cortada ao meio. */
.cs--midia--duo .cs__midia > :first-child {
  padding-bottom: clamp(var(--sp-7), 8vw, var(--sp-9));
}

/* A dupla texto + imagem. Aparece uma vez na página: três seções
   seguidas neste arranjo é a alternância banal que denuncia o molde. */
.cs__par {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(var(--sp-7), 6vw, var(--sp-9));
  align-items: center;
}

/* ============================================================
   cs--filme
   Pôster local com botão de play. O iframe da Vimeo só nasce no
   clique: o embed puxa perto de 900 KB de terceiro e grava cookie
   antes de qualquer consentimento. Sem clique, nenhum byte.
   ============================================================ */
.cs__filme {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: var(--r-moldura);
  overflow: hidden;
  background: var(--c-black);
  /* Medida do disco em um lugar só: ela decide o tamanho do alvo e, com
     ele, onde a legenda começa. Nos dois lugares, o mesmo número. */
  --disco: 84px;
}

.cs__filme img,
.cs__filme iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  object-fit: cover;
}

/* `place-content: center` e não `place-items: center`.

   A diferença é o que estava errado. `place-items` alinha cada filho
   DENTRO da célula dele; quem distribui as células é `align-content`,
   que por omissão vale `normal` e, em linhas automáticas, se comporta
   como `stretch`. Então as duas linhas - disco e legenda - esticavam
   para dividir a altura do vídeo entre si: medido num quadro de 737px,
   elas ficaram com 390 e 328, e cada filho, centrado na própria faixa,
   foi parar longe do centro do quadro. O disco a 172px acima dele.

   Com `place-content`, quem é centrado é o conjunto das linhas, e as
   linhas voltam a ter a altura do conteúdo.

   Só que centrar o conjunto ainda deixaria o disco acima do centro pela
   metade da altura da legenda. E o disco é o alvo: é nele que se clica,
   é ele que precisa estar no meio do quadro. Então a legenda sai do
   fluxo e é pendurada abaixo dele - ver `.cs__play-txt`. */
.cs__play {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  width: 100%;
  background: color-mix(in srgb, var(--c-black) 26%, transparent);
  border: 0;
  color: var(--c-white);
  cursor: pointer;
  transition: background-color var(--dur-base) var(--ease-out);
}

.cs__play:hover {
  background: color-mix(in srgb, var(--c-black) 42%, transparent);
}

.cs__play-disco {
  display: grid;
  place-items: center;
  width: var(--disco);
  height: var(--disco);
  border-radius: var(--r-pill);
  background: var(--c-white);
  color: var(--c-black);
  transition: scale var(--dur-base) var(--ease-expand);
}

.cs__play:hover .cs__play-disco {
  scale: 1.08;
}

/* Sem o recuo óptico de 4px que havia aqui: o triângulo fica centrado
   pela geometria, e não pela percepção. */
.cs__play-disco svg {
  width: 26px;
  height: 26px;
  fill: currentColor;
  margin-left: 0;
}

/* Fora do fluxo, pendurada a meio disco mais um respiro abaixo do centro
   do quadro. Assim ela acompanha o disco sem participar da conta que
   define onde ele fica: quem manda no centro é o alvo, e a legenda é
   anotação. Se ela crescer para duas linhas, o disco não se mexe. */
.cs__play-txt {
  position: absolute;
  left: 0;
  right: 0;
  top: calc(50% + var(--disco) / 2 + var(--sp-4));
  text-align: center;
  font-size: var(--fs-small);
  font-weight: 500;
  letter-spacing: 0.04em;
}

.cs__filme.is-tocando .cs__play {
  display: none;
}

/* ============================================================
   cs--lista
   Dez veículos não são uma lista com dez filetes. São dois
   blocos, com um filete cada.
   ============================================================ */
.cs__lista {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
  gap: var(--sp-6) var(--sp-7);
}

.cs__grupo {
  padding-top: var(--sp-4);
  border-top: 1px solid var(--line);
}

.cs__grupo h3 {
  font-size: var(--fs-label);
  font-weight: 500;
  line-height: 1;
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--fg-muted);
  margin-bottom: var(--sp-4);
}

.cs__grupo ul {
  display: grid;
  gap: var(--sp-2);
  font-size: var(--fs-body-l);
}

/* O grupo aceita prosa, e não só lista. É o que permite a autoria do
   case - uma frente, o que coube a ela - usar o mesmo bloco de dois
   filetes das listas, em vez de pedir um módulo próprio para dizer duas
   coisas. */
.cs__grupo p {
  font-size: var(--fs-body);
  line-height: 1.55;
  max-width: 46ch;
}

/* ============================================================
   cs--premios
   ============================================================ */
.cs__resumo {
  font-weight: 300;
  font-size: var(--fs-display-m);
  line-height: 1.06;
  letter-spacing: var(--tr-display);
  max-width: 24ch;
  margin-bottom: clamp(var(--sp-7), 6vh, var(--sp-8));
}

/* Cartões soltos, e não uma tabela de filetes. A grade de 1px com fundo
   pintado prendia os prêmios numa malha contínua: dez prêmios viravam um
   bloco cinza de dez linhas, e o que se lia era a malha, não os prêmios.
   Soltos, cada um é uma peça, e o conjunto se lê contando peças.

   O selo do festival vai em cima e sozinho, com o texto abaixo: lado a
   lado, a coluna de 56px obrigava logotipos de proporções muito
   diferentes (um 200x56, outro 94x124) a caber na mesma caixa, e o
   vertical saía espremido a 44px de altura. */
/* Contagem explícita de colunas, e não `auto-fill`. Com `auto-fill` o
   número de cartões por linha muda com a largura da tela E com o mínimo
   declarado, então duas fileiras de prêmios na mesma página podiam
   quebrar em contagens diferentes - e cartão de premiação que muda de
   largura entre uma fileira e outra lê como dois componentes, não como
   um. Com `--colunas` a fileira é uma decisão, igual à do grid de fotos. */
/* A linha de resumo da premiação, e a nota depois dela. São as duas
   frases que o documento escreve em volta da lista - uma antes,
   anunciando a conta, e uma depois, com o que não é prêmio mas é
   seleção. Ficam em corpo de texto e cinza: quem tem que crescer aqui
   são os oito cartões, não o que os apresenta. */
.cs__resumo-linha {
  margin-top: var(--sp-5);
  margin-bottom: clamp(var(--sp-7), 6vh, var(--sp-8));
  font-size: var(--fs-body-l);
  line-height: 1.5;
  max-width: 62ch;
  color: var(--fg-muted);
}

.cs__nota {
  margin-top: clamp(var(--sp-6), 5vh, var(--sp-7));
  font-size: var(--fs-body);
  line-height: 1.55;
  max-width: 62ch;
  color: var(--fg-muted);
}

.cs__premios {
  display: grid;
  grid-template-columns: repeat(var(--colunas, 4), minmax(0, 1fr));
  gap: var(--sp-5);
}

/* Entre 900 e 1180 quatro cartões dariam menos de 250px cada, e o nome
   do festival passaria a quebrar em três linhas dentro de um cartão que
   tem 64px só de selo. */
@media (max-width: 1180px) {
  .cs__premios {
    --colunas: 3;
  }
}

.cs__premios + .cs__premios {
  margin-top: var(--sp-5);
}

/* `align-content: space-between` empurra o texto para a base: os selos
   têm alturas visuais diferentes mesmo dentro da mesma caixa, e sem isso
   a linha "Ouro" começava numa altura diferente em cada cartão. */
/* ---------- a cor da colocação ----------
   Um custom property por cartão, e não três regras repetindo cor em
   cada lugar que a usa. `--colocacao` é declarada uma vez pelo
   atributo e lida por quem precisar dela.

   HOJE QUEM LÊ É SÓ O OLHO. A borda do cartão chegou a receber a cor
   junto e saiu: oito cartões contornados em ouro, prata e bronze
   viravam oito molduras disputando atenção, e o que some no meio é a
   fileira lida como um conjunto. A borda volta a ser `--line` para
   todos, e a cor fica no rótulo - um por cartão, pequeno, onde a
   informação de fato está.

   O fallback não é enfeite: "Finalista" e "Menção" não têm cor de
   metal, e cartão sem `data-colocacao` tem de continuar saindo com o
   olho neutro de antes. */
.cs__premio[data-colocacao="ouro"]   { --colocacao: var(--c-ouro); }
.cs__premio[data-colocacao="prata"]  { --colocacao: var(--c-prata); }
.cs__premio[data-colocacao="bronze"] { --colocacao: var(--c-bronze); }

.cs__premio {
  position: relative;
  display: grid;
  align-content: space-between;
  gap: var(--sp-6);
  padding: var(--sp-6);
  border: 1px solid var(--line);
  border-radius: var(--r-moldura);
}

/* ---------- a caixa do selo ----------
   Caixa fixa e `contain`, e não teto de largura mais teto de altura. Com
   dois tetos, cada logotipo encosta no que a proporção dele mandar: o
   fiesp (200x56, deitado) saía com 140x39 e o recall (94x124, em pé) com
   42x56 - o mesmo prêmio parecendo três vezes maior num cartão do que no
   outro. Com uma caixa só, nenhum selo pode ocupar mais espaço que o
   vizinho, e o que varia é quanto de dentro dela cada um preenche.

   O resto é decisão de arquivo, não de CSS: um SVG com muita margem
   branca embutida vai parecer menor dentro da caixa, e a correção é
   aparar o arquivo. */
/* O teto de largura é por selo, e não um só para todos. A caixa de 64px
   de altura com `contain` já iguala a ALTURA de qualquer arquivo, mas
   não a presença: um selo horizontal e largo, como o da Fiesp, encosta
   nos 160px e fica visivelmente maior que os vizinhos mesmo tendo a
   mesma altura de caixa. Quem sabe o teto certo é quem olha a fileira,
   então ele é uma variável com padrão - o cartão que precisa de outro
   escreve `--largura-selo` no próprio `style`. */
.cs__premio img {
  width: 100%;
  height: 64px;
  max-width: var(--largura-selo, 160px);
  object-fit: contain;
  object-position: left center;
  opacity: 0.75;
}

.cs__premio dt {
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--colocacao, var(--acento));
  margin-bottom: var(--sp-3);
}

.cs__premio dd {
  margin: 0;
  font-size: var(--fs-body);
  line-height: 1.35;
}

.cs__premio dd small {
  display: block;
  margin-top: var(--sp-2);
  font-size: var(--fs-small);
  color: var(--fg-muted);
}

/* O contador, e quando ele NÃO se usa.
   Cada prêmio é uma categoria e uma colocação, e as duas coisas são
   únicas: ouro em embalagem não é ouro em campanha. Somar isso num "×2"
   apaga a informação que interessa. Por isso a fileira normal não tem
   contador nenhum.

   Ele existe para a fileira mescladas: quando a página opta por agrupar
   várias categorias sob um festival e uma colocação, aí sim o número diz
   quantas foram - e é o único caso em que somar não perde nada, porque a
   lista das categorias vai escrita no próprio cartão.

   `<= 1` não é caso tratado: cartão de uma vez só não escreve o atributo. */
.cs__premio[data-vezes]::after {
  content: "×" attr(data-vezes);
  position: absolute;
  top: var(--sp-5);
  right: var(--sp-5);
  padding: 0.2em 0.6em;
  border: 1px solid var(--acento);
  border-radius: var(--r-pill);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: var(--tr-label);
  color: var(--acento);
  font-variant-numeric: tabular-nums;
}

/* ============================================================
   cs--escuro
   O fechamento. A paleta do case sai e entra o preto com o
   gradiente do grupo: a página termina onde a home mora. É o que
   mantém os oito cases como conjunto sem obrigar nenhum deles a
   usar amarelo.
   ============================================================ */
/* O degradê saiu. Ele era a segunda aparição do gradiente-assinatura na
   página, e a regra do sistema permite duas - mas permitir não é
   precisar. Aqui embaixo ele iluminava um bloco que agora tem uma peça
   só, o convite, e um facho de luz atrás de uma frase e um botão é
   cenário para quem não tem o que mostrar. Preto chapado, e o convite
   sozinho no escuro.

   Com ele saem o `overflow: clip` e o `position: relative` dos filhos,
   que existiam só para conter o vazamento de 10% do facho. */
.cs--escuro {
  background: var(--c-black);
  color: var(--c-white);
  --fg: var(--c-white);
  --fg-muted: var(--c-gray-on-dark);
  --line: var(--c-line-dark);
  /* Sem `--acento` próprio: ele já lê `--fg`, e `--fg` aqui é branco. */
}

.tinta-viva .cs--escuro {
  background: var(--c-black);
}

/* ============================================================
   cs--cta
   ============================================================ */
/* `.cs__cta` E `.cs__cta-frase` SUBIRAM PARA `layout.css` (25/08), pelo
   mesmo motivo e pelo mesmo caminho que a `.placa` subiu antes: o índice
   de /cases fecha com esta peça e não carrega esta folha. Ficam aqui as
   duas regras que são MESMO da página de case - a inversão do botão
   dentro de `.cs--escuro`, logo abaixo. */

/* A REGRA DO RODAPÉ ESCURO SUBIU PARA `layout.css` (25/08), junto com as
   das outras três páginas de chão claro. Eram quatro cópias da mesma
   decisão e duas estavam incompletas - o porquê está lá. */

.cs--escuro .cs__cta .btn {
  --btn-bg: var(--c-white);
  --btn-fg: var(--c-black);
}

.cs--escuro .cs__cta .btn:hover {
  --btn-bg: var(--c-yellow);
  --btn-fg: var(--c-black);
}

/* ============================================================
   cs--proximos
   ============================================================ */
.cs__proximos {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  gap: var(--sp-6);
}

.cs__proximo img {
  width: 100%;
  height: auto;
  border-radius: var(--r-moldura);
  transition: scale var(--dur-scene) var(--ease-out);
}

.cs__proximo-moldura {
  overflow: hidden;
  border-radius: var(--r-moldura);
}

.cs__proximo:hover img {
  scale: 1.04;
}

.cs__proximo b {
  display: block;
  margin-top: var(--sp-4);
  font-size: var(--fs-title);
  font-weight: 500;
  letter-spacing: -0.02em;
}

.cs__proximo span {
  font-size: var(--fs-small);
  color: var(--fg-muted);
}

/* Modo editorial do cartão de "leia também": a categoria vira um rótulo
   em caixa alta acima do título, como o olho dos cartões de /conteudo, e
   quem fica em escala é o título (o `b`). O `.cs__proximo span` comum
   continua servindo o modo case (cliente grande + nome do case). A cor e
   o caixa alta vêm de `.t-label`; aqui só o espaçamento entre a moldura,
   o rótulo e o título. */
.cs__proximo-cat {
  margin-top: var(--sp-4);
}

.cs__proximo-cat + b {
  margin-top: var(--sp-2);
}

/* ============================================================
   etiquetas do catálogo
   Só existem em lab/case-modulos.html. Ficam aqui, e não numa
   folha à parte, para o catálogo usar exatamente o mesmo CSS da
   página real - catálogo com estilo próprio mente sobre o que ele
   está catalogando.
   ============================================================ */
.cs__tag-lab {
  position: absolute;
  top: 0;
  left: var(--margin);
  z-index: var(--z-raised);
  padding: 0.35em 0.7em;
  background: var(--c-black);
  color: var(--c-white);
  border-radius: 0 0 6px 6px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 11px;
  letter-spacing: 0.02em;
}

.cs__tag-lab em {
  font-style: normal;
  color: var(--c-gray-on-dark);
}

/* ============================================================
   mobile
   Toda grade multi-coluna declara aqui como colapsa. Nenhuma
   confia em "o navegador resolve".
   ============================================================ */
@media (max-width: 900px) {
  .cs__par,
  .cs--midia--duo .cs__midia,
  .cs--texto--duas-colunas .cs__texto {
    grid-template-columns: 1fr;
    gap: var(--sp-6);
  }

  .cs--texto--duas-colunas .cs__texto > :first-child {
    position: static;
  }

  /* O rótulo empilha em cima do texto e perde o empurrão: sem coluna ao
     lado não há linha com que alinhar, e o que sobraria seria um vão
     torto entre as duas peças. */
  .cs--texto--rotulo .cs__texto {
    grid-template-columns: 1fr;
    gap: var(--sp-5);
  }

  .cs--texto--rotulo .cs__texto > :first-child {
    margin-block-start: 0;
  }

  .cs--midia--duo .cs__midia > :first-child {
    padding-bottom: 0;
  }

  .case-hero__ficha {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .cs__numeros {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  /* As boias saem da sobreposição e viram uma faixa embaixo do texto: em
     tela estreita não há margem lateral para elas flutuarem sem cobrir a
     leitura. O que elas NÃO fazem mais é sumir - eram duas imagens de
     conteúdo escondidas por falta de espaço, e conteúdo que só existe no
     desktop é conteúdo que o celular não recebeu. Com duas, as duas
     cabem lado a lado. */
  .cs__boia {
    position: static;
    width: 100%;
    max-width: none;
  }

  .cs__flutuante {
    gap: var(--sp-7);
  }

  /* Sem boia ao lado para desviar, o texto usa a largura inteira. */
  .cs__flutuante .cs__texto {
    max-width: none;
  }

  .cs__flutuante-boias {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--sp-4);
    width: 100%;
  }

  /* Três colunas de foto em 900px dariam 260px cada, e a régua da grade
     passaria a ser mais estreita que a legenda embaixo dela. */
  .cs__grid-fotos {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  /* A contagem de colunas dos prêmios é explícita, então ela precisa ser
     redeclarada aqui - `auto-fill` se defendia sozinho, `--colunas` não.
     É o preço de trocar contagem automática por decisão, e o troco é a
     fileira não mudar de largura entre uma e outra. */
  .cs__premios {
    --colunas: 2;
  }

  /* Cartão grande em tela pequena é o cartão inteiro menos a margem: o
     que sobra da direita é a promessa de que há mais um adiante. */
  .cs__rail,
  .cs--galeria--grande .cs__rail {
    grid-auto-columns: 82vw;
  }
}

@media (max-width: 560px) {
  .case-hero__ficha,
  .cs__numeros,
  .cs__grid-fotos {
    grid-template-columns: 1fr;
  }

  .cs__premios {
    --colunas: 1;
  }

  /* Uma coluna também para as boias: a 375px, duas viram 160px cada, e
     160px não é uma imagem, é um ícone. */
  .cs__flutuante-boias {
    grid-template-columns: 1fr;
  }
}

/* ============================================================
   sem movimento
   Não é atenuação: é desligamento. Nada flutua, nada é tocado
   pela rolagem, e o tom de cada capítulo volta a ser pintado pelo
   próprio capítulo.
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  .cs__boia {
    transform: none !important;
  }

  /* A mídia assenta em 1.0 e fica. O quadro continua sendo quadro. */
  .cs__zoom > img,
  .cs__zoom > video,
  .cs__zoom > picture {
    transform: none !important;
  }

  /* Sem condução: a faixa volta a ser uma caixa de rolagem. */
  .cs__rail.is-conduzida {
    overflow-x: auto;
    scroll-snap-type: x proximity;
    scrollbar-width: thin;
  }
}

/* ============================================================
   cs--grafico
   Variação de indicador: antes contra depois, numa base comum.

   Existe porque o `.cs--numeros` não mostra RELAÇÃO. "+575%" é um
   número e cabe lá; "1,2 milhão virou 8,1 milhões" é uma comparação, e
   comparação se lê por comprimento. Barra horizontal e não pizza: o
   olho compara comprimento melhor do que ângulo, e todas as barras
   partem da mesma origem à esquerda.

   A escala é do MÓDULO, não da linha. Cada linha declara `--antes` e
   `--depois` como porcentagem do maior valor do conjunto inteiro - é
   isso que torna duas linhas comparáveis entre si. Se cada uma
   normalizasse pelo próprio máximo, todas as barras "depois" bateriam
   em 100% e o gráfico não diria nada.
   ============================================================ */
.cs__grafico {
  display: grid;
  gap: var(--sp-5);
  margin-top: clamp(var(--sp-6), 5vh, var(--sp-7));
}

/* Três faixas: nome à esquerda, pistas no meio, variação à direita. A
   variação fica na ponta porque é o número que fecha a linha - lido
   depois das duas barras, ele é conclusão; lido antes, seria spoiler. */
.cs__linha {
  display: grid;
  grid-template-columns: minmax(0, 6fr) minmax(0, 14fr) minmax(0, 3fr);
  gap: var(--sp-4) var(--sp-5);
  align-items: center;
  padding-block: var(--sp-4);
  border-top: 1px solid var(--line);
}

.cs__linha:last-child {
  border-bottom: 1px solid var(--line);
}

.cs__linha-nome {
  font-size: var(--fs-body);
  line-height: 1.35;
}

/* As duas pistas empilhadas, e não uma barra dividida em duas cores:
   dividida, o "antes" e o "depois" partiriam de origens diferentes e
   deixariam de ser comparáveis. Empilhadas, as duas começam no mesmo
   zero e a diferença de comprimento É o resultado. */
.cs__pistas {
  display: grid;
  gap: var(--sp-2);
  min-width: 0;
}

.cs__pista {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-width: 0;
}

/* A barra vive numa trilha de largura cheia: sem a trilha, duas barras
   de comprimentos diferentes não têm nada em comum para comparar. */
.cs__trilha {
  position: relative;
  flex: 1;
  min-width: 0;
  height: 10px;
  border-radius: var(--r-pill);
  /* 12% e não 8%: a trilha é a base contra a qual os dois comprimentos
     se comparam, e uma base que ninguém enxerga não é base. */
  background: color-mix(in srgb, var(--fg) 12%, transparent);
  overflow: hidden;
}

/* O comprimento é a única coisa que a variável controla. `width` e não
   `transform: scaleX`: a barra tem cantos arredondados, e escalar em X
   esmagaria o raio da ponta num oval. */
.cs__trilha i {
  display: block;
  height: 100%;
  width: var(--w, 0%);
  border-radius: inherit;
  background: var(--cor-barra, var(--acento));
  transition: width var(--dur-scene, 900ms) var(--ease-out, cubic-bezier(0.22, 1, 0.36, 1));
  transition-delay: calc(var(--i, 0) * 90ms);
}

/* Sem condução, a barra nasce cheia; com condução, ela parte de zero e
   o script solta o `is-cheio` quando a linha entra na tela. A ordem
   importa: se o valor final morasse só na classe, um erro de script
   deixaria o gráfico zerado - e gráfico zerado mente. */
.cs__grafico:not(.is-cheio) .cs__trilha i {
  width: 0%;
}

/* 52%, e o número saiu de uma conta de contraste, não de gosto. Em 26%
   a barra dava #bdbdbd sobre a trilha #ebebeb: 1,6:1. A regra 1.4.11 pede
   3:1 para elemento gráfico que carrega informação, e esta carrega - o
   comprimento dela é metade da comparação. Em 52% são 3,25:1 no capítulo
   claro e 4,47:1 no escuro, porque a mistura acompanha o `--fg` dos dois. */
.cs__pista--antes {
  --w: var(--antes);
  --cor-barra: color-mix(in srgb, var(--fg) 52%, transparent);
}

.cs__pista--depois {
  --w: var(--depois);
  --cor-barra: var(--acento);
}

/* Largura reservada para o valor: sem ela, os números de cada pista
   assentavam em colunas diferentes e a leitura vertical se perdia. */
.cs__pista-valor {
  flex: none;
  min-width: 7ch;
  font-size: var(--fs-small);
  font-variant-numeric: tabular-nums;
  color: var(--fg-muted);
}

.cs__pista--depois .cs__pista-valor {
  color: var(--fg);
  font-weight: 500;
}

.cs__linha-delta {
  justify-self: end;
  text-align: right;
  font-size: var(--fs-title);
  font-weight: 300;
  line-height: 1;
  letter-spacing: var(--tr-display);
  font-variant-numeric: tabular-nums;
}

/* ---------- variante: cartões de indicador ----------
   Para o que não é antes-e-depois. Um KPI solto com a variação ao lado
   não precisa de barra: não há segundo comprimento para comparar, e uma
   barra sozinha seria uma régua sem nada medido nela. */
.cs__kpis {
  display: grid;
  grid-template-columns: repeat(var(--colunas, 3), minmax(0, 1fr));
  gap: var(--sp-4);
  margin-top: clamp(var(--sp-6), 5vh, var(--sp-7));
}

.cs__kpi {
  display: grid;
  gap: var(--sp-3);
  align-content: start;
  padding: var(--sp-5);
  border: 1px solid var(--line);
  border-radius: 16px;
}

.cs__kpi-valor {
  font-size: var(--fs-numeral);
  font-weight: 300;
  line-height: 0.95;
  letter-spacing: var(--tr-display);
  font-variant-numeric: tabular-nums;
}

.cs__kpi-nome {
  font-size: var(--fs-small);
  line-height: 1.4;
  color: var(--fg-muted);
}

/* A seta é `aria-hidden` e a direção também está escrita no texto do
   valor: cor e ângulo sozinhos não dizem "subiu" para quem não vê
   nenhum dos dois. */
.cs__kpi-variacao {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-small);
  font-weight: 500;
  color: var(--acento);
}

@media (max-width: 900px) {
  /* O nome sobe para cima das barras e a variação desce para a direita
     dele: em 390px, três colunas deixavam a trilha com 90px, e barra de
     90px não compara nada. */
  /* Duas faixas: nome e variação em cima, barras embaixo atravessando a
     largura toda. As posições são explícitas porque a colocação
     automática não resolve: as pistas atravessam a grade e empurram a
     variação para uma terceira fileira, onde ela deixa de ser o
     fechamento da linha e vira uma sobra solta embaixo dela. */
  .cs__linha {
    grid-template-columns: minmax(0, 1fr) auto;
  }

  .cs__linha-nome {
    grid-column: 1;
    grid-row: 1;
  }

  .cs__linha-delta {
    grid-column: 2;
    grid-row: 1;
  }

  .cs__pistas {
    grid-column: 1 / -1;
    grid-row: 2;
  }

  .cs__linha-delta {
    font-size: var(--fs-body-l);
  }

  .cs__kpis {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 560px) {
  .cs__kpis {
    grid-template-columns: 1fr;
  }
}

/* ===================================================================
   OS DOIS ÚLTIMOS MÓDULOS, VINDOS DA BANCADA EM 27/08/2026
   ===================================================================
   `.cs--texto--rotulo--icone` e `.cs--carrossel` nasceram em 26/08 como
   proposta, e por isso moravam em `lab/case-delta.css` — fora da folha
   de produção, para serem vistos ao lado dos módulos que já valiam antes
   de entrar em case nenhum.

   A CONVERSÃO PARA VELK DECIDIU POR ELES. `site.json` declara os dois
   na lista de módulos e `partials/modulos.php` desenha os dois — quem
   escolhesse qualquer um receberia a marcação certa sem uma regra de
   estilo em lugar nenhum. Módulo oferecido no painel é módulo em
   produção; o resto é uma armadilha silenciosa.

   Os comentários abaixo vieram inteiros do delta: eles explicam por que
   cada regra existe, e nenhum deles envelheceu com a mudança de pasta.
   `lab/case-delta.css` continua lá, no projeto estático, e agora está
   vazio de propósito — nada em teste hoje.
   =================================================================== */

/* ============================================================
   cs--texto--rotulo--icone
   O mesmo rótulo à esquerda, com um ícone na frente dele.
   Soma-se a `.cs--texto--rotulo`; sozinha, esta classe não
   compõe nada.
   ============================================================ */

/* O tamanho do ícone é token porque a conta do registro, lá embaixo,
   depende dele: um valor, dois lugares que o leem.

   24px, e não os 28px que a cena 3 da /grupo usa. Lá o ícone é a peça
   do item e o nome vem embaixo dele; aqui ele está DIANTE de um rótulo
   de 11px, e a 28px o desenho passava a ser o assunto da linha em vez
   de a marca dela.

   TETO REAL: por volta de 34px. Acima disso a compensação de registro
   fica maior que a margem que ela corrige, o resultado vira negativo e
   o rótulo sobe para fora do alto da coluna. A conta está no comentário
   da margem. */
.cs--texto--rotulo--icone {
  --cs-olho-icone: 24px;
}

/* O ícone e a palavra são uma peça só, e por isso moram os dois dentro
   do `h2`. Ele continua sendo o título da seção, e o nome acessível
   dele continua sendo só a palavra: o `<img>` entra com `alt=""` porque
   o desenho REPETE o rótulo que está do lado. Ícone com texto
   alternativo aqui faria o leitor de tela anunciar "Sentido Sentido". */
.cs--texto--rotulo--icone .cs__olho {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
}

/* `object-fit: contain` num quadrado fixo, e não `height: auto`.
   Os quatro ícones do método são 30x30 e a diferença seria nenhuma -
   mas a conta do registro assume que a ALTURA do ícone é o token, e um
   quinto ícone que não fosse quadrado desalinharia a linha inteira sem
   avisar. Com a caixa fixa, um ícone fora de esquadro sobra em
   letterbox e a conta continua verdadeira.

   `filter: brightness(0)` é o mesmo caminho que `.metodo__icone` já usa
   em `scenes.css`: os arquivos são desenhados com traço `#fd0`, que é
   invisível em chão claro, e o filtro zera os canais e devolve preto
   com o alfa intacto. É o preço de reaproveitar os MESMOS arquivos que
   a /grupo usa, em vez de redesenhar os quatro em `currentColor` e
   passar a ter dois desenhos para cada etapa.

   O QUE ISSO CUSTA: o ícone fica preso em preto, enquanto o rótulo do
   lado lê `var(--acento)`. Hoje os dois batem - `--acento` é `--fg`, e
   `--fg` em chão claro é preto. No dia em que um case puxar o acento da
   frente que o assinou (a linha está prevista em `case.css`), esta
   regra é a que sai de sincronia, e a saída é trocar o filtro por
   `mask-image` com `background: var(--acento)`.

   NO HTML, o `<img>` leva `width="24" height="24"` - o tamanho que esta
   regra desenha, e não os 30 do viewBox. Os arquivos não declaram
   dimensão própria, e sem os atributos o navegador assume 150x150: com
   CSS o resultado é o mesmo, mas sem CSS o rótulo abriria com um ícone
   de 150px do lado. Se o token acima mudar, os atributos mudam junto.

   E SEM `loading="lazy"`, ao contrário de `.metodo__icone`. Lá o ícone
   é a peça de um item, numa cena presa lá embaixo da /grupo; aqui ele
   está DENTRO de um título, e um arquivo de 700 bytes não tem o que
   economizar sendo adiado - só o que perder, se chegar depois da
   palavra que ele acompanha. */
.cs__olho-icone {
  flex: none;
  width: var(--cs-olho-icone);
  height: var(--cs-olho-icone);
  object-fit: contain;
  filter: brightness(0);
}

/* O REGISTRO, que é a única coisa que o ícone quebra.

   A folha de produção empurra o rótulo para baixo para que a base da
   palavra caia na mesma linha que a base da primeira linha do texto ao
   lado: `1.05 * corpo - 0.75 * rótulo`, a conta está em `case.css`.

   Com o ícone, o `h2` deixa de ter a altura da linha do rótulo (11px) e
   passa a ter a do ícone (24px). Centrados um contra o outro, a palavra
   desce metade da diferença dentro da própria caixa - 6,5px -, e é
   exatamente isso que sai da margem aqui. Nada de olhômetro: a conta é
   a mesma de cima com um termo a mais, e ela acompanha sozinha tanto o
   `clamp` do corpo quanto uma troca do tamanho do ícone.

   Mesma especificidade da regra que ela corrige (0,2,1). Quem decide é
   a ordem, e a ordem é o `<link>` do delta depois do da produção. */
.cs--texto--rotulo--icone .cs__texto > :first-child {
  margin-block-start: calc(
    1.05 * var(--fs-body-l) - 0.75 * var(--fs-label)
    - (var(--cs-olho-icone) - var(--fs-label)) / 2
  );
}

@media (max-width: 900px) {
  /* Empilhado não há linha com que registrar, e a produção já zera a
     margem. Esta regra existe porque a de cima é do MESMO peso e vem
     DEPOIS: sem ela, a compensação venceria o zero e o rótulo do
     celular seria o único a nascer com 5px de recuo. */
  .cs--texto--rotulo--icone .cs__texto > :first-child {
    margin-block-start: 0;
  }
}

/* ============================================================
   cs--carrossel
   ============================================================ */

/* A caixa tem teto e é centrada. Sem teto, numa tela de 1440 a foto
   passaria de 1300px de largura e, em 4:3, de 970px de altura - mais
   alta que a tela de quem olha. O carrossel deixaria de ser uma foto
   por vez e viraria a faixa que já existe.

   Com teto de 980px a conta fecha em qualquer largura. Medido: foto de
   844 por 633 num desktop de 1280, 283 por 212 num celular de 375, e a
   próxima sempre à mostra - 132px lá, 48px aqui, entre 15% e 17% da
   largura da principal nos dois casos. */
.cs__carrossel {
  max-width: 980px;
  margin-inline: auto;
}

/* `--espia`: quanto da próxima foto fica à vista. É o pedido inteiro
   deste módulo em uma variável - "carrossel estilo Instagram, mas com
   preview do que vem" -, então ela é o número que se mexe quando o
   ajuste for de composição.

   O valor inclui o vão: com `--espia` de 64px e `gap` de 16px, sobram
   48px de foto aparecendo. Menos que isso não lê como foto, lê como
   sujeira na borda. */
.cs__carrossel-pista {
  --espia: clamp(64px, 13vw, 148px);
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: calc(100% - var(--espia));
  gap: var(--sp-4);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  margin: 0;
  /* Os 6px são folga para o anel de foco não ser cortado pela caixa de
     rolagem; a margem negativa devolve a faixa às bordas do shell.

     E o `100%` lá de cima resolve contra a CAIXA DE ROLAGEM, com o
     padding dentro - medido, não deduzido: num desktop de 1280 a caixa
     dá 992 e o item sai com 844, que é 992 menos os 148 de `--espia`.
     Quem mexer nestes 6px muda a largura da foto junto. */
  padding: 6px;
  margin-inline: -6px;
  list-style: none;
  /* A barra de rolagem nativa some porque este módulo tem controles
     próprios: setas, contador e o arrasto. Duas indicações do mesmo
     eixo é uma a mais. A faixa de `.cs--galeria` mantém a dela porque
     lá não há seta nenhuma. */
  scrollbar-width: none;
}

.cs__carrossel-pista::-webkit-scrollbar {
  display: none;
}

.cs__carrossel-item {
  scroll-snap-align: start;
  margin: 0;
}

.cs__carrossel-item figure {
  margin: 0;
}

/* O alvo é um `<a href>` para o arquivo da foto, e não um `<button>`.
   Sem JS o pop-up não existe, e um botão morto seria um controle que
   promete e não cumpre; o link abre a imagem em tamanho cheio, que é
   uma ampliação pobre mas verdadeira. Com JS, o script intercepta o
   clique e abre a lupa. */
.cs__carrossel-abrir {
  position: relative;
  display: block;
  border-radius: var(--r-moldura);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  -webkit-tap-highlight-color: transparent;
}

.cs__carrossel-abrir img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}

/* ---------- o selo "Ampliar" ----------
   VISÍVEL SEMPRE, e não no `:hover`. Metade de quem vai ver isto está
   no celular, onde `:hover` não existe - um indicador que só aparece
   ao passar o mouse é um indicador que não existe para essa metade.

   O fundo é preto a 62%. Sobre a pior foto possível (uma branca) isso
   resolve em #616161, e texto branco em cima mede 6,2:1 - acima do AA
   de 4,5 que um rótulo de 11px exige. Sobre foto escura sobra mais. O
   `blur` não entra na conta: borrão de branco continua branco. */
.cs__carrossel-selo {
  position: absolute;
  top: var(--sp-3);
  right: var(--sp-3);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0.45em 0.75em;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.62);
  color: var(--c-white);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  line-height: 1;
  backdrop-filter: blur(4px);
}

.cs__carrossel-selo svg {
  width: 13px;
  height: 13px;
  flex: none;
}

/* ---------- a legenda ----------
   Nasce VISÍVEL e o script a esconde, nunca o contrário. Se o JS não
   rodar - erro de módulo, rede caída, navegador antigo -, a legenda
   continua embaixo da foto e o bloco vira uma galeria comum. Escondê-la
   no CSS deixaria o texto inalcançável exatamente quando não há pop-up
   para mostrá-lo.

   Some com `display: none` de propósito, e não com `.visually-hidden`:
   aqui ela não deve ser lida por leitor de tela também, porque o mesmo
   texto entra no rótulo do link ("Ampliar foto 3 de 8 - ...") e depois
   aparece inteiro dentro da lupa. Anunciada nos três lugares, seria a
   mesma frase três vezes. */
.cs__carrossel-item figcaption {
  margin-top: var(--ar-legenda);
  font-size: var(--fs-small);
  line-height: 1.5;
  color: var(--fg-muted);
  max-width: 46ch;
}

.cs__carrossel.is-pronto .cs__carrossel-item figcaption {
  display: none;
}

/* ---------- a barra de controle ----------
   Contador de um lado, setas do outro. Chega ao DOM com `hidden` e o
   script tira: mesma regra da legenda, nenhum controle aparece antes
   de existir quem o atenda. */
.cs__carrossel-barra {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-5);
  margin-top: var(--sp-5);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--line);
}

.cs__carrossel-barra[hidden] {
  display: none;
}

/* "3 de 8". O numeral em peso 500 e o resto em `--fg-muted`: a
   informação que muda é o primeiro número, e é ele que precisa ser
   lido de relance. */
.cs__carrossel-conta {
  font-size: var(--fs-label);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--fg-muted);
  font-variant-numeric: tabular-nums;
}

.cs__carrossel-conta b {
  font-weight: 500;
  color: var(--fg);
}

.cs__carrossel-setas {
  display: flex;
  gap: var(--sp-2);
}

/* 44px é o alvo mínimo de toque - não é um número escolhido pela
   composição. */
.cs__carrossel-seta {
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: none;
  color: var(--fg);
  cursor: pointer;
  transition: border-color var(--dur-micro) var(--ease-out),
              background-color var(--dur-micro) var(--ease-out);
}

.cs__carrossel-seta svg {
  width: 18px;
  height: 18px;
}

.cs__carrossel-seta:hover:not(:disabled) {
  border-color: var(--fg);
}

/* Desabilitada no fim da fila, e não escondida: um controle que some
   muda a largura da barra e faz o contador pular de lugar. */
.cs__carrossel-seta:disabled {
  opacity: 0.3;
  cursor: default;
}

/* ============================================================
   cs__lupa
   O pop-up. É o PRIMEIRO modal do site inteiro - não havia
   `<dialog>`, `role="dialog"` nem `showModal` em lugar nenhum -,
   e por isso ele é um `<dialog>` nativo e não uma div.

   O que vem de graça com o elemento certo, e que uma div exigiria
   escrever à mão e manter: foco preso em modo modal, `Esc` para
   fechar, o resto da página inerte para leitor de tela, a camada
   de topo (sem disputa de `z-index`) e o foco devolvido ao link
   que abriu. Cinco defeitos clássicos de lightbox, nenhum deles
   nosso.

   O que ainda é nosso: parar o Lenis, senão a página continua
   rolando por baixo do modal.
   ============================================================ */
/* ALTURA FIXA, E NÃO `max-height` (26/08). A diferença não é de estilo,
   é o que faz a ampliação existir.

   Com `max-height` o diálogo era um EMBRULHO: ele se moldava à foto, a
   linha `1fr` do grid virava altura de conteúdo, e uma porcentagem lá
   dentro não tinha contra o que resolver. Sem isso a foto não tinha como
   saber quanto espaço havia, e parava no tamanho do arquivo - o selo
   dizia "Ampliar" e entregava, numa foto de 493px, 351px A MENOS de
   largura do que ela já tinha no carrossel.

   Com altura fixa o diálogo vira MOLDURA: a linha `1fr` passa a ter
   medida, a foto resolve `height: 100%` contra ela e cresce até encostar.
   De quebra some o outro defeito da versão anterior - a caixa mudava de
   tamanho a cada foto, e navegar entre duas fotos de tamanhos diferentes
   fazia o modal pular na tela.

   `min(1000px, 94vh)`, e os dois números foram medidos, não herdados.

   94vh e não os 92vh de antes porque a ALTURA é quem recorta a foto -
   a largura sobra em quase todo caso. Numa janela de 1280x900 os 2% a
   mais viram 18px de quadro, 18px de foto e 24px de largura, e são eles
   que decidem se o pop-up abre maior ou menor que o carrossel: sem eles
   a foto sai com 826px contra os 844px que ela já tinha na página.

   O teto em pixels: quem manda em tela comum é o vh, e ele só
   existe para o modal não virar uma faixa preta de dois metros num
   monitor em pé. O teto foi de 880 para 1000 depois de medir: num
   1920x1080 o 880 recortava 114px de altura que a tela tinha para dar, e
   era justamente o que faltava para a foto passar do tamanho que ela já
   tinha no carrossel. */
.cs__lupa {
  width: min(1100px, 92vw);
  height: min(1000px, 94vh);
  /* O topo é maior que os outros lados: o botão de fechar é absoluto e
     mede 44px, e sem esta folga ele pousaria em cima da foto. */
  padding: var(--sp-8) var(--sp-6) var(--sp-6);
  border: 0;
  border-radius: var(--r-moldura);
  background: var(--c-black);
  color: var(--c-white);
  /* O modal é uma superfície escura no meio de uma página clara: os
     tokens de texto precisam virar junto, senão a legenda herda o
     cinza de chão claro e some. Mesma regra dos blocos `.cs--escuro`. */
  --fg: var(--c-white);
  --fg-muted: var(--c-gray-on-dark);
  --line: var(--c-line-dark);
  /* `display: none` AQUI, e `grid` só com `[open]`, logo abaixo.

     A folha do navegador já traz `dialog:not([open]) { display: none }`,
     e mesmo assim não bastava: regra de AUTOR vence regra de navegador
     seja qual for a especificidade - a ordem de origem é decidida antes
     de a especificidade ser consultada. Um `display: grid` solto aqui
     apagava a regra do navegador inteira, e o diálogo FECHADO continuava
     desenhado.

     O sintoma: ao clicar no X o `close()` rodava, o diálogo saía da
     camada de topo - e com ela o desfoque do `::backdrop` -, mas a caixa
     preta continuava na tela. Parecia que o botão tinha tirado o blur e
     deixado o modal; o que ele fez foi fechar um modal que nunca some.

     Estava latente desde o primeiro dia deste bloco. O que a altura fixa
     mudou foi o ESCONDERIJO: com `height: auto` a caixa fechada pousava
     no fim do documento (medido: topo 6078, fora de vista, e na bancada
     ainda por cima do bloco preto de fechamento); com altura definida ela
     passa a centrar na viewport (topo 27) e cobre a página. */
  display: none;
  /* `minmax(0, 1fr)` e não a coluna implícita. A implícita é `auto`, e
     `auto` CRESCE com o conteúdo: uma foto panorâmica alargava a coluna
     para além do próprio diálogo, e o `overflow: hidden` logo abaixo
     escondia o estrago em vez de evitá-lo - a foto ficava cortada à
     direita e a barra de baixo, deslocada. O `0` de mínimo é o que
     permite à coluna ser menor que o conteúdo dela. */
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: 1fr auto;
  gap: var(--sp-5);
  overflow: hidden;
}

/* O único estado em que a lupa existe na tela. Devolve ao `<dialog>` o
   comportamento que a folha do navegador já dava e que a regra de cima
   tinha tirado - agora dito por nós, que é o único jeito de o autor
   não precisar lembrar de não atrapalhar. */
.cs__lupa[open] {
  display: grid;
}

.cs__lupa::backdrop {
  background: rgba(0, 0, 0, 0.78);
  backdrop-filter: blur(6px);
}

/* No celular este botão é a ÚNICA saída visível: não há `Esc` num
   teclado que não existe, e não há fundo para tocar porque a lupa
   ocupa a tela inteira. Um controle nessa posição não pode ser
   discreto - os 10% de branco de antes davam um disco quase invisível
   sobre preto. Agora tem contorno, e no celular cresce para 48px.

   `touch-action: manipulation` tira o atraso de ~300ms que alguns
   navegadores de toque guardam esperando um duplo-toque. Meio segundo
   de nada é tempo de sobra para a pessoa concluir que o botão não
   funciona e tocar de novo. */
.cs__lupa-fechar {
  position: absolute;
  top: var(--sp-4);
  right: var(--sp-4);
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.14);
  color: var(--c-white);
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

.cs__lupa-fechar svg {
  width: 18px;
  height: 18px;
}

.cs__lupa-fechar:hover {
  background: rgba(255, 255, 255, 0.24);
  border-color: var(--c-white);
}

/* O ÍCONE NÃO É ALVO. Sem isto o `event.target` de um toque é o `<svg>`
   ou o `<path>` de dentro, e o WebKit já teve mais de uma versão em que
   o teste de acerto em filho de SVG dentro de botão saía errado. Com
   `pointer-events: none` o alvo é sempre o botão, em qualquer motor. */
.cs__lupa-fechar svg,
.cs__lupa-seta svg,
.cs__carrossel-seta svg,
.cs__carrossel-selo svg {
  pointer-events: none;
}

/* FLEX, e não grid com `1fr auto`. A versão anterior estava errada e o
   erro só aparecia no celular: numa grade `1fr auto`, a linha `1fr`
   engole toda a sobra, então `align-content: center` não tem o que
   centrar. E `align-items` fica no `stretch` padrão, que para imagem
   com proporção intrínseca se comporta como `start`.

   Resultado medido num celular de 375: palco de 647px de altura, foto
   de 253px encostada no topo, e a legenda sozinha lá embaixo, a quase
   400px dela. No desktop passava despercebido porque a foto é grande e
   sobra pouco.

   Com coluna flex e `justify-content: center`, foto e legenda viram um
   bloco só, centrado como um só - a legenda encosta na foto que ela
   descreve, e a sobra se divide em cima e embaixo.

   `touch-action: none` porque agora os DOIS eixos são nossos: o
   horizontal troca de foto, o vertical fecha. Não há o que rolar aqui
   dentro para o navegador cuidar - o `<dialog>` é `overflow: hidden`. */
/* `container-type: inline-size` existe por UMA razão: é o que torna a
   largura deste palco legível como COMPRIMENTO dentro de um `calc()`,
   via `100cqw`. Sem isso a foto lá embaixo não teria como comparar o
   espaço horizontal com o vertical, e a caixa dela erraria em toda foto
   mais larga que o palco. Contenção só no eixo em linha: a altura
   continua vindo da linha `1fr` do diálogo, como antes.

   Podia ser uma conta à mão - `min(1100px, 92vw)` menos as margens -,
   e não é de propósito: seria a fórmula da largura do diálogo copiada
   num segundo lugar, e no celular ela nem vale, porque lá a lupa ocupa
   a tela. `100cqw` acompanha a caixa de verdade, nas duas telas. */
.cs__lupa-palco {
  container-type: inline-size;
  margin: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: var(--sp-4);
  touch-action: none;
}

/* `contain` e não `cover`: aqui a pessoa pediu para VER a foto. Cortar
   a imagem dentro do pop-up que existe para ampliá-la seria desfazer o
   próprio gesto.

   `flex: 0 1 auto` com `min-height: 0` é o que deixa a foto ENCOLHER
   quando ela e a legenda juntas não cabem - uma foto em pé numa tela
   baixa. Sem isso, a foto tomaria a altura toda e empurraria a legenda
   para fora da caixa. Encolhendo, a largura em `auto` acompanha a
   proporção sozinha: medido, a razão se mantém em todos os casos.

   `height: 100%` NO LUGAR DE `height: auto` (26/08), e é esta linha que
   corrige o defeito. Num `<img>`, `auto` significa "o tamanho do
   arquivo", e `max-width`/`max-height` só sabem ENCOLHER a partir dali -
   ampliar, nunca. Um arquivo de 493px abria a 493px dentro de um palco
   de 1036, menor do que ele já aparecia no carrossel. Com `100%` a foto
   parte da altura disponível e a largura a acompanha.

   O `min()` DA ALTURA É O QUE FECHA A CONTA, e ele existe porque só
   `height: 100%` não bastava. Uma foto mais larga que o palco esbarra na
   largura antes de esbarrar na altura, e aí `max-width` recorta a
   largura mas NÃO devolve altura: o CSS só recalcula a dimensão que
   está em `auto`, e a que sobrou declarada aqui é a altura. Resultado
   medido, num palco de 1036x776 com uma foto 3:2: caixa de 1036x776
   para uma imagem que só pinta 1036x691. O `object-fit` salvava a foto,
   mas sobravam 85px de preto entre ela e a legenda que a descreve.

   Os dois tetos, então, e o menor manda:

     100%                  o que a altura do palco permite
     100cqw / --cs-razao   a altura que faz a largura bater exatamente
                           na borda do palco

   `--cs-razao` é a proporção da foto, escrita pelo JS a cada troca -
   dos atributos `width`/`height` na hora, e refeita no `load` a partir
   do arquivo. É a única coisa desta conta que o CSS não tem como saber
   sozinho: `aspect-ratio: auto` conhece a razão da imagem, mas não a
   entrega para um `calc()`.

   SEM O JS a variável não existe, o `var()` cai no `1` de reserva, a
   conta devolve a largura do palco como altura - quase sempre maior que
   `100%` - e o `min()` escolhe `100%`. Ou seja: a lupa volta sozinha ao
   comportamento de antes desta linha, que já era correto para toda foto
   até a razão do palco. Nada quebra, só deixa de ser exato no extremo.

   E `object-fit: contain` fica, mesmo com a caixa certa: ele é a rede
   para o dia em que a razão escrita e o arquivo discordarem - miniatura
   e imagem cheia recortadas diferente, por exemplo. Com a rede, a foto
   aparece inteira e mal enquadrada; sem ela, aparece esticada. */
.cs__lupa-foto {
  flex: 0 1 auto;
  min-height: 0;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  /* DUAS declarações, e a primeira não é sobra. Navegador sem unidades
     de container não entende `cqw`, e a linha inteira cai na análise -
     sem a de cima ele voltaria a `height: auto`, que é exatamente o
     defeito que este bloco corrige. Com ela, o que sobra é o
     comportamento bom para toda foto até a razão do palco. */
  height: 100%;
  height: min(100%, calc(100cqw / var(--cs-razao, 1)));
  object-fit: contain;
  border-radius: 8px;
  -webkit-user-drag: none;
  user-select: none;
}

/* `flex: none` para a legenda NÃO ser espremida. Quando a foto e a
   legenda não cabem juntas, quem cede é a foto - ela tem pixels de
   sobra e a legenda tem palavras. Sem esta linha as duas encolhem em
   proporção, e uma legenda de três linhas perde a última. */
.cs__lupa-legenda {
  flex: none;
  max-width: 62ch;
  text-align: center;
  font-size: var(--fs-small);
  line-height: 1.5;
  color: var(--fg-muted);
}

.cs__lupa-legenda[hidden] {
  display: none;
}

/* Setas e contador no rodapé do modal, e não sobrepostos à foto. Seta
   por cima de imagem é contraste que depende do que está atrás dela -
   e o que está atrás muda a cada foto. */
.cs__lupa-barra {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-5);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--line);
}

.cs__lupa-conta {
  min-width: 7ch;
  text-align: center;
  font-size: var(--fs-label);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--fg-muted);
  font-variant-numeric: tabular-nums;
}

.cs__lupa-conta b {
  font-weight: 500;
  color: var(--c-white);
}

.cs__lupa-seta {
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: none;
  color: var(--c-white);
  cursor: pointer;
  touch-action: manipulation;
}

.cs__lupa-seta svg {
  width: 18px;
  height: 18px;
}

.cs__lupa-seta:hover:not(:disabled) {
  border-color: var(--c-white);
}

.cs__lupa-seta:disabled {
  opacity: 0.3;
  cursor: default;
}

/* O anel de foco do sistema é amarelo, e amarelo sobre preto dentro do
   modal fica bom - mas o modal vive numa página de chão claro, cujo
   escopo declara foco preto. Aqui ele volta a ser claro. */
.cs__lupa :focus-visible {
  outline: 2px solid var(--c-white);
  outline-offset: 3px;
  box-shadow: none;
}

/* Trava a página atrás do modal. O Lenis é parado pelo script, mas com
   movimento reduzido ou sem GSAP não há Lenis nenhum - e aí quem
   segura é isto. */
html.lupa-aberta,
html.lupa-aberta body {
  overflow: hidden;
}

/* ============================================================
   mobile
   ============================================================ */
@media (max-width: 700px) {
  /* Tela cheia por POSIÇÃO, e não por tamanho em `100vw`/`100dvh`.
     Todo `<dialog>` modal já é `position: fixed`, então `inset: 0`
     encosta nos quatro lados sem depender de unidade de viewport -
     `100vw` conta a barra de rolagem onde ela existe, e `dvh` não
     existe em WebKit antigo, onde a declaração inteira cairia. O `dvh`
     fica só como TETO, para a barra de baixo não se esconder atrás da
     barra do navegador onde a unidade é suportada. */
  .cs__lupa {
    inset: 0;
    margin: 0;
    width: auto;
    /* `100dvh` e não `auto`: é o mesmo motivo da altura fixa no desktop.
       Com `auto` a linha `1fr` volta a ser de conteúdo, o `height: 100%`
       da foto deixa de ter contra o que resolver, e o celular seria a
       única tela onde a lupa ainda encolheria a foto. */
    height: 100dvh;
    max-width: none;
    max-height: 100dvh;
    border-radius: 0;
    /* O respiro do topo é derivado da posição do botão de fechar, e não
       um número solto: `topo do botão + 56px`. Assim os dois se movem
       juntos quando o `env()` empurra o botão para baixo do entalhe, em
       vez de a foto subir por cima dele. */
    padding:
      calc(max(var(--sp-4), env(safe-area-inset-top)) + 56px)
      var(--sp-4)
      max(var(--sp-4), env(safe-area-inset-bottom));
  }

  /* `env(safe-area-inset-top)` porque em tela cheia, num aparelho com
     entalhe, o topo do modal pode cair debaixo da barra de status - e é
     exatamente ali que mora a única saída da lupa no celular. Um botão
     meio encoberto recebe metade dos toques. */
  .cs__lupa-fechar {
    top: max(var(--sp-4), env(safe-area-inset-top));
    width: 48px;
    height: 48px;
  }

  .cs__carrossel-barra {
    gap: var(--sp-4);
  }
}
