Commit graph

39 commits

Author SHA1 Message Date
AyoubBenziza
b91d09b7e7 feat: category filter in lobby settings
- settings carry `categories` (empty = all); quiz/image pool filters by category
  (DB + in-code bank), threaded through prepareQuizForRoom/loadGroup/loadQuizPool
- public GET /api/categories lists available categories
- lobby: category chips (Toutes + each), shown when quiz/images is active
- shared: RoomSettings.categories + UpdateSettingsPayload

Verified e2e: /api/categories returns the list; filtering by ["Pokémon"] serves
only Pokémon questions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 15:37:33 +02:00
AyoubBenziza
fa7c97c402 feat(web): richer desktop layout for end screen and back-office
- end screen: wider container (max-w-4xl), podium/awards stay centered, and the
  two long sections (musiques + récap des manches) sit side-by-side on desktop
- back-office: 2-column layout (form | questions list) on desktop, wider

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 15:24:34 +02:00
AyoubBenziza
879bc5b388 feat: per-submode mixed settings, fastest award, wider desktop layout
Mixed settings fix:
- RoundConfig carries a `pool` hint (quiz | image) per quiz round; lobby shows
  a separate count for each selected sub-mode (Quiz, Images) and the blindtest
  config — not just the quiz one
- server pool composes the queue per round's pool (quiz formats vs image_reveal),
  in order; verified e2e (2 quiz + 2 images honored)

Awards:
- " Le plus rapide" — derived client-side from the speed bonus baked into
  quiz/image points (points above the 100 base on correct answers)

Desktop restyle:
- room page gets an ambient halo background and a responsive container
  (lobby max-w-3xl with a 2-column players|settings grid, game/end max-w-lg)
- game/end views widened to max-w-lg

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 15:10:09 +02:00
AyoubBenziza
976ebf0799 feat(web): end-game awards, copy results, avatars in recap
- FunStats: award badges from the recap data — 🎯 Sans-faute, 🧠 Le cerveau
  (most correct), 🦹 Roi de la tromperie (blindtest decoy points), 🪨 Le boulet
  (0 correct). Shown with player avatars.
- "Copier les résultats" button: copies ranking + per-round answers to clipboard
- avatars next to each player in the round-by-round recap

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 01:11:06 +02:00
AyoubBenziza
331d375349 feat(web): recap polish — sort answers by points, highlight you, found count
- per round, answers sorted by points (then correct first)
- the local player's line is bolded with "(toi)"
- per-round "✓ X" badge showing how many got it right

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 01:05:14 +02:00
AyoubBenziza
f7ee2204d1 feat: round recap shows each player's answer
- shared: RoundRecap gains answers[] (RoundPlayerAnswer: text + correct)
- server: quiz/blindtest recap() build per-player answers (choice text / free
  text / blindtest guess formatted by mode), with correctness
- client: recap lists each player's answer per round, green/red by correctness,
  with their points when they scored

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 01:00:50 +02:00
AyoubBenziza
03a0dfa998 feat: end-of-game round recap (all rounds with answers + scorers)
- shared: RoundRecap (+ scorers); game:end carries recap[]
- server: GameRound.recap(ctx) per mode (quiz: prompt + correct answer +
  image; blindtest: "title — artist" + addedBy + links); engine accumulates a
  history per round (with that round's scorers from the score deltas) and sends
  it at game:end
- client: GameEndView shows a collapsible "Récapitulatif des manches" — each
  round's label, answer, media thumbnail (image/youtube) and who scored
- engine test dummy round implements recap

Verified e2e: game:end recap lists every round with answer + scorers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 00:54:40 +02:00
AyoubBenziza
5e96182489 feat(web): dedicated transition for the image mode
image_reveal rounds (RoundType "quiz") now get their own "IMAGE" transition
(emerald/teal theme, 🖼️) instead of reusing the quiz one. The store derives a
roundKind (quiz | image | blindtest) from the round payload format; mode-change
detection and the custom-media folder use it (assets/transitions/image/).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 00:35:32 +02:00
AyoubBenziza
8bc184fab7 feat: bigger image_reveal, optional image prompt, mixed sub-mode toggles, Pokémon seed
- image_reveal display: larger, object-contain (whole image up to 58vh), no crop
- image prompt optional in back-office (defaults to "Qui est-ce ?"), overridable
- mixed mode reworked: select "Mixte" then toggle sub-modes (Quiz/Images/
  Blindtest). Blindtest sub-mode locked under 3 players; mixed itself no longer
  needs 3. Server quiz pool formats derived from gameType + mixedModes.
  shared: MixMode + RoomSettings.mixedModes (default quiz+image).
- DB: prompt uniqueness is now a partial index (excludes image_reveal) + unique
  index on image_url; lets many images share the generic prompt, dedup by URL.
- seed: db:seed:images pulls Pokémon (PokéAPI) as image_reveal questions with
  official artwork (remote URL) + FR/EN accepted answers; idempotent by image_url.

Verified: migration applied, Pokémon seed inserts FR+EN, image mode serves only
image_reveal, all checks green (23 tests).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 00:20:28 +02:00
AyoubBenziza
62b536cc2b feat: "Images" as a distinct mode, refined lobby settings, upload button, app title
- image_reveal becomes its own game mode: GameType gains "image"; the quiz
  pool is filtered by allowed formats derived from gameType (image →
  image_reveal only, quiz → mcq/truefalse/free, mixed → all). loadQuizPool
  takes a formats list; pool fallback respects it.
- lobby settings reworked into a refined "Réglages" card: 4 mode tiles
  (icon + label, locked <3 players for blindtest/mixed), question count as a
  stepper, blindtest config grouped; dynamic label for image mode + hint.
- admin: real upload button (hidden input + styled Button with Upload icon)
  instead of the bare file input.
- app title set to "NerdWare …" in index.html (+ lang fr, description).

Verified e2e: image mode serves only image_reveal questions (with imageUrl).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 23:31:30 +02:00
AyoubBenziza
0619c02cdf feat(quiz): image_reveal format (progressive blur) end-to-end
Server:
- quiz-round handles image_reveal like a text-answer format (tolerant match
  on acceptedAnswers), exposing imageUrl in the round payload; degressive
  scoring comes for free from the existing speed bonus (earlier = blurrier =
  more points)
- repo includes image_reveal in the pool (requires imageUrl + acceptedAnswers)
- back-office: image upload (@fastify/multipart) saved to UPLOADS_DIR and
  served statically on /uploads (@fastify/static); create supports image_reveal
- env: UPLOADS_DIR; gitignore uploads/

Client:
- admin form: "Image à deviner" — upload + preview + accepted answers
- in-game: ImageReveal shows the image with a time-cadenced decreasing blur
  (synced via server timestamps) + text input; reveal shows the clear image
  and the answer

shared: QuizQuestionPayload.imageUrl

Verified against real server+DB: upload (url), static serve (200), create
(201), validation (400).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 21:41:34 +02:00
AyoubBenziza
db0100e272 feat(web): victory sound on the end screen (drop-in)
- lib/sound: plays src/assets/sounds/victory.{mp3,ogg,wav} if present,
  auto-detected via import.meta.glob; no-op when absent
- played alongside the confetti when the end screen appears
- README documenting where to drop the file

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 21:17:25 +02:00
AyoubBenziza
e490f23dec feat(web): screen transitions + confetti when the lead changes
- RoomPage animates between coarse phases (lobby ↔ game ↔ end) with a fade/
  slide (AnimatePresence, mode wait). Keyed by phase, not status, so the
  YouTube player survives in_round → reveal → scores.
- PlayerCards fires a small confetti burst when a player overtakes to take
  the lead during the game.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 18:57:49 +02:00
AyoubBenziza
7093ce8cd9 feat(web): victory confetti on the end screen
canvas-confetti burst (center pop + side jets) fired once when the game-end
screen appears.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 17:54:23 +02:00
AyoubBenziza
b3b6b056eb feat(web): propagate YouTube brand icon to the lobby track submission
- "Ajouter" button shows the SiYoutube logo
- each submitted track card gets a YouTube open link (verify the pasted song)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 17:38:50 +02:00
AyoubBenziza
3f36798207 feat(web): brand icons (Spotify/YouTube) via react-simple-icons
Add @icons-pack/react-simple-icons for brand logos lucide doesn't provide.
End-screen track recap now uses official SiSpotify (green) and SiYoutube (red)
icons instead of generic ones. Lucide stays for UI icons.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 17:35:20 +02:00
AyoubBenziza
c0a4340bae feat(web): Spotify export on the end screen (roadmap V1 step 8)
From the blindtest tracks revealed at game end:
- per-track Spotify search link (green button) built from "title artist"
- "Playlist" button copies the whole tracklist ("title — artist" per line)
  to the clipboard for pasting into Spotify
- YouTube open link kept alongside

Pure client-side, derived from the tracks already sent in game:end.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 17:31:38 +02:00
AyoubBenziza
d3ce52b60a feat(admin): manual quiz back-office + rewrite README
Back-office (roadmap V1 step 7):
- server: token-protected HTTP API under /api/admin (ADMIN_TOKEN), Drizzle-backed
  CRUD for manual questions — list, create (mcq/truefalse/free, category upsert,
  dedup on prompt, validation), delete; 401/503 when unauthorized/unconfigured
- client: /admin page with token login + TanStack Query (list/create/delete),
  format-aware form (QCM / vrai-faux / réponse libre)
- env: ADMIN_TOKEN

README: replace the shadcn template with a real NerdWare overview (stack,
structure, getting started, scripts, game modes, back-office).

Verified against real Postgres: auth (401), create (201), dedup (409),
validation (400), list, delete (200/404).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 16:43:19 +02:00
AyoubBenziza
ff84dc4f6b feat(lobby): block start until everyone submitted; tracks count stepper
- blindtest/mixed: the host can't start until every connected player has
  submitted their track quota — client disables the button with a hint, and
  game:start rejects with TRACKS_PENDING (authoritative)
- tracks-per-player is now a number stepper (− / + buttons + free typing,
  clamped 1..10) instead of fixed 1/2/3 buttons

Verified e2e: start rejected while a player hasn't submitted, accepted once all did.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 16:19:31 +02:00
AyoubBenziza
3f0cab8f5f feat: end-of-game track recap, no self-vote in who_added, festive home + icons
- game:end now carries the played blindtest tracks (title/artist/youtubeId/
  url/submittedByName); the end screen lists them as cards with a YouTube
  open link so players can recover/export the songs
- who_added vote no longer lets you pick yourself (filtered out)
- home page: animated halo, floating geek emojis, springy gradient title,
  hover/tap on buttons (Framer Motion) + Lucide icons
- lobby settings get Lucide icons (game type, quiz, blindtest mode, tracks,
  start)

Verified e2e: game:end includes the track list with links.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 16:04:14 +02:00
AyoubBenziza
1071a59b68 feat: pick pseudo inside the room + shareable invite link
Restructure onboarding (option B):
- home is just Create / Join (code) — no name field
- name is chosen in the room: room:create/join no longer require a name;
  new player:setName event. Nameless players are hidden from the snapshot
  (players/scores/submissions) and excluded from eligible voters and the
  3-player blindtest gate until they pick a pseudo.
- PseudoScreen with live DiceBear avatar preview gates RoomPage until set
- /join/:code auto-joins then redirects to the room (seamless invite links)
- RoomCode gains a "copier le lien" button (origin/join/CODE)

Verified e2e: nameless players excluded from the room until setName.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 15:32:24 +02:00
AyoubBenziza
6251146c19 feat: replay from lobby, contributor mislead card, editable track cards
Return to lobby (replay without recreating the room):
- lobby:return (host): reset to lobby, scores to 0, keep players + tracks
- client: end screen has "Rejouer" (host) + "Quitter"; store clears transient
  game state whenever the room goes back to lobby

Contributor has nothing to input:
- round:start carries a private `mine: true` to the contributor only (via
  RoundStart.secretPlayerId targeted emit); they're excluded from eligible
  voters. Client shows a "fais-les se tromper" card instead of the vote form.

Editable track submission:
- submitTrack ack returns trackId + youtubeId; new blindtest:removeTrack
- lobby shows submitted tracks as cards (YouTube thumbnail + title) with delete

Verified e2e (3 players): add/remove, private mine flag, return-to-lobby reset.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 15:13:54 +02:00
AyoubBenziza
a2dc687ac0 fix(web): lobby falls back to quiz UI below 3 players
Default game type stays "mixed", but while fewer than 3 players are connected
the lobby renders the quiz config only (Quiz highlighted, Mixed/Blindtest
disabled) instead of showing blindtest settings to a lone host. It switches
back to the stored type automatically once 3 players are present — no manual
click needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 14:51:19 +02:00
AyoubBenziza
a2e3fae858 fix(blindtest): keep neutral DJ; only the contributor is misdirection-scored
Revert the DJ change: the DJ is again a neutral player (never the track's
contributor), broadcast to all and counted as an eligible voter (votes blind).
The contributor is a normal voter whose vote never scores on their own track;
they only earn the who_added/mixed misdirection bonus (+50 per wrong guess).

- engine: restore single round:start broadcast + DJ-included eligible voters
- blindtest-round: restore neutral pickDj; contributor may vote (ignored in
  scoring) so the round can still end on "all voted"
- client: DJ controls + everyone (DJ included) votes; reveal message simplified

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 14:40:02 +02:00
AyoubBenziza
a9eb6098f2 feat(blindtest): contributor-as-DJ scoring, 3-player gate, 16:9 player + seek
- DJ is now the track's contributor (controls their own song); their identity
  is sent only to them so "who added" stays non-trivial. The contributor
  doesn't vote and is excluded from the eligible-voters count.
- Scoring: the contributor earns nothing except in who_added/mixed, where they
  get a misdirection bonus (50) per player who guesses the wrong adder.
- Blindtest (and mixed) require >= 3 connected players: lobby disables the
  options with a hint, and game:start rejects with NEED_THREE.
- Player card is now 16:9 and full width — the video banner shows at reveal;
  DJ gets a seek bar (current/duration) to jump anywhere, plus play/pause/restart.
- Mixed game order is shuffled (no predictable quiz/blindtest alternation).

Verified e2e (3 players): guard rejects at 2, DJ id hidden from others,
misdirection scoring correct. 23 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 14:30:47 +02:00
AyoubBenziza
e3315d3b35 feat: mixed games by default, free-text quiz, mode-aware transitions
Mixed mode (default):
- gameType is now mixed | quiz | blindtest, default "mixed"; the lobby
  interleaves quiz + blindtest rounds so modes alternate
- lobby reworked: 3-way game-type switch, quiz count and/or blindtest config
  + track submission shown per selected type

Free-text quiz questions:
- quiz supports the `free` format (e.g. maths) alongside mcq/truefalse:
  payload without choices, {text} vote, tolerant matching on acceptedAnswers
- shared QuizQuestionPayload.choices optional; reveal truth carries answer
- match util moved to game/match.ts (shared by quiz + blindtest)
- repo/seed include free + acceptedAnswers; in-code bank gains free questions
- client quiz view renders a text input for free questions

Mode-aware transitions:
- RoundTransition themed per mode (colors/label/emoji); announces the mode
  on change (quiz <-> blindtest), lighter teaser within the same mode
- custom media buckets per mode: assets/transitions/{quiz,blindtest}/ + shared
- store tracks roundModeChanged

Verified e2e: mixed game alternates quiz/blindtest to completion; free-text
scoring covered by tests (22 pass).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 13:41:29 +02:00
AyoubBenziza
98e06b8206 feat(blindtest): YouTube blindtest mode with neutral DJ + synced playback
Server:
- BlindtestRound (GameRound): neutral DJ pick (never the contributor),
  per-mode scoring (title_artist fuzzy / who_added / mixed), reveal exposes
  title/artist/submittedBy; contributor doesn't score on own track
- track submission: blindtest:submitTrack validates via YouTube oEmbed
  (exists + embeddable) and captures title/artist; per-player quota; dedup
- DJ media relay: media:control (DJ only) → media:sync broadcast (engine)
- per-room shuffled track pool; submissions count in room snapshot
- shared: blindtest payloads, gameType/tracksPerPlayer in settings
- tests: DJ neutrality, scoring per mode, fuzzy match, youtube id extraction

Client:
- YouTube IFrame player (hidden behind a spinning disc — audio only, no
  title leak), DJ transport (play/pause/restart), non-DJ follows media:sync
  with latency compensation
- lobby: game-type switch (Quiz/Blindtest), blindtest config + per-player
  track submission UI
- vote forms per mode; reveal card with title/artist/who-added

Roadmap V1 step 6 (highest technical risk). Server flow verified end-to-end
over the wire (submit→start→DJ relay→vote→reveal→score).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 13:02:28 +02:00
AyoubBenziza
7dda00c65c feat: lead-in timer compensation + GIF support for round transitions
Server (option 2 — don't eat answer time):
- engine: leadMs prep delay (default 1600ms) before the clock starts; the
  round timer now fires at lead + duration, and scoring uses the post-lead
  start so the speed bonus spans the full answer window
- shared: round:start carries startsAt (answers begin) alongside endsAt
- engine test passes leadMs:0

Client:
- Countdown shows the full duration (no ticking) until startsAt, then counts
  down — so the WarioWare transition plays in full without stealing time
- store/ActiveRound carry startsAt

Custom transition media:
- RoundTransition auto-loads any gif/webp/apng/png/avif dropped in
  src/assets/transitions/ (import.meta.glob), plays one at random per round,
  falls back to the Framer animation when none are present
- README documenting how to add animations

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 11:25:17 +02:00
AyoubBenziza
723d91943e feat(web): WarioWare-style transition between rounds
- RoundTransition overlay: fullscreen colored wipe-in, spinning sunburst,
  big bouncing "QUESTION N" + "PRÊT ?!", then wipe-out (Framer Motion)
- store: roundKey counter incremented on round:start
- rendered at RoomPage, keyed by roundKey so it plays once per round

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 11:15:36 +02:00
AyoubBenziza
0c2534f3a4 feat(web): podium on the end screen + larger avatars
- GameEndView: top-3 podium — 2nd left, champion center (taller), 3rd right,
  with gold/silver/bronze pedestals, staggered rise-in (climax on the champion),
  crown + pulsing gold glow kept on #1; ranks 4+ listed below
- bigger avatars where they read too small: lobby (size-7→11) and in-game
  player cards (size-9→14, slightly wider cards)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 11:05:54 +02:00
AyoubBenziza
c3e00ab9d9 refactor(web): generate DiceBear avatars locally instead of via HTTP API
Self-hosted over the HTTP API: no runtime dependency on api.dicebear.com,
works offline/LAN, and no player pseudo leaks to a third party.

- @dicebear/core@10 + @dicebear/styles@10 (lorelei.json), CC0 1.0
- Avatar builds a data URI locally (new Style/Avatar), memoized per seed
- same component API (seed/className) → no call-site changes
- enable resolveJsonModule for the style import

Cost: ~64 kB gzip added to the bundle — acceptable for a self-hosted game.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 10:58:23 +02:00
AyoubBenziza
4f10d86fb0 feat(web): DiceBear lorelei avatars for players
- Avatar component using DiceBear 9.x lorelei, seeded by the player name
  (stable, recognizable across screens)
- shown in the lobby list, the in-game player cards (gold ring on leader),
  and the final leaderboard (large on the winner, small on runners-up)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 10:50:17 +02:00
AyoubBenziza
2fce63530b feat(quiz): show per-player answer status during the round
- voteAck now carries `voted` (playerIds who validated) — never their answer,
  so no leak; the count/total stay for the progress text
- PlayerCards show a green "Prêt" check for players who answered and a
  pulsing "en attente" dot for those who haven't, during in_round only
- score stays visible on the cards alongside the status
- engine test updated for the new voteAck shape

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 10:30:03 +02:00
AyoubBenziza
0c9eaca312 feat(web): glorify the winner on the final leaderboard
- dedicated winner card: gold border, gold gradient, pulsing gold glow,
  spring scale-in, and a Crown that drops & bounces in (Framer Motion)
- champion label + big gold score; runners-up listed below from rank 2
- drop the generic Scoreboard component (now unused)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 10:25:28 +02:00
AyoubBenziza
c9f3197300 feat(web): real explosion at 0 — fullscreen flash + giant 💥
- store: boomKey counter + boom() action
- Countdown fires boom() once when it reaches 0 (timer expiry only)
- Boom component (portal to body) plays a fullscreen white flash + giant 💥
- rendered at RoomPage level, keyed by boomKey so it plays fully (independent
  of the countdown unmounting on reveal) and replays each round

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 16:05:42 +02:00
AyoubBenziza
f82c7d9700 feat(web): bomb countdown, copyable room code, persistent score cards
- Countdown: under 10s it ramps into a "bomb" — white→red color and an
  escalating scale/wiggle pulse (faster & wider as it nears 0) via Framer Motion
- RoomCode: click-to-copy the room code with visual feedback
- PlayerCards: always-on score HUD during the game (cards per player,
  animated reorder, crown on the leader) so scores are tracked continuously
  instead of only appearing at reveal
- quiz view: drop the reveal-only scoreboard (now covered by PlayerCards)
- add framer-motion dependency

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 15:56:57 +02:00
AyoubBenziza
5cc9113b8c feat(quiz): quiz game mode end-to-end (server mode + playable client UI)
Server:
- quiz mode (GameRound): in-memory FR question bank (mcq + truefalse),
  per-room anti-repeat, first-vote lock, score = base + speed bonus ∝ time
  left; registered via registerRound, loaded at startup
- shared: typed quiz payloads (QuizQuestionPayload / reveal truth / result)
- tests: quiz scoring, vote lock, bounds, reveal (bun test)

Client:
- room store handles round:start / voteAck / reveal / game:end
- RoomPage dispatches by status: lobby (host start controls) → quiz view
  (question, countdown, vote, reveal + scoreboard) → game-end view
- replaces standalone lobby page

Roadmap V1 step 4. Verified end-to-end over the wire (start→vote→reveal→
score→next round→game:end, no answer leaked, anti-repeat works).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 15:43:17 +02:00
AyoubBenziza
29faa18731 feat(server,web): first real-time cycle — room create/join + lobby
Server (@nerdware/server):
- RoomManager: in-memory rooms (Map), code generation, create/join,
  disconnect handling, public snapshot projection (no secrets)
- Socket.IO handlers: room:create / room:join with typed acks,
  room:state broadcast on every change, lobby:updateSettings (host-only),
  player:left on disconnect

Web (web):
- typed Socket.IO client + Zustand store mirroring room:state
- wouter routing: home (create/join) + minimal lobby (live player list)
- @nerdware/shared wired into the client

Roadmap V1 step 2. Verified end-to-end (create/join/broadcast/disconnect).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 15:16:26 +02:00
AyoubBenziza
e7b2f46e81 feat: initial commit 2026-06-07 18:45:52 +02:00