<?php
declare(strict_types=1);
require_once __DIR__ . '/_bootstrap.php';
require_login();

$id = (int)($_GET['id'] ?? 0);
$page = null;
foreach (page_all() as $p) { if ((int)$p['id'] === $id) { $page = $p; break; } }
if (!$page) { http_response_code(404); exit('ページが見つかりません。'); }

$defs = part_defs();

/* ============================================================
 *  POST 処理
 * ============================================================ */
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    require_post_csrf();
    $action = (string)($_POST['action'] ?? '');

    if ($action === 'save_meta') {
        $title = trim((string)($_POST['title'] ?? ''));
        $desc  = trim((string)($_POST['meta_description'] ?? ''));
        $status = ((string)($_POST['status'] ?? 'public') === 'draft') ? 'draft' : 'public';
        // ホームは常に公開（トップを非公開にできると入口が消えるため）
        if (($page['slug'] ?? '') === 'home') $status = 'public';
        db()->prepare("UPDATE pages SET title=?, meta_description=?, status=?, updated_at=datetime('now') WHERE id=?")
            ->execute([$title, $desc, $status, $id]);
        flash_set('ページ情報を保存しました。');
        redirect('/admin/page_edit.php?id=' . $id);
    }

    if ($action === 'add_block') {
        $part = (string)($_POST['part'] ?? '');
        if (isset($defs[$part])) {
            $ms = db()->prepare("SELECT COALESCE(MAX(sort),0) FROM blocks WHERE page_id=?");
            $ms->execute([$id]);
            $maxSort = (int)$ms->fetchColumn();
            db()->prepare("INSERT INTO blocks(page_id,part,data,sort) VALUES(?,?,?,?)")
                ->execute([$id, $part, '{}', $maxSort + 1]);
            $newId = (int)db()->lastInsertId();
            flash_set('「' . ($defs[$part]['label'] ?? $part) . '」を追加しました。下に追加されています。');
            redirect('/admin/page_edit.php?id=' . $id . '&open=' . $newId . '&new=1#block-' . $newId);
        }
        redirect('/admin/page_edit.php?id=' . $id);
    }

    if ($action === 'toggle_block_status') {
        // パーツ単位の公開/非公開。ページのトグル（admin/index.php）と同じ作法・同じ見た目。
        $blockId = (int)($_POST['block_id'] ?? 0);
        $st = db()->prepare("SELECT status FROM blocks WHERE id=? AND page_id=?");
        $st->execute([$blockId, $id]);
        $row = $st->fetch();
        if ($row) {
            $new = ((string)($row['status'] ?? 'public') === 'draft') ? 'public' : 'draft';
            db()->prepare("UPDATE blocks SET status=? WHERE id=? AND page_id=?")->execute([$new, $blockId, $id]);
            flash_set($new === 'draft' ? 'このパーツを「非公開」にしました。' : 'このパーツを「公開」にしました。');
        }
        redirect('/admin/page_edit.php?id=' . $id . '#block-' . $blockId);
    }

    if ($action === 'save_block') {
        $blockId = (int)($_POST['block_id'] ?? 0);
        $st = db()->prepare("SELECT * FROM blocks WHERE id=? AND page_id=?");
        $st->execute([$blockId, $id]);
        $blk = $st->fetch();
        if ($blk && isset($defs[$blk['part']])) {
            // 保存前にこのブロックが使っていた画像/動画名を控える（差し替え・削除で不要になった分を後で掃除）
            $oldFiles = cms_extract_upload_names((string)($blk['data'] ?? ''));
            $data = parse_part_input($defs[$blk['part']]);
            $newJson = json_encode($data, JSON_UNESCAPED_UNICODE);
            db()->prepare("UPDATE blocks SET data=? WHERE id=?")
                ->execute([$newJson, $blockId]);
            // 今回の保存で使われなくなった画像だけ物理削除（他で使っていれば残る）
            $removed = array_diff($oldFiles, cms_extract_upload_names($newJson));
            if ($removed) cms_cleanup_files_if_unused($removed);
            flash_set('「' . ($defs[$blk['part']]['label'] ?? '') . '」を保存しました。');
        }
        // 保存後はページ上部へ（フラッシュを見せる／各ブロックは閉じた状態に戻す）
        redirect('/admin/page_edit.php?id=' . $id);
    }

    // 着せ替え（スキン切替）：ブロックの part を「同じ family の別パーツ」に付け替える。
    // data はそのまま（＝入力内容を引き継ぐ）。別スキンでだけ使う項目は data に温存され、
    // 元のスキンに戻せば再表示される（保存時も温存＝parse_part_input／フォームの hidden 温存）。
    if ($action === 'set_skin') {
        $blockId = (int)($_POST['block_id'] ?? 0);
        $newPart = (string)($_POST['skin'] ?? '');
        $st = db()->prepare("SELECT * FROM blocks WHERE id=? AND page_id=?");
        $st->execute([$blockId, $id]);
        $blk = $st->fetch();
        if ($blk && isset($defs[$blk['part']]) && isset($defs[$newPart])) {
            $curFam = (string)($defs[$blk['part']]['family'] ?? '');
            $newFam = (string)($defs[$newPart]['family'] ?? '');
            // 同じ family 内でのみ許可（別カテゴリのパーツに化けさせない）
            if ($curFam !== '' && $curFam === $newFam && $newPart !== $blk['part']) {
                db()->prepare("UPDATE blocks SET part=? WHERE id=?")->execute([$newPart, $blockId]);
                flash_set('デザインを「' . ($defs[$newPart]['label'] ?? $newPart) . '」に切り替えました。入力内容はそのまま引き継いでいます。');
            }
        }
        redirect('/admin/page_edit.php?id=' . $id . '&open=' . $blockId . '#block-' . $blockId);
    }

    if ($action === 'delete_block') {
        $blockId = (int)($_POST['block_id'] ?? 0);
        // 削除前にこのブロックが使っている画像/動画ファイル名を控える
        $stB = db()->prepare("SELECT data FROM blocks WHERE id=? AND page_id=?");
        $stB->execute([$blockId, $id]);
        $files = cms_extract_upload_names((string)$stB->fetchColumn());
        db()->prepare("DELETE FROM blocks WHERE id=? AND page_id=?")->execute([$blockId, $id]);
        cms_cleanup_files_if_unused($files); // どこからも使われなくなった画像だけ物理削除
        flash_set('ブロックを削除しました。');
        redirect('/admin/page_edit.php?id=' . $id);
    }

    if ($action === 'move_block') {
        $blockId = (int)($_POST['block_id'] ?? 0);
        $dir = (string)($_POST['dir'] ?? '');
        move_block($id, $blockId, $dir);
        redirect('/admin/page_edit.php?id=' . $id . '#block-' . $blockId);
    }
}

/* ============================================================
 *  入力パーサ（フォーム定義に従って $_POST/$_FILES を data 配列へ）
 * ============================================================ */
function parse_part_input(array $def): array {
    $data = [];
    // 共通の見出し＋本文 ＋ パーツ固有フィールド
    $allFields = array_merge(common_head_fields(), $def['fields'] ?? []);
    foreach ($allFields as $f) {
        $key  = $f['key'] ?? '';
        $type = $f['type'] ?? 'text';
        if ($key === '') continue;
        if ($type === 'image') {
            $existing = (string)($_POST['f'][$key] ?? '');
            $fk  = 'file_' . $key;
            $del = !empty($_POST['imgdel'][$fk]);
            $data[$key] = resolve_image_value($fk, $existing, $del);
        } else {
            $data[$key] = (string)($_POST['f'][$key] ?? '');
        }
    }
    // 着せ替え温存（トップ階層）：この定義に無い f[] は生値のまま残す＝別スキンでだけ使う項目を消さない。
    // 値はフォームの hidden（このパーツの内容の先頭に出力）から届く。
    foreach ((array)($_POST['f'] ?? []) as $ek => $ev) {
        if (!array_key_exists($ek, $data) && is_string($ev)) $data[(string)$ek] = (string)$ev;
    }
    if (isset($def['repeater']) && is_array($def['repeater'])) {
        $rkey = $def['repeater']['key'] ?? 'rows';
        $rows = $_POST['rows'] ?? [];
        $out = [];
        if (is_array($rows)) {
            foreach ($rows as $idx => $row) {
                if (!is_array($row)) continue;
                $r = [];
                $hasContent = false;
                foreach (($def['repeater']['fields'] ?? []) as $f) {
                    $key  = $f['key'] ?? '';
                    $type = $f['type'] ?? 'text';
                    if ($key === '') continue;
                    if ($type === 'image') {
                        $existing = (string)($row[$key] ?? '');
                        $fk  = 'rowfile_' . $idx . '_' . $key;
                        $del = !empty($_POST['imgdel'][$fk]);
                        $r[$key] = resolve_image_value($fk, $existing, $del);
                    } else {
                        $r[$key] = (string)($row[$key] ?? '');
                    }
                    if ($r[$key] !== '') $hasContent = true;
                }
                // 着せ替え温存（各行）：この定義に無い行キーは生値のまま残す（別スキンの行項目を消さない）。
                foreach ($row as $ek => $ev) {
                    if (!array_key_exists($ek, $r) && is_string($ev)) {
                        $r[(string)$ek] = (string)$ev;
                        if ($ev !== '') $hasContent = true;
                    }
                }
                if ($hasContent) $out[] = $r;
            }
        }
        $data[$rkey] = $out;
    }
    return $data;
}

function move_block(int $pageId, int $blockId, string $dir): void {
    $rows = db()->prepare("SELECT id,sort FROM blocks WHERE page_id=? ORDER BY sort,id");
    $rows->execute([$pageId]);
    $list = $rows->fetchAll();
    $pos = null;
    foreach ($list as $i => $r) { if ((int)$r['id'] === $blockId) { $pos = $i; break; } }
    if ($pos === null) return;
    $swap = $dir === 'up' ? $pos - 1 : $pos + 1;
    if ($swap < 0 || $swap >= count($list)) return;
    $a = $list[$pos]; $b = $list[$swap];
    $u = db()->prepare("UPDATE blocks SET sort=? WHERE id=?");
    $u->execute([(int)$b['sort'], (int)$a['id']]);
    $u->execute([(int)$a['sort'], (int)$b['id']]);
}

/* ============================================================
 *  フォーム入力部品のレンダリング
 * ============================================================ */
function adm_field(array $f, $value): string {
    $key   = $f['key'] ?? '';
    $type  = $f['type'] ?? 'text';
    $label = $f['label'] ?? $key;
    $hint  = isset($f['hint']) ? '<span class="fld-hint">' . $f['hint'] . '</span>' : '';
    $name  = 'f[' . h($key) . ']';
    $v     = (string)($value ?? ($f['default'] ?? ''));
    $fldClass = 'fld' . (!empty($f['sub']) ? ' fld-sub' : ''); // sub=調整項目（従属表示）
    $out   = '<div class="' . $fldClass . '" data-fld-key="' . h($key) . '"><label class="fld-label">' . h($label) . '</label>' . $hint;

    if ($type === 'toggle' || $type === 'swatch') {
        $out .= adm_button_group($f, $name, $v, $type === 'swatch');
    } elseif ($type === 'textarea' || $type === 'checklist') {
        if ($type === 'checklist' && $hint === '') {
            $out .= '<span class="fld-hint">1行＝1項目。行頭に「×」を付けると「含まない（取り消し線）」になります。</span>';
        }
        $rt = $type === 'textarea' ? ' data-richtext' : ''; // 本文欄は太字/リンクのツールバーを付ける
        $out .= '<textarea name="' . $name . '" rows="5"' . $rt . '>' . h($v) . '</textarea>';
    } elseif ($type === 'bullets') {
        if ($hint === '') {
            $out .= '<span class="fld-hint">1行＝1項目で入力してください。</span>';
        }
        $out .= '<textarea name="' . $name . '" rows="5">' . h($v) . '</textarea>';
    } elseif ($type === 'table') {
        if ($hint === '') {
            $out .= '<span class="fld-hint">1行＝表の1行。各セルは縦棒「<code>|</code>」で区切ります。列の数は自由です。例：<code>項目|プランA|プランB</code></span>';
        }
        $out .= '<textarea name="' . $name . '" rows="6" placeholder="項目|プランA|プランB&#10;料金|無料|月1,000円&#10;サポート|メール|メール・電話">' . h($v) . '</textarea>';
    } elseif ($type === 'stars') {
        $out .= adm_stars_select($name, $v);
    } elseif ($type === 'embed') {
        if ($hint === '') {
            $out .= '<span class="fld-hint">YouTube動画のURL／Googleマップの「埋め込みURL」または貼り付けた&lt;iframe&gt;タグ／https:// のページURL を入れてください。</span>';
        }
        $out .= '<textarea name="' . $name . '" rows="3" placeholder="https://www.youtube.com/watch?v=...">' . h($v) . '</textarea>';
    } elseif ($type === 'select') {
        // 静的options が無く options_from があれば、プラグインの動的選択肢を使う
        $options = $f['options'] ?? [];
        if (empty($options) && !empty($f['options_from']) && function_exists('cms_field_options')) {
            $options = cms_field_options((string)$f['options_from']);
        }
        $out .= '<select name="' . $name . '">';
        foreach ($options as $opt) {
            // 文字列 or {value,label}（値とラベルを分けたい時）両対応
            if (is_array($opt)) { $ov = (string)($opt['value'] ?? ''); $ol = (string)($opt['label'] ?? $ov); }
            else { $ov = (string)$opt; $ol = (string)$opt; }
            $sel = ($ov === $v) ? ' selected' : '';
            $out .= '<option value="' . h($ov) . '"' . $sel . '>' . h($ol) . '</option>';
        }
        $out .= '</select>';
    } elseif ($type === 'link') {
        $out .= adm_link_input($name, $v);
    } elseif ($type === 'image') {
        $out .= adm_image_input('file_' . $key, $name, $v);
    } elseif ($type === 'icon') {
        $out .= adm_icon_input($name, $v);
    } else {
        $out .= '<input type="text" name="' . $name . '" value="' . h($v) . '">';
    }
    $out .= '</div>';
    return $out;
}

/** セグメント型ボタン群（toggle＝文字ボタン／swatch＝色見本ボタン）。ラジオで1つ選択。 */
function adm_button_group(array $f, string $name, string $v, bool $swatch = false): string {
    static $seq = 0;
    $cls = $swatch ? 'adm-swatch' : 'adm-seg';
    $out = '<div class="' . $cls . '">';
    foreach (($f['options'] ?? []) as $opt) {
        if (is_array($opt)) { $ov = (string)($opt['value'] ?? ''); $ol = (string)($opt['label'] ?? $ov); $col = (string)($opt['color'] ?? ''); }
        else { $ov = (string)$opt; $ol = (string)$opt; $col = ''; }
        // color_var 指定があれば、選択中フレームの実際のテーマ色をチップ色に使う（サイトと一致させる）
        if (is_array($opt) && !empty($opt['color_var']) && function_exists('cms_active_colors')) {
            $rc = cms_active_colors();
            if (!empty($rc[$opt['color_var']])) $col = (string)$rc[$opt['color_var']];
        }
        $id = 'opt' . (++$seq);
        $chk = ($ov === $v) ? ' checked' : '';
        $chip = ($swatch && $col !== '') ? '<span class="adm-swatch-chip" style="background:' . h($col) . '"></span>' : '';
        $out .= '<input type="radio" id="' . $id . '" name="' . $name . '" value="' . h($ov) . '"' . $chk . '>'
              . '<label for="' . $id . '">' . $chip . h($ol) . '</label>';
    }
    return $out . '</div>';
}

/** 星評価の入力（1〜5の選択） */
function adm_stars_select(string $name, string $v): string {
    $cur = $v === '' ? '5' : $v;
    $out = '<select name="' . h($name) . '">';
    for ($i = 5; $i >= 1; $i--) {
        $out .= '<option value="' . $i . '"' . ((string)$i === $cur ? ' selected' : '') . '>'
              . str_repeat('★', $i) . '（星' . $i . '）</option>';
    }
    return $out . '</select>';
}

/** リピーター行の1フィールドを描画（text/textarea/select/checklist/stars/image/icon 対応） */
function adm_repeater_field(array $f, string $rowName, string $fileName, string $val): string {
    $k = $f['key'] ?? '';
    $type = $f['type'] ?? 'text';
    $lab = '<label class="fld-label">' . h($f['label'] ?? $k) . '</label>';
    if ($type === 'image') {
        return '<div class="fld">' . $lab . adm_image_input($fileName, $rowName, $val) . '</div>';
    }
    if ($type === 'icon') {
        return '<div class="fld">' . $lab . adm_icon_input($rowName, $val) . '</div>';
    }
    if ($type === 'stars') {
        return '<div class="fld">' . $lab . adm_stars_select($rowName, $val) . '</div>';
    }
    if ($type === 'link') {
        return '<div class="fld">' . $lab . adm_link_input($rowName, $val) . '</div>';
    }
    if ($type === 'textarea' || $type === 'checklist') {
        $hint = $type === 'checklist'
            ? '<span class="fld-hint">1行＝1項目。行頭に「×」を付けると「含まない（取り消し線）」になります。</span>' : '';
        $rt = $type === 'textarea' ? ' data-richtext' : '';
        return '<div class="fld">' . $lab . $hint
            . '<textarea name="' . h($rowName) . '" rows="3"' . $rt . '>' . h($val) . '</textarea></div>';
    }
    if ($type === 'select') {
        $opts = '';
        foreach (($f['options'] ?? []) as $opt) {
            if (is_array($opt)) { $ov = (string)($opt['value'] ?? ''); $ol = (string)($opt['label'] ?? $ov); }
            else { $ov = (string)$opt; $ol = (string)$opt; }
            $opts .= '<option value="' . h($ov) . '"' . ($ov === $val ? ' selected' : '') . '>' . h($ol) . '</option>';
        }
        return '<div class="fld">' . $lab . '<select name="' . h($rowName) . '">' . $opts . '</select></div>';
    }
    return '<div class="fld">' . $lab . '<input type="text" name="' . h($rowName) . '" value="' . h($val) . '"></div>';
}

/**
 * リンク入力（type:link）。
 *  - 「サイト内ページから選ぶ」プルダウン＋自由入力欄の2本立て。
 *  - 実際に保存される値は hidden（data-link-value）。ページ選択時は "page:<slug>"、自由入力時はその文字列。
 *  - 表示時はエンジンの cms_link_url() が page:slug を現在のURLへ変換（削除済みページはリンクなし）。
 */
function adm_link_input(string $name, string $value): string {
    static $pages = null, $slugs = null;
    if ($pages === null) { $pages = page_all(); $slugs = array_column($pages, 'slug'); }
    $value  = (string)$value;
    $isPage = (strncmp($value, 'page:', 5) === 0);
    // ページ参照だが、その参照先が現存するか（削除済みなら自由入力扱いに正規化）
    $pageOk = $isPage && in_array(substr($value, 5), $slugs, true);
    $stored = ($isPage && !$pageOk) ? '' : $value; // 死んだページ参照は空に直す（次の保存で自動的に消える）

    $h  = '<div class="fld-link" data-link-field>';
    $h .= '<input type="hidden" name="' . $name . '" value="' . h($stored) . '" data-link-value>';
    $h .= '<select class="fld-link-select" data-link-page>';
    $h .= '<option value="">サイト内ページ以外（URLを以下↓に直接手入力）</option>';
    foreach ($pages as $p) {
        $tok  = 'page:' . $p['slug'];
        $path = ($p['slug'] === 'home') ? '/' : '/' . $p['slug'];
        $sel  = ($pageOk && $value === $tok) ? ' selected' : '';
        $h .= '<option value="' . h($tok) . '"' . $sel . '>' . h($p['title']) . '（' . h($path) . '）</option>';
    }
    $h .= '</select>';
    $textVal  = $pageOk ? '' : $stored; // ページ確定時は空、自由入力/死参照時は値（死参照は空）
    $textHide = $pageOk ? ' style="display:none"' : '';
    $h .= '<input type="text" class="fld-link-text" data-link-text value="' . h($textVal) . '" placeholder="https://… や #section"' . $textHide . '>';
    $h .= '</div>';
    return $h;
}

/** アイコン入力（プレビュー＋選択ボタン。値はFAクラス文字列） */
function adm_icon_input(string $name, string $value): string {
    $ic = $value !== '' ? '<i class="' . h($value) . '"></i>' : '';
    return '<div class="icon-field" data-icon-field>'
         . '<span class="icon-preview" data-icon-preview>' . $ic . '</span>'
         . '<input type="text" name="' . $name . '" value="' . h($value) . '" data-icon-input readonly placeholder="未選択">'
         . '<button type="button" class="adm-btn ghost sm" data-icon-pick>アイコンを選ぶ</button>'
         . '<button type="button" class="adm-btn ghost sm" data-icon-clear>クリア</button>'
         . '</div>';
}

/** 画像入力（現在のサムネ＋差し替えファイル＋既存値hidden） */
function adm_image_input(string $fileName, string $hiddenName, string $current): string {
    $thumb = '';
    if ($current !== '') {
        $thumb = is_video_file($current)
            ? '<video class="fld-thumb" src="' . h(upload_url($current)) . '" muted></video>'
            : '<img class="fld-thumb" src="' . h(upload_url($current)) . '" alt="">';
    }
    return '<div class="fld-image">'
         . $thumb
         . '<input type="hidden" name="' . $hiddenName . '" value="' . h($current) . '">'
         . '<input type="file" name="' . h($fileName) . '" accept="image/*">'
         . ($current !== '' ? '<span class="fld-current">現在: ' . h($current) . '</span>' : '')
         . ($current !== ''
              ? '<label class="fld-imgdel"><input type="checkbox" name="imgdel[' . h($fileName) . ']" value="1"> この画像を削除する</label>'
              : '')
         . '</div>';
}

$blocks = blocks_of($id);
admin_header('ページ編集：' . $page['title'], 'pages');
?>
<div class="adm-pageedit">
<a class="adm-back" href="<?= h(url('/admin/index.php#pages')) ?>">← ページ一覧へ</a>
<h1 class="adm-h1">ページ編集：<?= h($page['title']) ?></h1>
<p class="adm-note">
  <a href="<?= h(page_url($page['slug'])) ?>" target="_blank" rel="noopener">このページを別タブで表示 ↗</a>
  （保存後に更新すると反映されます）<br>
  <span style="font-size:.84rem;"><?= h(cms_upload_formats_note()) ?></span>
</p>
<?php flash_render(); ?>

<!-- ページ情報 -->
<details class="adm-block meta-block">
  <summary>ページ情報（タイトル・メニュー表示など）</summary>
  <form method="post" class="adm-form">
    <input type="hidden" name="_csrf" value="<?= h(csrf_token()) ?>">
    <input type="hidden" name="action" value="save_meta">
    <div class="fld"><label class="fld-label">ページ名</label>
      <input type="text" name="title" value="<?= h($page['title']) ?>"></div>
    <div class="fld"><label class="fld-label">説明（meta description）</label>
      <input type="text" name="meta_description" value="<?= h($page['meta_description']) ?>"></div>
    <?php $pStatus = (string)($page['status'] ?? 'public'); ?>
    <?php if (($page['slug'] ?? '') === 'home'): ?>
      <input type="hidden" name="status" value="public">
      <p class="fld-hint">※ トップページは常に公開です。</p>
    <?php else: ?>
      <div class="fld"><label class="fld-label">公開状態</label>
        <select name="status">
          <option value="public" <?= $pStatus !== 'draft' ? 'selected' : '' ?>>公開（誰でも見られる）</option>
          <option value="draft" <?= $pStatus === 'draft' ? 'selected' : '' ?>>非公開（自分だけ確認）</option>
        </select>
        <span class="fld-hint">「非公開」にすると、一般の人がURLを開いても表示されません。作りかけのページや、今は見せたくないページを隠しておけます。</span>
      </div>
    <?php endif; ?>
    <p class="fld-hint">※ メニューへの表示は「メニュー管理」で設定します。</p>
    <button class="adm-btn primary" type="submit">ページ情報を保存</button>
  </form>
</details>

<!-- ブロック一覧 -->
<h2 class="adm-h2">コンテンツ（パーツ）</h2>

<?php if (!$blocks): ?>
  <p class="adm-empty">まだパーツがありません。下から追加してください。</p>
<?php endif; ?>


<?php foreach ($blocks as $bi => $b):
    $def = $defs[$b['part']] ?? null;
    if (!$def) continue;
    $data = json_array($b['data']);
?>
<div class="adm-block is-collapsed" id="block-<?= (int)$b['id'] ?>">
  <div class="adm-block-head">
    <button type="button" class="adm-block-label" data-block-toggle aria-expanded="false">
      <span class="block-chevron" aria-hidden="true">▶</span>
      <span class="adm-block-label-text">
        <span class="adm-block-name"><?= h($def['label']) ?></span>
        <?php $bHead = trim((string)($data['h_text'] ?? '')); if ($bHead !== ''): ?>
          <span class="adm-block-heading"><?= h($bHead) ?></span>
        <?php endif; ?>
      </span>
    </button>
    <span class="adm-block-tools">
      <?php $bDraft = ((string)($b['status'] ?? 'public') === 'draft'); ?>
      <form method="post" style="display:inline"><input type="hidden" name="_csrf" value="<?= h(csrf_token()) ?>">
        <input type="hidden" name="action" value="toggle_block_status"><input type="hidden" name="block_id" value="<?= (int)$b['id'] ?>">
        <button class="pg-status <?= $bDraft ? 'is-draft' : 'is-public' ?>" type="submit" title="クリックで公開／非公開を切り替え"><?= $bDraft ? '非公開' : '公開' ?></button>
      </form>
      <form method="post" style="display:inline"><input type="hidden" name="_csrf" value="<?= h(csrf_token()) ?>">
        <input type="hidden" name="action" value="move_block"><input type="hidden" name="block_id" value="<?= (int)$b['id'] ?>">
        <button class="adm-btn ghost sm" name="dir" value="up" <?= $bi === 0 ? 'disabled' : '' ?>>↑</button>
        <button class="adm-btn ghost sm" name="dir" value="down" <?= $bi === count($blocks) - 1 ? 'disabled' : '' ?>>↓</button>
      </form>
      <form method="post" style="display:inline" onsubmit="return confirm('このパーツを削除しますか？');">
        <input type="hidden" name="_csrf" value="<?= h(csrf_token()) ?>">
        <input type="hidden" name="action" value="delete_block"><input type="hidden" name="block_id" value="<?= (int)$b['id'] ?>">
        <button class="adm-btn danger sm" type="submit">削除</button>
      </form>
    </span>
  </div>

  <?php // 非公開のパーツだけ、そのパーツの中（見出しの直下）に案内を出す。
        // 折りたたみ（.adm-form）の外なので、閉じた状態でも見える。
        if ($bDraft) echo adm_hidden_note();
  ?>

  <?php if (!empty($def['description'])): ?>
    <p class="part-note"><?= part_description_html($def['description']) ?></p>
  <?php endif; ?>

  <?php
  $skins = cms_part_family_skins($b['part']);
  if (count($skins) >= 2):
      // 「サムネで選ぶ」モーダル用：同じ family の仲間だけをJSONで持たせる。
      $skinList = [];
      foreach ($skins as $sn => $sl) {
          $st   = cms_part_thumb($sn);
          $sdef = part_def($sn);
          $skinList[] = [
              'key'     => $sn,
              'label'   => $sl,
              'cat'     => (string)($sdef['category'] ?? ''),
              'thumb'   => $st['url'],
              'video'   => $st['video'],
              'desc'    => (string)($sdef['description'] ?? ''),
              'current' => ($sn === $b['part']),
          ];
      }
      $skinJson = json_encode($skinList, JSON_UNESCAPED_UNICODE);
  ?>
  <form method="post" class="adm-skin-form">
    <input type="hidden" name="_csrf" value="<?= h(csrf_token()) ?>">
    <input type="hidden" name="action" value="set_skin">
    <input type="hidden" name="block_id" value="<?= (int)$b['id'] ?>">
    <label class="adm-skin-label">デザイン（見た目）：
      <select name="skin" onchange="this.form.submit()">
        <?php foreach ($skins as $sn => $sl): ?>
          <option value="<?= h($sn) ?>"<?= $sn === $b['part'] ? ' selected' : '' ?>><?= h($sl) ?></option>
        <?php endforeach; ?>
      </select>
    </label>
    <button type="button" class="adm-btn soft sm" data-skin-open data-block="<?= (int)$b['id'] ?>" data-skins="<?= h($skinJson) ?>">サムネで選ぶ</button>
    <span class="fld-hint">入力した内容はそのまま、別の見た目に一発で切り替えられます（先に「このパーツを保存」してから切り替えてください）。</span>
    <noscript><button class="adm-btn ghost sm" type="submit">切り替える</button></noscript>
  </form>
  <?php endif; ?>

  <form method="post" enctype="multipart/form-data" class="adm-form">
    <input type="hidden" name="_csrf" value="<?= h(csrf_token()) ?>">
    <input type="hidden" name="action" value="save_block">
    <input type="hidden" name="block_id" value="<?= (int)$b['id'] ?>">
    <?php
    // 着せ替え温存（トップ階層）：現在のスキンに無い data キーを hidden で持ち回る＝保存で消えない。
    // SECTION_ATTR/HEAD は描画時の計算値なので data には無い。common_head と定義フィールド、
    // リピーターキー以外の scalar キーだけを対象にする。
    $knownTop = ['SECTION_ATTR' => 1, 'HEAD' => 1];
    foreach (common_head_fields() as $cf) $knownTop[$cf['key']] = 1;
    foreach (($def['fields'] ?? []) as $cf) $knownTop[$cf['key']] = 1;
    $rkeyTop = $def['repeater']['key'] ?? null;
    foreach ($data as $dk => $dv) {
        if (isset($knownTop[$dk])) continue;
        if ($rkeyTop !== null && $dk === $rkeyTop) continue;
        if (is_array($dv)) continue;
        echo '<input type="hidden" name="f[' . h((string)$dk) . ']" value="' . h((string)$dv) . '">';
    }
    ?>

    <div class="adm-cardgroup">
      <div class="adm-cardgroup-title">セクション全体の設定（先頭に1つ）</div>
      <?php
      // 見出しの文字は常時表示（セクション見出しは通常入力するため）。
      foreach (common_head_fields() as $f) {
          if (($f['key'] ?? '') === 'h_text') { echo adm_field($f, $data['h_text'] ?? null); break; }
      }
      ?>
      <details class="adm-meta">
        <summary class="adm-btn soft adm-meta-toggle">セクションの背景色・見出しスタイル・説明文・ID</summary>
        <div class="adm-meta-body">
          <?php foreach (common_head_fields() as $f) {
              if (($f['key'] ?? '') === 'h_text') continue; // 上で常時表示済み
              echo adm_field($f, $data[$f['key']] ?? null);
          } ?>
        </div>
      </details>
    </div>

    <?php if (!empty($def['fields']) || isset($def['repeater'])): ?>
    <div class="adm-cardgroup">
      <div class="adm-cardgroup-title"><?= isset($def['repeater']) ? '各カードの内容（くり返し）' : 'このパーツの内容' ?></div>
      <?php foreach (($def['fields'] ?? []) as $f) {
          echo adm_field($f, $data[$f['key']] ?? null);
      } ?>

    <?php if (isset($def['repeater'])):
        $rkey = $def['repeater']['key'] ?? 'rows';
        $rfields = $def['repeater']['fields'] ?? [];
        $rows = is_array($data[$rkey] ?? null) ? $data[$rkey] : [];
    ?>
    <div class="rep" data-rep data-rep-label="<?= h($def['repeater']['label'] ?? '項目') ?>">
      <label class="fld-label"><?= h($def['repeater']['label'] ?? '繰り返し') ?></label>
      <div class="rep-rows" data-rep-rows>
        <?php foreach ($rows as $ri => $row): ?>
          <div class="rep-row is-collapsed" data-rep-row>
            <div class="rep-row-head" data-rep-head>
              <span class="rep-row-num" data-rep-num></span>
              <span class="rep-row-title" data-rep-title></span>
              <span class="rep-row-move">
                <button type="button" class="rep-row-mv" data-rep-up title="上へ移動" aria-label="上へ移動"><i class="ti ti-chevron-up"></i></button>
                <button type="button" class="rep-row-mv" data-rep-down title="下へ移動" aria-label="下へ移動"><i class="ti ti-chevron-down"></i></button>
              </span>
              <button type="button" class="adm-btn ghost sm rep-row-del" data-rep-del>削除</button>
            </div>
            <div class="rep-row-body" data-rep-body>
            <?php foreach ($rfields as $f):
                $k = $f['key']; $val = (string)($row[$k] ?? '');
                echo adm_repeater_field($f, 'rows[' . $ri . '][' . h($k) . ']', 'rowfile_' . $ri . '_' . $k, $val);
            endforeach; ?>
            <?php
            // 着せ替え温存（各行）：現在のスキンに無い行キーを hidden で持ち回る＝保存で消えない。
            $rfKeys = [];
            foreach ($rfields as $f) $rfKeys[$f['key']] = 1;
            foreach ($row as $rk => $rv) {
                if (isset($rfKeys[$rk]) || is_array($rv)) continue;
                echo '<input type="hidden" name="rows[' . $ri . '][' . h((string)$rk) . ']" value="' . h((string)$rv) . '">';
            }
            ?>
            </div>
          </div>
        <?php endforeach; ?>
      </div>
      <button type="button" class="adm-btn soft" data-rep-add>＋ <?= h($def['repeater']['label'] ?? '項目') ?>を追加</button>
      <template data-rep-tpl>
        <div class="rep-row" data-rep-row>
          <div class="rep-row-head" data-rep-head>
            <span class="rep-row-num" data-rep-num></span>
            <span class="rep-row-title" data-rep-title></span>
            <span class="rep-row-move">
              <button type="button" class="rep-row-mv" data-rep-up title="上へ移動" aria-label="上へ移動"><i class="ti ti-chevron-up"></i></button>
              <button type="button" class="rep-row-mv" data-rep-down title="下へ移動" aria-label="下へ移動"><i class="ti ti-chevron-down"></i></button>
            </span>
            <button type="button" class="adm-btn ghost sm rep-row-del" data-rep-del>削除</button>
          </div>
          <div class="rep-row-body" data-rep-body>
          <?php foreach ($rfields as $f): $k = $f['key'];
              echo adm_repeater_field($f, 'rows[__IDX__][' . h($k) . ']', 'rowfile___IDX___' . h($k), '');
          endforeach; ?>
          </div>
        </div>
      </template>
    </div>
    <?php endif; /* repeater */ ?>
    </div><!-- /.adm-cardgroup（このパーツの内容） -->
    <?php endif; /* fields or repeater */ ?>

    <button class="adm-btn primary" type="submit">このパーツを保存</button>
  </form>
</div>
<?php endforeach; ?>

<!-- パーツ追加（コンパクトなプルダウン＋「サムネから選ぶ」モーダル） -->
<?php $cats = array_keys(part_defs_by_category()); ?>
<div class="adm-addblock">
  <h3>＋ パーツを追加</h3>
  <form method="post" class="adm-inline-form">
    <input type="hidden" name="_csrf" value="<?= h(csrf_token()) ?>">
    <input type="hidden" name="action" value="add_block">
    <label>カテゴリ
      <select id="cat-select">
        <?php foreach ($cats as $c): ?>
          <option value="<?= h($c) ?>"><?= h($c) ?></option>
        <?php endforeach; ?>
      </select>
    </label>
    <label>パーツ
      <select name="part" id="part-select">
        <?php foreach ($defs as $key => $def): ?>
          <option value="<?= h($key) ?>" data-cat="<?= h($def['category']) ?>"><?= h($def['label']) ?></option>
        <?php endforeach; ?>
      </select>
    </label>
    <button class="adm-btn primary" type="submit">追加</button>
    <button class="adm-btn soft" type="button" data-part-modal-open>サムネから選ぶ</button>
  </form>
</div>
</div>

<!-- サムネ一覧モーダル（クリックで即追加） -->
<div class="part-modal" data-part-modal hidden>
  <div class="part-modal-backdrop" data-part-modal-close></div>
  <div class="part-modal-panel" role="dialog" aria-modal="true" aria-label="パーツをサムネから選ぶ">
    <div class="part-modal-head">
      <strong class="part-modal-title">サムネから選ぶ（クリックで追加）</strong>
      <button type="button" class="part-modal-x" data-part-modal-close aria-label="閉じる">×</button>
    </div>
    <div class="part-picker-tools">
      <input type="search" class="part-search" data-part-search placeholder="パーツ名で検索（例：カラム、料金、FAQ…）" aria-label="パーツを検索">
      <div class="part-cat-chips" data-part-chips>
        <button type="button" class="part-chip is-active" data-cat="">すべて</button>
        <?php foreach ($cats as $c): ?>
          <button type="button" class="part-chip" data-cat="<?= h($c) ?>"><?= h($c) ?></button>
        <?php endforeach; ?>
      </div>
    </div>
    <form method="post" class="part-modal-body">
      <input type="hidden" name="_csrf" value="<?= h(csrf_token()) ?>">
      <input type="hidden" name="action" value="add_block">
      <div class="part-grid" data-part-grid>
        <?php foreach ($defs as $key => $def):
            $thumb  = cms_part_thumb($key);
            $search = strtolower($def['label'] . ' ' . $key . ' ' . (string)($def['description'] ?? '') . ' ' . $def['category']);
            $lbData = json_encode([
                'url'   => $thumb['url'],
                'video' => $thumb['video'],
                'label' => $def['label'],
                'cat'   => $def['category'],
                'desc'  => (string)($def['description'] ?? ''),
                'key'   => $key,
            ], JSON_UNESCAPED_UNICODE);
        ?>
          <div class="part-card" data-cat="<?= h($def['category']) ?>" data-search="<?= h($search) ?>">
            <button type="submit" name="part" value="<?= h($key) ?>" class="part-card-add" title="クリックでこのパーツを追加：<?= h((string)($def['description'] ?? $def['label'])) ?>">
              <span class="part-thumb-wrap">
                <?php if ($thumb['url'] !== ''): ?>
                  <?php if ($thumb['video']): ?>
                    <video class="part-thumb" src="<?= h($thumb['url']) ?>" autoplay loop muted playsinline preload="metadata"></video>
                  <?php else: ?>
                    <img class="part-thumb" src="<?= h($thumb['url']) ?>" alt="" loading="lazy">
                  <?php endif; ?>
                <?php else: ?>
                  <span class="part-thumb part-thumb-none" aria-hidden="true"><?= h(mb_substr($def['label'], 0, 1)) ?></span>
                <?php endif; ?>
              </span>
              <span class="part-name"><?= h($def['label']) ?></span>
              <span class="part-cat-badge"><?= h($def['category']) ?></span>
            </button>
            <button type="button" class="part-zoom" data-part-zoom data-thumb="<?= h($lbData) ?>" aria-label="<?= h($def['label']) ?> を拡大して見る" title="拡大して見る"><i class="ti ti-zoom-in"></i></button>
          </div>
        <?php endforeach; ?>
      </div>
      <p class="part-empty" data-part-empty hidden>該当するパーツがありません。検索語やカテゴリを変えてみてください。</p>
    </form>
  </div>
</div>

<!-- サムネ拡大ライトボックス（モーダルの上に開く。虫眼鏡ボタンから） -->
<div class="part-lightbox" data-part-lightbox hidden>
  <div class="part-lightbox-backdrop" data-lb-close></div>
  <div class="part-lightbox-panel" role="dialog" aria-modal="true" aria-label="パーツの拡大プレビュー">
    <button type="button" class="part-lightbox-x" data-lb-close aria-label="閉じる">×</button>
    <div class="part-lightbox-media" data-lb-media></div>
    <div class="part-lightbox-info">
      <strong class="part-lightbox-name" data-lb-name></strong>
      <span class="part-lightbox-cat" data-lb-cat></span>
      <p class="part-lightbox-desc" data-lb-desc></p>
      <form method="post">
        <input type="hidden" name="_csrf" value="<?= h(csrf_token()) ?>">
        <input type="hidden" name="action" value="add_block" data-lb-action>
        <input type="hidden" name="block_id" value="" data-lb-block>
        <button type="submit" class="adm-btn primary" name="part" value="" data-lb-add>このパーツをページに追加</button>
      </form>
    </div>
  </div>
</div>

<!-- 着せ替え（デザイン）をサムネで選ぶモーダル：同じ family の仲間だけ。検索なし。 -->
<div class="part-modal" data-skin-modal hidden>
  <div class="part-modal-backdrop" data-skin-close></div>
  <div class="part-modal-panel" role="dialog" aria-modal="true" aria-label="デザイン（見た目）をサムネで選ぶ">
    <div class="part-modal-head">
      <strong class="part-modal-title">デザインを選ぶ（中身はそのまま・見た目だけ変更）</strong>
      <button type="button" class="part-modal-x" data-skin-close aria-label="閉じる">×</button>
    </div>
    <form method="post" class="part-modal-body" data-skin-form>
      <input type="hidden" name="_csrf" value="<?= h(csrf_token()) ?>">
      <input type="hidden" name="action" value="set_skin">
      <input type="hidden" name="block_id" value="" data-skin-block>
      <div class="part-grid" data-skin-grid></div>
    </form>
  </div>
</div>

<script>
/* パーツ追加：カテゴリでパーツ候補を絞り込む（コンパクトなプルダウン） */
(function () {
  var cat = document.getElementById('cat-select');
  var part = document.getElementById('part-select');
  if (!cat || !part) return;
  function sync() {
    var c = cat.value, first = null;
    Array.prototype.forEach.call(part.options, function (o) {
      var show = o.getAttribute('data-cat') === c;
      o.hidden = !show; o.disabled = !show;
      if (show && first === null) first = o;
    });
    var cur = part.selectedOptions[0];
    if (first && (!cur || cur.hidden)) part.value = first.value;
  }
  cat.addEventListener('change', sync);
  sync();
})();

/* サムネモーダル：開閉＋検索＋カテゴリ絞り込み（カードのクリックで即追加） */
(function () {
  var modal = document.querySelector('[data-part-modal]');
  if (!modal) return;
  var openBtn = document.querySelector('[data-part-modal-open]');
  function open() { modal.hidden = false; document.body.style.overflow = 'hidden'; var s = modal.querySelector('[data-part-search]'); if (s) s.focus(); }
  function close() { modal.hidden = true; document.body.style.overflow = ''; }
  if (openBtn) openBtn.addEventListener('click', open);
  modal.addEventListener('click', function (e) { if (e.target.closest('[data-part-modal-close]')) close(); });
  document.addEventListener('keydown', function (e) {
    if (e.key !== 'Escape' || modal.hidden) return;
    var lb = document.querySelector('[data-part-lightbox]');
    if (lb && !lb.hidden) return; // ライトボックスが開いている時はそちらを優先
    close();
  });

  var search = modal.querySelector('[data-part-search]');
  var chips  = modal.querySelector('[data-part-chips]');
  var cards  = Array.prototype.slice.call(modal.querySelectorAll('.part-card'));
  var empty  = modal.querySelector('[data-part-empty]');
  var cat = '';
  function apply() {
    var q = (search.value || '').trim().toLowerCase();
    var shown = 0;
    cards.forEach(function (c) {
      var okCat = !cat || c.getAttribute('data-cat') === cat;
      var okQ   = !q || c.getAttribute('data-search').indexOf(q) !== -1;
      var show  = okCat && okQ;
      c.hidden = !show;
      if (show) shown++;
    });
    if (empty) empty.hidden = shown !== 0;
  }
  if (search) search.addEventListener('input', apply);
  if (chips) chips.addEventListener('click', function (e) {
    var b = e.target.closest('.part-chip'); if (!b) return;
    cat = b.getAttribute('data-cat') || '';
    chips.querySelectorAll('.part-chip').forEach(function (x) { x.classList.toggle('is-active', x === b); });
    apply();
  });
  apply();
})();

/* サムネ拡大ライトボックス：虫眼鏡ボタンで大きく見る（ここでは追加しない。中の「追加」ボタンで追加） */
(function () {
  var lb = document.querySelector('[data-part-lightbox]');
  if (!lb) return;
  var media = lb.querySelector('[data-lb-media]');
  var name  = lb.querySelector('[data-lb-name]');
  var catEl = lb.querySelector('[data-lb-cat]');
  var desc  = lb.querySelector('[data-lb-desc]');
  var addBtn= lb.querySelector('[data-lb-add]');
  var actInput = lb.querySelector('[data-lb-action]');
  var blkInput = lb.querySelector('[data-lb-block]');
  function openLb(d) {
    if (d.url) {
      media.innerHTML = d.video
        ? '<video src="' + encodeURI(d.url) + '" autoplay loop muted playsinline controls></video>'
        : '<img src="' + encodeURI(d.url) + '" alt="">';
    } else {
      media.innerHTML = '<div class="part-lightbox-none">見本画像なし</div>';
    }
    name.textContent = d.label || '';
    catEl.textContent = d.cat || '';
    desc.textContent = d.desc || '';
    desc.hidden = !d.desc;
    var skin = d.mode === 'skin';
    actInput.value = skin ? 'set_skin' : 'add_block';
    blkInput.value = skin ? (d.block || '') : '';
    addBtn.setAttribute('name', skin ? 'skin' : 'part');
    addBtn.value = d.key || '';
    if (skin && d.current) { addBtn.disabled = true;  addBtn.textContent = '使用中（現在の見た目）'; }
    else { addBtn.disabled = false; addBtn.textContent = skin ? 'この見た目に切り替える' : 'このパーツをページに追加'; }
    lb.hidden = false;
  }
  function closeLb() { lb.hidden = true; media.innerHTML = ''; }
  document.addEventListener('click', function (e) {
    var z = e.target.closest('[data-part-zoom]');
    if (z) { e.preventDefault();
      try { openLb(JSON.parse(z.getAttribute('data-thumb'))); } catch (err) {}
      return; }
    if (e.target.closest('[data-lb-close]')) closeLb();
  });
  document.addEventListener('keydown', function (e) { if (e.key === 'Escape' && !lb.hidden) closeLb(); });
})();

/* 着せ替え（デザイン）をサムネで選ぶモーダル：同じ family の仲間だけ・検索なし・クリックで即切替 */
(function () {
  var modal = document.querySelector('[data-skin-modal]');
  if (!modal) return;
  var grid = modal.querySelector('[data-skin-grid]');
  var blkInput = modal.querySelector('[data-skin-block]');
  function buildCard(m, blockId) {
    var card = document.createElement('div');
    card.className = 'part-card';
    var add = document.createElement('button');
    add.type = 'submit'; add.name = 'skin'; add.value = m.key; add.className = 'part-card-add';
    add.title = m.current ? '使用中の見た目です' : ('この見た目に切り替える：' + (m.label || ''));
    var wrap = document.createElement('span'); wrap.className = 'part-thumb-wrap';
    if (m.thumb) {
      var el = m.video ? document.createElement('video') : document.createElement('img');
      el.className = 'part-thumb'; el.src = m.thumb;
      if (m.video) { el.autoplay = true; el.loop = true; el.muted = true; el.setAttribute('playsinline',''); el.preload = 'metadata'; }
      else { el.alt = ''; el.loading = 'lazy'; }
      wrap.appendChild(el);
    } else {
      var ph = document.createElement('span'); ph.className = 'part-thumb part-thumb-none';
      ph.textContent = (m.label || '?').slice(0, 1); wrap.appendChild(ph);
    }
    add.appendChild(wrap);
    var nm = document.createElement('span'); nm.className = 'part-name'; nm.textContent = m.label || ''; add.appendChild(nm);
    if (m.current) { var cb = document.createElement('span'); cb.className = 'part-card-current'; cb.textContent = '使用中'; add.appendChild(cb); }
    card.appendChild(add);
    var zoom = document.createElement('button');
    zoom.type = 'button'; zoom.className = 'part-zoom'; zoom.title = '拡大して見る';
    zoom.setAttribute('aria-label', (m.label || '') + ' を拡大して見る');
    zoom.setAttribute('data-part-zoom', '');
    zoom.setAttribute('data-thumb', JSON.stringify({
      url: m.thumb, video: m.video, label: m.label, cat: m.cat, desc: m.desc,
      key: m.key, mode: 'skin', block: blockId, current: m.current
    }));
    zoom.innerHTML = '<i class="ti ti-zoom-in"></i>';
    card.appendChild(zoom);
    return card;
  }
  function open(btn) {
    var list = []; try { list = JSON.parse(btn.getAttribute('data-skins')) || []; } catch (e) {}
    var blockId = btn.getAttribute('data-block') || '';
    blkInput.value = blockId;
    grid.innerHTML = '';
    list.forEach(function (m) { grid.appendChild(buildCard(m, blockId)); });
    modal.hidden = false; document.body.style.overflow = 'hidden';
  }
  function close() { modal.hidden = true; document.body.style.overflow = ''; }
  document.addEventListener('click', function (e) {
    var opener = e.target.closest('[data-skin-open]');
    if (opener) { e.preventDefault(); open(opener); return; }
    if (!modal.hidden && e.target.closest('[data-skin-close]')) close();
  });
  document.addEventListener('keydown', function (e) {
    if (e.key !== 'Escape' || modal.hidden) return;
    var lb = document.querySelector('[data-part-lightbox]');
    if (lb && !lb.hidden) return; // ライトボックス優先
    close();
  });
})();
</script>

<script>
window.CMS_ICONS = <?= json_encode(cms_icon_set(), JSON_UNESCAPED_UNICODE) ?>;
window.CMS_ICONS_ALL = <?= json_encode(cms_tabler_all_icons(), JSON_UNESCAPED_UNICODE) ?>;
window.CMS_PAGES = <?= json_encode(array_map(function ($p) {
    return ['slug' => $p['slug'], 'title' => $p['title'], 'path' => ($p['slug'] === 'home' ? '/' : '/' . $p['slug'])];
}, page_all()), JSON_UNESCAPED_UNICODE) ?>;
</script>

<?php admin_footer(); ?>
