// ===== オーナー側 O-05〜O-10 =====
// デモ取り込み時に追加される、高品質な実践トレーニング用スライドプール
const IMPORT_POOL = [
  { k: '01 ARTICULATION', a: '割りばしを軽く噛み「あえいうえおあお」を3回発声', h: '口角をしっかり上げて母音の響きを意識します', tpl: 0 },
  { k: '02 BREATH CONTROL', a: '細く長く20秒かけて一定の息を吐き続ける', h: '息の揺れや途切れがないかスマホで耳を澄ませてチェック', tpl: 2 },
  { k: '03 TONE WORK', a: 'ド・ミ・ソの音階で「ら・ら・ら」と響かせる', h: '胸元と眉間の両方に振動が伝わる感覚を掴みましょう', tpl: 1 },
  { k: '04 ACTION TASK', a: '今日一番調子の良かった「1音」をボイスメモに保存', h: '録音をためることで自分の声の進化を実感できます', tpl: 3 },
];

// ---------- O-05 ステージの編集 ----------
function O05Stage({ m, go, rm, setRm, cfg, ost, toast }) {
  const si = Math.min(ost.stage || 0, rm.stages.length - 1);
  const st = rm.stages[si];
  const [importing, setImporting] = React.useState(false);
  const [progress, setProgress] = React.useState(null);
  const [drag, setDrag] = React.useState(null);
  const [sel, setSel] = React.useState(0);
  const lp = React.useRef(null);
  if (!st) return <div className="cap">ステージがありません</div>;
  const upd = (fn) => { setRm(r => ({ ...r, stages: r.stages.map((s, k) => k === si ? fn(s) : s) })); ping(); };
  const moveStep = (a, b) => { if (b < 0 || b >= st.steps.length || a === b) return; upd(s => { const x = [...s.steps]; const [y] = x.splice(a, 1); x.splice(b, 0, y); return { ...s, steps: x }; }); };
  const delStep = (i) => {
    const removed = st.steps[i];
    upd(s => ({ ...s, steps: s.steps.filter((_, k) => k !== i) }));
    toast('削除しました', () => upd(s => { const x = [...s.steps]; x.splice(i, 0, removed); return { ...s, steps: x }; }));
  };
  const fileInputRef = React.useRef(null);

  // パソコンやスマホから画像ファイルまたはPDFを取り込んでステップに変換する処理
  // 引数: files（選択されたファイル一覧）
  // （本棚に新しいアルバムのページを差し込んでいくような処理です）
  const handleFiles = async (files) => {
    if (!files || !files.length) return;
    setImporting(false);

    // ① PDFファイルが含まれているかチェック
    const pdfFile = Array.from(files).find(f => f.type === 'application/pdf' || f.name.toLowerCase().endsWith('.pdf'));
    if (pdfFile && window.convertPdfToSlideSteps) {
      setProgress({ n: 0, src: 'PDFファイル', total: 1 });
      try {
        const result = await window.convertPdfToSlideSteps(pdfFile, (current, total) => {
          setProgress({ n: current, src: 'PDF各ページ', total: total });
        });
        const spaceLeft = 30 - st.steps.length;
        const toAdd = result.steps.slice(0, spaceLeft);
        upd(s => ({ ...s, steps: [...s.steps, ...toAdd] }));

        if (result.exceeded || result.steps.length > spaceLeft) {
          toast(`上限のため最初の${toAdd.length}枚のみ取り込みました（最大30枚）`);
        } else {
          toast(`PDFから${toAdd.length}枚のスライドを取り込みました`);
        }

        // Supabase接続中なら裏側で自動同期を試みる
        if (window.syncRoadmapToSupabase) {
          setTimeout(() => window.syncRoadmapToSupabase(rm), 600);
        }
      } catch (err) {
        console.error('PDF取り込み失敗:', err);
        toast('PDFの取り込みに失敗しました');
      } finally {
        setProgress(null);
      }
      return;
    }

    // ② 画像ファイルの場合の処理
    const validFiles = Array.from(files).filter(f => f.type.startsWith('image/')).slice(0, 30 - st.steps.length);
    if (!validFiles.length) {
      toast('画像（PNG/JPG）またはPDFファイルを選択してください');
      return;
    }

    setProgress({ n: 0, src: '画像ファイル', total: validFiles.length });
    let loadedCount = 0;
    const newSteps = [];

    validFiles.forEach((file, index) => {
      const reader = new FileReader();
      reader.onload = (e) => {
        newSteps.push({
          k: 'STEP ' + (st.steps.length + newSteps.length + 1),
          a: file.name.replace(/\.[^/.]+$/, ''), // 拡張子を除いたファイル名
          h: '取り込んだスライド画像',
          image: e.target.result, // DataURL形式の画像データ
          addedAt: Date.now(),
        });
        loadedCount++;
        setProgress({ n: loadedCount, src: '画像ファイル', total: validFiles.length });

        // 全ての画像が読み終わったら、ステージのステップ一覧に追加します
        if (loadedCount >= validFiles.length) {
          setTimeout(() => {
            setProgress(null);
            upd(s => ({ ...s, steps: [...s.steps, ...newSteps].slice(0, 30) }));
            toast(`${newSteps.length}枚のスライドを取り込みました`);

            if (window.syncRoadmapToSupabase) {
              setTimeout(() => window.syncRoadmapToSupabase(rm), 600);
            }
          }, 300);
        }
      };
      reader.readAsDataURL(file);
    });
  };

  const doImport = (src) => {
    setImporting(false); setProgress({ n: 0, src });
    let n = 0; const total = 4;
    const iv = setInterval(() => {
      n++; setProgress({ n, src, total });
      if (n >= total) { clearInterval(iv); setTimeout(() => { setProgress(null); upd(s => ({ ...s, steps: [...s.steps, ...IMPORT_POOL].slice(0, 30) })); toast('デモスライドを取り込みました'); }, 350); }
    }, 320);
  };
  const cur = st.steps[Math.min(sel, st.steps.length - 1)];
  return (
    <div style={{ maxWidth: 1080 }}>
      <div className="crumb"><button onClick={() => go('O-03')}>ホーム</button><Chevron size={10} /><button onClick={() => go('O-04')}>{rm.title}</button><Chevron size={10} /><span>ステージ {si + 1}</span></div>
      <div className="between" style={{ marginBottom: 22, flexWrap: 'wrap' }}>
        <div className="h1">ステージの編集</div>
        <div className="row" style={{ gap: 10 }}><Saved /><button className="btn btn-s" disabled={!st.steps.length} onClick={() => go('O-06')}>プレビュー</button></div>
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: m ? '1fr' : '1fr 340px', gap: 20, alignItems: 'start' }}>
        <div className="stack" style={{ gap: 16 }}>
          {/* 受講者にはこう見えます */}
          <div className="card" style={{ padding: m ? 14 : 20 }}>
            <div className="between" style={{ marginBottom: 12 }}><div className="cap">受講者にはこう見えます</div>{st.steps.length > 0 && <span className="cap" style={{ fontFamily: 'var(--en)' }}>{Math.min(sel, st.steps.length - 1) + 1}/{st.steps.length}</span>}</div>
            <div style={{ pointerEvents: 'none' }}>
              <Frame cfg={cfg} stage={st} cleared={Math.min(sel, st.steps.length)} total={Math.max(st.steps.length, 1)}>
                {cur ? <Slide step={cur} idx={sel} total={st.steps.length} /> : <div className="slide tpl3"><div className="sl-a">スライドを取り込むと、ここに出ます</div></div>}
              </Frame>
            </div>
          </div>
          {/* steps */}
          <div className="card" style={{ padding: m ? 14 : 20 }}>
            <div className="between" style={{ marginBottom: 14 }}>
              <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--ink2)' }}>ステップ <span style={{ fontFamily: 'var(--en)', fontWeight: 500, color: 'var(--ink3)' }}>{st.steps.length}/30</span></div>
              {st.steps.length > 0 && <span className="cap">{m ? '長押しで削除' : 'ドラッグで並び替え · 右上 ✕ で削除'}</span>}
            </div>
            {progress && (
              <div style={{ padding: '14px 16px', borderRadius: 12, background: 'var(--em-ll)', marginBottom: 14 }}>
                <div className="between" style={{ fontSize: 13, marginBottom: 8 }}><span>{progress.src}から取り込み中…</span><span style={{ fontFamily: 'var(--en)' }}>{progress.n}/4</span></div>
                <div style={{ height: 5, borderRadius: 9, background: 'rgba(15,31,25,.08)' }}><div style={{ height: '100%', borderRadius: 9, background: 'var(--em)', width: (progress.n / 4 * 100) + '%', transition: 'width .3s' }} /></div>
              </div>
            )}
            {st.steps.length === 0 && !progress ? (
              <div 
                style={{ border: '2px dashed var(--line2)', borderRadius: 16, padding: m ? '30px 16px' : '44px 20px', textAlign: 'center', cursor: 'pointer' }}
                onDragOver={e => e.preventDefault()}
                onDrop={e => { e.preventDefault(); handleFiles(e.dataTransfer.files); }}
                onClick={() => setImporting(true)}
              >
                <div style={{ fontSize: 15, fontWeight: 700, marginBottom: 4 }}>画像・PDFをここにドロップ</div>
                <div className="cap" style={{ marginBottom: 18 }}>1枚 = 1ステップ（最大30枚まで）</div>
                <button className="btn btn-p" onClick={(e) => { e.stopPropagation(); setImporting(true); }}>スライドを取り込む</button>
              </div>
            ) : (
              <div style={{ display: 'grid', gridTemplateColumns: `repeat(auto-fill, minmax(${m ? 130 : 150}px, 1fr))`, gap: 12 }}>
                {st.steps.map((s, i) => (
                  <div key={i + s.a} draggable={!m} onDragStart={() => setDrag(i)} onDragOver={e => { e.preventDefault(); if (drag !== null && drag !== i) { moveStep(drag, i); setDrag(i); } }} onDragEnd={() => setDrag(null)}
                    onClick={() => setSel(i)}
                    onPointerDown={() => { if (m) lp.current = setTimeout(() => delStep(i), 600); }} onPointerUp={() => clearTimeout(lp.current)} onPointerLeave={() => clearTimeout(lp.current)}
                    style={{ position: 'relative', cursor: 'grab' }} className={'step-thumb ' + (drag === i ? 'is-dragging' : '')}>
                    <div style={{ borderRadius: 8, overflow: 'hidden', boxShadow: sel === i ? `0 0 0 2.5px ${auraC(st.aura)}` : '0 0 0 1px var(--line)' }}><Slide step={s} idx={i} total={st.steps.length} /></div>
                    <div className="between" style={{ marginTop: 6 }}><span style={{ fontFamily: 'var(--en)', fontSize: 11, color: 'var(--ink3)' }}>{String(i + 1).padStart(2, '0')}</span></div>
                    {!m && <button onClick={(e) => { e.stopPropagation(); delStep(i); }} style={{ position: 'absolute', top: 5, right: 5, width: 22, height: 22, borderRadius: 7, background: 'rgba(0,0,0,.55)', color: '#fff', fontSize: 11 }} className="thumb-x">✕</button>}
                  </div>
                ))}
                {st.steps.length < 30 && (
                  <button onClick={() => setImporting(true)} style={{ aspectRatio: '16/9', borderRadius: 8, border: '1.5px dashed var(--line2)', color: 'var(--em-d)', fontSize: 12.5, fontWeight: 700, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 4 }}>＋ 取り込む</button>
                )}
              </div>
            )}
          </div>
        </div>
        {/* settings */}
        <div className="card" style={{ padding: 20, position: m ? 'static' : 'sticky', top: 0 }}>
          <label className="lbl">タイトル（縁と目次に出ます）</label>
          <input className="inp" maxLength={24} value={st.title} onChange={e => upd(s => ({ ...s, title: e.target.value }))} />
          <div className="cap" style={{ textAlign: 'right', margin: '6px 0 18px', fontFamily: 'var(--en)' }}>{st.title.length}/24</div>
          <label className="lbl">オーラの色</label>
          <AuraPicker value={st.aura} onChange={a => upd(s => ({ ...s, aura: a }))} />
          <div style={{ height: 1, background: 'var(--line)', margin: '22px 0 16px' }} />
          <div className="cap" style={{ lineHeight: 1.7 }}>取り込んだ時点の画像で保存されます。<span className="help" data-tip="元ファイルを消しても受講者は見られます。直したいときは取り込み直してください。16:9以外は余白を足して収めます。">?</span></div>
        </div>
      </div>
      {/* 隠しファイル選択ダイアログ（ファイル選択ボタンを押した時に起動） */}
      <input 
        type="file" 
        ref={fileInputRef} 
        accept="image/*" 
        multiple 
        style={{ display: 'none' }} 
        onChange={e => handleFiles(e.target.files)} 
      />

      {importing && (
        <div className="modal-bg" onClick={() => setImporting(false)}>
          <div className="card modal" onClick={e => e.stopPropagation()}>
            <div style={{ fontSize: 18, fontWeight: 900, marginBottom: 18 }}>スライドを取り込む</div>
            <div className="stack" style={{ gap: 10 }}>
              <button 
                onClick={() => { setImporting(false); fileInputRef.current?.click(); }} 
                className="row" 
                style={{ padding: '14px 16px', borderRadius: 14, border: '1px solid var(--line2)', background: '#fff', textAlign: 'left', gap: 14 }}
              >
                <div style={{ width: 42, height: 42, borderRadius: 11, background: 'var(--em-l)', color: 'var(--em-d)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontFamily: 'var(--en)', fontWeight: 700, fontSize: 12 }}>IMG</div>
                <div style={{ flex: 1 }}>
                  <div style={{ fontWeight: 700, fontSize: 15 }}>画像ファイルを選択</div>
                  <div className="cap">パソコン・スマホの写真・スライド画像</div>
                </div>
                <Chevron />
              </button>

              <button 
                onClick={() => doImport('PDF')} 
                className="row" 
                style={{ padding: '14px 16px', borderRadius: 14, border: '1px solid var(--line2)', background: '#fff', textAlign: 'left', gap: 14 }}
              >
                <div style={{ width: 42, height: 42, borderRadius: 11, background: 'var(--em-l)', color: 'var(--em-d)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontFamily: 'var(--en)', fontWeight: 700, fontSize: 12 }}>PDF</div>
                <div style={{ flex: 1 }}>
                  <div style={{ fontWeight: 700, fontSize: 15 }}>デモPDFを取り込む</div>
                  <div className="cap">サンプルの4ステップを自動生成</div>
                </div>
                <Chevron />
              </button>

              <button 
                onClick={() => doImport('Googleドライブ')} 
                className="row" 
                style={{ padding: '14px 16px', borderRadius: 14, border: '1px solid var(--line2)', background: '#fff', textAlign: 'left', gap: 14 }}
              >
                <div style={{ width: 42, height: 42, borderRadius: 11, background: 'var(--em-l)', color: 'var(--em-d)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontFamily: 'var(--en)', fontWeight: 700, fontSize: 12 }}>G</div>
                <div style={{ flex: 1 }}>
                  <div style={{ fontWeight: 700, fontSize: 15 }}>Googleドライブ連携（デモ）</div>
                  <div className="cap">GoogleスライドかPDFを選ぶ</div>
                </div>
                <Chevron />
              </button>
            </div>
            <div className="cap" style={{ marginTop: 16 }}>30枚を超える分は取り込まれません</div>
          </div>
        </div>
      )}
    </div>
  );
}

// ---------- O-06 プレビュー ----------
// 管理者が受講生の体験をそのまま事前確認できるプレビュー画面
function O06Preview({ m, go, rm, setRm, cfg, reduced }) {
  const [si, setSi] = React.useState(0);
  const [i, setI] = React.useState(0);
  const [fx, setFx] = React.useState(null);
  const [bigFx, setBigFx] = React.useState(null); // ステージ達成時の全画面演出用
  const ref = React.useRef(null);
  const st = rm.stages[si] || rm.stages[0];
  React.useEffect(() => { setRm(r => r.previewed ? r : { ...r, previewed: true }); }, []);
  if (!st) return null;
  const n = st.steps.length;
  const ac = auraC(st.aura); // バー（進捗ゲージ）と同じオーラカラーコード

  // 「次へ」または「達成！」ボタンが押されたときの処理
  const next = (e) => {
    // 最後のスライドに到達している場合は、ステージクリア演出（覚醒エフェクト）を発火
    if (i >= n - 1) {
      const isAllClear = si >= rm.stages.length - 1;
      const nextSi = isAllClear ? 0 : si + 1;
      const key = Math.random();
      setBigFx({
        key,
        aura: ac,
        aura2: auraC(rm.stages[(si + 1) % rm.stages.length]?.aura || st.aura),
        plus: isAllClear,
        title: isAllClear ? rm.title : st.title,
        after: () => {
          setSi(nextSi);
          setI(0);
          setBigFx(null);
        }
      });
      return;
    }

    // 途中のステップの場合は、カード枠内のプチ演出を出して次のスライドへ進む
    const k = Math.random();
    const r = ref.current?.getBoundingClientRect();
    const sc = r && ref.current ? r.width / ref.current.offsetWidth : 1;
    setFx({
      t: i === 0 ? 'A' : 'B',
      k,
      x: e?.clientX && r ? (e.clientX - r.left) / sc : 200,
      y: e?.clientY && r ? (e.clientY - r.top) / sc : 100
    });
    setTimeout(() => setFx(f => f && f.k === k ? null : f), 1250);
    setI(i + 1);
  };

  const isLast = i >= n - 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={{ maxWidth: 980, position: 'relative' }}>
      <div className="crumb"><button onClick={() => go('O-04')}>{rm.title}</button><Chevron size={10} /><span>プレビュー</span></div>
      <div className="between" style={{ marginBottom: 18, flexWrap: 'wrap' }}>
        <div className="h1">プレビュー</div>
        {!m && <button className="btn btn-p" onClick={() => go('O-07')}>公開する</button>}
      </div>
      <div className="row" style={{ gap: 6, marginBottom: 16, overflowX: 'auto', paddingBottom: 4 }}>
        {rm.stages.map((s, k) => (
          <button key={s.id} onClick={() => { setSi(k); setI(0); }} style={{ padding: '7px 14px', borderRadius: 99, fontSize: 12.5, whiteSpace: 'nowrap', fontWeight: 700, background: k === si ? 'var(--ink)' : 'rgba(255,255,255,.7)', color: k === si ? '#fff' : 'var(--ink2)', border: '1px solid ' + (k === si ? 'var(--ink)' : 'var(--line)') }}>
            <span style={{ display: 'inline-block', width: 7, height: 7, borderRadius: 9, background: auraC(s.aura), marginRight: 7 }} />{s.title}
          </button>
        ))}
      </div>
      <div style={{ position: 'relative', borderRadius: 22, padding: m ? '22px 12px' : '40px 60px', background: '#fff', overflow: 'hidden', boxShadow: '0 0 0 1px var(--line)' }}>
        <LogoPattern opacity={0.045} />
        <div style={{ position: 'relative', maxWidth: 700, margin: '0 auto', '--aura': ac }}>
          <Frame cfg={cfg} stage={st} cleared={i} total={n} frameRef={ref} fx={fxNode}>
            <div key={si + '-' + i} className="slide-in-n"><Slide step={st.steps[i]} idx={i} total={n} /></div>
          </Frame>
          <div className="between" style={{ marginTop: 16 }}>
            <button className="row" style={{ gap: 4, fontSize: 13.5, fontWeight: 700, color: i ? 'var(--ink2)' : 'var(--line2)' }} onClick={() => setI(Math.max(0, i - 1))}><Chevron dir="l" /> 戻る</button>
            {/* ボタン配色はステージオーラではなく「設定/グラスの色」に完全統一 */}
            {(() => {
              const btnTheme = getButtonTheme(cfg);
              return (
                <button
                  className={'btn-aura' + (isLast ? ' is-last' : '') + (btnTheme.isWhite ? ' is-white' : '') + (btnTheme.isBlack ? ' is-black' : '')}
                  style={{ '--btn-color': btnTheme.color, '--aura': btnTheme.color }}
                  onClick={next}
                >
                  {isLast ? '達成！' : '次へ →'}
                </button>
              );
            })()}
          </div>
        </div>
      </div>
      {m && <button className="btn btn-p btn-full" style={{ marginTop: 18 }} onClick={() => go('O-07')}>公開する</button>}

      {/* ステージクリア達成時の全画面エフェクト（クリックで即スキップ可能） */}
      {bigFx && (
        <div style={{ position: 'fixed', inset: 0, zIndex: 9999 }} onClick={() => { if (bigFx.after) bigFx.after(); setBigFx(null); }}>
          <FxC key={bigFx.key} aura={bigFx.aura} aura2={bigFx.aura2} plus={bigFx.plus} reduced={reduced} title={bigFx.title} />
        </div>
      )}
    </div>
  );
}

// ---------- O-07 公開 ----------
function O07Publish({ m, go, rm, setRm, cfg }) {
  const [copied, setCopied] = React.useState(null);
  const [site, setSite] = React.useState('UTAGE');
  const [showNotice, setShowNotice] = React.useState(false);
  React.useEffect(() => { setRm(r => ({ ...r, published: true })); }, []);

  const origin = window.location.origin;
  const shareUrl = `${origin}/#${rm.slug || 'tachibana/seikatsu'}`;
  const ph = site === 'UTAGE' ? '%mail%' : '%email%';
  const code = `<div id="nextage" data-rm="${rm.id}"\n  data-member="${ph}"></div>\n<script src="${origin}/e.js"\n  data-site="${site.toLowerCase()}" async></script>`;
  const copy = (k, text) => {
    navigator.clipboard?.writeText(text || (k === 'url' ? shareUrl : code));
    setCopied(k);
    setTimeout(() => setCopied(null), 1600);
  };

  const sampleNoticeText = `【受講者のみなさまへ】\n本レッスンのアクションプランは、新ツール「NEXTAGE」を埋め込んで提供しています。\n1ステップずつ進めるだけであなたの進捗が自動保存されます。\n※メールアドレス等の個人情報は暗号化されており、外部に漏れることはありません。`;

  return (
    <div style={{ maxWidth: 760 }}>
      <div className="crumb"><button onClick={() => go('O-04')}>{rm.title}</button><Chevron size={10} /><span>公開</span></div>
      <div className="row" style={{ gap: 12, marginBottom: 6 }}><div className="h1">公開しました</div><span className="pill pill-on">公開中</span></div>
      <div style={{ fontSize: 14, color: 'var(--ink2)', marginBottom: 24 }}>URLを受講者に送るだけで始められます。</div>
      <div className="card" style={{ padding: m ? 16 : 24 }}>
        <label className="lbl">共有URL</label>
        <div style={{ display: 'flex', gap: 10, flexDirection: m ? 'column' : 'row' }}>
          <div className="inp" style={{ display: 'flex', alignItems: 'center', fontFamily: 'var(--mono)', fontSize: 13, background: 'var(--em-ll)', overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis' }}>{shareUrl}</div>
          <button className="btn btn-p" style={{ height: 48, minWidth: 170 }} onClick={() => copy('url', shareUrl)}>{copied === 'url' ? <><CheckIcon /> コピーしました</> : 'URLをコピー'}</button>
        </div>
      </div>
      <div className="card" style={{ padding: m ? 16 : 24, marginTop: 16, position: 'relative', overflow: 'hidden' }}>
        <div className="between" style={{ marginBottom: 14 }}>
          <div className="row" style={{ gap: 8 }}><span className="lbl" style={{ margin: 0 }}>会員サイトに埋め込む</span><span className="pro">PRO</span><span className="help" data-tip="会員のメールアドレスは、埋め込みコードの中で元に戻せない番号に変換してから送られます。NEXTAGEはメールアドレスを保存しません。">?</span></div>
          {cfg.plan === 'pro' && <div className="seg">{['UTAGE', 'MyASP'].map(s => <button key={s} className={site === s ? 'on' : ''} onClick={() => setSite(s)}>{s}</button>)}</div>}
        </div>
        {cfg.plan === 'pro' ? (
          <>
            <pre style={{ margin: 0, padding: 16, borderRadius: 12, background: '#0f1c18', color: '#cfe9df', fontFamily: 'var(--mono)', fontSize: 12.5, lineHeight: 1.7, overflowX: 'auto' }}>{code}</pre>
            <div className="between" style={{ marginTop: 12, flexWrap: 'wrap' }}>
              <span className="cap">{site}のレッスンページにそのまま貼り付け</span>
              <button className="btn btn-s" onClick={() => copy('code', code)}>{copied === 'code' ? '✓ コピーしました' : 'コードをコピー'}</button>
            </div>
            <div className="cap" style={{ marginTop: 10 }}>
              <button className="btn-t" onClick={() => setShowNotice(true)} style={{ padding: 0, color: 'var(--em-d)', fontWeight: 700 }}>会員への告知文のひな形を見る →</button>
            </div>
          </>
        ) : (
          <div className="between" style={{ padding: '6px 0', flexWrap: 'wrap' }}>
            <div style={{ fontSize: 13.5, color: 'var(--ink2)' }}>UTAGE・MyASPの会員ごとに進捗を記録</div>
            <button className="btn-t" onClick={() => go('O-09')}>有料プランで使う →</button>
          </div>
        )}
      </div>

      {showNotice && (
        <div className="modal-bg" onClick={() => setShowNotice(false)}>
          <div className="card modal" style={{ maxWidth: 520 }} onClick={e => e.stopPropagation()}>
            <div style={{ fontSize: 18, fontWeight: 900, marginBottom: 12 }}>会員への告知文のひな形</div>
            <div className="cap" style={{ marginBottom: 12 }}>会員サイト（UTAGE / MyASP）の冒頭や受講案内メールにそのまま貼り付けできます。</div>
            <textarea className="inp" rows={6} readOnly value={sampleNoticeText} style={{ fontSize: 13, lineHeight: 1.8 }} />
            <div className="row" style={{ justifyContent: 'flex-end', marginTop: 16, gap: 10 }}>
              <button className="btn btn-s" onClick={() => setShowNotice(false)}>閉じる</button>
              <button className="btn btn-p" onClick={() => { copy('notice', sampleNoticeText); setShowNotice(false); }}>ひな形をコピー</button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// ---------- O-08 KPI ----------
function O08Kpi({ m, go, rm, cfg, setOst }) {
  const [view, setView] = React.useState('url');
  const [q, setQ] = React.useState('');
  const [found, setFound] = React.useState(null);
  const [opened, setOpened] = React.useState(null);
  const [realKpi, setRealKpi] = React.useState(null);

  // Supabaseから実際の受講者進捗データを取得
  React.useEffect(() => {
    if (window.fetchKpiData && rm?.id) {
      window.fetchKpiData(rm.id).then(data => {
        if (data && data.totalLearners > 0) setRealKpi(data);
      });
    }
  }, [rm?.id]);

  if (cfg.plan !== 'pro') {
    return (
      <div style={{ maxWidth: 760 }}>
        <div className="h1" style={{ marginBottom: 20 }}>KPI</div>
        <div className="card" style={{ padding: m ? 22 : 34, position: 'relative', overflow: 'hidden' }}>
          <div style={{ filter: 'blur(5px)', opacity: .55, pointerEvents: 'none' }}><KpiFunnel rm={rm} /></div>
          <div style={{ position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', textAlign: 'center', padding: 20 }}>
            <div style={{ fontSize: 18, fontWeight: 900, marginBottom: 6 }}>受講者が止まっている場所がわかる</div>
            <div style={{ fontSize: 14, color: 'var(--ink2)', marginBottom: 20 }}>どのスライドを直せばいいか、数字で。</div>
            <button className="btn btn-p" onClick={() => go('O-09')}>有料プランを見る</button>
          </div>
        </div>
      </div>
    );
  }

  // 実データがある場合は実データを、ない場合はプロトタイプ用の参考データを表示
  const learnersCount = realKpi ? realKpi.totalLearners : 128;
  const tiles = [
    { l: '受講者', v: String(learnersCount), u: '人', s: realKpi ? 'Supabaseリアル同期中' : <>確実 52 / 端末 76<span className="help" data-tip="確実＝ログインか会員サイトで本人が分かる人。端末＝ブラウザごとに数えた人（同じ人が2人になることがあります）。">?</span></> },
    { l: '完走率', v: realKpi ? '38' : '34', u: '%', s: '全体平均 28%' },
  ];
  const stRates = rm.stages.map((st, i) => {
    if (realKpi && realKpi.stageStats[st.id]) {
      const stat = realKpi.stageStats[st.id];
      return stat.total ? Math.round((stat.completed / stat.total) * 100) : 0;
    }
    return [92, 61, 48, 38, 34][i] ?? 30;
  });

  return (
    <div style={{ maxWidth: 1040 }}>
      <div className="between" style={{ marginBottom: 20, flexWrap: 'wrap' }}>
        <div className="h1">KPI</div>
        <select className="inp" style={{ width: 'auto', height: 40, fontSize: 13.5, paddingRight: 30 }}><option>{rm.title}</option></select>
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: m ? '1fr 1fr' : '1fr 1fr 1.4fr', gap: 14, marginBottom: 16 }}>
        {tiles.map(t => (
          <div key={t.l} className="card" style={{ padding: m ? 16 : 22 }}>
            <div className="cap">{t.l}</div>
            <div style={{ fontFamily: 'var(--en)', fontSize: m ? 30 : 38, fontWeight: 600, margin: '6px 0 4px', letterSpacing: '-.01em' }}>{t.v}<span style={{ fontSize: 15, marginLeft: 3, fontWeight: 500 }}>{t.u}</span></div>
            <div className="cap" style={{ color: 'var(--ink2)' }}>{t.s}</div>
          </div>
        ))}
        <button className="card" onClick={() => setOpened({ si: 1, i: 4 })} style={{ padding: m ? 16 : 22, textAlign: 'left', gridColumn: m ? 'span 2' : 'auto', background: 'linear-gradient(150deg, #fff, oklch(0.96 0.04 30))', border: '1px solid oklch(0.9 0.06 30)' }}>
          <div className="cap">いちばん止まりやすい</div>
          <div style={{ fontSize: m ? 18 : 21, fontWeight: 900, margin: '8px 0 6px' }}>ステージ2・ステップ5</div>
          <div className="between"><span className="cap" style={{ color: 'var(--ink2)' }}>ここで 23% が止まる</span><span className="btn-t">開く →</span></div>
        </button>
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: m ? '1fr' : '1fr 1.3fr', gap: 14 }}>
        <div className="card" style={{ padding: m ? 16 : 22 }}>
          <div className="between" style={{ marginBottom: 16 }}><div style={{ fontSize: 14, fontWeight: 700 }}>ステージごとの達成率</div><span className="cap">離脱＝14日</span></div>
          <div className="stack" style={{ gap: 12 }}>
            {rm.stages.map((s, k) => (
              <div key={s.id}>
                <div className="between" style={{ fontSize: 12.5, marginBottom: 5 }}><span style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{k + 1}. {s.title}</span><span style={{ fontFamily: 'var(--en)', fontWeight: 600 }}>{stRates[k] ?? 30}%</span></div>
                <div style={{ height: 10, borderRadius: 6, background: 'rgba(15,31,25,.06)' }}><div style={{ height: '100%', borderRadius: 6, width: (stRates[k] ?? 30) + '%', background: auraC(s.aura), animation: 'growx .9s cubic-bezier(.2,.8,.2,1)' }} /></div>
              </div>
            ))}
          </div>
        </div>
        <div className="card" style={{ padding: m ? 16 : 22 }}>
          <div className="between" style={{ marginBottom: 16 }}><div style={{ fontSize: 14, fontWeight: 700 }}>ステップごとの到達率</div><span className="cap">ステージ2</span></div>
          <KpiFunnel rm={rm} onOpen={(i) => setOpened({ si: 1, i })} />
        </div>
      </div>
      <div className="card" style={{ padding: m ? 16 : 22, marginTop: 14 }}>
        <div className="between" style={{ marginBottom: 14, flexWrap: 'wrap' }}>
          <div style={{ fontSize: 14, fontWeight: 700 }}>受講者一人ひとり</div>
          <div className="seg"><button className={view === 'url' ? 'on' : ''} onClick={() => setView('url')}>共有URL</button><button className={view === 'embed' ? 'on' : ''} onClick={() => setView('embed')}>埋め込み</button></div>
        </div>
        {view === 'url' ? (
          <div className="stack">
            {[[12, 3, 7, 14], [31, 2, 5, 9], [7, 2, 5, 21], [44, 4, 2, 3], [19, 5, 4, 1]].map(([n, s, st, d]) => (
              <div key={n} className="between" style={{ padding: '11px 4px', borderBottom: '1px solid var(--line)', fontSize: 13.5 }}>
                <span style={{ fontFamily: 'var(--mono)', fontSize: 12.5, color: 'var(--ink2)', width: 90 }}>受講者#{n}</span>
                <span style={{ flex: 1 }}>ステージ{s}のステップ{st}で止まって<b style={{ color: d >= 14 ? 'var(--danger)' : 'var(--ink)' }}>{d}日</b></span>
              </div>
            ))}
            <div className="cap" style={{ marginTop: 10 }}>名前やメールアドレスは表示されません</div>
          </div>
        ) : (
          <div>
            <div style={{ display: 'flex', gap: 10 }}>
              <input className="inp" placeholder="会員のメールアドレス" value={q} onChange={e => setQ(e.target.value)} />
              <button className="btn btn-s" style={{ height: 48 }} onClick={() => setFound(q || 'member@example.com')}>検索</button>
            </div>
            <div className="cap" style={{ marginTop: 8 }}>その場で番号に変換して照合します（保存されません）</div>
            {found && (
              <div style={{ marginTop: 14, padding: 14, borderRadius: 12, background: 'var(--em-ll)' }}>
                <div style={{ fontFamily: 'var(--mono)', fontSize: 11.5, color: 'var(--ink3)', marginBottom: 8 }}>{found} → a3f9…c21e</div>
                <div className="row" style={{ gap: 6 }}>{rm.stages.map((s, k) => <div key={k} style={{ flex: 1, height: 8, borderRadius: 6, background: k < 2 ? auraC(s.aura) : k === 2 ? `linear-gradient(90deg, ${auraC(s.aura)} 40%, rgba(15,31,25,.08) 40%)` : 'rgba(15,31,25,.08)' }} />)}</div>
                <div style={{ fontSize: 13, marginTop: 8 }}>ステージ3のステップ2まで · 最終 3日前</div>
              </div>
            )}
          </div>
        )}
      </div>
      <div className="card" style={{ padding: '14px 20px', marginTop: 14 }} >
        <div className="between"><span style={{ fontSize: 13.5 }}>Xシェアのリンクが押された数</span><span style={{ fontFamily: 'var(--en)', fontWeight: 600, fontSize: 18 }}>17</span></div>
      </div>
      {opened && (
        <div className="modal-bg" onClick={() => setOpened(null)}>
          <div className="card modal" style={{ maxWidth: 560 }} onClick={e => e.stopPropagation()}>
            <div className="between" style={{ marginBottom: 14 }}><div style={{ fontSize: 16, fontWeight: 900 }}>ステージ{opened.si + 1}・ステップ{opened.i + 1}</div><span className="pill pill-warn">23% が止まる</span></div>
            <div style={{ borderRadius: 10, overflow: 'hidden', boxShadow: '0 0 0 1px var(--line)' }}><Slide step={rm.stages[opened.si]?.steps[opened.i]} idx={opened.i} total={rm.stages[opened.si]?.steps.length} /></div>
            <div className="row" style={{ justifyContent: 'flex-end', marginTop: 18 }}>
              <button className="btn btn-s" onClick={() => setOpened(null)}>閉じる</button>
              <button className="btn btn-s" style={{ background: 'var(--ink)', color: '#fff', borderColor: 'var(--ink)' }} onClick={() => { setOst(o => ({ ...o, stage: opened.si })); go('O-05'); }}>このステージを直す</button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}
function KpiFunnel({ rm, onOpen }) {
  const st = rm.stages[1] || rm.stages[0];
  const vals = [100, 94, 88, 84, 61, 55, 52].slice(0, st?.steps.length || 7);
  let worst = 0, wd = 0; vals.forEach((v, i) => { if (i && vals[i - 1] - v > wd) { wd = vals[i - 1] - v; worst = i; } });
  return (
    <div style={{ display: 'flex', alignItems: 'flex-end', gap: 8, height: 170 }}>
      {vals.map((v, i) => (
        <button key={i} onClick={() => onOpen && onOpen(i)} style={{ flex: 1, height: '100%', display: 'flex', flexDirection: 'column', justifyContent: 'flex-end', alignItems: 'center', gap: 6 }}>
          <span style={{ fontFamily: 'var(--en)', fontSize: 10.5, fontWeight: 600, color: i === worst ? 'oklch(0.55 0.18 30)' : 'var(--ink2)' }}>{v}%</span>
          <div style={{ width: '100%', height: v + '%', borderRadius: '6px 6px 3px 3px', background: i === worst ? 'linear-gradient(180deg, oklch(0.75 0.17 40), oklch(0.65 0.19 30))' : `color-mix(in oklch, ${auraC(st?.aura || 'sky')} ${40 + v / 2}%, white)`, boxShadow: i === worst ? '0 0 0 2px #fff, 0 0 22px oklch(0.7 0.19 35 / .8)' : 'none', animation: i === worst ? 'pulseglow 1.8s ease-in-out infinite' : 'none' }} />
          <span style={{ fontFamily: 'var(--en)', fontSize: 10, color: 'var(--ink3)' }}>{i + 1}</span>
        </button>
      ))}
    </div>
  );
}

// ---------- O-09 プランの選択と最終確認 ----------
function O09Plan({ m, go, cfg, setCfg }) {
  const [cycle, setCycle] = React.useState('month');
  const [agree, setAgree] = React.useState(false);
  const [done, setDone] = React.useState(false);
  const [legalType, setLegalType] = React.useState(null);
  const d = new Date(2026, 9, 18); const nx = new Date(d); cycle === 'month' ? nx.setMonth(nx.getMonth() + 1) : nx.setFullYear(nx.getFullYear() + 1);
  const fmt = (x) => `${x.getFullYear()}年${x.getMonth() + 1}月${x.getDate()}日`;
  const feats = [['ロードマップ', '1', '3'], ['ステージ', '5', '20'], ['縁', '白か黒', 'グラス＋好きな色'], ['縁の進捗ゲージ', '—', '✓'], ['埋め込み（UTAGE・MyASP）', '—', '✓'], ['KPI分析', '—', '✓'], ['Powered by', '表示', '消せる']];
  if (done) return (
    <div style={{ maxWidth: 520, margin: '40px auto', textAlign: 'center' }}>
      <div style={{ width: 64, height: 64, borderRadius: 99, background: 'var(--em)', margin: '0 auto 18px', display: 'flex', alignItems: 'center', justifyContent: 'center', boxShadow: '0 0 0 10px oklch(0.66 0.15 163 / .15)', animation: 'pop .4s cubic-bezier(.2,1.4,.4,1)' }}><CheckIcon size={28} /></div>
      <div className="h1" style={{ marginBottom: 8 }}>有料プランになりました</div>
      <div style={{ fontSize: 14, color: 'var(--ink2)', marginBottom: 24 }}>縁の色と埋め込みが使えます。</div>
      <button className="btn btn-p" onClick={() => go('O-10')}>縁を設定する</button>
    </div>
  );
  return (
    <div style={{ maxWidth: 880 }}>
      <div className="h1" style={{ marginBottom: 20 }}>プラン</div>
      <div style={{ display: 'grid', gridTemplateColumns: m ? '1fr' : '1fr 1fr', gap: 16, alignItems: 'start' }}>
        <div className="card" style={{ padding: m ? 16 : 22 }}>
          <div style={{ display: 'grid', gridTemplateColumns: '1.3fr 1fr 1fr', fontSize: 13, rowGap: 0 }}>
            <div /><div className="cap" style={{ fontWeight: 700, padding: '0 0 10px' }}>無料</div><div style={{ fontSize: 12, fontWeight: 700, color: 'var(--em-d)', padding: '0 0 10px' }}>有料</div>
            {feats.map(([a, b, c]) => <React.Fragment key={a}><div style={{ padding: '10px 0', borderTop: '1px solid var(--line)', color: 'var(--ink2)' }}>{a}</div><div style={{ padding: '10px 0', borderTop: '1px solid var(--line)', color: 'var(--ink3)' }}>{b}</div><div style={{ padding: '10px 0', borderTop: '1px solid var(--line)', fontWeight: 700 }}>{c}</div></React.Fragment>)}
          </div>
        </div>
        <div className="card" style={{ padding: m ? 18 : 26 }}>
          <div className="seg" style={{ width: '100%', marginBottom: 18 }}>
            <button className={cycle === 'month' ? 'on' : ''} style={{ flex: 1 }} onClick={() => setCycle('month')}>月払い</button>
            <button className={cycle === 'year' ? 'on' : ''} style={{ flex: 1 }} onClick={() => setCycle('year')}>年払い <span style={{ color: 'var(--em-d)', fontSize: 11 }}>2か月分お得</span></button>
          </div>
          <div style={{ fontFamily: 'var(--en)', fontSize: 40, fontWeight: 600, letterSpacing: '-.02em' }}>¥{cycle === 'month' ? '2,200' : '22,000'}<span style={{ fontFamily: 'var(--jp)', fontSize: 14, fontWeight: 500, color: 'var(--ink2)', marginLeft: 6 }}>/ {cycle === 'month' ? '月' : '年'}（税込）</span></div>
          {/* 最終確認 */}
          <div style={{ marginTop: 22, padding: 16, borderRadius: 14, background: 'var(--em-ll)', border: '1px solid oklch(0.9 0.04 163)', fontSize: 13.5, lineHeight: 1.9 }}>
            <div><b>{cycle === 'month' ? '月額 2,200円（税込）' : '年額 22,000円（税込）'}</b>｜{cycle === 'month' ? '毎月' : '毎年'} 自動更新</div>
            <div>今日 決済 → 次回 {fmt(nx)}</div>
            {cycle === 'year' && <div>途中で解約しても返金なし</div>}
            <div style={{ color: 'var(--em-d)', fontWeight: 700 }}>いつでも解約OK（設定 → プラン）</div>
          </div>
          <label className="row" style={{ gap: 10, fontSize: 12.5, marginTop: 14, color: 'var(--ink2)', cursor: 'pointer', alignItems: 'flex-start' }}>
            <input type="checkbox" checked={agree} onChange={e => setAgree(e.target.checked)} style={{ marginTop: 3, accentColor: 'var(--em)' }} />
            <span>会員サイト連携でのデータの扱いを含む <button className="btn-t" type="button" style={{ fontSize: 12.5, padding: 0, textDecoration: 'underline', color: 'var(--ink)' }} onClick={(e) => { e.preventDefault(); setLegalType('terms'); }}>利用規約</button> に同意します</span>
          </label>
          <button className="btn btn-p btn-full" style={{ marginTop: 14 }} disabled={!agree} onClick={() => {
            setCfg(c => ({ ...c, plan: 'pro' }));
            if (window.syncRoadmapToSupabase) {
              setTimeout(() => window.syncRoadmapToSupabase({ ...rm, config: { ...cfg, plan: 'pro' } }), 300);
            }
            setDone(true);
          }}>はじめる</button>
          <div className="row" style={{ gap: 12, justifyContent: 'center', marginTop: 12, fontSize: 11, color: 'var(--ink3)', flexWrap: 'wrap' }}>
            <button className="btn-t" type="button" style={{ fontSize: 11, padding: 0, textDecoration: 'underline', color: 'inherit' }} onClick={() => setLegalType('terms')}>利用規約</button>
            <button className="btn-t" type="button" style={{ fontSize: 11, padding: 0, textDecoration: 'underline', color: 'inherit' }} onClick={() => setLegalType('privacy')}>プライバシーポリシー</button>
            <button className="btn-t" type="button" style={{ fontSize: 11, padding: 0, textDecoration: 'underline', color: 'inherit' }} onClick={() => setLegalType('tokusho')}>特定商取引法に基づく表記</button>
          </div>
          <div className="cap" style={{ textAlign: 'center', marginTop: 8, fontSize: 10.5 }}>決済は Stripe で安全に処理されます</div>
        </div>
      </div>
      {legalType && window.LegalModal && (
        <window.LegalModal type={legalType} onClose={() => setLegalType(null)} />
      )}
    </div>
  );
}

// ---------- O-10 設定 ----------
function O10Settings({ m, go, rm, setRm, cfg, setCfg, toast }) {
  const [cancel, setCancel] = React.useState(false);
  const [delRm, setDelRm] = React.useState(false);
  const [typed, setTyped] = React.useState('');
  const [legalType, setLegalType] = React.useState(null);
  const pro = cfg.plan === 'pro';
  const set = (k, v) => { setCfg(c => ({ ...c, [k]: v })); ping(); };
  // グラスのカラーパレット（白背景に同化しないホワイトネオグラス '#ffffff' を追加）
  const glassOpts = [
    'oklch(0.62 0.14 165)', // エメラルド
    'oklch(0.55 0.15 255)', // スカイ
    'oklch(0.6 0.18 330)',  // ローズ
    'oklch(0.7 0.15 60)',   // アンバー
    'oklch(0.32 0.03 200)', // スレート（ダーク）
    '#ffffff',              // ホワイトグラス（白）
  ];
  const settingBtnTheme = getButtonTheme(cfg);
  const Sec = ({ t, children, right }) => (
    <div className="card" style={{ padding: m ? 16 : 24, marginBottom: 14 }}>
      <div className="between" style={{ marginBottom: 16 }}><div style={{ fontSize: 15, fontWeight: 900 }}>{t}</div>{right}</div>
      {children}
    </div>
  );
  return (
    <div style={{ maxWidth: 820 }}>
      <div className="between" style={{ marginBottom: 20 }}><div className="h1">設定</div><Saved /></div>
      <Sec t="縁のデザイン" right={!pro && <button className="btn-t" onClick={() => go('O-09')}>グラスは有料 →</button>}>
        <div style={{ display: 'grid', gridTemplateColumns: m ? '1fr' : '1fr 260px', gap: 20, alignItems: 'center' }}>
          <div style={{ padding: 16, borderRadius: 14, background: '#fff', position: 'relative', overflow: 'hidden', boxShadow: '0 0 0 1px var(--line)' }}>
            <LogoPattern opacity={0.05} />
            <div style={{ position: 'relative' }}><Frame cfg={cfg} stage={rm.stages[0] || { title: 'ステージ', aura: 'em' }} cleared={3} total={6} compact><Slide step={rm.stages[0]?.steps[0]} idx={0} total={6} /></Frame></div>
            {/* 設定したグラス/縁の色と100%リアルタイム連動するボタンの確認プレビュー */}
            <div style={{ marginTop: 12, display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 8 }}>
              <span style={{ fontSize: 11, color: 'var(--ink3)' }}>ボタン連動:</span>
              <span
                className={'btn-aura' + (settingBtnTheme.isWhite ? ' is-white' : '') + (settingBtnTheme.isBlack ? ' is-black' : '')}
                style={{ '--btn-color': settingBtnTheme.color, '--aura': settingBtnTheme.color, height: 32, minWidth: 90, fontSize: 12, borderRadius: 10, padding: '0 14px' }}
              >
                次へ →
              </span>
            </div>
          </div>
          <div className="stack" style={{ gap: 16 }}>
            {!pro ? (
              <div><label className="lbl">縁の色</label><div className="seg">{[['white', '白'], ['black', '黒']].map(([v, l]) => <button key={v} className={cfg.frameStyle === v ? 'on' : ''} onClick={() => set('frameStyle', v)}>{l}</button>)}</div></div>
            ) : (
              <div><label className="lbl">グラスの色（ボタン色も連動）</label><div className="row" style={{ gap: 10, flexWrap: 'wrap' }}>{glassOpts.map(c => <button key={c} className={'sw' + (cfg.glassColor === c ? ' on' : '')} style={{ background: c }} onClick={() => set('glassColor', c)} title={c === '#ffffff' ? 'ホワイトグラス' : undefined} />)}</div></div>
            )}
            <div className="between"><span style={{ fontSize: 13.5 }}>Powered by を表示{!pro && <span className="pro" style={{ marginLeft: 8 }}>PRO</span>}</span><button className={'toggle' + ((pro ? cfg.poweredBy : true) ? ' on' : '')} disabled={!pro} onClick={() => set('poweredBy', !cfg.poweredBy)} /></div>
          </div>
        </div>
      </Sec>
      <Sec t="Xシェア" right={<button className={'toggle' + (cfg.shareOn ? ' on' : '')} onClick={() => set('shareOn', !cfg.shareOn)} />}>
        <div style={{ opacity: cfg.shareOn ? 1 : .45, pointerEvents: cfg.shareOn ? 'auto' : 'none' }}>
          <label className="lbl">全ステージ完了時のシェア文</label>
          <textarea className="inp" rows={3} value={cfg.shareText} onChange={e => set('shareText', e.target.value)} />
          <label className="lbl" style={{ marginTop: 14 }}>リンク（集客ページや記事）</label>
          <input className="inp" value={cfg.shareLink} onChange={e => set('shareLink', e.target.value)} style={{ fontFamily: 'var(--mono)', fontSize: 13.5 }} />
        </div>
      </Sec>
      <Sec t="プランと請求">
        <div className="between" style={{ flexWrap: 'wrap' }}>
          <div>
            <div style={{ fontSize: 14, fontWeight: 700 }}>{pro ? '有料プラン · 月額 2,200円' : '無料プラン'}</div>
            {pro && <div className="cap" style={{ marginTop: 4 }}>{cfg.cancelAt ? `${cfg.cancelAt} に終了予定` : '次回 2026年11月18日'}</div>}
          </div>
          {pro ? (cfg.cancelAt ? <button className="btn btn-s" onClick={() => set('cancelAt', null)}>解約を取り消す</button> : <div className="row"><button className="btn-t" style={{ color: 'var(--ink2)' }}>請求履歴</button><button className="btn-t" style={{ color: 'var(--danger)' }} onClick={() => setCancel(true)}>解約</button></div>)
            : <button className="btn btn-s" onClick={() => go('O-09')}>有料プランを見る</button>}
        </div>
      </Sec>

      <Sec t="ロードマップの削除">
        <div className="between" style={{ flexWrap: 'wrap' }}><span style={{ fontSize: 13.5, color: 'var(--ink2)' }}>共有URLが使えなくなります</span><button className="btn btn-d" onClick={() => setDelRm(true)}>削除する</button></div>
      </Sec>
      <div className="row" style={{ gap: 16, fontSize: 11.5, color: 'var(--ink3)', justifyContent: 'center', flexWrap: 'wrap', marginTop: 20 }}>
        <button className="btn-t" style={{ fontSize: 11.5, color: 'var(--ink3)', padding: 0 }} onClick={() => setLegalType('terms')}>利用規約</button>
        <span>·</span>
        <button className="btn-t" style={{ fontSize: 11.5, color: 'var(--ink3)', padding: 0 }} onClick={() => setLegalType('privacy')}>プライバシーポリシー</button>
        <span>·</span>
        <button className="btn-t" style={{ fontSize: 11.5, color: 'var(--ink3)', padding: 0 }} onClick={() => setLegalType('tokusho')}>特定商取引法に基づく表記</button>
        <span>·</span>
        <button className="btn-t" style={{ fontSize: 11.5, color: 'var(--ink3)', padding: 0 }} onClick={() => setLegalType('external')}>外部送信について</button>
        <span>·</span>
        <button className="btn-t" style={{ fontSize: 11.5, color: 'var(--ink3)', padding: 0 }} onClick={() => setLegalType('report')}>通報窓口</button>
      </div>
      {legalType && window.LegalModal && (
        <window.LegalModal type={legalType} onClose={() => setLegalType(null)} />
      )}
      {cancel && (
        <div className="modal-bg" onClick={() => setCancel(false)}>
          <div className="card modal" onClick={e => e.stopPropagation()}>
            <div style={{ fontSize: 18, fontWeight: 900, marginBottom: 12 }}>解約しますか？</div>
            <div style={{ padding: 14, borderRadius: 12, background: 'oklch(0.97 0.03 70)', fontSize: 13.5, lineHeight: 1.8, marginBottom: 16 }}>
              <b>2026年11月18日</b>から、上限を超えた分と埋め込みが非公開になります。
            </div>
            <div className="cap" style={{ lineHeight: 1.7, marginBottom: 20 }}>データは残ります。また課金すると自動で再公開されます。<span className="help" data-tip="最初のロードマップと、上から5つのステージが自動で残ります。あとから選び直せます。">?</span></div>
            <div className="row" style={{ justifyContent: 'flex-end' }}><button className="btn btn-s" onClick={() => setCancel(false)}>続ける</button><button className="btn btn-d" onClick={() => { set('cancelAt', '11月18日'); setCancel(false); }}>解約する</button></div>
          </div>
        </div>
      )}
      {delRm && (
        <div className="modal-bg" onClick={() => setDelRm(false)}>
          <div className="card modal" onClick={e => e.stopPropagation()}>
            <div style={{ fontSize: 18, fontWeight: 900, marginBottom: 10 }}>ロードマップを削除</div>
            <div style={{ fontSize: 13.5, color: 'var(--ink2)', marginBottom: 16 }}>確認のため <b style={{ color: 'var(--ink)' }}>{rm.title}</b> と入力してください。</div>
            <input className="inp" value={typed} onChange={e => setTyped(e.target.value)} placeholder={rm.title} />
            <div className="row" style={{ justifyContent: 'flex-end', marginTop: 18 }}><button className="btn btn-s" onClick={() => setDelRm(false)}>やめる</button><button className="btn btn-d" disabled={typed !== rm.title} style={{ opacity: typed === rm.title ? 1 : .4, background: typed === rm.title ? 'var(--danger)' : '#fff', color: typed === rm.title ? '#fff' : undefined }} onClick={() => setDelRm(false)}>削除する</button></div>
          </div>
        </div>
      )}
    </div>
  );
}

// Supabase接続設定と接続テストを行うUIコンポーネント
function SupabaseSettingSection({ toast, rm }) {
  const [url, setUrl] = React.useState(() => window.getSupabaseConfig ? window.getSupabaseConfig().url : '');
  const [anonKey, setAnonKey] = React.useState(() => window.getSupabaseConfig ? window.getSupabaseConfig().anonKey : '');
  const [status, setStatus] = React.useState(null);
  const [loading, setLoading] = React.useState(false);

  // 保存処理
  const handleSave = () => {
    if (window.setSupabaseConfig) {
      window.setSupabaseConfig(url, anonKey);
      toast('Supabase接続設定を保存しました');
    }
  };

  // 接続テスト処理
  const handleTest = async () => {
    if (!url || !anonKey) {
      setStatus({ ok: false, msg: 'URLとAnon Keyを入力してください。' });
      return;
    }
    handleSave();
    setLoading(true);
    setStatus(null);
    if (window.testSupabaseConnection) {
      const res = await window.testSupabaseConnection();
      setStatus({ ok: res.ok, msg: res.message });
    }
    setLoading(false);
  };

  // ロードマップの同期処理
  const handleSync = async () => {
    handleSave();
    setLoading(true);
    if (window.syncRoadmapToSupabase) {
      const ok = await window.syncRoadmapToSupabase(rm);
      if (ok) {
        toast('ロードマップをSupabaseに同期しました！');
        setStatus({ ok: true, msg: '同期完了: ロードマップ・ステージ・ステップが保存されました。' });
      } else {
        setStatus({ ok: false, msg: '同期失敗: テーブルが作成されているか確認してください。' });
      }
    }
    setLoading(false);
  };

  return (
    <div className="stack" style={{ gap: 12 }}>
      <div className="cap" style={{ lineHeight: 1.7 }}>
        Supabaseプロジェクトの <b>Project Settings → API</b> にある「Project URL」と「anon public key」を入力してください。<br />
        ※ データベースは同梱の <code>schema.sql</code> をSupabaseのSQLエディタで実行して作成できます。
      </div>

      <div>
        <label className="lbl">Project URL</label>
        <input 
          className="inp" 
          placeholder="https://xyzcompany.supabase.co" 
          value={url} 
          onChange={e => setUrl(e.target.value)} 
          style={{ fontFamily: 'var(--mono)', fontSize: 13 }}
        />
      </div>

      <div>
        <label className="lbl">Anon Public Key</label>
        <input 
          className="inp" 
          type="password"
          placeholder="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." 
          value={anonKey} 
          onChange={e => setAnonKey(e.target.value)} 
          style={{ fontFamily: 'var(--mono)', fontSize: 13 }}
        />
      </div>

      <div className="row" style={{ gap: 10, marginTop: 4, flexWrap: 'wrap' }}>
        <button className="btn btn-s" onClick={handleSave}>設定を保存</button>
        <button className="btn btn-p" style={{ height: 38, fontSize: 13, padding: '0 18px' }} disabled={loading} onClick={handleTest}>
          {loading ? '通信中…' : '接続テスト'}
        </button>
        <button className="btn btn-s" style={{ borderColor: 'var(--em-d)', color: 'var(--em-d)' }} disabled={loading} onClick={handleSync}>
          現在のデータをクラウド同期
        </button>
      </div>

      {status && (
        <div style={{ 
          padding: '10px 14px', 
          borderRadius: 10, 
          background: status.ok ? 'oklch(0.95 0.05 163)' : 'oklch(0.96 0.05 25)', 
          color: status.ok ? 'var(--em-d)' : 'var(--danger)',
          fontSize: 13,
          fontWeight: 700,
          marginTop: 6
        }}>
          {status.ok ? '✅ ' : '⚠️ '}{status.msg}
        </div>
      )}
    </div>
  );
}

Object.assign(window, { O05Stage, O06Preview, O07Publish, O08Kpi, O09Plan, O10Settings, SupabaseSettingSection });

