/* ==========================================================================
   Daniel Flores — Home
   Depende de tokens/colors_and_type.css, que deve ser carregado antes.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Base
   -------------------------------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  background: var(--bg-canvas);
}

body {
  margin: 0;
  background: var(--bg-canvas);
  color: var(--text-primary);
  font-family: var(--font-core);
  font-size: var(--size-md);
  font-weight: var(--fw-regular);
  line-height: var(--lh-body);
  overflow-x: hidden;
}

p { margin: 0; text-wrap: pretty; }

a { color: inherit; text-decoration: none; }

button {
  font: inherit; color: inherit;
  background: none; border: 0; padding: 0;
  cursor: pointer;
}

::selection { background: var(--green-sage); color: var(--ink-950); }

:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 4px;
}

/* Lenis */
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }
.lenis.lenis-stopped { overflow: hidden; }

/* Margem editorial: o mesmo respiro lateral em toda a página */
:root {
  --margem: clamp(20px, 5vw, 96px);
  --rotulo: var(--size-3xs);
}

/* --------------------------------------------------------------------------
   2. Rótulos — a única voz tipográfica secundária da home
   -------------------------------------------------------------------------- */

.rotulo,
.nav__item,
.bloco__nome,
.bloco__acao,
.rodape__linha,
.busca__rotulo,
.busca__fechar,
.abertura__pular {
  font-size: var(--rotulo);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  line-height: 1;
}

/* --------------------------------------------------------------------------
   3. Cabeçalho
   -------------------------------------------------------------------------- */

/* Fixa e permanente: uma vez que entra, não sai mais. O véu abaixo é o que
   garante a legibilidade quando um bloco escuro passa por baixo. */
.cabecalho {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  padding: var(--space-5) var(--margem);
  transition: opacity var(--dur-base) var(--ease-out);
  mix-blend-mode: normal;
}

.cabecalho::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, var(--bg-canvas) 30%, transparent);
  opacity: 0;
  transition: opacity var(--dur-slow) var(--ease-out);
  pointer-events: none;
  z-index: -1;
}

.cabecalho[data-fundo="sim"]::before { opacity: 1; }

/* Lockup horizontal do sistema de design, escala 35.
   Toda a métrica desce de --marca-u (= size / 5); nenhum valor é solto.
   O módulo precisa ser INTEIRO. Com 6,8 px cada quadrado era posicionado em
   múltiplos fracionários e o navegador arredondava cada borda sozinho — no
   estado fechado sobrava um fio de fundo entre os módulos. 35 / 5 = 7. */
.cabecalho__marca {
  --marca-u: 7px;
  display: inline-flex;
  align-items: flex-start;
  gap: calc(var(--marca-u) * 0.383);
}

/* Sem preenchimento: qualquer folga aqui entraria na métrica do lockup.
   A área de toque vem do ::after, que não ocupa espaço. */
.cabecalho__simbolo {
  position: relative;
  display: block;
  color: var(--text-primary);
}

.cabecalho__simbolo::after {
  content: "";
  position: absolute;
  inset: calc(var(--space-2) * -1);
}

/* Wordmark: duas linhas, recuado para que a linha de base de FLORES caia na
   base do símbolo — 3.879u e 4.984u contra os 5u de altura da caixa. */
.cabecalho__nome {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  margin-top: calc(var(--marca-u) * 2.939);
  font-family: var(--font-core);
  font-size: calc(var(--marca-u) * 1.1055);
  font-weight: var(--fw-extrabold);
  letter-spacing: -0.02em;
  text-transform: uppercase;
  line-height: 1;
  transition: color var(--dur-base) var(--ease-puzzle);
}

/* O wordmark não recebe verde no hover: no sistema, símbolo e wordmark herdam
   sempre a mesma cor, e acender só metade do lockup quebraria isso. Recua um
   tom, e o recuo já diz que é clicável. */
.cabecalho__nome:hover { color: var(--text-secondary); }

/* O símbolo, montado por JS a partir da geometria do PixelMark */
.marca {
  position: relative;
  display: block;
  width: calc(var(--marca-u) * 3);
  height: calc(var(--marca-u) * 5);
}

.marca i {
  position: absolute;
  width: var(--marca-u);
  height: var(--marca-u);
  background: currentColor;
  transition: transform var(--dur-slide) var(--ease-puzzle);
}

/* Ao passar o mouse, as três peças voltam para a posição fechada: a marca se
   recolhe no quadrado. É a própria animação de abertura, ao contrário, em
   miniatura — e anuncia o que o clique faz. */
.cabecalho__simbolo:hover .marca i,
.cabecalho__simbolo:focus-visible .marca i {
  transform: translate(var(--dx, 0), var(--dy, 0));
}

.cabecalho__simbolo:disabled { cursor: default; }
.cabecalho__simbolo:disabled .marca i { transform: none; }

.cabecalho__nav {
  display: flex;
  align-items: center;
  gap: var(--space-5);
}

.nav__item {
  position: relative;
  font-weight: var(--fw-medium);
  color: var(--text-secondary);
  padding-bottom: 2px;
  transition: color var(--dur-fast) var(--ease-puzzle);
}

/* A lupa é o único item da barra que não existe nas duas páginas: a busca de
   hoje filtra as classificações da home. Sem recolhimento ela desaparecia no
   quadro da troca de documento — a barra é permanente, mas um detalhe dentro
   dela não pode piscar fora. Recolhe em escala, como as tags de projetos, e
   volta pelo mesmo gesto. A duração é escrita por js/site.js, a partir do
   objeto LUPA. */
.nav__item--icone {
  transform-origin: 50% 50%;
  transition: transform var(--dur-slide) var(--ease-puzzle),
              color var(--dur-fast) var(--ease-puzzle);
}

/* Só a LUPA recolhe, não todo ícone da barra.

   A regra era `.nav__item--icone` e passou a colher também o botão de tema,
   que existe nas três páginas e é permanente como o resto da barra — ele
   sumiria junto na troca de documento, sem nunca voltar. A lupa é a exceção
   porque a busca de hoje só existe na home; ver a seção 6e do handoff. */
html[data-lupa="recolhida"] [data-acao="abrir-busca"] {
  transform: scale(0);
  pointer-events: none;
}

.nav__item::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform var(--dur-base) var(--ease-puzzle);
}

.nav__item:hover { color: var(--text-primary); }
.nav__item:hover::after { transform: scaleX(1); }

/* Gatilho da busca: ícone, não palavra. O sublinhado dos itens de texto não
   serve a um ícone — o estado vem da cor, e do aria-expanded quando aberto. */
.nav__item--icone {
  display: inline-flex;
  align-items: center;
  padding-bottom: 0;
}

.nav__item--icone::after { display: none; }
.nav__item--icone[aria-expanded="true"] { color: var(--text-primary); }
.nav__item--icone .icone { display: block; }

/* --------------------------------------------------------------------------
   4. Busca
   -------------------------------------------------------------------------- */

/* O painel desce por trás da própria borda, com a curva do quebra-cabeça —
   a mesma do símbolo. O `hidden` continua existindo, mas só é reposto quando
   a saída termina; quem decide isso é o transitionend, em js/site.js, para não
   repetir em JS a duração que já está aqui. */
.busca {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 50;
  background: var(--bg-canvas);
  border-bottom: 1px solid var(--border-strong);
  padding: var(--space-6) var(--margem) var(--space-5);
  transform: translate3d(0, -100%, 0);
  transition: transform var(--dur-slow) var(--ease-puzzle);
}

.busca[data-aberta="sim"] { transform: none; }

.busca[hidden] { display: none; }

/* O conteúdo acende depois que o painel já está descendo: primeiro o plano,
   depois o que está escrito nele. */
.busca__interior {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: end;
  gap: var(--space-5);
  column-gap: 0;
  max-width: var(--container-xl);
  margin: 0 auto;
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease-out);
}

.busca[data-aberta="sim"] .busca__interior {
  opacity: 1;
  transition-delay: 120ms;
}

.busca__rotulo {
  grid-column: 1 / -1;
  font-weight: var(--fw-medium);
  color: var(--text-muted);
  margin-bottom: var(--space-3);
}

.busca__campo {
  width: 100%;
  border: 0;
  border-bottom: 1px solid var(--border-strong);
  background: transparent;
  padding: 0 0 var(--space-3);
  font-family: var(--font-core);
  font-size: clamp(24px, 4vw, var(--size-3xl));
  font-weight: var(--fw-semibold);
  letter-spacing: var(--track-heading);
  color: var(--text-primary);
}

.busca__campo::placeholder { color: var(--ink-300); }
.busca__campo:focus { outline: none; border-bottom-color: var(--accent); }
.busca__campo::-webkit-search-cancel-button { display: none; }

/* FECHAR fica sobre a mesma linha do campo: mesma borda, mesmo recuo até ela,
   e a coluna sem vão — a régua atravessa a largura inteira, sem emenda. */
.busca__fechar {
  font-weight: var(--fw-medium);
  color: var(--text-secondary);
  border-bottom: 1px solid var(--border-strong);
  padding: 0 0 var(--space-3) var(--space-5);
  white-space: nowrap;
  transition: color var(--dur-base) var(--ease-puzzle),
              border-color var(--dur-base) var(--ease-puzzle);
}

.busca__fechar:hover { border-bottom-color: var(--accent); }

.busca__fechar:hover { color: var(--text-primary); }

.busca__vazio {
  max-width: var(--container-xl);
  margin: var(--space-4) auto 0;
  font-family: var(--font-mono);
  font-weight: var(--fw-light);
  font-size: var(--size-xs);
  color: var(--text-muted);
}

.busca__vazio[hidden] { display: none; }

/* --------------------------------------------------------------------------
   5. Página e grid editorial
   -------------------------------------------------------------------------- */

.pagina { position: relative; z-index: 10; }

/* Rede de segurança para o salto nativo: sem JavaScript, ou se o clique
   escapar do interceptador, a âncora ainda para abaixo da barra fixa em vez
   de esconder o título por trás dela. */
.sobre,
.contato { scroll-margin-top: 120px; }

.blocos {
  display: grid;
  grid-template-columns: repeat(var(--grid-columns), 1fr);
  column-gap: var(--gutter);
  row-gap: clamp(160px, 28vh, 360px);
  align-items: start;
  padding: 0 var(--margem);
  max-width: var(--container-xl);
  margin: 0 auto;
  /* Primeira dobra: o bloco 01 nasce onde o símbolo terminou */
  padding-top: 24vh;
}

/* Coluna, largura e linha são explícitas. Sem a linha, o navegador empacota
   os blocos sozinho e a composição muda conforme a largura da tela — o que
   não serve para uma página que é, antes de tudo, diagramação. */
.bloco {
  grid-column: var(--col) / span var(--span);
  grid-row: var(--linha);
  margin-top: var(--folga);
}

.bloco__media {
  position: relative;
  display: block;
  width: 100%;
  overflow: hidden;
  background: var(--cor);
  /* O reveal é geométrico: o bloco abre de baixo para cima, sem deslocar layout.
     A curva é a do quebra-cabeça, não ease-out — a expo cobre 90% do percurso
     nos primeiros 15% do tempo e o bloco parece estalar aberto. Mesma razão
     documentada para a decomposição. */
  clip-path: inset(100% 0 0 0);
  transition: clip-path var(--dur-hold) var(--ease-puzzle);
}

.bloco[data-formato="vertical"] .bloco__media { aspect-ratio: 4 / 5; }
.bloco[data-formato="horizontal"] .bloco__media { aspect-ratio: 3 / 2; }

.bloco[data-visivel="sim"] .bloco__media { clip-path: inset(0 0 0 0); }

/* Camada de parallax. Hoje carrega a trama de módulos da marca;
   na versão com fotografia, vira a <img> e o parallax continua idêntico. */
.bloco__camada {
  position: absolute;
  inset: -12% 0;
  will-change: transform;
}

/* Trama de módulos, no limite do perceptível. Existe por um motivo funcional:
   parallax sobre cor chapada é invisível. Sai numa linha quando a fotografia
   entrar. */
.bloco__trama {
  position: absolute;
  inset: 0;
  opacity: .05;
  background-image:
    linear-gradient(to right, currentColor 1px, transparent 1px),
    linear-gradient(to bottom, currentColor 1px, transparent 1px);
  background-size: 25% 20%;
  color: var(--ink-000);
}

.bloco[data-tom="claro"] .bloco__trama { color: var(--ink-950); opacity: .07; }

/* Linha de legenda: classificação + número à esquerda, ação à direita.
   A entrada NÃO acontece na linha inteira de uma vez. Cada parte sobe por
   conta própria, em fila — número, nome, ação — enquanto o bloco ainda está
   abrindo. Uma linha que acende inteira num quadro é o que se lia como
   "aparece de repente", mesmo tendo transição. */
.bloco__meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-5);
  padding-top: var(--space-4);
}

.bloco__numero,
.bloco__nome,
.bloco__acao {
  opacity: 0;
  transform: translate3d(0, var(--space-3), 0);
  transition: opacity var(--dur-slide) var(--ease-puzzle),
              transform var(--dur-slide) var(--ease-puzzle);
}

/* O `:not([data-abertura="rodando"])` é o que faz a fila ser vista.
   `data-visivel` NÃO serve sozinho como gatilho: ele é posto durante a
   decomposição, porque o módulo precisa pousar num bloco já aceso — ou seja,
   uns dois segundos e meio antes de o overlay sair. Sem esta condição a fila
   inteira roda atrás de um painel opaco e, quando o painel some, a legenda já
   está pronta e simplesmente aparece.
   A fila começa com o bloco já aberto pela metade: o texto sobe enquanto a cor
   ainda cresce, e as duas coisas terminam juntas. */
html:not([data-abertura="rodando"]) .bloco[data-visivel="sim"] .bloco__numero { transition-delay: 260ms; }
html:not([data-abertura="rodando"]) .bloco[data-visivel="sim"] .bloco__nome   { transition-delay: 350ms; }

/* A ação NÃO usa `transition-delay`, e é o único caso.

   `transition-delay` vale para TODAS as propriedades transicionadas, e a ação
   é a única das três que transiciona `color` — por causa do hover. O atraso da
   fila de entrada acabava valendo também para a cor: trocando de tema, a
   legenda "Ver marcas" só começava a mudar 440 ms depois, e levava mais 220 ms
   para chegar. Meio segundo de atraso num gesto que devia ser imediato.

   Aqui o atraso vai escrito ao lado de cada propriedade, dentro do atalho.
   Fica imune à ordem da lista, que uma lista de três valores em
   `transition-delay` não ficaria. */
html:not([data-abertura="rodando"]) .bloco[data-visivel="sim"] .bloco__acao {
  transition: opacity var(--dur-slide) var(--ease-puzzle) 440ms,
              transform var(--dur-slide) var(--ease-puzzle) 440ms,
              color var(--dur-base) var(--ease-puzzle);
}

html:not([data-abertura="rodando"]) .bloco[data-visivel="sim"] .bloco__numero,
html:not([data-abertura="rodando"]) .bloco[data-visivel="sim"] .bloco__nome,
html:not([data-abertura="rodando"]) .bloco[data-visivel="sim"] .bloco__acao {
  opacity: 1;
  transform: none;
}

.bloco__identificacao {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  min-width: 0;
}

.bloco__numero {
  font-family: var(--font-mono);
  font-weight: var(--fw-light);
  font-size: var(--rotulo);
  letter-spacing: var(--track-label);
  color: var(--accent);
  flex: none;
}

.bloco__nome {
  position: relative;
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
}

/* Sublinhado que cresce da esquerda — o mesmo gesto dos itens do cabeçalho.
   Fica no ::after para não somar altura à linha de base. */
.bloco__nome::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -3px;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform var(--dur-base) var(--ease-puzzle);
}

/* A entrada e o hover convivem: opacidade e deslocamento vêm da fila acima,
   a cor responde ao ponteiro. Declarar tudo junto evita que a segunda regra
   apague a primeira — foi isso que aconteceu na primeira tentativa. */
.bloco__acao {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-mono);
  font-weight: var(--fw-light);
  color: var(--text-secondary);
  white-space: nowrap;
  flex: none;
  transition: opacity var(--dur-slide) var(--ease-puzzle),
              transform var(--dur-slide) var(--ease-puzzle),
              color var(--dur-base) var(--ease-puzzle);
}

.bloco__acao span {
  display: block;
  width: 4px; height: 4px;
  background: currentColor;
  transition: transform var(--dur-base) var(--ease-puzzle);
}

.bloco__link { display: block; }
.bloco__link:hover .bloco__nome::after { transform: scaleX(1); }
.bloco__link:hover .bloco__acao { color: var(--text-accent); }
.bloco__link:hover .bloco__acao span { transform: translate3d(6px, 0, 0); }

/* A trama cresce de leve sob o ponteiro. Hoje quase não se vê, porque ela vive
   a 5% de opacidade — mas é a mesma regra que vai mover a fotografia quando
   ela entrar, e por isso já fica escrita. O parallax não é tocado: ele mora em
   .bloco__camada e é escrito por JS a cada quadro. */
.bloco__trama {
  transform-origin: 50% 50%;
  transition: transform var(--dur-hold) var(--ease-puzzle);
}

.bloco__link:hover .bloco__trama { transform: scale(1.04); }

/* Atenuação durante a busca */
.bloco[data-atenuado="sim"] {
  opacity: .12;
  transition: opacity var(--dur-base) var(--ease-out);
}

/* --------------------------------------------------------------------------
   5b. Sobre
   Mesma grade de 12 colunas dos blocos, mesma margem, mesmo contêiner: a
   seção não é uma página colada no fim, é a última linha da diagramação.
   Título em 1–3, prosa em 5–8, listas em 10–12.
   -------------------------------------------------------------------------- */

.sobre {
  display: grid;
  grid-template-columns: repeat(var(--grid-columns), 1fr);
  column-gap: var(--gutter);
  align-items: start;
  padding: clamp(180px, 26vh, 320px) var(--margem) 0;
  max-width: var(--container-xl);
  margin: 0 auto;
}

/* A fila de entrada. --passo é a posição do elemento, escrita por JS; o
   intervalo mora aqui, num lugar só. Acrescentar uma lista em js/sobre.js não
   exige tocar em tempo nenhum. */
.sobre__cabeca,
.sobre__paragrafo,
.sobre__grupo {
  opacity: 0;
  transform: translate3d(0, var(--space-4), 0);
  transition: opacity var(--dur-slide) var(--ease-puzzle),
              transform var(--dur-slide) var(--ease-puzzle);
}

.sobre[data-visivel="sim"] .sobre__cabeca,
.sobre[data-visivel="sim"] .sobre__paragrafo,
.sobre[data-visivel="sim"] .sobre__grupo {
  opacity: 1;
  transform: none;
  transition-delay: calc(var(--passo, 0) * 70ms);
}

/* Coluna 1 — número e título */
.sobre__cabeca {
  grid-column: 1 / span 3;
  display: flex;
  flex-direction: column;
}

.sobre__numero {
  font-family: var(--font-mono);
  font-weight: var(--fw-light);
  font-size: var(--rotulo);
  letter-spacing: var(--track-label);
  color: var(--accent);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--border-strong);
}

.sobre__titulo {
  margin: var(--space-5) 0 0;
  font-family: var(--font-core);
  font-weight: var(--fw-extrabold);
  font-size: clamp(28px, 3.4vw, var(--size-4xl));
  line-height: var(--lh-tight);
  letter-spacing: var(--track-display);
  text-transform: uppercase;
  color: var(--text-primary);
}

/* Coluna 2 — a prosa, em Geist Mono.
   O mono aqui não é enfeite: dá à seção uma voz de ficha técnica e a separa
   dos blocos sem precisar de moldura, borda ou fundo. É o único lugar do site
   onde ele carrega parágrafo. */
.sobre__texto {
  grid-column: 5 / span 3;
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.sobre__paragrafo {
  font-family: var(--font-mono);
  font-weight: var(--fw-light);
  font-size: var(--size-sm);
  line-height: var(--lh-loose);
  letter-spacing: var(--track-mono);
  color: var(--text-secondary);
  max-width: 46ch;
}

/* Coluna 3 — as listas, atrás de um filete.
   Cinco colunas e duas subcolunas: são 22 itens, e numa coluna só eles formam
   uma torre de mais de 1300 px contra a prosa ao lado. A repartição entre as
   duas subcolunas é feita em js/site.js, pela contagem de itens. */
.sobre__lado {
  grid-column: 8 / span 5;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-6);
  padding-left: var(--space-6);
  border-left: 1px solid var(--border-subtle);
  align-self: stretch;
}

.sobre__coluna {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  min-width: 0;
}

/* O rótulo do grupo é a segunda voz da seção, depois do título. Sai do mono e
   vai para Geist extrabold: contra itens de 12 px em mono light, ele precisa
   de peso e de corpo, não só de caixa-alta. A régua embaixo é a mesma que
   fecha o número lá na primeira coluna — a seção inteira usa uma régua só. */
.sobre__rotulo {
  margin: 0 0 var(--space-4);
  padding-bottom: var(--space-2);
  border-bottom: 1px solid var(--border-strong);
  font-family: var(--font-core);
  font-weight: var(--fw-extrabold);
  font-size: var(--size-md);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  line-height: 1;
  color: var(--text-primary);
}

.sobre__lista {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* O marcador é um quadrado de 3 px, não um ponto: é o módulo do símbolo na
   menor escala em que ainda se lê. Mesma família do quadradinho da ação. */
.sobre__item {
  position: relative;
  padding-left: var(--space-4);
  font-family: var(--font-mono);
  letter-spacing: var(--track-mono);
}

.sobre__item::before {
  content: "";
  position: absolute;
  left: 0;
  top: .5em;
  width: 3px; height: 3px;
  background: var(--accent);
}

.sobre__termo {
  display: block;
  font-weight: var(--fw-medium);
  font-size: var(--size-2xs);
  line-height: 1.4;
  color: var(--text-primary);
}

/* O desdobramento do termo. Recua um tom e perde peso: quem varre a lista lê
   só os termos; quem parou num deles encontra o detalhe embaixo. */
.sobre__nota {
  display: block;
  margin-top: 2px;
  font-weight: var(--fw-light);
  font-size: var(--size-3xs);
  line-height: 1.45;
  color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   6. Rodapé
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   5c. Contato
   Fecha a página com uma frase, não com um formulário. Sem número e sem as
   três zonas da seção Sobre — duas seções iguais seguidas achatam o fim.
   -------------------------------------------------------------------------- */

.contato {
  position: relative;
  display: grid;
  grid-template-columns: repeat(var(--grid-columns), 1fr);
  column-gap: var(--gutter);
  padding: clamp(140px, 20vh, 240px) var(--margem) 0;
  max-width: var(--container-xl);
  margin: 0 auto;
}

/* Mesmo filete recuado do rodapé: a régua da página é uma só. */
.contato::before {
  content: "";
  position: absolute;
  top: clamp(90px, 13vh, 150px);
  left: var(--margem);
  right: var(--margem);
  height: 1px;
  background: var(--border-subtle);
}

.contato__chamada,
.contato__email,
.contato__linha,
.contato__form {
  opacity: 0;
  transform: translate3d(0, var(--space-4), 0);
  transition: opacity var(--dur-slide) var(--ease-puzzle),
              transform var(--dur-slide) var(--ease-puzzle);
}

.contato[data-visivel="sim"] .contato__chamada,
.contato[data-visivel="sim"] .contato__email,
.contato[data-visivel="sim"] .contato__linha,
.contato[data-visivel="sim"] .contato__form {
  opacity: 1;
  transform: none;
  transition-delay: calc(var(--passo, 0) * 90ms);
}

/* Coluna explícita, como nos blocos. A chamada e o formulário dividem a linha:
   o convite à esquerda, o lugar de responder à direita. */
.contato__intro {
  grid-column: 1 / span 5;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.contato__chamada {
  margin: 0;
  font-family: var(--font-core);
  font-weight: var(--fw-extrabold);
  font-size: clamp(32px, 4.6vw, var(--size-5xl));
  line-height: var(--lh-tight);
  letter-spacing: var(--track-display);
  text-transform: uppercase;
  color: var(--text-primary);
}

/* O endereço é o segundo maior corpo da página, atrás só da chamada. Ele é o
   assunto do bloco — escondê-lo num rótulo de 11 px seria contradizer a razão
   da seção existir. */
.contato__email {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-7);
  font-family: var(--font-mono);
  font-weight: var(--fw-light);
  font-size: var(--size-sm);
  letter-spacing: var(--track-mono);
  color: var(--text-primary);
}

.contato__endereco {
  position: relative;
  padding-bottom: var(--space-2);
}

/* A régua embaixo do endereço já nasce inteira e some da direita ao passar o
   ponteiro — o contrário do sublinhado que cresce nos itens do menu. Aqui o
   link é o assunto, não uma opção entre outras: ele não precisa se anunciar,
   precisa reagir. */
.contato__endereco::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1px;
  background: currentColor;
  transform-origin: 100% 50%;
  transition: transform var(--dur-base) var(--ease-puzzle);
}

.contato__marcador {
  display: block;
  width: 5px; height: 5px;
  background: currentColor;
  flex: none;
  transition: transform var(--dur-base) var(--ease-puzzle);
}

.contato__email:hover { color: var(--text-accent); }
.contato__email:hover .contato__endereco::after { transform: scaleX(0); }
.contato__email:hover .contato__marcador { transform: translate3d(6px, 0, 0); }

.contato__linha {
  margin: var(--space-5) 0 0;
  max-width: 34ch;
  font-family: var(--font-mono);
  font-weight: var(--fw-light);
  font-size: var(--size-2xs);
  line-height: var(--lh-loose);
  letter-spacing: var(--track-mono);
  color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   Formulário
   Os campos são réguas, não caixas: a mesma construção do campo de busca —
   sem borda em volta, só a linha de baixo. Caixa com contorno de quatro lados
   é vocabulário de formulário administrativo, e não existe em nenhum outro
   ponto deste site.
   -------------------------------------------------------------------------- */

.contato__form {
  grid-column: 7 / span 6;
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

/* Fora da tela, fora da tabulação e fora do leitor de tela. Só robô preenche. */
.contato__isca {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.campo {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.campo__rotulo {
  font-family: var(--font-mono);
  font-weight: var(--fw-medium);
  font-size: var(--rotulo);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  line-height: 1;
  color: var(--text-muted);
  transition: color var(--dur-base) var(--ease-puzzle);
}

.campo:focus-within .campo__rotulo { color: var(--text-primary); }

.campo__entrada {
  width: 100%;
  border: 0;
  border-bottom: 1px solid var(--border-strong);
  background: transparent;
  padding: 0 0 var(--space-3);
  font-family: var(--font-mono);
  font-weight: var(--fw-light);
  font-size: var(--size-sm);
  line-height: var(--lh-body);
  letter-spacing: var(--track-mono);
  color: var(--text-primary);
  transition: border-color var(--dur-base) var(--ease-puzzle);
}

.campo__entrada--area { resize: vertical; min-height: 6em; }

.campo__entrada::placeholder { color: var(--ink-300); }
.campo__entrada:hover { border-bottom-color: var(--text-secondary); }

/* O anel de foco padrão contornaria um retângulo invisível. Aqui o foco é a
   própria régua acendendo — é onde o olho já está. */
.campo__entrada:focus {
  outline: none;
  border-bottom-color: var(--accent);
}

/* Só depois de mexer no campo. `:invalid` sozinho marcaria de erro um
   formulário que ninguém tocou ainda.
   O sistema de design não tem cor de erro — não inventei uma. O campo com
   problema fica com a régua na tinta cheia, contra a régua discreta dos
   demais: a diferença é de peso, não de cor. Se você quiser um vermelho, ele
   precisa nascer em tokens/colors_and_type.css primeiro. */
.campo__entrada:not(:placeholder-shown):invalid {
  border-bottom-color: var(--text-primary);
  border-bottom-width: 2px;
}

.contato__rodape {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  flex-wrap: wrap;
}

.contato__enviar {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  padding-bottom: var(--space-2);
  border-bottom: 1px solid var(--text-primary);
  font-family: var(--font-mono);
  font-weight: var(--fw-medium);
  font-size: var(--rotulo);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--text-primary);
  transition: color var(--dur-base) var(--ease-puzzle),
              border-color var(--dur-base) var(--ease-puzzle);
}

.contato__enviar:hover {
  color: var(--text-accent);
  border-bottom-color: var(--accent);
}

.contato__enviar:hover .contato__marcador { transform: translate3d(6px, 0, 0); }

.contato__enviar:disabled { color: var(--text-muted); border-bottom-color: var(--border-subtle); cursor: default; }
.contato__enviar:disabled .contato__marcador { transform: none; }

.contato__aviso {
  flex: 1 1 16ch;
  font-family: var(--font-mono);
  font-weight: var(--fw-light);
  font-size: var(--size-2xs);
  line-height: var(--lh-body);
  letter-spacing: var(--track-mono);
  color: var(--text-muted);
}

.contato__aviso[data-tipo="sucesso"] { color: var(--text-accent); }
.contato__aviso[data-tipo="erro"] { color: var(--text-primary); }

/* --------------------------------------------------------------------------
   5d. Projetos — capa, faixa de tags e grade
   Só entra em projetos.html. A home não usa nada daqui.

   O PRINCÍPIO: nada nesta página aparece nem desaparece. Tag e cartão são
   retângulos posicionados de forma absoluta e movidos por `transform`. Trocar
   de filtro é trocar um transform — quem permanece desliza para o novo lugar,
   quem sai encolhe sobre o próprio centro, quem entra cresce a partir do
   lugar onde vai ficar. É o deslize do símbolo, na escala da página.

   POR QUE PISTA E INTERIOR SÃO ELEMENTOS SEPARADOS
   Caixa absoluta se mede contra o padding box do ancestral posicionado, ou
   seja, ignora o padding. Se a pista carregasse a margem editorial, todo
   cartão nasceria colado na borda da tela. O interior segura a margem; a
   pista, só o referencial.
   -------------------------------------------------------------------------- */

/* Capa ------------------------------------------------------------------- */

.capa {
  display: grid;
  grid-template-columns: repeat(var(--grid-columns), 1fr);
  column-gap: var(--gutter);
  align-items: end;
  padding: clamp(180px, 24vh, 300px) var(--margem) var(--space-8);
  max-width: var(--container-xl);
  margin: 0 auto;
}

/* A capa entra em fila, como a legenda dos blocos e as partes do Sobre. A
   duração e o intervalo são escritos por js/grade.js a partir do TEMPO — aqui
   fica só o estado de partida e a curva. */
.capa > * {
  opacity: 0;
  transform: translate3d(0, var(--space-4), 0);
  transition: opacity var(--dur-slide) var(--ease-puzzle),
              transform var(--dur-slide) var(--ease-puzzle);
}

.capa[data-visivel="sim"] > * {
  opacity: 1;
  transform: none;
}

.capa__rotulo {
  grid-column: 1 / span 3;
  grid-row: 1;
  font-family: var(--font-mono);
  font-weight: var(--fw-light);
  font-size: var(--rotulo);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--accent);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--border-strong);
}

.capa__titulo {
  grid-column: 1 / span 6;
  grid-row: 2;
  margin: var(--space-5) 0 0;
  font-weight: var(--fw-extrabold);
  font-size: clamp(38px, 6vw, var(--size-6xl));
  line-height: var(--lh-tight);
  letter-spacing: var(--track-display);
  text-transform: uppercase;
}

.capa__prosa {
  grid-column: 8 / span 5;
  grid-row: 2;
  font-family: var(--font-mono);
  font-weight: var(--fw-light);
  font-size: var(--size-xs);
  line-height: var(--lh-loose);
  letter-spacing: var(--track-mono);
  color: var(--text-secondary);
}

/* Faixa de tags ---------------------------------------------------------- */

/* A faixa NÃO gruda. Ela pertence ao topo da página, junto da capa: uma barra
   de filtro perseguindo o leitor por trinta cartões é vocabulário de painel
   administrativo, e este site não tem nenhum. Quem quiser trocar de território
   volta ao topo, que é de onde a página se lê. */
.faixa {
  background: var(--bg-canvas);
  border-bottom: 1px solid var(--border-subtle);
  transition: opacity var(--dur-slide) var(--ease-puzzle);
}

/* O flash de entrada. O navegador pinta o HTML antes de o JavaScript rodar, e
   o que está escrito à mão — a régua da faixa, o rodapé — aparecia de uma vez
   no instante em que a cortina da página anterior saía, antes de qualquer
   animação começar. Aqui nascem apagados; js/grade.js troca o atributo para
   "pronto" na primeira disposição. Mesmo princípio das regras de
   html[data-abertura="rodando"] na home, e com a mesma armadilha: sem
   JavaScript ninguém troca o atributo, e o <noscript> de projetos.html
   desfaz. */
html[data-grade="carregando"] .faixa,
html[data-grade="carregando"] .rodape { opacity: 0; }

.faixa__interior {
  max-width: var(--container-xl);
  margin: 0 auto;
  padding: var(--space-5) var(--margem);
}

/* Os vãos moram no CSS e são lidos por js/grade.js a cada medição — espaço
   entre elementos é diagramação, e diagramação não se escreve em JavaScript. */
.faixa__pista {
  --vao: var(--space-3);
  --vao-linha: var(--space-3);
  position: relative;
  transition: height var(--dur-slide) var(--ease-puzzle);
}

/* Durante o redimensionamento as posições são reescritas sem percurso:
   animar um recálculo de largura não é movimento, é tremor. */
.faixa__pista[data-quieto="sim"] .tag,
.faixa__pista[data-quieto="sim"] .faixa__risco,
.grade__pista[data-quieto="sim"] .cartao { transition: none !important; }

.faixa__pista[data-quieto="sim"],
.grade__pista[data-quieto="sim"] { transition: none !important; }

/* A tag é um retângulo, não uma pílula: o vocabulário desta marca é reto.
   `transform-origin` no centro é o que faz o colapso parecer recolhimento e
   não sucção para o canto — o translate posiciona a quina, a escala respeita
   o meio. */
.tag {
  position: absolute;
  left: 0;
  top: 0;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--border-subtle);
  background: var(--bg-canvas);
  color: var(--text-secondary);
  font-size: var(--rotulo);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  line-height: 1;
  white-space: nowrap;
  transform-origin: 50% 50%;
  transition: transform var(--dur-slide) var(--ease-puzzle),
              color var(--dur-base) var(--ease-puzzle),
              background var(--dur-base) var(--ease-puzzle),
              border-color var(--dur-base) var(--ease-puzzle);
}

.tag:hover { color: var(--text-primary); border-color: var(--border-default); }

.tag[data-estado="fora"] { pointer-events: none; }

.tag[data-ativo="sim"] {
  background: var(--bg-inverse);
  border-color: var(--bg-inverse);
  color: var(--text-inverse);
}

/* O número da classificação vem junto e recua um tom: identifica sem
   competir com o nome. Na tag ativa ele acende no verde. */
.tag__numero {
  font-family: var(--font-mono);
  font-weight: var(--fw-light);
  color: var(--text-muted);
  transition: color var(--dur-base) var(--ease-puzzle);
}

.tag[data-ativo="sim"] .tag__numero { color: var(--accent); }

/* O risco separa as principais das filhas. É um elemento de layout como
   qualquer outro: entra, sai e desliza com as tags, senão a hierarquia
   apareceria de repente no meio de uma faixa em movimento. */
.faixa__risco {
  position: absolute;
  left: 0;
  top: 0;
  width: 1px;
  background: var(--border-default);
  transform-origin: 50% 50%;
  transition: transform var(--dur-slide) var(--ease-puzzle);
}

/* Grade ------------------------------------------------------------------ */

.grade {
  max-width: var(--container-xl);
  margin: 0 auto;
  padding: var(--space-8) var(--margem) 0;
}

/* O número de colunas mora no CSS, não no JavaScript: é diagramação, e
   diagramação se lê em media query. js/grade.js apenas consulta esta
   variável a cada medição. */
.grade__pista {
  --colunas: 4;
  --vao: var(--gutter);
  /* O vão vertical é maior que o horizontal de propósito: a legenda de cada
     cartão precisa pertencer ao cartão de cima, não flutuar entre dois.
     Valor literal, não calc(): o JavaScript lê esta variável com parseFloat,
     e o valor computado de uma custom property preserva o calc() sem
     resolvê-lo — "calc(24px * 1.75)" não vira número. */
  --vao-v: 42px;
  position: relative;
  transition: height var(--dur-hold) var(--ease-puzzle);
}

.cartao {
  position: absolute;
  left: 0;
  top: 0;
  transform-origin: 50% 50%;
  transition: transform var(--dur-hold) var(--ease-puzzle);
}

.cartao[data-estado="fora"] { pointer-events: none; }

.cartao__link { display: block; }

/* A cor é a da casa do projeto enquanto nenhum filtro está ativo. Com um
   território escolhido, todos os cartões convergem para a cor dele durante a
   viagem — o mesmo gesto da decomposição, que interpola a cor do módulo até a
   cor do bloco em pleno voo. */
/* `display: block` não é redundante: a mídia é um <span>, e `aspect-ratio`
   não se aplica a caixa inline não substituída. Sem esta linha o cartão fica
   com altura zero e a cor não aparece — que foi exatamente o que aconteceu. */
.cartao__media {
  display: block;
  position: relative;
  overflow: hidden;
  aspect-ratio: var(--ar);
  background: var(--cor);
  transition: background var(--dur-hold) var(--ease-puzzle);
}

/* Camada de parallax, idêntica à do bloco da home — inclusive na sobra de 12%
   em cima e embaixo, que é o que dá curso ao deslocamento sem descobrir a
   borda. O transform dela é escrito a cada quadro por js/site.js e por isso
   não leva transição: uma transição aqui arrastaria o parallax meio segundo
   atrás do scroll. */
.cartao__camada {
  position: absolute;
  inset: -12% 0;
  will-change: transform;
}

/* Mesma trama dos blocos da home, pela mesma razão: cor chapada não deixa
   avaliar movimento. Sai numa linha quando a fotografia entrar. */
.cartao__trama {
  position: absolute;
  inset: 0;
  opacity: .05;
  background-image:
    linear-gradient(to right, currentColor 1px, transparent 1px),
    linear-gradient(to bottom, currentColor 1px, transparent 1px);
  background-size: 25% 25%;
  color: var(--ink-000);
  transform-origin: 50% 50%;
  transition: transform var(--dur-hold) var(--ease-puzzle);
}

.cartao[data-tom="claro"] .cartao__trama { color: var(--ink-950); opacity: .07; }

.cartao__link:hover .cartao__trama { transform: scale(1.04); }

.cartao__meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  padding-top: var(--space-3);
}

.cartao__titulo {
  position: relative;
  margin: 0;
  font-size: var(--size-xs);
  font-weight: var(--fw-semibold);
  line-height: 1.3;
  color: var(--text-primary);
}

.cartao__titulo::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -3px;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform var(--dur-base) var(--ease-puzzle);
}

.cartao__link:hover .cartao__titulo::after { transform: scaleX(1); }

.cartao__ano {
  flex: none;
  font-family: var(--font-mono);
  font-weight: var(--fw-light);
  font-size: var(--rotulo);
  letter-spacing: var(--track-label);
  color: var(--text-muted);
}

/* Vazio, estado e ver mais ------------------------------------------------ */

.grade__vazio {
  padding: var(--space-9) var(--margem);
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--size-xs);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--text-muted);
}

.grade__rodape {
  display: flex;
  justify-content: center;
  padding: var(--space-8) var(--margem) 0;
}

.grade__mais {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-6);
  border: 1px solid var(--border-strong);
  font-size: var(--rotulo);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  line-height: 1;
  color: var(--text-primary);
  transition: color var(--dur-base) var(--ease-puzzle),
              background var(--dur-base) var(--ease-puzzle),
              border-color var(--dur-base) var(--ease-puzzle);
}

.grade__mais span {
  display: block;
  width: 4px; height: 4px;
  background: currentColor;
  transition: transform var(--dur-base) var(--ease-puzzle);
}

.grade__mais:hover {
  background: var(--bg-inverse);
  border-color: var(--bg-inverse);
  color: var(--text-inverse);
}

.grade__mais:hover span { transform: translate3d(0, 3px, 0); }

.grade__rodape[hidden] { display: none; }

/* Só para leitor de tela: a contagem muda a cada filtro, e quem não vê a
   grade precisa saber que ela mudou. */
.oculto-visual {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* O filete no topo fecha a seção Sobre e abre o rodapé. Ele vive num ::before
   recuado pela margem, e não num border-top: a borda do elemento iria de
   ponta a ponta do contêiner, desalinhada do texto que ela deve acompanhar. */
.rodape {
  position: relative;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-5);
  margin: clamp(120px, 18vh, 200px) auto 0;
  padding: var(--space-7) var(--margem);
  max-width: var(--container-xl);
}

.rodape::before {
  content: "";
  position: absolute;
  top: 0;
  left: var(--margem);
  right: var(--margem);
  height: 1px;
  background: var(--border-subtle);
}

.rodape__linha {
  font-weight: var(--fw-medium);
  color: var(--text-muted);
}

.rodape__linha--fim { font-family: var(--font-mono); font-weight: var(--fw-light); }

/* --------------------------------------------------------------------------
   5e. Página de projeto — capa, blocos do corpo e vizinhos
   Só entra em projeto.html.

   O PRINCÍPIO, E ELE É DIFERENTE DO DA GRADE
   Cada bloco aqui precisa se comportar em QUALQUER contêiner, porque um bloco
   pode estar solto no corpo ou dentro de um slide de carrossel — e no
   WordPress, dentro de qualquer coisa que aceite InnerBlocks. Por isso nenhum
   bloco se posiciona por coluna da grade: cada um resolve a própria largura
   com `max-width` e margem automática. Amarrar um bloco a `grid-column`
   funcionaria no corpo e quebraria dentro do slide.
   -------------------------------------------------------------------------- */

/* Capa ------------------------------------------------------------------- */

/* A capa reaproveita .capa, .capa__rotulo, .capa__titulo e .capa__prosa de
   5d — é a mesma diagramação. Só a ficha e os termos são novos, e ocupam a
   terceira fileira. Larguras sobre a grade real de 1440 (coluna de 86 px,
   vão de 24): ficha 416 px, termos 746 px. */

.capa__rotulo a { color: inherit; }

.ficha {
  grid-column: 1 / span 4;
  grid-row: 3;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-2) var(--space-4);
  margin: var(--space-7) 0 0;
  font-family: var(--font-mono);
  font-size: var(--size-2xs);
  line-height: var(--lh-body);
}

.ficha__termo {
  font-weight: var(--fw-light);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--text-muted);
}

.ficha__valor { margin: 0; color: var(--text-secondary); }

/* As subtags. Réguas, não pílulas: pílula é vocabulário de outro sistema, e
   este site resolve tudo com filete. Cada uma leva de volta à grade já
   filtrada por ?c= e ?s=. */
.termos {
  grid-column: 6 / span 7;
  grid-row: 3;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-5);
  align-content: end;
  margin: var(--space-7) 0 0;
  padding: 0;
  list-style: none;
}

.termos__link {
  display: inline-block;
  padding-bottom: var(--space-1);
  border-bottom: var(--border-hair) solid var(--border-default);
  font-family: var(--font-mono);
  font-weight: var(--fw-light);
  font-size: var(--size-2xs);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--text-secondary);
  transition: color var(--dur-fast) var(--ease-puzzle),
              border-color var(--dur-fast) var(--ease-puzzle);
}

.termos__link:hover {
  color: var(--text-primary);
  border-bottom-color: var(--border-strong);
}

/* Corpo ------------------------------------------------------------------ */

/* O espaçamento entre blocos é GAP do contêiner, não margem do bloco.

   Foi margem, e quebrou: `.mockup` e `.video` são <figure> e precisam zerar a
   margem que o navegador dá a eles. Feito com o atalho `margin: 0`, isso tem a
   mesma especificidade de `.bloco-corpo` e vem depois no arquivo — o atalho
   ganhava e a janela colava no parágrafo. Gap é do pai: nenhum filho pode
   anular, aconteça o que acontecer com a margem dele. */
.corpo {
  display: grid;
  gap: clamp(64px, 10vh, 128px);
  max-width: var(--container-xl);
  margin: 0 auto;
  padding: 0 var(--margem);
}

.bloco-corpo {
  opacity: 0;
  transform: translate3d(0, var(--space-5), 0);
  transition: opacity var(--dur-slide) var(--ease-puzzle),
              transform var(--dur-slide) var(--ease-puzzle);
}

.bloco-corpo[data-visivel="sim"] { opacity: 1; transform: none; }

/* Dentro de um slide o bloco não espera revelação: quem entra em cena é o
   slide, e um bloco preso ao IntersectionObserver dentro de um slide fora de
   vista nunca acenderia. */
.carrossel__slide .bloco-corpo {
  opacity: 1;
  transform: none;
}

/* Texto ------------------------------------------------------------------ */

/* A largura é medida tipográfica, e medida se conta em caracteres — por isso
   `ch` e não coluna da grade. Também é o que mantém o bloco correto dentro de
   um slide estreito, onde a grade da página não vale. */
.bloco-texto { max-width: 58ch; }

.bloco-texto__titulo {
  margin: 0 0 var(--space-5);
  font-weight: var(--fw-semibold);
  font-size: clamp(24px, 3vw, var(--size-2xl));
  line-height: var(--lh-heading);
  letter-spacing: var(--track-heading);
}

.bloco-texto__p {
  margin: 0 0 var(--space-4);
  font-size: var(--size-md);
  line-height: var(--lh-body);
  color: var(--text-secondary);
}

.bloco-texto__p:last-child { margin-bottom: 0; }

/* Mídia — a caixa que toda imagem da página usa -------------------------- */

.midia {
  position: relative;
  display: block;
  overflow: hidden;
  background: var(--bg-inset);
}

/* Sobra de 12% em cima e embaixo, igual à do bloco da home e à do cartão: é o
   que dá curso ao parallax sem descobrir a borda. Sem transição — o transform
   é escrito a cada quadro por js/site.js. */
.midia__camada {
  position: absolute;
  inset: -12% 0;
  will-change: transform;
}

.midia__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Mesma trama do cartão e do bloco, pela mesma razão: cor chapada não deixa
   avaliar movimento. Sai numa linha quando a fotografia entrar. */
.midia__trama {
  position: absolute;
  inset: 0;
  opacity: .05;
  background-image:
    linear-gradient(to right, currentColor 1px, transparent 1px),
    linear-gradient(to bottom, currentColor 1px, transparent 1px);
  background-size: 25% 25%;
  color: var(--ink-000);
}

.midia[data-tom="claro"] .midia__trama { color: var(--ink-950); opacity: .07; }

/* O que a imagem vai ser, enquanto ela não é. Some sozinho quando `src`
   existir, porque o JavaScript nem cria este elemento nesse caso. */
.midia__espera {
  position: absolute;
  left: var(--space-4);
  bottom: var(--space-4);
  font-family: var(--font-mono);
  font-weight: var(--fw-light);
  font-size: var(--size-3xs);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--ink-000);
  opacity: .7;
}

.midia[data-tom="claro"] .midia__espera { color: var(--ink-950); }

/* Mockup de navegador ---------------------------------------------------- */

/* Duas decisões de identidade, e as duas são subtração: os três círculos do
   macOS viram três MÓDULOS QUADRADOS, porque a marca é uma grade de quadrados
   e círculo é vocabulário emprestado; e o canto é reto, porque o site não tem
   raio em lugar nenhum. A barra se separa do conteúdo por um filete, a mesma
   construção do .contato::before. */
.mockup {
  margin: 0;
  border: var(--border-hair) solid var(--border-subtle);
  background: var(--bg-surface);
}

.mockup__barra {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border-bottom: var(--border-hair) solid var(--border-subtle);
}

.mockup__modulos { display: flex; gap: var(--space-1); }

.mockup__modulos i {
  width: 8px;
  height: 8px;
  background: var(--border-default);
}

.mockup__url {
  font-family: var(--font-mono);
  font-weight: var(--fw-light);
  font-size: var(--size-3xs);
  letter-spacing: var(--track-mono);
  color: var(--text-muted);
}

.mockup[data-tema="escuro"] {
  background: var(--ink-950);
  border-color: var(--ink-800);
}

.mockup[data-tema="escuro"] .mockup__barra { border-bottom-color: var(--ink-800); }
.mockup[data-tema="escuro"] .mockup__modulos i { background: var(--ink-600); }
.mockup[data-tema="escuro"] .mockup__url { color: var(--ink-400); }

/* Vídeo com hover play --------------------------------------------------- */

.video { margin: 0; }

.video__caixa {
  position: relative;
  overflow: hidden;
  background: var(--bg-inset);
}

.video__el { width: 100%; height: 100%; object-fit: cover; display: block; }

/* O indicador de progresso é uma RÉGUA, a mesma construção do foco dos campos
   do formulário. Nada de ícone de play arredondado: o site não tem um único
   canto curvo, e um triângulo dentro de um círculo viria de outro sistema. */
.video__regua {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: var(--border-thick);
  background: var(--state-hover-veil);
}

.video__regua i {
  display: block;
  height: 100%;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: 0 50%;
}

.video__legenda {
  padding-top: var(--space-3);
  font-family: var(--font-mono);
  font-weight: var(--fw-light);
  font-size: var(--size-3xs);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--text-muted);
}

/* Galeria com grupos ----------------------------------------------------- */

.galeria__titulo,
.carrossel__titulo,
.vizinhos__titulo {
  margin: 0 0 var(--space-5);
  font-family: var(--font-mono);
  font-weight: var(--fw-light);
  font-size: var(--rotulo);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--accent);
  padding-bottom: var(--space-3);
  border-bottom: var(--border-hair) solid var(--border-strong);
}

.galeria__grupos {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-5);
  margin-bottom: var(--space-6);
}

/* O grupo ativo se marca por INVERSÃO — o filete embaixo engrossa e assume a
   cor do grupo. Não por opacidade: opacidade não é estado neste sistema. */
.galeria__grupo {
  padding-bottom: var(--space-2);
  border-bottom: var(--border-thick) solid transparent;
  font-family: var(--font-mono);
  font-weight: var(--fw-light);
  font-size: var(--size-2xs);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--text-muted);
  transition: color var(--dur-fast) var(--ease-puzzle),
              border-color var(--dur-fast) var(--ease-puzzle);
}

.galeria__grupo:hover { color: var(--text-primary); }

.galeria__grupo[aria-selected="true"] {
  color: var(--text-primary);
  border-bottom-color: var(--cor);
}

/* COLUNAS DE CSS, e não grid.

   Com `repeat(3, 1fr)` cada fileira fica com a altura do item mais alto: numa
   coluna de 416 px, um 16×9 tem 234 px de altura e um 9×16 tem 740 px, então
   a primeira fileira abria 740 px e deixava dois buracos de meio metro de
   tela. Uma galeria mistura formatos por natureza — é o inventário, não um
   descuido de ordenação.

   O handoff rejeita `columns` para a GRADE de projetos, e com razão: lá os
   itens precisam de coordenada estável para animar de um lugar a outro, e
   `columns` reflui sozinho entre as colunas. Aqui nada viaja — as imagens
   entram e saem no próprio lugar, por escala. A objeção não se aplica. */
.galeria__painel {
  columns: 3;
  column-gap: var(--gutter);
}

/* `hidden` é `display: none` na folha do navegador, com especificidade ZERO.
   Qualquer `display` de autor ganha dele — foi assim que os três grupos
   apareceram ao mesmo tempo na tela. Esta linha devolve o atributo. */
.galeria__painel[hidden] { display: none; }

/* Quebrar uma imagem entre duas colunas é o defeito clássico de `columns`. */
.galeria__item {
  break-inside: avoid;
  margin-bottom: var(--gutter);
}

/* A fila de entrada das imagens: `--passo` é a posição do item, escrita pelo
   JavaScript, e o CSS multiplica pelo intervalo. Acrescentar imagem não exige
   tocar em tempo nenhum — mesma construção da seção Sobre. */
.galeria__item {
  opacity: 0;
  transform: scale(.96);
  transition: opacity var(--dur-slide) var(--ease-puzzle),
              transform var(--dur-slide) var(--ease-puzzle);
  transition-delay: calc(var(--passo, 0) * 80ms);
}

.galeria__painel[data-visivel="sim"] .galeria__item { opacity: 1; transform: none; }

/* Carrossel -------------------------------------------------------------- */

/* A altura acompanha o slide ativo, escrita por js/projeto.js.

   Sem isso a janela fica com a altura do slide MAIS ALTO, porque os slides são
   irmãos num flex — mostrando o mockup 16×9 de 234 px, a janela continuava com
   os 740 px do vídeo 9×16 e sobrava meia tela de vazio embaixo.

   Aqui a altura é MEDIDA, ao contrário de tudo mais no site. É legítimo pela
   razão inversa da grade: lá o layout é nosso e a coordenada é conhecida antes
   de ser escrita; aqui o slide contém texto, cuja altura só o navegador sabe. */
.carrossel__janela {
  overflow: hidden;
  transition: height var(--dur-hold) var(--ease-puzzle);
}

.carrossel__pista {
  display: flex;
  transition: transform var(--dur-hold) var(--ease-puzzle);
}

/* `align-self: start` para o slide não esticar até a altura do irmão mais
   alto — sem isso a medição devolveria sempre o mesmo número. */
.carrossel__slide {
  flex: 0 0 100%;
  min-width: 0;
  align-self: start;
  display: grid;
  gap: var(--space-6);
}

.carrossel__controles {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  padding-top: var(--space-5);
}

.carrossel__conta {
  margin-right: auto;
  font-family: var(--font-mono);
  font-weight: var(--fw-light);
  font-size: var(--size-2xs);
  letter-spacing: var(--track-label);
  color: var(--text-muted);
}

.carrossel__seta {
  padding-bottom: var(--space-1);
  border-bottom: var(--border-hair) solid var(--border-default);
  font-family: var(--font-mono);
  font-weight: var(--fw-light);
  font-size: var(--size-2xs);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  transition: color var(--dur-fast) var(--ease-puzzle),
              border-color var(--dur-fast) var(--ease-puzzle);
}

.carrossel__seta:hover:not(:disabled) { border-bottom-color: var(--border-strong); }

/* Desabilitado usa a opacidade do sistema. É estado de disponibilidade, não
   de hover — a proibição de opacidade vale para hover. */
.carrossel__seta:disabled {
  opacity: var(--state-disabled-opacity);
  border-bottom-color: transparent;
  cursor: default;
}

/* Vizinhos --------------------------------------------------------------- */

.vizinhos {
  max-width: var(--container-xl);
  margin: clamp(96px, 14vh, 160px) auto 0;
  padding: 0 var(--margem);
}

.vizinhos__lista { margin: 0; padding: 0; list-style: none; }

.vizinhos__link {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-5);
  padding: var(--space-5) 0;
  border-bottom: var(--border-hair) solid var(--border-subtle);
}

.vizinhos__nome {
  font-weight: var(--fw-semibold);
  font-size: clamp(20px, 2.4vw, var(--size-xl));
  letter-spacing: var(--track-heading);
}

.vizinhos__ano {
  font-family: var(--font-mono);
  font-weight: var(--fw-light);
  font-size: var(--size-2xs);
  color: var(--text-muted);
}

.vizinhos__link:hover { border-bottom-color: var(--border-strong); }

/* Estados da página ------------------------------------------------------ */

.projeto__vazio {
  display: none;
  padding: 25vh var(--margem);
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--size-xs);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--text-muted);
}

.projeto__vazio a { border-bottom: var(--border-hair) solid var(--border-default); }

html[data-projeto="vazio"] .projeto__vazio { display: block; }
html[data-projeto="vazio"] .capa,
html[data-projeto="vazio"] .corpo,
html[data-projeto="vazio"] .vizinhos { display: none; }

/* Mesma rede do data-grade de projetos.html: o rodapé está escrito à mão e
   apareceria de uma vez antes de qualquer animação. */
html[data-projeto="carregando"] .rodape { opacity: 0; }

/* --------------------------------------------------------------------------
   5f. Tema escuro — o que os tokens não resolvem sozinhos
   --------------------------------------------------------------------------
   Quase tudo vira sozinho: superfície, texto, linha e acento são tokens, e
   [data-theme="dark"] em tokens/colors_and_type.css troca os três de uma vez.
   Sobra um problema, e ele é de composição, não de token.

   AS NOVE CORES DOS BLOCOS SÃO HEXADECIMAIS LITERAIS, e têm que ser: elas são
   a rampa tonal da home, que desce do denso para o claro ao longo da página.
   A rampa vai de #121110 a #DAD7CD, ou seja de ponta a ponta da escala — então
   ela sempre encosta em um dos dois canvas. No claro, o bloco 09 (#DAD7CD)
   fica perto do fundo e ainda lê. No escuro, o bloco 01 (#121110) é
   EXATAMENTE o canvas, e some.

   Inverter a ordem da rampa no escuro não resolve: só muda de ponta — o 09
   passaria a ser #121110 e sumiria do outro lado.

   A saída é um filete. Nenhuma cor nova, nenhuma das nove tocada, e é o
   vocabulário que o site já usa em tudo. `box-shadow: inset` em vez de
   `border` porque a mídia tem clip-path e aspect-ratio: borda mudaria a caixa
   e a proporção declarada deixaria de bater, enquanto a sombra interna não
   ocupa espaço e ainda acompanha o recorte.
   -------------------------------------------------------------------------- */

[data-theme="dark"] .bloco__media,
[data-theme="dark"] .cartao__media,
[data-theme="dark"] .midia {
  box-shadow: inset 0 0 0 var(--border-hair) var(--border-subtle);
}

/* --------------------------------------------------------------------------
   6b. Cursor
   --------------------------------------------------------------------------
   A geometria vem toda de --cursor-u, o módulo da grade de 12 do glifo,
   escrito pelo JS a partir de CURSOR.tamanho. Nada de tamanho literal aqui:
   trocar 24 por 36 no js/site.js reposiciona tudo sozinho.

   Nenhuma troca de estado usa opacidade — o sistema diz que opacidade não é
   estado de hover. As três formas se substituem por escala, o mesmo
   vocabulário dos cartões da grade de projetos.
   -------------------------------------------------------------------------- */

.cursor {
  position: fixed;
  top: 0;
  left: 0;
  /* Acima da abertura, que é 100: o botão "Pular abertura" é clicável, e um
     cursor que sumisse atrás do painel deixaria a tela sem ponteiro por cinco
     segundos e meio. */
  z-index: 200;
  pointer-events: none;
  will-change: transform;
}

/* O press vive numa camada própria porque tem duração diferente da troca de
   forma — 140 ms contra 220. Com origem no ponto do ponteiro ele serve às
   três: para a seta esse ponto é a ponta, para o quadrado e a régua é o
   centro delas. */
.cursor__press {
  position: absolute;
  transform-origin: 0 0;
  transition: transform var(--dur-fast) var(--ease-puzzle);
}

/* Camada da rotação. Origem no ponto do ponteiro, então a seta gira em torno
   da própria ponta e a ponta nunca sai do lugar. Sem transição: o ângulo é
   escrito a cada quadro e já vem amaciado do JS. */
.cursor__giro {
  position: absolute;
  transform-origin: 0 0;
}

/* A troca de forma era --dur-base, 220 ms, e acompanhava o assentamento da
   mola. Ele desceu para --dur-fast e esta desce junto: uma forma trocando mais
   devagar do que a seta viaja faz a substituição chegar depois do movimento
   que a pediu. */
.cursor__seta,
.cursor__quadrado,
.cursor__regua {
  position: absolute;
  display: block;
  transform: scale(0);
  transition: transform var(--dur-fast) var(--ease-puzzle);
}

/* A ponta da seta é a quina de cima à esquerda do próprio SVG. Nada a recuar:
   a caixa começa onde o ponteiro está. A origem da escala é a mesma ponta,
   para a seta crescer e encolher a partir dela como gira a partir dela. */
/* Tinta e contorno em token SEMÂNTICO, não em --ink-950 e --ink-000 literais:
   no tema escuro os dois invertem sozinhos, e a seta continua legível. Com os
   literais, o cursor ficaria preto sobre canvas preto. */
.cursor__seta {
  left: 0;
  top: 0;
  transform-origin: 0 0;
  fill: var(--text-primary);
  stroke: var(--bg-canvas);
  /* Dois módulos de traço, metade para fora: sobra exatamente 1 módulo de
     contorno claro, o mínimo que o sistema permite num ícone contornado. Sem
     ele a seta desaparece sobre o bloco 01, que é #121110. */
  stroke-width: 2;
  paint-order: stroke;
  stroke-linejoin: miter;
}

.cursor__quadrado {
  width: var(--cursor-quadrado);
  height: var(--cursor-quadrado);
  left: calc(var(--cursor-quadrado) / -2);
  top: calc(var(--cursor-quadrado) / -2);
  transform-origin: 50% 50%;
  border: var(--border-thick) solid var(--text-primary);
  /* Mesmo papel do stroke da seta. Contorno não ocupa espaço, então o
     quadrado continua com o lado exato de 6 módulos. */
  outline: var(--border-hair) solid var(--bg-canvas);
}

.cursor__regua {
  width: var(--border-thick);
  height: var(--cursor-regua);
  left: calc(var(--border-thick) / -2);
  top: calc(var(--cursor-regua) / -2);
  transform-origin: 50% 50%;
  /* A mesma régua de 2 px que é o foco dos campos do formulário. Sobre um
     campo de texto o cursor vira aquilo em que vai se transformar. */
  background: var(--accent);
  outline: var(--border-hair) solid var(--bg-canvas);
}

.cursor[data-forma="seta"] .cursor__seta,
.cursor[data-forma="quadrado"] .cursor__quadrado,
.cursor[data-forma="regua"] .cursor__regua { transform: scale(1); }

/* Press. Compressão, não pulo — e no quadrado o contorno vira preenchimento,
   que é a definição de inversão do sistema. */
.cursor[data-press="sim"] .cursor__press { transform: scale(.88); }
.cursor[data-press="sim"] .cursor__quadrado { background: var(--text-primary); }

/* O atributo é escrito pelo JS só depois de o substituto estar no documento.
   Falhando o script, o ponteiro do sistema continua onde está.

   O !important é deliberado e é o único caminho honesto: esta regra precisa
   vencer TODA declaração de cursor do arquivo, e duas delas — `button` e
   `.cabecalho__simbolo:disabled` — têm especificidade maior que qualquer
   seletor descendente razoável. Subir a especificidade com truque de :not(#x)
   esconderia a intenção; aqui ela está escrita. */
html[data-cursor="ativo"],
html[data-cursor="ativo"] * { cursor: none !important; }

/* --------------------------------------------------------------------------
   7. Abertura
   -------------------------------------------------------------------------- */

.abertura {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: var(--bg-canvas);
  pointer-events: none;
}

/* Só existe enquanto roda. O atributo é escrito no <html> por um script
   inline no <head>, antes da primeira pintura — sem piscada de conteúdo. */
html:not([data-abertura="rodando"]) .abertura { display: none; }

/* Token semântico, não --ink-950 literal: no tema escuro o canvas da abertura
   é o mesmo --ink-950, e a marca se desmontaria invisível dentro do próprio
   overlay. Com --text-primary ela é escura no claro e clara no escuro, sem
   nenhuma regra a mais. js/site.js LÊ esta cor em vez de repeti-la. */
.abertura__modulo {
  position: absolute;
  top: 0; left: 0;
  background: var(--text-primary);
  will-change: transform, opacity;
}

.abertura__pular {
  position: absolute;
  right: var(--margem);
  bottom: var(--space-6);
  pointer-events: auto;
  font-weight: var(--fw-medium);
  color: var(--text-muted);
  opacity: 0;
  transition: opacity var(--dur-slow) var(--ease-out), color var(--dur-fast) linear;
}

.abertura__pular:hover { color: var(--text-primary); }
html[data-abertura="rodando"] .abertura__pular { opacity: 1; transition-delay: 900ms; }

/* Durante a abertura o conteúdo existe no layout — a decomposição precisa
   medir posições reais — mas não é visto, e o scroll fica travado.
   Só a opacidade é tocada: mexer no clip-path aqui faria o bloco fechar de
   volta, com transição visível, no instante em que o overlay sai. */
html[data-abertura="rodando"],
html[data-abertura="rodando"] body { overflow: hidden; }

html[data-abertura="rodando"] .cabecalho { opacity: 0; pointer-events: none; }
html[data-abertura="rodando"] .bloco__media { opacity: 0; transition: none; }

/* Quem segura a legenda durante a abertura agora são as próprias partes, pelo
   `:not([data-abertura="rodando"])` da seção 5 — e elas seguram COM a transição
   armada, e não anulada. Esta regra continua como rede: se algum caminho novo
   acender a legenda antes da hora, ela some junto com o overlay. */
html[data-abertura="rodando"] .bloco .bloco__meta { opacity: 0; }
html[data-abertura="rodando"] .rodape,
html[data-abertura="rodando"] .sobre,
html[data-abertura="rodando"] .contato { opacity: 0; }

.rodape { transition: opacity var(--dur-slow) var(--ease-out); }

/* --------------------------------------------------------------------------
   7b. Saída de página
   Colunas que sobem em fila e cobrem a viewport antes de o navegador trocar
   de documento. A duração e o escalonamento vivem no objeto SAIDA, em
   js/site.js — aqui fica só a forma e a curva.
   -------------------------------------------------------------------------- */

/* Abaixo do cabeçalho (40) e acima da página (10). A barra é o único elemento
   permanente do site: ela não entra, não sai e não é coberta. Quem troca é o
   conteúdo, e a cortina passa por baixo dela — o que também faz a barra ser a
   ponte visual entre uma página e a seguinte. */
.saida {
  position: fixed;
  inset: 0;
  z-index: 30;
  pointer-events: none;
}

/* Sobe a partir da base, como o bloco da home abre. `scaleY` em vez de
   `height` porque escala não recalcula layout — e uma cortina que provoca
   reflow enquanto fecha é justamente o quadro perdido que se enxerga.

   A curva é `--ease-cortina`, e este é o único lugar do site que não usa
   `--ease-puzzle`. O motivo está no token: cobrir não é entrar, e a curva do
   quebra-cabeça gasta os primeiros 400 ms em movimento invisível. */
.saida i {
  position: absolute;
  top: 0;
  bottom: 0;
  background: var(--bg-canvas);
  transform: scaleY(0);
  transform-origin: 50% 100%;
  transition: transform var(--dur-slide) var(--ease-cortina);
}

.saida[data-estado="fechando"] i { transform: scaleY(1); }

/* --------------------------------------------------------------------------
   8. Responsivo
   -------------------------------------------------------------------------- */

@media (max-width: 1100px) {
  .blocos { --gutter: 20px; }

  /* Projetos: a contagem de colunas do masonry é diagramação e por isso mora
     aqui, não no JavaScript. js/grade.js lê --colunas a cada medição. */
  .grade__pista { --colunas: 3; }
}

@media (max-width: 900px) {
  .capa__titulo,
  .capa__prosa { grid-column: 1 / -1; }

  .capa__prosa { grid-row: 3; margin-top: var(--space-6); }
  .capa__rotulo { grid-column: 1 / -1; }

  .grade__pista { --colunas: 2; --vao-v: 32px; }
}

@media (max-width: 860px) {
  :root { --gutter: 16px; }

  /* No celular a diagramação de 12 colunas não cabe: os blocos empilham e a
     alternância passa a ser de largura e de alinhamento, não de coluna. */
  .blocos {
    grid-template-columns: 1fr;
    row-gap: 0;
    padding-top: 22vh;
  }

  .bloco {
    grid-column: 1 / -1;
    grid-row: auto;
    margin-top: clamp(72px, 18vw, 160px);
  }

  .bloco:first-child { margin-top: 0; }

  .bloco[data-formato="vertical"] { width: 78%; }
  .bloco[data-formato="vertical"]:nth-of-type(4n + 3) { margin-left: auto; }

  /* Sem o wordmark, o lockup vira a variante `symbol` do sistema — que é uma
     aplicação prevista, não um lockup mutilado. */
  .cabecalho__nome { display: none; }
  .cabecalho__nav { gap: var(--space-4); }

  /* Sobre: as três colunas empilham. O filete vertical vira horizontal —
     continua separando as listas da prosa, só que na outra direção. */
  .sobre {
    grid-template-columns: 1fr;
    row-gap: var(--space-7);
    padding-top: clamp(120px, 18vh, 200px);
  }

  .sobre__cabeca,
  .sobre__texto,
  .sobre__lado { grid-column: 1 / -1; }

  .sobre__lado {
    padding: var(--space-6) 0 0;
    border-left: 0;
    border-top: 1px solid var(--border-subtle);
  }

  /* Contato empilha: a nota desce para baixo do e-mail. */
  .contato { padding-top: clamp(110px, 16vh, 180px); }
  .contato::before { top: clamp(70px, 10vh, 110px); }

  .contato__intro,
  .contato__form {
    grid-column: 1 / -1;
  }

  .contato__form { margin-top: var(--space-8); }

  .rodape { flex-direction: column; gap: var(--space-2); }
}

@media (max-width: 520px) {
  /* Duas subcolunas de listas não cabem: viram uma. */
  .sobre__lado { grid-template-columns: 1fr; }

  /* Uma coluna: o masonry vira pilha, e a animação de filtro continua a
     mesma — os cartões deslizam na vertical em vez de trocar de coluna. */
  .grade__pista { --colunas: 1; --vao-v: 28px; }

  .bloco[data-formato="vertical"] { width: 86%; }
  .bloco__meta { gap: var(--space-3); }
  .bloco__nome { font-size: 10px; }
  .bloco__acao { font-size: 10px; }
}

/* --------------------------------------------------------------------------
   9. Movimento reduzido
   Preferência do sistema operacional, não opção do site. Quem liga isso
   costuma ter enxaqueca vestibular, epilepsia fotossensível ou distúrbio
   vestibular — deslocamento amplo causa mal-estar real. Aqui trocamos
   deslocamento por opacidade e desligamos parallax e scroll com inércia.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    transition-delay: 0s !important;
    scroll-behavior: auto !important;
  }

  .abertura { display: none !important; }

  /* A busca NÃO recebe `transform: none` aqui de propósito. É o transitionend
     do transform que repõe o `hidden` no fim da saída; anular a propriedade
     mataria o evento e o painel ficaria no documento para sempre. A regra
     geral acima já reduz a transição a 0,01 ms — o deslocamento não é visto. */

  .bloco__media { clip-path: none !important; }
  .bloco__camada { transform: none !important; inset: 0; }
  /* A fila da legenda mora nas partes, não na linha — a exceção precisa
     alcançar as três, senão elas ficam invisíveis para sempre. */
  .bloco__meta,
  .bloco__numero,
  .bloco__nome,
  .bloco__acao { opacity: 1 !important; transform: none !important; }

  .bloco__trama { transform: none !important; }

  .sobre__cabeca,
  .sobre__paragrafo,
  .sobre__grupo,
  .contato__chamada,
  .contato__email,
  .contato__linha,
  .contato__form { opacity: 1 !important; transform: none !important; }

  .bloco { opacity: 0; transition: opacity 200ms linear !important; }
  .bloco[data-visivel="sim"] { opacity: 1; }

  /* Projetos. O transform do cartão e da tag NÃO pode ser anulado: ele não é
     enfeite, é o posicionamento — sem ele todos os cartões se empilhariam na
     mesma quina. A regra geral acima já reduz a transição a 0,01 ms, então
     eles trocam de lugar sem percurso, que é exatamente o que a preferência
     pede. Só a escala do hover sai. */
  .cartao__trama { transform: none !important; }
  .cartao__camada { transform: none !important; inset: 0; }
  .capa > * { opacity: 1 !important; transform: none !important; }
  .grade__pista,
  .faixa__pista { transition: none !important; }

  /* Página de projeto. Mesma armadilha do data-grade: a regra geral acima já
     reduz a transição a 0,01 ms, mas quem nasce com opacidade zero precisa
     ser aceso à força — senão os blocos, as imagens da galeria e o rodapé
     ficariam invisíveis para sempre. */
  .bloco-corpo,
  .galeria__item { opacity: 1 !important; transform: none !important; }
  .midia__camada { transform: none !important; inset: 0; }
  .carrossel__pista { transition: none !important; }
  html[data-projeto="carregando"] .rodape { opacity: 1; }
}
