/* 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. */
  /* O ritmo vira token porque agora ele tem DOIS usuários: aqui e o
     vão antes do fechamento, lá embaixo em `.cs--capa + .cs--escuro`.
     Duas cópias do mesmo clamp é como a `.placa` e o convite de
     fechamento divergiram - uma muda, a outra fica. */
  --cs-ritmo: clamp(var(--sp-8), 8.5vh, var(--sp-10));
  padding-block: var(--cs-ritmo);
  /* 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);
}

/* A capa quando encontra o FECHAMENTO.

   `padding-block: 0` delega o respiro ao vizinho, e a delegação só
   funciona enquanto o vizinho for de chão claro: o que separa é o
   branco do padding DELE. O bloco de fechamento tem esse respiro - os
   mesmos 64px -, só que PINTADO. O ar continua lá; ele deixou de ser
   branco, e o que se vê é a foto encostada numa faixa preta.

   Medido em 28/08 nos dois cases que terminam em capa, /cases/datainfo
   e /cases/vimaplas: 0px entre a base da foto e o topo do preto. Nos
   que terminam em citação, 56px. O pior é o canto: o raio de 16px da
   moldura vira duas fatias brancas espremidas entre a curva da foto e
   a borda do preto - lê-se como defeito, não como decisão.

   O vão é o RITMO da seção, e não os 16px do `--margem-capa` que
   separam duas capas. A regra acima diz por quê: 16px são pratos
   empilhados, o mesmo assunto; 64px são troca de capítulo. Entre a
   última foto e o convite está a maior troca de capítulo da página - a
   página acaba ali. Com 16px o fechamento entraria na sequência de
   fotos, que é o contrário do que ele é.

   A margem fica no bloco escuro em vez de um `padding-block-end` na
   capa com `:has(+ ...)`: mesmo resultado, seletor mais barato. O
   comentário fica aqui porque o defeito é do contrato da CAPA, e o
   contrato se lê inteiro num lugar só.

   O que esta regra não cobre: uma seção com `--cs-tint` logo depois de
   uma capa teria o mesmo problema, pela mesma razão. Hoje nenhuma
   declara tom (ver o comentário do `--acento`), e seletor para caso que
   não existe é regra que ninguém consegue testar. */
.cs--capa + .cs--escuro {
  margin-block-start: var(--cs-ritmo);
}

/* ============================================================
   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. */
/* A LARGURA DO CARTÃO É VARIÁVEL PORQUE A PROPORÇÃO PASSOU A SER.

   Enquanto a faixa era sempre 16:10, uma largura fixa bastava: 860px de
   largura são 537px de altura, e 537 é a altura em que o cartão ainda
   cabe na tela com a legenda embaixo. Com a proporção configurável isso
   quebra — 860px em 4:5 dão 1075px de altura, mais que o viewport de
   qualquer notebook, e a faixa deixaria de ser faixa.

   Então quem escolhe a proporção escolhe a largura junto, pela tabela do
   `modulos.php`: cada linha dela foi calculada para pousar na mesma
   banda de altura. O padrão aqui é o valor de sempre, ao pé da letra —
   galeria sem proporção declarada não muda um pixel. */
.cs__rail {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: var(--cartao, 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;
}

/* `--proporcao` chega da lista fechada do `modulos.php`, nunca do campo
   cru — mesma política do `.cs__grid-fotos`, e pelo mesmo motivo: o valor
   entra num `style` e um `select` do painel é promessa, não garantia.
   O padrão continua 16:10, que era a proporção fixa do módulo. */
.cs__rail img {
  width: 100%;
  height: auto;
  aspect-ratio: var(--proporcao, 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--topicos
   A lista agrupada quando o que importa é a CONTA.

   `lista` continua certa para "o que tem dentro": rótulo curto,
   itens em marcador, colunas que o leitor varre sem contar nada.
   Este módulo responde outra pergunta - quantos, e quais. Um
   escopo entregue, um inventário de requisitos, um checklist de
   projeto: coisas em que o número é o argumento, e em que cada
   item precisa de endereço para ser citado depois.

   O TOTAL É CONTADO, E NÃO DIGITADO. Não há campo para escrevê-lo.
   Um "20" digitado no topo é um número que envelhece na primeira
   vez que alguém acrescenta um item e não volta lá em cima - e
   envelhece em SILÊNCIO, porque nada no sistema sabe que aqueles
   dois campos eram para bater. Contado, ele não tem como discordar
   da lista logo abaixo dele.

   A NUMERAÇÃO DOS ITENS É CONTÍNUA entre os grupos, e é ela que
   fecha com o contador. Reiniciando a cada grupo, o último item de
   uma lista de vinte seria "03", e quem quisesse conferir os vinte
   teria de somar cinco listas de cabeça.

   O CROMO É PRETO. A referência que originou o módulo trazia o
   numeral do grupo em azul e o resumo em verde-oliva; aqui os dois
   são preto e cinza. Não é descuido: a página de case decidiu que
   filete, olho, numeral e rótulo são preto sobre papel, e que a cor
   da página vem do trabalho mostrado - o porquê está no comentário
   do `--acento`, lá em cima. Um módulo novo com paleta própria
   seria a sexta cor que não é do Grupo Dls/ nem do cliente.
   ============================================================ */
.cs__topicos-topo {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--sp-5) var(--sp-7);
  align-items: end;
  padding-bottom: var(--sp-6);
}

.cs__topicos-titulo {
  font-weight: 300;
  font-size: var(--fs-display-m);
  line-height: 1.06;
  letter-spacing: var(--tr-display);
  max-width: 18ch;
}

/* O número e a legenda alinham pela ESQUERDA um do outro, e é o PAR que
   vai para a direita da linha. Alinhados pela direita, o numeral de dois
   dígitos e a legenda de trinta caracteres não teriam borda comum
   nenhuma: a legenda é sempre a mais larga, e o número ficaria pendurado
   no canto sem nada com que registrar.

   Sem manchete o contador cai sozinho na primeira coluna, à esquerda, e
   está certo assim - empurrá-lo para a direita deixaria um vão de
   largura arbitrária à esquerda de um número solto. */
.cs__topicos-conta {
  display: grid;
  gap: var(--sp-2);
  justify-items: start;
  align-content: end;
}

.cs__topicos-conta 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__topicos-conta span {
  font-size: var(--fs-label);
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--fg-muted);
  /* Teto em pixel, e nao em `ch`. A unidade mede a largura do zero na
     fonte e nao sabe nada de `letter-spacing`: com os 0,14em desta
     legenda, cada caractere ocupa uns 25%% a mais do que o `ch` promete,
     e 22ch - que deveriam caber 22 letras - couberam 17. Medido: a
     coluna nasceu com 153px e quebrou um rotulo de vinte caracteres em
     duas linhas. 240px e a largura de uma legenda de umas trinta letras
     em uma linha; passando disso, quebrar esta certo. */
  max-width: 240px;
}

.cs__topicos {
  display: grid;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* O FILETE VAI NO TOPO DE CADA GRUPO, e não embaixo: assim o primeiro
   fecha o cabeçalho, cada um dos seguintes separa dois grupos, e a lista
   não termina com uma linha solta anunciando um grupo que não vem. */
.cs__topico {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: var(--sp-5) var(--sp-7);
  padding-block: var(--sp-7) var(--sp-6);
  border-top: 1px solid var(--line);
}

.cs__topico-cabeca {
  display: grid;
  gap: var(--sp-3);
  align-content: start;
}

.cs__topico-num {
  font-size: var(--fs-label);
  font-weight: 500;
  line-height: 1;
  letter-spacing: var(--tr-label);
  font-variant-numeric: tabular-nums;
  color: var(--acento);
}

.cs__topico-titulo {
  font-size: var(--fs-title);
  font-weight: 500;
  line-height: 1.2;
  max-width: 20ch;
}

.cs__topico-resumo {
  font-size: var(--fs-small);
  line-height: 1.45;
  color: var(--fg-muted);
  max-width: 32ch;
}

/* Duas colunas de itens em UMA GRADE, e não duas listas lado a lado. A
   grade dá fileiras, e é a fileira que faz o filete de um item de uma
   linha terminar na mesma altura do filete do item de três que está ao
   lado dele. Em duas listas independentes cada coluna andaria no seu
   passo, e o que se leria na página seria o desencontro dos filetes. */
.cs__topico-itens {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 var(--sp-6);
  align-content: start;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* `baseline` e não `start`: o número é menor que o texto, e alinhados
   pelo topo da caixa as duas primeiras linhas sairiam com alturas de
   letra diferentes na mesma altura de caixa. Pela linha de base elas se
   assentam onde o olho espera. */
.cs__topico-itens li {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--sp-4);
  align-items: baseline;
  padding-block: var(--sp-3);
  border-bottom: 1px solid var(--line);
}

.cs__topico-item-num {
  font-size: var(--fs-small);
  line-height: 1.45;
  font-variant-numeric: tabular-nums;
  color: var(--fg-muted);
}

.cs__topico-item-texto {
  font-size: var(--fs-body);
  line-height: 1.45;
}
/* ============================================================
   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%;
  }

  /* O cabeçalho do grupo sobe para cima dos itens. A coluna de 5fr, que
     em 1280 tem 380px, em 900 teria 260px - largura em que um título de
     duas palavras já quebra em duas linhas e o resumo em quatro. */
  .cs__topicos-topo,
  .cs__topico {
    grid-template-columns: 1fr;
  }

  .cs__topicos-topo {
    align-items: start;
  }

  .cs__topico {
    padding-block: var(--sp-6) var(--sp-5);
  }
  /* 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.

     Só que esta faixa de 0 a 900px atende telefone E tablet, e 82vw
     significam coisas diferentes nos dois: 307px num telefone de 375,
     738px num tablet de 900. Em 16:10 tanto faz; em 4:5 o tablet daria
     922px de altura. Por isso a proporção também traz o seu valor de
     tela pequena, e ele vem com teto em px além do vw. */
  .cs__rail,
  .cs--galeria--grande .cs__rail {
    grid-auto-columns: var(--cartao-mob, 82vw);
  }
}

@media (max-width: 560px) {
  .case-hero__ficha,
  .cs__numeros,
  .cs__grid-fotos {
    grid-template-columns: 1fr;
  }

  .cs__premios {
    --colunas: 1;
  }

  /* Duas colunas de item a 560px dariam 250px cada, e metade dos itens
     quebraria em três linhas para caber - a lista deixaria de ser
     varrível, que é a única coisa que ela tem de ser. */
  .cs__topico-itens {
    grid-template-columns: 1fr;
  }

  /* 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);
  }
}
