// Player screens — adapted from e2e-screens.jsx in the design bundle.
// Wired to live socket.io room state. No iPhone frame; runs full mobile.

(() => {
const R = window.R;
const Tag = window.Tag;
const Btn = window.Btn;
const Overprint = window.Overprint;
const RisoStamp = window.RisoStamp;
const RisoBars = window.RisoBars;
const RisoConfetti = window.RisoConfetti;
const Screen = window.Screen;
const YearDrum = window.YearDrum;

function Splash({ onJoin, onSolo }) {
  return (
    <Screen>
      <RisoStamp size={300} color={R.pink} top={-80} left={-100} duration={28} />
      <RisoStamp size={240} color={R.blue} top={420} left={140} duration={22} delay={1} />

      <div style={{ padding: '8px 22px', display: 'flex', justifyContent: 'space-between' }}>
        <Tag color={R.ink}>Hitfothit</Tag>
        <span style={{ fontSize: 11, fontWeight: 800, letterSpacing: '.14em' }}>EDITION 04</span>
      </div>

      <div style={{ padding: '32px 22px 0', position: 'relative', zIndex: 2 }}>
        <Overprint sizes={72}>NAME<br/>THE<br/>YEAR.</Overprint>
        <div style={{ marginTop: 14, fontSize: 13, lineHeight: 1.5, maxWidth: 270, fontWeight: 500 }}>
          A song plays. You guess when. Closest year wins one point. Exact wins two. <b>That is the entire game.</b>
        </div>
      </div>

      <div style={{ flex: 1 }} />

      <div style={{ padding: '0 22px', position: 'relative', zIndex: 2, display: 'flex', flexDirection: 'column', gap: 10 }}>
        <Btn bg={R.pink} onClick={onJoin}>I'M JOINING →</Btn>
        <Btn bg={R.green} onClick={onSolo}>SOLO MODE →</Btn>
        <a href="/host" style={{ textDecoration: 'none' }}>
          <Btn bg={R.paper2}>HOST A ROOM</Btn>
        </a>
      </div>
    </Screen>
  );
}

function Name({ name, setName, onNext, onBack }) {
  const [val, setVal] = React.useState(name || '');
  const inputRef = React.useRef(null);
  React.useEffect(() => { inputRef.current?.focus(); }, []);
  return (
    <Screen>
      <div style={{ padding: '8px 22px', display: 'flex', justifyContent: 'space-between' }}>
        <Tag color={R.blue}>Step 01 of 02</Tag>
        <span style={{ fontSize: 11, fontWeight: 800, letterSpacing: '.14em', cursor: 'pointer' }} onClick={onBack}>← BACK</span>
      </div>
      <div style={{ padding: '20px 22px', flex: 1, display: 'flex', flexDirection: 'column', gap: 18 }}>
        <Overprint sizes={56}>WHAT DO<br/>WE CALL<br/>YOU?</Overprint>

        <div style={{ marginTop: 8 }}>
          <input
            ref={inputRef}
            value={val}
            onChange={(e) => setVal(e.target.value.slice(0, 12))}
            placeholder="Your name"
            onKeyDown={(e) => { if (e.key === 'Enter' && val.trim().length >= 2) { setName(val.trim()); onNext(); } }}
            style={{
              width: '100%', padding: '16px 14px',
              fontSize: 24, fontWeight: 800, color: R.ink,
              background: R.paper2,
              border: `2px solid ${R.ink}`,
              boxShadow: `4px 4px 0 ${R.ink}`,
              outline: 'none', letterSpacing: '-.01em',
            }}
          />
          <div style={{ marginTop: 6, fontSize: 11, color: R.muted, fontWeight: 700, letterSpacing: '.14em' }}>
            {val.length}/12
          </div>
        </div>

        <div style={{ flex: 1 }} />
        <Btn bg={R.yellow} disabled={val.trim().length < 2}
          onClick={() => { setName(val.trim()); onNext(); }}>
          NEXT →
        </Btn>
      </div>
    </Screen>
  );
}

function Join({ name, onJoin, onBack, error }) {
  const [code, setCode] = React.useState(['', '', '', '']);
  const refs = [React.useRef(), React.useRef(), React.useRef(), React.useRef()];
  const [submitting, setSubmitting] = React.useState(false);
  const [shake, setShake] = React.useState(false);
  // Track the last code we actually fired off — after a failed attempt we let
  // the user edit the code without leaving the screen. The effect re-fires the
  // moment they type a *different* full code; same-code re-tries are blocked
  // so we don't spam the server while error is still being cleared upstream.
  const lastTriedRef = React.useRef('');
  React.useEffect(() => { refs[0].current?.focus(); }, []);
  React.useEffect(() => {
    if (error) {
      setShake(true);
      setSubmitting(false);
      // Focus the first cell so the user can immediately retype.
      setTimeout(() => { setShake(false); refs[0].current?.focus(); refs[0].current?.select?.(); }, 400);
    }
  }, [error]);

  const setIdx = (i, ch) => {
    const c = ch.toUpperCase().replace(/[^A-Z0-9]/g, '').slice(0, 1);
    const next = [...code]; next[i] = c; setCode(next);
    if (c && i < 3) refs[i + 1].current?.focus();
  };
  const onKey = (i, e) => {
    if (e.key === 'Backspace' && !code[i] && i > 0) refs[i - 1].current?.focus();
  };

  const filled = code.every((c) => c.length === 1);
  const joined = code.join('');

  React.useEffect(() => {
    if (!filled || submitting) return;
    if (joined === lastTriedRef.current) return;
    lastTriedRef.current = joined;
    setSubmitting(true);
    onJoin(joined);
  }, [joined, filled, submitting]);

  return (
    <Screen>
      <div style={{ padding: '8px 22px', display: 'flex', justifyContent: 'space-between' }}>
        <Tag color={R.blue}>Step 02 of 02</Tag>
        <span style={{ fontSize: 11, fontWeight: 800, letterSpacing: '.14em', cursor: 'pointer' }} onClick={onBack}>← BACK</span>
      </div>
      <div style={{ padding: '20px 22px', flex: 1, display: 'flex', flexDirection: 'column', gap: 18 }}>
        <Overprint sizes={56}>HEY <span style={{ color: R.yellow, mixBlendMode: 'normal' }}>{(name || '').toUpperCase()}.</span><br/>ROOM<br/>CODE?</Overprint>

        <div style={{ display: 'flex', gap: 8, marginTop: 8 }}>
          {code.map((c, i) => (
            <input
              key={i}
              ref={refs[i]}
              value={c}
              onChange={(e) => setIdx(i, e.target.value)}
              onKeyDown={(e) => onKey(i, e)}
              maxLength={1}
              inputMode="text"
              autoCapitalize="characters"
              autoComplete="off"
              style={{
                flex: 1, minWidth: 0, height: 76,
                padding: 0, lineHeight: '72px',
                fontSize: 40, fontWeight: 900, textAlign: 'center',
                color: R.ink,
                background: c ? (error ? R.pink : R.yellow) : R.paper2,
                border: `2px solid ${R.ink}`,
                boxShadow: `4px 4px 0 ${R.ink}`,
                outline: 'none', letterSpacing: '-.02em',
                transition: 'background .15s, transform .15s',
                animation: shake ? 'rshake .35s' : 'none',
              }}
            />
          ))}
        </div>

        {error && (
          <div style={{ fontSize: 12, color: R.pink, fontWeight: 700, letterSpacing: '.06em' }}>
            {error === 'no_room' ? 'No room with that code.' : error === 'room_full' ? 'Room is full.' : 'Could not join.'}
          </div>
        )}

        <div style={{ flex: 1 }} />
        <Btn
          bg={filled ? R.green : R.paper3}
          disabled={!filled || submitting}
          onClick={() => {
            if (!filled || submitting) return;
            // Retry this exact code: clear the dedup gate so the effect
            // re-submits, and clear the local submitting flag.
            lastTriedRef.current = '';
            setSubmitting(false);
            onJoin(joined);
            setSubmitting(true);
            lastTriedRef.current = joined;
          }}
        >
          {submitting ? 'JOINING…' : filled ? 'JOIN →' : 'ENTER CODE…'}
        </Btn>
      </div>
    </Screen>
  );
}

function Lobby({ name, players, code, hostConnected }) {
  const myPid = window.PLAYER_ID;
  return (
    <Screen>
      <RisoStamp size={200} color={R.blue} top={-50} left={200} duration={26} />

      <div style={{ padding: '8px 22px', display: 'flex', justifyContent: 'space-between' }}>
        <Tag color={R.pink}>Lobby · {code}</Tag>
        <span style={{ fontSize: 11, fontWeight: 800, letterSpacing: '.14em' }}>{players.length} IN</span>
      </div>

      <div style={{ padding: '12px 22px 0', position: 'relative', zIndex: 2 }}>
        <Overprint sizes={56}>NICE.<br/>YOU'RE IN.</Overprint>
        <div style={{ marginTop: 14, fontSize: 13, color: R.muted, fontWeight: 600 }}>
          {hostConnected ? 'Waiting for the host to start the round.' : 'Host disconnected — sit tight…'}
        </div>
      </div>

      <div style={{ padding: '14px 22px 8px', flex: 1, display: 'flex', flexDirection: 'column', gap: 8, overflow: 'auto' }}>
        {players.map((p) => {
          const self = p.id === myPid;
          return (
            <div key={p.id} style={{
              padding: '8px 12px',
              background: R.paper2,
              border: `2px solid ${R.ink}`,
              boxShadow: `3px 3px 0 ${R.ink}`,
              display: 'flex', alignItems: 'center', gap: 10,
              animation: 'rpopin .4s',
            }}>
              <div style={{
                width: 26, height: 26, background: p.color, color: R.ink,
                border: `2px solid ${R.ink}`,
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                fontSize: 12, fontWeight: 900,
              }}>{(p.name || '?')[0].toUpperCase()}</div>
              <span style={{ flex: 1, fontSize: 14, fontWeight: 800, letterSpacing: '-.01em' }}>
                {p.name}{self ? ' (YOU)' : ''}
              </span>
              <span style={{ fontSize: 9, fontWeight: 800, letterSpacing: '.16em' }}>● HERE</span>
            </div>
          );
        })}

        {window.TapTheBeat && <window.TapTheBeat />}
      </div>

      <div style={{ padding: '0 22px' }}>
        <div style={{
          padding: '12px',
          background: R.yellow,
          border: `2px solid ${R.ink}`,
          boxShadow: `4px 4px 0 ${R.ink}`,
          textAlign: 'center', fontSize: 13, fontWeight: 900, letterSpacing: '.02em',
        }}>
          WAITING FOR HOST…
        </div>
      </div>
    </Screen>
  );
}

function RoundIntro({ round }) {
  return (
    <Screen>
      <RisoStamp size={400} color={R.pink} top={-100} left={-100} duration={20} />
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', position: 'relative', zIndex: 2, gap: 8 }}>
        <div style={{ fontSize: 16, fontWeight: 800, letterSpacing: '.3em', color: R.muted, animation: 'rfade .4s' }}>ROUND</div>
        <div style={{
          fontSize: 220, fontWeight: 900, lineHeight: .8, letterSpacing: '-.06em',
          color: R.blue, mixBlendMode: 'multiply',
          animation: 'rdrop .8s cubic-bezier(.2,1.4,.4,1)',
        }}>
          {String(round).padStart(2, '0')}
        </div>
        <div style={{ fontSize: 16, fontWeight: 800, letterSpacing: '.16em', color: R.pink, animation: 'rfade .8s .3s backwards' }}>
          GET READY…
        </div>
      </div>
    </Screen>
  );
}

// Countdown chip — same shape on host and player so the round timer reads
// identically across screens. Re-renders every 250ms while live.
function PlayerCountdown({ deadline, warnAt = 10 }) {
  const [now, setNow] = React.useState(Date.now());
  React.useEffect(() => {
    if (!deadline) return;
    const id = setInterval(() => setNow(Date.now()), 250);
    return () => clearInterval(id);
  }, [deadline]);
  if (!deadline) return null;
  const remaining = Math.max(0, Math.ceil((deadline - now) / 1000));
  const warn = remaining <= warnAt;
  return (
    <span style={{
      padding: '3px 10px',
      background: warn ? R.pink : R.ink,
      color: R.paper,
      fontSize: 12, fontWeight: 900, letterSpacing: '.14em',
      fontFamily: 'ui-monospace, monospace',
      border: `2px solid ${R.ink}`,
      animation: warn && remaining > 0 ? 'rshake .35s' : 'none',
    }}>⏱ {remaining}s</span>
  );
}

function Guess({ round, year, setYear, onLock, players, locked, roundDeadline }) {
  return (
    <Screen>
      <div style={{ padding: '8px 22px', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
        <Tag color={R.pink}>Round {String(round).padStart(2, '0')}</Tag>
        <PlayerCountdown deadline={roundDeadline} />
      </div>

      <div style={{ padding: '12px 22px', flex: 1, display: 'flex', flexDirection: 'column', gap: 14 }}>
        <div style={{
          padding: 14, background: R.paper2,
          border: `2px solid ${R.ink}`, boxShadow: `3px 3px 0 ${R.ink}`,
          display: 'flex', alignItems: 'center', gap: 12,
        }}>
          <div style={{
            width: 56, height: 56, background: R.ink, position: 'relative', overflow: 'hidden',
            backgroundImage: `repeating-linear-gradient(45deg, ${R.pink} 0 4px, ${R.ink} 4px 9px)`,
            animation: 'rspin 12s linear infinite',
          }} />
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: '.18em', color: R.pink }}>NOW PLAYING</div>
            <div style={{ fontSize: 14, fontWeight: 800, marginTop: 2 }}>Mystery track</div>
          </div>
          <RisoBars />
        </div>

        <YearDrum value={year} onChange={setYear} accent={R.yellow} />

        <div style={{ flex: 1 }} />

        <Btn bg={locked ? R.green : R.pink} fg={R.paper} disabled={locked}
          onClick={() => onLock(year)}>
          {locked ? `LOCKED · ${year}` : `LOCK IN · ${year}`}
        </Btn>

        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', fontSize: 11, fontWeight: 800, letterSpacing: '.14em' }}>
          <span>{players.filter(p => p.locked).length}/{players.length} LOCKED</span>
          <span style={{ display: 'flex', gap: 4 }}>
            {players.map((p, i) => (
              <span key={i} title={p.name} style={{
                width: 14, height: 14,
                background: p.locked ? p.color : 'transparent',
                border: `2px solid ${R.ink}`,
              }} />
            ))}
          </span>
        </div>
      </div>
    </Screen>
  );
}

function Locked({ year, players, roundDeadline }) {
  const lockedCount = players.filter(p => p.locked).length;
  const total = players.length;
  const stillWaiting = lockedCount < total;
  return (
    <Screen>
      <div style={{ padding: '8px 22px', display: 'flex', justifyContent: 'space-between', gap: 8, alignItems: 'center' }}>
        <Tag color={R.green}>Locked</Tag>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          <PlayerCountdown deadline={roundDeadline} />
          <span style={{ fontSize: 11, fontWeight: 800, letterSpacing: '.14em' }}>{lockedCount}/{total}</span>
        </div>
      </div>

      <div style={{ padding: '14px 22px 0', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8 }}>
        <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: '.3em', color: R.muted }}>YOU SAID</div>
        <div style={{
          fontSize: 110, fontWeight: 900, lineHeight: .85, letterSpacing: '-.05em',
          color: R.blue, mixBlendMode: 'multiply',
          animation: 'rstamp-in .6s cubic-bezier(.2,1.4,.4,1)',
          fontVariantNumeric: 'tabular-nums',
        }}>{year}</div>
        <div style={{
          padding: '8px 12px',
          background: R.yellow, border: `2px solid ${R.ink}`, boxShadow: `3px 3px 0 ${R.ink}`,
          fontSize: 11, fontWeight: 800, letterSpacing: '.14em',
        }}>
          {stillWaiting ? `WAITING ON ${total - lockedCount}…` : 'EVERYONE’S IN'}
        </div>
      </div>

      <div style={{ flex: 1, padding: '14px 22px 8px', display: 'flex', flexDirection: 'column', justifyContent: 'flex-end' }}>
        {stillWaiting && window.TapTheBeat && <window.TapTheBeat />}
      </div>

      <div style={{ padding: '0 22px' }}>
        <div style={{ height: 6, background: R.paper2, border: `2px solid ${R.ink}`, position: 'relative', overflow: 'hidden' }}>
          <div style={{ position: 'absolute', inset: 0, width: `${(lockedCount / Math.max(1, total)) * 100}%`, background: R.green,
            transition: 'width .5s ease-out' }} />
        </div>
      </div>
    </Screen>
  );
}

function Reveal({ year, song, myGuess, guesses = [], players = [], round = 1 }) {
  const [step, setStep] = React.useState(0);
  const [display, setDisplay] = React.useState(1955);

  React.useEffect(() => {
    const t1 = setTimeout(() => setStep(1), 400);
    return () => clearTimeout(t1);
  }, []);

  React.useEffect(() => {
    if (step !== 1) return;
    setDisplay(1955);
    const target = year;
    const total = 1400;
    const start = performance.now();
    let raf;
    const tick = (now) => {
      const t = Math.min(1, (now - start) / total);
      const eased = 1 - Math.pow(1 - t, 3);
      const cur = Math.round(1955 + (target - 1955) * eased);
      setDisplay(cur);
      if (t < 1) raf = requestAnimationFrame(tick);
      else setStep(2);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [step, year]);

  const exact = myGuess?.guess === year;
  const myPid = window.PLAYER_ID;
  const sortedGuesses = [...guesses].sort((a, b) =>
    (b.points - a.points) || ((a.dist ?? 1e9) - (b.dist ?? 1e9))
  );
  const sortedPlayers = [...players].sort((a, b) => b.score - a.score);
  const anyExact = guesses.some(g => g.points === 2);
  // Compact rendering once the room gets crowded so 10+ players still fit.
  const dense = sortedPlayers.length >= 7;
  const guessRowPad = dense ? '6px 10px' : '8px 12px';
  const guessFs = dense ? 13 : 14;
  const totalRowPad = dense ? '4px 8px' : '6px 10px';
  const totalFs = dense ? 12 : 13;

  return (
    <Screen>
      {step === 2 && exact && <RisoConfetti count={70} />}
      {step === 2 && !exact && anyExact && <RisoConfetti count={30} />}

      <div style={{ padding: '8px 22px', display: 'flex', justifyContent: 'space-between' }}>
        <Tag color={R.pink}>Reveal · {String(round).padStart(2, '0')}</Tag>
        <span style={{ fontSize: 11, fontWeight: 800, letterSpacing: '.14em' }}>THE YEAR WAS</span>
      </div>

      <div style={{
        padding: '10px 22px 18px', flex: 1,
        display: 'flex', flexDirection: 'column', gap: 12,
        position: 'relative', zIndex: 5, overflow: 'auto',
      }}>
        <div style={{ position: 'relative', textAlign: 'center', marginTop: 4, height: 110 }}>
          <div style={{
            position: 'absolute', left: '50%', top: 6, transform: 'translateX(calc(-50% - 4px))',
            fontSize: 110, fontWeight: 900, color: R.pink, lineHeight: .85,
            letterSpacing: '-.05em', mixBlendMode: 'multiply', opacity: .9,
            fontVariantNumeric: 'tabular-nums',
          }}>{display}</div>
          <div style={{
            fontSize: 110, fontWeight: 900, color: R.blue, lineHeight: .85,
            letterSpacing: '-.05em', mixBlendMode: 'multiply', opacity: .9,
            fontVariantNumeric: 'tabular-nums',
            animation: step === 2 ? 'rstamp-in .55s cubic-bezier(.2,1.4,.4,1)' : 'none',
          }}>{display}</div>
        </div>

        <div style={{
          padding: 12, background: R.paper2,
          border: `2px solid ${R.ink}`, boxShadow: `3px 3px 0 ${R.ink}`,
          display: 'flex', alignItems: 'center', gap: 12,
          opacity: step >= 2 ? 1 : 0,
          transform: step >= 2 ? 'translateY(0)' : 'translateY(12px)',
          transition: 'opacity .4s, transform .4s',
        }}>
          <div style={{
            width: 48, height: 48, background: R.ink, flexShrink: 0,
            backgroundImage: `repeating-linear-gradient(0deg, ${R.yellow} 0 4px, ${R.ink} 4px 9px)`,
          }} />
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: '.18em', color: R.pink }}>THE TRACK</div>
            <div style={{
              fontSize: 17, fontWeight: 900, lineHeight: 1.05, letterSpacing: '-.01em', marginTop: 2,
              whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
            }}>{song?.title}</div>
            <div style={{ fontSize: 12, fontWeight: 700, color: R.muted, marginTop: 1, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{song?.artist}</div>
          </div>
        </div>

        {step >= 2 && sortedGuesses.length > 0 && (
          <div>
            <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: '.18em', color: R.muted, marginBottom: 6 }}>ROUND RESULTS</div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: dense ? 4 : 6 }}>
              {sortedGuesses.map((g, i) => {
                const self = g.id === myPid;
                return (
                  <div key={g.id} style={{
                    padding: guessRowPad,
                    background: g.points === 2 ? R.yellow : g.points === 1 ? R.green : R.paper2,
                    border: `${self ? 3 : 2}px solid ${R.ink}`,
                    boxShadow: self ? `3px 3px 0 ${R.ink}` : 'none',
                    display: 'flex', alignItems: 'center', gap: 10,
                    animation: `rpopin-r .35s ${0.1 + i * 0.05}s backwards`,
                  }}>
                    <div style={{ width: 18, height: 18, background: g.color, border: `2px solid ${R.ink}`, flexShrink: 0 }} />
                    <span style={{ flex: 1, fontSize: guessFs, fontWeight: 800, minWidth: 0, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                      {g.name}
                      {self && <span style={{ marginLeft: 6, fontSize: 9, background: R.ink, color: R.paper, padding: '2px 5px', letterSpacing: '.16em', fontWeight: 800 }}>YOU</span>}
                    </span>
                    <span style={{ fontSize: 12, fontWeight: 700, fontFamily: 'ui-monospace, monospace' }}>{g.guess ?? '—'}</span>
                    <span style={{
                      fontSize: 12, fontWeight: 900, fontFamily: 'ui-monospace, monospace',
                      padding: '2px 8px', minWidth: 32, textAlign: 'center',
                      background: g.points === 2 ? R.pink : g.points === 1 ? R.yellow : 'transparent',
                      color: g.points === 2 ? R.paper : R.ink,
                      border: `2px solid ${R.ink}`,
                    }}>+{g.points}</span>
                  </div>
                );
              })}
            </div>
          </div>
        )}

        {step >= 2 && sortedPlayers.length > 0 && (
          <div style={{ marginTop: 18 }}>
            {/* Riso-style dashed divider sets TOTAL apart from ROUND RESULTS */}
            <div style={{
              height: 4, marginBottom: 10,
              backgroundImage: `repeating-linear-gradient(90deg, ${R.ink} 0 10px, transparent 10px 16px)`,
            }} />
            <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 }}>
              <span style={{
                padding: '4px 10px', background: R.ink, color: R.paper,
                fontSize: 11, fontWeight: 900, letterSpacing: '.18em',
                border: `2px solid ${R.ink}`, boxShadow: `2px 2px 0 ${R.pink}`,
              }}>
                TOTAL · AFTER {String(round).padStart(2, '0')}
              </span>
              <span style={{ fontSize: 10, fontWeight: 700, color: R.muted, letterSpacing: '.12em' }}>
                {sortedPlayers.length} {sortedPlayers.length === 1 ? 'PLAYER' : 'PLAYERS'}
              </span>
            </div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: dense ? 2 : 4 }}>
              {sortedPlayers.map((p, i) => {
                const self = p.id === myPid;
                return (
                  <div key={p.id} style={{
                    padding: totalRowPad,
                    background: i === 0 ? R.yellow : R.paper,
                    border: `${self ? 3 : 2}px solid ${R.ink}`,
                    display: 'flex', alignItems: 'center', gap: 10,
                    animation: `rpopin-r .3s ${0.4 + i * 0.04}s backwards`,
                  }}>
                    <span style={{ fontSize: dense ? 13 : 14, fontWeight: 900, width: 18, color: i === 0 ? R.pink : R.ink }}>{i + 1}</span>
                    <div style={{ width: 12, height: 12, background: p.color, border: `2px solid ${R.ink}`, flexShrink: 0 }} />
                    <span style={{ flex: 1, fontSize: totalFs, fontWeight: 800, minWidth: 0, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                      {p.name}{self ? ' (YOU)' : ''}
                    </span>
                    <span style={{ fontSize: dense ? 14 : 16, fontWeight: 900, fontFamily: 'ui-monospace, monospace' }}>{p.score}</span>
                  </div>
                );
              })}
            </div>
          </div>
        )}
      </div>
    </Screen>
  );
}

function Leaderboard({ players, round }) {
  const sorted = [...players].sort((a, b) => b.score - a.score);
  const max = sorted[0]?.score || 1;
  const myPid = window.PLAYER_ID;
  return (
    <Screen>
      <div style={{ padding: '8px 22px', display: 'flex', justifyContent: 'space-between' }}>
        <Tag color={R.pink}>Standings</Tag>
        <span style={{ fontSize: 11, fontWeight: 800, letterSpacing: '.14em' }}>AFTER {String(round).padStart(2, '0')}</span>
      </div>

      <div style={{ padding: '0 22px' }}>
        <Overprint sizes={48}>{(sorted[0]?.name || 'NO ONE').toUpperCase()}<br/><span style={{ color: R.pink, mixBlendMode: 'normal' }}>LEADS.</span></Overprint>
      </div>

      <div style={{ padding: '20px 22px', flex: 1, display: 'flex', flexDirection: 'column', gap: 0, overflow: 'auto' }}>
        {sorted.map((p, i) => {
          const w = (p.score / Math.max(1, max)) * 100;
          const bg = i === 0 ? R.yellow : i === 1 ? R.paper2 : R.paper;
          const self = p.id === myPid;
          return (
            <div key={p.id} style={{
              padding: '12px 14px',
              background: bg,
              border: `2px solid ${R.ink}`,
              marginTop: i === 0 ? 0 : -2,
              animation: `rpopin-r .35s ${i * 0.1}s backwards`,
            }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                <span style={{ fontSize: 22, fontWeight: 900, width: 26, color: i === 0 ? R.pink : R.ink }}>{i + 1}</span>
                <span style={{ flex: 1, fontSize: 18, fontWeight: 800, letterSpacing: '-.01em' }}>
                  {p.name}{self ? ' (YOU)' : ''}
                  {self && i === 0 && <span style={{ marginLeft: 8, fontSize: 10, background: R.pink, color: R.paper, padding: '2px 6px', letterSpacing: '.16em', fontWeight: 800 }}>👑</span>}
                </span>
                <span style={{ fontSize: 22, fontWeight: 900, fontFamily: 'ui-monospace, monospace' }}>{p.score}</span>
              </div>
              <div style={{ marginTop: 6, height: 6, border: `1.5px solid ${R.ink}`, background: R.paper, overflow: 'hidden' }}>
                <div style={{ height: '100%', width: `${w}%`, background: i === 0 ? R.pink : R.blue,
                  animation: 'rgrow .9s cubic-bezier(.2,1.2,.4,1)',
                  ['--w']: `${w}%`,
                }} />
              </div>
            </div>
          );
        })}
      </div>
    </Screen>
  );
}

function Final({ players, onSeeStats }) {
  const sorted = [...players].sort((a, b) => b.score - a.score);
  const winner = sorted[0];
  const myPid = window.PLAYER_ID;
  const youWon = winner?.id === myPid;
  const myRank = sorted.findIndex(p => p.id === myPid) + 1;
  const dense = sorted.length >= 7;
  return (
    <Screen>
      <RisoConfetti count={youWon ? 90 : 30} />
      <RisoStamp size={300} color={R.yellow} top={-100} left={140} duration={20} />

      <div style={{ padding: '8px 22px', display: 'flex', justifyContent: 'space-between' }}>
        <Tag color={R.ink}>Game Over</Tag>
      </div>

      <div style={{
        padding: '20px 22px 0', flex: 1,
        display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 10,
        position: 'relative', zIndex: 5, overflow: 'auto',
      }}>
        <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: '.3em', color: R.muted }}>WINNER</div>
        <Overprint sizes={64} color1={R.pink} color2={R.blue}>{(winner?.name || '—').toUpperCase()}</Overprint>
        <div style={{
          fontSize: 64, fontWeight: 900, color: R.ink, lineHeight: 1, letterSpacing: '-.04em',
          fontVariantNumeric: 'tabular-nums',
          animation: 'rstamp-in .6s .2s backwards',
        }}>{winner?.score || 0}</div>
        <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: '.18em', color: R.muted }}>POINTS</div>

        {/* Self placement chip — only when there's actually competition */}
        {sorted.length > 1 && (
          <div style={{
            marginTop: 6, padding: '6px 12px',
            background: youWon ? R.yellow : R.paper2,
            border: `2px solid ${R.ink}`, boxShadow: `2px 2px 0 ${R.ink}`,
            fontSize: 12, fontWeight: 800, letterSpacing: '.04em', textAlign: 'center',
          }}>
            {youWon ? '🎉 YOU NAILED IT' : `You placed ${myRank} of ${sorted.length}`}
          </div>
        )}

        {/* Full standings — every player, score, rank. Mirrors HostFinal. */}
        <div style={{ width: '100%', marginTop: 14, display: 'flex', flexDirection: 'column', gap: dense ? 3 : 6 }}>
          <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: '.18em', color: R.muted, marginBottom: 2 }}>FINAL STANDINGS</div>
          {sorted.map((p, i) => {
            const self = p.id === myPid;
            return (
              <div key={p.id} style={{
                padding: dense ? '6px 10px' : '8px 12px',
                background: i === 0 ? R.yellow : i === 1 ? R.paper2 : R.paper,
                border: `${self ? 3 : 2}px solid ${R.ink}`,
                boxShadow: self ? `3px 3px 0 ${R.ink}` : 'none',
                display: 'flex', alignItems: 'center', gap: 10,
                animation: `rpopin-r .35s ${0.1 + i * 0.06}s backwards`,
              }}>
                <span style={{ fontSize: dense ? 14 : 16, fontWeight: 900, width: 22, color: i === 0 ? R.pink : R.ink }}>{i + 1}</span>
                <div style={{ width: 14, height: 14, background: p.color, border: `2px solid ${R.ink}`, flexShrink: 0 }} />
                <span style={{ flex: 1, fontSize: dense ? 13 : 14, fontWeight: 800, minWidth: 0, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                  {p.name}{self ? ' (YOU)' : ''}
                </span>
                <span style={{ fontSize: dense ? 14 : 16, fontWeight: 900, fontFamily: 'ui-monospace, monospace' }}>{p.score}</span>
              </div>
            );
          })}
        </div>
      </div>

      {onSeeStats && (
        <div style={{ padding: '12px 22px', position: 'relative', zIndex: 5 }}>
          <Btn bg={R.yellow} onClick={onSeeStats}>📊 SEE THE BREAKDOWN →</Btn>
        </div>
      )}
    </Screen>
  );
}

window.PlayerScreens = { Splash, Name, Join, Lobby, RoundIntro, Guess, Locked, Reveal, Leaderboard, Final };
})();
