Fundação

Motion

O Spinlist usa movimento mínimo e intencional. Toda animação tem um propósito — feedback de toque, carregamento ou transição de estado. Toque nos exemplos abaixo.

Active state

Toda superfície tocável recua levemente ao toque. Feedback imediato sem animação de saída.

Kind of Blue

Miles Davis

Botões primários (CTA)

active:scale-[0.96] active:opacity-80 transition: transform 75ms, opacity 75ms

Ações secundárias / chips

active:opacity-75 transition: opacity 75ms

List rows

active:bg-[#111] transition: background 0.1s

Global (index.css)

button { transition: transform 75ms ease, opacity 75ms ease; }

Transição de scroll

Header do Disco Detalhe: fundo e título fazem fade ao passar o threshold da capa.

scrollY < threshold

Dark Side of the Moon

scrollY > threshold

Dark Side of the Moon

Transição

transition-all duration-200

Propriedades animadas

background (transparent → #0f0f0f) border-bottom (none → 1px solid #1a1a1a) opacity do título (0 → 1) background dos botões pill (rgba(0,0,0,0.6) → transparent)

Threshold

COVER_HEIGHT − 80px

Heart burst

Aparece ao dar duplo toque na capa do Feed. Roda uma vez e desaparece.

toque duas vezes

Keyframe

@keyframes heart-burst { 0% { scale(0); opacity:1 } 40% { scale(1.5); opacity:1 } 70% { scale(1.1); opacity:1 } 100% { scale(1.1); opacity:0 } } animation: heart-burst 0.8s ease forwards

Gatilho

Duplo toque em < 300ms Apenas quando não está curtido

Ícone

w-24 h-24 fill="#C8F53C" drop-shadow-lg

Shimmer (Skeleton)

Varre da esquerda para direita infinitamente enquanto o conteúdo carrega.

Keyframe

@keyframes shimmer { 0% { translateX(-100%) } 100% { translateX(100%) } } animation: shimmer 1.4s ease-in-out infinite

Gradiente

linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.04) 50%, transparent 100%)

Toast entrada / saída

Aparece em 0,25s e sai com fade + escala 220ms antes de ser removido do DOM.

Entrada (toast-in)

Disco adicionado à coleção

Saída (toast-out)

Disco adicionado à coleção

Entrada

@keyframes toast-in { from { opacity:0; scale(0.96); translateY(8px) } to { opacity:1; scale(1); translateY(0) } } duration: 0.25s ease

Saída

@keyframes toast-out { from { opacity:1; scale(1); translateY(0) } to { opacity:0; scale(0.96); translateY(6px) } } duration: 0.22s ease · dispara 220ms antes de remover o nó do DOM

Haptic feedback

Vibração do dispositivo reforça ações sem saída visual. Usa navigator.vibrate() (Android + alguns PWAs). iOS ignora silenciosamente.

light8ms · curtir, tabs da nav, FAB medium20ms · seguir/deixar de seguir success[12, 40, 12]ms · confirmação de ação destrutiva error[80, 30, 80]ms · erros críticos

Utility

// src/utils/haptic.ts type HapticType = 'light' | 'medium' | 'success' | 'error' export function haptic(type = 'light') { try { navigator.vibrate?.(PATTERNS[type]) } catch { /* noop */ } }

Onde usar

haptic('light') → useLike toggleLike haptic('light') → BottomNav tabs haptic('light') → FAB actions haptic('medium') → toggleFollow

Input focus

Toque no campo abre com glow sutil em verde. Border-color e box-shadow transitam juntos.

Sem foco

Com foco

CSS global (index.css)

input:focus, textarea:focus { box-shadow: 0 0 0 3px rgba(200, 245, 60, 0.07); transition: border-color 0.15s ease, box-shadow 0.15s ease; }

Border no focus

rgba(200, 245, 60, 0.5) (definido via Tailwind focus:border-[...] ou CSS override)

Resumo de durações

Animação Duração Easing Onde
Active press 75ms ease Todos os botões (global CSS)
List row press 100ms ease List items
Toast entrada 250ms ease ToastProvider
Toast saída 220ms ease forwards ToastProvider (3,3s após exibir)
Input focus glow 150ms ease Todos os inputs
Tab dot indicator 300ms ease Bottom nav tab ativa
Header scroll 200ms ease Disco Detalhe
Heart burst 800ms ease forwards Feed card
Shimmer 1.4s ease-in-out ∞ Skeleton