// ===== NEXTAGE shared data + primitives =====
const AURAS = [
  { id: 'em', name: 'エメラルド', c: 'oklch(0.78 0.17 163)' },
  { id: 'sky', name: 'スカイ', c: 'oklch(0.75 0.15 235)' },
  { id: 'amber', name: 'アンバー', c: 'oklch(0.82 0.16 75)' },
  { id: 'rose', name: 'ローズ', c: 'oklch(0.72 0.19 355)' },
  { id: 'violet', name: 'バイオレット', c: 'oklch(0.68 0.19 295)' },
  { id: 'flame', name: 'フレイム', c: 'oklch(0.7 0.2 35)' },
];
const auraC = (id) => (AURAS.find(a => a.id === id) || AURAS[0]).c;

// 設定（cfg）の「グラスの色」または「縁の色」から、次へ・達成ボタンの配色と白・黒フラグを判定する関数
// cfg: オーナーの設定オブジェクト（{ plan, glassColor, frameStyle }）
// 戻り値: { color: CSSカラー値, isWhite: 真偽値, isBlack: 真偽値 }
function getButtonTheme(cfg) {
  // 設定が未ロードの場合はデフォルトのエメラルドグラス色を適用
  if (!cfg) return { color: 'oklch(0.62 0.14 165)', isWhite: false, isBlack: false };

  // 有料プラン（PRO）の場合は設定された「グラスの色」を採用
  // 無料プランの場合は「縁の色（白または黒）」を採用する（料理で言えばお皿の材質に合わせてカトラリーを選ぶイメージ）
  const isPro = cfg.plan === 'pro';
  let color = isPro
    ? (cfg.glassColor || 'oklch(0.62 0.14 165)')
    : (cfg.frameStyle === 'black' ? '#0d0f0e' : '#ffffff');

  // 白系統判定（#ffffff, white, または明度0.95以上のoklch）
  // 背景の白に同化しないよう、特別な境界線と影を付与するためのフラグ
  const lower = String(color).toLowerCase().trim();
  const isWhite = lower === '#ffffff' || lower === '#fff' || lower === 'white' || lower.includes('0.98') || lower.includes('0.97') || lower.includes('0.99');
  const isBlack = lower === '#0d0f0e' || lower === '#000000' || lower === '#000' || lower === 'black' || lower.includes('0.32');

  return { color, isWhite, isBlack };
}

const S = (k, a, h, tpl) => ({ k, a, h, tpl });
const INITIAL_ROADMAP = {
  id: 'rm1',
  title: '声活アクションプラン',
  owner: 'たちばな',
  slug: 'tachibana/seikatsu',
  published: true,
  stages: [
    { id: 's1', title: '声の土台をつくる', aura: 'em', steps: [
      S('01 BODY', '鏡の前で、肩の力を抜いてまっすぐ立つ', '頭のてっぺんが透明な糸で吊られているイメージ', 0),
      S('02 BREATH', 'お腹に手を当てて、4秒かけて深く吸う', '肩が上がらないよう横隔膜の広がりを意識', 2),
      S('03 BREATH', '8秒かけて一定の息で「スー」と吐き切る', '最後まで音の太さとスピードを変えないのがコツ', 0),
      S('04 LOOP', '吸う→吐くの呼吸を丁寧に5セット繰り返す', '終わったら首と肩を脱力して軽く回しましょう', 3),
      S('05 CHECK', '今日の体の軽さ・姿勢を10段階でメモ', '毎日の感覚の差を記録して自分の調子を知る', 1),
      S('06 HABIT', '明日も同じ時間にアラームをセットする', 'プロの声は毎日の小さな継続から生まれます', 4),
    ]},
    { id: 's2', title: '毎日の発声ルーティン', aura: 'sky', steps: [
      S('01 MOUTH', '割りばしを軽くくわえて「あえいうえおあお」', '口角が下がらないよう笑顔の表情筋をキープ', 2),
      S('02 LIP', '唇を震わせるリップロールを連続30秒キープ', '途中で止まったら息のスピードを一定に調整', 0),
      S('03 PITCH', '一番低い音から高い音まで「ん〜」と滑らかに移行', '喉を締めず、鼻の奥（鼻腔共鳴）に響かせる感覚', 2),
      S('04 BASE', '普段話す高さ（地声のキー）を1音決める', 'ピアノアプリで音名（CやDなど）を記録', 1),
      S('05 READ', '外郎売（ういろううり）の冒頭をノーミスで1回読む', '上手さよりも、言葉の子音を粒立ちよく立てる', 3),
      S('06 SLOW', 'もう1回、半分のスピードで一音一音丁寧に読む', '舌と唇の可動域を限界まで使って発声する', 2),
      S('07 TIME', '今日のルーティンにかかった時間を記録する', '毎日15分以内を目安に習慣化しましょう', 4),
    ]},
    { id: 's3', title: '録音して客観的に聴く', aura: 'amber', steps: [
      S('01 PREP', 'スマホのボイスメモを開いて機内モードにする', '着信や通知の振動が入らないように準備', 3),
      S('02 REC', '外郎売の冒頭を「撮り直しなし」で1回録音', '完璧を目指さず、今のリアルな声を残す勇気', 0),
      S('03 LISTEN', 'イヤホンをして、耳を澄ませて1回通して聴く', '最初は恥ずかしくて当然。それが上達の第一歩', 1),
      S('04 FOCUS', '直したいポイントを「1つだけ」ノートに書き出す', '改善点を1つに絞ることが最短上達の秘訣', 2),
      S('05 SAVE', '音源ファイル名に日付をつけてクラウド保存', '1か月後の自分の声と聴き比べる宝物になります', 4),
    ]},
    { id: 's4', title: 'ボイスサンプルを作る', aura: 'rose', steps: [
      S('01 CHARACTER', '自分の声に似合うキャラクター・役を3つ選定', '「人からよく言われる声の印象」を素直に入れる', 0),
      S('02 SCRIPT', '1役につき15秒程度の短いセリフを用意する', '喜怒哀楽の感情がはっきり伝わるセリフに絞る', 2),
      S('03 REC', '部屋の反響を抑えて3役を順番に録音する', 'クローゼットや布団を活用するだけで音質劇的向上', 3),
      S('04 EDIT', '冒頭と末尾の余計な無音部分をトリミング', '頭の無音は0.3秒〜0.5秒だけ残すのが業界標準', 1),
      S('05 FEEDBACK', '信頼できる友人1人に聴いてもらい感想を聞く', '「どの役が一番自然に聞こえたか」だけを尋ねる', 2),
      S('06 FINISH', '最高テイクを書き出してポートフォリオ完成', 'いつでも送れるように共有URLを控えておく', 4),
    ]},
    { id: 's5', title: 'オーディションに応募する', aura: 'violet', steps: [
      S('01 TARGET', '応募したいオーディションや事務所を3つリストアップ', '所属声優のボイスサンプルを聴いて傾向をリサーチ', 0),
      S('02 GUIDELINE', '募集要項の形式（mp3/wav/尺）を再確認する', '提出ミスを防ぐため形式のチェックリストを作成', 1),
      S('03 SUBMIT', '自信を持ってボイスサンプルを1社に提出する', '送った日時と提出したファイル名を必ず記録', 2),
      S('04 NEXT', '結果を待たずに次の応募日をカレンダーに入れる', '1社に固執せず、行動し続けることで道が開けます', 4),
    ]},
  ],
};

// 受講者の初期進捗（デモ用）: stageId -> cleared count, 'new' flags
const INITIAL_PROGRESS = { s1: 6, s2: 3, s3: 0, s4: 0, s5: 0 };

// ===== primitives =====
function Logo({ size = 18, color, mark = true, style }) {
  return (
    <span className="logo" style={{ fontSize: size, color, ...style }}>
      {mark && <span className="logo-mark"><i /><i /><i /></span>}
      NEXTAGE
    </span>
  );
}

function LogoPattern({ opacity = 0.05, color = '#0f1f19', rows = 16 }) {
  const row = Array.from({ length: 10 }, (_, i) => <span key={i}>NEXTAGE</span>);
  return (
    <div className="lpat" style={{ opacity, color }} aria-hidden>
      <div className="lpat-in">
        {Array.from({ length: rows }, (_, r) => <div className="lpat-row" key={r}>{row}</div>)}
      </div>
    </div>
  );
}

// スライド右側に配置する近未来アニメ調の図解ビジュアル（SVG）
// ステップの内容（姿勢、呼吸、共鳴、ループ等）に合わせて最適なダイアグラムを動的描画します
function SlideVisual({ type = 'generic' }) {
  if (type === 'posture') {
    // 姿勢・軸ガイドダイアグラム
    return (
      <svg viewBox="0 0 160 160" fill="none">
        <circle cx="80" cy="80" r="72" stroke="rgba(255,255,255,0.08)" strokeWidth="1" strokeDasharray="3 3" />
        <circle cx="80" cy="80" r="50" stroke="rgba(255,255,255,0.06)" strokeWidth="1" />
        <line x1="80" y1="12" x2="80" y2="148" stroke="var(--aura, #1fae7f)" strokeWidth="2.5" strokeLinecap="round" />
        <line x1="30" y1="50" x2="130" y2="50" stroke="rgba(255,255,255,0.2)" strokeWidth="1" />
        <line x1="40" y1="95" x2="120" y2="95" stroke="rgba(255,255,255,0.2)" strokeWidth="1" />
        <circle cx="80" cy="30" r="7" fill="#ffffff" filter="drop-shadow(0 0 8px var(--aura, #1fae7f))" />
        <circle cx="80" cy="50" r="4.5" fill="var(--aura, #1fae7f)" />
        <circle cx="80" cy="95" r="5" fill="var(--aura, #1fae7f)" />
        <circle cx="80" cy="138" r="6" fill="#ffffff" />
        <text x="80" y="156" textAnchor="middle" fill="rgba(255,255,255,0.5)" fontSize="7" fontFamily="var(--en)" letterSpacing="0.1em">VERTICAL AXIS 90°</text>
      </svg>
    );
  }

  if (type === 'breath') {
    // 呼吸リズム波形ダイアグラム
    return (
      <svg viewBox="0 0 160 160" fill="none">
        <circle cx="80" cy="80" r="70" stroke="rgba(255,255,255,0.06)" strokeWidth="1" />
        <path d="M 15 80 Q 45 25, 75 80 T 145 80" stroke="var(--aura, #1fae7f)" strokeWidth="3" strokeLinecap="round" fill="none" filter="drop-shadow(0 0 10px var(--aura, #1fae7f))" />
        <line x1="10" y1="80" x2="150" y2="80" stroke="rgba(255,255,255,0.12)" strokeWidth="1" strokeDasharray="2 4" />
        <circle cx="45" cy="42" r="5" fill="#ffffff" filter="drop-shadow(0 0 6px var(--aura, #1fae7f))" />
        <text x="45" y="30" textAnchor="middle" fill="var(--aura, #1fae7f)" fontSize="8.5" fontWeight="800" fontFamily="var(--en)">IN 4s</text>
        <circle cx="110" cy="98" r="5" fill="var(--aura, #1fae7f)" />
        <text x="110" y="118" textAnchor="middle" fill="#94a3b8" fontSize="8.5" fontWeight="800" fontFamily="var(--en)">OUT 8s</text>
        <circle cx="45" cy="42" r="10" stroke="var(--aura, #1fae7f)" strokeWidth="1" opacity="0.6" />
      </svg>
    );
  }

  if (type === 'sound') {
    // 音響・共鳴レゾナンスダイアグラム
    return (
      <svg viewBox="0 0 160 160" fill="none">
        <circle cx="80" cy="80" r="16" fill="var(--aura, #1fae7f)" opacity="0.2" />
        <circle cx="80" cy="80" r="34" stroke="var(--aura, #1fae7f)" strokeWidth="1.5" strokeDasharray="4 2" opacity="0.5" />
        <circle cx="80" cy="80" r="54" stroke="rgba(255,255,255,0.2)" strokeWidth="1" />
        <circle cx="80" cy="80" r="70" stroke="var(--aura, #1fae7f)" strokeWidth="1.5" opacity="0.8" filter="drop-shadow(0 0 12px var(--aura, #1fae7f))" />
        <rect x="52" y="68" width="6" height="24" rx="3" fill="var(--aura, #1fae7f)" />
        <rect x="64" y="52" width="6" height="56" rx="3" fill="#ffffff" />
        <rect x="76" y="38" width="8" height="84" rx="4" fill="var(--aura, #1fae7f)" filter="drop-shadow(0 0 8px var(--aura, #1fae7f))" />
        <rect x="90" y="56" width="6" height="48" rx="3" fill="#ffffff" />
        <rect x="102" y="70" width="6" height="20" rx="3" fill="var(--aura, #1fae7f)" />
        <text x="80" y="154" textAnchor="middle" fill="rgba(255,255,255,0.6)" fontSize="7.5" fontFamily="var(--en)" letterSpacing="0.12em">ACOUSTIC SPECTRUM</text>
      </svg>
    );
  }

  if (type === 'loop') {
    // 習慣・反復ループダイアグラム
    return (
      <svg viewBox="0 0 160 160" fill="none">
        <circle cx="80" cy="80" r="64" stroke="rgba(255,255,255,0.08)" strokeWidth="4" />
        <circle cx="80" cy="80" r="64" stroke="var(--aura, #1fae7f)" strokeWidth="4" strokeDasharray="320 80" strokeLinecap="round" filter="drop-shadow(0 0 10px var(--aura, #1fae7f))" />
        <rect x="68" y="68" width="24" height="24" rx="6" fill="#ffffff" filter="drop-shadow(0 0 12px var(--aura, #1fae7f))" />
        <path d="M75 80 L79 84 L87 76" stroke="#05100c" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" />
        <circle cx="80" cy="16" r="5" fill="var(--aura, #1fae7f)" />
        <circle cx="144" cy="80" r="5" fill="#ffffff" />
        <text x="80" y="154" textAnchor="middle" fill="rgba(255,255,255,0.6)" fontSize="7.5" fontFamily="var(--en)" letterSpacing="0.12em">HABIT FORMATION</text>
      </svg>
    );
  }

  // generic: 万能オーラヘキサゴン＆オービット
  return (
    <svg viewBox="0 0 160 160" fill="none">
      <circle cx="80" cy="80" r="68" stroke="rgba(255,255,255,0.07)" strokeWidth="1" />
      <circle cx="80" cy="80" r="48" stroke="color-mix(in srgb, var(--aura, #1fae7f) 40%, transparent)" strokeWidth="1.5" strokeDasharray="6 3" />
      <polygon points="80,32 122,56 122,104 80,128 38,104 38,56" stroke="var(--aura, #1fae7f)" strokeWidth="2" fill="color-mix(in srgb, var(--aura, #1fae7f) 10%, transparent)" filter="drop-shadow(0 0 12px var(--aura, #1fae7f))" />
      <polygon points="80,52 104,66 104,94 80,108 56,94 56,66" fill="#ffffff" opacity="0.9" filter="drop-shadow(0 0 8px #fff)" />
    </svg>
  );
}

// スライド1枚を表示するコンポーネント（Neo-Action Slide System）
// 引数: step（スライドのデータ情報）, idx（現在のスライド番号 0始まり）, total（全体の枚数）, style（CSSスタイル）
function Slide({ step, idx, total, style }) {
  if (!step) return null;

  // 画像が登録されている場合は実画像スライドとして表示
  if (step.image) {
    return (
      <div className="slide slide-real-image" style={style}>
        <img src={step.image} alt={step.a || 'スライド'} style={{ width: '100%', height: '100%', objectFit: 'contain', display: 'block' }} />
        <div className="sl-n">
          <span className="sl-n-cur">{String(idx + 1).padStart(2, '0')}</span>
          <span className="sl-n-sep">/</span>
          <span className="sl-n-tot">{String(total).padStart(2, '0')}</span>
        </div>
      </div>
    );
  }

  // ステップの種類やキーワードからビジュアル種別を動的判定
  const text = ((step.k || '') + ' ' + (step.a || '')).toUpperCase();
  let vizType = 'generic';
  if (text.includes('POSTURE') || text.includes('BODY') || text.includes('立') || text.includes('姿')) vizType = 'posture';
  else if (text.includes('BREATH') || text.includes('息') || text.includes('吸') || text.includes('吐')) vizType = 'breath';
  else if (text.includes('RESONANCE') || text.includes('PITCH') || text.includes('REC') || text.includes('録音') || text.includes('声') || text.includes('MOUTH')) vizType = 'sound';
  else if (text.includes('LOOP') || text.includes('HABIT') || text.includes('TIME') || text.includes('時間') || text.includes('継続')) vizType = 'loop';

  return (
    <div className="slide neo-slide" style={style}>
      {/* 背景のアンビエント光彩とテックグリッド */}
      <div className="sl-bg-glow" />
      <div className="sl-bg-grid" />
      <div className="sl-corner c-tl" />
      <div className="sl-corner c-br" />

      {/* スライド番号バッジ（右上） */}
      <div className="sl-n">
        <span className="sl-n-cur">{String(idx + 1).padStart(2, '0')}</span>
        <span className="sl-n-sep">/</span>
        <span className="sl-n-tot">{String(total).padStart(2, '0')}</span>
      </div>

      {/* メインコンテンツ（左側） */}
      <div className="sl-main">
        {step.k && (
          <div className="sl-k">
            <span className="sl-k-dot" />
            <span>{step.k}</span>
          </div>
        )}
        <div className="sl-a">{step.a}</div>
        {step.h && (
          <div className="sl-h">
            <span className="sl-h-tag">POINT</span>
            <span className="sl-h-text">{step.h}</span>
          </div>
        )}
      </div>

      {/* 右側のスタイリッシュな図解ダイアグラム */}
      <div className="sl-viz">
        <SlideVisual type={vizType} />
      </div>
    </div>
  );
}

function CheckIcon({ size = 14, color = '#fff' }) {
  return (
    <svg width={size} height={size} viewBox="0 0 16 16" fill="none"><path d="M3 8.5l3.2 3L13 4.5" stroke={color} strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" /></svg>
  );
}
function Chevron({ dir = 'r', size = 14 }) {
  const r = { r: 0, l: 180, d: 90, u: -90 }[dir];
  return (
    <svg width={size} height={size} viewBox="0 0 16 16" style={{ transform: `rotate(${r}deg)` }} fill="none"><path d="M6 3l5 5-5 5" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /></svg>
  );
}

// 縁 (frame) — the core product surface
function Frame({ cfg, stage, cleared, total, compact, children, frameRef, onSlideClick, fx }) {
  const isPro = cfg.plan === 'pro';
  const style = isPro ? 'glass' : (cfg.frameStyle === 'black' ? 'black' : 'white');
  const gc = cfg.glassColor;
  const pct = total ? (cleared / total) * 100 : 0;
  const fill = style === 'glass' ? '#fff' : auraC(stage.aura);
  return (
    <div ref={frameRef} className={'frame fr-' + style + (compact ? ' compact' : '')} style={{ '--gc': gc, '--aura': auraC(stage.aura) }}>
      <div className="fr-slide" onClick={onSlideClick}>{children}</div>
      <div className="fr-band">
        <div className="fr-title">{stage.title}</div>
        <div className="fr-right">
          {isPro && (
            <div className="fr-gauge">
              <div className="fr-gtrack"><div className="fr-gfill" style={{ width: pct + '%', background: fill, boxShadow: `0 0 10px ${fill}` }} /></div>
              <span>{cleared}/{total}</span>
            </div>
          )}
          {(!isPro || cfg.poweredBy) && (
            <div className="fr-pow">Powered by <Logo size={10} color="currentColor" /></div>
          )}
        </div>
      </div>
      {fx}
    </div>
  );
}

Object.assign(window, { AURAS, auraC, getButtonTheme, INITIAL_ROADMAP, INITIAL_PROGRESS, Logo, LogoPattern, Slide, CheckIcon, Chevron, Frame });
