/* The games that are not the arena.
 *
 * Ported from party.js one for one — same markup, same class names, styled by
 * the generated party.css. Each takes the room view `v` and its game block `g`
 * and sends intents up; none of them decides anything, because the server owns
 * every result and a phone that guesses looks wrong for 200ms on a bad line.
 */
(function () {

const esc = s => String(s ?? '');
const nameOfIn = (v, pid) => ((v.players || []).find(x => x.pid === pid) || {}).name || '—';
const playerIn = (v, pid) => (v.players || []).find(x => x.pid === pid) || {};

/* ----------------------------------------------------------- mouse trap -- */

function GameTap({ v, g, me, on, HostStrip }) {
  let cls = 'armed', label = 'WAIT…';
  if (g.phase === 'go') {
    if (g.me == null) { cls = 'go'; label = 'TAP!'; }
    else { cls = 'done'; label = `${g.me} ms`; }
  } else if (g.phase === 'arm') {
    if (g.me === -1) { cls = 'early'; label = 'TOO SOON'; }
  } else if (g.phase === 'reveal') {
    const mine = (g.last || []).find(r => r.pid === me.pid);
    cls = mine && mine.early ? 'early' : 'done';
    label = mine ? (mine.early ? 'JUMPED IT' : mine.ms ? `${mine.ms} ms` : 'MISSED') : 'ROUND OVER';
  }

  return (
    <>
      <HostStrip v={v} on={on} />
      <div className="center" style={{ marginBottom: 10 }}>
        <div style={{ fontSize: 19, fontWeight: 800 }}>🧀 Mouse Trap</div>
        <div className="muted">Round {g.round} of {g.rounds}</div>
      </div>
      <div className="stage" style={{ minHeight: '52vh' }}>
        <button className={`tap-target ${cls}`} onClick={() => on({ t: 'tap' })}>{label}</button>
        {g.phase === 'reveal' && g.last ? (
          <div className="plist" style={{ width: '100%' }}>
            {[...g.last].sort((a, b) => (b.points || 0) - (a.points || 0)).map(r => {
              const p = playerIn(v, r.pid);
              return (
                <div className={`prow${r.pid === me.pid ? ' me' : ''}`} key={r.pid}>
                  <span className="em">{p.emoji || '🐭'}</span>
                  <div className="grow">
                    <div className="nm">{esc(p.name || '—')}</div>
                    <div className="st">{r.early ? 'jumped the gun' : r.ms ? `${r.ms} ms` : 'no tap'}</div>
                  </div>
                  <span className="sc">{r.points ? `+${r.points}` : '—'}</span>
                </div>
              );
            })}
          </div>
        ) : (
          <div className="muted">{g.phase === 'arm' ? 'Do not tap until it turns green.' : `${g.tapped} in`}</div>
        )}
      </div>
    </>
  );
}

/* ------------------------------------------------------- mouse majority -- */

function GameMajority({ v, g, on, HostStrip, Timer, nowServer }) {
  return (
    <>
      <HostStrip v={v} on={on} />
      <div className="center" style={{ marginBottom: 4 }}>
        <div style={{ fontSize: 19, fontWeight: 800 }}>🗳️ Mouse Majority</div>
        <div className="muted">Round {g.round} of {g.rounds} · go with the crowd</div>
      </div>
      {g.phase === 'ask' ? <Timer endsAt={v.endsAt || g.endsAt} total={15} calm nowServer={nowServer} /> : null}
      <div className="q-text">{esc(g.q)}</div>
      <div className="opts">
        {g.opts.map((o, i) => {
          const win = g.winners && g.winners.includes(i);
          const cls = g.phase === 'reveal'
            ? (win ? 'right' : (g.me === i ? 'wrong' : ''))
            : (g.me === i ? 'picked' : '');
          return (
            <button key={i} className={`opt ${cls}`} disabled={g.me != null}
                    onClick={() => on({ t: 'pick', opt: i })}>
              <span className="k">{'ABCD'[i]}</span><span className="grow">{esc(o)}</span>
              {g.tally ? <span className="bar">{g.tally[i]}</span> : null}
            </button>
          );
        })}
      </div>
      <div className="muted center" style={{ marginTop: 14 }}>
        {g.phase === 'reveal' ? 'Matching the crowd scores.' : `${g.picked} of ${g.of} picked`}
      </div>
    </>
  );
}

/* ----------------------------------------------------------- punchline -- */

function GamePunchline({ v, g, on, HostStrip, Timer, nowServer }) {
  const [text, setText] = React.useState('');

  if (g.phase === 'write') {
    return (
      <>
        <HostStrip v={v} on={on} />
        <div className="center">
          <div style={{ fontSize: 19, fontWeight: 800 }}>✍️ Punchline</div>
          <div className="muted">Round {g.round} of {g.rounds}</div>
        </div>
        <Timer endsAt={v.endsAt || g.endsAt} total={50} calm nowServer={nowServer} />
        <div className="q-text">{esc(g.prompt)}</div>
        {g.mine ? (
          <div className="card center">
            <div style={{ fontSize: 17, fontWeight: 700 }}>“{esc(g.mine)}”</div>
            <div className="muted" style={{ marginTop: 8 }}>In. {g.submitted} of {g.of} answered.</div>
          </div>
        ) : (
          <>
            <input className="field" placeholder="your answer" maxLength={80} autoComplete="off"
                   value={text} onChange={e => setText(e.target.value)} />
            <button className="btn primary" style={{ marginTop: 10 }} disabled={!text.trim()}
                    onClick={() => { on({ t: 'answer', text: text.trim() }); setText(''); }}>Send it</button>
            <div className="muted center" style={{ marginTop: 10 }}>{g.submitted} of {g.of} answered</div>
          </>
        )}
      </>
    );
  }

  if (g.phase === 'vote') {
    return (
      <>
        <HostStrip v={v} on={on} />
        <div className="center">
          <div style={{ fontSize: 19, fontWeight: 800 }}>✍️ Punchline</div>
          <div className="muted">pick your favourite — not your own</div>
        </div>
        <Timer endsAt={v.endsAt || g.endsAt} total={25} calm nowServer={nowServer} />
        <div className="q-text">{esc(g.prompt)}</div>
        <div className="stack">
          {g.cards.map(c => (
            <button key={c.id} className={`answer-card${c.mine ? ' mine' : ''}${g.voted === c.id ? ' picked' : ''}`}
                    disabled={!!c.mine || !!g.voted} onClick={() => on({ t: 'vote', for: c.id })}>
              {esc(c.text)}{c.mine ? <span className="muted"> (yours)</span> : null}
            </button>
          ))}
        </div>
        <div className="muted center" style={{ marginTop: 12 }}>{g.votes} voted</div>
      </>
    );
  }

  return (
    <>
      <HostStrip v={v} on={on} />
      <div className="center"><div style={{ fontSize: 19, fontWeight: 800 }}>✍️ Punchline</div></div>
      <div className="q-text">{esc(g.prompt)}</div>
      <div className="stack">
        {(g.result || []).map((r, i) => {
          const p = playerIn(v, r.pid);
          return (
            <div key={i} className={`answer-card${i === 0 && r.votes ? ' picked' : ''}`}>
              <span className="v">{r.votes ? '★'.repeat(Math.min(5, r.votes)) : ''}</span>
              {esc(r.text)}
              <div className="muted" style={{ marginTop: 6 }}>
                {p.emoji || ''} {esc(p.name || '')}{r.votes ? ` · +${r.votes * 500}` : ''}
              </div>
            </div>
          );
        })}
      </div>
    </>
  );
}

/* ---------------------------------------------------------------- quiz -- */

function GameQuiz({ v, g, on, HostStrip, Timer, nowServer }) {
  const spread = g.spread || null;
  return (
    <>
      <HostStrip v={v} on={on} />
      <div className="center" style={{ marginBottom: 4 }}>
        <div style={{ fontSize: 19, fontWeight: 800 }}>{g.emoji} {esc(g.category)}</div>
        <div className="muted">Question {g.round} of {g.rounds}</div>
      </div>
      {g.phase === 'ask' ? <Timer endsAt={g.endsAt} total={16} nowServer={nowServer} /> : null}
      <div className="q-text">{esc(g.q)}</div>
      <div className="opts">
        {g.opts.map((o, i) => {
          let cls = g.me === i ? 'picked' : '';
          if (g.phase === 'reveal') {
            if (i === g.correct) cls = 'right';
            else if (g.me === i) cls = 'wrong';
            else cls = '';
          }
          return (
            <button key={i} className={`opt ${cls}`} disabled={g.me != null || g.phase !== 'ask'}
                    onClick={() => on({ t: 'answer', opt: i })}>
              <span className="k">{'ABCD'[i]}</span><span className="grow">{esc(o)}</span>
              {spread ? <span className="bar">{spread[i]}</span> : null}
            </button>
          );
        })}
      </div>
      <div className="center" style={{ marginTop: 14 }}>
        {g.phase === 'reveal'
          ? (g.gained
            ? <div className="gain">+{g.gained}{g.streak > 1 ? ` · ${g.streak} in a row 🔥` : ''}</div>
            : <div className="muted">no points that time</div>)
          : <div className="muted">{g.answered} of {g.of} answered</div>}
      </div>
    </>
  );
}

/* ------------------------------------------------------------ heads up -- */

function GameHeadsUp({ v, g, on, HostStrip, Timer, nowServer }) {
  const who = playerIn(v, g.guesser);

  /* The ears deal: the word is on the wearer's own head — the one screen at
   * the party they cannot see — and the gyro in the ear does the judging.
   * Phones are stage directions and a live scoreboard, nothing more. */
  if (g.earsMode) {
    if (g.phase === 'pass') {
      return (
        <>
          <HostStrip v={v} on={on} />
          <div className="stage" style={{ minHeight: '46vh' }}>
            <div className="huge">🐭</div>
            <div className="head">{g.amGuesser ? 'Put the ears on!' : `Hand the ears to ${esc(who.name || 'the next player')}`}</div>
            <div className="body">{g.amGuesser
              ? 'The words show up there — where everyone but you can read them. Nod DOWN for got it, tilt BACK to pass.'
              : 'Their words go on their own ears. Read them out in clues — never the word itself.'}</div>
            <div className="muted">{esc(g.category)} · turn {g.turn} of {g.turns}</div>
          </div>
          <div className="sticky-cta">
            <button className="btn green" onClick={() => on({ t: 'go' })}>
              {g.amGuesser ? "They're on — GO" : 'Ears are on — GO'}
            </button>
          </div>
        </>
      );
    }
    if (g.phase === 'live') {
      return (
        <>
          <HostStrip v={v} on={on} />
          <div className="stage" style={{ minHeight: '46vh' }}>
            <div className="huge">{g.amGuesser ? '🙉' : '📣'}</div>
            <div className="head">{g.amGuesser ? 'The word is on your ears' : `Read ${esc(who.name || 'the wearer')}'s ears`}</div>
            <div className="body">{g.amGuesser
              ? 'Listen to the room. Nod DOWN when you get it, tilt BACK to pass.'
              : 'Shout clues — never the word itself. The nod scores it.'}</div>
            <div className="gain" style={{ fontSize: 30 }}>{g.score} got</div>
            {g.earLost ? <div className="muted">the ears went quiet — hold on…</div> : null}
          </div>
          <Timer endsAt={g.endsAt} total={64} calm nowServer={nowServer} />
        </>
      );
    }
    /* recap */
    return (
      <>
        <HostStrip v={v} on={on} />
        <div className="stage" style={{ minHeight: '34vh' }}>
          <div className="huge">{g.score}</div>
          <div className="head">{esc(who.name || 'They')} got {g.score}</div>
          {g.earLost ? <div className="muted">the ears never answered — that turn is on the house</div>
                     : <div className="body">The full word list is on the ears.</div>}
        </div>
      </>
    );
  }

  const clock = g.phase === 'play'
    ? <Timer endsAt={g.endsAt} total={60} calm nowServer={nowServer} /> : null;

  if (g.phase === 'ready') {
    return (
      <>
        <HostStrip v={v} on={on} />
        <div className="stage" style={{ minHeight: '52vh' }}>
          <div className="huge">{g.amGuesser ? '🙈' : '🤫'}</div>
          <div className="head">{g.amGuesser ? "You're up" : `${esc(who.name || 'Next player')} is up`}</div>
          <div className="body">{g.amGuesser
            ? 'Do not look at your own phone. Listen to the room.'
            : 'Your phone will show the word. Shout clues — never the word itself.'}</div>
          <div className="muted">{esc(g.category)} · turn {g.turn} of {g.turns}</div>
        </div>
        <Timer endsAt={g.endsAt} total={4} calm nowServer={nowServer} />
      </>
    );
  }

  if (g.phase === 'recap') {
    const r = g.recap || { got: [], passed: [] };
    const rows = [...r.got.map(w => ({ w, ok: 1 })), ...r.passed.map(w => ({ w, ok: 0 }))];
    return (
      <>
        <HostStrip v={v} on={on} />
        <div className="stage" style={{ minHeight: '22vh' }}>
          <div className="huge">{r.got.length}</div>
          <div className="head">{esc(who.name || 'They')} got {r.got.length}</div>
        </div>
        <div className="card"><h2>The words</h2><div className="plist">
          {rows.length ? rows.map((x, i) => (
            <div className="prow" key={i}>
              <span className="em">{x.ok ? '✅' : '⤼'}</span>
              <div className="grow"><div className="nm">{esc(x.w)}</div></div>
            </div>
          )) : <div className="muted center">none that time</div>}
        </div></div>
      </>
    );
  }

  if (g.amGuesser) {
    return (
      <>
        <HostStrip v={v} on={on} />
        <div className="stage" style={{ minHeight: '52vh' }}>
          <div className="huge">🙈</div>
          <div className="head">Guess!</div>
          <div className="body">Keep your eyes up. The room can see your word.</div>
          <div className="gain" style={{ fontSize: 28 }}>{g.got} got</div>
        </div>
        {clock}
      </>
    );
  }

  return (
    <>
      <HostStrip v={v} on={on} />
      <div className="center" style={{ marginBottom: 8 }}>
        <div className="muted">{esc(who.name || 'Guesser')} is guessing · {esc(g.category)}</div>
      </div>
      <div className="stage hu-word" style={{ minHeight: '34vh' }}>
        {g.flash ? <div className={`hu-flash ${g.flash}`}>{g.flash === 'got' ? 'GOT IT' : 'PASS'}</div> : null}
        <div className="huge" style={{ fontSize: 'clamp(30px,9vw,54px)', lineHeight: 1.1 }}>{esc(g.word || '')}</div>
      </div>
      {clock}
      <div className="btns2">
        <button className="btn green hu-judge" onClick={() => on({ t: 'judge', got: 1 })}>GOT IT</button>
        <button className="btn primary hu-judge" onClick={() => on({ t: 'judge', got: 0 })}>PASS</button>
      </div>
      <div className="muted center" style={{ marginTop: 8 }}>{g.got} got · {g.passed} passed</div>
    </>
  );
}

/* -------------------------------------------------------- odd one out -- */

function GameOddOne({ v, g, me, on, HostStrip, Timer, nowServer }) {
  const [text, setText] = React.useState('');
  const nameOf = pid => {
    const p = playerIn(v, pid);
    return { name: p.name || '—', emoji: p.emoji || '🐭' };
  };
  const Head = (
    <div className="center" style={{ marginBottom: 8 }}>
      <div style={{ fontSize: 19, fontWeight: 800 }}>🕵️ Odd One Out</div>
      <div className="muted">Round {g.round} of {g.rounds}</div>
    </div>
  );

  if (g.phase === 'write') {
    return (
      <>
        <HostStrip v={v} on={on} />{Head}
        <div className="stage" style={{ minHeight: '24vh' }}>
          <div className="head">{esc(g.prompt)}</div>
          <div className="muted">One short answer. Everyone sees it.</div>
        </div>
        {g.mine ? (
          <div className="card center">
            <div className="gain" style={{ fontSize: 22 }}>{esc(g.mine)}</div>
            <div className="muted">locked in — {g.wrote} of {g.of} in</div>
          </div>
        ) : (
          <>
            <input className="field" placeholder="your answer" maxLength={40} autoComplete="off"
                   value={text} onChange={e => setText(e.target.value)} />
            <button className="btn primary" style={{ width: '100%', marginTop: 10 }} disabled={!text.trim()}
                    onClick={() => { on({ t: 'answer', text: text.trim() }); setText(''); }}>Lock it in</button>
          </>
        )}
        <Timer endsAt={g.endsAt} total={40} calm nowServer={nowServer} />
      </>
    );
  }

  if (g.phase === 'read') {
    return (
      <>
        <HostStrip v={v} on={on} />{Head}
        <div className="stage" style={{ minHeight: '14vh' }}>
          <div className="head">Everyone answered…</div>
          <div className="muted">One of these came from a different question.</div>
        </div>
        <div className="card"><div className="plist">
          {(g.answers || []).map(a => {
            const p = nameOf(a.pid);
            return (
              <div className="prow" key={a.pid}>
                <span className="em">{p.emoji}</span>
                <div className="grow"><div className="nm">{esc(a.text)}</div><div className="st">{esc(p.name)}</div></div>
              </div>
            );
          })}
        </div></div>
        <Timer endsAt={g.endsAt} total={9} calm nowServer={nowServer} />
      </>
    );
  }

  if (g.phase === 'vote') {
    return (
      <>
        <HostStrip v={v} on={on} />{Head}
        <div className="stage" style={{ minHeight: '12vh' }}><div className="head">Who was it?</div></div>
        <div className="opts">
          {(g.answers || []).filter(a => a.pid !== me.pid).map(a => {
            const p = nameOf(a.pid);
            return (
              <button key={a.pid} className={`opt ${g.myVote === a.pid ? 'right' : ''}`} disabled={!!g.myVote}
                      onClick={() => on({ t: 'vote', for: a.pid })}>
                <span className="k">{p.emoji}</span>
                <span className="grow">{esc(a.text)}<br /><small className="muted">{esc(p.name)}</small></span>
              </button>
            );
          })}
        </div>
        <div className="muted center" style={{ marginTop: 10 }}>{g.voted} of {g.of} voted</div>
        <Timer endsAt={g.endsAt} total={28} calm nowServer={nowServer} />
      </>
    );
  }

  const r = g.result || { tally: {}, finders: [], gained: {} };
  const odd = nameOf(r.odd);
  return (
    <>
      <HostStrip v={v} on={on} />{Head}
      <div className="stage" style={{ minHeight: '24vh' }}>
        <div className="huge">{odd.emoji}</div>
        <div className="head">{esc(odd.name)} was the odd one</div>
        <div className="body">Their question: “{esc((r.prompt || [])[1] || '')}”</div>
        <div className="muted">{r.finders.length ? `${r.finders.length} saw through it` : 'nobody spotted them'}</div>
      </div>
      <div className="card"><div className="plist">
        {(v.players || []).map(p => {
          const got = r.gained[p.pid] || 0;
          const votes = r.tally[p.pid] || 0;
          return (
            <div className={`prow${p.pid === me.pid ? ' me' : ''}`} key={p.pid}>
              <span className="em">{p.emoji}</span>
              <div className="grow"><div className="nm">{esc(p.name)}</div>
                <div className="st">{votes} vote{votes === 1 ? '' : 's'}</div></div>
              <span className="sc">{got ? `+${got}` : '—'}</span>
            </div>
          );
        })}
      </div></div>
      <Timer endsAt={g.endsAt} total={9} calm nowServer={nowServer} />
    </>
  );
}

/* ----------------------------------------------------------- tall tale -- */

function GameTallTale({ v, g, me, on, HostStrip, Timer, nowServer }) {
  const [text, setText] = React.useState('');
  const nameOf = pid => playerIn(v, pid).name || '—';
  const Head = (
    <div className="center" style={{ marginBottom: 8 }}>
      <div style={{ fontSize: 19, fontWeight: 800 }}>🤥 Tall Tale</div>
      <div className="muted">Round {g.round} of {g.rounds}</div>
    </div>
  );

  if (g.phase === 'write') {
    return (
      <>
        <HostStrip v={v} on={on} />{Head}
        <div className="stage" style={{ minHeight: '20vh' }}>
          <div className="head">{esc(g.q)}</div>
          <div className="body">Write an answer that <b>sounds</b> right — you want someone to pick it.</div>
        </div>
        {g.mine && !g.mineDropped ? (
          <div className="card center">
            <div className="gain" style={{ fontSize: 20 }}>{esc(g.mine)}</div>
            <div className="muted">in — {g.wrote} of {g.of} written</div>
          </div>
        ) : (
          <>
            {g.mineDropped ? (
              <div className="card center"><div className="muted">
                That is the real answer — good instinct, but it would give the round away. Try another one.
              </div></div>
            ) : null}
            <input className="field" placeholder="your fake answer" maxLength={44} autoComplete="off"
                   value={text} onChange={e => setText(e.target.value)} />
            <button className="btn primary" style={{ marginTop: 10 }} disabled={!text.trim()}
                    onClick={() => { on({ t: 'answer', text: text.trim() }); setText(''); }}>Lock it in</button>
          </>
        )}
        <Timer endsAt={g.endsAt} total={45} calm nowServer={nowServer} />
      </>
    );
  }

  if (g.phase === 'pick') {
    return (
      <>
        <HostStrip v={v} on={on} />{Head}
        <div className="stage" style={{ minHeight: '14vh' }}>
          <div className="head">{esc(g.q)}</div>
          <div className="muted">One of these is true. The rest are each other's lies.</div>
        </div>
        <div className="opts plain">
          {(g.cards || []).map(c => (
            <button key={c.id} className={`opt ${g.picked === c.id ? 'picked' : ''}`}
                    disabled={!!g.picked || !!c.mine} onClick={() => on({ t: 'pick', id: c.id })}>
              <span className="grow">{esc(c.text)}</span>
              {c.mine ? <span className="bar">yours</span> : null}
            </button>
          ))}
        </div>
        <div className="muted center" style={{ marginTop: 10 }}>{g.picks} of {g.of} picked</div>
        <Timer endsAt={g.endsAt} total={30} calm nowServer={nowServer} />
      </>
    );
  }

  const r = g.result || { cards: [], gained: {} };
  const truth = r.cards.find(c => c.truth);
  return (
    <>
      <HostStrip v={v} on={on} />{Head}
      <div className="stage" style={{ minHeight: '18vh' }}>
        <div className="muted">The real answer was</div>
        <div className="head" style={{ color: 'var(--gold)' }}>{esc(truth ? truth.text : '')}</div>
      </div>
      <div className="card"><div className="plist">
        {r.cards.map((c, i) => (
          <div className={`prow ${c.truth ? 'r1' : ''}`} key={i}>
            <span className="em">{c.truth ? '✅' : '🤥'}</span>
            <div className="grow"><div className="nm">{esc(c.text)}</div>
              <div className="st">{c.truth
                ? (c.picks.length ? `found by ${c.picks.map(nameOf).join(', ')}` : 'nobody found it')
                : `${c.authors.map(nameOf).join(' & ')}${c.picks.length ? ` fooled ${c.picks.length}` : ''}`}</div>
            </div>
            <span className="sc">{c.picks.length || ''}</span>
          </div>
        ))}
      </div></div>
      <div className="card"><div className="plist">
        {(v.players || []).map(p => (
          <div className={`prow${p.pid === me.pid ? ' me' : ''}`} key={p.pid}>
            <span className="em">{p.emoji}</span>
            <div className="grow"><div className="nm">{esc(p.name)}</div></div>
            <span className="sc">{r.gained[p.pid] ? `+${r.gained[p.pid]}` : '—'}</span>
          </div>
        ))}
      </div></div>
      <Timer endsAt={g.endsAt} total={10} calm nowServer={nowServer} />
    </>
  );
}

/* ---------------------------------------------------------- dial it in -- */

function GameDial({ v, g, me, on, HostStrip, Timer, nowServer }) {
  const [clue, setClue] = React.useState('');
  const [at, setAt] = React.useState(g.mine == null ? 50 : g.mine);
  const nameOf = pid => playerIn(v, pid).name || '—';

  const Head = (
    <div className="center" style={{ marginBottom: 8 }}>
      <div style={{ fontSize: 19, fontWeight: 800 }}>🎚️ Dial It In</div>
      <div className="muted">Turn {g.turn} of {g.turns} · {esc(nameOf(g.giver))} is clueing</div>
    </div>
  );
  const Poles = <div className="poles"><span>{esc(g.left)}</span><span>{esc(g.right)}</span></div>;

  if (g.phase === 'clue') {
    if (g.amGiver) {
      return (
        <>
          <HostStrip v={v} on={on} />{Head}
          <div className="stage" style={{ minHeight: '18vh' }}>
            <div className="head">Your target</div>
            {Poles}
            <div className="dial"><i style={{ left: `${g.target}%` }} /></div>
            <div className="body">Give <b>one word</b> that lands there.</div>
          </div>
          <input className="field" placeholder="one word" maxLength={24} autoComplete="off"
                 value={clue} onChange={e => setClue(e.target.value)} />
          <button className="btn primary" style={{ marginTop: 10 }} disabled={!clue.trim()}
                  onClick={() => { on({ t: 'clue', text: clue.trim() }); setClue(''); }}>Send the clue</button>
          <Timer endsAt={g.endsAt} total={35} calm nowServer={nowServer} />
        </>
      );
    }
    return (
      <>
        <HostStrip v={v} on={on} />{Head}
        <div className="stage" style={{ minHeight: '34vh' }}>
          <div className="huge">🎚️</div>
          <div className="head">{esc(nameOf(g.giver))} is thinking</div>
          {Poles}
          <div className="body">A one-word clue is coming. Then you guess where it sits.</div>
        </div>
        <Timer endsAt={g.endsAt} total={35} calm nowServer={nowServer} />
      </>
    );
  }

  if (g.phase === 'guess') {
    if (g.amGiver) {
      return (
        <>
          <HostStrip v={v} on={on} />{Head}
          <div className="stage" style={{ minHeight: '34vh' }}>
            <div className="head">“{esc(g.clue)}”</div>
            {Poles}
            <div className="body">Sit on your hands. {g.guessed} of {g.of} have guessed.</div>
          </div>
          <Timer endsAt={g.endsAt} total={35} calm nowServer={nowServer} />
        </>
      );
    }
    const locked = g.mine != null;
    return (
      <>
        <HostStrip v={v} on={on} />{Head}
        <div className="stage" style={{ minHeight: '14vh' }}><div className="head">“{esc(g.clue)}”</div></div>
        {Poles}
        <div className="dialwrap">
          <div className="dial big"><i style={{ left: `${locked ? g.mine : at}%` }} /></div>
          <input type="range" min="0" max="100" value={locked ? g.mine : at} disabled={locked}
                 onChange={e => setAt(+e.target.value)} />
        </div>
        {locked
          ? <div className="muted center" style={{ marginTop: 10 }}>locked at {g.mine} — {g.guessed} of {g.of} in</div>
          : <button className="btn primary" style={{ marginTop: 10 }} onClick={() => on({ t: 'guess', at })}>Lock it in</button>}
        <Timer endsAt={g.endsAt} total={35} calm nowServer={nowServer} />
      </>
    );
  }

  const r = g.result || { rows: [] };
  return (
    <>
      <HostStrip v={v} on={on} />{Head}
      <div className="stage" style={{ minHeight: '16vh' }}>
        <div className="muted">“{esc(r.clue || g.clue || '')}” was aiming at</div>
        <div className="head" style={{ color: 'var(--gold)' }}>{r.target == null ? '—' : r.target}</div>
        {Poles}
        <div className="dial big reveal">
          <i className="t" style={{ left: `${r.target == null ? 50 : r.target}%` }} />
          {r.rows.filter(x => x.at != null).map(x => (
            <b key={x.pid} style={{ left: `${x.at}%` }}>{String(nameOf(x.pid) || '?')[0]}</b>
          ))}
        </div>
      </div>
      <div className="card"><div className="plist">
        {r.rows.map(x => (
          <div className={`prow${x.pid === me.pid ? ' me' : ''}`} key={x.pid}>
            <span className="em">{playerIn(v, x.pid).emoji || '🐭'}</span>
            <div className="grow"><div className="nm">{esc(nameOf(x.pid))}</div>
              <div className="st">{x.dist == null ? 'no guess' : `${x.dist} off`}</div></div>
            <span className="sc">{x.points ? `+${x.points}` : '—'}</span>
          </div>
        ))}
      </div></div>
      <Timer endsAt={g.endsAt} total={10} calm nowServer={nowServer} />
    </>
  );
}

window.PartyGameViews = {
  GameTap, GameMajority, GamePunchline, GameQuiz, GameHeadsUp,
  GameOddOne, GameTallTale, GameDial,
};
window.PartyGameHelpers = { esc, nameOfIn, playerIn };

window.PartyGameView = function PartyGameView(props) {
  const g = props.v.game;
  const V = window.PartyGameViews;
  const map = {
    tap: V.GameTap,
    majority: V.GameMajority,
    punchline: V.GamePunchline,
    quiz: V.GameQuiz,
    headsup: V.GameHeadsUp,
    oddoneout: V.GameOddOne,
    talltale: V.GameTallTale,
    dialitin: V.GameDial,
  };
  const C = map[g.key];
  if (!C) {
    return (
      <div className="stage">
        <div className="head">{esc(g.name || 'Playing')}</div>
        <div className="muted">Follow along on the big screen.</div>
      </div>
    );
  }
  return <C {...props} g={g} />;
};

})();
