Stretchy morph UI
Esta skill produz interfaces onde cada mudança de estado morfoseia: um indicador de aba se estica como uma minhoca a caminho da próxima aba, um botão se comprime em um círculo de carregamento e incha de volta, uma imagem de produto se levanta do cartão, encolhe em um chip e cai no carrinho. Foi destilada da construção de um showcase de 24 componentes (em assets/showcase.html) e dos bugs e correções de design encontrados ao longo do caminho.
A aparência vem de uma ideia: o alongamento é causado por algo. Um elemento se deforma porque suas partes discordam (uma borda chega antes da outra), porque está se movendo rapidamente (alonga-se ao longo de seu caminho), porque está sendo comprimido (incha no outro sentido para manter seu volume), ou porque acabou de ser atingido (oscila). Uma oscilação aleatória em loop parece barata; alongamento com causa parece material.
Etapa 0, sempre: audite o código do usuário e confirme
Antes de construir ou editar qualquer coisa, descubra o que na própria interface do usuário pode se tornar elástico e deixe-o escolher. Detalhes, critérios de julgamento e modelos de mensagem estão em references/audit.md; a versão resumida:
- Encontre o código: código colado, arquivos anexados (no claude.ai, liste
/mnt/user-data/uploads), o diretório de trabalho ou repositório, um link de artefato (leia com a ferramenta de artefato), ou uma fonte que o usuário indicar. Se não houver, diga o que procurou e ofereça dois caminhos: compartilhe o código ou construa algo novo do zero. - Escaneie-o:
python scripts/audit_ui.py <caminhos>relata a pilha, movimento existente, manipulação ausente de movimento reduzido e candidatos por categoria de UI comarquivo:linha, cada um mapeado para um componente de showcase e técnica. - Leia cada acerto em contexto e mantenha apenas candidatos reais. Para cada um, anote onde está, o que acontece agora, a frase de transformação que se tornaria, esforço (pequeno, médio, grande) e qualquer risco (marcação de biblioteca de componentes, biblioteca de animação existente, um controle nativo cujo seletor móvel seria perdido, listas muito longas).
- Priorize por visibilidade × frequência × adequação, agrupe duplicatas (todos os botões que usam um
Buttoncompartilhado são um item), limite a lista a cerca de dez, e liste qualquer coisa contra a qual você aconselhe com o motivo. - Peça confirmação na linguagem do usuário: as propostas numeradas, depois uma pergunta convidando-o a escolher números, pegar todas as recomendadas ou digitar partes extras que ele deseja que sejam elásticas e que não estão na lista. Use uma ferramenta de escolha tocável se o ambiente tiver uma, caso contrário texto simples. Termine o turno aí; não faça edições antes da resposta.
- Construa o que eles escolheram mais o que eles adicionaram, depois siga o Fluxo de Trabalho abaixo e relate por item.
Se o usuário já nomeou as partes que deseja, ainda audite: coloque as partes dele em primeiro lugar, ofereça o restante como opcional e mantenha a pergunta curta.
Arquivos
Todos os caminhos são relativos à pasta desta skill (a pasta que contém este SKILL.md), não ao projeto do usuário. Resolva-os em relação à pasta da skill quando você os ler ou executar.
| Caminho | O que é | Quando abrir |
|---|---|---|
assets/stretchy-core.js | O motor de molas mais ajudantes (pares de borda, indicador segmentado, escala de volume, bordas SVG que incham, reordenação FLIP, objetos lançados) | Cole em cada página que você construir. Leia uma vez para conhecer os ajudantes. |
assets/starter.html | Esqueleto da página: tokens claro/escuro, fonte variável com suporte a tailandês, tratamento de movimento reduzido, o core embutido, um componente de exemplo | Comece uma nova página a partir dele |
assets/showcase.html | Todos os 24 componentes funcionais da construção original. Cada seção JS começa com um banner como /* ---------- tabs ---------- */ | Quando uma solicitação corresponder a um componente do catálogo, grep -n "/\* ---------- " assets/showcase.html e leia apenas aquela seção mais seu CSS |
references/audit.md | Como encontrar, escanear, julgar e priorizar o código do usuário, e os modelos de mensagem de confirmação (tailandês e inglês) | Na Etapa 0, sempre |
references/techniques.md | As 11 técnicas de alongamento em profundidade, com código e números de ajuste | Antes de construir algo que você não construiu antes |
references/components.md | Catálogo: receita de cada componente, valores de mola e pegadinhas | Quando a solicitação nomear ou se assemelhar a um componente do catálogo |
references/new-components.md | Como projetar uma versão elástica de qualquer componente, mais 14 designs trabalhados não presentes no showcase | Sempre que a solicitação estiver fora do catálogo |
scripts/audit_ui.py | Scanner para o código do usuário: pilha, movimento existente e candidatos elásticos com arquivo:linha | Na Etapa 0, sempre |
scripts/check_page.py | Verificação headless do navegador: erros JS, transbordamento lateral no celular e desktop, elementos invisíveis roubando cliques e folhas de contato de quadros de animação | Após cada construção, antes de entregar qualquer coisa |
Se uma skill de design visual estiver disponível (por exemplo frontend-design ou ui-ux-pro-max), leia-a também. Essa skill decide como a página se parece (paleta, tipografia, layout); esta decide como ela se move. O polimento do movimento não pode resgatar uma página de aparência genérica.
Fluxo de trabalho (após o usuário confirmar)
- Escreva a frase de transformação para cada componente. "Quando gatilho, forma A se torna forma B, e ela se alonga porque causa." Exemplo: "Quando uma aba é clicada, o indicador se move para ela, e ele se alonga porque sua borda dianteira é mais rígida que a traseira." Se você não conseguir preencher a causa, escolha uma da tabela abaixo. Esta frase é o design; todo o resto é implementação.
- Escolha a(s) técnica(s) da tabela. A maioria dos componentes usa um alongamento primário mais uma pequena oscilação de feedback.
- Defina estados de repouso como números: larguras, alturas, raios, posições, progresso de 0 a 1. Molas animam números e você os escreve em
stylenoonUpdate. Transições CSS lidam apenas com cor e opacidade (e crossfades curtos de conteúdo). - Planeje o conteúdo. Texto e ícones não devem refluir ou se achatar enquanto o contêiner se transforma. Dê a cada estado de conteúdo sua própria camada em seu tamanho final, ancorada (geralmente centralizada), e faça crossfade: saída rápida (≈150 ms), entrada após um atraso de ≈100–150 ms com uma ligeira escala ou desfoque. O
overflow:hiddendo contêiner revela o conteúdo à medida que ele cresce. - Construa a partir de
assets/starter.htmlcom o core embutido. Escreva estilos diretamente em callbacks de molas; nunca acione uma re-renderização de framework por quadro. - Passagem de acessibilidade e interrupção (lista de verificação abaixo).
- Teste e observe. Execute
scripts/check_page.py, depois capture folhas de contato de quadros das interações importantes e realmente olhe para elas. A animação de adicionar ao carrinho na construção original "funcionou" mas parecia barata até os quadros serem inspecionados; as correções estão em "Fazendo parecer bom".
De onde vem o alongamento
| Situação | Técnica (detalhes em references/techniques.md) | Visto em |
|---|---|---|
| Algo se move entre posições | Par de bordas: duas molas para bordas dianteira e traseira, dianteira rígida, trocada por direção | Abas, pontos de carrossel, interruptores, chips de escolha, ponto de rádio, stepper, destaque de dropdown, cápsula de avaliação, navegação de barra lateral |
| Um contêiner muda de tamanho ou forma | Eixos divididos: molas separadas para largura, altura, raio com rigidez diferente, de modo que um eixo lidera | Ilha dinâmica, FAB para formulário, modal a partir de botão, dropdown, botão de adicionar ao carrinho |
| Algo fica mais estreito ou mais largo rapidamente | Preservação de volume: escalar o outro eixo pela velocidade, scaleY = clamp(1 − v/k) | Botão de envio, campo de pesquisa, largura de dropdown |
| Algo viaja rápido | Alongamento por velocidade: alongar na direção do movimento, afinar transversalmente | Polegar de controle deslizante, item de carrinho lançado, cartões de carrossel (inclinação), dica de ferramenta |
| Uma superfície grande se move | Borda que incha: desenhar a superfície como um caminho SVG cujo ponto de controle de borda é deslocado por −velocidade | Barra lateral, folha inferior |
| Uma parte depende de uma parte móvel | Seguidor atrasado: uma mola mais suave persegue a principal; use a lacuna para inclinação ou deslocamento | Bolha de valor de controle deslizante, texto interno de acordeão |
| Feedback para um toque ou chegada | Chute: adicionar velocidade a uma mola em repouso em 0, ela oscila e se acomoda | Caixa de seleção, chips, carrinho, estrelas, emblema, tremor de limite de quantidade |
| Blobs se juntam ou se separam | Filtro pegajoso: desfoque mais limite alfa, fonte nítida composta em cima | Menu pegajoso |
| Um elemento se torna um elemento diferente | Transformação de contêiner: comece no retângulo do gatilho, vá para o retângulo alvo com mola, crossfade de conteúdo | Modal, FAB, lançamento de carrinho |
| A mudança precisa de uma pausa no meio | Transformação sequenciada através de uma forma intermediária que carrega significado (spinner, verificação) | Botão de envio, botão de adicionar ao carrinho |
| Arrastar além de um limite, ou uma lista reordena | Elástico (dividir overscroll por ≈3) e FLIP com molas que mantêm velocidade | Carrossel, folha, lista de produtos ordenada |
O motor em um parágrafo
Spring é um oscilador harmônico amortecido integrado com Euler semi-implícito em 8 subpassos por quadro (delta do quadro limitado a 1/30 s para que uma aba em segundo plano não o exploda). to(alvo) redireciona sem redefinir a velocidade, que é o que torna cliques rápidos contínuos em vez de reiniciar. set(x) pula sem animação, drag(x, v) segue um ponteiro enquanto registra a velocidade, kick(v) injeta um impulso. O amortecimento é multiplicado por um fator global bounce a cada tick, então um controle no nível da página (suave 1.8, saltitante 1, elástico 0.5) reajusta cada componente ao vivo. Sob prefers-reduced-motion: reduce, to() vai direto ao alvo e dispara onRest imediatamente, para que a lógica que espera uma animação ainda seja executada.
Razão de amortecimento ζ = c / (2√k). Mudanças de estado da UI parecem corretas em torno de ζ 0.6–0.8; feedback lúdico em 0.3–0.5; acima de 1 nada ultrapassa. As predefinições usadas no showcase:
| Função | k | c | Notas |
|---|---|---|---|
| Borda dianteira de um par de bordas | 420–520 | 30–34 | Chega primeiro |
| Borda traseira | 140–170 | 17–19 | Atrasa, o que cria o alongamento |
| Largura do contêiner (transformação) | 240–320 | 18–24 | |
| Altura do contêiner (transformação) | 150–170 | 15–17 | Mais lenta que a largura, então a forma se alonga lateralmente primeiro |
| Seguidor de ponteiro (controle deslizante) | 650 | 42 | Apertado, mas ainda tem velocidade para ler |
| Seguidor atrasado (bolha) | 150 | 11 | Propositalmente solto |
| Feedback de chute | 380–420 | 12–16 | Chute com 6–10 |
| Altura com ultrapassagem visível (acordeão) | 210 | 14 |
Regras práticas
- Limite toda deformação. Controles: preservação de volume dentro de ±7–15 %. Coisas pequenas e rápidas (um chip lançado, um polegar de controle deslizante): até 35–70 % de alongamento. Sempre limite os tamanhos a um mínimo positivo, já que uma mola subamortecida pode ultrapassar abaixo de zero.
- Troque a rigidez por direção. Em um par de bordas, qualquer borda que enfrente a direção do movimento recebe a mola rígida. Sem a troca, o indicador se alonga corretamente em um sentido e comprime no outro.
- Tempo oposto no retorno. Se a largura lidera a altura ao abrir, deixe a altura liderar a largura ao fechar. A forma então refaz um caminho diferente, que é lido como elástico em vez de um rebobinado.
- Molas para mudanças de estado, curvas baseadas em tempo para caminhos cuja forma importa. Um objeto lançado precisa de um arco previsível (Bézier quadrática, progresso ease-in-out, cerca de 800 ms). Molas x/y independentes dão caminhos errantes. Derive o alongamento da velocidade quadro a quadro de qualquer forma.
- Mantenha o conteúdo reto. Para alongar algo em uma direção arbitrária sem girar sua imagem ou texto, use
rotate(a) scale(1+s, 1−s·0.45) rotate(−a). - Um movimento ousado por componente. O resto do componente permanece quieto: uma mudança de cor, um fade curto.
- Calmo quando ocioso. Nada oscila por conta própria. No máximo um momento orquestrado no carregamento da página (o showcase abre a ilha uma vez após 900 ms).
Fazendo parecer bom (lições da construção)
A primeira versão de adicionar ao carrinho voou uma bola colorida lisa ao longo de duas molas independentes, encolheu o botão em uma oval violeta vazia e esmagou o carrinho. O usuário disse que não era bonito. O que consertou generaliza:
- Mova a coisa real. Clone a imagem do produto, levante-a ligeiramente com uma sombra, depois encolha-a em um chip redondo que ainda mostra a imagem enquanto voa. Pontos abstratos parecem saída de depuração.
- Formas intermediárias devem carregar significado. Um botão encolhido é uma bolha vazia a menos que mostre algo: um spinner, uma marca de verificação que se desenha, uma contagem. O botão de adicionar agora se torna um círculo verde com uma marca, depois cresce para o stepper − n +.
- Volume sutil em controles. Um inchaço vertical de ±18 % em um botão parecia um ovo em forma de pílula. ±7 % parece material macio.
- A chegada precisa de um receptor. O ícone do carrinho mergulha e inclina, um anel pulsa para fora, o emblema aparece. Algo deve visivelmente pegar o que foi lançado.
- Não corte marcas de status. Um emblema dentro de uma pílula com
overflow:hiddenem transformação é cortado; coloque-o em um wrapper fora do clipe. - Não repita informações. A pílula do carrinho mostrava "3 ชิ้น 195 บาท" ao lado de um emblema dizendo 3. Agora mostra apenas o total.
- Desenhe ilustrações em uma proporção fixa. Formas CSS posicionadas por porcentagem dentro de um bloco se alongam quando o bloco se alarga. Use um SVG inline com um
viewBox, centralizado no bloco.
Lista de verificação de acessibilidade e robustez
- Respeite movimento reduzido (o core o faz; certifique-se de que keyframes CSS e transições também sejam cortados em uma media query
prefers-reduced-motion). - Use
button,input,role="switch",role="tab",role="slider"reais, etc. com o estadoaria-*correto, atualizado na mesma chamada que inicia a animação. - Conteúdo que está oculto por uma transformação recebe
inert(e não deve interceptar cliques: dê a camadas invisíveispointer-events:none, depois ligue-o novamente com o estado visível). - Mova o foco deliberadamente quando o controle focado desaparecer em uma transformação: para o controle de substituição, de volta ao gatilho após um diálogo fechar, e apenas depois que o elemento estiver visível novamente (um elemento
visibility:hiddennão pode receber foco). - Apenas uma mola global
bouncepor página. Não aninhe; um bounce aninhado distorce as proporções de amortecimento. - A mola de um componente vive até que sua página seja descartada. Se o componente for removido do DOM, pare a mola ou ela continuará a definir estilos em um nó removido.