// ===== オーナー側 O-01〜O-05 + shell =====
function useToast() {
  const [t, setT] = React.useState(null);
  const tm = React.useRef(null);
  const show = (msg, undo) => { clearTimeout(tm.current); const k = Math.random(); setT({ msg, undo, k }); tm.current = setTimeout(() => setT(null), 5000); };
  const node = t && (
    <div className="toast" key={t.k}>
      <span>{t.msg}</span>
      {t.undo && <button onClick={() => { t.undo(); setT(null); }}>元に戻す</button>}
      <div className="toast-bar" />
    </div>
  );
  return [show, node];
}

function Saved() {
  const [k, setK] = React.useState(0);
  React.useEffect(() => { const h = () => setK(x => x + 1); window.addEventListener('nx-saved', h); return () => window.removeEventListener('nx-saved', h); }, []);
  return <span className="saved" key={k}><span className="ck"><CheckIcon size={10} /></span>保存済み</span>;
}
const ping = () => window.dispatchEvent(new Event('nx-saved'));

function OwnerShell({ m, screen, go, cfg, children }) {
  const [currentUser, setCurrentUser] = React.useState(null);
  React.useEffect(() => {
    if (window.getCurrentUser) window.getCurrentUser().then(u => setCurrentUser(u));
    if (window.onAuthStateChange) {
      const sub = window.onAuthStateChange((_, u) => setCurrentUser(u));
      return () => sub?.unsubscribe && sub.unsubscribe();
    }
  }, []);

  const handleLogout = async () => {
    if (window.signOutUser) await window.signOutUser();
    setCurrentUser(null);
    go('O-01');
  };

  const navs = [
    ['O-03', 'ホーム', ['O-03', 'O-04', 'O-05', 'O-06', 'O-07']],
    ['O-08', 'KPI', ['O-08']],
    ['O-10', '設定', ['O-10', 'O-09']],
  ];
  const active = navs.find(n => n[2].includes(screen))?.[0];
  const displayName = currentUser ? (currentUser.email?.split('@')[0] || 'オーナー') : 'たちばな';
  const initialLetter = displayName.charAt(0).toUpperCase();
  const isAdmin = currentUser && (
    ['eitosato0818@gmail.com', 'tachibana06060818@gmail.com', 'tachibana@example.com'].includes(currentUser.email?.toLowerCase()) ||
    currentUser.email?.toLowerCase().includes('eitosato')
  );

  if (m) {
    return (
      <div className="o-root m">
        <div className="o-bg"><LogoPattern opacity={0.04} color="var(--em-d)" /></div>
        <div className="o-mtop">
          <Logo size={15} />
          <div className="row" style={{ gap: 8 }}>
            {cfg.plan === 'pro' && <span className="pro">{isAdmin ? 'ADMIN' : 'PRO'}</span>}
            <div style={{ width: 30, height: 30, borderRadius: 99, background: 'var(--em-l)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 900, color: 'var(--em-d)', fontSize: 13 }}>{initialLetter}</div>
          </div>
        </div>
        <div className="o-main">{children}</div>
        <div className="o-mtab">{navs.map(([id, l]) => <button key={id} className={active === id ? 'on' : ''} onClick={() => go(id)}><i />{l}</button>)}</div>
      </div>
    );
  }
  return (
    <div className="o-root">
      <div className="o-bg"><LogoPattern opacity={0.04} color="var(--em-d)" /></div>
      <div className="o-side">
        <div style={{ padding: '4px 12px 26px' }}><Logo size={17} /></div>
        {navs.map(([id, l]) => (
          <button key={id} className={'o-nav' + (active === id ? ' on' : '')} onClick={() => go(id)}>
            <span className="dot" />{l}{id === 'O-08' && cfg.plan !== 'pro' && <span className="pro" style={{ marginLeft: 'auto' }}>PRO</span>}
          </button>
        ))}

        <div style={{ height: 1, background: 'var(--line)', margin: '14px 10px' }} />
        <div className="cap" style={{ padding: '0 12px 6px', fontSize: 11, fontWeight: 700, color: 'var(--ink3)' }}>プレビュー確認</div>
        <button className="o-nav" onClick={() => go('L-01', { preview: true, embed: false })}>
          <span className="dot" style={{ background: 'var(--em)' }} />受講者ビュー
        </button>
        <button className="o-nav" onClick={() => go('L-01', { preview: true, embed: true })}>
          <span className="dot" style={{ background: '#3b82f6' }} />埋め込みビュー
        </button>

        <div style={{ marginTop: 'auto', padding: 12, borderRadius: 14, background: 'rgba(255,255,255,.7)' }}>
          <div className="row" style={{ gap: 10, alignItems: 'center' }}>
            <div style={{ width: 32, height: 32, borderRadius: 99, background: 'var(--em-l)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 900, color: 'var(--em-d)' }}>{initialLetter}</div>
            <div style={{ minWidth: 0, flex: 1 }}>
              <div style={{ fontSize: 13, fontWeight: 700, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{displayName}</div>
              <div className="cap" style={{ color: isAdmin ? 'var(--em-d)' : undefined, fontWeight: isAdmin ? 700 : undefined }}>
                {isAdmin ? '管理者特権（永久Pro）' : (cfg.plan === 'pro' ? '有料プラン' : '無料プラン')}
              </div>
            </div>
            {currentUser && (
              <button className="btn-t" onClick={handleLogout} style={{ color: 'var(--ink3)', fontSize: 11, padding: 0 }} title="ログアウト">退場</button>
            )}
          </div>
          {cfg.plan !== 'pro' && !isAdmin && <button className="btn-t" style={{ marginTop: 8 }} onClick={() => go('O-09')}>有料プランを見る →</button>}
        </div>
      </div>
      <div className="o-main">{children}</div>
    </div>
  );
}

// ---------- O-01 LP（デモ付き） ----------
// トップページで未登録のユーザーが直接触って試せる「プロの3ステップ体験スライド」
const DEMO_STAGE = { id: 'demo', title: '声活 3ステップ体験', aura: 'em', steps: [
  { k: '01 POSTURE', a: '壁にかかと・肩甲骨・後頭部をつけてまっすぐ立つ', h: '頭のてっぺんが透明な糸でスーッと吊られている感覚を意識' },
  { k: '02 BREATH', a: '下腹部に手を当て、4秒で吸って8秒「スー」と吐く', h: '肩を上げず、横隔膜の広がりを手のひらで感じてキープ' },
  { k: '03 RESONANCE', a: 'あくびの口で喉を開き「まー」と1音響かせる', h: '鼻の奥と頭頂部に抜ける倍音の響きを感じたら、クリア！' },
]};
function O01LP({ m, go, reduced }) {
  const [i, setI] = React.useState(0);
  const [c, setC] = React.useState(0);
  const [fx, setFx] = React.useState(null);
  const [big, setBig] = React.useState(false);
  const [end, setEnd] = React.useState(false);
  const [legalType, setLegalType] = React.useState(null);
  const ref = React.useRef(null);
  const cfg = { plan: 'pro', glassColor: 'oklch(0.62 0.14 165)', poweredBy: true };
  const last = i === 2;
  const next = (e) => {
    if (end) return;
    if (last) { setC(3); setBig(true); setTimeout(() => { setBig(false); setEnd(true); }, reduced ? 900 : 2500); return; }
    const r = ref.current.getBoundingClientRect(), sc = r.width / ref.current.offsetWidth;
    const x = e?.clientX ? (e.clientX - r.left) / sc : r.width / 2, y = e?.clientY ? (e.clientY - r.top) / sc : 0;
    const k = Math.random(); setFx({ t: i === 0 ? 'A' : 'B', k, x, y });
    setTimeout(() => setFx(f => f && f.k === k ? null : f), 1250);
    setC(Math.max(c, i + 1)); setI(i + 1);
  };
  const fxNode = fx && (fx.t === 'A' ? <FxA key={fx.k} reduced={reduced} /> : <FxB key={fx.k} x={fx.x} y={fx.y} reduced={reduced} />);
  return (
    <div style={{ position: 'absolute', inset: 0, overflow: 'auto', background: 'var(--em-ll)' }}>
      <div className="o-bg" style={{ position: 'absolute' }}><LogoPattern opacity={0.045} color="var(--em-d)" /></div>
      <div style={{ position: 'relative', zIndex: 2, maxWidth: 1080, margin: '0 auto', padding: m ? '0 16px 40px' : '0 40px 60px' }}>
        <div className="between" style={{ height: m ? 60 : 76 }}>
          <Logo size={m ? 16 : 19} />
          <button className="btn-t" style={{ color: 'var(--ink2)' }} onClick={() => go('O-02')}>ログイン</button>
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: m ? '1fr' : '0.8fr 1.2fr', gap: m ? 22 : 48, alignItems: 'center', marginTop: m ? 6 : 30 }}>
          <div>
            <div style={{ fontFamily: 'var(--en)', fontSize: 11, letterSpacing: '.2em', color: 'var(--em-d)', fontWeight: 600, marginBottom: 14 }}>ONE SLIDE, ONE ACTION</div>
            <h1 style={{ fontSize: m ? 30 : 44, fontWeight: 900, lineHeight: 1.3, letterSpacing: '.01em' }}>1段1段<br />踏みしめて、<br />次のステージへ。</h1>
            <p style={{ fontSize: m ? 14 : 15.5, color: 'var(--ink2)', marginTop: 16, lineHeight: 1.8 }}>{m ? '下のデモで「次へ」を押してみてください。' : '右のデモで「次へ」を押してみてください。'}</p>
            {!m && <button className="btn btn-p" style={{ marginTop: 28 }} onClick={() => go('O-02')}>はじめる（無料）</button>}
          </div>
          <div style={{ position: 'relative' }}>
            <div style={{ position: 'relative', '--aura': auraC('em') }}>
              <Frame cfg={cfg} stage={DEMO_STAGE} cleared={c} total={3} frameRef={ref} fx={fxNode}>
                {end ? (
                  <div className="slide neo-slide end-slide" style={{ justifyContent: 'center', alignItems: 'center', textAlign: 'center', flexDirection: 'column' }}>
                    <div className="sl-bg-glow" style={{ left: '50%', top: '50%', transform: 'translate(-50%, -50%)', width: '85cqw', height: '85cqw' }} />
                    <div className="sl-bg-grid" />
                    <div style={{ position: 'relative', zIndex: 2, display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
                      <div style={{ fontFamily: 'var(--en)', fontSize: '1.9cqw', letterSpacing: '.22em', color: 'var(--em)', fontWeight: 800, marginBottom: '1.4cqw' }}>
                        STAGE COMPLETED
                      </div>
                      <div style={{ fontSize: '5cqw', fontWeight: 900, lineHeight: 1.3, letterSpacing: '.02em', color: '#ffffff', textShadow: '0 4px 20px rgba(0,0,0,.6)' }}>
                        あなたの講座でも、これを。
                      </div>
                      <div style={{ fontSize: '2.2cqw', color: '#cbd5e1', marginTop: '1.8cqw', fontWeight: 500 }}>
                        1枚1アクションで、受講者が迷わず完走する世界へ
                      </div>
                    </div>
                  </div>
                ) : (
                  <div key={i} className="slide-in-n"><Slide step={DEMO_STAGE.steps[i]} idx={i} total={3} /></div>
                )}
              </Frame>
            </div>
            <div className="between" style={{ marginTop: 18, justifyContent: 'flex-end' }}>
              {end ? (
                <button className="btn btn-p" style={{ height: 60, fontSize: 17, padding: '0 34px' }} onClick={() => go('O-02')}>はじめる（無料）<Chevron /></button>
              ) : (() => {
                const btnTheme = getButtonTheme(cfg);
                return (
                  <button
                    className={'btn-aura' + (last ? ' is-last' : '') + (btnTheme.isWhite ? ' is-white' : '') + (btnTheme.isBlack ? ' is-black' : '')}
                    style={{ '--btn-color': btnTheme.color, '--aura': btnTheme.color, minWidth: 180, height: 54, fontSize: 16 }}
                    onClick={next}
                  >
                    {last ? '達成！' : <>次へ <Chevron /></>}
                  </button>
                );
              })()}
            </div>
            {end && <button className="btn-t" style={{ color: 'var(--ink3)', fontWeight: 400, marginTop: 6 }} onClick={() => { setI(0); setC(0); setEnd(false); }}>もう一度体験する</button>}
          </div>
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: m ? '1fr' : 'repeat(3,1fr)', gap: 14, marginTop: m ? 40 : 80 }}>
          {[['1枚1アクション', '縁にはタイトルだけ。だから迷わない。'], ['1歩ごとに祝福', '進むたびに光る。続けたくなる。'], ['止まる場所がわかる', 'どのスライドで止まったか、数字で。']].map(([a, b], k) => (
            <div key={k} className="card" style={{ padding: 22 }}>
              <div style={{ fontFamily: 'var(--en)', fontSize: 12, color: 'var(--em-d)', fontWeight: 600, marginBottom: 10 }}>0{k + 1}</div>
              <div style={{ fontSize: 17, fontWeight: 900, marginBottom: 6 }}>{a}</div>
              <div style={{ fontSize: 13.5, color: 'var(--ink2)' }}>{b}</div>
            </div>
          ))}
        </div>
        {/* フッターリンク（クリックで法律・規約モーダルを起動） */}
        <div className="row" style={{ justifyContent: 'center', gap: 18, marginTop: 40, fontSize: 12, color: 'var(--ink3)', flexWrap: 'wrap' }}>
          <button className="btn-t" style={{ fontSize: 12, color: 'var(--ink3)', padding: 0 }} onClick={() => setLegalType('terms')}>利用規約</button>
          <span>·</span>
          <button className="btn-t" style={{ fontSize: 12, color: 'var(--ink3)', padding: 0 }} onClick={() => setLegalType('privacy')}>プライバシーポリシー</button>
          <span>·</span>
          <button className="btn-t" style={{ fontSize: 12, color: 'var(--ink3)', padding: 0 }} onClick={() => setLegalType('tokusho')}>特定商取引法に基づく表記</button>
          <span>·</span>
          <button className="btn-t" style={{ fontSize: 12, color: 'var(--ink3)', padding: 0 }} onClick={() => setLegalType('external')}>外部送信について</button>
          <span>·</span>
          <button className="btn-t" style={{ fontSize: 12, color: 'var(--ink3)', padding: 0 }} onClick={() => setLegalType('report')}>通報窓口</button>
        </div>
      </div>
      {legalType && window.LegalModal && (
        <window.LegalModal type={legalType} onClose={() => setLegalType(null)} />
      )}
      {big && <div style={{ position: 'absolute', inset: 0, zIndex: 90 }}><FxC aura={auraC('em')} reduced={reduced} title={DEMO_STAGE.title} /></div>}
    </div>
  );
}

// ---------- O-02 ログイン ----------
function O02Login({ m, go }) {
  const [mode, setMode] = React.useState('pick');
  const [email, setEmail] = React.useState('');
  const [code, setCode] = React.useState('');
  const [loading, setLoading] = React.useState(false);
  const [errMsg, setErrMsg] = React.useState('');
  const [infoMsg, setInfoMsg] = React.useState('');
  const [legalType, setLegalType] = React.useState(null);

  // ① Googleログイン
  const handleGoogle = async () => {
    setLoading(true);
    setErrMsg('');
    if (window.signInWithGoogle) {
      const res = await window.signInWithGoogle();
      if (!res.ok) {
        // SupabaseでGoogleプロバイダが未有効の場合はデモログインにフォールバック
        console.warn('Googleログインスキップ（デモ遷移）:', res.message);
        go('O-03', { empty: false });
      }
    } else {
      go('O-03', { empty: false });
    }
    setLoading(false);
  };

  // ② メール宛に6桁コードを送信
  const handleSendEmail = async () => {
    if (!email || !email.includes('@')) return;
    setLoading(true);
    setErrMsg('');
    setInfoMsg('');
    if (window.sendOtpCode) {
      const res = await window.sendOtpCode(email);
      if (res.ok) {
        setInfoMsg(res.message);
        setMode('code');
      } else {
        // Supabase未設定などの場合はデモモードとしてコード入力へ進む
        setErrMsg(res.message);
        setTimeout(() => setMode('code'), 1200);
      }
    } else {
      setMode('code');
    }
    setLoading(false);
  };

  // ③ 6桁コードを照合してログイン完了
  const handleVerify = async () => {
    if (code.length < 6) return;
    setLoading(true);
    setErrMsg('');
    // デモコード '123456' の場合はそのまま通過
    if (code === '123456') {
      go('O-03', { empty: false });
      return;
    }
    if (window.verifyOtpCode) {
      const res = await window.verifyOtpCode(email, code);
      if (res.ok) {
        go('O-03', { empty: false });
      } else {
        setErrMsg(res.message || 'コードが正しくありません。');
      }
    } else {
      go('O-03', { empty: false });
    }
    setLoading(false);
  };

  return (
    <div style={{ position: 'absolute', inset: 0, background: 'var(--em-ll)', display: 'flex', alignItems: m ? 'flex-start' : 'center', justifyContent: 'center', padding: m ? '70px 16px' : 20 }}>
      <div className="o-bg"><LogoPattern opacity={0.045} color="var(--em-d)" /></div>
      <div className="card" style={{ position: 'relative', width: '100%', maxWidth: 400, padding: m ? '28px 22px' : 36 }}>
        <Logo size={16} />
        <div style={{ fontSize: 22, fontWeight: 900, margin: '22px 0 6px' }}>{mode === 'code' ? 'コードを入力' : 'はじめましょう'}</div>
        <div style={{ fontSize: 14, color: 'var(--ink2)', marginBottom: 20 }}>パスワードは不要です。</div>

        {infoMsg && (
          <div style={{ padding: '10px 14px', borderRadius: 10, background: 'var(--em-l)', color: 'var(--em-d)', fontSize: 13, marginBottom: 16 }}>
            {infoMsg}
          </div>
        )}
        {errMsg && (
          <div style={{ padding: '10px 14px', borderRadius: 10, background: 'rgba(239,68,68,.12)', color: 'var(--danger)', fontSize: 13, marginBottom: 16 }}>
            {errMsg}
          </div>
        )}

        {mode === 'pick' && (
          <div className="stack" style={{ gap: 12 }}>
            <button className="btn btn-s" disabled={loading} style={{ height: 50, fontSize: 15, borderRadius: 13 }} onClick={handleGoogle}>
              <span style={{ fontFamily: 'var(--en)', fontWeight: 700, color: '#4285f4' }}>G</span> Googleで続ける
            </button>
            <div className="row" style={{ gap: 10, color: 'var(--ink3)', fontSize: 12 }}><div style={{ flex: 1, height: 1, background: 'var(--line)' }} />または<div style={{ flex: 1, height: 1, background: 'var(--line)' }} /></div>
            <input className="inp" placeholder="メールアドレス" type="email" value={email} onChange={e => setEmail(e.target.value)} onKeyDown={e => e.key === 'Enter' && handleSendEmail()} />
            <button className="btn btn-p btn-full" disabled={!email.includes('@') || loading} onClick={handleSendEmail}>
              {loading ? '送信中…' : '続ける'}
            </button>
            <button className="btn-t" style={{ color: 'var(--ink3)', fontWeight: 400, alignSelf: 'center' }} onClick={() => setEmail('tachibana@example.com')}>（デモ：入力する）</button>
          </div>
        )}
        {mode === 'code' && (
          <div className="stack" style={{ gap: 14 }}>
            <CodeInput email={email} code={code} setCode={setCode} onDone={handleVerify} btnLabel={loading ? '確認中…' : 'ログイン'} />
            <button className="btn-t" style={{ color: 'var(--ink3)', fontSize: 12 }} onClick={() => { setMode('pick'); setErrMsg(''); }}>← メールアドレスを入力し直す</button>
          </div>
        )}
        <div className="cap" style={{ marginTop: 22, textAlign: 'center', fontSize: 11 }}>
          続けると <button className="btn-t" style={{ fontSize: 11, padding: 0, textDecoration: 'underline', color: 'inherit' }} onClick={() => setLegalType('terms')}>利用規約</button> と <button className="btn-t" style={{ fontSize: 11, padding: 0, textDecoration: 'underline', color: 'inherit' }} onClick={() => setLegalType('privacy')}>プライバシーポリシー</button> に同意したことになります
        </div>
      </div>
      {legalType && window.LegalModal && (
        <window.LegalModal type={legalType} onClose={() => setLegalType(null)} />
      )}
    </div>
  );
}

// ---------- 公開までの小さなゲージ ----------
function LaunchGauge({ rm, m }) {
  const checks = [
    ['ロードマップ', !!rm], ['ステージ', rm && rm.stages.length > 0], ['スライド', rm && rm.stages.some(s => s.steps.length)], ['プレビュー', rm && rm.previewed], ['公開', rm && rm.published],
  ];
  const done = checks.filter(c => c[1]).length;
  if (done === 5) return null;
  return (
    <div className="card" style={{ padding: '14px 18px', display: 'flex', alignItems: 'center', gap: 16, marginBottom: 20, flexWrap: m ? 'wrap' : 'nowrap' }}>
      <div style={{ fontSize: 13.5, fontWeight: 700, whiteSpace: 'nowrap' }}>公開まであと <span style={{ fontFamily: 'var(--en)', color: 'var(--em-d)', fontSize: 16 }}>{5 - done}</span> ステップ</div>
      <div style={{ display: 'flex', gap: 5, flex: 1, minWidth: 160 }}>
        {checks.map(([l, ok], i) => (
          <div key={i} style={{ flex: 1 }}>
            <div style={{ height: 6, borderRadius: 9, background: ok ? 'var(--em)' : 'rgba(15,31,25,.08)', boxShadow: ok ? '0 0 8px oklch(0.66 0.15 163 / .5)' : 'none', transition: 'all .4s' }} />
            {!m && <div className="cap" style={{ fontSize: 10.5, marginTop: 5, color: ok ? 'var(--em-d)' : 'var(--ink3)' }}>{l}</div>}
          </div>
        ))}
      </div>
    </div>
  );
}

// ---------- O-03 ホーム ----------
function O03Home({ m, go, rm, setRm, cfg, ost, setOst }) {
  const [creating, setCreating] = React.useState(false);
  const [title, setTitle] = React.useState('');
  const empty = ost.empty;
  const limit = cfg.plan === 'pro' ? 3 : 1;
  const create = () => {
    setRm(r => ({ ...r, title: title || r.title, stages: [], published: false, previewed: false }));
    setOst(o => ({ ...o, empty: false }));
    setCreating(false); ping(); go('O-04');
  };
  const list = empty ? [] : [rm];
  const totalSteps = rm.stages.reduce((a, s) => a + s.steps.length, 0);
  return (
    <div style={{ maxWidth: 980 }}>
      <div className="between" style={{ marginBottom: 20 }}>
        <div className="h1">ロードマップ</div>
        {!empty && !m && <span className="cap" style={{ fontFamily: 'var(--en)' }}>{list.length} / {limit}</span>}
      </div>
      <LaunchGauge rm={empty ? null : rm} m={m} />
      {empty ? (
        <div className="card" style={{ padding: m ? '34px 20px' : '56px 40px', textAlign: 'center', display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
          <div style={{ width: m ? '100%' : 360, marginBottom: 26, pointerEvents: 'none', opacity: .95 }}>
            <Frame cfg={{ plan: 'free', frameStyle: 'white' }} stage={{ title: 'ステージのタイトル', aura: 'em' }} cleared={0} total={1} compact>
              <div className="slide tpl3"><div className="sl-a">ここにあなたのスライド</div></div>
            </Frame>
          </div>
          <div style={{ fontSize: 18, fontWeight: 900, marginBottom: 6 }}>最初の講座をつくりましょう</div>
          <div style={{ fontSize: 14, color: 'var(--ink2)', marginBottom: 24 }}>受講者には、こう見えます。</div>
          <button className="btn btn-p" onClick={() => setCreating(true)}>＋ ロードマップを作る</button>
        </div>
      ) : (
        <div style={{ display: 'grid', gridTemplateColumns: m ? '1fr' : 'repeat(auto-fill, minmax(300px, 1fr))', gap: 16 }}>
          {list.map(r => (
            <button key={r.id} className="card" style={{ padding: 0, textAlign: 'left', overflow: 'hidden' }} onClick={() => go('O-04')}>
              <div style={{ padding: 14, background: 'linear-gradient(160deg, var(--em-l), #fff)' }}>
                <div style={{ pointerEvents: 'none' }}>
                  {r.stages[0] && r.stages[0].steps[0] ? (
                    <Frame cfg={cfg} stage={r.stages[0]} cleared={0} total={r.stages[0].steps.length} compact><Slide step={r.stages[0].steps[0]} idx={0} total={r.stages[0].steps.length} /></Frame>
                  ) : <div className="slide tpl3"><div className="sl-a">スライド未登録</div></div>}
                </div>
              </div>
              <div style={{ padding: '14px 18px 18px' }}>
                <div className="between"><div style={{ fontSize: 16, fontWeight: 900 }}>{r.title}</div>{r.published ? <span className="pill pill-on">公開中</span> : <span className="pill pill-off">下書き</span>}</div>
                <div className="cap" style={{ marginTop: 6 }}>{r.stages.length} ステージ · {totalSteps} ステップ</div>
                {cfg.cancelAt && <div className="pill pill-warn" style={{ marginTop: 10 }}>{cfg.cancelAt} に一部非公開</div>}
              </div>
            </button>
          ))}
          {list.length < limit ? (
            <button className="card" onClick={() => setCreating(true)} style={{ minHeight: 200, display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column', gap: 8, borderStyle: 'dashed', borderColor: 'var(--line2)', background: 'rgba(255,255,255,.4)', color: 'var(--ink2)', fontWeight: 700, fontSize: 14 }}>
              <span style={{ fontSize: 26, fontWeight: 300, color: 'var(--em-d)' }}>＋</span>ロードマップを作る
            </button>
          ) : (
            <div className="card" style={{ minHeight: 200, display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column', gap: 10, background: 'rgba(255,255,255,.4)', padding: 20, textAlign: 'center' }}>
              <div style={{ fontSize: 13.5, color: 'var(--ink2)' }}>無料プランは1つまで<span className="help" data-tip="有料プランでは、別々の講座ごとにロードマップを3つまで作れます。1つの講座を分けるためのものではありません。">?</span></div>
              <button className="btn-t" onClick={() => go('O-09')}>有料プランで3つまで →</button>
            </div>
          )}
        </div>
      )}
      {creating && (
        <div className="modal-bg" onClick={() => setCreating(false)}>
          <div className="card modal" onClick={e => e.stopPropagation()}>
            <div style={{ fontSize: 18, fontWeight: 900, marginBottom: 18 }}>ロードマップを作る</div>
            <label className="lbl">講座のタイトル</label>
            <input className="inp" autoFocus placeholder="例：声活アクションプラン" value={title} onChange={e => setTitle(e.target.value)} onKeyDown={e => e.key === 'Enter' && create()} />
            <div className="cap" style={{ margin: '8px 0 22px' }}>1つの講座 = 1つのロードマップ</div>
            <button className="btn btn-p btn-full" onClick={create}>作る</button>
          </div>
        </div>
      )}
    </div>
  );
}

// ---------- O-04 ロードマップの編集 ----------
function O04Roadmap({ m, go, rm, setRm, cfg, setOst, toast }) {
  const [creating, setCreating] = React.useState(false);
  const [del, setDel] = React.useState(null);
  const [menu, setMenu] = React.useState(null);
  const [drag, setDrag] = React.useState(null);
  const limit = cfg.plan === 'pro' ? 20 : 5;
  const move = (from, to) => {
    if (to < 0 || to >= rm.stages.length || from === to) return;
    setRm(r => { const a = [...r.stages]; const [x] = a.splice(from, 1); a.splice(to, 0, x); return { ...r, stages: a }; }); ping();
  };
  const doDelete = (i) => {
    const removed = rm.stages[i];
    setRm(r => ({ ...r, stages: r.stages.filter((_, k) => k !== i) })); setDel(null); ping();
    toast('ステージを削除しました', () => setRm(r => { const a = [...r.stages]; a.splice(i, 0, removed); return { ...r, stages: a }; }));
  };
  return (
    <div style={{ maxWidth: 860 }}>
      <div className="crumb"><button onClick={() => go('O-03')}>ホーム</button><Chevron size={10} /><span>{rm.title}</span></div>
      <div className="between" style={{ marginBottom: 22, flexWrap: 'wrap' }}>
        <div className="h1">{rm.title}</div>
        <div className="row" style={{ gap: 10 }}>
          <Saved />
          <button className="btn btn-s" disabled={!rm.stages.some(s => s.steps.length)} onClick={() => go('O-06')}>プレビュー</button>
        </div>
      </div>
      <div className="between" style={{ marginBottom: 10 }}>
        <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--ink2)' }}>ステージ <span style={{ fontFamily: 'var(--en)', fontWeight: 500, color: 'var(--ink3)' }}>{rm.stages.length}/{limit}</span></div>
        {!m && rm.stages.length > 1 && <span className="cap">ドラッグで並び替え</span>}
      </div>
      <div className="stack" style={{ gap: 8 }}>
        {rm.stages.map((st, i) => (
          <div key={st.id} draggable={!m} onDragStart={() => setDrag(i)} onDragOver={e => { e.preventDefault(); if (drag !== null && drag !== i) { move(drag, i); setDrag(i); } }} onDragEnd={() => setDrag(null)}
            className={'card card-drag-item ' + (drag === i ? 'is-dragging' : '')}
            style={{ padding: m ? '12px 12px' : '14px 16px', display: 'flex', alignItems: 'center', gap: m ? 10 : 14, borderRadius: 16, cursor: m ? 'default' : 'grab', position: 'relative' }}>
            {!m && <span style={{ color: 'var(--ink3)', letterSpacing: -2, fontSize: 14, cursor: 'grab' }}>⋮⋮</span>}
            <div style={{ width: 10, height: 34, borderRadius: 4, background: auraC(st.aura), boxShadow: `0 0 12px -2px ${auraC(st.aura)}`, flex: 'none' }} />
            <button style={{ flex: 1, minWidth: 0, textAlign: 'left' }} onClick={() => { setOst(o => ({ ...o, stage: i })); go('O-05'); }}>
              <div style={{ fontSize: 15, fontWeight: 700, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}><span style={{ fontFamily: 'var(--en)', fontSize: 11, color: 'var(--ink3)', marginRight: 8 }}>{String(i + 1).padStart(2, '0')}</span>{st.title}</div>
              <div className="cap" style={{ marginTop: 2 }}>{st.steps.length} ステップ{!st.steps.length && <span style={{ color: 'var(--danger)' }}> · スライド未登録</span>}</div>
            </button>
            {!m && (
              <div style={{ display: 'flex', gap: 3 }}>
                {st.steps.slice(0, 4).map((s, k) => <div key={k} style={{ width: 46, borderRadius: 3, overflow: 'hidden', boxShadow: '0 0 0 1px var(--line)' }}><Slide step={s} idx={k} total={st.steps.length} /></div>)}
              </div>
            )}
            {m && (
              <div className="stack" style={{ gap: 2 }}>
                <button onClick={() => move(i, i - 1)} style={{ width: 32, height: 22, color: i === 0 ? 'var(--line2)' : 'var(--ink2)' }}><Chevron dir="u" size={12} /></button>
                <button onClick={() => move(i, i + 1)} style={{ width: 32, height: 22, color: i === rm.stages.length - 1 ? 'var(--line2)' : 'var(--ink2)' }}><Chevron dir="d" size={12} /></button>
              </div>
            )}
            <button onClick={() => setMenu(menu === i ? null : i)} style={{ width: 34, height: 34, borderRadius: 9, color: 'var(--ink3)', fontSize: 18, lineHeight: 1 }}>···</button>
            {menu === i && (
              <div className="card" style={{ position: 'absolute', right: 10, top: '100%', zIndex: 20, padding: 6, borderRadius: 12, minWidth: 150 }} onMouseLeave={() => setMenu(null)}>
                <button className="o-nav" style={{ fontSize: 13 }} onClick={() => { setMenu(null); setOst(o => ({ ...o, stage: i })); go('O-05'); }}>編集</button>
                <button className="o-nav" style={{ fontSize: 13, color: 'var(--danger)' }} onClick={() => { setMenu(null); setDel(i); }}>削除</button>
              </div>
            )}
          </div>
        ))}
      </div>
      <div style={{ marginTop: 18, display: 'flex', justifyContent: m ? 'stretch' : 'flex-start' }}>
        {rm.stages.length < limit ? (
          <button className={'btn ' + (rm.stages.length ? 'btn-s' : 'btn-p')} style={{ width: m ? '100%' : 'auto', height: rm.stages.length ? 44 : 52 }} onClick={() => setCreating(true)}>＋ ステージを作る</button>
        ) : <div className="cap">上限に達しました · <button className="btn-t" onClick={() => go('O-09')}>有料プランで20まで</button></div>}
      </div>
      {rm.stages.length === 0 && <div className="cap" style={{ marginTop: 12 }}>ステージ＝1つのアクションプラン（章）</div>}
      {creating && <StageCreate m={m} onClose={() => setCreating(false)} onCreate={(t, a) => { const id = 's' + Date.now(); setRm(r => ({ ...r, stages: [...r.stages, { id, title: t, aura: a, steps: [] }] })); setCreating(false); setOst(o => ({ ...o, stage: rm.stages.length })); ping(); go('O-05'); }} />}
      {del !== null && (
        <div className="modal-bg" onClick={() => setDel(null)}>
          <div className="card modal" onClick={e => e.stopPropagation()}>
            <div style={{ fontSize: 18, fontWeight: 900, marginBottom: 10 }}>「{rm.stages[del].title}」を削除</div>
            <div style={{ fontSize: 14, color: 'var(--ink2)', marginBottom: 22, lineHeight: 1.7 }}><b style={{ color: 'var(--ink)' }}>{rm.stages[del].steps.length}枚のステップ</b>と受講者の進捗が非表示になります。</div>
            <div className="row" style={{ justifyContent: 'flex-end' }}><button className="btn btn-s" onClick={() => setDel(null)}>やめる</button><button className="btn btn-d" style={{ background: 'var(--danger)', color: '#fff' }} onClick={() => doDelete(del)}>削除する</button></div>
          </div>
        </div>
      )}
    </div>
  );
}

function AuraPicker({ value, onChange }) {
  return (
    <div className="row" style={{ gap: 10, flexWrap: 'wrap' }}>
      {AURAS.map(a => <button key={a.id} title={a.name} className={'sw' + (value === a.id ? ' on' : '')} style={{ background: a.c, boxShadow: value === a.id ? `0 0 0 2px var(--ink), 0 0 14px ${a.c}` : undefined }} onClick={() => onChange(a.id)} />)}
    </div>
  );
}

function StageCreate({ m, onClose, onCreate }) {
  const [t, setT] = React.useState('');
  const [a, setA] = React.useState('sky');
  return (
    <div className="modal-bg" onClick={onClose}>
      <div className="card modal" onClick={e => e.stopPropagation()} style={{ maxWidth: 480 }}>
        <div style={{ fontSize: 18, fontWeight: 900, marginBottom: 18 }}>ステージを作る</div>
        <div style={{ pointerEvents: 'none', marginBottom: 20 }}>
          <Frame cfg={{ plan: 'free', frameStyle: 'white' }} stage={{ title: t || 'ステージのタイトル', aura: a }} cleared={0} total={1} compact>
            <div className="slide tpl3" style={{ boxShadow: `inset 0 0 0 3px ${auraC(a)}` }}><div className="sl-a" style={{ fontSize: '3cqw' }}>縁の下にタイトルが出ます</div></div>
          </Frame>
        </div>
        <label className="lbl">タイトル（ステージのテーマ）</label>
        <input className="inp" autoFocus maxLength={24} placeholder="例：毎日の発声ルーティン" value={t} onChange={e => setT(e.target.value)} />
        <div className="cap" style={{ textAlign: 'right', margin: '6px 0 14px', fontFamily: 'var(--en)' }}>{t.length}/24</div>
        <label className="lbl">オーラの色<span className="help" data-tip="受講者が1ステップ進むたびに、この色の光で祝福されます。">?</span></label>
        <AuraPicker value={a} onChange={setA} />
        <button className="btn btn-p btn-full" style={{ marginTop: 24 }} disabled={!t.trim()} onClick={() => onCreate(t.trim(), a)}>作る</button>
      </div>
    </div>
  );
}

Object.assign(window, { OwnerShell, O01LP, O02Login, O03Home, O04Roadmap, AuraPicker, useToast, Saved, ping });
