// kova/vault2.jsx — VAULT v2 · CEO capital-intelligence dashboard
// ----------------------------------------------------------------
// "Palantir Foundry, redesigned for a luxury global jewellery CEO."
// VAULT is the hub where signals (Orbit), prices (Price), designs (Kraft)
// and logistics (Connect) converge into one approve / reject decision feed.
//
// This file is fully self-contained (modular isolation): it ships its own
// atoms (Sparkline, Meter, ConfRing, Delta, Tag), molecules (KpiCard,
// StatCard, MeterRow, TrendRow, SectionHeading, DrawerSection), organisms
// (KpiStrip, WorldFlowMap, RankedMatrix, SignalList, VaultDecisionCard,
// ApprovalQueue, FilterBar), drawer panels, and the assembled
// VaultCeoDashboard page + mock data. Everything resolves to tokens.css.
//
// Frontend-only: every action is local (toast / banner / drawer / modal).
// The wiring points to a future backend are documented in
// api-docs/VAULT_V2_API.md.
// ----------------------------------------------------------------

const {
  Btn, Chip, Icon, Card, Drawer, Modal, ToastStack, Switch, Input, Textarea,
} = window.K;

const { useState, useMemo, useEffect, useRef, useCallback } = React;


/* ============================================================
   ACTION + HEAT METADATA
   ============================================================ */
const V_ACTION = {
  transfer: { label: 'Transfer', tone: 'blue',    hair: 'var(--accent)',   icon: 'transfer' },
  reprice:  { label: 'Reprice',  tone: 'warn',    hair: 'var(--warn)',     icon: 'reprice'  },
  remake:   { label: 'Remake',   tone: 'indigo',  hair: 'var(--m-kraft)',  icon: 'remake'   },
  hold:     { label: 'Hold',     tone: 'neutral', hair: 'var(--mute)',     icon: 'hold'     },
  melt:     { label: 'Melt',     tone: 'risk',    hair: 'var(--danger)',   icon: 'melt'     },
};
const HEAT = {
  high:   { color: 'var(--danger)', label: 'Ageing stock' },
  demand: { color: 'var(--accent)', label: 'Demand pull' },
  mid:    { color: 'var(--warn)',   label: 'Watch' },
};


/* ============================================================
   MOCK DATA · §6 data model (indicative figures)
   ============================================================ */
const VAULT_DATA = {
  regions: [
    { id: 'usa', name: 'United States', short: 'USA', x: 235, y: 172, locked: 6.8, age: 168, velocity: 41, demand: 58, marginRisk: 72, opportunity: 88, sellThrough: 34, trend: 'Heavy bridal cooling', heat: 'high', unitsK: 4.1,
      topCats: [['Heavy bridal', 38], ['Diamond studs', 22], ['Tennis bracelets', 16]],
      note: 'Ageing heavy-bridal stock with the lowest velocity in the network. Demand is migrating to the Gulf — this is the prime transfer + reprice candidate.' },
    { id: 'eu', name: 'Europe', short: 'EU', x: 505, y: 150, locked: 4.2, age: 152, velocity: 55, demand: 63, marginRisk: 64, opportunity: 79, sellThrough: 47, trend: 'Studs oversupplied', heat: 'mid', unitsK: 3.3,
      topCats: [['Diamond studs', 41], ['Minimal bands', 27], ['Pendants', 14]],
      note: 'A diamond-stud glut is pressuring margin. A measured reprice clears inventory without eroding the brand price ladder.' },
    { id: 'uae', name: 'United Arab Emirates', short: 'UAE', x: 612, y: 230, locked: 3.1, age: 96, velocity: 78, demand: 91, marginRisk: 28, opportunity: 84, sellThrough: 68, trend: 'Bridal demand +23%', heat: 'demand', unitsK: 2.2,
      topCats: [['Bridal sets', 44], ['High-karat', 31], ['Rose gold', 12]],
      note: 'Strongest bridal pull and highest velocity in the network — the natural destination for the USA transfer stock.' },
    { id: 'india', name: 'India', short: 'IND', x: 702, y: 250, locked: 3.6, age: 188, velocity: 36, demand: 74, marginRisk: 58, opportunity: 82, sellThrough: 39, trend: 'Wedding season approaching', heat: 'high', unitsK: 5.0,
      topCats: [['High-karat bridal', 49], ['Temple jewellery', 21], ['Gold chains', 18]],
      note: 'High-karat bridal is aged, but wedding season is ~60 days out. Hold to protect margin rather than discounting into a rising-demand window.' },
    { id: 'gcc', name: 'Wider GCC', short: 'GCC', x: 588, y: 286, locked: 1.7, age: 124, velocity: 62, demand: 70, marginRisk: 44, opportunity: 71, sellThrough: 53, trend: 'Rose gold +17%', heat: 'mid', unitsK: 1.4,
      topCats: [['Rose gold', 33], ['Minimal', 29], ['Pendants', 19]],
      note: 'Balanced market with rose-gold momentum — a candidate for remaking ageing yellow-gold stock into the trending palette.' },
  ],
  flows: [
    { id: 'fl1', from: 'usa',   to: 'uae', skus: 214, value: 1.2, cat: 'Bridal',    primary: true },
    { id: 'fl2', from: 'eu',    to: 'uae', skus: 60,  value: 0.4, cat: 'Studs',     primary: false },
    { id: 'fl3', from: 'india', to: 'gcc', skus: 48,  value: 0.3, cat: 'Rose gold', primary: false },
  ],
  kpis: [
    { id: 'k1', label: 'Capital Locked',          value: '$18.4M',     delta: '+4.1%', deltaDir: 'up',   deltaTone: 'risk', desc: 'Inventory > 120 days',           action: 'View Breakdown',  drawer: 'capital',    spark: [12, 13, 13.5, 15, 16.2, 17.1, 18.4] },
    { id: 'k2', label: 'Deadstock Risk',          value: '$4.2M',      delta: '+0.6M', deltaDir: 'up',   deltaTone: 'risk', desc: 'Sell-probability < 20%',         action: 'See Risk Drivers', drawer: 'risk',       spark: [2.9, 3.1, 3.4, 3.5, 3.8, 4.0, 4.2] },
    { id: 'k3', label: 'Recommended Recovery',    value: '$2.8M',      delta: '5 actions', deltaDir: 'flat', deltaTone: 'pos', desc: 'Recoverable this quarter',    action: 'Review Actions',  drawer: '__feed',     spark: [0.4, 0.9, 1.3, 1.8, 2.2, 2.5, 2.8] },
    { id: 'k4', label: 'Expected Revenue Uplift', value: '+$3.1M',     delta: 'if approved', deltaDir: 'flat', deltaTone: 'pos', desc: 'If recommendations approved', action: 'Run Simulation', drawer: 'simulation', spark: [1.1, 1.5, 1.9, 2.2, 2.6, 2.9, 3.1] },
    { id: 'k5', label: 'Margin Recovery',         value: '+11.8%',     delta: 'vs liq.', deltaDir: 'up', deltaTone: 'pos',  desc: 'vs. liquidation baseline',       action: 'Why?',            drawer: 'margin',     spark: [4, 6, 7.5, 9, 10.2, 11, 11.8] },
    { id: 'k6', label: 'High-Risk Markets',       value: 'USA · India', delta: '2 of 5', deltaDir: 'flat', deltaTone: 'warn', desc: 'Ageing stock + low velocity',  action: 'Investigate',     drawer: 'regional:usa', spark: [] },
  ],
  capitalBreakdown: [
    { market: 'United States', value: 6.8, pct: 37 },
    { market: 'Europe',        value: 4.2, pct: 23 },
    { market: 'India',         value: 3.6, pct: 20 },
    { market: 'UAE',           value: 2.1, pct: 11 },
    { market: 'Wider GCC',     value: 1.7, pct: 9  },
  ],
  riskDrivers: [
    { id: 'rd1', icon: 'clock',   label: 'Inventory aged > 240 days',        impact: '$1.9M', tone: 'risk' },
    { id: 'rd2', icon: 'trend',   label: 'Sell-through collapse (heavy bridal)', impact: '$1.1M', tone: 'risk' },
    { id: 'rd3', icon: 'reprice', label: 'Price above market band',          impact: '$0.7M', tone: 'warn' },
    { id: 'rd4', icon: 'globe',   label: 'Demand migrated to other region',  impact: '$0.5M', tone: 'warn' },
  ],
  marginStrategies: [
    { label: 'Transfer to demand', value: 5.4, tone: 'blue' },
    { label: 'Targeted reprice',   value: 3.1, tone: 'warn' },
    { label: 'Remake to trend',    value: 2.0, tone: 'indigo' },
    { label: 'Hold for season',    value: 1.3, tone: 'pos' },
  ],
  trends: [
    { id: 't1', label: 'Dubai bridal',     region: 'UAE',    delta: '+23%', dir: 'up',   note: 'Velocity outpacing supply', spark: [40, 44, 49, 55, 61, 68, 78] },
    { id: 't2', label: 'USA heavy bridal', region: 'USA',    delta: '-12%', dir: 'down', note: 'Demand cooling, stock ageing', spark: [62, 58, 55, 50, 46, 42, 38] },
    { id: 't3', label: 'Rose gold',        region: 'GCC',    delta: '+17%', dir: 'up',   note: 'Palette momentum across Gulf', spark: [30, 33, 36, 40, 44, 49, 53] },
    { id: 't4', label: 'Minimal settings', region: 'Europe', delta: '+18%', dir: 'up',   note: 'Younger buyer mix rising', spark: [27, 29, 32, 36, 40, 43, 47] },
  ],
  recommendations: [
    {
      id: 'rec-transfer', type: 'transfer', status: 'pending',
      title: 'Move 214 bridal SKUs from USA to UAE',
      route: { from: 'USA', to: 'UAE' }, timeline: '45-day window',
      confidence: 92, recovery: '+$1.2M', recoveryAmount: 1200, units: 214,
      market: 'USA', category: 'Bridal', metalType: 'White gold', ageBand: '120–240d', trendAlign: 'Aligned',
      reason: 'Heavy-bridal velocity in the USA has fallen to 41 while UAE demand is up 23%. Relocating stock to the demand pull recovers value at full margin instead of discounting.',
      metrics: [['Units', '214'], ['Avg age', '168 d'], ['Gold weight', '3.4 kg'], ['Capital freed', '$1.2M']],
      evidence: {
        points: [
          ['UAE bridal demand +23% QoQ (Orbit signal ORB-2241)', 'pos'],
          ['USA heavy-bridal velocity 41, network-low', 'risk'],
          ['Transfer lane USA→UAE active in Connect, 45-day SLA', 'neutral'],
          ['No price erosion — full-margin sell-through modelled', 'pos'],
        ],
        confidenceBars: [['Demand signal strength', 94], ['Lane reliability', 88], ['Margin protection', 96], ['Stock match to demand', 90]],
      },
    },
    {
      id: 'rec-remake', type: 'remake', status: 'pending',
      title: 'Remake Victorian necklace set for current taste',
      timeline: 'Studio · 6-week lead', cta: { label: 'Create KRAFT Brief' },
      confidence: 84, recovery: '+$480K', recoveryAmount: 480, units: 36,
      market: 'Europe', category: 'Necklaces', metalType: 'Yellow gold', ageBand: '> 240d', trendAlign: 'Misaligned',
      reason: 'The Victorian set has sat 280+ days with near-zero velocity. The stones and gold are recoverable; a redesign brief into the trending minimal palette converts dead capital into a sellable line.',
      metrics: [['Units', '36'], ['Avg age', '284 d'], ['Gold weight', '1.1 kg'], ['Recoverable', '$480K']],
      evidence: {
        points: [
          ['Minimal settings demand +18% in Europe (ORB-2255)', 'pos'],
          ['Victorian set velocity ~0 over 280 days', 'risk'],
          ['Stone + gold reclaim value verified by Price', 'neutral'],
          ['Studio capacity available in next sprint', 'pos'],
        ],
        confidenceBars: [['Trend fit of redesign', 86], ['Reclaim value certainty', 90], ['Studio capacity', 82], ['Historical remake ROI', 78]],
      },
    },
    {
      id: 'rec-reprice', type: 'reprice', status: 'pending',
      title: 'Reprice 1,180 diamond studs in Europe',
      timeline: 'Effective immediately',
      confidence: 88, recovery: '+$640K', recoveryAmount: 640, units: 1180,
      market: 'Europe', category: 'Diamond studs', metalType: 'White gold', ageBand: '120–240d', trendAlign: 'Watch',
      reason: 'A diamond-stud glut is pricing 6.5% above the live market band. A targeted reprice restores velocity and clears $640K of locked capital while staying inside the CEO margin floor.',
      metrics: [['Units', '1,180'], ['Avg age', '152 d'], ['Margin Δ', '−6.5%'], ['Capital freed', '$640K']],
      evidence: {
        points: [
          ['Listed price 6.5% above market band (Price engine)', 'risk'],
          ['Stud velocity 55, recoverable with price move', 'neutral'],
          ['Reprice stays above CEO margin floor', 'pos'],
          ['Competitor band tightened 4% last 30 days', 'warn'],
        ],
        confidenceBars: [['Price-band evidence', 92], ['Velocity recovery model', 84], ['Margin-floor safety', 95], ['Competitor read', 80]],
      },
    },
    {
      id: 'rec-hold', type: 'hold', status: 'pending',
      title: 'Hold 320 high-karat bridal in India for wedding season',
      timeline: 'Re-evaluate in 60 days',
      confidence: 79, recovery: 'protect $1.4M', recoveryAmount: 1400, units: 320,
      market: 'India', category: 'High-karat bridal', metalType: 'High-karat gold', ageBand: '> 240d', trendAlign: 'Aligned',
      reason: 'Although aged, this stock sits ahead of the Indian wedding season (~60 days out) where high-karat bridal demand spikes. Holding protects $1.4M of margin that a pre-season discount would destroy.',
      metrics: [['Units', '320'], ['Avg age', '188 d'], ['Gold weight', '6.2 kg'], ['Protected', '$1.4M']],
      evidence: {
        points: [
          ['Wedding season demand spike in ~60 days (ORB-2270)', 'pos'],
          ['High-karat bridal margin best in network', 'pos'],
          ['Stock aged but not at deadstock threshold', 'warn'],
          ['Discounting now would forfeit seasonal premium', 'risk'],
        ],
        confidenceBars: [['Seasonality evidence', 88], ['Margin protection value', 90], ['Carry-cost tolerance', 70], ['Demand-timing certainty', 68]],
      },
    },
    {
      id: 'rec-melt', type: 'melt', status: 'pending',
      title: 'Melt 540 low-probability SKUs → refinery',
      timeline: 'Irreversible · refinery 10-day',
      confidence: 81, recovery: '+$390K', recoveryAmount: 390, units: 540,
      market: 'USA', category: 'Mixed', metalType: 'Mixed gold', ageBand: '> 240d', trendAlign: 'Misaligned',
      reason: 'These 540 SKUs have sell-probability below 12% across every market and no remake fit. Recovering the gold at refinery rate returns $390K of trapped capital. This action is irreversible.',
      metrics: [['Units', '540'], ['Avg age', '312 d'], ['Gold weight', '4.8 kg'], ['Gold recovery', '$390K']],
      evidence: {
        points: [
          ['Sell-probability < 12% in all 5 markets', 'risk'],
          ['No viable remake fit (Kraft assessment)', 'neutral'],
          ['Refinery rate locked for 10 days', 'pos'],
          ['Irreversible — gold reclaim cannot be undone', 'risk'],
        ],
        confidenceBars: [['Deadstock certainty', 92], ['Remake-fit ruled out', 86], ['Refinery rate lock', 88], ['Reversibility risk', 58]],
      },
    },
  ],
  approvals: [
    { id: 'ap1', type: 'transfer', title: 'USA → UAE bridal transfer manifest', meta: 'CONNECT · logistics co-sign', value: '+$1.2M', urgency: 'high' },
    { id: 'ap2', type: 'reprice',  title: 'Europe diamond-stud reprice',        meta: 'PRICE · Head of Pricing',     value: '+$640K', urgency: 'mid' },
    { id: 'ap3', type: 'remake',   title: 'Victorian set redesign brief',       meta: 'KRAFT · Studio Director',     value: '+$480K', urgency: 'mid' },
    { id: 'ap4', type: 'melt',     title: 'Refinery melt · 540 SKUs',           meta: 'CONNECT · refinery partner',  value: '+$390K', urgency: 'low' },
  ],
};


/* ============================================================
   ATOMS
   ============================================================ */

// Sparkline — micro line+area chart. data[], w, h, tone, fill
const TONE_STROKE = { pos: 'var(--success)', risk: 'var(--danger)', warn: 'var(--warn)', blue: 'var(--accent)' };
const Sparkline = ({ data = [], w = 96, h = 28, tone = 'blue', fill = true }) => {
  if (!data.length) return <svg width={w} height={h} aria-hidden="true" />;
  const min = Math.min(...data), max = Math.max(...data);
  const span = max - min || 1;
  const stroke = TONE_STROKE[tone] || TONE_STROKE.blue;
  const pts = data.map((d, i) => {
    const x = (i / (data.length - 1)) * (w - 2) + 1;
    const y = h - 2 - ((d - min) / span) * (h - 4);
    return [x, y];
  });
  const line = pts.map((p, i) => `${i ? 'L' : 'M'}${p[0].toFixed(1)} ${p[1].toFixed(1)}`).join(' ');
  const area = `${line} L${pts[pts.length - 1][0].toFixed(1)} ${h} L${pts[0][0].toFixed(1)} ${h} Z`;
  return (
    <svg width={w} height={h} viewBox={`0 0 ${w} ${h}`} aria-hidden="true" style={{ display: 'block' }}>
      {fill && <path d={area} fill={stroke} opacity="0.1" />}
      <path d={line} fill="none" stroke={stroke} strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="round" />
    </svg>
  );
};

// Meter — horizontal score bar. value, max, tone, h
const Meter = ({ value, max = 100, tone = 'blue', h = 6 }) => {
  const pct = Math.max(0, Math.min(100, (value / max) * 100));
  return (
    <div className="v2-meter" style={{ height: h }}>
      <div className={`v2-meter-fill v2-meter-${tone}`} style={{ width: `${pct}%` }} />
    </div>
  );
};

// ConfRing — circular confidence gauge (0–100), color thresholds
const ConfRing = ({ value = 0, size = 52, stroke = 5 }) => {
  const r = (size - stroke) / 2;
  const c = 2 * Math.PI * r;
  const dash = (value / 100) * c;
  const color = value >= 88 ? 'var(--success)' : value >= 78 ? 'var(--accent)' : 'var(--warn)';
  return (
    <div style={{ position: 'relative', width: size, height: size, flexShrink: 0 }} title={`Confidence ${value}%`}>
      <svg width={size} height={size} style={{ transform: 'rotate(-90deg)' }} aria-hidden="true">
        <circle cx={size / 2} cy={size / 2} r={r} fill="none" stroke="var(--surface-3)" strokeWidth={stroke} />
        <circle cx={size / 2} cy={size / 2} r={r} fill="none" stroke={color} strokeWidth={stroke}
                strokeDasharray={`${dash} ${c}`} strokeLinecap="round"
                style={{ transition: 'stroke-dasharray var(--m-4) var(--ease-out)' }} />
      </svg>
      <div style={{ position: 'absolute', inset: 0, display: 'grid', placeItems: 'center',
                    fontSize: 13, fontWeight: 600, color: 'var(--ink)', fontVariantNumeric: 'tabular-nums', letterSpacing: '-0.02em' }}>
        {value}
      </div>
    </div>
  );
};

// Delta — up/down trend chip
const Delta = ({ value, dir = 'flat', tone = 'mute' }) => {
  const arrow = dir === 'up' ? '↑' : dir === 'down' ? '↓' : '·';
  return (
    <span className={`v2-delta v2-delta-${tone}`}>
      {dir !== 'flat' && <span className="v2-delta-arrow">{arrow}</span>}
      {value}
    </span>
  );
};

// Tag — pill with tone variants + optional leading icon
const Tag = ({ children, tone = 'neutral', icon }) => (
  <span className={`v2-tag v2-tag-${tone}`}>
    {icon && <span className="v2-tag-icon"><Icon name={icon} size={11} /></span>}
    {children}
  </span>
);


/* ============================================================
   MOLECULES
   ============================================================ */

const KpiCard = ({ label, value, delta, deltaDir, deltaTone, desc, action, spark, sparkTone, onAction }) => (
  <div className="v2-kpi">
    <div className="v2-kpi-label">{label}</div>
    <div className="v2-kpi-value">{value}</div>
    <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
      {delta && <Delta value={delta} dir={deltaDir} tone={deltaTone} />}
      {spark && spark.length > 0 && <Sparkline data={spark} w={72} h={20} tone={sparkTone || (deltaTone === 'risk' ? 'risk' : 'blue')} />}
    </div>
    <div className="v2-kpi-desc">{desc}</div>
    <div className="v2-kpi-foot">
      <button className="v2-kpi-link" onClick={onAction}>{action} <Icon name="arrow" size={12} /></button>
    </div>
  </div>
);

const StatCard = ({ label, value, sub, tone }) => (
  <div className="v2-statcard">
    <div className="v2-statcard-label">{label}</div>
    <div className="v2-statcard-value" style={tone ? { color: `var(--${tone})` } : undefined}>{value}</div>
    {sub && <div className="v2-statcard-sub">{sub}</div>}
  </div>
);

const MeterRow = ({ label, value, max = 100, tone = 'blue', display }) => (
  <div className="v2-meterrow">
    <span className="v2-meterrow-label">{label}</span>
    <Meter value={value} max={max} tone={tone} />
    <span className="v2-meterrow-val">{display != null ? display : value}</span>
  </div>
);

const TrendRow = ({ label, region, note, spark, delta, dir, tone, onSend }) => (
  <div className="v2-trendrow">
    <div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 }}>
      <span className="v2-trendrow-label">{label}</span>
      <Tag tone="neutral">{region}</Tag>
    </div>
    <span className="v2-trendrow-note">{note}</span>
    <Sparkline data={spark} w={88} h={22} tone={tone} />
    <Delta value={delta} dir={dir} tone={tone} />
    <button className="v2-trendrow-send" onClick={onSend}>Send to Recommendations →</button>
  </div>
);

const SectionHeading = ({ eyebrow, title, desc, actions }) => (
  <div className="v2-section-head">
    <div>
      {eyebrow && <div className="v2-section-eyebrow">{eyebrow}</div>}
      <h2 className="v2-section-title">{title}</h2>
      {desc && <p className="v2-section-desc">{desc}</p>}
    </div>
    {actions && <div style={{ display: 'flex', gap: 8, flexShrink: 0 }}>{actions}</div>}
  </div>
);

const DrawerSection = ({ eyebrow, children }) => (
  <div className="v2-drawer-section">
    {eyebrow && <div className="v2-drawer-section-eyebrow">{eyebrow}</div>}
    {children}
  </div>
);


/* ============================================================
   ORGANISM · KpiStrip (+ skeleton)
   ============================================================ */
const KpiStripSkeleton = () => (
  <div className="v2-kpistrip">
    {Array.from({ length: 6 }).map((_, i) => (
      <div key={i} className="v2-kpi">
        <div className="k-skel" style={{ width: '60%', height: 11 }} />
        <div className="k-skel" style={{ width: '80%', height: 26, marginTop: 6 }} />
        <div className="k-skel" style={{ width: '40%', height: 12 }} />
        <div className="k-skel" style={{ width: '70%', height: 11, marginTop: 'auto' }} />
      </div>
    ))}
  </div>
);

const KpiStrip = ({ kpis, onAction }) => (
  <div className="v2-kpistrip">
    {kpis.map(k => (
      <KpiCard key={k.id} {...k} sparkTone={k.deltaTone === 'risk' ? 'risk' : k.deltaTone === 'pos' ? 'pos' : 'blue'}
               onAction={() => onAction(k)} />
    ))}
  </div>
);


/* ============================================================
   ORGANISM · WorldFlowMap (hero)
   ============================================================ */
const VB_W = 1000, VB_H = 460;

const bezierMid = (x1, y1, cx, cy, x2, y2) => ({
  x: 0.25 * x1 + 0.5 * cx + 0.25 * x2,
  y: 0.25 * y1 + 0.5 * cy + 0.25 * y2,
});

// Abstract dotted field — graticule + faint dot grid (NOT a literal geo-map)
const MapField = () => {
  const dots = [];
  for (let gx = 40; gx < VB_W; gx += 26) {
    for (let gy = 40; gy < VB_H; gy += 26) {
      // gentle organic thinning so it reads as clustered, not a rigid grid
      const edge = Math.min(gx, VB_W - gx, gy, VB_H - gy);
      if (edge < 30) continue;
      if ((gx + gy) % 52 === 0) continue;
      dots.push(<circle key={`${gx}-${gy}`} className="v2-mapdot" cx={gx} cy={gy} r={1.4} />);
    }
  }
  const lines = [];
  for (let gx = 0; gx <= VB_W; gx += 100) lines.push(<line key={`vx${gx}`} className="v2-graticule" x1={gx} y1={0} x2={gx} y2={VB_H} />);
  for (let gy = 0; gy <= VB_H; gy += 92)  lines.push(<line key={`hz${gy}`} className="v2-graticule" x1={0} y1={gy} x2={VB_W} y2={gy} />);
  return <g>{lines}{dots}</g>;
};

const WorldFlowMap = ({ regions, flows, onSelect, selected }) => {
  const [hover, setHover] = useState(null);
  const byId = useMemo(() => Object.fromEntries(regions.map(r => [r.id, r])), [regions]);
  const hoverRegion = hover ? byId[hover] : null;

  return (
    <div className="v2-map-wrap" style={{ aspectRatio: `${VB_W} / ${VB_H}` }}>
      <div className="v2-map-head">
        <div className="v2-section-eyebrow">Network · capital heat &amp; flows</div>
        <h2 className="v2-section-title" style={{ marginTop: 2 }}>Where capital is trapped</h2>
      </div>
      <div className="v2-map-legend">
        <div className="v2-map-legend-row"><span className="v2-legend-dot" style={{ background: HEAT.high.color }} />Ageing stock</div>
        <div className="v2-map-legend-row"><span className="v2-legend-dot" style={{ background: HEAT.demand.color }} />Demand pull</div>
        <div className="v2-map-legend-row"><span className="v2-legend-dot" style={{ background: HEAT.mid.color }} />Watch</div>
        <div className="v2-map-legend-row" style={{ marginTop: 2, color: 'var(--mute)' }}>
          <span style={{ width: 16, borderTop: '2px dashed var(--accent)', display: 'inline-block' }} />Transfer flow
        </div>
      </div>

      <svg viewBox={`0 0 ${VB_W} ${VB_H}`} width="100%" height="100%" preserveAspectRatio="xMidYMid meet"
           style={{ display: 'block' }}>
        <defs>
          <marker id="v2-arrow" markerWidth="9" markerHeight="9" refX="6" refY="3" orient="auto" markerUnits="userSpaceOnUse">
            <path d="M0,0 L6,3 L0,6 Z" fill="var(--accent)" />
          </marker>
        </defs>

        <MapField />

        {/* flow arcs */}
        {flows.map(f => {
          const a = byId[f.from], b = byId[f.to];
          if (!a || !b) return null;
          const cx = (a.x + b.x) / 2, cy = (a.y + b.y) / 2 - 70;
          const mid = bezierMid(a.x, a.y, cx, cy, b.x, b.y);
          const labelW = `+$${f.value}M`.length * 7 + 12;
          return (
            <g key={f.id}>
              <path className={`v2-arc${f.primary ? ' v2-arc-march' : ''}`}
                    d={`M${a.x} ${a.y} Q${cx} ${cy} ${b.x} ${b.y}`}
                    stroke={f.primary ? 'var(--accent)' : 'var(--accent-2)'}
                    strokeWidth={f.primary ? 2 : 1.4}
                    strokeDasharray={f.primary ? undefined : '4 6'}
                    opacity={f.primary ? 1 : 0.5}
                    markerEnd="url(#v2-arrow)" />
              {f.primary && (
                <g>
                  <rect className="v2-arc-label-bg" x={mid.x - labelW / 2} y={mid.y - 10} width={labelW} height={16} rx={4} />
                  <text className="v2-arc-label" x={mid.x} y={mid.y + 2}>+${f.value}M</text>
                </g>
              )}
            </g>
          );
        })}

        {/* region nodes */}
        {regions.map(r => {
          const color = HEAT[r.heat].color;
          const radius = 10 + r.locked * 2.0;
          const isSel = selected === r.id;
          return (
            <g key={r.id} className={`v2-node${r.heat === 'high' ? ' is-high' : ''}${isSel ? ' is-selected' : ''}`}
               onMouseEnter={() => setHover(r.id)} onMouseLeave={() => setHover(h => (h === r.id ? null : h))}
               onClick={() => onSelect(r.id)}>
              {r.heat === 'high' && <circle className="v2-node-pulse" cx={r.x} cy={r.y} r={radius} fill={color} opacity="0.4" />}
              <circle className="v2-node-core" cx={r.x} cy={r.y} r={radius} fill={color} opacity="0.18" />
              <circle className="v2-node-core" cx={r.x} cy={r.y} r={radius * 0.45} fill={color} />
              <text className="v2-node-label" x={r.x} y={r.y + radius + 15}>{r.short}</text>
              <text className="v2-node-val" x={r.x} y={r.y + radius + 28}>${r.locked}M</text>
            </g>
          );
        })}
      </svg>

      {hoverRegion && (
        <div className="v2-map-tip" style={{ left: `${(hoverRegion.x / VB_W) * 100}%`, top: `${(hoverRegion.y / VB_H) * 100}%` }}>
          <div className="v2-map-tip-title">{hoverRegion.name}</div>
          <div className="v2-map-tip-row"><span>Locked capital</span><b>${hoverRegion.locked}M</b></div>
          <div className="v2-map-tip-row"><span>Avg age</span><b>{hoverRegion.age} d</b></div>
          <div className="v2-map-tip-row"><span>Demand</span><b>{hoverRegion.demand}</b></div>
          <div className="v2-map-tip-row"><span>Velocity</span><b>{hoverRegion.velocity}</b></div>
          <div className="v2-map-tip-cta">Click for regional intelligence →</div>
        </div>
      )}
    </div>
  );
};


/* ============================================================
   ORGANISM · RankedMatrix
   ============================================================ */
const MATRIX_COLS = [
  { key: 'locked',     label: 'Locked Capital', invert: true,  fmt: (v) => `$${v}M`, max: 8 },
  { key: 'velocity',   label: 'Inventory Velocity', invert: false, fmt: (v) => String(v), max: 100 },
  { key: 'demand',     label: 'Demand Score', invert: false, fmt: (v) => String(v), max: 100 },
  { key: 'marginRisk', label: 'Margin Risk',  invert: true,  fmt: (v) => String(v), max: 100 },
  { key: 'opportunity', label: 'Opportunity Score', invert: false, fmt: (v) => String(v), max: 100, primary: true },
];

const RankedMatrix = ({ regions, onSelect }) => {
  const ranked = useMemo(() => [...regions].sort((a, b) => b.opportunity - a.opportunity), [regions]);
  return (
    <Card style={{ padding: 0, overflow: 'hidden' }}>
      <table className="v2-matrix">
        <thead>
          <tr>
            <th style={{ width: 28 }}>#</th>
            <th>Market</th>
            {MATRIX_COLS.map(c => (
              <th key={c.key} className={`is-num${c.primary ? ' is-primary' : ''}`}>
                {c.primary && <Icon name="target" size={11} style={{ marginRight: 4 }} />}{c.label}
              </th>
            ))}
          </tr>
        </thead>
        <tbody>
          {ranked.map((r, i) => (
            <tr key={r.id} className="v2-matrix-row" onClick={() => onSelect(r.id)}>
              <td className="v2-matrix-rank">{i + 1}</td>
              <td><span className="v2-matrix-market">{r.name}</span></td>
              {MATRIX_COLS.map(c => {
                const v = r[c.key];
                const pct = (v / c.max) * 100;
                // tone: for inverted (risk/locked) columns, high = bad
                let tone;
                if (c.primary) tone = 'blue';
                else if (c.invert) tone = pct > 66 ? 'risk' : pct > 40 ? 'warn' : 'pos';
                else tone = pct > 66 ? 'pos' : pct > 40 ? 'warn' : 'risk';
                return (
                  <td key={c.key} className="is-num">
                    <div className={`v2-matrix-cell${c.primary ? ' is-primary' : ''}`} style={{ marginLeft: 'auto' }}>
                      <span className="v2-matrix-cell-val">{c.fmt(v)}</span>
                      <Meter value={v} max={c.max} tone={tone} h={5} />
                    </div>
                  </td>
                );
              })}
            </tr>
          ))}
        </tbody>
      </table>
    </Card>
  );
};


/* ============================================================
   ORGANISM · SignalList
   ============================================================ */
const SignalList = ({ trends, onSend }) => (
  <Card>
    <div style={{ display: 'flex', flexDirection: 'column' }}>
      {trends.map(t => (
        <TrendRow key={t.id} {...t} tone={t.dir === 'up' ? 'pos' : 'risk'}
                  onSend={() => onSend(t)} />
      ))}
    </div>
  </Card>
);


/* ============================================================
   ORGANISM · VaultDecisionCard (the heart)
   ============================================================ */
const VaultDecisionCard = ({ rec, onEvidence, onModify, onReject, onApprove, onKraft, onDraftGpt, draftState }) => {
  const meta = V_ACTION[rec.type];
  const isRemake = rec.type === 'remake';
  const settled = rec.status === 'approved' || rec.status === 'dismissed';
  const draftBusy = draftState?.status === 'loading';
  const draftReady = draftState?.status === 'ready';
  const draftError = draftState?.status === 'error';

  return (
    <div className={`v2-decision${settled ? ' is-settled' : ''}${rec.status === 'dismissed' ? ' is-dismissed' : ''}`}>
      <div className="v2-decision-hair" style={{ background: meta.hair }} />
      <div className="v2-decision-body">
        <div className="v2-decision-head">
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
            <Tag tone={meta.tone} icon={meta.icon}>{meta.label}</Tag>
            {rec.route && (
              <span className="t-mono" style={{ fontSize: 12, color: 'var(--ink-2)' }}>
                {rec.route.from} <Icon name="arrow" size={11} /> {rec.route.to}
              </span>
            )}
            <span className="t-mono" style={{ fontSize: 11, color: 'var(--mute)', display: 'inline-flex', alignItems: 'center', gap: 4 }}>
              <Icon name="clock" size={11} /> {rec.timeline}
            </span>
          </div>
          <ConfRing value={rec.confidence} />
        </div>

        <h3 className="v2-decision-title">{rec.title}</h3>
        <p className="v2-decision-reason">{rec.reason}</p>

        <div className="v2-decision-metrics">
          {rec.metrics.map(([k, v], i) => (
            <div key={i}>
              <div className="v2-decision-metric-l">{k}</div>
              <div className="v2-decision-metric-v">{v}</div>
            </div>
          ))}
        </div>

        <div className="v2-decision-recovery">
          <span className="v2-decision-recovery-l">{rec.type === 'hold' ? 'Margin protected' : 'Expected recovery'}</span>
          <span className="v2-decision-recovery-v">{rec.recovery}</span>
        </div>

        {!settled && (
          <div className="v2-decision-foot">
            <Btn size="sm" variant="ghost" icon="doc" onClick={onEvidence}>View Evidence</Btn>
            {isRemake && (
              <Btn size="sm" variant="ghost" icon="k" onClick={onDraftGpt} loading={draftBusy} disabled={draftBusy}>
                {draftReady ? 'View GPT Draft' : draftError ? 'Retry GPT Draft' : 'Draft with GPT'}
              </Btn>
            )}
            <Btn size="sm" variant="ghost" icon="sliders" onClick={onModify}>Modify</Btn>
            <span className="v2-spacer" />
            <Btn size="sm" variant="reject" onClick={onReject}>Reject</Btn>
            {isRemake
              ? <Btn size="sm" variant="primary" icon="remake" onClick={onKraft}>Create KRAFT Brief</Btn>
              : <Btn size="sm" variant="primary" onClick={onApprove}>
                  {rec.type === 'transfer' ? 'Approve Transfer' : rec.type === 'hold' ? 'Confirm Hold' : 'Approve'}
                </Btn>}
          </div>
        )}
      </div>

      {rec.status === 'approved' && (
        <div className="v2-decision-banner is-approved">
          <Icon name="check" /> {isRemake ? 'Handed to KRAFT — redesign brief opened' : 'Approved — routed to execution'}
        </div>
      )}
      {rec.status === 'dismissed' && (
        <div className="v2-decision-dismissed-note">Dismissed{rec.reason ? ` · ${rec.reason}` : ''} · logged for the engine</div>
      )}
    </div>
  );
};


/* ============================================================
   ORGANISM · ApprovalQueue
   ============================================================ */
const URGENCY = { high: { tone: 'risk', label: 'High' }, mid: { tone: 'warn', label: 'Medium' }, low: { tone: 'neutral', label: 'Low' } };

const ApprovalQueue = ({ approvals, resolved, onResolve }) => {
  const pending = approvals.filter(a => !resolved[a.id]);
  return (
    <Card style={{ position: 'sticky', top: 'calc(var(--top-h) + 16px)' }}>
      <div className="v2-section-eyebrow" style={{ marginBottom: 4 }}>CEO approval rail</div>
      <div className="t-meta" style={{ marginBottom: 12 }}>
        {pending.length} pending · cross-module requests land here
      </div>
      <div>
        {approvals.map(a => {
          const meta = V_ACTION[a.type];
          const u = URGENCY[a.urgency];
          const res = resolved[a.id];
          return (
            <div className="v2-queue-row" key={a.id}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 8, justifyContent: 'space-between' }}>
                <Tag tone={meta.tone} icon={meta.icon}>{meta.label}</Tag>
                {!res && <Tag tone={u.tone}>{u.label}</Tag>}
              </div>
              <div className="v2-queue-title">{a.title}</div>
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
                <span className="v2-queue-meta">{a.meta}</span>
                <span className="t-num" style={{ color: 'var(--success)', fontWeight: 600, fontSize: 13 }}>{a.value}</span>
              </div>
              {res ? (
                <div className="v2-queue-resolved" style={{ color: res === 'Approved' ? 'var(--success)' : res === 'Escalated to board' ? 'var(--warn)' : 'var(--accent)' }}>
                  {res === 'Approved' ? '✓ ' : ''}{res}
                </div>
              ) : (
                <div className="v2-queue-actions">
                  <Btn size="sm" variant="primary" onClick={() => onResolve(a.id, 'Approved')}>Approve</Btn>
                  <Btn size="sm" variant="ghost" onClick={() => onResolve(a.id, 'In review')}>Review</Btn>
                  <Btn size="sm" variant="ghost" onClick={() => onResolve(a.id, 'Escalated to board')}>Escalate</Btn>
                </div>
              )}
            </div>
          );
        })}
      </div>
    </Card>
  );
};


/* ============================================================
   ORGANISM · FilterBar
   ============================================================ */
const Dropdown = ({ label, value, options, onChange }) => {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);
  useEffect(() => {
    if (!open) return;
    const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    setTimeout(() => document.addEventListener('click', onDoc), 0);
    return () => document.removeEventListener('click', onDoc);
  }, [open]);
  const active = value && value !== 'all';
  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <button className={`v2-drop${active ? ' is-active' : ''}`} onClick={() => setOpen(o => !o)}>
        {active ? value : label}<span className="v2-drop-caret">▾</span>
      </button>
      {open && (
        <div className="v2-drop-menu">
          <div className={`v2-drop-item${!active ? ' is-on' : ''}`} onClick={() => { onChange('all'); setOpen(false); }}>
            All {label.toLowerCase()}{!active && <Icon name="check" size={12} />}
          </div>
          {options.map(o => (
            <div key={o} className={`v2-drop-item${value === o ? ' is-on' : ''}`} onClick={() => { onChange(o); setOpen(false); }}>
              {o}{value === o && <Icon name="check" size={12} />}
            </div>
          ))}
        </div>
      )}
    </div>
  );
};

const FILTER_TYPES = [
  { id: 'all', label: 'All' },
  { id: 'transfer', label: 'Move', icon: 'transfer' },
  { id: 'reprice', label: 'Reprice', icon: 'reprice' },
  { id: 'remake', label: 'Remake', icon: 'remake' },
  { id: 'hold', label: 'Hold', icon: 'hold' },
  { id: 'melt', label: 'Melt', icon: 'melt' },
];

const FilterBar = ({ filter, setFilter, counts, options }) => (
  <div className="v2-filterbar">
    <div className="v2-seg">
      {FILTER_TYPES.map(t => (
        <button key={t.id} className={filter.type === t.id ? 'is-on' : ''} onClick={() => setFilter(f => ({ ...f, type: t.id }))}>
          {t.icon && <Icon name={t.icon} size={11} />}{t.label}
          {typeof counts[t.id] === 'number' && <span className="v2-seg-count">{counts[t.id]}</span>}
        </button>
      ))}
    </div>
    <Dropdown label="Country"  value={filter.country}  options={options.country}  onChange={(v) => setFilter(f => ({ ...f, country: v }))} />
    <Dropdown label="Category" value={filter.category} options={options.category} onChange={(v) => setFilter(f => ({ ...f, category: v }))} />
    <Dropdown label="Inventory Age" value={filter.age} options={options.age}     onChange={(v) => setFilter(f => ({ ...f, age: v }))} />
    <Dropdown label="Metal Type" value={filter.metal} options={options.metal}    onChange={(v) => setFilter(f => ({ ...f, metal: v }))} />
    <Dropdown label="Trend Alignment" value={filter.trend} options={options.trend} onChange={(v) => setFilter(f => ({ ...f, trend: v }))} />
  </div>
);


/* ============================================================
   DRAWER PANELS
   ============================================================ */
const RegionalIntelligencePanel = ({ region, onRecommendations, onMove }) => (
  <>
    <DrawerSection eyebrow="At a glance">
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
        <StatCard label="Locked capital" value={`$${region.locked}M`} sub={`${region.unitsK}K units`} tone={region.heat === 'high' ? 'danger' : undefined} />
        <StatCard label="Avg age" value={`${region.age} d`} sub="time in vault" />
        <StatCard label="Sell-through" value={`${region.sellThrough}%`} sub="trailing 90d" />
        <StatCard label="Margin opportunity" value={`${region.opportunity}`} sub="opportunity score" tone="success" />
      </div>
    </DrawerSection>
    <DrawerSection eyebrow="Scorecard">
      <MeterRow label="Demand"          value={region.demand}     tone={region.demand > 66 ? 'pos' : 'warn'} />
      <MeterRow label="Velocity"        value={region.velocity}   tone={region.velocity > 60 ? 'pos' : region.velocity > 40 ? 'warn' : 'risk'} />
      <MeterRow label="Trend alignment" value={region.opportunity} tone="blue" />
      <MeterRow label="Margin risk"     value={region.marginRisk} tone={region.marginRisk > 60 ? 'risk' : 'warn'} />
    </DrawerSection>
    <DrawerSection eyebrow="Top categories">
      {region.topCats.map(([name, pct]) => (
        <MeterRow key={name} label={name} value={pct} tone="mute" display={`${pct}%`} />
      ))}
    </DrawerSection>
    <DrawerSection eyebrow="The read">
      <p className="t-body-sm" style={{ margin: 0, lineHeight: 1.6 }}>{region.note}</p>
    </DrawerSection>
  </>
);

const EvidencePanel = ({ rec }) => {
  const meta = V_ACTION[rec.type];
  return (
    <>
      <DrawerSection eyebrow="Recommendation">
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
          <Tag tone={meta.tone} icon={meta.icon}>{meta.label}</Tag>
          <ConfRing value={rec.confidence} size={40} stroke={4} />
        </div>
        <div style={{ fontSize: 15, fontWeight: 600, color: 'var(--ink)', marginBottom: 4 }}>{rec.title}</div>
        <p className="t-body-sm" style={{ margin: 0, lineHeight: 1.6 }}>{rec.reason}</p>
      </DrawerSection>
      <DrawerSection eyebrow="Evidence trail">
        {rec.evidence.points.map(([text, tone], i) => (
          <div key={i} style={{ display: 'flex', gap: 10, padding: '8px 0', borderTop: i ? '1px solid var(--line-2)' : 'none' }}>
            <span style={{ width: 7, height: 7, borderRadius: 999, flexShrink: 0, marginTop: 6,
                           background: tone === 'pos' ? 'var(--success)' : tone === 'risk' ? 'var(--danger)' : tone === 'warn' ? 'var(--warn)' : 'var(--mute-2)' }} />
            <span className="t-body-sm" style={{ color: 'var(--ink-2)' }}>{text}</span>
          </div>
        ))}
      </DrawerSection>
      <DrawerSection eyebrow="Confidence model">
        {rec.evidence.confidenceBars.map(([label, value]) => (
          <MeterRow key={label} label={label} value={value} tone={value >= 88 ? 'pos' : value >= 78 ? 'blue' : 'warn'} display={`${value}`} />
        ))}
      </DrawerSection>
    </>
  );
};

const GptDraftPanel = ({ rec, draft, error }) => {
  if (error) {
    return (
      <>
        <DrawerSection eyebrow="Local GPT status">
          <div className="v2-ai-draft-callout is-error">
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap', marginBottom: 8 }}>
              <Tag tone="warn" icon="warning">Not generated</Tag>
              {error.code && <span className="t-mono" style={{ fontSize: 12, color: 'var(--mute)' }}>{error.code}</span>}
            </div>
            <p className="t-body-sm" style={{ margin: 0, lineHeight: 1.55 }}>{error.message}</p>
          </div>
        </DrawerSection>
        <DrawerSection eyebrow="Source recommendation">
          <div style={{ fontSize: 15, fontWeight: 600, color: 'var(--ink)', marginBottom: 4 }}>{rec.title}</div>
          <p className="t-body-sm" style={{ margin: 0, lineHeight: 1.6 }}>{rec.reason}</p>
        </DrawerSection>
      </>
    );
  }

  const result = draft?.result || {};
  const limitations = draft?.limitations || [];
  return (
    <>
      <DrawerSection eyebrow="AI draft wrapper">
        <div className="v2-ai-draft-callout">
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap', marginBottom: 8 }}>
            <Tag tone="warn" icon="warning">Draft · unverified</Tag>
            <Tag tone="neutral">Local only</Tag>
            {draft?.model && <span className="t-mono" style={{ fontSize: 12, color: 'var(--mute)' }}>{draft.model}</span>}
          </div>
          <p className="t-body-sm" style={{ margin: 0, lineHeight: 1.55 }}>
            This is model-generated planning text. It is not an approved business action until deterministic receipts and human approval are attached.
          </p>
        </div>
      </DrawerSection>

      <DrawerSection eyebrow="Draft recommendation">
        <div className="v2-ai-draft-block">
          <div className="v2-ai-draft-title">{result.summary || 'Draft unavailable'}</div>
          {result.recommendedAction && <p className="t-body-sm" style={{ margin: '6px 0 0', lineHeight: 1.6 }}>{result.recommendedAction}</p>}
        </div>
      </DrawerSection>

      {result.designBrief && (
        <DrawerSection eyebrow="KRAFT brief direction">
          <p className="t-body-sm" style={{ margin: 0, lineHeight: 1.6 }}>{result.designBrief}</p>
        </DrawerSection>
      )}

      {Array.isArray(result.nextSteps) && result.nextSteps.length > 0 && (
        <DrawerSection eyebrow="Draft next steps">
          <ol className="v2-ai-draft-list">
            {result.nextSteps.map((step, i) => <li key={i}>{step}</li>)}
          </ol>
        </DrawerSection>
      )}

      {Array.isArray(result.evidencePlaceholders) && result.evidencePlaceholders.length > 0 && (
        <DrawerSection eyebrow="Evidence still required">
          <ul className="v2-ai-draft-list">
            {result.evidencePlaceholders.map((item, i) => <li key={i}>{item}</li>)}
          </ul>
        </DrawerSection>
      )}

      {Array.isArray(result.risks) && result.risks.length > 0 && (
        <DrawerSection eyebrow="Risks and limits">
          <ul className="v2-ai-draft-list">
            {result.risks.map((item, i) => <li key={i}>{item}</li>)}
          </ul>
        </DrawerSection>
      )}

      {limitations.length > 0 && (
        <DrawerSection eyebrow="Wrapper limitations">
          <ul className="v2-ai-draft-list">
            {limitations.map((item, i) => <li key={i}>{item}</li>)}
          </ul>
        </DrawerSection>
      )}
    </>
  );
};

const CapitalBreakdownPanel = ({ data }) => (
  <>
    <DrawerSection eyebrow="Total locked > 120 days">
      <StatCard label="Capital locked" value="$18.4M" sub="across 5 markets" tone="danger" />
    </DrawerSection>
    <DrawerSection eyebrow="By market">
      {data.map(d => (
        <MeterRow key={d.market} label={d.market} value={d.pct} tone="risk" display={`$${d.value}M`} />
      ))}
    </DrawerSection>
    <DrawerSection eyebrow="Share of locked capital">
      {data.map(d => (
        <div key={d.market} style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13, padding: '5px 0' }}>
          <span className="t-meta">{d.market}</span>
          <span className="t-num" style={{ color: 'var(--ink)' }}>{d.pct}%</span>
        </div>
      ))}
    </DrawerSection>
  </>
);

const RiskDriversPanel = ({ drivers }) => (
  <>
    <DrawerSection eyebrow="Total at risk">
      <StatCard label="Deadstock risk" value="$4.2M" sub="sell-probability < 20%" tone="danger" />
    </DrawerSection>
    <DrawerSection eyebrow="Risk drivers">
      {drivers.map(d => (
        <div key={d.id} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '10px 0', borderTop: '1px solid var(--line-2)' }}>
          <span style={{ width: 30, height: 30, borderRadius: 8, display: 'grid', placeItems: 'center', flexShrink: 0,
                         background: d.tone === 'risk' ? 'var(--danger-soft)' : 'var(--warn-soft)',
                         color: d.tone === 'risk' ? 'var(--danger)' : 'var(--warn)' }}>
            <Icon name={d.icon} size={14} />
          </span>
          <span className="t-body-sm" style={{ flex: 1, color: 'var(--ink-2)' }}>{d.label}</span>
          <span className="t-num" style={{ fontWeight: 600, color: d.tone === 'risk' ? 'var(--danger)' : 'var(--warn)' }}>{d.impact}</span>
        </div>
      ))}
    </DrawerSection>
  </>
);

const MarginRecoveryPanel = ({ strategies }) => (
  <>
    <DrawerSection eyebrow="Blended margin recovery">
      <StatCard label="vs. liquidation baseline" value="+11.8%" sub="weighted across all recommended actions" tone="success" />
    </DrawerSection>
    <DrawerSection eyebrow="Per-strategy contribution">
      {strategies.map(s => (
        <MeterRow key={s.label} label={s.label} value={s.value} max={6} tone={s.tone} display={`+${s.value}%`} />
      ))}
      <p className="t-meta" style={{ marginTop: 12, lineHeight: 1.6 }}>
        Each strategy is benchmarked against the cash a same-day liquidation would yield. Transferring to live demand
        and remaking to trend protect the most margin; melting recovers gold value only.
      </p>
    </DrawerSection>
  </>
);

const SimulationPanel = ({ aggressiveness, horizon, setAgg, setHorizon }) => {
  // live recompute — deterministic model from the two dials
  const aggF = aggressiveness / 100;     // 0..1
  const horF = horizon / 12;             // 0..1 (months / 12)
  const uplift = (1.4 + aggF * 2.6 + horF * 0.8).toFixed(1);
  const capital = (1.2 + aggF * 2.2 + horF * 0.5).toFixed(1);
  const margin = (6 + aggF * 9 - horF * 2).toFixed(1);
  const actions = Math.max(1, Math.round(2 + aggF * 6 + horF * 1));
  return (
    <>
      <DrawerSection eyebrow="Levers">
        <div className="v2-slider-row">
          <div className="v2-slider-head">
            <span className="v2-slider-label">Aggressiveness</span>
            <span className="v2-slider-val">{aggressiveness < 34 ? 'Conservative' : aggressiveness < 67 ? 'Balanced' : 'Aggressive'} · {aggressiveness}</span>
          </div>
          <input type="range" className="v2-range" min="0" max="100" value={aggressiveness} onChange={(e) => setAgg(+e.target.value)} />
        </div>
        <div className="v2-slider-row">
          <div className="v2-slider-head">
            <span className="v2-slider-label">Horizon</span>
            <span className="v2-slider-val">{horizon} months</span>
          </div>
          <input type="range" className="v2-range" min="1" max="12" value={horizon} onChange={(e) => setHorizon(+e.target.value)} />
        </div>
      </DrawerSection>
      <DrawerSection eyebrow="Projected outcome">
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
          <StatCard label="Revenue uplift"   value={`+$${uplift}M`} tone="success" />
          <StatCard label="Capital recovered" value={`$${capital}M`} tone="success" />
          <StatCard label="Margin recovery"  value={`+${margin}%`} tone={margin > 0 ? 'success' : 'danger'} />
          <StatCard label="Actions triggered" value={String(actions)} sub="recommendations" />
        </div>
      </DrawerSection>
      <p className="t-meta" style={{ lineHeight: 1.6 }}>
        Indicative model. Higher aggressiveness front-loads reprice and melt actions (more capital, less margin);
        a longer horizon favours holds and transfers that protect margin.
      </p>
    </>
  );
};


/* ============================================================
   MODAL CONTENT · Modify · Reject
   ============================================================ */
const ModifyModalBody = ({ rec, qty, setQty, dest }) => {
  const frac = rec.units ? qty / rec.units : 1;
  const modeled = Math.round(rec.recoveryAmount * frac);
  const fmt = (n) => n >= 1000 ? `$${(n / 1000).toFixed(2)}M` : `$${n}K`;
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
      <div className="v2-slider-row" style={{ margin: 0 }}>
        <div className="v2-slider-head">
          <span className="v2-slider-label">Quantity</span>
          <span className="v2-slider-val">{qty} of {rec.units} units</span>
        </div>
        <input type="range" className="v2-range" min="1" max={rec.units} value={qty} onChange={(e) => setQty(+e.target.value)} />
      </div>
      {rec.type === 'transfer' && (
        <div>
          <div className="v2-statcard-label" style={{ marginBottom: 6 }}>Destination</div>
          <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
            {['UAE', 'GCC', 'India', 'Europe'].map(d => (
              <Chip key={d} variant={d === dest.value ? 'solid' : 'mute'} onClick={() => dest.set(d)}>{d}</Chip>
            ))}
          </div>
        </div>
      )}
      <div style={{ background: 'var(--success-soft)', border: '1px solid var(--success-line)', borderRadius: 8, padding: 12,
                    display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
        <span className="t-body-sm" style={{ color: 'var(--ink-2)' }}>Re-modelled recovery</span>
        <span style={{ fontSize: 20, fontWeight: 600, color: 'var(--success)', fontVariantNumeric: 'tabular-nums' }}>+{fmt(modeled)}</span>
      </div>
    </div>
  );
};

const REJECT_REASONS = [
  'Disagree with the evidence',
  'Timing is wrong',
  'Capital better deployed elsewhere',
  'Need more confidence',
  'Already actioned offline',
];
const RejectModalBody = ({ reason, setReason, note, setNote }) => (
  <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
    <div>
      <div className="v2-statcard-label" style={{ marginBottom: 8 }}>Reason</div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
        {REJECT_REASONS.map(r => (
          <label key={r} style={{ display: 'flex', alignItems: 'center', gap: 10, cursor: 'pointer', fontSize: 13,
                                   color: reason === r ? 'var(--ink)' : 'var(--ink-2)' }}
                 onClick={() => setReason(r)}>
            <span style={{ width: 16, height: 16, borderRadius: 999, border: `2px solid ${reason === r ? 'var(--accent)' : 'var(--line-strong)'}`,
                           display: 'grid', placeItems: 'center', flexShrink: 0 }}>
              {reason === r && <span style={{ width: 8, height: 8, borderRadius: 999, background: 'var(--accent)' }} />}
            </span>
            {r}
          </label>
        ))}
      </div>
    </div>
    <div>
      <div className="v2-statcard-label" style={{ marginBottom: 6 }}>Note (optional)</div>
      <Textarea value={note} onChange={setNote} rows={3} placeholder="Context for the engine — this trains future recommendations." />
    </div>
  </div>
);


/* ============================================================
   PAGE · VaultCeoDashboard
   ============================================================ */
const VaultCeoDashboard = ({ user = 'CEO', onCrossModule }) => {
  const D = VAULT_DATA;
  const [loading, setLoading] = useState(true);
  useEffect(() => { const t = setTimeout(() => setLoading(false), 650); return () => clearTimeout(t); }, []);

  const [selectedRegion, setSelectedRegion] = useState(null);
  const [recStatus, setRecStatus] = useState(() => Object.fromEntries(D.recommendations.map(r => [r.id, { status: 'pending' }])));
  const [recRecovery, setRecRecovery] = useState({});
  const [gptDrafts, setGptDrafts] = useState({});
  const [resolved, setResolved] = useState({});
  const [filter, setFilter] = useState({ type: 'all', country: 'all', category: 'all', age: 'all', metal: 'all', trend: 'all' });

  const [drawer, setDrawer] = useState({ open: false, kind: null, data: null });
  const [modal, setModal] = useState({ open: false, kind: null, rec: null });
  const [toasts, setToasts] = useState([]);

  // simulation dials (shared so header + KPI4 + drawer agree)
  const [agg, setAgg] = useState(55);
  const [horizon, setHorizon] = useState(6);

  // modify modal local state
  const [modQty, setModQty] = useState(0);
  const [modDest, setModDest] = useState('UAE');
  // reject modal local state
  const [rejReason, setRejReason] = useState(REJECT_REASONS[0]);
  const [rejNote, setRejNote] = useState('');

  const toastRef = useRef(0);
  const pushToast = useCallback((t) => {
    const id = ++toastRef.current;
    setToasts(ts => [...ts, { id, ...t }]);
    setTimeout(() => setToasts(ts => ts.filter(x => x.id !== id)), 4000);
  }, []);

  const closeDrawer = useCallback(() => setDrawer(d => ({ ...d, open: false })), []);
  const closeModal = useCallback(() => setModal(m => ({ ...m, open: false })), []);

  const scrollToFeed = useCallback(() => {
    document.getElementById('v2-feed')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
  }, []);

  /* ----- KPI / region drawer routing ----- */
  const openDrawerKind = useCallback((kind) => {
    if (kind === '__feed') { scrollToFeed(); return; }
    if (kind.startsWith('regional:')) {
      const r = D.regions.find(x => x.id === kind.split(':')[1]);
      setSelectedRegion(r.id);
      setDrawer({ open: true, kind: 'regional', data: r });
      return;
    }
    setDrawer({ open: true, kind, data: null });
  }, [D.regions, scrollToFeed]);

  const onSelectRegion = useCallback((id) => {
    const r = D.regions.find(x => x.id === id);
    setSelectedRegion(id);
    setDrawer({ open: true, kind: 'regional', data: r });
  }, [D.regions]);

  /* ----- decision-card actions ----- */
  const approveRec = useCallback((rec) => {
    setRecStatus(s => ({ ...s, [rec.id]: { status: 'approved' } }));
    pushToast({ kind: 'success', title: `${V_ACTION[rec.type].label} approved`, body: `${rec.title} · routed to execution` });
  }, [pushToast]);

  const kraftRec = useCallback((rec) => {
    setRecStatus(s => ({ ...s, [rec.id]: { status: 'approved' } }));
    pushToast({ kind: 'info', title: 'KRAFT brief created', body: `${rec.title} · redesign brief opened in Kraft` });
    onCrossModule?.('kraft');
  }, [pushToast, onCrossModule]);

  const openModify = useCallback((rec) => {
    setModQty(rec.units || 1); setModDest(rec.route?.to || 'UAE');
    setModal({ open: true, kind: 'modify', rec });
  }, []);
  const openReject = useCallback((rec) => {
    setRejReason(REJECT_REASONS[0]); setRejNote('');
    setModal({ open: true, kind: 'reject', rec });
  }, []);
  const openEvidence = useCallback((rec) => setDrawer({ open: true, kind: 'evidence', data: rec }), []);
  const openGptDraft = useCallback(async (rec) => {
    const existing = gptDrafts[rec.id];
    if (existing?.status === 'ready') {
      setDrawer({ open: true, kind: 'gpt-draft', data: { rec, draft: existing.data } });
      return;
    }

    setGptDrafts(s => ({ ...s, [rec.id]: { status: 'loading' } }));
    pushToast({ kind: 'info', title: 'Local GPT draft queued', body: 'OPENAI_API_KEY stays in your local server environment.' });

    try {
      const draft = await window.K.requestLocalKraftRecoveryDraft(rec);
      setGptDrafts(s => ({ ...s, [rec.id]: { status: 'ready', data: draft } }));
      setDrawer({ open: true, kind: 'gpt-draft', data: { rec, draft } });
      pushToast({ kind: 'success', title: 'GPT draft ready', body: `${rec.title} · draft only, not approved.` });
    } catch (e) {
      const error = {
        code: e.code || 'local_gpt_error',
        status: e.status,
        message: e.message || 'Local GPT request failed.',
      };
      setGptDrafts(s => ({ ...s, [rec.id]: { status: 'error', error } }));
      setDrawer({ open: true, kind: 'gpt-draft', data: { rec, error } });
      pushToast({
        kind: error.code === 'missing_openai_api_key' ? 'warn' : 'error',
        title: error.code === 'missing_openai_api_key' ? 'OpenAI API key required' : 'Local GPT blocked',
        body: error.message,
      });
    }
  }, [gptDrafts, pushToast]);

  const confirmModify = useCallback(() => {
    const rec = modal.rec;
    const frac = rec.units ? modQty / rec.units : 1;
    const modeled = Math.round(rec.recoveryAmount * frac);
    const disp = modeled >= 1000 ? `+$${(modeled / 1000).toFixed(2)}M` : `+$${modeled}K`;
    setRecRecovery(r => ({ ...r, [rec.id]: disp }));
    closeModal();
    pushToast({ kind: 'success', title: 'Recommendation modified', body: `${modQty} units${rec.type === 'transfer' ? ` → ${modDest}` : ''} · recovery re-modelled to ${disp}` });
  }, [modal.rec, modQty, modDest, closeModal, pushToast]);

  const confirmReject = useCallback(() => {
    const rec = modal.rec;
    setRecStatus(s => ({ ...s, [rec.id]: { status: 'dismissed', reason: rejReason } }));
    closeModal();
    pushToast({ kind: 'warn', title: 'Recommendation dismissed', body: `${rejReason} · logged to train the engine` });
  }, [modal.rec, rejReason, closeModal, pushToast]);

  const resolveApproval = useCallback((id, label) => {
    setResolved(r => ({ ...r, [id]: label }));
    const kind = label === 'Approved' ? 'success' : label === 'Escalated to board' ? 'warn' : 'info';
    pushToast({ kind, title: label, body: `Approval ${id} · ${label.toLowerCase()}` });
  }, [pushToast]);

  const sendTrend = useCallback((t) => {
    pushToast({ kind: 'info', title: 'Sent to Recommendations', body: `${t.label} (${t.region}) ${t.delta} — generating an action` });
    scrollToFeed();
  }, [pushToast, scrollToFeed]);

  /* ----- filtering ----- */
  const decorated = useMemo(() => D.recommendations.map(r => ({
    ...r,
    status: recStatus[r.id]?.status || 'pending',
    reason: recStatus[r.id]?.reason || r.reason,
    recovery: recRecovery[r.id] || r.recovery,
  })), [D.recommendations, recStatus, recRecovery]);

  const counts = useMemo(() => {
    const c = { all: D.recommendations.length };
    FILTER_TYPES.slice(1).forEach(t => { c[t.id] = D.recommendations.filter(r => r.type === t.id).length; });
    return c;
  }, [D.recommendations]);

  const options = useMemo(() => ({
    country:  [...new Set(D.recommendations.map(r => r.market))],
    category: [...new Set(D.recommendations.map(r => r.category))],
    age:      [...new Set(D.recommendations.map(r => r.ageBand))],
    metal:    [...new Set(D.recommendations.map(r => r.metalType))],
    trend:    [...new Set(D.recommendations.map(r => r.trendAlign))],
  }), [D.recommendations]);

  const filtered = useMemo(() => decorated.filter(r => {
    if (filter.type !== 'all' && r.type !== filter.type) return false;
    if (filter.country !== 'all' && r.market !== filter.country) return false;
    if (filter.category !== 'all' && r.category !== filter.category) return false;
    if (filter.age !== 'all' && r.ageBand !== filter.age) return false;
    if (filter.metal !== 'all' && r.metalType !== filter.metal) return false;
    if (filter.trend !== 'all' && r.trendAlign !== filter.trend) return false;
    return true;
  }), [decorated, filter]);

  const pendingActions = decorated.filter(r => r.status === 'pending').length;
  const clearFilters = () => setFilter({ type: 'all', country: 'all', category: 'all', age: 'all', metal: 'all', trend: 'all' });

  /* ----- drawer config by kind ----- */
  const drawerCfg = (() => {
    switch (drawer.kind) {
      case 'regional': return drawer.data && {
        eyebrow: `Regional intelligence · ${drawer.data.short}`, title: drawer.data.name,
        body: <RegionalIntelligencePanel region={drawer.data} />,
        footer: (
          <>
            <Btn variant="ghost" onClick={() => { closeDrawer(); scrollToFeed(); }}>View Recommendations</Btn>
            <Btn variant="primary" onClick={() => { closeDrawer(); pushToast({ kind: 'info', title: 'Move Inventory', body: `Opening transfer planner for ${drawer.data.name}` }); }}>Move Inventory</Btn>
          </>
        ),
      };
      case 'evidence': return drawer.data && {
        eyebrow: 'Decision evidence', title: 'Why we recommend this',
        body: <EvidencePanel rec={drawer.data} />,
        footer: <Btn variant="primary" onClick={() => { const rec = drawer.data; closeDrawer(); rec.type === 'remake' ? kraftRec(rec) : approveRec(rec); }}>
          {drawer.data.type === 'remake' ? 'Create KRAFT Brief' : 'Approve Action'}
        </Btn>,
      };
      case 'gpt-draft': return drawer.data && {
        eyebrow: 'Local GPT workflow', title: 'KRAFT recovery draft',
        body: <GptDraftPanel rec={drawer.data.rec} draft={drawer.data.draft} error={drawer.data.error} />,
        footer: drawer.data.draft ? (
          <>
            <Btn variant="ghost" onClick={closeDrawer}>Keep reviewing</Btn>
            <Btn variant="primary" onClick={() => { const rec = drawer.data.rec; closeDrawer(); kraftRec(rec); }}>Create KRAFT Brief</Btn>
          </>
        ) : <Btn variant="ghost" onClick={closeDrawer}>Close</Btn>,
      };
      case 'capital': return {
        eyebrow: 'KPI · capital locked', title: 'Capital by market',
        body: <CapitalBreakdownPanel data={D.capitalBreakdown} />,
      };
      case 'risk': return {
        eyebrow: 'KPI · deadstock risk', title: 'Risk drivers',
        body: <RiskDriversPanel drivers={D.riskDrivers} />,
      };
      case 'margin': return {
        eyebrow: 'KPI · margin recovery', title: 'Why +11.8%?',
        body: <MarginRecoveryPanel strategies={D.marginStrategies} />,
      };
      case 'simulation': return {
        eyebrow: 'KPI · recovery simulation', title: 'Model the recovery',
        body: <SimulationPanel aggressiveness={agg} horizon={horizon} setAgg={setAgg} setHorizon={setHorizon} />,
      };
      default: return null;
    }
  })();

  return (
    <React.Fragment>
    <div className="v2-enter">
      {/* 1 · Greeting header */}
      <header className="k-pagehead" style={{ marginBottom: 20 }}>
        <div>
          <div className="v2-section-eyebrow">VAULT · inventory-capital intelligence</div>
          <h1 className="t-h1" style={{ margin: '6px 0 0' }}>Good morning, {user}.</h1>
          <div className="t-meta" style={{ marginTop: 6 }}>
            $18.4M locked in ageing stock across 5 markets · {pendingActions} reversible action{pendingActions === 1 ? '' : 's'} ready for your decision.
          </div>
        </div>
        <div className="k-pagehead-actions">
          <Btn size="sm" icon="sliders" onClick={() => openDrawerKind('simulation')}>Run Simulation</Btn>
          <Btn size="sm" variant="primary" onClick={scrollToFeed}>Review {pendingActions} Actions</Btn>
        </div>
      </header>

      {/* 2 · KPI strip */}
      <div className="v2-enter-1" style={{ marginBottom: 24 }}>
        {loading ? <KpiStripSkeleton /> : <KpiStrip kpis={D.kpis} onAction={(k) => openDrawerKind(k.drawer)} />}
      </div>

      {/* 3 · World flow map (hero) */}
      <div className="v2-enter-2" style={{ marginBottom: 28 }}>
        {loading
          ? <div className="v2-map-wrap" style={{ aspectRatio: `${VB_W} / ${VB_H}` }}><div className="k-skel" style={{ position: 'absolute', inset: 16, borderRadius: 12 }} /></div>
          : <WorldFlowMap regions={D.regions} flows={D.flows} selected={selectedRegion} onSelect={onSelectRegion} />}
      </div>

      {/* 4 · Ranked matrix */}
      <div className="v2-enter-3" style={{ marginBottom: 28 }}>
        <SectionHeading eyebrow="Country comparison · ranked by opportunity"
                        title="Which market deserves capital"
                        desc="Markets ranked by opportunity score. Risk and locked-capital columns are inverted — high is bad. Click a row for regional intelligence." />
        <RankedMatrix regions={D.regions} onSelect={onSelectRegion} />
      </div>

      {/* 5 · Signal list */}
      <div className="v2-enter-3" style={{ marginBottom: 28 }}>
        <SectionHeading eyebrow="Trend intelligence · fed by Orbit"
                        title="Demand signals by market"
                        desc="The same stream Orbit feeds. Send any signal to the recommendation engine to generate an action." />
        <SignalList trends={D.trends} onSend={sendTrend} />
      </div>

      {/* 6 · Decision feed */}
      <div id="v2-feed" className="v2-enter-4" style={{ marginBottom: 12, scrollMarginTop: 'calc(var(--top-h) + 16px)' }}>
        <SectionHeading eyebrow="Decision feed · the heart of VAULT"
                        title="Approve, modify or dismiss — with evidence"
                        desc="Five reversible actions. Every approval is logged for the board." />
        <FilterBar filter={filter} setFilter={setFilter} counts={counts} options={options} />
        <div className="v2-feed-grid">
          <div>
            {filtered.length === 0 ? (
              <Card>
                <div className="k-empty">
                  <div className="k-empty-mark"><Icon name="filter" /></div>
                  <div className="k-empty-title">No recommendations match these filters.</div>
                  <div className="k-empty-body">Adjust or clear the filters to see the full decision feed.</div>
                  <div className="k-empty-cta"><Btn size="sm" variant="primary" onClick={clearFilters}>Clear filters</Btn></div>
                </div>
              </Card>
            ) : (
              <div className="v2-decisions">
                {filtered.map(rec => (
                  <VaultDecisionCard key={rec.id} rec={rec}
                    onEvidence={() => openEvidence(rec)}
                    onDraftGpt={() => openGptDraft(rec)}
                    draftState={gptDrafts[rec.id]}
                    onModify={() => openModify(rec)}
                    onReject={() => openReject(rec)}
                    onApprove={() => approveRec(rec)}
                    onKraft={() => kraftRec(rec)} />
                ))}
              </div>
            )}
          </div>
          <ApprovalQueue approvals={D.approvals} resolved={resolved} onResolve={resolveApproval} />
        </div>
      </div>

      {/* 7 · Footer */}
      <footer className="v2-footer">
        <span className="v2-footer-meta">VAULT engine · vault_capital_agent v2.3 · skills @ 2026.05.18</span>
        <span className="v2-footer-meta">Indicative figures · every approval is logged for the board · architecture v7 §8.3</span>
      </footer>
    </div>

      {/* Overlays live OUTSIDE .v2-enter: that wrapper keeps a transform from
          its entrance animation, which would otherwise become the containing
          block for these position:fixed portals and break viewport centering. */}
      {/* Drawer */}
      <Drawer open={drawer.open && !!drawerCfg} onClose={closeDrawer}
              eyebrow={drawerCfg?.eyebrow} title={drawerCfg?.title} footer={drawerCfg?.footer}>
        {drawerCfg?.body}
      </Drawer>

      {/* Modify modal */}
      <Modal open={modal.open && modal.kind === 'modify'} onClose={closeModal}
             title={modal.rec ? `Modify · ${V_ACTION[modal.rec.type].label}` : 'Modify'}
             footer={<>
               <Btn variant="ghost" onClick={closeModal}>Cancel</Btn>
               <Btn variant="primary" onClick={confirmModify}>Save changes</Btn>
             </>}>
        {modal.rec && modal.kind === 'modify' && (
          <ModifyModalBody rec={modal.rec} qty={modQty} setQty={setModQty} dest={{ value: modDest, set: setModDest }} />
        )}
      </Modal>

      {/* Reject modal */}
      <Modal open={modal.open && modal.kind === 'reject'} onClose={closeModal} destructive
             title="Reject recommendation"
             footer={<>
               <Btn variant="ghost" onClick={closeModal}>Cancel</Btn>
               <Btn variant="reject" onClick={confirmReject}>Dismiss &amp; log</Btn>
             </>}>
        {modal.rec && modal.kind === 'reject' && (
          <RejectModalBody reason={rejReason} setReason={setRejReason} note={rejNote} setNote={setRejNote} />
        )}
      </Modal>

      <ToastStack toasts={toasts} />
    </React.Fragment>
  );
};


/* ============================================================
   EXPORT to window.K
   ============================================================ */
Object.assign(window.K, {
  // atoms
  Sparkline, Meter, ConfRing, Delta, Tag,
  // molecules
  KpiCard, StatCard, MeterRow, TrendRow, SectionHeading, DrawerSection,
  // organisms
  KpiStrip, KpiStripSkeleton, WorldFlowMap, RankedMatrix, SignalList,
  VaultDecisionCard, ApprovalQueue, FilterBar,
  // page + data
  VaultCeoDashboard, VAULT_DATA,
});
