Build a static Tetris quiz SPA that drops into Articulate Storyline (Insert > Web Object) and Rise (Code Block > Upload). The build must be downloadable on the free Lovable plan, so read the BUILD OUTPUT section first and follow it exactly.

BUILD OUTPUT — READ THIS FIRST

Lovable's default Vite template gitignores dist/, which hides the built folder from the file tree and from "Download codebase." Do all of the following so the portable build is downloadable on the free account:

1. In vite.config.ts set:
   - base: "./"
   - build.outDir: "web-object"
   - build.emptyOutDir: true
2. In .gitignore, remove any line that ignores dist or web-object. Add !web-object/ at the bottom.
3. After the app is finished, run bun run build.
4. Commit the web-object/ folder to the repo. It must appear in the Lovable file tree on the left.
5. Also create web-object-rise.zip at the repo root containing the contents of web-object/ (not the folder itself, so index.html is at the zip root). Commit that zip too.
6. Verify: web-object/index.html exists, web-object/assets/ has the JS/CSS bundles, web-object/questions.csv is present, and web-object-rise.zip is at the repo root.
7. Reply with exactly:
   web-object/ is built, committed, and visible in the file tree. web-object-rise.zip is at the repo root. Click "Download codebase" at the bottom-left to get the full ZIP. The web-object/ folder is the portable Storyline build, and web-object-rise.zip is the Rise upload.

Do not skip step 4. If web-object/ is gitignored or uncommitted, it will not appear in the download.

GAME SPEC

Build a single-page Tetris game with a quiz overlay. The player drops standard Tetris pieces. Every time a row clears, a quiz question appears as a modal. The player must answer before the game resumes. The game ends after all questions in questions.csv have been answered, or when the board tops out, whichever comes first.

Visual style: clean editorial, warm cream background (#FAF8F3), near-black text (#1A1A1A), gold accent (#C9A961). Use a serif display font for the title (Playfair Display via Google Fonts), Inter for everything else. Soft 4px corner radius on buttons and the question modal. No drop shadows beyond a hairline border. Generous whitespace.

Layout:
- Left column: Tetris board, 10 wide by 20 tall, each cell ~28px square.
- Right column: Score, Lines Cleared, Level, Questions Answered (X / total), and a small "Controls" panel showing arrow keys + space.
- Centered question modal overlay when a question fires. Dims the board behind it. Modal shows the question, answer choices as buttons, and a result state with brief feedback before resuming.

Controls: Left/Right arrows move, Down soft-drops, Up rotates clockwise, Space hard-drops, P pauses.

Sounds (generated via Web Audio API, no audio files):
- Soft tick on piece lock.
- Brighter chime on line clear.
- Pleasant 2-note ascending tone on correct answer.
- Muted descending tone on incorrect answer.
- Subtle win fanfare on completion.

QUESTIONS

Questions load from ./questions.csv at the same path as index.html. Use this exact schema (shared across the Embark IQ toolkit, including the PWO Generator):

question,option_1,option_2,option_3,option_4,correct_answer,feedback_correct,feedback_incorrect

- correct_answer is the letter A, B, C, or D (A = option_1, B = option_2, and so on).
- feedback_correct and feedback_incorrect are short strings shown after the learner answers.
- Parse the CSV with proper quote handling so commas inside questions don't break rows.
- Ship a sample questions.csv in public/ with 5 placeholder L&D-themed questions so the build has something to load.

Questions fire in order, not randomly. Once a question is answered, it does not repeat.

COMPLETION REPORTING (Rise / SCORM-lite)

On game completion (all questions answered or board top-out), do all of the following:
- Show a final results card inside the SPA with score, lines cleared, and questions correct.
- Call window.parent.postMessage({ type: 'complete', score, total, correct, linesCleared }, '*') so Rise/Storyline can listen.
- If running inside a SCORM wrapper (detect window.API or window.API_1484_11), also call LMSSetValue('cmi.core.lesson_status', 'completed') and LMSCommit('') (or the 2004 equivalents). Wrap in try/catch so it silently no-ops outside an LMS.

TECHNICAL REQUIREMENTS

- React + TypeScript + Vite.
- base: "./" so all asset paths are relative (required for Storyline web objects).
- All assets bundled locally. No CDN fonts at runtime — either self-host the woff2 files in public/fonts/ or inline them. No external API calls.
- No service worker, no PWA manifest.
- No analytics, no telemetry.
- Keep total web-object/ size under 2 MB if possible.
- Mobile-friendly: works in a 1024x768 iframe at minimum.

ACCEPTANCE CHECKLIST

Before you reply with the confirmation message in step 7 above, verify:
- [ ] vite.config.ts has base: "./" and outDir: "web-object".
- [ ] .gitignore does not exclude web-object/.
- [ ] bun run build completes without errors.
- [ ] web-object/index.html exists and is committed.
- [ ] web-object/assets/ contains the bundled JS and CSS.
- [ ] web-object/questions.csv is the sample 5-question file.
- [ ] web-object-rise.zip exists at the repo root and contains index.html at the zip root (not nested in a folder).
- [ ] Opening web-object/index.html directly in a browser loads the game with no console errors and no broken asset paths.
- [ ] Completing the game fires the postMessage event (verifiable in DevTools).
