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,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Organized game discovery with powerful player, genre, multiplayer, platform, and gameplay filters.
翻譯準備中
取自儲存庫 README
正在載入完整 README。 以下為已儲存的摘錄。
Find the right game. An organised game directory: filter games by number of players, co-op or versus, local or online, genre, platform, price, session length and more.檢視完整 README ↗Find the right game. An organised game directory: filter games by number of players, co-op or versus, local or online, genre, platform, price, session length and more.
Live: https://gameatlas.freewebtoolss.workers.dev
node scripts/build.mjs # validate data/ and build dist/
node scripts/serve.mjs # serve dist/ at http://localhost:8788 (mimics Cloudflare)
npm test # logic tests: search, filters, storage, catalog validation
node scripts/e2e.mjs # 58 headless-Chrome tests against a local server (incl. every game played in its page and fake-controller tests)
BASE_URL=https://gameatlas.freewebtoolss.workers.dev node scripts/e2e.mjs # same tests, live site
node scripts/check-sources.mjs # check every game's source link and Steam facts
node scripts/fetch-images.mjs # fetch official artwork for listed games -> assets/games + data/images.json
node scripts/capture-originals.mjs # gameplay screenshots for GameAtlas Originals (after a build)
node scripts/render-images.mjs # re-render OG images + favicons into assets/ (after a build)
npx wrangler deploy # builds, then uploads dist/ to Cloudflare
Node 22+ is the only requirement. There are no npm dependencies; the browser tests drive a local Chrome over the DevTools protocol (scripts/lib/chrome.mjs, set CHROME_PATH if Chrome isn't in the usual place).
data/
games.json the catalog (one object per game, schema below)
categories.json category pages; each one is a filter definition
taxonomy.json genres, modes, platforms, lengths, features (labels + ids)
site.json name, URLs, ad slots, accounts flag
src/
js/app.js entry for every page: theme, menu, saves, search dialog, page router
js/lib/ pure logic, shared by the build (Node) and the browser
filters.js filter state, matching rules, facet counts, URL (de)serialisation
search.js query parser ("4 player fighting" -> filters) and ranking
storage.js localStorage document: favourites, recent, saved searches, prefs
account.js optional-account interface (no backend in V1)
format.js labels ("2–4 players", "Local & online co-op")
catalog.js loads the hashed catalog JSON once
js/ui/ card.js (shared card markup), icons.js (SVG sprite), searchbox.js, ads.js
js/pages/ browse.js, game.js, saved.js, account.js, home.js, notfound.js
styles/ tokens.css (light/dark), base, components, browse, game, pages
play/ GameAtlas Originals (self-contained HTML5 games, embedded via iframe)
scripts/
build.mjs the static site generator
lib/templates.mjs page templates; lib/covers.mjs original SVG cover art
lib/validate.mjs catalog rules (the build fails on any error)
serve.mjs, e2e.mjs, check-sources.mjs, render-images.mjs, lib/chrome.mjs
assets/ favicons and OG images (committed, made by render-images.mjs)
test/logic.test.mjs node:test unit tests
dist/ is generated and not committed. wrangler.jsonc points Cloudflare at ./dist and runs the build before every deploy.
data/games.json. Copy a similar game and change every field.summary (max 110 characters) and description yourself. Never paste store text.sourceUrl must be the official store page, official site or publisher page. For Steam games also set steamAppId.node scripts/check-sources.mjs, then npm test and node scripts/build.mjs.node scripts/fetch-images.mjs <slug> for its official artwork, then node scripts/build.mjs and node scripts/render-images.mjs <slug> for its share image.| Field | Meaning |
|---|---|
players | { min, max, local?, online?, plus? }. max: null = no fixed limit; plus: true shows "8+". local/online are the max on one screen / online. |
modes | any of single, local-coop, local-pvp, online-coop, online-pvp, team, turn-based |
genres, platforms | ids from taxonomy.json; platforms are browser, pc, mobile, console |
price | free (includes free-to-play) or paid; put details in priceNote |
sessionLength | under-10, 10-30, 30-60, 60-plus (a typical round or sitting) |
downloadRequired, accountRequired, controllerSupport, keyboardSupport, mobileFriendly, familyFriendly | booleans; accountNote explains which account |
embedAllowed, embedUrl | only for games GameAtlas hosts itself (/play/<slug>). The validator refuses anything else. |
featured | shows the game earlier in default ordering |
The validator also enforces consistency (a 1-player game can't have multiplayer modes, a game with a single-player mode must have min: 1, category slugs can't collide with game slugs, and so on).
min <= n <= max.src/js/lib/search.js): phrases are recognised first ("4 player", "couch co-op", "free", "horror", "quick" …) and become filters, shown as removable "Showing:" chips. Leftover words must match the title, tags, developer or description (prefix and small-typo matching). A query that is part of a title always finds that game. If keywords match nothing, the structured part is shown instead with a note.?players=4&genre=fighting&mode=local), so every result list can be shared or saved.data/categories.json defines each page as a filter, e.g. { "slug": "local-co-op", "filters": { "modes": ["local", "coop"] } }. To add one, add an entry with an original h1, description and intro. The build skips categories with fewer than minGames games, so there are no empty SEO pages.
adSlot() in scripts/lib/templates.mjs renders one small banner labelled "Ad" near the bottom of the home page, every results page and every game page (468×60, 320×50 on phones). Slots are never inside the game player, filters, navigation or fullscreen. Each banner already contains a house ad for one of the GameAtlas Originals, so there is never an empty box.
Paid ads come from Adsterra (banner units only, never popunders or "social bar"):
'key' : '…') and the script address (<script src="…">) into data/site.json under ads.adsterra.units["468x60"] / ["320x50"] (key, src). A size left empty keeps showing the house ad on screens of that width.npx wrangler deploy -c ad-frame/wrangler.jsonc (the build writes the units into ad-frame/public/units.js) and npx wrangler deploy. The build adds the units to the slots, allows only the ad-frame origin in frame-src and updates the About page and footer.The ad code never runs on GameAtlas pages. ad-frame/ is a separate Worker (gameatlas-ads, deploy with npx wrangler deploy -c ad-frame/wrangler.jsonc) whose /frame page loads one banner. src/js/ui/ads.js embeds it in a sandboxed iframe without allow-top-navigation, so an ad cannot read the page or redirect it, and only shows the frame once it reports that an ad rendered; otherwise the house ad stays. Visitors whose time zone is in Europe get a one-line "Allow / No thanks" choice under the banner before any ad loads (stored as prefs.adConsent).
With the keys empty (the default) the site makes no third-party requests. Another network can be plugged in with registerAdProvider().
The site never requires an account. V1 has no auth backend: the header's "Log in" leads to a page that says so plainly and offers the real benefit (moving saves between devices) through export/import of the same JSON document storage.js keeps. src/js/lib/account.js defines the provider interface a future sync service would implement. The only account prompt is a quiet, dismissible line on the Saved page after 3 saves.
src/play/<slug>/ holds small original games (Paddle Duel, Four in a Row, Light Trails, Reflex Party, Memory Pairs, Dots and Boxes, Loop Racer, Serpent, Brick Breaker, Air Hockey, Mine Field, Block Drop, Tank Duel). The shared shell gives every game controller support (D-pad/stick through menus and boards, A select, Start pause, Select returns to the page), and touch controls on phones. They are plain classic scripts sharing src/play/shared/shell.js, run in a sandboxed iframe (allow-scripts allow-same-origin allow-pointer-lock: no pop-ups or top navigation, but a real origin so the Gamepad API works), and never show ads. Fullscreen targets only the game frame; browsers without the Fullscreen API (iPhone) get a fixed-position fallback with an exit bar above, not over, the game.
Every description is original. Game images are the publishers' official artwork (Steam store capsule or the official page's share image), fetched by scripts/fetch-images.mjs, recorded with source and credit in data/images.json, and credited on each game page; a few browser games without share images use a capture of their own title screen. Originals use gameplay screenshots from scripts/capture-originals.mjs. Generated SVG covers (scripts/lib/covers.mjs) remain as a fallback. Third-party games are only linked, never embedded or re-hosted.