---
name: frontend-crafter
description: Especialista em Frontend Mobile-First, UX Black & Orange, Sistema de Cache Offline (IndexedDB), Navegação SPA v3.0 e Performance. Use para criar/ajustar telas, componentes, otimizar CSS/JS, implementar patterns de cache ou debugging de frontend issues.
allowed-tools: Read, Grep, LS, Bash, Edit
---
# Frontend Crafter Skill (Mobile-First Master)
## 🎯 Missão
Criar experiências frontend excepcionais para o Super Cartola Manager com foco em mobile-first, performance e UX consistente.
---
## 1. 🎨 Design System - Black & Orange
### 1.1 Paleta de Cores
```css
:root {
/* === PRIMÁRIAS === */
--laranja: #FF4500; /* Cor principal */
--laranja-hover: #FF5500; /* Hover states */
--laranja-dark: #CC3700; /* Variação escura */
/* === BACKGROUNDS === */
--bg-card: #1a1a1a; /* Cards dark */
--bg-secondary: #2a2a2a; /* Seções alternadas */
--bg-overlay: rgba(0,0,0,0.8);/* Modals/overlays */
/* === STATUS === */
--verde-lucro: #10b981; /* Lucro/Vitória */
--vermelho-prejuizo: #ef4444; /* Prejuízo/Derrota */
--amarelo-neutro: #f59e0b; /* Neutro/Atenção */
/* === TEXTOS === */
--text-primary: #ffffff;
--text-secondary: #a0a0a0;
--text-muted: #666666;
/* === BORDAS === */
--border-color: #333333;
--border-radius: 12px;
}
```
### 1.2 Typography (TRES FONTES OBRIGATORIAS)
```css
/* OBRIGATORIO: Sistema de 3 fontes */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Russo+One&display=swap');
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap');
:root {
/* === FAMILIAS DE FONTE === */
--font-family-base: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--font-family-brand: 'Russo One', sans-serif; /* Titulos, stats, CTAs */
--font-family-mono: 'JetBrains Mono', 'Fira Code', monospace; /* Codigo, numeros */
}
body {
font-family: var(--font-family-base);
-webkit-font-smoothing: antialiased;
}
/* INTER - Corpo de texto (padrao) */
.body { font-family: var(--font-family-base); font-size: 16px; font-weight: 400; }
.small { font-family: var(--font-family-base); font-size: 14px; font-weight: 400; }
.caption { font-family: var(--font-family-base); font-size: 12px; font-weight: 500; }
/* RUSSO ONE - Titulos e destaques (brand) */
.font-brand { font-family: var(--font-family-brand); font-weight: 400; }
.h1 { font-family: var(--font-family-brand); font-size: 28px; letter-spacing: 0.5px; }
.h2 { font-family: var(--font-family-brand); font-size: 24px; letter-spacing: 0.5px; }
.h3 { font-family: var(--font-family-brand); font-size: 20px; letter-spacing: 0.3px; }
/* JETBRAINS MONO - Codigo e numeros tabulares */
.font-mono { font-family: var(--font-family-mono); }
.tabular-nums { font-variant-numeric: tabular-nums; }
```
**REGRA:** Russo One so tem peso 400, usar `letter-spacing` para ajustar espacamento.
### 1.3 Componentes Base
```html
check
Confirmar
```
### 1.4 Icons - Material Icons OBRIGATÓRIO
```html
home
trophy
account_balance_wallet
bar_chart
```
---
## 2. 📱 Arquitetura Mobile SPA v3.0
### 2.1 Estrutura de Fragmentos
```
public/participante/
├── fronts/ # Templates (fragmentos HTML)
│ ├── home.html
│ ├── ranking.html
│ ├── extrato.html
│ └── perfil.html
├── core/
│ ├── navigation.js # Sistema de navegação v3.0
│ ├── cache-manager.js # IndexedDB manager
│ └── api-client.js # HTTP client
└── modules/
├── ranking/
│ ├── ranking.js # Lógica do módulo
│ └── ranking.css # Estilos específicos
└── extrato/
└── ...
```
**IMPORTANTE:** Fragmentos são HTML puro sem ``, `` ou ``.
```html
...
...
```
### 2.2 Navegação SPA v3.0
```javascript
// participante-navigation.js
class NavigationManager {
constructor() {
this.currentPage = null;
this.debounceTimer = null;
this.DEBOUNCE_DELAY = 100; // ms
}
async navigate(page, skipHistory = false) {
// Debounce - NUNCA usar flag de travamento
clearTimeout(this.debounceTimer);
this.debounceTimer = setTimeout(async () => {
await this._doNavigate(page, skipHistory);
}, this.DEBOUNCE_DELAY);
}
async _doNavigate(page, skipHistory) {
// Validar página
const validPages = ['home', 'ranking', 'extrato', 'perfil'];
if (!validPages.includes(page)) {
console.error('Página inválida:', page);
return;
}
// Evitar navegação duplicada
if (this.currentPage === page) return;
try {
// 1. Loading state
this.showLoading();
// 2. Carregar fragmento
const html = await fetch(`/participante/fronts/${page}.html`).then(r => r.text());
// 3. Renderizar
document.getElementById('main-content').innerHTML = html;
// 4. Executar módulo específico
await this.loadModule(page);
// 5. Atualizar history
if (!skipHistory) {
window.history.pushState({ page }, '', `#${page}`);
}
// 6. Atualizar nav
this.updateActiveNav(page);
this.currentPage = page;
} catch (error) {
console.error('Erro ao navegar:', error);
this.showError();
} finally {
this.hideLoading();
}
}
async loadModule(page) {
// Carregar script do módulo dinamicamente
if (typeof window[`${page}Module`] === 'object') {
await window[`${page}Module`].init();
}
}
showLoading() {
// Glass overlay - OBRIGATÓRIO em reloads
const overlay = document.createElement('div');
overlay.id = 'loading-overlay';
overlay.className = 'glass-overlay';
overlay.innerHTML = '
';
document.body.appendChild(overlay);
}
hideLoading() {
document.getElementById('loading-overlay')?.remove();
}
}
// Interceptar botão voltar
window.addEventListener('popstate', (e) => {
if (e.state && e.state.page) {
navManager.navigate(e.state.page, true);
}
});
```
### 2.3 Loading States
```html
```
---
## 3. 💾 Performance & Cache (IndexedDB)
### 3.1 Cache Strategy - Cache-First
```javascript
// cache-manager.js
class CacheManager {
constructor() {
this.dbName = 'super_cartola_cache';
this.version = 1;
this.db = null;
}
async init() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.version);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
this.db = request.result;
resolve();
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
// Criar stores
if (!db.objectStoreNames.contains('participante')) {
db.createObjectStore('participante', { keyPath: 'id' });
}
if (!db.objectStoreNames.contains('ranking')) {
db.createObjectStore('ranking', { keyPath: 'key' });
}
if (!db.objectStoreNames.contains('extrato')) {
db.createObjectStore('extrato', { keyPath: 'key' });
}
};
});
}
async get(store, key) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([store], 'readonly');
const objectStore = transaction.objectStore(store);
const request = objectStore.get(key);
request.onsuccess = () => {
const data = request.result;
// Verificar TTL
if (data && this.isExpired(data)) {
this.delete(store, key);
resolve(null);
} else {
resolve(data);
}
};
request.onerror = () => reject(request.error);
});
}
async set(store, data, ttl = null) {
const record = {
...data,
_timestamp: Date.now(),
_ttl: ttl || this.getTTL(store)
};
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([store], 'readwrite');
const objectStore = transaction.objectStore(store);
const request = objectStore.put(record);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
isExpired(data) {
if (!data._timestamp || !data._ttl) return false;
return Date.now() - data._timestamp > data._ttl;
}
getTTL(store) {
const TTL_MAP = {
participante: 24 * 60 * 60 * 1000, // 24h
liga: 24 * 60 * 60 * 1000, // 24h
ranking: 60 * 60 * 1000, // 1h
extrato: 30 * 60 * 1000 // 30min
};
return TTL_MAP[store] || 60 * 60 * 1000; // default 1h
}
}
// Cache-First Pattern
async function loadRanking() {
// 1. Tentar cache (render instantâneo)
const cached = await cacheManager.get('ranking', 'current');
if (cached) {
renderRanking(cached.data);
}
// 2. Fetch fresh (background)
try {
const fresh = await fetch('/api/ranking').then(r => r.json());
await cacheManager.set('ranking', { key: 'current', data: fresh });
// 3. Re-render se mudou
if (!cached || JSON.stringify(cached.data) !== JSON.stringify(fresh)) {
renderRanking(fresh);
}
} catch (error) {
// Se fetch falhar e temos cache, continuar com cache
if (!cached) {
showError('Não foi possível carregar dados');
}
}
}
```
### 3.2 TTL por Módulo
```javascript
const CACHE_TTL = {
// Dados estáticos/semi-estáticos
participante: 24 * 60 * 60 * 1000, // 24h
liga: 24 * 60 * 60 * 1000, // 24h
config: 7 * 24 * 60 * 60 * 1000, // 7 dias
// Dados dinâmicos
ranking: 60 * 60 * 1000, // 1h
extrato: 30 * 60 * 1000, // 30min
rodadaAtual: 10 * 60 * 1000, // 10min
// Dados em tempo real
liveFeed: 60 * 1000 // 1min
};
```
---
## 4. 📱 Componentes Mobile Premium (v3.2)
### 4.1 Header com Avatar e Badge
```html
```
### 4.2 Grid de Atalhos (4 colunas)
```html
emoji_events
Premiacoes
groups
Participantes
description
Regras
workspace_premium
Cartola PRO
```
### 4.3 Card de Status do Time (Split Layout)
```html
Pontos
0
Aguardando 1a rodada
Posicao
--
Aguardando 1a rodada
```
### 4.4 FAB do Mercado (Floating Action Button)
```html
storefront
Fecha em 7d 5h
Aberto R1
```
### 4.5 Card de Jogo (Match Card)
```html
```
### 4.6 Bottom Navigation (4 itens)
```html
home
Inicio
leaderboard
Ranking
apps
Menu
account_balance_wallet
Financeiro
```
### 4.7 Mapeamento Font Awesome → Material Icons
| Font Awesome | Material Icons | Uso |
|--------------|----------------|-----|
| `fa-trophy` | `emoji_events` | Premiacoes |
| `fa-users` | `groups` | Participantes |
| `fa-clipboard-list` | `description` | Regras |
| `fa-crown` | `workspace_premium` | Premium |
| `fa-home` | `home` | Inicio |
| `fa-chart-line` | `leaderboard` | Ranking |
| `fa-th` | `apps` | Menu |
| `fa-wallet` | `account_balance_wallet` | Financeiro |
| `fa-shop` | `storefront` | Mercado |
| `fa-coins` | `payments` | Saldo |
| `fa-bell` | `notifications` | Alertas |
| `fa-shield-alt` | `shield` | Escudo |
---
## 5. 🎭 Admin UI (Desktop)
### 4.1 Layout Padrão
```html
```
### 4.2 Módulos Admin
```javascript
// Padrão de módulo admin
const adminTesouraria = {
currentLigaId: null,
currentTemporada: null,
async render(container, ligaId, temporada) {
this.currentLigaId = ligaId;
this.currentTemporada = temporada;
// Carregar template
const template = await fetch('/admin/modules/tesouraria.html').then(r => r.text());
document.querySelector(container).innerHTML = template;
// Carregar dados
await this.loadData();
// Bind events
this.bindEvents();
},
async loadData() {
const data = await fetch(`/api/tesouraria/${this.currentLigaId}/${this.currentTemporada}`)
.then(r => r.json());
this.renderTable(data);
this.renderStats(data);
},
renderTable(data) {
const tbody = document.querySelector('#tesouraria-table tbody');
tbody.innerHTML = data.participantes.map(p => `
${p.nome}
R$ ${p.saldo.toFixed(2).replace('.', ',')}
Ver Extrato
`).join('');
},
bindEvents() {
// ...
},
// API pública
async recarregar() {
await this.loadData();
},
mudarTemporada(temporada) {
this.currentTemporada = temporada;
this.recarregar();
}
};
```
---
## 6. 📤 Export System (Mobile Dark HD)
### 5.1 Configuração Padrão
```javascript
const EXPORT_CONFIG = {
backgroundColor: '#000000',
scale: 2, // Retina
useCORS: true,
logging: false,
width: 1080,
height: 1920,
pixelRatio: 2
};
async function exportarModulo(elementId, filename) {
const element = document.getElementById(elementId);
// Aplicar classe de export (mobile otimizado)
element.classList.add('export-mode');
try {
const canvas = await html2canvas(element, EXPORT_CONFIG);
// Download
const link = document.createElement('a');
link.download = `${filename}_${Date.now()}.png`;
link.href = canvas.toDataURL('image/png');
link.click();
// Feedback
showToast('✅ Imagem exportada com sucesso!');
} catch (error) {
console.error('Erro ao exportar:', error);
showToast('🔴 Erro ao exportar imagem');
} finally {
element.classList.remove('export-mode');
}
}
```
### 5.2 CSS para Export
```css
/* Otimizações para export */
.export-mode {
width: 1080px !important;
min-height: 1920px !important;
padding: 40px !important;
background: linear-gradient(180deg, #1a1a1a 0%, #0a0a0a 100%) !important;
}
.export-mode .card {
box-shadow: 0 8px 32px rgba(0,0,0,0.4) !important;
}
.export-mode .text {
-webkit-font-smoothing: antialiased !important;
text-rendering: optimizeLegibility !important;
}
```
---
## 7. 🛠️ Debugging & Tools
### 6.1 Performance Monitoring
```javascript
// Adicionar no index.html
if ('performance' in window) {
window.addEventListener('load', () => {
const perfData = window.performance.timing;
const pageLoadTime = perfData.loadEventEnd - perfData.navigationStart;
const connectTime = perfData.responseEnd - perfData.requestStart;
const renderTime = perfData.domComplete - perfData.domLoading;
console.log('📊 Performance Metrics:');
console.log(` Page Load: ${pageLoadTime}ms`);
console.log(` Connect: ${connectTime}ms`);
console.log(` Render: ${renderTime}ms`);
// Enviar para analytics (se configurado)
if (window.analytics) {
window.analytics.track('page_performance', {
pageLoadTime,
connectTime,
renderTime
});
}
});
}
```
### 6.2 Responsive Debug
```html
```
---
## 8. 📋 Checklists
### 7.1 Novo Módulo Mobile
```markdown
□ Criar fragmento em /fronts/
□ Criar arquivo JS do módulo
□ Implementar Cache-First pattern
□ Adicionar rota no navigation.js
□ Criar ícone Material Icons
□ Testar em viewport mobile (360x640)
□ Validar TTL do cache
□ Implementar loading states
□ Testar offline
□ Validar export (se aplicável)
```
### 7.2 CSS Performance
```markdown
□ Evitar seletores complexos (> 3 níveis)
□ Usar transform para animações (não top/left)
□ Adicionar will-change em elementos animados
□ Minificar antes de deploy
□ Verificar bundle size (<100KB)
□ Usar CSS Grid/Flexbox (não floats)
□ Lazy load imagens (loading="lazy")
```
---
**STATUS:** Frontend Crafter - READY TO CRAFT
**Versao:** 3.2 (Mobile Premium Components)
**Ultima atualizacao:** 2026-01-23
**Changelog v3.2:**
- Nova secao 4: Componentes Mobile Premium
- Header com Avatar e Badge Premium
- Grid de Atalhos 4 colunas (outlined icons)
- Card de Status do Time com split Pontos/Posicao
- FAB do Mercado com timer integrado e gradiente verde
- Match Card com escudos circulares
- Bottom Navigation padronizado (4 itens)
- Tabela de conversao Font Awesome → Material Icons
- Todos componentes usando variaveis CSS do Design System
**Changelog v3.1:**
- Documentado sistema de 3 fontes: Inter (base), Russo One (brand), JetBrains Mono (mono)
- Classe `.font-brand` para titulos com Russo One
- Variaveis CSS padronizadas: `--font-family-base`, `--font-family-brand`, `--font-family-mono`