// Friends.jsx — friends' shared learning progress: list, detail, material peek

function FriendAvatar({ name, accent, size = 44 }) {
  return (
    <span style={{ width: size, height: size, borderRadius: '50%', background: accent, color: '#fff',
      display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 'none',
      fontSize: size * 0.36, fontWeight: 600, letterSpacing: '-0.01em', boxShadow: 'var(--shadow-sm)' }}>
      {initialsOf(name)}
    </span>);

}

const fmtMin = (m) => `${Math.floor(m / 60)}h ${String(m % 60).padStart(2, '0')}m`;

function StreakChip({ days }) {
  return (
    <span className="tag streak-chip">
      <Icon name="flame" size={13} /> {days}-day
    </span>);

}

// ---------------------------------------------------------------- list

function FriendCard({ friend, onOpen }) {
  const overall = friendOverall(friend);
  return (
    <button className="card pad hover" onClick={() => onOpen(friend.id)}
    style={{ textAlign: 'left', display: 'flex', flexDirection: 'column', gap: 15, border: '1px solid var(--line)' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
        <FriendAvatar name={friend.name} accent={friend.accent} size={46} />
        <div style={{ minWidth: 0, flex: 1 }}>
          <div style={{ fontSize: 16.5, fontWeight: 600, letterSpacing: '-0.02em' }}>{friend.name}</div>
          <div className="mono" style={{ fontSize: 11.5, color: 'var(--ink-3)', marginTop: 2 }}>Active {friend.lastActive}</div>
        </div>
        <StreakChip days={friend.streak} />
      </div>

      <div style={{ fontSize: 13.5, color: 'var(--ink-2)', lineHeight: 1.5 }}>{friend.headline}</div>

      <div>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 7 }}>
          <span className="mono" style={{ fontSize: 11, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '.04em' }}>Overall</span>
          <span className="mono" style={{ fontSize: 12.5, fontWeight: 600, color: 'var(--ink-2)' }}>{overall}%</span>
        </div>
        <ProgressBar value={overall} />
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 14, flexWrap: 'wrap' }}>
        {friend.domains.map((d) =>
        <span key={d.id} style={{ display: 'inline-flex', alignItems: 'center', gap: 7, fontSize: 12.5, color: 'var(--ink-2)' }}>
            <span style={{ width: 9, height: 9, borderRadius: 99, background: d.accent }} /> {d.name}
          </span>
        )}
      </div>

      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginTop: 'auto', paddingTop: 4 }}>
        <span className="mono" style={{ fontSize: 11.5, color: 'var(--ink-3)' }}>
          {friendTopicCount(friend)} topics · {fmtMin(friend.weekMin)} this week
        </span>
        {friend.shared ?
        <span className="tag" style={{ background: 'var(--sky)', color: 'var(--blue-ink)' }}><span className="dot" style={{ background: 'var(--blue-ink)' }} /> Shared with you</span> :
        <Icon name="arrow-up-right" size={16} style={{ color: 'var(--ink-4)' }} />}
      </div>
    </button>);

}

// ---------------------------------------------------------------- per-friend progress (past vs now)

// Build a ProgressCompareModal subject for a single friend, from their domains/topics.
function friendSubject(friend) {
  const domains = friend.domains.map((d) => ({ id: d.id, name: d.name, accent: d.accent,
    progress: Math.round(d.topics.reduce((s, t) => s + t.progress, 0) / d.topics.length) }));
  const allT = friend.domains.flatMap((d) => d.topics);
  const overall = friendOverall(friend);
  const topics = allT.length;
  const notes = allT.reduce((s, t) => s + (t.notes || 0), 0);
  const materials = allT.filter((t) => t.summary).length * 2 + Math.round(topics * 0.6);
  const cards = Math.round(topics * (14 + overall * 0.5));
  const hours = Math.round(topics * (1.1 + overall / 100 * 2.2));
  return { name: friend.name, you: false, seed: friend.id, accent: friend.accent,
    now: { overall, topics, notes, materials, cards, hours }, domains };
}

function FriendsScreen({ openFriend, toast, profile, accent }) {
  const [q, setQ] = React.useState('');
  const query = q.trim().toLowerCase();
  const friends = query ? FRIENDS.filter((f) =>
    f.name.toLowerCase().includes(query) ||
    (f.headline || '').toLowerCase().includes(query) ||
    (f.domains || []).some((d) => d.name.toLowerCase().includes(query))
  ) : FRIENDS;
  const totalQ = FRIENDS.reduce((s, f) => s + f.questions, 0);
  const totalC = FRIENDS.reduce((s, f) => s + f.challenges, 0);
  const totalMin = FRIENDS.reduce((s, f) => s + f.weekMin, 0);

  return (
    <div className="content-pad fade-up">
      <div className="page-head">
        <div>
          <p className="eyebrow">Community</p>
          <h1>Friends</h1>
          <p className="sub">{FRIENDS.length} friends in your study circle · {fmtMin(totalMin)} learned together this week</p>
        </div>
        <button className="btn" onClick={() => toast && toast('Invite link copied')}><Icon name="link" size={16} /> Invite a friend</button>
      </div>

      <div style={{ position: 'relative', maxWidth: 420, marginBottom: 22 }}>
        <Icon name="search" size={16} style={{ position: 'absolute', left: 13, top: '50%', transform: 'translateY(-50%)', color: 'var(--ink-4)' }} />
        <input className="input" value={q} onChange={(e) => setQ(e.target.value)} placeholder="Search friends by name…" style={{ paddingLeft: 37, paddingRight: q ? 36 : 13 }} />
        {q && <button className="icon-btn" style={{ width: 26, height: 26, border: 'none', position: 'absolute', right: 7, top: '50%', transform: 'translateY(-50%)' }} onClick={() => setQ('')}><Icon name="x" size={15} /></button>}
      </div>

      <div className="col-split">
        <div className="grid" style={{ gridTemplateColumns: 'repeat(auto-fill,minmax(290px,1fr))', alignItems: 'stretch' }}>
          {friends.length
            ? friends.map((f) => <FriendCard key={f.id} friend={f} onOpen={openFriend} />)
            : <EmptyHint icon="search" title="No friends match" sub={'No one in your circle matches “' + q + '”.'} action={<button className="btn" onClick={() => setQ('')}>Clear search</button>} />}
        </div>

        <div className="card pad" style={{ position: 'sticky', top: 8, display: 'flex', flexDirection: 'column', gap: 4 }}>
          <div className="section-title" style={{ marginBottom: 12 }}>
            <h2 style={{ whiteSpace: 'nowrap' }}><Icon name="activity" size={17} style={{ color: 'var(--ink-3)' }} /> This week</h2>
            <span className="count-pill">engagement</span>
          </div>
          <div style={{ display: 'flex', gap: 18, padding: '4px 4px 12px', marginBottom: 4, borderBottom: '1px solid var(--line-2)' }}>
            <div style={{ flex: 1 }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 6, color: 'var(--blue-ink)' }}>
                <Icon name="message" size={14} /><span className="mono" style={{ textTransform: 'uppercase', letterSpacing: '.04em', fontWeight: "700", fontSize: "11px" }}>Questions</span>
              </div>
              <div style={{ fontSize: 22, fontWeight: 600, letterSpacing: '-0.02em', marginTop: 4, padding: "1px" }}>{totalQ}</div>
            </div>
            <div style={{ flex: 1 }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 6, color: 'var(--clay, #9C5B40)' }}>
                <Icon name="flag" size={14} /><span className="mono" style={{ textTransform: 'uppercase', letterSpacing: '.04em', color: "rgb(184, 124, 99)", fontWeight: "700", fontSize: "11px" }}>Challenges</span>
              </div>
              <div style={{ fontSize: 22, fontWeight: 600, letterSpacing: '-0.02em', marginTop: 4, padding: "1px" }}>{totalC}</div>
            </div>
          </div>
          {(query ? friends : FRIENDS).map((f, i) =>
          <button key={f.id} onClick={() => openFriend(f.id)}
          style={{ display: 'flex', alignItems: 'center', gap: 11, padding: '11px 4px', border: 'none', background: 'transparent',
            cursor: 'pointer', textAlign: 'left', borderTop: i ? '1px solid var(--line-2)' : 'none', width: '100%', color: 'var(--ink)' }}>
              <FriendAvatar name={f.name} accent={f.accent} size={32} />
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 13.5, fontWeight: 600, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{f.name.split(' ')[0]}</div>
              </div>
              <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, flex: 'none' }} title={f.questions + ' questions asked'}>
                <Icon name="message" size={13} style={{ color: 'var(--blue-ink)' }} />
                <span className="mono" style={{ fontSize: 12, fontWeight: 600, color: 'var(--ink-2)', width: 22, textAlign: 'right' }}>{f.questions}</span>
              </span>
              <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, flex: 'none' }} title={f.challenges + ' challenges faced'}>
                <Icon name="flag" size={13} style={{ color: 'var(--clay, #9C5B40)' }} />
                <span className="mono" style={{ fontSize: 12, fontWeight: 600, color: 'var(--ink-2)', width: 16, textAlign: 'right' }}>{f.challenges}</span>
              </span>
            </button>
          )}
          <div className="mono" style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 12, lineHeight: 1.5 }}>
            Questions asked of Rocky and challenges faced across shared topics this week.
          </div>
        </div>
      </div>
    </div>);

}

// ---------------------------------------------------------------- concept map peek

function FriendConceptMap({ title, concepts }) {
  const n = concepts.length;
  const pos = concepts.map((_, k) => {
    const ang = (-90 + k * (360 / n)) * Math.PI / 180;
    return [50 + Math.cos(ang) * 35, 50 + Math.sin(ang) * 34];
  });
  const [sel, setSel] = React.useState(null);
  return (
    <div>
      <div className="map-stage" style={{ aspectRatio: '16/10', maxHeight: 360, resize: 'none', minWidth: 0, minHeight: 0 }}>
        <ConstellationField opacity={0.35} />
        <svg className="map-lines" viewBox="0 0 100 100" preserveAspectRatio="none">
          {pos.map((r, k) =>
          <line key={k} x1={50} y1={50} x2={r[0]} y2={r[1]} stroke="var(--ink-4)" strokeWidth="1"
          strokeDasharray="2.5 2.5" vectorEffect="non-scaling-stroke" opacity={sel == null || sel === k ? 0.75 : 0.25} />
          )}
        </svg>
        <div className="map-node mn-center" style={{ left: '50%', top: '50%', width: 'clamp(92px,15vw,130px)', cursor: 'default' }}>
          <span className="nm" style={{ fontSize: 14 }}>{title}</span>
        </div>
        {concepts.map((c, k) =>
        <div key={k} className="map-node mn-leaf" onMouseEnter={() => setSel(k)} onMouseLeave={() => setSel(null)}
        style={{ left: pos[k][0] + '%', top: pos[k][1] + '%', cursor: 'default',
          boxShadow: sel === k ? '0 0 0 2px var(--blue)' : 'var(--shadow-sm)',
          opacity: sel == null || sel === k ? 1 : 0.5, transition: 'opacity .15s, box-shadow .15s' }}>
            <span className="accent" style={{ background: 'var(--blue)' }} /> {c.label}
          </div>
        )}
      </div>
      <div style={{ marginTop: 18, fontSize: 12, fontWeight: 600, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '.05em', fontFamily: 'var(--font-mono)', marginBottom: 10 }}>
        Key concepts
      </div>
      <div className="grid" style={{ gridTemplateColumns: 'repeat(auto-fill,minmax(220px,1fr))', gap: 10 }}>
        {concepts.map((c, k) =>
        <div key={k} className="card tight" onMouseEnter={() => setSel(k)} onMouseLeave={() => setSel(null)}
        style={{ display: 'flex', gap: 10, padding: '12px 13px', alignItems: 'flex-start',
          border: sel === k ? '1px solid var(--blue)' : '1px solid var(--line)', transition: 'border-color .15s' }}>
            <span style={{ width: 8, height: 8, borderRadius: 99, background: 'var(--blue)', marginTop: 6, flex: 'none' }} />
            <div style={{ minWidth: 0 }}>
              <div style={{ fontSize: 13.5, fontWeight: 600, letterSpacing: '-0.01em' }}>{c.label}</div>
              <div style={{ fontSize: 12.5, color: 'var(--ink-3)', lineHeight: 1.45, marginTop: 2 }}>{c.note}</div>
            </div>
          </div>
        )}
      </div>
    </div>);

}

function FriendSummary({ summary }) {
  const s = summary;
  return (
    <div>
      <h2 style={{ fontSize: 21, fontWeight: 600, letterSpacing: '-0.02em', margin: '0 0 12px' }}>{s.head}</h2>
      <p className="t-body" style={{ fontSize: 15, lineHeight: 1.7, marginTop: 0 }}>{s.intro}</p>
      {s.secs.map((sec, i) =>
      <div key={i} style={{ marginTop: 20 }}>
          <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--blue-ink)', textTransform: 'uppercase', letterSpacing: '.05em', marginBottom: 7, fontFamily: 'var(--font-mono)' }}>{sec.h}</div>
          <p className="t-body" style={{ fontSize: 14.5, lineHeight: 1.65, margin: 0 }}>{sec.b}</p>
        </div>
      )}
      <div style={{ marginTop: 26, paddingTop: 18, borderTop: '1px solid var(--line)' }}>
        <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '.05em', marginBottom: 11, fontFamily: 'var(--font-mono)', display: 'flex', alignItems: 'center', gap: 7 }}>
          <Icon name="link" size={13} /> Sources
        </div>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
          {s.links.map((l, i) =>
          <a key={i} href={l.url} target="_blank" rel="noreferrer"
          style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13.5, color: 'var(--blue-ink)', textDecoration: 'none' }}>
              <Icon name="arrow-up-right" size={14} style={{ flex: 'none', opacity: .7 }} />
              <span style={{ textDecoration: 'underline', textUnderlineOffset: 2 }}>{l.label}</span>
            </a>
          )}
        </div>
      </div>
    </div>);

}

function FriendMaterialModal({ friend, topic, onClose }) {
  const [tab, setTab] = React.useState('summary');
  return ReactDOM.createPortal(
    <div className="scrim" onMouseDown={(e) => {if (e.target === e.currentTarget) onClose();}}>
      <div className="modal" style={{ maxWidth: 720, width: '92vw', maxHeight: '90vh', display: 'flex', flexDirection: 'column' }}>
        <div className="mhead">
          <div style={{ display: 'flex', alignItems: 'center', gap: 11, minWidth: 0 }}>
            <FriendAvatar name={friend.name} accent={friend.accent} size={32} />
            <div style={{ minWidth: 0 }}>
              <div style={{ fontWeight: 600, fontSize: 15.5, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{topic.name}</div>
              <div className="mono" style={{ fontSize: 11, color: 'var(--ink-3)' }}>{topic.field} · shared by {friend.name.split(' ')[0]}</div>
            </div>
          </div>
          <button className="icon-btn" style={{ width: 32, height: 32, border: 'none' }} onClick={onClose}><Icon name="x" size={18} /></button>
        </div>

        <div style={{ padding: '14px 22px 0' }}>
          <div className="segmented">
            <button className={tab === 'summary' ? 'on' : ''} onClick={() => setTab('summary')}><Icon name="book" size={14} /> Summary</button>
            <button className={tab === 'map' ? 'on' : ''} onClick={() => setTab('map')}><Icon name="map" size={14} /> Concept map</button>
          </div>
        </div>

        <div className="mbody" style={{ overflowY: 'auto', padding: '20px 24px 26px' }}>
          {tab === 'summary' ?
          <FriendSummary summary={topic.summary} /> :
          <FriendConceptMap title={topic.name} concepts={topic.concepts} />}
        </div>
      </div>
    </div>,
    document.body
  );
}

// ---------------------------------------------------------------- detail

function FriendTopicCard({ friend, topic, onOpen }) {
  const has = !!topic.summary;
  const Tag = has ? 'button' : 'div';
  return (
    <Tag className={'card pad' + (has ? ' hover' : '')} onClick={has ? () => onOpen(topic) : undefined}
    style={{ textAlign: 'left', display: 'flex', flexDirection: 'column', gap: 11, border: '1px solid var(--line)',
      cursor: has ? 'pointer' : 'default', width: '100%' }}>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10 }}>
        <span className="mono" style={{ fontSize: 10.5, letterSpacing: '.04em', textTransform: 'uppercase', color: 'var(--ink-3)' }}>{topic.field}</span>
        <span className="mono" style={{ fontSize: 12, fontWeight: 600, color: 'var(--ink-2)' }}>{topic.progress}%</span>
      </div>
      <div style={{ fontSize: 16, fontWeight: 600, letterSpacing: '-0.02em', lineHeight: 1.2 }}>{topic.name}</div>
      <ProgressBar value={topic.progress} />
      {topic.note && <div style={{ fontSize: 12.5, color: 'var(--ink-3)', lineHeight: 1.5 }}>{topic.note}</div>}
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginTop: 'auto', paddingTop: 2 }}>
        <span className="mono" style={{ fontSize: 11, color: 'var(--ink-3)' }}>{topic.notes} notes</span>
        {has ?
        <span style={{ display: 'inline-flex', gap: 6 }}>
              <span className="tag" style={{ background: MAT_TYPE.summary.bg, color: MAT_TYPE.summary.tint }}><Icon name="book" size={12} /> Summary</span>
              <span className="tag" style={{ background: MAT_TYPE.map.bg, color: MAT_TYPE.map.tint }}><Icon name="map" size={12} /> Map</span>
            </span> :
        <span className="mono" style={{ fontSize: 11, color: 'var(--ink-4)' }}>progress only</span>}
      </div>
    </Tag>);

}

// ---------------------------------------------------------------- learning map (domains → topics)

function friendMapLayout(friend) {
  const D = friend.domains.length;
  const nodes = [];
  friend.domains.forEach((d, di) => {
    const base = 360 / D * di + (D === 2 ? 0 : -90);
    const ar = base * Math.PI / 180;
    nodes.push({ id: 'dom-' + d.id, type: 'domain', x: 50 + Math.cos(ar) * 24, y: 50 + Math.sin(ar) * 18,
      accent: d.accent, domain: d });
    const m = d.topics.length;
    const arc = Math.min(140, 360 / D * 0.74);
    d.topics.forEach((t, ti) => {
      const frac = m === 1 ? 0.5 : ti / (m - 1);
      const a = (base - arc / 2 + frac * arc) * Math.PI / 180;
      nodes.push({ id: 'top-' + t.id, type: 'topic', x: 50 + Math.cos(a) * 43, y: 50 + Math.sin(a) * 40,
        accent: d.accent, topic: t, domainId: d.id });
    });
  });
  return nodes;
}

function resolveFriendMap(np, stage) {
  if (!stage) return np;
  const rect = stage.getBoundingClientRect();
  const W = rect.width,H = rect.height;
  if (!W || !H) return np;
  const sizes = {};
  stage.querySelectorAll('[data-nid]').forEach((n) => {
    const r = n.getBoundingClientRect();
    sizes[n.dataset.nid] = { hw: r.width / 2 + 6, hh: r.height / 2 + 6 };
  });
  const ids = Object.keys(np).filter((k) => sizes[k]);
  if (!ids.length) return np;
  const P = {};ids.forEach((k) => P[k] = [np[k][0] / 100 * W, np[k][1] / 100 * H]);
  const fixed = new Set(['__center']);
  for (let it = 0; it < 18; it++) {
    for (let a = 0; a < ids.length; a++) for (let b = a + 1; b < ids.length; b++) {
      const i = ids[a],j = ids[b];
      const dx = P[j][0] - P[i][0],dy = P[j][1] - P[i][1];
      const ox = sizes[i].hw + sizes[j].hw - Math.abs(dx);
      const oy = sizes[i].hh + sizes[j].hh - Math.abs(dy);
      if (ox > 0 && oy > 0) {
        let px = 0,py = 0;
        if (ox < oy) px = (dx < 0 ? -1 : 1) * ox;else py = (dy < 0 ? -1 : 1) * oy;
        const fi = fixed.has(i),fj = fixed.has(j);
        if (fi && fj) continue;
        if (fi) {P[j][0] += px;P[j][1] += py;} else
        if (fj) {P[i][0] -= px;P[i][1] -= py;} else
        {P[i][0] -= px / 2;P[i][1] -= py / 2;P[j][0] += px / 2;P[j][1] += py / 2;}
      }
    }
  }
  const out = { ...np };
  ids.forEach((k) => {
    const hw = sizes[k].hw,hh = sizes[k].hh;
    const cx = Math.max(hw + 8, Math.min(W - hw - 8, P[k][0]));
    const cy = Math.max(hh + 6, Math.min(H - hh - 6, P[k][1]));
    out[k] = [cx / W * 100, cy / H * 100];
  });
  out.__center = np.__center;
  return out;
}

function FriendLearningMap({ friend, onOpenTopic }) {
  const stageRef = React.useRef(null);
  const seed = React.useMemo(() => {
    const o = { __center: [50, 50] };
    friendMapLayout(friend).forEach((n) => o[n.id] = [n.x, n.y]);
    return o;
  }, [friend.id]);
  const nodes = React.useMemo(() => friendMapLayout(friend), [friend.id]);
  const stageH = Math.min(640, 392 + Math.max(...friend.domains.map((d) => d.topics.length)) * 26);
  const [pos, setPos] = React.useState(seed);
  React.useEffect(() => {
    let tries = 0,raf,alive = true;
    const attempt = () => {
      if (!alive) return;
      const st = stageRef.current;
      if (st && st.getBoundingClientRect().width) {
        let p = resolveFriendMap(seed, st);
        setPos(p);
        // second settle pass after the DOM reflows to the new positions
        requestAnimationFrame(() => {if (alive && stageRef.current) setPos(resolveFriendMap(seed, stageRef.current));});
      } else if (tries++ < 40) {raf = requestAnimationFrame(attempt);}
    };
    raf = requestAnimationFrame(attempt);
    const onR = () => {const st = stageRef.current;if (st) setPos(resolveFriendMap(seed, st));};
    window.addEventListener('resize', onR);
    return () => {alive = false;cancelAnimationFrame(raf);window.removeEventListener('resize', onR);};
  }, [seed]);

  const lines = [];
  const ctr = pos.__center || [50, 50];
  nodes.forEach((n) => {
    if (n.type === 'domain') {const p = pos[n.id];if (p) lines.push({ a: ctr, b: p, strong: true });} else
    {const dp = pos['dom-' + n.domainId],tp = pos[n.id];if (dp && tp) lines.push({ a: dp, b: tp, strong: false });}
  });

  // drag-or-click: drag any node (a domain drags its child topics too); a click
  // that didn't move still opens the topic.
  const movedRef = React.useRef(false);
  const startDrag = (ids) => (e) => {
    if (e.button != null && e.button !== 0) return;
    e.preventDefault();e.stopPropagation();
    const stage = stageRef.current;if (!stage) return;
    const rect = stage.getBoundingClientRect();
    const sx = e.clientX,sy = e.clientY;
    const starts = {};ids.forEach((id) => starts[id] = pos[id] || [50, 50]);
    movedRef.current = false;
    const cx = (v) => Math.max(3, Math.min(97, v));
    const cy = (v) => Math.max(3, Math.min(97, v));
    const move = (ev) => {
      if (Math.abs(ev.clientX - sx) > 3 || Math.abs(ev.clientY - sy) > 3) movedRef.current = true;
      const dx = (ev.clientX - sx) / rect.width * 100;
      const dy = (ev.clientY - sy) / rect.height * 100;
      setPos((p) => {const np = { ...p };ids.forEach((id) => {np[id] = [cx(starts[id][0] + dx), cy(starts[id][1] + dy)];});return np;});
    };
    const up = () => {document.removeEventListener('pointermove', move);document.removeEventListener('pointerup', up);};
    document.addEventListener('pointermove', move);document.addEventListener('pointerup', up);
  };
  const dragStyle = { cursor: 'grab', touchAction: 'none', userSelect: 'none' };

  return (
    <div className="map-stage" ref={stageRef} style={{ aspectRatio: 'auto', height: stageH, maxHeight: 'none', resize: 'none', minWidth: 0, minHeight: 0 }}>
      <ConstellationField opacity={0.3} />
      <svg className="map-lines" viewBox="0 0 100 100" preserveAspectRatio="none">
        {lines.map((l, i) =>
        <line key={i} x1={l.a[0]} y1={l.a[1]} x2={l.b[0]} y2={l.b[1]} stroke="var(--ink-4)"
        strokeWidth={l.strong ? 1.4 : 1} strokeDasharray={l.strong ? 'none' : '2.5 2.5'}
        vectorEffect="non-scaling-stroke" opacity={l.strong ? 0.7 : 0.55} />
        )}
      </svg>

      <div className="map-node mn-center" data-nid="__center" onPointerDown={startDrag(['__center'])}
      style={{ left: ctr[0] + '%', top: ctr[1] + '%', width: 'clamp(84px,12vw,116px)', ...dragStyle, backgroundColor: friend.accent, color: '#fff' }}>
        <Icon name="brain" size={22} />
        <span className="nm" style={{ fontSize: 13 }}>{friend.name.split(' ')[0]}</span>
        <span className="mt">{friend.domains.length} domains</span>
      </div>

      {nodes.filter((n) => n.type === 'domain').map((n) => {
        const ts = n.domain.topics;
        const prog = Math.round(ts.reduce((s, t) => s + t.progress, 0) / ts.length);
        const p = pos[n.id] || [n.x, n.y];
        const groupIds = [n.id, ...ts.map((t) => 'top-' + t.id)];
        return (
          <div key={n.id} className="map-node mn-domain" data-nid={n.id} onPointerDown={startDrag(groupIds)}
          style={{ left: p[0] + '%', top: p[1] + '%', ...dragStyle }} title="Drag to move this domain and its topics">
            <div className="nm"><span className="accent" style={{ background: n.accent }} /> {n.domain.name}</div>
            <div className="progress"><i style={{ width: prog + '%', background: n.accent }} /></div>
            <div className="mt">{ts.length} topics · {prog}%</div>
          </div>);

      })}

      {nodes.filter((n) => n.type === 'topic').map((n) => {
        const has = !!n.topic.summary;
        const p = pos[n.id] || [n.x, n.y];
        return (
          <div key={n.id} className="map-node mn-leaf" data-nid={n.id} onPointerDown={startDrag([n.id])}
          onClick={has ? () => {if (movedRef.current) {movedRef.current = false;return;}onOpenTopic(n.topic);} : undefined}
          style={{ left: p[0] + '%', top: p[1] + '%', ...dragStyle, maxWidth: 158, whiteSpace: 'normal', lineHeight: 1.25 }} role={has ? 'button' : undefined}
          title={has ? 'Drag to move · click to open summary & concept map' : 'Drag to move'}>
            <span className="accent" style={{ background: n.accent, marginTop: 4, alignSelf: 'flex-start' }} /> {n.topic.name}
          </div>);

      })}
    </div>);

}

function Stat({ label, value, icon }) {
  return (
    <div style={{ flex: 1, minWidth: 92 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 6, color: 'var(--ink-3)', marginBottom: 5 }}>
        <Icon name={icon} size={14} /><span className="mono" style={{ fontSize: 10.5, textTransform: 'uppercase', letterSpacing: '.04em' }}>{label}</span>
      </div>
      <div style={{ fontSize: 21, fontWeight: 600, letterSpacing: '-0.02em' }}>{value}</div>
    </div>);

}

function FriendDetailScreen({ friendId, back, toast }) {
  const friend = friendById(friendId);
  const [peek, setPeek] = React.useState(null);
  const [compareProg, setCompareProg] = React.useState(false);
  if (!friend) return null;
  const overall = friendOverall(friend);

  return (
    <div className="content-pad fade-up">
      <button className="btn ghost sm" onClick={back} style={{ marginBottom: 18, paddingLeft: 4 }}>
        <Icon name="arrow-left" size={16} /> All friends
      </button>

      <div className="card pad" style={{ padding: '26px 28px', marginBottom: 26 }}>
        <div style={{ display: 'flex', gap: 18, alignItems: 'flex-start', flexWrap: 'wrap' }}>
          <FriendAvatar name={friend.name} accent={friend.accent} size={64} />
          <div style={{ flex: 1, minWidth: 220 }}>
            <h1 style={{ fontSize: 26, fontWeight: 600, letterSpacing: '-0.025em', margin: 0, lineHeight: 1.1 }}>{friend.name}</h1>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap', marginTop: 9 }}>
              <StreakChip days={friend.streak} />
              {friend.shared && <span className="tag" style={{ background: 'var(--sky)', color: 'var(--blue-ink)' }}><span className="dot" style={{ background: 'var(--blue-ink)' }} /> Shared with you</span>}
            </div>
            <div style={{ fontSize: 14, color: 'var(--ink-2)', marginTop: 12 }}>{friend.headline}</div>
            <div className="t-body" style={{ fontSize: 13.5, marginTop: 8, maxWidth: '54ch' }}>{friend.blurb}</div>
          </div>
          <div style={{ display: 'flex', gap: 8 }}>
            <button className="btn" onClick={() => toast && toast('Sent ' + friend.name.split(' ')[0] + ' a reminder to keep learning')}><Icon name="nudge" size={16} /> Nudge</button>
            <button className="btn blue" onClick={() => setCompareProg(true)}><Icon name="chart" size={16} /> Compare</button>
          </div>
        </div>
        <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', marginTop: 24, paddingTop: 20, borderTop: '1px solid var(--line)' }}>
          <Stat label="Overall" value={overall + '%'} icon="trending" />
          <Stat label="Streak" value={friend.streak + 'd'} icon="flame" />
          <Stat label="This week" value={fmtMin(friend.weekMin)} icon="clock" />
          <Stat label="Topics" value={friendTopicCount(friend)} icon="layers" />
        </div>
      </div>

      <div className="section-title">
        <h2><Icon name="map" size={17} style={{ color: 'var(--ink-3)' }} /> Learning map</h2>
        <span className="mono" style={{ fontSize: 12, color: 'var(--ink-3)' }}>{friendTopicCount(friend)} topics</span>
      </div>
      <div className="card pad" style={{ marginBottom: 30, padding: 14 }}>
        <FriendLearningMap key={friend.id} friend={friend} onOpenTopic={setPeek} />
        <p className="t-small" style={{ textAlign: 'center', margin: '12px 0 2px', color: 'var(--ink-3)' }}>
          {friend.shared ? 'Tap any topic node to open its summary and concept map.' : 'A map of the domains and topics in ' + friend.name.split(' ')[0] + '\u2019s record.'}
        </p>
      </div>

      {friend.domains.map((d) => {
        const avg = Math.round(d.topics.reduce((s, t) => s + t.progress, 0) / d.topics.length);
        return (
          <div key={d.id} style={{ marginBottom: 30 }}>
            <div className="section-title">
              <h2>
                <span style={{ width: 11, height: 11, borderRadius: 99, background: d.accent }} /> {d.name}
                <span className="count-pill">{d.topics.length} topics</span>
              </h2>
              <span className="mono" style={{ fontSize: 12, color: 'var(--ink-3)' }}>{avg}% avg</span>
            </div>
            <div className="grid" style={{ gridTemplateColumns: 'repeat(auto-fill,minmax(248px,1fr))' }}>
              {d.topics.map((t) => <FriendTopicCard key={t.id} friend={friend} topic={t} onOpen={setPeek} />)}
            </div>
          </div>);

      })}

      <div className="section-title"><h2><Icon name="activity" size={17} style={{ color: 'var(--ink-3)' }} /> Recent activity</h2></div>
      <div className="card pad">
        {friend.activity.map((a, i) =>
        <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '11px 4px', borderTop: i ? '1px solid var(--line-2)' : 'none' }}>
            <span style={{ width: 9, height: 9, borderRadius: 99, background: a.accent, flex: 'none' }} />
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontSize: 14, fontWeight: 500 }}>{a.text}</div>
            </div>
            <span className="mono" style={{ fontSize: 11.5, color: 'var(--ink-3)' }}>{a.meta}</span>
          </div>
        )}
      </div>

      {peek && <FriendMaterialModal friend={friend} topic={peek} onClose={() => setPeek(null)} />}
      {compareProg && <ProgressCompareModal subject={friendSubject(friend)} onClose={() => setCompareProg(false)} />}
    </div>);

}

Object.assign(window, { FriendsScreen, FriendDetailScreen });