mirror of
https://github.com/andwati/copilot-dev-days.git
synced 2026-10-18 18:22:53 +00:00
Initial commit
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
import type { BingoSquareData } from '../types';
|
||||
import { BingoSquare } from './BingoSquare';
|
||||
|
||||
interface BingoBoardProps {
|
||||
board: BingoSquareData[];
|
||||
winningSquareIds: Set<number>;
|
||||
onSquareClick: (squareId: number) => void;
|
||||
}
|
||||
|
||||
export function BingoBoard({ board, winningSquareIds, onSquareClick }: BingoBoardProps) {
|
||||
return (
|
||||
<div className="grid grid-cols-5 gap-1 w-full max-w-md mx-auto aspect-square">
|
||||
{board.map((square) => (
|
||||
<BingoSquare
|
||||
key={square.id}
|
||||
square={square}
|
||||
isWinning={winningSquareIds.has(square.id)}
|
||||
onClick={() => onSquareClick(square.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
interface BingoModalProps {
|
||||
onDismiss: () => void;
|
||||
}
|
||||
|
||||
export function BingoModal({ onDismiss }: BingoModalProps) {
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center p-4 z-50">
|
||||
<div className="bg-white rounded-xl p-6 max-w-xs w-full text-center shadow-xl animate-[bounce_0.5s_ease-out]">
|
||||
<div className="text-5xl mb-4">🎉</div>
|
||||
<h2 className="text-3xl font-bold text-amber-500 mb-2">BINGO!</h2>
|
||||
<p className="text-gray-600 mb-6">You completed a line!</p>
|
||||
|
||||
<button
|
||||
onClick={onDismiss}
|
||||
className="w-full bg-accent text-white font-semibold py-3 px-6 rounded-lg active:bg-accent-light transition-colors"
|
||||
>
|
||||
Keep Playing
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { BingoSquareData } from '../types';
|
||||
|
||||
interface BingoSquareProps {
|
||||
square: BingoSquareData;
|
||||
isWinning: boolean;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export function BingoSquare({ square, isWinning, onClick }: BingoSquareProps) {
|
||||
const baseClasses =
|
||||
'relative flex items-center justify-center p-1 text-center border border-gray-300 rounded transition-all duration-150 select-none min-h-[60px] text-xs leading-tight';
|
||||
|
||||
const stateClasses = square.isMarked
|
||||
? isWinning
|
||||
? 'bg-amber-200 border-amber-400 text-amber-900'
|
||||
: 'bg-marked border-marked-border text-green-800'
|
||||
: 'bg-white text-gray-700 active:bg-gray-100';
|
||||
|
||||
const freeSpaceClasses = square.isFreeSpace ? 'font-bold text-sm' : '';
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
disabled={square.isFreeSpace}
|
||||
className={`${baseClasses} ${stateClasses} ${freeSpaceClasses}`}
|
||||
aria-pressed={square.isMarked}
|
||||
aria-label={square.isFreeSpace ? 'Free space' : square.text}
|
||||
>
|
||||
<span className="wrap-break-word hyphens-auto">{square.text}</span>
|
||||
{square.isMarked && !square.isFreeSpace && (
|
||||
<span className="absolute top-0.5 right-0.5 text-green-600 text-xs">✓</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import type { BingoSquareData } from '../types';
|
||||
import { BingoBoard } from './BingoBoard';
|
||||
|
||||
interface GameScreenProps {
|
||||
board: BingoSquareData[];
|
||||
winningSquareIds: Set<number>;
|
||||
hasBingo: boolean;
|
||||
onSquareClick: (squareId: number) => void;
|
||||
onReset: () => void;
|
||||
}
|
||||
|
||||
export function GameScreen({
|
||||
board,
|
||||
winningSquareIds,
|
||||
hasBingo,
|
||||
onSquareClick,
|
||||
onReset,
|
||||
}: GameScreenProps) {
|
||||
return (
|
||||
<div className="flex flex-col min-h-full bg-gray-50">
|
||||
{/* Header */}
|
||||
<header className="flex items-center justify-between p-3 bg-white border-b border-gray-200">
|
||||
<button
|
||||
onClick={onReset}
|
||||
className="text-gray-500 text-sm px-3 py-1.5 rounded active:bg-gray-100"
|
||||
>
|
||||
← Back
|
||||
</button>
|
||||
<h1 className="font-bold text-gray-900">Bingo Mixer</h1>
|
||||
<div className="w-16"></div>
|
||||
</header>
|
||||
|
||||
{/* Instructions */}
|
||||
<p className="text-center text-gray-500 text-sm py-2 px-4">
|
||||
Tap a square when you find someone who matches it.
|
||||
</p>
|
||||
|
||||
{/* Bingo indicator */}
|
||||
{hasBingo && (
|
||||
<div className="bg-amber-100 text-amber-800 text-center py-2 font-semibold text-sm">
|
||||
🎉 BINGO! You got a line!
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Board */}
|
||||
<div className="flex-1 flex items-center justify-center p-3">
|
||||
<BingoBoard
|
||||
board={board}
|
||||
winningSquareIds={winningSquareIds}
|
||||
onSquareClick={onSquareClick}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
interface StartScreenProps {
|
||||
onStart: () => void;
|
||||
}
|
||||
|
||||
export function StartScreen({ onStart }: StartScreenProps) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-full p-6 bg-gray-50">
|
||||
<div className="text-center max-w-sm">
|
||||
<h1 className="text-4xl font-bold text-gray-900 mb-2">Bingo Mixer</h1>
|
||||
<p className="text-lg text-gray-600 mb-8">Find your people!</p>
|
||||
|
||||
<div className="bg-white rounded-lg p-6 shadow-sm border border-gray-200 mb-8">
|
||||
<h2 className="font-semibold text-gray-800 mb-3">How to play</h2>
|
||||
<ul className="text-left text-gray-600 text-sm space-y-2">
|
||||
<li>• Find people who match the questions</li>
|
||||
<li>• Tap a square when you find a match</li>
|
||||
<li>• Get 5 in a row to win!</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={onStart}
|
||||
className="w-full bg-accent text-white font-semibold py-4 px-8 rounded-lg text-lg active:bg-accent-light transition-colors"
|
||||
>
|
||||
Start Game
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user