// health/lib.jsx
// Shared Ordentus iOS components — icons, tab bar, status bar wrapper,
// metric primitives, charts. All theming via the `dark` boolean.
//
// Globals exported (window.*): Icon, TabBar, BigTitle, NavBar, IslandStatus,
// MetricTile, RingChart, BarChart, ConsistencyDots, StageStack, HBar,
// SectionHead, InsightCard, Pill, TrendChip, SegControl, Scrim, Banner,
// FrameScreen.

// ─────────────────────────────────────────────────────────────
// SF Symbol-style icons, stroke 2.2, currentColor.
// We draw chrome icons (Ordentus has its own pack) — line weight matches
// the assets/icons/* SVGs in the design system. Names mirror Ordentus's
// icon library but stay neutral/HIG-aligned.
// ─────────────────────────────────────────────────────────────
const Icon = ({ name, size = 22, stroke = 2, style }) => {
  const s = { width: size, height: size, ...style };
  const sw = stroke;
  const common = { fill: 'none', stroke: 'currentColor', strokeWidth: sw, strokeLinecap: 'round', strokeLinejoin: 'round' };
  switch (name) {
    case 'calendar':
      return (<svg viewBox="0 0 24 24" style={s}><rect x="3.2" y="4.5" width="17.6" height="16.3" rx="3" {...common}/><path d="M3.2 9.2h17.6" {...common}/><path d="M8 2.7v3.6M16 2.7v3.6" {...common}/></svg>);
    case 'calendar-filled':
      return (<svg viewBox="0 0 24 24" style={s}><path d="M8 2.7v3M16 2.7v3" {...common}/><rect x="3.2" y="4.5" width="17.6" height="16.3" rx="3" fill="currentColor" stroke="currentColor" strokeWidth="1" strokeLinejoin="round"/><path d="M3.2 9.2h17.6" stroke="#fff" strokeWidth="1.6" fill="none"/><rect x="6.4" y="12" width="3.2" height="3.2" rx="0.7" fill="#fff"/></svg>);
    case 'heart-filled':
      return (<svg viewBox="0 0 24 24" style={s}><path d="M12 20.4S3.5 14.8 3.5 9.2A4.7 4.7 0 0 1 12 6.4 4.7 4.7 0 0 1 20.5 9.2C20.5 14.8 12 20.4 12 20.4Z" fill="currentColor" stroke="currentColor" strokeWidth="1.2" strokeLinejoin="round"/></svg>);
    case 'today':
      return (<svg viewBox="0 0 24 24" style={s}><rect x="3.2" y="4.5" width="17.6" height="16.3" rx="3" {...common}/><path d="M3.2 9.2h17.6M8 2.7v3.6M16 2.7v3.6" {...common}/><circle cx="12" cy="15" r="2.4" fill="currentColor" stroke="none"/></svg>);
    case 'heart':
      return (<svg viewBox="0 0 24 24" style={s}><path d="M12 20.4S3.5 14.8 3.5 9.2A4.7 4.7 0 0 1 12 6.4 4.7 4.7 0 0 1 20.5 9.2C20.5 14.8 12 20.4 12 20.4Z" {...common}/></svg>);
    case 'share':
      return (<svg viewBox="0 0 24 24" style={s}><path d="M12 3v12M8 7l4-4 4 4" {...common}/><path d="M5 12v6a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-6" {...common}/></svg>);
    case 'settings':
      return (<svg viewBox="0 0 24 24" style={s}><circle cx="12" cy="12" r="3" {...common}/><path d="M19.4 14.5a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V20a2 2 0 0 1-4 0v-.08a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H4a2 2 0 0 1 0-4h.08a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06A2 2 0 1 1 8.03 4.4l.06.06a1.65 1.65 0 0 0 1.82.33h.01a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.08a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82v.01a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.08a1.65 1.65 0 0 0-1.51 1Z" {...common}/></svg>);
    case 'moon':
      return (<svg viewBox="0 0 24 24" style={s}><path d="M20.4 13.5A8 8 0 1 1 10.5 3.6a6.5 6.5 0 0 0 9.9 9.9Z" {...common}/></svg>);
    case 'cognition':
    case 'brain':
      return (<svg viewBox="0 0 24 24" style={s}><path d="M8.5 4A3 3 0 0 0 6 7v0a2.7 2.7 0 0 0-1.4 4.7A2.7 2.7 0 0 0 6 17a3 3 0 0 0 3 3h2V4Z" {...common}/><path d="M15.5 4A3 3 0 0 1 18 7v0a2.7 2.7 0 0 1 1.4 4.7A2.7 2.7 0 0 1 18 17a3 3 0 0 1-3 3h-2V4Z" {...common}/><path d="M12 4v16" {...common}/></svg>);
    case 'activity':
    case 'bolt':
      return (<svg viewBox="0 0 24 24" style={s}><path d="M13 2 4 14h7l-1 8 9-12h-7l1-8Z" {...common}/></svg>);
    case 'mindfulness':
    case 'sun':
      return (<svg viewBox="0 0 24 24" style={s}><circle cx="12" cy="12" r="4" {...common}/><path d="M12 2v2.5M12 19.5V22M2 12h2.5M19.5 12H22M4.6 4.6l1.8 1.8M17.6 17.6l1.8 1.8M4.6 19.4l1.8-1.8M17.6 6.4l1.8-1.8" {...common}/></svg>);
    case 'trend-up':
      return (<svg viewBox="0 0 24 24" style={s}><path d="M3 17 10 10l4 4 7-7M14 7h7v7" {...common}/></svg>);
    case 'chevron-right':
      return (<svg viewBox="0 0 24 24" style={s}><path d="M9 6l6 6-6 6" {...common}/></svg>);
    case 'chevron-left':
      return (<svg viewBox="0 0 24 24" style={s}><path d="M15 6l-6 6 6 6" {...common}/></svg>);
    case 'chevron-down':
      return (<svg viewBox="0 0 24 24" style={s}><path d="M6 9l6 6 6-6" {...common}/></svg>);
    case 'ellipsis':
      return (<svg viewBox="0 0 24 24" style={s}><circle cx="5" cy="12" r="1.6" fill="currentColor" stroke="none"/><circle cx="12" cy="12" r="1.6" fill="currentColor" stroke="none"/><circle cx="19" cy="12" r="1.6" fill="currentColor" stroke="none"/></svg>);
    case 'plus':
      return (<svg viewBox="0 0 24 24" style={s}><path d="M12 5v14M5 12h14" {...common}/></svg>);
    case 'close':
      return (<svg viewBox="0 0 24 24" style={s}><path d="M6 6l12 12M18 6l-6 6-6 6" {...common}/></svg>);
    case 'check':
      return (<svg viewBox="0 0 24 24" style={s}><path d="M5 12l5 5L20 7" {...common}/></svg>);
    case 'refresh':
      return (<svg viewBox="0 0 24 24" style={s}><path d="M3 12a9 9 0 0 1 15.5-6.4L21 8M21 3v5h-5M21 12a9 9 0 0 1-15.5 6.4L3 16M3 21v-5h5" {...common}/></svg>);
    case 'search':
      return (<svg viewBox="0 0 24 24" style={s}><circle cx="11" cy="11" r="7" {...common}/><path d="m20 20-4-4" {...common}/></svg>);
    case 'arrow-up':
      return (<svg viewBox="0 0 24 24" style={s}><path d="M12 19V5M5 12l7-7 7 7" {...common}/></svg>);
    case 'arrow-down':
      return (<svg viewBox="0 0 24 24" style={s}><path d="M12 5v14M5 12l7 7 7-7" {...common}/></svg>);
    case 'arrow-right':
      return (<svg viewBox="0 0 24 24" style={s}><path d="M5 12h14M12 5l7 7-7 7" {...common}/></svg>);
    case 'alert':
      return (<svg viewBox="0 0 24 24" style={s}><path d="M12 3 22 20H2L12 3Z" {...common}/><path d="M12 10v4" {...common}/><circle cx="12" cy="17" r="0.6" fill="currentColor" stroke="none"/></svg>);
    case 'wifi-off':
      return (<svg viewBox="0 0 24 24" style={s}><path d="M2 8.8a16 16 0 0 1 6-3.5M22 8.8a16 16 0 0 0-7-3.5M5.5 12.5a10 10 0 0 1 4-2.5M18.5 12.5a10 10 0 0 0-3.5-2.4M9 16a4 4 0 0 1 6 0" {...common}/><circle cx="12" cy="19.5" r="0.6" fill="currentColor" stroke="none"/><path d="M3 3l18 18" {...common}/></svg>);
    case 'bed':
      return (<svg viewBox="0 0 24 24" style={s}><path d="M3 19V8M3 14h18M21 19v-4a2 2 0 0 0-2-2H10v-3" {...common}/></svg>);
    case 'pencil':
      return (<svg viewBox="0 0 24 24" style={s}><path d="M16 3.5 20.5 8 8 20.5H3.5V16Z" {...common}/></svg>);
    case 'shuffle':
      return (<svg viewBox="0 0 24 24" style={s}><path d="M16 3h5v5M21 3l-7 7M16 21h5v-5M21 21l-5-5M3 3l18 18" {...common}/></svg>);
    case 'clock':
      return (<svg viewBox="0 0 24 24" style={s}><circle cx="12" cy="12" r="9" {...common}/><path d="M12 7v5l3 2" {...common}/></svg>);
    case 'footsteps':
      return (<svg viewBox="0 0 24 24" style={s}><path d="M7 4a2 2 0 0 1 2 2v4a2 2 0 0 1-4 0V6a2 2 0 0 1 2-2ZM7 13l-1 5a2 2 0 0 0 3.9 1L11 14M15 7a2 2 0 0 1 2 2v4a2 2 0 0 1-4 0V9a2 2 0 0 1 2-2ZM15 16l-1 4a2 2 0 0 0 3.9.8L19 17" {...common}/></svg>);
    case 'flame':
      return (<svg viewBox="0 0 24 24" style={s}><path d="M12 2s5 5 5 10a5 5 0 0 1-10 0c0-2 1-3 1-3s-1 1-1 3a3 3 0 0 0 6 0c0-3-1-5-1-5" {...common}/></svg>);
    case 'lock':
      return (<svg viewBox="0 0 24 24" style={s}><rect x="4.5" y="10.5" width="15" height="10.5" rx="2.5" {...common}/><path d="M8 10.5V7a4 4 0 0 1 8 0v3.5" {...common}/></svg>);
    case 'sparkles':
      return (<svg viewBox="0 0 24 24" style={s}><path d="M12 3v4M12 17v4M3 12h4M17 12h4M5.5 5.5l2.8 2.8M15.7 15.7l2.8 2.8M5.5 18.5l2.8-2.8M15.7 8.3l2.8-2.8" {...common}/></svg>);
    case 'home':
      return (<svg viewBox="0 0 24 24" style={s}><path d="M3.5 11 12 4l8.5 7" {...common}/><path d="M5.5 9.8V20h13V9.8" {...common}/><path d="M10 20v-5.5h4V20" {...common}/></svg>);
    case 'home-filled':
      return (<svg viewBox="0 0 24 24" style={s}><path d="M3.5 11 12 4l8.5 7" {...common}/><path d="M5.5 9.8V20h4.5v-5.5h4V20h4.5V9.8" fill="currentColor" stroke="currentColor" strokeWidth="0.8" strokeLinejoin="round"/></svg>);
    case 'user':
    case 'person':
      return (<svg viewBox="0 0 24 24" style={s}><circle cx="12" cy="8.2" r="3.7" {...common}/><path d="M4.6 20.4c.8-3.7 3.9-6.2 7.4-6.2s6.6 2.5 7.4 6.2" {...common}/></svg>);
    case 'person-filled':
      return (<svg viewBox="0 0 24 24" style={s}><circle cx="12" cy="8.2" r="3.7" fill="currentColor" stroke="currentColor"/><path d="M4.6 20.4c.8-3.7 3.9-6.2 7.4-6.2s6.6 2.5 7.4 6.2" fill="currentColor" stroke="currentColor"/></svg>);
    case 'cloud-sun':
      return (<svg viewBox="0 0 24 24" style={s}><circle cx="8" cy="9" r="3" {...common}/><path d="M8 2v1.5M8 14.5V16M2 9h1.5M12.5 9H14M3.6 4.6l1 1M11.4 4.6l-1 1M3.6 13.4l1-1" {...common}/><path d="M9 14a4 4 0 0 1 7.7-1.4A3 3 0 1 1 18 19H10a3 3 0 0 1-1-5Z" {...common}/></svg>);
    case 'thermometer':
      return (<svg viewBox="0 0 24 24" style={s}><path d="M14 14.7V5a2 2 0 1 0-4 0v9.7a4 4 0 1 0 4 0Z" {...common}/><path d="M12 9v6" {...common}/></svg>);
    default:
      return null;
  }
};

// ─────────────────────────────────────────────────────────────
// Tab bar — 5 tabs, frosted glass on iOS
// ─────────────────────────────────────────────────────────────
const TAB_DEFS = [
  { id: 'calendar', label: 'Calendar', icon: 'calendar' },
  { id: 'today',    label: 'Today',    icon: 'today' },
  { id: 'health',   label: 'Health',   icon: 'heart' },
  { id: 'share',    label: 'Share',    icon: 'share' },
  { id: 'settings', label: 'Settings', icon: 'settings' },
];
const TabBar = ({ active = 'health' }) => (
  <div className="tabbar">
    {TAB_DEFS.map(t => (
      <div key={t.id} className={'tab' + (t.id === active ? ' active' : '')}>
        <div className="ic"><Icon name={t.icon} size={26} stroke={t.id === active ? 2.4 : 2}/></div>
        <div className="lb">{t.label}</div>
      </div>
    ))}
  </div>
);

// ─────────────────────────────────────────────────────────────
// Custom big-title nav (replaces ios-frame's default so we can
// place a clean avatar + ellipsis trailing pair).
// ─────────────────────────────────────────────────────────────
const NavTopRow = ({ leading, trailing }) => (
  <div className="navrow">
    <div>{leading}</div>
    <div style={{ display:'flex', gap: 8 }}>{trailing}</div>
  </div>
);

const Avatar = ({ initial = 'L' }) => (
  <div className="avatar">{initial}</div>
);

const PillBtn = ({ children, onClick }) => (
  <button className="pill-btn" onClick={onClick} style={{
    width: 36, height: 36, borderRadius: '50%',
    display: 'grid', placeItems: 'center',
    background: 'rgba(120,120,128,.16)', backdropFilter: 'blur(20px)',
    border: 'none', color: 'inherit', cursor: 'pointer'
  }}>{children}</button>
);

const BigTitle = ({ title, leading, trailing, sub }) => (
  <>
    <NavTopRow leading={leading} trailing={trailing} />
    <div className="biglt">
      <div>
        <h1>{title}</h1>
        {sub && <div className="fn" style={{ color: 'var(--io-label-secondary)', marginTop: 4 }}>{sub}</div>}
      </div>
    </div>
  </>
);

// Inline nav (after push) — back chevron + centered title
const InlineNav = ({ back = 'Health', title }) => (
  <div style={{
    paddingTop: 56, paddingBottom: 8, position: 'relative',
    display: 'flex', alignItems: 'center', justifyContent: 'center',
  }}>
    <div style={{
      position: 'absolute', left: 8, display:'flex', alignItems:'center', gap: 2,
      color: 'var(--io-blue)', fontSize: 17, letterSpacing: -0.4,
    }}>
      <Icon name="chevron-left" size={22} stroke={2.6}/>
      <span style={{ marginLeft: -2 }}>{back}</span>
    </div>
    <div className="hd">{title}</div>
  </div>
);

// ─────────────────────────────────────────────────────────────
// Section header inside a screen scroll content
// ─────────────────────────────────────────────────────────────
const SectionHead = ({ icon, color, label, see }) => (
  <div className="secthd">
    <div className="lhs">
      {icon && <span className="ico" style={{ color: color || 'var(--io-label)' }}><Icon name={icon} size={22} stroke={2.2}/></span>}
      <div className="t2" style={{ letterSpacing: -0.45 }}>{label}</div>
    </div>
    {see && <a className="see">{see}</a>}
  </div>
);

// ─────────────────────────────────────────────────────────────
// Pill / trend chip
// ─────────────────────────────────────────────────────────────
const Pill = ({ tone = 'info', children }) => (
  <span className={`pill ${tone}`}>{children}</span>
);
const TrendChip = ({ dir = 'flat', value }) => (
  <span className={`trend ${dir}`}>
    {dir === 'up' && <Icon name="arrow-up" size={11} stroke={3}/>}
    {dir === 'down' && <Icon name="arrow-down" size={11} stroke={3}/>}
    {dir === 'flat' && <span style={{ width: 8, height: 2, background: 'currentColor', borderRadius: 2 }}/>}
    <span data-num>{value}</span>
  </span>
);

// ─────────────────────────────────────────────────────────────
// Metric tile (Apple Health hierarchy: lbl → value → trend → ctx)
// ─────────────────────────────────────────────────────────────
const MetricTile = ({ dotColor, label, value, unit, trendDir, trendValue, ctx, accent }) => (
  <div className="tile">
    <div className="head">
      {dotColor && <span className="dot" style={{ background: dotColor }}/>}
      <span className="lb">{label}</span>
    </div>
    <div className="val">
      <span className="m-lg" style={{ color: accent || 'var(--io-label)' }} data-num>{value}</span>
      {unit && <span className="m-unit" style={{ fontSize: 16, color: 'var(--io-label-secondary)' }}>{unit}</span>}
    </div>
    {(trendDir || ctx) && (
      <div className="stack" style={{ gap: 2, marginTop: 'auto' }}>
        {trendDir && <TrendChip dir={trendDir} value={trendValue}/>}
        {ctx && <div className="ctx">{ctx}</div>}
      </div>
    )}
  </div>
);

// ─────────────────────────────────────────────────────────────
// Ring chart (animated stroke-end on appear)
// Centered text uses baseline-aligned number+unit, with optional
// sub-labels below. We let the inner flex column dictate visual center
// so the number sits dead-centre in the ring regardless of how many
// caption lines follow.
// ─────────────────────────────────────────────────────────────
const RingChart = ({ percent = 78, size = 130, stroke = 12, color = 'var(--io-purple)', label, sublabel, valueSize, valueUnit = '%', dark }) => {
  const r = (size - stroke) / 2;
  const c = 2 * Math.PI * r;
  const dash = c * Math.min(Math.max(percent, 0), 100) / 100;
  const trackColor = dark ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.06)';
  // Auto-scale the number to the ring. Heuristic: ~35% of diameter looks
  // right at every size we use (86 / 108 / 110 / 130).
  const numSize = valueSize ?? Math.round(size * 0.34);
  const unitSize = Math.round(numSize * 0.42);
  return (
    <div className="ring" style={{ width: size, height: size, position: 'relative' }}>
      <svg width={size} height={size} style={{ transform: 'rotate(-90deg)', display: 'block' }}>
        <circle cx={size/2} cy={size/2} r={r} fill="none" stroke={trackColor} strokeWidth={stroke}/>
        <circle cx={size/2} cy={size/2} r={r} fill="none" stroke={color} strokeWidth={stroke}
          strokeLinecap="round" strokeDasharray={`${dash} ${c - dash}`}/>
      </svg>
      <div style={{
        position: 'absolute', inset: 0,
        display: 'flex', flexDirection: 'column',
        alignItems: 'center', justifyContent: 'center',
        gap: 2, textAlign: 'center',
        padding: stroke + 2,
      }}>
        <div style={{
          display: 'flex', alignItems: 'baseline', gap: 1,
          fontVariantNumeric: 'tabular-nums',
          lineHeight: 1,
        }} data-num>
          <span style={{
            fontSize: numSize, fontWeight: 300,
            letterSpacing: numSize > 30 ? -1.2 : -0.5,
            color: 'var(--io-label)',
          }}>{percent}</span>
          <span style={{
            fontSize: unitSize, fontWeight: 400,
            color: 'var(--io-label-secondary)',
            letterSpacing: 0,
          }}>{valueUnit}</span>
        </div>
        {label && <div style={{
          fontSize: Math.max(10, Math.round(size * 0.085)),
          color: 'var(--io-label-secondary)',
          letterSpacing: 0.1, fontWeight: 500,
          marginTop: 1,
        }}>{label}</div>}
        {sublabel && <div style={{
          fontSize: Math.max(9, Math.round(size * 0.075)),
          color: 'var(--io-label-tertiary)',
          letterSpacing: 0.1,
        }}>{sublabel}</div>}
      </div>
    </div>
  );
};

// ─────────────────────────────────────────────────────────────
// 7-day bar chart with dashed goal line
// ─────────────────────────────────────────────────────────────
const BarChart = ({ data, goal = 8, max = 10, height = 130, todayIndex = 6, showLabels = true, accent = 'var(--io-purple)' }) => {
  // data: [{ day:'Mon', hours: 6.2 }, ...]
  const goalPct = (1 - goal / max) * 100;
  return (
    <div style={{ position: 'relative' }}>
      <div className="bars" style={{ height }}>
        <div className="goal-line" style={{ top: `${goalPct}%` }}>
          <span className="goal-lbl" style={{ top: -14 }}>Goal {goal}h</span>
        </div>
        {data.map((d, i) => {
          const h = Math.max(2, (d.hours / max) * height);
          const under = d.hours < goal - 1.5;
          return (
            <div key={i} className={`bar ${i === todayIndex ? 'today' : ''} ${under && i !== todayIndex ? 'under' : ''}`}
              style={{
                height: h,
                background: under ? 'var(--io-orange)' : accent,
                opacity: i === todayIndex ? 1 : 0.85,
              }}/>
          );
        })}
      </div>
      {showLabels && (
        <div className="bar-labels">
          {data.map((d, i) => (
            <span key={i} style={{ color: i === todayIndex ? 'var(--io-label)' : 'var(--io-label-secondary)', fontWeight: i === todayIndex ? 600 : 400 }}>{d.day}</span>
          ))}
        </div>
      )}
    </div>
  );
};

// ─────────────────────────────────────────────────────────────
// Sleep stage stack (REM / Deep / Core / Awake)
// ─────────────────────────────────────────────────────────────
const StageStack = ({ stages }) => {
  // stages: [{ kind: 'deep', pct: 21, label: '1h 18m' }, ...]
  const colors = {
    deep: 'var(--io-stage-deep)',
    rem: 'var(--io-stage-rem)',
    core: 'var(--io-stage-core)',
    awake: 'var(--io-stage-awake)',
  };
  return (
    <div>
      <div className="stage-row">
        {stages.map((s, i) => (<span key={i} style={{ width: `${s.pct}%`, background: colors[s.kind] }}/>))}
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 8, marginTop: 12 }}>
        {stages.map((s, i) => (
          <div key={i}>
            <div className="cp" style={{ color: 'var(--io-label-secondary)', textTransform: 'uppercase', letterSpacing: 0.6, fontWeight: 600 }}>
              <span style={{ display: 'inline-block', width: 8, height: 8, borderRadius: 50, background: colors[s.kind], marginRight: 6, verticalAlign: 1 }}/>
              {s.kind === 'rem' ? 'REM' : s.kind[0].toUpperCase() + s.kind.slice(1)}
            </div>
            <div className="ca" style={{ fontWeight: 500, marginTop: 4 }} data-num>{s.label}</div>
            <div className="cp" style={{ color: 'var(--io-label-tertiary)', marginTop: 1 }}>{s.pct}%</div>
          </div>
        ))}
      </div>
    </div>
  );
};

// ─────────────────────────────────────────────────────────────
// Consistency dots (consistent wake/sleep rhythm)
// ─────────────────────────────────────────────────────────────
const ConsistencyDots = ({ filled, total = 10 }) => (
  <span className="dots">
    {Array.from({ length: total }).map((_, i) => (
      <i key={i} className={i < filled ? 'on' : ''}/>
    ))}
  </span>
);

// ─────────────────────────────────────────────────────────────
// Horizontal bars (cognitive overview)
// ─────────────────────────────────────────────────────────────
const HBar = ({ rows }) => (
  <div className="hbar">
    {rows.map((r, i) => (
      <div key={i} className="hbar-row">
        <div className="lb">{r.label}</div>
        <div className="track">
          <div className="fill" style={{ width: `${r.pct}%`, background: r.color }}/>
        </div>
        <div className="v" data-num>{r.value}</div>
      </div>
    ))}
  </div>
);

// ─────────────────────────────────────────────────────────────
// Insight card
// ─────────────────────────────────────────────────────────────
const InsightCard = ({ emoji, icon, color, title, body }) => (
  <div className="insight">
    <div className={'ic' + (emoji ? ' em' : '')} style={{ color: color || 'var(--io-blue)' }}>
      {emoji ? emoji : <Icon name={icon} size={18} stroke={2.2}/>}
    </div>
    <div className="stack" style={{ gap: 2 }}>
      <div className="hd" style={{ fontSize: 15, fontWeight: 600 }}>{title}</div>
      <div className="fn" style={{ color: 'var(--io-label-secondary)', lineHeight: 1.4 }}>{body}</div>
    </div>
  </div>
);

// ─────────────────────────────────────────────────────────────
// Segmented control
// ─────────────────────────────────────────────────────────────
const SegControl = ({ items, active, onChange }) => (
  <div className="seg">
    {items.map(it => (
      <div key={it} className={'s' + (it === active ? ' on' : '')} onClick={() => onChange && onChange(it)}>{it}</div>
    ))}
  </div>
);

// ─────────────────────────────────────────────────────────────
// Banner (inline error / offline / warn)
// ─────────────────────────────────────────────────────────────
const Banner = ({ kind = 'warn', icon = 'alert', children, action }) => (
  <div className={`banner ${kind}`}>
    <Icon name={icon} size={16} stroke={2.2}/>
    <div style={{ flex: 1 }}>{children}</div>
    {action && <span style={{ fontWeight: 600 }}>{action}</span>}
  </div>
);

// ─────────────────────────────────────────────────────────────
// Wrapper that hosts IOSDevice + theme attribute on inner root.
// We forgo IOSNavBar — every screen draws its own chrome.
// ─────────────────────────────────────────────────────────────
const FrameScreen = ({ dark = false, children, width = 402, height = 874 }) => (
  <IOSDevice width={width} height={height} dark={dark}>
    <div className="io" data-theme={dark ? 'dark' : 'light'} style={{ minHeight: '100%', position: 'relative' }}>
      {children}
    </div>
  </IOSDevice>
);

Object.assign(window, {
  Icon, TabBar, NavTopRow, Avatar, PillBtn, BigTitle, InlineNav, SectionHead,
  Pill, TrendChip, MetricTile, RingChart, BarChart, StageStack, ConsistencyDots,
  HBar, InsightCard, SegControl, Banner, FrameScreen,
});
