// Roast Battle v2 — leaner screens.
// Cuts: mode picker, topic picker, separate winner, separate inbox, trophy case,
// rank / streak / win%, onboard rank picker. Merges: handle+avatar into one onboarding,
// friends + inbox into Home, reveal+result into one screen.
// 7 screens total: splash, onboard, friends, home, write, reveal, profile.

const FRIEND2 = (id) => window.FRIENDS.find(f => f.id === id);

// ───────────────────────────────────────────────────────────────
// 1. SPLASH (same as v1, just trimmed copy + single CTA)
// ───────────────────────────────────────────────────────────────
function V2Splash({ t, go }) {
  return (
    <Screen t={t} scroll={false} padTop={0} padBottom={0}>
      <Halftone color={t.halftone} opacity={0.12} />
      <div style={{
        position: 'absolute', inset: 0,
        display: 'flex', flexDirection: 'column',
        justifyContent: 'center', alignItems: 'center',
        padding: '0 18px',
      }}>
        <Sticker t={t} color={t.accent} ink={t.accentInk} tilt={-6} style={{ marginBottom: 14, fontSize: 13 }}>
          friends only ✦ no losers
        </Sticker>

        <div style={{ position: 'relative', textAlign: 'center', lineHeight: 0.85 }}>
          <div style={{
            fontFamily: 'Bungee, system-ui', fontSize: 84,
            color: t.ink, transform: 'rotate(-2deg)', letterSpacing: -1,
            WebkitTextStroke: `2px ${t.ink}`,
            textShadow: `5px 5px 0 ${t.pop}`,
          }}>ROAST</div>
          <div style={{
            fontFamily: 'Bungee, system-ui', fontSize: 84,
            color: t.pop, transform: 'rotate(2deg) translateY(-6px)', letterSpacing: -1,
            WebkitTextStroke: `2px ${t.ink}`,
            textShadow: `5px 5px 0 ${t.ink}`,
          }}>BATTLE</div>
          <Sparkle size={36} color={t.sun} style={{ position: 'absolute', top: -18, right: -4 }}/>
          <Sparkle size={22} color={t.accent} style={{ position: 'absolute', bottom: 30, left: -10 }}/>
        </div>

        <div style={{
          marginTop: 28, fontFamily: 'Instrument Serif, serif', fontStyle: 'italic',
          fontSize: 22, color: t.ink, textAlign: 'center', maxWidth: 280, lineHeight: 1.15,
        }}>say it to their face<br/>(from across the country, like a coward)</div>

        <div style={{ marginTop: 36 }}>
          <ChunkyButton t={t} onClick={() => go('onboard')} color={t.pop} ink={t.popInk}>
            ENTER THE PIT →
          </ChunkyButton>
        </div>
      </div>
    </Screen>
  );
}

// ───────────────────────────────────────────────────────────────
// 2. ONBOARD — handle + avatar in ONE step
// ───────────────────────────────────────────────────────────────
function V2Onboard({ t, go, state, setState }) {
  const VIBES = ['🐀', '🍒', '🪩', '🦴', '🥀', '🐛', '🦷', '🧃', '👁', '🤡', '🪦', '🥩'];
  const SUGGESTIONS = ['rat.king', 'lil.menace', 'gum.disease', 'shrimp', 'op.shop'];

  const profile = window.useProfile();
  const [saving, setSaving] = React.useState(false);
  const [error, setError] = React.useState(null);

  // If user already onboarded, prefill from their saved row.
  React.useEffect(() => {
    if (profile.ready && profile.row && !state.fromProfile) {
      setState({ handle: profile.row.handle, avatar: profile.row.avatar, fromProfile: true });
    }
  }, [profile.ready, profile.row]);

  const onNext = async () => {
    setError(null);
    setSaving(true);
    try {
      await window.upsertMyProfile({ handle: state.handle, avatar: state.avatar });
      go('friends');
    } catch (e) {
      setError(e.message || String(e));
    } finally {
      setSaving(false);
    }
  };

  const onHandleInput = (raw) => {
    // Match the schema: lowercase, [a-z0-9._], up to 20 chars. Clear error.
    const cleaned = String(raw || '').toLowerCase().replace(/[^a-z0-9._]/g, '').slice(0, 20);
    setState({ ...state, handle: cleaned });
    if (error) setError(null);
  };

  return (
    <Screen t={t} padTop={56}>
      <TopBar t={t} onBack={() => go('splash')} title="1 / 2" />
      <div style={{ padding: '6px 22px 0' }}>
        <div style={{ fontFamily: 'Bungee', fontSize: 30, lineHeight: 1, color: t.ink, letterSpacing: -0.5 }}>
          who are <span style={{ color: t.pop }}>u</span>?
        </div>
        <div style={{ fontFamily: 'Instrument Serif', fontStyle: 'italic', fontSize: 17, color: t.sub, marginTop: 8 }}>
          ur friends find u by this. they'll see it every time u cook them.
        </div>

        {/* preview */}
        <div style={{ marginTop: 22, display: 'flex', justifyContent: 'center' }}>
          <div style={{ position: 'relative' }}>
            <StarBurst size={120} color={t.sun} points={20} />
            <div style={{
              position: 'absolute', inset: 0, display: 'flex',
              alignItems: 'center', justifyContent: 'center',
              fontSize: 56,
            }}>{state.avatar || '🐀'}</div>
          </div>
        </div>
        <div style={{
          textAlign: 'center', marginTop: 8,
          fontFamily: 'JetBrains Mono', fontSize: 18, fontWeight: 700,
          color: state.handle ? t.ink : t.sub,
          opacity: state.handle ? 1 : 0.5,
        }}>@{state.handle || 'ur.handle'}</div>

        {/* avatar grid */}
        <div style={{
          marginTop: 18, display: 'grid', gridTemplateColumns: 'repeat(6, 1fr)', gap: 8,
        }}>
          {VIBES.map(v => (
            <button key={v} onClick={() => setState({ ...state, avatar: v })} style={{
              appearance: 'none', cursor: 'pointer',
              aspectRatio: '1', borderRadius: 12, minHeight: 44,
              background: state.avatar === v ? t.pop : t.paper,
              border: `2px solid ${t.rule}`,
              fontSize: 22, display: 'flex', alignItems: 'center', justifyContent: 'center',
            }}>{v}</button>
          ))}
        </div>

        {/* handle */}
        <div style={{ marginTop: 18 }}>
          <PaperCard t={t} style={{ padding: 14 }}>
            <div style={{ fontFamily: 'Bungee', fontSize: 10, color: t.sub, letterSpacing: 1, marginBottom: 6 }}>HANDLE</div>
            <div style={{ display: 'flex', alignItems: 'baseline', gap: 2 }}>
              <span style={{ color: t.sub, fontFamily: 'JetBrains Mono', fontSize: 22 }}>@</span>
              <input
                value={state.handle || ''}
                onChange={e => onHandleInput(e.target.value)}
                placeholder="ur.handle"
                spellCheck={false}
                autoCapitalize="none"
                autoCorrect="off"
                style={{
                  flex: 1, minWidth: 0,
                  appearance: 'none', border: 'none', outline: 'none',
                  background: 'transparent', color: t.ink,
                  fontFamily: 'JetBrains Mono', fontSize: 22, fontWeight: 700,
                  padding: 0,
                }}
              />
            </div>
            <div style={{ display: 'flex', gap: 6, marginTop: 10, flexWrap: 'wrap' }}>
              {SUGGESTIONS.map(s => (
                <button key={s} onClick={() => onHandleInput(s)} style={{
                  appearance: 'none', cursor: 'pointer',
                  fontFamily: 'JetBrains Mono', fontSize: 11,
                  padding: '6px 10px', borderRadius: 999,
                  background: state.handle === s ? t.ink : 'transparent',
                  color: state.handle === s ? t.bg : t.ink,
                  border: `1.5px solid ${t.rule}`,
                }}>@{s}</button>
              ))}
            </div>
          </PaperCard>
        </div>

        {error && (
          <div style={{
            marginTop: 14, padding: '10px 14px', borderRadius: 12,
            background: t.pop, color: t.popInk,
            fontFamily: 'Space Grotesk', fontSize: 13, fontWeight: 500,
            border: `2px solid ${t.rule}`,
          }}>{error}</div>
        )}

        <div style={{ marginTop: 28, display: 'flex', justifyContent: 'flex-end' }}>
          <ChunkyButton t={t} onClick={onNext}>
            {saving ? 'SAVING…' : 'NEXT →'}
          </ChunkyButton>
        </div>
      </div>
    </Screen>
  );
}

// ───────────────────────────────────────────────────────────────
// 3. ADD FRIENDS
// ───────────────────────────────────────────────────────────────
function V2Friends({ t, go, setTab }) {
  const profile = window.useProfile();
  const friends = window.useFriends();
  const [query, setQuery] = React.useState('');
  const [searching, setSearching] = React.useState(false);
  const [match, setMatch] = React.useState(null);    // user found by search
  const [info, setInfo] = React.useState(null);      // status message
  const [error, setError] = React.useState(null);
  const [shareLabel, setShareLabel] = React.useState('SHARE MY HANDLE');
  const [removingId, setRemovingId] = React.useState(null);

  // Returning users (profile already saved) reach this screen via the
  // TabBar — they get a Home back-arrow and a tab bar. New users in the
  // 2/2 onboarding flow get the original back-to-Onboard chevron.
  const fromOnboarding = !profile.row || !profile.row.handle;

  const cleanedQuery = query.trim().toLowerCase().replace(/^@+/, '');

  const searchHandle = async (handle) => {
    setError(null); setInfo(null); setMatch(null);
    const cleaned = String(handle || '').trim().toLowerCase().replace(/^@+/, '');
    if (!cleaned) return;
    setSearching(true);
    try {
      const found = await window.searchUserByHandle(cleaned);
      if (!found) {
        setInfo("nobody's got that handle yet.");
      } else if (found.id === (profile.row && profile.row.id)) {
        setInfo("that's u.");
      } else if (friends.list.some(f => f.id === found.id)) {
        setMatch(found);
        setInfo('already in your squad.');
      } else {
        setMatch(found);
      }
    } catch (e) {
      setError(e.message || String(e));
    } finally {
      setSearching(false);
    }
  };

  const onSearch = () => searchHandle(cleanedQuery);

  // Share-link consumer (P1.18): if a pending @handle is sitting in
  // sessionStorage from a ?add= deep-link, prefill the search and run it
  // once the friends list has rehydrated. Cleared after the first run so
  // it doesn't replay on navigation.
  React.useEffect(() => {
    if (!friends.ready) return;
    try {
      const pending = sessionStorage.getItem('rb_pending_add');
      if (pending) {
        sessionStorage.removeItem('rb_pending_add');
        setQuery(pending);
        searchHandle(pending);
      }
    } catch (e) {}
  }, [friends.ready]);

  const onRemove = async (friend) => {
    if (removingId) return;
    setError(null); setInfo(null);
    setRemovingId(friend.id);
    try {
      await window.removeFriend(friend.id);
      setInfo(`removed @${friend.handle}.`);
    } catch (e) {
      setError(e.message || String(e));
    } finally {
      setRemovingId(null);
    }
  };

  const onShare = async () => {
    const me = profile.row;
    if (!me) return;
    const url = `${location.origin}${location.pathname}?add=${encodeURIComponent(me.handle)}`;
    try {
      if (navigator.share) {
        await navigator.share({ title: 'roast me', text: `add @${me.handle} on roast battle`, url });
        setShareLabel('SHARED ✓');
      } else if (navigator.clipboard) {
        await navigator.clipboard.writeText(url);
        setShareLabel('COPIED ✓');
      } else {
        // Last resort: legacy fallback. Shouldn't trigger on any modern browser.
        const ta = document.createElement('textarea');
        ta.value = url; document.body.appendChild(ta); ta.select();
        document.execCommand('copy'); ta.remove();
        setShareLabel('COPIED ✓');
      }
      setTimeout(() => setShareLabel('SHARE MY HANDLE'), 1800);
    } catch (e) {
      // User aborted the share sheet — silent.
    }
  };

  const onAdd = async (handle) => {
    setError(null); setInfo(null);
    try {
      const them = await window.addFriendByHandle(handle);
      setInfo(`added @${them.handle}.`);
      setMatch(null);
      setQuery('');
    } catch (e) {
      setError(e.message || String(e));
    }
  };

  return (
    <Screen t={t} padTop={56} padBottom={fromOnboarding ? 42 : 110}>
      <TopBar t={t}
        onBack={() => go(fromOnboarding ? 'onboard' : 'home')}
        title={fromOnboarding ? '2 / 2' : null} />
      <div style={{ padding: '6px 22px 0' }}>
        <div style={{ fontFamily: 'Bungee', fontSize: 30, lineHeight: 1, color: t.ink, letterSpacing: -0.5 }}>
          add some <span style={{ color: t.pop }}>victims</span>.
        </div>
        <div style={{ fontFamily: 'Instrument Serif', fontStyle: 'italic', fontSize: 17, color: t.sub, marginTop: 8 }}>
          can't roast alone. that's just journaling.
        </div>

        {/* share-my-handle (the inverse of search) */}
        {profile.row && (
          <div style={{ marginTop: 14 }}>
            <button onClick={onShare} style={{
              appearance: 'none', cursor: 'pointer', width: '100%',
              padding: '10px 14px', borderRadius: 14,
              background: t.accent, color: t.accentInk,
              border: `2px solid ${t.rule}`,
              boxShadow: `3px 3px 0 0 ${t.shadow}`,
              fontFamily: 'Bungee', fontSize: 12, letterSpacing: 0.6,
              display: 'flex', alignItems: 'center', justifyContent: 'space-between',
              gap: 8, minHeight: 44,
            }}>
              <span>{shareLabel}</span>
              <span style={{ fontFamily: 'JetBrains Mono', fontSize: 11, opacity: 0.8 }}>@{profile.row.handle}</span>
            </button>
          </div>
        )}

        {/* search input */}
        <div style={{ marginTop: 14 }}>
          <PaperCard t={t} style={{ padding: 12 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
              <span style={{ fontFamily: 'JetBrains Mono', fontSize: 18, color: t.sub }}>@</span>
              <input
                value={query}
                onChange={e => { setQuery(e.target.value); setMatch(null); setInfo(null); setError(null); }}
                onKeyDown={e => { if (e.key === 'Enter') onSearch(); }}
                placeholder="their.handle"
                spellCheck={false}
                autoCapitalize="none"
                autoCorrect="off"
                style={{
                  flex: 1, minWidth: 0,
                  appearance: 'none', border: 'none', outline: 'none',
                  background: 'transparent', color: t.ink,
                  fontFamily: 'JetBrains Mono', fontSize: 17, fontWeight: 700,
                  padding: '4px 0',
                }}
              />
              <button onClick={onSearch} disabled={!cleanedQuery || searching} style={{
                appearance: 'none', cursor: cleanedQuery && !searching ? 'pointer' : 'default',
                opacity: cleanedQuery && !searching ? 1 : 0.4,
                fontFamily: 'Bungee', fontSize: 11, letterSpacing: 0.5,
                padding: '8px 12px', borderRadius: 999,
                background: t.ink, color: t.bg, border: 'none',
                minHeight: 44,
              }}>{searching ? '…' : 'FIND'}</button>
            </div>
          </PaperCard>
        </div>

        {/* search result */}
        {match && (
          <div style={{
            marginTop: 10, display: 'flex', alignItems: 'center', gap: 12,
            padding: '10px 12px', borderRadius: 14,
            background: t.paper, border: `2px solid ${t.rule}`,
            boxShadow: `3px 3px 0 0 ${t.shadow}`,
          }}>
            <Avatar person={match} t={t} size={42} ring={t.sun} />
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontFamily: 'JetBrains Mono', fontSize: 14, fontWeight: 700 }}>@{match.handle}</div>
            </div>
            <button onClick={() => onAdd(match.handle)} disabled={friends.list.some(f => f.id === match.id)} style={{
              appearance: 'none',
              cursor: friends.list.some(f => f.id === match.id) ? 'default' : 'pointer',
              opacity: friends.list.some(f => f.id === match.id) ? 0.5 : 1,
              fontFamily: 'Bungee', fontSize: 11, letterSpacing: 0.5,
              padding: '10px 14px', borderRadius: 999,
              background: t.accent, color: t.accentInk,
              border: `2px solid ${t.rule}`, minHeight: 44,
            }}>ADD</button>
          </div>
        )}

        {info && (
          <div style={{
            marginTop: 10, padding: '8px 12px', borderRadius: 10,
            fontFamily: 'Space Grotesk', fontSize: 13, color: t.sub,
          }}>{info}</div>
        )}
        {error && (
          <div style={{
            marginTop: 10, padding: '10px 14px', borderRadius: 12,
            background: t.pop, color: t.popInk,
            fontFamily: 'Space Grotesk', fontSize: 13, fontWeight: 500,
            border: `2px solid ${t.rule}`,
          }}>{error}</div>
        )}

        {/* current squad */}
        <div style={{ marginTop: 22 }}>
          <div style={{ fontFamily: 'Bungee', fontSize: 12, letterSpacing: 1, color: t.ink, marginBottom: 10 }}>
            YOUR SQUAD · {friends.list.length}
          </div>
          {!friends.ready ? (
            <div style={{ fontFamily: 'Space Grotesk', fontSize: 13, color: t.sub }}>loading…</div>
          ) : friends.list.length === 0 ? (
            <div style={{ fontFamily: 'Instrument Serif', fontStyle: 'italic', fontSize: 14, color: t.sub }}>
              empty. find someone.
            </div>
          ) : (
            <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
              {friends.list.map(f => {
                const isRemoving = removingId === f.id;
                return (
                  <div key={f.id} style={{
                    display: 'flex', alignItems: 'center', gap: 12,
                    padding: '8px 12px', borderRadius: 14,
                    background: t.paper, border: `2px solid ${t.rule}`,
                    opacity: isRemoving ? 0.5 : 1,
                    transition: 'opacity 120ms',
                  }}>
                    <Avatar person={f} t={t} size={38} ring={t.sun} />
                    <div style={{ flex: 1, fontFamily: 'JetBrains Mono', fontSize: 14, fontWeight: 700 }}>@{f.handle}</div>
                    <button
                      onClick={() => onRemove(f)}
                      disabled={isRemoving}
                      aria-label={`remove @${f.handle}`}
                      style={{
                        appearance: 'none',
                        cursor: isRemoving ? 'default' : 'pointer',
                        width: 32, height: 32, borderRadius: '50%',
                        background: 'transparent',
                        color: t.sub,
                        border: `1.5px solid ${t.rule}`,
                        fontSize: 14, lineHeight: 1, padding: 0,
                        display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                      }}
                    >✕</button>
                  </div>
                );
              })}
            </div>
          )}
        </div>

        {fromOnboarding && (
          <div style={{ marginTop: 28, display: 'flex', justifyContent: 'flex-end' }}>
            <ChunkyButton t={t} onClick={() => go('home')}>
              {friends.list.length > 0 ? "LET'S GO →" : 'SKIP →'}
            </ChunkyButton>
          </div>
        )}
      </div>

      {!fromOnboarding && <TabBar t={t} current="friends" go={setTab} />}
    </Screen>
  );
}

// ───────────────────────────────────────────────────────────────
// 4. HOME — friends list with active battles inline
// One screen: pending replies up top (the only "inbox" surface),
// then friend grid you tap to start a battle.
// ───────────────────────────────────────────────────────────────
function V2Home({ t, go, setTab }) {
  const profile = window.useProfile();
  const friends = window.useFriends();
  const pendingBattles = window.usePendingBattles();
  const me = profile.row;

  // Time-ago helper for the pending callout list. Keeps it lowercase, terse.
  const ago = (iso) => {
    const ms = Date.now() - new Date(iso).getTime();
    const s = Math.floor(ms / 1000);
    if (s < 60) return `${s}s`;
    const m = Math.floor(s / 60);
    if (m < 60) return `${m}m`;
    const h = Math.floor(m / 60);
    if (h < 24) return `${h}h`;
    return `${Math.floor(h / 24)}d`;
  };

  return (
    <Screen t={t} padTop={56} padBottom={110}>
      <div style={{ padding: '0 18px 6px', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
        <div>
          <div style={{ fontFamily: 'Space Grotesk', fontSize: 12, color: t.sub }}>logged in as</div>
          <div style={{ fontFamily: 'Bungee', fontSize: 22, color: t.ink, letterSpacing: -0.3 }}>
            @{me?.handle || '…'}
          </div>
        </div>
        <Avatar person={me || { avatar: '🐀' }} t={t} size={44} ring={t.pop}/>
      </div>

      {/* pending callouts — the only urgent surface on Home */}
      {pendingBattles.list.length > 0 && (
        <div style={{ padding: '10px 18px 0' }}>
          <div style={{ fontFamily: 'Bungee', fontSize: 12, letterSpacing: 1, color: t.ink, marginBottom: 8 }}>
            UR TURN · {pendingBattles.list.length}
          </div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
            {pendingBattles.list.map((b, i) => {
              const f = b.challenger || { avatar: '👤', handle: '…' };
              return (
                <div key={b.id}
                     onClick={() => go('write', { battle_id: b.id, opp: f.id })}
                     style={{
                       display: 'flex', alignItems: 'center', gap: 12,
                       padding: '12px 14px', borderRadius: 18,
                       background: t.pop, color: t.popInk,
                       border: `2px solid ${t.rule}`,
                       boxShadow: `3px 3px 0 0 ${t.shadow}`,
                       cursor: 'pointer',
                       transform: `rotate(${i % 2 ? -0.6 : 0.6}deg)`,
                     }}>
                  <Avatar person={f} t={t} size={40} ring={t.sun}/>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontFamily: 'JetBrains Mono', fontSize: 12, fontWeight: 700 }}>@{f.handle}</div>
                    <div style={{ fontFamily: 'Space Grotesk', fontSize: 13, marginTop: 2 }}>
                      called u out
                    </div>
                  </div>
                  <div style={{ fontFamily: 'Bungee', fontSize: 11 }}>{ago(b.created_at)}</div>
                </div>
              );
            })}
          </div>
        </div>
      )}

      {/* friend grid — primary surface */}
      <div style={{ padding: '20px 18px 0' }}>
        <div style={{ display: 'flex', alignItems: 'baseline', gap: 6, marginBottom: 10 }}>
          <div style={{ fontFamily: 'Bungee', fontSize: 12, letterSpacing: 1, color: t.ink }}>SQUAD</div>
          <div style={{ marginLeft: 'auto', fontFamily: 'Instrument Serif', fontStyle: 'italic', fontSize: 13, color: t.sub }}>
            {friends.list.length > 0 ? 'tap to cook →' : 'no one to cook'}
          </div>
        </div>

        {!friends.ready ? (
          <div style={{ fontFamily: 'Space Grotesk', fontSize: 13, color: t.sub, padding: '12px 0' }}>loading…</div>
        ) : friends.list.length === 0 ? (
          <div style={{
            padding: '24px 18px', borderRadius: 18,
            background: t.paper, border: `2px dashed ${t.rule}`, textAlign: 'center',
          }}>
            <div style={{ fontSize: 36, marginBottom: 8 }}>🦗</div>
            <div style={{ fontFamily: 'Instrument Serif', fontStyle: 'italic', fontSize: 15, color: t.ink }}>
              add a victim first.
            </div>
            <div style={{ marginTop: 12 }}>
              <ChunkyButton t={t} small onClick={() => go('friends')}>ADD FRIENDS →</ChunkyButton>
            </div>
          </div>
        ) : (
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 10 }}>
            {friends.list.slice(0, 9).map((f, i) => (
              <div key={f.id} onClick={() => go('write', { opp: f.id, battle_id: null })} style={{
                position: 'relative', cursor: 'pointer',
                background: t.paper, border: `2px solid ${t.rule}`,
                borderRadius: 18, padding: '14px 8px', minHeight: 96,
                boxShadow: `3px 3px 0 0 ${t.shadow}`,
                transform: `rotate(${(i % 3 - 1) * 1.2}deg)`,
                textAlign: 'center',
              }}>
                <div style={{ fontSize: 36 }}>{f.avatar}</div>
                <div style={{ fontFamily: 'JetBrains Mono', fontSize: 11, marginTop: 6, color: t.ink, fontWeight: 700 }}>
                  @{f.handle.split('.')[0]}
                </div>
              </div>
            ))}
          </div>
        )}
      </div>

      <TabBar t={t} current="home" go={setTab} />
    </Screen>
  );
}

// ───────────────────────────────────────────────────────────────
// 5. WRITE — no topic picker, no timer pressure. Just write & send.
// ───────────────────────────────────────────────────────────────
function V2Write({ t, go, ctx }) {
  // Two modes:
  // - ctx.battle_id missing  → fresh challenge: insert battle + first roast
  // - ctx.battle_id present  → response: insert roast only; trigger flips
  //                            status pending→half→complete
  const isResponse = !!ctx.battle_id;

  const [opp, setOpp] = React.useState(null);
  React.useEffect(() => {
    let cancelled = false;
    (async () => {
      if (!ctx.opp) { setOpp(null); return; }
      try {
        const u = await window.getUserById(ctx.opp);
        if (cancelled) return;
        setOpp(u || FRIEND2(ctx.opp) || null);
      } catch (e) {
        if (!cancelled) setOpp(FRIEND2(ctx.opp) || null);
      }
    })();
    return () => { cancelled = true; };
  }, [ctx.opp]);

  // Draft persistence (P1.24): unique key per (battle_id || 'fresh') + opp_id
  // so a fresh challenge draft doesn't collide with a hit-back draft.
  const draftKey = React.useMemo(() => {
    const slot = ctx.battle_id || 'fresh';
    return `rb_draft:${slot}:${ctx.opp || 'none'}`;
  }, [ctx.battle_id, ctx.opp]);

  const [text, setText] = React.useState(() => {
    try { return localStorage.getItem(draftKey) || ''; } catch (e) { return ''; }
  });
  const [sending, setSending] = React.useState(false);
  const [error, setError] = React.useState(null);
  const limit = 180;
  const used = text.length;
  const pct = Math.min(1, used / limit);

  // Re-read the draft when the key changes (different battle/opponent).
  React.useEffect(() => {
    try { setText(localStorage.getItem(draftKey) || ''); } catch (e) {}
  }, [draftKey]);

  // Persist on every change. Tiny payload, no debounce needed.
  React.useEffect(() => {
    try {
      if (text) localStorage.setItem(draftKey, text);
      else localStorage.removeItem(draftKey);
    } catch (e) {}
  }, [text, draftKey]);

  // In response mode, load their roast so the user can see what they're
  // hitting back at. Fresh challenges don't have an opponent roast yet,
  // so this no-ops.
  const [theirRoast, setTheirRoast] = React.useState(null);
  React.useEffect(() => {
    if (!isResponse || !ctx.battle_id) { setTheirRoast(null); return; }
    let cancelled = false;
    (async () => {
      try {
        const roasts = await window.getBattleRoasts(ctx.battle_id);
        const myId = window.IDENTITY && window.IDENTITY.uid;
        const theirs = roasts.find(r => r.author_id !== myId);
        if (!cancelled) setTheirRoast(theirs || null);
      } catch (e) {
        // Don't surface — the response flow still works without the preview.
        console.warn('[rb] failed to load their roast', e);
      }
    })();
    return () => { cancelled = true; };
  }, [isResponse, ctx.battle_id]);

  const onSend = async () => {
    if (sending) return;
    const trimmed = text.trim();
    if (trimmed.length < 1) { setError('say something at least'); return; }
    setError(null);
    setSending(true);
    try {
      let battleId = ctx.battle_id;
      if (!isResponse) {
        const battle = await window.createBattle(ctx.opp);
        battleId = battle.id;
      }
      await window.submitRoast(battleId, trimmed);
      try { localStorage.removeItem(draftKey); } catch (e) {}
      go('reveal', { ...ctx, battle_id: battleId });
    } catch (e) {
      setError(e.message || String(e));
    } finally {
      setSending(false);
    }
  };

  return (
    <Screen t={t} padTop={56} scroll={false}>
      <TopBar t={t} onBack={() => go('home')} title={isResponse ? 'HIT BACK' : 'ROAST'} />

      {/* who you're cooking */}
      <div style={{ padding: '0 18px 14px' }}>
        <div style={{
          display: 'flex', alignItems: 'center', gap: 12,
          background: t.paper, border: `2px solid ${t.rule}`, borderRadius: 18,
          padding: '10px 14px', boxShadow: `3px 3px 0 0 ${t.shadow}`,
        }}>
          <Avatar person={opp || { avatar: '👤' }} t={t} size={44} ring={t.accent}/>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontFamily: 'Space Grotesk', fontSize: 12, color: t.sub }}>
              {isResponse ? 'cooking u back' : 'cooking'}
            </div>
            <div style={{ fontFamily: 'JetBrains Mono', fontSize: 15, fontWeight: 700 }}>
              @{opp?.handle || '…'}
            </div>
          </div>
        </div>
      </div>

      {/* their shot — only shown in response mode */}
      {isResponse && theirRoast && (
        <div style={{ padding: '0 18px 14px' }}>
          <PaperCard t={t} color={t.accent} style={{ padding: '14px 16px', position: 'relative' }}>
            <Tape color={t.pop} tilt={-5} w={70} top={-12} left={14}/>
            <div style={{
              fontFamily: 'Bungee', fontSize: 10, color: t.accentInk,
              opacity: 0.7, letterSpacing: 1, marginBottom: 6,
            }}>THEY SAID</div>
            <div style={{
              fontFamily: 'Space Grotesk, system-ui',
              fontSize: 16, fontWeight: 500, lineHeight: 1.4,
              letterSpacing: -0.1,
              color: t.accentInk,
              textWrap: 'pretty',
            }}>{theirRoast.text}</div>
          </PaperCard>
        </div>
      )}

      {/* the notepad */}
      <div style={{ padding: '0 18px', flex: 1 }}>
        <div style={{
          position: 'relative',
          background: t.paper, border: `2px solid ${t.rule}`, borderRadius: 20,
          boxShadow: `4px 4px 0 0 ${t.shadow}`,
          padding: '22px 18px 14px',
        }}>
          <Tape color={t.sun} tilt={-6} w={120} top={-12} left={20}/>
          <Tape color={t.pop} tilt={8} w={60} top={-10} left={'70%'} opacity={0.85}/>

          {/* big decorative quote glyph */}
          <div style={{
            position: 'absolute', top: 6, right: 16,
            fontFamily: 'Bungee, system-ui', fontSize: 56,
            color: t.pop, opacity: 0.18, lineHeight: 1,
            pointerEvents: 'none', userSelect: 'none',
          }}>“</div>

          <textarea
            value={text}
            onChange={e => setText(e.target.value.slice(0, limit))}
            placeholder="say something insane (with love)…"
            style={{
              width: '100%', minHeight: 200,
              border: 'none', outline: 'none', resize: 'none',
              background: 'transparent', color: t.ink,
              fontFamily: 'Space Grotesk, system-ui',
              fontSize: 19, fontWeight: 500, lineHeight: 1.4,
              letterSpacing: -0.1,
              position: 'relative', zIndex: 1,
            }}
          />

          <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 8, position: 'relative', zIndex: 1 }}>
            <div style={{
              fontFamily: 'JetBrains Mono', fontSize: 11,
              color: used > limit * 0.85 ? t.pop : t.sub,
            }}>{used} / {limit}</div>
            <div style={{ flex: 1, height: 3, background: t.rule + '22', borderRadius: 2, overflow: 'hidden' }}>
              <div style={{ width: `${pct * 100}%`, height: '100%', background: pct > 0.85 ? t.pop : t.ink }} />
            </div>
          </div>
        </div>
      </div>

      {error && (
        <div style={{
          margin: '0 18px 8px', padding: '10px 14px', borderRadius: 12,
          background: t.pop, color: t.popInk,
          fontFamily: 'Space Grotesk', fontSize: 13, fontWeight: 500,
          border: `2px solid ${t.rule}`,
        }}>{error}</div>
      )}

      {/* send dock */}
      <div style={{ padding: '16px 18px 28px', display: 'flex', gap: 10, alignItems: 'center' }}>
        <button onClick={() => setText('')} disabled={sending} style={{
          appearance: 'none', cursor: sending ? 'default' : 'pointer',
          width: 48, height: 48, borderRadius: '50%',
          background: t.paper, border: `2px solid ${t.rule}`,
          boxShadow: `2px 2px 0 0 ${t.shadow}`,
          fontSize: 18, opacity: sending ? 0.5 : 1,
        }}>🗑</button>
        <ChunkyButton t={t} onClick={onSend} full style={{ flex: 1 }}>
          {sending ? 'SENDING…' : 'SEND IT 💀'}
        </ChunkyButton>
      </div>
    </Screen>
  );
}

// ───────────────────────────────────────────────────────────────
// 6a. REACTIONS ROW — used inside V2Reveal, lives here because it
// needs the battle_id / myId / animation phase that Reveal owns.
// ───────────────────────────────────────────────────────────────
function V2ReactionsRow({ t, battleId, myId, phase }) {
  const reactions = window.useReactions(battleId);
  const [busy, setBusy] = React.useState(false);
  const emojis = window.REACTION_EMOJIS || ['🔥', '💀', '👀', '✨'];

  // Tally: { emoji: count }
  const counts = React.useMemo(() => {
    const c = Object.fromEntries(emojis.map(e => [e, 0]));
    for (const r of reactions.list) {
      if (c[r.emoji] !== undefined) c[r.emoji] += 1;
    }
    return c;
  }, [reactions.list]);

  const myEmoji = reactions.list.find(r => r.user_id === myId)?.emoji || null;

  const onTap = async (emoji) => {
    if (busy) return;
    setBusy(true);
    try {
      await window.setMyReaction(battleId, emoji);
    } catch (e) {
      // RLS will reject if the user can't see / react to this battle.
      // Most common cause: trying to react before battle.status flips
      // to 'complete'. Silently log — there's no good place to surface.
      console.warn('[rb] reaction failed', e);
    } finally {
      setBusy(false);
    }
  };

  return (
    <div style={{
      marginTop: 22,
      opacity: phase >= 2 ? 1 : 0,
      transform: phase >= 2 ? 'translateY(0)' : 'translateY(20px)',
      transition: 'all 400ms cubic-bezier(.2,.8,.2,1)',
    }}>
      <div style={{
        fontFamily: 'Bungee', fontSize: 11, letterSpacing: 1,
        marginBottom: 10, textAlign: 'center', color: t.sub,
      }}>
        REACT
      </div>
      <div style={{ display: 'flex', justifyContent: 'center', gap: 10 }}>
        {emojis.map(e => {
          const isMine = myEmoji === e;
          return (
            <button key={e} onClick={() => onTap(e)} disabled={busy} style={{
              appearance: 'none', cursor: busy ? 'default' : 'pointer',
              minWidth: 56, minHeight: 44,
              padding: '8px 12px', borderRadius: 999,
              background: isMine ? t.pop : t.paper,
              color: isMine ? t.popInk : t.ink,
              border: `2px solid ${t.rule}`,
              boxShadow: isMine ? 'none' : `2px 2px 0 0 ${t.shadow}`,
              transform: isMine ? 'translate(2px, 2px)' : 'none',
              transition: 'transform 80ms, box-shadow 80ms',
              fontFamily: 'JetBrains Mono', fontSize: 13, fontWeight: 700,
              display: 'inline-flex', alignItems: 'center', gap: 6,
            }}>
              <span style={{ fontSize: 18 }}>{e}</span>
              <span>{counts[e] || 0}</span>
            </button>
          );
        })}
      </div>

      {/* who reacted — small avatar strip with the emoji as a corner badge */}
      {reactions.list.length > 0 && (
        <div style={{
          display: 'flex', flexWrap: 'wrap', justifyContent: 'center',
          gap: 12, marginTop: 12,
        }}>
          {reactions.list.map(r => {
            const user = r.user || { avatar: '👤', handle: '…' };
            return (
              <div key={r.user_id}
                   title={`@${user.handle} · ${r.emoji}`}
                   style={{ position: 'relative', display: 'inline-flex', flexDirection: 'column', alignItems: 'center', gap: 4 }}>
                <div style={{ position: 'relative' }}>
                  <Avatar person={user} t={t} size={32} ring={t.paper}/>
                  <div style={{
                    position: 'absolute', bottom: -3, right: -6,
                    fontSize: 14, lineHeight: 1,
                    background: t.paper, borderRadius: '50%',
                    border: `1.5px solid ${t.rule}`,
                    width: 20, height: 20,
                    display: 'flex', alignItems: 'center', justifyContent: 'center',
                  }}>{r.emoji}</div>
                </div>
                <div style={{ fontFamily: 'JetBrains Mono', fontSize: 9, color: t.sub }}>
                  @{(user.handle || '').split('.')[0]}
                </div>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

// ───────────────────────────────────────────────────────────────
// 6. REVEAL — both shots side by side, friend reactions inline.
// No separate "winner" screen — result lives here.
// ───────────────────────────────────────────────────────────────
function V2Reveal({ t, go, ctx }) {
  const profile = window.useProfile();
  const me = profile.row;

  // Battle state pulled from Supabase. We hydrate the battle row + roasts
  // up front, then subscribe to roasts INSERTs so the second roast lands
  // live without polling. Friend reactions (P1.11) ship next.
  const [battle, setBattle] = React.useState(null);
  const [roasts, setRoasts] = React.useState([]);
  const [error, setError] = React.useState(null);
  const [animPhase, setAnimPhase] = React.useState(0);

  // Initial load.
  React.useEffect(() => {
    let cancelled = false;
    (async () => {
      if (!ctx.battle_id) { setError('no battle'); return; }
      try {
        const [b, rs] = await Promise.all([
          window.getBattle(ctx.battle_id),
          window.getBattleRoasts(ctx.battle_id),
        ]);
        if (cancelled) return;
        if (!b) { setError("can't find this battle"); return; }
        setBattle(b);
        setRoasts(rs);
      } catch (e) {
        if (!cancelled) setError(e.message || String(e));
      }
    })();
    return () => { cancelled = true; };
  }, [ctx.battle_id]);

  // Realtime: append any new roasts as they land. De-dup on (battle_id, author_id).
  React.useEffect(() => {
    if (!ctx.battle_id) return undefined;
    return window.subscribeBattleRoasts(ctx.battle_id, (row) => {
      setRoasts(prev => prev.some(r => r.author_id === row.author_id) ? prev : [...prev, row]);
    });
  }, [ctx.battle_id]);

  // Both roasts in → drive the staged animation.
  const bothIn = roasts.length >= 2;
  React.useEffect(() => {
    if (!bothIn) { setAnimPhase(0); return; }
    if (animPhase >= 3) return;
    const id = setTimeout(() => setAnimPhase(p => p + 1), 700);
    return () => clearTimeout(id);
  }, [bothIn, animPhase]);

  // Figure out who's who without trusting the order roasts arrived in.
  const myId  = me?.id || null;
  const oppRow = battle && (battle.challenger?.id === myId ? battle.opponent : battle.challenger);
  const myRoast  = roasts.find(r => r.author_id === myId) || null;
  const oppRoast = roasts.find(r => r.author_id !== myId) || null;

  if (error) {
    return (
      <Screen t={t} padTop={56}>
        <TopBar t={t} onBack={() => go('home')} title="REVEAL" />
        <div style={{ padding: '24px 22px', textAlign: 'center' }}>
          <div style={{ fontFamily: 'Bungee', fontSize: 22, color: t.pop, marginBottom: 8 }}>nope</div>
          <div style={{ fontFamily: 'Space Grotesk', fontSize: 14, color: t.sub }}>{error}</div>
          <div style={{ marginTop: 18 }}>
            <ChunkyButton t={t} onClick={() => go('home')}>HOME</ChunkyButton>
          </div>
        </div>
      </Screen>
    );
  }

  if (!battle) {
    return (
      <Screen t={t} padTop={56}>
        <TopBar t={t} onBack={() => go('home')} title="REVEAL" />
        <div style={{ padding: '24px 22px', fontFamily: 'Space Grotesk', fontSize: 14, color: t.sub }}>
          loading…
        </div>
      </Screen>
    );
  }

  // Waiting state: my roast is in, theirs isn't. We hide my text behind a
  // "veiled" card so the opponent picking up the same device can't peek.
  if (!bothIn) {
    return (
      <Screen t={t} padTop={56}>
        <TopBar t={t} onBack={() => go('home')} title="WAITING" />
        <div style={{ padding: '24px 22px', textAlign: 'center' }}>
          <div style={{ display: 'inline-block', position: 'relative', marginBottom: 14 }}>
            <StarBurst size={92} color={t.sun} points={16} />
            <div style={{
              position: 'absolute', inset: 0, display: 'flex',
              alignItems: 'center', justifyContent: 'center', fontSize: 40,
            }}>{oppRow?.avatar || '👤'}</div>
          </div>
          <div style={{ fontFamily: 'Bungee', fontSize: 22, color: t.ink, marginBottom: 6 }}>
            waiting on @{oppRow?.handle || '…'}
          </div>
          <div style={{ fontFamily: 'Instrument Serif', fontStyle: 'italic', fontSize: 15, color: t.sub, marginBottom: 18 }}>
            this screen flips the second they fire back.
          </div>

          {/* veiled "your shot" card so a passer-by can't read the punchline */}
          <PaperCard t={t} color={t.pop} style={{ padding: '14px 16px', textAlign: 'left' }}>
            <div style={{ fontFamily: 'Bungee', fontSize: 10, color: t.popInk, letterSpacing: 1, opacity: 0.7, marginBottom: 6 }}>
              UR SHOT · LOCKED
            </div>
            <div style={{
              fontFamily: 'Space Grotesk, system-ui',
              fontSize: 15, fontWeight: 500, lineHeight: 1.4,
              color: t.popInk, opacity: 0.85,
              filter: 'blur(6px)',
              userSelect: 'none',
            }}>{myRoast?.text || '…'}</div>
          </PaperCard>

          <div style={{ marginTop: 18, display: 'flex', gap: 8, justifyContent: 'center' }}>
            <ChunkyButton t={t} small color={t.paper} ink={t.ink} onClick={() => go('home')}>HOME</ChunkyButton>
            <ChunkyButton t={t} small color={t.pop} ink={t.popInk} onClick={async () => {
              const ok = window.confirm(`call it off? @${oppRow?.handle || 'them'} won't see this battle anymore.`);
              if (!ok) return;
              try {
                await window.cancelBattle(ctx.battle_id);
                go('home');
              } catch (e) {
                setError(e.message || String(e));
              }
            }}>CALL IT OFF</ChunkyButton>
          </div>
        </div>
      </Screen>
    );
  }

  // Both roasts in — staged reveal.
  return (
    <Screen t={t} padTop={56}>
      <TopBar t={t} onBack={() => go('home')} title="REVEAL" />
      <div style={{ padding: '6px 18px 0' }}>
        {/* mine */}
        <div style={{
          opacity: animPhase >= 0 ? 1 : 0,
          transform: animPhase >= 0 ? 'translateX(0) rotate(-1deg)' : 'translateX(-30px) rotate(-3deg)',
          transition: 'all 500ms cubic-bezier(.2,.8,.2,1)',
          marginBottom: 14,
        }}>
          <PaperCard t={t} color={t.pop} style={{ padding: '16px 18px' }}>
            <Tape color={t.sun} tilt={-4} w={70} top={-12} left={14}/>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10 }}>
              <Avatar person={me || { avatar: '🐀' }} t={t} size={32} ring={t.sun}/>
              <div style={{ fontFamily: 'JetBrains Mono', fontSize: 12, color: t.popInk, fontWeight: 700 }}>@{me?.handle || '…'}</div>
            </div>
            <div style={{
              fontFamily: 'Space Grotesk, system-ui',
              fontSize: 18, fontWeight: 500, lineHeight: 1.4,
              letterSpacing: -0.1,
              color: t.popInk,
              textWrap: 'pretty',
            }}>{myRoast?.text || ''}</div>
          </PaperCard>
        </div>

        {/* VS slam */}
        <div style={{
          display: 'flex', justifyContent: 'center', margin: '-4px 0',
          opacity: animPhase >= 1 ? 1 : 0,
          transform: animPhase >= 1 ? 'scale(1) rotate(0deg)' : 'scale(0.4) rotate(-30deg)',
          transition: 'all 400ms cubic-bezier(.2,1.4,.2,1)',
        }}>
          <VsSlam t={t} size={56} />
        </div>

        {/* theirs */}
        <div style={{
          opacity: animPhase >= 1 ? 1 : 0,
          transform: animPhase >= 1 ? 'translateX(0) rotate(1.2deg)' : 'translateX(30px) rotate(4deg)',
          transition: 'all 500ms cubic-bezier(.2,.8,.2,1) 100ms',
          marginTop: 6,
        }}>
          <PaperCard t={t} color={t.accent} style={{ padding: '16px 18px' }}>
            <Tape color={t.pop} tilt={6} w={80} top={-12} left={'60%'}/>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10 }}>
              <Avatar person={oppRow || { avatar: '👤' }} t={t} size={32} ring={t.sun}/>
              <div style={{ fontFamily: 'JetBrains Mono', fontSize: 12, color: t.accentInk, fontWeight: 700 }}>@{oppRow?.handle || '…'}</div>
            </div>
            <div style={{
              fontFamily: 'Space Grotesk, system-ui',
              fontSize: 18, fontWeight: 500, lineHeight: 1.4,
              letterSpacing: -0.1,
              color: t.accentInk,
              textWrap: 'pretty',
            }}>{oppRoast?.text || ''}</div>
          </PaperCard>
        </div>

        {/* friend reactions — live via useReactions() */}
        <V2ReactionsRow t={t} battleId={ctx.battle_id} myId={myId} phase={animPhase} />


        {/* CTAs */}
        <div style={{
          marginTop: 22, display: 'flex', gap: 8, justifyContent: 'center',
          opacity: animPhase >= 2 ? 1 : 0,
          transition: 'opacity 400ms 200ms',
        }}>
          <ChunkyButton t={t} small color={t.paper} ink={t.ink} onClick={() => go('home')}>HOME</ChunkyButton>
          <ChunkyButton t={t} small onClick={() => go('write', { opp: oppRow?.id, battle_id: null })}>HIT BACK →</ChunkyButton>
        </div>
      </div>
    </Screen>
  );
}

// ───────────────────────────────────────────────────────────────
// 7. PROFILE — minimal. No trophies, no rank, no win%. Just you
// and your last few roasts (the things you actually said).
// ───────────────────────────────────────────────────────────────
function V2Profile({ t, go, setTab }) {
  const profile = window.useProfile();
  const receipts = window.useReceipts();
  const me = profile.row;
  const myId = me?.id;

  // Inline editor state. Opens with the current handle/avatar prefilled.
  const VIBES = ['🐀','🍒','🪩','🦴','🥀','🐛','🦷','🧃','👁','🤡','🪦','🥩'];
  const [editing, setEditing] = React.useState(false);
  const [draftHandle, setDraftHandle] = React.useState('');
  const [draftAvatar, setDraftAvatar] = React.useState('🐀');
  const [saving, setSaving] = React.useState(false);
  const [editError, setEditError] = React.useState(null);

  const openEditor = () => {
    if (!me) return;
    setDraftHandle(me.handle || '');
    setDraftAvatar(me.avatar || '🐀');
    setEditError(null);
    setEditing(true);
  };

  const saveEditor = async () => {
    setSaving(true); setEditError(null);
    try {
      await window.upsertMyProfile({ handle: draftHandle, avatar: draftAvatar });
      setEditing(false);
    } catch (e) {
      setEditError(e.message || String(e));
    } finally {
      setSaving(false);
    }
  };

  const onHandleInput = (raw) => {
    const cleaned = String(raw || '').toLowerCase().replace(/[^a-z0-9._]/g, '').slice(0, 20);
    setDraftHandle(cleaned);
    if (editError) setEditError(null);
  };

  // Format the joined date as e.g. "feb 2026" to match the original aesthetic.
  const joinedLabel = React.useMemo(() => {
    if (!me?.created_at) return '';
    try {
      const d = new Date(me.created_at);
      return d.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }).toLowerCase();
    } catch (e) { return ''; }
  }, [me?.created_at]);

  // Same when-ago logic as Home's pending list, but rendered for completed
  // battles ('5d ago' / '3w ago').
  const ago = (iso) => {
    if (!iso) return '';
    const ms = Date.now() - new Date(iso).getTime();
    const s = Math.floor(ms / 1000);
    if (s < 60)        return `${s}s ago`;
    if (s < 3600)      return `${Math.floor(s / 60)}m ago`;
    if (s < 86400)     return `${Math.floor(s / 3600)}h ago`;
    if (s < 86400 * 7) return `${Math.floor(s / 86400)}d ago`;
    return `${Math.floor(s / 86400 / 7)}w ago`;
  };

  // Receipt = your roast + the opponent's handle from the battle row.
  // oppId is threaded so the receipt's onClick can route into Reveal with
  // the right ctx (Reveal needs both battle_id and opp to render the
  // "their roast" card correctly).
  const greatest = React.useMemo(() => {
    return (receipts.list || []).map(r => {
      const b = r.battle || {};
      const opp = b.challenger?.id === myId ? b.opponent : b.challenger;
      return {
        id: r.battle_id,
        oppId: opp?.id || null,
        text: r.text,
        to: opp?.handle || 'someone',
        when: ago(b.completed_at || r.created_at),
      };
    });
  }, [receipts.list, myId]);

  return (
    <Screen t={t} padTop={56} padBottom={110}>
      <TopBar t={t} right={
        <button
          aria-label="log out"
          onClick={async () => {
            // Anonymous accounts can't be recovered — be loud about it.
            const ok = window.confirm("log out? this clears ur account on this device. since there's no email/phone yet, you can't come back to this profile.");
            if (!ok) return;
            try { await window.SB.auth.signOut(); } catch (e) {}
            try { localStorage.removeItem('rb-auth'); } catch (e) {}
            location.reload();
          }}
          style={{
            width: 38, height: 38, borderRadius: '50%',
            background: t.paper, border: `2px solid ${t.rule}`,
            boxShadow: `2px 2px 0 0 ${t.shadow}`, cursor: 'pointer',
            fontSize: 16, padding: 0,
          }}>⚙️</button>
      } />

      <div style={{ padding: '0 18px', textAlign: 'center' }}>
        <div style={{ display: 'inline-block', position: 'relative' }}>
          <StarBurst size={120} color={t.sun} points={18} />
          <div style={{
            position: 'absolute', inset: 0, display: 'flex',
            alignItems: 'center', justifyContent: 'center', fontSize: 56,
          }}>{me?.avatar || '🐀'}</div>
        </div>
        <div style={{ marginTop: 8, display: 'inline-flex', alignItems: 'center', gap: 8 }}>
          <div style={{ fontFamily: 'JetBrains Mono', fontSize: 18, fontWeight: 700 }}>
            @{me?.handle || '…'}
          </div>
          {me && !editing && (
            <button
              aria-label="edit profile"
              onClick={openEditor}
              style={{
                appearance: 'none', cursor: 'pointer', padding: 0,
                width: 28, height: 28, borderRadius: '50%',
                background: 'transparent', border: `1.5px solid ${t.rule}`,
                color: t.sub, fontSize: 12,
                display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
              }}
            >✎</button>
          )}
        </div>
        {joinedLabel && (
          <div style={{ marginTop: 6, fontFamily: 'Instrument Serif', fontStyle: 'italic', fontSize: 14, color: t.sub }}>
            joined {joinedLabel}
          </div>
        )}
      </div>

      {editing && (
        <div style={{ padding: '14px 18px 0' }}>
          <PaperCard t={t} style={{ padding: 14 }}>
            <div style={{ fontFamily: 'Bungee', fontSize: 10, color: t.sub, letterSpacing: 1, marginBottom: 8 }}>EDIT</div>

            {/* avatar grid */}
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(6, 1fr)', gap: 8, marginBottom: 12 }}>
              {VIBES.map(v => (
                <button key={v} onClick={() => setDraftAvatar(v)} style={{
                  appearance: 'none', cursor: 'pointer',
                  aspectRatio: '1', borderRadius: 10, minHeight: 44,
                  background: draftAvatar === v ? t.pop : t.paper,
                  border: `2px solid ${t.rule}`,
                  fontSize: 20, display: 'flex', alignItems: 'center', justifyContent: 'center',
                }}>{v}</button>
              ))}
            </div>

            {/* handle */}
            <div style={{ display: 'flex', alignItems: 'baseline', gap: 2 }}>
              <span style={{ color: t.sub, fontFamily: 'JetBrains Mono', fontSize: 20 }}>@</span>
              <input
                value={draftHandle}
                onChange={e => onHandleInput(e.target.value)}
                spellCheck={false} autoCapitalize="none" autoCorrect="off"
                placeholder="ur.handle"
                style={{
                  flex: 1, minWidth: 0,
                  appearance: 'none', border: 'none', outline: 'none',
                  background: 'transparent', color: t.ink,
                  fontFamily: 'JetBrains Mono', fontSize: 20, fontWeight: 700,
                  padding: 0,
                }}
              />
            </div>

            {editError && (
              <div style={{
                marginTop: 10, padding: '8px 12px', borderRadius: 10,
                background: t.pop, color: t.popInk, fontFamily: 'Space Grotesk', fontSize: 12,
              }}>{editError}</div>
            )}

            <div style={{ marginTop: 14, display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
              <ChunkyButton t={t} small color={t.paper} ink={t.ink} onClick={() => setEditing(false)}>CANCEL</ChunkyButton>
              <ChunkyButton t={t} small onClick={saveEditor}>
                {saving ? 'SAVING…' : 'SAVE'}
              </ChunkyButton>
            </div>
          </PaperCard>
        </div>
      )}

      {/* receipts — your roasts, that's it */}
      <div style={{ padding: '24px 18px 0' }}>
        <div style={{ fontFamily: 'Bungee', fontSize: 12, letterSpacing: 1, marginBottom: 10, color: t.ink }}>RECEIPTS</div>
        {greatest.length === 0 ? (
          <div style={{
            padding: '24px 18px', borderRadius: 18,
            background: t.paper, border: `2px dashed ${t.rule}`, textAlign: 'center',
          }}>
            <div style={{ fontSize: 28, marginBottom: 6 }}>📭</div>
            <div style={{ fontFamily: 'Instrument Serif', fontStyle: 'italic', fontSize: 14, color: t.sub }}>
              no receipts yet. go cook someone.
            </div>
          </div>
        ) : (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            {greatest.map((r, i) => (
              <PaperCard
                key={r.id || i}
                t={t}
                tilt={i % 2 ? 0.6 : -0.6}
                onClick={r.id && r.oppId ? () => go('reveal', { battle_id: r.id, opp: r.oppId }) : undefined}
                style={{ padding: '14px 16px', position: 'relative' }}
              >
                <div style={{
                  position: 'absolute', top: -4, left: 10,
                  fontFamily: 'Bungee, system-ui', fontSize: 44,
                  color: t.pop, opacity: 0.22, lineHeight: 1,
                  pointerEvents: 'none', userSelect: 'none',
                }}>“</div>
                <div style={{
                  fontFamily: 'Space Grotesk, system-ui',
                  fontSize: 16, fontWeight: 500, lineHeight: 1.4,
                  letterSpacing: -0.1,
                  color: t.ink,
                  textWrap: 'pretty',
                  paddingLeft: 4,
                }}>{r.text}</div>
                <div style={{ marginTop: 10, display: 'flex', alignItems: 'center', gap: 6, fontFamily: 'JetBrains Mono', fontSize: 10, color: t.sub }}>
                  <span>→ @{r.to}</span>
                  <span style={{ marginLeft: 'auto' }}>{r.when}</span>
                </div>
              </PaperCard>
            ))}
          </div>
        )}
      </div>

      <TabBar t={t} current="profile" go={setTab} />
    </Screen>
  );
}

Object.assign(window, {
  V2Splash, V2Onboard, V2Friends, V2Home, V2Write, V2Reveal, V2Profile,
});
