Voltar à vitrine ↖

PROJETO 004 / HERO INTERATIVO

SAPO
TE SEGUE.

Um sapo, um clipe de 4 segundos e um cursor para apontar.

Do clipe aos 381 frames interpolados: como o personagem passou a apontar para o mouse só com frames reais, por que ele nunca descansa num frame borrado, e os oito prompts que fizeram isso acontecer.

Entrar no site ↗

Sapo é uma marca fictícia criada para este estudo. Não há produtos, vendas ou cadastro.

O Sapo, de moletom azul-petróleo e óculos laranja, apontando na direção do cursor no hero do site CAPTURA DO SITE REAL · PRÉVIA MUDA

Um pedido em inglês, um vídeo e a pergunta: como deixar o gesto suave?

O Jorge enviou um clipe de 4 segundos e um pedido curto: extrair todos os frames com FFmpeg, montar uma sprite sheet e fazer o personagem seguir o mouse em JavaScript puro. A primeira versão não estava suave: cada gesto tinha só 7 frames. O clipe foi interpolado por movimento a 96 fps, os 67 frames do caminho esquerda → parado → direita foram para uma sheet em grade e uma mola amortecida passou a percorrê-los no canvas. Como o desfoque está gravado no vídeo, o Sapo só descansa em frames nítidos. Sete mensagens de ajuste depois, o site foi publicado com a página Raio-X, os prompts em português e o kit para download. Feito pelo Claude sozinho, a pedido do Jorge.

381frames depois da interpolação a 96 fpssrc/data/raio-x.js · Etapa 3 (97 originais a 24 fps)

60 fpsmedidos numa varredura completa do cursorsrc/data/raio-x.js · Etapa 5 (mediana 16,7 ms · p95 16,8 ms)

0,62limiar de nitidez para o Sapo descansarsrc/sprite-hero.js · CONFIG.sharpMin; tools/qa.mjs confere em repouso

  • Só frames reais97 frames extraídos e 381 interpolados por compensação de movimento; nenhuma pose desenhada. A sheet tem os 67 do caminho entre os picos, em grade de 12 × 6.
  • Mola, não tweenO X do cursor vira uma pose em [-1, 1]; uma mola (omega 24, zeta 0,92) persegue a pose, e a curva de deslocamento medida a converte em frame. 60 fps medidos.
  • Descanso em frame nítidoNitidez medida por frame (variância do Laplaciano). Em movimento, qualquer frame vale; parado, só acima do limiar de 0,62.

Os prompts, na ordem em que foram usados.

Um pedido de construção e sete mensagens de ajuste, todos enviados ao Claude (Claude Code, modelo Claude Fable 5.1) junto com o clipe. Os textos abaixo são exatamente os que foram usados, sem edição; em cada um, o que mudou no resultado.

1. O pedido inicial (em inglês, como foi enviado)Envie o clipe de 4 segundos junto com:
Create an interactive hero using the uploaded 4-second cat animation. Use FFmpeg to extract every frame, combine them into a single horizontal sprite sheet, and build a frame-based animation system in vanilla JavaScript. Make the character naturally track the mouse by smoothly scrubbing through the frames based on cursor position, with eased interpolation and a smooth return to the idle pose when the mouse leaves. Preserve the original composition, keep the character centered, and make the interaction feel premium and responsive.
1b. O mesmo pedido em portuguêsTradução do pedido acima, para quem preferir enviar assim:
Crie um hero interativo usando a animação de 4 segundos enviada (o "gato" do pedido era, na verdade, um sapo). Use o FFmpeg para extrair todos os frames, combine-os em uma única sprite sheet horizontal e construa um sistema de animação baseado em frames em JavaScript puro (vanilla). Faça o personagem acompanhar o mouse de forma natural, percorrendo os frames suavemente conforme a posição do cursor, com interpolação com easing e um retorno suave à pose de repouso quando o mouse sair. Preserve a composição original, mantenha o personagem centralizado e faça a interação transmitir uma sensação premium e responsiva.
2. "o efeito não está suave"O que mudou: interpolação de movimento para 96 fps (28 frames por gesto), sheet em grade e render em canvas.
o efeito não está suave
3. "deixe o site em portugues tb"O que mudou: português como padrão e botão PT/EN no topo que lembra a escolha; todo o texto em src/data/hero.js nas duas línguas.
deixe o site em portugues tb
4. "pq vc não criou todos os frames do video ?"O que mudou: os 97 originais e os 381 interpolados entraram no kit; a sheet ficou só com o caminho esquerda → parado → direita, explicado no Raio-X.
pq vc não criou todos os frames do video ?
5. "quando eu mexo o mouse faz uma sombra, parece que está em camera lenta"O que mudou: cross-fade desligado (frame mais próximo) e mola de tracking mais rígida: omega 11 → 24, zeta 0,92.
quando eu mexo o mouse faz uma sombra , parece que está em camera lenta
6. Os textos aparecem conforme o ladoO que mudou: headline à esquerda, texto e botões à direita, escondidos no centro; no celular e com "reduzir movimento", sempre visíveis.
outro ponto quando eu mexe o curso para esquerda quero que apareca Frames reais · 96 fps Mexa. Ele segue e pára direita O Sapo segue o seu cursor percorrendo frames reais da animação: sem player de vídeo, sem biblioteca, só uma sheet de pixels e uma mola.
7. "o braço ainda fica borrado" e as frases ao lado do SapoO que mudou: nitidez medida por frame e descanso no frame nítido mais próximo; no desktop, os textos ao lado do personagem.
o braço ainda fica borrado, dá pra melhorar? outro ponto eu gostaria que no computador as frases aparecessem do lado do sapo
8. PublicarO que mudou: o site em sites.amentedojorge.com.br/sapo/, a página Raio-X com os prompts e o kit para download.
maravilha sobe a na vps usando a skill C:\Users\jorge\.claude\skills\publicar-site-imersivo\SKILL.md detalha tudo , sobe os arquivos para qlq um conseguir criar tb, inclua os prompt em portugues

O prompt que gerou o clipe de 4 segundos não foi registrado: o vídeo chegou pronto, e o registro começa nele. Duas coisas que o pedido inicial pede e o resultado adapta: a sheet é em grade, não em tira única (navegadores não decodificam imagens com mais de ~16.383 px em WebP ou ~32.767 px em JPEG/PNG de largura), e nem todos os 97 frames entram nela (68 são holds idênticos pixel a pixel).

NÃO É UM BOTÃO MÁGICO.

É UMA SEQUÊNCIA
DE DECISÕES.

O raio-X mostra inclusive o que foi reprovado. Porque saber o que corrigir também é construir.

  1. 01

    Pesquisar a marca

    Referências, linguagem e fidelidade do produto antes da ferramenta.

  2. 02

    Escrever a experiência

    Um roteiro de cenas, transições e intenção de cada momento.

  3. 03

    Dirigir a arte

    Gerar, compor, comparar e corrigir as imagens no contexto.

  4. 04

    Dar movimento às imagens

    Frames aprovados, prompts de UGC e seleção das saídas do Flow.

  5. 05

    Construir com código

    Astro, 3D, movimento de scroll e alternativas por capacidade do dispositivo.

  6. 06

    Revisar de verdade

    Rótulo, recortes, teclado, contraste, carga e comportamento.

  7. 07

    Preparar a publicação

    SEO, segurança, artefatos estáticos e entrega para integração.

RAIO-X

O resultado tem bastidores.

9 recortes de um único projeto: o clipe, a análise dos frames, a interpolação, a sheet em grade, o motor, a nitidez, os painéis, o retrato e a página Raio-X. Artefatos reais e os prompts como foram enviados.

RAIO-X / Sapo · hero

Um clipe de 4 segundos virou o hero.

O Jorge enviou um clipe gerado por IA de 4 segundos (2206×946, 24 fps, HEVC 10 bits, 97 frames): um sapo de moletom azul-petróleo e óculos laranja faz sinal de paz, aponta para a esquerda, volta, aponta para a direita e volta. Nenhuma pose foi desenhada: tudo o que o hero mostra é frame desse clipe, original ou interpolado entre dois originais. Não há vídeo em runtime; o clipe só existe dentro do kit para download. O pedido inicial foi em inglês e chamava o personagem de "gato".

Frame de repouso do clipe (frame 55)
Frame de repouso do clipe (frame 55)
Hero em produção, cursor à esquerda
Hero em produção, cursor à esquerda
Ferramenta / modelo · conforme registro
Claude Fable 5.1 (Claude Code) sozinho, a pedido do Jorge; o Codex não participou. O prompt que gerou o clipe não foi registrado: o vídeo chegou pronto. FFmpeg 7.1, Python (Pillow, NumPy, OpenCV) e JavaScript puro.
Tempo de produção
Não documentado. Não estimado.
Prompt ou instrução real · Etapa 1 · 1. O pedido inicial (em inglês, como foi enviado)
Create an interactive hero using the uploaded 4-second cat animation. Use FFmpeg to extract every frame, combine them into a single horizontal sprite sheet, and build a frame-based animation system in vanilla JavaScript. Make the character naturally track the mouse by smoothly scrubbing through the frames based on cursor position, with eased interpolation and a smooth return to the idle pose when the mouse leaves. Preserve the original composition, keep the character centered, and make the interaction feel premium and responsive.

Documento de origem: E:\Sapo\src\data\raio-x.js · Etapa 1 · 1. O pedido inicial (em inglês, como foi enviado)

Entrada: hf_20260816_…mp4 (clipe de 4 s, 97 frames)
Saída: public/media/sapo-sheet-1x.webp + src/sprite-hero.js

RAIO-X / Sapo · análise

Medir antes de programar.

Os 97 frames foram extraídos em PNG sem perdas (ffmpeg -vsync 0). Como o fundo é um laranja uniforme, uma máscara de cor separa o sapo e o centroide horizontal dos pixels dele diz para onde ele se inclinou: parado (0–17), aponta para a esquerda (18–24), segura (25–42), volta (43–49), parado (50–60), aponta para a direita (61–67), segura (68–82), volta (83–89), parado (90–96). A descoberta que define o projeto: 42 → 55 → 68 é um caminho contínuo, em ordem cronológica, do pico esquerdo ao pico direito passando pelo repouso. Os outros 68 frames são holds pixel-idênticos aos extremos.

Grade de contato: 1 miniatura a cada 8 frames
Grade de contato: 1 miniatura a cada 8 frames
Frames 42, 55 e 68: os dois picos e o repouso
Frames 42, 55 e 68: os dois picos e o repouso
Ferramenta / modelo · conforme registro
FFmpeg 7.1 (extração; filtros select + tile para a grade de contato); Python com Pillow e NumPy para o centroide por frame; leitura pelo Claude.
Tempo de produção
Não documentado. Não estimado.
Prompt ou instrução real · Etapa 1 · tradução do pedido inicial; Etapa 2 · Extrair todos os frames e ler o movimento
Crie um hero interativo usando a animação de 4 segundos enviada (o "gato" do pedido era, na verdade, um sapo). Use o FFmpeg para extrair todos os frames, combine-os em uma única sprite sheet horizontal e construa um sistema de animação baseado em frames em JavaScript puro (vanilla). Faça o personagem acompanhar o mouse de forma natural, percorrendo os frames suavemente conforme a posição do cursor, com interpolação com easing e um retorno suave à pose de repouso quando o mouse sair. Preserve a composição original, mantenha o personagem centralizado e faça a interação transmitir uma sensação premium e responsiva.

Documento de origem: E:\Sapo\src\data\raio-x.js · Etapa 1 · tradução do pedido inicial; Etapa 2 · Extrair todos os frames e ler o movimento

Entrada: frames/frame_001.png … frame_097.png (2206×946)
Saída: public/raio-x/contato.webp + picos.webp

RAIO-X / Sapo · interpolação

De 7 para 28 frames por gesto.

Percorridos pelo cursor, 7 frames por gesto viram saltos; misturados por cross-fade, viram fantasmas. Além disso, mover background-position numa imagem de 23 mil px repintava a camada inteira a cada quadro. O filtro minterpolate do FFmpeg (modo mci, estimativa bidirecional) gera frames intermediários reais por compensação de movimento: o clipe inteiro foi interpolado a 96 fps, 381 frames em 1104×474, cerca de 1 minuto de processamento. Limite honesto: a interpolação cria frames entre dois originais e não remove o motion blur gravado neles.

Original: picos e repouso, 7 frames entre cada gesto
Original: picos e repouso, 7 frames entre cada gesto
Dez frames interpolados consecutivos (arquivos 173 a 182)
Dez frames interpolados consecutivos (arquivos 173 a 182)
Ferramenta / modelo · conforme registro
FFmpeg 7.1 (binário do pacote imageio-ffmpeg): scale=1104:474:flags=lanczos,minterpolate=fps=96:mi_mode=mci:mc_mode=aobmc:me_mode=bidir:me=epzs:vsbmc=1:search_param=64.
Tempo de produção
Não documentado. Não estimado.
Prompt ou instrução real · Etapa 1 · 2. "o efeito não está suave"
o efeito não está suave

Documento de origem: E:\Sapo\src\data\raio-x.js · Etapa 1 · 2. "o efeito não está suave"

Entrada: frames/ (97 originais, 24 fps)
Saída: frames-96fps/f_0001.png … f_0381.png

RAIO-X / Sapo · sheet

67 frames em grade, não 97 em tira.

O Jorge perguntou por que nem todos os frames do vídeo tinham sido criados. Foram: os 97 originais estão em frames/ e os 381 interpolados em frames-96fps/, e os dois conjuntos vão no kit. O que não tem todos os frames é a sheet: 68 dos 97 originais são holds pixel-idênticos e os navegadores não decodificam WebP com mais de 16.383 px nem JPEG/PNG com mais de ~32.767 px de largura. A sheet fica com os interpolados 166–197 (esquerda até o repouso) e 240–274 (repouso até a direita): 67 frames, índices 0–30 à esquerda, 31 parado, 32–66 à direita, em grade de 12 colunas × 6 linhas. Duas resoluções: 1× em WebP (552×237 por frame, 0,5 MB) para a primeira pintura e 2× em JPEG (960×412, 2,1 MB) para telas grandes. O frame i fica na coluna i mod 12 e na linha ⌊i / 12⌋.

O clipe inteiro: 97 frames, 68 deles holds idênticos
O clipe inteiro: 97 frames, 68 deles holds idênticos
Recorte da sheet 1×: 4 colunas × 2 linhas de 12 × 6
Recorte da sheet 1×: 4 colunas × 2 linhas de 12 × 6
Ferramenta / modelo · conforme registro
FFmpeg 7.1: concat da lista de 67 frames, scale=960:412 (2×) ou 552:237 (1×) + tile=12x6; poster do repouso a partir do frame 56.
Tempo de produção
Não documentado. Não estimado.
Prompt ou instrução real · Etapa 1 · 4. "pq vc não criou todos os frames do video ?"
pq vc não criou todos os frames do video ?

Documento de origem: E:\Sapo\src\data\raio-x.js · Etapa 1 · 4. "pq vc não criou todos os frames do video ?"

Entrada: frames-96fps/f_0166 … f_0197 + f_0240 … f_0274
Saída: public/media/sapo-sheet-1x.webp + sapo-sheet-2x.jpg

RAIO-X / Sapo · motor

O cursor vira pose; a pose vira frame.

O X do ponteiro, relativo ao centro do hero, vira uma pose p em [-1, 1] por uma curva leve (1 − (1 − |x|)^1,15). A pose atual persegue a pedida por uma mola amortecida integrada em sub-passos de ~4 ms, e é convertida em frame invertendo a curva de deslocamento medida nos próprios frames (CONFIG.motion): metade do cursor é metade da inclinação, não metade dos frames. O desenho é um único drawImage da célula certa num canvas (DPR até 2). O Jorge viu "sombra" e "câmera lenta": a sombra era o cross-fade desenhando o braço duas vezes no meio do gesto (ficou desligado, CONFIG.blend); a câmera lenta era a mola de tracking macia demais (omega 11 → 24, zeta 0,92: em 120 ms o Sapo chega a ~90% da posição do cursor). Medido no navegador numa varredura completa: mediana 16,7 ms por quadro, p95 16,8 ms.

Cursor à esquerda: pose −0,99, frame 3
Cursor à esquerda: pose −0,99, frame 3
Cursor à direita: pose +0,94, frame 57
Cursor à direita: pose +0,94, frame 57
Ferramenta / modelo · conforme registro
JavaScript puro (canvas 2D; requestAnimationFrame que dorme quando a mola assenta), sem bibliotecas; escrito pelo Claude Fable 5.1.
Tempo de produção
Não documentado. Não estimado.
Prompt ou instrução real · Etapa 1 · 5. "quando eu mexo o mouse faz uma sombra, parece que está em camera lenta"
quando eu mexo o mouse faz uma sombra , parece que está em camera lenta

Documento de origem: E:\Sapo\src\data\raio-x.js · Etapa 1 · 5. "quando eu mexo o mouse faz uma sombra, parece que está em camera lenta"

Entrada: src/sprite-hero.js (Spring, poseFromX, poseToFrame, blit)
Saída: .qa/local/report.json · 1440 e 2560 px

RAIO-X / Sapo · nitidez

Nunca descansar num frame borrado.

Nos frames do meio do gesto o braço é um borrão e a nitidez cai para um quinto da pose parada; nenhum filtro reconstrói isso, e a interpolação só cria frames entre dois originais borrados. A saída foi medir a nitidez de cada frame da sheet (variância do Laplaciano na região do tronco e dos braços, com OpenCV, normalizada pelo máximo) e usar a tabela no motor: em movimento, qualquer frame vale, porque o blur lê como movimento; parado, só frame nítido. Quando o ponteiro fica 140 ms sem se mover e o frame está abaixo de 0,62, a pose assenta no frame nítido mais próximo em espaço de pose, com viés para "apontando" (0,7) longe do centro. Bug real no caminho: o Chrome redispara pointermove com as mesmas coordenadas quando a interface muda sob um cursor parado, o que reiniciava o timer de descanso; eventos sem mudança de coordenada passaram a ser ignorados.

Frames originais 46 e 47 (borrados) contra o 56 (repouso)
Frames originais 46 e 47 (borrados) contra o 56 (repouso)
Frames na fronteira do limiar: 0,50 · 0,72 · 0,65 · 0,78
Frames na fronteira do limiar: 0,50 · 0,72 · 0,65 · 0,78
Ferramenta / modelo · conforme registro
OpenCV (cv2.Laplacian(roi).var()) para a tabela CONFIG.sharpness de 67 valores; nearestSharp() em JavaScript; tools/qa.mjs confere nitidez ≥ 0,62 em repouso (medido: 0,90 à esquerda, 0,78 à direita).
Tempo de produção
Não documentado. Não estimado.
Prompt ou instrução real · Etapa 1 · 7. "o braço ainda fica borrado" e as frases ao lado do Sapo
o braço ainda fica borrado, dá pra melhorar? outro ponto eu gostaria que no computador as frases aparecessem do lado do sapo

Documento de origem: E:\Sapo\src\data\raio-x.js · Etapa 1 · 7. "o braço ainda fica borrado" e as frases ao lado do Sapo

Entrada: frames/frame_047.png, frame_048.png, frame_057.png
Saída: public/raio-x/nitidez.webp + CONFIG.sharpness

RAIO-X / Sapo · painéis

Os textos aparecem conforme o lado.

O Jorge pediu que, com o cursor à esquerda, aparecesse "Frames reais · 96 fps / Mexa. Ele segue" e, à direita, o parágrafo com os botões. hero.dataset.side vira left, right ou vazio conforme a pose, com zona morta de ±0,12, e o CSS mostra cada bloco no seu lado. No desktop, headline e texto ficam ao lado do personagem, centralizados na altura dele, em colunas de até 23% da largura; o personagem nunca passa de 46% da largura do hero, para não colidir com o texto. Sem cursor (toque, telas estreitas, "reduzir movimento", sem JavaScript), os dois blocos ficam sempre visíveis.

Cursor no centro: só o Sapo, painéis escondidos
Cursor no centro: só o Sapo, painéis escondidos
Cursor à direita: texto e botões ao lado do Sapo
Cursor à direita: texto e botões ao lado do Sapo
Ferramenta / modelo · conforme registro
CSS orientado por data-side; layout() em JavaScript com teto de 46% da largura para o personagem (maxCharacterShare); texto em src/data/hero.js nas duas línguas.
Tempo de produção
Não documentado. Não estimado.
Prompt ou instrução real · Etapa 1 · 6. Os textos aparecem conforme o lado
outro ponto quando eu mexe o curso para esquerda quero que apareca Frames reais · 96 fps Mexa. Ele segue e pára direita O Sapo segue o seu cursor percorrendo frames reais da animação: sem player de vídeo, sem biblioteca, só uma sheet de pixels e uma mola.

Documento de origem: E:\Sapo\src\data\raio-x.js · Etapa 1 · 6. Os textos aparecem conforme o lado

Entrada: src/data/hero.js (eyebrow, h1Html, pitchHtml)
Saída: .qa/local/1440-direita.png

RAIO-X / Sapo · retrato

No celular, o Sapo inteiro.

O palco é sempre o quadro original 2206×946, nunca cortado na sheet. Ele cobre o hero, mas com dois tetos: no celular, nunca amplia além de mostrar 900 px da fonte na largura (o Sapo tem 780 px de largura, então fica inteiro); no desktop alto, o personagem nunca passa de 46% da largura do hero. No celular a headline vai para cima e o texto para baixo, e o palco sobe um pouco (47%) para os tênis não encostarem no parágrafo. Arrastar o dedo faz o tracking enquanto pressionado. O QA em 390 e 768 px confere que o palco não passa de 2,5× a largura da tela (medido: 2,45×) e que os dois painéis ficam visíveis.

Quadro original 2206×946 (poster do repouso)
Quadro original 2206×946 (poster do repouso)
Retrato em produção: Sapo inteiro, painéis visíveis
Retrato em produção: Sapo inteiro, painéis visíveis
Ferramenta / modelo · conforme registro
layout() em src/sprite-hero.js (minSpan 900 px, maxCharacterShare 0,46); QA em Playwright (tools/qa.mjs) em 390, 768, 1440 e 2560 px.
Tempo de produção
Não documentado. Não estimado.
Prompt ou instrução real · Etapa 7 · Composição, painéis laterais e retrato
const cover = Math.max(r.width, r.height * CONFIG.aspect); const spanCap = r.width * 2206 / CONFIG.minSpan; // celular: sapo inteiro const shareCap = r.width * CONFIG.maxCharacterShare * 2206 / 780; // desktop alto: longe do texto const w = Math.min(cover, spanCap, r.width > 860 ? shareCap : Infinity);

Documento de origem: E:\Sapo\src\data\raio-x.js · Etapa 7 · Composição, painéis laterais e retrato

Entrada: src/sprite-hero.js (layout)
Saída: .qa/local/390-esquerda.png

RAIO-X / Sapo · raio-x

Os prompts, em português, dentro do site.

O último pedido do Jorge foi publicar, detalhar tudo, subir os arquivos para qualquer um conseguir criar também e incluir os prompts em português. A página /sapo/raio-x/ tem as etapas de 0 a 10: entrada, prompts, FFmpeg, interpolação, sheet, motor, nitidez, layout, QA, erros e kit, mais o sprite-hero.js inteiro. O pedido inicial aparece em inglês, como foi enviado, e traduzido logo abaixo; as sete mensagens de ajuste, verbatim, cada uma com o que mudou. Dois zips: sapo-kit.zip (clipe original, sheets, poster, código-fonte e scripts, ~7 MB) e sapo-frames.zip (97 frames extraídos e 381 interpolados, ~27 MB). Todo o texto vive em src/data/ e é renderizado em HTML estático no build, sem script inline.

Abertura da página Raio-X
Abertura da página Raio-X
Etapa 1: os prompts, na ordem em que foram usados
Etapa 1: os prompts, na ordem em que foram usados
Ferramenta / modelo · conforme registro
Vite + plugin de conteúdo próprio (scripts/content-plugin.mjs); kit empacotado por tools/pack-kit.sh; tools/qa.mjs confere os 9 blocos de prompt e a acessibilidade da página.
Tempo de produção
Não documentado. Não estimado.
Prompt ou instrução real · Etapa 1 · 8. Publicar
maravilha sobe a na vps usando a skill C:\Users\jorge\.claude\skills\publicar-site-imersivo\SKILL.md detalha tudo , sobe os arquivos para qlq um conseguir criar tb, inclua os prompt em portugues

Documento de origem: E:\Sapo\src\data\raio-x.js · Etapa 1 · 8. Publicar

Entrada: src/data/raio-x.js
Saída: dist/raio-x/index.html + download/sapo-kit.zip

O PRÓXIMO PROJETO PODE SER O SEU.

DE “COMO FIZERAM?”
PARA “EU FIZ”.

Aprenda a conectar direção de arte, IA e código. Comece pelo processo, não pela lista de ferramentas.

Quero aprender a fazer isso