// TeamLeaderboard — team→PR token-flow Sankey
// Team-focused counterpart to LiveLeaderboard. Teams on the left rail
// (sorted by PR tokens), every PR they shipped on the right, hatched
// lane for tokens with no linked PR. Cross-team PRs converge on a
// single right-rail node with per-team sub-bands.

const fmtTokT = (n) => {
  if (n >= 1000) return (n / 1000).toFixed(1) + 'B';
  if (n >= 1) return n.toFixed(1) + 'M';
  return (n * 1000).toFixed(0) + 'k';
};

// Co-authorship splits the per-row fixture can't express. Designed to put
// 10-20% of each top-6 team's tokens into PRs primarily owned by another
// team — three of these are 3-team to show wider collaboration. Intra-team
// splits (e.g. a.park + r.iyer both on platform) are intentionally NOT
// here because they collapse to a single team band and add no signal.
const SHARED_PR_OVERRIDES_T = {
  // PR-3812: auth refactor — platform-led with frontend on the cookie-v1
  // UX deprecation and appsec on the threat model. 3-team.
  'PR-3812': [
    { author: 'a.park',     share: 0.60 },  // platform (primary)
    { author: 'd.kowalski', share: 0.25 },  // frontend
    { author: 'h.lin',      share: 0.15 },  // appsec
  ],
  // PR-3829: redis-backed session store — platform-led with growth on
  // post-login cart-state cleanup.
  'PR-3829': [
    { author: 'a.park',   share: 0.65 },    // platform (primary)
    { author: 'j.okafor', share: 0.35 },    // growth
  ],
  // PR-3915: sdk migration guide — platform-led with infra on the
  // service-side onboarding docs.
  'PR-3915': [
    { author: 'a.park',   share: 0.70 },    // platform (primary)
    { author: 's.tanaka', share: 0.30 },    // infra
  ],
  // PR-3940: webhook replay race — platform-led with infra on the queue
  // semantics and appsec on the signature check. 3-team.
  'PR-3940': [
    { author: 'r.iyer', share: 0.50 },      // platform (primary)
    { author: 'm.chen', share: 0.35 },      // infra
    { author: 'h.lin',  share: 0.15 },      // appsec
  ],
  // PR-2210: checkout v2 ab harness — growth-led with platform on the
  // variant runtime and frontend on the component shells. 3-team.
  'PR-2210': [
    { author: 'j.okafor',   share: 0.60 },  // growth (primary)
    { author: 'a.park',     share: 0.25 },  // platform
    { author: 'd.kowalski', share: 0.15 },  // frontend
  ],
  // PR-1820: settings rebuild — frontend-led with platform on the
  // session/auth wiring.
  'PR-1820': [
    { author: 'd.kowalski', share: 0.80 },  // frontend (primary)
    { author: 'a.park',     share: 0.20 },  // platform
  ],
  // PR-680: keda triggers — infra-led with growth on the queue-bound
  // checkout/billing handlers.
  'PR-680': [
    { author: 's.tanaka', share: 0.60 },    // infra (primary)
    { author: 'n.bauer',  share: 0.40 },    // growth
  ],
  // PR-991: custom sql lints + CI hooks — data-led with platform on
  // the CI-side integration.
  'PR-991': [
    { author: 'l.fernandez', share: 0.85 }, // data (primary)
    { author: 'r.iyer',      share: 0.15 }, // platform
  ],
  // PR-220: SBOM supply-chain gate — appsec-led with data on the
  // service-inventory ingest.
  'PR-220': [
    { author: 'h.lin',       share: 0.70 }, // appsec (primary)
    { author: 'l.fernandez', share: 0.30 }, // data
  ],
};

// Synthetic per-team unattributed share. Keys are team ids from tt-data.js.
// Tuned so the hatched right-rail lane visibly proves the "every token,
// attributed or counted as missing" framing without dominating the chart.
const UNATTR_SHARE_T = {
  infra:    0.18,
  platform: 0.22,
  growth:   0.30,
  frontend: 0.16,
  data:     0.20,
  ml:       0.24,
  design:   0.28,
  appsec:   0.14,
  sre:      0.20,
};

// Synthetic per-team agent contribution share — surfaces "agents and humans
// on one board" at team granularity. Rendered as a small label suffix on
// each team's rail; not part of the Sankey flow widths.
const AGENT_SHARE_T = {
  infra:    0.12,
  platform: 0.09,
  growth:   0.21,
  frontend: 0.16,
  data:     0.08,
  ml:       0.18,
  design:   0.06,
  appsec:   0.04,
  sre:      0.11,
};

function buildTLBData(maxRows) {
  const TT = window.TT;
  if (!TT) return { teams: [], pullRequests: [] };

  const peopleById = new Map(TT.people.map((p) => [p.id, p]));
  const teamById = new Map(TT.teams.map((t) => [t.id, t]));
  const projectById = new Map(TT.projects.map((p) => [p.id, p]));

  const resolveTeam = (authorId, projectId) => {
    const person = peopleById.get(authorId);
    if (person?.team) return person.team;
    const project = projectById.get(projectId);
    if (project?.team) return project.team;
    return null;
  };

  // 1) Expand artifacts → per-(team, PR) rows. SHARED_PR_OVERRIDES splits a
  //    PR's tokens across multiple authors; we then collapse co-authors who
  //    sit on the same team so the right-rail PR node shows one band per
  //    contributing team (not per contributing engineer).
  const prKeyToTeamShares = new Map();
  const prKeyToMeta = new Map();

  TT.artifacts.filter((a) => a.kind === 'pr').forEach((pr) => {
    const split = SHARED_PR_OVERRIDES_T[pr.id];
    const contribs = split
      ? split.map(({ author, share }) => ({ author, tokens: +(pr.tokens * share).toFixed(2) }))
      : [{ author: pr.author, tokens: pr.tokens }];

    const teamShares = new Map();
    contribs.forEach(({ author, tokens }) => {
      const teamId = resolveTeam(author, pr.project);
      if (!teamId) return;
      teamShares.set(teamId, (teamShares.get(teamId) ?? 0) + tokens);
    });
    if (teamShares.size === 0) return;

    prKeyToTeamShares.set(pr.id, teamShares);
    prKeyToMeta.set(pr.id, pr);
  });

  // 2) Aggregate per-team totals + PR set.
  const byTeam = new Map();
  prKeyToTeamShares.forEach((teamShares, prId) => {
    teamShares.forEach((tokens, teamId) => {
      let entry = byTeam.get(teamId);
      if (!entry) { entry = { id: teamId, prTokens: 0, prIds: new Set() }; byTeam.set(teamId, entry); }
      entry.prTokens += tokens;
      entry.prIds.add(prId);
    });
  });

  const teams = Array.from(byTeam.values())
    .map((e) => {
      const t = teamById.get(e.id);
      const unattrFrac = UNATTR_SHARE_T[e.id] ?? 0.2;
      const unattributed = +(e.prTokens * unattrFrac).toFixed(2);
      return {
        id: e.id,
        name: t?.name ?? e.id,
        dept: t?.dept ?? '',
        head: t?.head ?? 0,
        delta: t?.delta ?? 0,
        agentShare: AGENT_SHARE_T[e.id] ?? 0.1,
        prTokens: +e.prTokens.toFixed(2),
        unattributed,
        tokens: +(e.prTokens + unattributed).toFixed(2),
        prCount: e.prIds.size,
      };
    })
    .sort((a, b) => b.prTokens - a.prTokens)
    .slice(0, maxRows);

  // 3) Flatten PRs to one row per (team, PR) for the visible teams. The
  //    layout function consumes this and re-bundles by PR key for shared
  //    right-rail nodes — matching LiveLeaderboard's contract.
  const visibleIds = new Set(teams.map((t) => t.id));
  const pullRequests = [];
  prKeyToTeamShares.forEach((teamShares, prId) => {
    const meta = prKeyToMeta.get(prId);
    teamShares.forEach((tokens, teamId) => {
      if (!visibleIds.has(teamId)) return;
      pullRequests.push({
        id: `${teamId}-${prId}`,
        prId,
        url: meta.url,
        teamId,
        title: meta.title,
        tokens: +tokens.toFixed(2),
        status: meta.status === 'merged' ? 'merged' : meta.status === 'review' ? 'review' : 'open',
      });
    });
  });

  return { teams, pullRequests };
}

function statusColorT(s) {
  return s === 'merged' ? 'var(--accent)' : s === 'review' ? 'var(--fg-2)' : 'var(--fg-1)';
}

function truncateTLB(s, n) {
  return s.length > n ? s.slice(0, n - 1) + '…' : s;
}

function layoutTLB({ rows, pullRequests, innerL, innerR, startY, flowH }) {
  const minNodeH = 2;
  const interItemGap = 0.5;
  const interTeamGap = 6;

  const teamById = new Map(rows.map((t) => [t.id, t]));
  const teamRank = new Map(rows.map((t, i) => [t.id, i]));

  // Bundle PR rows back into a single node per upstream PR. contribs is the
  // list of teams (not engineers) contributing tokens to that PR.
  const prByKey = new Map();
  pullRequests.forEach((pr) => {
    const key = pr.prId || pr.url || `${pr.teamId}::${pr.title}`;
    if (!key) return;
    const team = teamById.get(pr.teamId);
    if (!team) return;
    let entry = prByKey.get(key);
    if (!entry) { entry = { key, pr, contribs: [], total: 0 }; prByKey.set(key, entry); }
    entry.contribs.push({ teamId: pr.teamId, tokens: pr.tokens, team });
    entry.total += pr.tokens;
  });
  prByKey.forEach((e) => {
    e.contribs.sort((a, b) => (teamRank.get(a.teamId) ?? 0) - (teamRank.get(b.teamId) ?? 0));
  });

  // Anchor each PR under its primary (most-tokens) team; ties broken by rank.
  const prsByPrimary = new Map();
  rows.forEach((t) => prsByPrimary.set(t.id, []));
  prByKey.forEach((entry) => {
    if (!entry.contribs.length) return;
    const primary = [...entry.contribs].sort((a, b) => {
      const dt = b.tokens - a.tokens;
      if (dt !== 0) return dt;
      return (teamRank.get(a.teamId) ?? 0) - (teamRank.get(b.teamId) ?? 0);
    })[0].teamId;
    prsByPrimary.get(primary)?.push(entry);
  });
  prsByPrimary.forEach((arr) => arr.sort((a, b) => b.total - a.total));

  const rItems = [];
  rows.forEach((t) => {
    (prsByPrimary.get(t.id) ?? []).forEach((pr) => {
      rItems.push({ kind: 'pr', weight: pr.total, pr, teamId: t.id });
    });
    if (t.unattributed > 0) {
      rItems.push({ kind: 'unattr', weight: t.unattributed, team: t, teamId: t.id });
    }
  });

  let totalGaps = 0;
  for (let i = 0; i + 1 < rItems.length; i++) {
    totalGaps += rItems[i].teamId === rItems[i + 1].teamId ? interItemGap : interTeamGap;
  }
  const usableH = Math.max(0, flowH - totalGaps);

  const heights = new Array(rItems.length).fill(0);
  const clamped = new Array(rItems.length).fill(false);
  let remainingBudget = usableH;
  let remainingWeight = rItems.reduce((s, it) => s + it.weight, 0) || 1;
  for (let iter = 0; iter < 6; iter++) {
    const k = remainingBudget / (remainingWeight || 1);
    let changed = false;
    for (let i = 0; i < rItems.length; i++) {
      if (clamped[i]) continue;
      if (rItems[i].weight * k < minNodeH) {
        clamped[i] = true;
        heights[i] = minNodeH;
        remainingBudget -= minNodeH;
        remainingWeight -= rItems[i].weight;
        changed = true;
      }
    }
    if (!changed) break;
  }
  if (remainingBudget < 0) remainingBudget = 0;
  const finalK = remainingBudget / (remainingWeight || 1);
  for (let i = 0; i < rItems.length; i++) {
    if (!clamped[i]) heights[i] = rItems[i].weight * finalK;
  }

  const nodes = [];
  const prSubbands = new Map();
  const unattrNodeByTeam = new Map();
  const rightGroupRange = new Map();

  let cy = startY;
  rItems.forEach((it, i) => {
    if (i > 0) cy += rItems[i - 1].teamId === it.teamId ? interItemGap : interTeamGap;
    const h = heights[i];
    if (it.kind === 'pr') {
      const node = {
        kind: 'pr',
        id: `pr-${it.pr.key}`,
        team: it.pr.contribs[0]?.team ?? rows[0],
        pr: it.pr.pr,
        x: innerR, y: cy, h, w: 14,
        value: it.pr.total, hatch: false,
        contributors: it.pr.contribs.map((c) => ({ id: c.teamId, tokens: c.tokens })),
      };
      nodes.push(node);
      const subs = new Map();
      const totalW = it.pr.contribs.reduce((s, c) => s + c.tokens, 0) || 1;
      let subY = cy;
      it.pr.contribs.forEach((c, idx) => {
        const subH = idx === it.pr.contribs.length - 1 ? cy + h - subY : (c.tokens / totalW) * h;
        subs.set(c.teamId, { y: subY, h: subH });
        subY += subH;
      });
      prSubbands.set(it.pr.key, subs);
    } else {
      const node = {
        kind: 'unattr',
        id: `unattr-${it.team.id}`,
        team: it.team,
        pr: { id: '', teamId: it.team.id, title: '', tokens: 0, status: 'open' },
        x: innerR, y: cy, h, w: 14,
        value: it.team.unattributed, hatch: true,
        contributors: [{ id: it.team.id, tokens: it.team.unattributed }],
      };
      nodes.push(node);
      unattrNodeByTeam.set(it.team.id, node);
    }

    const range = rightGroupRange.get(it.teamId);
    if (!range) rightGroupRange.set(it.teamId, { yStart: cy, yEnd: cy + h });
    else range.yEnd = cy + h;

    cy += h;
  });

  const outflowsByTeam = new Map();
  rows.forEach((t) => outflowsByTeam.set(t.id, []));
  prByKey.forEach((entry, key) => {
    const subs = prSubbands.get(key);
    const target = nodes.find((n) => n.id === `pr-${key}`);
    if (!subs || !target) return;
    entry.contribs.forEach((c) => {
      const sub = subs.get(c.teamId);
      if (!sub) return;
      outflowsByTeam.get(c.teamId).push({ target, targetY: sub.y, targetH: sub.h });
    });
  });
  rows.forEach((t) => {
    const node = unattrNodeByTeam.get(t.id);
    if (!node) return;
    outflowsByTeam.get(t.id).push({ target: node, targetY: node.y, targetH: node.h });
  });
  outflowsByTeam.forEach((arr) => arr.sort((a, b) => a.targetY - b.targetY));

  const teamBoxes = [];
  let leftCy = startY;
  rows.forEach((t) => {
    const flows = outflowsByTeam.get(t.id) ?? [];
    const totalH = flows.reduce((s, f) => s + f.targetH, 0);
    const h = Math.max(totalH, minNodeH);
    const range = rightGroupRange.get(t.id);
    let y = range ? (range.yStart + range.yEnd) / 2 - h / 2 : leftCy;
    y = Math.max(y, leftCy, startY);
    teamBoxes.push({ team: t, x: innerL, y, h, w: 14 });
    leftCy = y + h + interTeamGap;
  });

  const links = [];
  teamBoxes.forEach((tb) => {
    const flows = outflowsByTeam.get(tb.team.id) ?? [];
    let sourceY = tb.y;
    flows.forEach((f) => {
      const lh = f.targetH;
      links.push({
        teamId: tb.team.id,
        nodeId: f.target.id,
        kind: f.target.kind,
        x0: tb.x + tb.w, y0: sourceY + lh / 2,
        x1: f.target.x,  y1: f.targetY + lh / 2,
        h: lh,
        color: f.target.kind === 'unattr' ? 'var(--fg-3)' : 'var(--accent)',
        hatch: f.target.kind === 'unattr',
      });
      sourceY += lh;
    });
  });

  return { nodes, teamBoxes, links };
}

function pathOfTLB(l) {
  const mx = (l.x0 + l.x1) / 2;
  return `M${l.x0},${l.y0} C${mx},${l.y0} ${mx},${l.y1} ${l.x1},${l.y1}`;
}

function TeamLeaderboard({ rows = 6 }) {
  const [hover, setHover] = React.useState(null);
  const isMobile = window.useMediaQuery ? window.useMediaQuery('(max-width: 768px)') : false;

  const { teams, pullRequests } = React.useMemo(() => buildTLBData(rows), [rows]);

  const totals = React.useMemo(() => {
    const grandPR = teams.reduce((s, t) => s + t.prTokens, 0);
    const grandUnattr = teams.reduce((s, t) => s + t.unattributed, 0);
    const grandAgent = teams.reduce((s, t) => s + t.prTokens * t.agentShare, 0);
    return { grandPR, grandUnattr, grandAgent, grandTotal: grandPR + grandUnattr };
  }, [teams]);

  // Fixed layout dimensions rather than stretching to the container — the
  // flow region was visibly over-wide on bigger viewports, with the green
  // curves stretched horizontally for no information gain. Now flowW is
  // hard-capped at 290 (desktop) / 240 (mobile) and the SVG width is the
  // sum of all bands. On wide columns the SVG sits left-aligned with empty
  // space beside it; on narrow mobile the parent's overflow-x:auto scrolls.
  const padL = isMobile ? 150 : 210;
  const padR = isMobile ? 200 : 320;
  const gutterL = isMobile ? 16 : 28;
  const gutterR = isMobile ? 18 : 28;
  const flowW = isMobile ? 240 : 290;
  const innerL = padL + gutterL;
  const innerR = innerL + flowW;
  const width = innerR + gutterR + padR;

  const topPad = 28;
  const bottomPad = 28;
  const rowHeight = isMobile ? 48 : 60;
  const flowH = Math.max(teams.length * rowHeight, 360);
  const startY = topPad;
  const height = startY + flowH + bottomPad;

  const layout = React.useMemo(
    () => layoutTLB({ rows: teams, pullRequests, innerL, innerR, startY, flowH }),
    [teams, pullRequests, innerL, innerR, startY, flowH],
  );

  const isFocused = (l) => {
    if (!hover) return false;
    if (hover.kind === 'team') return l.teamId === hover.id;
    if (hover.kind === 'node') return l.nodeId === hover.id;
    return false;
  };
  const anyFocus = !!hover;

  const totalPRs = layout.nodes.filter((n) => n.kind === 'pr').length;
  const unattrPct = totals.grandTotal > 0 ? (totals.grandUnattr / totals.grandTotal) * 100 : 0;
  const agentPct = totals.grandPR > 0 ? (totals.grandAgent / totals.grandPR) * 100 : 0;

  return (
    <div
      style={{
        position: 'relative',
        background: 'var(--bg-1)',
        border: '1px solid var(--border)',
        borderRadius: 'var(--radius-md)',
        overflow: 'hidden',
        minWidth: 0,
        width: '100%',
      }}
    >
      <div style={{
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        padding: '12px 16px',
        borderBottom: '1px dashed var(--fg-3)',
        background: 'var(--bg-1)',
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
          <div style={{ display: 'flex', gap: 6 }}>
            <span style={{ width: 8, height: 8, borderRadius: '50%', background: 'var(--bg-3)' }} />
            <span style={{ width: 8, height: 8, borderRadius: '50%', background: 'var(--bg-3)' }} />
            <span style={{ width: 8, height: 8, borderRadius: '50%', background: 'var(--accent)' }} />
          </div>
          <span style={{ fontFamily: 'var(--font-mono)', fontSize: 12, color: 'var(--fg-2)' }}>
            team token flow · this week
          </span>
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
          <span style={{ width: 6, height: 6, borderRadius: '50%', background: 'var(--accent)', boxShadow: '0 0 8px var(--accent)' }} />
          <span style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--fg-3)' }}>live · 60s</span>
        </div>
      </div>

      <div style={{ padding: isMobile ? 12 : 18, overflowX: 'auto' }}>
        <svg width={width} height={height} style={{ display: 'block' }}>
          <defs>
            <pattern id="tlb-hatch" patternUnits="userSpaceOnUse" width="6" height="6" patternTransform="rotate(45)">
              <rect width="6" height="6" fill="var(--bg-2)" />
              <line x1="0" y1="0" x2="0" y2="6" stroke="var(--fg-3)" strokeOpacity="0.55" strokeWidth="1.1" />
            </pattern>
            <pattern id="tlb-hatch-bold" patternUnits="userSpaceOnUse" width="6" height="6" patternTransform="rotate(45)">
              <rect width="6" height="6" fill="var(--bg-1)" />
              <line x1="0" y1="0" x2="0" y2="6" stroke="var(--fg-2)" strokeOpacity="0.85" strokeWidth="1.4" />
            </pattern>
          </defs>

          <line x1={innerL} x2={innerR} y1={startY - 12} y2={startY - 12}
            stroke="var(--fg-4)" strokeDasharray="2 4" />
          <text x={innerL - 8} y={startY - 18} textAnchor="end"
            fontFamily="var(--font-mono)" fontSize="10" fill="var(--fg-3)"
            style={{ textTransform: 'uppercase', letterSpacing: '0.08em' }}>
            // team
          </text>
          <text x={innerL - 8} y={startY - 4} textAnchor="end"
            fontFamily="var(--font-display)" fontSize="13" fontWeight="600" fill="var(--fg-0)">
            ranked · PR tokens
          </text>
          <text x={innerR + 8} y={startY - 18}
            fontFamily="var(--font-mono)" fontSize="10" fill="var(--fg-3)"
            style={{ textTransform: 'uppercase', letterSpacing: '0.08em' }}>
            // pull request
          </text>
          <text x={innerR + 8} y={startY - 4}
            fontFamily="var(--font-display)" fontSize="13" fontWeight="600" fill="var(--fg-0)">
            every token, attributed
          </text>

          <g>
            {layout.links.map((l, i) => {
              const focused = isFocused(l);
              const dim = anyFocus && !focused;
              return (
                <path key={`${l.teamId}-${l.nodeId}-${i}`}
                  d={pathOfTLB(l)} fill="none"
                  stroke={l.hatch ? 'url(#tlb-hatch)' : l.color}
                  strokeOpacity={dim ? 0.04 : focused ? 0.62 : 0.22}
                  strokeWidth={Math.max(1, l.h)}
                  style={{
                    transition: 'stroke-opacity var(--dur-fast) var(--ease)',
                    pointerEvents: 'stroke',
                  }}
                  onMouseEnter={() => setHover({ kind: 'node', id: l.nodeId })}
                  onMouseLeave={() => setHover(null)}
                />
              );
            })}
          </g>

          {/* Team left rail */}
          <g>
            {layout.teamBoxes.map((tb, i) => {
              const t = tb.team;
              const focused = hover?.kind === 'team' && hover.id === t.id;
              const dim = anyFocus && !focused && hover?.kind !== 'node';
              const cy = tb.y + tb.h / 2;
              const agentPctTeam = Math.round(t.agentShare * 100);

              // Progressive disclosure: meta splits into 3 lines, dropped
              // from the bottom up as tb.h shrinks. Below the name's own
              // line height, the row renders no labels at all (just the
              // flow source rect).
              const NAME_H = 16;
              const META_H = 12;
              const metaItems = [
                `${t.prCount} ${t.prCount === 1 ? 'PR' : 'PRs'}`,
                fmtTokT(t.prTokens),
                `${agentPctTeam}% agent`,
              ];
              const budget = tb.h;
              let stackH = NAME_H <= budget ? NAME_H : 0;
              const renderedMeta = [];
              if (stackH > 0) {
                for (const m of metaItems) {
                  if (stackH + META_H > budget) break;
                  stackH += META_H;
                  renderedMeta.push(m);
                }
              }
              const stackTop = cy - stackH / 2;
              const nameBaseline = stackTop + 11;

              return (
                <g key={t.id}
                  style={{ cursor: 'pointer', opacity: dim ? 0.4 : 1, transition: 'opacity var(--dur-fast) var(--ease)' }}
                  onMouseEnter={() => setHover({ kind: 'team', id: t.id })}
                  onMouseLeave={() => setHover(null)}>
                  {stackH > 0 && (
                    <>
                      <text x={12} y={nameBaseline}
                        fontFamily="var(--font-mono)" fontSize="11" fill="var(--fg-3)">
                        {String(i + 1).padStart(2, '0')}
                      </text>
                      <text x={36} y={nameBaseline}
                        fontFamily="var(--font-display)" fontSize="14" fontWeight="600" fill="var(--fg-0)">
                        {t.name}
                      </text>
                      {renderedMeta.map((m, mi) => (
                        <text key={mi} x={36}
                          y={stackTop + NAME_H + mi * META_H + 8}
                          fontFamily="var(--font-mono)" fontSize="10" fill="var(--fg-3)">
                          {m}
                        </text>
                      ))}
                    </>
                  )}
                  <rect x={tb.x} y={tb.y} width={tb.w} height={tb.h} fill="var(--fg-1)" />
                </g>
              );
            })}
          </g>

          {/* PR right rail */}
          <g>
            {layout.nodes.map((n) => {
              const focused = hover?.kind === 'node' && hover.id === n.id;
              const teamFocused = hover?.kind === 'team' && n.contributors.some((c) => c.id === hover.id);
              const dim = anyFocus && !focused && !teamFocused;
              const sharedSuffix = n.kind === 'pr' && n.contributors.length > 1 ? ` · ${n.contributors.length}-team` : '';
              const labelTitle = n.kind === 'pr' ? n.pr.title : `unattributed · ${n.team.name}`;
              const labelMeta = n.kind === 'pr'
                ? `${fmtTokT(n.value)} · ${n.pr.status}${sharedSuffix}`
                : `${fmtTokT(n.value)} · no PR`;
              const labelColor = n.kind === 'pr' ? 'var(--fg-1)' : 'var(--fg-3)';
              const showLabel = n.h >= 9;
              const labelMax = isMobile ? 22 : 34;
              return (
                <g key={n.id}
                  style={{ opacity: dim ? 0.35 : 1, transition: 'opacity var(--dur-fast) var(--ease)' }}
                  onMouseEnter={() => setHover({ kind: 'node', id: n.id })}
                  onMouseLeave={() => setHover(null)}>
                  <rect x={n.x} y={n.y} width={n.w} height={n.h}
                    fill={n.hatch ? 'url(#tlb-hatch-bold)' : n.kind === 'pr' ? statusColorT(n.pr.status) : 'var(--fg-3)'}
                    stroke={n.hatch ? 'var(--fg-3)' : 'transparent'}
                    strokeWidth={n.hatch ? 1 : 0} />
                  {showLabel && (
                    <>
                      <text x={n.x + 22} y={n.y + Math.min(13, n.h / 2 + 4)}
                        fontFamily="var(--font-body)"
                        fontSize={Math.min(12, Math.max(10, n.h - 4))}
                        fill={labelColor}>
                        {truncateTLB(labelTitle, labelMax)}
                      </text>
                      {n.h >= 22 && (
                        <text x={n.x + 22} y={n.y + Math.min(28, n.h - 4)}
                          fontFamily="var(--font-mono)" fontSize="10" fill="var(--fg-3)">
                          {labelMeta}
                        </text>
                      )}
                    </>
                  )}
                </g>
              );
            })}
          </g>
        </svg>
      </div>

      <div style={{
        padding: '12px 16px',
        borderTop: '1px dashed var(--fg-3)',
        fontFamily: 'var(--font-mono)',
        fontSize: 11,
        color: 'var(--fg-1)',
        display: 'flex', flexWrap: 'wrap', gap: 4, alignItems: 'baseline',
      }}>
        <span style={{ color: 'var(--fg-3)' }}>// </span>
        <span>{fmtTokT(totals.grandTotal)}</span>
        <span style={{ color: 'var(--fg-3)' }}>total ·</span>
        <span style={{ color: 'var(--accent)' }}>{fmtTokT(totals.grandPR)}</span>
        <span style={{ color: 'var(--fg-3)' }}>across {totalPRs} PRs ·</span>
        <span style={{ color: 'var(--fg-1)' }}>{agentPct.toFixed(0)}%</span>
        <span style={{ color: 'var(--fg-3)' }}>from agents ·</span>
        <span style={{ color: 'var(--danger)' }}>{fmtTokT(totals.grandUnattr)}</span>
        <span style={{ color: 'var(--fg-3)' }}>unattributed ({unattrPct.toFixed(1)}%)</span>
        {!isMobile && (
          <span style={{ marginLeft: 'auto', color: 'var(--fg-3)' }}>
            hover a team to focus their PRs
          </span>
        )}
      </div>
    </div>
  );
}

Object.assign(window, { TeamLeaderboard });
