// Hi-fi shared kit — atoms + chrome used across every screen.
// Calm-utility language: Geist throughout, white cards on warm paper,
// muted accent, status pills, mono numerals. Theme tokens come in via `t`.

const { FONT_DISPLAY: KIT_DISPLAY, FONT_BODY: KIT_BODY, FONT_MONO: KIT_MONO } = window.HIFI;

// ---------- Viewport hook ----------
function useViewport() {
  const [w, setW] = React.useState(typeof window !== 'undefined' ? window.innerWidth : 1280);
  React.useEffect(() => {
    const on = () => setW(window.innerWidth);
    window.addEventListener('resize', on);
    return () => window.removeEventListener('resize', on);
  }, []);
  return { w, isMobile: w <= 680, isTablet: w > 680 && w <= 1024 };
}

// ---------- Surfaces ----------
const Card = ({ t, children, style = {}, pad = 18, onClick, className }) => (
  <div onClick={onClick} className={className} style={{
    background: t.card,
    border: `1px solid ${t.border}`,
    borderRadius: 14,
    padding: pad,
    boxShadow: t.shadowCard,
    ...(onClick ? { cursor: 'pointer' } : {}),
    ...style,
  }}>{children}</div>
);

// ---------- Status atoms ----------
const StatusDot = ({ color, size = 8, style = {} }) => (
  <span style={{
    display: 'inline-block', width: size, height: size, borderRadius: '50%',
    background: color, flex: '0 0 auto', ...style,
  }}/>
);

const StatusBar = ({ color, h = 28 }) => (
  <span style={{
    display: 'inline-block', width: 3, height: h, borderRadius: 2,
    background: color, flex: '0 0 auto',
  }}/>
);

const Pill = ({ t, color, bg, children, style = {} }) => (
  <span style={{
    display: 'inline-flex', alignItems: 'center', gap: 5,
    padding: '2px 9px',
    background: bg || (t ? t.infoBg : 'transparent'),
    color: color || (t ? t.inkSoft : 'inherit'),
    fontSize: 11, fontWeight: 500, letterSpacing: 0.1,
    borderRadius: 999, whiteSpace: 'nowrap', ...style,
  }}>{children}</span>
);

// Map a routine/asset status to its (text color, bg) pair from the theme.
const statusTone = (t, s) =>
  s === 'overdue' ? { c: t.overdue, bg: t.overdueBg } :
  s === 'soon'    ? { c: t.soon,    bg: t.soonBg } :
                    { c: t.ok,      bg: t.okBg };

const isGap = v => typeof v === 'string' && v.startsWith('?');

// Roll up an asset's routine statuses.
const assetStatusInfo = id => {
  const rs = routinesByAsset(id);
  return {
    overdue: rs.filter(r => r.status === 'overdue').length,
    soon:    rs.filter(r => r.status === 'soon').length,
    ok:      rs.filter(r => r.status === 'ok').length,
    total:   rs.length,
  };
};

// Pick the accent color for an asset from its rollup.
const assetAccent = (t, s) =>
  s.overdue ? t.overdue : s.soon ? t.soon : s.total === 0 ? t.inkFaint : t.ok;

// Category → icon kind.
const CAT_ICON = {
  'HVAC': 'sun',
  'Water Features': 'drop',
  'Plumbing': 'drop',
  'Electrical': 'bolt',
  'Appliances': 'plug',
  'Exterior': 'tree',
};

// ---------- Buttons ----------
const Btn = ({ t, children, primary, danger, onClick, style = {}, title }) => {
  const [hover, setHover] = React.useState(false);
  const base = primary
    ? { background: t.accent, color: t.accentInk, border: `1px solid ${t.accent}` }
    : danger
    ? { background: t.card, color: t.overdue, border: `1px solid ${t.border}` }
    : { background: t.card, color: t.ink, border: `1px solid ${t.border}` };
  const hov = primary
    ? { background: t.accentDeep, border: `1px solid ${t.accentDeep}` }
    : { background: t.hover };
  return (
    <button title={title} onClick={onClick}
      onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
      style={{
        display: 'inline-flex', alignItems: 'center', gap: 6,
        padding: '8px 14px', borderRadius: 10,
        fontFamily: KIT_BODY, fontSize: 13, fontWeight: 500,
        cursor: 'pointer', lineHeight: 1, whiteSpace: 'nowrap',
        transition: 'background 0.12s ease',
        ...base, ...(hover ? hov : {}), ...style,
      }}>{children}</button>
  );
};

// Square icon button
const IconBtn = ({ t, kind, onClick, badge, title, style = {} }) => {
  const [hover, setHover] = React.useState(false);
  return (
    <button title={title} onClick={onClick}
      onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
      style={{
        position: 'relative',
        width: 36, height: 36, borderRadius: 10,
        background: hover ? t.hover : t.card,
        border: `1px solid ${t.border}`, color: t.ink,
        display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
        cursor: 'pointer', transition: 'background 0.12s ease', ...style,
      }}>
      <HIcon kind={kind} size={16}/>
      {badge && <span style={{
        position: 'absolute', top: 7, right: 8,
        width: 7, height: 7, borderRadius: '50%', background: t.overdue,
        border: `1.5px solid ${t.card}`,
      }}/>}
    </button>
  );
};

// ---------- Filter chip ----------
const FilterChip = ({ t, active, color, children, onClick }) => {
  const [hover, setHover] = React.useState(false);
  const activeC = color || t.ink;
  return (
    <button onClick={onClick}
      onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
      style={{
        padding: '6px 13px', borderRadius: 999,
        fontFamily: KIT_BODY, fontSize: 13, fontWeight: 500, lineHeight: 1,
        cursor: 'pointer', whiteSpace: 'nowrap',
        transition: 'all 0.12s ease',
        background: active ? activeC : (hover ? t.hover : t.card),
        color: active ? (color ? '#fff' : t.accentInk === '#ffffff' ? t.card : '#fff') : t.inkSoft,
        border: `1px solid ${active ? activeC : t.border}`,
      }}>{children}</button>
  );
};

// ---------- Section eyebrow ----------
const Eyebrow = ({ t, children, style = {} }) => (
  <div style={{
    fontSize: 11, color: t.inkFaint, fontWeight: 600,
    letterSpacing: 0.8, textTransform: 'uppercase', ...style,
  }}>{children}</div>
);

const PanelHeader = ({ t, title, action }) => (
  <div style={{
    display: 'flex', alignItems: 'center', justifyContent: 'space-between',
    marginBottom: 14,
  }}>
    <div style={{ fontSize: 14, fontWeight: 600, color: t.ink }}>{title}</div>
    {action}
  </div>
);

// Inline text link
const TLink = ({ t, children, onClick, style = {} }) => {
  const [hover, setHover] = React.useState(false);
  return (
    <span onClick={onClick}
      onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
      style={{
        color: t.accentDeep, cursor: 'pointer',
        textDecoration: hover ? 'underline' : 'none',
        textUnderlineOffset: 2, ...style,
      }}>{children}</span>
  );
};

// ---------- Top bar for inner pages ----------
const TopBar = ({ t, title, sub, breadcrumb, actions }) => (
  <div className="hh-pad" style={{
    padding: '22px 28px 18px',
    borderBottom: `1px solid ${t.border}`,
    background: t.paper,
    flex: '0 0 auto',
    position: 'sticky', top: 0, zIndex: 5,
  }}>
    {breadcrumb && (
      <div style={{ fontSize: 13, color: t.inkSoft, marginBottom: 8 }}>{breadcrumb}</div>
    )}
    <div className="hh-wrap" style={{ display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', gap: 16 }}>
      <div style={{ minWidth: 0 }}>
        <div style={{
          fontFamily: KIT_DISPLAY, fontSize: 30, lineHeight: 1.05,
          letterSpacing: -0.8, fontWeight: 600, color: t.ink,
        }}>{title}</div>
        {sub && <div style={{ marginTop: 6, fontSize: 14, color: t.inkSoft }}>{sub}</div>}
      </div>
      <div className="hh-wrap" style={{ display: 'flex', gap: 8, alignItems: 'center', flex: '0 0 auto' }}>{actions}</div>
    </div>
  </div>
);

// ---------- Form field (read-only display style, used in overlays/modals) ----------
const FormField = ({ t, label, value, multi, placeholder, status }) => (
  <div>
    <div style={{ fontSize: 12, color: t.inkSoft, marginBottom: 5, fontWeight: 500 }}>{label}</div>
    <div style={{
      padding: multi ? '10px 12px' : '9px 12px',
      minHeight: multi ? 60 : 'auto',
      background: t.inputBg,
      border: `1px solid ${t.border}`,
      borderRadius: 9,
      color: status || (value ? t.ink : t.inkFaint),
      fontSize: 14, lineHeight: 1.4,
    }}>{value || placeholder || '—'}</div>
  </div>
);

// ---------- Clickable nav sidebar (persistent across screens) ----------
const NavRow = ({ t, icon, label, active, badge, onClick }) => {
  const [hover, setHover] = React.useState(false);
  return (
    <div onClick={onClick}
      onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
      style={{
        display: 'flex', alignItems: 'center', gap: 12,
        padding: '9px 12px', borderRadius: 10,
        background: active ? t.card : (hover ? t.hover : 'transparent'),
        boxShadow: active ? t.shadowCard : 'none',
        border: `1px solid ${active ? t.border : 'transparent'}`,
        color: active ? t.ink : t.inkSoft,
        fontWeight: active ? 600 : 500, fontSize: 14,
        cursor: 'pointer', userSelect: 'none',
        transition: 'background 0.12s ease',
      }}>
      <HIcon kind={icon} size={18} style={{ color: active ? t.ink : t.inkSoft }}/>
      <span style={{ flex: 1 }}>{label}</span>
      {badge != null && badge > 0 && (
        <span style={{
          display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
          minWidth: 20, height: 20, padding: '0 6px',
          background: active ? t.accent : t.infoBg,
          color: active ? t.accentInk : t.inkSoft,
          borderRadius: 999, fontSize: 11, fontWeight: 600, fontFamily: KIT_MONO,
        }}>{badge}</span>
      )}
    </div>
  );
};

// ---------- Mobile chrome (top bar + bottom tab nav) ----------
const mTopBtn = (t) => ({ width: 36, height: 36, borderRadius: 9, background: t.card, border: `1px solid ${t.border}`, color: t.ink, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer' });

const MobileTopBar = ({ t, nav }) => {
  const me = window.Account.current();
  const notifN = window.notificationCount ? window.notificationCount() : 0;
  return (
    <div style={{
      flex: '0 0 auto', display: 'flex', alignItems: 'center', gap: 10,
      padding: '10px 14px', background: t.paper, borderBottom: `1px solid ${t.border}`,
    }}>
      <HIcon kind="kingsleigh" size={22} style={{ color: t.ink }}/>
      <span style={{ fontFamily: KIT_DISPLAY, fontSize: 17, color: t.ink, fontWeight: 600, letterSpacing: -0.3 }}>Kingsleigh</span>
      <span style={{ flex: 1 }}/>
      <button onClick={() => nav.openSearch && nav.openSearch()} title="Search" style={mTopBtn(t)}><HIcon kind="search" size={17}/></button>
      <button onClick={() => nav.openNotifications && nav.openNotifications()} title="Notifications" style={{ ...mTopBtn(t), position: 'relative' }}>
        <HIcon kind="bell" size={17}/>
        {notifN > 0 && <span style={{ position: 'absolute', top: -3, right: -3, minWidth: 15, height: 15, padding: '0 3px', borderRadius: 999, background: t.overdue, color: '#fff', fontSize: 9, fontWeight: 700, display: 'flex', alignItems: 'center', justifyContent: 'center', border: `2px solid ${t.paper}`, fontFamily: KIT_MONO }}>{notifN}</span>}
      </button>
      <button onClick={() => nav.go('profile')} title="Profile" style={{ border: 'none', background: 'transparent', padding: 0, cursor: 'pointer' }}>
        <div style={{ width: 30, height: 30, borderRadius: '50%', background: `linear-gradient(135deg, ${t.accent}, ${t.accentDeep})`, color: t.accentInk, display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 600, fontSize: 13 }}>{me.initial || 'O'}</div>
      </button>
    </div>
  );
};

const MobileBottomNav = ({ t, screen, nav }) => {
  const overdueN = ROUTINES.filter(r => r.status === 'overdue').length;
  const todoN = TODOS.filter(td => td.status !== 'Done').length;
  const is = (...ss) => ss.includes(screen);
  const tabs = [
    { label: 'Home', icon: 'home', screen: 'dashboard', on: is('dashboard') },
    { label: 'Assets', icon: 'grid', screen: 'assets', on: is('assets', 'asset-detail') },
    { label: 'Routines', icon: 'repeat', screen: 'routines', on: is('routines'), badge: overdueN },
    { label: 'Contacts', icon: 'phone', screen: 'contacts', on: is('contacts', 'contact-detail') },
    { label: 'To-Dos', icon: 'check', screen: 'todos', on: is('todos'), badge: todoN },
  ];
  return (
    <div style={{
      flex: '0 0 auto', display: 'flex', background: t.card, borderTop: `1px solid ${t.border}`,
      paddingBottom: 'env(safe-area-inset-bottom, 0px)',
    }}>
      {tabs.map(tb => (
        <button key={tb.screen} onClick={() => nav.go(tb.screen)} style={{
          flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 3,
          padding: '9px 0 8px', background: 'transparent', border: 'none', cursor: 'pointer',
          color: tb.on ? t.accentDeep : t.inkSoft,
        }}>
          <span style={{ position: 'relative' }}>
            <HIcon kind={tb.icon} size={21}/>
            {tb.badge > 0 && <span style={{ position: 'absolute', top: -4, right: -7, minWidth: 14, height: 14, padding: '0 3px', borderRadius: 999, background: t.overdue, color: '#fff', fontSize: 9, fontWeight: 700, display: 'flex', alignItems: 'center', justifyContent: 'center', fontFamily: KIT_MONO }}>{tb.badge}</span>}
          </span>
          <span style={{ fontSize: 10.5, fontWeight: tb.on ? 600 : 500, whiteSpace: 'nowrap' }}>{tb.label}</span>
        </button>
      ))}
    </div>
  );
};

const NavSidebar = ({ t, screen, nav, onTheme, themeId }) => {
  const overdueN = ROUTINES.filter(r => r.status === 'overdue').length;
  const todoN = TODOS.filter(td => td.status !== 'Done').length;
  const is = (...ss) => ss.includes(screen);
  const me = window.Account.current();
  return (
    <div style={{
      width: 230, flex: '0 0 auto',
      background: t.paper,
      borderRight: `1px solid ${t.border}`,
      display: 'flex', flexDirection: 'column',
      padding: '20px 14px',
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '0 6px 22px' }}>
        <HIcon kind="kingsleigh" size={26} style={{ color: t.ink }}/>
        <span style={{ fontFamily: KIT_DISPLAY, fontSize: 19, color: t.ink, lineHeight: 1, letterSpacing: -0.3, fontWeight: 600 }}>Kingsleigh</span>
        <span style={{ flex: 1 }}/>
        {(() => {
          const notifN = window.notificationCount ? window.notificationCount() : 0;
          return (
            <button onClick={() => nav.openNotifications && nav.openNotifications()} title="Notifications" style={{
              position: 'relative', width: 34, height: 34, borderRadius: 9,
              background: t.card, border: `1px solid ${t.border}`, color: t.ink,
              display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer',
            }}>
              <HIcon kind="bell" size={16}/>
              {notifN > 0 && <span style={{
                position: 'absolute', top: -5, right: -5, minWidth: 17, height: 17, padding: '0 4px',
                borderRadius: 999, background: t.overdue, color: '#fff', fontSize: 10, fontWeight: 700,
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                border: `2px solid ${t.paper}`, fontFamily: KIT_MONO,
              }}>{notifN}</span>}
            </button>
          );
        })()}
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
        <NavRow t={t} icon="home"   label="Dashboard" active={is('dashboard')} onClick={() => nav.go('dashboard')}/>
        <NavRow t={t} icon="grid"   label="Assets"    active={is('assets','asset-detail')} onClick={() => nav.go('assets')}/>
        <NavRow t={t} icon="repeat" label="Routines"  active={is('routines')} badge={overdueN} onClick={() => nav.go('routines')}/>
        <NavRow t={t} icon="phone"  label="Contacts"  active={is('contacts','contact-detail')} onClick={() => nav.go('contacts')}/>
        <NavRow t={t} icon="check"  label="To-Dos"    active={is('todos')} badge={todoN} onClick={() => nav.go('todos')}/>
      </div>

      <div style={{ flex: 1 }}/>

      <div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
        <NavRow t={t} icon="settings" label="Settings" active={is('settings','profile')} onClick={() => nav.go('profile')}/>
      </div>

      {/* User block — single-user, no switcher. */}
      <div onClick={() => nav.go('profile')} style={{
        marginTop: 'auto', paddingTop: 10, borderTop: `1px solid ${t.border}`,
        display: 'flex', alignItems: 'center', gap: 10, padding: '10px 8px', cursor: 'pointer',
        borderRadius: 10,
      }}
        onMouseEnter={e => e.currentTarget.style.background = t.hover}
        onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
        <div style={{
          width: 30, height: 30, borderRadius: '50%',
          background: `linear-gradient(135deg, ${t.accent} 0%, ${t.accentDeep} 100%)`,
          flex: '0 0 auto', display: 'flex', alignItems: 'center', justifyContent: 'center',
          color: t.accentInk, fontWeight: 600, fontSize: 13,
        }}>{me.initial || 'O'}</div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 13, color: t.ink, fontWeight: 600, lineHeight: 1.1, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{(me.name || 'Owner').split(' ')[0]}</div>
          <div style={{ fontSize: 11, color: t.inkSoft, lineHeight: 1.2 }}>View profile</div>
        </div>
        <HIcon kind="chev" size={14} style={{ color: t.inkSoft }}/>
      </div>
    </div>
  );
};

const MenuItem = ({ t, icon, label, onClick }) => (
  <div onClick={onClick} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '8px 10px', borderRadius: 8, cursor: 'pointer', color: t.ink, fontSize: 13 }}
    onMouseEnter={e => e.currentTarget.style.background = t.hover} onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
    <HIcon kind={icon} size={15} style={{ color: t.inkSoft }}/>
    <span>{label}</span>
  </div>
);

// ---------- Editable inputs (used in the Add modal) ----------
const fieldShell = (t, focused) => ({
  width: '100%', boxSizing: 'border-box',
  padding: '9px 12px',
  background: t.inputBg,
  border: `1px solid ${focused ? t.accent : t.border}`,
  borderRadius: 9,
  fontSize: 14, color: t.ink, lineHeight: 1.4,
  fontFamily: KIT_BODY, outline: 'none',
  transition: 'border 0.12s ease, box-shadow 0.12s ease',
  boxShadow: focused ? `0 0 0 3px ${t.okBg}` : 'none',
});

const FieldLabel = ({ t, label, required, marks }) => {
  const n = marks || (required ? 1 : 0);
  return label ? <div style={{ fontSize: 12, color: t.inkSoft, marginBottom: 5, fontWeight: 500 }}>
    {label}{n > 0 && <span style={{ color: t.overdue }}> {'*'.repeat(n)}</span>}
  </div> : null;
};

// Robust paste: splices clipboard text in at the caret and pushes it through
// onChange, so pasting works even if an ancestor swallows the native event.
const pasteInto = (value, onChange) => (e) => {
  const cd = e.clipboardData || window.clipboardData;
  if (!cd) return;
  const text = cd.getData('text');
  if (text == null) return;
  e.preventDefault();
  const el = e.target;
  const v = value || '';
  const start = el.selectionStart != null ? el.selectionStart : v.length;
  const end = el.selectionEnd != null ? el.selectionEnd : start;
  onChange(v.slice(0, start) + text + v.slice(end));
  const pos = start + text.length;
  requestAnimationFrame(() => { try { el.setSelectionRange(pos, pos); } catch (_) {} });
};

const TextInput = ({ t, label, value, onChange, placeholder, required, marks, type = 'text' }) => {
  const [f, setF] = React.useState(false);
  return (
    <label style={{ display: 'block' }}>
      <FieldLabel t={t} label={label} required={required} marks={marks}/>
      <input type={type} value={value || ''} placeholder={placeholder}
        onChange={e => onChange(e.target.value)}
        onPaste={pasteInto(value, onChange)}
        onFocus={() => setF(true)} onBlur={() => setF(false)}
        style={fieldShell(t, f)}/>
    </label>
  );
};

const TextArea = ({ t, label, value, onChange, placeholder, rows = 3 }) => {
  const [f, setF] = React.useState(false);
  return (
    <label style={{ display: 'block' }}>
      <FieldLabel t={t} label={label}/>
      <textarea value={value || ''} placeholder={placeholder} rows={rows}
        onChange={e => onChange(e.target.value)}
        onPaste={pasteInto(value, onChange)}
        onFocus={() => setF(true)} onBlur={() => setF(false)}
        style={{ ...fieldShell(t, f), resize: 'vertical', minHeight: 60 }}/>
    </label>
  );
};

// options: array of strings OR { value, label }. `placeholder` becomes a disabled first row.
// `clearable` shows a small "Clear" link in the label row when value is truthy.
const SelectInput = ({ t, label, value, onChange, options, placeholder, required, marks, clearable }) => {
  const [f, setF] = React.useState(false);
  const opts = options.map(o => typeof o === 'string' ? { value: o, label: o } : o);
  const showClear = clearable && value;
  return (
    <label style={{ display: 'block', position: 'relative' }}>
      <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 8 }}>
        <FieldLabel t={t} label={label} required={required} marks={marks}/>
        {showClear && (
          <span
            onMouseDown={e => { e.preventDefault(); e.stopPropagation(); onChange(''); }}
            title="Clear selection"
            style={{ fontSize: 11, color: t.inkSoft, cursor: 'pointer', marginBottom: 5, fontWeight: 500, userSelect: 'none' }}
            onMouseEnter={e => e.currentTarget.style.color = t.ink}
            onMouseLeave={e => e.currentTarget.style.color = t.inkSoft}>
            × Clear
          </span>
        )}
      </div>
      <div style={{ position: 'relative' }}>
        <select value={value || ''} onChange={e => onChange(e.target.value)}
          onFocus={() => setF(true)} onBlur={() => setF(false)}
          style={{ ...fieldShell(t, f), appearance: 'none', WebkitAppearance: 'none',
            paddingRight: 34, cursor: 'pointer', color: value ? t.ink : t.inkFaint }}>
          {placeholder && <option value="" disabled>{placeholder}</option>}
          {opts.map(o => <option key={o.value} value={o.value}>{o.label}</option>)}
        </select>
        <span style={{ position: 'absolute', right: 12, top: '50%', transform: 'translateY(-50%)', pointerEvents: 'none', color: t.inkSoft, display: 'flex' }}>
          <HIcon kind="chevd" size={14}/>
        </span>
      </div>
    </label>
  );
};

// ---------- Inline-editable note (saves on blur when changed) ----------
const EditableNote = ({ t, value, placeholder, onSave, rows = 3 }) => {
  const [v, setV] = React.useState(value || '');
  const [focus, setFocus] = React.useState(false);
  React.useEffect(() => { setV(value || ''); }, [value]);
  return (
    <div style={{ position: 'relative' }}>
      <textarea value={v} placeholder={placeholder} rows={rows}
        onChange={e => setV(e.target.value)}
        onPaste={pasteInto(v, setV)}
        onFocus={() => setFocus(true)}
        onBlur={() => { setFocus(false); if ((v || '') !== (value || '')) onSave(v); }}
        style={{
          width: '100%', boxSizing: 'border-box', padding: '9px 12px',
          background: t.inputBg, border: `1px solid ${focus ? t.accent : t.border}`,
          borderRadius: 9, fontSize: 14, color: t.ink, lineHeight: 1.45,
          fontFamily: KIT_BODY, outline: 'none', resize: 'vertical', minHeight: 58,
          boxShadow: focus ? `0 0 0 3px ${t.okBg}` : 'none', transition: 'border 0.12s ease, box-shadow 0.12s ease',
        }}/>
      {focus && <span style={{ position: 'absolute', right: 10, bottom: 8, fontSize: 11, color: t.inkFaint, pointerEvents: 'none' }}>saves on exit</span>}
    </div>
  );
};

// ---------- Empty state (first-run / zero-data) ----------
const EmptyState = ({ t, icon = 'grid', title, body, primaryLabel, onPrimary, primaryIcon = 'plus', secondaryLabel, onSecondary, compact }) => (
  <div style={{
    display: 'flex', flexDirection: 'column', alignItems: 'center', textAlign: 'center',
    gap: 6, padding: compact ? '28px 24px' : '52px 24px',
  }}>
    <span style={{
      width: 60, height: 60, borderRadius: 16, marginBottom: 8,
      background: t.inputBg, border: `1px solid ${t.border}`, color: t.inkSoft,
      display: 'flex', alignItems: 'center', justifyContent: 'center',
    }}><HIcon kind={icon} size={28}/></span>
    <div style={{ fontFamily: KIT_DISPLAY, fontSize: 19, fontWeight: 600, letterSpacing: -0.4, color: t.ink }}>{title}</div>
    {body && <div style={{ fontSize: 13.5, color: t.inkSoft, lineHeight: 1.5, maxWidth: 380 }}>{body}</div>}
    {(primaryLabel || secondaryLabel) && (
      <div style={{ display: 'flex', gap: 10, marginTop: 14, flexWrap: 'wrap', justifyContent: 'center' }}>
        {primaryLabel && <Btn t={t} primary onClick={onPrimary}>{primaryIcon && <HIcon kind={primaryIcon} size={14}/>} {primaryLabel}</Btn>}
        {secondaryLabel && <Btn t={t} onClick={onSecondary}>{secondaryLabel}</Btn>}
      </div>
    )}
  </div>
);

Object.assign(window, {
  Card, StatusDot, StatusBar, Pill, statusTone, isGap,
  assetStatusInfo, assetAccent, CAT_ICON,
  Btn, IconBtn, FilterChip, Eyebrow, PanelHeader, TLink, TopBar, FormField,
  TextInput, TextArea, SelectInput, EditableNote, EmptyState,
  NavRow, NavSidebar, useViewport, MobileTopBar, MobileBottomNav,
});
