// blog-page.jsx — Blog index + article pages (脚本パックン)
// Layout reference: kyakuhonpakkun-blog (kyakuhonpakkun-blog.vercel.app)
// Header/Footer: SiteHeader / SiteFooter from site-chrome.jsx (unchanged)
// UI reference: hitodeblog.com — speech bubbles, markers, 2-level TOC, scroll reveal

const { useState: useBlogState, useEffect: useBlogEffect, useRef: useBlogRef } = React;

/** 読書進捗バー */
function ReadingProgress() {
  const [pct, setPct] = useBlogState(0);
  useBlogEffect(() => {
    const onScroll = () => {
      const el = document.documentElement;
      const h = el.scrollHeight - el.clientHeight;
      setPct(h > 0 ? Math.min(100, (window.scrollY / h) * 100) : 0);
    };
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);
  return (
    <div style={{
      position: "fixed", top: 0, left: 0, zIndex: 9999,
      height: 3, width: `${pct}%`,
      background: "linear-gradient(90deg, #2196F3 0%, #42A5F5 100%)",
      transition: "width 0.08s linear",
      pointerEvents: "none",
      borderRadius: "0 2px 2px 0",
    }}/>
  );
}

// ─────────────────────────────────────────────────────────
// Data
// ─────────────────────────────────────────────────────────

const CATEGORIES = ["ハウツー", "基礎知識", "導入事例", "ツール比較"];

const BLOG_POSTS = [
  {
    slug: "kobancho",
    category: "基礎知識",
    publishedAt: "2026-05-26",
    href: "/blog/callsheet",
    title: "【2026年版】香盤表とは？現役プロデューサーが作り方を徹底解説！",
    description: "香盤表の基本項目8つ・作り方5ステップ・現場で使える3つのコツを、広告・MV・映画制作を手がける現役プロデューサーが解説。",
  },
  {
    slug: "ppm",
    category: "基礎知識",
    publishedAt: "2026-05-29",
    href: "/blog/ppm",
    title: "【2026年版】PPM資料とは？現役プロデューサーが必要項目を全部公開！",
    description: "PPM資料の基礎から作り方まで解説。10ブロックの必須構成・6ステップの作り方・精度を上げる3つのコツを現役プロデューサーが公開。",
  },
];

// SVG icon helpers
const sw = { fill:"none", stroke:"currentColor", strokeWidth:2, strokeLinecap:"round", strokeLinejoin:"round" };
const IcoClipboard  = (p) => <svg {...sw} {...p} viewBox="0 0 24 24"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><rect x="8" y="2" width="8" height="4" rx="1" ry="1"/></svg>;
const IcoBook       = (p) => <svg {...sw} {...p} viewBox="0 0 24 24"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>;
const IcoCheckSquare= (p) => <svg {...sw} {...p} viewBox="0 0 24 24"><polyline points="9 11 12 14 22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>;
const IcoScales     = (p) => <svg {...sw} {...p} viewBox="0 0 24 24"><line x1="12" y1="3" x2="12" y2="20"/><polyline points="6 8 12 3 18 8"/><path d="M6 18H2l4-8 4 8H6z"/><path d="M18 18h-4l4-8 4 8h-4z"/><line x1="5" y1="21" x2="19" y2="21"/></svg>;
const IcoImage      = (p) => <svg {...sw} {...p} viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>;

const CATEGORY_STYLES = {
  ハウツー:   { bg: "linear-gradient(135deg, #1E88E5 0%, #42A5F5 100%)", IcoComponent: IcoClipboard },
  基礎知識:   { bg: "linear-gradient(135deg, #5C6BC0 0%, #7986CB 100%)", IcoComponent: IcoBook },
  導入事例:   { bg: "linear-gradient(135deg, #00897B 0%, #26A69A 100%)", IcoComponent: IcoCheckSquare },
  ツール比較: { bg: "linear-gradient(135deg, #F57C00 0%, #FFA726 100%)", IcoComponent: IcoScales },
};

const KOBANCHO_HEADINGS = [
  { id: "香盤表とは何か",              text: "香盤表とは何か？",                               level: 2 },
  { id: "なぜ香盤表が必要なのか",      text: "なぜ香盤表が必要なのか？",                        level: 2 },
  { id: "香盤表の基本項目8つは何か",   text: "香盤表の基本項目8つ",                           level: 2 },
  { id: "香盤表の作り方5ステップ",     text: "香盤表の作り方【5ステップ】",                     level: 2 },
  { id: "step1",                       text: "ステップ1：脚本を読み込んでシーンを洗い出す",     level: 3 },
  { id: "step2",                       text: "ステップ2：撮影順を最適化する",                   level: 3 },
  { id: "step3",                       text: "ステップ3：各シーンの撮影時間を見積もる",         level: 3 },
  { id: "step4",                       text: "ステップ4：出演者・スタッフの拘束時間を割り付ける", level: 3 },
  { id: "step5",                       text: "ステップ5：全スタッフへ共有する",                 level: 3 },
  { id: "現場で使える3つのコツは何か", text: "現場で使える3つのコツ",                         level: 2 },
  { id: "香盤作成を自動化する方法は",  text: "香盤作成を自動化する方法はあるの？",             level: 2 },
  { id: "まとめ",                      text: "まとめ",                                         level: 2 },
  { id: "よくある質問FAQ",             text: "よくある質問（FAQ）",                            level: 2 },
];

const KOBANCHO_FAQ = [
  {
    q: "香盤表はExcelで作るべきですか？",
    a: "ExcelやGoogleスプレッドシートが一般的。どちらも無料のテンプレートが配布されているので、まず試してみるといい。ただ、脚本変更のたびに手動で更新する手間が発生するから、更新頻度が高い案件では脚本パックンのような専用ツールを使う方が現実的だと思う。",
  },
  {
    q: "香盤表と絵コンテの違いは何ですか？",
    a: "香盤表はスケジュール・段取りの管理ドキュメント、絵コンテは各カットの映像イメージを図示したドキュメントだ。役割が異なるので、通常はセットで使われる。",
  },
  {
    q: "香盤表はいつまでに作るべきですか？",
    a: "撮影日の1週間前を目安に初稿を作って、3日前には全スタッフへ共有するのが現場の標準。直前の変更は現場混乱の原因になるから、なるべく早めに動きたい。",
  },
  {
    q: "小規模な自主制作でも香盤表は必要ですか？",
    a: "必要だ。出演者が1人でも、スタッフが3人でも、香盤表があるかないかで現場の動きは大きく変わる。小規模ほどリカバリーが難しいから、むしろ丁寧に作ることをすすめる。",
  },
  {
    q: "香盤表のテンプレートはどこで入手できますか？",
    a: <span>Excelテンプレートは<a href="https://www.kic-factory.co.jp/column/6787/" target="_blank" rel="noopener noreferrer" style={{ color: "#2196F3", textDecoration: "underline", textUnderlineOffset: "3px" }}>こちらのサイト</a>で無料配布している。自分で項目を調整したい場合はおすすめ。脚本データがあるなら、<a href="https://service.kyakuhon-pakkun.com/" target="_blank" rel="noopener noreferrer" style={{ color: "#2196F3", textDecoration: "underline", textUnderlineOffset: "3px" }}>脚本パックン</a>で脚本をアップロードするだけで香盤表そのものが自動生成できる。どちらも無料で試せる。</span>,
  },
];

const PPM_HEADINGS = [
  { id: "PPM資料とは何か",          text: "PPM資料とは何か？",                   level: 2 },
  { id: "なぜPPM資料が必要なのか",   text: "なぜPPM資料が必要なのか？",            level: 2 },
  { id: "PPM資料の基本構成10ブロック", text: "PPM資料の基本構成10ブロックは何か？", level: 2 },
  { id: "PPM資料の作り方6ステップ",  text: "PPM資料の作り方【6ステップ】",         level: 2 },
  { id: "ppm-step1",                text: "ステップ1：必要情報をどう集めるか？",   level: 3 },
  { id: "ppm-step2",                text: "ステップ2：スライド構成をどう設計するか？", level: 3 },
  { id: "ppm-step3",                text: "ステップ3：ビジュアル素材をどう集めるか？", level: 3 },
  { id: "ppm-step4",                text: "ステップ4：スライドをどう組み立てるか？", level: 3 },
  { id: "ppm-step5",                text: "ステップ5：社内レビューはどう回すか？",  level: 3 },
  { id: "ppm-step6",                text: "ステップ6：クライアントへの提出はどう進めるか？", level: 3 },
  { id: "現場で使える3つのコツ",     text: "現場で使える3つのコツは何か？",         level: 2 },
  { id: "PPM資料作成を自動化する方法", text: "PPM資料作成を自動化する方法は？",     level: 2 },
  { id: "ppmまとめ",                 text: "まとめ",                              level: 2 },
  { id: "PPMよくある質問",           text: "よくある質問（FAQ）",                  level: 2 },
];

const PPM_FAQ = [
  {
    q: "PPM資料はPowerPointとKeynoteどちらで作るべきですか？",
    a: "どちらでもOK。共同編集が必要ならGoogle Slidesが便利だけど、印刷品質や細かいレイアウト調整はPowerPoint・Keynoteのほうが強い。",
  },
  {
    q: "PPM資料と絵コンテの違いは何ですか？",
    a: "PPM資料は撮影に関わる全要素を網羅する「合意のための資料」、絵コンテは各カットの映像構成を図示する「演出のための資料」。絵コンテはPPM資料の中の1セクションとして組み込まれることが多い。",
  },
  {
    q: "PPMはいつ開催するのが標準ですか？",
    a: "撮影日の1〜2週間前が一般的。広告系の大型案件では2週間前、ショートドラマや小規模案件では1週間前が現場の感覚値。",
  },
  {
    q: "小規模な自主制作でもPPM資料は必要ですか？",
    a: "関係者が3人以上いるなら作ったほうがいい。最低5ブロック（企画概要・脚本・キャスト・美術・ロケ地）はドキュメント化することをすすめる。",
  },
  {
    q: "PPM資料のテンプレートはどこで入手できますか？",
    a: <span>たとえば <a href="https://www.bizocean.jp/doc/category/158/" target="_blank" rel="noopener noreferrer" style={{color:"#2196F3",textDecoration:"underline",textUnderlineOffset:"3px"}}>bizocean</a> で無料テンプレートが配布されているので参考にするといい。脚本データがあれば脚本パックンでPPMの叩き台を自動生成することもできる。</span>,
  },
];

// ─────────────────────────────────────────────────────────
// UI utilities
// ─────────────────────────────────────────────────────────

/** スクロール連動フェードイン */
function Reveal({ children, delay = 0 }) {
  const ref = useBlogRef(null);
  const [visible, setVisible] = useBlogState(false);
  useBlogEffect(() => {
    const el = ref.current;
    if (!el) return;
    if (typeof IntersectionObserver === "undefined") { setVisible(true); return; }
    const obs = new IntersectionObserver(
      ([entry]) => { if (entry.isIntersecting) { setVisible(true); obs.disconnect(); } },
      { threshold: 0, rootMargin: "0px 0px -40px 0px" }
    );
    obs.observe(el);
    return () => obs.disconnect();
  }, []);
  return (
    <div ref={ref} style={{
      opacity: visible ? 1 : 0,
      transform: visible ? "none" : "translateY(18px)",
      transition: `opacity 0.5s ease ${delay}s, transform 0.5s ease ${delay}s`,
    }}>
      {children}
    </div>
  );
}

/** マーカーハイライト（黄色蛍光ペン） */
function Marker({ children }) {
  return (
    <span style={{ background: "linear-gradient(transparent 55%, #FFF176 55%)", fontWeight: 700 }}>
      {children}
    </span>
  );
}

/** 吹き出し（制作進行キャラ） */
function SpeechBubble({ speaker = "制作進行", children }) {
  return (
    <div style={{ display: "flex", gap: 14, margin: "2.25rem 0", alignItems: "flex-start" }}>
      {/* アバター */}
      <div style={{
        width: 52, height: 52, borderRadius: "50%", flexShrink: 0,
        background: PRIM,
        display: "flex", alignItems: "center", justifyContent: "center",
        fontFamily: WM, fontWeight: 900, fontSize: 26, color: "#fff",
        boxShadow: "0 2px 10px rgba(33,150,243,0.3)",
      }}>パ</div>
      {/* バブル */}
      <div style={{ flex: 1, position: "relative" }}>
        {/* 吹き出しの尾 */}
        <div style={{
          position: "absolute", left: -9, top: 16,
          width: 0, height: 0,
          borderTop: "7px solid transparent",
          borderBottom: "7px solid transparent",
          borderRight: "9px solid #F0F7FF",
        }}/>
        <div style={{
          background: "#F0F7FF", border: "1.5px solid #BFDBFE",
          borderRadius: "4px 16px 16px 16px", padding: "14px 18px",
        }}>
          <p style={{ fontFamily: HD, fontSize: 15, color: "#374151", margin: 0, lineHeight: 1.88, fontWeight: 500 }}>
            {children}
          </p>
        </div>
        <span style={{ display: "block", marginTop: 5, marginLeft: 4, fontFamily: HD, fontSize: 11, fontWeight: 700, color: FAINT }}>
          {speaker}
        </span>
      </div>
    </div>
  );
}

/** イラストプレースホルダー */
function IllustSlot({ label = "イラスト" }) {
  return (
    <div style={{
      margin: "2.5rem 0", borderRadius: 12, background: "#F8F9FA",
      border: "2px dashed #D1D5DB",
      display: "flex", flexDirection: "column", alignItems: "center",
      justifyContent: "center", padding: "40px 16px", gap: 10,
    }}>
      <IcoImage width={38} height={38} style={{ color: "#9CA3AF" }}/>
      <span style={{ fontFamily: HD, fontSize: 12, color: "#9CA3AF", letterSpacing: ".05em" }}>{label}</span>
    </div>
  );
}

/** ヒーロービジュアル SVG — 香盤表記事用（Excelスプレッドシート風） */
function HeroCallsheet() {
  // Excel カラー定数
  const XL_TITLE   = "#1D6F42"; // タイトルバー（深緑）
  const XL_GREEN   = "#217346"; // アクティブ要素・ヘッダー行
  const XL_ORANGE  = "#ED7D31"; // アクティブタブ下線
  const XL_GRID    = "#D4D4D4"; // グリッド線
  const XL_COLHDR  = "#E0E0E0"; // 列ヘッダー背景
  const XL_TEXT    = "#1E1E1E"; // セル文字
  const XL_SUBTEXT = "#595959"; // グレー文字

  // レイアウト y 座標（手計算: 合計 506px ぴったり）
  // タイトル: 0–30 / リボンタブ: 30–56 / リボン本体: 56–94 / 数式バー: 94–116
  // 列ヘッダー: 116–138 / データ行 h=26 × 12行: 138–450
  // シートタブ: 450–476 / ステータス: 476–506
  const Y_TITLE  = 0,  H_TITLE  = 30;
  const Y_TAB    = 30, H_TAB    = 26;
  const Y_RBN    = 56, H_RBN    = 38;
  const Y_FMLA   = 94, H_FMLA   = 22;
  const Y_CHDR   = 116,H_CHDR   = 22;
  const Y_DATA   = 138,H_ROW    = 26;
  const Y_STAB   = 450,H_STAB   = 26;
  const Y_STATUS = 476,H_STATUS = 30;

  // グリッド列定義 [id, x, w, letter]
  const C = [
    { id:"rn",    x:0,   w:28,  ltr:"" },
    { id:"A",     x:28,  w:68,  ltr:"A" },
    { id:"B",     x:96,  w:155, ltr:"B" },
    { id:"C",     x:251, w:133, ltr:"C" },
    { id:"D",     x:384, w:76,  ltr:"D" },
    { id:"E",     x:460, w:76,  ltr:"E" },
    { id:"F",     x:536, w:76,  ltr:"F" },
    { id:"G",     x:612, w:288, ltr:"G" },
  ];
  const GRID_BOTTOM = Y_STAB;
  const GRID_H      = GRID_BOTTOM - Y_CHDR;

  // ヘッダーラベル（列 A–G 対応）
  const HDR_LABELS = ["SC#", "撮影場所", "出演者", "集合", "開始", "終了", "備考"];

  // データ行
  const ROWS = [
    { rn:"2", sc:"SC-01", place:"スタジオ A",     cast:"田中 / 鈴木", call:"08:30", start:"09:00", end:"12:00", notes:"昼休憩 12:00–13:00", bg:"#DDEBF7" },
    { rn:"3", sc:"SC-03", place:"公園ロケ",        cast:"田中",         call:"12:30", start:"13:00", end:"15:30", notes:"—",                  bg:"#E2EFDA" },
    { rn:"4", sc:"SC-05", place:"オフィスセット",  cast:"全キャスト",   call:"15:00", start:"15:30", end:"18:30", notes:"—",                  bg:"#EAE8F1" },
    { rn:"5", sc:"SC-02", place:"スタジオ A",     cast:"鈴木 / 佐藤",  call:"18:00", start:"18:30", end:"20:30", notes:"機材搬出 20:30",       bg:"#DDEBF7" },
  ];

  // アクティブセル: D2（集合列 / 1行目データ）
  const ACT_COL = C[4]; // D
  const ACT_Y   = Y_DATA + H_ROW; // row 2

  // リボンタブ
  const TABS = [
    { label:"ファイル", w:62,  green:true },
    { label:"ホーム",   w:68,  active:true },
    { label:"挿入",     w:54 },
    { label:"ページレイアウト", w:100 },
    { label:"数式",     w:54 },
    { label:"データ",   w:60 },
    { label:"校閲",     w:54 },
    { label:"表示",     w:54 },
  ];

  return (
    <svg viewBox="0 0 900 506" style={{ width:"100%", display:"block" }} xmlns="http://www.w3.org/2000/svg">

      {/* ═══ タイトルバー ═══ */}
      <rect x="0" y={Y_TITLE} width="900" height={H_TITLE} fill={XL_TITLE}/>
      {/* Mac トラフィックライト */}
      <circle cx="18" cy={Y_TITLE+15} r="5.5" fill="#FF5F57"/>
      <circle cx="34" cy={Y_TITLE+15} r="5.5" fill="#FEBC2E"/>
      <circle cx="50" cy={Y_TITLE+15} r="5.5" fill="#28C840"/>
      {/* クイックアクセス */}
      <text x="70" y={Y_TITLE+19} fontFamily="sans-serif" fontSize="11" fill="rgba(255,255,255,0.55)">↩ ↪ 💾</text>
      {/* ファイル名（中央） */}
      <text x="450" y={Y_TITLE+19} textAnchor="middle" fontFamily="Noto Sans JP,sans-serif" fontWeight="600" fontSize="12" fill="white">香盤表_夏空ドリンクCM.xlsx</text>
      {/* ウィンドウボタン右 */}
      <text x="844" y={Y_TITLE+19} fontFamily="sans-serif" fontSize="12" fill="rgba(255,255,255,0.5)">－ □ ✕</text>

      {/* ═══ リボンタブ行 ═══ */}
      <rect x="0" y={Y_TAB} width="900" height={H_TAB} fill="#F2F2F2"/>
      <rect x="0" y={Y_TAB+H_TAB-1} width="900" height="1" fill="#BDBDBD"/>
      {(() => {
        let tx = 0;
        return TABS.map(({ label, w, green, active }) => {
          const el = (
            <g key={label}>
              {green  && <rect x={tx} y={Y_TAB} width={w} height={H_TAB} fill={XL_GREEN}/>}
              {active && <rect x={tx} y={Y_TAB} width={w} height={H_TAB-1} fill="white"/>}
              {active && <rect x={tx} y={Y_TAB} width={w} height="2.5" fill={XL_ORANGE}/>}
              <text x={tx+w/2} y={Y_TAB+16} textAnchor="middle"
                fontFamily="Noto Sans JP,sans-serif"
                fontWeight={active||green ? "700" : "400"} fontSize="11"
                fill={green ? "white" : active ? "#1A1A1A" : XL_SUBTEXT}
              >{label}</text>
            </g>
          );
          tx += w;
          return el;
        });
      })()}

      {/* ═══ リボン本体 ═══ */}
      <rect x="0" y={Y_RBN} width="900" height={H_RBN} fill="white"/>
      <rect x="0" y={Y_RBN+H_RBN-1} width="900" height="1" fill="#D0D0D0"/>
      {/* 貼り付けボタン */}
      <rect x="8" y={Y_RBN+4} width="26" height={H_RBN-12} rx="3" fill="#F5F5F5" stroke="#D0D0D0" strokeWidth="0.8"/>
      <text x="21" y={Y_RBN+21} textAnchor="middle" fontSize="13">📋</text>
      {/* フォントセレクタ */}
      <rect x="42" y={Y_RBN+8} width="108" height="16" rx="2" fill="white" stroke="#BDBDBD" strokeWidth="0.8"/>
      <text x="48" y={Y_RBN+20} fontFamily="Noto Sans JP,sans-serif" fontSize="10" fill={XL_TEXT}>游ゴシック</text>
      <rect x="154" y={Y_RBN+8} width="36" height="16" rx="2" fill="white" stroke="#BDBDBD" strokeWidth="0.8"/>
      <text x="172" y={Y_RBN+20} textAnchor="middle" fontFamily="JetBrains Mono,monospace" fontSize="10" fill={XL_TEXT}>11</text>
      {/* B I U */}
      <text x="202" y={Y_RBN+22} fontFamily="sans-serif" fontWeight="900" fontSize="13" fill={XL_TEXT}>B</text>
      <text x="216" y={Y_RBN+22} fontFamily="sans-serif" fontStyle="italic" fontSize="13" fill={XL_SUBTEXT}>I</text>
      <text x="230" y={Y_RBN+22} fontFamily="sans-serif" fontSize="13" fill={XL_SUBTEXT}>U</text>
      <rect x="228" y={Y_RBN+27} width="14" height="2.5" rx="1" fill="#2196F3"/>
      {/* セパレータ */}
      <rect x="250" y={Y_RBN+6} width="1" height={H_RBN-14} fill="#D0D0D0"/>
      {/* 配置アイコン（線3本 × 3グループ） */}
      {[262,278,294].map(bx => (
        <g key={bx}>{[0,5,10].map(dy => (
          <rect key={dy} x={bx} y={Y_RBN+10+dy} width="14" height="2" rx="1" fill={XL_SUBTEXT} opacity="0.45"/>
        ))}</g>
      ))}
      {/* セルの色ボタン */}
      <rect x="314" y={Y_RBN+6} width="28" height={H_RBN-14} rx="2" fill="#F5F5F5" stroke="#D0D0D0" strokeWidth="0.8"/>
      <text x="328" y={Y_RBN+19} textAnchor="middle" fontFamily="sans-serif" fontWeight="900" fontSize="11" fill={XL_TEXT}>A</text>
      <rect x="316" y={Y_RBN+25} width="24" height="3" rx="1" fill="#FEBC2E"/>

      {/* ═══ 数式バー ═══ */}
      <rect x="0" y={Y_FMLA} width="900" height={H_FMLA} fill="#F2F2F2"/>
      <rect x="0" y={Y_FMLA+H_FMLA-1} width="900" height="1" fill="#BDBDBD"/>
      {/* 名前ボックス */}
      <rect x="4" y={Y_FMLA+3} width="52" height="16" rx="2" fill="white" stroke="#ABABAB" strokeWidth="0.8"/>
      <text x="30" y={Y_FMLA+15} textAnchor="middle" fontFamily="JetBrains Mono,monospace" fontSize="10" fill={XL_TEXT}>D2</text>
      <rect x="60" y={Y_FMLA+3} width="1" height="16" fill="#D0D0D0"/>
      <text x="70" y={Y_FMLA+15} fontFamily="serif" fontStyle="italic" fontSize="12" fill={XL_SUBTEXT}>fx</text>
      <rect x="84" y={Y_FMLA+3} width="1" height="16" fill="#D0D0D0"/>
      <text x="90" y={Y_FMLA+15} fontFamily="JetBrains Mono,monospace" fontSize="10" fill={XL_TEXT}>08:30</text>

      {/* ═══ 列ヘッダー ═══ */}
      {/* 全列ヘッダー背景 */}
      <rect x="0" y={Y_CHDR} width="900" height={H_CHDR} fill={XL_COLHDR}/>
      {/* アクティブ列（D）のハイライト */}
      <rect x={ACT_COL.x} y={Y_CHDR} width={ACT_COL.w} height={H_CHDR} fill="#BDD7EE"/>
      <rect x={ACT_COL.x} y={Y_CHDR} width={ACT_COL.w} height="2.5" fill={XL_GREEN}/>
      {/* 列文字ラベル */}
      {C.map(col => (
        <text key={col.id} x={col.x+col.w/2} y={Y_CHDR+15} textAnchor="middle"
          fontFamily="Noto Sans JP,sans-serif"
          fontWeight={col.id==="D" ? "700" : "400"} fontSize="10.5"
          fill={col.id==="D" ? XL_GREEN : XL_SUBTEXT}
        >{col.ltr}</text>
      ))}
      {/* 列区切り線（グリッド全高） */}
      {C.map(col => (
        <rect key={col.id} x={col.x+col.w-1} y={Y_CHDR} width="1" height={GRID_H} fill={XL_GRID}/>
      ))}
      {/* 列ヘッダー下線 */}
      <rect x="0" y={Y_CHDR+H_CHDR-1} width="900" height="1" fill="#BDBDBD"/>

      {/* ═══ ヘッダー行（行1） ═══ */}
      <rect x={C[0].w} y={Y_DATA} width={900-C[0].w} height={H_ROW} fill={XL_GREEN}/>
      {/* 行番号セル */}
      <rect x="0" y={Y_DATA} width={C[0].w} height={H_ROW} fill={XL_COLHDR}/>
      <text x={C[0].w/2} y={Y_DATA+17} textAnchor="middle" fontFamily="JetBrains Mono,monospace" fontSize="10" fill={XL_SUBTEXT}>1</text>
      {/* ヘッダーテキスト */}
      {HDR_LABELS.map((lbl, i) => {
        const col = C[i+1];
        return (
          <text key={lbl} x={col.x+6} y={Y_DATA+17}
            fontFamily="Noto Sans JP,sans-serif" fontWeight="700" fontSize="10.5" fill="white"
          >{lbl}</text>
        );
      })}
      {/* ヘッダー行下線 */}
      <rect x="0" y={Y_DATA+H_ROW-1} width="900" height="1" fill="rgba(255,255,255,0.25)"/>

      {/* ═══ データ行 ═══ */}
      {ROWS.map((row, ri) => {
        const ry = Y_DATA + H_ROW + ri * H_ROW;
        const vals = [row.sc, row.place, row.cast, row.call, row.start, row.end, row.notes];
        return (
          <g key={ri}>
            {/* セル背景 */}
            <rect x={C[0].w} y={ry} width={900-C[0].w} height={H_ROW} fill={row.bg}/>
            {/* アクティブセル（D2のみ） */}
            {ri === 0 && (
              <rect x={ACT_COL.x} y={ry} width={ACT_COL.w} height={H_ROW}
                fill="rgba(68,114,196,0.12)" stroke={XL_GREEN} strokeWidth="1.5"/>
            )}
            {/* 行番号 */}
            <rect x="0" y={ry} width={C[0].w} height={H_ROW} fill={XL_COLHDR}/>
            <text x={C[0].w/2} y={ry+17} textAnchor="middle"
              fontFamily="JetBrains Mono,monospace" fontSize="10" fill={XL_SUBTEXT}
            >{row.rn}</text>
            {/* セル値 */}
            {vals.map((val, ci) => {
              const col = C[ci+1];
              const isMono = ci >= 3 && ci <= 5; // 集合・開始・終了は等幅
              return (
                <text key={ci} x={col.x+6} y={ry+17}
                  fontFamily={isMono ? "JetBrains Mono,monospace" : "Noto Sans JP,sans-serif"}
                  fontSize="10.5" fill={XL_TEXT}
                >{val}</text>
              );
            })}
            {/* 行区切り線 */}
            <rect x="0" y={ry+H_ROW-1} width="900" height="1" fill={XL_GRID}/>
          </g>
        );
      })}

      {/* ═══ 空行（残り） ═══ */}
      {Array.from({ length: 7 }).map((_, ri) => {
        const ry = Y_DATA + H_ROW * (ROWS.length + 1) + ri * H_ROW;
        if (ry >= Y_STAB) return null;
        return (
          <g key={ri}>
            <rect x="0" y={ry} width={C[0].w} height={H_ROW} fill={XL_COLHDR}/>
            <text x={C[0].w/2} y={ry+17} textAnchor="middle"
              fontFamily="JetBrains Mono,monospace" fontSize="10" fill={XL_SUBTEXT}
            >{ROWS.length+2+ri}</text>
            <rect x="0" y={ry+H_ROW-1} width="900" height="1" fill={XL_GRID}/>
          </g>
        );
      })}

      {/* ═══ シートタブ ═══ */}
      <rect x="0" y={Y_STAB} width="900" height={H_STAB} fill="#F2F2F2"/>
      <rect x="0" y={Y_STAB} width="900" height="1" fill="#BDBDBD"/>
      {/* ← → ナビ */}
      <text x="8" y={Y_STAB+17} fontFamily="sans-serif" fontSize="11" fill={XL_SUBTEXT}>◀ ▶</text>
      {/* アクティブタブ「香盤表」 */}
      <rect x="34" y={Y_STAB+2} width="72" height={H_STAB-2} fill="white" stroke="#BDBDBD" strokeWidth="0.8"/>
      <rect x="34" y={Y_STAB+2} width="72" height="2.5" fill={XL_GREEN}/>
      <text x="70" y={Y_STAB+17} textAnchor="middle"
        fontFamily="Noto Sans JP,sans-serif" fontWeight="700" fontSize="10.5" fill={XL_GREEN}
      >香盤表</text>
      {/* 非アクティブタブ「PPM」 */}
      <rect x="108" y={Y_STAB+4} width="52" height={H_STAB-4} fill="#E8E8E8" stroke="#BDBDBD" strokeWidth="0.8"/>
      <text x="134" y={Y_STAB+17} textAnchor="middle"
        fontFamily="Noto Sans JP,sans-serif" fontSize="10.5" fill={XL_SUBTEXT}
      >PPM</text>
      {/* ＋ 新規シート */}
      <circle cx="174" cy={Y_STAB+13} r="8" fill="none" stroke="#BDBDBD" strokeWidth="1"/>
      <text x="174" y={Y_STAB+17} textAnchor="middle"
        fontFamily="sans-serif" fontSize="12" fill={XL_SUBTEXT}
      >+</text>

      {/* ═══ ステータスバー ═══ */}
      <rect x="0" y={Y_STATUS} width="900" height={H_STATUS} fill="#F2F2F2"/>
      <rect x="0" y={Y_STATUS} width="900" height="1" fill="#D0D0D0"/>
      <text x="10" y={Y_STATUS+18} fontFamily="Noto Sans JP,sans-serif" fontSize="10" fill={XL_SUBTEXT}>準備完了</text>
      <text x="450" y={Y_STATUS+18} textAnchor="middle"
        fontFamily="Noto Sans JP,sans-serif" fontSize="10" fill={XL_SUBTEXT}
      >平均: 08:30　データの個数: 4　合計: 4シーン</text>
      {/* ズームスライダー（右） */}
      <text x="840" y={Y_STATUS+18} textAnchor="middle"
        fontFamily="JetBrains Mono,monospace" fontSize="9" fill={XL_SUBTEXT}
      >─────── 100%</text>
      <text x="882" y={Y_STATUS+18} fontFamily="sans-serif" fontSize="11" fill={XL_SUBTEXT}>+</text>

    </svg>
  );
}

/** 基本8項目 イラスト */
function IllustItems8() {
  const items = [
    { n:1, label:"シーン番号",      sub:"撮影順管理の基本単位" },
    { n:2, label:"撮影日・時間",    sub:"日時と時間帯を明記" },
    { n:3, label:"ロケ地・スタジオ",sub:"正式名称と住所" },
    { n:4, label:"出演者・拘束時間",sub:"集合〜解放の時間" },
    { n:5, label:"スタッフ役割",    sub:"監督・カメラ・音声等" },
    { n:6, label:"使用機材",        sub:"カメラ・照明・音響" },
    { n:7, label:"カット数・メモ",  sub:"カット数と撮影内容" },
    { n:8, label:"備考・特記事項",  sub:"天候・許可・注意点" },
  ];
  const colors = ["#2196F3","#4CAF50","#9C27B0","#FF9800","#E91E63","#00BCD4","#FF5722","#607D8B"];
  const W=860, H=286, cols=4, rows=2, padX=18, padY=18;
  const cW=(W-padX*2)/cols, cH=(H-padY*2)/rows;
  return (
    <svg viewBox={`0 0 ${W} ${H}`} style={{ width:"100%", display:"block" }} xmlns="http://www.w3.org/2000/svg">
      <rect width={W} height={H} fill="#EFF6FF" rx="14"/>
      {items.map((item, i) => {
        const col=i%cols, row=Math.floor(i/cols);
        const x=padX+col*cW, y=padY+row*cH, c=colors[i];
        const cx2=x+cW/2, cy2=y+cH/2;
        return (
          <g key={i}>
            <rect x={x+6} y={y+6} width={cW-12} height={cH-12} rx="10" fill="white" stroke="#E2E8F0" strokeWidth="1.5"/>
            {/* left accent bar */}
            <rect x={x+6} y={y+14} width="5" height={cH-28} rx="2.5" fill={c}/>
            {/* number circle */}
            <circle cx={x+38} cy={cy2-8} r="16" fill={`${c}1A`}/>
            <circle cx={x+38} cy={cy2-8} r="16" fill="none" stroke={c} strokeWidth="1.5"/>
            <text x={x+38} y={cy2-3} textAnchor="middle" fontFamily="JetBrains Mono,monospace" fontWeight="800" fontSize="13" fill={c}>{item.n}</text>
            {/* label */}
            <text x={x+64} y={cy2-2} fontFamily="Noto Sans JP,sans-serif" fontWeight="800" fontSize="12.5" fill="#1E293B">{item.label}</text>
            {/* sub */}
            <text x={x+64} y={cy2+17} fontFamily="Noto Sans JP,sans-serif" fontSize="10" fill="#94A3B8">{item.sub}</text>
          </g>
        );
      })}
    </svg>
  );
}

/** 5ステップ フロー図 */
function IllustFlow5Steps() {
  const steps = [
    { n:1, lines:["脚本","読み込み"] },
    { n:2, lines:["撮影順","最適化"] },
    { n:3, lines:["時間","見積もり"] },
    { n:4, lines:["拘束時間","割り付け"] },
    { n:5, lines:["全員へ","共有"] },
  ];
  const W=860, H=164, bW=130, bH=94, aW=34;
  const totalW=steps.length*bW+(steps.length-1)*aW;
  const sx=(W-totalW)/2, sy=(H-bH)/2;
  const alphas=[0.38,0.52,0.65,0.78,0.92];
  return (
    <svg viewBox={`0 0 ${W} ${H}`} style={{ width:"100%", display:"block" }} xmlns="http://www.w3.org/2000/svg">
      <rect width={W} height={H} fill="#EFF6FF" rx="14"/>
      {steps.map((step, i) => {
        const x=sx+i*(bW+aW), isLast=i===steps.length-1;
        const a=alphas[i];
        return (
          <g key={i}>
            {/* box */}
            <rect x={x} y={sy} width={bW} height={bH} rx="10" fill={`rgba(33,150,243,${a})`}/>
            {/* step label */}
            <text x={x+12} y={sy+20} fontFamily="JetBrains Mono,monospace" fontWeight="800" fontSize="10" fill={`rgba(255,255,255,${a<0.6?0.7:0.85})`}>STEP {step.n}</text>
            {/* main text lines */}
            {step.lines.map((line, j) => (
              <text key={j} x={x+bW/2} y={sy+42+j*24} textAnchor="middle" fontFamily="Noto Sans JP,sans-serif" fontWeight="800" fontSize="15" fill="white">{line}</text>
            ))}
            {/* arrow */}
            {!isLast && (
              <g>
                <line x1={x+bW+5} y1={sy+bH/2} x2={x+bW+aW-7} y2={sy+bH/2} stroke="#93C5FD" strokeWidth="2"/>
                <polygon points={`${x+bW+aW-7},${sy+bH/2-5} ${x+bW+aW-1},${sy+bH/2} ${x+bW+aW-7},${sy+bH/2+5}`} fill="#93C5FD"/>
              </g>
            )}
          </g>
        );
      })}
    </svg>
  );
}

/** ヒーロービジュアル SVG — PPM記事用（PowerPoint アプリ UI 風） */
function HeroPPM() {
  // PowerPoint カラー定数
  const PP_RED     = "#B7472A"; // PowerPoint ブランドレッド（タイトルバー・ファイルタブ）
  const PP_PANEL   = "#F5F5F5"; // 左パネル背景
  const PP_WS      = "#ADADAD"; // スライドワークスペース（グレー）
  const PP_TEXT    = "#1E1E1E"; // リボン文字
  const PP_SUBTEXT = "#595959"; // リボン薄文字

  // レイアウト y 座標（合計 506px ぴったり）
  // タイトルバー: 0–30 / リボンタブ: 30–54 / リボン本体: 54–98
  // 左パネル+ワークスペース: 98–476 / ステータスバー: 476–506
  const Y_TITLE  = 0,   H_TITLE  = 30;
  const Y_TAB    = 30,  H_TAB    = 24;
  const Y_RBN    = 54,  H_RBN    = 44;
  const Y_WORK   = 98;
  const Y_STATUS = 476, H_STATUS = 30;
  const H_WORK   = Y_STATUS - Y_WORK; // 378

  const PANEL_W = 130, WORK_X = PANEL_W, WORK_W = 900 - PANEL_W; // 770

  // スライドサイズ（16:9、ワークスペース中央）
  const SL_W = 620;
  const SL_H = Math.round(SL_W * 9 / 16);                          // 349
  const SL_X = WORK_X + Math.round((WORK_W - SL_W) / 2);           // 205
  const SL_Y = Y_WORK + Math.round((H_WORK - SL_H) / 2);           // 113
  const SL_HDR_H = 54;

  // キャストデータ
  const CAST = [
    { role: "主演",     name: "田中 一郎", call: "08:30", scene: "SC-01,03,05", c: "#1E88E5" },
    { role: "ヒロイン", name: "鈴木 花子", call: "09:00", scene: "SC-01,03",    c: "#E91E63" },
    { role: "脇役A",   name: "佐藤 次郎", call: "13:00", scene: "SC-05",        c: "#43A047" },
  ];
  const CARD_GAP = 12;
  const CARDS_X  = SL_X + 18;
  const CARDS_Y  = SL_Y + SL_HDR_H + 14;
  const CARD_W   = Math.floor((SL_W - 36 - CARD_GAP * 2) / 3); // 186
  const CARD_H   = SL_Y + SL_H - 14 - CARDS_Y;                 // 267

  // リボンタブ
  const TABS = [
    { label: "ファイル",       w: 60, file:   true },
    { label: "ホーム",         w: 56, active: true },
    { label: "挿入",           w: 44 },
    { label: "デザイン",       w: 60 },
    { label: "画面切り替え",   w: 90 },
    { label: "アニメーション", w: 90 },
    { label: "スライドショー", w: 88 },
    { label: "校閲",           w: 44 },
    { label: "表示",           w: 44 },
  ];

  // スライドサムネイル
  const TH_W = 104, TH_H = 59;
  const TH_X = 20;
  const TH_COLORS = ["#1A237E", PP_RED, "#37474F", "#1B5E20", "#E65100"];
  const TH_LABELS = ["表紙", "キャスト", "衣装", "ロケ地", "スケジュール"];

  return (
    <svg viewBox="0 0 900 506" style={{ width:"100%", display:"block" }} xmlns="http://www.w3.org/2000/svg">
      <defs>
        <clipPath id="ppm-sl-clip">
          <rect x={SL_X} y={SL_Y} width={SL_W} height={SL_H} rx="2"/>
        </clipPath>
      </defs>

      {/* ═══ タイトルバー ═══ */}
      <rect width="900" height={H_TITLE} fill={PP_RED}/>
      <circle cx="18" cy={Y_TITLE+15} r="5.5" fill="#FF5F57"/>
      <circle cx="34" cy={Y_TITLE+15} r="5.5" fill="#FEBC2E"/>
      <circle cx="50" cy={Y_TITLE+15} r="5.5" fill="#28C840"/>
      <text x="70" y={Y_TITLE+19} fontFamily="sans-serif" fontSize="11" fill="rgba(255,255,255,0.55)">↩ ↪ 💾</text>
      <text x="450" y={Y_TITLE+19} textAnchor="middle" fontFamily="Noto Sans JP,sans-serif" fontWeight="600" fontSize="12" fill="white">PPM資料_夏空ドリンクCM_v1.0.pptx</text>
      <text x="844" y={Y_TITLE+19} fontFamily="sans-serif" fontSize="12" fill="rgba(255,255,255,0.5)">－ □ ✕</text>

      {/* ═══ リボンタブ行 ═══ */}
      <rect x="0" y={Y_TAB} width="900" height={H_TAB} fill="#F3F3F3"/>
      <rect x="0" y={Y_TAB+H_TAB-1} width="900" height="1" fill="#C8C8C8"/>
      {(() => {
        let tx = 0;
        return TABS.map(({ label, w, file, active }) => {
          const el = (
            <g key={label}>
              {file   && <rect x={tx} y={Y_TAB} width={w} height={H_TAB} fill={PP_RED}/>}
              {active && <rect x={tx} y={Y_TAB} width={w} height={H_TAB-1} fill="white"/>}
              {active && <rect x={tx} y={Y_TAB} width={w} height="2.5" fill={PP_RED}/>}
              <text x={tx+w/2} y={Y_TAB+15} textAnchor="middle"
                fontFamily="Noto Sans JP,sans-serif"
                fontWeight={active || file ? "700" : "400"} fontSize="11"
                fill={file ? "white" : active ? "#1A1A1A" : PP_SUBTEXT}
              >{label}</text>
            </g>
          );
          tx += w;
          return el;
        });
      })()}

      {/* ═══ リボン本体 ═══ */}
      <rect x="0" y={Y_RBN} width="900" height={H_RBN} fill="white"/>
      <rect x="0" y={Y_RBN+H_RBN-1} width="900" height="1" fill="#D0D0D0"/>
      {/* 新しいスライドボタン */}
      <rect x="6" y={Y_RBN+4} width="52" height={H_RBN-10} rx="3" fill="#F5F5F5" stroke="#D0D0D0" strokeWidth="0.8"/>
      <text x="32" y={Y_RBN+17} textAnchor="middle" fontSize="11">📊</text>
      <text x="32" y={Y_RBN+27} textAnchor="middle" fontFamily="Noto Sans JP,sans-serif" fontSize="6" fill={PP_TEXT}>新しい</text>
      <text x="32" y={Y_RBN+34} textAnchor="middle" fontFamily="Noto Sans JP,sans-serif" fontSize="6" fill={PP_TEXT}>スライド</text>
      {/* セパレータ */}
      <rect x="64" y={Y_RBN+6} width="1" height={H_RBN-14} fill="#D0D0D0"/>
      {/* フォント名ボックス */}
      <rect x="72" y={Y_RBN+8} width="110" height="16" rx="2" fill="white" stroke="#BDBDBD" strokeWidth="0.8"/>
      <text x="78" y={Y_RBN+20} fontFamily="Noto Sans JP,sans-serif" fontSize="10" fill={PP_TEXT}>游ゴシック</text>
      {/* フォントサイズボックス */}
      <rect x="186" y={Y_RBN+8} width="34" height="16" rx="2" fill="white" stroke="#BDBDBD" strokeWidth="0.8"/>
      <text x="203" y={Y_RBN+20} textAnchor="middle" fontFamily="JetBrains Mono,monospace" fontSize="10" fill={PP_TEXT}>24</text>
      {/* B I U */}
      <text x="232" y={Y_RBN+26} fontFamily="sans-serif" fontWeight="900" fontSize="13" fill={PP_TEXT}>B</text>
      <text x="248" y={Y_RBN+26} fontFamily="sans-serif" fontStyle="italic" fontSize="13" fill={PP_SUBTEXT}>I</text>
      <text x="262" y={Y_RBN+26} fontFamily="sans-serif" fontSize="13" fill={PP_SUBTEXT}>U</text>
      <rect x="260" y={Y_RBN+31} width="14" height="2.5" rx="1" fill={PP_RED}/>
      {/* セパレータ */}
      <rect x="282" y={Y_RBN+6} width="1" height={H_RBN-14} fill="#D0D0D0"/>
      {/* 配置アイコン（線 3 本 × 3 グループ） */}
      {[292,308,324].map(bx => (
        <g key={bx}>{[0,6,12].map(dy => (
          <rect key={dy} x={bx} y={Y_RBN+12+dy} width="14" height="2" rx="1" fill={PP_SUBTEXT} opacity="0.45"/>
        ))}</g>
      ))}
      {/* セパレータ */}
      <rect x="346" y={Y_RBN+6} width="1" height={H_RBN-14} fill="#D0D0D0"/>
      {/* フォントカラーボタン */}
      <rect x="354" y={Y_RBN+6} width="28" height={H_RBN-14} rx="2" fill="#F5F5F5" stroke="#D0D0D0" strokeWidth="0.8"/>
      <text x="368" y={Y_RBN+22} textAnchor="middle" fontFamily="sans-serif" fontWeight="900" fontSize="11" fill={PP_TEXT}>A</text>
      <rect x="356" y={Y_RBN+30} width="24" height="3" rx="1" fill={PP_RED}/>

      {/* ═══ 左パネル（スライドサムネイル） ═══ */}
      <rect x="0" y={Y_WORK} width={PANEL_W} height={H_WORK} fill={PP_PANEL}/>
      <rect x={PANEL_W-1} y={Y_WORK} width="1" height={H_WORK} fill="#C8C8C8"/>
      {TH_LABELS.map((label, i) => {
        const ty = Y_WORK + 12 + i * (TH_H + 10);
        const isActive = i === 1;
        const tc = TH_COLORS[i];
        return (
          <g key={i}>
            {/* アクティブ選択ハイライト */}
            {isActive && <rect x={TH_X-5} y={ty-5} width={TH_W+10} height={TH_H+10} rx="3"
              fill={`${PP_RED}20`} stroke={PP_RED} strokeWidth="1.5"/>}
            {/* サムネイル白地 */}
            <rect x={TH_X} y={ty} width={TH_W} height={TH_H} fill="white" rx="1"/>
            {/* サムネイルヘッダー色帯 */}
            <rect x={TH_X} y={ty} width={TH_W} height={Math.round(TH_H*0.3)} fill={tc} rx="1"/>
            {/* コンテンツ擬似行 */}
            {[0,1,2].map(li => (
              <rect key={li} x={TH_X+5} y={ty+Math.round(TH_H*0.38)+li*8} width={TH_W-10} height="4" rx="2" fill="#E0E0E0"/>
            ))}
            {/* スライド番号 */}
            <text x={TH_X-4} y={ty+TH_H/2+4} textAnchor="end"
              fontFamily="JetBrains Mono,monospace" fontSize="9"
              fill={isActive ? PP_RED : PP_SUBTEXT}>{i+1}</text>
          </g>
        );
      })}

      {/* ═══ スライドワークスペース ═══ */}
      <rect x={WORK_X} y={Y_WORK} width={WORK_W} height={H_WORK} fill={PP_WS}/>
      {/* スライド影 */}
      <rect x={SL_X+3} y={SL_Y+3} width={SL_W} height={SL_H} rx="2" fill="rgba(0,0,0,0.22)"/>
      {/* スライド本体（clipPath でヘッダー含め丸角を揃える） */}
      <g clipPath="url(#ppm-sl-clip)">
        <rect x={SL_X} y={SL_Y} width={SL_W} height={SL_H} fill="white"/>

        {/* ── スライドヘッダー ── */}
        <rect x={SL_X} y={SL_Y} width={SL_W} height={SL_HDR_H} fill={PP_RED}/>
        {/* PPMバッジ */}
        <rect x={SL_X+14} y={SL_Y+14} width={38} height={18} rx="9" fill="rgba(255,255,255,0.22)"/>
        <text x={SL_X+33} y={SL_Y+26} textAnchor="middle"
          fontFamily="JetBrains Mono,monospace" fontSize="10" fontWeight="800" fill="white">PPM</text>
        {/* スライドタイトル */}
        <text x={SL_X+62} y={SL_Y+22} fontFamily="Noto Sans JP,sans-serif" fontSize="9.5" fill="rgba(255,255,255,0.65)">プリプロダクションミーティング資料</text>
        <text x={SL_X+62} y={SL_Y+43} fontFamily="Noto Sans JP,sans-serif" fontSize="15" fontWeight="900" fill="white">キャスト・出演者</text>
        {/* ページ番号 */}
        <text x={SL_X+SL_W-12} y={SL_Y+34} textAnchor="end"
          fontFamily="JetBrains Mono,monospace" fontSize="9.5" fill="rgba(255,255,255,0.4)">2 / 10</text>

        {/* ── キャストカード ── */}
        {CAST.map(({ role, name, call, scene, c }, ci) => {
          const cx = CARDS_X + ci * (CARD_W + CARD_GAP);
          const cy = CARDS_Y;
          const acx = cx + CARD_W / 2;
          const acy = cy + 64;
          return (
            <g key={ci}>
              <rect x={cx} y={cy} width={CARD_W} height={CARD_H} rx="6"
                fill="white" stroke="#E2E8F0" strokeWidth="1.2"/>
              <rect x={cx} y={cy} width={CARD_W} height={5} rx="2" fill={c}/>
              {/* アバター */}
              <circle cx={acx} cy={acy} r="26" fill={`${c}1A`}/>
              <circle cx={acx} cy={acy} r="26" fill="none" stroke={c} strokeWidth="1.5"/>
              <circle cx={acx} cy={acy-8} r="10" fill={c} opacity="0.7"/>
              <ellipse cx={acx} cy={acy+17} rx="16" ry="9" fill={c} opacity="0.5"/>
              {/* 役割バッジ */}
              <rect x={acx-22} y={cy+100} width={44} height={17} rx="8.5" fill={c}/>
              <text x={acx} y={cy+112} textAnchor="middle"
                fontFamily="Noto Sans JP,sans-serif" fontSize="9" fontWeight="800" fill="white">{role}</text>
              {/* 名前 */}
              <text x={acx} y={cy+133} textAnchor="middle"
                fontFamily="Noto Sans JP,sans-serif" fontSize="13" fontWeight="700" fill="#1E293B">{name}</text>
              {/* 区切り線 */}
              <rect x={cx+12} y={cy+144} width={CARD_W-24} height="1" fill="#E2E8F0"/>
              {/* 集合時間 */}
              <text x={cx+12} y={cy+160} fontFamily="Noto Sans JP,sans-serif" fontSize="7.5" fill="#94A3B8">集合時間</text>
              <text x={acx} y={cy+176} textAnchor="middle"
                fontFamily="JetBrains Mono,monospace" fontSize="13" fontWeight="700" fill={c}>{call}</text>
              {/* 出演シーン */}
              <text x={cx+12} y={cy+193} fontFamily="Noto Sans JP,sans-serif" fontSize="7.5" fill="#94A3B8">出演シーン</text>
              <rect x={cx+12} y={cy+199} width={CARD_W-24} height="18" rx="4" fill={`${c}14`}/>
              <text x={acx} y={cy+211} textAnchor="middle"
                fontFamily="JetBrains Mono,monospace" fontSize="8.5" fontWeight="700" fill={c}>{scene}</text>
            </g>
          );
        })}
      </g>

      {/* ═══ ステータスバー ═══ */}
      <rect x="0" y={Y_STATUS} width="900" height={H_STATUS} fill="#F3F3F3"/>
      <rect x="0" y={Y_STATUS} width="900" height="1" fill="#D0D0D0"/>
      <text x="10" y={Y_STATUS+18} fontFamily="Noto Sans JP,sans-serif" fontSize="10" fill={PP_SUBTEXT}>スライド 2 / 10</text>
      <text x="450" y={Y_STATUS+18} textAnchor="middle" fontFamily="Noto Sans JP,sans-serif" fontSize="10" fill={PP_SUBTEXT}>オフィス テーマ</text>
      {/* ズームスライダー（右） */}
      <text x="840" y={Y_STATUS+18} textAnchor="middle"
        fontFamily="JetBrains Mono,monospace" fontSize="9" fill={PP_SUBTEXT}>───────  80%</text>
      <text x="886" y={Y_STATUS+18} fontFamily="sans-serif" fontSize="11" fill={PP_SUBTEXT}>+</text>

    </svg>
  );
}

/** PPM 10ブロック イラスト */
function IllustPPM10Blocks() {
  const items = [
    { n: 1,  label: "表紙",           sub: "プロジェクト名・PPM日" },
    { n: 2,  label: "スタッフリスト", sub: "全役職と担当者名" },
    { n: 3,  label: "企画概要",       sub: "目的・ターゲット・コンセプト" },
    { n: 4,  label: "あらすじ",       sub: "映像構成の流れ" },
    { n: 5,  label: "脚本",           sub: "確定版（版番号明記）" },
    { n: 6,  label: "キャスト一覧",   sub: "写真・役名・出演シーン" },
    { n: 7,  label: "衣装・ヘアメイク", sub: "参考写真と方向性" },
    { n: 8,  label: "美術・小道具",   sub: "写真と使用シーン" },
    { n: 9,  label: "ロケ地情報",     sub: "住所・写真・搬入経路" },
    { n: 10, label: "仮香盤",         sub: "撮影スケジュール案" },
  ];
  const colors = ["#2196F3","#4CAF50","#9C27B0","#FF9800","#E91E63","#00BCD4","#FF5722","#607D8B","#3F51B5","#F44336"];
  const W = 860, H = 310, cols = 5, rows = 2, padX = 18, padY = 18;
  const cW = (W - padX * 2) / cols, cH = (H - padY * 2) / rows;
  return (
    <svg viewBox={`0 0 ${W} ${H}`} style={{ width: "100%", display: "block" }} xmlns="http://www.w3.org/2000/svg">
      <rect width={W} height={H} fill="#EFF6FF" rx="14"/>
      {items.map((item, i) => {
        const col = i % cols, row = Math.floor(i / cols);
        const x = padX + col * cW, y = padY + row * cH, c = colors[i];
        const cy2 = y + cH / 2;
        return (
          <g key={i}>
            <rect x={x + 6} y={y + 6} width={cW - 12} height={cH - 12} rx="10" fill="white" stroke="#E2E8F0" strokeWidth="1.5"/>
            <rect x={x + 6} y={y + 14} width="5" height={cH - 28} rx="2.5" fill={c}/>
            <circle cx={x + 36} cy={cy2 - 10} r="15" fill={`${c}1A`}/>
            <circle cx={x + 36} cy={cy2 - 10} r="15" fill="none" stroke={c} strokeWidth="1.5"/>
            <text x={x + 36} y={cy2 - 4} textAnchor="middle" fontFamily="JetBrains Mono,monospace" fontWeight="800" fontSize="11" fill={c}>{item.n}</text>
            <text x={x + 60} y={cy2 - 2} fontFamily="Noto Sans JP,sans-serif" fontWeight="800" fontSize="11" fill="#1E293B">{item.label}</text>
            <text x={x + 60} y={cy2 + 16} fontFamily="Noto Sans JP,sans-serif" fontSize="9" fill="#94A3B8">{item.sub}</text>
          </g>
        );
      })}
    </svg>
  );
}

/** PPM 6ステップ フロー図 */
function IllustPPMFlow6() {
  const steps = [
    { n: 1, lines: ["情報", "収集"] },
    { n: 2, lines: ["構成", "設計"] },
    { n: 3, lines: ["素材", "収集"] },
    { n: 4, lines: ["スライド", "組み立て"] },
    { n: 5, lines: ["社内", "レビュー"] },
    { n: 6, lines: ["クライアント", "提出"] },
  ];
  const W = 860, H = 164, bW = 108, bH = 94, aW = 22;
  const totalW = steps.length * bW + (steps.length - 1) * aW;
  const sx = (W - totalW) / 2, sy = (H - bH) / 2;
  const alphas = [0.38, 0.52, 0.62, 0.72, 0.82, 0.93];
  return (
    <svg viewBox={`0 0 ${W} ${H}`} style={{ width: "100%", display: "block" }} xmlns="http://www.w3.org/2000/svg">
      <rect width={W} height={H} fill="#EFF6FF" rx="14"/>
      {steps.map((step, i) => {
        const x = sx + i * (bW + aW), isLast = i === steps.length - 1;
        const a = alphas[i];
        return (
          <g key={i}>
            <rect x={x} y={sy} width={bW} height={bH} rx="10" fill={`rgba(33,150,243,${a})`}/>
            <text x={x + 8} y={sy + 18} fontFamily="JetBrains Mono,monospace" fontWeight="800" fontSize="9"
              fill={`rgba(255,255,255,${a < 0.6 ? 0.65 : 0.85})`}>STEP {step.n}</text>
            {step.lines.map((line, j) => (
              <text key={j} x={x + bW / 2} y={sy + 40 + j * 22} textAnchor="middle"
                fontFamily="Noto Sans JP,sans-serif" fontWeight="800" fontSize="13" fill="white">{line}</text>
            ))}
            {!isLast && (
              <g>
                <line x1={x + bW + 4} y1={sy + bH / 2} x2={x + bW + aW - 6} y2={sy + bH / 2} stroke="#93C5FD" strokeWidth="2"/>
                <polygon points={`${x + bW + aW - 6},${sy + bH / 2 - 4} ${x + bW + aW},${sy + bH / 2} ${x + bW + aW - 6},${sy + bH / 2 + 4}`} fill="#93C5FD"/>
              </g>
            )}
          </g>
        );
      })}
    </svg>
  );
}

// ─────────────────────────────────────────────────────────
// Shared components
// ─────────────────────────────────────────────────────────

/** カテゴリ別グラデーションプレースホルダー */
function CoverPlaceholder({ category }) {
  const s = CATEGORY_STYLES[category] ?? { bg: "linear-gradient(135deg, #2196F3, #42A5F5)", IcoComponent: (p) => <svg {...sw} {...p} viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg> };
  const Ico = s.IcoComponent;
  return (
    <div style={{
      width: "100%", aspectRatio: "16/9",
      background: s.bg, display: "flex", alignItems: "center", justifyContent: "center",
    }}>
      <span style={{ display:"inline-flex", opacity: 0.55, color:"#fff" }}><Ico width={40} height={40}/></span>
    </div>
  );
}

/** 記事ごとのサムネイル画像（slug で分岐） */
function PostThumbnail({ post }) {
  if (post.slug === "kobancho") return <HeroCallsheet/>;
  if (post.slug === "ppm") return <HeroPPM/>;
  return <CoverPlaceholder category={post.category}/>;
}

/** 記事カード（featured = 大カード） */
function PostCard({ post, featured }) {
  const [hovered, setHovered] = useBlogState(false);

  if (featured) {
    return (
      <a
        href={post.href}
        style={{
          display: "block", borderRadius: 16, overflow: "hidden",
          background: "#fff",
          border: `2px solid ${hovered ? PRIM : "#E2E8F0"}`,
          boxShadow: hovered ? "0 8px 28px rgba(33,150,243,0.13)" : "0 1px 4px rgba(0,0,0,0.05)",
          textDecoration: "none", color: "inherit",
          transition: "border-color .2s, box-shadow .2s",
        }}
        onMouseEnter={() => setHovered(true)}
        onMouseLeave={() => setHovered(false)}
      >
        <div style={{ width: "100%", overflow: "hidden", aspectRatio: "16/9", maxHeight: 320 }}>
          <PostThumbnail post={post}/>
        </div>
        <div style={{ padding: 28 }}>
          <div style={{ marginBottom: 14 }}>
            <span style={{ background: PRIM, color: "#fff", fontSize: 11, fontWeight: 800, padding: "4px 12px", borderRadius: 999, fontFamily: HD }}>
              {post.category}
            </span>
          </div>
          <h2 style={{
            fontSize: "clamp(1.1rem, 2.5vw, 1.35rem)", fontWeight: 800,
            color: hovered ? PRIM : INK, lineHeight: 1.35, letterSpacing: "-.015em",
            marginBottom: 12, fontFamily: HD, transition: "color .2s",
          }}>
            {post.title}
          </h2>
          <p style={{ fontSize: 14, color: MUTED, lineHeight: 1.75, marginBottom: 20, fontFamily: HD }}>
            {post.description}
          </p>
          <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
            <time style={{ fontSize: 11, color: FAINT, fontFamily: MN }}>{post.publishedAt}</time>
            <span style={{ fontSize: 13, fontWeight: 700, color: hovered ? PRIM : FAINT, fontFamily: HD, transition: "color .2s" }}>
              読む →
            </span>
          </div>
        </div>
      </a>
    );
  }

  return (
    <a
      href={post.href}
      style={{
        display: "flex", flexDirection: "column", borderRadius: 12, overflow: "hidden",
        background: "#fff",
        border: `1.5px solid ${hovered ? PRIM : "#E2E8F0"}`,
        boxShadow: hovered ? "0 4px 18px rgba(33,150,243,0.11)" : "0 1px 3px rgba(0,0,0,0.04)",
        textDecoration: "none", color: "inherit",
        transition: "border-color .2s, box-shadow .2s",
      }}
      onMouseEnter={() => setHovered(true)}
      onMouseLeave={() => setHovered(false)}
    >
      <div style={{ width: "100%", overflow: "hidden", aspectRatio: "16/9" }}>
        <PostThumbnail post={post}/>
      </div>
      <div style={{ padding: 16, display: "flex", flexDirection: "column", flex: 1 }}>
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 10 }}>
          <span style={{ background: "#E3F2FD", color: "#1565C0", fontSize: 10, fontWeight: 800, padding: "2px 10px", borderRadius: 999, fontFamily: HD }}>
            {post.category}
          </span>
          <time style={{ fontSize: 10, color: FAINT, fontFamily: MN }}>{post.publishedAt}</time>
        </div>
        <h2 style={{
          fontSize: "0.95rem", fontWeight: 700,
          color: hovered ? PRIM : INK, lineHeight: 1.5, letterSpacing: "-.01em",
          flex: 1, fontFamily: HD, transition: "color .2s",
        }}>
          {post.title}
        </h2>
        <div style={{ display: "flex", justifyContent: "flex-end", marginTop: 12 }}>
          <span style={{ fontSize: 11, fontWeight: 700, color: hovered ? PRIM : "#D1D5DB" }}>→</span>
        </div>
      </div>
    </a>
  );
}

/** インライン目次（H2+H3 二階層対応） */
function InlineTOC({ headings }) {
  if (!headings || headings.length < 2) return null;
  let h2Num = 0;
  return (
    <nav
      style={{ marginBottom: 36, borderRadius: 12, padding: "20px 24px", background: "#fff", border: "1.5px solid #E2E8F0" }}
      aria-label="目次"
    >
      <p style={{ fontFamily: MN, fontSize: 12, fontWeight: 900, color: PRIM, letterSpacing: ".1em", textTransform: "uppercase", margin: "0 0 14px" }}>
        目次
      </p>
      <ol style={{ listStyle: "none", padding: 0, margin: 0, display: "flex", flexDirection: "column" }}>
        {headings.map((h) => {
          const isH2 = h.level === 2;
          if (isH2) h2Num++;
          const num = h2Num;
          if (isH2) {
            return (
              <li key={h.id} style={{
                display: "flex", alignItems: "baseline", gap: 10,
                padding: "6px 0", borderBottom: "1px solid #DBEAFE",
              }}>
                <span style={{ fontFamily: MN, fontSize: "0.63rem", fontWeight: 800, color: "#93C5FD", minWidth: "1.6rem", flexShrink: 0 }}>
                  {String(num).padStart(2, "0")}
                </span>
                <a href={`#${h.id}`} style={{ fontSize: 14, fontWeight: 600, color: "#374151", lineHeight: 1.45, textDecoration: "none", fontFamily: HD }}>
                  {h.text}
                </a>
              </li>
            );
          } else {
            return (
              <li key={h.id} style={{ display: "flex", alignItems: "baseline", gap: 8, padding: "4px 0 4px 30px" }}>
                <span style={{ fontSize: 10, color: "#BFDBFE", flexShrink: 0 }}>└</span>
                <a href={`#${h.id}`} style={{ fontSize: 12.5, color: "#6B7280", lineHeight: 1.4, textDecoration: "none", fontFamily: HD }}>
                  {h.text}
                </a>
              </li>
            );
          }
        })}
      </ol>
    </nav>
  );
}

/** FAQ アコーディオン */
function FAQAccordion({ items }) {
  const [open, setOpen] = useBlogState(null);
  return (
    <div style={{ border: "1px solid #E2E8F0", borderRadius: 12, overflow: "hidden" }}>
      {items.map((item, i) => (
        <div key={i} style={{ borderTop: i === 0 ? "none" : "1px solid #E2E8F0" }}>
          <button
            onClick={() => setOpen(open === i ? null : i)}
            style={{
              width: "100%", textAlign: "left", padding: "16px 20px",
              background: open === i ? "#EEF6FF" : "#fff",
              border: "none", cursor: "pointer",
              display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 12,
              fontFamily: HD, fontWeight: 700, fontSize: 15, color: INK,
              transition: "background .15s",
            }}
          >
            <span style={{ display: "flex", alignItems: "flex-start", gap: 10, flex: 1 }}>
              <span style={{ color: PRIM, fontFamily: MN, fontSize: 11, fontWeight: 800, flexShrink: 0, marginTop: 1 }}>Q{i + 1}</span>
              <span>{item.q}</span>
            </span>
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke={PRIM} strokeWidth="2.5"
              style={{ flexShrink: 0, transform: open === i ? "rotate(180deg)" : "none", transition: "transform .2s", marginTop: 2 }}>
              <path d="M6 9l6 6 6-6" strokeLinecap="round" strokeLinejoin="round"/>
            </svg>
          </button>
          {open === i && (
            <div style={{
              padding: "4px 20px 18px 44px",
              fontFamily: HD, fontSize: 15, color: "#374151", lineHeight: 1.85, background: "#EEF6FF",
            }}>
              <p style={{ margin: 0 }}>{item.a}</p>
            </div>
          )}
        </div>
      ))}
    </div>
  );
}

/** ブログ CTA — Aパターン：青背景センター揃え（カード全体がリンク） */
function BlogCTACenter() {
  const [hovCard, setHovCard] = useBlogState(false); // カード全体ホバー → スケール
  const [hovBtn,  setHovBtn]  = useBlogState(false); // ボタンのみホバー  → シャドウ
  return (
    <a
      href="https://service.kyakuhon-pakkun.com/pricing"
      onMouseEnter={() => setHovCard(true)}
      onMouseLeave={() => { setHovCard(false); setHovBtn(false); }}
      style={{
        display: "block", marginTop: 52, padding: "48px 40px",
        borderRadius: 18, textAlign: "center", textDecoration: "none",
        background: "linear-gradient(160deg, #1E88E5 0%, #2196F3 100%)",
        boxShadow: "0 4px 24px rgba(33,150,243,0.3)",
        cursor: "pointer",
        transform: hovCard ? "scale(1.015)" : "scale(1)",
        transition: "transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1)",
      }}
    >
      {/* product */}
      <div style={{
        display: "inline-block", background: "#fff", borderRadius: 12,
        padding: "8px 20px", margin: "0 0 10px",
      }}>
        <span style={{ fontFamily: WM, fontWeight: 900, fontSize: 20, color: PRIM, letterSpacing: ".02em" }}>脚本パックン</span>
      </div>
      <p style={{ fontFamily: HD, fontSize: 12, color: "rgba(255,255,255,0.55)", margin: "0 0 22px" }}>香盤AI生成ツール</p>
      {/* headline */}
      <p style={{ fontFamily: HD, fontWeight: 800, fontSize: 22, color: "#fff", lineHeight: 1.45, margin: "0 0 30px" }}>
        映像制作の進行管理を、<br/>もっとスマートに。
      </p>
      {/* divider */}
      <div style={{ borderTop: "1px solid rgba(255,255,255,0.2)", maxWidth: 280, margin: "0 auto 28px" }}/>
      {/* button — ボタン自身のホバーだけシャドウ */}
      <div
        onMouseEnter={() => setHovBtn(true)}
        onMouseLeave={() => setHovBtn(false)}
        style={{
          display: "inline-block", fontWeight: 800, fontSize: 16,
          padding: "16px 48px", borderRadius: 999,
          background: "#fff", color: PRIM, fontFamily: HD,
          boxShadow: hovBtn ? "0 10px 28px rgba(0,0,0,0.22)" : "none",
          transition: "box-shadow 0.22s ease",
        }}
      >
        プランを見る →
      </div>
    </a>
  );
}

/** ブログ CTA — Bパターン：左揃え × スプリット */
function BlogCTALeft() {
  const [cardH, setCardH] = useBlogState(false);
  const [btn1H, setBtn1H] = useBlogState(false);
  const [btn2H, setBtn2H] = useBlogState(false);
  const features = [
    "脚本をアップロードするだけ",
    "手作業1週間 → 数分に短縮",
    "脚本変更にも自動で追従",
  ];
  return (
    <div
      onMouseEnter={() => setCardH(true)} onMouseLeave={() => setCardH(false)}
      style={{
        marginTop: 52, borderRadius: 18, overflow: "hidden",
        border: `1.5px solid ${cardH ? "#BFDBFE" : "#E2E8F0"}`,
        boxShadow: cardH ? "0 6px 32px rgba(33,150,243,0.1)" : "0 2px 18px rgba(0,0,0,0.05)",
        display: "flex",
        transition: "border-color .25s, box-shadow .25s",
      }}
    >
      {/* ── 左：テキスト ── */}
      <div style={{ flex: "0 0 57%", padding: "38px 36px 38px 40px", background: "#fff" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 20 }}>
          <div style={{
            width: 42, height: 42, borderRadius: 11, background: PRIM, flexShrink: 0,
            display: "flex", alignItems: "center", justifyContent: "center",
            fontFamily: WM, fontWeight: 900, fontSize: 21, color: "#fff",
            boxShadow: "0 3px 12px rgba(33,150,243,0.3)",
            transform: cardH ? "scale(1.06)" : "scale(1)",
            transition: "transform .25s ease",
          }}>パ</div>
          <div>
            <p style={{ fontFamily: HD, fontSize: 11, fontWeight: 800, color: PRIM, letterSpacing: ".1em", textTransform: "uppercase", margin: 0 }}>脚本パックン</p>
            <p style={{ fontFamily: HD, fontSize: 12, color: FAINT, margin: 0 }}>香盤AI生成ツール</p>
          </div>
        </div>
        <p style={{ fontFamily: HD, fontWeight: 800, fontSize: 20, color: INK, lineHeight: 1.5, margin: "0 0 26px" }}>
          映像制作の進行管理を、<br/>もっとスマートに。
        </p>
        <div style={{ display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }}>
          <a
            href="https://service.kyakuhon-pakkun.com/pricing"
            onMouseEnter={() => setBtn1H(true)} onMouseLeave={() => setBtn1H(false)}
            style={{
              display: "inline-block", fontWeight: 800, fontSize: 14,
              padding: "13px 26px", borderRadius: 999,
              background: PRIM, color: "#fff", textDecoration: "none", fontFamily: HD,
              boxShadow: btn1H ? "0 8px 22px rgba(33,150,243,0.42)" : "0 3px 12px rgba(33,150,243,0.28)",
              transform: btn1H ? "translateY(-2px) scale(1.03)" : "none",
              transition: "all .2s ease",
            }}
          >
            プランを見る →
          </a>
          <a
            href="https://service.kyakuhon-pakkun.com"
            onMouseEnter={() => setBtn2H(true)} onMouseLeave={() => setBtn2H(false)}
            style={{
              display: "inline-block", fontWeight: 700, fontSize: 14,
              padding: "12px 20px", borderRadius: 999,
              border: `1.5px solid ${btn2H ? PRIM : "#E2E8F0"}`,
              background: "#fff", color: btn2H ? PRIM : MUTED,
              textDecoration: "none", fontFamily: HD,
              transition: "all .18s ease",
            }}
          >
            サービス詳細
          </a>
        </div>
      </div>
      {/* ── 右：特徴リスト ── */}
      <div style={{
        flex: 1, padding: "38px 32px",
        background: "#F0F7FF", borderLeft: "1px solid #E2E8F0",
        display: "flex", flexDirection: "column", justifyContent: "center", gap: 18,
      }}>
        {features.map((f, i) => (
          <div key={i} style={{ display: "flex", alignItems: "flex-start", gap: 12 }}>
            <div style={{
              width: 24, height: 24, borderRadius: "50%", flexShrink: 0, marginTop: 1,
              background: "#fff", border: "1.5px solid #BFDBFE",
              display: "flex", alignItems: "center", justifyContent: "center",
            }}>
              <svg width="11" height="11" viewBox="0 0 11 11" fill="none">
                <path d="M1.5 5.5L4.2 8.2L9.5 2.5" stroke={PRIM} strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/>
              </svg>
            </div>
            <span style={{ fontFamily: HD, fontSize: 13.5, fontWeight: 700, color: "#1E293B", lineHeight: 1.6 }}>{f}</span>
          </div>
        ))}
      </div>
    </div>
  );
}

/** ブログ CTA — 全ブログ共通（Aパターン：センター揃え） */
function BlogCTA() { return <BlogCTACenter/>; }

// ─────────────────────────────────────────────────────────
// Blog Index page
// ─────────────────────────────────────────────────────────

function BlogIndex() {
  const [activeCategory, setActiveCategory] = useBlogState("ALL");

  const filtered = activeCategory === "ALL"
    ? BLOG_POSTS
    : BLOG_POSTS.filter((p) => p.category === activeCategory);

  const featured = filtered[0];
  const rest = filtered.slice(1);

  return (
    <>
      <SiteHeader/>

      <main style={{ flex: 1, maxWidth: 960, margin: "0 auto", width: "100%", padding: "0 20px" }}>

        {/* Hero */}
        <section style={{ paddingTop: 48, paddingBottom: 40, borderBottom: "1px solid #E5E7EB" }}>
          <span style={{
            display: "inline-block", fontSize: 12, fontWeight: 800,
            color: PRIM, letterSpacing: ".2em", background: "#E3F2FD",
            padding: "8px 18px", borderRadius: 30, marginBottom: 20,
            fontFamily: HD, textTransform: "uppercase",
          }}>
            BLOG
          </span>
          <h1 style={{
            fontSize: "clamp(2rem, 5vw, 3.2rem)", fontWeight: 900,
            color: INK, lineHeight: 1.3, letterSpacing: "-.02em",
            marginBottom: 16, fontFamily: HD, fontFeatureSettings: '"palt"',
          }}>
            ブログ
          </h1>
          <p style={{ fontSize: 15, color: MUTED, lineHeight: 1.8, fontFamily: HD }}>
            香盤表・PPM資料・進行管理——映像制作の現場の課題に、プロが実際に使う知識でアプローチします。
          </p>
        </section>

        {/* Category filter */}
        <div style={{ display: "flex", flexWrap: "wrap", gap: 8, padding: "16px 0", borderBottom: "1px solid #E2E8F0" }}>
          {["ALL", ...CATEGORIES].map((cat) => {
            const active = activeCategory === cat;
            return (
              <button
                key={cat}
                onClick={() => setActiveCategory(cat)}
                style={{
                  padding: "6px 16px", borderRadius: 999, fontSize: 11, fontWeight: 800,
                  fontFamily: HD, textTransform: "uppercase", letterSpacing: ".05em",
                  cursor: "pointer", border: active ? "none" : "1.5px solid #E2E8F0",
                  background: active ? PRIM : "#fff",
                  color: active ? "#fff" : MUTED,
                  transition: "all .15s",
                }}
              >
                {cat}
              </button>
            );
          })}
        </div>

        {/* Featured */}
        {featured && (
          <section style={{ paddingTop: 28, paddingBottom: 28, borderBottom: "1px solid #E2E8F0" }}>
            <PostCard post={featured} featured/>
          </section>
        )}

        {/* Grid */}
        {rest.length > 0 && (
          <section style={{ paddingTop: 28, paddingBottom: 56 }}>
            <div className="post-grid">
              {rest.map((post) => <PostCard key={post.slug} post={post}/>)}
            </div>
          </section>
        )}

        {filtered.length === 0 && (
          <p style={{ textAlign: "center", padding: "80px 0", fontSize: 14, color: FAINT, fontFamily: HD }}>
            このカテゴリの記事はまだありません。
          </p>
        )}
      </main>

      <SiteFooter/>
    </>
  );
}

// ─────────────────────────────────────────────────────────
// Article page: kobancho
// ─────────────────────────────────────────────────────────

function BlogArticleKobancho() {
  return (
    <>
      <ReadingProgress/>
      <SiteHeader/>

      <main style={{
        flex: 1, maxWidth: 900, margin: "0 auto", width: "100%",
        padding: "0 48px 128px",
        background: "#fff",
        boxShadow: "0 0 0 1px rgba(0,0,0,0.04), 0 4px 32px rgba(0,0,0,0.06)",
      }}>

        {/* Breadcrumb */}
        <nav style={{
          display: "flex", alignItems: "center", gap: 8,
          paddingTop: 28, fontSize: 11, color: FAINT, fontFamily: HD,
        }}>
          <a href="/blog" style={{ color: PRIM }}>Blog</a>
          <span style={{ color: "#D1D5DB" }}>/</span>
          <span>基礎知識</span>
        </nav>

        {/* Article header */}
        <header style={{ marginBottom: 32, marginTop: 18 }}>
          <span style={{
            display: "inline-block", fontSize: 11, fontWeight: 800,
            padding: "4px 12px", borderRadius: 999, marginBottom: 16,
            background: PRIM, color: "#fff", letterSpacing: ".05em", fontFamily: HD,
          }}>
            基礎知識
          </span>

          {/* H1 */}
          <h1 style={{
            fontSize: "clamp(1.6rem, 3.5vw, 2.1rem)", fontWeight: 900,
            color: INK, lineHeight: 1.3, letterSpacing: "-.015em",
            marginBottom: 14, fontFamily: HD, fontFeatureSettings: '"palt"',
          }}>
            【2026年版】香盤表とは？現役プロデューサーが作り方を徹底解説！
          </h1>

          {/* 小見出し — H1直下 */}
          <p style={{ fontSize: 16, color: MUTED, lineHeight: 1.85, marginBottom: 22, fontFamily: HD }}>
            香盤表の基本項目8つ・作り方5ステップ・現場で使える3つのコツを、広告・MV・映画制作を手がける現役プロデューサーが解説。
          </p>

          {/* ヒーロー画像 */}
          <div style={{ borderRadius: 16, overflow: "hidden", marginBottom: 24, boxShadow: "0 4px 24px rgba(0,0,0,0.10)" }}>
            <HeroCallsheet/>
          </div>

          {/* Meta */}
          <div style={{ display: "flex", alignItems: "center", gap: 16, fontSize: 11, color: FAINT, fontFamily: MN }}>
            <span>公開: <time dateTime="2026-05-26">2026-05-26</time></span>
          </div>
          <div style={{ display: "flex", flexWrap: "wrap", gap: 8, marginTop: 12 }}>
            {["香盤表", "映像制作", "進行管理", "AP", "助監督"].map((tag) => (
              <span key={tag} style={{ fontSize: "0.68rem", background: "#F3F4F6", color: FAINT, padding: "2px 10px", borderRadius: 999, fontFamily: HD }}>
                #{tag}
              </span>
            ))}
          </div>
        </header>

        {/* TL;DR */}
        <Reveal>
          <div style={{
            marginBottom: 36, borderRadius: 12, padding: "22px 26px",
            background: "#F0F7FF", border: "1.5px solid #BFDBFE",
          }}>
            <p style={{ fontFamily: MN, fontSize: 11, fontWeight: 800, color: PRIM, letterSpacing: ".12em", textTransform: "uppercase", margin: "0 0 14px" }}>
              TL;DR ― この記事の結論
            </p>
            <ul style={{ listStyle: "none", padding: 0, margin: 0, display: "flex", flexDirection: "column", gap: 10 }}>
              {[
                "香盤表とは、撮影に必要な全情報を一覧化したスケジュール管理表である。",
                "基本項目は8つ。シーン番号・日時・ロケ地・出演者・スタッフ・機材・カット数・備考が最低限必要だ。",
                "作り方は5ステップ。脚本読み込み→撮影順最適化→時間見積→拘束割付→共有の順で進める。",
                "精度を上げる実践コツは3つ：色分け・バッファ・出演者拘束最小化。",
                "脚本をアップロードするだけで香盤表を自動生成できる「脚本パックン」を使えば、手作業1週間分が大幅に短縮できる。",
              ].map((item, i) => (
                <li key={i} style={{ display: "flex", alignItems: "flex-start", gap: 10, fontFamily: HD, fontSize: 15, color: INK, lineHeight: 1.85 }}>
                  <span style={{ color: PRIM, fontWeight: 800, flexShrink: 0, marginTop: 2 }}>✓</span>
                  <span>{item}</span>
                </li>
              ))}
            </ul>
          </div>
        </Reveal>

        {/* Intro */}
        <Reveal delay={0.05}>
          <div className="prose">
            <p>1週間かけて作ったのに全部やり直しになった——。</p>
            <p>映像制作の現場に入ったばかりの頃、そんな経験をしたことはないだろうか。「とりあえず作ってみた」香盤表が、監督やプロデューサーに一発でNGを食らう。どこが間違っているのかもわからないまま、深夜に作り直す。</p>
            <p>正しい作り方を知れば、そのループから抜け出せる。この記事では、広告・MV・映画制作など幅広いプロジェクトを手がける現役プロデューサーの視点から、香盤表の基礎と正しい作り方を整理する。</p>
          </div>
        </Reveal>

        {/* 目次 */}
        <Reveal delay={0.08}>
          <InlineTOC headings={KOBANCHO_HEADINGS}/>
        </Reveal>

        {/* ═══════════ Section 1 ═══════════ */}
        <Reveal>
          <div className="prose">
            <h2 id="香盤表とは何か">香盤表とは何か？</h2>
            <p className="answer-capsule"><strong>香盤表とは、撮影に必要な情報を一覧化したスケジュール管理表である。</strong></p>
            <p>シーン番号・撮影場所・出演者・スタッフ・使用機材・カット数——これを1枚に集約して、撮影当日の全員の動きを統括する。映像制作における<Marker>設計図みたいなもの</Marker>だと思っておけばいい。英語では "Call Sheet"（コールシート）と呼ばれ、商業撮影の現場では国内外問わず必ず存在するドキュメントだ。</p>
            <p>香盤表がなければ、スタッフ10人・出演者5人が動く現場でも「誰がいつどこにいるべきか」が全員に伝わらない。<Marker>1日の撮影コストが数十万円を超える商業案件では、1枚の香盤表の精度が制作の成否を左右する。</Marker></p>
          </div>
        </Reveal>

        <Reveal delay={0.05}>
          <SpeechBubble speaker="現役プロデューサー">
            コールシートって英語で言うと「そんなの知ってるよ」ってなる人も多いんだけど、日本の現場だと"香盤表"って言わないと通じないこともある。用語は現場に合わせて使い分けるのが吉。
          </SpeechBubble>
        </Reveal>

        {/* ═══════════ Section 2 ═══════════ */}
        <Reveal>
          <div className="prose">
            <h2 id="なぜ香盤表が必要なのか">なぜ香盤表が必要なのか？</h2>
            <p className="answer-capsule"><strong>香盤表がない現場では、情報の伝達ミスが連鎖的に起きる。</strong></p>
            <p>実際に香盤表なし・あるいは不備のある香盤表で現場を回すと、こういうことが起きる。</p>
            <ul>
              <li>出演者の呼び時間がズレて、スタジオ待機が1時間発生（スタジオ代：数万円/時）</li>
              <li>機材の搬入経路が共有されておらず、撮影開始が30分押し</li>
              <li>シーンの撮影順が最適化されておらず、衣装・ロケ地の移動が非効率に</li>
              <li>「その情報、聞いてませんでした」が現場で多発</li>
            </ul>
            <p>1分の遅れが数十万円のロスにつながる商業撮影では、香盤表の精度がそのまま制作コストに直結する。逆に言えば、<Marker>精度の高い香盤表1枚が、現場全体のムダを削ぎ落とす最も確実な手段</Marker>だと思う。</p>
          </div>
        </Reveal>

        <Reveal delay={0.05}>
          <SpeechBubble speaker="現役プロデューサー">
            「その情報、聞いてませんでした」——これ、現場で一番ヒヤッとするやつ。香盤表の情報共有が不十分だと、スタッフが各自で違う情報を持ったまま当日を迎えることになる。
          </SpeechBubble>
        </Reveal>

        {/* ═══════════ Section 3 ═══════════ */}
        <Reveal>
          <div className="prose">
            <h2 id="香盤表の基本項目8つは何か">香盤表の基本項目8つ</h2>
            <p className="answer-capsule"><strong>香盤表に最低限必要な項目は次の8つである。</strong></p>
            <table>
              <thead>
                <tr><th>項目</th><th>内容</th></tr>
              </thead>
              <tbody>
                <tr><td>シーン番号</td><td>脚本上のシーン番号（撮影順と一致しない場合あり）</td></tr>
                <tr><td>撮影日・撮影時間</td><td>各シーンの予定撮影時間帯</td></tr>
                <tr><td>ロケ地・スタジオ名</td><td>撮影場所の正式名称・住所</td></tr>
                <tr><td>出演者名・拘束時間</td><td>キャスト名と現場への集合〜解放までの時間</td></tr>
                <tr><td>スタッフ役割分担</td><td>監督・カメラ・音声・照明・制作進行など</td></tr>
                <tr><td>使用機材リスト</td><td>カメラ機材・照明・音響機材など</td></tr>
                <tr><td>カット数・内容メモ</td><td>各シーンで撮影するカット数と撮影内容の概要</td></tr>
                <tr><td>備考・特記事項</td><td>天候条件・許可申請状況・注意点など</td></tr>
              </tbody>
            </table>
            <p>この8項目は、ExcelやGoogleスプレッドシートで作る場合も、脚本パックンのような専用ツールを使う場合も、<Marker>共通して必要になる最低限のセット。</Marker>プロジェクト規模や案件の種類によって、ロケ弁の手配情報や駐車場情報なんかも加えていく。</p>
          </div>
        </Reveal>

        <Reveal delay={0.05}>
          <div style={{ margin: "2.5rem 0" }}>
            <IllustItems8/>
          </div>
        </Reveal>

        {/* ═══════════ Section 4 ═══════════ */}
        <Reveal>
          <div className="prose">
            <h2 id="香盤表の作り方5ステップ">香盤表の作り方【5ステップ】</h2>
            <p className="answer-capsule"><strong>香盤表は「脚本の読み込み」から始まり「全スタッフへの共有」で完成する。</strong></p>

            <h3 id="step1">ステップ1：脚本を読み込んでシーンを洗い出すには？</h3>
            <p>脚本の全シーンをリストアップし、「ロケ地」「出演者」「時間帯」「美術・小道具」の4軸で整理する。</p>
            <p>ここで大切なのは、<Marker>文字として読むのではなく、映像として想像しながら読む</Marker>ことだ。たとえばスマートフォンを使うシーンがあるなら、メーカーロゴが映り込まないようにケースが必要かもしれない。雨の日のシーンなら傘という小道具が必要になる。美術・小道具の抜け漏れは現場で初めて気づくことが多く、そうなるとリカバリーが一番難しい。</p>
            <p>結局ここで問われるのは、<strong>「映像として成立させるために何が必要か」を先読みできるかどうか</strong>だと思う。この段階での漏れが、後のステップ全体に響いてくるから。</p>

            <h3 id="step2">ステップ2：撮影順はどう最適化するのか？</h3>
            <p>脚本順に撮影する現場はほとんどない。以下の観点で撮影順を組み直すのが基本だ。</p>
            <ul>
              <li>ロケ地・スタジオの移動コストを最小化</li>
              <li>出演者の拘束時間を圧縮</li>
              <li>照明セッティングの切り替えを最小化</li>
              <li>天候・日照条件に左右されるシーンを優先</li>
            </ul>
            <p>ここで見落としがちなのが、<Marker>ロケ地の「性質」と「撮影日時」の掛け算。</Marker>たとえば観光地として有名なロケ地を土曜日に押さえると、道路が激混みして移動時間が読めなくなる。ロケ地がどんな場所で、撮影日時によってどんな影響が出るかを事前に想定できるかどうかが、香盤表の精度を大きく左右する。</p>

            <h3 id="step3">ステップ3：各シーンの撮影時間はどう見積もるか？</h3>
            <p>シーンごとに「セッティング時間＋テイク数×平均テイク時間＋撤収時間」の合計で算出する。経験則として、<Marker>この見積もりに1.2〜1.5倍のバッファを乗せるのが現場の常識。</Marker>「この現場はタイトだから詰め詰めで組む」という判断が現場崩壊の引き金になる——これ、ほんとによく見る。</p>

            <h3 id="step4">ステップ4：出演者・スタッフの拘束時間はどう割り付けるか？</h3>
            <p>各出演者・スタッフが「何時から何時まで現場にいる必要があるか」を明示する。特に出演者の拘束時間は事務所との契約に関わるから、正確さが求められる。「この出演者が必要なシーンをまとめて撮る」という撮影順の最適化が、拘束時間圧縮の基本戦略でもある。</p>

            <h3 id="step5">ステップ5：全スタッフへの共有はどう進めるか？</h3>
            <p>香盤表は「作って終わり」じゃない。監督・プロデューサー・各部門チーフへの確認を経て、全スタッフへ配布する。</p>
            <p>ただ、全員のOKが揃った後でも<strong>クライアント都合で脚本が変わることは珍しくない</strong>。代理店・監督・クライアントの三者が合意したはずの内容が、上流のひとことで覆ることもある。<Marker>香盤表はプロジェクトが動いている間、常に脚本に追従し続けるドキュメント</Marker>だと認識しておこう。更新時はバージョン管理（v1.0、v1.1形式）を徹底したい。「古い版で現場が動いてた」という事故、わりとあるから。</p>
          </div>
        </Reveal>

        <Reveal delay={0.05}>
          <SpeechBubble speaker="現役プロデューサー">
            ステップ5の「バージョン管理」、これマジで大事。「最新版はどれ？」って現場で聞かれる状況、一回でも経験すると絶対ルール化したくなる。ファイル名に日付とversionを必ず入れること。
          </SpeechBubble>
        </Reveal>

        <Reveal delay={0.08}>
          <div style={{ margin: "2.5rem 0" }}>
            <IllustFlow5Steps/>
          </div>
        </Reveal>

        {/* ═══════════ Section 5 ═══════════ */}
        <Reveal>
          <div className="prose">
            <h2 id="現場で使える3つのコツは何か">現場で使える3つのコツ</h2>
            <p className="answer-capsule"><strong>香盤表の精度を上げる実践的なコツは「色分け」「バッファ」「拘束最小化」の3つだ。</strong></p>

            <h3>コツ1：ロケ地・出演者ごとに色分けする</h3>
            <p>ExcelやGoogleスプレッドシートで作る場合、ロケ地別・出演者別にセルを色分けすると視認性がかなり上がる。現場では香盤表を印刷して使う場面も多いので、<Marker>白黒印刷でも判別できる工夫（ハッチングや太枠など）</Marker>も一緒に入れておくといい。</p>

            <h3>コツ2：すべての工程に10〜20%のバッファを入れる</h3>
            <p>移動時間・セッティング・天候待ちなど、想定外のロスは必ず発生する。各シーンの見積もり時間に<Marker>10〜20%のバッファを乗せるのが現場の鉄則。</Marker>「詰め詰めで組めばいける」は現場崩壊フラグだと思っておくくらいがちょうどいい。</p>

            <h3>コツ3：出演者の拘束時間を最小化する</h3>
            <p>出演者の拘束時間が長くなるほど、事務所との交渉コストとタレントの疲労が増す。ステップ2の撮影順最適化と組み合わせて、「この出演者が必要なシーンをまとめて撮る」という設計を徹底することが拘束時間圧縮の基本戦略だ。</p>
            <p>この3つ、共通してるのは結局<Marker>「もし〇〇だったら？」を事前にどれだけ考えられるか</Marker>、だと思う。それが香盤表の完成度に直結する。</p>
          </div>
        </Reveal>

        <Reveal delay={0.05}>
          <SpeechBubble speaker="現役プロデューサー">
            特に自主制作やインディーズ案件だと「バッファ込みで組んだら時間が余った」って言われることもある。でも余るくらいがちょうどいい。本番は絶対どこかで押すから。
          </SpeechBubble>
        </Reveal>

        {/* ═══════════ Section 6 ═══════════ */}
        <Reveal>
          <div className="prose">
            <h2 id="香盤作成を自動化する方法は">香盤作成を自動化する方法はあるの？</h2>
            <p className="answer-capsule"><strong>脚本パックンを使えば、脚本をアップロードするだけで香盤表が完成する。</strong></p>
            <p>ここまで解説してきた香盤表の作成プロセス、実は脚本データさえあれば自動化できる。<strong>脚本パックン</strong>は、脚本をアップロードするだけでシーン・ロケ地・出演者・美術情報を自動で読み取り、そのまま使える香盤表を生成する日本初のAIツールだ（自社調べ）。海外ではすでに類似ツールが存在するが、脚本の日本語構造に対応した自動生成は国内でまだ例がない。<Marker>手作業で1週間かかっていた作業が、数分で完了する。</Marker></p>
            <p>脚本変更が発生した場合も、脚本を再アップロードするだけで差分を反映できるから、ステップ5の「バージョン管理の手間」も解消される。</p>
          </div>
        </Reveal>

        {/* ═══════════ まとめ ═══════════ */}
        <Reveal>
          <div className="prose">
            <h2 id="まとめ">まとめ</h2>
            <ul>
              <li>香盤表とは、撮影に必要な全情報を一覧化したスケジュール管理表</li>
              <li>基本項目は8つ：シーン・日時・場所・出演者・スタッフ・機材・カット数・備考</li>
              <li>作り方は5ステップ：脚本読み込み→撮影順最適化→時間見積→拘束割付→共有</li>
              <li>精度を上げる3コツ：色分け・バッファ・拘束最小化</li>
            </ul>
            <p>まず一枚、作ってみてほしい。香盤表は、現場で磨かれていくものだから。</p>

            <h2 id="よくある質問FAQ">よくある質問（FAQ）</h2>
          </div>
        </Reveal>

        {/* FAQ accordion */}
        <Reveal delay={0.05}>
          <FAQAccordion items={KOBANCHO_FAQ}/>
        </Reveal>

        {/* CTA */}
        <Reveal delay={0.08}>
          <BlogCTA/>
        </Reveal>

        {/* Back */}
        <div style={{ marginTop: 36, paddingTop: 28, borderTop: "1px solid #E2E8F0" }}>
          <a href="/blog" style={{ fontSize: 14, fontWeight: 700, color: PRIM, fontFamily: HD }}>
            ← ブログ一覧へ
          </a>
        </div>

      </main>

      <SiteFooter/>
    </>
  );
}

// ─────────────────────────────────────────────────────────
// Article page: ppm
// ─────────────────────────────────────────────────────────

function BlogArticlePPM() {
  return (
    <>
      <ReadingProgress/>
      <SiteHeader/>

      <main style={{
        flex: 1, maxWidth: 900, margin: "0 auto", width: "100%",
        padding: "0 48px 128px",
        background: "#fff",
        boxShadow: "0 0 0 1px rgba(0,0,0,0.04), 0 4px 32px rgba(0,0,0,0.06)",
      }}>

        {/* Breadcrumb */}
        <nav style={{
          display: "flex", alignItems: "center", gap: 8,
          paddingTop: 28, fontSize: 11, color: FAINT, fontFamily: HD,
        }}>
          <a href="/blog" style={{ color: PRIM }}>Blog</a>
          <span style={{ color: "#D1D5DB" }}>/</span>
          <span>基礎知識</span>
        </nav>

        {/* Article header */}
        <header style={{ marginBottom: 32, marginTop: 18 }}>
          <span style={{
            display: "inline-block", fontSize: 11, fontWeight: 800,
            padding: "4px 12px", borderRadius: 999, marginBottom: 16,
            background: PRIM, color: "#fff", letterSpacing: ".05em", fontFamily: HD,
          }}>基礎知識</span>

          <h1 style={{
            fontSize: "clamp(1.6rem, 3.5vw, 2.1rem)", fontWeight: 900,
            color: INK, lineHeight: 1.3, letterSpacing: "-.015em",
            marginBottom: 14, fontFamily: HD, fontFeatureSettings: '"palt"',
          }}>
            【2026年版】PPM資料とは？現役プロデューサーが必要項目を全部公開！
          </h1>

          <p style={{ fontSize: 16, color: MUTED, lineHeight: 1.85, marginBottom: 22, fontFamily: HD }}>
            PPM資料の基礎から作り方まで解説。10ブロックの必須構成・6ステップの作り方・精度を上げる3つのコツを現役プロデューサーが公開。
          </p>

          <div style={{ borderRadius: 16, overflow: "hidden", marginBottom: 24, boxShadow: "0 4px 24px rgba(0,0,0,0.10)" }}>
            <HeroPPM/>
          </div>

          <div style={{ display: "flex", alignItems: "center", gap: 16, fontSize: 11, color: FAINT, fontFamily: MN }}>
            <span>公開: <time dateTime="2026-05-29">2026-05-29</time></span>
          </div>
          <div style={{ display: "flex", flexWrap: "wrap", gap: 8, marginTop: 12 }}>
            {["PPM資料", "映像制作", "プリプロダクション", "AP", "プロデューサー"].map((tag) => (
              <span key={tag} style={{ fontSize: "0.68rem", background: "#F3F4F6", color: FAINT, padding: "2px 10px", borderRadius: 999, fontFamily: HD }}>
                #{tag}
              </span>
            ))}
          </div>
        </header>

        {/* TL;DR */}
        <Reveal>
          <div style={{
            marginBottom: 36, borderRadius: 12, padding: "22px 26px",
            background: "#F0F7FF", border: "1.5px solid #BFDBFE",
          }}>
            <p style={{ fontFamily: MN, fontSize: 11, fontWeight: 800, color: PRIM, letterSpacing: ".12em", textTransform: "uppercase", margin: "0 0 14px" }}>
              TL;DR ― この記事の結論
            </p>
            <ul style={{ listStyle: "none", padding: 0, margin: 0, display: "flex", flexDirection: "column", gap: 10 }}>
              {[
                "PPM資料とは、撮影前に関係者全員で「撮るもの」を合意するための事前共有資料である。",
                "基本構成は10ブロック。表紙・スタッフ・企画概要・あらすじ・脚本・キャスト・衣装メイク・美術・ロケ地・仮香盤が最低限必要だ。",
                "作り方は6ステップ。情報収集→構成設計→ビジュアル収集→スライド組み立て→社内レビュー→提出の順で進める。",
                "精度を上げる3コツ：判断項目は1スライド1要素・画像解像度の統一・代替案の事前準備。",
                "脚本パックンを使えば、脚本からPPM資料の叩き台を自動生成できる。",
              ].map((item, i) => (
                <li key={i} style={{ display: "flex", alignItems: "flex-start", gap: 10, fontFamily: HD, fontSize: 15, color: INK, lineHeight: 1.85 }}>
                  <span style={{ color: PRIM, fontWeight: 800, flexShrink: 0, marginTop: 2 }}>✓</span>
                  <span>{item}</span>
                </li>
              ))}
            </ul>
          </div>
        </Reveal>

        {/* Intro */}
        <Reveal delay={0.05}>
          <div className="prose">
            <p>PPM前日の深夜、衣装スライドを作りながらふと気づいた。</p>
            <p>脚本から香盤表に書き写して、今度は香盤表からPPMに書き写してる。同じ情報を何回転記するんだろう、と。スタッフ20名の広告案件。PPM本番まであと12時間。衣装がどのシーンで出るかを香盤表と見比べながら、ExcelからPowerPointに手入力していく。ミスが怖くて目視確認を繰り返すから、全然進まない。</p>
            <p>代理店・監督・クライアントが集まる場で、資料の不備は致命傷になる。「この衣装、ブランドの世界観に合わない」って指摘されても、代替案がなければその場が止まる。逆に、PPM資料の精度が高ければ、撮影前の懸念を1回のミーティングで潰しきれる。</p>
            <p>この記事では、広告・MV・ショートドラマなど幅広いプロジェクトを手がける現役プロデューサーの視点から、PPM資料の基礎・必須項目・作り方を整理する。</p>
          </div>
        </Reveal>

        {/* 目次 */}
        <Reveal delay={0.08}>
          <InlineTOC headings={PPM_HEADINGS}/>
        </Reveal>

        {/* ═══ Section 1 ═══ */}
        <Reveal>
          <div className="prose">
            <h2 id="PPM資料とは何か">PPM資料とは何か？</h2>
            <p className="answer-capsule"><strong>PPM資料とは「撮影前に関係者全員で撮るものを合意するための資料」だ。</strong></p>
            <p>PPMはPre-Production Meeting（プリプロダクションミーティング）の略。日本の映像制作現場では、撮影日の1〜2週間前に実施することが多い。脚本・キャスト・衣装・美術・ロケ地・撮影スケジュール——撮影に関わる全要素をスライドにまとめて、関係者全員で内容をすり合わせる場だ。</p>
            <p>PPM資料がないと、撮影当日に「思ってたのと違う」が連発する。広告案件なら、クライアントがイメージしていた商品の見せ方と現場の絵作りがズレる。ショートドラマなら、キャストの衣装やヘアメイクの方向性が合わなくて撮影開始が押す。<Marker>撮影現場の精度を、撮影前に確定させるためのドキュメント</Marker>——そういう認識でOK。</p>
          </div>
        </Reveal>

        <Reveal delay={0.05}>
          <SpeechBubble speaker="現役プロデューサー">
            海外ではPPMを "Pre-Production Deck" とか "PPM Deck" と呼ぶことが多い。"Deck" はスライドデッキ（プレゼン資料）のこと。PPM資料が「スライド形式で作るもの」というのは世界共通の感覚だよ。
          </SpeechBubble>
        </Reveal>

        {/* ═══ Section 2 ═══ */}
        <Reveal>
          <div className="prose">
            <h2 id="なぜPPM資料が必要なのか">なぜPPM資料が必要なのか？</h2>
            <p className="answer-capsule"><strong>PPM資料がない現場では、撮影当日の判断ミスがそのまま予算オーバーに直結する。</strong></p>
            <p>実際にPPMなし、もしくは不備のある状態で本番に突入するとこういうことが起きる。</p>
            <ul>
              <li>クライアントが現場に来て初めて衣装を見て「これは違う」→再撮または衣装手配やり直し</li>
              <li>ロケ地の搬入経路・電源確保が共有されておらず、機材セッティングが押す</li>
              <li>美術小道具のディテール（メーカーロゴ・色味・サイズ）がクライアント想定とズレる</li>
              <li>キャストのヘアメイク方向性が噛み合わず、当日に修正</li>
            </ul>
            <p>1日の撮影コストが数十万〜数百万円の商業案件では、こういう「言った言わない」「聞いてなかった」が即予算オーバーにつながる。撮影が始まってからの修正は、PPMで潰すコストの10倍以上かかると思っておいたほうがいい——これ、本当に。</p>
            <p><Marker>精度の高いPPM資料1セットが、現場での判断停止と予算ロスをまとめて防いでくれる。</Marker></p>
          </div>
        </Reveal>

        <Reveal delay={0.05}>
          <SpeechBubble speaker="現役プロデューサー">
            「PPM資料が完成した時点で、実質的に撮影の勝負は8割決まっている」——先輩プロデューサーにそう言われたことがある。PPM当日に「この内容でお願いします」と合意を取りつけた瞬間、撮影に向けた最大のリスクが消える。
          </SpeechBubble>
        </Reveal>

        {/* ═══ Section 3 ═══ */}
        <Reveal>
          <div className="prose">
            <h2 id="PPM資料の基本構成10ブロック">PPM資料の基本構成10ブロックは何か？</h2>
            <p className="answer-capsule"><strong>PPM資料に最低限必要な構成は10ブロック。</strong></p>
            <table>
              <thead>
                <tr><th>ブロック</th><th>内容</th></tr>
              </thead>
              <tbody>
                <tr><td>① 表紙</td><td>プロジェクト名・撮影日・PPM実施日</td></tr>
                <tr><td>② スタッフリスト</td><td>監督・カメラ・美術・制作など全役職と担当者名</td></tr>
                <tr><td>③ 企画概要</td><td>プロジェクトの目的・ターゲット・コンセプト</td></tr>
                <tr><td>④ あらすじ</td><td>ストーリーや映像構成の流れ</td></tr>
                <tr><td>⑤ 脚本</td><td>確定版の脚本（版番号明記）</td></tr>
                <tr><td>⑥ キャスト一覧</td><td>出演者ごとに写真・サイズ・役名・出演シーン</td></tr>
                <tr><td>⑦ 衣装・ヘアメイクイメージ</td><td>各キャストの衣装・メイクの参考写真と方向性</td></tr>
                <tr><td>⑧ 美術・小道具詳細</td><td>使用する小道具・装飾品の写真と使用シーン</td></tr>
                <tr><td>⑨ ロケ地情報</td><td>住所・地図・内観写真・搬入経路・利用条件</td></tr>
                <tr><td>⑩ 仮香盤</td><td>撮影スケジュール案（末尾付録）</td></tr>
              </tbody>
            </table>
            <p>この中で<Marker>一番スライド枚数が多くなるのは「美術・小道具詳細」</Marker>。実際に手がけたショートドラマ案件では、PPM資料37スライド中13スライドがこのブロックだった。小道具1点ごとに「何のシーンで使うか・どんなサイズか・どのメーカーか」を写真付きで示す必要があるから、ここが一番時間かかる。</p>
          </div>
        </Reveal>

        <Reveal delay={0.05}>
          <div style={{ margin: "2.5rem 0" }}>
            <IllustPPM10Blocks/>
          </div>
        </Reveal>

        <Reveal delay={0.05}>
          <SpeechBubble speaker="現役プロデューサー">
            ブロックの数は案件規模によって変わる。小規模な自主制作なら5〜6ブロックでもいい。ただ、「ロケ地」と「キャスト」だけは絶対に外せない——ここで合意を取らずに撮影に入ると、当日に確実に何かが起きるから。
          </SpeechBubble>
        </Reveal>

        {/* ═══ Section 4 ═══ */}
        <Reveal>
          <div className="prose">
            <h2 id="PPM資料の作り方6ステップ">PPM資料の作り方【6ステップ】</h2>
            <p className="answer-capsule"><strong>PPM資料は「情報収集」から始まり「クライアント提出」で完成する。</strong></p>

            <h3 id="ppm-step1">ステップ1：必要情報をどう集めるか？</h3>
            <p>まず脚本・絵コンテ・企画書から、PPMで議題に上がる要素を全部洗い出す。シーン一覧・キャスト一覧・小道具リスト・ロケ地候補・衣装メイク方向性——これらを「いつ・誰に・何を確認するか」の単位で整理していく。</p>
            <p><Marker>脚本上の1行が、PPMでは1スライドになることが普通にある。</Marker>だから脚本を「文字として読む」のではなく「撮影現場を想像しながら読む」のが重要だ。</p>

            <h3 id="ppm-step2">ステップ2：スライド構成をどう設計するか？</h3>
            <p>集めた情報を10ブロックに振り分けて、ざっくりとした構成案を作る。スタッフ紹介→企画概要→脚本→キャスト→衣装メイク→美術→ロケ地→スケジュールという順番が現場では自然だ。</p>
            <p>重要なのは<Marker>「クライアントの頭の中で物語が組み上がっていく順番」を意識して並べる</Marker>こと。それだけで説明が通りやすい構成になる。</p>

            <h3 id="ppm-step3">ステップ3：ビジュアル素材をどう集めるか？</h3>
            <p>PPM資料はテキストだけだとほぼ機能しない。<Marker>8割以上が写真・画像で構成されるくらいのイメージ</Marker>でOK。「合意のための画像」と「説明のための画像」を混ぜないことが重要だ。クライアントが判断すべきものと、情報として共有するだけのものを、スライドの段階で分けておく。</p>

            <h3 id="ppm-step4">ステップ4：スライドをどう組み立てるか？</h3>
            <p>クライアントの判断が入りそうな項目——特に衣装とメインの小道具——は、1枚ずつ分けておくほうが場が締まる。1枚に複数の衣装案を詰め込むと、どれについて議論しているかわからなくなる。</p>
            <p>自分は小道具を関連するもので1枚にまとめてしまうことが多い。ただ、それで痛い目を見ることがある。<strong>「このスライドでクライアントに何を決めてもらうか」を1つに絞る</strong>のが鉄則。</p>

            <h3 id="ppm-step5">ステップ5：社内レビューはどう回すか？</h3>
            <p>完成したPPM資料は、そのままクライアントに出さない。監督・カメラマン・美術・制作チーフの最低3人にレビューを入れてもらう。<Marker>PPM資料は提出版に到達するまでに3〜5回のドラフト更新を経るのが自然。</Marker>特に監督の確認は必須だ。</p>

            <h3 id="ppm-step6">ステップ6：クライアントへの提出はどう進めるか？</h3>
            <p>PPM当日の2〜3営業日前には、クライアント・代理店に資料を共有する。PPM後は確定事項・宿題事項を整理した「改訂版」を<Marker>24時間以内に共有するのが現場の標準。</Marker>版管理（v1.0、v1.1形式）は徹底したい。「古い版で現場が動いてた」という事故、わりとある。</p>
          </div>
        </Reveal>

        <Reveal delay={0.05}>
          <SpeechBubble speaker="現役プロデューサー">
            ステップ5の「3〜5回のドラフト更新」、これが一番疲弊するポイント。脚本変更が入るたびにPPMも更新が走る。だから版管理のルールを最初から決めておくことが大事。ファイル名に日付とversionを必ず入れること。
          </SpeechBubble>
        </Reveal>

        <Reveal delay={0.08}>
          <div style={{ margin: "2.5rem 0" }}>
            <IllustPPMFlow6/>
          </div>
        </Reveal>

        {/* ═══ Section 5 ═══ */}
        <Reveal>
          <div className="prose">
            <h2 id="現場で使える3つのコツ">現場で使える3つのコツは何か？</h2>
            <p className="answer-capsule"><strong>PPM資料の精度を上げる実践的なコツは「1スライド1要素」「画像解像度統一」「代替案の準備」の3つだ。</strong></p>

            <h3>コツ1：判断が必要な項目は1スライド1要素にする</h3>
            <p>「Aは良いけどBは違う」みたいな複合的なフィードバックが返ってきたとき、議事録に落とすのが地味に大変だ。<Marker>「このスライドでクライアントに何を決めてもらうか」を1つに絞る。</Marker>衣装・ヘアメイク・主要小道具などの合意が必要な項目は、必ず1スライド1要素にする。</p>

            <h3>コツ2：画像解像度・縦横比を統一する</h3>
            <p>解像度がバラバラだと「資料の作り込みが甘い」という印象がわりとすぐ出る。写真は<Marker>最低でも横幅1200px以上、縦横比はカテゴリごとに統一する。</Marker>衣装は全身縦位置・小道具は正面俯瞰、といったルールを自分の中で決めておくと資料全体の統一感が出る。</p>

            <h3>コツ3：代替案を事前に1〜2枚用意しておく</h3>
            <p>「揺れそうな項目」にA案だけでなくB案・C案を裏スライドとして準備しておく。「B案もあります」と即座に出せるかどうかがPPMでの判断スピードを大きく左右する。<Marker>代替案があるかないかで、その場の合意率が全然違う。</Marker></p>
          </div>
        </Reveal>

        <Reveal delay={0.05}>
          <SpeechBubble speaker="現役プロデューサー">
            コツ3の「裏スライド」、PPM当日はほぼ確実に出番が来る。「このスライドの代替案はありますか？」って聞かれたとき、すぐ出せるかどうかがプロとそうでないかの分かれ目だと思う。
          </SpeechBubble>
        </Reveal>

        {/* ═══ Section 6 ═══ */}
        <Reveal>
          <div className="prose">
            <h2 id="PPM資料作成を自動化する方法">PPM資料作成を自動化する方法は？</h2>
            <p className="answer-capsule"><strong>脚本パックンを使えば、脚本からPPM資料の叩き台を自動生成できる。</strong></p>
            <p><strong>脚本パックン</strong>は、脚本をアップロードするだけでシーン一覧・キャストリスト・美術小道具リスト・ロケ地一覧を自動抽出して、PPMの叩き台となるドキュメントを生成する日本初のAIツールだ（自社調べ）。手作業で1〜2日かかっていた「情報収集と構成設計」が、数分で土台まで完了する。</p>
            <p>叩き台から実際のPPM資料に仕上げる作業は必要だが、<Marker>「何もない状態からスライドを組み始める」という最もつらいフェーズをスキップできる。</Marker>脚本変更が発生した場合も、再アップロードするだけで差分を反映できるから、ステップ6の版管理の手間も大幅に減る。</p>
          </div>
        </Reveal>

        {/* ═══ まとめ ═══ */}
        <Reveal>
          <div className="prose">
            <h2 id="ppmまとめ">まとめ</h2>
            <ul>
              <li>PPM資料とは、撮影前に関係者全員で「撮るもの」を合意するための事前共有資料</li>
              <li>基本構成は10ブロック：表紙・スタッフ・企画・あらすじ・脚本・キャスト・衣装メイク・美術・ロケ地・仮香盤</li>
              <li>作り方は6ステップ：情報収集→構成設計→ビジュアル収集→スライド組み立て→社内レビュー→提出</li>
              <li>精度を上げる3コツ：1スライド1要素・画像解像度統一・代替案の事前準備</li>
            </ul>
            <p>撮影現場の精度は、PPM資料の精度で決まる。まず一案件、自分で一通り組んでみてほしい。</p>

            <h2 id="PPMよくある質問">よくある質問（FAQ）</h2>
          </div>
        </Reveal>

        {/* FAQ accordion */}
        <Reveal delay={0.05}>
          <FAQAccordion items={PPM_FAQ}/>
        </Reveal>

        {/* CTA */}
        <Reveal delay={0.08}>
          <BlogCTA/>
        </Reveal>

        {/* Back */}
        <div style={{ marginTop: 36, paddingTop: 28, borderTop: "1px solid #E2E8F0" }}>
          <a href="/blog" style={{ fontSize: 14, fontWeight: 700, color: PRIM, fontFamily: HD }}>
            ← ブログ一覧へ
          </a>
        </div>

      </main>

      <SiteFooter/>
    </>
  );
}

// ─────────────────────────────────────────────────────────
// Mount
// ─────────────────────────────────────────────────────────

const _rootEl = document.getElementById("root");
const _page = _rootEl ? _rootEl.dataset.page : null;

if (_page === "blog-index") {
  ReactDOM.createRoot(_rootEl).render(<BlogIndex/>);
} else if (_page === "blog-callsheet") {
  ReactDOM.createRoot(_rootEl).render(<BlogArticleKobancho/>);
} else if (_page === "blog-ppm") {
  ReactDOM.createRoot(_rootEl).render(<BlogArticlePPM/>);
}
