<?php
/**
 * Style Module Guide — an in-app, theme-aware reference for the theme system.
 *
 * Rendered by StyleController::guide(). Follows the shared --pp-* tokens via
 * theme-head.php, so the guide itself obeys light/dark like every other module.
 *
 * The token tables are generated from ThemeCompiler's own catalogue/docs, so
 * this page can never drift from what the compiler actually emits.
 *
 * @var string $baseUrl
 * @var string $assetsUrl
 * @var array  $project
 * @var array<string, list<string>> $catalogue
 * @var array<string, string>       $docs
 * @var list<string>                $authored
 * @var array                       $navbars
 * @var array                       $presets
 */
if (!isset($baseUrl)) $baseUrl = '';
if (!isset($assetsUrl)) $assetsUrl = rtrim($baseUrl, '/') . '/assets';

$slug        = (string) ($project['slug'] ?? '');
$styleUrl    = $baseUrl . '/project/' . rawurlencode($slug) . '/style';
$authoredSet = array_flip($authored ?? []);
$totalTokens = array_sum(array_map('count', $catalogue ?? []));

/** Fenced code block with a language label + copy button. $src is literal. */
$code = function (string $src, string $lang = 'css'): string {
    $id = 'c' . substr(md5($src . mt_rand()), 0, 8);
    return '<div class="st-code" data-lang="' . htmlspecialchars($lang) . '">'
        . '<button type="button" class="st-code-copy" data-copy="' . $id . '" title="Copy"><i class="bi bi-clipboard"></i></button>'
        . '<pre><code id="' . $id . '">' . htmlspecialchars(rtrim($src, "\n")) . '</code></pre>'
        . '</div>';
};

$toc = [
    ['intro',     'What the Style module is', 'bi-lightbulb'],
    ['tokens',    'Design tokens',            'bi-palette2'],
    ['derived',   'Authored vs derived',      'bi-magic'],
    ['darkmode',  'Light &amp; dark',         'bi-circle-half'],
    ['tabs',      'The six tabs',             'bi-layout-sidebar-inset'],
    ['navbars',   'Navbar layouts',           'bi-window-sidebar'],
    ['menu',      'Menu editor',              'bi-list-nested'],
    ['customcss', 'Custom CSS',               'bi-code-slash'],
    ['build',     'How it reaches the app',   'bi-box-seam'],
    ['newtheme',  'Creating a theme',         'bi-plus-circle'],
    ['reference', 'Token reference',          'bi-table'],
    ['pitfalls',  'Pitfalls',                 'bi-exclamation-triangle'],
];
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><?= htmlspecialchars(\PpPhp\Core\Brand::name()) ?> · Style Module Guide</title>
    <link href="<?= $assetsUrl ?>/bootstrap/css/bootstrap.min.css" rel="stylesheet">
    <link href="<?= $assetsUrl ?>/icons/bootstrap-icons.css" rel="stylesheet">
    <script src="<?= $assetsUrl ?>/bootstrap/js/bootstrap.bundle.min.js" defer></script>
    <?php include constant('BASE_PATH') . '/core/views/theme-head.php'; ?>
    <style>
    /* Everything here rides the shared --pp-* tokens, so the guide follows the
       console's own light/dark exactly as the other module guides do. */
    body.style-guide-page { background:var(--pp-app-bg); color:var(--pp-text); padding-top:56px; }

    .st-nav { display:flex; align-items:center; gap:1rem; padding:.7rem 1.1rem;
        background:var(--pp-surface); border-bottom:1px solid var(--pp-border); position:sticky; top:0; z-index:30; }
    .st-nav .back { display:inline-flex; align-items:center; gap:.5rem; font-weight:700; font-size:.88rem;
        color:var(--pp-text); text-decoration:none; }
    .st-nav .back:hover { color:var(--pp-accent); }
    .st-nav .title { font-size:.8rem; font-weight:800; letter-spacing:.1em; text-transform:uppercase;
        color:var(--pp-text-muted); }
    .st-nav .spacer { margin-left:auto; }

    .st-wrap { max-width:1320px; margin:0 auto; padding:1.6rem 1.1rem 4rem; }
    .st-guide { display:grid; grid-template-columns:264px minmax(0,1fr); gap:2rem; align-items:start; }
    .st-toc { position:sticky; top:70px; align-self:start; max-height:calc(100vh - 96px); overflow:auto;
        border:1px solid var(--pp-border); border-radius:16px; background:var(--pp-surface); padding:.85rem; }
    .st-toc-title { font-size:.66rem; letter-spacing:.14em; text-transform:uppercase; font-weight:800;
        color:var(--pp-text-muted); padding:.2rem .55rem .55rem; }
    .st-toc a { display:flex; align-items:center; gap:.55rem; padding:.5rem .6rem; border-radius:9px;
        color:var(--pp-text); font-size:.83rem; font-weight:600; text-decoration:none; line-height:1.2; }
    .st-toc a i { color:var(--pp-accent); font-size:.95rem; flex:0 0 auto; }
    .st-toc a:hover { background:var(--pp-surface-soft); }
    .st-toc a.active { background:var(--pp-accent-soft); color:var(--pp-accent); }
    .st-main { min-width:0; }

    .st-hero { border-radius:18px; padding:1.6rem 1.8rem; margin-bottom:1.8rem; border:1px solid var(--pp-border);
        background:linear-gradient(135deg, color-mix(in srgb,var(--pp-accent) 22%, var(--pp-surface)), var(--pp-surface)); }
    .st-hero .eyebrow { font-size:.68rem; letter-spacing:.14em; text-transform:uppercase; font-weight:800;
        color:var(--pp-accent); margin-bottom:.4rem; }
    .st-hero h1 { font-size:1.7rem; font-weight:850; margin:0 0 .35rem; }
    .st-hero p { margin:0; color:var(--pp-text-muted); font-size:.98rem; max-width:62ch; line-height:1.6; }

    .st-sec { scroll-margin-top:80px; margin-bottom:2.4rem; }
    .st-sec > h2 { display:flex; align-items:center; gap:.6rem; font-size:1.35rem; font-weight:800; margin:0 0 .5rem;
        padding-bottom:.55rem; border-bottom:1px solid var(--pp-border); }
    .st-sec > h2 .num { display:inline-grid; place-items:center; width:30px; height:30px; border-radius:9px;
        background:var(--pp-accent-soft); color:var(--pp-accent); font-size:.82rem; font-weight:800; flex:0 0 auto; }
    .st-sec h3 { font-size:1rem; font-weight:750; margin:1.5rem 0 .5rem; }
    .st-sec p, .st-sec li { line-height:1.62; font-size:.92rem; }
    .st-sec p, .st-sec ul, .st-sec ol { margin-bottom:.85rem; }
    .st-sec a { color:var(--pp-accent); }
    .st-lead { font-size:1.02rem; color:var(--pp-text-muted); line-height:1.6; }
    .st-sec code:not(pre code) { background:var(--pp-surface-muted); color:var(--pp-accent);
        padding:.08em .4em; border-radius:5px; font-size:.86em;
        border:1px solid color-mix(in srgb, var(--pp-border) 70%, transparent); }

    .st-code { position:relative; margin:.4rem 0 1rem; border:1px solid var(--pp-border-strong);
        border-radius:12px; overflow:hidden; background:var(--pp-surface-muted,#0b1020); }
    .st-code::before { content:attr(data-lang); position:absolute; top:8px; left:14px; font-size:.62rem;
        letter-spacing:.12em; text-transform:uppercase; font-weight:800; color:var(--pp-text-muted,#8ea0c4); }
    .st-code pre { margin:0; padding:2rem 1rem 1rem; overflow-x:auto; }
    .st-code code { font-family:'SF Mono',ui-monospace,Menlo,Consolas,monospace; font-size:.8rem;
        line-height:1.65; color:var(--pp-text,#dfe6f5); white-space:pre; }
    .st-code-copy { position:absolute; top:6px; right:6px; z-index:2; width:30px; height:30px; border-radius:8px;
        border:1px solid var(--pp-border,rgba(255,255,255,.14)); background:color-mix(in srgb, var(--pp-accent,#4f46e5) 8%, transparent); color:var(--pp-text-muted,#c7d2e8);
        display:grid; place-items:center; cursor:pointer; }
    .st-code-copy:hover { background:color-mix(in srgb, var(--pp-accent,#4f46e5) 16%, transparent); color:var(--pp-text,#fff); }
    .st-code-copy.copied { color:#4ade80; border-color:#4ade80; }

    .st-note { display:flex; gap:.7rem; padding:.85rem 1rem; border-radius:12px; margin:.4rem 0 1rem;
        border:1px solid var(--pp-border); background:var(--pp-surface-soft); font-size:.88rem; line-height:1.55; }
    .st-note i { font-size:1.05rem; flex:0 0 auto; margin-top:.06rem; }
    .st-note.tip { border-color:color-mix(in srgb,#22c55e 45%,var(--pp-border)); background:color-mix(in srgb,#22c55e 10%,var(--pp-surface)); }
    .st-note.tip i { color:#16a34a; }
    .st-note.warn { border-color:color-mix(in srgb,#f59e0b 50%,var(--pp-border)); background:color-mix(in srgb,#f59e0b 12%,var(--pp-surface)); }
    .st-note.warn i { color:#d97706; }

    .st-cards { display:grid; grid-template-columns:repeat(auto-fit,minmax(220px,1fr)); gap:.9rem; margin:.6rem 0 1rem; }
    .st-card { border:1px solid var(--pp-border); border-radius:14px; padding:1rem; background:var(--pp-surface); }
    .st-card .ic { width:40px; height:40px; border-radius:11px; display:grid; place-items:center; font-size:1.15rem;
        background:var(--pp-accent-soft); color:var(--pp-accent); margin-bottom:.6rem; }
    .st-card h4 { font-size:.95rem; font-weight:750; margin:0 0 .25rem; }
    .st-card p { font-size:.82rem; color:var(--pp-text-muted); margin:0; line-height:1.5; }

    .st-ref-wrap { overflow-x:auto; }
    .st-ref { width:100%; border-collapse:separate; border-spacing:0; font-size:.85rem; margin:.3rem 0 1rem;
        border:1px solid var(--pp-border); border-radius:12px; overflow:hidden; }
    .st-ref th { text-align:left; background:var(--pp-surface-muted); font-weight:750; padding:.55rem .7rem;
        font-size:.74rem; text-transform:uppercase; letter-spacing:.04em; }
    .st-ref td { padding:.5rem .7rem; border-top:1px solid var(--pp-border); vertical-align:top; }
    .st-ref td code { background:var(--pp-surface-muted); color:var(--pp-accent); padding:.05em .35em;
        border-radius:4px; font-size:.9em; }
    .st-tag { display:inline-block; font-size:.62rem; font-weight:800; padding:.12em .45em; border-radius:4px;
        vertical-align:1px; }
    .st-tag.a { background:var(--pp-accent); color:#fff; }
    .st-tag.d { background:var(--pp-surface-muted); color:var(--pp-text-muted); border:1px solid var(--pp-border); }
    .st-grp-title { display:flex; align-items:center; gap:.5rem; margin:1.4rem 0 .4rem; font-size:.78rem;
        font-weight:800; text-transform:uppercase; letter-spacing:.06em; color:var(--pp-text-muted); }
    .st-grp-title .n { font-size:.66rem; padding:.1em .5em; border-radius:20px; background:var(--pp-surface-muted);
        border:1px solid var(--pp-border); letter-spacing:0; }

    .st-stats { display:flex; flex-wrap:wrap; gap:.7rem; margin:.2rem 0 1.2rem; }
    .st-stat { flex:1 1 130px; border:1px solid var(--pp-border); border-radius:12px; padding:.8rem .9rem;
        background:var(--pp-surface); }
    .st-stat b { display:block; font-size:1.45rem; font-weight:850; line-height:1.1; }
    .st-stat span { display:block; font-size:.72rem; text-transform:uppercase; letter-spacing:.05em;
        color:var(--pp-text-muted); margin-top:.15rem; }

    @media (max-width: 991.98px) { .st-guide { grid-template-columns:1fr; } .st-toc { position:static; max-height:none; } }
    </style>
</head>
<body class="style-guide-page">
<?php
    $user = $user ?? \PpPhp\Core\Security::CurrentUserData();
    $navBrandLabel = 'Style guide';
    $navBrandIcon  = 'palette';
    $navBack = ['url' => $styleUrl, 'label' => 'Back to Style', 'icon' => 'arrow-left'];
    include constant('BASE_PATH') . '/core/views/console-navbar.php';
?>

<div class="st-wrap">
  <div class="st-guide">
    <aside class="st-toc" id="stToc">
        <div class="st-toc-title">On this page</div>
        <?php foreach ($toc as $i => [$anchor, $label, $icon]): ?>
            <a href="#<?= $anchor ?>"<?= $i === 0 ? ' class="active"' : '' ?>><i class="bi <?= $icon ?>"></i><span><?= $label ?></span></a>
        <?php endforeach; ?>
    </aside>

    <main class="st-main">
        <div class="st-hero">
            <div class="eyebrow">DESIGN THE LOOK</div>
            <h1>Style Module Guide</h1>
            <p>One place to define how every generated app looks — colours, shape, typography, page frame and navigation. Themes are pure data, and the compiler derives a complete, contrast-checked palette from a handful of choices.</p>
        </div>

        <div class="st-stats">
            <div class="st-stat"><b><?= (int) $totalTokens ?></b><span>design tokens</span></div>
            <div class="st-stat"><b><?= count($authored ?? []) ?></b><span>you author</span></div>
            <div class="st-stat"><b><?= (int) $totalTokens - count($authored ?? []) ?></b><span>derived for you</span></div>
            <div class="st-stat"><b><?= count($navbars ?? []) ?></b><span>navbar layouts</span></div>
        </div>

        <!-- 1 -->
        <section class="st-sec" id="intro">
            <h2><span class="num">1</span> What the Style module is</h2>
            <p class="st-lead">The Style module is the <strong>blueprint</strong> for your application's appearance. Everything it produces is data — no CSS is written by hand — and that data compiles into the stylesheet every generated page loads.</p>
            <p>A <strong>theme</strong> is a small JSON document: a handful of base colours, a radius, a font, plus optional dark-mode overrides and layout choices. It lives in your project's <code>config/themes.json</code>, so it travels with the project and can be duplicated, exported and version-controlled.</p>
            <p>The payoff is that appearance is <em>centralised</em>. Because no component hardcodes a colour, switching the active theme re-skins every page, table, form, badge, tooltip and chart at once — including parts you never touched.</p>
            <div class="st-note tip"><i class="bi bi-lightbulb"></i><div>Edits preview live. The panel on the right renders real designed pages with your draft theme applied, so you can judge a change before saving it.</div></div>
        </section>

        <!-- 2 -->
        <section class="st-sec" id="tokens">
            <h2><span class="num">2</span> Design tokens</h2>
            <p>A <strong>design token</strong> is a named CSS custom property that stands for one decision — "the page background", "the colour of a selected row". Components reference the name, never the value:</p>
            <?= $code(".pp-card {\n  background: var(--pp-surface);\n  border : 1px solid var(--pp-border);\n  color  : var(--pp-text);\n  border-radius: var(--pp-radius);\n}", 'css') ?>
            <p>Every token this platform emits is prefixed <code>--pp-</code> and grouped by purpose. The <strong>Palette</strong> tab lists all <?= (int) $totalTokens ?> of them with their resolved values and what each is for; click one to copy its <code>var(…)</code> reference.</p>
            <h3>Why this matters</h3>
            <ul>
                <li><strong>Themability</strong> — one change cascades everywhere, instantly.</li>
                <li><strong>Consistency</strong> — the same decision cannot drift between two screens.</li>
                <li><strong>Dark mode for free</strong> — a token resolves to a different value under <code>.is-dark</code>; the component's CSS is unchanged.</li>
            </ul>
            <div class="st-note warn"><i class="bi bi-exclamation-triangle"></i><div>Never write a raw colour in custom CSS or a plugin. Use a token. A hardcoded <code>#fff</code> will stay white in dark mode and break the theme.</div></div>
        </section>

        <!-- 3 -->
        <section class="st-sec" id="derived">
            <h2><span class="num">3</span> Authored vs derived</h2>
            <p>You author only <strong><?= count($authored ?? []) ?></strong> tokens on the <em>Theme</em> tab. The compiler derives the other <strong><?= (int) $totalTokens - count($authored ?? []) ?></strong> from them.</p>
            <p>That is deliberate. Hand-authoring ninety colours is tedious and error-prone; deriving them means <em>every</em> theme is complete by construction — even a partial or very old one — so no component ever needs a fallback.</p>
            <h3>What derivation does</h3>
            <ul>
                <li><strong>Tints and shades</strong> — <code>--pp-row-hover</code> is your primary mixed into the surface, so hover states pick up your brand automatically.</li>
                <li><strong>Contrast checking</strong> — text tokens such as <code>--pp-success-text</code> are computed <em>against</em> the background they sit on, to a minimum ratio. They stay legible whatever brand colour you pick.</li>
                <li><strong>Luminance awareness</strong> — hover veils flip direction based on how light the surface is: white over a dark bar, black over a light one. A fixed translucent white is invisible on a light shell.</li>
            </ul>
            <p>Change <code>primary</code> to a deep rose and the link colour, focus ring, hover row, selected row and selection highlight all move with it — no further edits.</p>
            <div class="st-note tip"><i class="bi bi-magic"></i><div>In the Palette tab, authored tokens carry an <span class="st-tag a">A</span> badge and a coloured left edge. Everything else is derived — editing the source token is how you change it.</div></div>
        </section>

        <!-- 4 -->
        <section class="st-sec" id="darkmode">
            <h2><span class="num">4</span> Light &amp; dark</h2>
            <p>Every theme ships both schemes. The compiler emits light values at the root and dark values inside an <code>.is-dark</code> block, and the generated app toggles that class:</p>
            <?= $code(":root {\n  --pp-surface: #ffffff;\n  --pp-text   : #0f172a;\n}\n:root.is-dark {\n  --pp-surface: #111827;\n  --pp-text   : #e5e7eb;\n}", 'css') ?>
            <p>You only override on the <em>dark</em> mode toggle what genuinely differs. Anything you leave alone is derived again from the dark surface, so status chips, hover rows and focus rings are re-tinted for the dark background rather than being reused from light.</p>
            <div class="st-note warn"><i class="bi bi-exclamation-triangle"></i><div><strong>Testing dark mode:</strong> do not add or remove <code>.is-dark</code> by hand in devtools — the page's own theme script re-applies from <code>localStorage</code> and will fight you, producing contradictory results. Use the app's toggle, or set <code>localStorage.pp_theme = 'dark'</code> and reload.</div></div>
        </section>

        <!-- 5 -->
        <section class="st-sec" id="tabs">
            <h2><span class="num">5</span> The six tabs</h2>
            <div class="st-cards">
                <div class="st-card"><div class="ic"><i class="bi bi-palette"></i></div><h4>Theme</h4><p>The authored tokens — brand, surfaces, text, status colours, radius, fonts. Everything else derives from here. Disabled on a locked built-in.</p></div>
                <div class="st-card"><div class="ic"><i class="bi bi-layout-sidebar-inset"></i></div><h4>Layout</h4><p>The page frame: navbar type, topbar, sidebar, logo, menu placement, footer, and density. Options adapt to the layout you pick.</p></div>
                <div class="st-card"><div class="ic"><i class="bi bi-table"></i></div><h4>Grid</h4><p>Data-table appearance — panel and grid surfaces, header, striping, borders.</p></div>
                <div class="st-card"><div class="ic"><i class="bi bi-list-nested"></i></div><h4>Menu</h4><p>The navigation tree for the generated app, including per-item role visibility.</p></div>
                <div class="st-card"><div class="ic"><i class="bi bi-code-slash"></i></div><h4>CSS</h4><p>Free-form custom CSS appended after the compiled tokens, for anything the options do not cover.</p></div>
                <div class="st-card"><div class="ic"><i class="bi bi-eyedropper"></i></div><h4>Palette</h4><p>Read-only reference: every token, its resolved value, what it is for, and whether it was authored or derived.</p></div>
            </div>
            <p>Only <em>Layout</em>, <em>Menu</em> and the project shell are project-wide. <em>Theme</em>, <em>Grid</em> and <em>CSS</em> belong to the individual theme, so duplicating a theme carries them along.</p>
        </section>

        <!-- 6 -->
        <section class="st-sec" id="navbars">
            <h2><span class="num">6</span> Navbar layouts</h2>
            <p>The navbar type sets the whole page frame. Pick one on the Layout tab; contextual options appear underneath for whichever you choose.</p>
            <div class="st-ref-wrap">
            <table class="st-ref">
                <thead><tr><th style="width:22%">Layout</th><th>What it does</th><th style="width:26%">Extra options</th></tr></thead>
                <tbody>
                <?php
                $extras = [
                    'classic'    => '—',
                    'horizontal' => '—',
                    'sidebar'    => '—',
                    'rail'       => 'Collapsed &amp; expanded width',
                    'boxed'      => 'Max width',
                    'hero'       => 'Height, gradient angle',
                ];
                foreach (($navbars ?? []) as $nb):
                    $id = (string) ($nb['id'] ?? ''); ?>
                    <tr>
                        <td><strong><?= htmlspecialchars((string) ($nb['label'] ?? $id)) ?></strong><br><code><?= htmlspecialchars($id) ?></code></td>
                        <td><?= htmlspecialchars((string) ($nb['description'] ?? '')) ?></td>
                        <td><?= $extras[$id] ?? '—' ?></td>
                    </tr>
                <?php endforeach; ?>
                </tbody>
            </table>
            </div>
            <h3>Topbar options</h3>
            <p>The Topbar group on the Layout tab controls what the bar contains and how it is finished. Every one of these is read by the build and rendered in the generated app.</p>
            <div class="st-ref-wrap">
            <table class="st-ref">
                <thead><tr><th style="width:26%">Option</th><th>What it does</th></tr></thead>
                <tbody>
                    <tr><td><strong>Show topbar</strong></td><td>Renders the bar at all. Turning it off gives a sidebar-only frame.</td></tr>
                    <tr><td><strong>Theme</strong></td><td>Shell palette for the bar — drives <code>--pp-topbar-*</code>, including the hover veil, which flips direction by luminance.</td></tr>
                    <tr><td><strong>Height</strong></td><td>Bar height (<code>--pp-topbar-h</code>). Content offset follows automatically.</td></tr>
                    <tr><td><strong>Fixed</strong></td><td>Pins the bar while the page scrolls. Unpinned, it scrolls away with the content.</td></tr>
                    <tr><td><strong>Gradient</strong></td><td>Blends the topbar colour into the theme primary across the bar.</td></tr>
                    <tr><td><strong>Edge</strong></td><td>How the bar separates from the page: drop <em>shadow</em>, a hairline <em>border</em>, or <em>none</em>.</td></tr>
                    <tr><td><strong>Show search</strong></td><td>Adds quick find — type to filter your menu and jump. <kbd>/</kbd> focuses it, arrows navigate, <kbd>Enter</kbd> opens, <kbd>Esc</kbd> closes. It only ever lists pages the signed-in user's role can reach.</td></tr>
                    <tr><td><strong>Show user menu</strong></td><td>The avatar menu with Profile and Sign out. Hidden automatically when database authentication is off — there is no user to show.</td></tr>
                    <tr><td><strong>Frosted (blur)</strong></td><td>Translucent bar with a backdrop blur, so content softly shows through. Needs <em>Fixed</em>.</td></tr>
                    <tr><td><strong>Shrink on scroll</strong></td><td>The bar compacts once the page scrolls, giving content more room. Needs <em>Fixed</em>.</td></tr>
                </tbody>
            </table>
            </div>
            <h3>Sidebar options</h3>
            <div class="st-ref-wrap">
            <table class="st-ref">
                <thead><tr><th style="width:26%">Option</th><th>What it does</th></tr></thead>
                <tbody>
                    <tr><td><strong>Show sidebar</strong></td><td>Renders the vertical menu. Only applies when the menu is placed in the sidebar rather than the topbar.</td></tr>
                    <tr><td><strong>Position</strong></td><td>Left or right.</td></tr>
                    <tr><td><strong>Theme</strong> / <strong>Width</strong></td><td>Shell palette and full width — <code>--pp-sidebar-*</code>.</td></tr>
                    <tr><td><strong>Compact (icons only)</strong></td><td>Permanently icons-only, expanding on hover. Reveals the rail width settings.</td></tr>
                    <tr><td><strong>Fixed</strong></td><td>Pins the sidebar; unpinned it scrolls away with the page.</td></tr>
                    <tr><td><strong>Auto hide</strong></td><td>Shrinks the sidebar to an icon rail once the window drops below your threshold, giving wide pages their room back. Hover peeks the labels. It reverses automatically when the window grows, never writes to storage, and an explicit collapse always wins over it.</td></tr>
                    <tr><td><strong>Auto hide below</strong></td><td>The window width that triggers it — default <code>1280px</code>.</td></tr>
                </tbody>
            </table>
            </div>
            <div class="st-note tip"><i class="bi bi-lightbulb"></i><div><strong>Auto hide vs Compact.</strong> <em>Compact</em> is a permanent choice — always icons. <em>Auto hide</em> is responsive — full menu when there is room, icons when there is not. Use Auto hide for data-heavy apps with wide tables.</div></div>

            <p>Long menus also get two automatic touches: the active item is scrolled into view on load, and the top/bottom edges fade while there is more to scroll to.</p>

                        <h3>Navigation &amp; accessibility</h3>
            <p>Every generated app ships these regardless of layout — you do not configure them, and they cannot be switched off:</p>
            <ul>
                <li><strong>Skip to content</strong> — the first focusable element on the page, so keyboard and screen-reader users can jump past the whole menu.</li>
                <li><strong><code>aria-current="page"</code></strong> on the active menu item, so assistive tech announces where you are.</li>
                <li><strong>Visible focus rings</strong> via <code>:focus-visible</code> — keyboard users see them, pointer users never do.</li>
                <li><strong>Keyboard shortcuts</strong> — <kbd>[</kbd> toggles the sidebar; <kbd>/</kbd> focuses quick find when search is on.</li>
                <li><strong>Mobile drawer behaves like a dialog</strong> — opening moves focus inside, <kbd>Tab</kbd> is trapped, the page behind cannot scroll, <kbd>Esc</kbd> or tapping a link closes it and focus returns to the button that opened it.</li>
                <li><strong>Reduced motion respected</strong> — transitions are dropped for users who ask for that at OS level.</li>
                <li><strong>Collapsed state persists</strong> across pages via <code>localStorage</code>.</li>
            </ul>

                        <div class="st-note tip"><i class="bi bi-info-circle"></i><div>The <strong>notification bell</strong> and the <strong>light/dark switcher</strong> are not here on purpose — they are controlled by the <em>Advanced</em> and <em>Misc</em> modules respectively, so they stay in one place rather than being configurable from two.</div></div>

            <p>Each layout is driven by the same shell tokens (<code>--pp-topbar-*</code>, <code>--pp-sidebar-*</code>), so all of them inherit your theme — including the light/dark hover veils — without any per-layout colour work.</p>
            <div class="st-note tip"><i class="bi bi-phone"></i><div>Below 992px every layout falls back to the drawer pattern automatically. You do not need a separate mobile setting.</div></div>
        </section>

        <!-- 7 -->
        <section class="st-sec" id="menu">
            <h2><span class="num">7</span> Menu editor</h2>
            <p>The Menu tab is a three-pane editor — tree, detail, preview — for the navigation of the generated app. Items can be nested, reordered by drag, given icons, and divided with labelled or drawn separators.</p>
            <h3>Role visibility</h3>
            <p>Each item can be restricted to specific roles or groups. This is enforced in the generated app's sidebar, not merely hidden with CSS — an item a user may not see is not rendered for them.</p>
            <p>The role and group vocabulary is read from your project's own Security configuration, so roles you define for the app appear here automatically.</p>
        </section>

        <!-- 8 -->
        <section class="st-sec" id="customcss">
            <h2><span class="num">8</span> Custom CSS</h2>
            <p>The CSS tab is appended <em>after</em> the compiled tokens, so it can override anything. Reference tokens rather than literals so your rules follow the theme:</p>
            <?= $code("/* Good — follows every theme, light and dark */\n.pp-card--callout {\n  background: var(--pp-primary-soft);\n  border-left: 3px solid var(--pp-primary);\n  color: var(--pp-text);\n}\n\n/* Bad — frozen in light mode forever */\n.pp-card--callout {\n  background: #eff6ff;\n  color: #0f172a;\n}", 'css') ?>
            <p>If you need a value that does not exist yet, define your own custom property derived from an existing token rather than inventing a literal.</p>
        </section>

        <!-- 9 -->
        <section class="st-sec" id="build">
            <h2><span class="num">9</span> How it reaches the generated app</h2>
            <p>On Build, the active theme is compiled once and written into the output:</p>
            <ol>
                <li><code>ThemeCompiler</code> derives the full palette and emits <code>assets/css/theme.css</code> — the tokens, light and dark.</li>
                <li>The shell CSS adds layout rules for the chosen navbar type, all reading those tokens.</li>
                <li>A Bootstrap bridge re-points <code>--bs-*</code> properties at the <code>--pp-*</code> equivalents, so Bootstrap components follow the theme instead of staying Bootstrap blue.</li>
                <li>Your Custom CSS is appended last.</li>
            </ol>
            <p>The generated page then simply loads that stylesheet. There is no runtime theme engine to configure and nothing to wire up per page.</p>
            <div class="st-note tip"><i class="bi bi-arrow-repeat"></i><div>Changing a theme does not require touching any page design. Re-run the Build and every page picks up the new palette.</div></div>
        </section>

        <!-- 10 -->
        <section class="st-sec" id="newtheme">
            <h2><span class="num">10</span> Creating a theme</h2>
            <h3>Built-in vs Custom</h3>
            <p>The theme list has two groups, and the distinction is enforced, not cosmetic:</p>
            <ul>
                <li><strong>Built-in</strong> — the ten themes that ship with the platform. Each carries a <i class="bi bi-lock-fill"></i> lock. Selecting one shows every value so you can read it, but <em>all fields are disabled</em>, the name cannot be edited, and Delete is unavailable. The Save button becomes <strong>Duplicate to edit</strong>.</li>
                <li><strong>Custom</strong> — themes you create. Fully editable, renameable and deletable.</li>
            </ul>
            <div class="st-note tip"><i class="bi bi-shield-check"></i><div>Built-ins are locked so a project cannot break a theme every new project starts from. To change one, duplicate it — you get an identical, editable copy under Custom and the original stays intact.</div></div>

            <h3>Step by step</h3>
            <ol>
                <li>Press <strong>New</strong> for a blank theme, or select a built-in that is close to what you want and press <strong>Duplicate to edit</strong>.</li>
                <li>The cursor lands in the <strong>name field</strong> at the top of the editor — a theme needs a name before it can be saved.</li>
                <li>On <em>Theme</em>, set <code>primary</code> first — most of the palette follows from it — then <code>surface</code>, <code>bg</code> and <code>text</code>.</li>
                <li>Switch the preview to <strong>Dark</strong> and override only what genuinely differs. Usually that is just the surfaces and text.</li>
                <li>Check <em>Palette</em>: every group should look sensible. Because text tokens are contrast-checked, unreadable pairs should not occur — if one looks wrong, the source colour is usually the cause.</li>
                <li>Pick a <em>Layout</em>, adjust the contextual options, then <strong>Save</strong> and set it as the default.</li>
                <li>Build the project to publish it.</li>
            </ol>
        </section>

        <!-- 11 -->
        <section class="st-sec" id="reference">
            <h2><span class="num">11</span> Token reference</h2>
            <p>All <?= (int) $totalTokens ?> properties, grouped as they appear in the Palette tab. <span class="st-tag a">A</span> marks an authored token; <span class="st-tag d">D</span> marks a derived one.</p>
            <?php foreach (($catalogue ?? []) as $group => $props): ?>
                <div class="st-grp-title"><?= htmlspecialchars((string) $group) ?><span class="n"><?= count($props) ?></span></div>
                <div class="st-ref-wrap">
                <table class="st-ref">
                    <thead><tr><th style="width:30%">Property</th><th style="width:8%">Source</th><th>Purpose</th></tr></thead>
                    <tbody>
                    <?php foreach ($props as $prop): $isA = isset($authoredSet[$prop]); ?>
                        <tr>
                            <td><code><?= htmlspecialchars((string) $prop) ?></code></td>
                            <td><span class="st-tag <?= $isA ? 'a">A' : 'd">D' ?></span></td>
                            <td><?= htmlspecialchars((string) ($docs[$prop] ?? '')) ?></td>
                        </tr>
                    <?php endforeach; ?>
                    </tbody>
                </table>
                </div>
            <?php endforeach; ?>
        </section>

        <!-- 12 -->
        <section class="st-sec" id="pitfalls">
            <h2><span class="num">12</span> Pitfalls</h2>
            <ul>
                <li><strong>Hardcoded colours.</strong> The single most common cause of a broken dark mode. Always use a token.</li>
                <li><strong>Mismatched token pairs.</strong> A background from one family with text from another — for example a raised-surface background with primary-button text — reads fine in one scheme and becomes invisible in the other. Pair a background with <em>its own</em> text token.</li>
                <li><strong>Assuming "light theme" means a light surface.</strong> A theme can carry a dark surface in light mode. Decisions that invert (tooltips, veils, grid surfaces) should key off the surface's actual luminance, not the light/dark flag.</li>
                <li><strong>Overriding tokens for a test.</strong> Use <code>:root, :root.is-dark</code> — a bare <code>:root</code> loses to the dark block on specificity.</li>
                <li><strong>Editing a preset expecting it to change everywhere.</strong> Presets are templates; a save becomes a project theme.</li>
                <li><strong>Print styles.</strong> Print rules deliberately stay black-on-white and do not follow the theme. That is intended — a printed page should be ink-friendly.</li>
            </ul>
        </section>
    </main>
  </div>
</div>

<script>
(function () {
  document.querySelectorAll('.st-code-copy').forEach(function (btn) {
    btn.addEventListener('click', function () {
      var code = document.getElementById(btn.getAttribute('data-copy'));
      if (!code) return;
      var done = function () {
        btn.classList.add('copied');
        btn.innerHTML = '<i class="bi bi-check-lg"></i>';
        setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = '<i class="bi bi-clipboard"></i>'; }, 1400);
      };
      if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(code.textContent).then(done).catch(function () {});
      } else {
        var ta = document.createElement('textarea'); ta.value = code.textContent; document.body.appendChild(ta);
        ta.select(); try { document.execCommand('copy'); } catch (e) {} document.body.removeChild(ta); done();
      }
    });
  });

  // Scroll-spy for the TOC
  var links = Array.prototype.slice.call(document.querySelectorAll('#stToc a'));
  var map = {}; links.forEach(function (a) { map[a.getAttribute('href').slice(1)] = a; });
  var sections = Array.prototype.slice.call(document.querySelectorAll('.st-sec'));
  var spy = function () {
    var pos = window.scrollY + 120, current = sections[0];
    sections.forEach(function (s) { if (s.offsetTop <= pos) current = s; });
    links.forEach(function (a) { a.classList.remove('active'); });
    if (current && map[current.id]) map[current.id].classList.add('active');
  };
  window.addEventListener('scroll', spy, { passive: true });
  spy();
})();
</script>
</body>
</html>
