/* ═══════════════════════════════════════════════════════════
   MOTION — a coreografia do site
   Depende de tokens.css. Carregado DEPOIS de base.css.

   Camada separada de propósito: tudo aqui é aditivo. Apagar o
   <link> deste arquivo devolve o site exatamente ao que era,
   sem deixar regra órfã no base.css.

   ─── TRÊS RESTRIÇÕES QUE DESENHARAM ESTE ARQUIVO ───────────

   1. NÃO EXISTE JAVASCRIPT AQUI, E NÃO É ESCOLHA ESTÉTICA.
      A CSP em public/_headers é `script-src 'self'`, sem
      'unsafe-inline'. O padrão da indústria para revelação por
      rolagem é um script inline no <head> marcando
      <html class="js"> antes do primeiro quadro, e só então o
      CSS esconde os elementos. Aqui esse script é BLOQUEADO, e
      os dois scripts do site são `defer`, ou seja, rodam depois
      do parse. Esconder por CSS e revelar por JS deixaria a
      página em branco para quem tem JS falhando, e daria flash
      de conteúdo para todo mundo.

      Por isso a revelação é `animation-timeline: view()`, que é
      resolvido pelo próprio motor de layout, sempre, sem script.
      E vem dentro de `@supports`: onde não há suporte, nenhuma
      regra daqui se aplica e o conteúdo nasce visível. O modo de
      falhar deste arquivo é "sem animação", nunca "sem conteúdo".

   2. A FOTO DO HERO É O ELEMENTO DE LCP E NÃO PODE ANIMAR
      OPACIDADE. Elemento transparente não conta como pintado: um
      fade de 700ms na foto empurra o LCP em 700ms. Ela anima
      `scale` e `translate`, com opacidade cheia do primeiro
      quadro. Quem aparece é o vidro e o texto.

   3. `translate`/`scale` SEPARADOS, NUNCA `transform`. Duas
      animações que escrevem `transform` no mesmo elemento não
      somam — a última da lista ganha e a outra some. O retrato
      tem entrada (scale) E parallax (translate) ao mesmo tempo;
      só funciona porque são propriedades independentes.

   Fora daqui, no base.css, só uma mudança: a sublinha do menu
   deixou de animar a propriedade `right` (que recalcula layout
   a cada quadro) e passou a `transform: scaleX()`.
   ═══════════════════════════════════════════════════════════ */


/* ── 1. Transição entre páginas ──────────────────────────────

   O site é multipágina estático: cada ida para /cv, /cv-perfil
   ou /cv-en é uma navegação de documento, com o piscar branco
   que vem junto. Uma declaração troca isso por um cross-fade.

   Precisa estar nas DUAS pontas — a página que sai e a que
   entra — e por isso este arquivo é linkado também nas páginas
   de currículo, onde nenhuma outra regra daqui casa com nada.

   Os downloads (.pdf/.docx) têm Content-Disposition: attachment
   e não são navegação, então não passam por aqui. */

@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
}

/* A saída é mais curta que a entrada de propósito: o olho perdoa
   o antigo sumindo depressa, mas lê o novo chegando como corte
   seco se ele vier na mesma velocidade. */
::view-transition-old(root) {
  animation-duration: 140ms;
  animation-timing-function: var(--motion-ease);
}
::view-transition-new(root) {
  animation-duration: var(--motion-dur);
  animation-timing-function: var(--motion-ease-out);
}


/* ── 2. Vocabulário ──────────────────────────────────────────

   Dois gestos, e o site inteiro é feito deles.

   `m-surgir` usa `translate` e não `transform` pelo motivo 3 do
   cabeçalho, e porque assim ele pode ser aplicado a qualquer
   elemento sem apagar um transform que já esteja lá. */

@keyframes m-surgir {
  from { opacity: 0; translate: 0 var(--motion-rise); }
  to   { opacity: 1; translate: 0 0; }
}

/* Só opacidade. Existe para elementos INLINE (o <span class="serif">
   dentro dos títulos): transform e translate não se aplicam a
   inline não substituído — a regra passaria despercebida e o
   escalonamento simplesmente não aconteceria. */
@keyframes m-surgir-inline {
  from { opacity: 0; }
  to   { opacity: 1; }
}


/* ── 3. Entrada do hero ──────────────────────────────────────

   Três tempos, ~1s no total, e a ORDEM é o que segura o
   contraste: o vidro vem primeiro, o texto depois.

   Se o texto entrasse junto com o vidro, haveria uma janela de
   alguns quadros com as letras em `--c-ink` direto sobre o
   retrato, sem véu nenhum atrás — exatamente a condição que já
   foi medida em 1,10:1 neste hero. Com o texto atrasado em
   260ms, quando a primeira letra fica visível o vidro já passou
   dos 60% da própria entrada.

   Tudo escopado em `.hero-intro`: o 404.html usa .hero-title,
   .hero-lead e .hero-actions, mas não tem painel de vidro nem
   retrato, e uma entrada coreografada para o que não existe ali
   deixaria a página de erro com texto sumindo sem motivo. */

@media screen and (prefers-reduced-motion: no-preference) {

  /* O vidro se FORMANDO sobre a foto. Anima opacidade e não o
     `backdrop-filter`: interpolar blur de 0 a 36px obriga o
     compositor a refazer o desfoque do fundo a cada quadro, o
     que é caro justamente no primeiro segundo da página, que é
     quando fonte, imagem e layout ainda estão disputando. */
  .hero-intro::before {
    animation: m-surgir var(--motion-dur-slow) var(--motion-ease-out) both;
  }

  .hero-intro > * {
    animation: m-surgir var(--motion-dur-slow) var(--motion-ease-out) both;
    animation-delay: 200ms;
  }

  /* Escalonamento por delay funciona AQUI porque esta animação é
     baseada em tempo. Na revelação por rolagem, mais abaixo, não
     funciona — e o motivo está registrado lá. */
  .hero-intro > .hero-role    { animation-delay: 360ms; }
  .hero-intro > .hero-lead    { animation-delay: 520ms; }
  .hero-intro > .hero-actions { animation-delay: 680ms; }

  /* O nome, na serifada, chega por último. Só opacidade: é inline. */
  .hero-title .serif {
    animation: m-surgir-inline var(--motion-dur) var(--motion-ease-out) both;
    animation-delay: 800ms;
  }

  /* O retrato ASSENTA. Sem opacidade nenhuma na lista — motivo 2
     do cabeçalho. 1.035 é o teto: acima disso a borda de 1px do
     círculo denuncia o movimento como zoom. */
  .hero-portrait img {
    animation: m-retrato 900ms var(--motion-ease-out) both;
  }
}

@keyframes m-retrato {
  from { scale: 1.035; }
  to   { scale: 1; }
}


/* ── 4. Revelação por rolagem ────────────────────────────────

   O bloco central do arquivo. Tudo dentro de três guardas
   empilhadas, e cada uma existe por um motivo diferente:

   `@media screen`  — as páginas de currículo carregam este
     arquivo (pela transição de página) e são feitas para
     imprimir. Animação de rolagem em papel não tem timeline
     para resolver, e o risco não é ficar sem animação: é a
     impressão sair com o estado INICIAL, ou seja, em branco.

   `prefers-reduced-motion: no-preference` — guarda positiva. O
     bloco global do base.css zera `animation-duration` para
     .01ms, e isso NÃO neutraliza animação de timeline de
     progresso, onde a duração não é o que manda. Aqui a regra
     simplesmente não existe para quem pediu menos movimento.

   `@supports (animation-timeline: view())` — motivo 1 do
     cabeçalho: sem suporte, nada nasce escondido.

   ── DUAS ARMADILHAS QUE DECIDEM O DESENHO ───────────────────

   (a) `animation-delay` NÃO ESCALONA NADA AQUI. Delay é tempo, e
       numa timeline de rolagem não existe tempo, existe
       progresso — o navegador aceita a declaração e ignora o
       efeito, o que é pior do que rejeitar. O escalonamento sai
       deslocando o INÍCIO do `animation-range` por item, que é o
       que `--m-atraso` faz: irmãos de uma mesma linha da grade
       cruzam a viewport quase juntos, e o deslocamento de faixa
       é o que os separa no olho.

   (b) AS DUAS PONTAS DA FAIXA SÃO `cover`, E ISSO É O CONSERTO
       DE UM ERRO MEDIDO. A primeira versão usava `entry 4% cover
       26%`, e o resultado foi "carrega tudo de uma vez, sem
       sensação nenhuma" — corretamente, porque era isso mesmo
       que acontecia.

       O motivo é que as duas fases se medem em RÉGUAS
       DIFERENTES. `entry` vai do momento em que o topo do
       elemento cruza a borda de baixo da janela até o momento em
       que a base dele cruza a mesma borda: a distância rolada é
       a ALTURA DO ELEMENTO. Num título de 92px, a fase `entry`
       inteira dura 92px de rolagem, tudo dentro do rodapé da
       tela. `cover` vai da entrada até a saída pela borda de
       cima: a distância é ALTURA DA JANELA + altura do elemento,
       quase dez vezes maior no mesmo título.

       Medido com `entry 4% cover 26%`, em 1440x900: o título da
       seção Sobre começava a aparecer com o topo dele a 99% da
       altura da tela e já estava opaco a 66% — a revelação
       inteira acontecia na faixa de baixo, e quando o bloco
       chegava à zona de leitura não havia mais nada para ver.
       Cards mais altos percorriam 50% da tela e por isso eram os
       únicos que davam alguma sensação.

       Com as duas pontas em `cover`, a faixa passa a ser medida
       contra a JANELA e não contra o elemento, então bloco baixo
       e bloco alto percorrem parcelas parecidas da tela. Os
       valores de hoje (8% a 50%) põem a revelação entre ~90% e
       ~40% da altura da tela: ela acontece subindo, na frente de
       quem rola, e termina acima do meio.

       O que se perde ao abandonar `entry` como início: nada.
       `cover` é definida para qualquer altura de elemento, e por
       isso continua valendo o motivo original de nunca terminar
       a faixa em `entry` — bloco mais alto que a janela nunca
       completa a fase `entry` e ficaria preso a meio caminho da
       opacidade, para sempre.

       O PREÇO, e ele é real: terminar a 50% de `cover` exige que
       o elemento chegue a rolar meia janela depois de aparecer,
       e o PÉ DA PÁGINA não tem essa folga — o documento acaba
       antes. É por isso que existe o `check:motion`, e é por
       isso que a última seção tem faixa própria mais curta,
       algumas dezenas de linhas abaixo. */

@media screen and (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {

    .sec-head > *,
    .about-prose > p,
    .about-pivot,
    .arc-item,
    .clients,
    .svc-family-head,
    .svc-item,
    .svc-note,
    .mid-cta,
    .flow-step,
    .matrix-title,
    .matrix-wrap,
    .rules-title,
    .rules-item,
    .proj-card,
    .proj-nota,
    .why-card,
    .contact-path,
    .contact-direct li {
      --m-atraso: 0%;
      animation: m-surgir linear both;
      animation-timeline: view();
      animation-range: cover calc(8% + var(--m-atraso)) cover calc(50% + var(--m-atraso));
    }

    /* ── Pagina de case ─────────────────────────────────────
       Lista propria, e nao mais nomes na lista acima: a home e
       o case nao compartilham nenhuma classe, e juntar as duas
       deixaria o bloco ilegivel na primeira manutencao. */
    .case-ficha > div,
    .case-aviso,
    .case-nota-telas,
    .case-destaque,
    .case-prose,
    .case-passo,
    .case-modulo,
    .case-dec-head,
    .case-mapa-wrap,
    .case-artefato,
    .case-resultado-frase,
    .case-resultado-nota,
    .case-fim-bloco {
      --m-atraso: 0%;
      animation: m-surgir linear both;
      animation-timeline: view();
      animation-range: cover calc(8% + var(--m-atraso)) cover calc(50% + var(--m-atraso));
    }

    .case-passo:nth-child(2) { --m-atraso: 5%; }
    .case-passo:nth-child(3) { --m-atraso: 10%; }
    .case-passo:nth-child(4) { --m-atraso: 15%; }

    .case-modulo:nth-child(2) { --m-atraso: 5%; }
    .case-modulo:nth-child(3) { --m-atraso: 10%; }
    .case-modulo:nth-child(4) { --m-atraso: 15%; }
    .case-modulo:nth-child(5) { --m-atraso: 20%; }

    /* A metade serifada do título entra depois da outra — a
       divisão tipográfica que o site já tinha, agora em duas
       velocidades. Inline, então só opacidade (ver m-surgir-inline). */
    .sec-title .serif {
      animation: m-surgir-inline linear both;
      animation-timeline: view();
      animation-range: cover 18% cover 60%;
    }

    /* ── Escalonamento ──────────────────────────────────────
       Passo de 5%: abaixo de 3% os itens parecem simultâneos,
       acima de 8% o último chega tarde demais e a grade lê como
       se estivesse carregando aos pedaços. */
    .sec-head > *:nth-child(2),
    .svc-grid > *:nth-child(2),
    .flow-step:nth-child(2),
    .rules-item:nth-child(2),
    .why-card:nth-child(2),
    .contact-path:nth-child(2) { --m-atraso: 5%; }

    .sec-head > *:nth-child(3),
    .svc-grid > *:nth-child(3),
    .flow-step:nth-child(3),
    .rules-item:nth-child(3),
    .why-card:nth-child(3) { --m-atraso: 10%; }

    .svc-grid > *:nth-child(4),
    .flow-step:nth-child(4),
    .rules-item:nth-child(4),
    .why-card:nth-child(4) { --m-atraso: 15%; }

    .rules-item:nth-child(n + 5),
    .why-card:nth-child(n + 5) { --m-atraso: 20%; }

    /* ── O PÉ DA PÁGINA TEM FAIXA PRÓPRIA, E MAIS CURTA ────
       Este bloco é o preço da faixa em `cover`, e não é teórico:
       ele entrou depois de a produção entregar dois destes `li`
       parados em 0,776 e 0,854 de opacidade.

       A fase `cover` só chega a 100% quando o elemento sai pela
       borda de cima, e os últimos elementos de um documento
       nunca saem — a rolagem acaba antes. Medido em produção,
       1440x900, com a página em 9165px: rolando até o fim, o
       máximo de `cover` que cada um destes quatro itens alcança
       é 66%, 58%, 50% e 43%. Ou seja, o terceiro e o quarto
       jamais chegariam à faixa padrão, que termina em 50%.

       Terminar em 26–29% deixa de 14 a 37 pontos de folga até o
       teto de cada um, o que absorve mudança de conteúdo no
       rodapé sem voltar a prender ninguém.

       O escalonamento aqui é por VALOR LITERAL e não por
       `--m-atraso`: as regras de `nth-child` lá de cima têm
       especificidade maior e continuariam mandando na variável,
       então somar a ela traria de volta exatamente o atraso que
       causou o problema.

       Se um dia entrar seção nova DEPOIS de Contato, este bloco
       provavelmente deixa de ser necessário — e `check:motion`
       é quem responde isso. */
    .contact-direct li                { animation-range: cover  6% cover 26%; }
    .contact-direct li:nth-child(2)   { animation-range: cover  8% cover 27%; }
    .contact-direct li:nth-child(3)   { animation-range: cover 10% cover 28%; }
    .contact-direct li:nth-child(4)   { animation-range: cover 12% cover 29%; }


    /* A linha do tempo da seção Sobre fica FORA disto, e não é
       só por ser área congelada: `view()` resolve contra o
       scroller mais próximo, que para um `.tl-item` é o próprio
       `.tl-rail` — horizontal. A revelação dispararia ao
       arrastar a faixa de lado, não ao rolar a página. */
  }
}


/* ── 5. Parallax do retrato ──────────────────────────────────

   O retrato desloca mais devagar que a página ao longo do hero,
   o que dá profundidade real entre ele e o painel de vidro que
   passa por cima. 34px é o teto útil: acima disso o círculo
   descola do bloco de texto e o hero deixa de ler como uma
   composição só.

   A lista de animações é REDECLARADA inteira aqui, com a entrada
   junto, e isso é obrigatório: `animation` é shorthand, então
   declarar só o parallax apagaria a entrada do retrato definida
   na seção 3. Duas animações, duas timelines, duas faixas — e
   elas convivem porque uma escreve `scale` e a outra `translate`
   (motivo 3 do cabeçalho). */

@media screen and (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: scroll()) {

    .hero-portrait img {
      animation: m-retrato 900ms var(--motion-ease-out) both,
                 m-parallax linear both;
      animation-timeline: auto, scroll();
      animation-range: normal, 0 100vh;
    }

    /* ── O header se materializa ──────────────────────────
       No topo ele é invisível e o hero começa colado na borda da
       janela; passados ~140px de rolagem, o fundo e o filete
       aparecem.

       Anima cor de fundo e cor da borda, e NÃO a altura. Altura
       era o efeito mais óbvio e é justamente o que não pode: o
       header é `position: sticky`, ou seja, ocupa espaço no
       fluxo — encolher a altura durante a rolagem empurra toda a
       página para cima enquanto o visitante rola, e o gesto vira
       uma briga entre o dedo e o layout.

       O `backdrop-filter: blur(12px)` do base.css fica ligado o
       tempo todo. Desfocar um fundo liso é invisível, então no
       topo ele não custa nada visualmente, e ligá-lo no meio da
       animação criaria um pulo de composição.

       O ponto de partida é `--c-surface` a 0%, e não a palavra
       `transparent`: são a mesma cor final, mas interpolar a
       partir do preto transparente pode acinzentar o meio do
       caminho em motor que não premultiplica. */
    .site-header {
      animation: m-cabecalho linear both;
      animation-timeline: scroll();
      animation-range: 0 140px;
    }
  }
}

@keyframes m-parallax {
  to { translate: 0 -34px; }
}

@keyframes m-cabecalho {
  from {
    background-color: color-mix(in srgb, var(--c-surface) 0%, transparent);
    border-bottom-color: transparent;
  }
  to {
    background-color: color-mix(in srgb, var(--c-surface) 88%, transparent);
    border-bottom-color: var(--c-rule-soft);
  }
}


/* ── 6. Micro-interações de estado ───────────────────────────

   Regra seguida sem exceção aqui: todo `:hover` que muda algo
   ganha o `:focus-visible` equivalente. Retorno visual que só
   existe para quem usa mouse deixa quem navega por teclado sem
   saber onde está. */

/* Elevação NÃO entra nos cards. `.why-card` e `.contact-path`
   não são clicáveis — o que é clicável é o botão dentro de um
   deles. Card que sobe no hover promete um clique que não
   existe. O que muda é a borda: chama atenção sem prometer. */
.why-card,
.contact-path {
  transition: border-color var(--motion-dur-fast) var(--motion-ease),
              background-color var(--motion-dur-fast) var(--motion-ease);
}
.why-card:hover,
.contact-path:hover,
.contact-path:focus-within {
  border-color: var(--c-rule-strong);
}

.svc-num,
.flow-num,
.rules-num {
  transition: color var(--motion-dur-fast) var(--motion-ease);
}
.svc-item:hover .svc-num,
.flow-step:hover .flow-num,
.rules-item:hover .rules-num {
  color: var(--c-accent-ink);
}

/* `:not(:active)` é necessário: o base.css já dá
   `transform: translateY(1px)` no :active, e este arquivo carrega
   DEPOIS — sem a exclusão, o hover venceria o clique e o botão
   deixaria de afundar ao ser pressionado. */
.btn:hover:not(:active) { transform: translateY(-1px); }

.contact-direct a:focus-visible { border-color: var(--c-ink); }


/* ── 7. Quem pediu menos movimento ───────────────────────────

   O bloco global do base.css zera duração de animação e
   transição, e isso resolve o que é baseado em tempo. NÃO
   resolve o que é baseado em progresso de rolagem: ali a duração
   não é o que manda, e uma animação de timeline com duração de
   .01ms continua mapeada à posição da barra. Sem este bloco,
   quem pede `reduce` veria conteúdo aparecendo e sumindo
   conforme rola — o oposto do que pediu.

   Redundante com as guardas `no-preference` de cima, e fica
   assim de propósito: é a rede embaixo do trapézio, para o caso
   de alguém acrescentar regra aqui e esquecer a guarda. */

@media (prefers-reduced-motion: reduce) {
  .hero-intro::before,
  .hero-intro > *,
  .hero-title .serif,
  .hero-portrait img,
  .sec-title .serif,
  .site-header,
  .sec-head > *,
  .about-prose > p,
  .about-pivot,
  .arc-item,
  .clients,
  .svc-family-head,
  .svc-item,
  .svc-note,
  .mid-cta,
  .flow-step,
  .matrix-title,
  .matrix-wrap,
  .rules-title,
  .rules-item,
  .proj-card,
  .proj-nota,
  .why-card,
  .case-ficha > div,
  .case-aviso,
  .case-nota-telas,
  .case-destaque,
  .case-prose,
  .case-passo,
  .case-modulo,
  .case-dec-head,
  .case-mapa-wrap,
  .case-artefato,
  .case-resultado-frase,
  .case-resultado-nota,
  .case-fim-bloco,
  .contact-path,
  .contact-direct li {
    animation: none !important;
    opacity: 1 !important;
    translate: none !important;
    scale: none !important;
    clip-path: none !important;
  }
}
