{
  "id": "clube-morph",
  "titulo": "Uma forma, 28 batidas",
  "categoria": "saas",
  "resumo": "motion de interface de 14 s, quadrado, em que uma única forma nunca é cortada: ela vira botão, loader, check, Dynamic Island, player, slider de volume, toggle, abas, gráfico, barra ⌘K, paleta de comandos, toast e volta ao botão, uma mudança por batida, guiada por um cursor que clica e arrasta de verdade. O último quadro é igual ao primeiro, então o vídeo roda em loop sem emenda. É uma recriação: a referência é o \"Shape morphing through UI states\" de @twoclipping no prompt-motion.com (feito com o Claude Opus 5.5 em HTML + Playwright), que usava tela clara e componentes em preto e branco. A nossa versão seguiu o prompt dele inteiro e vestiu a identidade do Spotify: fundo escuro, verde como única cor de destaque e a música \"Maps\" tocando no próprio player. O autor aprovou a v2.1.",
  "indicado": "app, SaaS ou produto digital mostrando os componentes da interface; designer de produto montando portfólio de UI motion; post em loop para redes.",
  "estilo": [
    "UI motion",
    "morph contínuo",
    "modo escuro",
    "uma cor de destaque",
    "cursor real",
    "loop perfeito",
    "sincronizado na batida"
  ],
  "ficha": {
    "formato": "1440×1440, 60 qps, quadrado",
    "duracao": "14,0 s (840 quadros), 7 compassos a 120 BPM, sem narração",
    "motor": "html",
    "voz": "ver o guia de estilo",
    "musica": "ver o guia de estilo",
    "fontes": "ver o guia de estilo"
  },
  "ferramentas": [
    "Claude Code"
  ],
  "ficha_tecnica": {
    "Duração": "14,0 s (840 quadros), 7 compassos a 120 BPM, sem narração",
    "Formato": "1440×1440, 60 qps, quadrado",
    "Ferramenta": "HTML num arquivo só com `seek(t)` puro, render com Playwright (Python) e junção no ffmpeg",
    "Desfoque de movimento": "4 subquadros por quadro, obturador de 180°, `tmix` no ffmpeg",
    "Trilha": "\"Maps\", Maroon 5, refrão de 40,42 s a 54,42 s (120,0 BPM medidos)",
    "Som": "89 eventos de interface (cliques, pops, whooshes, teclas, tiques do arrasto), exportados do próprio HTML; −14,9 LUFS medido no final, pico −1,4 dB",
    "Loop": "`seek(14)` e `seek(0)` dão o mesmo quadro, pixel a pixel",
    "Referência": "\"Shape morphing through UI states\", @twoclipping, prompt-motion.com"
  },
  "etapas": [
    {
      "titulo": "O roteiro",
      "texto": "**Resumo:** o vídeo não tem narração; o roteiro é a grade de batidas escrita antes do código, com o estado da forma e o texto da tela em cada batida. A 120 BPM, a batida k cai em 0,5·k s, e 7 compassos fecham os 14 s do loop.\n\n## De onde veio a estrutura\n\nO prompt de @twoclipping traz a sequência pronta: botão → loader → check → Dynamic Island → player com morph de play/pause → arrastar a barra de progresso → a barra vira slider de volume que estica além do máximo → toggle na batida → o knob vira indicador líquido de abas → as abas abrem num gráfico que se desenha, com tooltip no hover → recolhe em ⌘K → digitar para filtrar → enter → toast → volta ao botão. Ele pede para perguntar os estados, a cor e a música antes de começar e para mostrar a grade antes de escrever código. Aqui as respostas foram os estados do próprio prompt com conteúdo do Spotify, o verde da marca e \"Maps\".\n\n## Grade de batidas\n\n | \n\n | Compasso\n | Batida\n | t (s)\n | Estado e ação\n | Texto na tela\n\n | 1\n | 0\n | 0,0\n | botão verde em hover (cresce 3%)\n | \"Gerar playlist\"\n\n | 1\n | 1\n | 0,5\n | clique: encolhe em loader circular\n | —\n\n | 1\n | 2\n | 1,0\n | o arco fecha em check\n | —\n\n | 1\n | 3\n | 1,5\n | estica em Dynamic Island com capa e equalizador verde\n | —\n\n | 2\n | 4\n | 2,0\n | abre no player\n | \"Maps\" · \"Maroon 5\" · \"0:40\" / \"3:09\"\n\n | 2\n | 5\n | 2,5\n | clique no play: morph play → pausa\n | —\n\n | 2\n | 6\n | 3,0\n | agarra a barra de progresso e arrasta; o tempo acompanha\n | —\n\n | 2\n | 7\n | 3,5\n | solta\n | —\n\n | 3\n | 8\n | 4,0\n | a barra vira slider de volume\n | —\n\n | 3\n | 9\n | 4,5\n | arrasta o volume para baixo; o ícone perde as ondas\n | —\n\n | 3\n | 10\n | 5,0\n | arrasta além do máximo: a forma estica\n | —\n\n | 3\n | 11\n | 5,5\n | solta: volta com mola de onde estava\n | —\n\n | 4\n | 12\n | 6,0\n | o polegar vira o knob de um toggle\n | —\n\n | 4\n | 13\n | 6,5\n | clique: o toggle liga (verde) e o knob estica na frente\n | —\n\n | 4\n | 14\n | 7,0\n | o knob vira o indicador líquido das abas\n | \"7 dias · 28 dias · 12 meses\"\n\n | 4\n | 15\n | 7,5\n | clique em \"28 dias\"\n | —\n\n | 5\n | 16\n | 8,0\n | clique em \"12 meses\"\n | —\n\n | 5\n | 17\n | 8,5\n | as abas abrem no cartão de streams; a linha se desenha e o número conta\n | \"Streams\" · \"Maps · Maroon 5\"\n\n | 5\n | 18\n | 9,0\n | hover no gráfico: o tooltip segue a linha\n | —\n\n | 5\n | 19\n | 9,5\n | tooltip no pico\n | —\n\n | 6\n | 20\n | 10,0\n | clique em \"28 dias\": linha e número mudam\n | —\n\n | 6\n | 21\n | 10,5\n | o cartão recolhe na barra de busca\n | \"Buscar\" · \"⌘K\"\n\n | 6\n | 22\n | 11,0\n | ⌘K abre a paleta de comandos\n | \"O que você quer ouvir?\" · \"Adicionar à fila\" · \"Ir para o rádio da música\" · \"Salvar em Músicas Curtidas\" · \"Abrir Músicas Curtidas\" · \"Compartilhar música\"\n\n | 6\n | 23\n | 11,5\n | digita \"curt\": a lista filtra\n | \"curt\"\n\n | 7\n | 24\n | 12,0\n | clique em \"Salvar em Músicas Curtidas\"; a linha acende em verde\n | —\n\n | 7\n | 25\n | 12,5\n | vira o toast\n | \"Adicionada às Músicas Curtidas\"\n\n | 7\n | 26\n | 13,0\n | o toast volta a ser o botão\n | \"Gerar playlist\"\n\n | 7\n | 27\n | 13,5\n | o cursor volta ao botão; hover, e o loop fecha\n | —\n\n## Momentos para a trilha\n\n- O trecho da música começa num tempo forte (40,42 s de \"Maps\") e dura exatamente 7 compassos, por isso o loop não tem emenda na música.\n\n- Cada mudança de estado tem um som na batida; os arrastos ganham um tique por passo e a paleta, uma tecla por letra.\n\n- Os sons ficam no próprio HTML (`window.SFX`), são exportados para `audio/sfx.json` e entram no mix pelo pico de cada arquivo."
    },
    {
      "titulo": "A identidade visual",
      "texto": "**Resumo:** interface escura do Spotify num quadrado de 1440 px, uma forma que muda de estado a cada batida, molas em forma fechada com no máximo uma passagem pequena e um cursor que age de verdade. O verde `#1ED760` é a única cor de destaque. Números tirados do `index.html` e do `mixar.py`.\n\n## Cores e fonte\n\n | \n\n | Token\n | Valor\n | Papel\n\n | Fundo\n | `#121212`\n | tela inteira\n\n | Superfície\n | `#1F1F1F`\n | player, slider, abas, cartão do gráfico\n\n | Superfície alta\n | `#282828`\n | barra ⌘K e paleta de comandos\n\n | Toggle desligado\n | `#535353`\n | knob apagado\n\n | Verde\n | `#1ED760`\n | botão, toggle ligado, linha do gráfico, equalizador, cursor de digitação\n\n | Texto\n | `#FFFFFF`\n | títulos\n\n | Secundário\n | `#B3B3B3`\n | artista, tempos, ícones\n\n | Terciário\n | `#8A8A8A`\n | placeholder, atalhos\n\n | Trilho da barra\n | `#4D4D4D`\n | progresso e volume\n\n | Grade do gráfico\n | `#2E2E2E`\n | linhas tracejadas\n\n | Tecla\n | `#3E3E3E`\n | teclas do atalho ⌘K\n\n | Toast\n | `#FFFFFF`\n | o único estado claro\n\n- **Fonte: Figtree**, a geométrica aberta mais próxima da tipografia do Spotify, com números tabulares.\n\n- Ícones em SVG com traço de 2 px e pontas redondas. Área do gráfico em degradê do verde (28% → 0).\n\n## Os estados da forma\n\n`[largura, altura, raio]` em px, no código (`ST`):\n\n | \n\n | Estado\n | Tamanho\n | Cor\n\n | botão\n | 236 × 60, raio 30\n | verde\n\n | loader\n | 60 × 60, raio 30\n | verde\n\n | Dynamic Island\n | 300 × 70, raio 35\n | preto\n\n | player\n | 380 × 236, raio 22\n | superfície\n\n | slider de volume\n | 400 × 64, raio 32, estica além do máximo\n | superfície\n\n | toggle\n | 116 × 64, raio 32\n | cinza → verde\n\n | abas\n | 420 × 60, raio 30\n | superfície\n\n | gráfico\n | 440 × 330, raio 24\n | superfície\n\n | ⌘K\n | 230 × 56, raio 28\n | superfície alta\n\n | paleta\n | 420 × 312, raio 18 (filtrada: 420 × 164)\n | superfície alta\n\n | toast\n | 400 × 60, raio 30\n | branco\n\n## Molas\n\nRespostas ao degrau em forma fechada, `[ω, ζ]` (frequência e amortecimento):\n\n | \n\n | Nome\n | ω\n | ζ\n | Uso\n\n | shape\n | 13\n | 0,84\n | a forma entre estados\n\n | cam\n | 7,2\n | 1\n | câmera que enquadra cada estado\n\n | cur\n | 10,5\n | 0,93\n | cursor\n\n | fast / slow\n | 21 / 10,5\n | 0,86 / 0,97\n | borda da frente e borda de trás do knob e do indicador\n\n | pin / pout\n | 27 / 26\n | 1\n | conteúdo que entra e sai\n\n | press\n | 32\n | 0,82\n | aperto do clique (cursor a 84%, forma a 95–96%)\n\n | data\n | 8\n | 0,95\n | linha do gráfico e contador\n\n | soft\n | 9\n | 1\n | hover\n\n- Um valor com vários alvos é a soma de uma mola por mudança; cada mudança parte do valor anterior.\n\n- O conteúdo entra com desfoque de 7 px que some e sai antes de a forma mudar.\n\n- O arrasto além do máximo usa resistência exponencial (no máximo 58 px de esticada) e, ao soltar, uma mola livre (ω 16, ζ 0,8) a partir da posição e da velocidade daquele instante.\n\n## Cursor\n\n- Trajeto por pontos com a mola do cursor; durante os arrastos, um arco leve de 5 a 8 px (seno ao quadrado, zera nas pontas).\n\n- Nove apertos, cada um com a seta encolhendo para 84% e uma onda leve na tela.\n\n- O valor da barra e do volume é calculado a partir da posição do cursor enquanto ele segura.\n\n## Câmera\n\nEscala por estado para cada um ocupar o quadro: 3,05 no loader, 2,9 na ilha, 2,74 no player, 2,66 no volume, 3,85 no toggle, 2,76 nas abas, 2,62 no gráfico, 3,05 no ⌘K e 2,56–2,72 no resto.\n\n## Render\n\n60 qps; cada quadro é a média de 4 subquadros espalhados em meio quadro (obturador de 180°), juntados com `tmix=frames=4` e `select` de 1 em 4.\n\n## Som\n\n | \n\n | Item\n | Valor\n\n | Música\n | \"Maps\", Maroon 5, refrão de 40,42 s a 54,42 s (7 compassos), ganho 0,27\n\n | Efeitos\n | 14 arquivos nivelados: cliques, clique de mouse, pops, teclas, digitação, whooshes curtos e longo\n\n | Posição\n | cada som em `evento − pico`, com tom variando ±3%\n\n | Reforços\n | sons de interface ×1,35 (v2) e cliques e digitação de 1,4 a 2,2 vezes mais (v2.1)\n\n | Loop\n | caudas que passam de 14 s voltam no início\n\n | Final\n | `loudnorm` em duas passagens; medido −14,9 LUFS, pico −1,4 dB\n\n## O que é \"humano\" neste vídeo\n\n- A mão faz arco no arrasto, aperta no clique e respira entre as ações.\n\n- O knob estica na frente antes de a traseira alcançar.\n\n- O volume puxado além do máximo resiste e volta de onde estava, sem salto.\n\n- O contador e a linha do gráfico assentam com mola, e o número ganha um desfoque leve só enquanto corre."
    },
    {
      "titulo": "A montagem em HTML",
      "texto": "| \n\n | Arquivo\n | O que faz\n\n | `html/index.html`\n | `seek(t)` calcula cada estilo do zero. As molas são respostas ao degrau em forma fechada; um valor que muda de alvo várias vezes é a soma de uma mola por mudança (`track`), com cópias do loop anterior para o fim encaixar no começo. Também guarda a lista de sons em `window.SFX`\n\n | `render.py beats`\n | um quadro no meio de cada uma das 28 batidas e a folha de contato, para revisar antes do render\n\n | `render.py frames 3.0 3.25`\n | quadros avulsos\n\n | `render.py sfx`\n | exporta `window.SFX` para `audio/sfx.json`\n\n | `render.py full`\n | 840 quadros × 4 subquadros em JPEG numa pasta temporária, prontos para a junção\n\n | `audio/batidas.py`\n | mede batidas e tempos fortes com o beat_this\n\n | `audio/mixar.py`\n | corta o refrão, posiciona cada som pelo pico medido com variação de tom de ±3%, faz as caudas que passam de 14 s darem a volta no início e normaliza em duas passagens\n\n- **Junte as pastas:** copie `audio/` para dentro de `projeto/` (o `render.py` grava em `audio/sfx.json` e a junção usa `audio/mix.wav`).\n\n- **Ver:** abra `html/index.html` no Chrome e chame `seek(3.2)` no console para parar num instante.\n\n- **Preparar:** `pip install playwright` e `python -m playwright install chromium`; ffmpeg no PATH; numpy para o mix e beat_this para medir batidas.\n\n- **Revisar por batida:** `python render.py beats` gera `revisao/batidas/` e a folha.\n\n- **Som:** no `audio/mixar.py`, aponte `SFX_DIR` para `audio/sfx/nivelados`; rode `python render.py sfx` e depois `python audio/mixar.py`.\n\n- **Vídeo:** `python render.py full` grava os subquadros na pasta definida no fim do `render.py` (troque pelo caminho que quiser) e depois junte:\n\nffmpeg -framerate 240 -i <pasta>/s%05d.jpg -i audio/mix.wav -filter_complex \"[0:v]tmix=frames=4,select='eq(mod(n\\,4)\\,3)',setpts=N/60/TB,format=yuv420p[v]\" -map \"[v]\" -map 1:a -r 60 -c:v libx264 -crf 14 -c:a aac -b:a 320k render/spotify-morph-vN.mp4\n\n**Pegadinhas:** o primeiro screenshot depois de abrir sai diferente (o `render.py` aquece com quatro capturas antes); nada de `will-change` no que a câmera escala, ou o texto sai borrado; texto que troca dentro de um contêiner que se transforma precisa de entrada e saída próprias, senão um atravessa o outro; nas bordas do knob, \"indo para a direita\" quer dizer borda direita rápida."
    },
    {
      "titulo": "As decisões de direção",
      "texto": "- **Uma forma só, do começo ao fim.** Cada estado é o mesmo elemento mudando largura, altura, raio e cor; o conteúdo troca com um desfoque curto e entra depois da forma, sai antes dela.\n\n- **Algo acontece em toda batida.** A grade de 28 batidas foi escrita antes do código, e cada estado tem a sua.\n\n- **O cursor dirige tudo.** Cliques com aperto e onda, arrastos que leem a posição do cursor (o valor segue a mão e volta com mola de onde estava quando solta) e um arco leve na mão durante o arrasto, porque a mão real não anda em linha reta.\n\n- **Bordas em molas diferentes.** No toggle e nas abas, a borda da frente vai numa mola rápida e a de trás numa lenta: o knob estica como líquido.\n\n- **Arrastar além do máximo estica a forma** com resistência crescente e devolve com mola livre ao soltar.\n\n- **A câmera enquadra cada estado** para que ele ocupe o quadro, com mola criticamente amortecida.\n\n- **Identidade do assunto.** O prompt pedia preto e branco ou uma cor de acento; a cor virou o verde do Spotify, a fonte virou Figtree, a capa é a oficial e o player mostra a própria música que está tocando.\n\n- **Loop perfeito**, com cursor, posição e velocidade iguais no primeiro e no último quadro; até as caudas dos sons dão a volta.\n\n- **Um som por ação, pelo pico medido**, incluindo um tique por passo do arrasto e do contador.\n\nDetalhes de cores, molas e som: estilo.md. Batida por batida: roteiro.md. Para fazer um motion de interface no mesmo estilo com o seu produto: PROMPT.md."
    },
    {
      "titulo": "As versões até o final",
      "texto": "| \n\n | Versão\n | O que mudou\n\n | v1\n | estrutura completa; revisão por batida corrigiu molas das bordas trocadas (o knob sumia na batida 12), conteúdo entrando lento, indicador por cima dos rótulos e área do gráfico aparecendo antes da linha\n\n | v2\n | o Gemini ouviu a v1 e apontou sons de interface enterrados na música, arrasto em linha reta e contador borrado: música a 0,30, sons +2,6 dB, um tique por passo do arrasto e do contador, arco no cursor e menos desfoque no número\n\n | **v2.1**\n | só o áudio: os cliques, medidos empatados com a música no pico, subiram cerca de 4 dB, e a música foi para 0,27; aprovada"
    }
  ],
  "pacote": [
    {
      "pasta": "projeto/",
      "conteudo": "`html/index.html` (a forma, os estados, o cursor, a câmera e a lista de sons), `html/Figtree.ttf`, `html/cover.jpg` (capa oficial do single) e `render.py`"
    },
    {
      "pasta": "audio/",
      "conteudo": "`mixar.py`, `batidas.py`, as batidas medidas (`batidas.json`), a lista de sons exportada (`sfx.json`), a música, o mix final e `sfx/` com os 14 arquivos de efeito usados"
    },
    {
      "pasta": "referencias/",
      "conteudo": "o prompt original de @twoclipping, as folhas de contato do vídeo original e a comparação lado a lado (original × nossa v1)"
    },
    {
      "pasta": "render.mp4",
      "conteudo": "o vídeo final (v2.1)"
    },
    {
      "pasta": "html/index.html",
      "conteudo": "`seek(t)` calcula cada estilo do zero. As molas são respostas ao degrau em forma fechada; um valor que muda de alvo várias vezes é a soma de uma mola por mudança (`track`), com cópias do loop anterior para o fim encaixar no começo. Também guarda a lista de sons em `window.SFX`"
    },
    {
      "pasta": "render.py beats",
      "conteudo": "um quadro no meio de cada uma das 28 batidas e a folha de contato, para revisar antes do render"
    },
    {
      "pasta": "render.py frames 3.0 3.25",
      "conteudo": "quadros avulsos"
    },
    {
      "pasta": "render.py sfx",
      "conteudo": "exporta `window.SFX` para `audio/sfx.json`"
    },
    {
      "pasta": "render.py full",
      "conteudo": "840 quadros × 4 subquadros em JPEG numa pasta temporária, prontos para a junção"
    },
    {
      "pasta": "audio/batidas.py",
      "conteudo": "mede batidas e tempos fortes com o beat_this"
    },
    {
      "pasta": "audio/mixar.py",
      "conteudo": "corta o refrão, posiciona cada som pelo pico medido com variação de tom de ±3%, faz as caudas que passam de 14 s darem a volta no início e normaliza em duas passagens"
    }
  ],
  "prompt": "Quero um motion de interface em loop no mesmo método do projeto \"Uma forma, 28 batidas\", que está em 07-videos/projetos/morph/, com o meu produto.\n\nProduto ou marca: {{PRODUTO}}\nSite oficial (cores, fonte, logo, telas reais): {{SITE}}\nEstados que a forma deve virar (8 a 12, na ordem): {{ESTADOS}}\nCor de destaque: {{COR}} (a referência usa só o verde do Spotify)\nDuração: {{DURACAO}} (a referência tem 14 s, 7 compassos a 120 BPM) · Formato: {{FORMATO}} (a referência é 1440×1440)\n(Se algum desses estiver vazio ou com as chaves, me pergunte antes de começar.)\n\n1. Estude o projeto antes de propor qualquer coisa. Leia LEIA-ME.md, estilo.md e roteiro.md e o prompt original em referencias/. Depois abra projeto/html/index.html e entenda S() (mola em forma fechada), track() (uma mola por mudança, com cópias do loop anterior), os estados em ST, a câmera em CAM, o cursor em CUR0/ARCS/PRESS e a lista window.SFX. Leia também render.py e audio/mixar.py. Me explique em poucas linhas o que você vai reaproveitar.\n\n2. Tire a identidade do assunto: cores do CSS do site, a fonte (ou a livre mais próxima), ícones no mesmo traço e um asset real do produto (capa, foto, logo). Uma cor de destaque só.\n\n3. Escolha a música nas minhas playlists, perto de 120 BPM, meça com audio/batidas.py e comece num tempo forte. Escreva a grade de batidas (compasso, batida, tempo, estado, ação do cursor, texto na tela) e me mostre antes de escrever código. Algo acontece em toda batida; nenhum estado fica sem ação.\n\n4. Construa copiando o projeto, sem editar o original. Mantenha: seek(t) sem transição CSS, timer nem estado entre quadros; molas fechadas somadas por mudança; o conteúdo que entra depois da forma e sai antes dela, com desfoque curto; as duas bordas do knob e do indicador em molas diferentes; arrasto que lê a posição do cursor e volta com mola livre de onde estava; arco leve na mão durante o arrasto; câmera que enquadra cada estado; nada de will-change no que a câmera escala. Troque: estados, textos, cores, ícones e asset.\n\n5. Faça o último quadro igual ao primeiro, com cursor, posição e velocidade iguais. Confira renderizando seek(0) e seek(duração) e comparando pixel a pixel.\n\n6. Rode render.py beats e me mostre a folha com um quadro por batida. Corrija o que estiver fora da grade, apertado ou difícil de ler antes do render completo.\n\n7. Som: um som por ação, declarado em window.SFX no próprio HTML, exportado com render.py sfx e posicionado pelo pico medido de cada arquivo, com caudas que passam do fim voltando ao começo. Música baixa o bastante para os cliques aparecerem; meça o pico de cada evento contra a música e suba o que estiver empatado. Final em −14 LUFS.\n\n8. Render com 4 subquadros por quadro, obturador de 180°, junção com tmix a 60 qps. Peça a uma IA que assista com áudio e confira cada apontamento nos quadros e na medição antes de mexer. Entregue o MP4, o HTML editável e uma anotação com a grade final.",
  "campos": [
    "COR",
    "DURACAO",
    "ESTADOS",
    "FORMATO",
    "PRODUTO",
    "SITE"
  ],
  "fonte": {
    "nome": "Clube do Motion",
    "url": "https://clubedomotion.com/projeto/?id=morph",
    "uso": "liberado pelo dono do Clube do Motion em 18/10/2026 (docs/permissoes/clube-do-motion.md)"
  }
}