← back to sami__knowyourdeen

Function bodies 62 total

All specs Real LLM only Function bodies
shuffleArray function · typescript · L32-L39 (8 LOC)
party/server.ts
function shuffleArray<T>(array: T[]): T[] {
  const shuffled = [...array];
  for (let i = shuffled.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
  }
  return shuffled;
}
constructor method · typescript · L44-L62 (19 LOC)
party/server.ts
  constructor(readonly room: Party.Room) {
    this.state = {
      roomCode: room.id,
      hostId: "",
      phase: "lobby",
      players: [],
      maxPlayers: MAX_PLAYERS,
      questionIds: [],
      currentQuestionIndex: 0,
      currentTurnPlayerId: null,
      questionsPerRound: 10,
      selectedAnswer: null,
      showExplanation: false,
      turnDeadline: null,
      turnTimeoutSeconds: TURN_TIMEOUT_SECONDS,
      lobbyDisconnectTimes: {},
      alarmPurpose: null,
    };
  }
send method · typescript · L66-L68 (3 LOC)
party/server.ts
  send(connection: Party.Connection, msg: ServerMessage) {
    connection.send(JSON.stringify(msg));
  }
broadcast method · typescript · L70-L72 (3 LOC)
party/server.ts
  broadcast(msg: ServerMessage, without?: string[]) {
    this.room.broadcast(JSON.stringify(msg), without);
  }
getSnapshot method · typescript · L74-L90 (17 LOC)
party/server.ts
  getSnapshot(): RoomStateSnapshot {
    return {
      roomCode: this.state.roomCode,
      hostId: this.state.hostId,
      phase: this.state.phase,
      players: this.state.players,
      maxPlayers: this.state.maxPlayers,
      questionIds: this.state.questionIds,
      currentQuestionIndex: this.state.currentQuestionIndex,
      currentTurnPlayerId: this.state.currentTurnPlayerId,
      questionsPerRound: this.state.questionsPerRound,
      selectedAnswer: this.state.selectedAnswer,
      showExplanation: this.state.showExplanation,
      turnDeadline: this.state.turnDeadline,
      turnTimeoutSeconds: this.state.turnTimeoutSeconds,
    };
  }
scheduleAlarm method · typescript · L92-L95 (4 LOC)
party/server.ts
  scheduleAlarm(ms: number, purpose: RoomState['alarmPurpose']) {
    this.state.alarmPurpose = purpose;
    this.room.storage.setAlarm(Date.now() + ms);
  }
getConnectedPlayers method · typescript · L97-L99 (3 LOC)
party/server.ts
  getConnectedPlayers(): OnlinePlayer[] {
    return this.state.players.filter(p => p.connected);
  }
Repobility analyzer · published findings · https://repobility.com
getNextTurnPlayerId method · typescript · L101-L110 (10 LOC)
party/server.ts
  getNextTurnPlayerId(): string | null {
    const connected = this.getConnectedPlayers();
    if (connected.length === 0) return null;

    if (!this.state.currentTurnPlayerId) return connected[0].id;

    const currentIdx = connected.findIndex(p => p.id === this.state.currentTurnPlayerId);
    const nextIdx = (currentIdx + 1) % connected.length;
    return connected[nextIdx].id;
  }
getCurrentQuestionId method · typescript · L112-L115 (4 LOC)
party/server.ts
  getCurrentQuestionId(): number | null {
    if (this.state.currentQuestionIndex >= this.state.questionIds.length) return null;
    return this.state.questionIds[this.state.currentQuestionIndex];
  }
broadcastPresenceCount method · typescript · L123-L126 (4 LOC)
party/server.ts
  broadcastPresenceCount() {
    const count = [...this.room.getConnections()].length;
    this.room.broadcast(JSON.stringify({ type: "presence-count", count }));
  }
onConnect method · typescript · L130-L151 (22 LOC)
party/server.ts
  onConnect(connection: Party.Connection, ctx: Party.ConnectionContext) {
    if (this.isPresenceRoom) {
      const count = [...this.room.getConnections()].length;
      this.send(connection, { type: "presence-count" as any, count } as any);
      this.broadcastPresenceCount();
      return;
    }

    // Check if this is a reconnecting player
    const existing = this.state.players.find(p => p.id === connection.id);
    if (existing) {
      existing.connected = true;
      delete this.state.lobbyDisconnectTimes[connection.id];
      this.send(connection, { type: "room-state", state: this.getSnapshot() });
      this.broadcast({ type: "player-reconnected", playerId: connection.id }, [connection.id]);
      return;
    }

    // New connection — don't add to players yet, wait for 'join' message
    // But send current state so client knows the room phase
    this.send(connection, { type: "room-state", state: this.getSnapshot() });
  }
onMessage method · typescript · L153-L187 (35 LOC)
party/server.ts
  onMessage(message: string | ArrayBuffer | ArrayBufferView, sender: Party.Connection) {
    if (this.isPresenceRoom) return;
    if (typeof message !== "string") return;

    let msg: ClientMessage;
    try {
      msg = JSON.parse(message);
    } catch {
      return;
    }

    switch (msg.type) {
      case "join":
        this.handleJoin(sender, msg.name);
        break;
      case "start-game":
        this.handleStartGame(sender);
        break;
      case "answer":
        this.handleAnswer(sender, msg.letter, msg.lang);
        break;
      case "next-question":
        this.handleNextQuestion(sender);
        break;
      case "set-question-count":
        this.handleSetQuestionCount(sender, msg.count);
        break;
      case "restart-game":
        this.handleRestartGame(sender);
        break;
      case "update-name":
        this.handleUpdateName(sender, msg.name);
        break;
    }
  }
onClose method · typescript · L189-L227 (39 LOC)
party/server.ts
  onClose(connection: Party.Connection) {
    if (this.isPresenceRoom) {
      this.broadcastPresenceCount();
      return;
    }

    const player = this.state.players.find(p => p.id === connection.id);
    if (!player) return;

    player.connected = false;
    this.broadcast({ type: "player-disconnected", playerId: connection.id });

    // In lobby, start grace period before removing the player
    if (this.state.phase === "lobby") {
      this.state.lobbyDisconnectTimes[connection.id] = Date.now();
      if (this.state.alarmPurpose !== "lobby-remove") {
        this.scheduleAlarm(LOBBY_DISCONNECT_GRACE_MS, "lobby-remove");
      }
    }

    // If host left, promote next connected player
    if (connection.id === this.state.hostId) {
      const nextHost = this.getConnectedPlayers()[0];
      if (nextHost) {
        this.state.hostId = nextHost.id;
        this.broadcast({ type: "host-changed", newHostId: nextHost.id });
      }
    }

    // If it was their turn during playing, s
onAlarm method · typescript · L229-L248 (20 LOC)
party/server.ts
  onAlarm() {
    switch (this.state.alarmPurpose) {
      case "turn-timeout":
        this.handleTurnTimeout();
        break;
      case "auto-advance":
        this.advanceToNextQuestion();
        break;
      case "lobby-remove":
        this.handleLobbyRemove();
        break;
      case "room-expiry":
        // Close all connections if room expired
        for (const conn of this.room.getConnections()) {
          conn.close(1000, "Room expired");
        }
        break;
    }
    this.state.alarmPurpose = null;
  }
handleJoin method · typescript · L252-L297 (46 LOC)
party/server.ts
  handleJoin(sender: Party.Connection, name: string) {
    // Reconnection: player already in the room (stable ID matched)
    const existing = this.state.players.find(p => p.id === sender.id);
    if (existing) {
      const trimmedName = name.trim().slice(0, 20);
      if (trimmedName && trimmedName !== existing.name) {
        existing.name = trimmedName;
        this.broadcast({ type: "room-state", state: this.getSnapshot() });
      }
      return;
    }

    // New player — only allow joining in lobby phase
    if (this.state.phase !== "lobby") {
      this.send(sender, { type: "error", message: "gameInProgress" });
      return;
    }

    if (this.state.players.filter(p => p.connected).length >= this.state.maxPlayers) {
      this.send(sender, { type: "error", message: "roomFull" });
      return;
    }

    const trimmedName = name.trim().slice(0, 20) || `Player ${this.state.players.length + 1}`;
    const player: OnlinePlayer = {
      id: sender.id,
      name: trimmedName,
Want fix-PRs on findings? Install Repobility's GitHub App · github.com/apps/repobility-bot
handleUpdateName method · typescript · L299-L308 (10 LOC)
party/server.ts
  handleUpdateName(sender: Party.Connection, name: string) {
    const player = this.state.players.find(p => p.id === sender.id);
    if (!player) return;

    const trimmedName = name.trim().slice(0, 20);
    if (!trimmedName || trimmedName === player.name) return;

    player.name = trimmedName;
    this.broadcast({ type: "room-state", state: this.getSnapshot() });
  }
handleStartGame method · typescript · L310-L351 (42 LOC)
party/server.ts
  handleStartGame(sender: Party.Connection) {
    if (sender.id !== this.state.hostId) {
      this.send(sender, { type: "error", message: "notHost" });
      return;
    }
    if (this.state.phase !== "lobby") return;

    // Clean up disconnected players before starting
    this.state.players = this.state.players.filter(p => p.connected);
    this.state.lobbyDisconnectTimes = {};

    const connectedCount = this.getConnectedPlayers().length;
    if (connectedCount < 2) {
      this.send(sender, { type: "error", message: "needMorePlayers" });
      return;
    }

    // Shuffle all 100 question IDs and take questionsPerRound * number of players
    // (each question is answered by one player, round-robin)
    const allIds = answerKeys.map(q => q.id);
    const shuffled = shuffleArray(allIds);
    const totalQuestions = this.state.questionsPerRound * connectedCount;
    this.state.questionIds = shuffled.slice(0, Math.min(totalQuestions, shuffled.length));

    // Reset scores
    for (
handleAnswer method · typescript · L353-L391 (39 LOC)
party/server.ts
  handleAnswer(sender: Party.Connection, letter: string, lang: 'ar' | 'en') {
    if (this.state.phase !== "playing") return;
    if (sender.id !== this.state.currentTurnPlayerId) return;
    if (this.state.selectedAnswer !== null) return; // Already answered

    const questionId = this.getCurrentQuestionId();
    if (questionId === null) return;

    const answerKey = answerKeys.find(a => a.id === questionId);
    if (!answerKey) return;

    const correctAnswer = lang === 'ar' ? answerKey.arAns : answerKey.enAns;
    const isCorrect = letter === correctAnswer;

    this.state.selectedAnswer = letter;
    this.state.showExplanation = true;

    if (isCorrect) {
      const player = this.state.players.find(p => p.id === sender.id);
      if (player) player.score += 1;
    }

    // Build scores map
    const scores: Record<string, number> = {};
    for (const p of this.state.players) {
      scores[p.id] = p.score;
    }

    this.broadcast({
      type: "answer-revealed",
      lette
handleNextQuestion method · typescript · L393-L399 (7 LOC)
party/server.ts
  handleNextQuestion(sender: Party.Connection) {
    // Allow host or current turn player to advance
    if (sender.id !== this.state.hostId && sender.id !== this.state.currentTurnPlayerId) return;
    if (!this.state.showExplanation) return;

    this.advanceToNextQuestion();
  }
handleSetQuestionCount method · typescript · L401-L410 (10 LOC)
party/server.ts
  handleSetQuestionCount(sender: Party.Connection, count: number) {
    if (sender.id !== this.state.hostId) return;
    if (this.state.phase !== "lobby") return;

    const clamped = Math.max(5, Math.min(25, count));
    this.state.questionsPerRound = clamped;

    // Broadcast updated state
    this.broadcast({ type: "room-state", state: this.getSnapshot() });
  }
handleTurnTimeout method · typescript · L412-L427 (16 LOC)
party/server.ts
  handleTurnTimeout() {
    if (this.state.phase !== "playing") return;
    if (this.state.showExplanation) return;

    const playerId = this.state.currentTurnPlayerId;
    if (!playerId) return;

    // Score 0 for timeout — mark answer as null, show explanation
    this.state.selectedAnswer = null;
    this.state.showExplanation = true;

    this.broadcast({ type: "turn-timeout", playerId });

    // Auto-advance after brief pause
    this.scheduleAlarm(3000, "auto-advance");
  }
handleLobbyRemove method · typescript · L429-L468 (40 LOC)
party/server.ts
  handleLobbyRemove() {
    if (this.state.phase !== "lobby") return;

    const now = Date.now();
    const toRemove: string[] = [];
    let nextRemovalMs = Infinity;

    for (const [playerId, disconnectTime] of Object.entries(this.state.lobbyDisconnectTimes)) {
      const elapsed = now - disconnectTime;
      if (elapsed >= LOBBY_DISCONNECT_GRACE_MS) {
        toRemove.push(playerId);
      } else {
        nextRemovalMs = Math.min(nextRemovalMs, LOBBY_DISCONNECT_GRACE_MS - elapsed);
      }
    }

    for (const playerId of toRemove) {
      this.state.players = this.state.players.filter(p => p.id !== playerId);
      delete this.state.lobbyDisconnectTimes[playerId];
      this.broadcast({ type: "player-left", playerId });
    }

    // If host was removed, promote next connected player
    if (toRemove.includes(this.state.hostId)) {
      const nextHost = this.getConnectedPlayers()[0];
      if (nextHost) {
        this.state.hostId = nextHost.id;
        this.broadcast({ type: "
handleRestartGame method · typescript · L470-L496 (27 LOC)
party/server.ts
  handleRestartGame(sender: Party.Connection) {
    if (sender.id !== this.state.hostId) {
      this.send(sender, { type: "error", message: "notHost" });
      return;
    }
    if (this.state.phase !== "finished") return;

    this.state.phase = "lobby";
    this.state.questionIds = [];
    this.state.currentQuestionIndex = 0;
    this.state.currentTurnPlayerId = null;
    this.state.selectedAnswer = null;
    this.state.showExplanation = false;
    this.state.turnDeadline = null;
    this.state.alarmPurpose = null;

    for (const p of this.state.players) {
      p.score = 0;
    }

    // Remove disconnected players
    this.state.players = this.state.players.filter(p => p.connected);
    this.state.lobbyDisconnectTimes = {};

    this.broadcast({ type: "room-state", state: this.getSnapshot() });
    this.scheduleAlarm(ROOM_EXPIRY_LOBBY_MS, "room-expiry");
  }
All rows above produced by Repobility · https://repobility.com
advanceToNextQuestion method · typescript · L498-L533 (36 LOC)
party/server.ts
  advanceToNextQuestion() {
    if (this.state.phase !== "playing") return;

    this.state.currentQuestionIndex += 1;

    // Check if game is over
    if (this.state.currentQuestionIndex >= this.state.questionIds.length) {
      this.state.phase = "finished";
      this.state.currentTurnPlayerId = null;
      this.state.turnDeadline = null;

      const rankings = [...this.state.players]
        .sort((a, b) => b.score - a.score)
        .map(p => ({ id: p.id, name: p.name, score: p.score }));

      this.broadcast({ type: "game-over", rankings });
      this.scheduleAlarm(ROOM_EXPIRY_FINISHED_MS, "room-expiry");
      return;
    }

    // Next turn
    this.state.currentTurnPlayerId = this.getNextTurnPlayerId();
    this.state.selectedAnswer = null;
    this.state.showExplanation = false;

    const deadline = Date.now() + this.state.turnTimeoutSeconds * 1000;
    this.state.turnDeadline = deadline;
    this.scheduleAlarm(this.state.turnTimeoutSeconds * 1000, "turn-timeout");

    
OnlineGame function · typescript · L23-L42 (20 LOC)
src/App.tsx
function OnlineGame({ onBack }: { onBack: () => void }) {
  const { screen, disconnect } = useOnlineStore();

  const handleBack = () => {
    disconnect();
    onBack();
  };

  switch (screen) {
    case 'lobby':
      return <LobbyScreen onBack={handleBack} />;
    case 'playing':
      return <OnlinePlayingScreen />;
    case 'finished':
      return <OnlineGameOverScreen onBack={onBack} />;
    case 'setup':
    default:
      return <OnlineSetupScreen onBack={handleBack} />;
  }
}
App function · typescript · L44-L93 (50 LOC)
src/App.tsx
export default function App() {
  const [appMode, setAppMode] = useState<AppMode>(() => {
    const params = new URLSearchParams(window.location.search);
    return params.get('room') ? 'online' : 'menu';
  });
  const { lang, gameScreen, currentTurn, setGameScreen } = useGameStore();
  const { screen: onlineScreen, players, currentTurnPlayerId } = useOnlineStore();
  useWakeLock(appMode === 'local' && gameScreen === 'playing');

  const isRTL = lang === 'ar';
  const onlinePlayerIndex = players.findIndex(p => p.id === currentTurnPlayerId);
  const currentTheme =
    appMode === 'local' && gameScreen === 'playing'
      ? PLAYER_THEMES[currentTurn]
      : appMode === 'online' && onlineScreen === 'playing'
        ? PLAYER_THEMES[onlinePlayerIndex % PLAYER_THEMES.length] || PLAYER_THEMES[0]
        : PLAYER_THEMES[0];

  return (
    <main dir={isRTL ? 'rtl' : 'ltr'} className={`min-h-screen transition-colors duration-500 font-sans flex flex-col items-center ${currentTheme}`}>
      {a
ErrorBoundary class · typescript · L12-L45 (34 LOC)
src/components/ErrorBoundary.tsx
export class ErrorBoundary extends Component<Props, State> {
  state: State = { hasError: false };

  static getDerivedStateFromError(): State {
    return { hasError: true };
  }

  componentDidCatch(error: Error, info: ErrorInfo) {
    console.error('ErrorBoundary caught:', error, info);
  }

  render() {
    if (this.state.hasError) {
      return (
        <div className="min-h-screen flex items-center justify-center bg-teal-50">
          <div className="text-center p-8 bg-white/80 backdrop-blur-md rounded-3xl shadow-xl max-w-sm">
            <h1 className="text-2xl font-bold text-teal-800 mb-4">Something went wrong</h1>
            <p className="text-gray-600 mb-6">An unexpected error occurred.</p>
            <button
              onClick={() => {
                this.setState({ hasError: false });
                window.location.reload();
              }}
              className="px-6 py-3 bg-teal-600 hover:bg-teal-700 text-white rounded-xl font-bold transition-all"
           
getDerivedStateFromError method · typescript · L15-L17 (3 LOC)
src/components/ErrorBoundary.tsx
  static getDerivedStateFromError(): State {
    return { hasError: true };
  }
componentDidCatch method · typescript · L19-L21 (3 LOC)
src/components/ErrorBoundary.tsx
  componentDidCatch(error: Error, info: ErrorInfo) {
    console.error('ErrorBoundary caught:', error, info);
  }
render method · typescript · L23-L44 (22 LOC)
src/components/ErrorBoundary.tsx
  render() {
    if (this.state.hasError) {
      return (
        <div className="min-h-screen flex items-center justify-center bg-teal-50">
          <div className="text-center p-8 bg-white/80 backdrop-blur-md rounded-3xl shadow-xl max-w-sm">
            <h1 className="text-2xl font-bold text-teal-800 mb-4">Something went wrong</h1>
            <p className="text-gray-600 mb-6">An unexpected error occurred.</p>
            <button
              onClick={() => {
                this.setState({ hasError: false });
                window.location.reload();
              }}
              className="px-6 py-3 bg-teal-600 hover:bg-teal-700 text-white rounded-xl font-bold transition-all"
            >
              Reload
            </button>
          </div>
        </div>
      );
    }
    return this.props.children;
  }
Footer function · typescript · L5-L29 (25 LOC)
src/components/Footer.tsx
export function Footer() {
  const { lang, gameScreen } = useGameStore();

  return (
    <div className="w-full text-center pb-6 pt-4 px-4 opacity-90 flex flex-col items-center gap-2 z-10 transition-all duration-300">
      <p className={`text-sm font-semibold uppercase tracking-widest ${gameScreen === 'playing' ? 'text-gray-700' : 'text-teal-800'}`}>
        {t('support', lang)}
      </p>
      <a
        href="https://www.instagram.com/yaseenyouthtours/"
        target="_blank"
        rel="noopener noreferrer"
        className="group flex flex-col items-center bg-white/60 hover:bg-white/95 backdrop-blur-sm px-8 py-3 rounded-3xl shadow-sm hover:shadow-lg hover:-translate-y-1 transition-all border border-black/5"
      >
        <span className="flex items-center gap-2 font-extrabold text-gray-800 text-lg group-hover:text-teal-700 transition-colors">
          <Instagram className="w-5 h-5 text-pink-600 drop-shadow-sm" />
          Yaseen Youth
        </span>
        <span classNa
Citation: Repobility (2026). State of AI-Generated Code. https://repobility.com/research/
GameOverScreen function · typescript · L6-L54 (49 LOC)
src/components/GameOverScreen.tsx
export function GameOverScreen() {
  const { lang, players, playerScores, questionsPerPlayer, confirmReset } = useGameStore();

  const ranked = players
    .map((p, i) => ({ ...p, score: playerScores[i] }))
    .sort((a, b) => b.score - a.score);

  const topScore = ranked[0]?.score ?? 0;
  const shareMessage = t('shareScoreMsg', lang)
    .replace('{score}', String(topScore))
    .replace('{total}', String(questionsPerPlayer));

  return (
    <div className="bg-white/90 backdrop-blur-lg p-10 rounded-3xl shadow-2xl w-full max-w-lg mx-auto text-center animate-fade-in">
      <h2 className="text-5xl font-extrabold text-teal-800 mb-8">{t('gameOver', lang)}</h2>
      <div className="space-y-4 mb-10">
        {ranked.map((p, index) => (
          <div
            key={p.id}
            className={`flex justify-between items-center p-5 rounded-2xl ${
              index === 0 ? 'bg-yellow-100 border-2 border-yellow-300 scale-105 shadow-md' : 'bg-gray-50'
            }`}
          >
      
ModeSelectScreen function · typescript · L12-L84 (73 LOC)
src/components/ModeSelectScreen.tsx
export function ModeSelectScreen({ onLocal, onOnline }: ModeSelectScreenProps) {
  const { lang, toggleLang } = useGameStore();
  const onlineCount = usePresence();
  const isRTL = lang === 'ar';

  return (
    <div className="bg-white/80 backdrop-blur-md p-8 rounded-3xl shadow-xl w-full max-w-md mx-auto text-center relative">
      <button
        onClick={toggleLang}
        className="absolute top-4 right-4 flex items-center gap-2 px-3 py-1.5 bg-white/50 hover:bg-white/80 rounded-full shadow-sm backdrop-blur-sm transition-all font-medium text-sm"
        aria-label={t('switchLangLabel', lang)}
      >
        <Globe className="w-4 h-4" aria-hidden="true" />
        {t('switchLangText', lang)}
      </button>

      <p
        className="text-teal-700/60 font-medium mb-6 mt-2 tracking-widest text-lg"
        style={{ fontFamily: '"Traditional Arabic", "Amiri", "Scheherazade New", serif' }}
      >
        {t('basmala', lang)}
      </p>
      <MoonIcon className="w-20 h-20 text-teal
MoonIcon function · typescript · L1-L7 (7 LOC)
src/components/MoonIcon.tsx
export function MoonIcon({ className }: { className?: string }) {
  return (
    <svg className={className} viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
      <path d="M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z" />
    </svg>
  );
}
ConnectionStatus function · typescript · L5-L18 (14 LOC)
src/components/online/ConnectionStatus.tsx
export function ConnectionStatus() {
  const { connected, screen } = useOnlineStore();
  const { lang } = useGameStore();

  // Only show when disconnected during an active session
  if (connected || screen === 'setup') return null;

  return (
    <div className="flex items-center justify-center gap-2 px-4 py-2 rounded-xl bg-yellow-50 border border-yellow-200 text-yellow-700 text-sm font-medium mb-4 animate-pulse">
      <div className="w-2 h-2 rounded-full bg-yellow-500" />
      {t('reconnecting', lang)}
    </div>
  );
}
LobbyScreen function · typescript · L17-L211 (195 LOC)
src/components/online/LobbyScreen.tsx
export function LobbyScreen({ onBack }: LobbyScreenProps) {
  const { lang, toggleLang } = useGameStore();
  const {
    roomCode, players, hostId, myPlayerId,
    questionsPerRound, maxPlayers,
    startGame, setQuestionCount, updateName,
  } = useOnlineStore();
  const [copied, setCopied] = useState(false);
  const [editingName, setEditingName] = useState(false);
  const [nameDraft, setNameDraft] = useState('');
  const nameInputRef = useRef<HTMLInputElement>(null);
  const isRTL = lang === 'ar';
  const isHost = myPlayerId === hostId;
  const connectedCount = players.filter(p => p.connected).length;

  useEffect(() => {
    if (editingName) nameInputRef.current?.focus();
  }, [editingName]);

  const handleCopy = async () => {
    try {
      await navigator.clipboard.writeText(roomCode || '');
    } catch {
      const textarea = document.createElement('textarea');
      textarea.value = roomCode || '';
      document.body.appendChild(textarea);
      textarea.select();
      docum
OnlineGameOverScreen function · typescript · L12-L101 (90 LOC)
src/components/online/OnlineGameOverScreen.tsx
export function OnlineGameOverScreen({ onBack }: OnlineGameOverScreenProps) {
  const { lang, toggleLang } = useGameStore();
  const { rankings, myPlayerId, hostId, questionsPerRound, restartGame, disconnect } = useOnlineStore();

  const isHost = myPlayerId === hostId;

  if (!rankings || rankings.length === 0) return null;

  const myRanking = rankings.find(r => r.id === myPlayerId);
  const myScore = myRanking?.score ?? 0;
  const shareMessage = t('shareScoreMsg', lang)
    .replace('{score}', String(myScore))
    .replace('{total}', String(questionsPerRound));

  const handleLeave = () => {
    disconnect();
    onBack();
  };

  return (
    <div className="bg-white/90 backdrop-blur-lg p-10 rounded-3xl shadow-2xl w-full max-w-lg mx-auto text-center animate-fade-in">
      <div className="flex justify-end mb-4">
        <button
          onClick={toggleLang}
          className="flex items-center gap-2 px-3 py-1.5 bg-white/50 hover:bg-white/80 rounded-full shadow-sm backdrop-blur-s
OnlinePlayingScreen function · typescript · L16-L231 (216 LOC)
src/components/online/OnlinePlayingScreen.tsx
export function OnlinePlayingScreen() {
  const { lang, toggleLang } = useGameStore();
  const {
    players, scores, currentQuestionIndex, questionIds,
    currentTurnPlayerId, myPlayerId, hostId,
    selectedAnswer, showExplanation, turnDeadline, turnTimeoutSeconds,
    lastAnswerCorrect, timeoutPlayerId,
    submitAnswer, nextQuestion,
  } = useOnlineStore();

  const isRTL = lang === 'ar';
  const isMyTurn = myPlayerId === currentTurnPlayerId;
  const isHost = myPlayerId === hostId;

  const questionId = questionIds[currentQuestionIndex];
  const question = questionsDB.find(q => q.id === questionId);
  if (!question) return null;

  const qData = question[lang];
  const currentPlayer = players.find(p => p.id === currentTurnPlayerId);
  const currentPlayerIndex = players.findIndex(p => p.id === currentTurnPlayerId);
  const cardTheme = CARD_THEMES[currentPlayerIndex % CARD_THEMES.length];

  const answeredCorrectly = lastAnswerCorrect === true;
  const wasTimeout = timeoutPlayerId !
OnlineSetupScreen function · typescript · L12-L165 (154 LOC)
src/components/online/OnlineSetupScreen.tsx
export function OnlineSetupScreen({ onBack }: OnlineSetupScreenProps) {
  const { lang, toggleLang } = useGameStore();
  const { createRoom, joinRoom, error, clearError, connecting } = useOnlineStore();
  const [name, setName] = useState(() => localStorage.getItem('kyd_player_name') || '');
  const [lastRoom] = useState(() => getLastRoom());
  const [roomCode, setRoomCode] = useState(() => {
    const params = new URLSearchParams(window.location.search);
    return params.get('room')?.toUpperCase().replace(/[^A-Z0-9]/g, '').slice(0, 4) || '';
  });

  useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    if (params.get('room')) {
      window.history.replaceState({}, '', window.location.pathname);
    }
  }, []);
  const isRTL = lang === 'ar';

  const handleCreate = () => {
    if (connecting) return;
    const playerName = name.trim() || (lang === 'ar' ? 'لاعب' : 'Player');
    localStorage.setItem('kyd_player_name', playerName);
    createRoom(playerN
Repobility analyzer · published findings · https://repobility.com
TurnTimer function · typescript · L8-L43 (36 LOC)
src/components/online/TurnTimer.tsx
export function TurnTimer({ deadline, totalSeconds }: TurnTimerProps) {
  const [remaining, setRemaining] = useState(totalSeconds);

  useEffect(() => {
    const update = () => {
      const ms = deadline - Date.now();
      setRemaining(Math.max(0, Math.ceil(ms / 1000)));
    };
    update();
    const interval = setInterval(update, 200);
    return () => clearInterval(interval);
  }, [deadline]);

  const fraction = remaining / totalSeconds;
  const isUrgent = remaining <= 5;
  const isWarning = remaining <= 15 && !isUrgent;

  const barColor = isUrgent ? 'bg-red-500' : isWarning ? 'bg-yellow-500' : 'bg-teal-500';
  const textColor = isUrgent ? 'text-red-600' : isWarning ? 'text-yellow-600' : 'text-teal-600';

  return (
    <div className="w-full">
      <div className="flex justify-end mb-1">
        <span className={`text-sm font-bold tabular-nums ${textColor} ${isUrgent ? 'animate-pulse' : ''}`}>
          {remaining}s
        </span>
      </div>
      <div className="w-full h-
PlayingScreen function · typescript · L12-L147 (136 LOC)
src/components/PlayingScreen.tsx
export function PlayingScreen() {
  const {
    lang, currentTurn, players, playerScores,
    questionIndices, questionsPerPlayer, selectedAnswer,
    showExplanation, handleAnswerSelect, nextTurn, activeQuestion: getActiveQuestion,
  } = useGameStore();

  const activeQuestion = getActiveQuestion();
  if (!activeQuestion) return null;

  const isRTL = lang === 'ar';
  const cardTheme = CARD_THEMES[currentTurn];
  const qData = activeQuestion[lang];
  const isCorrect = selectedAnswer === qData.ans;

  return (
    <div className="w-full max-w-2xl mx-auto flex flex-col h-full animate-fade-in">
      <div className="flex justify-between items-end mb-6">
        <div className={`px-6 py-3 rounded-2xl shadow-sm border-2 ${cardTheme} flex items-center gap-3 backdrop-blur-md`}>
          <div className="w-10 h-10 rounded-full bg-white flex items-center justify-center font-bold text-xl shadow-sm">
            {currentTurn + 1}
          </div>
          <div>
            <p className="text-xs
ResetModal function · typescript · L6-L88 (83 LOC)
src/components/ResetModal.tsx
export function ResetModal() {
  const { lang, showResetModal, setShowResetModal, confirmReset } = useGameStore();
  const dialogRef = useRef<HTMLDivElement>(null);
  const previousFocusRef = useRef<HTMLElement | null>(null);

  useEffect(() => {
    if (!showResetModal) return;

    previousFocusRef.current = document.activeElement as HTMLElement;

    // Focus the dialog
    const timer = setTimeout(() => {
      dialogRef.current?.focus();
    }, 50);

    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key === 'Escape') {
        setShowResetModal(false);
        return;
      }
      // Focus trap
      if (e.key === 'Tab' && dialogRef.current) {
        const focusable = dialogRef.current.querySelectorAll<HTMLElement>(
          'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
        );
        const first = focusable[0];
        const last = focusable[focusable.length - 1];
        if (e.shiftKey && document.activeElement === first) {
       
ScoreCardContent function · typescript · L31-L91 (61 LOC)
src/components/ScoreCard.tsx
function ScoreCardContent({ rankings, totalQuestions, lang }: Omit<ShareScoreCardProps, 'shareMessage'>) {
  const isRTL = lang === 'ar';

  return (
    <div
      style={{
        width: 400,
        padding: 32,
        background: 'linear-gradient(135deg, #0d9488 0%, #0891b2 100%)',
        borderRadius: 24,
        fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
        direction: isRTL ? 'rtl' : 'ltr',
      }}
    >
      <div style={{ textAlign: 'center', marginBottom: 24 }}>
        <svg width="48" height="48" viewBox="0 0 24 24" fill="white" style={{ margin: '0 auto 8px', display: 'block' }}>
          <path d="M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z" />
        </svg>
        <h2 style={{ color: 'white', fontSize: 24, fontWeight: 800, margin: 0 }}>
          {t('title', lang)}
        </h2>
      </div>

   
ShareScoreCard function · typescript · L93-L167 (75 LOC)
src/components/ScoreCard.tsx
export function ShareScoreCard({ rankings, totalQuestions, lang, shareMessage }: ShareScoreCardProps) {
  const cardRef = useRef<HTMLDivElement>(null);
  const [copied, setCopied] = useState(false);
  const [busy, setBusy] = useState(false);

  const handleCopyImage = async () => {
    if (!cardRef.current || busy) return;
    setBusy(true);
    try {
      const dataUrl = await toPng(cardRef.current, { quality: 0.95, pixelRatio: 2 });
      const res = await fetch(dataUrl);
      const blob = await res.blob();
      await navigator.clipboard.write([
        new ClipboardItem({ 'image/png': blob }),
      ]);
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    } catch {
      // Fallback: download if clipboard write fails
      try {
        const dataUrl = await toPng(cardRef.current, { quality: 0.95, pixelRatio: 2 });
        const a = document.createElement('a');
        a.href = dataUrl;
        a.download = 'knowyourdeen-score.png';
        document.body.app
SetupScreen function · typescript · L12-L107 (96 LOC)
src/components/SetupScreen.tsx
export function SetupScreen({ onBack }: SetupScreenProps) {
  const { lang, toggleLang, players, handlePlayerCountChange, updatePlayerName, startGame } = useGameStore();
  const isRTL = lang === 'ar';

  return (
    <div className="bg-white/80 backdrop-blur-md p-8 rounded-3xl shadow-xl w-full max-w-md mx-auto text-center">
      <div className="flex items-center justify-between mb-6">
        <button
          onClick={onBack}
          className="flex items-center gap-2 text-teal-600 hover:text-teal-800 font-medium transition-colors"
        >
          <ArrowLeft className={`w-5 h-5 ${isRTL ? 'rotate-180' : ''}`} />
          {t('back', lang)}
        </button>
        <button
          onClick={toggleLang}
          className="flex items-center gap-2 px-3 py-1.5 bg-white/50 hover:bg-white/80 rounded-full shadow-sm backdrop-blur-sm transition-all font-medium text-sm"
          aria-label={t('switchLangLabel', lang)}
        >
          <Globe className="w-4 h-4" aria-hidden="true" /
SharePopover function · typescript · L12-L88 (77 LOC)
src/components/SharePopover.tsx
export function SharePopover() {
  const { lang } = useGameStore();
  const [open, setOpen] = useState(false);
  const [copied, setCopied] = useState(false);
  const popoverRef = useRef<HTMLDivElement>(null);

  const message = t('shareAppMsg', lang);

  useEffect(() => {
    if (!open) return;
    const handleClickOutside = (e: MouseEvent) => {
      if (popoverRef.current && !popoverRef.current.contains(e.target as Node)) {
        setOpen(false);
      }
    };
    document.addEventListener('mousedown', handleClickOutside);
    return () => document.removeEventListener('mousedown', handleClickOutside);
  }, [open]);

  const handleCopy = async () => {
    try {
      await navigator.clipboard.writeText(message);
    } catch {
      const textarea = document.createElement('textarea');
      textarea.value = message;
      document.body.appendChild(textarea);
      textarea.select();
      document.execCommand('copy');
      document.body.removeChild(textarea);
    }
    setCopied(tru
TopBar function · typescript · L9-L47 (39 LOC)
src/components/TopBar.tsx
export function TopBar() {
  const { lang, gameScreen, currentTurn, toggleLang, setGameScreen, setShowResetModal } = useGameStore();

  return (
    <div className="w-full max-w-4xl p-4 flex justify-between items-center z-10">
      <div className="flex items-center gap-2">
        <MoonIcon className={`w-8 h-8 ${gameScreen === 'playing' ? MOON_COLORS[currentTurn] : 'text-teal-600'}`} />
        <h1 className="text-xl font-bold opacity-80">{t('title', lang)}</h1>
      </div>
      <div className="flex items-center gap-2">
        <button
          onClick={() => setGameScreen(gameScreen === 'library' ? 'setup' : 'library')}
          className="p-2 bg-white/50 hover:bg-white/80 rounded-full shadow-sm backdrop-blur-sm transition-all text-teal-600"
          title={t('libraryLabel', lang)}
          aria-label={t('libraryLabel', lang)}
        >
          <BookOpen className="w-5 h-5" aria-hidden="true" />
        </button>
        <SharePopover />
        <button
          onClick={() 
Want fix-PRs on findings? Install Repobility's GitHub App · github.com/apps/repobility-bot
shuffleArray function · typescript · L42-L49 (8 LOC)
src/hooks/useGameStore.ts
function shuffleArray<T>(array: T[]): T[] {
  const shuffled = [...array];
  for (let i = shuffled.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
  }
  return shuffled;
}
generateRoomCode function · typescript · L7-L14 (8 LOC)
src/hooks/useOnlineStore.ts
function generateRoomCode(): string {
  const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
  let code = '';
  for (let i = 0; i < 4; i++) {
    code += chars[Math.floor(Math.random() * chars.length)];
  }
  return code;
}
getOrCreatePlayerId function · typescript · L18-L30 (13 LOC)
src/hooks/useOnlineStore.ts
function getOrCreatePlayerId(roomCode: string): string {
  const key = PLAYER_ID_PREFIX + roomCode;
  const stored = localStorage.getItem(key);
  if (stored) {
    try {
      const { id, ts } = JSON.parse(stored);
      if (Date.now() - ts < 60 * 60 * 1000) return id;
    } catch { /* ignore corrupt entries */ }
  }
  const id = crypto.randomUUID().replace(/-/g, '').slice(0, 16);
  localStorage.setItem(key, JSON.stringify({ id, ts: Date.now() }));
  return id;
}
page 1 / 2next ›