/* ───────────────────────── EVE Monitor — Radar 0.0 components (compact) ───────────────────────── */
const { useState, useEffect, useRef, useMemo } = React;

/* ───────── primitives ───────── */
const Mono = ({ children, style, ...p }) => (
  <span style={{ fontFamily: "'JetBrains Mono', ui-monospace, monospace", ...style }} {...p}>{children}</span>
);

const Pill = ({ children, color = 'cyan', solid = false }) => {
  const map = {
    cyan:   { fg: '#38bdf8', bg: 'rgba(56,189,248,0.10)', bd: 'rgba(56,189,248,0.35)' },
    amber:  { fg: '#f5b042', bg: 'rgba(245,176,66,0.10)', bd: 'rgba(245,176,66,0.35)' },
    red:    { fg: '#ff5577', bg: 'rgba(255,85,119,0.10)', bd: 'rgba(255,85,119,0.40)' },
    green:  { fg: '#5be584', bg: 'rgba(91,229,132,0.10)', bd: 'rgba(91,229,132,0.35)' },
    dim:    { fg: '#8a96a8', bg: 'rgba(138,150,168,0.08)', bd: 'rgba(138,150,168,0.25)' },
  };
  const c = map[color];
  return (
    <Mono style={{
      display: 'inline-flex', alignItems: 'center', gap: 4,
      padding: '1px 6px',
      background: solid ? c.fg : c.bg,
      color: solid ? '#0a0e14' : c.fg,
      border: `1px solid ${c.bd}`,
      borderRadius: 1,
      fontSize: 8.5,
      letterSpacing: '0.18em',
      fontWeight: 600,
      textTransform: 'uppercase',
      lineHeight: 1.4,
    }}>{children}</Mono>
  );
};

const riskColor = (r) => r >= 70 ? '#ff5577' : r >= 40 ? '#f5b042' : r >= 15 ? '#38bdf8' : '#5be584';

const PanelHeader = ({ children }) => (
  <div style={{
    display: 'flex', alignItems: 'center', gap: 4,
    padding: '4px 8px',
    borderBottom: '1px solid rgba(56,189,248,0.15)',
    background: 'linear-gradient(180deg, rgba(56,189,248,0.06), rgba(56,189,248,0.02))',
    fontFamily: "'JetBrains Mono', monospace",
    fontSize: 9,
    color: '#e8ecf2',
    letterSpacing: '0.28em',
    fontWeight: 600,
    flexShrink: 0,
  }}>{children}</div>
);

/* ───────── Top bar ───────── */
const TopBar = ({ pilot, region, system, online, page = 'RADAR', onNav = () => {} }) => (
  <header style={{
    gridArea: 'top',
    display: 'flex', alignItems: 'center', gap: 16,
    padding: '4px 10px',
    borderBottom: '1px solid rgba(56,189,248,0.18)',
    background: 'rgba(15,20,28,0.7)',
  }}>
    <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
      <svg width="20" height="20" viewBox="0 0 200 200">
        <circle cx="100" cy="100" r="92" fill="none" stroke="#38bdf8" strokeWidth="8"/>
        <circle cx="100" cy="100" r="56" fill="none" stroke="#38bdf8" strokeWidth="4" opacity="0.4"/>
        <path d="M100,100 L100,8 A92,92 0 0,1 188,100 Z" fill="#38bdf8" opacity="0.5"/>
        <circle cx="100" cy="100" r="10" fill="#38bdf8"/>
      </svg>
      <Mono style={{ fontSize: 10, color: '#e8ecf2', letterSpacing: '0.3em', fontWeight: 700 }}>EVE MONITOR</Mono>
      <Mono style={{ fontSize: 8, color: '#38bdf8', letterSpacing: '0.35em' }}>· {page} 0.0</Mono>
    </div>

    {/* tabs */}
    <div style={{ display: 'flex', gap: 0, marginLeft: 12 }}>
      {['RADAR','INTEL','MARKET','KILLS','SETTINGS'].map(t => {
        const active = page === t;
        return (
          <button key={t} onClick={() => onNav(t)} style={{
            padding: '4px 10px',
            fontFamily: "'JetBrains Mono', monospace",
            fontSize: 9,
            letterSpacing: '0.25em',
            color: active ? '#38bdf8' : '#8a96a8',
            background: 'transparent',
            border: 'none',
            borderBottom: active ? '1px solid #38bdf8' : '1px solid transparent',
            cursor: 'pointer',
          }}>{t}</button>
        );
      })}
    </div>

    <div style={{ display: 'flex', alignItems: 'center', gap: 10, flex: 1, justifyContent: 'center' }}>
      <Mono style={{ color: '#5b6577', fontSize: 9, letterSpacing: '0.2em' }}>RGN</Mono>
      <Mono style={{ color: '#cbd5df', fontSize: 10, letterSpacing: '0.15em' }}>{region}</Mono>
      <Mono style={{ color: '#374151', fontSize: 9 }}>/</Mono>
      <Mono style={{ color: '#5b6577', fontSize: 9, letterSpacing: '0.2em' }}>SYS</Mono>
      <Mono style={{ color: '#38bdf8', fontSize: 10, letterSpacing: '0.15em', fontWeight: 600 }}>{system}</Mono>
    </div>

    <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
      <Pill color={online ? 'green' : 'red'}>
        <span style={{
          width: 5, height: 5, borderRadius: '50%',
          background: online ? '#5be584' : '#ff5577',
          boxShadow: online ? '0 0 6px #5be584' : '0 0 6px #ff5577',
          animation: 'pulse 1.6s ease-in-out infinite',
        }}/>
        {online ? 'sensor live' : 'sensor down'}
      </Pill>
      <Mono style={{ fontSize: 9, color: '#8a96a8', letterSpacing: '0.18em' }}>
        <span style={{ color: '#e8ecf2' }}>{pilot}</span>
      </Mono>
    </div>
  </header>
);

/* ───────── Constellation map ───────── */
const ConstellationMap = ({ systems, edges, focus, onFocus, sweepAngle, hotEvent }) => {
  const W = 720, H = 480;
  const cx = W / 2, cy = H / 2;
  const scale = 200;
  const project = (s) => ({ x: cx + s.x * scale * 1.5, y: cy + s.y * scale * 0.92 });

  return (
    <svg width="100%" height="100%" viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="xMidYMid meet" style={{ display: 'block' }}>
      <defs>
        <radialGradient id="sweep" cx="50%" cy="50%" r="50%">
          <stop offset="0%" stopColor="#38bdf8" stopOpacity="0"/>
          <stop offset="80%" stopColor="#38bdf8" stopOpacity="0.04"/>
          <stop offset="100%" stopColor="#38bdf8" stopOpacity="0"/>
        </radialGradient>
        <linearGradient id="sweepArm" x1="0" y1="0" x2="1" y2="0">
          <stop offset="0%" stopColor="#38bdf8" stopOpacity="0"/>
          <stop offset="100%" stopColor="#38bdf8" stopOpacity="0.6"/>
        </linearGradient>
        <pattern id="dots" x="0" y="0" width="32" height="32" patternUnits="userSpaceOnUse">
          <circle cx="1" cy="1" r="0.6" fill="rgba(56,189,248,0.18)"/>
        </pattern>
      </defs>
      <rect width={W} height={H} fill="url(#dots)"/>

      {[70, 140, 210, 280].map((r,i) => (
        <circle key={i} cx={cx} cy={cy} r={r} fill="none"
          stroke="rgba(56,189,248,0.10)" strokeWidth="1" strokeDasharray={i % 2 ? '2 4' : 'none'}/>
      ))}
      <circle cx={cx} cy={cy} r={280} fill="url(#sweep)"/>

      <g transform={`rotate(${sweepAngle} ${cx} ${cy})`}>
        <path d={`M${cx},${cy} L${cx},${cy - 280} A280,280 0 0,1 ${cx + 198},${cy - 198} Z`}
              fill="url(#sweepArm)" opacity="0.45"/>
        <line x1={cx} y1={cy} x2={cx} y2={cy - 280} stroke="#38bdf8" strokeWidth="1.2" opacity="0.85"/>
      </g>

      {edges.map(([a,b], i) => {
        const sa = systems.find(s => s.id === a);
        const sb = systems.find(s => s.id === b);
        if (!sa || !sb) return null;
        const pa = project(sa), pb = project(sb);
        return <line key={i} x1={pa.x} y1={pa.y} x2={pb.x} y2={pb.y}
          stroke="rgba(56,189,248,0.18)" strokeWidth="1"/>;
      })}

      <line x1={cx} y1={10} x2={cx} y2={H-10} stroke="rgba(56,189,248,0.10)" strokeDasharray="2 4"/>
      <line x1={10} y1={cy} x2={W-10} y2={cy} stroke="rgba(56,189,248,0.10)" strokeDasharray="2 4"/>

      {[
        [10,10, 10,38, 38,10], [W-10,10, W-38,10, W-10,38],
        [10,H-10, 38,H-10, 10,H-38], [W-10,H-10, W-10,H-38, W-38,H-10],
      ].map((pts,i) => (
        <polyline key={i}
          points={`${pts[2]},${pts[3]} ${pts[0]},${pts[1]} ${pts[4]},${pts[5]}`}
          fill="none" stroke="#38bdf8" strokeWidth="1.4"/>
      ))}

      {systems.map(s => {
        const p = project(s);
        const c = riskColor(s.risk);
        const isFocus = focus === s.id;
        const isHot = hotEvent === s.id;
        return (
          <g key={s.id} onClick={() => onFocus(s.id)} style={{ cursor: 'pointer' }}>
            {isHot && (
              <circle cx={p.x} cy={p.y} r="8" fill="none" stroke={c} strokeWidth="2">
                <animate attributeName="r" from="8" to="32" dur="1.2s" repeatCount="indefinite"/>
                <animate attributeName="opacity" from="0.9" to="0" dur="1.2s" repeatCount="indefinite"/>
              </circle>
            )}
            {(s.hub || isFocus) && (
              <circle cx={p.x} cy={p.y} r="16" fill="none" stroke={isFocus ? '#38bdf8' : 'rgba(56,189,248,0.4)'}
                strokeWidth="1.2" strokeDasharray={isFocus ? 'none' : '2 3'}/>
            )}
            <circle cx={p.x} cy={p.y} r={s.hub ? 6 : 3.8} fill={c}/>
            {s.hub && <circle cx={p.x} cy={p.y} r="2" fill="#0a0e14"/>}
            <g transform={`translate(${p.x + 10},${p.y - 4})`}>
              <rect x="-2" y="-7" width={s.id.length * 5.2 + 4} height="11" fill="rgba(10,14,20,0.75)" rx="1"/>
              <text x="1" y="2" fontFamily="JetBrains Mono, monospace" fontSize="8"
                    fill={isFocus ? '#38bdf8' : '#cbd5df'} letterSpacing="0.3">{s.id}</text>
            </g>
            {s.hub && (
              <text x={p.x} y={p.y + 22} fontFamily="JetBrains Mono, monospace" fontSize="7.5"
                    fill="#8a96a8" letterSpacing="1.5" textAnchor="middle">RISK {s.risk}</text>
            )}
          </g>
        );
      })}
    </svg>
  );
};

/* ───────── Risk gauge (compact horizontal) ───────── */
const RiskGauge = ({ value, delta }) => {
  const c = riskColor(value);
  const angle = (value / 100) * 270 - 135;
  const r = 40;
  const arcPath = (from, to) => {
    const start = (from - 90) * Math.PI / 180;
    const end = (to - 90) * Math.PI / 180;
    const x1 = 50 + r * Math.cos(start), y1 = 50 + r * Math.sin(start);
    const x2 = 50 + r * Math.cos(end),   y2 = 50 + r * Math.sin(end);
    const large = (to - from) > 180 ? 1 : 0;
    return `M${x1},${y1} A${r},${r} 0 ${large},1 ${x2},${y2}`;
  };

  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
      <svg width="86" height="86" viewBox="0 0 100 100" style={{ flexShrink: 0 }}>
        <path d={arcPath(45, 315)} fill="none" stroke="rgba(56,189,248,0.12)" strokeWidth="5"/>
        <path d={arcPath(45, 45 + (15/100)*270)} fill="none" stroke="#5be584" strokeWidth="1.2" opacity="0.55"/>
        <path d={arcPath(45 + (40/100)*270, 45 + (70/100)*270)} fill="none" stroke="#f5b042" strokeWidth="1.2" opacity="0.55"/>
        <path d={arcPath(45 + (70/100)*270, 315)} fill="none" stroke="#ff5577" strokeWidth="1.2" opacity="0.65"/>
        <path d={arcPath(45, 45 + (value/100)*270)} fill="none" stroke={c} strokeWidth="5"/>
        <g transform={`rotate(${angle} 50 50)`}>
          <line x1="50" y1="50" x2="50" y2="18" stroke={c} strokeWidth="1.6"/>
          <circle cx="50" cy="50" r="2.6" fill={c}/>
        </g>
      </svg>
      <div style={{ flex: 1, minWidth: 0 }}>
        <Mono style={{ fontSize: 8.5, color: '#5b6577', letterSpacing: '0.28em' }}>SYSTEM RISK</Mono>
        <div style={{ display: 'flex', alignItems: 'baseline', gap: 4, marginTop: 2 }}>
          <Mono style={{ fontSize: 30, color: c, fontWeight: 700, lineHeight: 1, letterSpacing: '-0.02em' }}>
            {value}
          </Mono>
          <Mono style={{ fontSize: 9, color: '#5b6577', letterSpacing: '0.18em' }}>/100</Mono>
        </div>
        <div style={{ marginTop: 4, display: 'flex', flexDirection: 'column', gap: 3 }}>
          <Pill color={delta >= 0 ? 'red' : 'green'}>
            {delta >= 0 ? '▲' : '▼'} {Math.abs(delta).toFixed(0)} · 5m
          </Pill>
          <Mono style={{ fontSize: 8.5, color: '#8a96a8', letterSpacing: '0.2em' }}>
            {value >= 70 ? 'HOSTILE PRESSURE' : value >= 40 ? 'CAUTION' : 'NOMINAL'}
          </Mono>
        </div>
      </div>
    </div>
  );
};

/* ───────── Intel feed ───────── */
const sevColor = (sev) => sev === 'high' ? 'red' : sev === 'med' ? 'amber' : 'green';

const IntelFeed = ({ events, paused, onTogglePause }) => {
  const fmt = (ts) => new Date(ts).toTimeString().slice(0,8);
  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%', minHeight: 0 }}>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 4 }}>
        <Mono style={{ fontSize: 9, color: '#e8ecf2', letterSpacing: '0.28em', fontWeight: 600 }}>
          ▸ INTEL FEED
        </Mono>
        <button onClick={onTogglePause} style={{
          background: 'transparent',
          border: '1px solid rgba(56,189,248,0.3)',
          color: paused ? '#f5b042' : '#38bdf8',
          padding: '1px 6px', borderRadius: 1,
          fontFamily: "'JetBrains Mono', monospace",
          fontSize: 8, letterSpacing: '0.2em', cursor: 'pointer',
        }}>
          {paused ? '▶ RESUME' : '❚❚ PAUSE'}
        </button>
      </div>
      <div style={{ flex: 1, overflow: 'auto', minHeight: 0 }}>
        {events.slice(0, 30).map((e, i) => (
          <a key={e.id}
             href={zkillUrl({ pilot: e.pilot, charId: e.charId, killId: e.killId })}
             target="_blank" rel="noopener noreferrer"
             title={e.pilot && e.pilot !== '—' ? `Cerca ${e.pilot} su zKillboard` : ''}
             style={{
               display: 'grid',
               gridTemplateColumns: '46px 56px 48px 1fr 10px',
               gap: 5,
               padding: '2px 2px',
               borderBottom: '1px solid rgba(56,189,248,0.06)',
               opacity: i === 0 ? 1 : Math.max(0.55, 1 - i * 0.025),
               animation: i === 0 ? 'fadeIn 0.4s ease' : undefined,
               alignItems: 'center',
               textDecoration: 'none', cursor: 'pointer',
               transition: 'background 0.12s',
             }}
             onMouseEnter={(ev) => ev.currentTarget.style.background = 'rgba(56,189,248,0.06)'}
             onMouseLeave={(ev) => ev.currentTarget.style.background = 'transparent'}>
            <Mono style={{ fontSize: 8.5, color: '#5b6577' }}>{fmt(e.ts)}</Mono>
            <Pill color={sevColor(e.sev)}>{e.tag}</Pill>
            <Mono style={{ fontSize: 9, color: '#38bdf8', letterSpacing: '0.05em', fontWeight: 600 }}>{e.sys}</Mono>
            <Mono style={{ fontSize: 9, color: '#cbd5df', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
              {e.text}
              <span style={{ color: '#5b6577', marginLeft: 4 }}>· {e.pilot || '—'}</span>
            </Mono>
            <Mono style={{ fontSize: 8, color: '#5b6577', textAlign: 'right' }}>↗</Mono>
          </a>
        ))}
      </div>
    </div>
  );
};

/* ───────── Adjacent systems (compact list) ───────── */
const AdjacentSystems = ({ systems, focus, onFocus }) => {
  const adj = systems.filter(s => s.id !== focus).slice(0, 8);
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 1, height: '100%', overflow: 'auto' }}>
      {adj.map(s => {
        const c = riskColor(s.risk);
        return (
          <button key={s.id} onClick={() => onFocus(s.id)} style={{
            background: 'transparent',
            border: 'none',
            borderLeft: `2px solid ${c}`,
            padding: '2px 4px 2px 6px',
            display: 'grid',
            gridTemplateColumns: '1fr auto auto',
            gap: 6,
            alignItems: 'center',
            textAlign: 'left',
            cursor: 'pointer',
            fontFamily: "'JetBrains Mono', monospace",
            borderBottom: '1px solid rgba(56,189,248,0.06)',
          }}>
            <span style={{ fontSize: 9, color: '#e8ecf2', letterSpacing: '0.08em', fontWeight: 600 }}>
              {s.id}
            </span>
            <span style={{ fontSize: 8, color: '#5b6577', letterSpacing: '0.18em' }}>R</span>
            <span style={{ fontSize: 10, color: c, fontWeight: 700, minWidth: 18, textAlign: 'right' }}>{s.risk}</span>
          </button>
        );
      })}
    </div>
  );
};

/* ───────── Sensor status ───────── */
const SensorStatus = ({ rate, eventCount, folder }) => {
  const rows = [
    { label: 'CHATLOG', value: 'OK', color: 'green' },
    { label: 'FOLDER',  value: folder, color: 'cyan', mono: true },
    { label: 'PARSE',   value: typeof rate === 'number' ? `${rate.toFixed(1)}/s` : String(rate ?? '—'), color: 'cyan' },
    { label: 'EVENTS',  value: typeof eventCount === 'number' ? eventCount.toLocaleString() : String(eventCount ?? 0), color: 'dim' },
    { label: 'EULA',    value: 'R/O', color: 'green' },
    { label: 'API',     value: 'ESI', color: 'cyan' },
  ];
  return (
    <div style={{ display: 'flex', flexDirection: 'column' }}>
      {rows.map((r,i) => (
        <div key={i} style={{
          display: 'flex', justifyContent: 'space-between', alignItems: 'center',
          padding: '3px 0',
          borderBottom: '1px solid rgba(56,189,248,0.06)',
        }}>
          <Mono style={{ fontSize: 8.5, color: '#5b6577', letterSpacing: '0.25em' }}>{r.label}</Mono>
          {r.mono
            ? <Mono style={{ fontSize: 8.5, color: '#38bdf8', letterSpacing: '0.05em' }}>{r.value}</Mono>
            : <Pill color={r.color}>{r.value}</Pill>}
        </div>
      ))}
    </div>
  );
};

/* ───────── Audio panel (compact mixer + per-alert toggle + test) ───────── */
// Suoni reali dall'app (stessi file di /account → Audio alerts).
// Fallback sintetizzato se il fetch fallisce.
const ALERT_MP3 = {
  high: '/audio/alerts/danger_alarm_a_ship_siren_3s.mp3',
  med:  '/audio/alerts/eve_alert_warning_proximity.mp3',
  low:  '/audio/alerts/eve_alert_clear_chime.mp3',
};
const _audioCache = {};
async function _loadAudio(url) {
  if (_audioCache[url]) return _audioCache[url];
  try {
    const ctx = window.__audioCtx || (window.__audioCtx = new (window.AudioContext || window.webkitAudioContext)());
    const res = await fetch(url);
    const buf = await res.arrayBuffer();
    const decoded = await ctx.decodeAudioData(buf);
    _audioCache[url] = decoded;
    return decoded;
  } catch { return null; }
}

const playBlip = (freq = 880, dur = 0.08, type = 'sine', gain = 0.12, sev = 'low') => {
  const vol = window.__masterVol ?? 0.7;
  const mp3 = ALERT_MP3[sev] || ALERT_MP3['low'];
  _loadAudio(mp3).then(buf => {
    try {
      const ctx = window.__audioCtx;
      if (!ctx || !buf) throw new Error('no audio');
      const src = ctx.createBufferSource();
      const g = ctx.createGain();
      src.buffer = buf;
      g.gain.value = vol;
      src.connect(g); g.connect(ctx.destination);
      src.start();
    } catch {
      // fallback sintetizzato
      try {
        const ctx = window.__audioCtx || (window.__audioCtx = new (window.AudioContext || window.webkitAudioContext)());
        const o = ctx.createOscillator(); const gn = ctx.createGain();
        o.type = type; o.frequency.value = freq;
        gn.gain.value = vol * gain;
        o.connect(gn); gn.connect(ctx.destination);
        const now = ctx.currentTime;
        gn.gain.setValueAtTime(gn.gain.value, now);
        gn.gain.exponentialRampToValueAtTime(0.001, now + dur);
        o.start(now); o.stop(now + dur + 0.02);
      } catch(e2) {}
    }
  });
};

const ALERT_SOUNDS = {
  hostile:  { label:'HOSTILE',   freq: 220, type:'square',   sev:'high' },
  cyno:     { label:'CYNO',      freq: 110, type:'square',   sev:'high' },
  bubble:   { label:'BUBBLE',    freq: 330, type:'sawtooth', sev:'high' },
  gatecamp: { label:'GATECAMP',  freq: 440, type:'square',   sev:'med'  },
  neutral:  { label:'NEUTRAL',   freq: 660, type:'sine',     sev:'med'  },
  clear:    { label:'CLEAR',     freq: 880, type:'sine',     sev:'low'  },
};

const SoundAlerts = ({ alerts, setAlerts, volume, setVolume, muteAll, setMuteAll }) => {
  const onCount = Object.values(alerts).filter(Boolean).length;
  const total = Object.keys(alerts).length;

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
      {/* master row — volume + mute */}
      <div style={{
        display: 'grid', gridTemplateColumns: '38px 1fr 26px 22px',
        gap: 6, alignItems: 'center',
        padding: '4px 0',
        borderBottom: '1px solid rgba(56,189,248,0.15)',
      }}>
        <Mono style={{ fontSize: 8, color: '#5b6577', letterSpacing: '0.22em' }}>VOL</Mono>
        <input
          type="range" min="0" max="100" value={muteAll ? 0 : volume}
          onChange={(e) => { setVolume(+e.target.value); window.__masterVol = (+e.target.value)/100; }}
          disabled={muteAll}
          style={{ width: '100%', accentColor: '#38bdf8', height: 4 }}
        />
        <Mono style={{ fontSize: 9, color: '#38bdf8', fontWeight: 600, textAlign: 'right' }}>
          {muteAll ? '—' : volume}
        </Mono>
        <button onClick={() => setMuteAll(!muteAll)} title={muteAll ? 'Unmute' : 'Mute all'} style={{
          background: muteAll ? 'rgba(255,85,119,0.2)' : 'transparent',
          border: `1px solid ${muteAll ? '#ff5577' : 'rgba(56,189,248,0.3)'}`,
          color: muteAll ? '#ff5577' : '#38bdf8',
          padding: 0, width: 18, height: 14, borderRadius: 1, cursor: 'pointer',
          fontSize: 10, lineHeight: '12px',
        }}>{muteAll ? '✕' : '♪'}</button>
      </div>

      {/* per-alert rows with TEST button */}
      <div style={{ flex: 1, overflow: 'auto' }}>
        {Object.entries(alerts).map(([k, v]) => {
          const meta = ALERT_SOUNDS[k] || { label: k.toUpperCase(), freq: 660, type: 'sine', sev: 'med' };
          const sevDot = meta.sev === 'high' ? '#ff5577' : meta.sev === 'med' ? '#f5b042' : '#5be584';
          return (
            <div key={k} style={{
              display: 'grid', gridTemplateColumns: '6px 1fr 18px 22px',
              gap: 6, alignItems: 'center',
              padding: '3px 0',
              borderBottom: '1px solid rgba(56,189,248,0.06)',
            }}>
              <div style={{ width: 4, height: 4, borderRadius: '50%', background: sevDot, opacity: v ? 1 : 0.3 }}/>
              <button onClick={() => setAlerts({ ...alerts, [k]: !v })} style={{
                background: 'transparent', border: 'none', textAlign: 'left',
                padding: 0, cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 4,
              }}>
                <Mono style={{ fontSize: 8.5, color: v ? '#e8ecf2' : '#5b6577',
                                letterSpacing: '0.18em', fontWeight: v ? 600 : 400 }}>
                  {meta.label}
                </Mono>
              </button>
              <button onClick={() => { if (!muteAll) playBlip(meta.freq, 0.1, meta.type, 0.12, meta.sev); }}
                disabled={muteAll || !v}
                title="Test"
                style={{
                  background: 'transparent', border: 'none', cursor: 'pointer',
                  color: (muteAll || !v) ? '#3a4453' : '#38bdf8',
                  fontFamily: "'JetBrains Mono', monospace",
                  fontSize: 8, padding: 0, letterSpacing: '0.1em',
                }}>▶</button>
              <button onClick={() => setAlerts({ ...alerts, [k]: !v })} style={{
                background: 'transparent', border: 'none', padding: 0, cursor: 'pointer',
              }}>
                <div style={{
                  width: 22, height: 10,
                  background: v ? '#38bdf8' : 'rgba(56,189,248,0.12)',
                  borderRadius: 5, position: 'relative', transition: 'background 0.2s',
                }}>
                  <div style={{
                    position: 'absolute', top: 1, left: v ? 13 : 1,
                    width: 8, height: 8, borderRadius: '50%',
                    background: v ? '#0a0e14' : '#8a96a8',
                    transition: 'left 0.2s',
                  }}/>
                </div>
              </button>
            </div>
          );
        })}
      </div>

      {/* footer status */}
      <div style={{
        padding: '3px 0 0', marginTop: 2,
        borderTop: '1px solid rgba(56,189,248,0.15)',
        display: 'flex', justifyContent: 'space-between', alignItems: 'center',
      }}>
        <Mono style={{ fontSize: 8, color: '#5b6577', letterSpacing: '0.22em' }}>
          {onCount}/{total} ACTIVE
        </Mono>
        <button onClick={() => { if (!muteAll) playBlip(660, 0.15, 'sine'); }}
          disabled={muteAll}
          style={{
            background: 'transparent',
            border: '1px solid rgba(56,189,248,0.3)',
            color: muteAll ? '#3a4453' : '#38bdf8',
            padding: '1px 6px', borderRadius: 1,
            fontFamily: "'JetBrains Mono', monospace",
            fontSize: 8, letterSpacing: '0.2em', cursor: muteAll ? 'default' : 'pointer',
          }}>♪ TEST ALL</button>
      </div>
    </div>
  );
};

/* ───────── Killboard (rows link to zKillboard) ───────── */
// killmail id is optional (mock data); fall back to character search if missing
const zkillUrl = (k) => {
  if (k.killId) return `https://zkillboard.com/kill/${k.killId}/`;
  if (k.charId) return `https://zkillboard.com/character/${k.charId}/`;
  const pilot = (k.pilot || k.victim || '').replace(/^k\./, '').trim();
  if (!pilot || pilot === '—' || pilot === '-') return 'https://zkillboard.com/';
  return `https://zkillboard.com/search/${encodeURIComponent(pilot)}/`;
};

const Killboard = ({ kills }) => (
  <div style={{ display: 'flex', flexDirection: 'column' }}>
    {kills.map((k,i) => (
      <a key={i} href={zkillUrl(k)} target="_blank" rel="noopener noreferrer"
         title={`Open ${k.pilot} on zKillboard`}
         style={{
           display: 'grid', gridTemplateColumns: '36px 1fr auto 12px',
           gap: 6, alignItems: 'center',
           padding: '3px 4px 3px 0',
           borderBottom: '1px solid rgba(56,189,248,0.06)',
           borderLeft: `2px solid ${k.loss ? '#ff5577' : '#5be584'}`,
           paddingLeft: 4,
           textDecoration: 'none',
           cursor: 'pointer',
           transition: 'background 0.12s',
         }}
         onMouseEnter={(e) => e.currentTarget.style.background = 'rgba(56,189,248,0.06)'}
         onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
        <Mono style={{ fontSize: 8.5, color: '#5b6577' }}>{k.time}</Mono>
        <div style={{ minWidth: 0 }}>
          <Mono style={{ fontSize: 9, color: '#e8ecf2', fontWeight: 600, display: 'block',
            overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
            {k.ship}
          </Mono>
          <Mono style={{ fontSize: 7.5, color: '#5b6577', display: 'block',
            overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
            {k.pilot}
          </Mono>
        </div>
        <Mono style={{ fontSize: 9, color: k.loss ? '#ff5577' : '#5be584', fontWeight: 600 }}>
          {k.loss ? '−' : '+'}{k.value}
        </Mono>
        <Mono style={{ fontSize: 9, color: '#5b6577', letterSpacing: 0, lineHeight: 1 }}>↗</Mono>
      </a>
    ))}
  </div>
);

/* ───────── Panel wrapper ───────── */
const Panel = ({ children, style, area }) => (
  <div style={{
    gridArea: area,
    background: 'rgba(15,20,28,0.55)',
    border: '1px solid rgba(56,189,248,0.18)',
    borderRadius: 2,
    overflow: 'hidden',
    position: 'relative',
    minHeight: 0,
    minWidth: 0,
    ...style,
  }}>
    {children}
  </div>
);

Object.assign(window, {
  Mono, Pill, riskColor, PanelHeader,
  TopBar, ConstellationMap, RiskGauge, IntelFeed,
  AdjacentSystems, SensorStatus, SoundAlerts, Killboard, Panel,
  playBlip, ALERT_SOUNDS, zkillUrl,
});
