From 5444630c713105add547fe46d59bed9542598491 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 19:04:17 +0000 Subject: [PATCH 1/2] Initial plan From 52ef2cb75d2880413aa9fbd6b758e85e5c9fda45 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 19:11:29 +0000 Subject: [PATCH 2/2] feat: add Quiz Wizard mode with brutalist newspaper aesthetic Co-authored-by: digitarald <8599+digitarald@users.noreply.github.com> --- index.html | 3 + src/App.tsx | 15 +- src/components/QuizWizardScreen.tsx | 230 ++++++++++++++++++++++++++++ src/components/StartScreen.tsx | 47 ++++-- src/index.css | 19 +++ 5 files changed, 296 insertions(+), 18 deletions(-) create mode 100644 src/components/QuizWizardScreen.tsx diff --git a/index.html b/index.html index 9f04ede..5373fd4 100644 --- a/index.html +++ b/index.html @@ -7,6 +7,9 @@ Soc Ops - Social Bingo + + +
diff --git a/src/App.tsx b/src/App.tsx index 36c4815..7e4b3f7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,9 +1,13 @@ +import { useState } from 'react'; import { useBingoGame } from './hooks/useBingoGame'; import { StartScreen } from './components/StartScreen'; import { GameScreen } from './components/GameScreen'; import { BingoModal } from './components/BingoModal'; +import { QuizWizardScreen } from './components/QuizWizardScreen'; function App() { + const [quizWizardActive, setQuizWizardActive] = useState(false); + const { gameState, board, @@ -15,8 +19,17 @@ function App() { dismissModal, } = useBingoGame(); + if (quizWizardActive) { + return setQuizWizardActive(false)} />; + } + if (gameState === 'start') { - return ; + return ( + setQuizWizardActive(true)} + /> + ); } return ( diff --git a/src/components/QuizWizardScreen.tsx b/src/components/QuizWizardScreen.tsx new file mode 100644 index 0000000..d157998 --- /dev/null +++ b/src/components/QuizWizardScreen.tsx @@ -0,0 +1,230 @@ +import { useState, useMemo, useRef, useEffect } from 'react'; +import { questions } from '../data/questions'; + +interface QuizWizardScreenProps { + onReset: () => void; +} + +function shuffleArray(arr: T[]): T[] { + const a = [...arr]; + for (let i = a.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [a[i], a[j]] = [a[j], a[i]]; + } + return a; +} + +export function QuizWizardScreen({ onReset }: QuizWizardScreenProps) { + const shuffled = useMemo(() => shuffleArray(questions), []); + const total = shuffled.length; + + const [currentIndex, setCurrentIndex] = useState(0); + const [foundCount, setFoundCount] = useState(0); + const [done, setDone] = useState(false); + const [animKey, setAnimKey] = useState(0); + const cardRef = useRef(null); + + // Animate card in on each question change + useEffect(() => { + const el = cardRef.current; + if (!el) return; + el.style.animation = 'none'; + void el.offsetHeight; // reflow + el.style.animation = 'wiz-slide-in 0.28s ease-out both'; + }, [animKey]); + + const advance = (found: boolean) => { + if (found) setFoundCount((c) => c + 1); + if (currentIndex + 1 >= total) { + setDone(true); + } else { + setCurrentIndex((i) => i + 1); + setAnimKey((k) => k + 1); + } + }; + + const progressPct = Math.round((currentIndex / total) * 100); + const stepLabel = String(currentIndex + 1).padStart(2, '0'); + const totalLabel = String(total).padStart(2, '0'); + + if (done) { + return ( +
+
+

+ Finished! +

+

+ {foundCount} +

+

+ out of {total} people found +

+ +
+
+ ); + } + + return ( +
+ {/* Header */} +
+ + + + Quiz Wizard + + + + {stepLabel}/{totalLabel} + +
+ + {/* Progress bar */} +
+
+
+ + {/* Body */} +
+ + {/* Step counter */} +
+ + {stepLabel} + + + of {totalLabel} + +
+ + {/* Question card */} +
+

+ {shuffled[currentIndex]} +

+
+ + {/* Instruction */} +

+ Find someone who… +

+ + {/* Action buttons */} +
+ + +
+ + {/* Found counter */} +

+ {foundCount} found so far +

+
+
+ ); +} diff --git a/src/components/StartScreen.tsx b/src/components/StartScreen.tsx index ee2a83d..1e773e1 100644 --- a/src/components/StartScreen.tsx +++ b/src/components/StartScreen.tsx @@ -1,29 +1,42 @@ interface StartScreenProps { - onStart: () => void; + onStartBingo: () => void; + onStartQuizWizard: () => void; } -export function StartScreen({ onStart }: StartScreenProps) { +export function StartScreen({ onStartBingo, onStartQuizWizard }: StartScreenProps) { return (
-
+

Soc Ops

Social Bingo

- -
-

How to play

-
    -
  • • Find people who match the questions
  • -
  • • Tap a square when you find a match
  • -
  • • Get 5 in a row to win!
  • -
+ +

Choose a mode

+ +
+ + +
- +

Find people who match each question

); diff --git a/src/index.css b/src/index.css index d110eaa..d28983f 100644 --- a/src/index.css +++ b/src/index.css @@ -6,6 +6,14 @@ --color-marked: #dcfce7; --color-marked-border: #22c55e; --color-bingo: #fbbf24; + + /* Quiz Wizard theme */ + --color-wiz-bg: #f0ebe0; + --color-wiz-ink: #0d0d0d; + --color-wiz-red: #c9281a; + --color-wiz-card: #ffffff; + --font-wiz-display: 'Unbounded', sans-serif; + --font-wiz-body: 'Courier Prime', monospace; } html, @@ -19,3 +27,14 @@ body { font-family: system-ui, -apple-system, sans-serif; -webkit-tap-highlight-color: transparent; } + +@keyframes wiz-slide-in { + from { opacity: 0; transform: translateY(16px); } + to { opacity: 1; transform: translateY(0); } +} + +@keyframes wiz-pop { + 0% { transform: scale(1); } + 40% { transform: scale(1.06); } + 100% { transform: scale(1); } +}