/* ============================================================
   Camada de animação — "Nível 1" (CSS puro moderno, zero biblioteca).

   Regras que valem para TUDO neste arquivo:

   1. Progressive enhancement. O estado padrão do site é "conteúdo visível".
      Nenhum elemento depende de JavaScript para voltar a aparecer: as entradas
      são @keyframes que o próprio CSS dispara, e os reveals de scroll vivem
      dentro de @supports (animation-timeline: view()) — quem não suporta vê
      tudo pintado, sem estado inicial invisível.
   2. Nada que atrapalhe o LCP. O <h1> do hero e o texto de apoio nunca partem
      de opacity: 0 — só de um transform sutil. O mesmo vale para o sticker do
      Nico, que é candidato a maior elemento pintado.
   3. Só transform e opacity. Nenhuma animação toca width/height/top/left.
   4. Sob prefers-reduced-motion: reduce, ESTA camada não existe — todo o
      movimento daqui está dentro de @media (prefers-reduced-motion:
      no-preference), então nada é declarado, nem encurtado. (Separadamente,
      base.css tem um reset global que reduz `animation-duration` para .01ms
      em qualquer animação vinda de outro lugar; são duas defesas distintas.)
   ============================================================ */

/* ============================ Badge do contador da lista
   Entrada quando o badge sai do zero e "pop" a cada mudança de número.

   As duas animações moram em ELEMENTOS DIFERENTES, e isso não é detalhe:
   - .botao-lista__contador  (invólucro, só existe quando há itens) → entrada;
   - .botao-lista__contador__pilula (a pílula coral com o número)   → pop.
   Se dividissem a propriedade `animation` do mesmo elemento, remover a classe
   .is-pop no fim do pop faria o animation-name voltar para `badge-entra` — o
   navegador leria isso como uma animação nova e o badge apagaria e re-entraria
   depois de cada mudança de número. Em elementos separados, remover .is-pop
   deixa a pílula sem animação nenhuma e nada é reproduzido de novo.

   A classe .is-pop é posta por ui/contadorLista.js; se o JS não rodar, o badge
   simplesmente não existe (ele só aparece com lista salva), então não há
   conteúdo escondido esperando script. */
@keyframes badge-entra {
  from { opacity: 0; transform: scale(.4); }
  to   { opacity: 1; transform: none; }
}

@keyframes badge-pop {
  0%   { transform: none; }
  35%  { transform: scale(1.32); }
  70%  { transform: scale(.94); }
  100% { transform: none; }
}

@media (prefers-reduced-motion: no-preference) {
  .botao-lista__contador,
  .botao-lista__contador__pilula { transform-origin: center; }

  .botao-lista__contador.is-visivel {
    animation: badge-entra .28s cubic-bezier(.2, .8, .3, 1.2) both;
  }

  .botao-lista__contador__pilula.is-pop {
    animation: badge-pop .34s cubic-bezier(.3, 1.3, .5, 1) both;
  }
}

/* ============================ Hero da home
   `hero-sobe` NÃO mexe em opacity: é o que o <h1> e o parágrafo principal usam,
   para o texto já nascer pintado (LCP intacto). O sticker do Nico usa o mesmo
   motivo, com escala, pela mesma razão. Só os elementos periféricos (eyebrow,
   CTAs, provas) fazem fade. */
@keyframes hero-sobe {
  from { transform: translateY(10px); }
  to   { transform: none; }
}

@keyframes hero-entra {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}

@keyframes hero-nico {
  from { transform: translateY(14px) scale(.96); }
  to   { transform: none; }
}

@media (prefers-reduced-motion: no-preference) {
  .hero h1,
  .hero__texto { animation: hero-sobe .5s cubic-bezier(.2, .7, .3, 1) both; }
  .hero__texto { animation-delay: .06s; }

  .hero__eyebrow { animation: hero-entra .45s cubic-bezier(.2, .7, .3, 1) both; }
  .hero__acoes   { animation: hero-entra .5s cubic-bezier(.2, .7, .3, 1) .14s both; }
  .hero__provas  { animation: hero-entra .5s cubic-bezier(.2, .7, .3, 1) .22s both; }

  .hero__nico { animation: hero-nico .6s cubic-bezier(.2, .7, .3, 1) .08s both; }
}

/* ============================ "Nico vivo" + "O Nico fala" (sticker do hero)
   Três movimentos no mesmo sticker, cada um no SEU elemento — nenhum deles
   compete pela mesma propriedade `transform`, então nenhum cancela ou reinicia
   o outro:

   .hero__nico ............. entrada do hero (hero-nico, acima)
   .nico-falante__inclina .. inclinação até 1,6° na direção do cursor
   .nico-falante__pulo ..... pulo elástico do clique
   .nico-falante__sticker .. respiração em loop

   POR QUE CAMADAS, E NÃO `animation-composition: add` OU CUSTOM PROPERTIES.
   `animation-composition` resolveria respiração + pulo, mas não compõe com o
   `transform` estático que o JS da inclinação escreve, e cai em "a última
   ganha" onde não houver suporte. Compor via custom properties num transform
   só (`translateY(var(--y)) rotate(...)`) exigiria registrar cada uma com
   `@property` para elas interpolarem, e passaria a animar propriedades
   customizadas — que rodam na main thread a cada frame, não no compositor.
   Camada por movimento é o que o projeto já faz com o badge do contador,
   custa dois <span> e mantém cada animação composta pela GPU.

   LCP: nada aqui toca `opacity` do sticker nem o tira do fluxo. O <img>
   continua elegível a LCP, com width/height e sem lazy. */

/* Respiração: sobe/desce ~6px, gira ~1,2° de ponta a ponta e cresce ~1,2%.
   Escala UNIFORME de propósito — marca.md §5 proíbe distorcer o mascote.
   Origem no peito (não no centro) para o movimento parecer respiração e não
   um balanço de pêndulo. */
@keyframes nico-respira {
  0%   { transform: translateY(0) rotate(-.6deg) scale(1); }
  50%  { transform: translateY(-6px) rotate(.6deg) scale(1.012); }
  100% { transform: translateY(0) rotate(-.6deg) scale(1); }
}

/* Pulo do clique: agacha, sobe, aterrissa e dá um repique curto. Sem
   squash-and-stretch (scaleX ≠ scaleY) pelo mesmo motivo da respiração. */
@keyframes nico-pulo {
  0%   { transform: translateY(0) scale(1); }
  16%  { transform: translateY(4px) scale(.985); }
  44%  { transform: translateY(-22px) scale(1.03); }
  68%  { transform: translateY(0) scale(.99); }
  84%  { transform: translateY(-6px) scale(1.004); }
  100% { transform: none; }
}

@media (prefers-reduced-motion: no-preference) {
  .nico-falante__sticker {
    transform-origin: 50% 88%;
    animation: nico-respira 4.6s ease-in-out infinite;
  }

  .nico-falante__pulo { transform-origin: 50% 100%; }
  .nico-falante__pulo.is-pulando {
    animation: nico-pulo .66s cubic-bezier(.3, .8, .4, 1);
  }

  /* Inclinação pelo cursor. O ângulo vem de ui/nicoFalante.js na custom
     property; a `transition` é o amortecedor — o JS escreve o alvo, o CSS faz
     o caminho, então nada precisa ser interpolado à mão em JavaScript.
     `(hover: hover) and (pointer: fine)` é dupla defesa: o JS já checa a mesma
     consulta antes de escutar o ponteiro. */
  @media (hover: hover) and (pointer: fine) {
    .nico-falante__inclina {
      transform-origin: 50% 90%;
      transform: rotate(var(--nico-inclina, 0deg));
      transition: transform .45s cubic-bezier(.22, .61, .36, 1);
    }
  }

  /* Balão: mesma transição de components.css, com overshoot elástico. Sob
     movimento reduzido este bloco não existe e sobra o fade curto de lá — o
     balão continua abrindo ao toque, que é o que não pode faltar. */
  .nico-falante__balao {
    transition: opacity .18s ease, transform .34s cubic-bezier(.34, 1.56, .64, 1);
  }
}

/* ============================ Reveal de seção no scroll
   Scroll-driven animation nativa (animation-timeline: view()): sem
   IntersectionObserver, sem JavaScript, sem custo de main thread.

   O estado inicial invisível fica INTEIRO dentro do @supports: navegador que
   não implementa timelines de scroll (hoje Safari e Firefox estáveis) nunca vê
   opacity: 0 — a página é a de sempre. */
@keyframes revela {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: none; }
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .secao__cabecalho,
    .pilar,
    .grade__item,
    .dica-nico,
    .chamada-final,
    .cartao-info {
      animation: revela linear both;
      animation-timeline: view();
      /* Fecha cedo (elemento ainda entrando) para nada depender de o item
         chegar ao topo da tela. */
      animation-range: entry 10% entry 70%;
    }
  }
}

/* ============================ "Cabecinha inclinada" (hover)
   O Nico inclina a cabeça 11° — o mesmo ângulo da língua do ícone oficial
   (design/marca.md §10) — quando o ponteiro passa pelo logotipo ou pelo selo
   "Aprovado pelo Nico". A curva vai ALÉM de 1 no fim (cubic-bezier com y=1.56),
   então o giro passa um pouco de -11° e volta: é o overshoot elástico, sem
   @keyframes e sem estado a limpar.

   Efeito só de ponteiro, por decisão do dono: em touch não existe hover e não
   vale gastar JS nisso. Nada aqui é informação — quem nunca vir o giro não
   perde nada da página.

   transform-origin no "pescoço" (50% 82%) e não no centro: girar pelo centro
   faz o ícone parecer uma engrenagem; girar pela base faz parecer cabeça. */
@media (prefers-reduced-motion: no-preference) {
  .logo__icone,
  .selo-nico__icone {
    transform-origin: 50% 82%;
    transition: transform .34s cubic-bezier(.34, 1.56, .64, 1);
  }

  /* :focus-visible vai no elemento interativo (o link), nunca no <svg>. */
  .logo:hover .logo__icone,
  .logo:focus-visible .logo__icone,
  .selo-nico:hover .selo-nico__icone { transform: rotate(-11deg); }
}

/* ============================ "O Nico leva pra lista"
   Elemento criado por ui/nicoVoador.js SÓ no clique de "Adicionar à lista" —
   não existe nenhum nó, nenhuma imagem e nenhum estilo em uso até lá, então o
   efeito custa zero no carregamento (LCP intacto).

   `position: fixed` + `pointer-events: none` por dois motivos duros: o voo não
   participa do fluxo (nenhum layout shift, CLS intacto) e não rouba clique de
   nada por baixo. A posição real vem toda de `transform` na Web Animations API;
   este bloco só define a "caixa" parada no canto superior esquerdo. */
.nico-voador {
  position: fixed;
  inset-block-start: 0;
  inset-inline-start: 0;
  width: 38px;
  height: 38px;
  color: var(--coral-500);
  pointer-events: none;
  z-index: var(--z-toast);
}

/* ============================ "O Nico espia" (bloco Dica do Nico)
   O sticker sobe de trás da borda de baixo do bloco, inclinado, e a frase
   aparece depois com pop de balão de fala.

   Duas coisas para não quebrar ao mexer aqui:

   1. O estado inicial (sticker abaixado, frase em opacity: 0) mora INTEIRO
      dentro do @supports. Navegador sem scroll-driven animation (Safari,
      Firefox estáveis) vê o bloco exatamente como sempre foi — nada de conteúdo
      escondido esperando suporte que não existe.
   2. `overflow: clip` no bloco é o que faz o sticker "vir de trás da borda":
      sem ele, o translateY vazaria para fora do card e poderia gerar
      barra de rolagem. Tem que ser `clip`, NUNCA `hidden`: `hidden` transforma
      o bloco em contêiner de rolagem, e aí `view()` passa a medir o sticker
      contra o próprio bloco — onde ele está sempre visível, o que trava a
      animação no estado final (bug pego no teste). `clip` corta sem criar
      scrollport, então a timeline continua sendo a da página.
      Fica dentro do @supports também, porque só é necessário onde a animação
      existe.

   O "160ms depois" pedido pelo design não pode ser animation-delay: numa
   timeline de scroll o tempo é a rolagem, não o relógio. O atraso vira
   deslocamento de animation-range — a frase começa 7% de `cover` depois do
   sticker —, que é o equivalente honesto nessa timeline.

   O range é medido em `cover` (não em `entry`, como os reveals): a fase `entry`
   dura só a altura do próprio bloco (~130px de rolagem), e nela os dois tempos
   estouravam quase juntos, sem dar para ler a encenação. Em `cover`, 20% valem
   ~200px de rolagem — o Nico sobe, e só depois a frase estoura.

   AMPLITUDE RELATIVA, E NÃO UM NÚMERO FIXO. O sticker é o elemento MAIS ALTO do
   bloco (`.dica-nico` é flex/align-items:center e mede ~95px de Nico + padding),
   então a distância entre o pé do sticker e a borda de baixo do bloco é exatamente
   o padding — nunca mais que isso. Qualquer deslocamento menor que
   `altura do sticker + padding` deixa o Nico já visível no começo da animação
   (era o caso dos 46px fixos: ele nascia ~3/4 pintado). Por isso o `from` é
   `100%` (a própria altura do elemento, seja qual for o breakpoint) mais uma
   folga maior que o padding do bloco: só assim ele parte REALMENTE atrás da
   borda. A folga é maior que o padding de propósito — o `rotate(7deg)` do
   quadro inicial cresce a caixa desenhada em ~5px de cada lado, e sem a sobra
   a ponta de uma orelha ficaria pintada na borda antes de a subida começar.

   E O SENTIDO MUDA COM O LAYOUT. Abaixo de 520px `.dica-nico` vira
   `flex-direction: column` com o sticker NO TOPO (components.css): descer o Nico
   ali não o esconde atrás de borda nenhuma — o joga por cima do texto, dentro do
   bloco. Nessa faixa a encenação inverte: ele desce de trás da borda de CIMA,
   com o transform-origin no topo, o que mantém o gesto de "espiar" coerente com
   o lugar onde ele de fato está. Duas variantes do mesmo motivo, não um efeito
   desligado no mobile. */
@keyframes nico-espia {
  from { transform: translateY(calc(100% + var(--sp-6))) rotate(7deg); }
  60%  { transform: translateY(-2px) rotate(-6deg); }
  to   { transform: translateY(0) rotate(-3deg); }
}

@keyframes nico-espia-de-cima {
  from { transform: translateY(calc(-100% - var(--sp-5))) rotate(7deg); }
  60%  { transform: translateY(2px) rotate(-6deg); }
  to   { transform: translateY(0) rotate(-3deg); }
}

@keyframes balao-fala {
  from { opacity: 0; transform: scale(.88) translateY(6px); }
  70%  { opacity: 1; transform: scale(1.04); }
  to   { opacity: 1; transform: none; }
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .dica-nico { overflow: clip; }

    .dica-nico__nico {
      transform-origin: 50% 100%;
      animation: nico-espia linear both;
      animation-timeline: view();
      animation-range: cover 4% cover 24%;
    }

    .dica-nico__frase {
      /* Origem no canto de baixo à esquerda: o balão "estoura" do lado de quem
         está falando, não do meio do parágrafo. */
      transform-origin: 0 100%;
      animation: balao-fala linear both;
      animation-timeline: view();
      animation-range: cover 11% cover 31%;
    }

    /* Mesmo breakpoint de components.css, onde o bloco vira coluna e o sticker
       passa a ser o PRIMEIRO filho: a borda mais próxima dele é a de cima. */
    @media (max-width: 520px) {
      .dica-nico__nico {
        transform-origin: 50% 0;
        animation-name: nico-espia-de-cima;
      }
    }
  }
}

/* ============================ Transição da grade filtrada (/produtos/, /servicos/)
   View Transitions API. Os nomes (view-transition-name) são atribuídos e
   removidos por ui/filtros.js só durante a transição — nada permanente no CSS,
   e navegador sem suporte cai no caminho direto sem erro. */
@media (prefers-reduced-motion: no-preference) {
  ::view-transition-group(*) {
    animation-duration: .32s;
    animation-timing-function: cubic-bezier(.2, .7, .3, 1);
  }
}

/* Rules separadas: seletor inválido derruba a regra inteira, e o wildcard nas
   pseudo-classes de view transition é mais novo que o próprio `root`. */
@media (prefers-reduced-motion: no-preference) {
  ::view-transition-group(root) { animation-duration: .2s; }
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}
