iso: explicadores isométricos animados
Uma cena conta uma história de produto em um loop de 6 a 14 segundos: as coisas chegam, a luz viaja entre elas, uma parte acende de cada vez e o loop retorna ao seu primeiro quadro. Um fluxo tem apenas uma forma. Escolha a forma que a história precisa da tabela de batidas: uma caixa que se abre para mostrar suas camadas, uma troca entre dois objetos, uma grade que se preenche, uma pilha que gira uma escolha ou telhas que o visualizador levanta ao passar o mouse. Tudo é SVG e HTML reais, escala com sua caixa, pausa fora da tela e para em movimento reduzido.
O kit fica ao lado deste arquivo:
lib/iso.ts: o construtorScene(projeção, blocos, faces, fios, pulsos, rótulos, trilhas de movimento),scene.exportSvg(),responsive()elazyBox().lib/runtime.ts: reproduz uma cena renderizada com WAAPI, inicia sua introdução quando 40% dela (ou metade da altura da tela, para uma cena alta) está visível, pausa fora da tela, descarta as animações quando está a mais de meia tela de distância e as recoloca no mesmo quadro antes de retornar, para para movimento reduzido, expõewindow.isoSeek(t).scripts/render.ts: renderiza um módulo de cena (exportação padrãoPage) em um arquivo HTML independente com o runtime embutido.scripts/shots.mjs: captura telas de qualquer página em momentos e larguras escolhidos e escrevesheet.png.scripts/check.ts: verificação de tipo estrita de arquivos de cena em qualquer lugar do disco contra o kit (bun scripts/check.ts scene.ts).scripts/page-idle.mjs: percorre uma página com muitas cenas uma vez, depois para em 0, 20, 35, 50, 60 e 80% e imprime o tempo da thread principal em cada um, com a CPU em 1x e 4x (node scripts/page-idle.mjs page=<url> width=393 dpr=3 scroll=4). Execute em qualquer página com mais de algumas cenas.scripts/perf.mjs: reproduz uma página no Chrome em largura de telefone com a CPU limitada (node scripts/perf.mjs page=<out.html> width=393 throttle=4 seconds=20) e imprime taxa de quadros, percentis de tempo de quadro, quadros atrasados, tarefas longas e tempo da thread principal. Execute em qualquer cena com 30 ou mais objetos em movimento.scripts/contrast.mjs,scripts/render-all.ts: mede cada texto em uma página renderizada contra os pixels atrás de seus glifos no quadro parado e mais quatro momentos, e falha abaixo de 4.5:1 (node scripts/contrast.mjs <out.html>;bun scripts/render-all.ts <dir>escreve cada exemplo do catálogo primeiro). Execute em cada cena antes de considerá-la concluída.scripts/study-rive.mjs,scripts/compare-rive.mjs: ferramentas opcionais para estudar uma animação.rivprópria quadro a quadro e compará-la com uma cena do kit. Ambos precisam deruntime=<folder with canvas_advanced.mjs and rive.wasm>.examples/: 64 cenas para produtos fictícios, cada arquivo exportaexample(título, história, prompt, nível, categoria, layout, interação eart: as cenas compacta e ampla deart(compact, wide, breakpoint)) e umaPagepadrão, entãoscripts/render.tsrenderiza qualquer uma delas. Uma cenaspoté uma única cena pequena,art(compact).hero.tsexportahero(umaArt) ematerial-gallery.tsexportagalleries(umaArtpor família de acabamento), cada um com umaPagepadrão.examples/shared.tscontém as superfícies, rampas de cores,paints, fontes efitX(), que mantém um rótulo dentro de sua cena no cartão mais estreito em que se encaixará.
Escolha o exemplo mais próximo da sua história e leia-o antes de desenhar:
| Exemplo | Nível | Mostra |
|---|---|---|
notifications.ts | simples | o arquivo mínimo: um núcleo se espalhando para três telhas, pulsos, flash de chegada e salto. Leia primeiro |
ci-deploy.ts | simples | um pacote saltando ao longo de uma corrente, um amassado no pouso, uma cor de estado apenas no final |
file-upload.ts | simples | chunks preenchendo uma barra de progresso, depois cópias se espalhando para pontos de borda; traços marchando em slots vazios |
db-branching.ts | simples | um fantasma tracejado que se torna sólido, uma telha de mudança que desliza de volta e se funde |
service-map.ts | simples | hover: nove telhas slab({ hover }) com rótulos revelados ao passar o mouse ou tocar, além de um loop que ainda conta a história |
auth-flow.ts | médio | um telefone panel() vertical, entrada digitada, fantasmas passando um token |
etl-pipeline.ts | médio | telhas de origem alimentando um núcleo ao longo de fios que se desenham junto com elas, depois uma grade de preencher e esvaziar; sobreposições agrupadas com o bloco em que se sentam para que se movam juntas |
queue-workers.ts | médio | uma fila de slots acionada por uma tabela de eventos que a construção valida (sem estouro, sem retirar do vazio) |
search-index.ts | médio | telhas de arte linear em uma página clara, uma consulta digitada, correspondências acendendo, resultados classificados subindo |
feature-flags.ts | médio | play: um <button> real em um rótulo e um script inline que repete uma etapa do loop com seek |
agent-tools.ts | complexo | um núcleo com satélites, pulsos de chamada e resposta, um cartão de resposta que digita |
checkout-payment.ts | complexo | um anel de etapas com um caminho de retorno tracejado e uma linha de status que faz crossfade |
edge-cache.ts | complexo | a mesma requisição duas vezes: uma falha para a origem, depois um acerto na borda |
observability.ts | complexo | três looks de sinal (traços, pontos, spans), barras prism com heightTo, um alerta e recuperação |
keycard-slot.ts, battery-charge.ts, tile-loader.ts, empty-box.ts | spot | um objeto pequeno para uma lista de recursos, carregador ou estado vazio; um cartão panel() vertical cortado no lábio de um slot, células heightTo, uma onda de queda diagonal, traços marchando |
sync-loop.ts, inbox-fill.ts, alert-stack.ts, table-scan.ts | spot | setas amassadas no plano, uma bandeja com frente de vidro, cartões deslizando em uma pilha com um badge que conta e cavalga o cartão superior, uma lente deslizando linha por linha |
audit-ledger.ts, dns-resolve.ts, cron-jobs.ts | simples | páginas empilhando com selos, um salto de cada vez com respostas, um dial que aciona tarefas |
vault-rotate.ts, firewall-filter.ts, zero-trust.ts | médio | uma troca de chave com o padrão de slot, um pacote rejeitado quicando de volta, cada requisição carimbada |
workload-scheduler.ts, autoscale.ts, health-check.ts | médio | posicionamento a partir de uma pequena tabela de regras, abrir espaço para um novo nó, um nó puxado e reintegrado |
blue-green.ts, snapshot-restore.ts | médio | um fio redirecionado, um cursor deslizando de volta ao longo de uma linha do tempo |
embedding-clusters.ts, training-loss.ts, recs-rerank.ts, ab-test.ts | médio | pontos se estabelecendo em clusters, barras prism caindo, uma prateleira de slot reordenando, uma divisão com um vencedor |
data-erasure.ts, rag-answer.ts | complexo | uma requisição se espalhando para seis sistemas com um contador, cartões recuperados voando para um modelo e uma resposta digitando |
hero.ts | complexo | um prompt cultiva um sistema em camadas de 1, 5, 15 e 40: um giro de disco e uma queda ponderada, saídas deslizando por baixo, pops, uma piscina que cai com um preenchimento de grade, requisições infinitas, uma inclinação em direção ao mouse em repouso; flashes por fill e telhas repetidas por <use> para manter o custo baixo; apenas text() SVG, então exportSvg() funciona nele |
funnel-live.ts, billing-cycle.ts, parcel-delivery.ts, test-shards.ts | médio, complexo | layout: 'wide': tiras 3:1 lidas da esquerda para a direita (estágios estreitando, uma tira de mês que muda sem salto, veículos carregando uma caixa, fragmentos correndo em pistas e se fundindo) |
kanban-move.ts, stock-sync.ts, diff-merge.ts, refund-flow.ts | simples a complexo | layout: 'diagonal': um cartão ou token viajando ao longo de um eixo iso, portões ficando verdes, um reembolso percorrendo o mesmo caminho em uma segunda cor |
live-cursors.ts, two-approvals.ts, design-comments.ts, translate-six.ts | simples, médio | layout: 'panels': panel()s verticais frente a frente; cursores digitando, duas marcas que destravam um cadeado, pins e um tópico, uma mensagem em seis idiomas com uma pilha de fontes tailandesa e japonesa |
cms-publish.ts, sensor-gateway.ts, smart-home.ts, currency-convert.ts | simples, médio | layout: 'radial': uma cópia para cinco canais, leituras entram e depois uma onda de chão sai, dispositivos mudando de estado por vez, um pulso uma vez ao redor de um fio de anel |
ota-rollout.ts, build-cache.ts, calendar-booking.ts, load-slider.ts | simples a complexo | layout: 'field': ondas através de um campo de dispositivo com uma parada e retentativa, acertos de cache calculados a partir de uma tabela de dependência, uma reunião ignorando um conflito; load-slider é um controle de arrasto (um input range real, mouse, toque e teclas) que busca o loop enquanto o tráfego ambiente continua em movimento |
video-transcode.ts, image-resize.ts, fraud-gauge.ts, data-lineage.ts, drill-down.ts | simples a complexo | layout: 'exploded': representações se levantando e codificando, tamanhos descascando, camadas de risco se somando além de uma linha, um caminho upstream acendendo através de camadas; drill-down abre uma parte em suas camadas a partir de um botão real buscando o segmento de loop dessa parte |
materials-*.ts, material-gallery.ts | 49 acabamentos de lado em seis famílias (Metal, Vidro, Natural, Padrão Tech, Retro, Generativo) como fábricas (scene) => Material, uma galeria sem rótulo por família. Acabamentos generativos recebem uma semente. Copie um acabamento em vez de inventar um |
Configuração única: cd <this dir>/scripts && bun install && bunx playwright install webkit (capturas de tela usam o Google Chrome instalado para Chromium). Verificação de tipo: scripts/node_modules/.bin/tsc -p <this dir>.
1. Storyboard antes de desenhar
Abra recipes.md primeiro. Ele contém todos os padrões medidos de movimento e composição (chegadas, conexões, fluxos, comparações, escolha, ocioso, interação, easing, superfícies), cada um com seus números e chamadas do kit. Componha a história a partir dessas receitas e mantenha seus tempos.
Escreva a história como uma tabela de linha do tempo primeiro: o que o espectador deve entender, as 3 a 6 batidas que mostram isso, e para cada batida qual elemento se move, como, quando e por quanto tempo. Uma ideia por batida. Uma batida que precisa de um parágrafo para explicar são duas batidas ou a imagem errada.
Formas de batida comprovadas, com tempos medidos:
| Batida | O que acontece | Tempo |
|---|---|---|
| Chegar | Peças caem uma a uma, ultrapassam um pouco, assentam | dropIn: 0,55 s cada, 83 ms de intervalo, 46 px de queda |
| Troca | Contornos fantasmas de uma telha deslizam de um objeto para outro | 4 fantasmas, 0,22 s de intervalo, 1,2 s cada, ease.io |
| Viajar | Um pulso de luz curto corre ao longo de um fio; o alvo pisca na chegada | 270 px/s, pulso ~0,12 a 0,16 do fio, subida 0,1 s, desaparecimento 0,6 s |
| Revelar camadas | Uma caixa sólida fica transparente; camadas internas acendem uma de cada vez com um rótulo | ligado 0,23 s, segurar ~1 s, desligado 0,2 s, próxima começa quando a última termina |
| Girar a escolha | Uma pilha sobe um slot; o cartão ativo fica de cor de destaque | 0,9 s de movimento a cada 3 s, início lento, ease-out longo |
| Preencher e esvaziar | Uma grade preenche telha por telha, segura, volta ao vazio | 7 quadros (0,12 s) de intervalo para dentro, 2 quadros de intervalo para fora |
| Puxar | Quando um fio encurta ou alonga, a caixa à qual está preso é puxada cerca de 6 px ao longo do fio, segura, depois volta lentamente, então o fio parece ter tensão | 0,23 s de puxada, 0,25 s de segurar, 0,65 s de volta com um ease-out longo |
| Levantar hover | Uma telha sobe 16 px e assume sua cor de marca, seu nome aparece | slab({ hover }): 270 ms ligado, 325 ms desligado, ease.out |
Regra de conexão: um fio está na tela apenas enquanto ambas as suas extremidades estão. Um fio para algo que chega no meio do loop se desenha com seu pulso (wire({ dash: '1 1' }), depois at(id, 'strokeDashoffset', t, d, 1, 0, ease.linear)) e sai com a coisa que se junta. Nunca coloque tal fio na introdução. Um fio nunca cruza a face superior de uma caixa: adicione-o antes dos objetos que ele une (para que eles pintem sobre ele) e comece-o na borda da caixa, não no seu centro.
Regra de loop: o último quadro é igual ao primeiro. Qualquer coisa que muda durante o loop muda de volta antes de terminar. Movimento apenas de introdução vai em intro(); o loop começa quando a introdução termina, e a introdução deve terminar na mesma imagem com que o loop começa. Um elemento sem introdução mostra o valor inicial do loop durante a introdução, que é o último to de seus segmentos de loop.
A ordem de pintura é fixa: o que é adicionado depois sempre cobre o que foi adicionado antes. Movimento que mudaria quem cobre quem (um cartão se movendo do topo de uma pilha para o fundo) é desenhado como slots. Cada elemento é um slot que se move um passo e desaparece ou recolor como seu vizinho, e cada segmento começa novamente a partir do valor de repouso do slot, então todos os slots pulam de volta juntos enquanto a imagem parece a mesma. Dê a cada slot um rótulo por opção e faça crossfade em qual é mostrado ao lado do slot ativo.
2. Escolha um layout, depois componha para o telefone primeiro
Cada exemplo nomeia sua composição em layout: wide (uma tira longa lida da esquerda para a direita; sua cena ampla é cerca de 3:1, como 1200x400, com ponto de quebra 720, e o showcase a mostra como um cartão de largura total), diagonal (uma fileira longa ao longo de um eixo iso), panels (panel()s verticais frente a frente), radial (um núcleo com coisas em um anel), field (muitas telhas vistas de cima; uma ratio um pouco mais íngreme ajuda), exploded (camadas levantadas verticalmente) ou object (um ou dois objetos para uma lista de recursos ou estado vazio). Escolha aquele que a história precisa antes de desenhar; uma página de cenas lê melhor quando elas variam.
Uma cena que o espectador pode reproduzir (interaction: 'play' | 'drag' | 'explore') envia um pequeno script de módulo inline no head de art(): <button> real ou <input type="range"> em um label({ html }), eventos de ponteiro para mouse e toque (touch-action: none em um controle de arrasto), teclas também, um MutationObserver para vincular cenas que montam mais tarde, o loop pausado enquanto o espectador age e retomado após alguns segundos ociosos, e um loop que ainda conta a história sem entrada (feature-flags.ts, load-slider.ts, drill-down.ts).
Páginas de estande e landing são abertas em telefones. Construa duas cenas e envolva-as com responsive(compact, wide):
- compacto: cerca de 390 de largura, retrato ou quadrado (390x420 a 390x640). Empilhe a história de cima para baixo. Projete em 390 unidades para que um rótulo
size: 13seja 13 px em um telefone. Nunca abaixo de 13. - amplo: cerca de 900 a 1200 de largura, paisagem, a história da esquerda para a direita.
Sa...
[truncado para tradução]