/* ===========================================================================
   07 · ANIMAÇÕES
   Os quadros-chave do sistema e a preferência de quem pediu menos movimento.

   Fica no FIM da cascata de propósito: o bloco `prefers-reduced-motion`
   precisa ser a última palavra sobre `transform` e `transition`, senão
   qualquer componente declarado depois dele volta a se mexer.
   =========================================================================== */

/* Giro do `.spinner` (04-componentes). */
@keyframes spin { to { transform: rotate(360deg); } }

/* ── Quadros-chave que hoje moram dentro das views ────────────────────────
   Estes dois estão aqui para a camada ficar completa e para o dia em que os
   blocos <style> das views saírem. Enquanto eles existirem, a definição da
   view vence (vem depois no documento), e as duas são idênticas, então nada
   muda hoje.

   `slideIn` NÃO está aqui, e a ausência é a decisão: ele tem DUAS definições
   conflitantes no sistema. Em views/elite-cred/layouts/scripts.ejs ele parte
   de `translateX(100%)`; em loan-success.ejs, payment-success.ejs e
   payment-receipt.ejs ele parte de `translateX(400px)`. Hoje quem ganha é a
   última declarada em cada página. Trazer uma das duas para cá escolheria por
   todo mundo em silêncio, e a escolha só apareceria no dia em que alguém
   apagasse o <style> da view. É decisão de quem converter a view, com as duas
   na mão. */
@keyframes slideOut {
    from { transform: translateX(0); opacity: 1; }
    to { transform: translateX(100%); opacity: 0; }
}

@keyframes scaleUp {
    from { transform: scale(0); opacity: 0; }
    to { transform: scale(1); opacity: 1; }
}

/* ── Trocar de página ──────────────────────────────────────────────────────
   O painel navega pelo SERVIDOR: link comum e formulário que posta. Esta é a
   transição nativa entre DOCUMENTOS, e o ponto dela é esse: nenhum dos 162
   links é interceptado, nenhum `submit` é segurado, nenhuma linha de
   JavaScript entra. Trocar o corte seco por um esmaecimento custa uma regra,
   e interceptar a navegação custaria uma classe inteira de bugs novos (o
   botão Voltar, o POST que redireciona, o link que abre em outra aba).

   ONDE VALE: só nas 23 telas que incluem layouts/head.ejs, que é onde mora a
   adesão. A transição entre documentos exige que os DOIS lados adiram, então
   entrar e sair do painel (login, 404, portal, páginas públicas) continua
   seco. É o desenho, não esquecimento.

   SEM SUPORTE NÃO ACONTECE NADA: `view-transition-name` vira propriedade
   desconhecida e é descartada, e `::view-transition-*` vira seletor inválido,
   o que faz o navegador jogar a regra inteira fora. Mesmo onde a API existe,
   esses pseudo-elementos só nascem enquanto a troca acontece. Nenhuma regra
   desta seção mexe na opacidade de um elemento de verdade, e é isso que
   garante que conteúdo nunca fica invisível nem travado. Conferido medindo:
   as 3 telas em 3 larguras, com e sem esta seção, dão a MESMA foto de página
   inteira, pixel a pixel.

   A ADESÃO (`@view-transition { navigation: auto }`) NÃO ESTÁ AQUI, e isso é
   decisão medida: ela mora inline no <head>, em
   views/elite-cred/layouts/head.ejs, junto do `navigation: none` de
   "reduzir movimento". Esta camada é a SÉTIMA de oito folhas pedidas em
   paralelo e chega tarde demais para a decisão que o documento que CHEGA toma
   logo no começo. Medido no Chrome 154: com a regra aqui, a transição não
   acontecia em 3 de 3 navegações entre telas reais; movendo o <link> para o
   primeiro lugar, acontecia. O comentário no head.ejs conta a medição inteira.
   O que fica aqui é a APARÊNCIA, que só é consultada quando a animação vai
   rodar, e nessa altura as 8 folhas já desceram. */

/* SÓ `opacity`, e nos retratos, nunca no `body`.
   O esqueleto do painel é sidebar `position: fixed` mais `.main-content` com
   `margin-left`. Qualquer `transform`/`filter` no `body` ou no `html` cria
   bloco de contenção, a sidebar passa a se posicionar em relação a ele em vez
   da janela, e o layout inteiro desmonta no meio da animação. Esmaecer é a
   única coisa que não tem como fazer isso.

   AS DUAS PONTAS COM A MESMA DURAÇÃO, A MESMA CURVA E SEM ATRASO: assim a
   soma das duas opacidades é 1 em todo instante (medido no meio do caminho:
   0,796806 saindo contra 0,203194 entrando), e a tela não clareia nem escurece
   na travessia, qualquer que seja o jeito do navegador compor os dois. O padrão
   do Chrome é 90ms de saída mais 210ms de entrada com 90ms de atraso: quebra
   essa soma e ainda custa 300ms de relógio.

   DURAÇÃO: `--dur-rapida`, 0,2s, que é a mesma das transições de hover do
   painel. Vale medida própria porque o painel é ferramenta de trabalho, aberta
   dezenas de vezes por dia: abaixo de 150ms a troca vira piscada e não explica
   nada; acima de 250ms ela entra na frente do trabalho, somando-se à ida ao
   servidor que já aconteceu. Reaproveitar o token em vez de inventar um número
   mantém a troca de página no mesmo compasso do resto da interface. */
@keyframes pagina-sai {
    from { opacity: 1; }
    to { opacity: 0; }
}

@keyframes pagina-entra {
    from { opacity: 0; }
    to { opacity: 1; }
}

/* O grupo da raiz interpola tamanho e posição do retrato. Entre duas telas do
   painel a janela é a mesma, então essa interpolação não desenha nada, mas o
   padrão dela é 250ms e é ELA que segura a transição aberta: medido, a troca
   terminava em 267ms enquanto o esmaecimento acabava em 200ms. Desligada, a
   duração declarada aqui é a duração de verdade. */
::view-transition-group(root) {
    animation: none;
}

/* As propriedades vão SEPARADAS, não no atalho `animation:`.
   Se um dia `--dur-rapida` deixar de resolver, o atalho inteiro cairia junto e
   os dois retratos ficariam parados em cima um do outro. Separadas, só a
   duração cai para zero: com o `fill-mode: both` logo abaixo, o retrato velho
   vai direto para opacidade 0 e a troca fica instantânea, que é o pior caso
   aceitável. (Hoje resolve: medido, 200ms e `ease` dentro dos
   pseudo-elementos, que herdam as variáveis do `:root` como qualquer filho.) */
::view-transition-old(root) {
    animation-name: pagina-sai;
    animation-duration: var(--dur-rapida);
    animation-timing-function: var(--curva);
    animation-delay: 0s;
    animation-fill-mode: both;
}

::view-transition-new(root) {
    animation-name: pagina-entra;
    animation-duration: var(--dur-rapida);
    animation-timing-function: var(--curva);
    animation-delay: 0s;
    animation-fill-mode: both;
}

/* A sidebar é idêntica nas 23 telas e hoje é redesenhada do zero a cada
   navegação. Com nome próprio ela sai do retrato da página e ganha o dela,
   que não é animado: fica parada enquanto o conteúdo esmaece.

   O NOME TEM QUE SER ÚNICO NO DOCUMENTO. Se duas sidebars aparecerem na mesma
   tela o navegador CANCELA a transição inteira, sem erro visível, e a
   navegação volta a ser seca. Hoje existe uma só: layouts/sidebar.ejs é
   include único e nenhuma tela o chama duas vezes.

   A declaração mora aqui e não em 03-layout, porque ela só existe por causa
   da animação: quem desligar esta seção não precisa caçar o resto. */
.sidebar {
    view-transition-name: sidebar;
}

/* `animation: none` nos três de uma vez, e o retrato novo por cima do velho.

   NO GRUPO, para a gaveta do celular não deslizar sozinha: navegando com ela
   aberta, o retrato velho está em `translateX(0)` e o novo em
   `translateX(-100%)`, e o grupo animado interpolaria os dois, inventando um
   fechamento que ninguém pediu.

   O `mix-blend-mode: normal` é cinto e suspensório, com motivo: a folha padrão
   da especificação põe `plus-lighter` nos dois retratos, que SOMA as cores, e
   somar dois retratos parados em cima um do outro deixaria a sidebar lavada de
   claro durante a troca inteira. O Chrome 154 já entrega `normal` (conferido
   revertendo a declaração e lendo o computado), mas a declaração custa uma
   linha e é ela que segura o desenho no dia em que outro motor seguir a letra
   da especificação. */
::view-transition-group(sidebar) {
    animation: none;
}

::view-transition-old(sidebar) {
    animation: none;
    opacity: 0;
}

::view-transition-new(sidebar) {
    animation: none;
    opacity: 1;
    mix-blend-mode: normal;
}

/* A barra do celular recebe o MESMO tratamento da sidebar, e aqui ele importa
   mais: ela é idêntica em todas as telas e fica permanentemente à vista
   justamente durante a troca de página, que é quando ela piscaria.

   O NOME É OUTRO, `barra-inferior`, e tinha que ser: nome repetido no mesmo
   documento faz o navegador CANCELAR a transição inteira, em silêncio, e a
   navegação volta a ser o corte seco. É único porque a barra sai de um include
   só (layouts/barra-inferior.ejs, emitido por layouts/sidebar.ejs) e nenhuma
   tela o chama duas vezes.

   Acima de 1024px ela é `display: none` e não gera caixa, então o nome não é
   reivindicado e a transição segue sem ela: não é preciso repetir o
   breakpoint aqui. */
.barra-inf {
    view-transition-name: barra-inferior;
}

::view-transition-group(barra-inferior) {
    animation: none;
}

::view-transition-old(barra-inferior) {
    animation: none;
    opacity: 0;
}

::view-transition-new(barra-inferior) {
    animation: none;
    opacity: 1;
    mix-blend-mode: normal;
}

/* ── Resposta do botão ao clique ───────────────────────────────────────────
   O sistema inteiro responde a hover e NADA responde ao clique: não havia uma
   única regra `:active` em todas as camadas. Em tela de toque isso é o estado
   que mais falta, porque hover não existe lá, e aqui o resto da tela só muda
   depois da ida ao servidor.

   O afundar é mínimo (2%) e a duração é menor que a do resto, 60ms: pressionar
   tem que acontecer DENTRO do clique. Com os 200ms do `.btn` o botão ainda
   estaria descendo quando a página já tivesse trocado.

   O `translateY(0)` que acompanhava o `scale` SAIU junto com o
   `translateY(-2px)` dos três hovers de gradiente: ele existia só para o botão
   não subir e encolher ao mesmo tempo, que lia como tremor. Sem hover que
   mova, ele passou a zerar coisa nenhuma. */
.btn:active {
    transform: scale(0.98);
    transition-duration: 60ms;
}

/* ── Menos movimento ───────────────────────────────────────────────────────
   Para quem ligou "reduzir movimento" no sistema operacional. */
/* ── Caixa de aviso ────────────────────────────────────────────────────────
   Entra descendo do topo e sai subindo, o movimento curto que diz de onde a
   coisa veio. A saída é animação (não transição) porque o elemento é removido
   do DOM no fim dela: o JS espera o `animationend`, com um timeout de rede de
   segurança para o caso de a animação não rodar. */
@keyframes aviso-entra {
    from { opacity: 0; transform: translateY(-12px) scale(.98); }
    to   { opacity: 1; transform: none; }
}

@keyframes aviso-sai {
    from { opacity: 1; transform: none; }
    to   { opacity: 0; transform: translateY(-10px) scale(.98); }
}

.aviso        { animation: aviso-entra var(--dur-rapida) ease-out both; }
.aviso.saindo { animation: aviso-sai var(--dur-rapida) ease-in forwards; }

@media (prefers-reduced-motion: reduce) {
    /* A transição de página é desligada na RAIZ, pelo `navigation: none` que
       mora no <head> junto da adesão: o navegador nem chega a tirar retrato.
       O que fica aqui é rede, para o dia em que a adesão voltar a valer por
       engano, e precisa estar escrita: o `*` logo abaixo NÃO alcança isto,
       porque ele casa com elementos e `::view-transition-*` não é elemento.
       Zerar a duração aqui, junto do
       `fill-mode: both` declarado lá em cima, manda o retrato velho direto
       para opacidade 0 e deixa o novo inteiro, sem o quadro de dois retratos
       empilhados que um `animation: none` produziria. */
    ::view-transition-old(root) { animation-duration: 0.01ms; }
    ::view-transition-new(root) { animation-duration: 0.01ms; }

    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }

    /* Zerar a duração não resolve o `translateY` do hover: o elemento continua
       pulando, só que instantaneamente, e pulo instantâneo é movimento igual.
       A sombra sozinha já dá o retorno visual.
       Os três `.btn-*:hover` saíram desta lista porque não movem mais nada: o
       realce virou anel na borda mais `brightness`, que não é movimento e por
       isso continua valendo para quem pediu menos movimento. Quem ainda sobe é
       o `.stat-card`, e quem ainda encolhe é o `.btn:active`. */
    .stat-card:hover,
    .btn:active {
        transform: none;
    }
}
