/* =========================================================================
   CISCO ARENA — Screens: Join, Lobby, Quiz, PhaseTransition, Triage, Wrap
   ========================================================================= */

const { useState: uS, useEffect: uE, useMemo: uM, useRef: uR } = React;

function JoinScreen({ theme, onJoin, me, leaderboard, connected }) {
  const [step, setStep] = uS("code"); // "code" or "register"
  const [code, setCode] = uS(connected ? (Session.getGameId() || "") : "");
  const [name, setName] = uS(connected ? me.name : "");
  const [email, setEmail] = uS(connected ? (Session.getEmail() || "") : "");
  const [busy, setBusy] = uS(false);
  const [error, setError] = uS("");
  const [gameName, setGameName] = uS("");
  // DOM data-theme is the source of truth (set in handleCodeSubmit before step transition)
  const themeId = document.documentElement.getAttribute("data-theme") || "cisco";
  const themeLabelMap = {
    cisco: "Cisco Arena",
    cup: "World Cup",
    gp: "Grand Prix Series",
    nfl: "Gridiron League",
    realm: "Realm Wars",
  };
  const themeMoodMap = {
    cisco: "Test your knowledge and compete with your peers.",
    cup: "Matchday atmosphere — trophy-gold highlights and stadium-night depth.",
    gp: "Race-weekend intensity — dramatic contrast and a fast-lane visual pulse.",
    nfl: "Prime-time kickoff energy — bold lights and a big-game presentation feel.",
    realm: "A dramatic fantasy arena with ceremonial glow and house-banner atmosphere.",
  };
  const activeThemeLabel = themeLabelMap[themeId] || "Cisco Arena";
  const activeThemeMood = themeMoodMap[themeId] || themeMoodMap.cisco;

  // If already connected, skip to registration step
  uE(() => {
    if (connected) setStep("register");
  }, [connected]);

  // Step 1: Validate code and fetch game config
  const handleCodeSubmit = async (e) => {
    if (e) e.preventDefault();
    const trimmed = code.trim().toUpperCase();
    if (!trimmed) { setError(tc("Enter a session code")); return; }
    setBusy(true);
    setError("");
    try {
      // Resolve join code → game_id
      let gameId = null;
      let gName = "";
      try {
        const joinRes = await fetch("/api/events/join", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ code: trimmed }),
        });
        if (joinRes.ok) {
          const joinData = await joinRes.json();
          gameId = joinData.game_id;
          gName = joinData.name || "";
        }
      } catch (_) {}

      // Fallback: try as direct game_id
      if (!gameId) {
        const res = await fetch("/api/games/" + encodeURIComponent(trimmed));
        if (res.ok) { gameId = trimmed; const d = await res.json(); gName = d.name || ""; }
      }

      if (!gameId) { setError(tc("Session not found — check your code")); setBusy(false); return; }

      // Fetch game config for theme
      try {
        const gRes = await fetch("/api/games/" + encodeURIComponent(gameId));
        if (gRes.ok) {
          const game = await gRes.json();
          gName = game.name || gName;
          // Apply theme immediately
          const tm = { 'f1': 'gp', 'world-cup': 'cup', 'nfl': 'nfl', 'realm': 'realm' };
          const clientTheme = tm[game.theme_id] || game.theme_id || 'cisco';
          document.documentElement.setAttribute("data-theme", clientTheme);
        }
      } catch (_) {}

      setGameName(gName);
      setCode(trimmed);
      setStep("register");
    } catch (err) {
      setError(tc("Connection error — try again"));
    }
    setBusy(false);
  };

  // Step 2: Register and join
  const handleRegister = async (e) => {
    if (e) e.preventDefault();
    if (!name.trim()) { setError(tc("Enter your name")); return; }
    if (!email.trim()) { setError(tc("Enter your email")); return; }
    setError("");
    setBusy(true);
    await onJoin(name, email, "", code);
    setBusy(false);
  };

  // Demo mode
  const handleDemo = () => {
    onJoin("", "", "", "");
  };

  // ── Step 1: Code Entry ──
  if (step === "code") {
    return (
      <div className="screen arena-screen arena-screen--landing">
        <div className="arena-shell arena-shell--landing">
          <div className="arena-grid"></div>
          <div className="arena-bg-orb arena-bg-orb--one"></div>
          <div className="arena-bg-orb arena-bg-orb--two"></div>
          <div className="arena-stage arena-stage--landing">
            <section className="arena-hero" aria-hidden="true">
              <div className="arena-brand arena-brand--hero">
                <span className="arena-brand__badge">Cisco</span>
                <span className="arena-brand__mark">Arena</span>
              </div>
              <div className="arena-copy arena-copy--hero">
                 <p className="arena-eyebrow">{tc("Live player check-in")}</p>
                <h1 className="arena-title">
                   {tc("Enter the")} <em>Arena</em>
                </h1>
                 <p className="arena-subtitle">
                   {tc("Join your live event in seconds. Enter the session code from the host screen to unlock the themed experience.")}
                 </p>
              </div>
            </section>

            <section className="arena-card arena-card--landing">
              <div className="arena-card-head arena-card-head--landing">
                 <p className="arena-card-kicker">{tc("Session access")}</p>
                 <h2 className="arena-card-title">{tc("Enter your event code")}</h2>
                 <p className="arena-card-copy">{tc("Use the code shown on the main display to continue.")}</p>
              </div>

              <form className="arena-form" onSubmit={handleCodeSubmit}>
                <label className="arena-label" htmlFor="arena-code">{tc("Session code")}</label>
                <input
                  id="arena-code"
                  className="arena-input arena-input--code"
                  type="text"
                  value={code}
                  onChange={(e) => setCode(e.target.value.toUpperCase())}
                   placeholder={tc("ENTER CODE")}
                  autoComplete="off"
                  autoCapitalize="characters"
                  spellCheck="false"
                  inputMode="text"
                  maxLength="20"
                  autoFocus
                />
                <p className="arena-form-hint">{tc("Enter the code shown on the projector screen.")}</p>
                {error && <div className="arena-error">{error}</div>}
                <button className="arena-button arena-button--primary" type="submit" disabled={busy}>
                   {busy ? tc("Validating…") : tc("Continue to registration")}
                </button>
              </form>

              <div className="arena-footer">
                <button type="button" className="arena-link-button" onClick={handleDemo}>
                   {tc("Explore the experience in demo mode")}
                </button>
                <a href="/v2/organizer.html" className="arena-link-button arena-link-button--staff" style={{display:'block', marginTop:8, opacity:0.7, fontSize:'0.85em'}}>
                  🔐 {tc("Staff login")}
                </a>
              </div>
            </section>
          </div>
        </div>
      </div>
    );
  }

  // ── Step 2: Registration ──
  return (
    <div className="screen arena-screen arena-screen--theme">
      <div className="arena-shell arena-shell--theme">
        <div className="arena-grid arena-grid--theme"></div>
        <div className="arena-theme-overlay"></div>
        <div className="arena-stage arena-stage--register">
          <section className="arena-theme-hero">
            <div className="arena-brand arena-brand--theme">
              <span className="arena-brand__mark">Cisco Arena</span>
              <span className="arena-brand__line"></span>
              <span className="arena-brand__theme">{activeThemeLabel}</span>
            </div>
             <p className="arena-eyebrow">{tc("Theme loaded")}</p>
            <h1 className="arena-event-name">{gameName || "Cisco Arena"}</h1>
            <p className="arena-event-subtitle">{activeThemeMood}</p>
            <div className="arena-meta-row">
              <span className="arena-meta-chip">Session {code}</span>
              <span className="arena-meta-chip">{activeThemeLabel}</span>
            </div>
          </section>

          <section className="arena-card arena-card--register">
            <div className="arena-card-head arena-card-head--register">
              <div>
               <p className="arena-card-kicker">{tc("Player registration")}</p>
               <h2 className="arena-card-title">{tc("Claim your spot on the board")}</h2>
              </div>
              <div className="arena-code-chip" aria-label="Session code">{code}</div>
            </div>

            <div className="arena-copy arena-copy--register">
              <p className="arena-subtitle arena-subtitle--register">
                Enter your details to join <strong>{gameName || activeThemeLabel}</strong> and sync your player identity before the first round begins.
              </p>
            </div>

            <form className="arena-form" onSubmit={handleRegister}>
              <div className="arena-field">
                <label className="arena-label" htmlFor="arena-name">{tc("Display name")}</label>
                <input
                  id="arena-name"
                  className="arena-input"
                  type="text"
                  value={name}
                  onChange={(e) => setName(e.target.value)}
                   placeholder={tc("e.g. Alex Morgan")}
                  autoComplete="name"
                  autoFocus
                />
              </div>
              <div className="arena-field">
                <label className="arena-label" htmlFor="arena-email">{tc("Work email")}</label>
                <input
                  id="arena-email"
                  className="arena-input"
                  type="email"
                  value={email}
                  onChange={(e) => setEmail(e.target.value)}
                   placeholder={tc("name@cisco.com")}
                  autoComplete="email"
                />
              </div>
              {error && <div className="arena-error">{error}</div>}
              <button className="arena-button arena-button--primary" type="submit" disabled={busy}>
                 {busy ? tc("Joining…") : tc("Enter Arena →")}
              </button>
            </form>

            {!connected && (
              <div className="arena-footer arena-footer--register">
                <button type="button" className="arena-link-button" onClick={() => { setStep("code"); setError(""); }}>
                   {tc("← Change session code")}
                </button>
              </div>
            )}
          </section>
        </div>
      </div>
    </div>
  );
}

function LobbyScreen({ theme, leaderboard, me, onStart, countdownSec,
                        draftStatus, draftTeams, draftUndrafted,
                        isCaptain, isMyTurn, onDraftPick, connected }) {
  const [secs, setSecs] = uS(42);
  const [draftNow, setDraftNow] = uS(Date.now());
  const [animatedHistoryKey, setAnimatedHistoryKey] = uS("");
  const [animatedMemberKey, setAnimatedMemberKey] = uS("");
  const [showConfetti, setShowConfetti] = uS(false);
  const [yourTurnFxSeed, setYourTurnFxSeed] = uS(0);
  const prevDraftActiveRef = uR(!!(draftStatus && draftStatus.active));
  const prevIsMyTurnRef = uR(false);
  const prevLatestHistoryKeyRef = uR("");
  const historyHydratedRef = uR(false);

  // Demo-mode only: local 42s countdown → auto-start quiz
  uE(() => {
    if (connected) return;                // connected mode: server controls transitions
    const t = setInterval(() => setSecs(s => (s > 0 ? s - 1 : 0)), 1000);
    return () => clearInterval(t);
  }, [connected]);

  const draftActive = !!(draftStatus && draftStatus.active);

  uE(() => {
    if (!draftActive) return;
    const t = setInterval(() => setDraftNow(Date.now()), 100);
    return () => clearInterval(t);
  }, [draftActive]);

  const display = countdownSec !== undefined ? countdownSec : secs;
  const mm = String(Math.floor(display/60)).padStart(2,"0");
  const ss = String(display%60).padStart(2,"0");

  // Demo-mode only: auto-advance to quiz when countdown hits 0
  uE(() => {
    if (connected) return;                // connected mode: never auto-advance
    if (!draftActive && display === 0) { const t = setTimeout(onStart, 600); return () => clearTimeout(t); }
  }, [display, draftActive, connected]);

  const getInitials = (name) => (name || "?").split(" ").map(n => n[0]).join("").slice(0, 2).toUpperCase();
  const getTeamTheme = (teamId) => theme.teams.find(t => t.id === teamId) || {};
  const getTeamName = (teamId) => getTeamTheme(teamId).name || teamId || "Unassigned";
  const getTeamColor = (teamId) => getTeamTheme(teamId).color || "var(--accent)";

  const getCurrentCaptainInfo = () => {
    if (!draftStatus || !draftStatus.active) return null;
    const order = draftStatus.captain_order || [];
    const idx = draftStatus.current_pick_index || 0;
    const round = draftStatus.round || 1;
    const n = order.length;
    if (n === 0) return null;
    const captainIdx = round % 2 === 1 ? (idx % n) : ((n - 1) - (idx % n));
    const email = order[captainIdx];
    for (const [teamId, teamData] of Object.entries(draftTeams || {})) {
      const captain = teamData.captain || (teamData.members || []).find(m => m.isCaptain);
      if (captain && captain.email === email) {
        const themeTeam = theme.teams.find(t => t.id === teamId);
        return { ...captain, teamId, teamName: themeTeam?.name || teamId, teamColor: themeTeam?.color || 'var(--accent)' };
      }
    }
    return { email, teamId: '', teamName: '', teamColor: 'var(--accent)' };
  };

  const currentCaptain = getCurrentCaptainInfo();
  const draftCountdown = draftActive && draftStatus?.pick_deadline
    ? Math.max(0, draftStatus.pick_deadline - (draftNow / 1000))
    : 0;
  const draftTotal = draftStatus?.pick_duration || 15;
  const draftPct = draftTotal > 0 ? Math.max(0, Math.min(100, (draftCountdown / draftTotal) * 100)) : 0;
  const timerColor = draftPct > 60 ? "var(--success)" : (draftPct > 30 ? "var(--warning)" : "var(--danger)");
  const pickGlow = 0.45 + (0.55 * ((Math.sin(draftNow / 280) + 1) / 2));
  const history = (draftStatus?.history || []).slice(-5).reverse();
  const rawHistory = draftStatus?.history || [];
  const latestHistoryItem = rawHistory[rawHistory.length - 1] || null;
  const maxManual = draftStatus?.max_manual_rounds || 2;
  const modeLabel = (draftStatus?.round || 1) > maxManual ? "AUTO-DRAFT" : "MANUAL DRAFT";
  const isUndraftedMe = !me?.tid && (draftUndrafted || []).some(p => (p.email || p.id) === me?.id);

  const getDraftHistoryKey = (item) => {
    if (!item) return "";
    return [
      item.pick_id,
      item.player_email,
      item.player_id,
      item.player_name,
      item.team,
      item.team_id,
      item.team_name,
      item.message,
    ].filter(Boolean).join("::");
  };

  const getDraftMemberKey = (teamId, entity) => [
    teamId || "",
    entity?.email || entity?.player_email || entity?.id || entity?.player_id || entity?.name || entity?.player_name || "",
  ].join("::");

  const latestHistoryKey = getDraftHistoryKey(latestHistoryItem);
  const latestHistoryTeamId = latestHistoryItem?.team || latestHistoryItem?.team_id || latestHistoryItem?.teamId || "";
  const latestHistoryMemberKey = latestHistoryItem
    ? getDraftMemberKey(latestHistoryTeamId, latestHistoryItem)
    : "";

  uE(() => {
    if (!historyHydratedRef.current) {
      historyHydratedRef.current = true;
      prevLatestHistoryKeyRef.current = latestHistoryKey;
      return;
    }
    if (!latestHistoryKey || latestHistoryKey === prevLatestHistoryKeyRef.current) {
      prevLatestHistoryKeyRef.current = latestHistoryKey;
      return;
    }
    prevLatestHistoryKeyRef.current = latestHistoryKey;
    setAnimatedHistoryKey(latestHistoryKey);
    setAnimatedMemberKey(latestHistoryMemberKey);
    const t = setTimeout(() => {
      setAnimatedHistoryKey("");
      setAnimatedMemberKey("");
    }, 1200);
    return () => clearTimeout(t);
  }, [latestHistoryKey, latestHistoryMemberKey]);

  uE(() => {
    if (isMyTurn && !prevIsMyTurnRef.current) setYourTurnFxSeed(s => s + 1);
    prevIsMyTurnRef.current = isMyTurn;
  }, [isMyTurn]);

  uE(() => {
    const wasDraftActive = prevDraftActiveRef.current;
    if (wasDraftActive && !draftActive) {
      setShowConfetti(true);
      const t = setTimeout(() => setShowConfetti(false), 3000);
      prevDraftActiveRef.current = draftActive;
      return () => clearTimeout(t);
    }
    prevDraftActiveRef.current = draftActive;
  }, [draftActive]);

  // ── Sound integration (DraftAudio) ────────────────────────
  const soundInitRef = uR(false);
  const prevSoundDraftActive = uR(false);
  const prevSoundHistoryKey = uR("");
  const prevSoundIsMyTurn = uR(false);

  // Init audio context on first user interaction
  uE(() => {
    if (soundInitRef.current) return;
    const handler = () => {
      if (typeof DraftAudio !== "undefined") {
        DraftAudio.init(theme?.id);
        soundInitRef.current = true;
      }
      document.removeEventListener("click", handler);
      document.removeEventListener("touchstart", handler);
    };
    document.addEventListener("click", handler, { once: true });
    document.addEventListener("touchstart", handler, { once: true });
    return () => { document.removeEventListener("click", handler); document.removeEventListener("touchstart", handler); };
  }, []);

  // Set theme when it changes
  uE(() => {
    if (typeof DraftAudio !== "undefined" && theme?.id) DraftAudio.setTheme(theme.id);
  }, [theme?.id]);

  // Draft start/end sounds
  uE(() => {
    if (typeof DraftAudio === "undefined") return;
    if (draftActive && !prevSoundDraftActive.current) {
      DraftAudio.draftStart();
      setTimeout(() => DraftAudio.startCrowd(), 800);
    }
    if (!draftActive && prevSoundDraftActive.current) {
      DraftAudio.draftComplete();
      DraftAudio.stopCrowd();
    }
    prevSoundDraftActive.current = draftActive;
  }, [draftActive]);

  // Pick sounds (new history entry)
  uE(() => {
    if (typeof DraftAudio === "undefined") return;
    if (latestHistoryKey && latestHistoryKey !== prevSoundHistoryKey.current && prevSoundHistoryKey.current !== "") {
      DraftAudio.pickConfirm();
      setTimeout(() => DraftAudio.crowdCheer(), 200);
    }
    prevSoundHistoryKey.current = latestHistoryKey;
  }, [latestHistoryKey]);

  // Your turn notification sound
  uE(() => {
    if (typeof DraftAudio === "undefined") return;
    if (isMyTurn && !prevSoundIsMyTurn.current) {
      DraftAudio.yourTurn();
    }
    prevSoundIsMyTurn.current = isMyTurn;
  }, [isMyTurn]);

  // Tick countdown (last 10s of pick timer)
  uE(() => {
    if (typeof DraftAudio === "undefined" || !draftActive) return;
    if (draftCountdown <= 10 && draftCountdown > 0) {
      DraftAudio.startTickCountdown(Math.round(draftCountdown));
    }
    if (draftCountdown <= 0) {
      DraftAudio.stopTickCountdown();
      if (draftCountdown === 0 && draftActive) DraftAudio.timeoutBuzzer();
    }
    return () => DraftAudio.stopTickCountdown();
  }, [draftActive, Math.round(draftCountdown)]);

  const confettiPieces = uM(() => (
    Array.from({ length: 18 }, (_, idx) => ({
      id: idx,
      left: 3 + (idx * 5.3),
      delay: (idx % 6) * 0.11,
      duration: 2.15 + ((idx % 5) * 0.16),
      width: idx % 4 === 0 ? 12 : 8,
      height: idx % 3 === 0 ? 24 : 18,
      color: ["var(--accent)", "var(--warning)", "var(--success)", "var(--danger)"][idx % 4],
    }))
  ), []);

  const confettiOverlay = showConfetti ? (
    <div className="draft-confetti" aria-hidden="true">
      {confettiPieces.map(piece => (
        <span
          key={piece.id}
          className="draft-confetti-piece"
          style={{
            left: `${piece.left}%`,
            width: piece.width,
            height: piece.height,
            background: `linear-gradient(180deg, ${piece.color}, color-mix(in oklch, ${piece.color} 66%, white 34%))`,
            animationDelay: `${piece.delay}s`,
            animationDuration: `${piece.duration}s`,
          }}
        />
      ))}
    </div>
  ) : null;

  const renderDraftBoard = () => (
    <div className="draft-board-grid" style={{display: "grid", gridTemplateColumns: "repeat(5, 1fr)", gap: 12, alignItems: "start"}}>
      {theme.teams.map(t => {
        const teamData = (draftTeams && draftTeams[t.id]) || {};
        const members = teamData.members || [];
        const captainEmail = teamData.captain?.email || (members.find(m => m.isCaptain) || {}).email;
        return (
          <div key={t.id} style={{
            background: "linear-gradient(180deg, color-mix(in oklch, var(--surface-raised) 90%, white 10%), var(--surface))",
            border: "1px solid var(--line)",
            borderTop: `4px solid ${t.color}`,
            borderRadius: 18,
            minHeight: 260,
            boxShadow: "0 14px 40px rgba(0,0,0,.20)",
            overflow: "hidden",
          }}>
            <div style={{padding: "14px 14px 10px"}}>
              <div style={{display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8}}>
                <div style={{fontWeight: 800, letterSpacing: ".04em"}}>{t.name}</div>
                <div className="mono" style={{fontSize: 11, color: t.color}}>{members.length}</div>
              </div>
              <div style={{fontSize: 12, color: "var(--ink-muted)", marginTop: 4}}>{(teamData.captain || members.find(m => m.isCaptain))?.name ? `★ ${(teamData.captain || members.find(m => m.isCaptain)).name}` : tc("Captain pending")}</div>
            </div>
            <div style={{maxHeight: 250, overflowY: "auto", padding: "0 10px 10px"}}>
              {members.length ? members.map(member => (
                <div
                  key={member.email || member.id || member.name}
                  className={getDraftMemberKey(t.id, member) === animatedMemberKey ? "draft-new-member" : ""}
                  style={{
                    display: "flex",
                    alignItems: "center",
                    gap: 10,
                    padding: "10px 8px",
                    borderRadius: 12,
                    background: (member.email || member.id) === me.id ? "color-mix(in oklch, var(--accent) 16%, transparent)" : "transparent",
                    border: `1px solid ${captainEmail === member.email ? "color-mix(in oklch, " + t.color + " 55%, white 45%)" : "transparent"}`,
                    marginBottom: 6,
                  }}
                >
                  <div style={{
                    width: 30,
                    height: 30,
                    borderRadius: 999,
                    display: "grid",
                    placeItems: "center",
                    background: `color-mix(in oklch, ${t.color} 74%, white 26%)`,
                    color: "#09121c",
                    fontWeight: 800,
                    fontSize: 11,
                    flexShrink: 0,
                  }}>{getInitials(member.name)}</div>
                  <div style={{minWidth: 0}}>
                    <div style={{fontSize: 13, fontWeight: 700, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis"}}>{member.name}</div>
                    <div className="mono" style={{fontSize: 10, color: "var(--ink-muted)"}}>{member.isCaptain ? tc("CAPTAIN") : tc("SIGNED")}</div>
                  </div>
                </div>
              )) : (
                <div style={{padding: "10px 8px", color: "var(--ink-muted)", fontSize: 13}}>{tc("No picks yet")}</div>
              )}
            </div>
          </div>
        );
      })}
    </div>
  );

  if (draftActive) {
    return (
      <div className="screen">
        {confettiOverlay}
        <div className="lobby" style={{gap: 18}}>
          <div className="hero-fx" />

          <div style={{
            display: "grid",
            gap: 16,
            padding: 18,
            borderRadius: 24,
            border: `1px solid ${isMyTurn ? "color-mix(in oklch, var(--accent) 65%, white 35%)" : "var(--line)"}`,
            background: isMyTurn
              ? "radial-gradient(circle at top left, color-mix(in oklch, var(--accent) 18%, transparent), transparent 48%), linear-gradient(180deg, color-mix(in oklch, var(--surface-raised) 92%, white 8%), var(--surface))"
              : "linear-gradient(180deg, color-mix(in oklch, var(--surface-raised) 90%, white 10%), var(--surface))",
            boxShadow: isMyTurn
              ? `0 0 0 1px color-mix(in oklch, var(--accent) 25%, transparent), 0 0 ${18 + (pickGlow * 18)}px color-mix(in oklch, var(--accent) ${Math.round(18 + (pickGlow * 20))}%, transparent), 0 24px 60px rgba(0,0,0,.28)`
              : "0 24px 60px rgba(0,0,0,.20)",
          }}>
            <div className="draft-header-row" style={{display: "flex", justifyContent: "space-between", gap: 16, alignItems: "flex-start", flexWrap: "wrap"}}>
              <div style={{minWidth: 0, flex: "1 1 280px"}}>
                <div
                  key={`draft-turn-header-${yourTurnFxSeed}`}
                  className={`mono ${isMyTurn ? "draft-your-turn" : ""}`}
                  style={{fontSize: 12, letterSpacing: ".18em", color: isMyTurn ? "var(--accent)" : "var(--ink-muted)", marginBottom: 8}}
                >
                  {isCaptain ? (isMyTurn ? tc("YOUR PICK") : `WAITING FOR ${(currentCaptain?.name || currentCaptain?.email || tc("CAPTAIN")).toUpperCase()}`) : tc("DRAFT IN PROGRESS")}
                </div>
                <div className="draft-hero-title" style={{fontSize: 34, lineHeight: 1, fontWeight: 900, letterSpacing: "-.03em"}}>
                  {isCaptain
                    ? (isMyTurn ? tc("Make it count.") : `${currentCaptain?.name || tc("Captain")} is on the clock`)
                    : `Round ${draftStatus?.round || 1} · ${modeLabel}`}
                </div>
                {!isCaptain && currentCaptain && (
                  <div className="draft-on-clock-pill" style={{
                    display: "inline-flex", alignItems: "center", gap: 10, padding: "8px 14px",
                    borderRadius: 999, marginTop: 12,
                    background: `color-mix(in oklch, ${currentCaptain.teamColor || 'var(--accent)'} 22%, transparent)`,
                    border: `1px solid color-mix(in oklch, ${currentCaptain.teamColor || 'var(--accent)'} 55%, var(--line))`,
                    fontSize: 14, fontWeight: 700, maxWidth: "100%",
                  }}>
                    <span style={{width: 12, height: 12, borderRadius: 999, background: currentCaptain.teamColor || 'var(--accent)', boxShadow: `0 0 14px ${currentCaptain.teamColor || 'var(--accent)'}`, flexShrink: 0}} />
                    <span style={{whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", minWidth: 0}}>
                      <strong>{currentCaptain.name || currentCaptain.email || tc("Captain")}</strong>
                      {currentCaptain.teamName ? <span style={{color: "var(--ink-muted)", fontWeight: 500}}> · {currentCaptain.teamName}</span> : null}
                    </span>
                    <span className="mono" style={{marginLeft: 6, fontSize: 13, color: timerColor, fontWeight: 800, flexShrink: 0}}>{draftCountdown.toFixed(1)}s</span>
                  </div>
                )}
                <div style={{marginTop: 10, color: "var(--ink-muted)", fontSize: 14}}>
                  {isCaptain
                    ? (isMyTurn
                        ? `You have ${draftCountdown.toFixed(1)}s to lock your next signing.`
                        : `${currentCaptain?.teamName || tc("Team")} is selecting now.`)
                    : (isUndraftedMe
                        ? tc("You're in the pool — a captain will pick you!")
                        : `Live board${me?.tid ? ` · currently assigned to ${getTeamName(me.tid)}` : ""}`)}
                </div>
              </div>

              <div className={`draft-timer-block ${draftPct < 33 ? "draft-timer-urgent" : ""}`} style={{minWidth: 220, flex: "0 0 240px"}}>
                <div className="mono" style={{display: "flex", justifyContent: "space-between", fontSize: 12, color: "var(--ink-muted)", marginBottom: 8}}>
                   <span>{tc("Pick clock")}</span>
                  <span style={{color: timerColor}}>{draftCountdown.toFixed(1)}s</span>
                </div>
                <div style={{height: 10, borderRadius: 999, background: "color-mix(in oklch, var(--surface-raised) 82%, black 18%)", overflow: "hidden", border: "1px solid var(--line)"}}>
                  <div style={{height: "100%", width: `${draftPct}%`, background: `linear-gradient(90deg, ${timerColor}, color-mix(in oklch, ${timerColor} 65%, white 35%))`, boxShadow: `0 0 24px ${timerColor}`}} />
                </div>
                <div style={{display: "flex", alignItems: "center", gap: 8, marginTop: 12, fontSize: 13, color: "var(--ink-muted)"}}>
                  <span style={{width: 10, height: 10, borderRadius: 999, background: currentCaptain?.teamColor || "var(--accent)", boxShadow: `0 0 18px ${currentCaptain?.teamColor || "var(--accent)"}`}} />
                  <span>{currentCaptain?.name || currentCaptain?.email || tc("Awaiting captain")}</span>
                   <span>·</span>
                   <span>{currentCaptain?.teamName || tc("Draft board")}</span>
                </div>
              </div>
            </div>

            {(draftUndrafted || []).length > 0 && (
              <div key={isCaptain && isMyTurn ? `draft-turn-panel-${yourTurnFxSeed}` : 'avail-players'} className={isCaptain && isMyTurn ? "draft-your-turn" : ""}>
                <div className="mono" style={{fontSize: 12, letterSpacing: ".16em", color: "var(--accent)", marginBottom: 12}}>
                  {isCaptain && isMyTurn ? tc("YOUR PICK — SELECT A PLAYER") : `${tc("AVAILABLE PLAYERS")} · ${(draftUndrafted || []).filter(p => (p.email || p.id) !== me?.id).length}`}
                </div>
                <div className="draft-available-grid" style={{display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 12, maxHeight: isCaptain && isMyTurn ? "none" : 320, overflowY: "auto"}}>
                  {(draftUndrafted || [])
                    /* Filter out self — captains can't pick themselves (server
                       blocks it anyway), and showing "you" in the pickable
                       list is confusing UX (see: clicked own card → server
                       no-op + 1.5s later auto-pick assigns me elsewhere → my
                       name vanishes mid-tap). */
                    .filter(player => (player.email || player.id) !== me?.id)
                    .map(player => (
                    <button
                      key={player.email || player.id}
                      className="draft-player-card"
                      type="button"
                      disabled={!(isCaptain && isMyTurn)}
                      onClick={() => isCaptain && isMyTurn && onDraftPick && onDraftPick(player.email || player.id)}
                      style={{
                        textAlign: "left",
                        padding: 14,
                        borderRadius: 18,
                        border: isCaptain && isMyTurn ? "1px solid color-mix(in oklch, var(--accent) 42%, var(--line))" : "1px solid var(--line)",
                        background: "linear-gradient(180deg, color-mix(in oklch, var(--surface-raised) 88%, white 12%), color-mix(in oklch, var(--surface) 92%, black 8%))",
                        color: "var(--ink)",
                        cursor: isCaptain && isMyTurn ? "pointer" : "default",
                        boxShadow: "0 12px 28px rgba(0,0,0,.18)",
                        opacity: isCaptain && isMyTurn ? 1 : 0.65,
                      }}
                    >
                      <div style={{display: "flex", alignItems: "center", gap: 12}}>
                        <div style={{
                          width: 42,
                          height: 42,
                          borderRadius: 999,
                          display: "grid",
                          placeItems: "center",
                          background: "color-mix(in oklch, var(--accent) 82%, white 18%)",
                          color: "#09121c",
                          fontWeight: 900,
                          flexShrink: 0,
                        }}>{getInitials(player.name)}</div>
                        <div style={{minWidth: 0}}>
                          <div style={{fontWeight: 800, fontSize: 15, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis"}}>{player.name}</div>
                          <div className="mono" style={{fontSize: 11, color: "var(--ink-muted)", marginTop: 4}}>{(player.score || 0).toLocaleString()} pts</div>
                        </div>
                      </div>
                    </button>
                  ))}
                </div>
              </div>
            )}

            {!isCaptain && history.length > 0 && (
              <div className="draft-bottom-split" style={{display: "grid", gridTemplateColumns: "1.1fr 2fr", gap: 14}}>
                <div style={{
                  border: "1px solid var(--line)",
                  borderRadius: 18,
                  background: "color-mix(in oklch, var(--surface-raised) 88%, black 12%)",
                  padding: 14,
                }}>
                  <div className="mono" style={{fontSize: 12, letterSpacing: ".16em", color: "var(--accent)", marginBottom: 10}}>{tc("SIGNINGS")}</div>
                  <div style={{maxHeight: 150, overflowY: "auto", display: "grid", gap: 8}}>
                    {history.map((item, idx) => (
                      <div
                        key={`${item.player_email || item.player_name || idx}-${idx}`}
                        className={`draft-pick-entry ${getDraftHistoryKey(item) === animatedHistoryKey ? "is-new" : ""}`}
                        style={{padding: "10px 12px", borderRadius: 12, fontSize: 13}}
                      >
                        <span style={{marginRight: 8}}>📣</span>
                        {item.message || `${item.player_name || tc("Player")} → ${item.team_name || getTeamName(item.team || item.team_id)}`}
                      </div>
                    ))}
                  </div>
                </div>
                <div>{renderDraftBoard()}</div>
              </div>
            )}

            {(isCaptain || history.length === 0) && renderDraftBoard()}
          </div>

          <div className="lobby-footer">
            <div className="note" style={{display: "flex", alignItems: "center", gap: 12, justifyContent: "center"}}>
              <span>{tc("Live draft board · Team colors hot · Pick window synced from server")}{connected ? "" : " (demo)"}</span>
              {typeof DraftAudio !== "undefined" && (
                <button type="button" onClick={() => { DraftAudio.toggleMute(); setDraftNow(Date.now()); }}
                  style={{background: "none", border: "1px solid var(--line)", borderRadius: 8, padding: "4px 10px", cursor: "pointer", color: "var(--ink-muted)", fontSize: 12}}>
                  {DraftAudio.isMuted() ? "🔇 " + tc("Unmute") : "🔊 " + tc("Mute")}
                </button>
              )}
            </div>
            {!draftActive && <button className="btn btn-primary btn-lg" onClick={onStart}>{tc("Skip countdown →")}</button>}
            <button type="button" className="arena-link-button" style={{marginTop: 8}} onClick={() => { window.location.href = '/v2/?reset'; }}>
              {tc("Wrong session? Change code")}
            </button>
          </div>
        </div>
      </div>
    );
  }

  // Pick team description based on game language
  const getLang = () => {
    try { return (typeof getLanguage === "function" ? getLanguage() : "en") || "en"; } catch(_) { return "en"; }
  };
  const lang = getLang();
  const getTeamDesc = (t) => {
    if (lang === "es" && t.desc_es) return t.desc_es;
    if (lang === "pt" && t.desc_pt) return t.desc_pt;
    return t.desc || t.city;
  };

  // Available (unassigned) players
  const teamIds = new Set(theme.teams.map(t => t.id));
  const availablePlayers = leaderboard.filter(p => !p.tid || !teamIds.has(p.tid));

  return (
    <div className="screen">
      {confettiOverlay}
      <div className="lobby">
        <div className="hero-fx" />
        <div className="lobby-countdown">
          <div className="label">{theme.phaseNames.lobby} · {connected ? tc("Waiting for the proctor") : tc("First whistle in")}</div>
          {!connected && <div className="clock mono">{mm}:{ss}</div>}
          {connected && <div className="clock mono" style={{fontSize:"1.2rem",opacity:.6}}>{tc("Sit tight — the action starts soon")}</div>}
        </div>

        <div className="lobby-rosters">
          {theme.teams.map(t => {
            const players = leaderboard.filter(p => p.tid === t.id);
            return (
              <div key={t.id} className="roster" style={{"--tc": t.color}}>
                <div className="roster-head">
                  {t.emoji
                    ? <div className="roster-emoji">{t.emoji}</div>
                    : <div className="roster-crest" style={{background: t.color}}>{t.short}</div>
                  }
                  <div>
                    <div className="roster-name">{t.name}</div>
                    <div className="roster-city">{getTeamDesc(t)}</div>
                  </div>
                </div>
                <div className="roster-players">
                  {players.map(p => (
                    <div key={p.id} className={`rp ${p.id === me.id ? "me" : ""}`}>
                      <span className="av">{p.name.split(" ").map(n=>n[0]).join("").slice(0,2)}</span>
                      <span className="nm">{p.name}</span>
                      {p.isCaptain && <span className="role mono" style={{color:"var(--warn)"}}>★ CPT</span>}
                    </div>
                  ))}
                  {Array.from({length: Math.max(0, 4 - players.length)}).map((_, i) => (
                    <div key={"e"+i} className="rp" style={{opacity:.3}}>
                      <span className="av">··</span>
                       <span className="nm" style={{color:"var(--ink-muted)"}}>{tc("waiting…")}</span>
                      <span className="role mono">—</span>
                    </div>
                  ))}
                </div>
              </div>
            );
          })}
        </div>

        {connected && availablePlayers.length > 0 && (
          <div className="lobby-available">
            <div className="lobby-available-hd">
              <span>🎮 {tc("Available Players")}</span>
              <span className="lobby-available-count">{availablePlayers.length}</span>
            </div>
            <div className="lobby-available-grid">
              {availablePlayers.map(p => (
                <div key={p.id} className={`lobby-available-chip ${p.id === me.id ? "me" : ""}`}>
                  <span className="av" style={{width:24,height:24,fontSize:10,borderRadius:"50%",background:"var(--accent)",color:"var(--accent-ink)",display:"inline-grid",placeItems:"center"}}>{p.name.split(" ").map(n=>n[0]).join("").slice(0,2)}</span>
                  <span>{p.name}</span>
                </div>
              ))}
            </div>
          </div>
        )}

        <div className="lobby-footer">
          {connected ? (
            <>
              <div className="note">{tc("Proctor is prepping the board · Ambient audio ON · Reduced-motion respected")}</div>
              <button type="button" className="arena-link-button" style={{marginTop: 8}} onClick={() => { window.location.href = '/v2/?reset'; }}>
                {tc("Wrong session? Change code")}
              </button>
            </>
          ) : (
            <>
              <div className="note">{tc("Proctor is prepping the board · Ambient audio ON · Reduced-motion respected")}</div>
              <button className="btn btn-primary btn-lg" onClick={onStart}>{tc("Skip countdown →")}</button>
            </>
          )}
        </div>
      </div>
    </div>
  );
}

function checkCorrect(question, answer) {
  const spec = question.spec || question;
  const correct = spec?.correct;
  const type = question?.type;

  // Proctor-scored types — cannot auto-grade
  if (['free_text', 'hotspot', 'buzzer', 'structured_form'].includes(type)) return null;

  // No answer key provided — skip for types that embed correctness in spec structure
  if ((correct === undefined || correct === null) && !['drag_match', 'cascade', 'numeric_slider'].includes(type)) return null;

  // Type-specific grading
  switch (type) {
    case 'single_select':
    case 'true_false':
      return answer === correct;

    case 'multi_select':
      if (!Array.isArray(answer) || !Array.isArray(correct)) return false;
      return answer.length === correct.length &&
        JSON.stringify([...answer].sort()) === JSON.stringify([...correct].sort());

    case 'rank_order':
      if (!Array.isArray(answer) || !Array.isArray(correct)) return false;
      return JSON.stringify(answer) === JSON.stringify(correct); // order matters!

    case 'cascade':
      if (spec.steps) return spec.steps.every(s => answer?.[s.id] === s.correct);
      return null;

    case 'drag_match':
      if (spec.pairs) return spec.pairs.every(p => answer?.[p.leftId] === p.rightId);
      return null;

    case 'group_sort':
      if (typeof correct === 'object' && !Array.isArray(correct)) {
        return Object.keys(correct).every(bucketId => {
          const expected = [...(correct[bucketId] || [])].sort();
          const actual = [...(answer?.[bucketId] || [])].sort();
          return expected.length === actual.length && expected.every((v, i) => v === actual[i]);
        });
      }
      return null;

    case 'numeric_slider':
      if (spec.target != null) {
        const tol = spec.tolerance ?? (spec.max - spec.min) * 0.1;
        return typeof answer === 'number' && Math.abs(answer - spec.target) <= tol;
      }
      return null;

    default:
      // Fallback for types without a dedicated case (e.g. boolean/string correct keys)
      if (typeof correct === 'boolean' || typeof correct === 'string') return answer === correct;
      return null; // unknown type — don't auto-pass
  }
}
window.checkCorrect = checkCorrect;

function QuizScreen({ theme, qIdx, question, totalQuestions: totalQProp, onAnswer, onNext, onSkip, selected, locked, correctKey, onSelect, gradedOutcome }) {
  const isNewFormat = question.type && window.RENDERERS && window.RENDERERS[question.type];

  // New-format state
  const [value, setValue] = uS(() =>
    isNewFormat ? window.RENDERERS[question.type].getDefaultValue(question.spec) : null
  );
  const [localLocked, setLocalLocked] = uS(false);
  const [localGraded, setLocalGraded] = uS(false);
  // Skip double-tap: first tap arms confirm, second tap fires
  const [skipPending, setSkipPending] = uS(false);
  const skipTimerRef = uR(null);

  // ── Per-question countdown timer ─────────────────────────────────────────
  // Client-side only. Resets to ARENA_CONFIG.timerPerQuestionSec (default 30)
  // each time qIdx changes. Visual urgency cue only — does NOT auto-submit
  // (server-side enforcement would require timestamping each question; we
  // can add later). When it hits 0, we just freeze at 0 and tint the bar
  // danger-color. Stops counting once the user locks in an answer.
  const perQDuration = (window.ARENA_CONFIG && window.ARENA_CONFIG.timerPerQuestionSec) || 30;
  const [qSecsLeft, setQSecsLeft] = uS(perQDuration);
  uE(() => {
    setQSecsLeft(perQDuration);
  }, [qIdx]);
  uE(() => {
    if (locked || localLocked) return; // freeze countdown after submit
    if (qSecsLeft <= 0) return;
    const t = setTimeout(() => setQSecsLeft(s => Math.max(0, s - 1)), 1000);
    return () => clearTimeout(t);
  }, [qSecsLeft, locked, localLocked]);
  const qPct = Math.max(0, Math.min(100, (qSecsLeft / perQDuration) * 100));
  const qTimerColor = qSecsLeft <= 5 ? 'var(--danger, #E10600)'
                    : qSecsLeft <= 10 ? 'var(--warning, #F0A000)'
                    : 'var(--accent, #00BCEB)';

  // ── Phase-level timer expiry guard ──────────────────────────────────────────
  // Polls /api/g/{gid}/timer/status every 5s. When quiz phase timer expires,
  // disables submit/next/skip so players don't keep clicking (server still 403s).
  const [phaseExpired, setPhaseExpired] = uS(false);
  uE(() => {
    var gid = typeof Session !== 'undefined' ? Session.getGameId() : null;
    if (!gid) return;
    function checkPhaseTimer() {
      fetch('/api/g/' + gid + '/timer/status')
        .then(function(r) { return r.json(); })
        .then(function(data) {
          var qt = data && data.quiz;
          setPhaseExpired(!!(qt && qt.expired && !qt.paused));
        })
        .catch(function() {});
    }
    checkPhaseTimer();
    var tid = setInterval(checkPhaseTimer, 5000);
    return function() { clearInterval(tid); };
  }, []);

  // Reset new-format state when question changes
  uE(() => {
    if (isNewFormat) {
      setValue(window.RENDERERS[question.type].getDefaultValue(question.spec));
      setLocalLocked(false);
      setLocalGraded(false);
      setSkipPending(false);
      if (skipTimerRef.current) clearTimeout(skipTimerRef.current);
    }
  }, [qIdx]);

  const totalQuestions = totalQProp || window.QUESTIONS?.length || 5;

  if (isNewFormat) {
    const renderer = window.RENDERERS[question.type];
    const Comp = renderer.Component;
    // Prefer authoritative server outcome (gradedOutcome) over local check.
    // Fallback to null (neutral) rather than false so that an unresolved state
    // never accidentally shows the "wrong" (err) feedback class.
    const isPartial = gradedOutcome === 'partial';
    const isSkipped = gradedOutcome === 'skipped';
    const localCheck = localGraded ? checkCorrect(question, value) : null;
    const isCorrect = gradedOutcome === 'correct' ? true
                    : gradedOutcome === 'wrong'   ? false
                    : isPartial                   ? null  // partial handled separately in feedback
                    : isSkipped                   ? null  // skipped handled separately in feedback
                    : localCheck;
    const canLockIn = renderer.isValid(value, question.spec) && !localLocked;

    // Strip correct/explanation from spec passed to renderer — anti-cheat (dev tools)
    const safeSpec = Object.assign({}, question.spec);
    delete safeSpec.correct;
    delete safeSpec.explanation;

    return (
      <div className="screen">
        <div className="quiz">
          <div className="quiz-meta">
            <div className="quiz-q-num">
              <span className="big mono">Q{String(qIdx+1).padStart(2,"0")}</span>
              <span className="of">/ {String(totalQuestions).padStart(2,"0")} · {question.tag}</span>
            </div>
            <div className="quiz-progress-bar">
              <span style={{width: `${((qIdx+1)/totalQuestions)*100}%`}}/>
            </div>
             <div className="live-dot">{tc("LIVE")}</div>
          </div>

          {/* Per-question countdown — visible always, sticky on mobile so it
              never scrolls out of view. Color shifts to warn → danger as
              time runs low. Pure visual timer (no auto-submit). */}
          <div className="quiz-q-timer" style={{
            display: 'flex', alignItems: 'center', gap: 12,
            padding: '8px 12px', marginTop: 8, marginBottom: 4,
            background: 'var(--bg-elev)',
            border: `1px solid ${qSecsLeft <= 5 ? qTimerColor : 'var(--line)'}`,
            borderRadius: 10,
            position: 'sticky', top: 0, zIndex: 4,
          }}>
            <span className="mono" style={{fontSize: 11, letterSpacing: '.14em', color: 'var(--ink-muted)'}}>
              {tc('TIME')}
            </span>
            <div style={{flex: 1, height: 8, background: 'var(--bg)', borderRadius: 6, overflow: 'hidden'}}>
              <div style={{
                height: '100%', width: `${qPct}%`,
                background: `linear-gradient(90deg, ${qTimerColor}, color-mix(in srgb, ${qTimerColor} 60%, white))`,
                transition: 'width .9s linear, background .3s',
              }}/>
            </div>
            <span className="mono" style={{
              fontSize: 16, fontWeight: 800, minWidth: 44, textAlign: 'right',
              color: qTimerColor,
              fontVariantNumeric: 'tabular-nums',
            }}>{qSecsLeft}s</span>
          </div>

          {/* Phase-timer expired banner */}
          {phaseExpired && (
            <div style={{
              margin: '8px 0 4px',
              padding: '10px 14px',
              background: 'color-mix(in srgb, var(--danger, #E10600) 12%, transparent)',
              border: '1px solid var(--danger, #E10600)',
              borderRadius: 8,
              fontSize: 13,
              color: 'var(--danger, #E10600)',
              fontWeight: 600,
              textAlign: 'center',
            }}>
              {tc('⏱ Tiempo terminado. Esperando al proctor para más tiempo o avanzar de fase.')}
            </div>
          )}

          <div className="quiz-body">
            <h2 className="quiz-prompt">{question.prompt}</h2>

            <div className="qtr" style={{marginTop: 16}}>
              <Comp
                spec={safeSpec}
                value={value}
                onChange={v => !localLocked && setValue(v)}
                locked={localLocked}
                graded={localGraded}
              />
            </div>

            {!localLocked && (
              <div style={{display:"flex", justifyContent:"flex-end", gap: 12, marginTop: 16, alignItems: "center"}}>
                {/* Universal skip — double-tap to confirm; first tap arms, second fires */}
                {onSkip && !phaseExpired && (
                  <button className="btn ghost"
                    style={{
                      fontSize: 13, padding: '8px 16px',
                      color: skipPending ? 'var(--danger)' : 'var(--ink-muted)',
                      borderColor: skipPending ? 'var(--danger)' : undefined,
                      transition: 'color .15s, border-color .15s',
                    }}
                    onClick={() => {
                      if (!skipPending) {
                        setSkipPending(true);
                        // Auto-dismiss confirm state after 3 s if not confirmed
                        if (skipTimerRef.current) clearTimeout(skipTimerRef.current);
                        skipTimerRef.current = setTimeout(() => setSkipPending(false), 3000);
                      } else {
                        if (skipTimerRef.current) clearTimeout(skipTimerRef.current);
                        setLocalLocked(true);
                        onSkip();
                      }
                    }}
                  >
                    {skipPending ? tc("Tap again to confirm") : tc("Skip →")}
                  </button>
                )}
                <button className="btn btn-primary btn-lg" onClick={() => {
                  setLocalLocked(true);
                  setLocalGraded(true);
                  onAnswer(value);
                }} disabled={!canLockIn || phaseExpired} style={{opacity: (canLockIn && !phaseExpired) ? 1 : 0.4}}>
                   {canLockIn && !phaseExpired ? tc('Submit answer') : phaseExpired ? tc('⏱ Tiempo terminado') : tc('Type your answer…')} <span className="mono" style={{opacity:.7, marginLeft:6, fontSize:12}}>↵</span>
                </button>
              </div>
            )}

            {localLocked && (() => {
              const isUngraded = isCorrect === null && !isPartial && !isSkipped;
              const fbClass = isSkipped ? "" : isPartial ? "ok" : isUngraded ? "ok" : (isCorrect ? "ok" : "err");
              const fbLabel = isSkipped
                ? tc("SKIPPED")
                : isPartial
                ? tc("PARTIAL CREDIT")
                : isUngraded ? tc("SUBMITTED ✓")
                : (isCorrect ? tc("CORRECT") : tc("NOT QUITE"));
              const fbPts = isSkipped
                ? `+0 · ${tc("streak broken")}`
                : isPartial
                ? tc("partial match · some pts awarded")
                : isUngraded
                ? `+${question.points || 50} · ${tc("participation")}`
                : (isCorrect ? `+${question.points || 120} · STREAK++` : `+0 · ${tc("streak broken")}`);
              return (
                <div className={`quiz-feedback ${fbClass}`}>
                  <div style={{minWidth: 120}}>
                    <div className="tag">{fbLabel}</div>
                    <div className="mono" style={{color:"var(--ink-muted)", fontSize:11, marginTop:2}}>
                      {fbPts}
                    </div>
                  </div>
                  {!isSkipped && question.spec && question.spec.explanation && <div className="expl">{question.spec.explanation}</div>}
                  {/* Skipped questions auto-advance; all others show explicit Next button */}
                  {!isSkipped && (
                    <button className="btn btn-primary btn-sm" onClick={onNext} disabled={phaseExpired} style={{opacity: phaseExpired ? 0.4 : 1}}>
                      {qIdx < totalQuestions - 1 ? tc("Next question →") : tc("Finish phase →")}
                    </button>
                  )}
                </div>
              );
            })()}
          </div>
        </div>
      </div>
    );
  }

  // Legacy format fallback
  return (
    <div className="screen">
      <div className="quiz">
          <div className="quiz-meta">
            <div className="quiz-q-num">
              <span className="big mono">Q{String(qIdx+1).padStart(2,"0")}</span>
              <span className="of">/ {String(totalQuestions).padStart(2,"0")} · {question.tag}</span>
            </div>
            <div className="quiz-progress-bar">
              <span style={{width: `${((qIdx+1)/totalQuestions)*100}%`}}/>
            </div>
          <div className="live-dot">{tc("LIVE")}</div>
        </div>

        {/* Phase-timer expired banner (legacy) */}
        {phaseExpired && (
          <div style={{
            margin: '8px 0 4px',
            padding: '10px 14px',
            background: 'color-mix(in srgb, var(--danger, #E10600) 12%, transparent)',
            border: '1px solid var(--danger, #E10600)',
            borderRadius: 8,
            fontSize: 13,
            color: 'var(--danger, #E10600)',
            fontWeight: 600,
            textAlign: 'center',
          }}>
            {tc('⏱ Tiempo terminado. Esperando al proctor para más tiempo o avanzar de fase.')}
          </div>
        )}

        <div className="quiz-body">
          <h2 className="quiz-prompt">{question.prompt}</h2>

          <div className="quiz-options">
            {question.options.map(o => {
              const isSel = selected === o.k;
              const isCorrect = locked && o.k === correctKey;
              // Only mark wrong when we have a known key; undefined correctKey must not penalise the chip.
              const isWrong = locked && isSel && correctKey != null && o.k !== correctKey;
              return (
                <button
                  key={o.k}
                  className={`qopt ${locked ? "locked":""} ${isSel && !locked ? "selected":""} ${isCorrect?"correct":""} ${isWrong?"incorrect":""}`}
                  onClick={() => !locked && !phaseExpired && onSelect(o.k)}
                  role="button"
                  aria-pressed={isSel}
                >
                  <span className="qopt-key mono">{o.k}</span>
                  <span className="qopt-text">{o.text}</span>
                  <span className="qopt-right mono">
                    {isCorrect ? "+120" : isWrong ? "×" : locked ? "" : "↵"}
                  </span>
                </button>
              );
            })}
            {locked && (() => {
              // Determine outcome: prefer authoritative gradedOutcome, then correctKey comparison.
              // If neither is available (correctKey undefined, no gradedOutcome), show neutral "submitted".
              const legacyCorrect = gradedOutcome === 'correct'
                || (correctKey != null && selected === correctKey);
              const legacyUnknown = correctKey == null && gradedOutcome == null;
              const fbClass   = legacyUnknown ? "ok" : (legacyCorrect ? "ok" : "err");
              const fbLabel   = legacyUnknown ? tc("SUBMITTED ✓") : (legacyCorrect ? tc("CORRECT") : tc("NOT QUITE"));
              const fbPts     = legacyUnknown
                ? `+${question.points || 50} · ${tc("participation")}`
                : (legacyCorrect ? `+120 · STREAK++` : `+0 · ${tc("streak broken")}`);
              return (
                 <div className={`quiz-feedback ${fbClass}`}>
                  <div style={{minWidth: 120}}>
                    <div className="tag">{fbLabel}</div>
                    <div className="mono" style={{color:"var(--ink-muted)", fontSize:11, marginTop:2}}>
                      {fbPts}
                    </div>
                  </div>
                  <div className="expl">{question.explanation}</div>
                  <button className="btn btn-primary btn-sm" onClick={onNext} disabled={phaseExpired} style={{opacity: phaseExpired ? 0.4 : 1}}>
                    {qIdx < totalQuestions - 1 ? tc("Next question →") : tc("Finish phase →")}
                  </button>
                </div>
              );
            })()}
            {!locked && selected && !phaseExpired && (
              <div style={{gridColumn:"1 / -1", display:"flex", justifyContent:"flex-end"}}>
                <button className="btn btn-primary btn-lg" onClick={() => onAnswer(selected)}>
                  {tc("Lock in answer")} <span className="mono" style={{opacity:.7, marginLeft:6, fontSize:12}}>↵</span>
                </button>
              </div>
            )}
          </div>
        </div>
      </div>
    </div>
  );
}

// ---------------------------------------------------------------------------
// Power-up card component
// ---------------------------------------------------------------------------
function PowerUpCard({ icon, title, subtitle, cost, status, disabled, active, onClick }) {
  return (
    <button
      type="button"
      className={"power-up-card" + (active ? " is-active" : "") + (disabled ? " is-disabled" : "")}
      onClick={onClick}
      disabled={disabled}
    >
      <div className="power-up-card-top">
        <span className="power-up-icon" aria-hidden="true">{icon}</span>
        <span className="power-up-cost mono">{cost}</span>
      </div>
      <div className="power-up-title-row">
        <strong className="power-up-title">{title}</strong>
        {status ? <span className="power-up-status mono">{status}</span> : null}
      </div>
      <div className="power-up-subtitle">{subtitle}</div>
    </button>
  );
}

// ---------------------------------------------------------------------------
// Skip confirmation modal
// ---------------------------------------------------------------------------
function SkipConfirmModal({ open, onCancel, onConfirm, penaltyPct, copy }) {
  if (!open) return null;
  return (
    <div className="power-modal-backdrop" role="dialog" aria-modal="true">
      <div className="power-modal">
        <div className="power-modal-kicker mono">{tc("⏭️ Skip confirmation")}</div>
        <h3 className="power-modal-title">{tc("Skip this ticket?")}</h3>
        <p className="power-modal-copy">{copy}</p>
        <div className="power-modal-penalty">
          <span className="mono">{tc("Penalty")}</span>
          <strong>{tc("-" + penaltyPct + "% of ticket value")}</strong>
        </div>
        <div className="power-modal-actions">
          <button type="button" className="btn btn-ghost" onClick={onCancel}>{tc("Stay on ticket")}</button>
          <button type="button" className="btn btn-primary power-modal-danger" onClick={onConfirm}>{tc("Confirm skip")}</button>
        </div>
      </div>
    </div>
  );
}

// ---------------------------------------------------------------------------
// Triage Screen — with power-ups
// ---------------------------------------------------------------------------
function TriageScreen({ theme, ticket, ticketIdx, totalTickets, gameId, onSubmit, grading, graded, gradeMsg, onFinish, onNextTicket, onShuffle }) {
  const [ans, setAns] = uS("");
  const [usedPowerUps, setUsedPowerUps] = uS({ hint: false, skip: false, shuffle: false, double: false });
  const [revealedPanels, setRevealedPanels] = uS({ hint: false });
  const [doubleArmed, setDoubleArmed] = uS(false);
  const [showSkipConfirm, setShowSkipConfirm] = uS(false);

  const hintPenalty = 20;
  const skipPenalty = 50;
  const shufflePenalty = 10;

  // Reset power-up state when ticket changes
  uE(() => {
    setAns("");
    setUsedPowerUps({ hint: false, skip: false, shuffle: false, double: false });
    setRevealedPanels({ hint: false });
    setDoubleArmed(false);
    setShowSkipConfirm(false);
  }, [ticket && ticket.id]);

  const locked = !!graded || usedPowerUps.skip;
  const canSubmit = ans.trim().length >= 20 && !locked;

  const markUsed = (key) => setUsedPowerUps(prev => ({ ...prev, [key]: true }));

  const handleHint = () => {
    if (usedPowerUps.hint || locked) return;
    markUsed("hint");
    setRevealedPanels(prev => ({ ...prev, hint: true }));
  };

  const handleShuffle = () => {
    if (usedPowerUps.shuffle || locked) return;
    markUsed("shuffle");
    if (onShuffle) onShuffle();
  };

  const handleDoubleToggle = () => {
    if (usedPowerUps.double || locked) return;
    setDoubleArmed(prev => !prev);
  };

  const handleSkipConfirm = () => {
    markUsed("skip");
    setShowSkipConfirm(false);
    onSubmit(null, { skipUsed: true, penaltyPct: skipPenalty });
  };

  const handleSubmitAnswer = () => {
    if (!canSubmit) return;
    if (doubleArmed) markUsed("double");
    onSubmit(ans.trim(), {
      hintUsed: usedPowerUps.hint,
      shuffleUsed: usedPowerUps.shuffle,
      double: !!doubleArmed,
      skipUsed: false,
      hintPenalty,
      shufflePenalty,
    });
  };

  // Guard: if no ticket loaded yet, or the assigned ticket is missing the
  // expected shape (no `log` array), render a loading state instead of
  // throwing on `ticket.log.map(...)` further down — that throw blanks the
  // screen ("black screen") when admin advances all players to triage before
  // tickets have been fetched, or when a server-loaded ticket lacks fields.
  if (!ticket || !Array.isArray(ticket.log)) {
    return (
      <div className="screen">
        <div className="triage" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 40 }}>
          <p style={{ color: 'var(--ink-muted)' }}>{tc('Loading ticket…')}</p>
        </div>
      </div>
    );
  }

  return (
    <div className="screen">
      <div className="triage">
        <div className="triage-left">
          <div className="ticket-header">
            <div>
              <div className="ticket-id mono">
                {tc('TICKET', 'ui')} · {ticket.id}
                {totalTickets > 1 && (
                  <span style={{marginLeft: 12, color: 'var(--ink-muted)', fontWeight: 400}}>
                    ({ticketIdx + 1} / {totalTickets})
                  </span>
                )}
              </div>
              <h2 className="triage-title">{tc(ticket.title)}</h2>
              <div className="triage-customer mono" style={{marginTop:4}}>{tc(ticket.customer)}</div>
            </div>
            <span className="ticket-prio mono">{tc(ticket.priority)}</span>
          </div>

          <p className="triage-desc">{tc(ticket.desc)}</p>

          {/* The actual prompt being asked of the player (audienceFraming). */}
          {ticket.question && (
            <div className="triage-question" style={{
              marginTop: 12, padding: '12px 14px',
              borderLeft: '3px solid var(--accent, #049FD9)',
              background: 'rgba(4,159,217,0.06)',
              borderRadius: 4, fontSize: 14, lineHeight: 1.5
            }}>
              <div className="mono" style={{
                fontSize: 11, textTransform: 'uppercase', letterSpacing: '.05em',
                color: 'var(--ink-muted)', marginBottom: 6
              }}>{tc('Your task')}</div>
              {tc(ticket.question)}
            </div>
          )}

          {/* Symptoms checklist (ISE tickets — separate from raw syslog). */}
          {Array.isArray(ticket.symptomsList) && ticket.symptomsList.length > 0 && (
            <div className="triage-symptoms" style={{ marginTop: 12 }}>
              <div className="mono" style={{
                fontSize: 11, textTransform: 'uppercase', letterSpacing: '.05em',
                color: 'var(--ink-muted)', marginBottom: 6
              }}>{tc('Reported symptoms')}</div>
              <ul style={{ margin: 0, paddingLeft: 18, fontSize: 13.5, lineHeight: 1.55 }}>
                {ticket.symptomsList.map((s, i) => <li key={i}>{tc(s)}</li>)}
              </ul>
            </div>
          )}

          {/* Licensing angle (collapsible — present for ISE tickets). */}
          {ticket.licensingAngle && (
            <details style={{
              marginTop: 12, padding: '8px 12px',
              border: '1px solid var(--line-soft, rgba(255,255,255,0.08))',
              borderRadius: 4, fontSize: 13
            }}>
              <summary className="mono" style={{
                cursor: 'pointer', fontSize: 11, textTransform: 'uppercase',
                letterSpacing: '.05em', color: 'var(--ink-muted)'
              }}>{tc('Licensing angle')}</summary>
              <div style={{ marginTop: 8, lineHeight: 1.5 }}>{tc(ticket.licensingAngle)}</div>
            </details>
          )}

          <div className="log-block">
            <div className="log-head">
              <span>{tc("Syslog · core-sw-west-01")}</span>
              <span>tail -f</span>
            </div>
            <div className="log-body">
              {ticket.log.map((l,i) => (
                <div key={i} className="line">
                  {l.includes("STP-2") ? <span className="hi">{l}</span> :
                   l.includes("DHCPD-6") ? <span className="k">{l}</span> :
                   l.includes("registered to") ? <span className="ok">{l}</span> :
                   l}
                </div>
              ))}
            </div>
          </div>
        </div>

        <div className="triage-right">
          {/* POWER-UPS DISABLED for live event 2026-04-25 — re-enable by removing this guard. Server-side validation pending (Oracle review P1). */}
          {false && (
          <div className="power-up-tray">
            <div className="power-up-tray-head">
              <div>
                <div className="power-up-kicker mono">{tc("Power-ups")}</div>
                <div className="power-up-heading">{tc("Choose your advantage")}</div>
              </div>
              <div className="power-up-note mono">{tc("Each power-up: one use per ticket · combine freely")}</div>
            </div>

            <div className="power-up-rail">
              <PowerUpCard
                icon="💡" title={tc("Hint")}
                subtitle={tc("Reveal a clue from the pit wall.")}
                cost={tc("-" + hintPenalty + "%")}
                status={usedPowerUps.hint ? tc("USED") : ""}
                disabled={usedPowerUps.hint || locked}
                active={revealedPanels.hint}
                onClick={handleHint}
              />
              <PowerUpCard
                icon="⏭️" title={tc("Skip")}
                subtitle={tc("Abandon this lap and protect your clock.")}
                cost={tc("-" + skipPenalty + "%")}
                status={usedPowerUps.skip ? tc("USED") : ""}
                disabled={usedPowerUps.skip || locked}
                active={showSkipConfirm}
                onClick={() => setShowSkipConfirm(true)}
              />
              <PowerUpCard
                icon="🔀" title={tc("Shuffle")}
                subtitle={tc("Swap this ticket for a different one.")}
                cost={tc("-" + shufflePenalty + "%")}
                status={usedPowerUps.shuffle ? tc("USED") : ""}
                disabled={usedPowerUps.shuffle || locked}
                active={false}
                onClick={handleShuffle}
              />
              <PowerUpCard
                icon="⚡" title={tc("Double")}
                subtitle={tc("Double points if right. Zero if wrong.")}
                cost={tc("RISK")}
                status={usedPowerUps.double ? tc("LOCKED") : (doubleArmed ? tc("ARMED") : "")}
                disabled={usedPowerUps.double || locked}
                active={doubleArmed}
                onClick={handleDoubleToggle}
              />
            </div>

            {/* Telemetry chips */}
            <div className="power-up-telemetry">
              {usedPowerUps.hint ? <span className="power-chip">{tc("💡 Hint used · -" + hintPenalty + "%")}</span> : null}
              {usedPowerUps.shuffle ? <span className="power-chip">{tc("🔀 Shuffle used · -" + shufflePenalty + "%")}</span> : null}
              {doubleArmed ? <span className="power-chip power-chip-risk">{tc("⚡ Double armed · high risk")}</span> : null}
            </div>
          </div>
          )}

          {/* Hint reveal */}
          {revealedPanels.hint && (
            <div className="power-reveal power-reveal-hint">
              <div className="power-reveal-head">
                <span className="mono">{tc("💡 Pit wall clue")}</span>
                <span className="power-reveal-tag">{tc("-" + hintPenalty + "% score")}</span>
              </div>
              <div className="power-reveal-copy">
                {tc((ticket.hint && typeof ticket.hint === 'object' ? ticket.hint.text : ticket.hint) || "Check the strongest signal first, then prove impact before remediation.")}
              </div>
            </div>
          )}

          {/* Answer area */}
          <div className="answer-head">
            <span className="answer-label">{tc("Your answer · diagnosis + next steps")}</span>
            <span className="answer-chars mono">{ans.length} / 800</span>
          </div>
          <textarea
            className={"input textarea" + (doubleArmed ? " is-double-armed" : "")}
            placeholder={tc("Diagnose the symptom, cite the signal in the logs, and propose a remediation path.")}
            value={ans}
            onChange={e => setAns(e.target.value.slice(0,800))}
            style={{minHeight: 220}}
          />

          {/* Double indicator + submit */}
          <div className="triage-submit-row">
            {doubleArmed && (
              <div className="double-or-nothing-indicator is-live">
                <span className="mono">{tc("⚡ Double or nothing")}</span>
                <strong>{tc("Armed for this submission")}</strong>
                <small>{tc((ticket.double && typeof ticket.double === 'object' ? (ticket.double.label || ticket.double.text) : ticket.double) || "Double points if correct, zero if wrong")}</small>
              </div>
            )}
            <div className="triage-actions">
              <button
                className={"btn btn-primary" + (doubleArmed ? " btn-double-live" : "")}
                onClick={handleSubmitAnswer}
                disabled={!canSubmit || grading}
              >
                {doubleArmed ? tc("Submit with double active") : tc("Submit for grading")}
              </button>
              <button className="btn btn-ghost">{tc("Save draft")}</button>
            </div>
          </div>

          {/* Grading loading animation */}
          {grading && !graded && (
            <div className="grade-loading">
              <div className="grade-loading-spinner" />
              <div className="grade-loading-text">
                <strong>{tc("Analyzing your diagnosis…")}</strong>
                <span>{tc("AI proctor is reviewing your response")}</span>
              </div>
            </div>
          )}

          {/* Grade result */}
          {graded && (
            <div className="grade-card">
              <div className="grade-badge">{gradeMsg.score}</div>
              <div className="grade-msg">
                <div className="title">{tc("Proctor scored:")} {gradeMsg.score}/100</div>
                <div className="sub">{gradeMsg.note}</div>
              </div>
              <div style={{ marginTop: '24px', textAlign: 'center' }}>
                {ticketIdx < totalTickets - 1 && onNextTicket ? (
                  <button
                    className="btn btn-primary btn-lg"
                    onClick={onNextTicket}
                    style={{ fontSize: '16px', padding: '14px 36px' }}
                  >
                    {tc("Next ticket →")}
                  </button>
                ) : onFinish ? (
                  <button
                    className="btn btn-primary btn-lg"
                    onClick={onFinish}
                    style={{ fontSize: '16px', padding: '14px 36px' }}
                  >
                    {tc("Continue to results →")}
                  </button>
                ) : null}
              </div>
            </div>
          )}

          {/* Activity feed removed — phase-advance events were the only
              signal flowing through and they aren't useful to players. */}

        </div>
      </div>

      <SkipConfirmModal
        open={showSkipConfirm}
        onCancel={() => setShowSkipConfirm(false)}
        onConfirm={handleSkipConfirm}
        penaltyPct={skipPenalty}
        copy={tc((ticket.skip && typeof ticket.skip === 'object' ? (ticket.skip.label || ticket.skip.text) : ticket.skip) || "Skip this ticket and take the penalty. Use when preserving time matters more than forcing an answer.")}
      />
    </div>
  );
}

// ---------------------------------------------------------------------------
// Phase Transition / Scoreboard Screen
// Shown between quiz → triage to celebrate scores and build anticipation
// ---------------------------------------------------------------------------

function getPhaseTransitionInitials(name) {
  return (name || "")
    .split(" ")
    .filter(Boolean)
    .map(part => part[0])
    .join("")
    .slice(0, 2)
    .toUpperCase() || "CA";
}

function PhaseTransitionScreen({ theme, leaderboard, me, onContinue, gameId, targetSubPhase = "triage", fromPhase = "quiz" }) {
  const advanceRef = uR(false);
  const [isLive, setIsLive] = uS(false);

  const nextPhaseName = (() => {
    if (targetSubPhase === "triage") return (theme && theme.phaseNames && theme.phaseNames.triage) || tc("Triage");
    if (targetSubPhase === "gameplan") return tc("Game Plan");
    if (targetSubPhase === "zonamixta") return tc("Zona Mixta");
    return tc(targetSubPhase);
  })();
  const fromPhaseName = (() => {
    if (fromPhase === "quiz") return (theme && theme.phaseNames && theme.phaseNames.quiz) || tc("Quiz");
    if (fromPhase === "triage") return (theme && theme.phaseNames && theme.phaseNames.triage) || tc("Triage");
    if (fromPhase === "gameplan") return tc("Game Plan");
    return (theme && theme.phaseNames && theme.phaseNames[fromPhase]) || tc(fromPhase);
  })();

  const copy = uM(() => {
    if (theme && theme.id === "gp") {
      return {
        eyebrow: tc("🏁 Race standings"),
        title: tc("Qualifying complete"),
        deck: tc("The grid is set. One sharp pit stop can still flip the podium."),
        standings: tc("Constructor standings"),
        playerCard: tc("Driver briefing"),
        nextCard: tc("Next up"),
        nextCopy: tc("Pit crew ready. Diagnose fast, stay precise, and protect every point."),
        button: tc("Continue to pit stop"),
      };
    }
    if (theme && theme.id === "cup") {
      return {
        eyebrow: tc("🏟️ Match update"),
        title: tc("Warmup complete"),
        deck: tc("The scoreline is alive. One clutch extra-time performance changes everything."),
        standings: tc("Team standings"),
        playerCard: tc("Player briefing"),
        nextCard: tc("Next up"),
        nextCopy: tc("The pressure rises now. Read the play, solve the issue, and steal the momentum."),
        button: tc("Continue to extra time"),
      };
    }
    if (theme && theme.id === "nfl") {
      return {
        eyebrow: tc("🏈 Drive summary"),
        title: tc("Film study complete"),
        deck: tc("The field is tight. One big audible can decide the game."),
        standings: tc("Team standings"),
        playerCard: tc("Player briefing"),
        nextCard: tc("Next up"),
        nextCopy: tc("New possession. Read the scenario, call the play, and execute cleanly."),
        button: tc("Continue to audible"),
      };
    }
    if (theme && theme.id === "realm") {
      return {
        eyebrow: tc("⚔️ Realm standings"),
        title: tc("The trial is complete"),
        deck: tc("The houses are close. One decisive siege can redraw the kingdom."),
        standings: tc("House standings"),
        playerCard: tc("Champion briefing"),
        nextCard: tc("Next up"),
        nextCopy: tc("Steel your nerves. Read the signals, break the problem, and claim the advantage."),
        button: tc("Continue to the siege"),
      };
    }
    return {
      eyebrow: tc("📊 Standings update"),
      title: tc("Phase complete"),
      deck: tc("The board is tightening. One strong final challenge can change the order."),
      standings: tc("Team standings"),
      playerCard: tc("Player briefing"),
      nextCard: tc("Next up"),
      nextCopy: tc("The next challenge starts now. Stay sharp, stay fast, and protect your lead."),
      button: tc("Continue"),
    };
  }, [theme]);

  const players = uM(() => {
    return (leaderboard || [])
      .map(p => ({
        id: p.id || p.email || p.name,
        tid: p.tid || p.team || "",
        name: p.name || tc("Player"),
        score: Number(p.score) || 0,
      }))
      .sort((a, b) => b.score - a.score);
  }, [leaderboard]);

  const teams = uM(() => {
    const themeTeams = (theme && theme.teams) || [];
    const mapped = themeTeams.map(teamDef => {
      const members = players.filter(p => p.tid === teamDef.id);
      const total = members.reduce((sum, p) => sum + (p.score || 0), 0);
      return {
        id: teamDef.id,
        name: teamDef.name,
        short: teamDef.short,
        emoji: teamDef.emoji,
        color: teamDef.color || "var(--accent)",
        members,
        memberCount: members.length,
        total,
      };
    }).sort((a, b) => b.total - a.total);

    const leaderTotal = mapped[0] ? mapped[0].total : 0;

    return mapped.map((teamItem, idx) => ({
      ...teamItem,
      rank: idx + 1,
      barWidth: leaderTotal > 0 ? Math.max(10, Math.round((teamItem.total / leaderTotal) * 100)) : 10,
    }));
  }, [theme, players]);

  const currentPlayer = uM(() => {
    const found = players.find(p =>
      p.id === me.id ||
      p.id === me.email ||
      p.name === me.name
    );
    return found || {
      id: me.id,
      tid: me.tid,
      name: me.name || tc("Player"),
      score: 0,
    };
  }, [players, me]);

  const currentTeam = uM(() => {
    return teams.find(t => t.id === currentPlayer.tid) || null;
  }, [teams, currentPlayer]);

  const playerRank = uM(() => {
    const idx = players.findIndex(p => p.id === currentPlayer.id);
    return idx >= 0 ? idx + 1 : players.length + 1;
  }, [players, currentPlayer]);

  const teamRank = uM(() => {
    const idx = teams.findIndex(t => t.id === currentPlayer.tid);
    return idx >= 0 ? idx + 1 : null;
  }, [teams, currentPlayer]);

  const leadPlayerScore = players[0] ? players[0].score : 0;
  const playerGap = Math.max(0, leadPlayerScore - (currentPlayer.score || 0));
  const topTeam = teams[0];

  const handleAdvance = () => {
    if (advanceRef.current) return;
    advanceRef.current = true;
    if (onContinue) onContinue();
  };

  uE(() => {
    advanceRef.current = false;
    setIsLive(false);

    const enterId = requestAnimationFrame(() => setIsLive(true));

    let pollId = null;
    if (gameId) {
      pollId = window.setInterval(() => {
        fetch(`/api/g/${gameId}/sub-phase`)
          .then(r => r.ok ? r.json() : null)
          .then(data => {
            if (data && data.sub_phase === targetSubPhase) {
              handleAdvance();
            }
          })
          .catch(() => {});
      }, 3000);
    }

    return () => {
      cancelAnimationFrame(enterId);
      if (pollId !== null) clearInterval(pollId);
    };
  }, [gameId]);

  return (
    <div className="screen">
      <section
        className={`phase-transition phase-transition--${(theme && theme.id) || "default"} ${isLive ? "is-live" : ""}`}
        aria-labelledby="phase-transition-title"
      >
        <div className="phase-transition__main">
          <header className="phase-transition__hero">
            <div className="phase-transition__flag" aria-hidden="true"></div>
            <div className="phase-transition__hero-copy">
              <div className="phase-transition__eyebrow">{copy.eyebrow}</div>
              <h1 id="phase-transition-title" className="phase-transition__title">{copy.title}</h1>
              <p className="phase-transition__deck">
                {copy.deck} {tc("The")} <strong>{fromPhaseName}</strong> {tc("phase is done.")}
              </p>
            </div>

            <div className="phase-transition__countdown" aria-live="polite">
              <div className="phase-transition__waiting-msg">
                {tc("⏳ Waiting for proctor to advance...")}
              </div>
            </div>
          </header>

          <section className="phase-transition__card phase-transition__card--standings">
            <div className="phase-transition__section-head">
              <div>
                <div className="phase-transition__section-kicker">{copy.standings}</div>
                <h2 className="phase-transition__section-title">
                  {topTeam ? `${topTeam.name} ${tc("lead the board")}` : tc("Standings loading")}
                </h2>
              </div>
              <div className="phase-transition__section-chip">
                {teams.length} {tc("teams")}
              </div>
            </div>

            <div className="phase-transition__team-list">
              {teams.map((teamItem, idx) => (
                <article
                  key={teamItem.id}
                  className={`phase-transition__team-row ${currentPlayer.tid === teamItem.id ? "is-mine" : ""} ${idx < 3 ? "is-top" : ""}`}
                  style={{
                    "--team-color": teamItem.color,
                    "--delay": `${120 + (idx * 110)}ms`,
                  }}
                >
                  <div className="phase-transition__team-rank mono">{tc("P")}{teamItem.rank}</div>

                  <div className="phase-transition__team-meta">
                    <div className="phase-transition__team-line">
                      <div className="phase-transition__team-badge" style={{ background: teamItem.color }}>
                        {teamItem.emoji || teamItem.short}
                      </div>
                      <div className="phase-transition__team-copy">
                        <div className="phase-transition__team-name">{teamItem.name}</div>
                        <div className="phase-transition__team-sub">
                          {teamItem.memberCount} {tc("players")}
                          {currentPlayer.tid === teamItem.id ? ` · ${tc("your team")}` : ""}
                        </div>
                      </div>
                    </div>

                    <div className="phase-transition__team-bar">
                      <span style={{ width: isLive ? `${teamItem.barWidth}%` : "0%" }} />
                    </div>
                  </div>

                  <div className="phase-transition__team-score mono">
                    {teamItem.total.toLocaleString()}
                  </div>
                </article>
              ))}
            </div>
          </section>
        </div>

        <aside className="phase-transition__side">
          <section className="phase-transition__card phase-transition__card--player">
            <div className="phase-transition__section-kicker">{copy.playerCard}</div>

            <div className="phase-transition__player-head">
              <div className="phase-transition__avatar">
                {getPhaseTransitionInitials(currentPlayer.name)}
              </div>
              <div>
                <div className="phase-transition__player-name">{currentPlayer.name}</div>
                <div className="phase-transition__player-team">
                  {currentTeam ? currentTeam.name : tc("No team assigned")}
                </div>
              </div>
            </div>

            <div className="phase-transition__stats">
              <div className="phase-transition__stat">
                <span>{tc("Individual rank")}</span>
                <strong className="mono">{tc("P")}{playerRank}</strong>
              </div>
              <div className="phase-transition__stat">
                <span>{tc("Points")}</span>
                <strong className="mono">{(currentPlayer.score || 0).toLocaleString()}</strong>
              </div>
              <div className="phase-transition__stat">
                <span>{tc("Team rank")}</span>
                <strong className="mono">{teamRank ? `${tc("P")}${teamRank}` : "—"}</strong>
              </div>
              <div className="phase-transition__stat">
                <span>{tc("Gap to lead")}</span>
                <strong className="mono">{playerGap > 0 ? `-${playerGap.toLocaleString()}` : tc("Leader")}</strong>
              </div>
            </div>
          </section>

          <section className="phase-transition__card phase-transition__card--next">
            <div className="phase-transition__section-kicker">{copy.nextCard}</div>
            <div className="phase-transition__next-phase">{nextPhaseName}</div>
            <p className="phase-transition__next-copy">{copy.nextCopy}</p>
          </section>
        </aside>
      </section>
    </div>
  );
}

function WrapScreen({ theme, leaderboard, me }) {
  const sorted = [...leaderboard].sort((a,b)=>b.score-a.score);
  const teamAvgById = leaderboard.reduce((acc, p) => {
    const tid = p.tid || p.team;
    if (!tid) return acc;
    if (!acc[tid]) acc[tid] = { total: 0, count: 0 };
    acc[tid].total += Number(p.score || 0);
    acc[tid].count += 1;
    return acc;
  }, {});
  const teamAvgLabel = (tid) => {
    const bucket = teamAvgById[tid];
    return bucket && bucket.count ? Math.round(bucket.total / bucket.count).toLocaleString() : "—";
  };

  // Certificate + FeedbackGate state
  const [feedbackDone, setFeedbackDone] = uS(false);
  const certRef = uR(null);

  if (!sorted || !sorted.length) {
    return <div className="screen wrap"><div style={{textAlign:'center',padding:'60px 20px',color:'var(--ink-dim)'}}>{tc("Waiting for final results…")}</div></div>;
  }

  // Computed values for certificate (derived after guard so sorted is non-empty)
  const gameId     = Session.getGameId() || 'arena';
  const myEntry    = sorted.find(p => p.email === me.id || p.name === me.name);
  const myRank     = myEntry ? sorted.indexOf(myEntry) + 1 : null;
  const myTeam     = theme.teams.find(t => t.id === me.tid);
  const isWinner   = myRank === 1;
  const playerEmail = me.id || '';

  const top3Teams = theme.teams
    .map(t => ({
      ...t,
      total: leaderboard.filter(p => p.tid === t.id).reduce((a,b)=>a+b.score, 0),
    }))
    .sort((a,b)=>b.total - a.total);

  const topTeam = top3Teams[0];
  const mvp = sorted[0];
  const today = new Date().toLocaleDateString("en-US", {weekday:"long", year:"numeric", month:"long", day:"numeric"});

  return (
    <div className="screen">
      <div className="wrap">
        <div className="news">
          <div className="news-head">
            <div className="news-masthead">{tc("THE ARENA DAILY")}</div>
            <div className="news-date mono">{today.toUpperCase()} · {tc("FINAL · VOL. 04 / ISSUE 17")}</div>
          </div>

          <div className="news-hero">
            <div>
              <div className="eyebrow">{theme.phaseNames.wrap.toUpperCase()} · {theme.label.toUpperCase()}</div>
                <h1 className="news-headline">
                  <em>{topTeam.name}</em> {tc("take the crown")}
                  {theme.id === "cup" && tc(" in a stunning full-time finish.")}
                  {theme.id === "gp" && tc(" from the front of the grid.")}
                  {theme.id === "nfl" && tc(" with a fourth-quarter comeback.")}
                  {theme.id === "realm" && tc(" and the ravens sing their name.")}
                  {theme.id === "cisco" && tc(" after five clinical rounds.")}
                </h1>
              <p className="news-deck">
                A session of sharp answers, a steady streak, and one decisive triage under the wire. The team banked {topTeam.total.toLocaleString()} total points — a margin of {topTeam.total - (top3Teams[1]?.total||0)} over {top3Teams[1]?.name||"the runners-up"}.
              </p>
            </div>

            <div className="news-side">
              <div className="eyebrow">{tc("Final standings")}</div>
              <div className="podium">
                {top3Teams.slice(0,4).map((t, i) => (
                  <div key={t.id} className={`podium-row p${i+1}`} style={{"--tc": t.color}}>
                    <div className="medal mono">{i===0?tc("1st"):i===1?tc("2nd"):i===2?tc("3rd"):tc("4th")}</div>
                    <div className="crest" style={{background: t.color}}>{t.short}</div>
                    <div className="team">{t.name}</div>
                    <div className="score mono">{t.total.toLocaleString()}</div>
                  </div>
                ))}
              </div>
            </div>
          </div>

          <div className="news-grid">
            <div className="news-card">
              <div className="cap">{tc("Match MVP")}</div>
              <div className="ttl">{mvp.name}</div>
              <div className="body">
                {sorted[0].score.toLocaleString()} {tc("points over 5 rounds. 4-question streak during the")} {theme.phaseNames.quiz.toLowerCase()} {tc("phase. First on triage; cleanest diagnosis of the night.")}
              </div>
            </div>
            <div className="news-card">
              <div className="cap">{tc("Biggest comeback")}</div>
              <div className="ttl">+{Math.floor((sorted[3]?.score ?? sorted[0].score) * 0.6)} in Round 4</div>
              <div className="body">
                {(sorted[3] || sorted[sorted.length-1]).name} {tc("opened Round 1 in 14th, closed Round 4 in the top five. Two clutch power-ups and a quiet textbook answer on the VoIP ticket.")}
              </div>
            </div>
            <div className="news-card">
              <div className="cap">{theme.id==="gp"?tc("Fastest lap"):theme.id==="cup"?tc("Golden boot"):theme.id==="nfl"?tc("Play of the game"):theme.id==="realm"?tc("The oracle"):tc("Best answer")}</div>
              <div className="ttl">{(sorted[2] || sorted[sorted.length-1]).name}</div>
              <div className="body">
                {tc("Fastest lock-in on Q02 (BPDU Guard) at 4.2 seconds. Cleanest read of the round.")}
              </div>
            </div>
            <div className="news-card">
              <div className="cap">{tc("Individual table")}</div>
              <div style={{display:"grid", gap:8, marginTop:8}}>
                {sorted.map((p, i) => {
                  const tid = p.tid || p.team;
                  return (
                    <div key={p.email || p.name || i} style={{display:"grid", gridTemplateColumns:"24px minmax(0,1fr) auto", gap:8, alignItems:"center", borderBottom:"1px solid var(--line-soft)", paddingBottom:7}}>
                      <span className="mono" style={{fontSize:11, color:"var(--ink-muted)"}}>#{i+1}</span>
                      <span style={{minWidth:0, overflow:"hidden", textOverflow:"ellipsis", whiteSpace:"nowrap", fontWeight:700, fontSize:13}}>{p.name}</span>
                      <span style={{textAlign:"right"}}>
                        <strong className="mono" style={{fontSize:13}}>{(p.score || 0).toLocaleString()}</strong>
                        <span className="mono" style={{display:"block", fontSize:11, color:"var(--ink-muted)", fontWeight:500}}>Team avg: {teamAvgLabel(tid)}</span>
                      </span>
                    </div>
                  );
                })}
              </div>
            </div>
          </div>

          <div className="news-footer">
            <div className="live-dot">{tc("NEXT SESSION · TUE 22:00 UTC")}</div>
            <div style={{display:"flex", gap:8}}>
              <button className="btn btn-primary btn-sm">{tc("Play again")}</button>
            </div>
          </div>

          {/* ── Certificate + Feedback Gate ─────────────────────────────── */}
          {!feedbackDone ? (
            <FeedbackGate
              onSubmit={function(data) {
                return new Promise(function(resolve) {
                  // Optionally POST feedback data to API here in the future
                  setFeedbackDone(true);
                  resolve();
                });
              }}
            />
          ) : (
            <div className="certificate-section">
              <ArenaCertificate
                certificateRef={certRef}
                id={"cert-" + gameId + "-" + playerEmail}
                playerName={me.name}
                teamName={myTeam ? myTeam.name : ''}
                teamColor={myTeam ? myTeam.color : undefined}
                rank={myRank}
                totalPlayers={sorted.length}
                isWinner={isWinner}
                finalScore={myEntry ? myEntry.score : 0}
                themeId={theme.id}
                eventName={theme.label}
                date={today}
              />
              <CertificateActions
                certificateRef={certRef}
                playerName={me.name}
                rank={myRank}
                totalPlayers={sorted.length}
                finalScore={myEntry ? myEntry.score : 0}
                eventName={theme.label}
              />
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

// ---------------------------------------------------------------------------
// Game Plan Screen — Customer brief review + BOM design
// ---------------------------------------------------------------------------
function GamePlanScreen({ theme, gameModule, me, gameId, onFinish }) {
  const [gpStatus, setGpStatus] = uS(null);
  const [briefIdx, setBriefIdx] = uS(0);
  const [bomUpload, setBomUpload] = uS(null);          // { url, filename, size, uploaded_at }
  const [archUpload, setArchUpload] = uS(null);
  const [bomUploading, setBomUploading] = uS(false);
  const [archUploading, setArchUploading] = uS(false);
  const [uploadError, setUploadError] = uS("");
  const [isLive, setIsLive] = uS(false);
  const [previewIdx, setPreviewIdx] = uS(null);
  const [selectingIdx, setSelectingIdx] = uS(null);
  const [pickerToasts, setPickerToasts] = uS([]);
  const [pickCelebration, setPickCelebration] = uS(null);
  const bomFileRef = uR(null);
  const archFileRef = uR(null);
  const fileRef = bomFileRef; // backward-compat for any existing references
  const prevSelectionsRef = uR(null);
  const celebrationTimerRef = uR(null);

  const briefs = uM(() => {
    if (!gameModule || !gameModule.data || !gameModule.data.briefs) return [];
    const all = gameModule.data.briefs;
    // Cap by organizer's gameplan_count if set (>0). 0/undefined = use all.
    const cap = (window.__GAME_CONFIG__ && window.__GAME_CONFIG__.gameplan_count) || 0;
    return cap > 0 ? all.slice(0, cap) : all;
  }, [gameModule]);

  const bomRubric = uM(() => {
    if (gameModule && gameModule.bomRubric) return gameModule.bomRubric;
    return [];
  }, [gameModule]);

  const copy = uM(() => {
    if (theme && theme.id === "gp") {
      return {
        eyebrow: tc("🏁 Pit wall brief"),
        title: tc("Build the race-winning deployment"),
        deck: tc("Read the telemetry, respect the constraints, and submit a BOM that can survive race day pressure."),
        statusLabel: tc("Garage status"),
        statusReady: tc("Your team is locked on this client brief."),
        statusWaiting: tc("Waiting for team allocations to settle across the grid."),
        contextTitle: tc("Race context"),
        risksTitle: tc("Pressure points"),
        constraintsTitle: tc("Pit lane limits"),
        requirementTitle: tc("Decisive requirement"),
        licensingTitle: tc("Licensing telemetry"),
        rubricTitle: tc("Scrutineering rubric"),
        uploadTitle: tc("Send the final setup sheet"),
        uploadDeck: tc("Upload the BOM once your team signs off on the architecture, licensing, and resilience plan."),
      };
    }
    if (theme && theme.id === "cup") {
      return {
        eyebrow: tc("🏟️ Tactical board"),
        title: tc("Shape the match-winning solution"),
        deck: tc("Study the opponent, protect the weak zones, and deliver a BOM ready for ninety relentless minutes."),
        statusLabel: tc("Locker room status"),
        statusReady: tc("Your squad is preparing this customer play."),
        statusWaiting: tc("Waiting for the remaining teams to lock their tactics."),
        contextTitle: tc("Match context"),
        risksTitle: tc("Pressure points"),
        constraintsTitle: tc("Tournament rules"),
        requirementTitle: tc("Winning condition"),
        licensingTitle: tc("Licensing board"),
        rubricTitle: tc("Scouting rubric"),
        uploadTitle: tc("Hand in the match plan"),
        uploadDeck: tc("Upload the BOM when the squad agrees on the formation, substitutions, and defensive cover."),
      };
    }
    if (theme && theme.id === "nfl") {
      return {
        eyebrow: tc("🏈 Playbook package"),
        title: tc("Call the right play under pressure"),
        deck: tc("Read the field, cover the gaps, and hand over a BOM that can execute for four hard quarters."),
        statusLabel: tc("Sideline status"),
        statusReady: tc("Your team owns this drive."),
        statusWaiting: tc("Waiting for all teams to finish the draft board."),
        contextTitle: tc("Drive context"),
        risksTitle: tc("Coverage gaps"),
        constraintsTitle: tc("Clock management"),
        requirementTitle: tc("Must-convert play"),
        licensingTitle: tc("Licensing breakdown"),
        rubricTitle: tc("Film room rubric"),
        uploadTitle: tc("Deliver the play sheet"),
        uploadDeck: tc("Upload the BOM once your offense, defense, and failover package are aligned."),
      };
    }
    if (theme && theme.id === "realm") {
      return {
        eyebrow: tc("⚔️ War council"),
        title: tc("Assemble the kingdom's defense"),
        deck: tc("Read the realm, fortify the borders, and submit a BOM worthy of the throne room."),
        statusLabel: tc("Council status"),
        statusReady: tc("Your house is sworn to this brief."),
        statusWaiting: tc("Waiting for the other houses to choose their battles."),
        contextTitle: tc("Realm context"),
        risksTitle: tc("Threats on the horizon"),
        constraintsTitle: tc("Ancient constraints"),
        requirementTitle: tc("Royal decree"),
        licensingTitle: tc("Licensing codex"),
        rubricTitle: tc("Council rubric"),
        uploadTitle: tc("Seal the battle plan"),
        uploadDeck: tc("Upload the BOM once your champions agree on the stronghold, licenses, and contingency plan."),
      };
    }
    return {
      eyebrow: tc("📋 Game plan"),
      title: tc("Design the winning customer plan"),
      deck: tc("Absorb the brief, respect the constraints, and submit a BOM that balances business fit with technical precision."),
      statusLabel: tc("Brief status"),
      statusReady: tc("Your team is assigned to this customer brief."),
      statusWaiting: tc("Waiting for all team assignments to finish."),
      contextTitle: tc("Client context"),
      risksTitle: tc("Pain points"),
      constraintsTitle: tc("Constraints"),
      requirementTitle: tc("Special requirement"),
      licensingTitle: tc("Licensing guide"),
      rubricTitle: tc("BOM scoring rubric"),
      uploadTitle: tc("Submit the BOM"),
      uploadDeck: tc("Upload the BOM after your team validates the architecture, license tier, and implementation fit."),
    };
  }, [theme]);

  const bomPoints = uM(() => {
    if (window.ScoringEngine && window.ScoringEngine.getBomPoints) {
      return window.ScoringEngine.getBomPoints((me && me.audienceMode) || "internal");
    }
    return 500;
  }, [me]);

  const addPickerToast = (head, msg, kind = "") => {
    const id = Date.now() + Math.random();
    setPickerToasts(prev => [...prev.slice(-3), { id, head, msg, kind }]);
    setTimeout(() => {
      setPickerToasts(prev => prev.filter(t => t.id !== id));
    }, 3500);
  };

  const teamMetaById = uM(() => {
    const map = {};
    ((theme && theme.teams) || []).forEach(team => {
      map[team.id] = team;
    });
    return map;
  }, [theme]);

  const isCaptain = uM(() => {
    if (me && me.isCaptain === true) return true;
    const role = String((me && me.role) || "").toLowerCase();
    return role === "captain";
  }, [me]);

  const launchPickCelebration = (selection) => {
    if (!selection) return;
    const pickedTeam = teamMetaById[selection.team_id] || null;
    const pickedBrief = briefs[selection.scenario_index] || null;
    if (!pickedTeam || !pickedBrief) return;
    setPickCelebration({
      key: `${selection.team_id}-${selection.scenario_index}-${selection.timestamp || Date.now()}`,
      team: pickedTeam,
      brief: pickedBrief,
    });
  };

  const fetchGameplanStatus = () => {
    if (!gameId) return Promise.resolve(null);
    return fetch(`/api/g/${gameId}/gameplan/status`)
      .then(r => r.ok ? r.json() : null)
      .then(data => {
        if (!data) return null;
        const incomingSelections = Array.isArray(data.selections) ? data.selections : [];
        const prevSelections = Array.isArray(prevSelectionsRef.current) ? prevSelectionsRef.current : null;
        if (prevSelections) {
          const prevKeys = new Set(prevSelections.map(s => `${s.team_id}:${s.scenario_index}`));
          const newestSelection = incomingSelections.find(s => !prevKeys.has(`${s.team_id}:${s.scenario_index}`));
          if (newestSelection) launchPickCelebration(newestSelection);
        }
        prevSelectionsRef.current = incomingSelections;
        setGpStatus(data);
        // Hydrate uploads for my team from server (survives refresh)
        try {
          const subs = (data && data.submissions) || {};
          const mine = (me && me.tid && subs[me.tid]) || null;
          if (mine) {
            if (mine.bom_file) setBomUpload({ url: mine.bom_file, filename: mine.bom_filename || "BOM", size: mine.bom_size || 0, uploaded_at: mine.bom_uploaded_at || 0 });
            if (mine.architecture_file) setArchUpload({ url: mine.architecture_file, filename: mine.architecture_filename || "Arquitectura", size: mine.architecture_size || 0, uploaded_at: mine.architecture_uploaded_at || 0 });
          }
        } catch (_) {}
        return data;
      })
      .catch(() => null);
  };

  // Poll game plan status
  uE(() => {
    if (!gameId) return;
    fetchGameplanStatus();
    const id = setInterval(fetchGameplanStatus, 3000);
    return () => clearInterval(id);
  }, [gameId, briefs, theme]);

  uE(() => {
    setIsLive(false);
    const id = requestAnimationFrame(() => setIsLive(true));
    return () => cancelAnimationFrame(id);
  }, []);

  uE(() => {
    if (!pickCelebration) return;
    if (celebrationTimerRef.current) clearTimeout(celebrationTimerRef.current);
    celebrationTimerRef.current = setTimeout(() => setPickCelebration(null), 3000);
    return () => {
      if (celebrationTimerRef.current) clearTimeout(celebrationTimerRef.current);
    };
  }, [pickCelebration]);

  // Find my team's selection
  const myTeamId = me.tid;
  const mySelection = uM(() => {
    if (!gpStatus || !gpStatus.selections) return null;
    return gpStatus.selections.find(s => s.team_id === myTeamId);
  }, [gpStatus, myTeamId]);

  const myTeam = uM(() => {
    const teams = (theme && theme.teams) || [];
    return teams.find(t => t.id === myTeamId) || null;
  }, [theme, myTeamId]);

  const currentTurnTeam = uM(() => {
    if (!gpStatus || !gpStatus.current_turn) return null;
    return teamMetaById[gpStatus.current_turn] || null;
  }, [gpStatus, teamMetaById]);

  const selectionByScenario = uM(() => {
    const map = {};
    ((gpStatus && gpStatus.selections) || []).forEach(sel => {
      map[sel.scenario_index] = sel;
    });
    return map;
  }, [gpStatus]);

  const availableCount = uM(() => {
    return briefs.filter((_, idx) => !selectionByScenario[idx]).length;
  }, [briefs, selectionByScenario]);

  const displayBrief = uM(() => {
    if (mySelection && briefs[mySelection.scenario_index]) return briefs[mySelection.scenario_index];
    if (briefs.length > 0) return briefs[briefIdx];
    return null;
  }, [mySelection, briefs, briefIdx]);

  const previewBrief = uM(() => {
    if (previewIdx === null || !briefs[previewIdx]) return null;
    return briefs[previewIdx];
  }, [briefs, previewIdx]);

  const contextParas = uM(() => {
    if (!displayBrief || !displayBrief.context) return [];
    return String(displayBrief.context).split("\n").map(p => p.trim()).filter(Boolean);
  }, [displayBrief]);

  const previewContextParas = uM(() => {
    if (!previewBrief || !previewBrief.context) return [];
    return String(previewBrief.context).split("\n").map(p => p.trim()).filter(Boolean);
  }, [previewBrief]);

  const selectionCount = gpStatus && gpStatus.selections ? gpStatus.selections.length : 0;
  const totalTeams = theme && theme.teams ? theme.teams.length : 0;
  const teamColor = (myTeam && myTeam.color) || "var(--accent)";

  const canPickNow = !!(gpStatus && gpStatus.current_turn === myTeamId && isCaptain && !mySelection);

  const getBriefComplexity = (brief) => {
    if (!brief) return "";
    return brief.difficulty || brief.complexity || brief.complexityHint || brief.complexity_hint || "";
  };

  const handleScenarioSelect = (scenarioIndex) => {
    const captainEmail = typeof Session !== 'undefined' ? Session.getEmail() : '';
    if (!captainEmail) {
      addPickerToast(tc("Pick failed"), tc("Captain email not available in session."), "danger");
      return;
    }
    setSelectingIdx(scenarioIndex);
    fetch(`/api/g/${gameId}/gameplan/select`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ captain_email: captainEmail, scenario_index: scenarioIndex })
    })
      .then(async r => {
        const data = await r.json().catch(() => null);
        if (!r.ok) throw new Error((data && (data.detail || data.message)) || tc("Try again"));
        return fetchGameplanStatus();
      })
      .then(() => setPreviewIdx(null))
      .catch(err => addPickerToast(tc("Pick failed"), (err && err.message) || tc("Try again"), "danger"))
      .finally(() => setSelectingIdx(null));
  };

  const handleUpload = (kind, evt) => {
    const file = evt && evt.target && evt.target.files && evt.target.files[0];
    if (!file) return;
    if (!gameId) return;
    const email = (typeof Session !== 'undefined' ? Session.getEmail() : '') || (me && me.email) || '';
    if (!email) { setUploadError(tc("Sesión no encontrada. Recarga la página.")); return; }
    setUploadError("");
    if (kind === 'bom') setBomUploading(true); else setArchUploading(true);
    const fd = new FormData();
    fd.append('kind', kind);
    fd.append('email', email);
    fd.append('file', file);
    fetch(`/api/g/${gameId}/gameplan/upload`, { method: 'POST', body: fd })
      .then(async r => {
        if (!r.ok) {
          const txt = await r.text().catch(() => '');
          let msg = txt;
          try { msg = JSON.parse(txt).detail || txt; } catch (_) {}
          throw new Error(msg || `HTTP ${r.status}`);
        }
        return r.json();
      })
      .then(data => {
        const entry = { url: data.url, filename: data.filename, size: data.size, uploaded_at: Date.now() / 1000 };
        if (kind === 'bom') setBomUpload(entry); else setArchUpload(entry);
        // Reset the input so the same file can be re-selected if needed
        try { evt.target.value = ''; } catch (_) {}
      })
      .catch(err => {
        setUploadError((err && err.message) || tc("Falló la subida. Intenta de nuevo."));
      })
      .finally(() => {
        if (kind === 'bom') setBomUploading(false); else setArchUploading(false);
      });
  };

  const renderPickerToasts = () => {
    if (!pickerToasts.length) return null;
    return (
      <div className="toasts">
        {pickerToasts.map(t => (
          <div key={t.id} className={`toast ${t.kind || ""}`}>
            <div className="t">{t.head}</div>
            <div>{t.msg}</div>
          </div>
        ))}
      </div>
    );
  };

  const renderPickCelebration = () => {
    if (!pickCelebration) return null;
    const confetti = Array.from({ length: 18 }).map((_, i) => ({
      left: 4 + (i * 5.1),
      dur: 1.9 + ((i % 5) * 0.14),
      delay: (i % 6) * 0.05,
      bg: i % 2 === 0
        ? (pickCelebration.team.color || "var(--accent)")
        : (i % 3 === 0 ? "var(--accent)" : "var(--warning)"),
      rot: (i * 37) % 360,
    }));
    return (
      <div className="gp-picker-celebration" style={{ "--pick-color": pickCelebration.team.color || "var(--accent)" }}>
        <div className="confetti" aria-hidden="true">
          {confetti.map((piece, idx) => (
            <span
              key={`${pickCelebration.key}-${idx}`}
              style={{
                left: `${piece.left}%`,
                background: piece.bg,
                transform: `rotate(${piece.rot}deg)`,
                animationDuration: `${piece.dur}s`,
                animationDelay: `${piece.delay}s`,
              }}
            />
          ))}
        </div>
        <div className="gp-picker-celebration-card">
          <div className="gp-picker-celebration-team">{pickCelebration.team.emoji || "🎯"}</div>
          <div className="gp-picker-celebration-copy">
            <div className="gp-picker-celebration-kicker mono">{tc("Scenario locked")}</div>
            <h2>{`${tc(pickCelebration.team.name)} ${tc("picked")} ${tc(pickCelebration.brief.client)}!`}</h2>
          </div>
        </div>
      </div>
    );
  };

  const renderPicker = () => {
    const currentTurnColor = (currentTurnTeam && currentTurnTeam.color) || teamColor;
    return (
      <div className="screen">
        <section
          className={`gameplan gameplan--${(theme && theme.id) || "default"} ${isLive ? "is-live" : ""}`}
          style={{ "--team-color": teamColor, "--turn-color": currentTurnColor }}
        >
          {renderPickerToasts()}
          {renderPickCelebration()}

          <div className="gameplan-main gp-picker-shell" style={{ gridColumn: "1 / -1" }}>
            <header className="gameplan-hero gp-picker-hero" style={{ "--delay": "60ms" }}>
              <div className="gameplan-hero-copy">
                <div className="gameplan-kicker">{tc("🎯 Scenario draft")}</div>
                <h1 className="gameplan-client gp-picker-title">{tc("Choose the customer battle")}</h1>
                <p className="gameplan-deck">
                  {canPickNow
                    ? tc("Your team is on the clock. Browse the full scenario pool, zoom into the details, and lock one shared brief before another captain does.")
                    : tc("Browse every customer scenario while the draft runs. Captains can only lock a brief when their team is officially on the clock.")}
                </p>

                <div className="gameplan-hero-chips">
                  <span className="gameplan-chip gameplan-chip--team">
                    {myTeam ? `${myTeam.emoji || "🎯"} ${tc(myTeam.name)}` : tc("Your team")}
                  </span>
                  <span className="gameplan-chip mono">{availableCount} {tc("available")}</span>
                  {currentTurnTeam && (
                    <span className="gameplan-chip gp-picker-chip-turn" style={{ "--pill-color": currentTurnTeam.color || "var(--accent)" }}>
                      {tc("On the clock")}: {`${currentTurnTeam.emoji || "🎯"} ${tc(currentTurnTeam.short || currentTurnTeam.name || currentTurnTeam.id)}`}
                    </span>
                  )}
                </div>
              </div>

              <div className="gameplan-hero-side">
                <div className="gameplan-logo-badge gp-picker-turn-badge">{currentTurnTeam ? (currentTurnTeam.emoji || "🎯") : "⏳"}</div>
                <div className="gameplan-hero-meta gp-picker-meta">
                  <span className="gameplan-meta-label mono">{tc("Draft status")}</span>
                  <strong>
                    {canPickNow
                      ? tc("Your captain can pick now")
                      : (isCaptain ? tc("Browse and be ready") : tc("Captain picks for your team"))}
                  </strong>
                  <span>{selectionCount} / {totalTeams} {tc("teams locked in")}</span>
                </div>
              </div>
            </header>

            <section className="gameplan-card gp-picker-rail-card" style={{ "--delay": "130ms" }}>
              <div className="gameplan-section-head">
                <div>
                  <div className="gameplan-section-kicker">{tc("Turn order")}</div>
                  <h2 className="gameplan-section-title">{tc("Lowest score picks first")}</h2>
                </div>
              </div>

              <div className="gp-picker-rail" role="list" aria-label={tc("Scenario pick order")}>
                {((gpStatus && gpStatus.turn_order) || []).map((slot, idx) => {
                  const slotTeam = teamMetaById[slot.team_id] || null;
                  const slotSelection = ((gpStatus && gpStatus.selections) || []).find(sel => sel.team_id === slot.team_id) || null;
                  const slotBrief = slotSelection ? briefs[slotSelection.scenario_index] : null;
                  const isCurrent = gpStatus && gpStatus.current_turn === slot.team_id;
                  const isPicked = !!slotSelection;
                  const isMine = myTeamId === slot.team_id;
                  return (
                    <div
                      key={`${slot.team_id}-${idx}`}
                      className={[
                        "gp-picker-rail-pill",
                        isPicked ? "is-picked" : "",
                        isCurrent ? "is-current" : "",
                        !isPicked && !isCurrent ? "is-waiting" : "",
                        isMine ? "is-mine" : ""
                      ].filter(Boolean).join(" ")}
                      style={{ "--pill-color": (slotTeam && slotTeam.color) || "var(--accent)" }}
                      role="listitem"
                    >
                      <div className="gp-picker-rail-order mono">#{idx + 1}</div>
                      <div className="gp-picker-rail-main">
                        <div className="gp-picker-rail-head">
                          <strong>{`${(slotTeam && slotTeam.emoji) || "🎯"} ${tc((slotTeam && (slotTeam.short || slotTeam.name)) || slot.team_id)}`}</strong>
                          <span className="mono">{slot.total_score}</span>
                        </div>
                        <div className="gp-picker-rail-sub">
                          {isPicked ? (
                            <>
                              <span>✓ {tc("Picked")}</span>
                              <span>{`${(slotBrief && slotBrief.logo) || "📋"} ${slotBrief ? tc(slotBrief.client) : tc("Scenario locked")}`}</span>
                            </>
                          ) : isCurrent ? (
                            <>
                              <span className="gp-picker-live-dot" />
                              <span>{tc("PICKING NOW")}</span>
                            </>
                          ) : (
                            <span>{tc("Waiting for turn")}</span>
                          )}
                          {isMine && <em className="mono">{tc("YOUR TEAM")}</em>}
                        </div>
                      </div>
                    </div>
                  );
                })}
              </div>
            </section>

            {previewBrief ? (
              <section className="gameplan-card gp-picker-preview" style={{ "--delay": "200ms" }}>
                <div className="gp-picker-preview-topbar">
                  <button className="btn btn-ghost gp-picker-back" type="button" onClick={() => setPreviewIdx(null)}>
                    {tc("← Back to all scenarios")}
                  </button>
                  <div className="gp-picker-preview-status-wrap">
                    {selectionByScenario[previewIdx] ? (
                      <span className="gp-picker-status gp-picker-status--taken">
                        {`${tc("Taken by")} ${((teamMetaById[selectionByScenario[previewIdx].team_id] || {}).emoji) || "🎯"} ${tc(((teamMetaById[selectionByScenario[previewIdx].team_id] || {}).short) || ((teamMetaById[selectionByScenario[previewIdx].team_id] || {}).name) || selectionByScenario[previewIdx].team_id)}`}
                      </span>
                    ) : (
                      <span className="gp-picker-status gp-picker-status--available">{tc("Available")}</span>
                    )}
                  </div>
                </div>

                <div className="gp-picker-preview-hero">
                  <div className="gp-picker-preview-logo">{previewBrief.logo || "📋"}</div>
                  <div className="gp-picker-preview-copy">
                    <div className="gameplan-section-kicker">{tc("Scenario preview")}</div>
                    <h2 className="gameplan-section-title gp-picker-preview-title">{tc(previewBrief.client)}</h2>
                    <div className="gameplan-industry mono">{tc(previewBrief.industry)}</div>
                    <div className="gp-picker-preview-chips">
                      {getBriefComplexity(previewBrief) && <span className="gameplan-chip mono">{tc(getBriefComplexity(previewBrief))}</span>}
                      {previewBrief.licensingRequirements && <span className="gameplan-chip mono">{`${tc("Tier")} · ${tc(previewBrief.licensingRequirements.expectedTier)}`}</span>}
                      {previewBrief.endpoints ? <span className="gameplan-chip mono">{`${(previewBrief.endpoints || 0).toLocaleString()} ${tc("endpoints")}`}</span> : null}
                    </div>
                  </div>
                </div>

                <div className="gp-picker-preview-grid">
                  <div className="gp-picker-preview-block">
                    <div className="gameplan-section-kicker">{copy.contextTitle}</div>
                    <div className="gp-picker-preview-stack">
                      {previewContextParas.length > 0 ? previewContextParas.map((para, idx) => (
                        <p key={idx} className="gameplan-context">{tc(para)}</p>
                      )) : <p className="gameplan-context">{tc("No context provided.")}</p>}
                    </div>
                  </div>

                  <div className="gp-picker-preview-block">
                    <div className="gameplan-section-kicker">{tc("Requirements summary")}</div>
                    <ul className="gameplan-list gameplan-list--risk">
                      {(previewBrief.painPoints || []).slice(0, 4).map((point, idx) => <li key={idx}>{tc(point)}</li>)}
                    </ul>
                  </div>

                  <div className="gp-picker-preview-block">
                    <div className="gameplan-section-kicker">{copy.constraintsTitle}</div>
                    <ul className="gameplan-list gameplan-list--constraint">
                      {(previewBrief.constraints || []).slice(0, 4).map((constraint, idx) => <li key={idx}>{tc(constraint)}</li>)}
                    </ul>
                  </div>

                  <div className="gp-picker-preview-block">
                    <div className="gameplan-section-kicker">{copy.licensingTitle}</div>
                    {previewBrief.licensingRequirements ? (
                      <div className="gp-picker-license-card">
                        <p className="gameplan-lic-copy">{tc(previewBrief.licensingRequirements.description)}</p>
                        <div className="gameplan-lic-details">
                          <span><em>{tc("Tier")}</em><strong>{tc(previewBrief.licensingRequirements.expectedTier)}</strong></span>
                          <span><em>{tc("Term")}</em><strong>{tc(previewBrief.licensingRequirements.subscriptionTerm)}</strong></span>
                        </div>
                      </div>
                    ) : (
                      <p className="gameplan-context">{tc("No licensing guidance included for this brief.")}</p>
                    )}
                  </div>
                </div>

                {previewBrief.trickRequirement && (
                  <div className="gameplan-trick gp-picker-preview-trick">
                    <div className="gameplan-section-kicker">{copy.requirementTitle}</div>
                    <h3 className="gameplan-mini-title">{tc("The make-or-break twist")}</h3>
                    <p>{tc(previewBrief.trickRequirement)}</p>
                  </div>
                )}

                {(((!selectionByScenario[previewIdx]) || (gpStatus && gpStatus.pool_exhausted)) && isCaptain && gpStatus && gpStatus.current_turn === myTeamId) && (
                  <div className="gp-picker-preview-cta">
                    <button
                      type="button"
                      className="btn btn-primary btn-lg gp-picker-select-btn"
                      disabled={selectingIdx === previewIdx}
                      onClick={() => handleScenarioSelect(previewIdx)}
                    >
                      {selectingIdx === previewIdx
                        ? tc("Locking scenario…")
                        : `${tc("Select this scenario for")} ${tc((myTeam && myTeam.name) || "your team")}`}
                    </button>
                  </div>
                )}
              </section>
            ) : (
              <section className="gameplan-card gp-picker-grid-card" style={{ "--delay": "200ms" }}>
                <div className="gameplan-section-head">
                  <div>
                    <div className="gameplan-section-kicker">{tc("Scenario pool")}</div>
                    <h2 className="gameplan-section-title">{tc("Open every brief before you commit")}</h2>
                  </div>
                </div>

                <div className="gp-picker-grid">
                  {briefs.map((brief, idx) => {
                    const selection = selectionByScenario[idx] || null;
                    const ownerTeam = selection ? (teamMetaById[selection.team_id] || null) : null;
                    const complexity = getBriefComplexity(brief);
                    return (
                      <button
                        key={brief.id || idx}
                        type="button"
                        className={`gp-picker-card ${selection ? "is-taken" : "is-available"}`}
                        onClick={() => setPreviewIdx(idx)}
                      >
                        <div className="gp-picker-card-head">
                          <div className="gp-picker-card-logo">{brief.logo || "📋"}</div>
                          <span className={`gp-picker-status ${selection ? "gp-picker-status--taken" : "gp-picker-status--available"}`}>
                            {selection
                              ? `${tc("Taken by")} ${(ownerTeam && ownerTeam.emoji) || "🎯"} ${tc((ownerTeam && (ownerTeam.short || ownerTeam.name)) || selection.team_id)}`
                              : tc("Available")}
                          </span>
                        </div>

                        <div className="gp-picker-card-copy">
                          <div className="gp-picker-card-kicker mono">{tc(brief.industry)}</div>
                          <h3 className="gp-picker-card-title">{tc(brief.client)}</h3>
                          <p className="gp-picker-card-snippet">
                            {tc((String(brief.context || "").split("\n").map(p => p.trim()).filter(Boolean)[0]) || "Open the preview to inspect context, constraints, and licensing detail.")}
                          </p>
                        </div>

                        <div className="gp-picker-card-foot">
                          <div className="gp-picker-card-meta">
                            {complexity && <span className="mono">{tc(complexity)}</span>}
                            {brief.licensingRequirements && <span className="mono">{`${tc("Tier")} · ${tc(brief.licensingRequirements.expectedTier)}`}</span>}
                          </div>
                          <span className="gp-picker-card-link mono">{tc("Open preview →")}</span>
                        </div>
                      </button>
                    );
                  })}
                </div>
              </section>
            )}
          </div>
        </section>
      </div>
    );
  };

  if (!gpStatus) {
    return (
      <div className="screen">
        <div className={`gameplan gameplan--${(theme && theme.id) || "default"}`}>
          <div className="gameplan-empty">
            <div className="grade-loading" style={{ margin: "0 auto", maxWidth: 360 }}>
              <div className="grade-loading-spinner" />
              <div className="grade-loading-text">
                <strong>{tc("Loading game plan state…")}</strong>
                <span>{tc("Checking turn order and scenario selections.")}</span>
              </div>
            </div>
          </div>
        </div>
      </div>
    );
  }

  if (briefs.length === 0) {
    return (
      <div className="screen">
        <div className={`gameplan gameplan--${(theme && theme.id) || "default"}`}>
          <div className="gameplan-empty">
            <h2>{tc("📋 Game Plan")}</h2>
            <p>{tc("Waiting for scenarios to be loaded by the proctor…")}</p>
          </div>
        </div>
      </div>
    );
  }

  if (gpStatus.all_selected === true && !mySelection) {
    return (
      <div className="screen">
        <div className={`gameplan gameplan--${(theme && theme.id) || "default"}`}>
          <div className="gameplan-empty">
            <h2>{tc("📋 Game Plan")}</h2>
            <p>{tc("All scenarios taken before your team picked — proctor will assign one.")}</p>
          </div>
        </div>
      </div>
    );
  }

  if (gpStatus.all_selected === false && !mySelection) {
    return renderPicker();
  }

  if (!displayBrief) {
    return (
      <div className="screen">
        <div className={`gameplan gameplan--${(theme && theme.id) || "default"}`}>
          <div className="gameplan-empty">
            <h2>{tc("📋 Game Plan")}</h2>
            <p>{tc("No customer briefs available for this module.")}</p>
            {onFinish && (
              <button className="btn btn-primary" onClick={onFinish} style={{marginTop: 24}}>
                {tc("Continue →")}
              </button>
            )}
          </div>
        </div>
      </div>
    );
  }

  return (
    <div className="screen">
      <section
        className={`gameplan gameplan--${(theme && theme.id) || "default"} ${isLive ? "is-live" : ""}`}
        style={{ "--team-color": teamColor }}
      >
        {renderPickerToasts()}
        {renderPickCelebration()}
        <div className="gameplan-main">
          <header className="gameplan-hero" style={{ "--delay": "80ms" }}>
            <div className="gameplan-hero-copy">
              <div className="gameplan-kicker">{copy.eyebrow}</div>
              <h1 className="gameplan-client">{tc(displayBrief.client)}</h1>
              <div className="gameplan-industry mono">{tc(displayBrief.industry)}</div>
              <p className="gameplan-deck">{copy.deck}</p>

              <div className="gameplan-hero-chips">
                <span className="gameplan-chip gameplan-chip--team">
                  {myTeam ? `${myTeam.emoji || "🎯"} ${tc(myTeam.name)}` : tc("Your team")}
                </span>
                <span className="gameplan-chip">
                  {copy.statusLabel}: {mySelection ? copy.statusReady : copy.statusWaiting}
                </span>
                {totalTeams > 0 && (
                  <span className="gameplan-chip mono">
                    {selectionCount} / {totalTeams} {tc("teams assigned")}
                  </span>
                )}
              </div>
            </div>

            <div className="gameplan-hero-side">
              <div className="gameplan-logo-badge">{displayBrief.logo || "📋"}</div>
              <div className="gameplan-hero-meta">
                <span className="gameplan-meta-label mono">{tc("Customer brief")}</span>
                <strong>{tc(displayBrief.client)}</strong>
                <span>{tc(displayBrief.budget)}</span>
              </div>
            </div>
          </header>

          <section className="gameplan-card gameplan-card--brief" style={{ "--delay": "150ms" }}>
            <div className="gameplan-section-head">
              <div>
                <div className="gameplan-section-kicker">{copy.contextTitle}</div>
                <h2 className="gameplan-section-title">{tc("Read the customer room before you design")}</h2>
              </div>
            </div>

            <div className="gameplan-context-stack">
              {contextParas.length > 0 ? contextParas.map((para, idx) => (
                <p key={idx} className="gameplan-context">{tc(para)}</p>
              )) : <p className="gameplan-context">{tc("No context provided.")}</p>}
            </div>
          </section>

          <section className="gameplan-card gameplan-card--requirements" style={{ "--delay": "220ms" }}>
            <div className="gameplan-split">
              <div className="gameplan-section-block">
                <div className="gameplan-section-kicker">{copy.risksTitle}</div>
                <h3 className="gameplan-mini-title">{tc("Signals you must solve")}</h3>
                <ul className="gameplan-list gameplan-list--risk">
                  {(displayBrief.painPoints || []).map((p, i) => <li key={i}>{tc(p)}</li>)}
                </ul>
              </div>

              <div className="gameplan-section-block">
                <div className="gameplan-section-kicker">{copy.constraintsTitle}</div>
                <h3 className="gameplan-mini-title">{tc("Boundaries you cannot break")}</h3>
                <ul className="gameplan-list gameplan-list--constraint">
                  {(displayBrief.constraints || []).map((c, i) => <li key={i}>{tc(c)}</li>)}
                </ul>
              </div>
            </div>

            {displayBrief.trickRequirement && (
              <div className="gameplan-trick">
                <div className="gameplan-section-kicker">{copy.requirementTitle}</div>
                <h3 className="gameplan-mini-title">{tc("This is where teams win or miss")}</h3>
                <p>{tc(displayBrief.trickRequirement)}</p>
              </div>
            )}
          </section>
        </div>

        <aside className="gameplan-side">
          <section className="gameplan-card gameplan-card--stats" style={{ "--delay": "120ms" }}>
            <div className="gameplan-section-kicker">{tc("Telemetry")}</div>
            <div className="gameplan-stats">
              <div className="gameplan-stat">
                <span>{tc("Employees")}</span>
                <strong className="mono">{(displayBrief.employees || 0).toLocaleString()}</strong>
              </div>
              <div className="gameplan-stat">
                <span>{tc("Sites")}</span>
                <strong className="mono">{displayBrief.sites}</strong>
              </div>
              <div className="gameplan-stat">
                <span>{tc("Endpoints")}</span>
                <strong className="mono">{(displayBrief.endpoints || 0).toLocaleString()}</strong>
              </div>
              <div className="gameplan-stat">
                <span>{tc("Budget")}</span>
                <strong>{tc(displayBrief.budget)}</strong>
              </div>
            </div>
          </section>

          {displayBrief.licensingRequirements && (
            <section className="gameplan-card gameplan-card--licensing" style={{ "--delay": "190ms" }}>
              <div className="gameplan-section-kicker">{copy.licensingTitle}</div>
              <h2 className="gameplan-section-title">{tc("Align the commercial package")}</h2>
              <p className="gameplan-lic-copy">{tc(displayBrief.licensingRequirements.description)}</p>
              <div className="gameplan-lic-details">
                <span><em>{tc("Tier")}</em><strong>{tc(displayBrief.licensingRequirements.expectedTier)}</strong></span>
                <span><em>{tc("Endpoints")}</em><strong>{(displayBrief.licensingRequirements.endpointCount || 0).toLocaleString()}</strong></span>
                <span><em>{tc("Term")}</em><strong>{tc(displayBrief.licensingRequirements.subscriptionTerm)}</strong></span>
                {displayBrief.licensingRequirements.deviceAdmin !== undefined && (
                  <span>
                    <em>{tc("Device Admin")}</em>
                    <strong>
                      {displayBrief.licensingRequirements.deviceAdmin
                        ? `${tc("Yes")} · ${(displayBrief.licensingRequirements.deviceAdminDevices || 0).toLocaleString()}`
                        : tc("No")}
                    </strong>
                  </span>
                )}
              </div>
            </section>
          )}

          {bomRubric.length > 0 && (
            <section className="gameplan-card gameplan-card--rubric" style={{ "--delay": "260ms" }}>
              <div className="gameplan-section-kicker">{copy.rubricTitle}</div>
              <h2 className="gameplan-section-title">{tc("How the facilitators will score it")}</h2>
              <div className="gameplan-rubric-list">
                {bomRubric.map((r, i) => (
                  <div key={i} className="gameplan-rubric-item">
                    <div className="gameplan-rubric-copy">
                      <div className="gameplan-rubric-cat">{tc(r.category)}</div>
                      <div className="gameplan-rubric-check">{tc(r.check)}</div>
                    </div>
                    <div className="gameplan-rubric-pts mono">+{r.points}</div>
                  </div>
                ))}
              </div>
            </section>
          )}

          <section className="gameplan-card gameplan-card--upload" style={{ "--delay": "330ms" }}>
            <div className="gameplan-upload-head">
              <div>
                <div className="gameplan-section-kicker">{copy.uploadTitle}</div>
                <h2 className="gameplan-section-title">
                  {isCaptain
                    ? <span>👑 {tc("Eres el capitán")} — {tc("sube el GamePlan de tu equipo")}</span>
                    : tc("Finish strong and submit the sheet")}
                </h2>
              </div>
              <span className="gameplan-reward-chip mono">+{bomPoints} {tc("pts")}</span>
            </div>

            <p className="gameplan-upload-copy">{copy.uploadDeck}</p>

            {!isCaptain ? (
              <div className="gameplan-upload-area gameplan-upload-area--locked" style={{ opacity: 0.7, cursor: 'not-allowed', pointerEvents: 'none', border: '1px dashed var(--line)', borderRadius: '8px', padding: '24px', textAlign: 'center' }}>
                <div style={{ fontSize: '2rem', marginBottom: '8px' }}>🔒</div>
                <p style={{ fontWeight: 600, margin: '0 0 4px' }}>{tc("Solo el capitán de tu equipo puede subir el BOM y arquitectura.")}</p>
                <p className="mono" style={{ fontSize: '12px', color: 'var(--ink-muted)', margin: 0 }}>
                  {bomUpload || archUpload
                    ? `${bomUpload ? "✅ BOM" : "⏳ BOM"} · ${archUpload ? "✅ Arquitectura" : "⏳ Arquitectura"}`
                    : tc("Espera a que el capitán de tu equipo suba el GamePlan.")}
                </p>
              </div>
            ) : (
              <div className="gameplan-upload-grid" style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(260px, 1fr))', gap: '14px' }}>
                {/* BOM SLOT */}
                <div className="gameplan-upload-slot" style={{ border: '1px solid var(--line)', borderRadius: '10px', padding: '16px', background: bomUpload ? 'rgba(74,222,128,0.06)' : 'transparent' }}>
                  <input
                    ref={bomFileRef}
                    type="file"
                    accept=".xlsx,.xls,.csv,.pdf,.numbers,.ods"
                    style={{ display: 'none' }}
                    onChange={(e) => handleUpload('bom', e)}
                  />
                  <div style={{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '8px' }}>
                    <div style={{ fontSize: '1.6rem' }}>{bomUpload ? '✅' : '📊'}</div>
                    <div style={{ flex: 1 }}>
                      <div style={{ fontWeight: 700, fontSize: '14px' }}>{tc("BOM (Bill of Materials)")}</div>
                      <div className="mono" style={{ fontSize: '11px', color: 'var(--ink-muted)' }}>.xlsx · .csv · .pdf</div>
                    </div>
                  </div>
                  {bomUpload ? (
                    <div>
                      <div style={{ fontSize: '13px', wordBreak: 'break-all', marginBottom: '6px' }}>
                        <a href={bomUpload.url} target="_blank" rel="noopener noreferrer" style={{ color: 'var(--accent)' }}>{bomUpload.filename}</a>
                        {bomUpload.size ? <span className="mono" style={{ marginLeft: '6px', fontSize: '11px', color: 'var(--ink-muted)' }}>{(bomUpload.size / 1024).toFixed(0)} KB</span> : null}
                      </div>
                      <button
                        className="btn btn-ghost"
                        style={{ fontSize: '12px', padding: '6px 12px' }}
                        disabled={bomUploading}
                        onClick={() => { if (bomFileRef.current) bomFileRef.current.click(); }}
                      >
                        {bomUploading ? tc("Subiendo…") : tc("Reemplazar BOM")}
                      </button>
                    </div>
                  ) : (
                    <button
                      className="btn btn-primary"
                      style={{ width: '100%' }}
                      disabled={bomUploading}
                      onClick={() => { if (bomFileRef.current) bomFileRef.current.click(); }}
                    >
                      {bomUploading ? tc("Subiendo…") : tc("📎 Subir BOM")}
                    </button>
                  )}
                </div>

                {/* ARCHITECTURE SLOT */}
                <div className="gameplan-upload-slot" style={{ border: '1px solid var(--line)', borderRadius: '10px', padding: '16px', background: archUpload ? 'rgba(74,222,128,0.06)' : 'transparent' }}>
                  <input
                    ref={archFileRef}
                    type="file"
                    accept="image/*,.pdf,.svg"
                    style={{ display: 'none' }}
                    onChange={(e) => handleUpload('architecture', e)}
                  />
                  <div style={{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '8px' }}>
                    <div style={{ fontSize: '1.6rem' }}>{archUpload ? '✅' : '🏗️'}</div>
                    <div style={{ flex: 1 }}>
                      <div style={{ fontWeight: 700, fontSize: '14px' }}>{tc("Arquitectura (diagrama)")}</div>
                      <div className="mono" style={{ fontSize: '11px', color: 'var(--ink-muted)' }}>jpg · png · pdf · svg</div>
                    </div>
                  </div>
                  {archUpload ? (
                    <div>
                      <div style={{ fontSize: '13px', wordBreak: 'break-all', marginBottom: '6px' }}>
                        <a href={archUpload.url} target="_blank" rel="noopener noreferrer" style={{ color: 'var(--accent)' }}>{archUpload.filename}</a>
                        {archUpload.size ? <span className="mono" style={{ marginLeft: '6px', fontSize: '11px', color: 'var(--ink-muted)' }}>{(archUpload.size / 1024).toFixed(0)} KB</span> : null}
                      </div>
                      <button
                        className="btn btn-ghost"
                        style={{ fontSize: '12px', padding: '6px 12px' }}
                        disabled={archUploading}
                        onClick={() => { if (archFileRef.current) archFileRef.current.click(); }}
                      >
                        {archUploading ? tc("Subiendo…") : tc("Reemplazar arquitectura")}
                      </button>
                    </div>
                  ) : (
                    <button
                      className="btn btn-primary"
                      style={{ width: '100%' }}
                      disabled={archUploading}
                      onClick={() => { if (archFileRef.current) archFileRef.current.click(); }}
                    >
                      {archUploading ? tc("Subiendo…") : tc("📎 Subir arquitectura")}
                    </button>
                  )}
                </div>
              </div>
            )}

            {uploadError && isCaptain ? (
              <div style={{ marginTop: '10px', padding: '10px 12px', borderRadius: '8px', background: 'rgba(225,6,0,0.1)', border: '1px solid rgba(225,6,0,0.3)', color: '#ff6b6b', fontSize: '13px' }}>
                ⚠️ {uploadError}
              </div>
            ) : null}

            {isCaptain && (bomUpload || archUpload) ? (
              <div className="gameplan-upload-done" style={{ marginTop: '12px' }}>
                <div>
                  <strong>
                    {bomUpload && archUpload ? tc("✅ GamePlan completo") : tc("⏳ Falta un archivo")}
                  </strong>
                  <p style={{ margin: 0, fontSize: '12px', color: 'var(--ink-muted)' }}>
                    {bomUpload && archUpload ? tc("Ready for facilitator review.") : tc("Sube ambos archivos: BOM y arquitectura.")}
                  </p>
                </div>
              </div>
            ) : null}

            {onFinish && (
              <button className="btn btn-ghost gameplan-continue" onClick={onFinish}>
                {tc("Continue →")}
              </button>
            )}
          </section>
        </aside>
      </section>
    </div>
  );
}

// ---------------------------------------------------------------------------
// Zona Mixta Screen — Curveball challenge scenarios (LIVE verbal answers)
// ---------------------------------------------------------------------------
function ZonaMixtaScreen({ theme, gameModule, me, onFinish }) {
  const [cbIdx, setCbIdx] = uS(0);
  const [showStrong, setShowStrong] = uS(!!(me && me.isProctor));
  const [showWeak, setShowWeak] = uS(!!(me && me.isProctor));
  const [isLive, setIsLive] = uS(false);

  const curveballs = uM(() => {
    if (!gameModule || !gameModule.data || !gameModule.data.curveballs) return [];
    const all = gameModule.data.curveballs;
    // Cap by organizer's zonamixta_count if set (>0). 0/undefined = use all.
    const cap = (window.__GAME_CONFIG__ && window.__GAME_CONFIG__.zonamixta_count) || 0;
    return cap > 0 ? all.slice(0, cap) : all;
  }, [gameModule]);

  const copy = uM(() => {
    if (theme && theme.id === "gp") {
      return {
        eyebrow: tc("🏁 Post-race debrief"),
        title: tc("Hold the microphone under pressure"),
        deck: tc("One sharp answer can swing perception, momentum, and points for the whole garage."),
        promptTitle: tc("What the pit wall expects"),
        promptCopy: tc("Answer with confidence, mention trade-offs, and land on a defensible next move."),
        scoringTitle: tc("Judging board"),
        progressTitle: tc("Debrief run sheet"),
      };
    }
    if (theme && theme.id === "cup") {
      return {
        eyebrow: tc("🏟️ Mixed zone pressure"),
        title: tc("Answer before the cameras move on"),
        deck: tc("This is the post-match microphone moment: clear thinking, composed language, and a tactical response."),
        promptTitle: tc("What the coaches want"),
        promptCopy: tc("Frame the answer around business impact, coverage, and the next tactical move."),
        scoringTitle: tc("Commentary scorecard"),
        progressTitle: tc("Interview queue"),
      };
    }
    if (theme && theme.id === "nfl") {
      return {
        eyebrow: tc("🏈 Sideline interview"),
        title: tc("Call the answer before the next snap"),
        deck: tc("The pressure is live, the cameras are rolling, and the team needs a crisp read on the play."),
        promptTitle: tc("What the sideline needs"),
        promptCopy: tc("State the read, explain the risk, and call the smartest next down."),
        scoringTitle: tc("Analyst grading"),
        progressTitle: tc("Drive sequence"),
      };
    }
    if (theme && theme.id === "realm") {
      return {
        eyebrow: tc("⚔️ Council challenge"),
        title: tc("Speak before the court turns restless"),
        deck: tc("Every response is weighed by the council. Clear judgment and strategic calm win favor."),
        promptTitle: tc("What the council demands"),
        promptCopy: tc("Answer with conviction, defend the trade-offs, and show the kingdom a path forward."),
        scoringTitle: tc("Council scoring"),
        progressTitle: tc("Petitions in queue"),
      };
    }
    return {
      eyebrow: tc("🎙️ Zona Mixta"),
      title: tc("Respond under pressure"),
      deck: tc("This is the live-response round. Think clearly, answer decisively, and bring the whole team with you."),
      promptTitle: tc("What good answers do"),
      promptCopy: tc("Show technical clarity, business relevance, and a concrete recommendation."),
      scoringTitle: tc("Scoring tiers"),
      progressTitle: tc("Scenario progress"),
    };
  }, [theme]);

  const zmScoring = uM(() => {
    if (window.ScoringEngine && window.ScoringEngine.getZonaMixtaScoring) {
      return window.ScoringEngine.getZonaMixtaScoring((me && me.audienceMode) || "internal");
    }
    return { weak: 100, partial: 250, solid: 500 };
  }, [me]);

  const currentTeam = uM(() => {
    const teams = (theme && theme.teams) || [];
    return teams.find(t => t.id === (me && me.tid)) || null;
  }, [theme, me]);

  const teamColor = (currentTeam && currentTeam.color) || "var(--accent)";

  const cb = curveballs[cbIdx];

  uE(() => {
    setIsLive(false);
    const id = requestAnimationFrame(() => setIsLive(true));
    return () => cancelAnimationFrame(id);
  }, []);

  uE(() => {
    setShowStrong(!!(me && me.isProctor));
    setShowWeak(!!(me && me.isProctor));
  }, [cbIdx, me]);

  if (!cb) {
    return (
      <div className="screen">
        <div className={`zonamixta zonamixta--${(theme && theme.id) || "default"}`}>
          <div className="zonamixta-empty">
            <h2>{tc("🎙️ Zona Mixta")}</h2>
            <p>{tc("No curveball scenarios available for this module.")}</p>
            {onFinish && (
              <button className="btn btn-primary" onClick={onFinish} style={{marginTop: 24}}>
                {tc("Continue to results →")}
              </button>
            )}
          </div>
        </div>
      </div>
    );
  }

  return (
    <div className="screen">
      <section
        className={`zonamixta zonamixta--${(theme && theme.id) || "default"} ${isLive ? "is-live" : ""}`}
        style={{ "--team-color": teamColor }}
      >
        <div className="zonamixta-main">
          <header className="zonamixta-hero" style={{ "--delay": "80ms" }}>
            <div>
              <div className="zonamixta-kicker">{copy.eyebrow}</div>
              <h1 className="zonamixta-title">{copy.title}</h1>
              <p className="zonamixta-deck">{copy.deck}</p>
            </div>

            <div className="zonamixta-header-meta">
              <div className="zonamixta-counter mono">{tc("Scenario")} {cbIdx + 1} / {curveballs.length}</div>
              <div className="zonamixta-nav">
                <button
                  className="btn btn-ghost"
                  disabled={cbIdx === 0}
                  onClick={() => setCbIdx(cbIdx - 1)}
                >
                  {tc("← Previous")}
                </button>
                <button
                  className="btn btn-ghost"
                  disabled={cbIdx === curveballs.length - 1}
                  onClick={() => setCbIdx(cbIdx + 1)}
                >
                  {tc("Next →")}
                </button>
              </div>
            </div>
          </header>

          <section className="zonamixta-card zonamixta-card--scenario" style={{ "--delay": "160ms" }}>
            <div className="zonamixta-icon">🎯</div>
            <div className="zonamixta-scenario-copy">
              <div className="zonamixta-section-kicker">{tc("Live prompt")}</div>
              <h2 className="zonamixta-text">{tc(cb.text)}</h2>
              {cb.context && <p className="zonamixta-context">{tc(cb.context)}</p>}
            </div>
          </section>

          <section className="zonamixta-card zonamixta-card--scoring" style={{ "--delay": "240ms" }}>
            <div className="zonamixta-section-head">
              <div>
                <div className="zonamixta-section-kicker">{copy.scoringTitle}</div>
                <h2 className="zonamixta-section-title">{tc("What different answer quality sounds like")}</h2>
              </div>
            </div>

            <div className="zonamixta-scoring">
              <div className="zonamixta-tier zonamixta-tier--weak">
                <span className="mono">{tc("Weak")}</span>
                <strong className="mono">+{zmScoring.weak}</strong>
                <p>{tc("Generic response, thin justification, and weak connection to customer risk.")}</p>
              </div>
              <div className="zonamixta-tier zonamixta-tier--partial">
                <span className="mono">{tc("Partial")}</span>
                <strong className="mono">+{zmScoring.partial}</strong>
                <p>{tc("Gets part of the story right, but leaves gaps in execution or business framing.")}</p>
              </div>
              <div className="zonamixta-tier zonamixta-tier--solid">
                <span className="mono">{tc("Solid")}</span>
                <strong className="mono">+{zmScoring.solid}</strong>
                <p>{tc("Clear recommendation, strong trade-offs, and a confident path forward for the customer.")}</p>
              </div>
            </div>
          </section>
        </div>

        <aside className="zonamixta-side">
          <section className="zonamixta-card zonamixta-card--progress" style={{ "--delay": "120ms" }}>
            <div className="zonamixta-section-kicker">{copy.progressTitle}</div>
            <div className="zonamixta-progress-rail">
              {curveballs.map((item, idx) => (
                <button
                  key={idx}
                  type="button"
                  className={`zonamixta-progress-dot ${idx === cbIdx ? "is-active" : ""}`}
                  onClick={() => setCbIdx(idx)}
                  aria-label={`${tc("Go to scenario")} ${idx + 1}`}
                >
                  <span className="mono">{idx + 1}</span>
                </button>
              ))}
            </div>
            <p className="zonamixta-progress-copy">{tc("Move through the full interview queue and sharpen the team answer with each new curveball.")}</p>
          </section>

          <section className="zonamixta-card zonamixta-card--briefing" style={{ "--delay": "210ms" }}>
            <div className="zonamixta-prompt">
              <div className="zonamixta-prompt-icon">💬</div>
              <div>
                <strong>{copy.promptTitle}</strong>
                <p>{copy.promptCopy}</p>
              </div>
            </div>

            {me && me.isProctor && cb.strongResponse && (
              <div className="zonamixta-response-wrap">
                <button type="button" className="zonamixta-toggle zonamixta-toggle--strong" onClick={() => setShowStrong(prev => !prev)}>
                  {showStrong ? tc("🔽 Strong response reference") : tc("▶️ Strong response reference")}
                </button>
                {showStrong && (
                  <div className="zonamixta-response zonamixta-response--strong">
                    {String(cb.strongResponse).split("\n").map((line, idx) => <p key={idx}>{tc(line)}</p>)}
                  </div>
                )}
              </div>
            )}

            {me && me.isProctor && cb.weakResponse && (
              <div className="zonamixta-response-wrap">
                <button type="button" className="zonamixta-toggle zonamixta-toggle--weak" onClick={() => setShowWeak(prev => !prev)}>
                  {showWeak ? tc("🔽 Common mistakes to avoid") : tc("▶️ Common mistakes to avoid")}
                </button>
                {showWeak && (
                  <div className="zonamixta-response zonamixta-response--weak">
                    <p>{tc(cb.weakResponse)}</p>
                  </div>
                )}
              </div>
            )}
          </section>

          {cbIdx === curveballs.length - 1 && onFinish && (
            <section className="zonamixta-card zonamixta-card--finish" style={{ "--delay": "300ms" }}>
              <div className="zonamixta-section-kicker">{tc("Next phase")}</div>
              <h2 className="zonamixta-section-title">{tc("Finish the mixed-zone round")}</h2>
              <p className="zonamixta-finish-copy">{tc("The microphones are off. Lock the result and move to the final standings.")}</p>
              <button className="btn btn-primary btn-lg zonamixta-finish-btn" onClick={onFinish}>
                {tc("Continue to results →")}
              </button>
            </section>
          )}
        </aside>
      </section>
    </div>
  );
}

window.JoinScreen = JoinScreen;
window.LobbyScreen = LobbyScreen;
window.QuizScreen = QuizScreen;
window.PhaseTransitionScreen = PhaseTransitionScreen;
window.TriageScreen = TriageScreen;
window.WrapScreen = WrapScreen;
window.GamePlanScreen = GamePlanScreen;
window.ZonaMixtaScreen = ZonaMixtaScreen;

// ============================================================================
// TECH-DAY QUIZ SCREEN
// ============================================================================
// Renders the active speaker's questions for tech-day events.
// Driven by tech-day state polling (parent passes tdState + my-questions).
// Each question is single-shot: submit once, see feedback, advance.
// When questions_open=false OR no active_agenda_id → parent unmounts us.
function TechDayQuizScreen({ theme, tdState, email, name }) {
  const RND = window.RENDERERS || {};
  const [data, setData] = uS(null);          // { agenda_id, questions, per_question_seconds, ... }
  const [answeredMap, setAnsweredMap] = uS({}); // question_id -> { correct, awarded }
  const [qIdx, setQIdx] = uS(0);
  const [value, setValue] = uS(null);
  const [submitting, setSubmitting] = uS(false);
  const [feedback, setFeedback] = uS(null);  // { correct, awarded, gradable, correct_answer }
  const [error, setError] = uS(null);
  const [nowTs, setNowTs] = uS(() => Math.floor(Date.now() / 1000));

  const agendaId = tdState && tdState.active_agenda_id;
  const closesAt = tdState && tdState.questions_closes_at;

  // ── Fetch questions when agenda changes ──────────────────────────────────
  uE(() => {
    if (!agendaId || !email) return;
    let cancelled = false;
    Promise.all([
      Api.getMyTechDayQuestions(email),
      Api.getMyTechDayAnswers(email).catch(() => ({ answers: [] }))
    ]).then(([qResp, aResp]) => {
      if (cancelled) return;
      // Skip qtypes that don't make sense async (buzzer = first-to-press)
      const SKIP_QTYPES = { buzzer: 1 };
      const filteredQs = (qResp.questions || []).filter(qq => !SKIP_QTYPES[qq.qtype || qq.type]);
      const filtered = Object.assign({}, qResp, { questions: filteredQs });
      setData(filtered);
      const map = {};
      (aResp.answers || []).forEach(a => { map[a.question_id] = { correct: !!a.correct, awarded: a.awarded || 0, prior: true }; });
      setAnsweredMap(map);
      // Skip to first un-answered
      const qs = filteredQs;
      let firstOpen = 0;
      while (firstOpen < qs.length && map[qs[firstOpen].question_id]) firstOpen++;
      setQIdx(Math.min(firstOpen, Math.max(0, qs.length - 1)));
      setFeedback(null);
      setValue(null);
      setError(null);
    }).catch(err => {
      if (cancelled) return;
      setError(err.message || 'Failed to load questions');
    });
    return () => { cancelled = true; };
  }, [agendaId, email]);

  // ── Countdown tick (1s) ──────────────────────────────────────────────────
  uE(() => {
    const t = setInterval(() => setNowTs(Math.floor(Date.now() / 1000)), 1000);
    return () => clearInterval(t);
  }, []);

  // ── Derived ──────────────────────────────────────────────────────────────
  const questions = (data && data.questions) || [];
  const total = questions.length;
  const q = questions[qIdx];
  const qtype = q && (q.qtype || q.type);
  const renderer = qtype && RND[qtype];
  const alreadyAnswered = q && !!answeredMap[q.question_id];
  const secsLeft = closesAt ? Math.max(0, Math.floor(closesAt - nowTs)) : null;
  const expired = secsLeft === 0;
  const allDone = total > 0 && Object.keys(answeredMap).length >= total;

  // Init value when question changes
  uE(() => {
    if (!q || !renderer) { setValue(null); return; }
    setValue(renderer.getDefaultValue(q.spec || {}));
    setFeedback(null);
  }, [q && q.question_id]);

  // ── Submit ───────────────────────────────────────────────────────────────
  const handleSubmit = async () => {
    if (!q || submitting || alreadyAnswered) return;
    if (renderer && !renderer.isValid(value, q.spec || {})) {
      setError(tc('Pick an answer first'));
      setTimeout(() => setError(null), 2500);
      return;
    }
    setSubmitting(true);
    setError(null);
    try {
      const res = await Api.submitTechDayAnswer(email, q.question_id, value);
      setFeedback({
        correct: !!res.correct,
        awarded: res.awarded || 0,
        gradable: !!res.gradable,
        correct_answer: res.correct_answer || ''
      });
      setAnsweredMap(prev => {
        const next = Object.assign({}, prev);
        next[q.question_id] = { correct: !!res.correct, awarded: res.awarded || 0 };
        return next;
      });
    } catch (err) {
      setError(err.message || 'Submit failed');
    } finally {
      setSubmitting(false);
    }
  };

  const handleNext = () => {
    setFeedback(null);
    setValue(null);
    if (qIdx + 1 < total) setQIdx(qIdx + 1);
    else setQIdx(qIdx); // stay; allDone branch handles UI
  };

  // ── States ───────────────────────────────────────────────────────────────
  if (!data) {
    return (
      <div className="screen">
        <div className="quiz" style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', minHeight: 200, padding: 24 }}>
          <div className="mono" style={{ letterSpacing: '.14em', fontSize: 11, color: 'var(--ink-muted)' }}>{tc('LOADING')}</div>
          <h2 style={{ marginTop: 12 }}>{tc('Fetching your questions…')}</h2>
        </div>
      </div>
    );
  }

  if (!total) {
    return (
      <div className="screen">
        <div className="quiz" style={{ padding: 24, textAlign: 'center' }}>
          <div className="mono" style={{ letterSpacing: '.14em', fontSize: 11, color: 'var(--ink-muted)' }}>{tc('TECH DAY')}</div>
          <h2 style={{ marginTop: 12 }}>{data.agenda_title || tdState.active_title || tc('Active talk')}</h2>
          <p style={{ color: 'var(--ink-muted)', marginTop: 16 }}>
            {tc('No questions for this talk yet. Sit back and listen — we will let you know when there is something to answer.')}
          </p>
        </div>
      </div>
    );
  }

  if (allDone) {
    const correctCount = Object.values(answeredMap).filter(a => a.correct).length;
    const totalEarned = Object.values(answeredMap).reduce((s, a) => s + (a.awarded || 0), 0);
    return (
      <div className="screen">
        <div className="quiz" style={{ padding: 24, textAlign: 'center' }}>
          <div className="mono" style={{ letterSpacing: '.14em', fontSize: 11, color: 'var(--ink-muted)' }}>{tc('ALL ANSWERED')}</div>
          <h2 style={{ marginTop: 12 }}>{tc('Nice work')}, {name || ''}!</h2>
          <p style={{ color: 'var(--ink-muted)', marginTop: 12 }}>
            {tc('You answered')} <strong style={{ color: 'var(--ink)' }}>{total}/{total}</strong> · {tc('correct')}: <strong style={{ color: 'var(--accent)' }}>{correctCount}</strong> · +{totalEarned} {tc('pts')}
          </p>
          <p style={{ color: 'var(--ink-muted)', marginTop: 16, fontSize: 14 }}>
            {tc('Wait for the next talk.')}
          </p>
        </div>
      </div>
    );
  }

  const Comp = renderer && renderer.Component;
  const safeSpec = Object.assign({}, q.spec || {});
  delete safeSpec.correct;
  delete safeSpec.explanation;

  const timerColor = secsLeft == null ? 'var(--accent)'
                  : secsLeft <= 10 ? 'var(--danger, #E10600)'
                  : secsLeft <= 30 ? 'var(--warning, #F0A000)'
                  : 'var(--accent, #00BCEB)';
  const mm = secsLeft == null ? '' : String(Math.floor(secsLeft / 60));
  const ss = secsLeft == null ? '' : String(secsLeft % 60).padStart(2, '0');

  return (
    <div className="screen">
      <div className="quiz">
        <div className="quiz-meta">
          <div className="quiz-q-num">
            <span className="big mono">Q{String(qIdx + 1).padStart(2, '0')}</span>
            <span className="of">/ {String(total).padStart(2, '0')} · {tdState.active_title || tc('Talk')}</span>
          </div>
          <div className="quiz-progress-bar">
            <span style={{ width: `${((qIdx + 1) / total) * 100}%` }} />
          </div>
          <div className="live-dot">{tc('LIVE Q&A')}</div>
        </div>

        {/* Talk-level countdown */}
        {secsLeft != null && (
          <div className="quiz-q-timer" style={{
            display: 'inline-flex', alignItems: 'center', gap: 10,
            padding: '6px 12px', marginTop: 8, marginBottom: 4,
            background: 'var(--bg-elev)',
            border: `1px solid ${secsLeft <= 10 ? timerColor : 'var(--line)'}`,
            borderRadius: 10,
            position: 'sticky', top: 0, zIndex: 4,
            alignSelf: 'flex-start',
            maxWidth: 200,
          }}>
            <span className="mono" style={{ fontSize: 11, letterSpacing: '.14em', color: 'var(--ink-muted)' }}>
              {expired ? tc('TIME UP') : tc('TIME LEFT')}
            </span>
            <span className="mono" style={{
              fontSize: 16, fontWeight: 800, color: timerColor, fontVariantNumeric: 'tabular-nums',
            }}>{mm}:{ss}</span>
          </div>
        )}

        <div className="quiz-body">
          <h2 className="quiz-prompt">{q.prompt}</h2>

          {alreadyAnswered && !feedback && (
            <div style={{
              marginTop: 12, padding: '10px 14px', borderRadius: 8,
              background: 'var(--bg-elev)', border: '1px solid var(--line)',
              color: 'var(--ink-muted)', fontSize: 14,
            }}>
              {answeredMap[q.question_id].correct
                ? `✓ ${tc('You answered this correctly earlier')} (+${answeredMap[q.question_id].awarded} ${tc('pts')})`
                : `${tc('You already submitted an answer for this question')} (+${answeredMap[q.question_id].awarded} ${tc('pts')})`}
            </div>
          )}

          <div className="qtr" style={{ marginTop: 16 }}>
            {Comp ? (
              <Comp
                spec={safeSpec}
                value={value}
                onChange={v => !feedback && !alreadyAnswered && setValue(v)}
                locked={!!feedback || alreadyAnswered}
                graded={!!feedback}
              />
            ) : (
              <div style={{ padding: 16, background: 'var(--bg-elev)', borderRadius: 8, color: 'var(--ink-muted)' }}>
                {tc('This question type is not yet renderable on mobile.')} ({qtype})
              </div>
            )}
          </div>

          {feedback && (
            <div style={{
              marginTop: 16, padding: 14, borderRadius: 10,
              background: feedback.correct ? 'rgba(0,188,235,.08)'
                       : feedback.gradable ? 'rgba(225,6,0,.08)'
                       : 'rgba(240,160,0,.08)',
              border: `1px solid ${feedback.correct ? 'var(--accent)' : feedback.gradable ? 'var(--danger)' : 'var(--warning)'}`,
              color: 'var(--ink)',
            }}>
              <div style={{ fontWeight: 700, fontSize: 18 }}>
                {feedback.correct ? `✓ ${tc('Correct!')}`
                 : feedback.gradable ? `✗ ${tc('Not quite')}`
                 : `${tc('Thanks!')} ${tc('Saved.')}`}
                {feedback.awarded > 0 && (
                  <span style={{ marginLeft: 10, color: 'var(--accent)' }}>+{feedback.awarded} {tc('pts')}</span>
                )}
              </div>
              {feedback.gradable && !feedback.correct && feedback.correct_answer && (
                <div style={{ marginTop: 8, fontSize: 14, color: 'var(--ink-muted)' }}>
                  {tc('Correct answer')}: <strong style={{ color: 'var(--ink)' }}>{feedback.correct_answer}</strong>
                </div>
              )}
            </div>
          )}

          {error && (
            <div style={{ marginTop: 12, padding: '8px 12px', borderRadius: 8, background: 'rgba(225,6,0,.1)', color: 'var(--danger)', fontSize: 14 }}>
              {error}
            </div>
          )}

          <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 12, marginTop: 16 }}>
            {!feedback && !alreadyAnswered && (
              <button
                className="btn btn-primary btn-lg"
                onClick={handleSubmit}
                disabled={submitting || expired}
              >
                {submitting ? tc('Submitting…') : expired ? tc('Time up') : tc('Submit answer')}
              </button>
            )}
            {(feedback || alreadyAnswered) && qIdx + 1 < total && (
              <button className="btn btn-primary btn-lg" onClick={handleNext}>
                {tc('Next question →')}
              </button>
            )}
            {(feedback || alreadyAnswered) && qIdx + 1 >= total && (
              <div style={{ color: 'var(--ink-muted)', alignSelf: 'center' }}>
                {tc('That was the last one.')}
              </div>
            )}
          </div>
        </div>
      </div>
    </div>
  );
}

window.TechDayQuizScreen = TechDayQuizScreen;
