// Main app — TypeScript

declare const React: any;
declare const ReactDOM: any;
declare const SOUNDS: any[];
declare const PRESETS_DEFAULT: any[];
declare const GROUP_KEYS: any[];
declare const GROUP_LABELS: Record<string, Record<string, string>>;
declare const STR: any;
declare const useTweaks: any;
declare const TweaksPanel: any;
declare const TweakSection: any;
declare const TweakRadio: any;
declare const TweakSelect: any;
declare const MasterBar: any;
declare const NowPlaying: any;
declare const CircularTimer: any;
declare const PresetsRow: any;
declare const SoundCard: any;
declare const CustomSoundCard: any;
declare const CustomSoundsBar: any;
declare const RecordModal: any;
declare const UpdateBanner: any;

interface TweakState {
  theme: 'light' | 'dark' | 'sepia';
  lang: 'ko' | 'en';
  background: 'aurora' | 'plain' | 'stars' | 'mesh';
  cardShape: 'rounded' | 'circle' | 'tile';
  cardDensity: 'compact' | 'comfy';
  activeAnim: 'breathe' | 'wave' | 'glow' | 'static';
  fontPair: 'pretendard' | 'serif' | 'mono';
}

interface SoundDef {
  id: string; group: string; emoji: string; ko: string; en: string;
  file?: string; meta_ko?: string; meta_en?: string;
  isCustom?: boolean; loopMode?: 'continuous' | 'interval'; intervalSec?: number;
  blob?: Blob;
}
interface PresetDef {
  id: string; ko: string; en: string; icon?: string;
  mix: Record<string, number>; custom?: boolean;
}

const TWEAK_DEFAULTS: TweakState = /*EDITMODE-BEGIN*/{
  "theme": "light",
  "lang": "ko",
  "background": "aurora",
  "cardShape": "rounded",
  "cardDensity": "comfy",
  "activeAnim": "breathe",
  "fontPair": "pretendard"
}/*EDITMODE-END*/;

// ── IndexedDB for custom sounds ─────────────────────────────────────────────
const DB_NAME = 'BabySleepDB';
const DB_VERSION = 1;
const STORE_CUSTOM = 'customSounds';

function openDb(): Promise<IDBDatabase> {
  return new Promise((resolve, reject) => {
    const req = indexedDB.open(DB_NAME, DB_VERSION);
    req.onupgradeneeded = (e: any) => {
      const db: IDBDatabase = e.target.result;
      if (!db.objectStoreNames.contains(STORE_CUSTOM)) {
        db.createObjectStore(STORE_CUSTOM, { keyPath: 'id' });
      }
    };
    req.onsuccess = () => resolve(req.result);
    req.onerror = () => reject(req.error);
  });
}
async function dbPut(sound: any): Promise<void> {
  const db = await openDb();
  return new Promise((resolve, reject) => {
    const tx = db.transaction(STORE_CUSTOM, 'readwrite');
    tx.objectStore(STORE_CUSTOM).put(sound);
    tx.oncomplete = () => resolve();
    tx.onerror = () => reject(tx.error);
  });
}
async function dbGetAll(): Promise<any[]> {
  const db = await openDb();
  return new Promise((resolve, reject) => {
    const tx = db.transaction(STORE_CUSTOM, 'readonly');
    const req = tx.objectStore(STORE_CUSTOM).getAll();
    req.onsuccess = () => resolve(req.result);
    req.onerror = () => reject(req.error);
  });
}
async function dbDelete(id: string): Promise<void> {
  const db = await openDb();
  return new Promise((resolve, reject) => {
    const tx = db.transaction(STORE_CUSTOM, 'readwrite');
    tx.objectStore(STORE_CUSTOM).delete(id);
    tx.oncomplete = () => resolve();
    tx.onerror = () => reject(tx.error);
  });
}

// ── User presets (localStorage) ─────────────────────────────────────────────
const USER_PRESETS_KEY = 'baby_sleep_user_presets';
const USER_PRESETS_LIMIT = 50;
function loadUserPresets(): PresetDef[] {
  try {
    const raw = localStorage.getItem(USER_PRESETS_KEY);
    if (!raw) return [];
    const parsed = JSON.parse(raw);
    if (!Array.isArray(parsed)) return [];
    // Ensure mix values are 0..1 (backward-compat: legacy stored 0..100)
    return parsed.map((p: any) => {
      const mix: Record<string, number> = {};
      Object.keys(p.mix || {}).forEach((k: string) => {
        const v = p.mix[k];
        mix[k] = v > 1.0001 ? v / 100 : v;
      });
      return {
        id: p.id || ('user-' + Math.random().toString(36).slice(2, 9)),
        ko: p.ko || p.name || '내 믹스',
        en: p.en || p.name || 'My Mix',
        icon: p.icon || '⭐',
        mix,
        custom: true,
      } as PresetDef;
    });
  } catch (e) { return []; }
}
function saveUserPresets(arr: PresetDef[]): void {
  try { localStorage.setItem(USER_PRESETS_KEY, JSON.stringify(arr)); } catch (e) {}
}

function App() {
  const [tw, setTweak] = useTweaks(TWEAK_DEFAULTS, 'baby_sleep_tweaks') as [TweakState, (k: any, v?: any) => void];
  const [activeSounds, setActiveSounds] = React.useState<string[]>([]);
  const [volumes, setVolumes] = React.useState<Record<string, number>>({});
  const [masterVol, setMasterVol] = React.useState<number>(0.7);
  const [timerMins, setTimerMins] = React.useState<number | null>(null);
  const [timerRunning, setTimerRunning] = React.useState<boolean>(false);
  const [timerRemaining, setTimerRemaining] = React.useState<number | null>(null);
  const [fadingOut, setFadingOut] = React.useState<boolean>(false);
  const [customSounds, setCustomSounds] = React.useState<SoundDef[]>([]);
  const [userPresets, setUserPresets] = React.useState<PresetDef[]>([]);
  const [savePromptOpen, setSavePromptOpen] = React.useState<boolean>(false);
  const [saveName, setSaveName] = React.useState<string>('');
  const [recModalOpen, setRecModalOpen] = React.useState<boolean>(false);
  const [updateWaiting, setUpdateWaiting] = React.useState<ServiceWorker | null>(null);
  const [filter, setFilter] = React.useState<string>('all');
  const [statusMsg, setStatusMsg] = React.useState<string>('');

  const t = STR[tw.lang];
  const engine = (window as any).audioEngine;

  // Combined sound list (built-in + custom) used everywhere
  const allSounds: SoundDef[] = React.useMemo(() => [...SOUNDS, ...customSounds], [customSounds]);

  // ── Mount: load custom sounds from IndexedDB, load user presets, register SW
  React.useEffect(() => {
    setUserPresets(loadUserPresets());
    (async () => {
      try {
        const list = await dbGetAll();
        list.sort((a: any, b: any) => (a.createdAt || 0) - (b.createdAt || 0));
        const customs: SoundDef[] = list.map((s: any) => ({
          id: s.id,
          group: 'custom',
          emoji: s.icon || '🎵',
          ko: s.name,
          en: s.name,
          isCustom: true,
          loopMode: s.loopMode || 'continuous',
          intervalSec: s.intervalSec || 30,
          blob: s.blob,
        }));
        customs.forEach((s: SoundDef) => engine.register(s));
        setCustomSounds(customs);
      } catch (e: any) {
        setStatusMsg('내 음원 로드 실패: ' + (e && e.message ? e.message : ''));
      }
    })();

    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register('sw.js').then((reg: ServiceWorkerRegistration) => {
        if (reg.waiting) setUpdateWaiting(reg.waiting);
        reg.addEventListener('updatefound', () => {
          const nw = reg.installing;
          if (!nw) return;
          nw.addEventListener('statechange', () => {
            if (nw.state === 'installed' && navigator.serviceWorker.controller) {
              setUpdateWaiting(nw);
            }
          });
        });
        setInterval(() => reg.update().catch(() => {}), 60 * 60 * 1000);
      }).catch(() => {});

      let reloading = false;
      navigator.serviceWorker.addEventListener('controllerchange', () => {
        if (reloading) return;
        reloading = true;
        location.reload();
      });
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // ── Document title + html lang sync (i18n + SEO) ─────────────────────────
  React.useEffect(() => {
    document.title = tw.lang === 'ko'
      ? '아기 백색소음 믹서 · 자장가 · 빗소리 무료 PWA'
      : 'Baby Sleep Mixer · Lullaby · Rain · Free PWA';
    document.documentElement.lang = tw.lang;
  }, [tw.lang]);

  // ── Master volume ────────────────────────────────────────────────────────
  React.useEffect(() => { engine.setMaster(masterVol); }, [masterVol]);

  // ── Timer countdown + fade-out at 0 ──────────────────────────────────────
  React.useEffect(() => {
    if (!timerRunning || timerRemaining == null) return;
    if (timerRemaining <= 0) {
      // 60-step fade-out over 15 seconds
      setFadingOut(true);
      const startMaster = masterVol;
      const totalSteps = 60;
      let step = 0;
      const fade = window.setInterval(() => {
        step++;
        const v = startMaster * (1 - step / totalSteps);
        engine.setMaster(Math.max(0, v));
        if (step >= totalSteps) {
          clearInterval(fade);
          activeSounds.forEach((id: string) => engine.stop(id));
          setActiveSounds([]);
          setTimerRunning(false);
          setTimerRemaining(null);
          setFadingOut(false);
          setMasterVol(startMaster);
          engine.setMaster(startMaster);
        }
      }, 250);
      return () => clearInterval(fade);
    }
    const tt = window.setTimeout(() => setTimerRemaining((r: number | null) => (r == null ? null : r - 1)), 1000);
    return () => clearTimeout(tt);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [timerRunning, timerRemaining]);

  // ── Auto-arm timer when sounds become active and a duration is set ───────
  React.useEffect(() => {
    if (activeSounds.length === 0) {
      if (timerRunning) { setTimerRunning(false); setTimerRemaining(null); }
      return;
    }
    if (timerMins && !timerRunning) {
      setTimerRunning(true);
      setTimerRemaining(timerMins * 60);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [activeSounds.length, timerMins]);

  // ── MediaSession integration ─────────────────────────────────────────────
  React.useEffect(() => {
    if (!('mediaSession' in navigator)) return;
    if (activeSounds.length === 0) {
      navigator.mediaSession.playbackState = 'paused';
      return;
    }
    const names = activeSounds
      .map((id: string) => allSounds.find((s: SoundDef) => s.id === id))
      .filter(Boolean)
      .map((s: SoundDef) => tw.lang === 'ko' ? s.ko : s.en);
    navigator.mediaSession.metadata = new (window as any).MediaMetadata({
      title: names.length === 1 ? names[0] : (tw.lang === 'ko' ? `${names.length}개 소리 믹스` : `${names.length} sound mix`),
      artist: tw.lang === 'ko' ? '🌙 아기 백색소음 믹서' : '🌙 Baby Sleep Mixer',
      album: names.join(' + '),
    });
    navigator.mediaSession.playbackState = 'playing';
  }, [activeSounds, allSounds, tw.lang]);

  React.useEffect(() => {
    if (!('mediaSession' in navigator)) return;
    navigator.mediaSession.setActionHandler('pause', () => stopAll());
    navigator.mediaSession.setActionHandler('play', () => {
      // Resume any sounds we know about (user must have started them previously)
      engine.resumeIfNeeded();
    });
    return () => {
      try { navigator.mediaSession.setActionHandler('pause', null as any); } catch (e) {}
      try { navigator.mediaSession.setActionHandler('play', null as any); } catch (e) {}
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // ── Sound toggle / volume ────────────────────────────────────────────────
  const toggleSound = (sound: SoundDef): void => {
    if (activeSounds.includes(sound.id)) {
      engine.stop(sound.id);
      setActiveSounds((a: string[]) => a.filter((x: string) => x !== sound.id));
    } else {
      const v = volumes[sound.id] ?? 0.5;
      engine.play(sound, v);
      setActiveSounds((a: string[]) => [...a, sound.id]);
      setVolumes((vs: Record<string, number>) => ({ ...vs, [sound.id]: v }));
    }
  };
  const setVolume = (id: string, v: number): void => {
    setVolumes((vs: Record<string, number>) => ({ ...vs, [id]: v }));
    engine.setVolume(id, v);
  };
  const stopAll = (): void => {
    engine.stopAll();
    setActiveSounds([]);
  };

  // ── Presets ──────────────────────────────────────────────────────────────
  const applyPreset = (p: PresetDef): void => {
    activeSounds.forEach((id: string) => engine.stop(id));
    const ids = Object.keys(p.mix);
    const newVols: Record<string, number> = {};
    const validIds: string[] = [];
    ids.forEach((id: string) => {
      const s = allSounds.find((x: SoundDef) => x.id === id);
      if (s) {
        const v = p.mix[id];
        newVols[id] = v;
        validIds.push(id);
        engine.play(s, v);
      }
    });
    setActiveSounds(validIds);
    setVolumes((vs: Record<string, number>) => ({ ...vs, ...newVols }));
  };
  const saveMix = (): void => {
    if (activeSounds.length === 0) return;
    setSavePromptOpen(true);
    setSaveName('');
  };
  const confirmSave = (): void => {
    const name = saveName.trim() || (tw.lang === 'ko' ? '내 믹스' : 'My Mix');
    const mix: Record<string, number> = {};
    activeSounds.forEach((id: string) => { mix[id] = volumes[id] ?? 0.5; });
    const preset: PresetDef = {
      id: 'custom-' + Date.now(),
      ko: name, en: name, icon: '⭐', mix, custom: true,
    };
    setUserPresets((cur: PresetDef[]) => {
      if (cur.length >= USER_PRESETS_LIMIT) {
        setStatusMsg(tw.lang === 'ko'
          ? `프리셋이 너무 많습니다 (최대 ${USER_PRESETS_LIMIT}개)`
          : `Too many presets (max ${USER_PRESETS_LIMIT})`);
        return cur;
      }
      const next = [...cur, preset];
      saveUserPresets(next);
      return next;
    });
    setSavePromptOpen(false);
  };
  const deletePreset = (id: string): void => {
    setUserPresets((cur: PresetDef[]) => {
      const next = cur.filter((x: PresetDef) => x.id !== id);
      saveUserPresets(next);
      return next;
    });
  };

  // ── Custom sounds (file / record) ────────────────────────────────────────
  const onAddFile = async (file: File): Promise<void> => {
    if (!file.type.startsWith('audio/')) {
      setStatusMsg(tw.lang === 'ko' ? '오디오 파일만 추가 가능합니다' : 'Audio files only');
      return;
    }
    const defaultName = file.name.replace(/\.[^.]+$/, '');
    const promptText = tw.lang === 'ko' ? '음원 이름:' : 'Sound name:';
    const name = (window.prompt(promptText, defaultName) || '').trim();
    if (!name) return;
    const sound = {
      id: 'custom_' + Date.now() + '_' + Math.random().toString(36).slice(2, 7),
      name, icon: '🎵', blob: file, mimeType: file.type,
      loopMode: 'continuous', intervalSec: 30, createdAt: Date.now(),
    };
    try {
      await dbPut(sound);
      const def: SoundDef = {
        id: sound.id, group: 'custom', emoji: sound.icon, ko: name, en: name,
        isCustom: true, loopMode: 'continuous', intervalSec: 30, blob: file,
      };
      engine.register(def);
      setCustomSounds((cs: SoundDef[]) => [...cs, def]);
      setStatusMsg(tw.lang === 'ko' ? `✓ "${name}" 추가됨` : `✓ "${name}" added`);
    } catch (e: any) {
      setStatusMsg('저장 실패: ' + (e && e.message ? e.message : ''));
    }
  };

  const onSaveRecording = async (blob: Blob, name: string): Promise<void> => {
    const sound = {
      id: 'custom_' + Date.now() + '_' + Math.random().toString(36).slice(2, 7),
      name, icon: '🎤', blob, mimeType: blob.type,
      loopMode: 'continuous', intervalSec: 30, createdAt: Date.now(),
    };
    try {
      await dbPut(sound);
      const def: SoundDef = {
        id: sound.id, group: 'custom', emoji: sound.icon, ko: name, en: name,
        isCustom: true, loopMode: 'continuous', intervalSec: 30, blob,
      };
      engine.register(def);
      setCustomSounds((cs: SoundDef[]) => [...cs, def]);
      setStatusMsg(tw.lang === 'ko' ? `✓ "${name}" 저장됨` : `✓ "${name}" saved`);
    } catch (e: any) {
      setStatusMsg('저장 실패: ' + (e && e.message ? e.message : ''));
    }
  };

  const onDeleteCustomSound = async (id: string): Promise<void> => {
    if (activeSounds.includes(id)) {
      engine.stop(id);
      setActiveSounds((a: string[]) => a.filter((x: string) => x !== id));
    }
    engine.unregister(id);
    setCustomSounds((cs: SoundDef[]) => cs.filter((s: SoundDef) => s.id !== id));
    try { await dbDelete(id); } catch (e) {}
  };

  const onUpdateCustomSettings = async (
    id: string, name: string, loopMode: 'continuous' | 'interval', intervalSec: number,
  ): Promise<void> => {
    const cur = customSounds.find((s: SoundDef) => s.id === id);
    if (!cur) return;
    // Update IndexedDB record (preserve blob)
    try {
      const list = await dbGetAll();
      const found = list.find((x: any) => x.id === id);
      if (found) {
        found.name = name; found.loopMode = loopMode; found.intervalSec = intervalSec;
        await dbPut(found);
      }
    } catch (e) {}
    engine.setLoopMode(id, loopMode, intervalSec);
    setCustomSounds((cs: SoundDef[]) =>
      cs.map((s: SoundDef) => s.id === id ? { ...s, ko: name, en: name, loopMode, intervalSec } : s));
  };

  // ── PWA update flow ──────────────────────────────────────────────────────
  const onApplyUpdate = (): void => {
    if (updateWaiting) updateWaiting.postMessage('SKIP_WAITING');
  };
  const onRefresh = async (): Promise<void> => {
    try {
      if ('serviceWorker' in navigator) {
        const reg = await navigator.serviceWorker.getRegistration();
        if (reg) await reg.update();
      }
    } catch (e) {}
    location.reload();
  };
  const onOpenTweaks = (): void => {
    window.postMessage({ type: '__activate_edit_mode' }, '*');
  };

  // ── Filtering ────────────────────────────────────────────────────────────
  const filteredSounds: SoundDef[] = filter === 'all'
    ? allSounds
    : allSounds.filter((s: SoundDef) => s.group === filter);

  const currentMix: Record<string, number> = {};
  activeSounds.forEach((id: string) => { currentMix[id] = volumes[id] ?? 0.5; });

  return (
    <div className={`app theme-${tw.theme} bg-${tw.background} font-${tw.fontPair}`}>
      <BackgroundLayer mood={tw.background} theme={tw.theme} active={activeSounds.length > 0} />

      <div className="app-frame">
        <MasterBar
          lang={tw.lang} masterVol={masterVol} onMaster={setMasterVol}
          activeCount={activeSounds.length} onStopAll={stopAll}
          onLangToggle={() => setTweak('lang', tw.lang === 'ko' ? 'en' : 'ko')}
          onThemeToggle={() => setTweak('theme', tw.theme === 'dark' ? 'light' : 'dark')}
          onRefresh={onRefresh}
          onOpenTweaks={onOpenTweaks}
          theme={tw.theme}
          t={t}
        />

        <UpdateBanner show={!!updateWaiting} onApply={onApplyUpdate} t={t} />

        <main className="app-main">
          <section className="hero">
            <div className="hero-left">
              <div className="hero-badges">
                {t.badges.map((b: string) => <span key={b} className="hero-badge">{b}</span>)}
              </div>
              <h1 className="hero-title">{t.tagline}</h1>
              <NowPlaying
                activeSounds={activeSounds} sounds={allSounds} volumes={volumes}
                onVolume={setVolume}
                onRemove={(id: string) => {
                  const s = allSounds.find((x: SoundDef) => x.id === id);
                  if (s) toggleSound(s);
                }}
                lang={tw.lang} t={t}
              />
              {fadingOut && <div className="fade-out-banner">{t.fadeOut}</div>}
              {!!statusMsg && <div className="status-msg">{statusMsg}</div>}
            </div>
            <div className="hero-right">
              <CircularTimer
                minutes={timerMins} remaining={timerRemaining} running={timerRunning}
                onChange={setTimerMins}
                onCancel={() => { setTimerRunning(false); setTimerRemaining(null); setTimerMins(null); }}
                lang={tw.lang} theme={tw.theme}
              />
            </div>
          </section>

          <PresetsRow
            presets={PRESETS_DEFAULT} customPresets={userPresets}
            onApply={applyPreset} onSave={saveMix} onDelete={deletePreset}
            currentMix={currentMix} sounds={allSounds} lang={tw.lang} t={t}
          />

          <section className="sounds-section">
            <div className="sounds-header">
              <div>
                <div className="section-title">{t.sounds}</div>
                <div className="section-hint">{t.soundsHint}</div>
              </div>
              <div className="filter-tabs">
                {GROUP_KEYS.map((g: string) => (
                  <button key={g}
                          className={`filter-tab ${filter === g ? 'active' : ''}`}
                          onClick={() => setFilter(g)}>
                    {GROUP_LABELS[tw.lang][g]}
                  </button>
                ))}
              </div>
            </div>

            <CustomSoundsBar
              onAddFile={onAddFile}
              onOpenRecord={() => setRecModalOpen(true)}
              lang={tw.lang} t={t}
            />

            <div className={`sounds-grid density-${tw.cardDensity}`}>
              {filteredSounds.map((s: SoundDef) => {
                if (s.isCustom) {
                  return (
                    <CustomSoundCard key={s.id} sound={s}
                      active={activeSounds.includes(s.id)}
                      volume={volumes[s.id] ?? 0.5}
                      onToggle={() => toggleSound(s)}
                      onVolume={(v: number) => setVolume(s.id, v)}
                      onSaveSettings={(name: string, mode: 'continuous' | 'interval', sec: number) =>
                        onUpdateCustomSettings(s.id, name, mode, sec)}
                      onDelete={() => onDeleteCustomSound(s.id)}
                      lang={tw.lang}
                      shape={tw.cardShape} anim={tw.activeAnim} density={tw.cardDensity}
                      t={t} />
                  );
                }
                return (
                  <SoundCard key={s.id} sound={s}
                    active={activeSounds.includes(s.id)}
                    volume={volumes[s.id] ?? 0.5}
                    onToggle={() => toggleSound(s)}
                    onVolume={(v: number) => setVolume(s.id, v)}
                    lang={tw.lang}
                    shape={tw.cardShape} anim={tw.activeAnim} density={tw.cardDensity} />
                );
              })}
              {filter === 'custom' && customSounds.length === 0 && (
                <div className="custom-none-hint">{t.customNoneHint}</div>
              )}
            </div>
          </section>
        </main>
      </div>

      {savePromptOpen && (
        <div className="modal-backdrop" onClick={() => setSavePromptOpen(false)}>
          <div className="modal" onClick={(e: React.MouseEvent) => e.stopPropagation()}>
            <h3>{t.saveMix}</h3>
            <div className="modal-mix">
              {activeSounds.map((id: string) => {
                const s = allSounds.find((x: SoundDef) => x.id === id);
                return s ? <span key={id} className="modal-mix-chip">{s.emoji} {tw.lang === 'ko' ? s.ko : s.en}</span> : null;
              })}
            </div>
            <input className="modal-input" type="text"
                   placeholder={t.namePreset} value={saveName}
                   onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSaveName(e.target.value)} autoFocus />
            <div className="modal-actions">
              <button className="btn-secondary" onClick={() => setSavePromptOpen(false)}>{t.cancel}</button>
              <button className="btn-primary" onClick={confirmSave}>{t.save}</button>
            </div>
          </div>
        </div>
      )}

      <RecordModal
        open={recModalOpen}
        onClose={() => setRecModalOpen(false)}
        onSave={onSaveRecording}
        lang={tw.lang}
        t={t}
      />

      <TweaksPanel title="Tweaks">
        <TweakSection label={tw.lang === 'ko' ? '테마' : 'Theme'} />
        <TweakRadio label={tw.lang === 'ko' ? '모드' : 'Mode'} value={tw.theme}
                    options={[{value:'light',label:'Light'},{value:'dark',label:'Dark'},{value:'sepia',label:'Sepia'}]}
                    onChange={(v: string) => setTweak('theme', v)} />
        <TweakSelect label={tw.lang === 'ko' ? '배경 무드' : 'Background'} value={tw.background}
                     options={[
                       {value:'aurora', label:'Aurora gradient'},
                       {value:'plain',  label:'Solid'},
                       {value:'stars',  label:'Stars & moon'},
                       {value:'mesh',   label:'Soft mesh'},
                     ]}
                     onChange={(v: string) => setTweak('background', v)} />
        <TweakRadio label={tw.lang === 'ko' ? '언어' : 'Language'} value={tw.lang}
                    options={[{value:'ko',label:'한국어'},{value:'en',label:'English'}]}
                    onChange={(v: string) => setTweak('lang', v)} />

        <TweakSection label={tw.lang === 'ko' ? '카드' : 'Cards'} />
        <TweakRadio label={tw.lang === 'ko' ? '모양' : 'Shape'} value={tw.cardShape}
                    options={[{value:'rounded',label:'Rounded'},{value:'circle',label:'Circle'},{value:'tile',label:'Tile'}]}
                    onChange={(v: string) => setTweak('cardShape', v)} />
        <TweakRadio label={tw.lang === 'ko' ? '밀도' : 'Density'} value={tw.cardDensity}
                    options={[{value:'compact',label:'Compact'},{value:'comfy',label:'Comfy'}]}
                    onChange={(v: string) => setTweak('cardDensity', v)} />
        <TweakSelect label={tw.lang === 'ko' ? '활성 애니메이션' : 'Active animation'} value={tw.activeAnim}
                     options={[
                       {value:'breathe', label:'Breathing pulse'},
                       {value:'wave',    label:'Waveform bars'},
                       {value:'glow',    label:'Soft glow'},
                       {value:'static',  label:'Color fill only'},
                     ]}
                     onChange={(v: string) => setTweak('activeAnim', v)} />

        <TweakSection label={tw.lang === 'ko' ? '타이포' : 'Typography'} />
        <TweakRadio label={tw.lang === 'ko' ? '폰트' : 'Font'} value={tw.fontPair}
                    options={[{value:'pretendard',label:'Pretendard'},{value:'serif',label:'Serif'},{value:'mono',label:'Mono'}]}
                    onChange={(v: string) => setTweak('fontPair', v)} />
      </TweaksPanel>
    </div>
  );
}

interface BgProps { mood: TweakState['background']; theme: TweakState['theme']; active: boolean }
function BackgroundLayer({ mood, active }: BgProps) {
  if (mood === 'stars') {
    return (
      <div className="bg-layer bg-stars">
        {Array.from({ length: 60 }).map((_: unknown, i: number) => {
          const x = (i * 137.508) % 100;
          const y = ((i * 79.31) % 100);
          const s = 0.4 + ((i * 13) % 7) / 8;
          const d = ((i * 17) % 30) / 10;
          return <span key={i} className="star" style={{ left: `${x}%`, top: `${y}%`, width: `${s}px`, height: `${s}px`, animationDelay: `${d}s` }} />;
        })}
        <div className="bg-moon" />
      </div>
    );
  }
  if (mood === 'mesh') return <div className="bg-layer bg-mesh" />;
  if (mood === 'plain') return <div className="bg-layer bg-plain" />;
  return <div className={`bg-layer bg-aurora ${active ? 'is-active' : ''}`} />;
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
