Skip to content
frontend-crafter logo

Frontend Crafter Skill (Mobile-First Master)

frontend-crafter

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.

SKILL.md

Full skill instructions

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

: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)

/​* 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

<!-- Card Padrão -->
<div class="card">
  <div class="card-header">
    <h3>Título</​h3>
  </​div>
  <div class="card-body">
    <!-- Conteúdo -->
  </​div>
</​div>

<style>
.card {
  background: var(--bg-card);
  border-radius: var(--border-radius);
  padding: 20px;
  margin-bottom: 16px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}
</​style>

<!-- Botão Primário -->
<button class="btn-primary">
  <i class="material-icons">check</​i>
  Confirmar
</​button>

<style>
.btn-primary {
  background: linear-gradient(135deg, var(--laranja), var(--laranja-dark));
  color: white;
  border: none;
  border-radius: 8px;
  padding: 12px 24px;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  transition: all 0.2s;
}
.btn-primary:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(255, 69, 0, 0.4);
}
</​style>

1.4 Icons - Material Icons OBRIGATÓRIO

<!-- NUNCA usar emojis, SEMPRE Material Icons -->
<link href="https://fonts.googleapis.com/​icon?family=Material+Icons" rel="stylesheet">

<!-- Exemplos -->
<i class="material-icons">home</​i>
<i class="material-icons">trophy</​i>
<i class="material-icons">account_balance_wallet</​i>
<i class="material-icons">bar_chart</​i>

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 <html>, <head> ou <body>.

<!-- ✅ CORRETO: fronts/​ranking.html -->
<div id="ranking-container">
  <h2>Ranking</​h2>
  <div class="ranking-list"></​div>
</​div>

<!-- ❌ ERRADO -->
<!DOCTYPE html>
<html>
  <head>...</​head>
  <body>...</​body>
</​html>

2.2 Navegação SPA v3.0

// 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 = '<div class="spinner"></​div>';
    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

<!-- Splash Screen (apenas 1ª visita) -->
<div id="splash-screen" class="splash">
  <img src="/​img/​logo.png" alt="Super Cartola">
  <div class="spinner"></​div>
</​div>

<!-- Glass Overlay (reloads/​PTR) -->
<div class="glass-overlay">
  <div class="spinner"></​div>
</​div>

<style>
.splash {
  position: fixed;
  inset: 0;
  background: #000;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  z-index: 9999;
}

.glass-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.8);
  backdrop-filter: blur(4px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
}

.spinner {
  width: 48px;
  height: 48px;
  border: 4px solid rgba(255, 69, 0, 0.3);
  border-top-color: var(--laranja);
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}
</​style>

3. 💾 Performance & Cache (IndexedDB)

3.1 Cache Strategy - Cache-First

// 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

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

<!-- Header Premium com identidade do usuario -->
<header class="header-premium">
  <div class="header-content">
    <!-- Avatar com Iniciais -->
    <div class="user-section">
      <div class="avatar-circle">
        <span class="avatar-initials font-brand">PM</​span>
      </​div>
      <div class="user-info">
        <h1 class="user-name font-brand">Paulinett Miranda</​h1>
        <div class="badge-premium">
          <i class="material-icons badge-icon">star</​i>
          <span>Premium</​span>
        </​div>
      </​div>
    </​div>
    <!-- Notificacoes -->
    <button class="btn-icon" aria-label="Notificacoes">
      <i class="material-icons">notifications</​i>
    </​button>
  </​div>
</​header>

<style>
.header-premium {
  padding: 48px 16px 24px;
  background: #000;
  position: sticky;
  top: 0;
  z-index: 20;
  border-bottom: 1px solid var(--border-color);
}

.header-content {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.user-section {
  display: flex;
  align-items: center;
  gap: 12px;
}

.avatar-circle {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: #bdc3c7;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 2px solid var(--bg-card);
}

.avatar-initials {
  color: #1a1a1a;
  font-size: 14px;
}

.user-name {
  font-size: 18px;
  color: var(--text-primary);
  line-height: 1.2;
}

.badge-premium {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-top: 2px;
}

.badge-premium .badge-icon {
  font-size: 12px;
  color: #f59e0b;
}

.badge-premium span {
  font-size: 10px;
  color: #f59e0b;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.btn-icon {
  padding: 8px;
  background: transparent;
  border: none;
  color: var(--text-primary);
  cursor: pointer;
}

.btn-icon .material-icons {
  font-size: 24px;
}
</​style>

4.2 Grid de Atalhos (4 colunas)

<!-- Grid de acoes rapidas -->
<section class="action-grid">
  <button class="action-item">
    <div class="action-icon">
      <i class="material-icons">emoji_events</​i>
    </​div>
    <span class="action-label">Premiacoes</​span>
  </​button>
  <button class="action-item">
    <div class="action-icon">
      <i class="material-icons">groups</​i>
    </​div>
    <span class="action-label">Participantes</​span>
  </​button>
  <button class="action-item">
    <div class="action-icon">
      <i class="material-icons">description</​i>
    </​div>
    <span class="action-label">Regras</​span>
  </​button>
  <button class="action-item">
    <div class="action-icon">
      <i class="material-icons">workspace_premium</​i>
    </​div>
    <span class="action-label">Cartola PRO</​span>
  </​button>
</​section>

<style>
.action-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 12px;
  padding: 0 16px;
}

.action-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  background: transparent;
  border: none;
  cursor: pointer;
}

.action-icon {
  width: 56px;
  height: 56px;
  border-radius: 12px;
  border: 1px solid var(--laranja);
  background: var(--bg-card);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.2s;
}

.action-icon:hover {
  background: var(--bg-secondary);
}

.action-icon .material-icons {
  font-size: 24px;
  color: var(--laranja);
}

.action-label {
  font-size: 10px;
  color: var(--text-secondary);
  font-weight: 500;
  text-align: center;
}
</​style>

4.3 Card de Status do Time (Split Layout)

<!-- Card com Pontos e Posicao lado a lado -->
<section class="team-status-card">
  <div class="team-header">
    <h2 class="team-name font-brand">Urubu Play F.C.</​h2>
    <div class="team-shield">
      <i class="material-icons">shield</​i>
    </​div>
  </​div>
  <div class="stats-split">
    <div class="stat-block">
      <span class="stat-label">Pontos</​span>
      <span class="stat-value font-mono text-accent">0</​span>
      <span class="stat-hint">Aguardando 1a rodada</​span>
    </​div>
    <div class="stat-divider"></​div>
    <div class="stat-block">
      <span class="stat-label">Posicao</​span>
      <span class="stat-value font-mono">--</​span>
      <span class="stat-hint">Aguardando 1a rodada</​span>
    </​div>
  </​div>
</​section>

<style>
.team-status-card {
  background: var(--bg-card);
  border-radius: var(--border-radius);
  padding: 20px;
  margin: 16px;
  border: 1px solid var(--border-color);
  box-shadow: 0 4px 16px rgba(0,0,0,0.3);
}

.team-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 16px;
}

.team-name {
  font-size: 20px;
  color: var(--text-primary);
}

.team-shield {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--bg-secondary);
  display: flex;
  align-items: center;
  justify-content: center;
}

.team-shield .material-icons {
  font-size: 16px;
  color: var(--text-muted);
}

.stats-split {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  gap: 16px;
}

.stat-block {
  display: flex;
  flex-direction: column;
}

.stat-label {
  font-size: 12px;
  color: var(--text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin-bottom: 4px;
}

.stat-value {
  font-size: 48px;
  font-weight: 700;
  color: var(--text-primary);
  line-height: 1;
}

.stat-value.text-accent {
  color: var(--laranja);
}

.stat-hint {
  font-size: 10px;
  color: var(--text-muted);
  margin-top: 4px;
}

.stat-divider {
  width: 1px;
  background: var(--border-color);
}
</​style>

4.4 FAB do Mercado (Floating Action Button)

<!-- FAB com Timer do Mercado -->
<div class="fab-mercado">
  <button class="fab-btn">
    <div class="fab-icon">
      <i class="material-icons">storefront</​i>
    </​div>
    <div class="fab-content">
      <span class="fab-timer">Fecha em 7d 5h</​span>
      <span class="fab-status font-brand">Aberto R1</​span>
    </​div>
  </​button>
</​div>

<style>
.fab-mercado {
  position: fixed;
  bottom: 96px; /​* Acima do bottom nav */
  right: 16px;
  z-index: 40;
}

.fab-btn {
  background: linear-gradient(135deg, var(--verde-lucro), #27ae60);
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 24px;
  padding: 10px 24px 10px 16px;
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 180px;
  cursor: pointer;
  box-shadow: 0 8px 24px rgba(16, 185, 129, 0.4);
  transition: transform 0.2s;
}

.fab-btn:hover {
  transform: scale(1.05);
}

.fab-icon {
  width: 32px;
  height: 32px;
  background: rgba(255,255,255,0.2);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.fab-icon .material-icons {
  font-size: 16px;
  color: #fff;
}

.fab-content {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.fab-timer {
  font-size: 10px;
  color: rgba(255,255,255,0.8);
  text-transform: uppercase;
  letter-spacing: 0.3px;
}

.fab-status {
  font-size: 14px;
  color: #fff;
  text-transform: uppercase;
}
</​style>

4.5 Card de Jogo (Match Card)

<!-- Card de partida com escudos -->
<div class="match-card">
  <div class="match-league">Brasileirao Serie A</​div>
  <div class="match-content">
    <!-- Time 1 -->
    <div class="match-team">
      <div class="team-badge" style="background-color: #c8102e;">
        <span>FLA</​span>
      </​div>
      <span class="team-name-short">Flamengo</​span>
    </​div>
    <!-- VS e Horario -->
    <div class="match-center">
      <span class="match-vs">VS</​span>
      <span class="match-time font-mono">16:00</​span>
    </​div>
    <!-- Time 2 -->
    <div class="match-team">
      <div class="team-badge" style="background-color: #006437;">
        <span>PAL</​span>
      </​div>
      <span class="team-name-short">Palmeiras</​span>
    </​div>
  </​div>
</​div>

<style>
.match-card {
  background: var(--bg-secondary);
  border-radius: 12px;
  padding: 12px;
  border: 1px solid var(--border-color);
}

.match-league {
  font-size: 11px;
  color: var(--text-muted);
  padding-bottom: 8px;
  margin-bottom: 12px;
  border-bottom: 1px solid var(--border-color);
}

.match-content {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.match-team {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 33%;
}

.team-badge {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 4px;
}

.team-badge span {
  font-size: 10px;
  font-weight: 700;
  color: #fff;
}

.team-name-short {
  font-size: 10px;
  color: var(--text-primary);
  text-align: center;
}

.match-center {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 33%;
}

.match-vs {
  font-size: 12px;
  color: var(--laranja);
  font-weight: 700;
  margin-bottom: 2px;
}

.match-time {
  font-size: 18px;
  font-weight: 700;
  color: var(--text-primary);
}
</​style>

4.6 Bottom Navigation (4 itens)

<!-- Bottom Nav fixo -->
<nav class="bottom-nav">
  <a href="#home" class="nav-item active">
    <i class="material-icons">home</​i>
    <span>Inicio</​span>
  </​a>
  <a href="#ranking" class="nav-item">
    <i class="material-icons">leaderboard</​i>
    <span>Ranking</​span>
  </​a>
  <a href="#menu" class="nav-item">
    <i class="material-icons">apps</​i>
    <span>Menu</​span>
  </​a>
  <a href="#financeiro" class="nav-item">
    <i class="material-icons">account_balance_wallet</​i>
    <span>Financeiro</​span>
  </​a>
</​nav>

<style>
.bottom-nav {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  height: 64px;
  background: #000;
  border-top: 1px solid var(--border-color);
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  z-index: 50;
  padding-bottom: env(safe-area-inset-bottom);
}

.bottom-nav .nav-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  color: var(--text-muted);
  text-decoration: none;
  transition: color 0.2s;
}

.bottom-nav .nav-item.active,
.bottom-nav .nav-item:hover {
  color: var(--laranja);
}

.bottom-nav .nav-item .material-icons {
  font-size: 22px;
}

.bottom-nav .nav-item span {
  font-size: 10px;
  font-weight: 500;
}
</​style>

4.7 Mapeamento Font Awesome → Material Icons

Font AwesomeMaterial IconsUso
fa-trophyemoji_eventsPremiacoes
fa-usersgroupsParticipantes
fa-clipboard-listdescriptionRegras
fa-crownworkspace_premiumPremium
fa-homehomeInicio
fa-chart-lineleaderboardRanking
fa-thappsMenu
fa-walletaccount_balance_walletFinanceiro
fa-shopstorefrontMercado
fa-coinspaymentsSaldo
fa-bellnotificationsAlertas
fa-shield-altshieldEscudo

5. 🎭 Admin UI (Desktop)

4.1 Layout Padrão

<div class="admin-layout">
  <aside class="sidebar">
    <div class="logo">
      <img src="/​img/​logo.png" alt="SC">
      <h3>Super Cartola</​h3>
    </​div>
    <nav>
      <a href="#dashboard" class="nav-item active">
        <i class="material-icons">dashboard</​i>
        Dashboard
      </​a>
      <a href="#ligas" class="nav-item">
        <i class="material-icons">emoji_events</​i>
        Ligas
      </​a>
      <!-- ... -->
    </​nav>
  </​aside>
  
  <main class="main-content">
    <header class="topbar">
      <h1>Dashboard</​h1>
      <div class="user-menu">...</​div>
    </​header>
    
    <div class="content">
      <!-- Módulo renderizado aqui -->
    </​div>
  </​main>
</​div>

<style>
.admin-layout {
  display: grid;
  grid-template-columns: 280px 1fr;
  height: 100vh;
}

.sidebar {
  background: var(--bg-card);
  border-right: 1px solid var(--border-color);
  padding: 24px;
  overflow-y: auto;
}

.main-content {
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.topbar {
  height: 64px;
  background: var(--bg-secondary);
  border-bottom: 1px solid var(--border-color);
  padding: 0 32px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.content {
  flex: 1;
  padding: 32px;
  overflow-y: auto;
}
</​style>

4.2 Módulos Admin

// 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 => `
      <tr>
        <td>${p.nome}</​td>
        <td class="${p.saldo >= 0 ? 'positivo' : 'negativo'}">
          R$ ${p.saldo.toFixed(2).replace('.', ',')}
        </​td>
        <td>
          <button onclick="adminTesouraria.verExtrato('${p.id}')">
            Ver Extrato
          </​button>
        </​td>
      </​tr>
    `).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

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

/​* 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

// 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

<!-- Adicionar no footer -->
<div id="debug-viewport" style="position: fixed; bottom: 0; right: 0; padding: 8px; background: rgba(0,0,0,0.8); color: lime; font-size: 12px; z-index: 10000;">
  <script>
    function updateViewport() {
      const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
      const vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);
      document.getElementById('debug-viewport').textContent = `${vw}x${vh}`;
    }
    window.addEventListener('resize', updateViewport);
    updateViewport();
  </​script>
</​div>

8. 📋 Checklists

7.1 Novo Módulo Mobile

□ 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

□ 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

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View