/* QUZIC — main app */

const { useState, useEffect, useRef, createContext, useContext } = React;

// ====== i18n (translations dictionary + lang context) ======
// 영어가 마스터. ko/ja는 비워두면 영어로 폴백.
// 나중에 카피 다듬을 때 ko/ja 값만 채워 넣으면 자동 적용.
const MESSAGES = {
  en: {
    'nav.product': 'Product',
    'nav.pricing': 'Pricing',
    'nav.download': 'Download',

    'hero.eyebrow.pill': '0.1 beta',
    'hero.eyebrow.text': 'Now in beta',
    'hero.h1.a': 'Your cues.',
    'hero.h1.b': 'Everywhere',
    'hero.h1.c': 'you play.',
    'hero.sub': 'Convert cues between Serato and Rekordbox. Sync them from any club USB. Restore a lost stick in thirty minutes.',
    'hero.btn.mac': 'Download for Mac',
    'hero.btn.win': 'Windows coming soon',
    'hero.trial': 'Free plan forever · Pro free for 7 days',

    'sc.convert.label': 'CUE CONVERT',
    'sc.convert.title': 'Cue Convert',
    'sc.convert.sub': 'Two-way cue translation between Serato and Rekordbox. Your colors mapped, your memory cues kept.',
    'sc.convert.chip1': '18-color → RGB',
    'sc.convert.chip2': '5,000+ tracks',
    'sc.convert.chip3': 'Both directions',

    'sc.remix.label': 'REMIX STUDIO',
    'sc.remix.title': 'Remix Studio',
    'sc.remix.sub': 'A DJ mini-DAW built in. Trim intros, chop tracks, set cues — then send the edit straight to your library.',
    'sc.remix.chip1': 'Stem-aware',
    'sc.remix.chip2': 'Cue + loop',
    'sc.remix.chip3': 'Export to library',

    'sc.master.label': 'SOUND ENHANCE',
    'sc.master.title': 'One-Click Mastering',
    'sc.master.sub': 'A quick AI master so you can hear where a track could go — a feel-check before you start real mastering or hand it to a studio.',
    'sc.master.chip1': 'One-click preview',
    'sc.master.chip2': '7-band AI',
    'sc.master.chip3': 'Before you commit',

    'sc.cloud.label': 'CLOUD USB',
    'sc.cloud.title': 'Never Lose a Stick',
    'sc.cloud.sub': 'Mirror your USB to the cloud. Lose a stick mid-tour? Restore the whole thing to a new one in about ten minutes.',
    'sc.cloud.chip1': '40GB in ~10 min',
    'sc.cloud.chip2': 'R2 backed',
    'sc.cloud.chip3': 'Per-playlist restore',

    'ins.h2.a': 'How',
    'ins.h2.b': "you'd",
    'ins.h2.c': 'use it.',
    'ins.lede': 'Four tools. Four scenarios where they fit into a real gig.',

    'ins.card1.label': 'SERATO → REKORDBOX',
    'ins.card1.quote': "You're a Serato DJ, but a big show needs a USB. A few clicks move your whole library into Rekordbox — hot cues and all — so there's no rebuilding cues by hand.",

    'ins.card2.label': 'REMIX STUDIO',
    'ins.card2.quote': "It's not a full DAW, but you can trim an intro or make a quick edit right inside the app, without switching programs while you prep.",

    'ins.card3.label': 'SOUND ENHANCE',
    'ins.card3.quote': 'Before paying for mastering or sending a track to a studio, run the one-click AI master to hear where it could go — a quick gut-check first.',

    'ins.card4.label': 'CLOUD USB',
    'ins.card4.quote': 'Back the USB up to the cloud before every trip. If a stick fails or goes missing, the full set restores to a new one in minutes.',

    'pr.h2': 'Play more. Fix less.',
    'pr.lede': 'Two plans. The first one is free.',
    'pr.free.label': 'FREE',
    'pr.free.amount': '$0',
    'pr.free.period': 'forever',
    'pr.pro.label': 'PRO',
    'pr.pro.amount': '$7.99',
    'pr.pro.period': 'per month',
    'pr.pro.list': '$9.99',
    'pr.pro.discount': '20% off',
    'pr.pro.trial': '7-day free trial',
    'pr.feat.convert': 'Cue Convert',
    'pr.feat.convert.sub': 'Serato ↔ Rekordbox. Unlimited folder-based conversion.',
    'pr.feat.daw': 'Remix Studio',
    'pr.feat.daw.sub': 'DJ mini-DAW. Stem edit, cue set, export to library.',
    'pr.feat.sync': 'USB Sync',
    'pr.feat.sync.sub': 'Club CDJ cues → PC Rekordbox. Auto-recovered.',
    'pr.feat.migrate': 'Library Migrate',
    'pr.feat.migrate.sub': 'Full library move, both directions.',
    'pr.feat.cloud': 'Cloud USB',
    'pr.feat.cloud.sub': 'USB lost → cloud → new USB in 30 min.',
    'pr.feat.enhance': 'Sound Enhancement',
    'pr.feat.enhance.sub': 'Auto-mastering for DJ edits and original tracks.',
    'pr.cta.download': 'Download',
    'pr.cta.getPro': 'Get Pro',
    'pr.cta.getProBeta': 'Beta (Free)',
    'pr.foot': 'Cancel anytime. Free plan never expires.',

    'footer.copy': '© 2026 QUZIC Labs',
    'footer.privacy': 'Privacy',
    'footer.terms': 'Terms',
    'footer.refund': 'Refunds',
    'footer.press': 'Press kit',
    'footer.status': 'Status',
  },
  ko: {
    'nav.product': '제품',
    'nav.pricing': '요금',
    'nav.download': '다운로드',

    'hero.eyebrow.pill': '0.1 beta',
    'hero.eyebrow.text': '베타 출시',
    'hero.h1.a': '너의 큐,',
    'hero.h1.b': '어디서든',
    'hero.h1.c': '그대로.',
    'hero.sub': 'Serato와 Rekordbox 사이에서 큐를 변환한다. 클럽 USB의 큐를 그대로 가져온다. 잃어버린 스틱은 30분 안에 복구한다.',
    'hero.btn.mac': 'Mac 다운로드',
    'hero.btn.win': 'Windows 곧',
    'hero.trial': '프리 플랜 영구 무료 · Pro 7일 무료체험',

    'sc.convert.label': '큐 컨버트',
    'sc.convert.title': 'Cue Convert',
    'sc.convert.sub': 'Serato ↔ Rekordbox 양방향 큐 변환. 색상은 매핑되고, 메모리큐는 살아남는다.',
    'sc.convert.chip1': '18색 → RGB',
    'sc.convert.chip2': '5,000곡 이상',
    'sc.convert.chip3': '양방향',

    'sc.remix.label': 'REMIX STUDIO',
    'sc.remix.title': 'Remix Studio',
    'sc.remix.sub': '앱에 내장된 DJ 미니 DAW. 인트로 자르고, 트랙 다듬고, 큐 찍어서 — 편집본을 바로 라이브러리로.',
    'sc.remix.chip1': '스템 단위 편집',
    'sc.remix.chip2': '큐 + 루프',
    'sc.remix.chip3': '라이브러리로 내보내기',

    'sc.master.label': 'SOUND ENHANCE',
    'sc.master.title': '원클릭 마스터링',
    'sc.master.sub': '간단한 AI 마스터링으로 트랙이 어디까지 갈 수 있는지 미리 들어본다 — 본격 마스터링이나 외주를 맡기기 전 느낌 체크.',
    'sc.master.chip1': '원클릭 미리듣기',
    'sc.master.chip2': '7밴드 AI',
    'sc.master.chip3': '맡기기 전 체험',

    'sc.cloud.label': '클라우드 USB',
    'sc.cloud.title': '스틱을 다시는 잃지 않는다',
    'sc.cloud.sub': 'USB를 클라우드에 미러링. 투어 중에 스틱을 잃었다? 새 USB로 약 10분 안에 통째로 복구.',
    'sc.cloud.chip1': '40GB / 약 10분',
    'sc.cloud.chip2': 'R2 기반',
    'sc.cloud.chip3': '플리 단위 복구',

    'ins.h2.a': '이렇게',
    'ins.h2.b': '쓰게',
    'ins.h2.c': '된다.',
    'ins.lede': '도구 넷. 실제 현장에서 이렇게 쓰이는 시나리오 넷.',

    'ins.card1.label': 'SERATO → REKORDBOX',
    'ins.card1.quote': '세라토를 쓰지만 큰 행사엔 USB로 플레이해야 하는 상황. 클릭 몇 번이면 핫큐까지 포함해 라이브러리 전체가 Rekordbox로 넘어가, 큐를 일일이 다시 찍을 필요가 없다.',

    'ins.card2.label': 'REMIX STUDIO',
    'ins.card2.quote': '전문 DAW는 아니지만, 큐 작업 흐름 그대로 앱 안에서 인트로를 자르거나 간단히 편집한다. 준비 중에 다른 프로그램으로 넘어갈 필요가 없다.',

    'ins.card3.label': 'SOUND ENHANCE',
    'ins.card3.quote': '본격 마스터링을 하거나 트랙을 외주 맡기기 전에, 원클릭 AI 마스터링을 한 번 돌려 어디까지 갈 수 있는지 들어본다. 부담 없는 사전 체크.',

    'ins.card4.label': 'CLOUD USB',
    'ins.card4.quote': '투어 전에 USB를 클라우드에 백업해둔다. 스틱이 고장 나거나 분실돼도 몇 분이면 새 USB에 셋 전체가 복구된다.',

    'pr.h2': '플레이는 더, 수습은 덜.',
    'pr.lede': '두 가지 플랜. 하나는 무료.',
    'pr.free.label': '무료',
    'pr.free.amount': '₩0',
    'pr.free.period': '영구',
    'pr.pro.label': 'PRO',
    'pr.pro.amount': '₩9,900',
    'pr.pro.period': '/ 월',
    'pr.pro.list': '₩12,900',
    'pr.pro.discount': '20% 할인',
    'pr.pro.trial': '7일 무료체험',
    'pr.feat.convert': 'Cue Convert',
    'pr.feat.convert.sub': 'Serato ↔ Rekordbox 양방향. 폴더 무제한 변환.',
    'pr.feat.daw': 'Remix Studio',
    'pr.feat.daw.sub': 'DJ 전용 미니 DAW. 스템 편집·큐 설정·라이브러리 즉시 반영.',
    'pr.feat.sync': 'USB Sync',
    'pr.feat.sync.sub': '부스 CDJ 큐 → PC Rekordbox 자동 복원.',
    'pr.feat.migrate': 'Library Migrate',
    'pr.feat.migrate.sub': '라이브러리 전체 이관, 양방향.',
    'pr.feat.cloud': 'Cloud USB',
    'pr.feat.cloud.sub': 'USB 분실 → 클라우드 → 새 USB 30분 내.',
    'pr.feat.enhance': 'Sound Enhancement',
    'pr.feat.enhance.sub': 'DJ 편집·자작곡 자동 마스터링.',
    'pr.cta.download': '다운로드',
    'pr.cta.getPro': 'Pro 시작',
    'pr.cta.getProBeta': '베타 (무료)',
    'pr.foot': '언제든 해지. 무료 플랜은 만료 없음.',

    'footer.copy': '© 2026 QUZIC Labs',
    'footer.privacy': '개인정보',
    'footer.terms': '약관',
    'footer.refund': '환불 정책',
    'footer.press': '프레스 킷',
    'footer.status': '서비스 상태',
  },
  ja: {
    'nav.product': 'プロダクト',
    'nav.pricing': '料金',
    'nav.download': 'ダウンロード',

    'hero.eyebrow.pill': '0.1 beta',
    'hero.eyebrow.text': 'ベータ公開中',
    'hero.h1.a': '君のキューを、',
    'hero.h1.b': 'どこでも',
    'hero.h1.c': 'そのまま。',
    'hero.sub': 'SeratoとRekordboxの間でキューを変換する。クラブのUSBからキューをそのまま持ち帰る。失くしたスティックは30分で復元する。',
    'hero.btn.mac': 'Mac版をダウンロード',
    'hero.btn.win': 'Windows版は近日公開',
    'hero.trial': '無料プランは永久 · Proは7日間無料体験',

    'sc.convert.label': 'キュー変換',
    'sc.convert.title': 'Cue Convert',
    'sc.convert.sub': 'Serato ↔ Rekordbox 双方向のキュー変換。カラーはマッピングされ、メモリーキューも残る。',
    'sc.convert.chip1': '18色 → RGB',
    'sc.convert.chip2': '5,000曲以上',
    'sc.convert.chip3': '双方向',

    'sc.remix.label': 'REMIX STUDIO',
    'sc.remix.title': 'Remix Studio',
    'sc.remix.sub': 'アプリ内蔵のDJミニDAW。イントロを切り、トラックを刻み、キューを打って — 編集をそのままライブラリへ。',
    'sc.remix.chip1': 'ステム編集',
    'sc.remix.chip2': 'キュー + ループ',
    'sc.remix.chip3': 'ライブラリへ書き出し',

    'sc.master.label': 'SOUND ENHANCE',
    'sc.master.title': 'ワンクリック・マスタリング',
    'sc.master.sub': '手軽なAIマスタリングで、トラックがどこまで化けるかを先に試聴 — 本格的なマスタリングや外注に出す前のフィールチェック。',
    'sc.master.chip1': 'ワンクリック試聴',
    'sc.master.chip2': '7バンドAI',
    'sc.master.chip3': '依頼の前に',

    'sc.cloud.label': 'クラウドUSB',
    'sc.cloud.title': 'もうスティックを失くさない',
    'sc.cloud.sub': 'USBをクラウドにミラー。ツアー中にスティックを失くした？新しいUSBに約10分で丸ごと復元。',
    'sc.cloud.chip1': '40GB / 約10分',
    'sc.cloud.chip2': 'R2ベース',
    'sc.cloud.chip3': 'プレイリスト単位で復元',

    'ins.h2.a': 'こう',
    'ins.h2.b': '使う',
    'ins.h2.c': 'ことになる。',
    'ins.lede': 'ツールは4つ。実際の現場でこう使われる4つのシナリオ。',

    'ins.card1.label': 'SERATO → REKORDBOX',
    'ins.card1.quote': 'Seratoを使っているが、大きなイベントではUSBでプレイする場面。数クリックでホットキューごとライブラリ全体がRekordboxに移り、キューを一つずつ打ち直す必要がない。',

    'ins.card2.label': 'REMIX STUDIO',
    'ins.card2.quote': '本格的なDAWではないが、キュー作業の流れのままアプリ内でイントロを切ったり簡単に編集できる。準備中に別のソフトへ移る必要がない。',

    'ins.card3.label': 'SOUND ENHANCE',
    'ins.card3.quote': '本格的なマスタリングや外注に出す前に、ワンクリックのAIマスタリングを一度かけて、どこまで化けるか聴いてみる。気軽な事前チェック。',

    'ins.card4.label': 'CLOUD USB',
    'ins.card4.quote': 'ツアー前にUSBをクラウドにバックアップ。スティックが壊れても失くしても、数分で新しいUSBにセット全体が復元される。',

    'pr.h2': 'プレイは多く、復旧は少なく。',
    'pr.lede': '2つのプラン。最初のひとつは無料。',
    'pr.free.label': 'FREE',
    'pr.free.amount': '$0',
    'pr.free.period': '永久',
    'pr.pro.label': 'PRO',
    'pr.pro.amount': '$7.99',
    'pr.pro.period': '月額',
    'pr.pro.list': '$9.99',
    'pr.pro.discount': '20% OFF',
    'pr.pro.trial': '7日間無料体験',
    'pr.feat.convert': 'Cue Convert',
    'pr.feat.convert.sub': 'Serato ↔ Rekordbox。フォルダ一括、無制限。',
    'pr.feat.daw': 'Remix Studio',
    'pr.feat.daw.sub': 'DJ専用ミニDAW。ステム編集・キュー設定・ライブラリ即時反映。',
    'pr.feat.sync': 'USB Sync',
    'pr.feat.sync.sub': 'ブースCDJキュー → PC Rekordboxへ自動同期。',
    'pr.feat.migrate': 'Library Migrate',
    'pr.feat.migrate.sub': 'ライブラリ全体を双方向に移行。',
    'pr.feat.cloud': 'Cloud USB',
    'pr.feat.cloud.sub': 'USB紛失 → クラウド → 新USB 30分以内。',
    'pr.feat.enhance': 'Sound Enhancement',
    'pr.feat.enhance.sub': 'DJ編集・自作曲の自動マスタリング。',
    'pr.cta.download': 'ダウンロード',
    'pr.cta.getPro': 'Proを始める',
    'pr.cta.getProBeta': 'ベータ (無料)',
    'pr.foot': 'いつでも解約可。無料プランに期限なし。',

    'footer.copy': '© 2026 QUZIC Labs',
    'footer.privacy': 'プライバシー',
    'footer.terms': '利用規約',
    'footer.refund': '返金ポリシー',
    'footer.press': 'プレスキット',
    'footer.status': 'ステータス',
  },
};

const LangContext = createContext({ lang: 'en', setLang: () => {} });

function detectInitialLang() {
  // UI language locked to EN. i18n backend preserved for future use.
  return 'en';
}

function LangProvider({ children }) {
  const [lang, setLang] = useState(detectInitialLang);
  useEffect(() => {
    try { localStorage.setItem('quzic-lang', lang); } catch (e) {}
    document.documentElement.lang = lang;
  }, [lang]);
  return (
    <LangContext.Provider value={{ lang, setLang }}>
      {children}
    </LangContext.Provider>
  );
}

function useT() {
  const { lang } = useContext(LangContext);
  return (key) => {
    const dict = MESSAGES[lang] || {};
    return (dict[key] !== undefined ? dict[key] : MESSAGES.en[key]) || key;
  };
}

// ====== Download links ======
const MAC_DMG_URL = 'https://github.com/iamChant/quzic-releases/releases/latest/download/Quzic_universal.dmg';  // 유니버설(M+Intel) DMG, 항상 최신 릴리스

// ====== Auth (BTF hub) ======
const HUB_BASE = 'https://auth.beatif.studio';
const QUZIC_DEVICE_KEY = 'quzic-device-id';
const QUZIC_SESSION_KEY = 'quzic-session';

function getOrCreateDeviceId() {
  try {
    let id = localStorage.getItem(QUZIC_DEVICE_KEY);
    if (!id) {
      id = (crypto.randomUUID ? crypto.randomUUID()
            : 'web-' + Date.now() + '-' + Math.random().toString(16).slice(2));
      localStorage.setItem(QUZIC_DEVICE_KEY, id);
    }
    return id;
  } catch (e) { return 'web-nols'; }
}

const AuthCtx = createContext({ session: null, setSession: () => {} });

function AuthProvider({ children }) {
  const [session, setSession] = useState(() => {
    try { return localStorage.getItem(QUZIC_SESSION_KEY); } catch (e) { return null; }
  });
  const saveSession = (tok) => {
    try { localStorage.setItem(QUZIC_SESSION_KEY, tok); } catch (e) {}
    setSession(tok);
  };
  return <AuthCtx.Provider value={{ session, setSession: saveSession }}>{children}</AuthCtx.Provider>;
}

function LoginModal({ onClose }) {
  const deviceId = getOrCreateDeviceId();
  const redirect = encodeURIComponent('https://quzic.beatif.studio/auth/callback');
  const devId = encodeURIComponent(deviceId);

  const providers = [
    {
      key: 'google',
      label: 'Continue with Google',
      icon: (
        <svg viewBox="0 0 24 24" width="20" height="20">
          <path fill="#EA4335" d="M12 10.2v3.9h5.5c-.24 1.4-1.7 4.1-5.5 4.1-3.3 0-6-2.7-6-6.1S8.7 6 12 6c1.9 0 3.1.8 3.8 1.5l2.6-2.5C16.8 3.4 14.6 2.4 12 2.4 6.7 2.4 2.4 6.7 2.4 12s4.3 9.6 9.6 9.6c5.5 0 9.2-3.9 9.2-9.4 0-.6-.07-1.1-.16-1.6H12z"/>
        </svg>
      ),
      iconBg: '#fff',
    },
    {
      key: 'kakao',
      label: 'Continue with Kakao',
      icon: (
        <svg viewBox="0 0 24 24" width="20" height="20">
          <path fill="#191600" d="M12 3.2c-5 0-9 3.2-9 7.1 0 2.5 1.7 4.7 4.2 6-.18.65-.67 2.4-.77 2.8-.12.5.18.5.39.36.16-.1 2.5-1.7 3.5-2.4.55.08 1.1.12 1.7.12 5 0 9-3.2 9-7.1S17 3.2 12 3.2z"/>
        </svg>
      ),
      iconBg: '#FEE500',
    },
    {
      key: 'line',
      label: 'Continue with LINE',
      icon: (
        <svg viewBox="0 0 24 24" width="20" height="20">
          <path fill="#fff" d="M12 3.5c-5 0-9 3.3-9 7.3 0 3.6 3.2 6.6 7.5 7.2.3.06.7.2.8.46.08.24.05.6.03.84l-.13.78c-.04.24-.18.94.82.51 1-.42 5.4-3.2 7.4-5.5 1.3-1.5 2-3 2-4.8 0-4-4-7.3-9-7.3z"/>
        </svg>
      ),
      iconBg: '#06C755',
    },
  ];

  return (
    <div className="qz-modal-overlay" onClick={onClose}>
      <div className="qz-modal-card" onClick={e => e.stopPropagation()}>
        <div className="qz-modal-header">
          <span className="qz-modal-title">Sign in to QUZIC</span>
          <button className="qz-modal-close" onClick={onClose} aria-label="Close">✕</button>
        </div>
        <p className="qz-modal-sub">One account. All BEATIF products.</p>
        <div className="qz-modal-providers">
          {providers.map(p => (
            <a
              key={p.key}
              className="qz-prov"
              href={HUB_BASE + '/auth/' + p.key + '?device_id=' + devId + '&redirect=' + redirect + '&lang=en'}
            >
              <span className="qz-prov-ico" style={{background: p.iconBg}}>{p.icon}</span>
              <span className="qz-prov-label">{p.label}</span>
              <span className="qz-prov-arr">→</span>
            </a>
          ))}
        </div>
        <p className="qz-modal-note">We only read your name and email to create your account.</p>
      </div>
    </div>
  );
}

function CallbackHandler() {
  const { setSession } = useContext(AuthCtx);
  const [status, setStatus] = useState('loading');
  const [error, setError] = useState('');

  useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    const code = params.get('code');
    const deviceId = (() => { try { return localStorage.getItem(QUZIC_DEVICE_KEY) || ''; } catch (e) { return ''; } })();

    if (!code) { setStatus('error'); setError('Missing authorization code.'); return; }
    if (!deviceId) { setStatus('error'); setError('Missing device id. Please start sign-in again.'); return; }

    fetch(HUB_BASE + '/auth/exchange', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ code: code, device_id: deviceId }),
    })
      .then(function (r) {
        if (!r.ok) throw new Error('exchange ' + r.status);
        return r.json();
      })
      .then(function (data) {
        if (!data || !data.token) throw new Error('no token in response');
        setSession(data.token);
        setStatus('ok');
        setTimeout(function () { window.location.replace('/'); }, 900);
      })
      .catch(function (e) { setStatus('error'); setError(String(e && e.message ? e.message : e)); });
  }, []);

  return (
    <div className="qz-cb">
      <div className="qz-cb-card">
        {status === 'loading' && (
          <>
            <div className="qz-cb-spin"/>
            <p className="qz-cb-msg">Signing you in…</p>
          </>
        )}
        {status === 'ok' && (
          <>
            <div className="qz-cb-check">✓</div>
            <p className="qz-cb-msg">Signed in. Redirecting…</p>
          </>
        )}
        {status === 'error' && (
          <>
            <p className="qz-cb-err">Sign-in failed</p>
            <p className="qz-cb-detail">{error}</p>
            <a className="btn btn-primary" href="/">← Back to QUZIC</a>
          </>
        )}
      </div>
    </div>
  );
}

function LangToggle() {
  const { lang, setLang } = useContext(LangContext);
  const [open, setOpen] = useState(false);
  const ref = useRef(null);
  // 토글 버튼은 코드, 드롭다운은 코드 + native name
  const codes = { en: 'EN', ko: 'KO', ja: 'JA' };
  const natives = { en: 'English', ko: '한국어', ja: '日本語' };

  useEffect(() => {
    const onDoc = (e) => {
      if (ref.current && !ref.current.contains(e.target)) setOpen(false);
    };
    document.addEventListener('mousedown', onDoc);
    return () => document.removeEventListener('mousedown', onDoc);
  }, []);

  return (
    <div className="lang-toggle" ref={ref}>
      <button
        className="lang-btn"
        onClick={() => setOpen(o => !o)}
        aria-haspopup="listbox"
        aria-expanded={open}
      >
        {codes[lang]}
        <span className="lang-arrow" aria-hidden="true">▾</span>
      </button>
      {open && (
        <div className="lang-menu" role="listbox">
          {['en', 'ko', 'ja'].map(l => (
            <button
              key={l}
              className={'lang-opt' + (l === lang ? ' active' : '')}
              onClick={() => { setLang(l); setOpen(false); }}
              role="option"
              aria-selected={l === lang}
            >
              <span className="lang-opt-code">{codes[l]}</span>
              <span className="lang-opt-name">{natives[l]}</span>
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

// ====== Icons ======
const Icon = {
  Search: (p) => (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" {...p}><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>),
  Play: (p) => (<svg viewBox="0 0 24 24" fill="currentColor" {...p}><path d="M8 5v14l11-7z"/></svg>),
  Pause: (p) => (<svg viewBox="0 0 24 24" fill="currentColor" {...p}><rect x="7" y="5" width="3.5" height="14" rx="1"/><rect x="13.5" y="5" width="3.5" height="14" rx="1"/></svg>),
  Prev: (p) => (<svg viewBox="0 0 24 24" fill="currentColor" {...p}><path d="M6 6h2v12H6zM20 6v12L9 12z"/></svg>),
  Next: (p) => (<svg viewBox="0 0 24 24" fill="currentColor" {...p}><path d="M16 6h2v12h-2zM4 6v12l11-6z"/></svg>),
  Shuffle: (p) => (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" {...p}><path d="m17 3 4 4-4 4M3 7h6l3 5M3 17h6l8-10h4M14 14l3 3M17 21l4-4-4-4"/></svg>),
  Disc: (p) => (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" {...p}><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="2.5"/></svg>),
  Star: (p) => (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" {...p}><path d="m12 3 2.7 5.7 6.3.6-4.7 4.4 1.3 6.3L12 17l-5.6 3 1.3-6.3L3 9.3l6.3-.6L12 3Z"/></svg>),
  Folder: (p) => (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" {...p}><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7Z"/></svg>),
  Mic: (p) => (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" {...p}><rect x="9" y="3" width="6" height="11" rx="3"/><path d="M5 11a7 7 0 0 0 14 0M12 18v3"/></svg>),
  Bolt: (p) => (<svg viewBox="0 0 24 24" fill="currentColor" {...p}><path d="M13 2 4 14h6l-1 8 9-12h-6l1-8Z"/></svg>),
  Wave: (p) => (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" {...p}><path d="M3 12h2l2-6 3 12 3-9 3 6 2-3h3"/></svg>),
  ArrowRight: (p) => (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" {...p}><path d="M5 12h14M13 6l6 6-6 6"/></svg>),
  Apple: (p) => (<svg viewBox="0 0 24 24" fill="currentColor" {...p}><path d="M17.7 12.6c0-2.4 2-3.6 2.1-3.6-1.1-1.7-2.9-1.9-3.5-2-1.5-.1-2.9.9-3.7.9-.8 0-1.9-.9-3.2-.9-1.6.1-3.1 1-3.9 2.4-1.7 2.9-.4 7.2 1.2 9.6.8 1.2 1.7 2.4 3 2.4 1.2-.1 1.6-.8 3.1-.8 1.4 0 1.8.8 3.1.7 1.3 0 2.1-1.2 2.9-2.3.9-1.3 1.3-2.6 1.3-2.7-.1 0-2.4-.9-2.4-3.7ZM15.3 6.1c.7-.8 1.1-2 1-3.1-1 0-2.2.7-2.9 1.5-.6.7-1.2 1.9-1 3 1.1.1 2.2-.5 2.9-1.4Z"/></svg>),
  Win: (p) => (<svg viewBox="0 0 24 24" fill="currentColor" {...p}><path d="M3 5.5 11 4v8H3V5.5ZM3 13h8v7l-8-1.5V13ZM12 4l9-1.5V12h-9V4ZM12 13h9v8.5L12 20v-7Z"/></svg>),
};

function NavAuthButton() {
  const { session } = useContext(AuthCtx);
  const [showModal, setShowModal] = useState(false);
  if (session) {
    return (
      <button className="btn btn-secondary" style={{fontSize:'13px', padding:'7px 14px'}} onClick={() => {
        try { localStorage.removeItem('quzic-session'); localStorage.removeItem('quzic-device-id'); } catch(e) {}
        window.location.reload();
      }}>Sign out</button>
    );
  }
  return (
    <>
      <button className="btn btn-secondary" style={{fontSize:'13px', padding:'7px 14px'}} onClick={() => setShowModal(true)}>Sign in</button>
      {showModal && <LoginModal onClose={() => setShowModal(false)}/>}
    </>
  );
}

function Nav() {
  const [scrolled, setScrolled] = useState(false);
  const t = useT();
  useEffect(() => {
    const h = () => setScrolled(window.scrollY > 8);
    h();
    window.addEventListener('scroll', h);
    return () => window.removeEventListener('scroll', h);
  }, []);
  return (
    <header className={'nav' + (scrolled ? ' scrolled' : '')}>
      <div className="nav-inner">
        <div className="nav-left">
          <a className="brand" href="/">

            <img className="brand-mark" src="/assets/quzic-logo.png" alt="QUZIC"/>
            <span>QUZIC</span>
          </a>
          <nav className="nav-links">
            <a className="nav-link" href="#showcase">{t('nav.product')}</a>
            <a className="nav-link" href="#pricing">{t('nav.pricing')}</a>
          </nav>
        </div>
        <div className="nav-right">
          <NavAuthButton/>
          <a className="btn btn-primary" href="#hero">
            <Icon.Apple width="14" height="14"/> {t('nav.download')}
          </a>
        </div>
      </div>
    </header>
  );
}

// ====== Hero ======
function Hero() {
  const sectionRef = useRef(null);
  const fxRef = useRef(null);
  const t = useT();

  useEffect(() => {
    const section = sectionRef.current;
    const fx = fxRef.current;
    if (!section || !fx) return;

    let raf = 0;
    let tx = 0, ty = 0, cx = 0, cy = 0;

    const onMove = (e) => {
      const r = section.getBoundingClientRect();
      const sx = e.clientX - r.left;
      const sy = e.clientY - r.top;
      tx = ((sx / r.width)  - 0.5) * 2;
      ty = ((sy / r.height) - 0.5) * 2;
      section.style.setProperty('--sx', sx + 'px');
      section.style.setProperty('--sy', sy + 'px');
    };

    const tick = () => {
      cx += (tx - cx) * 0.07;
      cy += (ty - cy) * 0.07;
      fx.style.setProperty('--mx', cx.toFixed(3));
      fx.style.setProperty('--my', cy.toFixed(3));
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);

    window.addEventListener('mousemove', onMove);
    return () => { cancelAnimationFrame(raf); window.removeEventListener('mousemove', onMove); };
  }, []);

  return (
    <section id="hero" className="hero" ref={sectionRef} style={{'--sx':'50%','--sy':'42%'}}>
      <div className="hero-fx" ref={fxRef}>
        <div className="hero-paint"/>
        <div className="hero-logo-bg">
          <img src="/assets/quzic-logo.png" alt=""/>
        </div>
        <div className="hero-streak"/>
      </div>
      {/* 로고 또렷 레이어 — 마우스 위치만 보임 */}
      <div className="hero-logo-fg">
        <img src="/assets/quzic-logo.png" alt=""/>
      </div>
      {/* 스포트라이트 — 시안 LED 점광원 */}
      <div className="hero-spot"/>
      <div className="shell">
        <a className="eyebrow" href="#">
          <span className="pill">{t('hero.eyebrow.pill')}</span>
          <span>{t('hero.eyebrow.text')}</span>
          <Icon.ArrowRight className="arrow" width="14" height="14"/>
        </a>
        <h1>
          <span className="grad">{t('hero.h1.a')}</span><br/>
          <span className="accent">{t('hero.h1.b')}</span> <span className="grad">{t('hero.h1.c')}</span>
        </h1>
        <p className="sub">{t('hero.sub')}</p>
        <div className="hero-ctas">
          <a className="btn btn-primary btn-lg" href={MAC_DMG_URL}>
            <Icon.Apple width="16" height="16"/> {t('hero.btn.mac')}
          </a>
          <button className="btn btn-secondary btn-lg" disabled aria-disabled="true">
            <Icon.Win width="14" height="14"/> {t('hero.btn.win')}
          </button>
        </div>
        <p className="hero-trial">{t('hero.trial')}</p>
      </div>
    </section>
  );
}

// ====== Showcase (sticky scroll app demo) ======
// 화면 데이터 — 나중에 6개로 확장하려면 여기에 추가만 하면 됨
const SHOWCASE_SCREENS = [
  {
    id: 'convert',
    labelKey: 'sc.convert.label',
    titleKey: 'sc.convert.title',
    subKey: 'sc.convert.sub',
    chipKeys: ['sc.convert.chip1', 'sc.convert.chip2', 'sc.convert.chip3'],
    dot: 'oklch(0.84 0.12 210)',
    glow: 'oklch(0.6 0.18 210 / 0.45)',
    img: '/assets/showcase/convert.png',
  },
  {
    id: 'remix',
    labelKey: 'sc.remix.label',
    titleKey: 'sc.remix.title',
    subKey: 'sc.remix.sub',
    chipKeys: ['sc.remix.chip1', 'sc.remix.chip2', 'sc.remix.chip3'],
    dot: 'oklch(0.72 0.16 330)',
    glow: 'oklch(0.55 0.18 330 / 0.45)',
    img: '/assets/showcase/remix.png',
  },
  {
    id: 'mastering',
    labelKey: 'sc.master.label',
    titleKey: 'sc.master.title',
    subKey: 'sc.master.sub',
    chipKeys: ['sc.master.chip1', 'sc.master.chip2', 'sc.master.chip3'],
    dot: 'oklch(0.78 0.16 60)',
    glow: 'oklch(0.62 0.18 60 / 0.4)',
    img: '/assets/showcase/mastering.png',
  },
  {
    id: 'cloud',
    labelKey: 'sc.cloud.label',
    titleKey: 'sc.cloud.title',
    subKey: 'sc.cloud.sub',
    chipKeys: ['sc.cloud.chip1', 'sc.cloud.chip2', 'sc.cloud.chip3'],
    dot: 'oklch(0.7 0.16 270)',
    glow: 'oklch(0.5 0.2 270 / 0.45)',
    img: '/assets/showcase/cloud.png',
  },
];

function Showcase() {
  const sectionRef = useRef(null);
  const [progress, setProgress] = useState(0); // 0 ~ SHOWCASE_SCREENS.length - 1
  const t = useT();

  useEffect(() => {
    const section = sectionRef.current;
    if (!section) return;
    let raf = 0;
    const tick = () => {
      const rect = section.getBoundingClientRect();
      const total = section.offsetHeight - window.innerHeight;
      const raw = Math.max(0, Math.min(1, -rect.top / total));
      setProgress(raw * (SHOWCASE_SCREENS.length - 1));
    };
    const onScroll = () => {
      cancelAnimationFrame(raf);
      raf = requestAnimationFrame(tick);
    };
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onScroll);
    tick();
    return () => {
      window.removeEventListener('scroll', onScroll);
      window.removeEventListener('resize', onScroll);
      cancelAnimationFrame(raf);
    };
  }, []);

  const activeIdx = Math.round(progress);
  const active = SHOWCASE_SCREENS[activeIdx];
  // 자석 스냅 — 슬라이드는 활성 화면(activeIdx)에 딱 맞춰 멈춤 (연속 슬라이드 금지)
  const slideX = -activeIdx * (100 / SHOWCASE_SCREENS.length);
  const totalVh = SHOWCASE_SCREENS.length * 110; // 110vh per screen — 살짝 여유

  return (
    <section
      id="showcase"
      className="showcase"
      ref={sectionRef}
      style={{ height: `${totalVh}vh` }}
    >
      <div className="sc-sticky">
        <div
          className="sc-glow"
          style={{ background: `radial-gradient(60% 50% at 30% 50%, ${active.glow}, transparent 70%)` }}
        />

        {/* 좌측: 데스크탑 앱 윈도우 (대각선 틸트) */}
        <div className="sc-stage">
          <div
            className="sc-window"
            style={{ '--accent-glow': active.glow }}
          >
            <div className="sc-titlebar">
              <div className="sc-dots"><span/><span/><span/></div>
              <div className="sc-tlcenter">quzic — {active.id}</div>
              <div className="sc-tlright"/>
            </div>
            <div
              className="sc-slider"
              style={{
                transform: `translateX(${slideX}%)`,
                width: `${SHOWCASE_SCREENS.length * 100}%`,
              }}
            >
              {SHOWCASE_SCREENS.map((s) => (
                <div className="sc-pane" key={s.id}>
                  <img className="sc-shot" src={s.img} alt={t(s.titleKey)} loading="lazy" />
                </div>
              ))}
            </div>
          </div>
        </div>

        {/* 우측: 설명 패널 + 진행률 */}
        <div className="sc-info">
          <div className="sc-cards">
            {SHOWCASE_SCREENS.map((s, i) => (
              <div
                key={s.id}
                className={'sc-card' + (i === activeIdx ? ' active' : '')}
              >
                <div className="sc-eyebrow">
                  <span className="sc-eyedot" style={{ background: s.dot, boxShadow: `0 0 10px ${s.dot}` }}/>
                  <span>{t(s.labelKey)}</span>
                </div>
                <h3 className="sc-title">{t(s.titleKey)}</h3>
                <p className="sc-sub">{t(s.subKey)}</p>
                <div className="sc-chips">
                  {s.chipKeys.map((ck) => (
                    <span className="sc-chip" key={ck}>{t(ck)}</span>
                  ))}
                </div>
              </div>
            ))}
          </div>
          <div className="sc-progress">
            {SHOWCASE_SCREENS.map((s, i) => (
              <div
                key={s.id}
                className={'sc-pill' + (i <= activeIdx ? ' filled' : '')}
                style={i <= activeIdx ? { background: s.dot } : null}
              />
            ))}
          </div>
        </div>
      </div>
    </section>
  );
}

// ====== Insurance Section ======
// 체험 시나리오 카드 — 도구별 예상 사용 시나리오 (실제 후기 아님: 출시 전 거짓 후기 리스크 회피)
const INSURANCE_CARDS = [
  { keyBase: 'ins.card1' },
  { keyBase: 'ins.card2' },
  { keyBase: 'ins.card3' },
  { keyBase: 'ins.card4' },
];

function InsuranceSection() {
  const t = useT();
  return (
    <section className="insurance" id="insurance">
      <div className="shell">
        <h2 className="ins-h2">
          {t('ins.h2.a')} <span className="accent">{t('ins.h2.b')}</span> {t('ins.h2.c')}
        </h2>
        <p className="ins-lede">{t('ins.lede')}</p>
        <div className="ins-grid">
          {INSURANCE_CARDS.map((c) => (
            <article className="ins-card" key={c.keyBase}>
              <p className="ins-quote">{t(c.keyBase + '.quote')}</p>
              <span className="ins-label">{t(c.keyBase + '.label')}</span>
            </article>
          ))}
        </div>
      </div>
    </section>
  );
}

// ====== Pricing Pro CTA ======
function ProCtaButton() {
  const { session } = useContext(AuthCtx);
  const t = useT();
  const [showModal, setShowModal] = useState(false);

  if (session) {
    // 로그인된 상태 — Beta 대기 표시 (결제업체 미정, Pro 비활성)
    return <button className="btn btn-primary btn-lg pr-cta" disabled aria-disabled="true">{t('pr.cta.getProBeta')}</button>;
  }

  return (
    <>
      <button className="btn btn-primary btn-lg pr-cta" onClick={() => setShowModal(true)}>
        {t('pr.cta.getProBeta')}
      </button>
      {showModal && <LoginModal onClose={() => setShowModal(false)}/>}
    </>
  );
}

// ====== Pricing — boxless split layout (anti-AI) ======
function Pricing() {
  const t = useT();
  return (
    <section className="pricing" id="pricing">
      <div className="shell">
        <div className="pr-head">
          <h2 className="pr-h2">{t('pr.h2')}</h2>
          <p className="pr-lede">{t('pr.lede')}</p>
        </div>

        <div className="pr-split">
          {/* Free */}
          <div className="pr-side">
            <div className="pr-label">{t('pr.free.label')}</div>
            <div className="pr-pricing">
              <div className="pr-price">
                <span className="pr-amount">{t('pr.free.amount')}</span>
                <span className="pr-period">{t('pr.free.period')}</span>
              </div>
            </div>
            <ul className="pr-features">
              <li><span className="pr-check">─</span><span className="pr-feat-body"><span>{t('pr.feat.convert')}</span><span className="pr-feat-sub">{t('pr.feat.convert.sub')}</span></span></li>
              <li><span className="pr-check">─</span><span className="pr-feat-body"><span>{t('pr.feat.daw')}</span><span className="pr-feat-sub">{t('pr.feat.daw.sub')}</span></span></li>
            </ul>
            <a className="btn btn-secondary btn-lg pr-cta" href={MAC_DMG_URL}>
              <Icon.Apple width="14" height="14"/> {t('pr.cta.download')}
            </a>
          </div>

          {/* Pro — no badge, just a small cyan dot next to label */}
          <div className="pr-side pr-pro">
            <div className="pr-label">{t('pr.pro.label')} <span className="pr-dot"/></div>
            <div className="pr-pricing">
              <div className="pr-price">
                <span className="pr-amount">{t('pr.pro.amount')}</span>
                <span className="pr-period">{t('pr.pro.period')}</span>
              </div>
              <div className="pr-price-sub">
                <span className="pr-list">{t('pr.pro.list')}</span>
                <span className="pr-discount">{t('pr.pro.discount')}</span>
              </div>
              <div className="pr-trial">{t('pr.pro.trial')}</div>
            </div>
            <ul className="pr-features">
              <li><span className="pr-check">─</span><span className="pr-feat-body"><span>{t('pr.feat.convert')}</span><span className="pr-feat-sub">{t('pr.feat.convert.sub')}</span></span></li>
              <li><span className="pr-check">─</span><span className="pr-feat-body"><span>{t('pr.feat.daw')}</span><span className="pr-feat-sub">{t('pr.feat.daw.sub')}</span></span></li>
              <li className="pr-pro-only"><span className="pr-check">─</span><span className="pr-feat-body"><span>{t('pr.feat.sync')}</span><span className="pr-feat-sub">{t('pr.feat.sync.sub')}</span></span></li>
              <li className="pr-pro-only"><span className="pr-check">─</span><span className="pr-feat-body"><span>{t('pr.feat.migrate')}</span><span className="pr-feat-sub">{t('pr.feat.migrate.sub')}</span></span></li>
              <li className="pr-pro-only"><span className="pr-check">─</span><span className="pr-feat-body"><span>{t('pr.feat.cloud')}</span><span className="pr-feat-sub">{t('pr.feat.cloud.sub')}</span></span></li>
              <li className="pr-pro-only"><span className="pr-check">─</span><span className="pr-feat-body"><span>{t('pr.feat.enhance')}</span><span className="pr-feat-sub">{t('pr.feat.enhance.sub')}</span></span></li>
            </ul>
            <ProCtaButton/>
          </div>
        </div>

        <p className="pr-foot">{t('pr.foot')}</p>
      </div>
    </section>
  );
}

// ====== Footer ======
function Footer() {
  const t = useT();
  return (
    <footer className="shell">
      <div className="footer">
        <div className="footer-top">
          <div style={{display:'flex', alignItems:'center', gap:10}}>
            <img src="/assets/quzic-logo.png" alt="QUZIC" style={{height:18, width:'auto', display:'block', opacity:0.85}}/>
            <span>{t('footer.copy')}</span>
          </div>
          <div className="footer-links">
            <a href="/terms">{t('footer.terms')}</a>
            <a href="/privacy">{t('footer.privacy')}</a>
            <a href="/refund">{t('footer.refund')}</a>
            <a href="https://instagram.com/quzic" target="_blank" rel="noopener noreferrer">@quzic</a>
          </div>
        </div>
        <div className="footer-biz">
          <span>BEATIF</span>
          <span className="footer-biz-dot">·</span>
          <span>CEO: Jaemin Lee</span>
          <span className="footer-biz-dot">·</span>
          <span>Business Reg. No.: 790-22-02059</span>
          <span className="footer-biz-dot">·</span>
          <span>+82-10-7929-3379</span>
          <span className="footer-biz-dot">·</span>
          <span>6, Sinjeong Jungang-ro, Yangcheon-gu, Seoul, Republic of Korea</span>
        </div>
      </div>
    </footer>
  );
}

// ====== Root ======
function App() {
  const isCallback = window.location.pathname === '/auth/callback';
  return (
    <LangProvider>
      <AuthProvider>
        {isCallback ? (
          <CallbackHandler/>
        ) : (
          <>
            <div className="bg-fx"/>
            <Nav/>
            <Hero/>
            <Showcase/>
            <InsuranceSection/>
            <Pricing/>
            <Footer/>
          </>
        )}
      </AuthProvider>
    </LangProvider>
  );
}

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