// Solo / single-player mode — adapted from design2/hitster/project/solo-screens.jsx.
// Self-contained: owns its own state machine, persistence, and audio. Loads a Spotify
// playlist (default = Hitster Norway) via /api/embed-playlist and uses the 30-second
// preview MP3 from each track for playback.

(() => {
const R = window.R;
const { Tag, Btn, Overprint, RisoStamp, RisoBars, RisoConfetti, Screen, GlobalRisoStyles, Stage, risoTex } = window;
const YearDrum = window.YearDrum;

const DEFAULT_PLAYLIST_ID = '0aPlDZsvBsCBBvrWym09Lf'; // HITSTER — Norway
const DECADES = ['60s', '70s', '80s', '90s', '00s', '10s', '20s'];
const STATS_KEY = 'hitfothit_solo_stats_v1';

function decadeOf(year) {
  const d = Math.floor(year / 10) * 10;
  return `${String(d).slice(2)}s`;
}

function scoreGuess(actual, guess) {
  const dist = Math.abs(actual - guess);
  let points;
  if (dist === 0) points = 100;
  else if (dist === 1) points = 70;
  else if (dist === 2) points = 50;
  else if (dist <= 4) points = 30;
  else if (dist <= 7) points = 15;
  else if (dist <= 12) points = 5;
  else points = 0;
  const bonus = dist === 0 ? 25 : 0;
  return { dist, points, bonus };
}

function defaultStats() {
  return {
    plays: 0,
    highscores: { daily: 0, drill: 0, endless: 0 },
    daily: { lastDate: null, streak: 0, bestToday: 0, played: false },
  };
}

function loadStats() {
  try {
    const raw = localStorage.getItem(STATS_KEY);
    if (!raw) return defaultStats();
    return { ...defaultStats(), ...JSON.parse(raw) };
  } catch (_) { return defaultStats(); }
}

function saveStats(s) {
  try { localStorage.setItem(STATS_KEY, JSON.stringify(s)); } catch (_) {}
}

function todayStr() {
  const d = new Date();
  return `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}`;
}

function dailyPlayedToday(stats) {
  return stats?.daily?.lastDate === todayStr();
}

// Deterministic seeded shuffle for the daily run. Same date+playlist → same 10.
function mulberry32(seed) {
  return function () {
    seed |= 0; seed = (seed + 0x6D2B79F5) | 0;
    let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}
function hashStr(s) {
  let h = 2166136261;
  for (let i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = Math.imul(h, 16777619); }
  return h >>> 0;
}
function shuffled(arr, rng) {
  const a = arr.slice();
  for (let i = a.length - 1; i > 0; i--) {
    const j = Math.floor(rng() * (i + 1));
    [a[i], a[j]] = [a[j], a[i]];
  }
  return a;
}

// ── Header chrome ──────────────────────────────────────────────
function SoloHeader({ title, subtitle, back, right }) {
  return (
    <div style={{ padding: '4px 22px 0', display: 'flex', alignItems: 'center', gap: 12, position: 'relative', zIndex: 3 }}>
      <button onClick={back} style={{
        width: 38, height: 38, border: `2px solid ${R.ink}`, background: R.paper, color: R.ink,
        fontSize: 18, fontWeight: 900, cursor: 'pointer', boxShadow: `3px 3px 0 ${R.ink}`,
        fontFamily: 'inherit', flexShrink: 0,
      }}>←</button>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: '.22em', color: R.muted, textTransform: 'uppercase' }}>{subtitle}</div>
        <div style={{ fontSize: 22, fontWeight: 900, letterSpacing: '-.02em', color: R.ink, lineHeight: 1.05 }}>{title}</div>
      </div>
      {right}
    </div>
  );
}

function ModeCard({ kind, label, sub, color, locked, badge, onClick }) {
  return (
    <button onClick={onClick} disabled={locked} style={{
      width: '100%', padding: '18px 18px 16px', textAlign: 'left',
      background: color, color: R.ink, border: `2px solid ${R.ink}`,
      boxShadow: locked ? `2px 2px 0 ${R.ink}` : `5px 5px 0 ${R.ink}`,
      fontFamily: 'inherit', cursor: locked ? 'not-allowed' : 'pointer',
      position: 'relative', overflow: 'hidden',
      backgroundImage: risoTex('rgba(26,26,26,.08)', 3),
      opacity: locked ? .55 : 1,
    }}>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
        <div style={{ fontSize: 11, fontWeight: 900, letterSpacing: '.28em', textTransform: 'uppercase', color: R.ink }}>{kind}</div>
        {badge && (
          <div style={{
            fontSize: 9, fontWeight: 900, letterSpacing: '.18em', padding: '3px 7px',
            background: R.ink, color: R.paper, textTransform: 'uppercase',
          }}>{badge}</div>
        )}
      </div>
      <div style={{ fontSize: 26, fontWeight: 900, letterSpacing: '-.02em', lineHeight: 1, marginTop: 6 }}>{label}</div>
      <div style={{ fontSize: 12, fontWeight: 700, color: R.ink, opacity: .75, marginTop: 6, lineHeight: 1.35 }}>{sub}</div>
    </button>
  );
}

function HeartRow({ lives, max = 3 }) {
  return (
    <div style={{ display: 'flex', gap: 4 }}>
      {Array.from({ length: max }).map((_, i) => {
        const alive = i < lives;
        return (
          <div key={i} style={{
            width: 14, height: 14,
            background: alive ? R.pink : 'transparent',
            border: `2px solid ${alive ? R.ink : R.dim}`,
            transform: 'rotate(45deg)',
            boxShadow: alive ? `2px 2px 0 ${R.ink}` : 'none',
          }} />
        );
      })}
    </div>
  );
}

// ── Screens ────────────────────────────────────────────────────
function SPHome({ stats, deckCount, onMode, onStats, onExit, fullMode, hasSpotifyToken, onToggleFull, onConnectSpotify, configReady }) {
  const playedToday = dailyPlayedToday(stats);
  return (
    <Screen>
      <RisoStamp size={260} color={R.yellow} top={-80} left={-90} duration={28} />
      <RisoStamp size={180} color={R.blue} top={-30} left={250} duration={32} delay={2} />

      <div style={{ padding: '14px 22px 0', display: 'flex', alignItems: 'center', justifyContent: 'space-between', position: 'relative', zIndex: 3 }}>
        <button onClick={onExit} style={{
          width: 38, height: 38, border: `2px solid ${R.ink}`, background: R.paper, color: R.ink,
          fontSize: 18, fontWeight: 900, cursor: 'pointer', boxShadow: `3px 3px 0 ${R.ink}`, fontFamily: 'inherit',
        }}>←</button>
        <button onClick={onStats} style={{
          padding: '8px 12px', border: `2px solid ${R.ink}`, background: R.yellow, color: R.ink,
          fontSize: 11, fontWeight: 900, letterSpacing: '.18em', cursor: 'pointer', boxShadow: `3px 3px 0 ${R.ink}`,
          fontFamily: 'inherit', textTransform: 'uppercase',
        }}>★ Stats</button>
      </div>

      <div style={{ padding: '14px 22px 8px', position: 'relative', zIndex: 3 }}>
        <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: '.3em', color: R.muted, textTransform: 'uppercase' }}>Solo</div>
        <Overprint sizes={56} color1={R.pink} color2={R.blue}>JUST<br/>YOU.</Overprint>
        <div style={{ marginTop: 10, fontSize: 13, color: R.muted, fontWeight: 600, lineHeight: 1.4 }}>
          One song. One year. Drag the dial. Climb the chart.
        </div>
      </div>

      <div style={{ flex: 1, padding: '10px 22px 18px', display: 'flex', flexDirection: 'column', gap: 10, position: 'relative', zIndex: 3, overflowY: 'auto' }}>
        <ModeCard
          kind="Daily · today"
          label={playedToday ? 'PLAYED TODAY' : "TODAY'S MIX"}
          sub={playedToday
            ? `Best today: ${stats.daily.bestToday} · Streak ${stats.daily.streak}🔥`
            : `Same set for everyone today. Streak ${stats.daily.streak}🔥`}
          color={R.yellow}
          badge={playedToday ? 'Done' : 'New'}
          onClick={() => onMode('daily')}
        />
        <ModeCard
          kind="Endless · survival"
          label="LAST ONE STANDING"
          sub={`3 lives. Every guess >2 yrs off costs one. Best: ${stats.highscores.endless || 0}`}
          color={R.pink}
          onClick={() => onMode('endless')}
        />
        <ModeCard
          kind="Decade drill · practice"
          label="ONE ERA, 10 SONGS"
          sub={`Pick a decade, sharpen up. Best: ${stats.highscores.drill || 0}`}
          color={R.blue}
          onClick={() => onMode('drill')}
        />
      </div>

      <div style={{ padding: '0 22px 4px', position: 'relative', zIndex: 3, display: 'flex', flexDirection: 'column', gap: 8 }}>
        <div onClick={onToggleFull} style={{
          padding: '10px 12px', border: `2px solid ${R.ink}`,
          background: fullMode ? (hasSpotifyToken ? R.green : R.yellow) : R.paper2,
          boxShadow: `3px 3px 0 ${R.ink}`,
          display: 'flex', alignItems: 'center', gap: 10, cursor: 'pointer',
        }}>
          <div style={{
            width: 20, height: 20, border: `2px solid ${R.ink}`, flexShrink: 0,
            background: fullMode ? R.ink : 'transparent',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            color: R.paper, fontSize: 13, fontWeight: 900,
          }}>{fullMode ? '✓' : ''}</div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 12, fontWeight: 900, letterSpacing: '.04em' }}>
              FULL SONGS · {hasSpotifyToken ? 'CONNECTED' : 'NEEDS SPOTIFY'}
            </div>
            <div style={{ fontSize: 10, fontWeight: 700, color: R.muted, marginTop: 2, lineHeight: 1.3 }}>
              {hasSpotifyToken
                ? (fullMode ? 'Plays full tracks via your Spotify Connect device.' : 'Tap to flip from 30-sec previews to full songs.')
                : 'Tap CONNECT SPOTIFY below — Premium account, then have Spotify open on a device.'}
            </div>
          </div>
        </div>

        {!hasSpotifyToken && (
          <Btn big={false} bg={configReady ? R.pink : R.paper3} fg={R.paper}
            disabled={!configReady} onClick={onConnectSpotify}>
            {configReady ? 'CONNECT SPOTIFY →' : 'SPOTIFY NOT CONFIGURED'}
          </Btn>
        )}

        {hasSpotifyToken && fullMode && (
          <div style={{
            padding: '8px 10px', background: R.paper2, border: `2px dashed ${R.ink}`,
            fontSize: 10, fontWeight: 700, color: R.muted, lineHeight: 1.4,
          }}>
            ⓘ Open Spotify on a phone/speaker and tap play once before starting — Hitfothit will route music there.
          </div>
        )}

        <div style={{
          padding: '8px 10px', border: `2px dashed ${R.ink}`, background: 'transparent',
          fontSize: 11, fontWeight: 700, color: R.muted, lineHeight: 1.4,
        }}>
          Plays: <b style={{ color: R.ink }}>{stats.plays}</b> · Deck: <b style={{ color: R.ink }}>{deckCount}</b> tracks · Stats local to this device.
        </div>
      </div>
    </Screen>
  );
}

function SPDecade({ deck, onPick, onBack }) {
  const counts = React.useMemo(() => {
    const m = {};
    DECADES.forEach((d) => { m[d] = deck.filter((s) => decadeOf(s.year) === d).length; });
    return m;
  }, [deck]);
  return (
    <Screen>
      <SoloHeader subtitle="Decade drill" title="PICK YOUR ERA" back={onBack} />
      <div style={{ flex: 1, padding: '20px 22px', display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, overflowY: 'auto' }}>
        {DECADES.map((d, i) => {
          const colors = [R.pink, R.blue, R.yellow, R.green, R.pink, R.blue, R.yellow];
          const c = colors[i % colors.length];
          const ready = counts[d] >= 6;
          return (
            <button key={d} onClick={() => ready && onPick(d)} disabled={!ready} style={{
              padding: '20px 14px', border: `2px solid ${R.ink}`, background: c, color: R.ink,
              boxShadow: `4px 4px 0 ${R.ink}`, cursor: ready ? 'pointer' : 'not-allowed', fontFamily: 'inherit',
              backgroundImage: risoTex('rgba(26,26,26,.08)', 3),
              opacity: ready ? 1 : .5, textAlign: 'left',
            }}>
              <div style={{ fontSize: 11, fontWeight: 900, letterSpacing: '.22em' }}>19{d.slice(0,1)}0–19{d.slice(0,1)}9</div>
              <div style={{ fontSize: 56, fontWeight: 900, letterSpacing: '-.04em', lineHeight: .9, marginTop: 6 }}>{d}</div>
              <div style={{ fontSize: 10, fontWeight: 700, marginTop: 6, opacity: .8 }}>{counts[d]} tracks {ready ? '' : '· too few'}</div>
            </button>
          );
        })}
      </div>
    </Screen>
  );
}

function SPIntro({ mode, decade, best, onContinue }) {
  const meta = {
    daily:   { title: "TODAY'S MIX", sub: 'Daily',    rules: ['10 songs', 'Same set for everyone', 'Resets at midnight'], color: R.yellow },
    endless: { title: 'ENDLESS',     sub: 'Survival', rules: ['3 lives', 'Lose 1 if guess > 2 yrs off', 'Last as long as you can'], color: R.pink },
    drill:   { title: `${decade || '90s'} DRILL`, sub: 'Practice', rules: ['10 songs from one era', 'No lives, just score', 'Beat your best'], color: R.blue },
  }[mode] || {};

  React.useEffect(() => {
    const t = setTimeout(() => onContinue(), 2200);
    return () => clearTimeout(t);
  }, []);

  return (
    <Screen>
      <RisoStamp size={320} color={meta.color} top={-100} left={-80} duration={26} />
      <div style={{ padding: '18px 22px 0', display: 'flex', justifyContent: 'space-between', alignItems: 'center', position: 'relative', zIndex: 3 }}>
        <Tag color={R.ink}>{meta.sub}</Tag>
        {best > 0 && <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: '.16em', color: R.muted }}>Best · <b style={{ color: R.ink }}>{best}</b></div>}
      </div>
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center', padding: '0 22px', position: 'relative', zIndex: 3, gap: 16 }}>
        <Overprint sizes={56} color1={R.pink} color2={R.blue}>{meta.title}</Overprint>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
          {meta.rules?.map((r, i) => (
            <div key={i} style={{
              padding: '10px 12px', background: R.paper2, border: `2px solid ${R.ink}`,
              boxShadow: `3px 3px 0 ${R.ink}`, display: 'flex', alignItems: 'center', gap: 10,
            }}>
              <div style={{ width: 8, height: 8, background: meta.color, border: `2px solid ${R.ink}` }} />
              <div style={{ fontSize: 14, fontWeight: 800 }}>{r}</div>
            </div>
          ))}
        </div>
      </div>
      <div style={{ padding: '0 22px', position: 'relative', zIndex: 3 }}>
        <Btn bg={meta.color} onClick={onContinue}>I'M IN →</Btn>
      </div>
    </Screen>
  );
}

function SPRoundIntro({ round, total, onContinue }) {
  React.useEffect(() => {
    const t = setTimeout(onContinue, 1100);
    return () => clearTimeout(t);
  }, []);
  return (
    <Screen>
      <RisoStamp size={400} color={R.blue} top={-120} 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: 13, fontWeight: 800, letterSpacing: '.32em', color: R.muted, animation: 'rfade .4s' }}>TRACK</div>
        <div style={{
          fontSize: 200, fontWeight: 900, lineHeight: .8, letterSpacing: '-.06em', color: R.pink,
          mixBlendMode: 'multiply', animation: 'rdrop .5s cubic-bezier(.2,1.4,.4,1)',
          fontFamily: 'inherit',
        }}>{String(round).padStart(2, '0')}</div>
        <div style={{ fontSize: 16, fontWeight: 800, letterSpacing: '.18em', color: R.ink, opacity: .6 }}>OF {total}</div>
      </div>
    </Screen>
  );
}

function SPGuess({ round, total, mode, lives, score, year, setYear, song, useFullSongs, playFull, pauseFull, onLock, onTimeout }) {
  const TURN = useFullSongs ? 60 : 25;
  const [time, setTime] = React.useState(TURN);
  const [playbackError, setPlaybackError] = React.useState(null);
  const audioRef = React.useRef(null);

  React.useEffect(() => {
    if (!song) return;
    setPlaybackError(null);

    if (useFullSongs && playFull) {
      // Spotify Connect — fire-and-forget, errors surface inline.
      playFull(song).catch((e) => setPlaybackError(e?.message || 'Playback failed.'));
      return () => { pauseFull?.().catch(() => {}); };
    }

    // Preview mode — HTML5 audio
    if (!song.previewUrl) return;
    const a = new Audio(song.previewUrl);
    a.volume = 1;
    audioRef.current = a;
    a.play().catch(() => { /* iOS may block — user can tap card to retry */ });
    return () => { try { a.pause(); a.src = ''; } catch (_) {} audioRef.current = null; };
  }, [song?.id, useFullSongs]);

  React.useEffect(() => {
    if (time <= 0) { onTimeout(); return; }
    const t = setTimeout(() => setTime((s) => s - 1), 1000);
    return () => clearTimeout(t);
  }, [time]);

  const playAgain = () => {
    if (useFullSongs && playFull && song) {
      playFull(song).catch((e) => setPlaybackError(e?.message || 'Playback failed.'));
      return;
    }
    if (!audioRef.current || !song?.previewUrl) return;
    audioRef.current.currentTime = 0;
    audioRef.current.play().catch(() => {});
  };

  return (
    <Screen>
      <div style={{ padding: '6px 22px 0', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10 }}>
        <Tag color={R.ink}>Track {round}/{total}</Tag>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
          {mode === 'endless' && <HeartRow lives={lives} />}
          <div style={{
            padding: '4px 8px', border: `2px solid ${R.ink}`, background: R.paper2,
            fontSize: 12, fontWeight: 900, fontFamily: 'ui-monospace, monospace', letterSpacing: '.04em',
          }}>{score}</div>
          <div style={{
            width: 34, height: 34, border: `2px solid ${R.ink}`,
            background: time <= 5 ? R.pink : R.yellow,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            fontSize: 14, fontWeight: 900, fontFamily: 'ui-monospace, monospace',
            boxShadow: `3px 3px 0 ${R.ink}`,
            animation: time <= 5 ? 'rpulse .6s ease-in-out infinite' : 'none',
          }}>{time}</div>
        </div>
      </div>

      <div style={{ padding: '14px 22px 0' }}>
        <div onClick={playAgain} style={{
          padding: '14px', background: R.paper2, border: `2px solid ${R.ink}`,
          boxShadow: `4px 4px 0 ${R.ink}`,
          display: 'flex', alignItems: 'center', gap: 12, cursor: 'pointer',
        }}>
          <div style={{
            width: 52, height: 52, background: R.ink, position: 'relative', overflow: 'hidden', flexShrink: 0,
            backgroundImage: `repeating-linear-gradient(45deg, ${R.pink} 0 4px, ${R.ink} 4px 9px)`,
            animation: 'rspin 12s linear infinite',
          }} />
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: '.18em', color: R.muted }}>
              NOW PLAYING · {useFullSongs ? 'SPOTIFY · FULL' : 'PREVIEW · 30s'}
            </div>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 6 }}>
              <RisoBars />
              <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: '.14em', color: R.ink }}>TAP TO REPLAY</div>
            </div>
          </div>
        </div>
        {playbackError && (
          <div style={{
            marginTop: 8, padding: '8px 10px', background: R.pink, color: R.paper,
            border: `2px solid ${R.ink}`, fontSize: 11, fontWeight: 700, lineHeight: 1.4,
          }}>{playbackError}</div>
        )}
      </div>

      <div style={{ flex: 1, padding: '10px 22px 0', display: 'flex', flexDirection: 'column' }}>
        <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: '.22em', color: R.muted, textAlign: 'center', marginBottom: 6 }}>SPIN TO YEAR</div>
        <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
          <YearDrum value={year} onChange={setYear} accent={R.pink} />
        </div>
      </div>

      <div style={{ padding: '0 22px' }}>
        <Btn bg={R.green} onClick={() => {
          try { audioRef.current?.pause(); } catch (_) {}
          if (useFullSongs && pauseFull) pauseFull().catch(() => {});
          onLock();
        }}>LOCK IT IN · {year} →</Btn>
      </div>
    </Screen>
  );
}

function SPReveal({ year, song, mode, lives, score, streak, onContinue }) {
  const [step, setStep] = React.useState(0);
  const [display, setDisplay] = React.useState(year);
  const r = scoreGuess(song.year, year);
  const totalPoints = r.points + r.bonus;
  const exact = r.dist === 0;
  const close = r.dist <= 2;
  const dead = mode === 'endless' && lives <= 0;

  React.useEffect(() => {
    setDisplay(year);
    const t1 = setTimeout(() => {
      const start = year, end = song.year, diff = end - start;
      const dur = 1000;
      const t0 = performance.now();
      const tick = (now) => {
        const p = Math.min(1, (now - t0) / dur);
        const e = 1 - Math.pow(1 - p, 3);
        setDisplay(Math.round(start + diff * e));
        if (p < 1) requestAnimationFrame(tick);
        else setTimeout(() => setStep(1), 250);
      };
      requestAnimationFrame(tick);
    }, 350);
    return () => clearTimeout(t1);
  }, [song?.id]);

  const accent = exact ? R.yellow : close ? R.green : R.pink;
  const verdict = exact ? 'BULLSEYE' : close ? 'NICE' : r.dist <= 7 ? 'CLOSE' : 'OFF';

  return (
    <Screen>
      {exact && <RisoConfetti count={45} />}
      <RisoStamp size={300} color={accent} top={-100} left={-90} duration={25} />

      <div style={{ padding: '14px 22px 0', display: 'flex', justifyContent: 'space-between', alignItems: 'center', position: 'relative', zIndex: 3 }}>
        <Tag color={accent}>{verdict}</Tag>
        <div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
          {mode === 'endless' && <HeartRow lives={lives} />}
          <div style={{ padding: '4px 8px', border: `2px solid ${R.ink}`, background: R.paper2, fontSize: 12, fontWeight: 900, fontFamily: 'ui-monospace, monospace' }}>
            {score + (step ? totalPoints : 0)}
          </div>
        </div>
      </div>

      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center', padding: '0 22px', position: 'relative', zIndex: 3, gap: 14 }}>
        <div style={{ textAlign: 'center' }}>
          <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: '.32em', color: R.muted }}>THE YEAR WAS</div>
          <div style={{
            fontSize: 130, fontWeight: 900, lineHeight: .85, letterSpacing: '-.05em', color: R.ink,
            fontFamily: 'ui-monospace, monospace', mixBlendMode: 'multiply', marginTop: 4,
          }}>{display}</div>
          <div style={{ fontSize: 12, fontWeight: 800, letterSpacing: '.18em', color: R.muted, marginTop: 4 }}>
            YOU SAID {year} {r.dist === 0 ? '· EXACT' : `· ${r.dist} ${r.dist === 1 ? 'YR' : 'YRS'} OFF`}
          </div>
        </div>

        <div style={{
          padding: '14px', background: accent, border: `2px solid ${R.ink}`,
          boxShadow: `4px 4px 0 ${R.ink}`, backgroundImage: risoTex('rgba(26,26,26,.08)', 3),
        }}>
          <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: '.18em', color: R.ink }}>THE TRACK</div>
          <div style={{ fontSize: 18, fontWeight: 900, lineHeight: 1.05, letterSpacing: '-.01em', marginTop: 4, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{song.title}</div>
          <div style={{ fontSize: 12, fontWeight: 700, marginTop: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{song.artist}</div>
        </div>

        <div style={{
          opacity: step ? 1 : 0, transform: `translateY(${step ? 0 : 12}px)`,
          transition: 'opacity .35s, transform .35s',
          display: 'flex', gap: 10,
        }}>
          <div style={{ flex: 1, padding: '12px', background: R.paper2, border: `2px solid ${R.ink}`, boxShadow: `3px 3px 0 ${R.ink}` }}>
            <div style={{ fontSize: 10, fontWeight: 800, letterSpacing: '.18em', color: R.muted }}>POINTS</div>
            <div style={{ fontSize: 32, fontWeight: 900, lineHeight: 1, marginTop: 2, fontFamily: 'ui-monospace, monospace' }}>+{totalPoints}</div>
            {r.bonus > 0 && <div style={{ fontSize: 10, fontWeight: 800, color: R.pink, marginTop: 2 }}>+{r.bonus} BULLSEYE</div>}
          </div>
          <div style={{ flex: 1, padding: '12px', background: streak >= 2 ? R.yellow : R.paper2, border: `2px solid ${R.ink}`, boxShadow: `3px 3px 0 ${R.ink}` }}>
            <div style={{ fontSize: 10, fontWeight: 800, letterSpacing: '.18em', color: R.muted }}>STREAK</div>
            <div style={{ fontSize: 32, fontWeight: 900, lineHeight: 1, marginTop: 2, fontFamily: 'ui-monospace, monospace' }}>{streak}🔥</div>
          </div>
        </div>
      </div>

      <div style={{ padding: '0 22px', position: 'relative', zIndex: 3, opacity: step ? 1 : .3, pointerEvents: step ? 'auto' : 'none', transition: 'opacity .3s' }}>
        <Btn bg={dead ? R.pink : R.green} onClick={() => onContinue({ points: totalPoints, dist: r.dist })}>
          {dead ? 'YOU DIED · SEE RESULTS →' : 'NEXT TRACK →'}
        </Btn>
      </div>
    </Screen>
  );
}

function SPEnd({ mode, score, rounds, best, isNewHi, stats, onShare, onReplay, onHome }) {
  return (
    <Screen>
      {isNewHi && <RisoConfetti count={60} />}
      <RisoStamp size={360} color={isNewHi ? R.yellow : R.blue} top={-120} left={-100} duration={28} />

      <div style={{ padding: '18px 22px 0', display: 'flex', justifyContent: 'space-between', position: 'relative', zIndex: 3 }}>
        <Tag color={R.ink}>Run end</Tag>
        <Tag color={isNewHi ? R.yellow : R.pink}>{isNewHi ? 'NEW HIGH' : (mode === 'endless' ? 'OUT OF LIVES' : 'COMPLETE')}</Tag>
      </div>

      <div style={{ flex: 1, padding: '20px 22px 0', display: 'flex', flexDirection: 'column', gap: 14, position: 'relative', zIndex: 3, overflowY: 'auto' }}>
        <Overprint sizes={44} color1={R.pink} color2={R.blue}>
          {isNewHi ? 'NEW BEST.' : (mode === 'endless' ? 'GAME OVER.' : 'NICE RUN.')}
        </Overprint>

        <div style={{
          padding: 18, border: `2px solid ${R.ink}`, background: isNewHi ? R.yellow : R.paper2,
          boxShadow: `5px 5px 0 ${R.ink}`, backgroundImage: risoTex('rgba(26,26,26,.08)', 3),
        }}>
          <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: '.22em', color: R.muted }}>FINAL SCORE</div>
          <div style={{ fontSize: 80, fontWeight: 900, lineHeight: .9, letterSpacing: '-.04em', fontFamily: 'ui-monospace, monospace', marginTop: 4 }}>{score}</div>
          <div style={{ fontSize: 13, fontWeight: 700, color: R.muted, marginTop: 6 }}>
            {rounds} {rounds === 1 ? 'track' : 'tracks'} ·
            avg <b style={{ color: R.ink }}>{rounds ? Math.round(score / rounds) : 0}</b> per track
          </div>
        </div>

        <div style={{ display: 'flex', gap: 10 }}>
          <div style={{ flex: 1, padding: '10px 12px', border: `2px solid ${R.ink}`, background: R.paper2, boxShadow: `3px 3px 0 ${R.ink}` }}>
            <div style={{ fontSize: 9, fontWeight: 800, letterSpacing: '.18em', color: R.muted }}>BEST {mode.toUpperCase()}</div>
            <div style={{ fontSize: 26, fontWeight: 900, fontFamily: 'ui-monospace, monospace', marginTop: 2 }}>{best || score}</div>
          </div>
          {mode === 'daily' ? (
            <div style={{ flex: 1, padding: '10px 12px', border: `2px solid ${R.ink}`, background: R.pink, boxShadow: `3px 3px 0 ${R.ink}`, color: R.ink }}>
              <div style={{ fontSize: 9, fontWeight: 800, letterSpacing: '.18em' }}>STREAK</div>
              <div style={{ fontSize: 26, fontWeight: 900, fontFamily: 'ui-monospace, monospace', marginTop: 2 }}>{stats.daily.streak}🔥</div>
            </div>
          ) : (
            <div style={{ flex: 1, padding: '10px 12px', border: `2px solid ${R.ink}`, background: R.paper2, boxShadow: `3px 3px 0 ${R.ink}` }}>
              <div style={{ fontSize: 9, fontWeight: 800, letterSpacing: '.18em', color: R.muted }}>PLAYS</div>
              <div style={{ fontSize: 26, fontWeight: 900, fontFamily: 'ui-monospace, monospace', marginTop: 2 }}>{stats.plays}</div>
            </div>
          )}
        </div>
      </div>

      <div style={{ padding: '14px 22px 0', display: 'flex', flexDirection: 'column', gap: 8, position: 'relative', zIndex: 3 }}>
        {mode === 'daily' && onShare && <Btn bg={R.yellow} onClick={onShare}>SHARE TODAY →</Btn>}
        <div style={{ display: 'flex', gap: 8 }}>
          <div style={{ flex: 1 }}>
            <Btn bg={R.green} big={false} onClick={onReplay}>PLAY AGAIN</Btn>
          </div>
          <div style={{ flex: 1 }}>
            <Btn bg={R.paper2} big={false} onClick={onHome}>HOME</Btn>
          </div>
        </div>
      </div>
    </Screen>
  );
}

function SPHighscores({ stats, onBack }) {
  const rows = [
    { mode: 'Daily',   k: 'daily',   color: R.yellow },
    { mode: 'Endless', k: 'endless', color: R.pink },
    { mode: 'Drill',   k: 'drill',   color: R.blue },
  ];
  return (
    <Screen>
      <SoloHeader subtitle="Personal" title="YOUR STATS" back={onBack} />

      <div style={{ flex: 1, padding: '20px 22px', display: 'flex', flexDirection: 'column', gap: 14, overflowY: 'auto' }}>
        <div style={{
          padding: 16, border: `2px solid ${R.ink}`, background: R.pink,
          boxShadow: `4px 4px 0 ${R.ink}`, backgroundImage: risoTex('rgba(26,26,26,.08)', 3),
        }}>
          <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: '.22em', color: R.ink }}>DAILY STREAK</div>
          <div style={{ fontSize: 64, fontWeight: 900, lineHeight: .9, letterSpacing: '-.03em', color: R.ink, fontFamily: 'ui-monospace, monospace', marginTop: 4 }}>
            {stats.daily.streak}🔥
          </div>
          <div style={{ fontSize: 12, fontWeight: 700, marginTop: 4, color: R.ink, opacity: .8 }}>
            {dailyPlayedToday(stats) ? `Played today · best ${stats.daily.bestToday}` : 'Come back tomorrow to keep it alive.'}
          </div>
        </div>

        <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
          <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: '.22em', color: R.muted }}>HIGHSCORES</div>
          {rows.map((r) => (
            <div key={r.k} style={{
              padding: '12px 14px', border: `2px solid ${R.ink}`, background: R.paper2,
              boxShadow: `3px 3px 0 ${R.ink}`,
              display: 'flex', alignItems: 'center', gap: 12,
            }}>
              <div style={{ width: 12, height: 28, background: r.color, border: `2px solid ${R.ink}` }} />
              <div style={{ flex: 1, fontSize: 14, fontWeight: 800, letterSpacing: '.04em' }}>{r.mode}</div>
              <div style={{ fontSize: 22, fontWeight: 900, fontFamily: 'ui-monospace, monospace' }}>{stats.highscores[r.k] || 0}</div>
            </div>
          ))}
        </div>

        <div style={{
          padding: '10px 12px', border: `2px dashed ${R.ink}`,
          fontSize: 11, fontWeight: 700, color: R.muted, lineHeight: 1.4,
        }}>
          Total plays: <b style={{ color: R.ink }}>{stats.plays}</b>. Stats live on this device.
        </div>
      </div>
    </Screen>
  );
}

function SPShare({ score, history, onBack, onHome }) {
  const [copied, setCopied] = React.useState(false);
  const grid = history.map((h) => {
    if (h.dist === 0) return '🟨';
    if (h.dist <= 2) return '🟩';
    if (h.dist <= 7) return '🟦';
    return '⬛';
  }).join('');
  const text = `Hitfothit · daily · ${score}\n${grid}\nhitforhit`;

  const copy = () => {
    try {
      navigator.clipboard?.writeText(text);
      setCopied(true);
      setTimeout(() => setCopied(false), 1600);
    } catch (_) {}
  };

  return (
    <Screen>
      <SoloHeader subtitle="Daily" title="SHARE IT" back={onBack} />

      <div style={{ flex: 1, padding: '20px 22px', display: 'flex', flexDirection: 'column', gap: 14, overflowY: 'auto' }}>
        <div style={{
          padding: 18, border: `2px solid ${R.ink}`, background: R.paper2, boxShadow: `5px 5px 0 ${R.ink}`,
        }}>
          <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: '.22em', color: R.muted }}>HITFOTHIT · DAILY</div>
          <div style={{ fontSize: 40, fontWeight: 900, fontFamily: 'ui-monospace, monospace', marginTop: 2, lineHeight: 1 }}>{score} pts</div>
          <div style={{
            marginTop: 10, fontSize: 32, lineHeight: 1, letterSpacing: '4px',
            fontFamily: 'apple color emoji, segoe ui emoji, sans-serif',
            wordBreak: 'break-all',
          }}>{grid}</div>
          <div style={{ marginTop: 10, fontSize: 11, fontWeight: 700, color: R.muted, letterSpacing: '.1em' }}>
            🟨 exact · 🟩 within 2 · 🟦 within 7 · ⬛ off
          </div>
        </div>

        <div style={{
          padding: '10px 12px', border: `2px dashed ${R.ink}`,
          fontSize: 11, fontWeight: 700, color: R.muted, lineHeight: 1.5,
        }}>
          No song titles. No years. Spoiler-free.
        </div>
      </div>

      <div style={{ padding: '0 22px', display: 'flex', flexDirection: 'column', gap: 8 }}>
        <Btn bg={copied ? R.green : R.yellow} onClick={copy}>{copied ? 'COPIED ✓' : 'COPY TO CLIPBOARD'}</Btn>
        <Btn bg={R.paper2} big={false} onClick={onHome}>BACK TO HOME</Btn>
      </div>
    </Screen>
  );
}

function SPLoading({ progress, error, onRetry, onBack }) {
  return (
    <Screen>
      <SoloHeader subtitle="Solo · Loading" title="WARMING UP" back={onBack} />
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: 22, gap: 14 }}>
        {error ? (
          <>
            <div style={{ fontSize: 13, fontWeight: 700, color: R.muted, textAlign: 'center', maxWidth: 280, lineHeight: 1.45 }}>{error}</div>
            <Btn bg={R.pink} onClick={onRetry}>TRY AGAIN</Btn>
          </>
        ) : (
          <>
            <RisoBars />
            <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: '.18em', color: R.muted }}>FETCHING THE DECK…</div>
            {progress && <div style={{ fontSize: 12, fontWeight: 700, color: R.muted }}>{progress}</div>}
          </>
        )}
      </div>
    </Screen>
  );
}

// ── Main solo app ──────────────────────────────────────────────
function SoloApp({ onExit }) {
  const [view, setView] = React.useState('loading');
  const [deck, setDeck] = React.useState([]);
  const [loadError, setLoadError] = React.useState(null);
  const [stats, setStats] = React.useState(loadStats());

  // Spotify Premium full-song state — shared with /host's stored token.
  const [fullMode, setFullMode] = React.useState(localStorage.getItem('hf_solo_full') === 'true');
  const [spotifyToken, setSpotifyToken] = React.useState(() => window.Spotify?.loadStoredToken?.() || null);
  const [config, setConfig] = React.useState(null);
  const [pickedDevice, setPickedDevice] = React.useState(localStorage.getItem('hf_device_id') || null);
  const tokenRef = React.useRef(spotifyToken);
  React.useEffect(() => { tokenRef.current = spotifyToken; }, [spotifyToken]);

  React.useEffect(() => {
    fetch('/api/config').then(r => r.json()).then(setConfig).catch(() => {});
  }, []);

  const refreshSpotify = React.useCallback(async () => {
    const Sp = window.Spotify;
    if (!Sp || !config?.spotifyClientId || !tokenRef.current?.refresh) {
      throw new Error('Spotify not configured.');
    }
    const t = await Sp.refreshToken(config.spotifyClientId, tokenRef.current.refresh);
    Sp.storeToken(t);
    tokenRef.current = t;
    setSpotifyToken(t);
    return t;
  }, [config]);

  const playFull = React.useCallback(async (track) => {
    const Sp = window.Spotify;
    if (!Sp || !spotifyToken) throw new Error('Spotify not connected. Open the host page first.');
    if (!track?.uri) throw new Error('No Spotify URI for this track.');
    const json = await Sp.spApi(spotifyToken, refreshSpotify, '/me/player/devices');
    const devices = json?.devices || [];
    if (devices.length === 0) {
      throw new Error("No Spotify device active. Open Spotify on your phone, hit play on anything for a moment, then try again.");
    }
    let deviceId = pickedDevice;
    if (!deviceId || !devices.find(d => d.id === deviceId)) {
      deviceId = (devices.find(d => d.is_active) || devices[0]).id;
      setPickedDevice(deviceId);
      localStorage.setItem('hf_device_id', deviceId);
    }
    await Sp.transferPlayback(spotifyToken, refreshSpotify, deviceId, false);
    await new Promise(r => setTimeout(r, 300));
    await Sp.playTrack(spotifyToken, refreshSpotify, track.uri, deviceId);
  }, [spotifyToken, pickedDevice, refreshSpotify]);

  const pauseFull = React.useCallback(async () => {
    const Sp = window.Spotify;
    if (!Sp || !spotifyToken || !pickedDevice) return;
    try { await Sp.pausePlayback(spotifyToken, refreshSpotify, pickedDevice); } catch (_) {}
  }, [spotifyToken, pickedDevice, refreshSpotify]);

  const useFullSongs = fullMode && !!spotifyToken;

  const toggleFullMode = () => {
    const next = !fullMode;
    setFullMode(next);
    localStorage.setItem('hf_solo_full', String(next));
  };

  // Direct Spotify auth from solo. Stores a "return to /" hint so /host's
  // auth callback drops us back here instead of the host playlist screen,
  // and pre-enables fullMode since the user clearly wants it.
  const connectSpotify = async () => {
    if (!config?.spotifyClientId) return;
    try {
      localStorage.setItem('hf_post_auth_return', '/');
      localStorage.setItem('hf_solo_full', 'true');
      await window.Spotify.startAuth(config.spotifyClientId, config.redirectUri);
    } catch (_) {
      localStorage.removeItem('hf_post_auth_return');
    }
  };

  // Run state
  const [mode, setMode] = React.useState('daily');
  const [decade, setDecade] = React.useState(null);
  const [queue, setQueue] = React.useState([]); // upcoming songs
  const [round, setRound] = React.useState(0);
  const [total, setTotal] = React.useState(0);
  const [year, setYear] = React.useState(1995);
  const [score, setScore] = React.useState(0);
  const [streak, setStreak] = React.useState(0);
  const [lives, setLives] = React.useState(3);
  const [history, setHistory] = React.useState([]);

  // Load default deck on mount
  const loadDeck = React.useCallback(async () => {
    setLoadError(null);
    try {
      const r = await fetch(`/api/embed-playlist?id=${DEFAULT_PLAYLIST_ID}`);
      if (!r.ok) throw new Error(`Couldn't load default deck (${r.status})`);
      const j = await r.json();
      const tracks = (j.tracks || []).filter(t => t.previewUrl && t.year);
      if (tracks.length < 10) throw new Error('Not enough playable tracks in deck.');
      setDeck(tracks);
      setView('home');
    } catch (e) {
      setLoadError(e.message || 'Failed to load deck.');
    }
  }, []);

  React.useEffect(() => { loadDeck(); }, [loadDeck]);

  const startMode = (m) => {
    setMode(m);
    if (m === 'drill') { setView('decade'); return; }
    pickAndStart(m, null);
  };

  const pickAndStart = (m, dec) => {
    let pool = deck;
    if (m === 'drill') pool = deck.filter(t => decadeOf(t.year) === dec);

    let q;
    let tot;
    if (m === 'daily') {
      const seed = hashStr(todayStr() + DEFAULT_PLAYLIST_ID);
      q = shuffled(pool, mulberry32(seed)).slice(0, 10);
      tot = q.length;
    } else if (m === 'endless') {
      q = shuffled(pool, mulberry32(Date.now() & 0xffff));
      tot = q.length; // upper bound; will end on lives = 0
    } else { // drill
      q = shuffled(pool, mulberry32(Date.now() & 0xffff)).slice(0, 10);
      tot = q.length;
    }
    if (q.length === 0) {
      setLoadError('No tracks for that selection.');
      setView('loading');
      return;
    }
    setDecade(dec);
    setQueue(q);
    setRound(1);
    setTotal(tot);
    setYear(1995);
    setScore(0);
    setStreak(0);
    setLives(3);
    setHistory([]);
    setView('intro');
  };

  const currentSong = queue[round - 1] || null;

  const lockGuess = () => {
    if (!currentSong) return;
    const r = scoreGuess(currentSong.year, year);
    const totalPoints = r.points + r.bonus;
    let nextLives = lives;
    if (mode === 'endless' && r.dist > 2) nextLives = Math.max(0, lives - 1);
    setLives(nextLives);
    setHistory(h => [...h, { dist: r.dist, points: totalPoints, song: currentSong, guess: year }]);
    setView('reveal');
  };

  const handleTimeout = () => {
    if (!currentSong) return;
    const r = scoreGuess(currentSong.year, year);
    let nextLives = lives;
    if (mode === 'endless' && r.dist > 2) nextLives = Math.max(0, lives - 1);
    setLives(nextLives);
    setHistory(h => [...h, { dist: r.dist, points: r.points + r.bonus, song: currentSong, guess: year }]);
    setView('reveal');
  };

  const finishReveal = ({ points, dist }) => {
    setScore(s => s + points);
    const newStreak = dist <= 2 ? streak + 1 : 0;
    setStreak(newStreak);

    const last = history[history.length - 1];
    const dead = mode === 'endless' && lives <= 0;

    const isLast = mode === 'endless'
      ? dead
      : round >= total;

    if (isLast) {
      finalize({ deltaScore: points });
    } else {
      setRound(r => r + 1);
      setYear(1995);
      setView('round-intro');
    }
  };

  const finalize = ({ deltaScore }) => {
    const finalScore = score + (deltaScore || 0);
    const newStats = { ...stats, plays: stats.plays + 1 };
    let isNewHi = false;
    if (mode === 'daily') {
      const today = todayStr();
      const wasYesterday = (() => {
        if (!stats.daily.lastDate) return false;
        const last = new Date(stats.daily.lastDate);
        const now = new Date(today);
        const diff = (now - last) / (1000 * 60 * 60 * 24);
        return diff >= 0.9 && diff <= 1.1;
      })();
      const continuingStreak = stats.daily.lastDate === today
        ? stats.daily.streak
        : (wasYesterday ? stats.daily.streak + 1 : 1);
      newStats.daily = {
        lastDate: today,
        streak: continuingStreak,
        bestToday: Math.max(stats.daily.bestToday || 0, finalScore),
        played: true,
      };
      if (finalScore > (stats.highscores.daily || 0)) {
        newStats.highscores = { ...stats.highscores, daily: finalScore };
        isNewHi = true;
      }
    } else if (mode === 'endless') {
      if (finalScore > (stats.highscores.endless || 0)) {
        newStats.highscores = { ...stats.highscores, endless: finalScore };
        isNewHi = true;
      }
    } else if (mode === 'drill') {
      if (finalScore > (stats.highscores.drill || 0)) {
        newStats.highscores = { ...stats.highscores, drill: finalScore };
        isNewHi = true;
      }
    }
    setStats(newStats);
    saveStats(newStats);
    window.__lastIsNewHi = isNewHi;
    setView('end');
  };

  const replay = () => pickAndStart(mode, decade);
  const goHome = () => setView('home');

  // Render
  if (view === 'loading') {
    return (
      <SPLoading
        error={loadError}
        onRetry={loadDeck}
        onBack={onExit}
      />
    );
  }
  if (view === 'home') return (
    <SPHome
      stats={stats}
      deckCount={deck.length}
      onMode={startMode}
      onStats={() => setView('highscores')}
      onExit={onExit}
      fullMode={fullMode}
      hasSpotifyToken={!!spotifyToken}
      onToggleFull={toggleFullMode}
      onConnectSpotify={connectSpotify}
      configReady={!!config?.spotifyClientId}
    />
  );
  if (view === 'decade') return (
    <SPDecade deck={deck} onPick={(d) => pickAndStart('drill', d)} onBack={() => setView('home')} />
  );
  if (view === 'intro') return (
    <SPIntro
      mode={mode} decade={decade}
      best={stats.highscores[mode] || 0}
      onContinue={() => setView('round-intro')}
    />
  );
  if (view === 'round-intro') return (
    <SPRoundIntro round={round} total={total} onContinue={() => setView('guess')} />
  );
  if (view === 'guess' && currentSong) return (
    <SPGuess
      round={round} total={total} mode={mode}
      lives={lives} score={score}
      year={year} setYear={setYear}
      song={currentSong}
      useFullSongs={useFullSongs}
      playFull={playFull}
      pauseFull={pauseFull}
      onLock={lockGuess}
      onTimeout={handleTimeout}
    />
  );
  if (view === 'reveal' && currentSong) return (
    <SPReveal
      year={year} song={currentSong}
      mode={mode} lives={lives} score={score} streak={streak}
      onContinue={finishReveal}
    />
  );
  if (view === 'end') return (
    <SPEnd
      mode={mode}
      score={score}
      rounds={history.length}
      best={stats.highscores[mode] || 0}
      isNewHi={!!window.__lastIsNewHi}
      stats={stats}
      onShare={mode === 'daily' ? () => setView('share') : null}
      onReplay={replay}
      onHome={goHome}
    />
  );
  if (view === 'highscores') return (
    <SPHighscores stats={stats} onBack={() => setView('home')} />
  );
  if (view === 'share') return (
    <SPShare score={score} history={history}
      onBack={() => setView('end')}
      onHome={goHome} />
  );

  return <SPLoading error="Something went sideways." onRetry={() => setView('home')} onBack={onExit} />;
}

window.SoloApp = SoloApp;
})();
