/* ==========================================================================
   NEXUS theme-final / assets/css/11-fx-extra.css
   Folha de apoio de assets/js/core/extra-fx.js: só o que os catorze efeitos
   extras precisam desenhar via seletor de atributo/classe, nunca via regra
   solta (treze até 2026-08-05; x-fundo-lanterna entrou em 2026-08-06, ver
   §8 abaixo). Território exclusivo deste par de arquivos (extra-fx.js e
   este): nenhum outro arquivo do projeto foi tocado para este trabalho.

   Carrega por último entre as folhas de efeito, depois de 10-skin-smooth.css
   e antes de nada mais: os efeitos daqui rodam nas cinco peles, então nenhum
   seletor abaixo é escopado por [data-skin]. Como todo seletor aqui está
   preso a um atributo ou classe que só extra-fx.js escreve, a folha inteira
   fica muda enquanto nenhum efeito estiver ligado, o mesmo "nasce desligado"
   do §6.0.2, só que em arquivo estático em vez de <style> injetado.

   Regras de código cumpridas à risca: nenhum hex, nenhuma unidade vh, nenhum
   !important. Onde os efeitos existentes (fx.js, cult-fx.js, smooth-fx.js)
   usam !important na rede de segurança de prefers-reduced-motion, aqui a
   mesma garantia vem inteira do lado do JS: todo aplicar() deste catálogo
   confere suportado() antes de escrever um estilo, e o interruptor de
   extra-fx.js limpa tudo no instante em que o dono desliga o movimento. A
   folha abaixo faz a parte que CSS puro resolve sem conflito de
   especificidade (parar keyframe, por exemplo), e não tenta fazer a parte
   que só JS resolve direito.
   ========================================================================== */

/* --------------------------------------------------------------------------
   0. REDE DE SEGURANÇA
   Mesmo espírito do bloco equivalente em core/fx.js: sob movimento reduzido
   nenhuma transição nova começa. Sem !important esta regra não vence um
   estilo inline já escrito, mas nenhum efeito daqui escreve estilo inline
   sem antes perguntar suportado() (ver aplicarExtra/aplicarSecaoExtra em
   extra-fx.js), então a janela de risco é a mesma, pequena e documentada,
   dos dois arquivos irmãos.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  [data-x-fx-item],
  [data-x-shine]::after,
  .nx-x-orb,
  .nx-x-provas-dot__fill {
    transition: none;
    animation: none;
  }
}

/* --------------------------------------------------------------------------
   1. BRILHO EM VARREDURA
   Usado por dois efeitos: a revelação x-rev-brilho-varredura (uma vez, ao
   entrar na tela) e o efeito de texto x-texto-brilho (repetido em intervalo,
   enquanto o título está visível). Faixa diagonal de luz sobre o próprio
   elemento, nunca sobre um filho: ::after não precisa de nó novo no DOM, e
   sai de cena sozinho quando o atributo que o liga é removido. Pintura pura
   (gradiente com alfa sobre hsl(var(--foreground))), nunca tinta de glifo.
   -------------------------------------------------------------------------- */
[data-x-shine] {
  position: relative;
}
[data-x-shine]::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  border-radius: inherit;
  background: linear-gradient(
    115deg,
    transparent 35%,
    hsl(var(--foreground) / var(--x-fx-shine-int, 0.16)) 50%,
    transparent 65%
  );
  transform: translate3d(-120%, 0, 0);
  opacity: 0;
}
[data-x-shine="ativo"]::after {
  animation: x-fx-varre calc(var(--x-fx-shine-dur, 900ms) * var(--motion)) var(--ease-out) 1;
}
@keyframes x-fx-varre {
  0% {
    transform: translate3d(-120%, 0, 0);
    opacity: 0;
  }
  15% {
    opacity: 1;
  }
  85% {
    opacity: 1;
  }
  100% {
    transform: translate3d(120%, 0, 0);
    opacity: 0;
  }
}

/* --------------------------------------------------------------------------
   2. TEXTO DATILOGRAFADO (x-texto-datilografia)
   Mesmo par cursor/pisca de cult-terminal, com classe e nome de keyframe
   próprios para não colidir com a folha injetada de cult-fx.js: os dois
   arquivos são territórios diferentes e não podem depender de seletor um do
   outro. white-space: pre-wrap porque o texto datilografado é montado
   caractere a caractere, sem <br>, e a quebra de linha natural precisa
   continuar contando espaço como espaço.
   -------------------------------------------------------------------------- */
.nx-x-tt {
  white-space: pre-wrap;
}
.nx-x-caret {
  display: inline-block;
  inline-size: 0.09em;
  min-inline-size: 1px;
  block-size: 1em;
  vertical-align: -0.14em;
  margin-inline-start: 0.12em;
  background: hsl(var(--foreground));
  animation: x-fx-pisca 1.06s steps(1, end) infinite;
}
@keyframes x-fx-pisca {
  0%,
  49% {
    opacity: 1;
  }
  50%,
  100% {
    opacity: 0;
  }
}

/* --------------------------------------------------------------------------
   3. TEXTO QUE REVELA POR PALAVRA (x-texto-revelar)
   Uma janela de recorte por palavra (overflow: hidden) mais a palavra em
   display: inline-block dentro dela: o mesmo par mascara/conteúdo de
   .nx-fx-linha em core/fx.js, só que a unidade é a palavra, não a linha.
   vertical-align: top evita que o recorte baixo da janela empurre a linha de
   base do texto ao redor enquanto a palavra ainda está fora de vista.
   -------------------------------------------------------------------------- */
.nx-x-mascara {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
}
.nx-x-palavra {
  display: inline-block;
  will-change: transform;
}

/* --------------------------------------------------------------------------
   4. GALERIA EM PILHA (x-extra-photostack)
   A duração e a curva moram aqui, de propósito: os NÚMEROS que mudam a cada
   quadro (repouso/leque) são responsabilidade do JS, que não sabe nada de
   tempo; quem sabe de tempo é a folha, do mesmo jeito que
   [data-fx-hover~="elevar"] separa as duas coisas em core/fx.js.

   POR QUE O TRANSFORM MORA NO WRAPPER, NÃO NA <img> (2026-08-05, correção
   documentada em extra-fx.js, mesmo bloco — leia lá a árvore completa da
   investigação). Resumo dos dois achados que forçaram este desenho:

   1) A <img> já leva `transform`/`opacity` de sections/portfolio.css §10.2
      (`.portfolio__shot`, `.portfolio__shot[data-ativo]`), com
      `transition-property` cobrindo as duas — e uma transição em
      andamento tem prioridade sobre QUALQUER declaração normal do mesmo
      elemento, inclusive estilo inline (CSS Cascading §4.1). Escrever
      transform inline na <img> (a primeira versão deste efeito) perdia
      essa corrida toda vez que --shot-zoom ou data-ativo mudava.
   2) Ainda que se troque para uma regra de classe MAIS específica na
      própria <img> (a segunda versão), sobra 'scroll-paralaxe' (core/fx.js,
      alvo ".media-frame img, .media-frame video, [data-parallax]", ligado
      em todo o site desde o mount) escrevendo `el.style.transform` inline
      na MESMA <img> a cada scroll — e estilo inline ganha de qualquer
      seletor de classe, sem transição envolvida nenhuma dessa vez.

   Não existe seletor de classe que vença um `el.style.transform` escrito
   por outro script na MESMA <img>. Por isso o JS embrulha cada
   <img data-portfolio-shot> num <div class="nx-x-photostack-wrap"> só
   deste efeito (nx-x-photostack-wrap abaixo) e gira/desloca/empilha o
   WRAPPER — scroll-paralaxe não enxerga o wrapper (não é <img>, <video>
   nem [data-parallax]), então os dois efeitos passam a mexer em CAMADAS
   diferentes e param de brigar pela mesma propriedade. O wrapper ocupa
   exatamente o espaço que a <img> ocupava dentro de .media-frame
   (position:absolute;inset:0, a mesma geometria de `.media-frame img` em
   04-sections.css), e vira o novo bloco de conteção da <img> — que
   continua com a MESMA regra `.media-frame img { position:absolute;
   inset:0 }`, então ocupa 100% do wrapper e o resultado visual não muda.

   Opacity fica na própria <img> (`.nx-x-photostack .portfolio__shot`
   abaixo): scroll-paralaxe não toca opacity, e é o único jeito de a pilha
   mostrar as imagens TODAS ao mesmo tempo por cima do rodízio de
   data-ativo do crossfade, sem tocar em data-ativo (o rodízio volta a
   decidir sozinho assim que a classe sai no limpar()). O zoom de hover do
   card (`--shot-zoom`) também fica só na <img>, via a regra de base já
   existente — não é duplicado aqui, senão o zoom compõe duas vezes
   (uma no wrapper, outra na <img>). */
.nx-x-photostack-wrap {
  position: absolute;
  inset: 0;
  transform:
    translate3d(var(--x-stack-x, 0px), 0, 0)
    rotate(var(--x-stack-rot, 0deg));
  z-index: var(--x-stack-z, 0);
  transition: transform calc(var(--dur-base) * var(--motion)) var(--ease-out);
  will-change: transform;
}
.nx-x-photostack .portfolio__shot {
  opacity: 1;
  transition: opacity calc(var(--dur-base) * var(--motion)) var(--ease-out);
}

/* --------------------------------------------------------------------------
   5. CARROSSEL DE PROVAS (x-extra-carrossel)
   Um slide por vez, empilhados por posição absoluta dentro do palco (mesma
   técnica de .nx-cult-3d__slide em cult-fx.js), mais uma régua de pontos
   segmentados abaixo: cada ponto é uma barrinha que a PRÓPRIA barra ativa
   preenche por scaleX, igual à barra de progresso de scroll-progresso em
   core/fx.js. Nenhuma altura anima: a altura do palco vem de min-height
   escrita pelo JS uma vez, a partir do maior card entre os provas.
   -------------------------------------------------------------------------- */
.nx-x-provas-rail {
  position: relative;
}
.nx-x-provas-palco {
  position: relative;
  overflow: hidden;
  inline-size: 100%;
  max-inline-size: 26rem;
  margin-inline: auto;
}
.nx-x-provas-slide {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  inline-size: 100%;
  opacity: 0;
  pointer-events: none;
  transition: opacity calc(var(--dur-slow) * var(--motion)) var(--ease-io);
}
.nx-x-provas-slide[data-ativo] {
  position: relative;
  opacity: 1;
  pointer-events: auto;
}
.nx-x-provas-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  margin-block-start: var(--space-5);
}
.nx-x-provas-dot {
  position: relative;
  inline-size: 2.25rem;
  block-size: 3px;
  border-radius: var(--radius-full);
  overflow: hidden;
  background: hsl(var(--border));
}
.nx-x-provas-dot__fill {
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  inline-size: 100%;
  transform: scaleX(0);
  transform-origin: 0 50%;
  background: hsl(var(--foreground));
}
.nx-x-provas-dot[data-passado] .nx-x-provas-dot__fill {
  transform: scaleX(1);
  transition: none;
}
.nx-x-provas-dot[data-ativo] .nx-x-provas-dot__fill {
  transform: scaleX(1);
  transition: transform var(--x-provas-intervalo, 4200ms) linear;
}

/* --------------------------------------------------------------------------
   6. BOTÃO DE COPIAR (x-extra-copiar)
   Botão pequeno, colado ao link de e-mail que o rodapé já renderiza. Dois
   glifos empilhados na mesma janela (o mesmo padrão de
   .header__toggle-glyph em partials/header.js, que troca menu por x): o que
   entra e sai é opacity e scale, nunca display, então a troca sempre
   transiciona, inclusive sob leitura de tela (os dois SVGs continuam
   aria-hidden, o rótulo acessível é o aria-label do próprio botão).
   -------------------------------------------------------------------------- */
.nx-x-copiar-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 1.8em;
  block-size: 1.8em;
  margin-inline-start: var(--space-2);
  vertical-align: -0.35em;
  border: var(--hairline) solid hsl(var(--border));
  border-radius: var(--radius-sm);
  background: transparent;
  color: hsl(var(--muted-foreground));
  cursor: pointer;
  transition:
    border-color calc(var(--dur-fast) * var(--motion)) var(--ease-out),
    color calc(var(--dur-fast) * var(--motion)) var(--ease-out);
}
.nx-x-copiar-btn:hover,
.nx-x-copiar-btn:focus-visible {
  border-color: hsl(var(--ring));
  color: hsl(var(--foreground));
}
.nx-x-copiar-icone {
  position: relative;
  display: inline-block;
  inline-size: 1em;
  block-size: 1em;
}
.nx-x-copiar-glifo {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transform: scale(0.6);
  transition:
    opacity calc(var(--dur-fast) * var(--motion)) var(--ease-out),
    transform calc(var(--dur-fast) * var(--motion)) var(--ease-out);
}
.nx-x-copiar-icone[data-x-copiar-estado="parado"] .nx-x-copiar-glifo--copiar {
  opacity: 1;
  transform: scale(1);
}
.nx-x-copiar-icone[data-x-copiar-estado="copiado"] .nx-x-copiar-glifo--check {
  opacity: 1;
  transform: scale(1);
}

/* --------------------------------------------------------------------------
   7. ORNAMENTO NO GATILHO DO ESTÚDIO (x-extra-orb)
   Disco de pintura (gradiente cônico com alfa sobre --signal e --foreground,
   nunca cor chapada) maior que o próprio botão e atrás dele em z-index: com
   o botão em fundo opaco (07-studio.css), o disco só aparece na franja que
   sobra ao redor, como um halo girando devagar. --signal continua abaixo dos
   3% de tela que o token pede: o disco mede poucos pixels de franja visível.
   -------------------------------------------------------------------------- */
.nx-x-orb {
  position: absolute;
  inset: calc(var(--x-orb-escala, 1.6) * -18%);
  z-index: -1;
  pointer-events: none;
  border-radius: var(--radius-full);
  background: conic-gradient(
    from 0deg,
    hsl(var(--signal) / 0.5),
    hsl(var(--foreground) / 0.16),
    hsl(var(--signal) / 0.5)
  );
  animation: x-fx-orb-girar calc(var(--x-orb-vel, 7s) * var(--motion)) var(--ease-io) infinite;
}
@keyframes x-fx-orb-girar {
  0% {
    transform: rotate(0deg) scale(0.94);
    opacity: 0.5;
  }
  50% {
    transform: rotate(180deg) scale(1.05);
    opacity: 0.85;
  }
  100% {
    transform: rotate(360deg) scale(0.94);
    opacity: 0.5;
  }
}

/* --------------------------------------------------------------------------
   8. LANTERNA NO FUNDO (x-fundo-lanterna)
   Pedido do dono, 2026-08-06: halo que segue o cursor pelo fundo da página
   inteira, iluminando sutilmente o padrão de grade/dots por onde o mouse
   passa. Ler o adendo datado no cabeçalho de core/extra-fx.js para a
   árvore completa da decisão de z-index; resumo do que importa aqui:

   `.nx-x-lanterna` usa z-index: -1, o MESMO valor de `body::before`
   (02-base.css §6) e de `.nx-pattern` (07-studio.css §19.1) — nunca um
   valor ≥ 0, que entraria na categoria de pintura que fica NA FRENTE do
   conteúdo comum em fluxo, não atrás. Como o JS só cria este nó quando o
   dono liga o efeito pelo painel (sempre depois de `.nx-pattern` já
   existir, criado no boot por studio.js), a ordem de árvore garante que
   `.nx-x-lanterna` pinta por CIMA dos dois padrões e por BAIXO de todo o
   resto da página — mesmo z-index, elementos mais tarde na árvore vencem
   o empate. Limite aceito na pele vidro: `[data-skin="glass"] body::after`
   (o grão, 06-skin-glass.css) também usa z-index -1 e, por ser ::after,
   conta sempre como o último nó gerado, então pode pintar por cima do
   halo ali — opacidade do grão é baixíssima, sem disputa perceptível.

   `--x-lanterna-raio`/`--x-lanterna-intensidade` são escritas UMA vez por
   aplicar() (core/extra-fx.js); o radial-gradient também é pintado uma
   vez, aqui, no CSS. O único valor que muda por quadro é `transform` no
   próprio elemento inline (nunca nesta folha): é essa separação que
   cumpre o adendo 16 (nenhum repaint, só composição). */
.nx-x-lanterna {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  contain: layout paint;
}
.nx-x-lanterna__halo {
  position: absolute;
  top: 0;
  left: 0;
  inline-size: calc(var(--x-lanterna-raio, 360px) * 2);
  block-size: calc(var(--x-lanterna-raio, 360px) * 2);
  margin-inline-start: calc(var(--x-lanterna-raio, 360px) * -1);
  margin-block-start: calc(var(--x-lanterna-raio, 360px) * -1);
  border-radius: var(--radius-full);
  /* Luz neutra, nunca cor nova: alfa de --foreground decaindo a
     transparente, a mesma receita de tinta de todo este arquivo. */
  background-image: radial-gradient(
    circle,
    hsl(var(--foreground) / var(--x-lanterna-intensidade, 0.25)) 0%,
    hsl(var(--foreground) / 0) 70%
  );
  /* Fora da tela até o primeiro pointermove escrever a posição real: sem
     isso o halo nasceria cravado no canto 0,0 por um quadro. Deslocamento
     em px fixo (nunca vh/vw: regra de código deste arquivo, cabeçalho) —
     qualquer tela real fica bem aquém dele. */
  transform: translate3d(-99999px, -99999px, 0);
  will-change: transform;
}
@media (prefers-reduced-motion: reduce) {
  .nx-x-lanterna {
    display: none;
  }
}
@container style(--motion: 0) {
  .nx-x-lanterna {
    display: none;
  }
}
