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
Gerenciar fichas de personagem do sistema Κρόnos em dispositivos móveis e web
Translation pending
AI summary · README
GamesTypeScript · PLpgSQLGitHub @sachow12OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
App mobile/web para os jogadores gerenciarem as fichas de personagem do sistema Κρόνος. Cada usuário faz login e gerencia as suas próprias fichas (pode ter quantas quiser) — os dados ficam no Supabase e o isolamento entre usuários é garantido no banco via Row Level Security (RLS). Feito com Expo + React Native (TypeScript) — roda em Android, iOS e Web com o mesmo código.Read the full README ↗
App mobile/web para os jogadores gerenciarem as fichas de personagem do sistema Κρόνος. Cada usuário faz login e gerencia as suas próprias fichas (pode ter quantas quiser) — os dados ficam no Supabase e o isolamento entre usuários é garantido no banco via Row Level Security (RLS).
Feito com Expo + React Native (TypeScript) — roda em Android, iOS e Web com o mesmo código.
Crie um projeto novo em https://supabase.com.
No painel, vá em SQL Editor → New query, cole o conteúdo de
supabase/schema.sql e clique em Run. Isso cria a tabela
fichas e as políticas de RLS.
⚠️ Já criou a tabela antes? Se você rodou o schema anterior (que permitia só 1 ficha por usuário), rode também
supabase/migrations/002_multiplas_fichas.sqlpara liberar múltiplas fichas.
🔑 Obrigatório para o login por usuário: rode
supabase/migrations/003_username.sql. Ele cria a tabelaprofiles, o gatilho que grava o username no cadastro e as funções usadas no login. Sem ele, o cadastro falha com um aviso pedindo a migration.
Vá em Project Settings → API e copie:
Obrigatório para o "Esqueci minha senha" — o Supabase só permite editar os templates de e-mail se houver SMTP próprio configurado, e o fluxo depende de um template customizado. Em Project Settings → Authentication → SMTP Settings, ative Enable Custom SMTP (o projeto usa Brevo, plano gratuito):
| Campo | Valor |
|---|---|
| Host | smtp-relay.brevo.com |
| Port | 587 |
| Username | o login SMTP do Brevo ([email protected]) |
| Password | a chave SMTP gerada no Brevo |
| Sender email | um remetente verificado no Brevo |
⚠️ No Brevo, desative o bloqueio de IP em Settings → Security → Authorized IPs → Deactivate blocking. O Supabase envia de IPs que mudam; com o bloqueio ativo os e-mails são recusados em silêncio (o app diz "enviado" e nada chega). O Brevo ativa esse bloqueio sozinho após 30 dias se não for desativado.
Depois, em Authentication → Emails → Reset password, troque o Subject para
Seu código de redefinição - Ficha Kronos e o Body (aba Source) por:
<h2>Redefinir sua senha</h2>
<p>Use este código para redefinir a senha da sua Ficha Κρόνος:</p>
<p style="font-size:28px;font-weight:bold;letter-spacing:4px">{{ .Token }}</p>
<p>O código expira em 1 hora. Se você não pediu isso, ignore este e-mail.</p>
O
{{ .Token }}é o que gera o código enviado por e-mail. O template padrão usa{{ .ConfirmationURL }}, que manda link — e o app espera código.
(Opcional, recomendado para testar rápido) Em Authentication → Providers → Email, você pode desligar "Confirm email" durante o desenvolvimento, assim o cadastro já entra direto sem precisar confirmar por e-mail.
Crie um arquivo .env na raiz do projeto com as credenciais do seu Supabase
(pegue em Project Settings → API).
O arquivo precisa das duas variáveis que o app lê em
lib/supabase.ts: a URL do projeto e a anon public key,
ambas com o prefixo EXPO_PUBLIC_ (exigido pelo Expo para expor a variável ao app).
Nunca use a
service_role keyaqui: ela ignora o RLS e daria acesso total ao banco a qualquer pessoa com o app instalado. Use somente aanon public.
O
.envestá no.gitignoree não vai para o repositório.
npm install
npm run webnpm run androidnpm run iosnpm startQuatro caminhos, do mais útil ao mais situacional:
Compila na sua máquina, sem nuvem e sem fila. O .apk gerado é autônomo: o JavaScript
fica embutido dentro dele, então o app funciona com o PC desligado, em qualquer rede.
JDK — já resolvido: o projeto usa Gradle 9.3.1, compatível com o JDK 21.
Confira com java -version.
Android SDK — instale o Android Studio. A instalação padrão não traz tudo: abra SDK Manager, marque Show Package Details e instale exatamente estes itens:
| Aba | Item |
|---|---|
| SDK Platforms | Android SDK Platform 36 (o projeto usa compileSdk 36) |
| SDK Tools | NDK (Side by side) 27.1.12297006 |
| SDK Tools | Android SDK Build-Tools 36.0.0 |
O NDK é o mais fácil de esquecer e pesa ~1 GB. Sem ele a build falha no meio.
Caminho do SDK — já resolvido: o arquivo android/local.properties aponta para
o SDK e o Gradle o lê direto, sem depender de variável de ambiente.
Se mudar de máquina/usuário, ajuste o
sdk.dirnesse arquivo. Use barras normais (C:/Users/...): em arquivos.propertiesa contrabarra é caractere de escape e quebra o caminho.Alternativa: definir
ANDROID_HOMEcomsetx ANDROID_HOME "$env:LOCALAPPDATA\Android\Sdk"— lembrando que osetxsó vale em terminais abertos depois do comando.
cd android; .\gradlew assembleRelease
A primeira build baixa dependências e demora bastante (10–25 min). As seguintes levam cerca de 1 minuto. O arquivo sai em:
android/app/build/outputs/apk/release/app-release.apk
Tamanho esperado: ~67 MB. Ele contém o bundle JavaScript em
assets/index.android.bundle — é isso que o torna autônomo.
Para conferir que um APK gerado está íntegro:
cd android; .\gradlew assembleRelease; dir app\build\outputs\apk\release
Copie para o celular (cabo, Google Drive, Telegram…) e instale, autorizando "instalar de fontes desconhecidas".
Atalho com o celular no cabo USB:
npx expo run:android --variant releasecompila e já instala no aparelho conectado.
O template do Expo assina o build de release com a keystore de debug. O APK instala e funciona normalmente para uso próprio e para passar ao cliente, mas:
Para gerar uma keystore própria (você define as senhas):
keytool -genkeypair -v -keystore kronos.keystore -alias kronos -keyalg RSA -keysize 2048 -validity 10000
Depois aponte-a em android/app/build.gradle, no bloco signingConfigs.
Guarde o arquivo e as senhas — sem eles não é possível atualizar o app depois.
Não usamos, e o eas.json foi removido do projeto justamente para não deixar
credenciais no repositório. Se um dia quiser buildar na nuvem, recrie a configuração com
npx eas-cli@latest build:configure e guarde as variáveis com eas env:create
(no servidor do Expo) — não as escreva dentro do eas.json.
Não existe equivalente ao APK no iOS. A Apple não permite instalar um app fora da loja sem assinatura de desenvolvedor. As opções reais são:
c abaixo) — funciona hoje, de graça, e no iPhone dá para
"Adicionar à Tela de Início" pelo Safari, ficando com ícone como um app.Para a maioria dos casos, a opção 2 resolve sem custo.
Para gerar localmente:
npx expo export --platform web
Gera a pasta dist/, publicável em qualquer hospedagem estática.
O vercel.json já traz o build command (npx expo export --platform web),
o diretório de saída (dist) e o rewrite que faz todas as rotas caírem no index.html.
Ao importar o repositório na Vercel, é obrigatório cadastrar as variáveis de ambiente
em Settings → Environment Variables (o .env não vai para o Git):
| Variável | Valor |
|---|---|
EXPO_PUBLIC_SUPABASE_URL | a URL do seu projeto Supabase |
EXPO_PUBLIC_SUPABASE_ANON_KEY | a anon public key |
Sem elas o site sobe, mas abre com o aviso "Supabase não configurado" e ninguém consegue entrar. Depois de cadastrá-las, refaça o deploy — as variáveis são embutidas durante a build, não lidas em tempo de execução.
Marque as duas para Production, Preview e Development.
No iPhone, o Safari permite "Adicionar à Tela de Início", deixando o site com ícone próprio e em tela cheia.
O Expo Go da loja acompanha uma versão fixa de SDK. Este projeto usa SDK 57; se o Expo Go instalado for de outro SDK, ele recusa o projeto com "incompatível". Não é erro do código. Para desenvolver no celular com hot reload, o substituto é um development build — com o aparelho no cabo USB:
npx expo run:android
Isso instala uma versão de debug que conecta ao Metro (npm start), igual ao Expo Go.
As variáveis EXPO_PUBLIC_* são lidas do .env e embutidas no bundle durante a build
local. Não há nada a configurar além do .env.
Elas ficam visíveis dentro do APK — isso é esperado e não é falha. Todo app cliente carrega suas credenciais consigo. A
anon keydo Supabase é pública por design: sozinha, ela não lê nem grava nada (veja Modelo de segurança abaixo).
./_/-; senha: mín. 6 caracteres).
Depois disso você pode entrar com usuário ou e-mail — o campo de login
aceita os dois (se tiver @, é tratado como e-mail).O código vale 1 hora. Se não chegar, confira o spam. O reenvio libera após 60s. Trocar a senha desconecta seus outros aparelhos.
Exporta o que está na tela, inclusive alterações ainda não salvas.
App.tsx Ponto de entrada; login -> lista -> editor
contexts/AuthContext.tsx Estado de autenticação (Supabase Auth)
components/
AuthScreen.tsx Tela de login / cadastro
FichaListScreen.tsx Lista das fichas do usuário (abrir, criar, apagar)
FichaScreen.tsx Editor completo de uma ficha
DialogProvider.tsx Diálogos de confirmação/aviso (web + mobile)
ui.tsx Componentes reutilizáveis (Field, Button, etc.)
lib/
supabase.ts Cliente Supabase
fichaModel.ts Tipos e modelo padrão da ficha (baseado no PDF)
fichaService.ts Listar / carregar / criar / salvar / apagar fichas
theme.ts Cores e espaçamentos
supabase/
schema.sql Tabela + políticas de RLS (instalação nova)
migrations/ Scripts para atualizar um banco já existente
config.toml Configuração do Supabase CLI
supabase/functions/login/ Edge Function do login por username
android/ Projeto nativo gerado por `expo prebuild` (fora do git)
FichasModelo/ PDF original da ficha (referência)
A pasta
android/é gerada e está no.gitignore. Se ela sumir ou quebrar, recrie comnpx expo prebuild --platform android. Só edite arquivos dentro dela se souber que a mudança precisa sobreviver — umprebuild --cleana sobrescreve.
⚠️ Não use
Alert.alertdo React Native neste projeto. Ele é um no-op noreact-native-web, então qualquer confirmação simplesmente não aparece no navegador (e a ação dependente nunca acontece). UseuseDialog()docomponents/DialogProvider.tsx, que funciona nas duas plataformas.
O PDF é desenhado do zero com pdf-lib, reproduzindo a folha
impressa: A4 retrato, duas colunas, Courier, círculo HP/DEF/SAN, tabela de perícias com
as caixas de valor e as grades de área das habilidades especiais.
Diferente do papel, os blocos crescem conforme o conteúdo — uma passiva longa não é cortada, e o documento passa para a página seguinte se precisar.
Decisões que valem conhecer antes de mexer:
FichasModelo/FICHA.pdf, embutido como
página em lib/logoKronos.ts (base64, ~64 KB). Isso preserva a fonte
Bogart, a sombra e o ornamento, e evita carregar uma fonte grega inteira (~700 KB).
Para regerar após mudar o modelo, recorte a área {left:412, bottom:746, right:592, top:824}
da página com embedPage e grave o base64 nesse arquivo.limpar() troca caracteres fora da tabela WinAnsi por ?.
Sem ela, um emoji digitado em um campo derrubaria a geração inteira.lib/exportarPdf.ts: na web monta um
Blob e dispara o download; no nativo grava em cache e chama o expo-sharing. As libs
nativas entram por import() dinâmico para não pesar o bundle web.Toda a ficha fica em uma coluna data (JSONB) na tabela fichas. Para adicionar campos
novos no futuro, basta estender o tipo em lib/fichaModel.ts e o
defaultFicha — não é preciso migrar o banco. A função normalizeFicha garante que fichas
antigas ganhem os campos novos automaticamente.
Estrutura atual da tabela fichas:
| Coluna | Tipo | Observação |
|---|---|---|
id | uuid | chave primária |
user_id | uuid | FK para auth.users; não é único (várias fichas por usuário) |
data | jsonb | a ficha inteira |
created_at | timestamptz | adicionada pela migration 002 |
updated_at | timestamptz | atualizada a cada save |
Tabela profiles (adicionada pela migration 003, para o login por usuário):
| Coluna | Tipo | Observação |
|---|---|---|
id | uuid | PK e FK para auth.users |
username | citext | único, case-insensitive; 3–20 caracteres |
created_at | timestamptz |
Scripts em supabase/:
schema.sql — instalação nova (cria fichas + RLS já no formato final).migrations/002_multiplas_fichas.sql — só para quem rodou o schema antigo
(user_id UNIQUE): remove a restrição e adiciona created_at. Já aplicado.migrations/003_username.sql — login por usuário: tabela profiles, gatilho
on_auth_user_created e as funções email_do_username / username_disponivel.
Já aplicado.migrations/004_remove_email_lookup.sql — remove a email_do_username, que expunha
o e-mail de qualquer jogador a partir do username. A tradução passou para a
Edge Function login. ⚠️ Rode depois de publicar a função.Mudanças nos campos da ficha nunca exigem SQL (é tudo JSONB). Só algo estrutural pede migration nova — por exemplo compartilhar fichas com o mestre.
A anon key dentro do APK não é uma vulnerabilidade. Ela apenas identifica o projeto; quem decide o que cada requisição pode fazer é o RLS. Testado com a chave real, sem sessão autenticada:
| Tentativa | Resultado |
|---|---|
GET /rest/v1/fichas (ler tudo) | [] — vazio |
GET /rest/v1/profiles | [] — vazio |
POST /rest/v1/fichas (gravar) | ❌ 42501: violates row-level security policy |
O que nunca pode entrar no app é a service_role key: ela ignora o RLS. Use-a apenas
em Edge Functions, onde o Supabase a injeta automaticamente.
loginO login por username precisa traduzir username → e-mail antes de autenticar, quando ainda não há sessão. Fazer isso com uma função SQL pública vazava o e-mail de qualquer jogador para quem soubesse o username.
Por isso a tradução mora em supabase/functions/login: ela roda
no servidor, usa a service_role (que nunca sai de lá) e devolve apenas os tokens da
sessão. Também responde CREDENCIAIS_INVALIDAS de forma genérica, sem revelar se o
usuário existe.
Login por e-mail não passa por ela — vai direto ao Supabase Auth.
Para publicar (ou republicar após editar a função):
npx supabase@latest functions deploy login --project-ref SEU-PROJECT-REF
Não precisa de Docker. O aviso "Docker is not running" é inofensivo.