// ─────────────────────────────────────────────────────────────
// TreeView — a SUSTAINABLE tree. It never renders the whole tree.
// Collapse-by-default: only the frontier under expanded nodes is drawn,
// so a 10k-node forest costs the same as a 50-node one. Implements the
// brainstorm board's §6.A: #2 spine + collapsed side-trees, #8 subtree-
// as-root, #12 frond glyph (a dense branch = one shape, one hit target).
//   · chains auto-open; branch points collapse to a frond + count
//   · tap a frond → expand one level · tap an open branch → collapse
//   · "Focus" a node → it becomes the root (subtree-as-root)
//   · colorMode 'author' | 'heat' (A2)  · live arrivals (C1/C3/B/A4)
// ─────────────────────────────────────────────────────────────
const COL = {
  you: '#4F8E51', them: '#A8C5A0', sun: '#D4960C', sun2: '#F4DCA0',
  edge: '#37482D', edgeHi: '#76AB78', fog3: '#8A9678',
};
const polar = (r, a) => ({ x: Math.cos(a - Math.PI / 2) * r, y: Math.sin(a - Math.PI / 2) * r });
function heatColor(h) { return h > 0.74 ? '#D4960C' : h > 0.5 ? '#C9A23C' : h > 0.3 ? '#76AB78' : h > 0.14 ? '#5E8048' : '#37472D'; }
function nodeRadius(n) { return n.kids.length === 0 ? 2.2 : Math.min(4.6, 2.0 + Math.log2(n.leaves + 1) * 0.55); }
function linkPath(p, n) {
  const m1 = polar((p.radius + n.radius) / 2, p.angle), m2 = polar((p.radius + n.radius) / 2, n.angle);
  return `M${p.x} ${p.y}C${m1.x} ${m1.y} ${m2.x} ${m2.y} ${n.x} ${n.y}`;
}

(function injectLT() {
  if (typeof document === 'undefined' || document.getElementById('lt-kf')) return;
  const s = document.createElement('style'); s.id = 'lt-kf';
  s.textContent = `
  @keyframes lt-bloom { 0%{transform:scale(0);opacity:0} 60%{transform:scale(1.3);opacity:1} 100%{transform:scale(1);opacity:1} }
  @keyframes lt-draw  { 0%{stroke-dashoffset:1;opacity:0} 12%{opacity:1} 100%{stroke-dashoffset:0;opacity:1} }
  @keyframes lt-sap   { 0%{offset-distance:0%;opacity:0} 12%{opacity:1} 70%{offset-distance:100%;opacity:1} 100%{offset-distance:100%;opacity:0} }
  @keyframes lt-ripple{ 0%{transform:scale(.2);opacity:0} 25%{opacity:.85} 100%{transform:scale(1);opacity:0} }
  @keyframes lt-fly   { 0%{opacity:0;transform:scale(2.4)} 28%{opacity:1;transform:scale(1)} 100%{opacity:.7;transform:scale(1)} }
  @keyframes lt-tw    { 0%{opacity:.5} 50%{opacity:1} 100%{opacity:.5} }
  @keyframes lt-grow  { from{opacity:0;transform:scale(.4)} to{opacity:1;transform:scale(1)} }
  .lt-bloom{animation:lt-bloom .42s cubic-bezier(.32,.72,.34,1) both;transform-box:fill-box;transform-origin:center}
  .lt-ripple{animation:lt-ripple .9s ease-out both;transform-box:fill-box;transform-origin:center}
  .lt-draw{animation:lt-draw .5s ease-out both}
  .lt-sap{animation:lt-sap .8s ease-in-out both}
  .lt-fly{animation:lt-fly 1.1s ease-out both;transform-box:fill-box;transform-origin:center}
  .lt-grow{animation:lt-grow .4s cubic-bezier(.32,.72,.34,1) both;transform-box:fill-box;transform-origin:center}`;
  document.head.appendChild(s);
})();

// the path of branch points from a node down its busiest line — used as the
// sensible default-expanded set so a thread is open without any taps
function defaultExpanded(T, rootId) {
  const set = new Set(); let cur = T.byId[rootId];
  while (cur) { if (cur.kids.length >= 2) set.add(cur.id); if (!cur.kids.length) break;
    cur = cur.kids.map((k) => T.byId[k]).sort((a, b) => b.size - a.size)[0]; }
  return set;
}

function Frond({ n, fill }) {
  const ang = n.angle - Math.PI / 2, ux = Math.cos(ang), uy = Math.sin(ang), px = -uy, py = ux;
  const L = Math.min(20, 8 + Math.log2(n.leaves + 1) * 2.6);
  const barbs = Math.min(6, 2 + Math.round(Math.log2(n.leaves + 1)));
  const tip = { x: n.x + ux * L, y: n.y + uy * L };
  const strokes = [<path key="v" d={`M${n.x} ${n.y}L${tip.x} ${tip.y}`} stroke={fill} strokeOpacity={0.75} strokeWidth={1.1} fill="none" strokeLinecap="round"/>];
  for (let i = 0; i < barbs; i++) {
    const t = (i + 1) / (barbs + 1), bx = n.x + ux * L * t, by = n.y + uy * L * t, side = i % 2 ? 1 : -1, bl = L * 0.42;
    const ex = bx + (ux * 0.5 + px * side) * bl, ey = by + (uy * 0.5 + py * side) * bl;
    strokes.push(<path key={i} d={`M${bx} ${by}Q${bx + px * side * bl * 0.5} ${by + py * side * bl * 0.5} ${ex} ${ey}`} stroke={fill} strokeOpacity={0.4 + t * 0.3} strokeWidth={0.9} fill="none" strokeLinecap="round"/>);
  }
  return <g className="lt-grow">{strokes}<circle cx={n.x} cy={n.y} r={3} fill={fill} stroke={COL.sun2} strokeOpacity={0.5} strokeWidth={0.5}/></g>;
}

function TreeView({ data, colorMode = 'author', live = false, level = 3, focusId, revealId, onFocus, onHome, onOpenChat, size, onStats }) {
  const T = data || window.LT;
  const W = size.w, H = size.h, CX = W / 2, CY = H / 2;
  const viewRoot = (focusId != null) ? focusId : T.root.id;
  const gRef = React.useRef(null), labelLayer = React.useRef(null), vpRectRef = React.useRef(null);
  const cam = React.useRef({ x: 0, y: 0, k: 1 }), rafRef = React.useRef(0), firstFit = React.useRef(true);
  const reveal = React.useRef(revealId);

  // expand all branch points within `level` of the view root; if opened from
  // Canvas, also open the path to the tapped branch so it's visible.
  const seedFor = (lvl) => {
    const rd0 = T.byId[viewRoot].depth;
    const base = viewRoot === T.root.id ? T.nodes : T.subtreeIds(viewRoot).map((id) => T.byId[id]);
    const next = new Set();
    for (const n of base) if (n.kids.length >= 2 && (n.depth - rd0) < lvl) next.add(n.id);
    if (reveal.current != null) T.pathTo(reveal.current).forEach((id) => { if (T.byId[id].kids.length >= 2) next.add(id); });
    return next;
  };
  const [expanded, setExpanded] = React.useState(() => seedFor(level));
  const [selected, setSelected] = React.useState(revealId != null ? revealId : null);
  React.useEffect(() => { setExpanded(seedFor(level)); }, [level, viewRoot, T]);
  const didMount = React.useRef(false);
  React.useEffect(() => { if (!didMount.current) { didMount.current = true; return; } setSelected(null); }, [viewRoot, T]);

  const isOpen = (id) => id === viewRoot || expanded.has(id) || T.byId[id].kids.length === 1;

  // ---- visible frontier (the ONLY thing we draw) ----
  const vis = React.useMemo(() => {
    const ids = [], set = new Set(), stack = [viewRoot];
    while (stack.length) { const id = stack.pop(); ids.push(id); set.add(id); if (isOpen(id)) T.byId[id].kids.forEach((k) => stack.push(k)); }
    return { ids, set };
  }, [T, viewRoot, expanded]);
  React.useEffect(() => { onStats && onStats(vis.ids.length); }, [vis]);

  // ---- camera ----
  const MINI = React.useMemo(() => { const box = 116, pad = 8, s = (box - pad * 2) / (2 * T.R); return { box, s, px: (x) => box / 2 + x * s, py: (y) => box / 2 + y * s }; }, [T]);
  function camFor(ids) {
    const b = T.bbox(ids), pad = 1.4;
    const k = Math.max(0.12, Math.min(4.2, Math.min(W / (b.w * pad + 80), H / (b.h * pad + 80))));
    return { x: b.cx, y: b.cy, k };
  }
  function applyCam() {
    const { x, y, k } = cam.current;
    if (gRef.current) gRef.current.setAttribute('transform', `translate(${CX} ${CY}) scale(${k}) translate(${-x} ${-y})`);
    const layer = labelLayer.current;
    if (layer) {
      const arr = []; for (const el of layer.children) { const n = T.byId[+el.dataset.id]; arr.push({ el, sx: CX + (n.x - x) * k, sy: CY + (n.y - y) * k }); }
      arr.sort((a, b) => a.sy - b.sy); const GAP = 15;
      for (let i = 1; i < arr.length; i++) if (arr[i].sy < arr[i - 1].sy + GAP) arr[i].sy = arr[i - 1].sy + GAP;
      for (const it of arr) { const vok = it.sx > -50 && it.sx < W + 50 && it.sy > 6 && it.sy < H - 40; it.el.style.transform = `translate(${it.sx}px,${it.sy}px)`; it.el.style.opacity = vok ? 1 : 0; }
    }
    if (vpRectRef.current) { const hw = (W / 2) / k, hh = (H / 2) / k; vpRectRef.current.setAttribute('x', MINI.px(x - hw)); vpRectRef.current.setAttribute('y', MINI.py(y - hh)); vpRectRef.current.setAttribute('width', Math.max(3, 2 * hw * MINI.s)); vpRectRef.current.setAttribute('height', Math.max(3, 2 * hh * MINI.s)); }
  }
  function tweenTo(target, dur = 800) {
    cancelAnimationFrame(rafRef.current); const from = { ...cam.current }, t0 = performance.now();
    const ease = (u) => u < 0.5 ? 4 * u * u * u : 1 - Math.pow(-2 * u + 2, 3) / 2;
    const step = (now) => { const u = Math.min(1, (now - t0) / dur), e = ease(u);
      cam.current = { x: from.x + (target.x - from.x) * e, y: from.y + (target.y - from.y) * e, k: from.k * Math.pow(target.k / from.k, e) };
      applyCam(); if (u < 1) rafRef.current = requestAnimationFrame(step); };
    rafRef.current = requestAnimationFrame(step);
  }
  function revealCam(id) {
    const n = T.byId[id], sibs = n.parent >= 0 ? T.byId[n.parent].kids.slice() : [];
    return camFor([id, n.parent >= 0 ? n.parent : id, ...sibs]);
  }
  // fit whenever the visible frontier changes
  React.useEffect(() => {
    if (firstFit.current) {
      firstFit.current = false;
      const target = reveal.current != null ? revealCam(reveal.current) : camFor(vis.ids);
      cam.current = { x: target.x, y: target.y, k: target.k * (reveal.current != null ? 0.45 : 1.6) }; applyCam();
      tweenTo(target, 1100);
      reveal.current = null;     // consume — later expansions fit to the frontier
    } else tweenTo(camFor(vis.ids), 760);
  }, [vis]);
  React.useLayoutEffect(() => { applyCam(); });

  // ---- render the frontier ----
  const heat = colorMode === 'heat';
  const fillOf = (n) => heat ? heatColor(n.heat) : (n.author === 'you' ? COL.you : COL.them);
  const scene = React.useMemo(() => {
    const edges = [], halos = [], dots = [], fronds = [];
    for (const id of vis.ids) {
      const n = T.byId[id]; if (id === viewRoot) continue;
      const p = T.byId[n.parent];
      const on = n.onSpine && p.onSpine;
      edges.push(<path key={'e' + id} d={linkPath(p, n)} stroke={heat ? heatColor((n.heat + p.heat) / 2) : (on ? COL.edgeHi : COL.edge)} strokeOpacity={on ? 0.85 : (heat ? 0.4 : 0.55)} strokeWidth={on ? 1.3 : 0.9} fill="none" strokeLinecap="round"/>);
    }
    if (heat) { const hot = [...vis.ids].map((i) => T.byId[i]).sort((a, b) => b.heat - a.heat).slice(0, 24).filter((n) => n.heat > 0.5);
      for (const n of hot) halos.push(<circle key={'g' + n.id} cx={n.x} cy={n.y} r={16 + n.heat * 32} fill={heatColor(n.heat)} fillOpacity={0.12 + n.heat * 0.14} style={n.heat > 0.82 ? { animation: 'lt-tw 2.6s ease-in-out infinite' } : undefined}/>); }
    for (const id of vis.ids) {
      const n = T.byId[id], collapsed = n.kids.length >= 2 && !isOpen(id), sel = id === selected, tip = id === T.tip, isRoot = id === viewRoot;
      if (collapsed) { fronds.push(<g key={'f' + id}><Frond n={n} fill={fillOf(n)}/></g>); continue; }
      const hot = tip || isRoot || sel;
      if (hot) dots.push(<circle key={'hl' + id} cx={n.x} cy={n.y} r={hot ? 10 : 0} fill={COL.sun} fillOpacity={0.13}/>);
      dots.push(<circle key={'n' + id} className="lt-grow" cx={n.x} cy={n.y} r={isRoot ? 5.5 : (hot ? 4.6 : nodeRadius(n))} fill={tip || sel ? COL.sun : fillOf(n)} fillOpacity={heat ? 0.5 + n.heat * 0.5 : 0.95} stroke={hot ? COL.sun2 : 'none'} strokeWidth={0.8}/>);
    }
    return <g>{halos}{edges}{fronds}{dots}</g>;
  }, [T, vis, selected, viewRoot, colorMode, expanded]);

  // labels: viewRoot, tip(if visible), selected, + a few open branch points
  const labelIds = React.useMemo(() => {
    const out = new Set([viewRoot]);
    if (vis.set.has(T.tip)) out.add(T.tip);
    if (selected != null && vis.set.has(selected)) out.add(selected);
    let added = 0;
    for (const id of vis.ids) { if (added >= 8) break; const n = T.byId[id]; if (n.kids.length >= 2 && !isOpen(id)) { out.add(id); added++; } }
    return [...out];
  }, [T, vis, selected, viewRoot, expanded]);

  // ---- live arrivals (C1/C3/B/A4) onto the visible frontier ----
  const [arrivals, setArrivals] = React.useState([]); const keyRef = React.useRef(0); const visRef = React.useRef(vis);
  visRef.current = vis;
  React.useEffect(() => {
    if (!live) { setArrivals([]); return; } let timer;
    const spawn = () => {
      const cand = visRef.current.ids.map((i) => T.byId[i]).filter((n) => n.depth < T.maxDepth - 1);
      let par = cand[0] || T.byId[T.tip];
      for (let i = 0; i < 7; i++) { const c = cand[Math.floor(Math.random() * cand.length)]; if (c && c.recency > par.recency * 0.6) par = c; }
      const burst = Math.random() < 0.28 ? 3 + Math.floor(Math.random() * 3) : 1, made = [];
      for (let i = 0; i < burst; i++) {
        const ang = par.angle + (burst > 1 ? (i - (burst - 1) / 2) * 0.12 : (Math.random() - 0.5) * 0.14);
        const rad = par.radius + T.RING * (0.85 + Math.random() * 0.35);
        made.push({ key: keyRef.current++, x: Math.cos(ang - Math.PI / 2) * rad, y: Math.sin(ang - Math.PI / 2) * rad, px: par.x, py: par.y, ang, rad, author: Math.random() < 0.5 ? 'you' : 'them', delay: i * 90 });
      }
      setArrivals((a) => [...a, ...made].slice(-22));
      timer = setTimeout(spawn, 2000 + Math.random() * 1600);
    };
    timer = setTimeout(spawn, 1100); return () => clearTimeout(timer);
  }, [live, T]);

  const liveLayer = arrivals.map((a) => {
    const p = { x: a.px, y: a.py, radius: Math.hypot(a.px, a.py), angle: Math.atan2(a.py, a.px) + Math.PI / 2 };
    const n = { x: a.x, y: a.y, radius: a.rad, angle: a.ang }, path = linkPath(p, n);
    return (
      <g key={a.key}>
        <path d={path} className="lt-draw" pathLength="1" style={{ strokeDasharray: 1, animationDelay: a.delay + 'ms' }} stroke={COL.edgeHi} strokeOpacity="0.9" strokeWidth="1.2" fill="none" strokeLinecap="round"/>
        <circle className="lt-sap" r="2.6" fill={COL.sun2} style={{ offsetPath: `path('${path}')`, animationDelay: a.delay + 'ms' }}/>
        <g transform={`translate(${a.px} ${a.py})`}><circle className="lt-ripple" r="13" fill="none" stroke={COL.sun} strokeWidth="1.2" style={{ animationDelay: a.delay + 'ms' }}/></g>
        <g transform={`translate(${a.x} ${a.y})`}><circle className="lt-bloom" r="2.8" fill={a.author === 'you' ? COL.you : COL.them} stroke={COL.sun2} strokeWidth="0.5" style={{ animationDelay: a.delay + 'ms' }}/></g>
      </g>
    );
  });

  // ---- interaction ----
  //   tap        → expand / collapse / select (fast path)
  //   press-hold → preview popover (works on touch AND mouse): opening line,
  //                hidden count, top reply, with Expand / Focus actions
  //   hover      → passive desktop tooltip (bonus)
  const [hover, setHover] = React.useState(null);
  const [preview, setPreview] = React.useState(null);
  const lastMove = React.useRef({ x: -99, y: -99 });
  const press = React.useRef({ timer: 0, x: 0, y: 0 });
  const suppressClick = React.useRef(false);

  function toWorld(e) {
    const r = e.currentTarget.getBoundingClientRect(), { x, y, k } = cam.current;
    const mx = e.clientX - r.left, my = e.clientY - r.top;
    return { mx, my, wx: (mx - CX) / k + x, wy: (my - CY) / k + y, k };
  }
  function nearestNode(wx, wy, k, frondOnly) {
    let best = null, bd = 1e9;
    for (const id of vis.ids) { const nd = T.byId[id]; if (frondOnly && !(nd.kids.length >= 2 && !isOpen(id))) continue; const d = (nd.x - wx) ** 2 + (nd.y - wy) ** 2; if (d < bd) { bd = d; best = nd; } }
    return best && Math.sqrt(bd) * k < (frondOnly ? 26 : 22) ? best : null;
  }
  function handleMove(e) {
    const { mx, my, wx, wy, k } = toWorld(e);
    if (press.current.timer && (Math.abs(mx - press.current.x) > 8 || Math.abs(my - press.current.y) > 8)) { clearTimeout(press.current.timer); press.current.timer = 0; }
    if (Math.abs(mx - lastMove.current.x) < 4 && Math.abs(my - lastMove.current.y) < 4) return;
    lastMove.current = { x: mx, y: my };
    const f = nearestNode(wx, wy, k, true); setHover(f ? { id: f.id, sx: mx, sy: my } : null);
  }
  function onDown(e) {
    const { mx, my, wx, wy, k } = toWorld(e); const node = nearestNode(wx, wy, k, false);
    press.current = { x: mx, y: my, timer: 0 }; if (!node) return;
    press.current.timer = setTimeout(() => { press.current.timer = 0; suppressClick.current = true; setHover(null); setPreview({ id: node.id, sx: mx, sy: my }); }, 420);
  }
  function onUp() { if (press.current.timer) { clearTimeout(press.current.timer); press.current.timer = 0; } }
  function expandId(id) { const next = new Set(expanded); next.add(id); setExpanded(next); }
  function collapseId(id) { const next = new Set(expanded); T.subtreeIds(id).forEach((s) => next.delete(s)); setExpanded(next); }
  function handleClick(e) {
    if (suppressClick.current) { suppressClick.current = false; return; }
    const { wx, wy, k } = toWorld(e); const best = nearestNode(wx, wy, k, false);
    if (!best) { setSelected(null); return; }
    setSelected(best.id);
    if (best.kids.length >= 2 && best.id !== viewRoot) { if (isOpen(best.id)) collapseId(best.id); else expandId(best.id); }
  }

  const selNode = selected != null ? T.byId[selected] : null;
  const selCollapsed = selNode && selNode.kids.length >= 2 && !isOpen(selected);

  return (
    <div style={{ position: 'absolute', inset: 0, overflow: 'hidden', background: 'radial-gradient(120% 90% at 50% 46%, #0f1c0e 0%, #07100A 78%)' }}>
      <svg width={W} height={H} onClick={handleClick} onMouseMove={handleMove} onMouseLeave={() => setHover(null)}
        onPointerDown={onDown} onPointerUp={onUp} onPointerCancel={onUp} onContextMenu={(e) => e.preventDefault()}
        style={{ position: 'absolute', inset: 0, cursor: 'pointer', display: 'block', touchAction: 'none' }}>
        <g ref={gRef}>{scene}{liveLayer}</g>
      </svg>

      {/* frond hover preview — passive desktop tooltip (§6.A #12) */}
      {hover && !preview && (() => {
        const n = T.byId[hover.id], top = n.kids.map((k) => T.byId[k]).sort((a, b) => b.size - a.size)[0];
        const below = hover.sy < 96;
        return (
          <div style={{ position: 'absolute', left: hover.sx, top: hover.sy, pointerEvents: 'none', zIndex: 6,
            transform: `translate(-50%, ${below ? 'calc(0% + 16px)' : 'calc(-100% - 14px)'})`, maxWidth: 236,
            padding: '8px 11px', borderRadius: 10, background: 'rgba(7,16,10,.96)', border: `0.5px solid ${COL.sun}`, boxShadow: '0 10px 26px rgba(0,0,0,.55)' }}>
            <div style={{ font: '600 8.5px var(--font-sans)', letterSpacing: '.1em', textTransform: 'uppercase', color: COL.sun2, marginBottom: 4 }}>+{n.size} hidden · {n.leaves} leaves</div>
            <div style={{ font: 'italic 400 12px/1.35 var(--font-serif)', color: '#EDEFE6', marginBottom: top ? 5 : 0 }}>{n.text}</div>
            {top && <div style={{ display: 'flex', alignItems: 'center', gap: 6, font: '400 11px var(--font-sans)', color: '#8A9678' }}>
              <span style={{ width: 5, height: 5, borderRadius: '50%', flexShrink: 0, background: top.author === 'you' ? COL.you : COL.them }}/>
              top reply: “{top.text.length > 26 ? top.text.slice(0, 25) + '…' : top.text}”</div>}
          </div>
        );
      })()}

      {/* press-and-hold preview — touch-friendly: actions + top reply */}
      {preview && (() => {
        const n = T.byId[preview.id], collapsed = n.kids.length >= 2 && !isOpen(preview.id);
        const top = n.kids.length ? n.kids.map((k) => T.byId[k]).sort((a, b) => b.size - a.size)[0] : null;
        const below = preview.sy < 150, cx = Math.max(124, Math.min(W - 124, preview.sx));
        return (
          <React.Fragment>
            <div onClick={() => setPreview(null)} style={{ position: 'absolute', inset: 0, zIndex: 8, background: 'transparent' }}/>
            <div style={{ position: 'absolute', left: cx, top: preview.sy, zIndex: 9, width: 244,
              transform: `translate(-50%, ${below ? '18px' : 'calc(-100% - 16px)'})`,
              padding: '12px 13px', borderRadius: 13, background: 'rgba(9,18,9,.98)', border: `0.5px solid ${COL.sun}`, boxShadow: '0 14px 34px rgba(0,0,0,.6)' }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 6 }}>
                <span style={{ width: 6, height: 6, borderRadius: '50%', background: n.author === 'you' ? COL.you : COL.them }}/>
                <span style={{ font: '600 8.5px var(--font-sans)', letterSpacing: '.1em', textTransform: 'uppercase', color: COL.sun2 }}>
                  {collapsed ? `+${n.size} hidden · ${n.leaves} leaves` : (n.kids.length ? `${n.size} in branch` : 'leaf')}
                </span>
              </div>
              <div style={{ font: 'italic 400 13px/1.4 var(--font-serif)', color: '#EDEFE6', marginBottom: top ? 7 : 10 }}>{n.text}</div>
              {top && <div style={{ display: 'flex', alignItems: 'flex-start', gap: 6, marginBottom: 10, font: '400 11.5px/1.4 var(--font-sans)', color: '#8A9678' }}>
                <span style={{ width: 5, height: 5, borderRadius: '50%', marginTop: 5, flexShrink: 0, background: top.author === 'you' ? COL.you : COL.them }}/>
                <span>top reply: “{top.text.length > 34 ? top.text.slice(0, 33) + '…' : top.text}”</span></div>}
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 7 }}>
                {(() => {
                  const open = isOpen(preview.id), isBranch = n.kids.length >= 2 && preview.id !== viewRoot;
                  const hasDeep = open && isBranch && T.subtreeIds(preview.id).some((s) => s !== preview.id && T.byId[s].kids.length >= 2 && !isOpen(s));
                  const primary = { flex: '1 1 44%', cursor: 'pointer', padding: '8px 10px', borderRadius: 9, background: '#2C5F2E', border: '0.5px solid #4F8E51', font: '600 11.5px var(--font-sans)', color: '#F2EDE4' };
                  const secondary = { ...primary, background: '#0F1A09', border: '0.5px solid #2D3A22', color: '#C9D2BB' };
                  const close = () => setPreview(null);
                  return (<React.Fragment>
                    {isBranch && !open && <button style={primary} onClick={() => { expandId(preview.id); setSelected(preview.id); close(); }}>Expand ▸</button>}
                    {isBranch && open && <button style={primary} onClick={() => { collapseId(preview.id); setSelected(preview.id); close(); }}>Collapse</button>}
                    {hasDeep && <button style={secondary} onClick={() => { const next = new Set(expanded); T.subtreeIds(preview.id).forEach((s) => { if (T.byId[s].kids.length >= 2) next.add(s); }); setExpanded(next); close(); }}>Expand all</button>}
                    {n.kids.length >= 1 && preview.id !== viewRoot && <button style={secondary} onClick={() => { onFocus(preview.id); close(); }}>Focus ⤢</button>}
                    <button style={secondary} onClick={() => { onOpenChat && onOpenChat(preview.id); close(); }}>View in chat 💬</button>
                  </React.Fragment>);
                })()}
              </div>
            </div>
          </React.Fragment>
        );
      })()}

      <div ref={labelLayer} style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>
        {labelIds.map((id) => {
          const n = T.byId[id], collapsed = n.kids.length >= 2 && !isOpen(id), hot = id === T.tip || id === viewRoot || id === selected;
          const txt = n.text.length > 24 ? n.text.slice(0, 23) + '…' : n.text;
          return (
            <div key={id} data-id={id} style={{ position: 'absolute', left: 0, top: 0, opacity: 0, transform: 'translate(-9999px,0)', willChange: 'transform' }}>
              <div style={{ transform: 'translate(9px,-50%)', display: 'inline-flex', alignItems: 'center', gap: 5, whiteSpace: 'nowrap', padding: hot ? '3px 9px' : '2px 8px', borderRadius: 999, background: hot ? 'rgba(58,42,7,.92)' : 'rgba(7,16,10,.84)', border: `0.5px solid ${hot ? COL.sun : '#2D3A22'}`, font: `${hot ? 500 : 400} 10.5px var(--font-serif)`, fontStyle: 'italic', color: hot ? COL.sun2 : '#C9D2BB' }}>
                <span style={{ width: 5, height: 5, borderRadius: '50%', flexShrink: 0, background: n.author === 'you' ? COL.you : COL.them }}/>
                {id === T.tip ? '✦ ' + txt : txt}
                {collapsed && <span style={{ font: '500 9px var(--font-mono)', color: '#76AB78' }}>+{n.size} ▸</span>}
              </div>
            </div>
          );
        })}
      </div>

      {/* minimap — symbolic whole-shape reference (2px dots), visible region lit */}
      <div style={{ position: 'absolute', top: 12, right: 12, width: MINI.box, height: MINI.box, background: 'rgba(7,16,10,.8)', border: '0.5px solid #2D3A22', borderRadius: 12, overflow: 'hidden' }}>
        <svg width={MINI.box} height={MINI.box}>
          {T.mini.map((d, i) => <circle key={i} cx={MINI.px(d.x)} cy={MINI.py(d.y)} r={d.s ? 1.3 : 0.9} fill={heat ? COL.sun : (d.s ? COL.sun : (d.a === 'you' ? COL.you : COL.them))} fillOpacity={d.s ? 1 : 0.45}/>)}
          {vis.ids.slice(0, 400).map((id) => { const n = T.byId[id]; return <circle key={'v' + id} cx={MINI.px(n.x)} cy={MINI.py(n.y)} r={1.2} fill={COL.sun2} fillOpacity={0.9}/>; })}
          {arrivals.slice(-10).map((a) => <circle key={'f' + a.key} cx={MINI.px(a.x)} cy={MINI.py(a.y)} r={1.8} fill={COL.sun2} className="lt-fly" style={{ animationDelay: a.delay + 'ms' }}/>)}
          <rect ref={vpRectRef} x={0} y={0} width={10} height={10} rx={2} fill="none" stroke={COL.fog3} strokeOpacity={0.7} strokeWidth={0.8}/>
        </svg>
        <span style={{ position: 'absolute', bottom: 4, left: 8, font: '500 7.5px var(--font-mono)', color: '#586552', letterSpacing: '0.1em' }}>{T.total} NODES</span>
      </div>

      {heat && (
        <div style={{ position: 'absolute', left: 12, top: 12, display: 'flex', gap: 11, padding: '6px 11px', background: 'rgba(11,19,6,0.82)', border: '0.5px solid #2D3A22', borderRadius: 999, font: '400 9.5px var(--font-mono)', color: '#8A9678' }}>
          {[['hot', '#D4960C'], ['warm', '#C9A23C'], ['cool', '#76AB78'], ['cold', '#37472D']].map(([l, c]) => <span key={l} style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}><span style={{ width: 7, height: 7, borderRadius: '50%', background: c }}/>{l}</span>)}
        </div>
      )}

      {/* bottom bar — the sustainability proof + selection actions */}
      <div style={{ position: 'absolute', left: 10, right: 10, bottom: 10, display: 'flex', alignItems: 'center', gap: 8 }}>
        <span style={{ flexShrink: 0, padding: '6px 11px', borderRadius: 999, background: 'rgba(11,19,6,0.86)', border: '0.5px solid #2D3A22', font: '500 10px var(--font-mono)', color: '#C9D2BB' }}>
          rendering <b style={{ color: COL.sun2 }}>{vis.ids.length}</b> of {T.total.toLocaleString()}
        </span>
        {selNode ? (
          <div style={{ flex: 1, minWidth: 0, display: 'flex', alignItems: 'center', gap: 8, padding: '5px 8px 5px 12px', borderRadius: 999, background: 'rgba(11,19,6,0.86)', border: '0.5px solid #2D3A22' }}>
            <span style={{ flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', font: 'italic 400 11px var(--font-serif)', color: '#C9D2BB' }}>
              {selCollapsed ? `+${selNode.size} hidden · tap to expand` : (selNode.kids.length >= 2 ? `${selNode.size} in this branch` : selNode.text)}
            </span>
            {selNode.kids.length >= 1 && selected !== viewRoot && (
              <button onClick={() => onFocus(selected)} style={{ flexShrink: 0, cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 5, padding: '4px 10px', borderRadius: 999, background: '#2C5F2E', border: '0.5px solid #4F8E51', font: '600 10px var(--font-sans)', color: '#F2EDE4' }}>
                focus ⤢
              </button>
            )}
          </div>
        ) : (
          <span style={{ flex: 1, font: 'italic 400 11px var(--font-serif)', color: '#586552', textAlign: 'right', paddingRight: 6 }}>
            {live ? 'replies arriving — watch the frontier grow' : 'tap a frond to expand · press & hold to preview'}
          </span>
        )}
      </div>
    </div>
  );
}

window.TreeView = TreeView;
