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。 以下為已儲存的摘錄。
Achetez et vendez des cryptomonnaies au prix réel du marché avec 10 000 $ fictifs, suivez vos plus-values et comparez-vous aux autres joueurs. Aucun argent réel. Je voulais m'entraîner sur Next.js et m'amuser sur un sujet qui m'intéresse : la finance. Un simulateur de trading réunit les deux, avec des données en temps réel et des calculs où l'exactitude compte, même quand l'argent est fictif.檢視完整 README ↗
Achetez et vendez des cryptomonnaies au prix réel du marché avec 10 000 $ fictifs, suivez vos plus-values et comparez-vous aux autres joueurs. Aucun argent réel.
Démo : https://papertrade-five-peach.vercel.app
Je voulais m'entraîner sur Next.js et m'amuser sur un sujet qui m'intéresse : la finance. Un simulateur de trading réunit les deux, avec des données en temps réel et des calculs où l'exactitude compte, même quand l'argent est fictif.
BTC puis Entrée, ⌘K pour y accéder) et
touches de fonction F1 à F3 pour changer d'écran| Domaine | Outils |
|---|---|
| Framework | Next.js 16 (App Router, Server Components, Server Actions), React 19, TypeScript strict |
| Interface | Tailwind CSS 4, Lightweight Charts, IBM Plex Mono |
| Données | PostgreSQL, Drizzle ORM, Zod |
| Auth | Better Auth (compte invité, OAuth GitHub) |
| Qualité | Vitest (tests unitaires et d'intégration sur Postgres), ESLint, GitHub Actions |
| Hébergement | Vercel + Neon (offres gratuites) |
Une interface de terminal de marché plutôt que d'application grand public : une seule police à
chasse fixe (IBM Plex Mono), des noirs neutres, l'ambre pour tout ce qui est interactif, le vert et
le rouge réservés aux variations. Pas d'arrondis ni d'ombres : des panneaux séparés par des filets.
Les choix et leurs raisons sont dans
design-system/papertrade/MASTER.md.
aria-sort, confirmations dans un <dialog> natif. Aucun raccourci à
touche unique (WCAG 2.1.4).Le formulaire n'envoie que l'actif, le sens et la quantité. La Server Action
(src/app/actions.ts) identifie l'utilisateur par sa session, valide
l'entrée avec Zod puis relit le prix chez Binance, sans cache, avant d'exécuter l'ordre. Modifier
la requête à la main ne permet donc pas d'acheter moins cher.
Chaque ordre s'exécute dans une transaction qui verrouille le portefeuille et la position
(SELECT … FOR UPDATE, voir src/lib/data/portfolio.ts). Le test
d'intégration envoie 10 achats simultanés de 2 000 $ avec 10 000 $ : exactement 5 passent.
Sans le verrou, les 10 passent (vérifié en le retirant). Des contraintes CHECK en base
(liquidités ≥ 0, quantité > 0) servent de dernier filet de sécurité.
En JavaScript, 0.1 + 0.2 !== 0.3. Les montants sont donc stockés en numeric(28, 8) et calculés
avec decimal.js (src/lib/trading.ts). Les arrondis se font au détriment du
joueur (coût d'achat arrondi au-dessus, produit de vente en dessous), comme sur une vraie
plateforme. Ces règles sont couvertes par des tests unitaires.
Toute l'application partage une connexion WebSocket
(src/lib/price-store.ts). Elle s'ouvre quand un composant en a besoin,
se ferme quand plus personne ne l'écoute et se reconnecte avec une attente exponentielle. Les
mises à jour sont regroupées toutes les 500 ms et lues via useSyncExternalStore : un composant
ne se re-rend que si le prix de son actif a changé.
Les réponses de l'API Binance sont validées avec Zod avant usage, et mises en cache côté serveur (10 s pour les prix, 30 s pour l'historique). Les prix servis par le serveur s'affichent dès le premier rendu, puis le flux temps réel prend le relais.
Un invité qui se déconnecte est supprimé. Une tâche planifiée Vercel
(src/app/api/cron/cleanup/route.ts) efface chaque nuit les
invités dont toutes les sessions ont expiré.
Prérequis : Node.js 24 ou plus, et Docker.
npm install
cp .env.example .env.local # puis renseigner BETTER_AUTH_SECRET (openssl rand -base64 32)
docker compose up -d # Postgres sur le port 5433
npm run db:migrate
npm run dev # http://localhost:3000
| Commande | Rôle |
|---|---|
npm run dev | Serveur de développement |
npm test | Tests unitaires et d'intégration (base Docker requise) |
npm run lint | ESLint |
npm run typecheck | Vérification TypeScript |
npm run db:generate | Génère une migration après modification du schéma |
npm run db:migrate | Applique les migrations |
npm run db:studio | Explore la base dans Drizzle Studio |
DATABASE_URL est ajoutée automatiquement.BETTER_AUTH_SECRET : générée avec openssl rand -base64 32CRON_SECRET : générée avec openssl rand -hex 16Pour activer la connexion GitHub (optionnel) :
https://<votre-projet>.vercel.app/api/auth/callback/github.BETTER_AUTH_URL (l'URL publique, pour que l'adresse de retour
envoyée à GitHub soit toujours la même), GITHUB_CLIENT_ID et GITHUB_CLIENT_SECRET (en
« Sensitive »).Une OAuth App n'accepte qu'une adresse de retour : la connexion GitHub fonctionne en production, pas sur les prévisualisations des pull requests.
Bon à savoir :
vercel.json place les fonctions à Francfort (fra1), près de la base.src/
├── app/ Pages, Server Actions (actions.ts) et routes API
├── components/ Composants partagés : prix en direct, en-tête, historique…
└── lib/
├── trading.ts Règles de calcul des ordres (tests unitaires)
├── data/ Accès aux données : ordres, portefeuille, classement
├── db/ Schéma Drizzle et connexion Postgres
├── binance.ts API REST Binance (serveur)
├── price-store.ts Flux WebSocket des prix (navigateur)
└── auth.ts Configuration Better Auth
design-system/ Design system (tokens, typographie, règles d'accessibilité)
drizzle/ Migrations SQL générées
scripts/migrate.mts Application des migrations (lancé au build)
Projet éducatif : aucune transaction réelle n'est effectuée et rien ici ne constitue un conseil en investissement. Données de marché fournies par Binance (prix en USDT, assimilés au dollar).