Rebrand to kaboot

This commit is contained in:
Joey Yakimowich-Payne 2026-01-13 10:59:50 -07:00
commit 8a8ec9bc0e
No known key found for this signature in database
GPG key ID: DDF6AF5B21B407D4
8 changed files with 59 additions and 27 deletions

View file

@ -55,7 +55,8 @@ function App() {
currentCorrectShape, currentCorrectShape,
selectedOption, selectedOption,
currentPlayerScore, currentPlayerScore,
currentStreak currentStreak,
currentPlayerId
} = useGame(); } = useGame();
const currentQ = quiz?.questions[currentQuestionIndex]; const currentQ = quiz?.questions[currentQuestionIndex];
@ -90,7 +91,7 @@ function App() {
{gameState === 'LOBBY' ? ( {gameState === 'LOBBY' ? (
<Lobby <Lobby
quizTitle={quiz?.title || 'OpenHoot'} quizTitle={quiz?.title || 'Kaboot'}
players={players} players={players}
gamePin={gamePin} gamePin={gamePin}
role={role} role={role}
@ -138,6 +139,7 @@ function App() {
players={players} players={players}
onNext={nextQuestion} onNext={nextQuestion}
isHost={role === 'HOST'} isHost={role === 'HOST'}
currentPlayerId={currentPlayerId}
/> />
) : null} ) : null}

View file

@ -39,7 +39,7 @@ export const Landing: React.FC<LandingProps> = ({ onGenerate, onCreateManual, on
<BrainCircuit size={48} className="text-white" /> <BrainCircuit size={48} className="text-white" />
</div> </div>
</div> </div>
<h1 className="text-5xl font-black mb-2 text-[#46178f] tracking-tight">OpenHoot</h1> <h1 className="text-5xl font-black mb-2 text-[#46178f] tracking-tight">Kaboot</h1>
<p className="text-gray-500 font-bold mb-6">The AI Quiz Party</p> <p className="text-gray-500 font-bold mb-6">The AI Quiz Party</p>
<div className="flex bg-gray-100 p-2 rounded-2xl mb-8"> <div className="flex bg-gray-100 p-2 rounded-2xl mb-8">

View file

@ -8,10 +8,15 @@ interface ScoreboardProps {
players: Player[]; players: Player[];
onNext: () => void; onNext: () => void;
isHost: boolean; isHost: boolean;
currentPlayerId: string | null;
} }
export const Scoreboard: React.FC<ScoreboardProps> = ({ players, onNext, isHost }) => { export const Scoreboard: React.FC<ScoreboardProps> = ({ players, onNext, isHost, currentPlayerId }) => {
const sortedPlayers = [...players].sort((a, b) => b.score - a.score).slice(0, 5); const playersWithDisplayName = players.map(p => ({
...p,
displayName: p.id === currentPlayerId ? `${p.name} (You)` : p.name
}));
const sortedPlayers = [...playersWithDisplayName].sort((a, b) => b.score - a.score).slice(0, 5);
return ( return (
<div className="flex flex-col h-screen p-8"> <div className="flex flex-col h-screen p-8">
@ -29,7 +34,7 @@ export const Scoreboard: React.FC<ScoreboardProps> = ({ players, onNext, isHost
<XAxis type="number" hide /> <XAxis type="number" hide />
<YAxis <YAxis
type="category" type="category"
dataKey="name" dataKey="displayName"
tick={{ fontSize: 24, fontWeight: 800, fill: '#333', fontFamily: 'Fredoka' }} tick={{ fontSize: 24, fontWeight: 800, fill: '#333', fontFamily: 'Fredoka' }}
width={200} width={200}
tickLine={false} tickLine={false}
@ -39,7 +44,7 @@ export const Scoreboard: React.FC<ScoreboardProps> = ({ players, onNext, isHost
{sortedPlayers.map((entry, index) => ( {sortedPlayers.map((entry, index) => (
<Cell <Cell
key={`cell-${index}`} key={`cell-${index}`}
fill={entry.id.startsWith('host') ? '#46178f' : '#8884d8'} fill={entry.color}
className="filter drop-shadow-md" className="filter drop-shadow-md"
/> />
))} ))}

View file

@ -23,3 +23,18 @@ export const BOT_NAMES = [
export const QUESTION_TIME = 20; // seconds export const QUESTION_TIME = 20; // seconds
export const POINTS_PER_QUESTION = 1000; export const POINTS_PER_QUESTION = 1000;
export const PLAYER_COLORS = [
'#46178f',
'#e21b3c',
'#1368ce',
'#26890c',
'#ffa602',
'#d89e00',
'#0aa3a3',
'#b8008a',
'#6a4c93',
'#ff6b6b',
'#4ecdc4',
'#45b7d1',
];

View file

@ -1,7 +1,7 @@
import { useState, useEffect, useRef, useCallback } from 'react'; import { useState, useEffect, useRef, useCallback } from 'react';
import { Quiz, Player, GameState, GameRole, NetworkMessage, AnswerOption, Question } from '../types'; import { Quiz, Player, GameState, GameRole, NetworkMessage, AnswerOption, Question } from '../types';
import { generateQuiz } from '../services/geminiService'; import { generateQuiz } from '../services/geminiService';
import { POINTS_PER_QUESTION, QUESTION_TIME } from '../constants'; import { POINTS_PER_QUESTION, QUESTION_TIME, PLAYER_COLORS } from '../constants';
import { Peer, DataConnection } from 'peerjs'; import { Peer, DataConnection } from 'peerjs';
export const useGame = () => { export const useGame = () => {
@ -19,6 +19,8 @@ export const useGame = () => {
const [selectedOption, setSelectedOption] = useState<AnswerOption | null>(null); const [selectedOption, setSelectedOption] = useState<AnswerOption | null>(null);
const [currentPlayerScore, setCurrentPlayerScore] = useState(0); const [currentPlayerScore, setCurrentPlayerScore] = useState(0);
const [currentStreak, setCurrentStreak] = useState(0); const [currentStreak, setCurrentStreak] = useState(0);
const [currentPlayerId, setCurrentPlayerId] = useState<string | null>(null);
const [currentPlayerName, setCurrentPlayerName] = useState<string | null>(null);
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null); const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
const peerRef = useRef<Peer | null>(null); const peerRef = useRef<Peer | null>(null);
@ -71,20 +73,23 @@ export const useGame = () => {
const pin = generateGamePin(); const pin = generateGamePin();
setGamePin(pin); setGamePin(pin);
const peer = new Peer(`openhoot-${pin}`); const peer = new Peer(`kaboot-${pin}`);
peerRef.current = peer; peerRef.current = peer;
peer.on('open', (id) => { peer.on('open', (id) => {
const hostPlayer: Player = { const hostPlayer: Player = {
id: 'host', id: 'host',
name: 'Host (You)', name: 'Host',
score: 0, score: 0,
streak: 0, streak: 0,
lastAnswerCorrect: null, lastAnswerCorrect: null,
isBot: false, isBot: false,
avatarSeed: Math.random() avatarSeed: Math.random(),
color: PLAYER_COLORS[0]
}; };
setPlayers([hostPlayer]); setPlayers([hostPlayer]);
setCurrentPlayerId('host');
setCurrentPlayerName('Host');
setGameState('LOBBY'); setGameState('LOBBY');
}); });
@ -103,21 +108,23 @@ export const useGame = () => {
const handleHostData = (conn: DataConnection, data: NetworkMessage) => { const handleHostData = (conn: DataConnection, data: NetworkMessage) => {
if (data.type === 'JOIN') { if (data.type === 'JOIN') {
const newPlayer: Player = {
id: conn.peer,
name: data.payload.name,
score: 0,
streak: 0,
lastAnswerCorrect: null,
isBot: false,
avatarSeed: Math.random()
};
setPlayers(prev => { setPlayers(prev => {
if (prev.find(p => p.id === newPlayer.id)) return prev; if (prev.find(p => p.id === conn.peer)) return prev;
const colorIndex = prev.length % PLAYER_COLORS.length;
const newPlayer: Player = {
id: conn.peer,
name: data.payload.name,
score: 0,
streak: 0,
lastAnswerCorrect: null,
isBot: false,
avatarSeed: Math.random(),
color: PLAYER_COLORS[colorIndex]
};
return [...prev, newPlayer]; return [...prev, newPlayer];
}); });
connectionsRef.current.set(conn.peer, conn); connectionsRef.current.set(conn.peer, conn);
conn.send({ type: 'WELCOME', payload: { playerId: conn.peer, quizTitle: 'OpenHoot', players: [] } }); conn.send({ type: 'WELCOME', payload: { playerId: conn.peer, quizTitle: 'Kaboot', players: [] } });
} }
if (data.type === 'ANSWER') { if (data.type === 'ANSWER') {
@ -247,11 +254,13 @@ export const useGame = () => {
setRole('CLIENT'); setRole('CLIENT');
setError(null); setError(null);
setGamePin(pin); setGamePin(pin);
setCurrentPlayerName(name);
const peer = new Peer(); const peer = new Peer();
peerRef.current = peer; peerRef.current = peer;
peer.on('open', () => { peer.on('open', (id) => {
const conn = peer.connect(`openhoot-${pin}`); setCurrentPlayerId(id);
const conn = peer.connect(`kaboot-${pin}`);
hostConnectionRef.current = conn; hostConnectionRef.current = conn;
conn.on('open', () => { conn.on('open', () => {
conn.send({ type: 'JOIN', payload: { name } }); conn.send({ type: 'JOIN', payload: { name } });
@ -369,7 +378,7 @@ export const useGame = () => {
}, []); }, []);
return { return {
role, gameState, quiz, players, currentQuestionIndex, timeLeft, error, gamePin, hasAnswered, lastPointsEarned, currentCorrectShape, selectedOption, currentPlayerScore, currentStreak, role, gameState, quiz, players, currentQuestionIndex, timeLeft, error, gamePin, hasAnswered, lastPointsEarned, currentCorrectShape, selectedOption, currentPlayerScore, currentStreak, currentPlayerId,
startQuizGen, startManualCreation, finalizeManualQuiz, joinGame, startGame: startHostGame, handleAnswer, nextQuestion startQuizGen, startManualCreation, finalizeManualQuiz, joinGame, startGame: startHostGame, handleAnswer, nextQuestion
}; };
}; };

View file

@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>OpenHoot</title> <title>Kaboot</title>
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Fredoka:wght@300;400;600;700&family=Montserrat:wght@400;700;900&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Fredoka:wght@300;400;600;700&family=Montserrat:wght@400;700;900&display=swap" rel="stylesheet">
<style> <style>

View file

@ -1,4 +1,4 @@
# OpenHoot Development Task List # Kaboot Development Task List
## Core Setup ## Core Setup
- [x] Initialize project structure (React, TypeScript, Vite-like setup) - [x] Initialize project structure (React, TypeScript, Vite-like setup)

View file

@ -39,6 +39,7 @@ export interface Player {
lastAnswerCorrect: boolean | null; lastAnswerCorrect: boolean | null;
isBot: boolean; isBot: boolean;
avatarSeed: number; avatarSeed: number;
color: string;
} }
// Network Types // Network Types