// ─────────────────────────────────────────────────────────────
// FIVE BRAINSTORM VARIANTS for the personal-DM TREE view.
//
// Constraint: the Tree view is a CONDENSED OVERVIEW of the conversation
// structure, not the primary read/write surface (that's Canvas). So
// every variant prioritises readability of the roots over rendering
// every single node. 332 total nodes, but only ~16 root branches —
// the variants surface the 16, not the 332.
//
// Shared shell:
//   AndroidPhone
//     DMTopBar     (avatar+ · Canvas/Tree/Manage · Load older)
//     DMHeaderCard (back · M · marina_k · 332 nodes · tree)
//     <variant body>
//     SelectedNodePanel (pinned bottom)
// ─────────────────────────────────────────────────────────────


// Pick one root to render as "selected" in the bottom panel — same
// across all variants so the visual diff is just the tree treatment.
const SELECTED = DM_ROOTS.find(r => r.id === 'n02');


// ════════════════════════════════════════════════════════════
// SHELL — wraps each variant body with the common chrome
// ════════════════════════════════════════════════════════════
function VariantShell({ variantTag, children, hideSelected = false }) {
  return (
    <div style={{ flex: 1, display: 'flex', flexDirection: 'column',
                  minHeight: 0, background: '#07100A', overflow: 'hidden' }}>
      <DMTopBar/>
      <DMHeaderCard variantTag={variantTag}/>
      <div style={{ flex: 1, minHeight: 0, overflow: 'hidden', position: 'relative' }}>
        {children}
      </div>
      {!hideSelected && <SelectedNodePanel node={SELECTED}/>}
    </div>
  );
}


// ════════════════════════════════════════════════════════════
// A · CHRONICLE
//   Read-first. The tree is a vertical chronicle of root-card
//   entries — each one a literary quote from the message + meta.
//   The "tree-ness" is implied by depth/branches counts and a
//   thin trailing leaf-line. Zero noise.
// ════════════════════════════════════════════════════════════
function TreeA_Chronicle() {
  return (
    <VariantShell variantTag="A · chronicle">
      <div style={{ height: '100%', overflowY: 'auto',
                    padding: '4px 14px 10px',
                    display: 'flex', flexDirection: 'column', gap: 4 }}>
        <div style={{ font: 'italic 400 12px/1.45 var(--font-serif)', color: '#8A9678',
                      padding: '4px 4px 8px' }}>
          Sixteen places this conversation grew. Newest first.
        </div>
        {DM_ROOTS.slice(0, 9).map((r, i) => (
          <ChronicleRow key={r.id} root={r} selected={r.id === SELECTED.id}/>
        ))}
        <div style={{
          margin: '8px 4px 0', padding: '8px 12px',
          background: 'transparent', border: '0.5px dashed #2D3A22', borderRadius: 10,
          font: 'italic 400 11.5px var(--font-serif)', color: '#586552',
          textAlign: 'center',
        }}>+ 7 older roots</div>
      </div>
    </VariantShell>
  );
}

function ChronicleRow({ root, selected = false }) {
  return (
    <button style={{
      display: 'flex', alignItems: 'stretch', gap: 12,
      padding: '12px 10px 12px 8px',
      background: 'transparent',
      borderTop: '0.5px solid #14210C',
      cursor: 'pointer', textAlign: 'left', width: '100%',
      border: 'none',
      borderLeft: selected ? '2px solid #D4960C' : '2px solid transparent',
    }}>
      {/* Date column */}
      <div style={{ width: 36, paddingTop: 2, flexShrink: 0,
                    textAlign: 'right' }}>
        <div style={{ font: '500 12px var(--font-mono)', color: '#C9D2BB' }}>
          {root.age}
        </div>
        <div style={{ font: 'italic 400 9.5px var(--font-serif)', color: '#586552',
                      marginTop: 1 }}>
          {root.from === 'you' ? 'you' : 'M'}
        </div>
      </div>
      {/* Mid gutter: from-dot + thin trailing line */}
      <div style={{ width: 8, display: 'flex', flexDirection: 'column',
                    alignItems: 'center', paddingTop: 6 }}>
        <FromDot from={root.from} size={8}/>
        <span style={{ flex: 1, width: 0.5, background: '#1F2B16',
                       marginTop: 4 }}/>
      </div>
      {/* Body */}
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ font: 'italic 400 14px/1.45 var(--font-serif)',
                      color: selected ? '#F2EDE4' : '#C9D2BB',
                      marginBottom: 4 }}>
          {root.body}
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8,
                      font: '400 10.5px var(--font-mono)', color: '#586552' }}>
          <BranchPip n={root.branches}/>
          <span>· {root.leaves} below</span>
          <span style={{ marginLeft: 'auto' }}>
            depth {root.depth}
          </span>
        </div>
      </div>
    </button>
  );
}

// A tiny inline pip-row: a few short vertical ticks whose count
// caps at 6 — gives a sense of "how busy this root got" without
// drawing a real tree.
function BranchPip({ n, max = 6, color = '#76AB78' }) {
  const pips = Math.min(n, max);
  return (
    <span style={{ display: 'inline-flex', alignItems: 'flex-end', gap: 2,
                   height: 10 }}>
      {Array.from({ length: pips }).map((_, i) => (
        <span key={i} style={{
          width: 2, height: 3 + (i * 1.2), background: color, opacity: 0.7,
          borderRadius: 1,
        }}/>
      ))}
      <span style={{ marginLeft: 4, font: '500 10.5px var(--font-mono)',
                     color: '#C9D2BB' }}>{n}</span>
    </span>
  );
}


// ════════════════════════════════════════════════════════════
// B · TRUNK
//   A vertical organic gradient trunk down the center; root
//   cards alternate L/R hanging off it via a soft spur. Looks
//   like a living tree, but only 12-ish elements are rendered
//   so it never becomes the dot-spaghetti of the original.
// ════════════════════════════════════════════════════════════
function TreeB_Trunk() {
  const visible = DM_ROOTS.slice(0, 8);
  const SLOT_H = 80;
  const TRUNK_X = 195;
  return (
    <VariantShell variantTag="B · trunk">
      <div style={{ height: '100%', overflowY: 'auto', position: 'relative' }}>
        <div style={{ font: 'italic 400 12px var(--font-serif)', color: '#8A9678',
                      padding: '8px 16px 4px' }}>
          The trunk you and Marina have been growing.
        </div>
        <div style={{ position: 'relative', height: visible.length * SLOT_H + 60,
                      minHeight: '100%' }}>
          {/* Trunk SVG */}
          <svg width="390" height={visible.length * SLOT_H + 60}
               style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>
            <defs>
              <linearGradient id="trunk-b" x1="0" y1="0" x2="0" y2="1">
                <stop offset="0%"  stopColor="#D4960C" stopOpacity="0.6"/>
                <stop offset="20%" stopColor="#76AB78" stopOpacity="0.55"/>
                <stop offset="70%" stopColor="#5E8048" stopOpacity="0.4"/>
                <stop offset="100%" stopColor="#445239" stopOpacity="0.18"/>
              </linearGradient>
            </defs>
            <path d={`M ${TRUNK_X} 8
                     C ${TRUNK_X - 8} 120, ${TRUNK_X + 10} 220, ${TRUNK_X - 4} 340
                     S ${TRUNK_X + 6} 540, ${TRUNK_X} ${visible.length * SLOT_H + 50}`}
              stroke="url(#trunk-b)" strokeWidth="1.6" fill="none" strokeLinecap="round"/>
            {/* Spurs from trunk to each card */}
            {visible.map((r, i) => {
              const y = 28 + i * SLOT_H;
              const left = i % 2 === 0;
              const cardX = left ? 18 : 222;
              const cardW = 150;
              const cardCenterX = cardX + cardW - 6;
              const x1 = left ? cardCenterX : cardX + 6;
              const cx = (x1 + TRUNK_X) / 2;
              return (
                <g key={r.id}>
                  <path d={`M ${TRUNK_X} ${y + 18}
                            Q ${cx} ${y + 18}, ${x1} ${y + 24}`}
                    stroke={r.id === SELECTED.id ? '#D4960C' : '#5E8048'}
                    strokeOpacity={r.id === SELECTED.id ? 0.8 : (0.35 + ageWeight(r.age) * 0.35)}
                    strokeWidth={r.id === SELECTED.id ? 1.4 : 1}
                    fill="none" strokeLinecap="round"/>
                  <circle cx={TRUNK_X} cy={y + 18} r={r.id === SELECTED.id ? 4 : 3}
                    fill={r.id === SELECTED.id ? '#D4960C' : '#76AB78'}
                    fillOpacity={r.id === SELECTED.id ? 1 : 0.7}/>
                </g>
              );
            })}
            {/* Roots / mycelium at bottom */}
            {Array.from({ length: 9 }).map((_, i) => {
              const a = (i / 9) * Math.PI;
              const x = TRUNK_X + Math.cos(a + Math.PI) * (12 + (i % 3) * 8);
              const y = visible.length * SLOT_H + 30 + Math.sin(a) * 14;
              return <circle key={i} cx={x} cy={y} r="1.6" fill="#5E8048"
                             fillOpacity={0.3 + (i % 4) * 0.08}/>;
            })}
          </svg>
          {/* Cards */}
          {visible.map((r, i) => {
            const y = 28 + i * SLOT_H;
            const left = i % 2 === 0;
            return (
              <TrunkCard key={r.id} root={r} y={y} left={left}
                selected={r.id === SELECTED.id}/>
            );
          })}
          <div style={{
            position: 'absolute', top: visible.length * SLOT_H + 22, left: 0, right: 0,
            display: 'flex', justifyContent: 'center',
          }}>
            <span style={{
              padding: '4px 10px', background: '#0F1A09',
              border: '0.5px solid #2D3A22', borderRadius: 999,
              font: 'italic 400 10.5px var(--font-serif)', color: '#586552',
            }}>+ 8 older · roots & leaves below</span>
          </div>
        </div>
      </div>
    </VariantShell>
  );
}

function TrunkCard({ root, y, left, selected }) {
  return (
    <div style={{
      position: 'absolute', top: y, width: 150,
      [left ? 'left' : 'right']: 18,
      padding: '8px 10px',
      background: selected ? '#243319' : '#0F1A09',
      border: selected ? '0.5px solid #D4960C' : '0.5px solid #1F2B16',
      borderRadius: 12,
      boxShadow: selected ? '0 0 0 3px rgba(212,150,12,0.18)' : 'none',
      textAlign: left ? 'right' : 'left',
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 6,
                    justifyContent: left ? 'flex-end' : 'flex-start',
                    marginBottom: 3 }}>
        {!left && <FromDot from={root.from} size={6}/>}
        <span style={{ font: '500 10px var(--font-mono)', color: '#8A9678' }}>{root.age}</span>
        {left && <FromDot from={root.from} size={6}/>}
      </div>
      <div style={{
        font: 'italic 400 12px/1.35 var(--font-serif)',
        color: selected ? '#F2EDE4' : '#C9D2BB',
        display: '-webkit-box', WebkitBoxOrient: 'vertical',
        WebkitLineClamp: 2, overflow: 'hidden', marginBottom: 4,
      }}>{root.body}</div>
      <div style={{ font: '400 9.5px var(--font-mono)', color: '#586552' }}>
        {root.branches} br · {root.leaves} lv
      </div>
    </div>
  );
}


// ════════════════════════════════════════════════════════════
// C · STRATA
//   Time bands. Recent bands expanded with detail; older bands
//   compress into a single horizontal strip of tiny pebble chips
//   (from-dot + opening words + leaf count). The information
//   density is honest — newer matters more, older exists.
// ════════════════════════════════════════════════════════════
function TreeC_Strata() {
  const grouped = AGE_BANDS.map(b => ({
    ...b, roots: DM_ROOTS.filter(r => r.ageBand === b.id),
  }));
  return (
    <VariantShell variantTag="C · strata">
      <div style={{ height: '100%', overflowY: 'auto',
                    padding: '4px 12px 12px' }}>
        {grouped.map((band, bi) => {
          const compressed = bi >= 2;
          return (
            <div key={band.id} style={{ marginBottom: 12 }}>
              {/* Band header */}
              <div style={{
                display: 'flex', alignItems: 'baseline', gap: 8,
                padding: '8px 4px 6px',
              }}>
                <span style={{
                  font: '500 10px var(--font-sans)',
                  letterSpacing: '0.14em', textTransform: 'uppercase',
                  color: bi === 0 ? '#F4DCA0' : '#76AB78',
                }}>{band.label}</span>
                <span style={{ font: 'italic 400 11px var(--font-serif)',
                               color: '#586552' }}>· {band.detail}</span>
                <span style={{ flex: 1, height: 0.5, background: '#1F2B16',
                               marginLeft: 4 }}/>
                <span style={{ font: '400 10px var(--font-mono)', color: '#586552' }}>
                  {band.roots.length}
                </span>
              </div>

              {compressed ? (
                /* Compact strip — tiny pebble chips, message head + leaf count */
                <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6,
                              padding: '2px 2px 2px' }}>
                  {band.roots.map(r => (
                    <button key={r.id} style={{
                      display: 'inline-flex', alignItems: 'center', gap: 5,
                      padding: '5px 9px', cursor: 'pointer',
                      background: '#0F1A09', border: '0.5px solid #1F2B16',
                      borderRadius: 999,
                      font: 'italic 400 11px var(--font-serif)', color: '#C9D2BB',
                      maxWidth: 170,
                    }}>
                      <FromDot from={r.from} size={6}/>
                      <span style={{ overflow: 'hidden',
                                     textOverflow: 'ellipsis',
                                     whiteSpace: 'nowrap' }}>
                        {r.head}
                      </span>
                      <span style={{ font: '400 9.5px var(--font-mono)',
                                     color: '#586552' }}>· {r.leaves}</span>
                    </button>
                  ))}
                </div>
              ) : (
                /* Expanded — root cards */
                <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                  {band.roots.map(r => (
                    <StrataRow key={r.id} root={r} selected={r.id === SELECTED.id}/>
                  ))}
                </div>
              )}
            </div>
          );
        })}
      </div>
    </VariantShell>
  );
}

function StrataRow({ root, selected = false }) {
  return (
    <button style={{
      display: 'flex', alignItems: 'center', gap: 10,
      padding: '10px 12px', cursor: 'pointer', textAlign: 'left', width: '100%',
      background: selected ? '#243319' : '#0F1A09',
      border: selected ? '0.5px solid #D4960C' : '0.5px solid #1F2B16',
      borderRadius: 12,
      boxShadow: selected ? '0 0 0 3px rgba(212,150,12,0.16)' : 'none',
    }}>
      {/* Depth-bar: 1-5 vertical pips, taller = deeper thread */}
      <span style={{
        display: 'inline-flex', flexDirection: 'column-reverse',
        gap: 1.5, height: 22, justifyContent: 'flex-start',
        flexShrink: 0,
      }}>
        {Array.from({ length: Math.min(root.depth, 5) }).map((_, i) => (
          <span key={i} style={{
            width: 4, height: 3 + i,
            background: depthColor(i + 1),
            opacity: 0.8, borderRadius: 1,
          }}/>
        ))}
      </span>
      <FromDot from={root.from} size={8}/>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ font: 'italic 400 13px/1.35 var(--font-serif)',
                      color: selected ? '#F2EDE4' : '#C9D2BB',
                      overflow: 'hidden', textOverflow: 'ellipsis',
                      whiteSpace: 'nowrap', marginBottom: 2 }}>
          {root.body}
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8,
                      font: '400 10.5px var(--font-mono)', color: '#586552' }}>
          <FromDot from={root.from} size={5}/>
          <span>{root.from === 'you' ? 'you' : 'M'}</span>
          <span>· {root.age}</span>
          <span style={{ marginLeft: 'auto', color: '#76AB78' }}>
            {root.branches} br · {root.leaves} lv
          </span>
        </div>
      </div>
    </button>
  );
}


// Small reusable footer pill — variants use it to declare their
// scaling strategy at large node counts.
function ScaleHint({ children, color = '#8A9678', icon }) {
  return (
    <div style={{
      display: 'inline-flex', alignItems: 'center', gap: 7,
      padding: '6px 10px',
      background: 'rgba(11,19,6,0.85)',
      border: '0.5px solid #1F2B16', borderRadius: 999,
      font: '500 10px var(--font-sans)', color,
      letterSpacing: '0.06em', textTransform: 'uppercase',
    }}>
      {icon}
      <span style={{ textTransform: 'none', letterSpacing: 'normal',
                     font: 'italic 400 11px var(--font-serif)',
                     color: '#C9D2BB' }}>{children}</span>
    </div>
  );
}


// ════════════════════════════════════════════════════════════
// D · CONSTELLATION
//   Spatial map. Each root is a brand-mark ring; core colour =
//   who sent it (canopy=you, dew=them); ring size = leaves;
//   brightness = recency. Labels are the first words of each
//   message — the only honest label we can show (no classifier).
//
//   Scaling: at 1k+ nodes / 200+ roots the same layout becomes
//   a pinch-to-zoom canvas with date anchors on the side and
//   labels surfaced only on tap. Today's-view is the "zoomed
//   in" sample shown here.
// ════════════════════════════════════════════════════════════
function TreeD_Constellation() {
  // Position roots along an organic descending S-curve. Layout is
  // hand-tuned to *feel* grown rather than plotted.
  const W = 390, H = 520;
  const placed = DM_ROOTS.map((r, i) => {
    const t = i / (DM_ROOTS.length - 1);
    const y = 30 + t * (H - 60);
    const sway = Math.sin(i * 0.85 + 0.3) * 95 + Math.sin(i * 1.8) * 30;
    const x = W / 2 + sway;
    const r0 = Math.max(7, Math.min(18, Math.sqrt(r.leaves) * 2.2));
    return { ...r, x, y, r: r0 };
  });

  // Date anchors — the only chronological scaffolding. Placed
  // alongside the chart so the user has an absolute reference.
  const dateAnchors = [
    { y: 30,  label: 'today' },
    { y: 140, label: '1 wk' },
    { y: 250, label: '1 mo' },
    { y: 360, label: '2 mo' },
    { y: 470, label: 'older' },
  ];

  // Find labels that are safe to show without collision:
  //   always: selected root, the largest root, hottest root (activeNow>0)
  const labelIds = new Set();
  labelIds.add(SELECTED.id);
  const biggest = [...placed].sort((a, b) => b.leaves - a.leaves)[0];
  if (biggest) labelIds.add(biggest.id);
  placed.filter(p => p.activeNow > 0).forEach(p => labelIds.add(p.id));

  return (
    <VariantShell variantTag="D · constellation">
      <div style={{ height: '100%', overflow: 'hidden', position: 'relative' }}>
        <div style={{ font: 'italic 400 12px var(--font-serif)', color: '#8A9678',
                      padding: '8px 16px 0' }}>
          The shape of what you and Marina have planted.
        </div>
        <svg width={W} height={H} viewBox={`0 0 ${W} ${H}`}
             style={{ display: 'block' }}>
          <defs>
            <radialGradient id="cons-glow" cx="50%" cy="40%" r="55%">
              <stop offset="0%"  stopColor="#1F3320" stopOpacity="0.55"/>
              <stop offset="100%" stopColor="#07100A" stopOpacity="0"/>
            </radialGradient>
          </defs>
          <rect x="0" y="0" width={W} height={H} fill="url(#cons-glow)"/>

          {/* Date anchors */}
          {dateAnchors.map(a => (
            <g key={a.label}>
              <line x1="14" y1={a.y} x2="22" y2={a.y}
                stroke="#445239" strokeOpacity="0.6" strokeWidth="0.6"/>
              <text x="8" y={a.y + 3}
                    style={{ font: 'italic 400 9.5px var(--font-serif)',
                             fill: '#586552' }}>{a.label}</text>
            </g>
          ))}

          {/* The dots themselves */}
          {placed.map((p) => {
            const isSel = p.id === SELECTED.id;
            const hot = p.activeNow > 0;
            const fromColor = FROM_COLORS[p.from];
            return (
              <g key={p.id}>
                {(isSel || hot) && (
                  <circle cx={p.x} cy={p.y} r={p.r + 9}
                    fill={isSel ? '#D4960C' : '#4F8E51'}
                    fillOpacity={isSel ? 0.16 : 0.10}/>
                )}
                <circle cx={p.x} cy={p.y} r={p.r}
                  stroke={isSel ? '#F4DCA0' : '#4F8E51'}
                  strokeOpacity={isSel ? 0.9 : (0.30 + ageWeight(p.age) * 0.4)}
                  strokeWidth="0.8" fill="none"/>
                <circle cx={p.x} cy={p.y} r={p.r * 0.6}
                  stroke={isSel ? '#F4DCA0' : '#76AB78'}
                  strokeOpacity={isSel ? 0.75 : (0.35 + ageWeight(p.age) * 0.3)}
                  strokeWidth="0.9" fill="none"/>
                <circle cx={p.x} cy={p.y} r={Math.max(2, p.r * 0.28)}
                  fill={isSel ? '#D4960C' : fromColor}
                  fillOpacity={0.55 + ageWeight(p.age) * 0.45}/>
              </g>
            );
          })}

          {/* Labels — only the selected/largest/hot get one; rest are
              tap-to-reveal. Label is the first words of the message,
              the only honest label we have (no classifier). */}
          {placed.filter(p => labelIds.has(p.id)).map(p => {
            const isSel = p.id === SELECTED.id;
            const leftOfTrunk = p.x < W / 2;
            return (
              <text key={p.id + '-l'}
                    x={p.x + (leftOfTrunk ? -(p.r + 6) : (p.r + 6))}
                    y={p.y + 3}
                    textAnchor={leftOfTrunk ? 'end' : 'start'}
                    style={{
                      font: `italic 400 10.5px var(--font-serif)`,
                      fill: isSel ? '#F4DCA0' : '#C9D2BB',
                    }}>{p.head}</text>
            );
          })}
        </svg>
        <div style={{
          position: 'absolute', left: 14, right: 14, bottom: 10,
          display: 'flex', flexDirection: 'column', gap: 6,
        }}>
          <div style={{
            padding: '7px 10px',
            background: 'rgba(11,19,6,0.85)',
            border: '0.5px solid #1F2B16', borderRadius: 999,
            font: 'italic 400 11px var(--font-serif)', color: '#8A9678',
            display: 'flex', alignItems: 'center', gap: 8,
            justifyContent: 'center',
          }}>
            <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}>
              <span style={{ width: 7, height: 7, borderRadius: '50%',
                             background: '#4F8E51' }}/> you
              <span style={{ width: 7, height: 7, borderRadius: '50%',
                             background: '#A8C5A0', marginLeft: 6 }}/> them
            </span>
            <span>· ring size = leaves · tap to label</span>
          </div>
          <div style={{ display: 'flex', justifyContent: 'center' }}>
            <ScaleHint icon={
              <span style={{ font: '500 11px var(--font-sans)', color: '#76AB78' }}>⊕</span>
            }>at 200+ roots: pinch-zoom + lazy labels</ScaleHint>
          </div>
        </div>
      </div>
    </VariantShell>
  );
}


// ════════════════════════════════════════════════════════════
// E · GARDEN
//   Each root is its own small "plant" tile — a brand-mark glyph
//   tiled in a 3-wide grid. Core colour = who sent it (canopy=you,
//   dew=them); outer-ring density = how much grew off it; leaf
//   dots = branch count. Caption = first words of the message
//   (no classifier).
//
//   Scaling: groups by time-band with collapsible older sections.
//   "today / this week" stay expanded; older months collapse to a
//   single line you can grow. So a 1k-node DM with 60+ roots stays
//   3 sections deep at default.
// ════════════════════════════════════════════════════════════
function TreeE_Garden() {
  const [collapsed, setCollapsed] = React.useState({
    'this-month': false, 'earlier': true, 'older': true,
  });
  const grouped = AGE_BANDS.map(b => ({
    ...b, roots: DM_ROOTS.filter(r => r.ageBand === b.id),
  })).filter(b => b.roots.length > 0);

  return (
    <VariantShell variantTag="E · garden">
      <div style={{ height: '100%', overflowY: 'auto',
                    padding: '6px 14px 12px' }}>
        <div style={{ font: 'italic 400 12px var(--font-serif)', color: '#8A9678',
                      padding: '2px 4px 8px' }}>
          A small field of what you've grown together.
        </div>

        {grouped.map(band => {
          const isCollapsed = collapsed[band.id];
          return (
            <div key={band.id} style={{ marginBottom: 10 }}>
              <button onClick={() => setCollapsed(s => ({...s, [band.id]: !isCollapsed}))} style={{
                width: '100%', textAlign: 'left',
                display: 'flex', alignItems: 'center', gap: 8,
                padding: '6px 4px 6px', cursor: 'pointer',
                background: 'transparent', border: 'none',
              }}>
                <span style={{ font: '500 10px var(--font-sans)', color: '#76AB78',
                               letterSpacing: '0.14em', textTransform: 'uppercase' }}>
                  {band.label}
                </span>
                <span style={{ font: 'italic 400 11px var(--font-serif)',
                               color: '#586552' }}>
                  · {band.roots.length} saplings
                </span>
                <span style={{ flex: 1, height: 0.5, background: '#1F2B16',
                               marginLeft: 4 }}/>
                <span style={{ font: '400 12px var(--font-sans)', color: '#586552' }}>
                  {isCollapsed ? '+' : '−'}
                </span>
              </button>
              {!isCollapsed && (
                <div style={{
                  display: 'grid',
                  gridTemplateColumns: 'repeat(3, 1fr)',
                  gap: 8,
                }}>
                  {band.roots.map(r => (
                    <GardenTile key={r.id} root={r} selected={r.id === SELECTED.id}/>
                  ))}
                </div>
              )}
            </div>
          );
        })}

        <div style={{ display: 'flex', justifyContent: 'center', marginTop: 8 }}>
          <ScaleHint>at 60+ roots: older months auto-collapse</ScaleHint>
        </div>
      </div>
    </VariantShell>
  );
}

function GardenTile({ root, selected = false }) {
  const fromColor = FROM_COLORS[root.from];
  const liveliness = Math.min(root.leaves / 50, 1);
  // Outer leaf dots — one per branch, capped at 6, around the rim.
  const leafDots = Math.min(root.branches, 6);
  return (
    <button style={{
      padding: '10px 8px 8px', cursor: 'pointer', textAlign: 'center',
      background: selected ? '#243319' : '#0F1A09',
      border: selected ? '0.5px solid #D4960C' : '0.5px solid #1F2B16',
      borderRadius: 14,
      boxShadow: selected ? '0 0 0 3px rgba(212,150,12,0.18)' : 'none',
      display: 'flex', flexDirection: 'column', gap: 6, alignItems: 'center',
    }}>
      <svg width="46" height="46" viewBox="0 0 52 52" fill="none">
        <circle cx="26" cy="26" r="22"
          stroke={selected ? '#F4DCA0' : '#4F8E51'}
          strokeOpacity={selected ? 0.55 : (0.18 + liveliness * 0.28)}
          strokeWidth="1"/>
        <circle cx="26" cy="26" r="15"
          stroke={selected ? '#F4DCA0' : '#76AB78'}
          strokeOpacity={selected ? 0.7 : (0.30 + liveliness * 0.4)}
          strokeWidth="1.2"/>
        <circle cx="26" cy="26" r="8"
          stroke={selected ? '#F4DCA0' : fromColor}
          strokeOpacity={selected ? 0.9 : (0.6 + liveliness * 0.3)}
          strokeWidth="1.4"/>
        <circle cx="26" cy="26" r="3.4"
          fill={selected ? '#D4960C' : fromColor}/>
        {Array.from({ length: leafDots }).map((_, i) => {
          const a = (i / leafDots) * Math.PI * 2 + 0.2;
          const x = 26 + Math.cos(a) * 22;
          const y = 26 + Math.sin(a) * 22;
          return <circle key={i} cx={x} cy={y} r="1.4"
            fill={fromColor} fillOpacity={0.65}/>;
        })}
      </svg>
      <div style={{
        font: 'italic 400 11px/1.2 var(--font-serif)',
        color: selected ? '#F2EDE4' : '#C9D2BB',
        whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
        width: '100%',
      }}>{root.head}</div>
      <div style={{
        font: '400 9px var(--font-mono)', color: '#586552',
      }}>{root.age} · {root.leaves}lv</div>
    </button>
  );
}


// ════════════════════════════════════════════════════════════
// F · TRELLIS
//   Vertical strands. Each root gets its own thin column running
//   down the screen; leaf-dots along the strand mark sub-branches
//   at their relative depth. Strand colour = who started it
//   (canopy=you, dew=them); strand width = how many leaves grew;
//   header label = the first words of the message.
//
//   Scaling: shows ONE time-window at a time (default: this week).
//   Drag the bottom pill or swipe left/right to walk the timeline
//   — older windows compress the strands & drop labels. At 60+
//   roots you scrub a month at a time instead of stacking them.
// ════════════════════════════════════════════════════════════
function TreeF_Trellis() {
  const [range, setRange] = React.useState('this-week');
  const windowRoots = DM_ROOTS.filter(r =>
    range === 'this-week' ? ['today', 'this-week'].includes(r.ageBand)
  : range === 'this-month' ? r.ageBand === 'this-month'
  : range === 'earlier' ? ['earlier', 'older'].includes(r.ageBand)
  : true
  );

  const COL_W = 38, COL_GAP = 6;
  const H = 420;
  const padX = 50;
  const strandsW = windowRoots.length * (COL_W + COL_GAP);
  const surfaceW = Math.max(390, padX + strandsW + 16);

  return (
    <VariantShell variantTag="F · trellis">
      <div style={{ height: '100%', overflow: 'hidden',
                    display: 'flex', flexDirection: 'column' }}>
        <div style={{ font: 'italic 400 12px var(--font-serif)', color: '#8A9678',
                      padding: '8px 16px 4px' }}>
          {windowRoots.length} strands in this window — read across.
        </div>

        <div style={{ flex: 1, overflowX: 'auto', overflowY: 'hidden',
                      padding: '4px 0 8px' }}>
          <div style={{ position: 'relative', width: surfaceW, height: H }}>
            {/* Time ticks on the left — fixed within the scrolling area */}
            <div style={{
              position: 'absolute', left: 8, top: 0, width: 36, height: H,
              display: 'flex', flexDirection: 'column',
              justifyContent: 'space-between',
              font: 'italic 400 9.5px var(--font-serif)', color: '#586552',
              paddingTop: 30, paddingBottom: 26,
            }}>
              <span>now</span>
              <span>·</span>
              <span>·</span>
              <span>back</span>
            </div>
            {/* Strands */}
            {windowRoots.map((r, i) => {
              const x = padX + i * (COL_W + COL_GAP);
              const isSel = r.id === SELECTED.id;
              const fromColor = FROM_COLORS[r.from];
              const leafCount = Math.min(r.leaves, 8);
              const strandTop = 24;
              const strandBottom = H - 28;
              const width = 1 + Math.min(r.leaves / 12, 3);
              return (
                <div key={r.id} style={{
                  position: 'absolute', left: x, top: 0,
                  width: COL_W, height: H,
                }}>
                  {/* Head label */}
                  <div style={{
                    padding: '4px 2px', textAlign: 'center',
                    font: 'italic 400 9.5px var(--font-serif)',
                    color: isSel ? '#F4DCA0' : '#C9D2BB',
                    whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
                    height: 22, lineHeight: '14px',
                  }}>{r.head}</div>
                  {/* Strand */}
                  <svg width={COL_W} height={strandBottom - strandTop + 10}
                       style={{ position: 'absolute', left: 0, top: strandTop,
                                pointerEvents: 'none' }}>
                    <path d={`M ${COL_W/2} 0
                              C ${COL_W/2 + (i % 2 ? -4 : 4)} 80,
                                ${COL_W/2 + (i % 2 ? 4 : -4)} 200,
                                ${COL_W/2} ${strandBottom - strandTop}`}
                      stroke={isSel ? '#D4960C' : fromColor}
                      strokeOpacity={isSel ? 0.95 : 0.55}
                      strokeWidth={isSel ? width + 0.5 : width}
                      fill="none" strokeLinecap="round"/>
                    {/* Root mark at top */}
                    <circle cx={COL_W/2} cy="3" r="3.5"
                      fill={isSel ? '#D4960C' : fromColor}
                      stroke={isSel ? '#F4DCA0' : '#76AB78'}
                      strokeWidth="0.8" strokeOpacity="0.7"/>
                    {/* Leaf dots */}
                    {Array.from({ length: leafCount }).map((_, k) => {
                      const y = 12 + ((k + 1) / (leafCount + 1)) * (strandBottom - strandTop - 24);
                      const dx = (k % 2 ? -3 : 3);
                      return <circle key={k} cx={COL_W/2 + dx} cy={y} r={1.8}
                        fill={isSel ? '#F4DCA0' : fromColor}
                        fillOpacity="0.7"/>;
                    })}
                  </svg>
                  {/* Counts */}
                  <div style={{
                    position: 'absolute', left: 0, right: 0,
                    top: strandBottom + 6,
                    textAlign: 'center',
                    font: '500 9px var(--font-mono)',
                    color: isSel ? '#F4DCA0' : '#586552',
                  }}>{r.leaves}</div>
                  <div style={{
                    position: 'absolute', left: 0, right: 0,
                    top: strandBottom + 18,
                    textAlign: 'center',
                    font: '400 9px var(--font-mono)',
                    color: '#586552',
                  }}>{r.age}</div>
                </div>
              );
            })}
            {/* Horizontal week-band dividers — faint hairlines */}
            {[100, 200, 300].map((y, i) => (
              <span key={i} style={{
                position: 'absolute', left: padX, right: 0, top: y,
                height: 0.5, background: '#14210C',
              }}/>
            ))}
          </div>
        </div>

        {/* Window scrubber — one timeline window at a time */}
        <div style={{ padding: '4px 14px 10px' }}>
          <div style={{
            display: 'flex', padding: 3, gap: 2,
            background: '#0F1A09', border: '0.5px solid #2D3A22',
            borderRadius: 999,
          }}>
            {[
              ['this-week',  'this week'],
              ['this-month', 'this month'],
              ['earlier',    'earlier'],
            ].map(([id, label]) => {
              const active = range === id;
              return (
                <button key={id} onClick={() => setRange(id)} style={{
                  flex: 1, padding: '6px 8px',
                  background: active ? '#1B3A1D' : 'transparent',
                  color: active ? '#F2EDE4' : '#586552',
                  border: 'none', borderRadius: 999, cursor: 'pointer',
                  font: '500 11.5px var(--font-sans)',
                }}>{label}</button>
              );
            })}
          </div>
          <div style={{ display: 'flex', justifyContent: 'center', marginTop: 8 }}>
            <ScaleHint>at 1k+ nodes: scrub windows, never stack all</ScaleHint>
          </div>
        </div>
      </div>
    </VariantShell>
  );
}


Object.assign(window, {
  TreeA_Chronicle, TreeB_Trunk, TreeC_Strata,
  TreeD_Constellation, TreeE_Garden, TreeF_Trellis,
});
