// components.jsx — UI building blocks for the SoftLAN launcher
// Loaded after data.jsx; relies on GAMES/STATE_META/ACTION_FOR_STATE/etc. on window.

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

// ────────────────────────────────────────────────────────────────────
// Iconography (tiny inline SVGs; no emoji)
// ────────────────────────────────────────────────────────────────────
const Icon = {
  search: (p) => <svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" {...p}><circle cx="7" cy="7" r="5"/><path d="m13.5 13.5-3-3"/></svg>,
  play:   (p) => <svg viewBox="0 0 16 16" width="12" height="12" fill="currentColor" {...p}><path d="M4 2.5v11l10-5.5z"/></svg>,
  server: (p) => <svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" {...p}><rect x="2" y="3" width="12" height="4.5" rx="1"/><rect x="2" y="8.5" width="12" height="4.5" rx="1"/><circle cx="4.6" cy="5.25" r=".55" fill="currentColor" stroke="none"/><circle cx="4.6" cy="10.75" r=".55" fill="currentColor" stroke="none"/><path d="M7 5.25h4.5M7 10.75h4.5"/></svg>,
  install:(p) => <svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M8 2v8"/><path d="m4.5 7 3.5 3.5L11.5 7"/><path d="M2.5 12.5h11"/></svg>,
  download:(p)=> <svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M8 2v8"/><path d="m4.5 7 3.5 3.5L11.5 7"/><path d="M2.5 13.5h11"/></svg>,
  folder: (p) => <svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" {...p}><path d="M1.75 3.75v8.5a1 1 0 0 0 1 1h10.5a1 1 0 0 0 1-1v-7a1 1 0 0 0-1-1H7.5L6 2.75H2.75a1 1 0 0 0-1 1z"/></svg>,
  kebab:  (p) => <svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor" {...p}><circle cx="8" cy="3.2" r="1.4"/><circle cx="8" cy="8" r="1.4"/><circle cx="8" cy="12.8" r="1.4"/></svg>,
  sort:   (p) => <svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M3 4h10"/><path d="M4.5 8h7"/><path d="M6 12h4"/></svg>,
  users:  (p) => <svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" {...p}><circle cx="6" cy="6" r="2.4"/><path d="M2 13c.6-2.2 2.2-3.4 4-3.4S9.4 10.8 10 13"/><circle cx="11.2" cy="5.4" r="1.8"/><path d="M10.4 9.8c1.7 0 3 1 3.6 2.6"/></svg>,
  close:  (p) => <svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" {...p}><path d="m4 4 8 8M12 4l-8 8"/></svg>,
  check:  (p) => <svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="m3 8 3.5 3.5L13 5"/></svg>,
  chevron:(p) => <svg viewBox="0 0 16 16" width="11" height="11" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="m4 6 4 4 4-4"/></svg>,
  trash:  (p) => <svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M3 4.5h10"/><path d="M5.5 4.5V3a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1v1.5"/><path d="M4.5 4.5 5 13a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1l.5-8.5"/></svg>,
};

// ────────────────────────────────────────────────────────────────────
// Cover art — stylized box-art placeholder
// ────────────────────────────────────────────────────────────────────
function GameCover({ game, aspect = 'box', size = 'normal' }) {
  const { c1, c2, accent } = game.cover;
  // Pick title sizing — shrink for longer names; line-clamp:2 handles wrap
  const title = game.title;
  const len = title.length;
  const fontPx = aspect === 'banner' ? (len > 22 ? 18 : len > 14 ? 22 : 28)
              : aspect === 'square' ? (len > 22 ? 18 : len > 14 ? 22 : 28)
              : (len > 26 ? 15 : len > 20 ? 17 : len > 14 ? 21 : 26);

  // Stable but varied per-game accent shape (id hash → angle / size)
  const h = [...game.id].reduce((a, c) => a + c.charCodeAt(0), 0);
  const angle = 110 + (h % 60); // 110-170
  const blobX = 60 + (h % 30);
  const blobY = 10 + ((h * 7) % 30);

  return (
    <div className="cover" data-aspect={aspect}>
      {/* base gradient */}
      <div className="cover-base" style={{
        background: `linear-gradient(${angle}deg, ${c1} 0%, ${c2} 100%)`,
      }}/>
      {/* radial accent blob */}
      <div className="cover-blob" style={{
        background: `radial-gradient(ellipse at ${blobX}% ${blobY}%, ${accent}38, transparent 55%)`,
      }}/>
      {/* scanline / grain */}
      <div className="cover-grain"/>
      {/* faint geometric mark */}
      <svg className="cover-mark" viewBox="0 0 100 100" preserveAspectRatio="xMaxYMax slice" aria-hidden="true">
        <path d={`M ${100 - (h%30)} ${100 - (h%20)} L 100 ${60 + (h%25)} L 100 100 Z`}
              fill={accent} fillOpacity="0.12"/>
        <circle cx={(h*3)%100} cy={(h*5)%100} r="0.6" fill={accent} fillOpacity="0.4"/>
      </svg>
      {/* title */}
      <div className="cover-titlewrap">
        <div className="cover-title" style={{ fontSize: fontPx, textShadow: `0 4px 16px ${c2}aa, 0 1px 0 rgba(0,0,0,.3)` }}>
          {title}
        </div>
      </div>
      {/* bottom darkening */}
      <div className="cover-vignette"/>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// State chip (corner of cover)
// ────────────────────────────────────────────────────────────────────
function StateChip({ state }) {
  const meta = STATE_META[state];
  if (!meta || !meta.label) return null;
  return (
    <div className="state-chip" data-state={state}>
      <span className="state-dot" style={{ background: meta.dot }}/>
      {meta.label}
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// Action button — Play / Install / Download / [Downloading progress]
// ────────────────────────────────────────────────────────────────────
function ActionButton({ state, accent, size = 'md', onClick, full = false, game }) {
  if (state === 'downloading' && game) {
    return <DownloadProgress game={game} accent={accent} size={size} full={full}/>;
  }
  const action = ACTION_FOR_STATE[state];
  const cls = `act-btn act-${action.kind} ${size === 'lg' ? 'act-lg' : ''} ${full ? 'act-full' : ''}`;
  const icon = action.kind === 'play' ? <Icon.play/>
            : action.kind === 'install' ? <Icon.install/>
            : <Icon.download/>;
  return (
    <button className={cls} onClick={(e) => { e.stopPropagation(); onClick && onClick(); }}
            style={action.kind === 'install' ? { background: accent } : undefined}>
      {icon}<span>{action.label}</span>
    </button>
  );
}

// ────────────────────────────────────────────────────────────────────
// Live download progress — animates from initial progress upward,
// jitters speed slightly. Two layouts:
//   md  → card tile. Container-query: shows %+speed, falls back to % only.
//   lg  → detail overlay. Two-line stats + percentage on the right.
// ────────────────────────────────────────────────────────────────────
function useLiveDownload(game) {
  const [p, setP] = useState(game.progress ?? 0.1);
  const [speed, setSpeed] = useState(game.speed ?? 30);
  useEffect(() => {
    const id = setInterval(() => {
      setP(prev => {
        // step proportional to current speed so smaller games finish faster
        const stepGb = (speed / 1024) * 0.6; // 600ms tick
        const next = prev + stepGb / game.size;
        return next >= 0.985 ? (game.progress ?? 0.1) : next;
      });
      setSpeed(prev => {
        const target = (game.speed ?? 30);
        const drift = (Math.random() - 0.5) * 6;
        return Math.max(2, target * 0.85 + prev * 0.15 + drift);
      });
    }, 600);
    return () => clearInterval(id);
  }, [game.id, game.size, game.speed, game.progress]);
  return { progress: p, speed };
}

function DownloadProgress({ game, accent, size = 'md', full = false }) {
  const { progress, speed } = useLiveDownload(game);
  const pct = Math.min(99, Math.round(progress * 100));
  const downloadedGb = game.size * progress;
  const remainingGb = Math.max(0, game.size - downloadedGb);
  const etaSec = remainingGb * 1024 / Math.max(speed, 0.1);
  const isLg = size === 'lg';

  const onCancel = (e) => { e.stopPropagation(); /* mock */ };

  if (isLg) {
    return (
      <div className={`dl dl-lg ${full ? 'dl-full' : ''}`}
           role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow={pct}
           aria-label={`Downloading ${game.title}`}>
        <div className="dl-fill" style={{ width: `${pct}%` }}/>
        <div className="dl-lg-grid">
          <div className="dl-lg-primary">
            <span className="dl-pulse" aria-hidden="true"/>
            <span className="dl-label">Downloading</span>
          </div>
          <div className="dl-lg-secondary">
            <span className="dl-bytes">
              <strong>{fmtBytes(downloadedGb)}</strong>
              <span className="dl-of"> / {fmtBytes(game.size)}</span>
            </span>
            <span className="dl-sep">·</span>
            <span className="dl-speed">{fmtSpeed(speed)}</span>
            {game.peers > 0 && (
              <React.Fragment>
                <span className="dl-sep dl-sep-peers">·</span>
                <span className="dl-peers" title={`Downloading from ${game.peers} ${game.peers === 1 ? 'peer' : 'peers'} on the LAN`}>
                  <Icon.users/>
                  <span>{game.peers}</span>
                </span>
              </React.Fragment>
            )}
            <span className="dl-sep dl-sep-eta">·</span>
            <span className="dl-eta">{fmtEta(etaSec)} left</span>
          </div>
          <div className="dl-lg-pct">{pct}<span className="dl-pct-sym">%</span></div>
          <button className="dl-cancel" onClick={onCancel} aria-label="Cancel download">
            <Icon.close/>
          </button>
        </div>
      </div>
    );
  }

  return (
    <div className={`dl dl-md ${full ? 'dl-full' : ''}`}
         role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow={pct}
         aria-label={`Downloading ${game.title}`}
         title={`${pct}% · ${fmtSpeed(speed)} · ${fmtEta(etaSec)} left`}>
      <div className="dl-fill" style={{ width: `${pct}%` }}/>
      <div className="dl-md-row">
        <span className="dl-pct">
          <span className="dl-pulse" aria-hidden="true"/>
          {pct}<span className="dl-pct-sym">%</span>
        </span>
        <span className="dl-speed">{fmtSpeedShort(speed)}</span>
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// Game card
// ────────────────────────────────────────────────────────────────────
function GameCard({ game, accent, aspect, onOpen }) {
  return (
    <article className="card" onClick={() => onOpen && onOpen(game)} tabIndex={0}>
      <div className="card-cover-wrap" data-aspect={aspect}>
        <GameCover game={game} aspect={aspect}/>
        <StateChip state={game.state}/>
        <div className="card-mp" title={`${game.players} players`}>
          <Icon.users/><span>{game.players}</span>
        </div>
      </div>
      <div className="card-body">
        <div className="card-title" title={game.title}>{game.title}</div>
        <div className="card-meta">
          <span>{fmtSize(game.size)}</span>
          <span className="card-dot">·</span>
          <span>{game.tags[0]}</span>
        </div>
        <ActionButton state={game.state} accent={accent} full game={game}/>
      </div>
    </article>
  );
}

// ────────────────────────────────────────────────────────────────────
// Filter controls
// ────────────────────────────────────────────────────────────────────
const FILTER_TABS = [
  { key: 'all',       label: 'All Games' },
  { key: 'local',     label: 'Local' },
  { key: 'installed', label: 'Installed' },
];

function SegmentedFilters({ value, onChange, counts, accent }) {
  const ref = useRef(null);
  const [thumb, setThumb] = useState({ left: 0, width: 0 });
  useEffect(() => {
    if (!ref.current) return;
    const el = ref.current.querySelector(`[data-key="${value}"]`);
    if (el) setThumb({ left: el.offsetLeft, width: el.offsetWidth });
  }, [value]);
  return (
    <div className="seg" ref={ref}>
      <div className="seg-thumb" style={{ left: thumb.left, width: thumb.width, background: accent }}/>
      {FILTER_TABS.map(t => (
        <button key={t.key} data-key={t.key} className={`seg-btn ${value === t.key ? 'is-active' : ''}`}
                onClick={() => onChange(t.key)}>
          <span>{t.label}</span>
          <span className="seg-count">{counts[t.key]}</span>
        </button>
      ))}
    </div>
  );
}

function UnderlineFilters({ value, onChange, counts, accent }) {
  return (
    <div className="utabs">
      {FILTER_TABS.map(t => (
        <button key={t.key} className={`utab ${value === t.key ? 'is-active' : ''}`}
                onClick={() => onChange(t.key)}
                style={value === t.key ? { '--accent': accent } : undefined}>
          <span className="utab-label">{t.label}</span>
          <span className="utab-count">{counts[t.key]}</span>
          <span className="utab-underline" style={{ background: accent }}/>
        </button>
      ))}
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// Search input
// ────────────────────────────────────────────────────────────────────
function SearchField({ value, onChange, accent, wide = false }) {
  return (
    <div className={`search ${wide ? 'search-wide' : ''}`} style={{ '--accent': accent }}>
      <Icon.search/>
      <input type="text" placeholder="Search games" value={value}
             onChange={(e) => onChange(e.target.value)}/>
      <span className="search-kbd">/</span>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// Sort menu (simple dropdown)
// ────────────────────────────────────────────────────────────────────
const SORTS = [
  { key: 'az',     label: 'Name (A–Z)' },
  { key: 'size',   label: 'Size (largest)' },
  { key: 'recent', label: 'Recently Played' },
  { key: 'state',  label: 'Status' },
];

function SortMenu({ value, onChange, accent }) {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);
  useEffect(() => {
    if (!open) return;
    const close = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('click', close);
    return () => document.removeEventListener('click', close);
  }, [open]);
  const current = SORTS.find(s => s.key === value) || SORTS[0];
  return (
    <div className="sort" ref={ref}>
      <button className="sort-btn" onClick={() => setOpen(o => !o)}>
        <Icon.sort/>
        <span>Sort: <strong>{current.label}</strong></span>
        <Icon.chevron/>
      </button>
      {open && (
        <div className="sort-menu">
          {SORTS.map(s => (
            <button key={s.key} onClick={() => { onChange(s.key); setOpen(false); }}
                    className={s.key === value ? 'is-active' : ''}
                    style={s.key === value ? { color: accent } : undefined}>
              <span className="sort-check">{s.key === value ? <Icon.check/> : null}</span>
              {s.label}
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// Storage meter
// ────────────────────────────────────────────────────────────────────
function StorageMeter({ accent, compact = false }) {
  const { installed, local, total } = STORAGE;
  const pctI = (installed / total) * 100;
  const pctL = (local / total) * 100;
  return (
    <div className={`storage ${compact ? 'storage-compact' : ''}`}>
      <div className="storage-bar">
        <div className="storage-i" style={{ width: `${pctI}%`, background: accent }}/>
        <div className="storage-l" style={{ width: `${pctL}%`, background: `${accent}55` }}/>
      </div>
      <div className="storage-text">
        <span><span className="storage-sq" style={{ background: accent }}/>{installed.toFixed(0)} GB installed</span>
        <span><span className="storage-sq" style={{ background: `${accent}55` }}/>{local.toFixed(0)} GB local</span>
        <span className="storage-free">{STORAGE.free.toFixed(0)} GB free</span>
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// Directory button (shows path)
// ────────────────────────────────────────────────────────────────────
function DirectoryButton({ path }) {
  const short = path.length > 36 ? '…' + path.slice(-34) : path;
  return (
    <button className="dirbtn" title={path}>
      <Icon.folder/>
      <span className="dirbtn-label">Game directory</span>
      <span className="dirbtn-path">{short}</span>
    </button>
  );
}

// ────────────────────────────────────────────────────────────────────
// Menu (kebab)
// ────────────────────────────────────────────────────────────────────
function KebabMenu({ items }) {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);
  useEffect(() => {
    if (!open) return;
    const close = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('click', close);
    return () => document.removeEventListener('click', close);
  }, [open]);
  return (
    <div className="kebab" ref={ref}>
      <button className="kebab-btn" onClick={() => setOpen(o => !o)} aria-label="More"><Icon.kebab/></button>
      {open && (
        <div className="kebab-menu">
          {items.map((it, i) => it === '-' ? <div key={i} className="kebab-sep"/> : (
            <button key={i} onClick={() => { setOpen(false); it.onClick && it.onClick(); }}>{it.label}</button>
          ))}
        </div>
      )}
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// Detail Modal
// ────────────────────────────────────────────────────────────────────
function GameDetailModal({ game, accent, onClose }) {
  if (!game) return null;
  const action = ACTION_FOR_STATE[game.state];
  const showCancel = game.state === 'downloading';
  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()}>
        <button className="modal-close" onClick={onClose} aria-label="Close"><Icon.close/></button>
        <div className="modal-hero">
          <GameCover game={game} aspect="banner"/>
          <div className="modal-hero-fade"/>
          <div className="modal-hero-text">
            <div className="modal-tags">
              {game.tags.map(t => <span key={t} className="modal-tag">{t}</span>)}
            </div>
            <h2 className="modal-title">{game.title}</h2>
          </div>
          <div className="modal-state">
            <StateChip state={game.state}/>
          </div>
        </div>
        <div className="modal-body">
          <div className="modal-meta">
            <div className="meta-cell">
              <div className="meta-label">Size</div>
              <div className="meta-value">{fmtSize(game.size)}</div>
            </div>
            <div className="meta-cell">
              <div className="meta-label">Players</div>
              <div className="meta-value"><Icon.users/> {game.players}</div>
            </div>
            <div className="meta-cell">
              <div className="meta-label">Version</div>
              <div className="meta-value meta-mono">{game.version}</div>
            </div>
            <div className="meta-cell">
              <div className="meta-label">Status</div>
              <div className="meta-value">{STATE_META[game.state].label || 'Not downloaded'}</div>
            </div>
          </div>
          <p className="modal-desc">{game.desc}</p>
          <div className="modal-actions">
            <ActionButton state={game.state} accent={accent} size="lg" game={game}/>
            {game.canHostServer && game.state === 'installed' && (
              <button className="act-btn act-lg act-server"
                      style={{ '--accent': accent }}
                      onClick={(e) => e.stopPropagation()}>
                <Icon.server/><span>Start Server</span>
              </button>
            )}
            {game.state === 'installed' && (
              <button className="ghost-btn ghost-danger"><Icon.trash/><span>Uninstall</span></button>
            )}
            {game.state === 'local' && (
              <button className="ghost-btn ghost-danger"><Icon.trash/><span>Delete from disk</span></button>
            )}
            {showCancel && (
              <button className="ghost-btn ghost-danger"><Icon.close/><span>Cancel</span></button>
            )}
            {game.state !== 'none' && <div className="modal-actions-spacer"/>}
            <button className="ghost-btn">View files</button>
          </div>
        </div>
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// Settings Dialog — in-app version of the Tweaks panel
// ────────────────────────────────────────────────────────────────────
const SETTING_OPTIONS = {
  accent: [
    { value: '#3b82f6', label: 'Blue' },
    { value: '#22d3ee', label: 'Cyan' },
    { value: '#a855f7', label: 'Violet' },
    { value: '#22c55e', label: 'Green' },
    { value: '#f59e0b', label: 'Amber' },
    { value: '#ef4444', label: 'Red' },
  ],
  bg:      [{ value: 'flat', label: 'Flat' }, { value: 'gradient', label: 'Gradient' }, { value: 'animated', label: 'Animated' }],
  density: [{ value: 'compact', label: 'Compact' }, { value: 'normal', label: 'Normal' }, { value: 'large', label: 'Large' }],
  aspect:  [{ value: 'box', label: 'Box-art' }, { value: 'square', label: 'Square' }, { value: 'banner', label: 'Banner' }],
  language: [{ value: 'en', label: 'English' }, { value: 'de', label: 'Deutsch' }],
};

function SettingsTextInput({ value, placeholder, maxLength = 24, onChange, accent }) {
  return (
    <div className="settings-text" style={{ '--accent': accent }}>
      <input type="text"
             value={value || ''}
             placeholder={placeholder}
             maxLength={maxLength}
             spellCheck={false}
             onChange={(e) => onChange(e.target.value)}/>
    </div>
  );
}

function SettingsRow({ label, hint, children }) {
  return (
    <div className="settings-row">
      <div className="settings-row-info">
        <div className="settings-row-label">{label}</div>
        {hint && <div className="settings-row-hint">{hint}</div>}
      </div>
      <div className="settings-row-control">{children}</div>
    </div>
  );
}

function SegmentedRadio({ value, options, onChange, accent }) {
  return (
    <div className="srad">
      {options.map(o => (
        <button key={o.value}
                className={`srad-btn ${value === o.value ? 'is-active' : ''}`}
                onClick={() => onChange(o.value)}
                style={value === o.value ? { background: accent, borderColor: accent } : undefined}>
          {o.label}
        </button>
      ))}
    </div>
  );
}

function ColorSwatchPicker({ value, options, onChange }) {
  return (
    <div className="swatch-row">
      {options.map(o => (
        <button key={o.value}
                className={`swatch ${value === o.value ? 'is-active' : ''}`}
                onClick={() => onChange(o.value)}
                style={{ color: o.value }}
                title={o.label}
                aria-label={o.label}>
          <span className="swatch-dot" style={{ background: o.value }}/>
          {value === o.value && <span className="swatch-check"><Icon.check/></span>}
        </button>
      ))}
    </div>
  );
}

function SettingsDialog({ settings, onChange, onClose }) {
  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal settings-modal" onClick={(e) => e.stopPropagation()}>
        <div className="settings-head">
          <h2>Settings</h2>
          <button className="modal-close settings-close" onClick={onClose} aria-label="Close"><Icon.close/></button>
        </div>
        <div className="settings-body">
          <div className="settings-section">
            <div className="settings-section-title">Profile</div>
            <SettingsRow label="Username" hint="Shown to other players on the LAN">
              <SettingsTextInput value={settings.username}
                                 placeholder="Enter a username"
                                 onChange={(v) => onChange('username', v)}
                                 accent={settings.accent}/>
            </SettingsRow>
            <SettingsRow label="Language" hint="Interface language">
              <SegmentedRadio value={settings.language || 'en'}
                              options={SETTING_OPTIONS.language}
                              onChange={(v) => onChange('language', v)}
                              accent={settings.accent}/>
            </SettingsRow>
          </div>
          <div className="settings-section">
            <div className="settings-section-title">Appearance</div>
            <SettingsRow label="Accent color" hint="Used for primary actions and highlights">
              <ColorSwatchPicker value={settings.accent}
                                 options={SETTING_OPTIONS.accent}
                                 onChange={(v) => onChange('accent', v)}/>
            </SettingsRow>
            <SettingsRow label="Background" hint="Backdrop behind the library">
              <SegmentedRadio value={settings.bg}
                              options={SETTING_OPTIONS.bg}
                              onChange={(v) => onChange('bg', v)}
                              accent={settings.accent}/>
            </SettingsRow>
          </div>
          <div className="settings-section">
            <div className="settings-section-title">Library</div>
            <SettingsRow label="Grid density" hint="How tightly cards are packed">
              <SegmentedRadio value={settings.density}
                              options={SETTING_OPTIONS.density}
                              onChange={(v) => onChange('density', v)}
                              accent={settings.accent}/>
            </SettingsRow>
            <SettingsRow label="Cover aspect" hint="Shape of the cover art on each card">
              <SegmentedRadio value={settings.aspect}
                              options={SETTING_OPTIONS.aspect}
                              onChange={(v) => onChange('aspect', v)}
                              accent={settings.accent}/>
            </SettingsRow>
          </div>
        </div>
        <div className="settings-foot">
          <button className="ghost-btn settings-done" onClick={onClose}
                  style={{ background: settings.accent, borderColor: settings.accent, color: 'white' }}>
            Done
          </button>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, {
  Icon, GameCover, StateChip, ActionButton, DownloadProgress, GameCard,
  SegmentedFilters, UnderlineFilters, SearchField, SortMenu,
  StorageMeter, DirectoryButton, KebabMenu, GameDetailModal,
  SettingsDialog,
});
