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。 儲存庫 README ↗
正在載入完整 README。
檢視完整 README ↗This is a touchscreen kiosk game designed for a 42-inch, 1920×1080 landscape display. It also adapts to laptops, tablets and phones. A doctor picks OPD or IPD, is dealt one of that pathway's cases and reviews the clinical clues. Each of 4 clinical questions they answer correctly unlocks a piece of a hidden MIQNAF puzzle. As they progress, the patient visibly recovers and the bedside monitor's ECG moves from red to gold to green. The game ends with the MIQNAF reveal and a "fastest doctors" leaderboard.
Built with Next.js 15 (App Router), React 19, TypeScript, Tailwind CSS 4, Framer Motion, Lucide icons and optional Supabase.
Requires Node.js 20+ (tested on 22.14).
npm install
npm run dev # http://localhost:3000 (development)
npm run build # production build
npm start # http://localhost:3000 (use this on the kiosk)
Other checks: npm run lint, npx tsc --noEmit.
Kiosk setup: open the app full-screen in kiosk mode, for example msedge --kiosk http://localhost:3000 --edge-kiosk-type=fullscreen or chrome --kiosk http://localhost:3000.
Responsive layout:
wide: Tailwind variant in app/globals.css.Offline: the game, fonts, artwork, voice-over and sound all run locally. Only the optional shared leaderboard needs a network connection.
| What | Where |
|---|---|
| Case bank (2 OPD + 2 IPD cases, 4 questions each), answer keys, rationales | data/cases.ts (from the client's "MIQNAF Cases_NAPCON" document) |
| On-screen copy and voice-over scripts | data/copy.ts |
| MIQNAF logo, puzzle artwork, scientific message, Learn More content, QR code | data/brand.ts |
| Logo image files | public/images/miqnaf-logo.png and miqnaf-puzzle.png, generated from the supplied logo by python scripts/prepare-logo.py <logo.jpg> |
| Kiosk behaviour (leaderboard size, idle timeout, auto-advance, sound default) | KIOSK in data/brand.ts |
| Brand colours | @theme in app/globals.css and BRAND_COLORS in data/brand.ts (#9A1F24 / #28397F / #E7A900) |
Demo (default): if no Supabase credentials are set, scores are saved only in this browser. The leaderboard shows the note "Demo leaderboard. Scores are saved on this device only." Demo scores are not shared between kiosks.
Shared:
supabase/schema.sql in the Supabase SQL editor..env.example to .env.local and set SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY.The key is read only by the server route app/api/leaderboard/route.ts, never in the browser. Row Level Security is on with no public policies.
Validation: submissions are checked on the server against lib/leaderboard/validate.ts:
., -, ')Failures: if the database is unreachable, the leaderboard shows an error with a Retry button and the game keeps working.
Ranking:
The supplied fonts are in public/fonts/:
| File | Used for |
|---|---|
AcuminVariableConcept.otf | headlines, titles, large numbers |
HelveticaNeue.ttc | questions, answers, body text, buttons |
JT-Distressed.ttf | not used; a distressed face doesn't suit the clean UI |
The app loads converted copies from public/fonts/web/. They come to about 0.95 MB in total; the originals are 5.5 MB. Browsers only read the first face of a .ttc collection, which would leave every bold heading faux-bolded. So the Regular, Medium, Bold and Italic weights of Helvetica Neue are extracted as separate WOFF2 files.
If the source fonts change, regenerate the web copies:
pip install fonttools brotli
python scripts/build-web-fonts.py
| Screen | Content (from the approved brief) |
|---|---|
| 1. Attract | "CAN YOU SAVE THIS PATIENT?", OPD/IPD case buttons, START THE CHALLENGE |
| Name entry | "ENTER YOUR NAME" right after START. Built-in on-screen keyboard on the kiosk; other devices use their own keyboard. The name is required and validated. |
| 2. Briefing | "YOUR PATIENT. YOUR DECISION.", the assigned case (e.g. "IPD Case 2" and its title), its clue cards, BEGIN CLINICAL CHALLENGE |
| 3. Deterioration alert | "YOUR PATIENT IS DETERIORATING", puzzle instructions, I'M READY (starts the clock) |
| 4 to 7. Questions 1 to 4 | One question per screen. The case's questions are dealt in a shuffled order and the four options are shuffled for every new game, so the correct answer is not always in the same position; letters A to D follow the displayed order, and the approved wording is unchanged. A correct answer shows the green pop-up (and the rationale, once approved wording is supplied), unlocks the next piece of the MIQNAF logo (left to right) in the HIDDEN IMAGE card under the patient: its gold, locked cover lifts away and the new piece glows gold (components/HiddenImageCard.tsx, components/PuzzleBoard.tsx), then moves to the next question automatically after 3.5 seconds (no button). A wrong answer shows the red pop-up with TRY AGAIN, keeps the piece locked and disables that option. The participant can always finish. |
| 9. Reveal | The puzzle assembles into the approved MIQNAF logo, then the patient stabilises and the monitor turns green. The score is saved to the leaderboard automatically under the name entered at the start, and the screen shows the rank (with Retry if saving fails). Buttons: VIEW LEADERBOARD, LEARN MORE, PLAY AGAIN. |
| 10. Leaderboard | Top 5 rows, with gold, silver and bronze for the top 3. The participant's own row is highlighted, even when they are outside the top 5. Loading, empty and error states are handled. |
| Learn More | Modal with MIQNAF content from data/brand.ts |
Case sets (exam-style mixing): each pathway has a bank of cases in CASES (data/cases.ts). On START the device takes the next ticket from a shared counter on the server (app/api/session/route.ts), and createSessionPlan in lib/gameMachine.ts turns it into a paper set:
The counter is held in server memory and starts at a random point. If the server cannot be reached, the device falls back to its own counter (lib/caseRotation.ts). To add a case, append it to the pathway's list in data/cases.ts with exactly 4 questions.
Game-style popups: instructions appear as popups rather than page text:
Visual style: three brand colours plus white, solid fills only (no gradients, no coloured shadows). The page background is white. Crimson #9A1F24 is the accent text colour and the alert highlight (wrong answers, alarms). Royal navy #28397F carries the top header bar, the answer option bars, the MIQNAF logo and body text. Golden yellow #E7A900 marks the primary CTA buttons, the A-D option badges and the jigsaw pieces. Green #15803D highlights a correct answer as an accent only: a green outline, tint and tick on the chosen answer bar, and a green outline, heading and tick on the white feedback panel. The patient scene is a supplied ICU illustration (public/images/patient-scene.webp, shown by components/PatientScene.tsx) in a white game-screen panel with a navy outline. The illustration's monitor screen is redrawn live: a scrolling ECG, heart and status word (ALERT, IMPROVING, STABLE) that follow the game. Buttons and answer bars nudge down when tapped, and the case clues are white case-note cards. Exceptions: the approved MIQNAF logo artwork appears unchanged, and the monitor trace keeps the brief's required red, yellow and green on its black screen.
Patient and monitor states:
| Stage | Patient | ECG |
|---|---|---|
| Attract, briefing, alert, question 1 | illustration (resting, on oxygen) | red, ALERT |
| Questions 2 to 4 | illustration | gold, IMPROVING |
| Reveal (after the completion popup) | recovered illustration fades in: awake, smiling, propped up | green |
Until the reveal, progress is shown on the illustration's live monitor; the recovered illustration (public/images/patient-recovered.webp) has its own monitor, which shows vitals. The monitor shows no numbers, only the status words ALERT, IMPROVING or STABLE. For 4K screens, supply the illustration at 2048x3072 or larger and replace public/images/patient-scene.webp.
Timing: the clock counts time spent on active questions, from I'M READY until the 4th correct answer. It pauses while the correct-answer pop-up is open, so reading the rationale doesn't cost time. Time spent after a wrong answer still counts.
Kiosk safeguards:
Code structure:
app/ page, layout, API route, global design system (fonts, fluid scale, breakpoint)
components/ GameShell, PatientScene (illustration + live monitor), ECGTrace, PuzzleBoard, screens/, question/, entry/
data/ cases.ts, copy.ts, brand.ts (editable content)
hooks/ useGame (state + sound), useIdleReset, useLeaderboard, useMediaQuery, useNow
lib/ gameMachine.ts (pure reducer), visualStage.ts, puzzle.ts, ecg.ts, audio.ts, leaderboard/
types/ game and leaderboard types
supabase/schema.sql database table
scripts/ build-web-fonts.py (fonts to WOFF2), prepare-logo.py (logo crop and puzzle artwork)
Answer checking lives in lib/gameMachine.ts, a pure reducer kept separate from the UI. It ignores double taps, repeat unlocks and any action that isn't valid in the current state.
speak() in lib/audio.ts.npm run build && npm start starts with no errors, and the page loads full-screen in kiosk mode