Easy-to-manage touch screens
Free, open-source touch screens. Guided USB setup, a drag-and-drop editor and local control. Made for Home Assistant.
Registre grátis cada figurinha e card das suas coleções — Álbum da Copa 2026, Legends, Gold Crumple Edition, Adrenalyn XL e mais — e saiba na hora o que falta e o que sobra. Quando quiser, transforme
Translation pending
LifestyleTypeScript · CSSGitHub @fnadlerOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Registre grátis cada figurinha e card das suas coleções — Álbum da Copa 2026, Legends, Gold Crumple Edition, Adrenalyn XL e mais — e saiba na hora o que falta e o que sobra. Quando quiser, transforme as repetidas em uma loja própria — os pedidos chegam no seu WhatsApp.
Translation pending
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
Handoff: Controle de Figurinhas Repetidas — Álbum Copa 2026 Overview App pessoal para registrar quais figurinhas do álbum da Copa 2026 o usuário tem e quantas repetidas de cada uma, ver o quadro completo (994 figurinhas) em uma única tela com rolagem e âncoras por grupo/seleção, filtrar por tipo (Seleções / FWC History / Coca-Cola) e por status (só repetidas / só faltantes), acompanhar totalizadores (por seleção e geral) e gerar a lista de repetidas para trocas.Read the full README ↗
App pessoal para registrar quais figurinhas do álbum da Copa 2026 o usuário tem e quantas repetidas de cada uma, ver o quadro completo (994 figurinhas) em uma única tela com rolagem e âncoras por grupo/seleção, filtrar por tipo (Seleções / FWC History / Coca-Cola) e por status (só repetidas / só faltantes), acompanhar totalizadores (por seleção e geral) e gerar a lista de repetidas para trocas.
O protótipo atual guarda tudo em localStorage. O objetivo deste handoff é migrar a persistência para Supabase (Postgres + Auth) e publicar na Vercel, mantendo a interface como está.
Os arquivos deste pacote são referências de design feitas em HTML — protótipos que mostram aparência e comportamento pretendidos, não código de produção para copiar direto. A tarefa é recriar este design no ambiente do codebase de destino (aqui: um projeto novo Next.js, já que não existe codebase) usando padrões e bibliotecas estabelecidos, e ligar a persistência ao Supabase.
Figurinhas Copa 2026.dc.html é um componente de um runtime proprietário de design (template + classe de lógica). Leia-o como especificação: a estrutura de dados, os cálculos de totais, os estados de célula e os estilos inline são todos exatos e podem ser portados para React quase 1:1. Não tente rodar esse arquivo em produção.
High-fidelity. Cores, tipografia, espaçamentos, estados e interações são finais. Recriar fielmente.
O álbum tem 50 blocos / 994 figurinhas:
| Bloco | Tipo | Códigos | Qtde |
|---|---|---|---|
| FIFA World Cup History | FWC | 00, FWC1–FWC19 | 20 |
| 48 seleções, 4 por grupo A–L | TEAM | <COD>1–<COD>20 (ex. BRA1–BRA20) | 960 |
| Coca-Cola | CC | CC1–CC14 | 14 |
Grupos (inferidos da ordem da planilha original do usuário — confirmar com ele antes de considerar definitivo):
A lista completa e pronta para consumo está em album.json ({ total, blocks: [{ id, nome, tipo, grupo, codes[] }] }).
qty é quantidade TOTAL em mãosqty === 0 -> não tenho (falta)
qty === 1 -> tenho 1, colada no álbum
qty >= 2 -> colada + (qty - 1) repetidas
Derivados:
coladas = count(qty >= 1)repetidas = sum(max(qty - 1, 0))faltam = 994 - coladasRode supabase_schema.sql no SQL Editor do projeto Supabase. Ele cria:
public.stickers — catálogo imutável das 994 figurinhas (já com o seed completo).public.collection — (user_id, code, qty, updated_at), PK composta; RLS liberando apenas as próprias linhas do usuário autenticado.public.collection_totals — view com coladas/repetidas por bloco.Usar Supabase Auth com magic link (OTP por e-mail) — o app é de uso pessoal, sem senha é mais simples. Sem sessão, renderizar o quadro em modo leitura com aviso de login (ou permitir uso local e sincronizar após login — ver "Migração" abaixo).
Cada clique em uma figurinha é um upsert otimista:
await supabase.from('collection')
.upsert({ user_id: user.id, code, qty: novoQty, updated_at: new Date().toISOString() },
{ onConflict: 'user_id,code' });
qty === 0 pode deletar a linha ou gravar 0 — prefira delete para manter a tabela enxuta.select code, qty from collection where user_id = ... e montar um mapa { [code]: qty }. Não fazer 994 requests.O usuário já tem marcações em localStorage (chave copa2026-figurinhas-v1, formato { v:1, atualizado, qtd: { [code]: number } }) e possivelmente em mais de um dispositivo. Implementar:
localStorage com dados e a coleção remota estiver vazia → enviar tudo em um upsert em lote e marcar como migrado.{qtd:{...}} quanto o mapa cru) e faz upsert em lote.@supabase/supabase-js e @supabase/ssr.NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY. Nunca expor a service_role key no cliente.export const dynamic = 'force-dynamic' na página autenticada.Layout
#0a120e + dois radiais decorativos: radial-gradient(1200px 500px at 12% -10%, rgba(0,196,106,.16), transparent 60%) e radial-gradient(900px 400px at 95% 0%, rgba(255,207,77,.10), transparent 60%).max-width: 1340px, padding: 18px.padding-bottom: 64px no wrapper.Header sticky (position: sticky; top: 0; z-index: 30)
background: rgba(10,18,14,.94), backdrop-filter: blur(10px), border-bottom: 1px solid #1e3328, padding: 16px 18px 10px, colunas em flex-direction: column; gap: 12px.letter-spacing: .22em, uppercase, #ffcf4d) + título "ÁLBUM COPA 2026" (Bebas Neue 40px, line-height: .92); à direita, três cartões de totais (#10201a, borda #21382c, raio 10px, padding: 8px 14px, min-width: 96px): COLADAS n/994 (Bebas 26px #7de2ab, denominador 15px #6b8a79), REPETIDAS (#ffcf4d), FALTAM (#ff8f6b). Rótulos: 10px, 600, letter-spacing: .14em, uppercase, #7f9c8c.display: flex; gap: 10px; flex-wrap: wrap:
input[type=search], flex: 1 1 240px, fundo #0d1a14, borda #21382c, raio 9px, padding: 9px 12px, 14px; foco → borda #00c46a. Placeholder "Buscar seleção ou código (ex: BRA9)". Filtra por nome da seleção, id do bloco ou código exato.#0d1a14, borda #21382c, raio 9px, padding: 3px, gap: 2px). Chip: Barlow Condensed 600, 13px, letter-spacing: .07em, uppercase, padding: 7px 12px, raio 7px; ativo → background: #00c46a; color: #04180d; inativo → transparente, color: #89a99a.
background: #ffcf4d, color: #0a120e, raio 9px, padding: 10px 14px, Barlow Condensed 700, 14px, letter-spacing: .08em, uppercase; hover #ffe08f.color: #9fc2ae, borda #21382c, raio 9px, padding: 10px 12px, 13px; hover , borda .Faixa de legenda (abaixo do header, padding: 18px 18px 0, 12px, #7f9c8c, gap: 18px, wrap)
letter-spacing: .12em).#12211a + borda 1px dashed #37563f), colada (#00c46a), repetida (#ffcf4d).#7de2ab: "Salvo HH:MM · N figurinhas na base". Erro #ff8f6b. No app com Supabase, trocar por "Sincronizado HH:MM" / "Salvando…" / "Offline — alterações pendentes".Blocos de seleção (um por bloco, gap: 14px)
background: #0e1b15, borda 1px solid #1c2f25, raio 14px, overflow: hidden, id="bl-<blockId>".padding: 12px 16px, background: linear-gradient(90deg,#132218,#0e1b15), borda inferior #1c2f25, display: flex; gap: 12px; align-items: center; flex-wrap: wrap:
letter-spacing: .14em, uppercase, raio 6px, padding: 3px 8px; TEAM → texto #7f9c8c, borda #2a4436; FWC/CC → texto #ffcf4d, borda #5a4a15.letter-spacing: .1em, uppercase, #6b8a79."18/20 coladas" (12px 600 #7de2ab) e "7 repetidas" (12px 600 #ffcf4d).#1c2f25, preenchimento #00c46a — vira #ffcf4d em 100%.display: grid; gap: 8px; padding: 14px 16px 18px; grid-template-columns: repeat(auto-fill, minmax(46px, 1fr)).Célula da figurinha
height: 46px (configurável 34–72px), raio 8px, flex centralizado, cursor: pointer, user-select: none, Barlow Condensed 700, número em round(altura * .36)px.qty 0 → fundo #12211a, borda 1px dashed #37563f, texto #5d7a6c; qty 1 → fundo/borda #00c46a, texto #04180d; qty ≥ 2 → fundo/borda #ffcf4d, texto #3a2a00.qty ≥ 2): +n no canto superior direito (top: -6px; right: -5px), fundo #0a120e, texto/borda #ffcf4d, raio 99px, 10px/700, padding: 1px 5px.BRA9 → "9"; a primeira do bloco FWC é "00").title = "BRA9 — tenho 3 (2 repetidas) · clique para somar, clique direito inverte".+1 (ou -1 no modo Tirar); clique direito faz o inverso e dá preventDefault. Limites 0..99. transition: transform .08s ease (prever um leve scale(.94) no active).<button> com aria-label completo e navegação por setas dentro do grid.Filtros: quando Status ≠ "Tudo" (ou há busca por código), as células não correspondentes são removidas do grid e blocos que ficam sem células desaparecem. Se nada sobra: mensagem centralizada "Nenhuma figurinha com esses filtros." (padding: 60px 20px, 15px, #6b8a79). Os totalizadores sempre refletem a base completa, não o filtro.
Overlay rgba(5,9,7,.78) + blur(4px), z-index: 60, centralizado, padding: 20px. Card max-width: 620px, max-height: 84vh, fundo #0e1b15, borda #21382c, raio 16px. Cabeçalho: título Bebas 27px "Minhas repetidas" + contagem (12px #7f9c8c) + botão amarelo COPIAR LISTA (vira "Copiado!") + botão × 32×32. Corpo rolável white-space: pre-wrap, 14px, line-height: 1.7, #cfe3d7.
Formato do texto:
REPETIDAS — ÁLBUM COPA 2026
África do Sul: RSA2 (x5), RSA3 (x21), RSA4 (x23)
Coreia do Sul: KOR2, KOR3 (x3)
(sem sufixo quando há apenas 1 repetida; só blocos com repetidas aparecem; vazio → "Nenhuma repetida registrada ainda.")
Mesmo card (z-index 70). Backup: aviso "Este texto é a sua base completa (N figurinhas)…", textarea readonly 190px com o JSON, botão Copiar (navigator.clipboard + fallback select()/execCommand) e Tentar baixar arquivo (data URL). Importar: textarea editável com placeholder "Cole aqui o texto do backup...", botão verde RESTAURAR BASE; JSON inválido → alerta. Aceita {v,atualizado,qtd:{}} ou o mapa cru. Textarea: fundo #0a120e, borda #21382c, raio 10px, padding: 12px, monospace 12px, line-height: 1.5, color: #cfe3d7.
| Estado | Tipo | Notas |
|---|---|---|
qtd | Record<string, number> | fonte da verdade da coleção; ausente = 0 |
tipo | 'ALL'|'TEAM'|'FWC'|'CC' | filtro de tipo |
stat | 'ALL'|'REP'|'MISS' | REP = qty >= 2, MISS = qty === 0 |
modo | 'add'|'sub' | efeito do clique simples (essencial no celular) |
busca | string | nome/código, case-insensitive |
trocas | boolean | modal de repetidas |
backup | null|'export'|'import' | modal de backup |
importText | string | textarea de importação |
salvoEm | Date | 'erro' | null | indicador de gravação |
Na versão Supabase, acrescentar session, syncing e uma fila de upserts pendentes (para funcionar offline em banca de troca e sincronizar ao voltar a conexão).
Cores
#0a120e fundo · #0e1b15 card · #10201a cartão de total · #0d1a14 campo · #12211a célula vazia · #132218 cabeçalho do card
Bordas: #1c2f25, #1e3328, #21382c, #2a4436, #37563f (dashed), #3b5c4a (hover), #5a4a15 (FWC), #6b1f1f (CC)
Texto: #e8f2ec · #cfe3d7 · #9fc2ae · #89a99a · #7f9c8c · #6b8a79 · #5d7a6c
Acentos: #00c46a colada (configurável) · #04180d texto sobre verde · #ffcf4d repetida/destaque · #ffe08f hover · #3a2a00 texto sobre amarelo · #7de2ab positivo · #ff8f6b faltam/erro · #ff9c9c CC
Overlay: rgba(5,9,7,.78) + blur(4px); header rgba(10,18,14,.94) + blur(10px)
Tipografia (Google Fonts)
Bebas Neue — títulos e números grandes (40 / 27 / 26 / 25px).
Barlow Condensed 500/600/700 — rótulos, chips, botões, número da célula (uppercase, letter-spacing .06–.22em).
Barlow 400/500/600/700 — corpo (12/13/14px).
Monospace do sistema — textareas de backup.
Espaçamento 2 · 3 · 6 · 7 · 8 · 10 · 12 · 14 · 16 · 18 px — Raios 4 · 6 · 7 · 8 · 9 · 10 · 14 · 16 · 99px — Transição transform .08s ease
Nenhuma imagem. Sem bandeiras/escudos (só código e nome da seleção) — se o usuário quiser bandeiras depois, sugerir SVGs licenciados em public/flags/<COD>.svg, nunca desenhadas à mão.
Figurinhas Copa 2026.dc.html — protótipo completo (template + lógica): referência exata de estilos, cálculos e interações.album.json — catálogo das 994 figurinhas (50 blocos, tipo, grupo, códigos em ordem).supabase_schema.sql — DDL, RLS, view de totais e seed das 994 figurinhas.Controle_Figurinhas_Ludopedio_pdf.pdf — planilha original do usuário, fonte da numeração.Implementado: Next.js (App Router) + TypeScript, sem Tailwind (CSS em app/globals.css com os tokens deste documento), @supabase/supabase-js + @supabase/ssr, auth por magic link, sync otimista com debounce de 300ms e fila offline, migração automática do localStorage no primeiro login, e PWA (manifest + ícones gerados). Ver components/AlbumApp.tsx como orquestrador principal.
Nota de versão: o projeto foi gerado com Next.js 16, que renomeou middleware.js para proxy.js (mesmo mecanismo, arquivo/nome de export diferentes — ver proxy.ts na raiz).
npm install
npm run dev
Crie um projeto em supabase.com, rode supabase_schema.sql no SQL Editor dele, e preencha .env.local (veja .env.example):
NEXT_PUBLIC_SUPABASE_URL=...
NEXT_PUBLIC_SUPABASE_ANON_KEY=...
Em Authentication → URL Configuration no painel do Supabase, adicione http://localhost:3000 (dev) e o domínio de produção (depois do deploy) em Site URL / Redirect URLs — sem isso o magic link não funciona.
Rode também supabase_migration_shop.sql no SQL Editor (depois das outras migrações). Ela cria shops, sticker_prices, orders e a função set_order_status.
/vendas (menu da conta → "Loja e pedidos"): link da loja, loja aberta/pausada, WhatsApp que recebe os pedidos, pedido mínimo, preços por grupo (FWC / Seleções / Coca-Cola) e individuais, e a lista de pedidos./loja/<token>: catálogo público com carrinho. Só vende repetidas (qty - 1) com preço definido; o preço individual sobrepõe o do grupo.novo e o comprador o envia pelo WhatsApp (wa.me) do anunciante. Frete é combinado por lá.supabase_migration_cart_holds.sql): cada navegador tem um carrinho (cookie copa_cart); hold_cart reserva os itens atomicamente, ajustando ao disponível. O prazo conta do primeiro item e não renova a cada alteração; vencido, as figurinhas voltam a ficar disponíveis e a próxima alteração (ou "Fechar pedido", que abre a tela de confirmação) tenta reservar de novo.supabase_migration_order_reservations.sql + supabase_migration_cart_holds.sql): pedido novo reserva os itens até reserved_until; disponível na loja = repetidas − pedidos novos no prazo − carrinhos ativos de outras pessoas. Vencido o prazo sem confirmação, o pedido vira "cancelado (expirado)" — a liberação é imediata (as contas olham o prazo) e a troca de status acontece na próxima leitura (expire_orders), sem cron. Criação (place_order), edição (update_order_items) e confirmação usam trava por vendedor para não vender a mesma repetida duas vezes.SUPABASE_SERVICE_ROLE_KEY no servidor (mesma env var do link público).Acesso à loja (por usuário). Rode supabase_migration_shop_access.sql. Só quem tem uma linha ativa em shop_entitlements vê "Loja e pedidos", acessa /vendas e tem a loja pública no ar. Para habilitar alguém: Supabase → Table Editor → shop_entitlements → inserir user_id (de Authentication → Users), com expires_at opcional. Usuários não conseguem se autoconceder acesso (não há policy de escrita). Loja de quem perdeu o acesso aparece como "pausada".
Legends. Coleção à parte de 80 figurinhas (20 atletas × Lilás, Bronze, Prata, Ouro), códigos LIL1–20, BRO1–20, PRA1–20, OUR1–20 — o número é o atleta em ordem alfabética (nomes em album.json → labels). Rode supabase_migration_legends.sql (sem ela, marcar Legends falha e a loja não carrega). Aparecem no quadro, na loja e em "Minhas repetidas", mas não entram em Coladas/Faltam do álbum (ver countsTowardAlbum em lib/album.ts).
Fotos das figurinhas. Os originais ficam em /images (fora do git, ~420 MB). npm run images gera WebP otimizados em public/stickers/{thumb,large} (~48 MB, versionados) e lib/sticker-images.json. Rode de novo sempre que adicionar/trocar fotos. Figurinha sem foto mostra o número como placeholder.
.env.local nas configurações do projeto na Vercel.Criar a coleção é grátis; a loja exige assinatura mensal (cartão, com teste grátis na primeira vez).
supabase_migration_subscriptions.sql (tabelas subscriptions e stripe_events, coluna shop_entitlements.source e a função sync_subscription).STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, STRIPE_PRICE_ID. Sem elas, /assinar mostra "em breve"./assinar → Checkout do Stripe → /assinar/sucesso (sincroniza na hora) → /vendas. O webhook /api/stripe/webhook mantém tudo em dia (renovação, falha, cancelamento). O perfil abre o portal do Stripe ("Gerenciar assinatura").cancel_reason = 'assinatura'). Acessos manuais (source = 'manual') nunca são rebaixados.stripe listen --forward-to localhost:3000/api/stripe/webhook./ — landing de divulgação (route group app/(marketing), visual claro próprio, fontes Nunito/Figtree só nessa área). Estática, regenerada de hora em hora (preço lido do Stripe). Nunca redireciona; com sessão, os CTAs viram "Ir para minha coleção". Texto em content/landing.ts./colecao — o álbum (antes em /). Login e confirmação de e-mail seguem para ?next= (só caminhos internos) ou para cá./login?modo=cadastro&next=/assinar — abre na aba de cadastro e segue para a assinatura./termos, /privacidade, /cancelamento, /contato — textos em content/legal.ts (primeira versão, a revisar com advogado). Preencha COMPANY (razão social, CNPJ, endereço, e-mail, foro).lib/brand.ts + supabase_migration_reserved_slugs.sql.Cada coleção é uma linha em albums (wc2026-panini = Álbum Copa do Mundo 2026, wc2026-adrenalyn = Adrenalyn XL). O código de cada item é único na plataforma inteira: o Álbum Copa usa os códigos de sempre (BRA1, FWC3, LIL1…) e a Adrenalyn XL usa AXL1–AXL630 — por isso collection, sticker_prices, pedidos e carrinhos não precisam de chave por coleção.
supabase_migration_collections.sql (Fase 1, só aditiva). Adrenalyn XL entra inativa; cria user_albums (coleções de cada pessoa, com nome próprio; trigger em auth.users para contas novas) e shop_albums (loja por coleção: ativa, pedido mínimo, preços por grupo em jsonb); adiciona album_id em orders, cart_holds e collection_shares.data/adrenalyn-xl.csv (número, seção, seleção, tipo, posição, nome, imagem). As fotos originais ficam em data/adrenalyn-xl/cards/ (fora do git).supabase_migration_adrenalyn_limited.sql, lista em data/adrenalyn-xl-limited.csv, fonte: Collectosk): 187 cards sem número oficial, em 6 blocos (Limited Edition 90, Holo 25, Premium 12, Premium Gold 6, XXL 36, XXL Holo 18), com códigos próprios AXLLE*, AXLLH*, AXLPR*, AXLPG*, AXLXL*, AXLXH*. Mostram o nome do jogador na célula e ficam fora do total de 630. Fotos: arquivo com o próprio código em data/adrenalyn-xl/cards/ (ex: AXLLH12.jpeg).wc2026-golden-crumple, /colecao/golden-crumple-2026, supabase_migration_golden_crumple.sql): as figurinhas do Álbum Copa (FWC + 48 seleções, 980 — sem Coca-Cola e Legends) com layout dourado. Códigos com prefixo GC (GCBRA9, GC00), mostrados sem ele. Catálogo e migração gerados por node scripts/build-golden-crumple-catalog.mjs a partir do album.json. Fotos: images/golden_crumple/<CÓDIGO>.png (fora do git), recortadas dos PDFs "GOLD CRUMPLE EDITION" — o álbum completo vem das quatro partes + holográficas + Seleção Brasileira.Catalog em lib/catalog.ts (blocos, filtros, grupos de preço, etiquetas, textos). Funções por código (stickerName, describeSticker, priceFor…) usam um índice global; o que percorre blocos recebe o catálogo — nos componentes, via useCatalog() (lib/CatalogContext.tsx, padrão: Álbum Copa). O catálogo da Adrenalyn (lib/catalogs/adrenalyn-xl.json) é gerado do CSV com node scripts/build-adrenalyn-catalog.mjs.Convite para o grupo de WhatsApp dos colecionadores, por e-mail e dentro da plataforma. Tudo depende de variáveis de ambiente — sem elas, nada aparece nem é enviado:
NEXT_PUBLIC_WHATSAPP_GROUP_URL — link de convite do grupo (https://chat.whatsapp.com/...). Liga o aviso no topo da coleção (components/CommunityBanner.tsx, some depois de fechado) e o item "Comunidade no WhatsApp" no menu da conta.RESEND_API_KEY e EMAIL_FROM — envio pelo Resend (lib/email/resend.ts); o remetente precisa ser de um domínio verificado lá (padrão: GN Coleciona <[email protected]>).CRON_SECRET — protege /api/cron/convite-comunidade.Migração: supabase_migration_community_invites.sql (tabela community_invites, uma linha por conta que já recebeu). O e-mail (lib/email/communityInvite.ts; prévia em /api/comunidade/preview, só em desenvolvimento) sai uma vez por conta:
app/auth/callback);vercel.json, 10h de Brasília), para contas dos últimos 7 dias que ainda não receberam;curl -H "Authorization: Bearer $CRON_SECRET" "https://www.gncoleciona.com.br/api/cron/convite-comunidade?todos=1&limite=200" (acrescente &teste=1 para só contar; repita até pendentes chegar a 0).color: #e8f2ec#3b5c4aoverflow-x: auto, gap: 6px, padding: 2px 0 10px) com 50 botões: FWC (borda #5a4a15, fundo #1a1706, texto #ffcf4d), 48 chips "<Grupo> · <COD>" (borda #21382c, fundo #0d1a14, texto #9fc2ae, title = nome da seleção) e COCA-COLA (borda #6b1f1f, fundo #1d0b0b, texto #ff9c9c). Raio 8px, padding: 7px 10px, Barlow Condensed 600/700 13px, white-space: nowrap.
offset = headerHeight + 12; window.scrollTo({ top: elTop + scrollY - offset, behavior: 'smooth' }). Cada bloco tem scroll-margin-top: 190px. Não usar offset fixo — o header muda de altura conforme a quebra de linha.supabase_migration_collections_phase2.sql): Adrenalyn XL ativa, um link público por coleção, colecoes reservado. Rotas: /colecao abre a última coleção usada (cookie gn_colecao), /colecao/<slug> (copa-2026, adrenalyn-xl) abre uma coleção, /colecoes adiciona coleções. O nome de cada coleção fica em user_albums.collection_name (editável em Meu perfil). Fotos dos cards: npm run images (lê data/adrenalyn-xl/cards/NNN.jpeg; gera só o que falta, -- --force refaz tudo).supabase_migration_collections_phase3.sql): a loja vende várias coleções. shop_albums guarda, por coleção, se está à venda, o pedido mínimo e o preço por grupo (as colunas price_*_cents/min_order_cents de shops ficam só como legado). Carrinho por coleção (cart_holds com album_id na chave; hold_cart/place_order recebem p_album) e o pedido grava a coleção. Vitrine: abas ?colecao=<slug> quando há mais de uma à venda. Admin: seletor de coleção em Preços; "Coleções à venda" em Configurações.