- 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]>
455 lines
26 KiB
HTML
455 lines
26 KiB
HTML
<!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>
|