Communitygithub.com

anhlamtruong/living-ui-effects

Adiciona efeitos interativos ao vivo que mantêm uma página web em movimento após o carregamento, incluindo shader aurora, cartas 3D holográficas, spotlight de grade e texto com glitch/scramble.

O que é living-ui-effects?

Este skill injeta um conjunto de quatro efeitos visuais sofisticados e de alto desempenho em uma página React/Next.js, transformando-a em uma experiência interativa e "viva". Os efeitos incluem: um shader WebGL de aurora/luz líquida que reage ao cursor, cartas 3D com efeito holográfico (inclinação, brilho e borda iridescente), um spotlight de grade que segue o ponteiro e texto com glitch (divisão RGB) e scramble (decodificação ao passar o mouse). A implementação é feita para ser drop-in: os arquivos são copiados para o projeto e montados via componentes React. O sistema é otimizado para evitar jank e layout shift, utilizando um loop de animação (rAF) compartilhado que "dorme" quando não há animações ativas, animando apenas propriedades de compositor como transform, opacity e clip-path. A acessibilidade é garantida: textos reais permanecem ocultos de leitores de tela e o layout não sofre alterações. O skill é ideal para portfólios, landing pages ou qualquer site que queira causar uma impressão "uau", premium e interativa. Ele suporta personalização via variáveis CSS e um sistema de temas para shaders, além de respeitar preferências de movimento e interação por toque.

Funciona com~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/anhlamtruong/living-ui-skills/tree/HEAD/skills/living-ui-effects

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Efeitos ao vivo

Quatro efeitos que mantêm uma página em movimento depois que seu conteúdo está na tela:

  • Aurora hero: um campo de luz suave (WebGL) atrás do hero que se curva ao redor do ponteiro.
  • Cartas holográficas: uma carta inclina-se em direção ao cursor, levanta-se e capta um brilho e uma borda iridescente.
  • Spotlight de grade: a grade de fundo da página acende em um círculo suave ao redor do cursor.
  • Texto com glitch e scramble: títulos com divisão RGB ao aparecer, e rótulos de links que decodificam ao passar o mouse.

O padrão é "vivo, nunca atrapalhando". As pessoas devem sentir a página reagir a elas, mas nada pode tremer, deslocar o layout, roubar cliques ou ocultar texto de um leitor de tela.

Exemplo ao vivo: https://www.lamanhtruong.com (passe o mouse sobre os cartões de projeto, mova-se sobre o hero, passe o mouse sobre a navegação).

O que está em assets/fx/

ArquivoFunção
ticker.tsUm loop requestAnimationFrame compartilhado que dorme quando nada solicita frames
built.tsPortão de prontidão (isBuilt, buildActive), frozen(), PRNG com semente, media query de ponteiro
aurora.tsO shader WebGL, seu escalonador de qualidade e ciclo de vida (createAurora)
hero-shader.tsx<HeroShader />, o wrapper React para a aurora
holo.tsInclinação + brilho + brilho em cartas, física de mola, overlay injetado
spotlight.tsSpotlight de grade que segue o cursor (apenas transform)
glitch.ts, glitch-copies.tsxAgendador de glitch, <GlitchCopies /> e <GlitchHeading />
scramble.ts, scramble-text.tsxExecutor de scramble e o hook de marcação <ScrambleText />
fx-root.tsx<FxRoot />: monta holo, spotlight, glitch e scramble uma vez
fx.cssTodos os estilos, tematizados com propriedades customizadas --fx-*

O código só precisa de React; nenhuma biblioteca de animação. Os templates são verificados com TypeScript estrito contra os tipos do React 18 e 19.

Instalação

  1. Copie assets/fx/ para o aplicativo, ex.: src/components/fx/. Mantenha os arquivos juntos, pois eles importam uns aos outros por caminho relativo.
  2. Monte-o. A página em si pode continuar sendo um componente servidor estático; apenas FxRoot e HeroShader são componentes cliente.
import { FxRoot } from "@/components/fx/fx-root";
import { HeroShader } from "@/components/fx/hero-shader";
import { GlitchHeading } from "@/components/fx/glitch-copies";
import { ScrambleText } from "@/components/fx/scramble-text";

export default function Page() {
  return (
    <div className="fx-grid">                        {/* grade base fixa; pule se você já tiver a sua */}
      <main className="relative z-[1]">              {/* conteúdo fica acima da grade + spotlight */}
        <section className="relative min-h-[80vh]">  {/* hero deve ser position: relative */}
          <HeroShader />                             {/* primeiro filho, atrás do conteúdo do hero */}
          <h1>…</h1>
        </section>

        <GlitchHeading as="h2" className="text-5xl font-semibold">Trabalhos selecionados</GlitchHeading>

        <article data-holo className="rounded-2xl border p-6">
          <a href="/work/x" className="inline-flex items-center gap-2">
            <ScrambleText>Leia o estudo de caso</ScrambleText> <ArrowIcon />
          </a>
        </article>
      </main>
      <FxRoot />                                     {/* uma vez por página, dentro do wrapper da grade */}
    </div>
  );
}
  1. Tema: edite as variáveis --fx-* no topo de fx.css e passe palette / intensity para <HeroShader /> (veja "Tematização" abaixo).
  2. Verifique com as verificações do Playwright em references/verification.md antes de considerar concluído.

Desligue partes com props: <FxRoot holo={false} spotlight={false} />. Aponte a inclinação para cartas existentes sem tocar na marcação delas: <FxRoot holo={{ selector: ".card", within: "main" }} />.

Regras que mantêm a suavidade

Estas regras são o que separa "premium" de "travado". Cada uma existe porque quebrá-la causou um problema visível.

  1. Aguarde até que o conteúdo esteja pronto. Nunca incline, glitche ou embaralhe algo que outro script ainda está revelando ou medindo. Uma transformação em um elemento durante a revelação corrompe as medições de quem o está animando, e o resultado parece um travamento. built.ts implementa um contrato: enquanto html[data-build="on"] estiver definido, um elemento só conta depois que seu [data-build-item] e [data-build-section] carregam [data-built]. Sem um mecanismo de build na página, tudo conta como construído imediatamente. Qualquer animação de introdução pode optar por isso definindo esses atributos. Detalhes: references/gating-and-performance.md.

  2. Use um único loop de frames e deixe-o dormir. Cada efeito assina o ticker.ts em vez de chamar requestAnimationFrame por conta própria. Um assinante retorna false quando se estabiliza, e quando ninguém precisa de frames, o loop para. Uma página ociosa então não custa nada, e os efeitos não podem competir entre si por frames. Glitch não usa frames: são keyframes CSS alternados por um atributo.

  3. Anime apenas transform, opacity e clip-path. Eles ficam no compositor. Nada aqui muda tamanho, posição no fluxo ou métricas de fonte, portanto, deslocamento de layout é zero por construção. O scramble mantém o texto real no lugar (transparente) e desenha os glifos em um overlay, então mesmo um rótulo em mudança nunca muda de largura.

  4. Combine o efeito com a entrada. A inclinação e o spotlight de rastreio só existem em (hover: hover) and (pointer: fine). No toque, inclinar sob um dedo parece um bug, então o toque recebe um breve fade no ponto de toque e uma lente de shader autodeslocante.

  5. Deixe o DOM do React em paz quando em repouso. O <span> do overlay holo e o transform inline são adicionados quando uma carta é ativada e removidos quando ela retorna ao repouso. Em repouso, o DOM é exatamente o que o React renderizou, então re-renderizações, hidratação e os estilos :hover da própria carta continuam funcionando. As camadas de glitch e os spans de scramble são escritos na marcação (<GlitchCopies>, <ScrambleText>) em vez de inseridos em torno de nós de propriedade do React em tempo de execução; reestruturar a árvore do React de fora quebra a reconciliação.

  6. Mantenha o texto real. As cópias de glitch são apenas do lado do cliente e aria-hidden, para que o HTML estático, a pesquisa na página e os leitores de tela vejam cada título uma vez. O overlay de scramble é aria-hidden e o rótulo real permanece o nome acessível durante todo o processo.

  7. Limpe e sobreviva ao StrictMode. Cada setup* retorna um descartável que remove listeners, observadores, overlays e estilos inline. O shader libera seu buffer e programa, mas deliberadamente não chama WEBGL_lose_context, porque o React StrictMode monta, desmonta e remonta no mesmo canvas, e um contexto perdido permanece perdido.

  8. Orce a GPU. O shader renderiza em resolução reduzida sob um orçamento de pixels, desenha apenas enquanto visível, limita os draws a ~80 por segundo em telas de alta taxa de atualização e reduz a qualidade uma vez se a página rodar abaixo de ~38 fps. Ele se recusa a iniciar em um fallback de software (failIfMajorPerformanceCaveat) e renderiza nada em vez de algo lento.

Os efeitos

Shader de aurora hero (aurora.ts, hero-shader.tsx)

O shader é ruído de valor com warp de domínio (fbm warpado por fbm), desenhado como um brilho amplo mais fios finos e brilhantes ao longo dos contornos warpados. O ponteiro atua como uma lente: ele amplia e gira o fluxo próximo. Entrar no hero, clicar ou mover-se rapidamente deixa cair um anel de ondulação suave. A saída é alpha pré-multiplicado e esmaecido em cada borda, para que o fundo da página e qualquer grade fixa apareçam, e o hero se misture com a próxima seção em vez de terminar em uma caixa.

Ciclo de vida: o canvas surge em 1,8 s depois que sua seção é construída. Ele renderiza apenas enquanto a seção está na tela (IntersectionObserver com margem de 80 px) e a aba está visível. Se o WebGL estiver ausente, não renderiza nada.

Controles:

  • palette: { veil, primary, secondary, accent } como hex. A paleta é compilada no shader, portanto, mudanças exigem uma remontagem.
  • intensity: multiplica a luz. 0.6 é calmo, 1.4 é alto.
  • Legibilidade do texto: o shader desvia a luz do canto inferior esquerdo, onde o texto do hero geralmente fica. Se o seu texto estiver em outro lugar, mude a linha bias (veja a referência).

Leia references/aurora-shader.md antes de alterar a matemática do shader, a velocidade ou o escalonador de qualidade.

Cartas holográficas (holo.ts)

pointerover delegado encontra a carta sob um ponteiro fino. Ao engajar, mede a caixa não transformada da carta, injeta um span de overlay aria-hidden e executa uma mola criticamente amortecida (sub-passo a 120 Hz para se comportar igual a 60 ou 144 fps) em direção a uma inclinação apontada para o ponteiro. A borda sob o cursor vem em direção ao visualizador. Ao sair, volta com um leve overshoot e remove tudo depois que se estabiliza.

  • A inclinação escala com o tamanho da carta, 2,5° a 7,5°: cartas grandes inclinam menos para que suas bordas distantes não balancem. A perspectiva é 1,6× o lado maior. A elevação é de 3 px.
  • O overlay tem um brilho radial no ponteiro, um leve lavagem de cor diagonal e um anel de gradiente cônico mascarado na borda (escuro ao redor, brilhante perto do ponteiro).
  • Em uma carta com overflow: hidden, a caixa da borda de 1 px é cortada, então o anel é desenhado logo dentro (.fx-holo--in, detectado automaticamente).
  • O overlay fica em z-index: 3 com pointer-events: none, então ele pinta sobre controles dentro da carta (um alternador de vídeo, um link) sem bloquear cliques.
  • Uma carta com position: static recebe position: relative apenas enquanto ativa.
  • Opt-out: data-holo="off" em uma carta ou qualquer ancestral. Pause toda inclinação com html[data-fx-freeze] enquanto algo lê retângulos reais (um inspetor, uma passagem de captura de tela). O alternador de inspeção living-ui-swarm é detectado automaticamente.

Spotlight de grade (spotlight.ts, div.fx-spot)

Uma janela fixa de 560 px, mascarada radialmente, contém uma cópia mais brilhante da grade base. Cada frame move apenas duas transformações: a janela segue o cursor, e a grade dentro é deslocada de volta por floor(x / cell) * cell - x para que suas linhas caiam exatamente no reticulado da grade base. Ambas as grades devem estar ancoradas na origem da viewport, e .fx-grid::before está. A lerp de rastreio se estabiliza e o loop dorme quando o cursor para. Ele se esconde no blur da janela ou quando o ponteiro sai da janela. O toque recebe um fade de 360 ms no ponto de toque.

O tamanho da célula da grade, o espaçamento das linhas finas e o tamanho da janela vêm de --fx-grid-cell, --fx-grid-fine e --fx-spot-size (valores em px), para que o CSS permaneça a única fonte da verdade.

Títulos com glitch (glitch.ts, <GlitchHeading> / <GlitchCopies>)

Marcação: um wrapper .fx-glitch[data-fx-glitch] contendo o título real (.fx-glitch__base) e duas cópias apenas do lado do cliente. GlitchHeading escreve tudo; use GlitchCopies diretamente quando o título já existir e carregar outros atributos.

  • Entrada: 560 ms quando um título construído está pelo menos 50% visível. O mesmo título não será reproduzido novamente dentro de 2,5 s.
  • Micro-glitch: 260 ms em um título visível aleatório a cada 8–14 s.
  • A aleatoriedade é semeada, então o ritmo é o mesmo em todas as visitas e nos testes.
  • Apenas um título glitcha por vez; as entradas se enfileiram atrás umas das outras.
  • Implementado como keyframes CSS puros alternados por [data-glitch]: fatie com clip-path: inset(), desloque com translate3d e divida a cor com text-shadow.
  • --fx-gi escala os deslocamentos por execução.

Texto scramble (scramble.ts, <ScrambleText>)

Envolva apenas o rótulo, nunca o ícone: <a><Icon/> <ScrambleText>{label}</ScrambleText></a>.

  • Gatilhos: hover (não toque) ou :focus-visible do teclado no a, button ou [data-fx-scr-host] envolvente.
  • Duração: 360–520 ms, dependendo do comprimento.
  • Resolução: os caracteres resolvem da esquerda para a direita com um pequeno tremor. A cauda não resolvida redesenha a ~18 Hz em vez de a cada frame, então ela é lida como decodificação, não ruído.
  • Pools de glifos: correspondência de maiúsculas/minúsculas, e pontuação como @ . / : - permanece no lugar para que e-mails e caminhos permaneçam legíveis durante o scramble.
  • Limpeza: o overlay é sempre esvaziado, incluindo um fallback de timer para abas em segundo plano onde o rAF para.

Use inline para links inline simples (mantém o sublinhado). Use quiet em botões preenchidos, junto com --fx-scr-tail: currentColor no botão.

Tematização

VariávelUsada porPadrão
--fx-accentcamada de glitch A, glifos de scramble, brilho do spotlight, sombra do holo#5eead4
--fx-hotcamada de glitch B#f472b6
--fx-sheen-1..5anel holo, em ordem ao redor do círculoteal, azul, violeta, rosa, âmbar
--fx-grid-line, --fx-grid-fine-linelinhas da grade baseslate com alpha baixo
--fx-grid-cell, --fx-grid-fineespaçamento da grade (px)96px, 24px
--fx-spot-sizejanela do spotlight (px)560px
--fx-monofonte dos glifos de scramblemono do sistema
--fx-scr-tailsubstituição de cor do glifo por elementonão definido

As cores se misturam através de color-mix(in srgb, …), suportado em todos os navegadores atuais. O shader recebe sua própria paleta: <HeroShader palette={{ accent: "#22d3ee", secondary: "#f0abfc" }} intensity={0.8} />.

Escolha cores de shader da mesma família do acento da página. O shader é um clima, não uma segunda cor de marca; se um visitante de primeira viagem notar o shader antes do título, ele está muito alto.

Camadas

Camadaz-indexNotas
Grade base (.fx-grid::before, fixa)0Ancorada na origem da viewport
Spotlight (div.fx-spot, fixo)0Após a grade na ordem da árvore, então pinta acima dela
Conteúdo (main, footer)1Deve ser posicionado com z-index: 1
Shader hero (canvas.fx-shader)autoAbsoluto dentro do hero, primeiro filho, para que o conteúdo posterior pinte sobre ele
Overlay holo3 dentro de sua cartapointer-events: none
Cabeçalho fixo, qualquer canvas de overlay de animação de buildacima do conteúdoNão afetado

Se o spotlight aparecer acima do seu texto, o wrapper de conteúdo está sem position: relative; z-index: 1.

Preferências de movimento

Em lamanhtruong.com o proprietário escolheu tocar todos os efeitos para todos, incluindo visitantes com prefers-reduced-motion, porque o movimento é o ponto do portfólio. Essa é uma decisão de produto legítima, mas tome-a com propósito. Para respeitar a configuração:

  • <FxRoot motion="reduce" /> desliga holo, spotlight e o movimento das cores de glitch, mas mantém o shader (ele se move lentamente por conta própria) e o scramble (ele resolve a igual, apenas mais rápido). O shader também reduz sua velocidade de deslocamento.
  • Passe motion="reduce" em FxRoot e seu componente de página pode ler prefers-reduced-motion no CSS e aplicar --fx-motion-reduce: 1; no corpo.
  • O shader já escuta prefers-reduced-motion e reduz a amplitude de deslocamento se estiver definido, mas motion="reduce" também o desacelera.

Suporte a build engines

O gating de build é opcional e automático. Se a página carregar qualquer atributo data-build relevante antes do primeiro requestAnimationFrame, o sistema espera até que esses nós estejam prontos antes de anexar efeitos. Caso contrário, ele age imediatamente.

Depuração

  • Defina localStorage.fx_log = "1" para ver logs de ciclo de vida de cada efeito no console.
  • Defina localStorage.fx_spam = "1" para registrar cada frame de ticker (alto volume).
  • Pressione Ctrl+Shift+D três vezes para ativar uma sobreposição de depuração que mostra o estado de cada efeito, contagem de frames e uso de GPU.

Testes

As verificações de regressão visual do Playwright estão em references/verification.md. Execute-as em um ambiente de CI que suporte WebGL (por exemplo, Playwright com --disable-gpu não é suficiente; use chromium com args: ['--use-gl=egl']).

Créditos

Efeitos de anhlamtruong/living-ui-effects. Consulte o repositório original para atualizações e problemas.

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

Habilidades Relacionadas