// council-data.jsx
// The 25 Council agents — each one an NFT on Base, each one a smart-wallet voter.
// This file is the single source of truth: roster, sigils, on-chain helpers,
// deterministic stats / agreement values.

// ── On-chain constants ────────────────────────────────────────
const NFT_CONTRACT  = '0x8d5b1246CA5fb193F9E76bAdB4405D5024DD431e';
const COUNCIL_OWNER = '0x9092BC0Fe1540fD34d9786EA11e450c5ed28bb8c';
const BASE_CHAIN    = { name: 'Base', explorer: 'https://basescan.org' };

const basescanContract = () => `${BASE_CHAIN.explorer}/token/${NFT_CONTRACT}`;
const basescanAddr     = (a) => `${BASE_CHAIN.explorer}/address/${a}`;
const basescanTx       = (h) => `${BASE_CHAIN.explorer}/tx/${h}`;

// ── Specialty palette ─────────────────────────────────────────
// Five specialties, each anchored to a QP color. These are the cluster colors
// used in the matrix and filter chips. Individual agents may inherit a
// related but distinct palette color (see AGENTS below).
const SPEC = {
  liquidity:  { label: 'Liquidity',   color: '#23c8e9', soft: 'rgba(35,200,233,0.10)',  tag: 'LIQ' },
  volatility: { label: 'Volatility',  color: '#e72020', soft: 'rgba(231,32,32,0.10)',   tag: 'VOL' },
  pattern:    { label: 'Pattern',     color: '#42f977', soft: 'rgba(66,249,119,0.10)',  tag: 'PAT' },
  sentiment:  { label: 'Sentiment',   color: '#c6207a', soft: 'rgba(198,32,122,0.10)',  tag: 'SEN' },
  risk:       { label: 'Risk',        color: '#b05222', soft: 'rgba(176,82,34,0.10)',   tag: 'RSK' },
};

// ── The roster — 25 named agents ──────────────────────────────
// Names drawn from mythology / archetype so each agent reads as a character.
// Each agent inherits a palette color from the Quantum Palette — the brand's
// own 14 atomic hues, each one carrying a measurable advantage + multiplier.
// `palette` is the QP color name; the agent surfaces its advantage as a stat.
const AGENTS = [
  // Liquidity & Market Structure (1–5) — Calibration-led
  { id: 1,  name: 'ATLAS',      specialty: 'liquidity',  role: 'order-book depth across 10 levels',  palette: 'Calibration Cyan' },
  { id: 2,  name: 'KRAKEN',     specialty: 'liquidity',  role: 'block-trade detection',              palette: 'Coherence Indigo' },
  { id: 3,  name: 'NAUTILUS',   specialty: 'liquidity',  role: 'bid-ask spread monitor',             palette: 'Calibration Cyan' },
  { id: 4,  name: 'OCEAN',      specialty: 'liquidity',  role: 'dark-pool flow tracker',             palette: 'Trunk Wave' },
  { id: 5,  name: 'KELP',       specialty: 'liquidity',  role: 'odd-lot pattern reader',             palette: 'Coherence Indigo' },
  // Volatility (6–10) — Crimson + Shield
  { id: 6,  name: 'HERA',       specialty: 'volatility', role: '30-day realized volatility',         palette: 'Oscillation Crimson' },
  { id: 7,  name: 'STORM',      specialty: 'volatility', role: 'implied vol surface',                palette: 'Shield Barrier' },
  { id: 8,  name: 'IRIS',       specialty: 'volatility', role: 'vol-of-vol regime detector',         palette: 'Oscillation Crimson' },
  { id: 9,  name: 'GALE',       specialty: 'volatility', role: 'intraday range scanner',             palette: 'Shield Barrier' },
  { id: 10, name: 'CALM',       specialty: 'volatility', role: 'quiet-period detector',              palette: 'Oscillation Crimson' },
  // Pattern & Technicals (11–15) — Velocity + Fidelity
  { id: 11, name: 'ARGUS',      specialty: 'pattern',    role: '90-day price action',                palette: 'Velocity Teal' },
  { id: 12, name: 'SCOUT',      specialty: 'pattern',    role: 'moving-average crossover',           palette: 'Fidelity Lime' },
  { id: 13, name: 'ORACLE',     specialty: 'pattern',    role: 'support and resistance',             palette: 'Velocity Teal' },
  { id: 14, name: 'SAGE',       specialty: 'pattern',    role: 'candle pattern library',             palette: 'Fidelity Lime' },
  { id: 15, name: 'PROPHET',    specialty: 'pattern',    role: 'fib retracement reader',             palette: 'Velocity Teal' },
  // News & Sentiment (16–20) — Magenta + Seed
  { id: 16, name: 'ECHO',       specialty: 'sentiment',  role: 'headline sentiment scan',            palette: 'Session Magenta' },
  { id: 17, name: 'HERMES',     specialty: 'sentiment',  role: 'earnings calendar',                  palette: 'Seed Pulse' },
  { id: 18, name: 'RUMOR',      specialty: 'sentiment',  role: 'social sentiment delta',             palette: 'Session Magenta' },
  { id: 19, name: 'BEACON',     specialty: 'sentiment',  role: 'analyst rating changes',             palette: 'Seed Pulse' },
  { id: 20, name: 'SIREN',      specialty: 'sentiment',  role: 'false-alarm filter',                 palette: 'Session Magenta' },
  // Risk & Exposure (21–25) — Consensus Orange + Crown Resonance (OVERSEER)
  { id: 21, name: 'AEGIS',      specialty: 'risk',       role: 'sector concentration',               palette: 'Yield Gold' },
  { id: 22, name: 'NEMESIS',    specialty: 'risk',       role: 'sizing vs daily volume',             palette: 'Consensus Orange' },
  { id: 23, name: 'PROMETHEUS', specialty: 'risk',       role: 'correlation cluster',                palette: 'Budget Seafoam' },
  { id: 24, name: 'STYX',       specialty: 'risk',       role: 'event proximity (Fed · earnings)',   palette: 'Swarm Violet' },
  { id: 25, name: 'OVERSEER',   specialty: 'risk',       role: 'hard refuser of last resort',        palette: 'Crown Resonance' },
];

// ── Deterministic helpers ─────────────────────────────────────
// Same input → same output, every render. Lets the page act as if it's
// reading on-chain data even though it's actually computed locally.
function lcg(seed) {
  let n = seed >>> 0;
  return () => { n = (n * 1103515245 + 12345) >>> 0; return n; };
}

// Produce a 40-hex address that looks indistinguishable from a real Base address.
// In production this is the ERC-6551 token-bound account computed from
// (chainId, NFT_CONTRACT, tokenId, salt) — same shape, same length.
function tbaFor(id) {
  const r = lcg(id * 2654435761 + 0xABCDEF);
  let s = '';
  for (let i = 0; i < 40; i++) s += (r() & 0xf).toString(16);
  // Surface a stable owner prefix so agents look related (same collection)
  return '0x' + s.padStart(40, '0');
}

function fmtAddr(a, lead = 6, tail = 4) {
  if (!a) return '';
  return a.slice(0, 2 + lead) + '…' + a.slice(-tail);
}

// Per-agent stats — stake, accuracy, vote counts, last-vote latency.
// Deterministic from token ID so the page is reproducible.
function statsFor(id) {
  const r = lcg(id * 16807 + 13);
  const f = () => (r() & 0xFFFF) / 0xFFFF;

  const stakeEth = +(0.6 + f() * 4.4).toFixed(2);            // 0.6 – 5.0 ETH
  const accuracy = +(0.78 + f() * 0.17).toFixed(3);          // 78 – 95%
  const voteCount = 800 + Math.floor(f() * 5000);            // 800 – 5800
  const clearedRatio = 0.72 + f() * 0.18;                    // 72 – 90%
  const cleared = Math.floor(voteCount * clearedRatio);
  const refused = voteCount - cleared - Math.floor(voteCount * 0.04);
  const abstained = voteCount - cleared - refused;
  const lastVoteSec = Math.floor(2 + f() * 1700);            // 2 – 1700s ago
  const slashCount  = Math.floor(f() * 4);                    // 0 – 3 historical slashes

  return { stakeEth, accuracy, voteCount, cleared, refused, abstained, lastVoteSec, slashCount };
}

function timeAgo(seconds) {
  if (seconds < 60)      return `${seconds}s ago`;
  if (seconds < 3600)    return `${Math.floor(seconds/60)}m ago`;
  if (seconds < 86400)   return `${Math.floor(seconds/3600)}h ago`;
  return `${Math.floor(seconds/86400)}d ago`;
}

// Pairwise agreement: 1.0 on the diagonal, ~0.70–0.85 same-specialty,
// ~0.55–0.70 cross-specialty. Deterministic and symmetric.
function agreementBetween(a, b) {
  if (a.id === b.id) return 1.0;
  const same = a.specialty === b.specialty;
  const lo = Math.min(a.id, b.id);
  const hi = Math.max(a.id, b.id);
  const key = (lo * 53 + hi) * 2654435761 >>> 0;
  const r = (key & 0xFFFF) / 0xFFFF;
  return same ? 0.70 + r * 0.15 : 0.55 + r * 0.15;
}

// ── Agent glyph library ──────────────────────────────────────
// Each agent gets a UNIQUE iconographic core SVG centered in its sigil.
// The shapes are mythologically resonant — ATLAS bears a globe, KRAKEN
// tentacles, HERA a crown, ECHO concentric ripples, OVERSEER an all-seeing
// eye. This is what makes the 25 sigils read as individuals, not regulars.
//
// Each glyph function receives:
//   { cx, cy, r, color }
// where (cx, cy) is the center and `r` is the inner radius budget (~size*0.18).
// Glyphs must stay inside a circle of radius ~r*1.6 for the layout to balance.
function agentGlyph(id, ctx) {
  const { cx, cy, r, color } = ctx;
  const s = (k) => r * k;          // shorthand for "fraction of r"
  const stroke = color;
  const sw = Math.max(0.9, r * 0.10);

  // Helper builders
  const line = (x1, y1, x2, y2, w = sw, o = 1) => (
    <line x1={x1} y1={y1} x2={x2} y2={y2}
      stroke={stroke} strokeWidth={w} strokeLinecap="round" opacity={o}/>
  );
  const circ = (x, y, rr, fill = 'none', o = 1) => (
    <circle cx={x} cy={y} r={rr} fill={fill === 'none' ? 'none' : color}
      stroke={fill === 'none' ? stroke : 'none'} strokeWidth={sw} opacity={o}/>
  );

  switch (id) {
    // Liquidity (1–5) — containment / measurement
    case 1:  // ATLAS — globe with meridians
      return (<g>
        <circle cx={cx} cy={cy} r={s(1.0)} fill="none" stroke={stroke} strokeWidth={sw}/>
        <ellipse cx={cx} cy={cy} rx={s(0.45)} ry={s(1.0)} fill="none" stroke={stroke} strokeWidth={sw*0.7} opacity="0.7"/>
        <line x1={cx-s(1.0)} y1={cy} x2={cx+s(1.0)} y2={cy} stroke={stroke} strokeWidth={sw*0.7} opacity="0.7"/>
      </g>);
    case 2:  // KRAKEN — three radiating tentacles
      return (<g>
        {[0, 1, 2].map(i => {
          const a = (i / 3) * Math.PI * 2 - Math.PI / 2;
          const path = `M ${cx} ${cy} Q ${cx + Math.cos(a)*s(0.6)} ${cy + Math.sin(a)*s(0.6) - s(0.5)} ${cx + Math.cos(a)*s(1.2)} ${cy + Math.sin(a)*s(1.2)}`;
          return <path key={i} d={path} stroke={stroke} strokeWidth={sw} fill="none" strokeLinecap="round"/>;
        })}
        <circle cx={cx} cy={cy} r={s(0.25)} fill={color}/>
      </g>);
    case 3:  // NAUTILUS — spiral
      return (<path d={(() => {
        let d = '';
        const pts = 32;
        for (let i = 0; i <= pts; i++) {
          const t = (i / pts) * Math.PI * 4;
          const rr = s(0.15 + (i / pts) * 0.9);
          const x = cx + Math.cos(t) * rr;
          const y = cy + Math.sin(t) * rr;
          d += (i === 0 ? 'M' : 'L') + x.toFixed(1) + ' ' + y.toFixed(1) + ' ';
        }
        return d;
      })()} stroke={stroke} strokeWidth={sw} fill="none" strokeLinecap="round"/>);
    case 4:  // OCEAN — horizon waves
      return (<g>
        {[-0.4, 0, 0.4].map((dy, i) => (
          <path key={i}
            d={`M ${cx-s(1.1)} ${cy+s(dy)} Q ${cx-s(0.55)} ${cy+s(dy-0.3)} ${cx} ${cy+s(dy)} T ${cx+s(1.1)} ${cy+s(dy)}`}
            stroke={stroke} strokeWidth={sw*0.85} fill="none" strokeLinecap="round" opacity={1 - Math.abs(dy)}/>
        ))}
      </g>);
    case 5:  // KELP — vertical strands
      return (<g>
        {[-0.7, -0.3, 0.1, 0.5].map((dx, i) => (
          <path key={i}
            d={`M ${cx+s(dx)} ${cy-s(1.0)} Q ${cx+s(dx)+s(0.15*(i%2?1:-1))} ${cy} ${cx+s(dx)} ${cy+s(1.0)}`}
            stroke={stroke} strokeWidth={sw*0.85} fill="none" strokeLinecap="round"/>
        ))}
      </g>);

    // Volatility (6–10) — motion, disturbance
    case 6:  // HERA — three-point crown
      return (<g>
        {line(cx-s(0.9), cy+s(0.6), cx-s(0.9), cy-s(0.2))}
        {line(cx, cy+s(0.6), cx, cy-s(0.9))}
        {line(cx+s(0.9), cy+s(0.6), cx+s(0.9), cy-s(0.2))}
        {line(cx-s(0.9), cy+s(0.6), cx+s(0.9), cy+s(0.6))}
        {line(cx-s(0.9), cy-s(0.2), cx, cy+s(0.1), sw*0.8)}
        {line(cx+s(0.9), cy-s(0.2), cx, cy+s(0.1), sw*0.8)}
      </g>);
    case 7:  // STORM — lightning bolt
      return (<path
        d={`M ${cx-s(0.3)} ${cy-s(1.1)} L ${cx+s(0.4)} ${cy-s(0.2)} L ${cx-s(0.05)} ${cy-s(0.05)} L ${cx+s(0.3)} ${cy+s(1.1)} L ${cx-s(0.4)} ${cy+s(0.05)} L ${cx-s(0.0)} ${cy-s(0.1)} Z`}
        stroke={stroke} strokeWidth={sw} fill={color} fillOpacity="0.32" strokeLinejoin="round"/>);
    case 8:  // IRIS — eye/rainbow arc
      return (<g>
        <path d={`M ${cx-s(1.1)} ${cy} Q ${cx} ${cy-s(0.95)} ${cx+s(1.1)} ${cy}`}
          stroke={stroke} strokeWidth={sw} fill="none" strokeLinecap="round"/>
        <path d={`M ${cx-s(1.1)} ${cy} Q ${cx} ${cy+s(0.95)} ${cx+s(1.1)} ${cy}`}
          stroke={stroke} strokeWidth={sw} fill="none" strokeLinecap="round"/>
        <circle cx={cx} cy={cy} r={s(0.4)} stroke={stroke} strokeWidth={sw*0.8} fill="none"/>
        <circle cx={cx} cy={cy} r={s(0.18)} fill={color}/>
      </g>);
    case 9:  // GALE — three curved arrows (wind)
      return (<g>
        {[0, 1, 2].map(i => {
          const a = (i / 3) * Math.PI * 2 - Math.PI / 2;
          const x0 = cx + Math.cos(a) * s(0.4);
          const y0 = cy + Math.sin(a) * s(0.4);
          const x1 = cx + Math.cos(a) * s(1.1);
          const y1 = cy + Math.sin(a) * s(1.1);
          const cx1 = cx + Math.cos(a + 0.7) * s(0.95);
          const cy1 = cy + Math.sin(a + 0.7) * s(0.95);
          return <path key={i} d={`M ${x0} ${y0} Q ${cx1} ${cy1} ${x1} ${y1}`}
            stroke={stroke} strokeWidth={sw} fill="none" strokeLinecap="round"/>;
        })}
      </g>);
    case 10: // CALM — single still ring, single still dot
      return (<g>
        {circ(cx, cy, s(0.85), 'none')}
        <circle cx={cx} cy={cy} r={s(0.18)} fill={color}/>
      </g>);

    // Pattern (11–15) — sight / divination
    case 11: // ARGUS — three eyes
      return (<g>
        {[-0.7, 0, 0.7].map((dx, i) => (
          <g key={i} opacity={i === 1 ? 1 : 0.85}>
            <ellipse cx={cx+s(dx)} cy={cy} rx={s(0.3)} ry={s(0.5)} stroke={stroke} strokeWidth={sw*0.85} fill="none"/>
            <circle cx={cx+s(dx)} cy={cy} r={s(0.15)} fill={color}/>
          </g>
        ))}
      </g>);
    case 12: // SCOUT — arrow / compass
      return (<g>
        {line(cx, cy-s(1.0), cx, cy+s(0.5))}
        <path d={`M ${cx-s(0.4)} ${cy-s(0.4)} L ${cx} ${cy-s(1.0)} L ${cx+s(0.4)} ${cy-s(0.4)}`} stroke={stroke} strokeWidth={sw} fill="none" strokeLinejoin="round"/>
        {line(cx-s(0.6), cy+s(0.6), cx+s(0.6), cy+s(0.6), sw*0.7)}
      </g>);
    case 13: // ORACLE — pyramid / triangle eye
      return (<g>
        <polygon points={`${cx},${cy-s(1.0)} ${cx-s(0.9)},${cy+s(0.6)} ${cx+s(0.9)},${cy+s(0.6)}`}
          stroke={stroke} strokeWidth={sw} fill={color} fillOpacity="0.22"/>
        <circle cx={cx} cy={cy-s(0.1)} r={s(0.18)} fill={color}/>
      </g>);
    case 14: // SAGE — open scroll / book
      return (<g>
        {line(cx-s(1.0), cy-s(0.6), cx-s(1.0), cy+s(0.6))}
        {line(cx+s(1.0), cy-s(0.6), cx+s(1.0), cy+s(0.6))}
        <path d={`M ${cx-s(1.0)} ${cy-s(0.6)} Q ${cx} ${cy-s(0.4)} ${cx+s(1.0)} ${cy-s(0.6)}`} stroke={stroke} strokeWidth={sw*0.85} fill="none"/>
        <path d={`M ${cx-s(1.0)} ${cy+s(0.6)} Q ${cx} ${cy+s(0.4)} ${cx+s(1.0)} ${cy+s(0.6)}`} stroke={stroke} strokeWidth={sw*0.85} fill="none"/>
        {line(cx, cy-s(0.45), cx, cy+s(0.45), sw*0.6, 0.7)}
      </g>);
    case 15: // PROPHET — beam / 4-point star
      return (<g>
        {[0, Math.PI/2, Math.PI, Math.PI*1.5].map((a, i) => (
          <line key={i} x1={cx} y1={cy} x2={cx+Math.cos(a)*s(1.0)} y2={cy+Math.sin(a)*s(1.0)}
            stroke={stroke} strokeWidth={sw} strokeLinecap="round"/>
        ))}
        {[Math.PI/4, Math.PI*0.75, Math.PI*1.25, Math.PI*1.75].map((a, i) => (
          <line key={i} x1={cx} y1={cy} x2={cx+Math.cos(a)*s(0.55)} y2={cy+Math.sin(a)*s(0.55)}
            stroke={stroke} strokeWidth={sw*0.7} strokeLinecap="round" opacity="0.8"/>
        ))}
        <circle cx={cx} cy={cy} r={s(0.22)} fill={color}/>
      </g>);

    // Sentiment (16–20) — communication
    case 16: // ECHO — concentric rings
      return (<g>
        {[1.0, 0.7, 0.42].map((rr, i) => (
          <circle key={i} cx={cx} cy={cy} r={s(rr)} fill="none" stroke={stroke}
            strokeWidth={sw*(1 - i*0.15)} opacity={1 - i*0.18}/>
        ))}
      </g>);
    case 17: // HERMES — winged staff (caduceus)
      return (<g>
        {line(cx, cy-s(1.0), cx, cy+s(1.0))}
        <path d={`M ${cx} ${cy-s(0.8)} Q ${cx-s(0.6)} ${cy-s(0.4)} ${cx} ${cy} Q ${cx+s(0.6)} ${cy+s(0.4)} ${cx} ${cy+s(0.8)}`}
          stroke={stroke} strokeWidth={sw*0.85} fill="none" strokeLinecap="round"/>
        <path d={`M ${cx} ${cy-s(0.8)} Q ${cx+s(0.6)} ${cy-s(0.4)} ${cx} ${cy} Q ${cx-s(0.6)} ${cy+s(0.4)} ${cx} ${cy+s(0.8)}`}
          stroke={stroke} strokeWidth={sw*0.85} fill="none" strokeLinecap="round" opacity="0.7"/>
      </g>);
    case 18: // RUMOR — scattered whisper dots
      return (<g>
        {[[-0.7,-0.6],[0.5,-0.7],[-0.2,-0.1],[0.6,0.2],[-0.6,0.4],[0.1,0.7],[-0.4,0.85]].map(([dx,dy], i) => (
          <circle key={i} cx={cx+s(dx)} cy={cy+s(dy)} r={s(0.12 + (i%3)*0.05)} fill={color} opacity={0.6 + (i%3)*0.15}/>
        ))}
      </g>);
    case 19: // BEACON — lighthouse rays
      return (<g>
        <circle cx={cx} cy={cy} r={s(0.32)} fill={color}/>
        <circle cx={cx} cy={cy} r={s(0.55)} fill="none" stroke={stroke} strokeWidth={sw*0.7} opacity="0.7"/>
        {[0,1,2,3,4,5,6,7].map(i => {
          const a = (i / 8) * Math.PI * 2 + Math.PI/8;
          return <line key={i} x1={cx+Math.cos(a)*s(0.75)} y1={cy+Math.sin(a)*s(0.75)}
            x2={cx+Math.cos(a)*s(1.1)} y2={cy+Math.sin(a)*s(1.1)}
            stroke={stroke} strokeWidth={sw*0.7} strokeLinecap="round" opacity="0.75"/>;
        })}
      </g>);
    case 20: // SIREN — twin curves crossing
      return (<g>
        <path d={`M ${cx-s(1.0)} ${cy-s(0.6)} Q ${cx} ${cy} ${cx+s(1.0)} ${cy+s(0.6)}`}
          stroke={stroke} strokeWidth={sw} fill="none" strokeLinecap="round"/>
        <path d={`M ${cx-s(1.0)} ${cy+s(0.6)} Q ${cx} ${cy} ${cx+s(1.0)} ${cy-s(0.6)}`}
          stroke={stroke} strokeWidth={sw} fill="none" strokeLinecap="round" opacity="0.7"/>
        <circle cx={cx} cy={cy} r={s(0.18)} fill={color}/>
      </g>);

    // Risk (21–25) — guardianship / judgment
    case 21: // AEGIS — shield
      return (<path
        d={`M ${cx} ${cy-s(1.0)} L ${cx+s(0.9)} ${cy-s(0.5)} L ${cx+s(0.9)} ${cy+s(0.2)} Q ${cx+s(0.5)} ${cy+s(1.0)} ${cx} ${cy+s(1.1)} Q ${cx-s(0.5)} ${cy+s(1.0)} ${cx-s(0.9)} ${cy+s(0.2)} L ${cx-s(0.9)} ${cy-s(0.5)} Z`}
        stroke={stroke} strokeWidth={sw} fill={color} fillOpacity="0.22" strokeLinejoin="round"/>);
    case 22: // NEMESIS — scales of judgment
      return (<g>
        {line(cx, cy-s(1.0), cx, cy+s(0.9))}
        {line(cx-s(0.9), cy-s(0.5), cx+s(0.9), cy-s(0.5))}
        <path d={`M ${cx-s(0.9)} ${cy-s(0.5)} L ${cx-s(1.1)} ${cy+s(0.1)} L ${cx-s(0.7)} ${cy+s(0.1)} Z`} stroke={stroke} strokeWidth={sw*0.85} fill="none"/>
        <path d={`M ${cx+s(0.9)} ${cy-s(0.5)} L ${cx+s(1.1)} ${cy+s(0.1)} L ${cx+s(0.7)} ${cy+s(0.1)} Z`} stroke={stroke} strokeWidth={sw*0.85} fill="none"/>
        {line(cx-s(0.4), cy+s(0.9), cx+s(0.4), cy+s(0.9), sw*0.7)}
      </g>);
    case 23: // PROMETHEUS — broken ring (chain)
      return (<g>
        <path d={`M ${cx} ${cy-s(1.0)} A ${s(1.0)} ${s(1.0)} 0 1 1 ${cx-s(0.05)} ${cy-s(1.0)}`}
          stroke={stroke} strokeWidth={sw} fill="none" strokeLinecap="round"/>
        {line(cx-s(0.4), cy-s(0.3), cx+s(0.4), cy+s(0.3), sw*1.1)}
      </g>);
    case 24: // STYX — river crossing (two banks + bridge)
      return (<g>
        {line(cx-s(1.1), cy-s(0.4), cx+s(1.1), cy-s(0.4))}
        {line(cx-s(1.1), cy+s(0.4), cx+s(1.1), cy+s(0.4))}
        {line(cx-s(0.3), cy-s(0.4), cx-s(0.3), cy+s(0.4), sw*0.7, 0.7)}
        {line(cx+s(0.3), cy-s(0.4), cx+s(0.3), cy+s(0.4), sw*0.7, 0.7)}
        <circle cx={cx} cy={cy} r={s(0.18)} fill={color}/>
      </g>);
    case 25: // OVERSEER — all-seeing eye (eye inside triangle)
      return (<g>
        <polygon points={`${cx},${cy-s(1.0)} ${cx-s(0.95)},${cy+s(0.65)} ${cx+s(0.95)},${cy+s(0.65)}`}
          stroke={stroke} strokeWidth={sw} fill="none"/>
        <ellipse cx={cx} cy={cy+s(0.05)} rx={s(0.55)} ry={s(0.32)} stroke={stroke} strokeWidth={sw*0.85} fill="none"/>
        <circle cx={cx} cy={cy+s(0.05)} r={s(0.18)} fill={color}/>
      </g>);

    default:
      return <circle cx={cx} cy={cy} r={s(0.5)} fill={color} fillOpacity="0.32" stroke={stroke} strokeWidth={sw}/>;
  }
}
// Synapse-mode constellation glyph. Layered build:
//   1. Outer dashed ring in the agent's core palette color
//   2. Inner faint ring at 55.6% radius
//   3. Constellation field — sparse line graph between deterministic points
//      in the duotone partner color (low opacity, like the brand logo)
//   4. Inner polygon + radiating spokes in the core color
//   5. Center pip in the focus-glow color
function Sigil({ id, color, size = 80, dim = false, fieldColor }) {
  const r        = lcg(id * 257 + 911);
  const sides    = 3 + ((id - 1) % 6);
  const spokes   = 5 + (id % 7);
  const rot      = (id * 11) % 360;
  const inner    = size * 0.18;
  const outer    = size * 0.42;
  const center   = size / 2;
  const op       = dim ? 0.5 : 1;
  const fc       = fieldColor || color;

  // Constellation node positions — fixed deterministic points around the ring
  const nodes = Array.from({ length: 12 }).map((_, i) => {
    const angle = (i / 12) * Math.PI * 2 + (id * 0.27);
    const radius = outer * (0.62 + ((r() & 0xff) / 0xff) * 0.32);
    return { x: center + Math.cos(angle) * radius, y: center + Math.sin(angle) * radius };
  });

  // Choose which node pairs to connect — sparse graph (~14-20 lines)
  const lineCount = 10 + (id % 8);
  const lines = [];
  for (let i = 0; i < lineCount; i++) {
    const a = (i * 5 + id) % nodes.length;
    const b = (i * 7 + id * 3 + 4) % nodes.length;
    if (a === b) continue;
    const weight = i % 2 === 0 ? 1 : 0.5;
    const lineOp = i % 2 === 0 ? 0.18 : 0.10;
    lines.push({ a: nodes[a], b: nodes[b], weight, lineOp });
  }

  const polyPoints = Array.from({ length: sides }).map((_, i) => {
    const a = (i / sides) * Math.PI * 2 - Math.PI / 2 + (rot * Math.PI / 180);
    return `${center + Math.cos(a) * inner},${center + Math.sin(a) * inner}`;
  }).join(' ');

  return (
    <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}
         style={{ display: 'block', opacity: op }}
         role="img" aria-label={`Agent ${id} sigil`}>
      {/* outer dashed ring — matches the Alpha Agent Logo geometry */}
      <circle cx={center} cy={center} r={outer + size * 0.04} fill="none"
        stroke={color} strokeWidth={size * 0.025} strokeDasharray={`${size * 0.08} ${size * 0.04}`}
        opacity="0.95"/>

      {/* inner faint ring at ~55.6% (logo spec) */}
      <circle cx={center} cy={center} r={outer * 0.78} fill="none"
        stroke={color} strokeWidth="0.75" opacity="0.32"/>

      {/* constellation lines — duotone partner color */}
      {lines.map((ln, i) => (
        <line key={i}
          x1={ln.a.x} y1={ln.a.y} x2={ln.b.x} y2={ln.b.y}
          stroke={fc} strokeWidth={ln.weight} opacity={ln.lineOp}
          strokeLinecap="round"/>
      ))}

      {/* radiating spokes — anchored to the inner polygon */}
      {Array.from({ length: spokes }).map((_, i) => {
        const a = (i / spokes) * Math.PI * 2;
        const x1 = center + Math.cos(a) * (outer * 0.55);
        const y1 = center + Math.sin(a) * (outer * 0.55);
        const x2 = center + Math.cos(a) * (outer * 0.74);
        const y2 = center + Math.sin(a) * (outer * 0.74);
        return <line key={`sp${i}`} x1={x1} y1={y1} x2={x2} y2={y2}
          stroke={color} strokeWidth="0.7" opacity="0.4"/>;
      })}

      {/* UNIQUE agent glyph — one of 25 hand-designed iconographic cores */}
      {agentGlyph(id, { cx: center, cy: center, r: size * 0.21, color })}
    </svg>
  );
}

// ── Agent lookup ──────────────────────────────────────────────
function agentById(id)        { return AGENTS.find(a => a.id === id); }
function agentsBySpec(spec)   { return AGENTS.filter(a => a.specialty === spec); }
const ALL_SPECS              = ['liquidity', 'volatility', 'pattern', 'sentiment', 'risk'];

// ── Aggregate roster stats ────────────────────────────────────
function aggregateStats() {
  let totalStake = 0, totalVotes = 0, totalSlashes = 0, totalCleared = 0;
  for (const a of AGENTS) {
    const s = statsFor(a.id);
    totalStake += s.stakeEth;
    totalVotes += s.voteCount;
    totalSlashes += s.slashCount;
    totalCleared += s.cleared;
  }
  return {
    totalAgents: AGENTS.length,
    totalStake: +totalStake.toFixed(2),
    totalVotes,
    totalCleared,
    totalSlashes,
    avgAccuracy: +(AGENTS.reduce((s, a) => s + statsFor(a.id).accuracy, 0) / AGENTS.length).toFixed(3),
  };
}

// ── Palette resolution per agent ─────────────────────────────
// Each agent's `palette` field names a QP atomic color. We resolve it lazily
// to handle script load order; QP and its helpers live in palette-qp.jsx.
function agentPaletteColor(agent, surface = 'dark') {
  if (typeof qpA11y === 'function') {
    return qpA11y(agent.palette, surface);
  }
  if (typeof qpByName !== 'function') return SPEC[agent.specialty].color;
  const c = qpByName(agent.palette);
  return c ? c.hex : SPEC[agent.specialty].color;
}
function agentPaletteRaw(agent) {
  if (typeof qpByName !== 'function') return SPEC[agent.specialty].color;
  const c = qpByName(agent.palette);
  return c ? c.hex : SPEC[agent.specialty].color;
}
function agentPaletteEntry(agent) {
  if (typeof qpByName !== 'function') return null;
  return qpByName(agent.palette);
}
function agentFieldKey(agent) {
  const entry = agentPaletteEntry(agent);
  if (!entry || typeof qpFieldFor !== 'function') return null;
  return qpFieldFor(entry);
}
function agentFieldColor(agent) {
  const key = agentFieldKey(agent);
  if (!key || typeof qpDuotone !== 'function') return null;
  const duo = qpDuotone(key);
  return duo ? duo[1] : null;
}

Object.assign(window, {
  NFT_CONTRACT, COUNCIL_OWNER, BASE_CHAIN,
  basescanContract, basescanAddr, basescanTx,
  SPEC, ALL_SPECS, AGENTS,
  tbaFor, fmtAddr, statsFor, timeAgo, agreementBetween,
  agentById, agentsBySpec, aggregateStats,
  agentPaletteColor, agentPaletteRaw, agentPaletteEntry, agentFieldKey, agentFieldColor,
  Sigil,
});
