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.