/* Shared data, contexts, atoms, and overlays for the Airtm ATS Dashboard variations. */

/* ─────────────── data ─────────────── */

const ROLES = [
  { id: 'exec', label: 'Executive', sub: 'C-level overview' },
  { id: 'manager', label: 'Hiring manager', sub: 'My reqs & candidates' },
  { id: 'recruiter', label: 'Recruiter', sub: 'Full pipeline' },
];

const ROLE_GROUPS = [
  { id: 'sr-eng',   label: 'Senior Engineer',  ttf: 60, ttfTarget: 30, tth: 7, tthTarget: 7, offer: 100, screen: 92, attr: 4 },
  { id: 'eng',      label: 'Engineer II',      ttf: 60, ttfTarget: 30, tth: 3, tthTarget: 7, offer: 100, screen: 78, attr: 2 },
  { id: 'designer', label: 'Product Designer', ttf: 80, ttfTarget: 30, tth: 6, tthTarget: 7, offer: 100, screen: 89, attr: 0 },
  { id: 'pm',       label: 'Product Manager',  ttf: 45, ttfTarget: 30, tth: 3, tthTarget: 7, offer: 100, screen: 94, attr: 1 },
  { id: 'analyst',  label: 'Data Analyst',     ttf: 22, ttfTarget: 30, tth: 2, tthTarget: 7, offer: 100, screen: 88, attr: 0 },
  { id: 'ops',      label: 'Operations Lead',  ttf: 14, ttfTarget: 30, tth: 2, tthTarget: 7, offer: 100, screen: 86, attr: 0 },
];

const PIPELINE = [
  { stage: 'Applied',     count: 312 },
  { stage: 'Screening',   count: 84 },
  { stage: 'Interview',   count: 41 },
  { stage: 'Take-home',   count: 18 },
  { stage: 'Offer',       count: 6 },
  { stage: 'Hired',       count: 3 },
];

/* deterministic-ish "AI" candidate set */
const CANDIDATES = [
  { id: 'c1', name: 'Mariana Quintero',   role: 'sr-eng',   loc: 'Bogotá, CO',        langs: ['EN','ES'], score: 94, stage: 'Interview',  lastTouch: '2h ago',  expYrs: 9,  source: 'Referral',     salary: 92,
    reasons: ['9y in distributed systems','LATAM time zone','Strong Go + TS','Led 4-eng team at Stripe alum'] },
  { id: 'c2', name: 'Daniel Okafor',      role: 'sr-eng',   loc: 'Lagos, NG',          langs: ['EN'],      score: 91, stage: 'Screening',  lastTouch: '6h ago',  expYrs: 8,  source: 'Inbound',      salary: 78,
    reasons: ['8y backend at fintech','Owns payments rails','Open-source contributor','Available <30 days'] },
  { id: 'c3', name: 'Sofía Restrepo',     role: 'designer', loc: 'Medellín, CO',       langs: ['EN','ES'], score: 89, stage: 'Take-home',  lastTouch: '1d ago',  expYrs: 6,  source: 'AirtmDirect', salary: 70,
    reasons: ['Wallet & remittance work','Strong systems thinker','Portfolio matches voice','LATAM time zone'] },
  { id: 'c4', name: 'Camila Vargas',      role: 'pm',       loc: 'Mexico City, MX',    langs: ['EN','ES'], score: 88, stage: 'Interview',  lastTouch: '4h ago',  expYrs: 7,  source: 'LinkedIn',     salary: 105,
    reasons: ['Shipped 2 LATAM fintech apps','Direct ops experience','Bilingual EN/ES'] },
  { id: 'c5', name: 'Anika Pillai',       role: 'eng',      loc: 'Bengaluru, IN',      langs: ['EN'],      score: 86, stage: 'Applied',    lastTouch: '12h ago', expYrs: 4,  source: 'Inbound',      salary: 48,
    reasons: ['Strong TS + React','Worked on Stripe webhooks','Time-zone fit risk'] },
  { id: 'c6', name: 'Thiago Almeida',     role: 'eng',      loc: 'São Paulo, BR',      langs: ['EN','PT'], score: 84, stage: 'Screening',  lastTouch: '1d ago',  expYrs: 5,  source: 'Referral',     salary: 65,
    reasons: ['Crypto-rails background','Speaks PT/EN','LATAM time zone'] },
  { id: 'c7', name: 'Lucas Méndez',       role: 'analyst',  loc: 'Buenos Aires, AR',   langs: ['EN','ES'], score: 82, stage: 'Offer',      lastTouch: '30m ago', expYrs: 5,  source: 'Inbound',      salary: 58,
    reasons: ['SQL+dbt+Looker','Built KPI dashboards','Already past final round'] },
  { id: 'c8', name: 'Renata Oliveira',    role: 'pm',       loc: 'Rio de Janeiro, BR', langs: ['EN','PT'], score: 80, stage: 'Screening',  lastTouch: '3h ago',  expYrs: 6,  source: 'LinkedIn',     salary: 98,
    reasons: ['Risk & compliance PM','Worked at Nubank','Salary slightly over band'] },
  { id: 'c9', name: 'Aaron Lee',          role: 'ops',      loc: 'Manila, PH',         langs: ['EN'],      score: 78, stage: 'Interview',  lastTouch: '5h ago',  expYrs: 8,  source: 'Inbound',      salary: 52,
    reasons: ['Ran 30-person CS team','Process-first','Time-zone fit risk'] },
  { id: 'c10',name: 'Valentina Cruz',     role: 'designer', loc: 'Lima, PE',           langs: ['EN','ES'], score: 76, stage: 'Applied',    lastTouch: '2d ago',  expYrs: 4,  source: 'Referral',     salary: 56,
    reasons: ['Mobile-first portfolio','Less systems work','LATAM time zone'] },
  { id: 'c11',name: 'Jonas Weber',        role: 'sr-eng',   loc: 'Berlin, DE',         langs: ['EN','DE'], score: 74, stage: 'Screening',  lastTouch: '1d ago',  expYrs: 11, source: 'Inbound',      salary: 135,
    reasons: ['Strong CV','Outside salary band','Time-zone overlap is tight'] },
  { id: 'c12',name: 'Pooja Iyer',         role: 'analyst',  loc: 'Mumbai, IN',         langs: ['EN'],      score: 71, stage: 'Applied',    lastTouch: '4d ago',  expYrs: 3,  source: 'Inbound',      salary: 42,
    reasons: ['Solid SQL','Junior for the band','Time-zone fit risk'] },
];

const REQS = [
  { id: 'r1', title: 'Senior Backend Engineer', role: 'sr-eng',   dept: 'Engineering', loc: 'Remote · LATAM', applicants: 184, days: 38, predicted: 42, target: 30, owner: 'Carla R.',   status: 'On track',  hires: '0 / 2' },
  { id: 'r2', title: 'Product Designer',        role: 'designer', dept: 'Design',      loc: 'Remote · LATAM', applicants: 96,  days: 51, predicted: 58, target: 30, owner: 'Diego F.',   status: 'At risk',   hires: '0 / 1' },
  { id: 'r3', title: 'Product Manager, Risk',   role: 'pm',       dept: 'Product',     loc: 'Remote · Global',applicants: 142, days: 21, predicted: 28, target: 30, owner: 'Carla R.',   status: 'On track',  hires: '0 / 1' },
  { id: 'r4', title: 'Frontend Engineer',       role: 'eng',      dept: 'Engineering', loc: 'Remote · LATAM', applicants: 211, days: 12, predicted: 24, target: 30, owner: 'Pablo M.',   status: 'On track',  hires: '0 / 2' },
  { id: 'r5', title: 'Data Analyst',            role: 'analyst',  dept: 'Data',        loc: 'Remote · LATAM', applicants: 88,  days: 9,  predicted: 18, target: 30, owner: 'Diego F.',   status: 'On track',  hires: '0 / 1' },
  { id: 'r6', title: 'Operations Lead, CS',     role: 'ops',      dept: 'Operations',  loc: 'Remote · APAC',  applicants: 53,  days: 33, predicted: 39, target: 30, owner: 'Pablo M.',   status: 'At risk',   hires: '0 / 1' },
];

const UPDATES = [
  { t: 'offer-accept', who: 'Lucas Méndez',     what: 'accepted offer · Data Analyst',         when: '12 min',  tone: 'prog' },
  { t: 'interview',    who: 'Mariana Quintero', what: 'interview scheduled · 3:30pm today',     when: '1h',      tone: 'brand' },
  { t: 'stage',        who: 'Daniel Okafor',    what: 'moved to Interview · Senior Backend',    when: '2h',      tone: 'neutral' },
  { t: 'offer-sent',   who: 'Camila Vargas',    what: 'offer sent · Product Manager, Risk',     when: '3h',      tone: 'brand' },
  { t: 'interview',    who: 'Sofía Restrepo',   what: 'interview scheduled · tomorrow 10:00',   when: '4h',      tone: 'brand' },
  { t: 'stage',        who: 'Thiago Almeida',   what: 'moved to Screening · Frontend Engineer', when: '6h',      tone: 'neutral' },
  { t: 'offer-decline',who: 'Jonas Weber',      what: 'declined offer · Senior Backend',        when: '1d',      tone: 'dest' },
  { t: 'stage',        who: 'Anika Pillai',     what: 'moved to Applied · Frontend Engineer',   when: '1d',      tone: 'neutral' },
];

const ATS_CONNECTORS = [
  { id: 'db',        name: 'In-house DB',status: 'Connected', detail: 'PostgreSQL on RDS · live' },
  { id: 'greenhouse',name: 'Greenhouse', status: 'Available', detail: 'One-click connect' },
  { id: 'workable',  name: 'Workable',   status: 'Available', detail: 'One-click connect' },
  { id: 'ashby',     name: 'Ashby',      status: 'Available', detail: 'One-click connect' },
  { id: 'csv',       name: 'CSV import', status: 'Available', detail: 'Upload a file' },
];

/* ─────────────── time periods ─────────────── */

const TIME_PERIODS = [
  { id: 'week',    label: 'This week',    short: 'Week',    bins: 7,  binLabel: (i) => ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'][i] },
  { id: 'month',   label: 'This month',   short: 'Month',   bins: 4,  binLabel: (i) => `W${i+1}` },
  { id: 'quarter', label: 'This quarter', short: 'Quarter', bins: 12, binLabel: (i) => `W${i+1}` },
  { id: 'year',    label: 'This year',    short: 'Year',    bins: 12, binLabel: (i) => ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'][i] },
];

/* Seeded pseudo-random so charts stay stable across renders */
function _h(s) { let h = 2166136261; for (let i=0;i<s.length;i++) h = (h ^ s.charCodeAt(i)) * 16777619 >>> 0; return h; }
function rand(seed) {
  let s = _h(seed);
  return () => { s = (s * 1103515245 + 12345) >>> 0; return ((s >>> 16) & 0x7fff) / 0x7fff; };
}

/* Generate a metric series for a given KPI id + period + reqId scope.
 * Returns { values, current, target, trendPct }. */
function metricSeries(kpiId, periodId, reqId = 'all') {
  const period = TIME_PERIODS.find(p => p.id === periodId) || TIME_PERIODS[1];
  const r = rand(`${kpiId}-${periodId}-${reqId}`);
  const reqFactor = reqId === 'all' ? 1 : (0.7 + r() * 0.6);
  const presets = {
    ttf:    { base: 45,  range: 18, target: 30, lowerBetter: true,  trend: -8 },
    tth:    { base: 4.2, range: 1.6,target: 7,  lowerBetter: true,  trend: -22 },
    offer:  { base: 96,  range: 8,  target: 90, lowerBetter: false, trend: 4 },
    screen: { base: 86,  range: 8,  target: 60, lowerBetter: false, trend: 6 },
    attr:   { base: 1.5, range: 1.0,target: 5,  lowerBetter: true,  trend: -1 },
    reqs:   { base: 6,   range: 2,  target: 6,  lowerBetter: false, trend: 1 },
    applied:{ base: 38,  range: 22, target: 40, lowerBetter: false, trend: 12 },
    hired:  { base: 1.4, range: 1.2,target: 2,  lowerBetter: false, trend: 0 },
  };
  const p = presets[kpiId] || presets.ttf;
  const values = [];
  for (let i = 0; i < period.bins; i++) {
    const v = (p.base + (r() - 0.5) * p.range) * reqFactor;
    values.push(Math.max(0, kpiId === 'reqs' ? Math.round(v) : v));
  }
  const current = values[values.length - 1];
  return {
    values,
    current,
    target: p.target,
    lowerBetter: p.lowerBetter,
    trendPct: p.trend,
    binLabel: period.binLabel,
    bins: period.bins,
  };
}

/* ─────────────── helpers ─────────────── */

const fmt = (n) => n.toLocaleString('en-US');
const reqById = (id) => REQS.find(r => r.id === id);
const candsForReq = (rid) => {
  const r = reqById(rid);
  if (!r) return [];
  return CANDIDATES.filter(c => c.role === r.role).sort((a,b) => b.score - a.score);
};
const roleLabel = (rid) => (ROLE_GROUPS.find(r => r.id === rid)?.label || rid);
const initials = (name) => name.split(' ').map(p => p[0]).slice(0,2).join('');
function scoreTone(s) {
  if (s >= 88) return 'prog';
  if (s >= 75) return 'brand';
  if (s >= 65) return 'warn';
  return 'neutral';
}

/* ─────────────── icons (inline, currentColor) ─────────────── */

const I = {
  Home:    (p) => (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M3 11l9-7 9 7v9a2 2 0 0 1-2 2h-4v-7H9v7H5a2 2 0 0 1-2-2z"/></svg>),
  Inbox:   (p) => (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M22 12h-6l-2 3h-4l-2-3H2"/><path d="M5.5 5h13a2 2 0 0 1 2 1.5L22 12v6a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-6L3.5 6.5A2 2 0 0 1 5.5 5z"/></svg>),
  Users:   (p) => (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>),
  Briefcase:(p)=> (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><rect x="2" y="7" width="20" height="14" rx="2"/><path d="M16 7V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2"/></svg>),
  Calendar:(p) => (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg>),
  Chart:   (p) => (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M3 3v18h18"/><path d="M7 14l4-4 4 3 5-7"/></svg>),
  Settings:(p) => (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .34 1.86l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.7 1.7 0 0 0-1.86-.34 1.7 1.7 0 0 0-1.03 1.56V21a2 2 0 1 1-4 0v-.09A1.7 1.7 0 0 0 9 19.4a1.7 1.7 0 0 0-1.86.34l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.7 1.7 0 0 0 4.6 15a1.7 1.7 0 0 0-1.56-1.03H3a2 2 0 1 1 0-4h.09A1.7 1.7 0 0 0 4.6 9a1.7 1.7 0 0 0-.34-1.86l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.7 1.7 0 0 0 9 4.6a1.7 1.7 0 0 0 1.03-1.56V3a2 2 0 1 1 4 0v.09A1.7 1.7 0 0 0 15 4.6a1.7 1.7 0 0 0 1.86-.34l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.7 1.7 0 0 0 19.4 9c.2.49.8.97 1.56 1.03H21a2 2 0 1 1 0 4h-.09A1.7 1.7 0 0 0 19.4 15z"/></svg>),
  Search:  (p) => (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>),
  Sparkle: (p) => (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M12 3l1.5 4.5L18 9l-4.5 1.5L12 15l-1.5-4.5L6 9l4.5-1.5z"/><path d="M19 16l.7 2L22 19l-2.3.7L19 22l-.7-2.3L16 19l2.3-.7z"/></svg>),
  Bell:    (p) => (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.7 21a2 2 0 0 1-3.4 0"/></svg>),
  ArrowUp: (p) => (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M7 17l10-10M7 7h10v10"/></svg>),
  ArrowDn: (p) => (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M7 7l10 10M17 7v10H7"/></svg>),
  Check:   (p) => (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M20 6L9 17l-5-5"/></svg>),
  X:       (p) => (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M18 6L6 18M6 6l12 12"/></svg>),
  Filter:  (p) => (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M22 3H2l8 9.5V19l4 2v-8.5z"/></svg>),
  Plus:    (p) => (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M12 5v14M5 12h14"/></svg>),
  Dots:    (p) => (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><circle cx="5" cy="12" r="1"/><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/></svg>),
  Globe:   (p) => (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15 15 0 0 1 0 20 15 15 0 0 1 0-20z"/></svg>),
  Clock:   (p) => (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>),
  Tag:     (p) => (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M20 12L12 20a2 2 0 0 1-3 0L2 13V4a2 2 0 0 1 2-2h9z"/><circle cx="7" cy="7" r="1.5"/></svg>),
  Plug:    (p) => (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M9 2v6M15 2v6M7 8h10v4a5 5 0 0 1-10 0z"/><path d="M12 17v5"/></svg>),
  TrendUp: (p) => (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M3 17l6-6 4 4 8-9"/><path d="M14 6h7v7"/></svg>),
  Send:    (p) => (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M22 2L11 13M22 2l-7 20-4-9-9-4z"/></svg>),
};

/* ─────────────── atoms ─────────────── */

function Avatar({ name, size = 36, tone = 'neutral' }) {
  const tones = {
    neutral: { bg: 'var(--bg-tertiary)',  fg: 'var(--content-primary)' },
    brand:   { bg: 'var(--indigo-20)',    fg: 'var(--indigo-90)' },
    prog:    { bg: 'var(--green-20)',     fg: 'var(--green-90)' },
    warn:    { bg: 'var(--yellow-20)',    fg: 'var(--yellow-90)' },
    violet:  { bg: 'var(--violet-20)',    fg: 'var(--violet-90)' },
    mint:    { bg: 'var(--mint-20)',      fg: 'var(--mint-90)' },
    orange:  { bg: 'var(--orange-20)',    fg: 'var(--orange-90)' },
  };
  const t = tones[tone] || tones.neutral;
  // deterministic tone from name
  const palettes = ['brand','prog','warn','violet','mint','orange','neutral'];
  const idx = (name || '').split('').reduce((a,c) => a + c.charCodeAt(0), 0) % palettes.length;
  const auto = tone === 'auto' ? tones[palettes[idx]] : t;
  return (
    <div style={{ width: size, height: size, borderRadius: 100, background: auto.bg, color: auto.fg,
      display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
      fontWeight: 600, fontSize: size * 0.36, flexShrink: 0, fontFamily: 'var(--font-sans)' }}>
      {initials(name)}
    </div>
  );
}

function Pill({ tone = 'neutral', children, dot, onClick, active }) {
  const map = {
    brand:   { bg: 'var(--status-bg-brand)',       fg: 'var(--status-text-brand)' },
    prog:    { bg: 'var(--status-bg-progressive)', fg: 'var(--status-text-progressive)' },
    warn:    { bg: 'var(--status-bg-warning)',     fg: 'var(--status-text-warning)' },
    dest:    { bg: 'var(--status-bg-destructive)', fg: 'var(--status-text-destructive)' },
    neutral: { bg: 'var(--status-bg-neutral)',     fg: 'var(--status-text-neutral)' },
  };
  const t = map[tone] || map.neutral;
  const Tag = onClick ? 'button' : 'span';
  return (
    <Tag onClick={onClick} style={{ display: 'inline-flex', alignItems: 'center', gap: 6, padding: '3px 10px',
      borderRadius: 100, background: t.bg, color: t.fg, fontSize: 12, fontWeight: 600, lineHeight: 1.4,
      whiteSpace: 'nowrap', border: active ? '1.5px solid currentColor' : '1.5px solid transparent',
      ...(onClick ? { cursor: 'pointer', fontFamily: 'var(--font-sans)' } : {}) }}>
      {dot && <span style={{ width: 6, height: 6, borderRadius: 50, background: 'currentColor' }} />}
      {children}
    </Tag>
  );
}

function ScoreRing({ score, size = 44, stroke = 4, label }) {
  const r = (size - stroke) / 2;
  const c = 2 * Math.PI * r;
  const dash = (score / 100) * c;
  const tone = scoreTone(score);
  const color = tone === 'prog' ? 'var(--green-50)'
              : tone === 'brand' ? 'var(--indigo-50)'
              : tone === 'warn' ? 'var(--yellow-50)'
              : 'var(--neutral-50)';
  return (
    <div style={{ position: 'relative', width: size, height: size, flexShrink: 0 }}>
      <svg width={size} height={size} style={{ transform: 'rotate(-90deg)' }}>
        <circle cx={size/2} cy={size/2} r={r} stroke="var(--bg-tertiary)" strokeWidth={stroke} fill="none" />
        <circle cx={size/2} cy={size/2} r={r} stroke={color} strokeWidth={stroke} fill="none"
          strokeDasharray={`${dash} ${c}`} strokeLinecap="round" />
      </svg>
      <div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center',
        fontWeight: 600, fontSize: size * 0.32, color: 'var(--content-primary)', fontVariantNumeric: 'tabular-nums' }}>
        {score}
      </div>
      {label && <div style={{ textAlign: 'center', fontSize: 10, color: 'var(--content-tertiary)', marginTop: 4 }}>{label}</div>}
    </div>
  );
}

function Sparkbar({ values, height = 28, color }) {
  const arr = Array.isArray(values) && values.length > 0 ? values : [0];
  const max = Math.max(...arr) || 1;
  return (
    <div style={{ display: 'flex', alignItems: 'flex-end', gap: 3, height }}>
      {arr.map((v,i) => (
        <div key={i} style={{ flex: 1, height: `${(v/max)*100}%`, background: color || 'var(--indigo-30)',
          borderRadius: 2, minHeight: 2 }} />
      ))}
    </div>
  );
}

/* ─────────────── role switcher ─────────────── */

const RoleContext = React.createContext({ role: 'exec', setRole: () => {} });

function RoleSwitcher({ compact }) {
  const { role, setRole } = React.useContext(RoleContext);
  return (
    <div style={{
      display: 'inline-flex', padding: 3, borderRadius: 100,
      background: 'var(--bg-secondary)', border: '1px solid var(--stroke-secondary)'
    }}>
      {ROLES.map(r => (
        <button key={r.id} onClick={() => setRole(r.id)} style={{
          padding: compact ? '5px 10px' : '7px 14px', borderRadius: 100,
          background: role === r.id ? 'var(--bg-primary)' : 'transparent',
          color: role === r.id ? 'var(--content-primary)' : 'var(--content-tertiary)',
          border: 0, fontFamily: 'var(--font-sans)', fontWeight: 600,
          fontSize: compact ? 12 : 13, cursor: 'pointer',
          boxShadow: role === r.id ? '0 1px 3px rgba(0,0,0,0.06)' : 'none',
          transition: 'background 150ms ease-out, color 150ms ease-out'
        }}>
          {r.label}
        </button>
      ))}
    </div>
  );
}

/* ─────────────── filter bar ─────────────── */

function Select({ value, onChange, options, icon, width = 220 }) {
  const [open, setOpen] = React.useState(false);
  const wrap = React.useRef(null);
  React.useEffect(() => {
    if (!open) return;
    const fn = (e) => { if (!wrap.current?.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', fn);
    return () => document.removeEventListener('mousedown', fn);
  }, [open]);
  const cur = options.find(o => o.value === value) || options[0];
  return (
    <div ref={wrap} style={{ position: 'relative' }}>
      <button onClick={() => setOpen(v => !v)} style={{
        display: 'inline-flex', alignItems: 'center', gap: 8,
        padding: '8px 12px', borderRadius: 10,
        background: 'var(--bg-primary)', border: '1px solid var(--stroke-secondary)',
        cursor: 'pointer', fontFamily: 'var(--font-sans)', fontSize: 13,
        color: 'var(--content-primary)', minWidth: width, fontWeight: 500
      }}>
        {icon && <span style={{ color: 'var(--content-tertiary)', display: 'inline-flex' }}>{icon}</span>}
        <span style={{ flex: 1, textAlign: 'left',
          overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{cur.label}</span>
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
          strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"
          style={{ color: 'var(--content-tertiary)' }}><path d="M6 9l6 6 6-6"/></svg>
      </button>
      {open && (
        <div style={{
          position: 'absolute', top: 'calc(100% + 4px)', left: 0, zIndex: 20,
          minWidth: width, maxHeight: 320, overflowY: 'auto',
          background: 'var(--bg-primary)', border: '1px solid var(--stroke-secondary)',
          borderRadius: 10, boxShadow: 'var(--shadow-lg)', padding: 4
        }}>
          {options.map(o => (
            <button key={o.value} onClick={() => { onChange(o.value); setOpen(false); }} style={{
              display: 'flex', alignItems: 'center', gap: 8, width: '100%',
              padding: '8px 10px', borderRadius: 6, border: 0, background: 'transparent',
              cursor: 'pointer', font: 'inherit', textAlign: 'left',
              color: 'var(--content-primary)',
              fontWeight: o.value === value ? 600 : 500,
              fontSize: 13
            }} onMouseEnter={(e) => e.currentTarget.style.background = 'var(--bg-secondary)'}
               onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
              {o.sub && <span style={{ fontSize: 11, color: 'var(--content-tertiary)',
                marginRight: 4, fontWeight: 600, fontVariantNumeric: 'tabular-nums' }}>{o.sub}</span>}
              <span style={{ flex: 1 }}>{o.label}</span>
              {o.value === value && <I.Check width="14" height="14" style={{ color: 'var(--content-highlight)' }} />}
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

function FilterBar({ req, setReq, period, setPeriod, extra }) {
  const reqOptions = [
    { value: 'all', label: 'All jobs', sub: REQS.length },
    ...REQS.map(r => ({ value: r.id, label: r.title, sub: r.applicants })),
  ];
  const periodOptions = TIME_PERIODS.map(p => ({ value: p.id, label: p.label }));
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
      <Select value={req} onChange={setReq} options={reqOptions} width={240}
        icon={<I.Briefcase width="14" height="14" />} />
      <Select value={period} onChange={setPeriod} options={periodOptions} width={160}
        icon={<I.Calendar width="14" height="14" />} />
      {extra}
    </div>
  );
}

/* ─────────────── topbar ─────────────── */

function Topbar({ onSearch, onSettings, title = 'Dashboard' }) {
  return (
    <div style={{
      height: 64, padding: '0 24px', display: 'flex', alignItems: 'center', gap: 16,
      background: 'var(--bg-primary)', borderBottom: '1px solid var(--stroke-secondary)'
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, minWidth: 0 }}>
        <h3 style={{ margin: 0, fontSize: 18, fontWeight: 600 }}>{title}</h3>
      </div>
      {/* "Ask anything" search + role switcher hidden for now */}
      <div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 6 }}>
        <button className="icon-btn" title="Notifications"><I.Bell width="20" height="20" /></button>
        <button className="icon-btn" title="Settings" onClick={onSettings}><I.Settings width="20" height="20" /></button>
        <Avatar name="You" tone="brand" size={32} />
      </div>
    </div>
  );
}

/* ─────────────── sidebar ─────────────── */

function Sidebar({ active = 'dashboard', onNav }) {
  const items = [
    { id: 'dashboard',  label: 'Dashboard',  icon: <I.Home width="18" height="18" /> },
    { id: 'candidates', label: 'Candidates', icon: <I.Users width="18" height="18" />,     count: CANDIDATES.length },
    { id: 'pipeline',   label: 'Kanban',     icon: <I.Chart width="18" height="18" /> },
    { id: 'reqs',       label: 'Jobs',       icon: <I.Briefcase width="18" height="18" />, count: REQS.length },
    { id: 'inbox',      label: 'Inbox',      icon: <I.Inbox width="18" height="18" />,     count: 12 },
    { id: 'interviews', label: 'Interview',  icon: <I.Calendar width="18" height="18" /> },
    { id: 'reports',    label: 'Reports',    icon: <I.Chart width="18" height="18" /> },
  ];
  return (
    <aside style={{
      width: 224, background: 'var(--bg-primary)',
      borderRight: '1px solid var(--stroke-secondary)',
      padding: '20px 12px', display: 'flex', flexDirection: 'column', gap: 2,
      flexShrink: 0
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '0 10px 18px' }}>
        <svg viewBox="0 0 32 32" width="26" height="26" style={{ color: 'var(--indigo-50)' }} fill="currentColor">
          <rect x="0" y="0" width="32" height="32" rx="8" />
          <path d="M9 20 L16 9 L23 20 H19.5 L16 14.5 L12.5 20 Z" fill="var(--bg-primary)" />
        </svg>
        <div style={{ display: 'flex', flexDirection: 'column', lineHeight: 1.1 }}>
          <span style={{ fontWeight: 700, fontSize: 15, letterSpacing: '-0.01em' }}>Airtm</span>
          <span style={{ fontSize: 10, color: 'var(--content-tertiary)', fontWeight: 600, letterSpacing: '.08em', textTransform: 'uppercase' }}>People</span>
        </div>
      </div>
      <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--content-tertiary)',
        letterSpacing: '.06em', textTransform: 'uppercase', padding: '12px 10px 6px' }}>Workspace</div>
      {items.map(it => (
        <button key={it.id} className={`nav-item ${active === it.id ? 'active' : ''}`}
          onClick={() => onNav && onNav(it.id)}>
          {it.icon}
          <span>{it.label}</span>
          {it.count != null && <span style={{ marginLeft: 'auto', fontSize: 11, fontWeight: 600,
            color: 'var(--content-tertiary)' }}>{it.count}</span>}
        </button>
      ))}
      <div style={{ flex: 1 }} />
      <div style={{ padding: 12, background: 'var(--bg-secondary)', borderRadius: 12, marginTop: 8 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 12, fontWeight: 600, marginBottom: 4 }}>
          <I.Plug width="14" height="14" style={{ color: 'var(--green-50)' }} />
          <span>Database connected</span>
        </div>
        <div style={{ fontSize: 11, color: 'var(--content-tertiary)' }}>
          PostgreSQL · live
        </div>
      </div>
    </aside>
  );
}

/* ─────────────── overlay shell ─────────────── */

function Modal({ open, onClose, width = 640, children, title, subtitle }) {
  if (!open) return null;
  return (
    <div onClick={onClose} style={{
      position: 'absolute', inset: 0, zIndex: 30,
      background: 'rgba(5,5,5,0.5)', display: 'flex', alignItems: 'flex-start', justifyContent: 'center',
      paddingTop: 80, padding: '80px 24px 24px'
    }}>
      <div onClick={(e) => e.stopPropagation()} style={{
        width: '100%', maxWidth: width, background: 'var(--bg-primary)',
        borderRadius: 16, boxShadow: 'var(--shadow-lg)', overflow: 'hidden',
        display: 'flex', flexDirection: 'column', maxHeight: 'calc(100% - 24px)'
      }}>
        {(title || onClose) && (
          <div style={{ padding: '18px 22px', display: 'flex', alignItems: 'center', gap: 12,
            borderBottom: '1px solid var(--stroke-secondary)' }}>
            <div style={{ flex: 1, minWidth: 0 }}>
              {title && <div style={{ fontWeight: 600, fontSize: 16 }}>{title}</div>}
              {subtitle && <div style={{ fontSize: 13, color: 'var(--content-tertiary)', marginTop: 2 }}>{subtitle}</div>}
            </div>
            <button className="icon-btn" onClick={onClose}><I.X width="18" height="18" /></button>
          </div>
        )}
        <div style={{ overflow: 'auto', flex: 1 }}>{children}</div>
      </div>
    </div>
  );
}

/* ─────────────── AI search modal ─────────────── */

function AISearchModal({ open, onClose, onOpenCandidate }) {
  const [q, setQ] = React.useState('');
  const samples = [
    'Senior backend engineers in LATAM with payments experience',
    'Designers under 30 days to start, EN/ES bilingual',
    'PMs at risk of declining our offer',
    'Reqs likely to miss the 30-day target this quarter',
  ];
  const matches = React.useMemo(() => {
    if (!q.trim()) return CANDIDATES.slice(0, 5);
    const tokens = q.toLowerCase().split(/\s+/).filter(Boolean);
    const score = (c) => {
      const hay = (c.name + ' ' + c.loc + ' ' + c.langs.join(' ') + ' ' + c.reasons.join(' ') + ' ' + roleLabel(c.role)).toLowerCase();
      let s = c.score / 4;
      tokens.forEach(t => { if (hay.includes(t)) s += 25; });
      return s;
    };
    return [...CANDIDATES].map(c => ({ ...c, _s: score(c) })).sort((a,b) => b._s - a._s).slice(0,6);
  }, [q]);
  React.useEffect(() => { if (open) setTimeout(() => document.getElementById('ai-q')?.focus(), 50); }, [open]);
  return (
    <Modal open={open} onClose={onClose} width={680}>
      <div style={{ padding: 18, borderBottom: '1px solid var(--stroke-secondary)',
        display: 'flex', alignItems: 'center', gap: 10 }}>
        <I.Sparkle width="20" height="20" style={{ color: 'var(--indigo-50)' }} />
        <input id="ai-q" placeholder="Ask anything about candidates, reqs, pipeline…"
          value={q} onChange={(e) => setQ(e.target.value)}
          style={{ flex: 1, border: 0, outline: 0, fontSize: 16, background: 'transparent',
            color: 'var(--content-primary)', fontFamily: 'var(--font-sans)' }} />
        <kbd style={{ fontSize: 11, padding: '2px 6px', borderRadius: 4,
          background: 'var(--bg-secondary)', border: '1px solid var(--stroke-secondary)',
          color: 'var(--content-tertiary)' }}>esc</kbd>
      </div>
      {!q && (
        <div style={{ padding: '12px 18px 4px', fontSize: 11, fontWeight: 600, color: 'var(--content-tertiary)',
          textTransform: 'uppercase', letterSpacing: '.06em' }}>Try</div>
      )}
      {!q && (
        <div style={{ padding: '4px 12px 12px', display: 'flex', flexDirection: 'column', gap: 2 }}>
          {samples.map((s,i) => (
            <button key={i} onClick={() => setQ(s)} style={{
              textAlign: 'left', padding: '10px 12px', borderRadius: 8,
              background: 'transparent', border: 0, cursor: 'pointer',
              font: 'inherit', color: 'var(--content-secondary)',
              display: 'flex', alignItems: 'center', gap: 10
            }} onMouseEnter={(e) => e.currentTarget.style.background = 'var(--bg-secondary)'}
              onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
              <I.Sparkle width="14" height="14" style={{ color: 'var(--indigo-50)', opacity: .7 }} />
              <span>{s}</span>
            </button>
          ))}
        </div>
      )}
      <div style={{ padding: '12px 18px 4px', fontSize: 11, fontWeight: 600, color: 'var(--content-tertiary)',
        textTransform: 'uppercase', letterSpacing: '.06em' }}>{q ? 'Matches' : 'Top candidates'}</div>
      <div style={{ padding: '4px 12px 16px', display: 'flex', flexDirection: 'column', gap: 2 }}>
        {matches.map(c => (
          <button key={c.id} onClick={() => { onClose(); onOpenCandidate(c); }} style={{
            textAlign: 'left', padding: '10px 12px', borderRadius: 8,
            background: 'transparent', border: 0, cursor: 'pointer',
            display: 'flex', alignItems: 'center', gap: 12, font: 'inherit'
          }} onMouseEnter={(e) => e.currentTarget.style.background = 'var(--bg-secondary)'}
            onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
            <ScoreRing score={c.score} size={36} stroke={3} />
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontWeight: 600, fontSize: 14 }}>{c.name}</div>
              <div style={{ fontSize: 12, color: 'var(--content-tertiary)' }}>
                {roleLabel(c.role)} · {c.loc} · {c.langs.join('/')}
              </div>
            </div>
            <Pill tone="neutral">{c.stage}</Pill>
          </button>
        ))}
      </div>
    </Modal>
  );
}

/* ─────────────── settings (ATS connectors) ─────────────── */

function SettingsModal({ open, onClose }) {
  const [active, setActive] = React.useState('db');
  return (
    <Modal open={open} onClose={onClose} title="Settings" subtitle="Connect your ATS — the dashboard syncs in real time" width={720}>
      <div style={{ padding: 22, display: 'flex', flexDirection: 'column', gap: 18 }}>
        <div>
          <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 10 }}>ATS connector</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
            {ATS_CONNECTORS.map(c => (
              <button key={c.id} onClick={() => setActive(c.id)} style={{
                display: 'flex', alignItems: 'center', gap: 14, padding: '14px 16px',
                border: `1px solid ${active === c.id ? 'var(--stroke-highlight)' : 'var(--stroke-secondary)'}`,
                borderRadius: 12, background: active === c.id ? 'var(--indigo-10)' : 'var(--bg-primary)',
                cursor: 'pointer', font: 'inherit', textAlign: 'left', width: '100%'
              }}>
                <div style={{ width: 40, height: 40, borderRadius: 10, background: 'var(--bg-secondary)',
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                  fontWeight: 700, fontSize: 14, color: 'var(--content-primary)' }}>
                  {c.name[0]}
                </div>
                <div style={{ flex: 1 }}>
                  <div style={{ fontWeight: 600, fontSize: 14 }}>{c.name}</div>
                  <div style={{ fontSize: 12, color: 'var(--content-tertiary)' }}>{c.detail}</div>
                </div>
                {c.id === 'db'
                  ? <Pill tone="prog" dot>Connected</Pill>
                  : <span style={{ fontSize: 13, color: 'var(--content-highlight)', fontWeight: 600 }}>Connect →</span>}
              </button>
            ))}
          </div>
        </div>
        <div style={{ height: 1, background: 'var(--stroke-secondary)' }} />
        <div>
          <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 10 }}>AI ranking model</div>
          <div style={{ padding: 14, border: '1px solid var(--stroke-secondary)', borderRadius: 12,
            display: 'flex', alignItems: 'center', gap: 14 }}>
            <I.Sparkle width="20" height="20" style={{ color: 'var(--indigo-50)' }} />
            <div style={{ flex: 1 }}>
              <div style={{ fontWeight: 600, fontSize: 14 }}>Airtm Rank v2 · trained on 142k hires</div>
              <div style={{ fontSize: 12, color: 'var(--content-tertiary)' }}>
                Calibrated weekly · 84% precision@10 over the last 90 days
              </div>
            </div>
            <Pill tone="brand">Active</Pill>
          </div>
        </div>
      </div>
    </Modal>
  );
}

/* ─────────────── candidate drawer ─────────────── */

// Map dashboard canonical stage label → pipeline stage ID (matches STAGE_CANONICAL in server.py)
const _CANONICAL_TO_ID = {
  'Applied':   'applied',
  'Screening': 'intro_screening',
  'Interview': 'hiring_manager',
  'Take-home': 'technical_test',
  'Offer':     'offer',
  'Hired':     'hired',
  'Rejected':  'rejected',
};
const _STAGE_NEXT = {
  'applied':         'intro_screening',
  'intro_screening': 'hiring_manager',
  'hiring_manager':  'technical_test',
  'technical_test':  'offer',
  'offer':           'hired',
};
const _STAGE_LABELS = {
  'intro_screening': 'Intro screening',
  'hiring_manager':  'Hiring manager',
  'technical_test':  'Technical test',
  'offer':           'Offer',
  'hired':           'Hired',
  'rejected':        'Rejected',
};
const _STAGE_BG = {
  'intro_screening': 'var(--neutral-20)',
  'hiring_manager':  'var(--indigo-10)',
  'technical_test':  'var(--violet-10)',
  'offer':           'var(--yellow-10)',
  'hired':           'var(--green-10)',
  'rejected':        'var(--red-10)',
};

function CandidateDrawer({ candidate, onClose }) {
  const [pendingMove, setPendingMove] = React.useState(null); // stageId string
  const [moving, setMoving]           = React.useState(false);

  // All hooks must be called before any early return (Rules of Hooks).
  const commitMove = React.useCallback((toStageId, emailData) => {
    if (!candidate) return;
    if (emailData && emailData.to && emailData.subject && emailData.body) {
      fetch('/api/email/send', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          to:      emailData.to,
          subject: emailData.subject,
          body:    emailData.body,
          opportunityId: candidate.id,
        }),
      })
        .then(r => { if (!r.ok) console.error('[ATS] email send failed', r.status); })
        .catch(err => console.error('[ATS] email send error', err));
    }
    fetch(`/api/opportunities/${candidate.id}/stage`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ stageId: toStageId }),
    }).catch(() => {});
    setPendingMove(null);
    onClose();
  }, [candidate, onClose]);

  if (!candidate) return null;

  // Live candidates (real DB) have a UUID id; mock candidates have ids like "c1".
  const isLive = /^[0-9a-f]{8}-/.test(candidate.id || '');

  // Use the posting title for live data, role-group label for mock data.
  const displayRole = candidate.roleLabel || roleLabel(candidate.role);

  const currentStageId = _CANONICAL_TO_ID[candidate.stage] || 'intro_screening';
  const nextStageId    = _STAGE_NEXT[currentStageId] || null;
  const nextStageLabel = nextStageId ? (_STAGE_LABELS[nextStageId] || nextStageId) : null;

  const req = REQS.find(r => r.role === candidate.role);

  const initiateMove = (toStageId) => {
    if (!isLive) return; // demo data — no real opportunity to update
    // If the email composer is loaded (pipeline.jsx has run), use it.
    if (window.EmailComposer) {
      setPendingMove(toStageId);
    } else {
      setMoving(true);
      fetch(`/api/opportunities/${candidate.id}/stage`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ stageId: toStageId }),
      }).then(() => onClose()).catch(() => setMoving(false));
    }
  };

  const EC = window.EmailComposer;
  const toStageLabel = pendingMove ? (_STAGE_LABELS[pendingMove] || pendingMove) : '';

  return (
    <>
      {pendingMove && EC && (
        <EC
          candidateName={candidate.name}
          candidateEmail={candidate.email || ''}
          candidateHue="auto"
          postingTitle={displayRole}
          fromStageId={currentStageId}
          fromStageLabel={_STAGE_LABELS[currentStageId] || candidate.stage}
          toStageId={pendingMove}
          toStageLabel={toStageLabel}
          toStageAccent={_STAGE_BG[pendingMove] || 'var(--indigo-10)'}
          fromStageAccent={_STAGE_BG[currentStageId] || 'var(--neutral-20)'}
          onSendAndMove={(emailData) => commitMove(pendingMove, emailData)}
          onDiscardAndMove={() => commitMove(pendingMove)}
          onCancel={() => setPendingMove(null)}
        />
      )}
      <div onClick={onClose} style={{
        position: 'absolute', inset: 0, zIndex: 40, background: 'rgba(5,5,5,0.4)'
      }}>
        <div onClick={(e) => e.stopPropagation()} style={{
          position: 'absolute', right: 0, top: 0, bottom: 0, width: 480,
          background: 'var(--bg-primary)', boxShadow: 'var(--shadow-lg)',
          display: 'flex', flexDirection: 'column'
        }}>
          <div style={{ padding: 20, display: 'flex', alignItems: 'center', gap: 12,
            borderBottom: '1px solid var(--stroke-secondary)' }}>
            <Avatar name={candidate.name} size={44} tone="auto" />
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontWeight: 600, fontSize: 16 }}>{candidate.name}</div>
              <div style={{ fontSize: 13, color: 'var(--content-tertiary)' }}>
                {displayRole} · {candidate.loc}
              </div>
            </div>
            <button className="icon-btn" onClick={onClose}><I.X width="18" height="18" /></button>
          </div>
          <div style={{ overflow: 'auto', padding: 20, display: 'flex', flexDirection: 'column', gap: 20, flex: 1 }}>
            <div style={{ display: 'flex', gap: 16, alignItems: 'center',
              padding: 16, borderRadius: 12, background: 'var(--bg-secondary)' }}>
              <ScoreRing score={candidate.score} size={72} stroke={6} />
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--content-tertiary)',
                  textTransform: 'uppercase', letterSpacing: '.06em' }}>Airtm Rank score</div>
                <div style={{ fontWeight: 600, fontSize: 18, marginTop: 2 }}>
                  {candidate.score >= 88 ? 'Strong fit' : candidate.score >= 75 ? 'Good fit' : candidate.score >= 65 ? 'Possible fit' : 'Weak fit'}
                </div>
                <div style={{ fontSize: 13, color: 'var(--content-tertiary)', marginTop: 4 }}>
                  {displayRole} applicant
                </div>
              </div>
            </div>
            {candidate.reasons && candidate.reasons.length > 0 && (
              <div>
                <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--content-tertiary)',
                  textTransform: 'uppercase', letterSpacing: '.06em', marginBottom: 8 }}>Why this rank</div>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                  {candidate.reasons.map((r,i) => (
                    <div key={i} style={{ display: 'flex', alignItems: 'flex-start', gap: 8,
                      padding: '8px 10px', borderRadius: 8, background: 'var(--bg-secondary)' }}>
                      <I.Check width="14" height="14" style={{ color: 'var(--green-50)', marginTop: 3, flexShrink: 0 }} />
                      <span style={{ fontSize: 13 }}>{r}</span>
                    </div>
                  ))}
                </div>
              </div>
            )}
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
              {candidate.expYrs > 0 && <Stat label="Experience" value={`${candidate.expYrs}y`} />}
              {candidate.salary > 0 && <Stat label="Salary ask" value={`$${candidate.salary}k`} />}
              {candidate.source  && <Stat label="Source"    value={candidate.source} />}
              {candidate.lastTouch && <Stat label="Last touch" value={candidate.lastTouch} />}
              {candidate.langs && candidate.langs.length > 0 && <Stat label="Languages" value={candidate.langs.join(' · ')} />}
              {candidate.stage && <Stat label="Stage" value={candidate.stage} />}
            </div>
            {req && (
              <div>
                <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--content-tertiary)',
                  textTransform: 'uppercase', letterSpacing: '.06em', marginBottom: 8 }}>Job</div>
                <div style={{ padding: 14, border: '1px solid var(--stroke-secondary)', borderRadius: 12 }}>
                  <div style={{ fontWeight: 600, fontSize: 14 }}>{req.title}</div>
                  <div style={{ fontSize: 12, color: 'var(--content-tertiary)', marginTop: 2 }}>
                    {req.dept} · {req.loc} · {req.applicants} applicants
                  </div>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 10 }}>
                    <Pill tone={req.status === 'On track' ? 'prog' : 'warn'} dot>{req.status}</Pill>
                    <span style={{ fontSize: 12, color: 'var(--content-tertiary)' }}>
                      AI-predicted fill in {req.predicted}d · target {req.target}d
                    </span>
                  </div>
                </div>
              </div>
            )}
          </div>
          <div style={{ padding: 16, borderTop: '1px solid var(--stroke-secondary)',
            display: 'flex', gap: 8 }}>
            <button
              className="btn btn-outline"
              style={{ flex: 1 }}
              onClick={() => initiateMove('rejected')}
              disabled={moving || !isLive}
              title={!isLive ? 'Demo data — no action' : 'Reject this candidate'}
            >Pass</button>
            {nextStageId && (
              <button
                className="btn btn-brand"
                style={{ flex: 2 }}
                onClick={() => initiateMove(nextStageId)}
                disabled={moving || !isLive}
                title={!isLive ? 'Demo data — no action' : `Move to ${nextStageLabel}`}
              >
                <I.Send width="14" height="14" />
                Move to {nextStageLabel}
              </button>
            )}
          </div>
        </div>
      </div>
    </>
  );
}

function Stat({ label, value }) {
  return (
    <div style={{ padding: 12, borderRadius: 10, background: 'var(--bg-secondary)' }}>
      <div style={{ fontSize: 11, color: 'var(--content-tertiary)', fontWeight: 600,
        textTransform: 'uppercase', letterSpacing: '.06em' }}>{label}</div>
      <div style={{ fontSize: 14, fontWeight: 600, marginTop: 2 }}>{value}</div>
    </div>
  );
}

/* ─────────────── export ─────────────── */

Object.assign(window, {
  ROLES, ROLE_GROUPS, PIPELINE, CANDIDATES, REQS, UPDATES, ATS_CONNECTORS, TIME_PERIODS,
  fmt, reqById, candsForReq, roleLabel, initials, scoreTone, metricSeries,
  I, Avatar, Pill, ScoreRing, Sparkbar, Stat, Select, FilterBar,
  RoleContext, RoleSwitcher, Topbar, Sidebar,
  Modal, AISearchModal, SettingsModal, CandidateDrawer,
});
