Underground 2 Revived
Play Need for Speed: Underground 2 (2004) in Chrome and Firefox with WebGPU graphics, Web Audio, up to 120 fps widescreen, controller support, free roam and Online races with friends. Fan project, not
翻譯準備中
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
Estatísticas para clubes do EA SPORTS FC 27 Pro Clubs — um mini Sofascore/Tracker: pesquise um clube e veja resumo, jogadores, rankings internos, histórico de partidas e (em breve) formações. O diferencial é o histórico próprio: a EA só expõe as 10 partidas mais recentes de cada tipo. Cada sincronização salva as partidas novas no nosso banco, e nada é apagado — com o tempo o clube acumula 100, 500, 1000 partidas. Next.js 16 (App Router, Server Components, Server Actions) · TypeScript strict · Tailwind CSS 4 · shadcn/ui · Supabase (PostgreSQL) · Zod · ESLint · deploy na Vercel.檢視完整 README ↗
Estatísticas para clubes do EA SPORTS FC 27 Pro Clubs — um mini Sofascore/Tracker: pesquise um clube e veja resumo, jogadores, rankings internos, histórico de partidas e (em breve) formações.
O diferencial é o histórico próprio: a EA só expõe as 10 partidas mais recentes de cada tipo. Cada sincronização salva as partidas novas no nosso banco, e nada é apagado — com o tempo o clube acumula 100, 500, 1000 partidas.
Next.js 16 (App Router, Server Components, Server Actions) · TypeScript strict · Tailwind CSS 4 · shadcn/ui · Supabase (PostgreSQL) · Zod · ESLint · deploy na Vercel.
Requisitos: Node.js ≥ 20.9 e um projeto no Supabase (o plano grátis serve).
npm install
cp .env.example .env.local # preencha as variáveis (abaixo)
npm run dev # http://localhost:3000
No painel do Supabase → SQL Editor, cole e rode supabase/schema.sql.
O script é idempotente (pode rodar de novo sem erro).
.env.local)| Variável | Onde achar | Uso |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL | Project Settings → API | URL do projeto |
NEXT_PUBLIC_SUPABASE_ANON_KEY | Project Settings → API (anon / publishable) | Leituras no servidor (RLS somente-select) |
SUPABASE_SERVICE_ROLE_KEY | Project Settings → API (service_role / secret) | Escrita da sincronização — somente servidor |
EA_API_BASE_URL | opcional | Sobrescreve https://proclubs.ea.com/api/fc |
GITHUB_DISPATCH_TOKEN | opcional (produção) | Faz o "Atualizar" pedir a sincronização ao GitHub Actions |
NEXT_PUBLIC_SITE_URL | opcional | URL pública para prévias de link (na Vercel é detectada) |
A service role nunca chega ao browser: os módulos que a usam importam server-only, e o
build falha se algum Client Component tentar importá-los.
npm run dev # desenvolvimento
npm run lint # ESLint
npm run typecheck # gera tipos de rota do Next + tsc --noEmit
npm run build # build de produção
npm run sync # sincroniza clubes com dados velhos (CLUB_ID=… para um clube)
Importe o repositório na Vercel e configure as variáveis do Supabase (Production e Preview).
Sincronização pelo GitHub Actions. A EA (Akamai) bloqueia os IPs da Vercel/AWS — testado:
HTTP 403 a partir da Vercel, HTTP 200 a partir do GitHub Actions com Node 22+ (o Node 20 é
bloqueado pela "impressão digital" da conexão). Por isso quem conversa com a EA em produção é
o workflow .github/workflows/sync-clubs.yml, que roda a
cada 15 min (grátis em repositório público) e grava direto no Supabase. Configure em
Settings → Secrets and variables → Actions:
| Tipo | Nome | Valor |
|---|---|---|
| Variable | NEXT_PUBLIC_SUPABASE_URL | URL do projeto |
| Variable | NEXT_PUBLIC_SUPABASE_ANON_KEY | chave publicável |
| Secret | SUPABASE_SERVICE_ROLE_KEY | chave secreta |
Para adicionar um clube novo manualmente: Actions → Sincronizar clubes → Run workflow com o ID do clube na EA.
Opcional — "Atualizar" na hora. Crie um token fine-grained no GitHub com acesso só a
este repositório e permissão Actions: Read and write, e coloque em GITHUB_DISPATCH_TOKEN
na Vercel. Aí o botão "Atualizar" e a abertura de um clube com dados velhos pedem a
sincronização ao Actions (os dados chegam em 1–2 min). Sem o token, vale o agendamento.
Localmente (rede residencial) a EA responde normalmente: o site sincroniza direto, sem Actions.
Visão geral interativa em /arquitetura (textos em src/components/architecture/content.ts; atualize junto quando a arquitetura mudar).
src/
├── app/ # rotas (Server Components por padrão)
│ ├── page.tsx # / busca + clubes recentes
│ ├── search/ # /search resultados da EA + clubes já salvos
│ ├── clubs/[clubId]/ # dashboard, players, matches, sessions, records, team-of-the-week, awards, formations
│ ├── actions/sync-club.ts # Server Actions: abrir clube / atualizar
├── components/
│ ├── clubs/ players/ matches/ formations/ layout/
│ └── ui/ # shadcn/ui
├── lib/
│ ├── ea/ # ÚNICA camada que fala com a EA
│ │ ├── client.ts # EAClient: base URL, headers, timeout, retries, parse + Zod
│ │ ├── types.ts # schemas Zod das respostas da EA (formato externo)
│ │ ├── mappers.ts # EA → modelos internos (ClubSnapshot, PlayerSnapshot, MatchSnapshot)
│ │ ├── clubs.ts players.ts matches.ts
│ │ └── constants.ts positions.ts errors.ts
│ ├── db/ # Supabase: clientes + repositórios (linhas ↔ modelos internos)
│ ├── stats/ # métricas derivadas (aproveitamento, G+A, rankings, ordenação)
│ ├── env.ts errors.ts format.ts
├── services/sync/ # orquestração da sincronização (idempotente)
scripts/sync-clubs.ts # sincronização fora da Vercel (GitHub Actions)
└── types/ # modelos de domínio + tipos do banco
Separação de camadas: EA (externo) → mappers → modelos internos → repositórios (banco) → UI. Componentes nunca veem o formato da EA nem linhas do banco.
pesquisa → resultado da EA → "Ver estatísticas" (Server Action)
→ clubs/info + overallStats → upsert clubs (falha aqui = erro para o usuário)
→ members/stats → upsert players (ex-membros: is_member = false)
→ clubs/matches (liga/playoff/amistoso, em paralelo)
→ upsert matches por (club_id, fingerprint) (antigas nunca são apagadas)
→ upsert match_team_stats por (match_id, side)
→ vincula jogadores (ID EA ↔ gamertag) e upsert player_match_stats por (match_id, player_id)
→ redirect para o dashboard
requestClubSync).ea:<matchId>; sem ID confiável, fp:sha256(clube|adversário|data|placar).clubs, players, matches, match_team_stats, player_match_stats, player_match_events
(contadores crus da EA), club_progress, player_progress, formations, formation_players —
ver supabase/schema.sql.
Decisões em relação ao modelo inicial:
clubs: unicidade é (ea_club_id, platform) — o ID da EA é por plataforma.matches: a EA não diz quem foi mandante → em vez de home_club_name/away_club_name,
guardamos opponent_* do ponto de vista do clube, mais match_type.match_team_stats (nova): estatísticas agregadas de cada equipe, usadas na comparação.players: a EA lista membros só pelo gamertag; unicidade (club_id, name) e
ea_player_id preenchido a partir das partidas.| Situação | Comportamento |
|---|---|
| Clube não encontrado | Busca vazia com dica; clubs/info 500 INVALID_CLUB_ID → mensagem própria |
| EA indisponível / timeout / 429 | Retry com backoff (2×) e mensagem amigável |
| Resposta inválida | Validação Zod → erro invalid_response; partidas malformadas são ignoradas individualmente |
| Banco indisponível | DatabaseError → mensagem amigável; páginas mostram error boundary com "Tentar novamente" |
| Dados parciais | Sincronização continua e mostra avisos |
Stack traces ficam só no log do servidor.
Detalhes, respostas e pendências em docs/ea-endpoints.md.
Funcionando (validado contra a API real): busca por nome e por ID, dados do clube, escudo, skill rating, recorde, membros com estatísticas, partidas de liga e amistosos com estatísticas por equipe e por jogador.
Bloqueado: chamadas a partir da Vercel (HTTP 403 do Akamai) — resolvido sincronizando pelo GitHub Actions (ver Deploy).
Pendente de validação: partidas de playoff (sem amostra), cartões amarelos e interceptações
(sem campo), mandante/visitante (sem campo), códigos de posição além de CB/CM/ST, nome da
região, semântica de winnerByDnf.
/clubs/[clubId]/players/[a]/vs/[b]): gols, assistências, pré-assistências,
passes certos e errados, conversão e desarmes lado a lado, em três abas: jogando juntos
(só os jogos com os dois em campo), todos os jogos salvos (totais) e por jogo (médias), com
placar de estatísticas vencidas, campanha dos dois
juntos e com só um deles, e card de prévia para o WhatsApp./clubs/[clubId]/players/x1): todos contra todos no elenco com o mesmo duelo
(mínimo de 5 jogos juntos), em tabela de campeonato (3 pontos por vitória, 1 por empate,
saldo de estatísticas vencidas) e grade com o placar de cada confronto.docs/ea-endpoints.md). Aparecem na partida, no
resumo da noite (Armador e Ladrão de bola), nos prêmios do mês, nos recordes e no perfil./clubs/[clubId]/formations — escolha um dos 27 esquemas prontos (linha de 4, de 3 e de 5,
com variações como "4-3-3 (ofensivo)" e "4-1-2-1-2 (aberto)") ou monte o seu: a prancheta deixa
arrastar cada carta (ou mover com as setas do teclado) — a sigla acompanha a região do campo
— e subir ou descer a linha inteira (defesa, meio, ataque) pelas alças ao lado. Mexeu, vira
"personalizado" (ex.: "3-5-2 personalizado") e dá para voltar ao esquema de partida. Clique em
cada posição e defina jogador (ou IA), a sigla, o arquétipo do FC 27, até 6 pontos fortes
(atributos como Curva e Passe curto) e uma observação. Ao
escolher o jogador, o montador mostra a nota de cada um naquele setor e sugere quem rende mais.
Também dá para definir capitão e cobradores de pênalti, falta e escanteio, e guardar o código
da tática no FC (gerado pelo próprio jogo ao compartilhar a tática; o site só guarda e mostra
com botão de copiar).
Abrir uma formação mostra a escalação de TV: cartas estilo FUT (OVR, vaga, arquétipo, nota
no setor e fase), linhas de química ligando quem dá assistência para quem entre os
escalados, ficha do time, bola parada e o plano de jogo de cada vaga. A edição fica em
/formations/[id]/edit.
src/lib/formations/ —
se a EA mudar algo num patch, só esses arquivos mudam.20261005_formation_slots.sql,
20261006_formation_roles.sql,
20261006_formation_free_layout.sql e
20261006_formation_game_code.sql.Sem login nesta versão: qualquer pessoa com o link pode editar as formações.
ea_player_id com nome diferente).opengraph-image.tsx, fontes Barlow em assets/fonts, licença SIL
OFL).club_progress.club_progress para mostrar as subidas e quedas.player_progress a cada
sincronização (por nº de jogos), com gráfico no perfil e "Quem mais subiu de OVR" na lista de
jogadores. Bancos antigos: rode
20261015_division_and_player_progress.sql.