Files
Mlcavalho1andClaude Sonnet 4.6 acbce4edc0 feat: multi-usuário com autenticação JWT
- Tabela `profiles` + coluna `profile_id` em todas as entidades
  (categories, transactions, recurring_expenses, accounts, player_profile,
   xp_events, player_quests, player_achievements, player_cosmetics)
- Dados existentes migrados para profile_id = 1 (Manoel)
- CLI `./api create-user --name <n> --password <p>` cria perfil com
  seed de categorias e player_profile; faz upsert de senha se já existir
- Auth substituída: cookie+APP_PASSWORD → JWT Bearer 24h (HS256)
- Middleware RequireAuth injeta profile_id no context de todas as rotas
- Todos os repositórios filtram por profile_id do context
- Endpoints: POST /api/auth/login, GET /api/auth/me,
  POST /api/auth/change-password, POST /api/logout
- Frontend: auth store usa localStorage (fc_token/fc_profile),
  api.ts envia Authorization header, LoginView usa campo name
- SettingsView reescrita com troca de senha e logout
- docker-compose.yml: remove APP_USERNAME/APP_PASSWORD, adiciona JWT_SECRET

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-27 20:04:44 -03:00

455 lines
26 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="pt-BR">
<head>
<meta charset="utf-8" />
<title>Financeiro Carvalho · Style Guide · Arcade Neon</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="stylesheet" href="tokens.css" />
<style>
/* preview-page-only chrome */
body { padding: 40px 32px 80px; max-width: 1200px; margin: 0 auto; }
h1 { font-family: var(--fc-font-pixel); font-size: 18px; color: var(--fc-accent-2); margin: 0 0 8px; }
h2 { font-family: var(--fc-font-pixel); font-size: 13px; color: var(--fc-accent); margin: 56px 0 6px; padding-bottom: 14px; border-bottom: 1px dashed var(--fc-panel-edge); letter-spacing: .04em; }
h2::before { content: ":: "; color: var(--fc-text-dim); }
p.lede { font-family: var(--fc-font-body); color: var(--fc-text-dim); margin: 0 0 32px; max-width: 720px; line-height: 1.6; }
h3 { font-family: var(--fc-font-pixel); font-size: 10px; color: var(--fc-text); letter-spacing: .04em; margin: 28px 0 12px; }
.grid { display: grid; gap: 16px; }
.g2 { grid-template-columns: 1fr 1fr; }
.g3 { grid-template-columns: 1fr 1fr 1fr; }
.g4 { grid-template-columns: repeat(4, 1fr); }
.row { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
/* swatch card */
.swatch { background: linear-gradient(180deg, var(--fc-bg-raised), var(--fc-bg-panel));
border: 2px solid var(--fc-panel-edge); border-radius: 4px;
padding: 0; overflow: hidden; box-shadow: var(--fc-shadow-panel); }
.swatch__chip { height: 80px; }
.swatch__body { padding: 10px 12px; }
.swatch__token { font-family: var(--fc-font-mono); font-size: 11px; color: var(--fc-text); font-weight: 600; }
.swatch__hex { font-family: var(--fc-font-mono); font-size: 10px; color: var(--fc-text-dim); margin-top: 3px; }
.swatch__note { font-family: var(--fc-font-body); font-size: 11px; color: var(--fc-text-dim); margin-top: 6px; line-height: 1.4; }
/* tokens / labels */
.label { font-family: var(--fc-font-pixel); font-size: 7px; color: var(--fc-text-dim); letter-spacing: .06em; margin-bottom: 6px; }
/* generic demo panel */
.demo {
background: linear-gradient(180deg, var(--fc-bg-raised), var(--fc-bg-panel));
border: 2px solid var(--fc-panel-edge);
border-radius: 4px;
padding: 16px;
box-shadow: var(--fc-shadow-panel);
position: relative;
}
.demo--glow { border-color: var(--fc-accent-3); box-shadow: var(--fc-shadow-panel-glow); }
.demo--danger { border-color: var(--fc-red); box-shadow: 0 0 0 1px var(--fc-red), 0 0 16px rgba(255,59,107,.3); }
.corner { position: absolute; width: 8px; height: 8px; border: 2px solid var(--fc-accent-2); }
.corner.tl { top:-2px; left:-2px; border-right:none; border-bottom:none; }
.corner.tr { top:-2px; right:-2px; border-left:none; border-bottom:none; }
.corner.bl { bottom:-2px; left:-2px; border-right:none; border-top:none; }
.corner.br { bottom:-2px; right:-2px; border-left:none; border-top:none; }
.ptitle { font-family: var(--fc-font-pixel); font-size: 9px; color: var(--fc-accent-2); margin-bottom: 12px; letter-spacing: .04em; }
/* buttons */
.btn {
font-family: var(--fc-font-pixel); font-size: 9px;
padding: 9px 12px; background: var(--fc-bg-raised);
border: 2px solid var(--fc-accent-3); color: var(--fc-text);
cursor: pointer; border-radius: 2px; letter-spacing: .05em;
text-transform: uppercase; transition: all .12s;
}
.btn:hover { background: var(--fc-accent-3); color: white; box-shadow: 0 0 16px rgba(168,85,247,.6); }
.btn--pink { border-color: var(--fc-accent); }
.btn--pink:hover { background: var(--fc-accent); box-shadow: 0 0 16px rgba(255,45,149,.6); }
.btn--cyan { border-color: var(--fc-accent-2); color: var(--fc-accent-2); }
.btn--cyan:hover { background: var(--fc-accent-2); color: var(--fc-bg); box-shadow: 0 0 16px rgba(0,240,255,.6); }
/* chips */
.chip { display: inline-block; font-family: var(--fc-font-pixel); font-size: 7px;
padding: 4px 6px; border-radius: 2px; letter-spacing: .05em; color: #000; }
.chip--magenta { background: var(--fc-accent); }
.chip--cyan { background: var(--fc-accent-2); }
.chip--gold { background: var(--fc-gold); }
.chip--green { background: var(--fc-green); }
.chip--red { background: var(--fc-red); color: #fff; }
/* bar */
.bar { height: 14px; background: var(--fc-bg); border: 2px solid var(--fc-panel-edge); position: relative; overflow: hidden; }
.bar__fill { height: 100%;
background: linear-gradient(90deg, var(--fc-accent-2), var(--fc-accent-3), var(--fc-accent));
position: relative; }
.bar__fill::after { content:''; position:absolute; inset:0;
background-image: repeating-linear-gradient(90deg, rgba(0,0,0,.2) 0 2px, transparent 2px 6px); }
.bar--success { border-color: var(--fc-green); }
.bar--success .bar__fill { background: linear-gradient(90deg, var(--fc-green), var(--fc-accent-2)); }
/* link */
.link { color: var(--fc-accent-2); font-family: var(--fc-font-pixel); font-size: 8px;
cursor: pointer; text-decoration: none; letter-spacing: .04em; }
.link:hover { text-decoration: underline; text-shadow: 0 0 8px var(--fc-accent-2); }
/* font sample */
.font-sample { display: grid; grid-template-columns: 140px 1fr; align-items: baseline; gap: 16px; padding: 12px 0; border-bottom: 1px dashed var(--fc-panel-edge); }
.font-sample__meta { font-family: var(--fc-font-mono); font-size: 11px; color: var(--fc-text-dim); }
.font-sample__demo .pix { font-family: var(--fc-font-pixel); }
.font-sample__demo .mon { font-family: var(--fc-font-mono); }
/* sprite cards */
.sprite-card { background: linear-gradient(180deg, var(--fc-bg-raised), var(--fc-bg-panel));
border: 2px solid var(--fc-panel-edge); border-radius: 4px;
padding: 16px 12px 12px; text-align: center;
box-shadow: var(--fc-shadow-panel); }
.sprite-card svg, .sprite-card img { display: block; margin: 0 auto 10px; image-rendering: pixelated; }
.sprite-card__name { font-family: var(--fc-font-pixel); font-size: 8px; color: var(--fc-text); margin-bottom: 4px; }
.sprite-card__meta { font-family: var(--fc-font-pixel); font-size: 6px; color: var(--fc-gold); letter-spacing: .06em; }
/* example HUD */
.hud-demo {
display: flex; align-items: center; gap: 16px; padding: 12px 20px;
border: 2px solid var(--fc-panel-edge);
background: linear-gradient(180deg, var(--fc-bg-panel), var(--fc-bg));
border-radius: 4px;
box-shadow: var(--fc-shadow-panel);
}
.hud-demo__logo { display: flex; align-items: center; gap: 10px; }
.hud-demo__mark { width: 28px; height: 28px; background: var(--fc-accent); box-shadow: 0 0 12px var(--fc-accent); position: relative; }
.hud-demo__mark::before { content:''; position:absolute; inset:5px; background: var(--fc-bg); }
.hud-demo__mark::after { content:''; position:absolute; top:9px; left:9px; width:10px; height:10px; background: var(--fc-accent-2); }
.stat { display: flex; flex-direction: column; gap: 2px; }
.stat__l { font-family: var(--fc-font-pixel); font-size: 7px; color: var(--fc-text-dim); }
.stat__v { display: flex; align-items: baseline; gap: 4px; font-family: var(--fc-font-pixel); font-size: 13px; }
.stat__s { font-family: var(--fc-font-mono); font-size: 10px; color: var(--fc-text-dim); }
code { font-family: var(--fc-font-mono); font-size: 11px; color: var(--fc-accent-2); background: rgba(0,240,255,.08); padding: 1px 6px; border-radius: 3px; }
hr { border: 0; border-top: 1px dashed var(--fc-panel-edge); margin: 32px 0; }
</style>
</head>
<body class="fc-app">
<header>
<div class="hud-demo">
<div class="hud-demo__logo">
<div class="hud-demo__mark"></div>
<div>
<div class="fc-pixel" style="font-size:10px;color:var(--fc-accent-2)">CARVALHO</div>
<div class="fc-pixel" style="font-size:7px;color:var(--fc-text-dim);margin-top:2px">STYLE GUIDE · ARCADE NEON</div>
</div>
</div>
<div style="flex:1"></div>
<div class="stat">
<span class="stat__l">VER</span>
<span class="stat__v" style="color:var(--fc-gold);text-shadow:0 0 8px rgba(255,216,77,.4)">1.0</span>
</div>
<div class="stat">
<span class="stat__l">DATA</span>
<span class="stat__v" style="color:var(--fc-accent-2);font-size:11px">26/05/26</span>
</div>
</div>
</header>
<h1 style="margin-top:32px">Style Guide · Financeiro Carvalho</h1>
<p class="lede">Referência visual da direção <strong>Arcade Neon</strong>. Use junto do <code>STYLE_GUIDE.md</code>. Tokens vivem em <code>tokens.css</code> — esta página apenas os exibe. Componentes Vue prontos pra copiar estão em <code>components/</code>.</p>
<!-- ─────────────── COLORS ─────────────── -->
<h2>Colors</h2>
<h3>Estrutura (escuro)</h3>
<div class="grid g4">
<div class="swatch"><div class="swatch__chip" style="background:#0a0418"></div><div class="swatch__body"><div class="swatch__token">--fc-bg</div><div class="swatch__hex">#0a0418</div><div class="swatch__note">Fundo base do app</div></div></div>
<div class="swatch"><div class="swatch__chip" style="background:#150827"></div><div class="swatch__body"><div class="swatch__token">--fc-bg-raised</div><div class="swatch__hex">#150827</div><div class="swatch__note">Topo do gradiente do painel</div></div></div>
<div class="swatch"><div class="swatch__chip" style="background:#1c0d33"></div><div class="swatch__body"><div class="swatch__token">--fc-bg-panel</div><div class="swatch__hex">#1c0d33</div><div class="swatch__note">Base do gradiente do painel</div></div></div>
<div class="swatch"><div class="swatch__chip" style="background:#2d1857"></div><div class="swatch__body"><div class="swatch__token">--fc-panel-edge</div><div class="swatch__hex">#2d1857</div><div class="swatch__note">Borda padrão de painel + dividers</div></div></div>
</div>
<h3>Texto</h3>
<div class="grid g2">
<div class="swatch"><div class="swatch__chip" style="background:#f4eaff"></div><div class="swatch__body"><div class="swatch__token">--fc-text</div><div class="swatch__hex">#f4eaff</div><div class="swatch__note">Texto principal. Off-white com tom lilás.</div></div></div>
<div class="swatch"><div class="swatch__chip" style="background:#8b75b8"></div><div class="swatch__body"><div class="swatch__token">--fc-text-dim</div><div class="swatch__hex">#8b75b8</div><div class="swatch__note">Texto secundário, labels, sub.</div></div></div>
</div>
<h3>Acentos</h3>
<div class="grid g4">
<div class="swatch"><div class="swatch__chip" style="background:#ff2d95;box-shadow:inset 0 0 24px rgba(0,0,0,.3)"></div><div class="swatch__body"><div class="swatch__token">--fc-accent</div><div class="swatch__hex">#ff2d95</div><div class="swatch__note">Magenta · alertas suaves, quest diária, CTA primário accent.</div></div></div>
<div class="swatch"><div class="swatch__chip" style="background:#00f0ff"></div><div class="swatch__body"><div class="swatch__token">--fc-accent-2</div><div class="swatch__hex">#00f0ff</div><div class="swatch__note">Ciano · links, navegação ativa, XP, quest semanal.</div></div></div>
<div class="swatch"><div class="swatch__chip" style="background:#a855f7"></div><div class="swatch__body"><div class="swatch__token">--fc-accent-3</div><div class="swatch__hex">#a855f7</div><div class="swatch__note">Roxo · botão padrão, painel em destaque, glow.</div></div></div>
<div class="swatch"><div class="swatch__chip" style="background:#ffd84d"></div><div class="swatch__body"><div class="swatch__token">--fc-gold</div><div class="swatch__hex">#ffd84d</div><div class="swatch__note">XP, conquistas, quest mensal.</div></div></div>
</div>
<h3>Estado</h3>
<div class="grid g2">
<div class="swatch"><div class="swatch__chip" style="background:#39ff7a"></div><div class="swatch__body"><div class="swatch__token">--fc-green</div><div class="swatch__hex">#39ff7a</div><div class="swatch__note">Meta atingida, valores positivos.</div></div></div>
<div class="swatch"><div class="swatch__chip" style="background:#ff3b6b"></div><div class="swatch__body"><div class="swatch__token">--fc-red</div><div class="swatch__hex">#ff3b6b</div><div class="swatch__note">Meta falhou, recorrência ausente, alerta crítico.</div></div></div>
</div>
<!-- ─────────────── TYPOGRAPHY ─────────────── -->
<h2>Typography</h2>
<div class="demo" style="padding: 0 20px">
<div class="font-sample">
<div class="font-sample__meta">Press Start 2P<br><span style="color:var(--fc-text-dim);font-size:9px">pixel · labels & numbers</span></div>
<div class="font-sample__demo">
<div class="pix" style="font-size:7px;color:var(--fc-text-dim)">:: LABEL HUD ::</div>
<div class="pix" style="font-size:9px;color:var(--fc-accent-2);margin-top:6px">:: TÍTULO DE PAINEL</div>
<div class="pix" style="font-size:14px;color:var(--fc-text);margin-top:6px">MAIO · 2026</div>
<div class="pix" style="font-size:40px;color:var(--fc-green);text-shadow:0 0 12px #39ff7a88;margin-top:8px">47%</div>
</div>
</div>
<div class="font-sample">
<div class="font-sample__meta">JetBrains Mono<br><span style="color:var(--fc-text-dim);font-size:9px">mono · valores</span></div>
<div class="font-sample__demo">
<div class="mon" style="font-size:10px;color:var(--fc-text-dim)">26/05/26 · Itaú · Crédito</div>
<div class="mon" style="font-size:11px;color:var(--fc-text);margin-top:4px">Mercado Pão de Açúcar</div>
<div class="mon" style="font-size:16px;color:var(--fc-text);font-weight:600;margin-top:4px">R$ 7.844,21</div>
</div>
</div>
<div class="font-sample" style="border-bottom:none">
<div class="font-sample__meta">Inter<br><span style="color:var(--fc-text-dim);font-size:9px">body · descrições</span></div>
<div class="font-sample__demo">
<div class="fc-body" style="font-size:13px;color:var(--fc-text);line-height:1.55;max-width:520px">
Mantenha gastos com veleiro abaixo de R$ 500 neste mês. Recompensa: <span class="fc-text-gold">+180 XP</span>. Última atualização há 2h.
</div>
</div>
</div>
</div>
<!-- ─────────────── PANELS ─────────────── -->
<h2>Panels</h2>
<p class="lede">Painel é o tijolo do app. Versão glow é reservada pro card de destaque da rota (poupado-do-mês, status do personagem). Cantos em "L" decoram só painéis especiais.</p>
<div class="grid g2">
<div class="demo">
<div class="ptitle">:: PAINEL PADRÃO</div>
<p class="fc-body" style="margin:0;color:var(--fc-text);font-size:13px;line-height:1.5">Container neutro. Use pra listar transações, categorias, contas, qualquer dado tabular.</p>
</div>
<div class="demo demo--glow">
<div class="ptitle">:: PAINEL COM GLOW</div>
<p class="fc-body" style="margin:0;color:var(--fc-text);font-size:13px;line-height:1.5">Borda roxa + halo. Reservado pro card mais importante da rota.</p>
</div>
<div class="demo">
<span class="corner tl"></span><span class="corner tr"></span><span class="corner bl"></span><span class="corner br"></span>
<div class="ptitle">:: PAINEL COM CANTOS EM L</div>
<p class="fc-body" style="margin:0;color:var(--fc-text);font-size:13px;line-height:1.5">Decoração HUD opcional. Use no hero ou no card-de-status do personagem.</p>
</div>
<div class="demo demo--danger">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px">
<span class="fc-blink" style="color:var(--fc-red);font-size:16px"></span>
<span class="fc-pixel" style="font-size:8px;color:var(--fc-red)">RECORRÊNCIA AUSENTE</span>
</div>
<p class="fc-body" style="margin:0;color:var(--fc-text);font-size:13px">Variante <code>danger</code>. Borda vermelha + halo + triângulo pulsante.</p>
</div>
</div>
<!-- ─────────────── BUTTONS ─────────────── -->
<h2>Buttons</h2>
<p class="lede">Texto sempre Press Start 2P em maiúscula. Verbo + (seta opcional). Hover acende o glow da cor da borda.</p>
<div class="row">
<button class="btn">REGISTRAR ▶</button>
<button class="btn btn--pink">IMPORTAR EXTRATO</button>
<button class="btn btn--cyan">VER DETALHES</button>
</div>
<!-- ─────────────── CHIPS ─────────────── -->
<h2>Chips</h2>
<p class="lede">Tags pequenas. Sempre Press Start 2P 7px maiúsculo. A cor é semântica.</p>
<div class="row">
<span class="chip chip--magenta">DIÁRIA</span>
<span class="chip chip--cyan">SEMANAL</span>
<span class="chip chip--gold">MENSAL</span>
<span class="chip chip--green">QUEST OK</span>
<span class="chip chip--red">QUEST FAIL</span>
</div>
<!-- ─────────────── BARS ─────────────── -->
<h2>Bars (XP, progresso)</h2>
<p class="lede">Sempre 2px de borda. Fill com gradiente diagonal + listras internas — assinatura visual.</p>
<div class="demo">
<div style="display:flex;justify-content:space-between;margin-bottom:6px">
<span class="fc-pixel" style="font-size:7px;color:var(--fc-text-dim)">XP</span>
<span class="fc-mono" style="font-size:10px;color:var(--fc-text-dim)">3240 / 4900</span>
</div>
<div class="bar" style="height:14px">
<div class="bar__fill" style="width:66%"></div>
</div>
<div style="display:flex;justify-content:space-between;margin:14px 0 6px">
<span class="fc-pixel" style="font-size:7px;color:var(--fc-text-dim)">PROGRESSO QUEST · DIÁRIA</span>
<span class="fc-mono" style="font-size:10px;color:var(--fc-text-dim)">3 / 5</span>
</div>
<div class="bar" style="height:6px"><div class="bar__fill" style="width:60%"></div></div>
<div style="display:flex;justify-content:space-between;margin:14px 0 6px">
<span class="fc-pixel" style="font-size:7px;color:var(--fc-text-dim)">META POUPANÇA · ATINGIDA</span>
<span class="fc-mono" style="font-size:10px;color:var(--fc-green)">47%</span>
</div>
<div class="bar bar--success" style="height:14px"><div class="bar__fill" style="width:47%"></div></div>
</div>
<!-- ─────────────── LINKS ─────────────── -->
<h2>Links (text actions)</h2>
<p class="lede">Verbo curto + seta. Sempre Press Start 2P em ciano. Hover ganha glow.</p>
<div class="row">
<a class="link">VER+</a>
<a class="link">ABRIR ▶</a>
<a class="link">REGISTRAR ▶</a>
</div>
<!-- ─────────────── HUD ─────────────── -->
<h2>HUD (top bar)</h2>
<p class="lede">Persistente em todas as rotas. Os stats RPG (LV/XP/STREAK/META) sempre visíveis — é assim que a direção mantém a metáfora de jogo.</p>
<div class="hud-demo">
<div class="hud-demo__logo">
<div class="hud-demo__mark"></div>
<div>
<div class="fc-pixel" style="font-size:10px;color:var(--fc-accent-2)">CARVALHO</div>
<div class="fc-pixel" style="font-size:7px;color:var(--fc-text-dim);margin-top:2px">FIN.SYS v1.4</div>
</div>
</div>
<div style="width:2px;height:32px;background:var(--fc-panel-edge);margin-inline:8px"></div>
<div class="stat">
<span class="stat__l">LV</span>
<span class="stat__v"><span style="color:var(--fc-gold);text-shadow:0 0 8px #ffd84d66">7</span></span>
</div>
<div class="stat">
<span class="stat__l">XP</span>
<span class="stat__v"><span style="color:var(--fc-accent-2);text-shadow:0 0 8px #00f0ff66">3240</span><span class="stat__s">/4900</span></span>
</div>
<div class="stat">
<span class="stat__l">STREAK</span>
<span class="stat__v"><span style="color:var(--fc-accent);text-shadow:0 0 8px #ff2d9566">23d</span></span>
</div>
<div class="stat">
<span class="stat__l">META</span>
<span class="stat__v"><span style="color:var(--fc-green);text-shadow:0 0 8px #39ff7a66">47%</span><span class="stat__s">/40%</span></span>
</div>
</div>
<!-- ─────────────── SPRITES ─────────────── -->
<h2>Pixel Sprites</h2>
<p class="lede">Personagem é 17×24 pixels. Cores parametrizadas por slot (hat / shirt / pants / boots). Cosméticos do banco fazem merge desses slots em runtime.</p>
<h3>Escalas canônicas</h3>
<div class="grid g4" id="scales"></div>
<h3>Variantes por cosmético</h3>
<div class="grid g4" id="variants"></div>
<!-- ─────────────── ANIMATIONS ─────────────── -->
<h2>Animations</h2>
<p class="lede">Toda animação tem propósito. Idle bob é o único motion "passivo". Celebrate roda 3s após quest complete. Level-up sobrepõe modal com glow magenta.</p>
<div class="grid g3">
<div class="demo" style="text-align:center">
<div class="ptitle">IDLE BOB</div>
<div id="bob-demo" style="line-height:0;margin:14px 0 8px"></div>
<span class="fc-mono" style="font-size:10px;color:var(--fc-text-dim)">animation: 2.2s steps(2)</span>
</div>
<div class="demo" style="text-align:center">
<div class="ptitle">CELEBRATE</div>
<div id="celebrate-demo" style="line-height:0;margin:14px 0 8px"></div>
<span class="fc-mono" style="font-size:10px;color:var(--fc-text-dim)">animation: 1s ease-in-out</span>
</div>
<div class="demo" style="text-align:center">
<div class="ptitle">ALERT BLINK</div>
<div style="font-size:48px;line-height:1;margin:14px 0 8px"><span class="fc-blink" style="color:var(--fc-red);text-shadow:0 0 14px #ff3b6b"></span></div>
<span class="fc-mono" style="font-size:10px;color:var(--fc-text-dim)">animation: 1.2s steps(1)</span>
</div>
</div>
<!-- ─────────────── SHADOWS ─────────────── -->
<h2>Shadows & Glow</h2>
<div class="grid g3">
<div style="background:var(--fc-bg-raised);border:2px solid var(--fc-panel-edge);padding:24px;border-radius:4px;text-align:center;
box-shadow:inset 0 1px 0 rgba(255,255,255,.05), 0 0 0 1px rgba(0,0,0,.4), 0 8px 24px rgba(0,0,0,.4)">
<div class="fc-pixel" style="font-size:8px;color:var(--fc-accent-2)">PADRÃO</div>
<div class="fc-mono" style="font-size:9px;color:var(--fc-text-dim);margin-top:6px">--fc-shadow-panel</div>
</div>
<div style="background:var(--fc-bg-raised);border:2px solid var(--fc-accent-3);padding:24px;border-radius:4px;text-align:center;
box-shadow:var(--fc-shadow-panel-glow)">
<div class="fc-pixel" style="font-size:8px;color:var(--fc-accent-3)">GLOW</div>
<div class="fc-mono" style="font-size:9px;color:var(--fc-text-dim);margin-top:6px">--fc-shadow-panel-glow</div>
</div>
<div style="background:var(--fc-bg-raised);border:2px solid var(--fc-red);padding:24px;border-radius:4px;text-align:center;
box-shadow:0 0 0 1px var(--fc-red), 0 0 16px rgba(255,59,107,.3), 0 8px 24px rgba(0,0,0,.4)">
<div class="fc-pixel" style="font-size:8px;color:var(--fc-red)">DANGER</div>
<div class="fc-mono" style="font-size:9px;color:var(--fc-text-dim);margin-top:6px">--fc-red border + halo</div>
</div>
</div>
<hr>
<p class="lede" style="margin-top:24px;font-size:11px">
<span class="fc-pixel" style="font-size:8px;color:var(--fc-text-dim)">v1.0 · </span>
Style guide gerado automaticamente. Para regenerar sprites: rode <code>run.js</code> em <code>sprites/</code>. Para perguntas, abra <code>STYLE_GUIDE.md</code> primeiro.
</p>
<script>
// Render sprite scales + variants from sprite-data.json
fetch('sprites/sprite-data.json').then(r => r.json()).then(data => {
const GRID = data.grid;
const COLS = data.cols;
const ROWS = data.rows;
const DEFAULTS = data._legend;
function spriteSvg(theme, scale, className) {
const w = COLS * scale, h = ROWS * scale;
let rects = '';
for (let y = 0; y < ROWS; y++) {
for (let x = 0; x < COLS; x++) {
const k = GRID[y][x];
if (k === '.' || !k) continue;
const color = (theme && theme[k]) || DEFAULTS[k];
if (!color) continue;
rects += `<rect x="${x*scale}" y="${y*scale}" width="${scale}" height="${scale}" fill="${color}"/>`;
}
}
return `<svg width="${w}" height="${h}" viewBox="0 0 ${w} ${h}" shape-rendering="crispEdges" class="${className||''}" style="image-rendering:pixelated">${rects}</svg>`;
}
// Scales
const defaultTheme = data.cosmetics.find(c => c.id === 'default').theme;
document.getElementById('scales').innerHTML = [
{ scale: 2, label: 'scale=2', sub: 'mobile inline' },
{ scale: 3, label: 'scale=3', sub: 'dashboard widget' },
{ scale: 4, label: 'scale=4', sub: 'padrão' },
{ scale: 6, label: 'scale=6', sub: 'personagem hero' },
].map(({scale, label, sub}) => `
<div class="sprite-card">
${spriteSvg(defaultTheme, scale)}
<div class="sprite-card__name">${label.toUpperCase()}</div>
<div class="sprite-card__meta">${sub.toUpperCase()}</div>
</div>
`).join('');
// Variants
document.getElementById('variants').innerHTML = data.cosmetics.map(cos => `
<div class="sprite-card">
${spriteSvg(cos.theme || {}, 4)}
<div class="sprite-card__name">${cos.name.toUpperCase()}</div>
<div class="sprite-card__meta">LV ${cos.unlockLv}</div>
</div>
`).join('');
// Bob demo
document.getElementById('bob-demo').innerHTML = spriteSvg(defaultTheme, 3, 'demo-bob');
document.getElementById('celebrate-demo').innerHTML = spriteSvg(defaultTheme, 3, 'demo-celebrate');
// animations
const s = document.createElement('style');
s.textContent = `
@keyframes b-bob { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-3px); } }
.demo-bob { animation: b-bob 2.2s steps(2) infinite; }
@keyframes b-cel { 0%,100% { transform: translateY(0) rotate(0); } 25% { transform: translateY(-6px) rotate(-3deg); } 75% { transform: translateY(-6px) rotate(3deg); } }
.demo-celebrate { animation: b-cel 1s ease-in-out infinite; }
`;
document.head.appendChild(s);
}).catch(e => {
document.getElementById('scales').innerHTML = '<p style="color:var(--fc-red)">Erro carregando sprites: ' + e.message + '. Abra este arquivo via servidor HTTP, não com file://.</p>';
});
</script>
</body>
</html>