7371 lines
214 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="Build Power. Not Rent It. Own your digital infrastructure.">
<meta name="author" content="Bunker Operations">
<link rel="canonical" href="https://bnkserve.org/v2/features/pages/page-builder/">
<link rel="prev" href="../../landing-pages/">
<link rel="next" href="../grapes-editor/">
<link rel="icon" href="../../../../assets/favicon.png">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.7.1">
<title>Page Builder - Changemaker Lite</title>
<link rel="stylesheet" href="../../../../assets/stylesheets/main.484c7ddc.min.css">
<link rel="stylesheet" href="../../../../assets/stylesheets/palette.ab4e12ef.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:300,300i,400,400i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Inter";--md-code-font:"JetBrains Mono"}</style>
<link rel="stylesheet" href="../../../../stylesheets/extra.css">
<link rel="stylesheet" href="../../../../stylesheets/home.css">
<link rel="stylesheet" href="../../../../assets/css/video-player.css">
<script>__md_scope=new URL("../../../..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
<meta property="og:type" content="website" />
<meta property="og:title" content="Page Builder - Changemaker Lite" />
<meta property="og:description" content="Build Power. Not Rent It. Own your digital infrastructure." />
<meta property="og:image" content="https://bnkserve.org/assets/images/social/v2/features/pages/page-builder.png" />
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:url" content="https://bnkserve.org/v2/features/pages/page-builder/" />
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:title" content="Page Builder - Changemaker Lite" />
<meta property="twitter:description" content="Build Power. Not Rent It. Own your digital infrastructure." />
<meta property="twitter:image" content="https://bnkserve.org/assets/images/social/v2/features/pages/page-builder.png" />
</head>
<body dir="ltr" data-md-color-scheme="slate" data-md-color-primary="deep-purple" data-md-color-accent="amber">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#page-builder" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header md-header--shadow md-header--lifted" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href="../../../.." title="Changemaker Lite" class="md-header__button md-logo" aria-label="Changemaker Lite" data-md-component="logo">
<img src="../../../../assets/logo.png" alt="logo">
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
Changemaker Lite
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
Page Builder
</span>
</div>
</div>
</div>
<form class="md-header__option" data-md-component="palette">
<input class="md-option" data-md-color-media="" data-md-color-scheme="slate" data-md-color-primary="deep-purple" data-md-color-accent="amber" aria-label="Switch to light mode" type="radio" name="__palette" id="__palette_0">
<label class="md-header__button md-icon" title="Switch to light mode" for="__palette_1" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="m17.75 4.09-2.53 1.94.91 3.06-2.63-1.81-2.63 1.81.91-3.06-2.53-1.94L12.44 4l1.06-3 1.06 3zm3.5 6.91-1.64 1.25.59 1.98-1.7-1.17-1.7 1.17.59-1.98L15.75 11l2.06-.05L18.5 9l.69 1.95zm-2.28 4.95c.83-.08 1.72 1.1 1.19 1.85-.32.45-.66.87-1.08 1.27C15.17 23 8.84 23 4.94 19.07c-3.91-3.9-3.91-10.24 0-14.14.4-.4.82-.76 1.27-1.08.75-.53 1.93.36 1.85 1.19-.27 2.86.69 5.83 2.89 8.02a9.96 9.96 0 0 0 8.02 2.89m-1.64 2.02a12.08 12.08 0 0 1-7.8-3.47c-2.17-2.19-3.33-5-3.49-7.82-2.81 3.14-2.7 7.96.31 10.98 3.02 3.01 7.84 3.12 10.98.31"/></svg>
</label>
<input class="md-option" data-md-color-media="" data-md-color-scheme="default" data-md-color-primary="deep-purple" data-md-color-accent="amber" aria-label="Switch to dark mode" type="radio" name="__palette" id="__palette_1">
<label class="md-header__button md-icon" title="Switch to dark mode" for="__palette_0" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 7a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5m0 2a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3m0-7 2.39 3.42C13.65 5.15 12.84 5 12 5s-1.65.15-2.39.42zM3.34 7l4.16-.35A7.2 7.2 0 0 0 5.94 8.5c-.44.74-.69 1.5-.83 2.29zm.02 10 1.76-3.77a7.131 7.131 0 0 0 2.38 4.14zM20.65 7l-1.77 3.79a7.02 7.02 0 0 0-2.38-4.15zm-.01 10-4.14.36c.59-.51 1.12-1.14 1.54-1.86.42-.73.69-1.5.83-2.29zM12 22l-2.41-3.44c.74.27 1.55.44 2.41.44.82 0 1.63-.17 2.37-.44z"/></svg>
</label>
</form>
<script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<a href="javascript:void(0)" class="md-search__icon md-icon" title="Share" aria-label="Share" data-clipboard data-clipboard-text="" data-md-component="search-share" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9a3 3 0 0 0-3 3 3 3 0 0 0 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.15c-.05.21-.08.43-.08.66 0 1.61 1.31 2.91 2.92 2.91s2.92-1.3 2.92-2.91A2.92 2.92 0 0 0 18 16.08"/></svg>
</a>
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
</nav>
<div class="md-search__suggest" data-md-component="search-suggest"></div>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
<div class="md-header__source">
<a href="https://gitea.bnkops.com/admin/changemaker.lite" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
changemaker.lite
</div>
</a>
</div>
</nav>
<nav class="md-tabs" aria-label="Tabs" data-md-component="tabs">
<div class="md-grid">
<ul class="md-tabs__list">
<li class="md-tabs__item">
<a href="../../../.." class="md-tabs__link">
Home
</a>
</li>
<li class="md-tabs__item md-tabs__item--active">
<a href="../../../" class="md-tabs__link">
V2 Documentation
</a>
</li>
<li class="md-tabs__item">
<a href="../../../../phil/" class="md-tabs__link">
Philosophy
</a>
</li>
<li class="md-tabs__item">
<a href="../../../../v1/" class="md-tabs__link">
V1 Documentation (Legacy)
</a>
</li>
<li class="md-tabs__item">
<a href="../../../../blog/" class="md-tabs__link">
Blog
</a>
</li>
</ul>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary md-nav--lifted" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href="../../../.." title="Changemaker Lite" class="md-nav__button md-logo" aria-label="Changemaker Lite" data-md-component="logo">
<img src="../../../../assets/logo.png" alt="logo">
</a>
Changemaker Lite
</label>
<div class="md-nav__source">
<a href="https://gitea.bnkops.com/admin/changemaker.lite" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
changemaker.lite
</div>
</a>
</div>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../../.." class="md-nav__link">
<span class="md-ellipsis">
Home
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--active md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_2" checked>
<div class="md-nav__link md-nav__container">
<a href="../../../" class="md-nav__link ">
<span class="md-ellipsis">
V2 Documentation
</span>
</a>
<label class="md-nav__link " for="__nav_2" id="__nav_2_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_2_label" aria-expanded="true">
<label class="md-nav__title" for="__nav_2">
<span class="md-nav__icon md-icon"></span>
V2 Documentation
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_2" >
<div class="md-nav__link md-nav__container">
<a href="../../../getting-started/" class="md-nav__link ">
<span class="md-ellipsis">
Getting Started
</span>
</a>
<label class="md-nav__link " for="__nav_2_2" id="__nav_2_2_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_2_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_2">
<span class="md-nav__icon md-icon"></span>
Getting Started
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../../getting-started/quick-start/" class="md-nav__link">
<span class="md-ellipsis">
Quick Start
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_3" >
<div class="md-nav__link md-nav__container">
<a href="../../../architecture/" class="md-nav__link ">
<span class="md-ellipsis">
Architecture
</span>
</a>
<label class="md-nav__link " for="__nav_2_3" id="__nav_2_3_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_3_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_3">
<span class="md-nav__icon md-icon"></span>
Architecture
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../../architecture/dual-api/" class="md-nav__link">
<span class="md-ellipsis">
Dual API System
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../architecture/authentication/" class="md-nav__link">
<span class="md-ellipsis">
Authentication & Security
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_4" >
<div class="md-nav__link md-nav__container">
<a href="../../../backend/" class="md-nav__link ">
<span class="md-ellipsis">
Backend
</span>
</a>
<label class="md-nav__link " for="__nav_2_4" id="__nav_2_4_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_4_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_4">
<span class="md-nav__icon md-icon"></span>
Backend
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../backend/modules/" class="md-nav__link">
<span class="md-ellipsis">
Modules
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../backend/services/" class="md-nav__link">
<span class="md-ellipsis">
Services
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../backend/middleware/" class="md-nav__link">
<span class="md-ellipsis">
Middleware
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../backend/utilities/" class="md-nav__link">
<span class="md-ellipsis">
Utilities
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_5" >
<div class="md-nav__link md-nav__container">
<a href="../../../frontend/" class="md-nav__link ">
<span class="md-ellipsis">
Frontend
</span>
</a>
<label class="md-nav__link " for="__nav_2_5" id="__nav_2_5_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_5_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_5">
<span class="md-nav__icon md-icon"></span>
Frontend
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../frontend/components/" class="md-nav__link">
<span class="md-ellipsis">
Components
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../frontend/layouts/" class="md-nav__link">
<span class="md-ellipsis">
Layouts
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../frontend/pages/" class="md-nav__link">
<span class="md-ellipsis">
Pages
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_6" >
<div class="md-nav__link md-nav__container">
<a href="../../../database/" class="md-nav__link ">
<span class="md-ellipsis">
Database
</span>
</a>
<label class="md-nav__link " for="__nav_2_6" id="__nav_2_6_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_6_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_6">
<span class="md-nav__icon md-icon"></span>
Database
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../../database/schema/" class="md-nav__link">
<span class="md-ellipsis">
Schema Overview
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../database/migrations/" class="md-nav__link">
<span class="md-ellipsis">
Migrations
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../database/seeding/" class="md-nav__link">
<span class="md-ellipsis">
Seeding
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../database/indexes/" class="md-nav__link">
<span class="md-ellipsis">
Indexes
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../database/models/" class="md-nav__link">
<span class="md-ellipsis">
Models
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--active md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_2_7" checked>
<div class="md-nav__link md-nav__container">
<a href="../../" class="md-nav__link ">
<span class="md-ellipsis">
Features
</span>
</a>
<label class="md-nav__link " for="__nav_2_7" id="__nav_2_7_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_7_label" aria-expanded="true">
<label class="md-nav__title" for="__nav_2_7">
<span class="md-nav__icon md-icon"></span>
Features
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../influence/" class="md-nav__link">
<span class="md-ellipsis">
Influence
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../map/" class="md-nav__link">
<span class="md-ellipsis">
Map
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--active md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_2_7_4" checked>
<div class="md-nav__link md-nav__container">
<a href="../../landing-pages/" class="md-nav__link ">
<span class="md-ellipsis">
Landing Pages
</span>
</a>
<label class="md-nav__link " for="__nav_2_7_4" id="__nav_2_7_4_label" tabindex="0">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="3" aria-labelledby="__nav_2_7_4_label" aria-expanded="true">
<label class="md-nav__title" for="__nav_2_7_4">
<span class="md-nav__icon md-icon"></span>
Landing Pages
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Page Builder
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<span class="md-ellipsis">
Page Builder
</span>
</a>
<nav class="md-nav md-nav--secondary" aria-label="On this page">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
On this page
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#overview" class="md-nav__link">
<span class="md-ellipsis">
Overview
</span>
</a>
<nav class="md-nav" aria-label="Overview">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#key-features" class="md-nav__link">
<span class="md-ellipsis">
Key Features
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#architecture-overview" class="md-nav__link">
<span class="md-ellipsis">
Architecture Overview
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#database-models" class="md-nav__link">
<span class="md-ellipsis">
Database Models
</span>
</a>
<nav class="md-nav" aria-label="Database Models">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#landingpage" class="md-nav__link">
<span class="md-ellipsis">
LandingPage
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#pageblock" class="md-nav__link">
<span class="md-ellipsis">
PageBlock
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#api-endpoints" class="md-nav__link">
<span class="md-ellipsis">
API Endpoints
</span>
</a>
<nav class="md-nav" aria-label="API Endpoints">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#admin-routes" class="md-nav__link">
<span class="md-ellipsis">
Admin Routes
</span>
</a>
<nav class="md-nav" aria-label="Admin Routes">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#list-pages" class="md-nav__link">
<span class="md-ellipsis">
List Pages
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#get-page" class="md-nav__link">
<span class="md-ellipsis">
Get Page
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#create-page" class="md-nav__link">
<span class="md-ellipsis">
Create Page
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#update-page" class="md-nav__link">
<span class="md-ellipsis">
Update Page
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#delete-page" class="md-nav__link">
<span class="md-ellipsis">
Delete Page
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#sync-overrides" class="md-nav__link">
<span class="md-ellipsis">
Sync Overrides
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#validate-exports" class="md-nav__link">
<span class="md-ellipsis">
Validate Exports
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#public-routes" class="md-nav__link">
<span class="md-ellipsis">
Public Routes
</span>
</a>
<nav class="md-nav" aria-label="Public Routes">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#view-published-page" class="md-nav__link">
<span class="md-ellipsis">
View Published Page
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#configuration" class="md-nav__link">
<span class="md-ellipsis">
Configuration
</span>
</a>
<nav class="md-nav" aria-label="Configuration">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#environment-variables" class="md-nav__link">
<span class="md-ellipsis">
Environment Variables
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#site-settings" class="md-nav__link">
<span class="md-ellipsis">
Site Settings
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#admin-workflow" class="md-nav__link">
<span class="md-ellipsis">
Admin Workflow
</span>
</a>
<nav class="md-nav" aria-label="Admin Workflow">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#creating-a-page" class="md-nav__link">
<span class="md-ellipsis">
Creating a Page
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#visual-editing-visual-mode" class="md-nav__link">
<span class="md-ellipsis">
Visual Editing (VISUAL Mode)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#code-editing-code-mode" class="md-nav__link">
<span class="md-ellipsis">
Code Editing (CODE Mode)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#publishing-a-page" class="md-nav__link">
<span class="md-ellipsis">
Publishing a Page
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#configuring-seo" class="md-nav__link">
<span class="md-ellipsis">
Configuring SEO
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#mkdocs-integration-settings" class="md-nav__link">
<span class="md-ellipsis">
MkDocs Integration Settings
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#syncing-overrides" class="md-nav__link">
<span class="md-ellipsis">
Syncing Overrides
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#validating-exports" class="md-nav__link">
<span class="md-ellipsis">
Validating Exports
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#public-workflow" class="md-nav__link">
<span class="md-ellipsis">
Public Workflow
</span>
</a>
<nav class="md-nav" aria-label="Public Workflow">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#viewing-a-published-page" class="md-nav__link">
<span class="md-ellipsis">
Viewing a Published Page
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#seo-meta-tags" class="md-nav__link">
<span class="md-ellipsis">
SEO Meta Tags
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#video-embedding" class="md-nav__link">
<span class="md-ellipsis">
Video Embedding
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#code-examples" class="md-nav__link">
<span class="md-ellipsis">
Code Examples
</span>
</a>
<nav class="md-nav" aria-label="Code Examples">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#creating-a-page-typescript" class="md-nav__link">
<span class="md-ellipsis">
Creating a Page (TypeScript)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#saving-editor-state-grapesjs" class="md-nav__link">
<span class="md-ellipsis">
Saving Editor State (GrapesJS)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#fetching-published-page-public-route" class="md-nav__link">
<span class="md-ellipsis">
Fetching Published Page (Public Route)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#mkdocs-export-logic-backend" class="md-nav__link">
<span class="md-ellipsis">
MkDocs Export Logic (Backend)
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#troubleshooting" class="md-nav__link">
<span class="md-ellipsis">
Troubleshooting
</span>
</a>
<nav class="md-nav" aria-label="Troubleshooting">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#problem-grapesjs-editor-not-loading" class="md-nav__link">
<span class="md-ellipsis">
Problem: GrapesJS Editor Not Loading
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#problem-published-page-not-rendering" class="md-nav__link">
<span class="md-ellipsis">
Problem: Published Page Not Rendering
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#problem-mobile-warning-shows-on-desktop" class="md-nav__link">
<span class="md-ellipsis">
Problem: Mobile Warning Shows on Desktop
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#problem-mkdocs-export-not-found" class="md-nav__link">
<span class="md-ellipsis">
Problem: MkDocs Export Not Found
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#problem-slug-collision-on-create" class="md-nav__link">
<span class="md-ellipsis">
Problem: Slug Collision on Create
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#problem-video-block-not-hydrating" class="md-nav__link">
<span class="md-ellipsis">
Problem: Video Block Not Hydrating
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#performance-considerations" class="md-nav__link">
<span class="md-ellipsis">
Performance Considerations
</span>
</a>
<nav class="md-nav" aria-label="Performance Considerations">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#editor-initialization" class="md-nav__link">
<span class="md-ellipsis">
Editor Initialization
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#large-pages" class="md-nav__link">
<span class="md-ellipsis">
Large Pages
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#htmloutput-storage" class="md-nav__link">
<span class="md-ellipsis">
htmlOutput Storage
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#public-page-rendering" class="md-nav__link">
<span class="md-ellipsis">
Public Page Rendering
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#security-considerations" class="md-nav__link">
<span class="md-ellipsis">
Security Considerations
</span>
</a>
<nav class="md-nav" aria-label="Security Considerations">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#admin-authored-html" class="md-nav__link">
<span class="md-ellipsis">
Admin-Authored HTML
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#slug-validation" class="md-nav__link">
<span class="md-ellipsis">
Slug Validation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#mkdocs-path-validation" class="md-nav__link">
<span class="md-ellipsis">
MkDocs Path Validation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#published-flag-enforcement" class="md-nav__link">
<span class="md-ellipsis">
Published Flag Enforcement
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#related-documentation" class="md-nav__link">
<span class="md-ellipsis">
Related Documentation
</span>
</a>
<nav class="md-nav" aria-label="Related Documentation">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#frontend-components" class="md-nav__link">
<span class="md-ellipsis">
Frontend Components
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#backend-modules" class="md-nav__link">
<span class="md-ellipsis">
Backend Modules
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#database" class="md-nav__link">
<span class="md-ellipsis">
Database
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#feature-documentation" class="md-nav__link">
<span class="md-ellipsis">
Feature Documentation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#external-resources" class="md-nav__link">
<span class="md-ellipsis">
External Resources
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../grapes-editor/" class="md-nav__link">
<span class="md-ellipsis">
GrapesJS Editor
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../block-library/" class="md-nav__link">
<span class="md-ellipsis">
Block Library
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../mkdocs-export/" class="md-nav__link">
<span class="md-ellipsis">
MkDocs Export
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../email-templates/" class="md-nav__link">
<span class="md-ellipsis">
Email Templates
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../media/" class="md-nav__link">
<span class="md-ellipsis">
Media
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../newsletter/" class="md-nav__link">
<span class="md-ellipsis">
Newsletter
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../observability/" class="md-nav__link">
<span class="md-ellipsis">
Observability
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../tunnel/" class="md-nav__link">
<span class="md-ellipsis">
Tunnel
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_8" >
<div class="md-nav__link md-nav__container">
<a href="../../../deployment/" class="md-nav__link ">
<span class="md-ellipsis">
Deployment
</span>
</a>
<label class="md-nav__link " for="__nav_2_8" id="__nav_2_8_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_8_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_8">
<span class="md-nav__icon md-icon"></span>
Deployment
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../../deployment/docker-compose/" class="md-nav__link">
<span class="md-ellipsis">
Docker Compose
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../deployment/environment-variables/" class="md-nav__link">
<span class="md-ellipsis">
Environment Variables
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../deployment/nginx/" class="md-nav__link">
<span class="md-ellipsis">
Nginx Configuration
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../deployment/ssl-tls/" class="md-nav__link">
<span class="md-ellipsis">
SSL/TLS
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../deployment/tunneling/" class="md-nav__link">
<span class="md-ellipsis">
Tunneling
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../deployment/monitoring-stack/" class="md-nav__link">
<span class="md-ellipsis">
Monitoring Stack
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../deployment/healthchecks/" class="md-nav__link">
<span class="md-ellipsis">
Health Checks
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../deployment/scaling/" class="md-nav__link">
<span class="md-ellipsis">
Scaling
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../deployment/backup-restore/" class="md-nav__link">
<span class="md-ellipsis">
Backup & Restore
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_9" >
<div class="md-nav__link md-nav__container">
<a href="../../../development/" class="md-nav__link ">
<span class="md-ellipsis">
Development
</span>
</a>
<label class="md-nav__link " for="__nav_2_9" id="__nav_2_9_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_9_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_9">
<span class="md-nav__icon md-icon"></span>
Development
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../../development/local-setup/" class="md-nav__link">
<span class="md-ellipsis">
Local Setup
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../development/docker-workflow/" class="md-nav__link">
<span class="md-ellipsis">
Docker Workflow
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../development/git-workflow/" class="md-nav__link">
<span class="md-ellipsis">
Git Workflow
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../development/npm-commands/" class="md-nav__link">
<span class="md-ellipsis">
NPM Commands
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../development/migrations/" class="md-nav__link">
<span class="md-ellipsis">
Migrations
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../development/typescript/" class="md-nav__link">
<span class="md-ellipsis">
TypeScript
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../development/testing/" class="md-nav__link">
<span class="md-ellipsis">
Testing
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../development/debugging/" class="md-nav__link">
<span class="md-ellipsis">
Debugging
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../development/code-style/" class="md-nav__link">
<span class="md-ellipsis">
Code Style
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_10" >
<div class="md-nav__link md-nav__container">
<a href="../../../api-reference/" class="md-nav__link ">
<span class="md-ellipsis">
API Reference
</span>
</a>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_10_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_10">
<span class="md-nav__icon md-icon"></span>
API Reference
</label>
<ul class="md-nav__list" data-md-scrollfix>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_11" >
<div class="md-nav__link md-nav__container">
<a href="../../../user-guides/" class="md-nav__link ">
<span class="md-ellipsis">
User Guides
</span>
</a>
<label class="md-nav__link " for="__nav_2_11" id="__nav_2_11_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_11_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_11">
<span class="md-nav__icon md-icon"></span>
User Guides
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../../user-guides/admin-guide/" class="md-nav__link">
<span class="md-ellipsis">
Admin Guide
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../user-guides/campaign-manager-guide/" class="md-nav__link">
<span class="md-ellipsis">
Campaign Manager Guide
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../user-guides/map-organizer-guide/" class="md-nav__link">
<span class="md-ellipsis">
Map Organizer Guide
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../user-guides/content-editor-guide/" class="md-nav__link">
<span class="md-ellipsis">
Content Editor Guide
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../user-guides/volunteer-guide/" class="md-nav__link">
<span class="md-ellipsis">
Volunteer Guide
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_12" >
<div class="md-nav__link md-nav__container">
<a href="../../../troubleshooting/" class="md-nav__link ">
<span class="md-ellipsis">
Troubleshooting
</span>
</a>
<label class="md-nav__link " for="__nav_2_12" id="__nav_2_12_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_12_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_12">
<span class="md-nav__icon md-icon"></span>
Troubleshooting
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../../troubleshooting/faq/" class="md-nav__link">
<span class="md-ellipsis">
FAQ
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../troubleshooting/common-errors/" class="md-nav__link">
<span class="md-ellipsis">
Common Errors
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../troubleshooting/auth-issues/" class="md-nav__link">
<span class="md-ellipsis">
Auth Issues
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../troubleshooting/database-issues/" class="md-nav__link">
<span class="md-ellipsis">
Database Issues
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../troubleshooting/docker-issues/" class="md-nav__link">
<span class="md-ellipsis">
Docker Issues
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../troubleshooting/email-issues/" class="md-nav__link">
<span class="md-ellipsis">
Email Issues
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../troubleshooting/geocoding-issues/" class="md-nav__link">
<span class="md-ellipsis">
Geocoding Issues
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../troubleshooting/monitoring-issues/" class="md-nav__link">
<span class="md-ellipsis">
Monitoring Issues
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../troubleshooting/performance-optimization/" class="md-nav__link">
<span class="md-ellipsis">
Performance Optimization
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_13" >
<div class="md-nav__link md-nav__container">
<a href="../../../migration/" class="md-nav__link ">
<span class="md-ellipsis">
Migration
</span>
</a>
<label class="md-nav__link " for="__nav_2_13" id="__nav_2_13_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_13_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_13">
<span class="md-nav__icon md-icon"></span>
Migration
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../../migration/feature-parity/" class="md-nav__link">
<span class="md-ellipsis">
Feature Parity
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../migration/breaking-changes/" class="md-nav__link">
<span class="md-ellipsis">
Breaking Changes
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../migration/api-changes/" class="md-nav__link">
<span class="md-ellipsis">
API Changes
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../migration/data-migration/" class="md-nav__link">
<span class="md-ellipsis">
Data Migration
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_14" >
<div class="md-nav__link md-nav__container">
<a href="../../../contributing/" class="md-nav__link ">
<span class="md-ellipsis">
Contributing
</span>
</a>
<label class="md-nav__link " for="__nav_2_14" id="__nav_2_14_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_14_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_14">
<span class="md-nav__icon md-icon"></span>
Contributing
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../../contributing/development-setup/" class="md-nav__link">
<span class="md-ellipsis">
Development Setup
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../contributing/code-of-conduct/" class="md-nav__link">
<span class="md-ellipsis">
Code of Conduct
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../contributing/pull-requests/" class="md-nav__link">
<span class="md-ellipsis">
Pull Requests
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../contributing/roadmap/" class="md-nav__link">
<span class="md-ellipsis">
Roadmap
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../../phil/" class="md-nav__link">
<span class="md-ellipsis">
Philosophy
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../../v1/" class="md-nav__link">
<span class="md-ellipsis">
V1 Documentation (Legacy)
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../../blog/" class="md-nav__link">
<span class="md-ellipsis">
Blog
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="On this page">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
On this page
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#overview" class="md-nav__link">
<span class="md-ellipsis">
Overview
</span>
</a>
<nav class="md-nav" aria-label="Overview">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#key-features" class="md-nav__link">
<span class="md-ellipsis">
Key Features
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#architecture-overview" class="md-nav__link">
<span class="md-ellipsis">
Architecture Overview
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#database-models" class="md-nav__link">
<span class="md-ellipsis">
Database Models
</span>
</a>
<nav class="md-nav" aria-label="Database Models">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#landingpage" class="md-nav__link">
<span class="md-ellipsis">
LandingPage
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#pageblock" class="md-nav__link">
<span class="md-ellipsis">
PageBlock
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#api-endpoints" class="md-nav__link">
<span class="md-ellipsis">
API Endpoints
</span>
</a>
<nav class="md-nav" aria-label="API Endpoints">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#admin-routes" class="md-nav__link">
<span class="md-ellipsis">
Admin Routes
</span>
</a>
<nav class="md-nav" aria-label="Admin Routes">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#list-pages" class="md-nav__link">
<span class="md-ellipsis">
List Pages
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#get-page" class="md-nav__link">
<span class="md-ellipsis">
Get Page
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#create-page" class="md-nav__link">
<span class="md-ellipsis">
Create Page
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#update-page" class="md-nav__link">
<span class="md-ellipsis">
Update Page
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#delete-page" class="md-nav__link">
<span class="md-ellipsis">
Delete Page
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#sync-overrides" class="md-nav__link">
<span class="md-ellipsis">
Sync Overrides
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#validate-exports" class="md-nav__link">
<span class="md-ellipsis">
Validate Exports
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#public-routes" class="md-nav__link">
<span class="md-ellipsis">
Public Routes
</span>
</a>
<nav class="md-nav" aria-label="Public Routes">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#view-published-page" class="md-nav__link">
<span class="md-ellipsis">
View Published Page
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#configuration" class="md-nav__link">
<span class="md-ellipsis">
Configuration
</span>
</a>
<nav class="md-nav" aria-label="Configuration">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#environment-variables" class="md-nav__link">
<span class="md-ellipsis">
Environment Variables
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#site-settings" class="md-nav__link">
<span class="md-ellipsis">
Site Settings
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#admin-workflow" class="md-nav__link">
<span class="md-ellipsis">
Admin Workflow
</span>
</a>
<nav class="md-nav" aria-label="Admin Workflow">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#creating-a-page" class="md-nav__link">
<span class="md-ellipsis">
Creating a Page
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#visual-editing-visual-mode" class="md-nav__link">
<span class="md-ellipsis">
Visual Editing (VISUAL Mode)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#code-editing-code-mode" class="md-nav__link">
<span class="md-ellipsis">
Code Editing (CODE Mode)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#publishing-a-page" class="md-nav__link">
<span class="md-ellipsis">
Publishing a Page
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#configuring-seo" class="md-nav__link">
<span class="md-ellipsis">
Configuring SEO
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#mkdocs-integration-settings" class="md-nav__link">
<span class="md-ellipsis">
MkDocs Integration Settings
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#syncing-overrides" class="md-nav__link">
<span class="md-ellipsis">
Syncing Overrides
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#validating-exports" class="md-nav__link">
<span class="md-ellipsis">
Validating Exports
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#public-workflow" class="md-nav__link">
<span class="md-ellipsis">
Public Workflow
</span>
</a>
<nav class="md-nav" aria-label="Public Workflow">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#viewing-a-published-page" class="md-nav__link">
<span class="md-ellipsis">
Viewing a Published Page
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#seo-meta-tags" class="md-nav__link">
<span class="md-ellipsis">
SEO Meta Tags
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#video-embedding" class="md-nav__link">
<span class="md-ellipsis">
Video Embedding
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#code-examples" class="md-nav__link">
<span class="md-ellipsis">
Code Examples
</span>
</a>
<nav class="md-nav" aria-label="Code Examples">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#creating-a-page-typescript" class="md-nav__link">
<span class="md-ellipsis">
Creating a Page (TypeScript)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#saving-editor-state-grapesjs" class="md-nav__link">
<span class="md-ellipsis">
Saving Editor State (GrapesJS)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#fetching-published-page-public-route" class="md-nav__link">
<span class="md-ellipsis">
Fetching Published Page (Public Route)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#mkdocs-export-logic-backend" class="md-nav__link">
<span class="md-ellipsis">
MkDocs Export Logic (Backend)
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#troubleshooting" class="md-nav__link">
<span class="md-ellipsis">
Troubleshooting
</span>
</a>
<nav class="md-nav" aria-label="Troubleshooting">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#problem-grapesjs-editor-not-loading" class="md-nav__link">
<span class="md-ellipsis">
Problem: GrapesJS Editor Not Loading
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#problem-published-page-not-rendering" class="md-nav__link">
<span class="md-ellipsis">
Problem: Published Page Not Rendering
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#problem-mobile-warning-shows-on-desktop" class="md-nav__link">
<span class="md-ellipsis">
Problem: Mobile Warning Shows on Desktop
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#problem-mkdocs-export-not-found" class="md-nav__link">
<span class="md-ellipsis">
Problem: MkDocs Export Not Found
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#problem-slug-collision-on-create" class="md-nav__link">
<span class="md-ellipsis">
Problem: Slug Collision on Create
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#problem-video-block-not-hydrating" class="md-nav__link">
<span class="md-ellipsis">
Problem: Video Block Not Hydrating
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#performance-considerations" class="md-nav__link">
<span class="md-ellipsis">
Performance Considerations
</span>
</a>
<nav class="md-nav" aria-label="Performance Considerations">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#editor-initialization" class="md-nav__link">
<span class="md-ellipsis">
Editor Initialization
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#large-pages" class="md-nav__link">
<span class="md-ellipsis">
Large Pages
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#htmloutput-storage" class="md-nav__link">
<span class="md-ellipsis">
htmlOutput Storage
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#public-page-rendering" class="md-nav__link">
<span class="md-ellipsis">
Public Page Rendering
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#security-considerations" class="md-nav__link">
<span class="md-ellipsis">
Security Considerations
</span>
</a>
<nav class="md-nav" aria-label="Security Considerations">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#admin-authored-html" class="md-nav__link">
<span class="md-ellipsis">
Admin-Authored HTML
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#slug-validation" class="md-nav__link">
<span class="md-ellipsis">
Slug Validation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#mkdocs-path-validation" class="md-nav__link">
<span class="md-ellipsis">
MkDocs Path Validation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#published-flag-enforcement" class="md-nav__link">
<span class="md-ellipsis">
Published Flag Enforcement
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#related-documentation" class="md-nav__link">
<span class="md-ellipsis">
Related Documentation
</span>
</a>
<nav class="md-nav" aria-label="Related Documentation">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#frontend-components" class="md-nav__link">
<span class="md-ellipsis">
Frontend Components
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#backend-modules" class="md-nav__link">
<span class="md-ellipsis">
Backend Modules
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#database" class="md-nav__link">
<span class="md-ellipsis">
Database
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#feature-documentation" class="md-nav__link">
<span class="md-ellipsis">
Feature Documentation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#external-resources" class="md-nav__link">
<span class="md-ellipsis">
External Resources
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<nav class="md-path" aria-label="Navigation" >
<ol class="md-path__list">
<li class="md-path__item">
<a href="../../../.." class="md-path__link">
<span class="md-ellipsis">
Home
</span>
</a>
</li>
<li class="md-path__item">
<a href="../../../" class="md-path__link">
<span class="md-ellipsis">
V2 Documentation
</span>
</a>
</li>
<li class="md-path__item">
<a href="../../" class="md-path__link">
<span class="md-ellipsis">
Features
</span>
</a>
</li>
<li class="md-path__item">
<a href="../../landing-pages/" class="md-path__link">
<span class="md-ellipsis">
Landing Pages
</span>
</a>
</li>
</ol>
</nav>
<article class="md-content__inner md-typeset">
<a href="https://gitea.bnkops.com/admin/changemaker.lite/src/branch/main/mkdocs/docs/v2/features/pages/page-builder.md" title="Edit this page" class="md-content__button md-icon" rel="edit">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M10 20H6V4h7v5h5v3.1l2-2V8l-6-6H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h4zm10.2-7c.1 0 .3.1.4.2l1.3 1.3c.2.2.2.6 0 .8l-1 1-2.1-2.1 1-1c.1-.1.2-.2.4-.2m0 3.9L14.1 23H12v-2.1l6.1-6.1z"/></svg>
</a>
<a href="https://gitea.bnkops.com/admin/changemaker.lite/src/branch/main/mkdocs/docs/v2/features/pages/page-builder.md" title="View source of this page" class="md-content__button md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M17 18c.56 0 1 .44 1 1s-.44 1-1 1-1-.44-1-1 .44-1 1-1m0-3c-2.73 0-5.06 1.66-6 4 .94 2.34 3.27 4 6 4s5.06-1.66 6-4c-.94-2.34-3.27-4-6-4m0 6.5a2.5 2.5 0 0 1-2.5-2.5 2.5 2.5 0 0 1 2.5-2.5 2.5 2.5 0 0 1 2.5 2.5 2.5 2.5 0 0 1-2.5 2.5M9.27 20H6V4h7v5h5v4.07c.7.08 1.36.25 2 .49V8l-6-6H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h4.5a8.2 8.2 0 0 1-1.23-2"/></svg>
</a>
<h1 id="page-builder">Page Builder<a class="headerlink" href="#page-builder" title="Permanent link">&para;</a></h1>
<p>Complete WYSIWYG landing page builder with GrapesJS editor, slug-based public routing, and MkDocs Material theme integration.</p>
<hr />
<h2 id="overview">Overview<a class="headerlink" href="#overview" title="Permanent link">&para;</a></h2>
<p>The Page Builder system provides a comprehensive solution for creating custom landing pages without coding. Administrators can use a visual drag-and-drop interface or write raw HTML/CSS directly.</p>
<h3 id="key-features">Key Features<a class="headerlink" href="#key-features" title="Permanent link">&para;</a></h3>
<ul>
<li><strong>Dual-Mode Editing</strong>: Switch between VISUAL (GrapesJS drag-and-drop) and CODE (raw HTML editor)</li>
<li><strong>Slug-Based Routing</strong>: Public pages accessible at <code>/p/:slug</code> (e.g., <code>/p/about-us</code>)</li>
<li><strong>MkDocs Export</strong>: Publish pages to MkDocs documentation site with Material theme integration</li>
<li><strong>SEO Meta Tags</strong>: Configure title, description, and Open Graph images</li>
<li><strong>Custom Blocks</strong>: Reusable components (hero, features, CTA, testimonials, contact forms)</li>
<li><strong>Video Integration</strong>: Embed media library videos with standard or advanced players</li>
<li><strong>Mobile Detection</strong>: Editor warns users on small screens (desktop-only editing)</li>
</ul>
<h3 id="architecture-overview">Architecture Overview<a class="headerlink" href="#architecture-overview" title="Permanent link">&para;</a></h3>
<pre class="mermaid"><code>graph LR
A[Admin] --&gt; B[LandingPagesPage]
B --&gt; C[Create Page Modal]
C --&gt; D[LandingPageEditor]
D --&gt; E[GrapesJS Editor]
E --&gt; F[Save API]
F --&gt; G[(LandingPage Model)]
G --&gt; H[Public Route]
H --&gt; I[/p/:slug]
D --&gt; J[Publish Toggle]
J --&gt; K[MkDocs Export]
K --&gt; L[overrides/*.html]
K --&gt; M[docs/*.md stub]
style E fill:#9d4edd
style G fill:#3498db
style K fill:#2ecc71</code></pre>
<p><strong>Flow:</strong></p>
<ol>
<li>Admin creates page via LandingPagesPage</li>
<li>Editor loads with GrapesJS (VISUAL mode) or Monaco (CODE mode)</li>
<li>Admin drags blocks, configures properties, saves (Ctrl+S)</li>
<li>API stores <code>projectData</code> (GrapesJS JSON), <code>htmlOutput</code>, <code>cssOutput</code></li>
<li>On publish: API exports <code>.html</code> override + <code>.md</code> stub to MkDocs</li>
<li>Public users access page at <code>/p/:slug</code> (React route renders HTML)</li>
</ol>
<hr />
<h2 id="database-models">Database Models<a class="headerlink" href="#database-models" title="Permanent link">&para;</a></h2>
<h3 id="landingpage">LandingPage<a class="headerlink" href="#landingpage" title="Permanent link">&para;</a></h3>
<p><strong>Table:</strong> <code>landing_pages</code></p>
<p><strong>Key Fields:</strong></p>
<table>
<thead>
<tr>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>String (UUID)</td>
<td>Primary key</td>
</tr>
<tr>
<td><code>slug</code></td>
<td>String</td>
<td>Unique URL-safe identifier (auto-generated from title)</td>
</tr>
<tr>
<td><code>title</code></td>
<td>String</td>
<td>Page title (internal + fallback SEO)</td>
</tr>
<tr>
<td><code>description</code></td>
<td>String?</td>
<td>Page description (internal)</td>
</tr>
<tr>
<td><code>editorMode</code></td>
<td>Enum</td>
<td><code>VISUAL</code> (GrapesJS) or <code>CODE</code> (raw HTML)</td>
</tr>
<tr>
<td><code>blocks</code></td>
<td>JSON</td>
<td>GrapesJS <code>projectData</code> (components tree)</td>
</tr>
<tr>
<td><code>htmlOutput</code></td>
<td>String?</td>
<td>Rendered HTML (cached output from editor)</td>
</tr>
<tr>
<td><code>cssOutput</code></td>
<td>String?</td>
<td>Rendered CSS (cached output from editor)</td>
</tr>
<tr>
<td><code>mkdocsPath</code></td>
<td>String?</td>
<td>Override file path (e.g., <code>about.html</code>)</td>
</tr>
<tr>
<td><code>mkdocsStubPath</code></td>
<td>String?</td>
<td>Stub Markdown path (e.g., <code>about.md</code>)</td>
</tr>
<tr>
<td><code>mkdocsExportMode</code></td>
<td>Enum</td>
<td><code>THEMED</code> (extends main.html) or <code>STANDALONE</code> (full HTML)</td>
</tr>
<tr>
<td><code>mkdocsHideNav</code></td>
<td>Boolean</td>
<td>Hide navigation sidebar in MkDocs</td>
</tr>
<tr>
<td><code>mkdocsHideToc</code></td>
<td>Boolean</td>
<td>Hide table of contents in MkDocs</td>
</tr>
<tr>
<td><code>mkdocsSkipExport</code></td>
<td>Boolean</td>
<td>Don't export to MkDocs (only accessible via /p/:slug)</td>
</tr>
<tr>
<td><code>published</code></td>
<td>Boolean</td>
<td>Public visibility (false = draft)</td>
</tr>
<tr>
<td><code>seoTitle</code></td>
<td>String?</td>
<td>Custom SEO title (overrides <code>title</code>)</td>
</tr>
<tr>
<td><code>seoDescription</code></td>
<td>String?</td>
<td>Meta description for search engines</td>
</tr>
<tr>
<td><code>seoImage</code></td>
<td>String?</td>
<td>Open Graph image URL</td>
</tr>
<tr>
<td><code>createdAt</code></td>
<td>DateTime</td>
<td>Creation timestamp</td>
</tr>
<tr>
<td><code>updatedAt</code></td>
<td>DateTime</td>
<td>Last modification timestamp</td>
</tr>
</tbody>
</table>
<p><strong>Indexes:</strong></p>
<ul>
<li><code>slug</code> (unique)</li>
<li><code>published</code> (filter index)</li>
</ul>
<p><strong>Relationships:</strong></p>
<ul>
<li>None (standalone model)</li>
</ul>
<h3 id="pageblock">PageBlock<a class="headerlink" href="#pageblock" title="Permanent link">&para;</a></h3>
<p>See <a href="../block-library/">Block Library</a> documentation.</p>
<hr />
<h2 id="api-endpoints">API Endpoints<a class="headerlink" href="#api-endpoints" title="Permanent link">&para;</a></h2>
<h3 id="admin-routes">Admin Routes<a class="headerlink" href="#admin-routes" title="Permanent link">&para;</a></h3>
<p><strong>Prefix:</strong> <code>/api/pages</code></p>
<p><strong>Authentication:</strong> Requires admin role (SUPER_ADMIN, INFLUENCE_ADMIN, or MAP_ADMIN)</p>
<h4 id="list-pages">List Pages<a class="headerlink" href="#list-pages" title="Permanent link">&para;</a></h4>
<div class="language-http highlight"><pre><span></span><code><span id="__span-0-1"><a id="__codelineno-0-1" name="__codelineno-0-1" href="#__codelineno-0-1"></a><span class="err">GET /api/pages?page=1&amp;limit=20&amp;search=campaign&amp;published=true</span>
</span></code></pre></div>
<p><strong>Query Parameters:</strong></p>
<ul>
<li><code>page</code> (number, default: 1) — Page number</li>
<li><code>limit</code> (number, default: 20, max: 100) — Results per page</li>
<li><code>search</code> (string?) — Search title, description, or slug (case-insensitive)</li>
<li><code>published</code> (string?) — Filter by status: <code>"true"</code>, <code>"false"</code>, or omit for all</li>
</ul>
<p><strong>Response:</strong></p>
<div class="language-json highlight"><pre><span></span><code><span id="__span-1-1"><a id="__codelineno-1-1" name="__codelineno-1-1" href="#__codelineno-1-1"></a><span class="p">{</span>
</span><span id="__span-1-2"><a id="__codelineno-1-2" name="__codelineno-1-2" href="#__codelineno-1-2"></a><span class="w"> </span><span class="nt">&quot;pages&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
</span><span id="__span-1-3"><a id="__codelineno-1-3" name="__codelineno-1-3" href="#__codelineno-1-3"></a><span class="w"> </span><span class="p">{</span>
</span><span id="__span-1-4"><a id="__codelineno-1-4" name="__codelineno-1-4" href="#__codelineno-1-4"></a><span class="w"> </span><span class="nt">&quot;id&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;abc123&quot;</span><span class="p">,</span>
</span><span id="__span-1-5"><a id="__codelineno-1-5" name="__codelineno-1-5" href="#__codelineno-1-5"></a><span class="w"> </span><span class="nt">&quot;slug&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;about-us&quot;</span><span class="p">,</span>
</span><span id="__span-1-6"><a id="__codelineno-1-6" name="__codelineno-1-6" href="#__codelineno-1-6"></a><span class="w"> </span><span class="nt">&quot;title&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;About Our Campaign&quot;</span><span class="p">,</span>
</span><span id="__span-1-7"><a id="__codelineno-1-7" name="__codelineno-1-7" href="#__codelineno-1-7"></a><span class="w"> </span><span class="nt">&quot;description&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Learn more about our mission.&quot;</span><span class="p">,</span>
</span><span id="__span-1-8"><a id="__codelineno-1-8" name="__codelineno-1-8" href="#__codelineno-1-8"></a><span class="w"> </span><span class="nt">&quot;editorMode&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;VISUAL&quot;</span><span class="p">,</span>
</span><span id="__span-1-9"><a id="__codelineno-1-9" name="__codelineno-1-9" href="#__codelineno-1-9"></a><span class="w"> </span><span class="nt">&quot;blocks&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="cm">/* GrapesJS JSON */</span><span class="w"> </span><span class="p">},</span>
</span><span id="__span-1-10"><a id="__codelineno-1-10" name="__codelineno-1-10" href="#__codelineno-1-10"></a><span class="w"> </span><span class="nt">&quot;htmlOutput&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;&lt;section&gt;...&lt;/section&gt;&quot;</span><span class="p">,</span>
</span><span id="__span-1-11"><a id="__codelineno-1-11" name="__codelineno-1-11" href="#__codelineno-1-11"></a><span class="w"> </span><span class="nt">&quot;cssOutput&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;section { padding: 40px; }&quot;</span><span class="p">,</span>
</span><span id="__span-1-12"><a id="__codelineno-1-12" name="__codelineno-1-12" href="#__codelineno-1-12"></a><span class="w"> </span><span class="nt">&quot;mkdocsPath&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;about.html&quot;</span><span class="p">,</span>
</span><span id="__span-1-13"><a id="__codelineno-1-13" name="__codelineno-1-13" href="#__codelineno-1-13"></a><span class="w"> </span><span class="nt">&quot;mkdocsStubPath&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;about.md&quot;</span><span class="p">,</span>
</span><span id="__span-1-14"><a id="__codelineno-1-14" name="__codelineno-1-14" href="#__codelineno-1-14"></a><span class="w"> </span><span class="nt">&quot;mkdocsExportMode&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;THEMED&quot;</span><span class="p">,</span>
</span><span id="__span-1-15"><a id="__codelineno-1-15" name="__codelineno-1-15" href="#__codelineno-1-15"></a><span class="w"> </span><span class="nt">&quot;mkdocsHideNav&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span>
</span><span id="__span-1-16"><a id="__codelineno-1-16" name="__codelineno-1-16" href="#__codelineno-1-16"></a><span class="w"> </span><span class="nt">&quot;mkdocsHideToc&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
</span><span id="__span-1-17"><a id="__codelineno-1-17" name="__codelineno-1-17" href="#__codelineno-1-17"></a><span class="w"> </span><span class="nt">&quot;mkdocsSkipExport&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span>
</span><span id="__span-1-18"><a id="__codelineno-1-18" name="__codelineno-1-18" href="#__codelineno-1-18"></a><span class="w"> </span><span class="nt">&quot;published&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
</span><span id="__span-1-19"><a id="__codelineno-1-19" name="__codelineno-1-19" href="#__codelineno-1-19"></a><span class="w"> </span><span class="nt">&quot;seoTitle&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;About Us | Campaign 2026&quot;</span><span class="p">,</span>
</span><span id="__span-1-20"><a id="__codelineno-1-20" name="__codelineno-1-20" href="#__codelineno-1-20"></a><span class="w"> </span><span class="nt">&quot;seoDescription&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Join our movement for change.&quot;</span><span class="p">,</span>
</span><span id="__span-1-21"><a id="__codelineno-1-21" name="__codelineno-1-21" href="#__codelineno-1-21"></a><span class="w"> </span><span class="nt">&quot;seoImage&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;https://example.com/og-image.jpg&quot;</span><span class="p">,</span>
</span><span id="__span-1-22"><a id="__codelineno-1-22" name="__codelineno-1-22" href="#__codelineno-1-22"></a><span class="w"> </span><span class="nt">&quot;createdAt&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;2026-01-15T10:00:00Z&quot;</span><span class="p">,</span>
</span><span id="__span-1-23"><a id="__codelineno-1-23" name="__codelineno-1-23" href="#__codelineno-1-23"></a><span class="w"> </span><span class="nt">&quot;updatedAt&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;2026-02-13T14:30:00Z&quot;</span>
</span><span id="__span-1-24"><a id="__codelineno-1-24" name="__codelineno-1-24" href="#__codelineno-1-24"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-1-25"><a id="__codelineno-1-25" name="__codelineno-1-25" href="#__codelineno-1-25"></a><span class="w"> </span><span class="p">],</span>
</span><span id="__span-1-26"><a id="__codelineno-1-26" name="__codelineno-1-26" href="#__codelineno-1-26"></a><span class="w"> </span><span class="nt">&quot;pagination&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-1-27"><a id="__codelineno-1-27" name="__codelineno-1-27" href="#__codelineno-1-27"></a><span class="w"> </span><span class="nt">&quot;page&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span>
</span><span id="__span-1-28"><a id="__codelineno-1-28" name="__codelineno-1-28" href="#__codelineno-1-28"></a><span class="w"> </span><span class="nt">&quot;limit&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">20</span><span class="p">,</span>
</span><span id="__span-1-29"><a id="__codelineno-1-29" name="__codelineno-1-29" href="#__codelineno-1-29"></a><span class="w"> </span><span class="nt">&quot;total&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span>
</span><span id="__span-1-30"><a id="__codelineno-1-30" name="__codelineno-1-30" href="#__codelineno-1-30"></a><span class="w"> </span><span class="nt">&quot;totalPages&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span>
</span><span id="__span-1-31"><a id="__codelineno-1-31" name="__codelineno-1-31" href="#__codelineno-1-31"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-1-32"><a id="__codelineno-1-32" name="__codelineno-1-32" href="#__codelineno-1-32"></a><span class="p">}</span>
</span></code></pre></div>
<h4 id="get-page">Get Page<a class="headerlink" href="#get-page" title="Permanent link">&para;</a></h4>
<div class="language-http highlight"><pre><span></span><code><span id="__span-2-1"><a id="__codelineno-2-1" name="__codelineno-2-1" href="#__codelineno-2-1"></a><span class="err">GET /api/pages/:id</span>
</span></code></pre></div>
<p><strong>Response:</strong> Single <code>LandingPage</code> object (same structure as list item above)</p>
<p><strong>Errors:</strong></p>
<ul>
<li><code>404 PAGE_NOT_FOUND</code> — Page doesn't exist</li>
</ul>
<h4 id="create-page">Create Page<a class="headerlink" href="#create-page" title="Permanent link">&para;</a></h4>
<div class="language-http highlight"><pre><span></span><code><span id="__span-3-1"><a id="__codelineno-3-1" name="__codelineno-3-1" href="#__codelineno-3-1"></a><span class="err">POST /api/pages</span>
</span><span id="__span-3-2"><a id="__codelineno-3-2" name="__codelineno-3-2" href="#__codelineno-3-2"></a><span class="err">Content-Type: application/json</span>
</span><span id="__span-3-3"><a id="__codelineno-3-3" name="__codelineno-3-3" href="#__codelineno-3-3"></a>
</span><span id="__span-3-4"><a id="__codelineno-3-4" name="__codelineno-3-4" href="#__codelineno-3-4"></a><span class="err">{</span>
</span><span id="__span-3-5"><a id="__codelineno-3-5" name="__codelineno-3-5" href="#__codelineno-3-5"></a><span class="err"> &quot;title&quot;: &quot;New Landing Page&quot;,</span>
</span><span id="__span-3-6"><a id="__codelineno-3-6" name="__codelineno-3-6" href="#__codelineno-3-6"></a><span class="err"> &quot;description&quot;: &quot;Page description&quot;,</span>
</span><span id="__span-3-7"><a id="__codelineno-3-7" name="__codelineno-3-7" href="#__codelineno-3-7"></a><span class="err"> &quot;editorMode&quot;: &quot;VISUAL&quot;</span>
</span><span id="__span-3-8"><a id="__codelineno-3-8" name="__codelineno-3-8" href="#__codelineno-3-8"></a><span class="err">}</span>
</span></code></pre></div>
<p><strong>Request Body:</strong></p>
<ul>
<li><code>title</code> (string, required) — Page title (slug auto-generated)</li>
<li><code>description</code> (string?) — Internal description</li>
<li><code>editorMode</code> (enum?, default: <code>VISUAL</code>) — <code>VISUAL</code> or <code>CODE</code></li>
<li><code>mkdocsPath</code> (string?) — Custom override path (defaults to <code>{slug}.html</code>)</li>
</ul>
<p><strong>Response:</strong> Created <code>LandingPage</code> object (201 status)</p>
<p><strong>Errors:</strong></p>
<ul>
<li><code>400 INVALID_MKDOCS_PATH</code> — Invalid path (traversal attempt, missing .html extension)</li>
</ul>
<p><strong>Behavior:</strong></p>
<ul>
<li>Slug auto-generated from title (lowercased, spaces→hyphens, alphanumeric only)</li>
<li>Slug collision handling (appends <code>-2</code>, <code>-3</code>, etc.)</li>
<li><code>blocks</code> initialized as empty JSON object</li>
<li><code>published</code> defaults to <code>false</code></li>
</ul>
<h4 id="update-page">Update Page<a class="headerlink" href="#update-page" title="Permanent link">&para;</a></h4>
<div class="language-http highlight"><pre><span></span><code><span id="__span-4-1"><a id="__codelineno-4-1" name="__codelineno-4-1" href="#__codelineno-4-1"></a><span class="err">PUT /api/pages/:id</span>
</span><span id="__span-4-2"><a id="__codelineno-4-2" name="__codelineno-4-2" href="#__codelineno-4-2"></a><span class="err">Content-Type: application/json</span>
</span><span id="__span-4-3"><a id="__codelineno-4-3" name="__codelineno-4-3" href="#__codelineno-4-3"></a>
</span><span id="__span-4-4"><a id="__codelineno-4-4" name="__codelineno-4-4" href="#__codelineno-4-4"></a><span class="err">{</span>
</span><span id="__span-4-5"><a id="__codelineno-4-5" name="__codelineno-4-5" href="#__codelineno-4-5"></a><span class="err"> &quot;blocks&quot;: { /* GrapesJS projectData */ },</span>
</span><span id="__span-4-6"><a id="__codelineno-4-6" name="__codelineno-4-6" href="#__codelineno-4-6"></a><span class="err"> &quot;htmlOutput&quot;: &quot;&lt;section&gt;...&lt;/section&gt;&quot;,</span>
</span><span id="__span-4-7"><a id="__codelineno-4-7" name="__codelineno-4-7" href="#__codelineno-4-7"></a><span class="err"> &quot;cssOutput&quot;: &quot;section { padding: 40px; }&quot;,</span>
</span><span id="__span-4-8"><a id="__codelineno-4-8" name="__codelineno-4-8" href="#__codelineno-4-8"></a><span class="err"> &quot;published&quot;: true</span>
</span><span id="__span-4-9"><a id="__codelineno-4-9" name="__codelineno-4-9" href="#__codelineno-4-9"></a><span class="err">}</span>
</span></code></pre></div>
<p><strong>Request Body:</strong> (all fields optional)</p>
<ul>
<li><code>title</code> (string?) — New title (regenerates slug if changed)</li>
<li><code>description</code> (string?)</li>
<li><code>blocks</code> (JSON?) — GrapesJS <code>projectData</code></li>
<li><code>htmlOutput</code> (string?) — Rendered HTML</li>
<li><code>cssOutput</code> (string?) — Rendered CSS</li>
<li><code>published</code> (boolean?) — Publish status</li>
<li><code>mkdocsPath</code> (string?) — Custom override path</li>
<li><code>mkdocsExportMode</code> (enum?) — <code>THEMED</code> or <code>STANDALONE</code></li>
<li><code>mkdocsHideNav</code> (boolean?)</li>
<li><code>mkdocsHideToc</code> (boolean?)</li>
<li><code>mkdocsSkipExport</code> (boolean?)</li>
<li><code>seoTitle</code> (string?)</li>
<li><code>seoDescription</code> (string?)</li>
<li><code>seoImage</code> (string?)</li>
</ul>
<p><strong>Response:</strong> Updated <code>LandingPage</code> object</p>
<p><strong>Errors:</strong></p>
<ul>
<li><code>404 PAGE_NOT_FOUND</code> — Page doesn't exist</li>
<li><code>400 INVALID_MKDOCS_PATH</code> — Invalid path</li>
</ul>
<p><strong>Side Effects:</strong></p>
<ul>
<li><strong>On publish (published=true, mkdocsSkipExport=false):</strong> Exports to MkDocs (writes <code>.html</code> + <code>.md</code> stub)</li>
<li><strong>On unpublish or mkdocsSkipExport=true:</strong> Removes MkDocs files</li>
<li><strong>On title change:</strong> Regenerates slug, updates <code>mkdocsPath</code> if it was auto-generated, cleans up old exports</li>
</ul>
<h4 id="delete-page">Delete Page<a class="headerlink" href="#delete-page" title="Permanent link">&para;</a></h4>
<div class="language-http highlight"><pre><span></span><code><span id="__span-5-1"><a id="__codelineno-5-1" name="__codelineno-5-1" href="#__codelineno-5-1"></a><span class="err">DELETE /api/pages/:id</span>
</span></code></pre></div>
<p><strong>Response:</strong> 204 No Content</p>
<p><strong>Errors:</strong></p>
<ul>
<li><code>404 PAGE_NOT_FOUND</code> — Page doesn't exist</li>
</ul>
<p><strong>Side Effects:</strong></p>
<ul>
<li>Removes MkDocs exports (<code>.html</code> override + <code>.md</code> stub) if they exist</li>
</ul>
<h4 id="sync-overrides">Sync Overrides<a class="headerlink" href="#sync-overrides" title="Permanent link">&para;</a></h4>
<div class="language-http highlight"><pre><span></span><code><span id="__span-6-1"><a id="__codelineno-6-1" name="__codelineno-6-1" href="#__codelineno-6-1"></a><span class="err">POST /api/pages/sync</span>
</span></code></pre></div>
<p><strong>Purpose:</strong> Import untracked <code>.html</code> files from <code>mkdocs/docs/overrides/</code> as CODE-mode pages. Useful for migrating hand-crafted HTML templates.</p>
<p><strong>Response:</strong></p>
<div class="language-json highlight"><pre><span></span><code><span id="__span-7-1"><a id="__codelineno-7-1" name="__codelineno-7-1" href="#__codelineno-7-1"></a><span class="p">{</span>
</span><span id="__span-7-2"><a id="__codelineno-7-2" name="__codelineno-7-2" href="#__codelineno-7-2"></a><span class="w"> </span><span class="nt">&quot;imported&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span>
</span><span id="__span-7-3"><a id="__codelineno-7-3" name="__codelineno-7-3" href="#__codelineno-7-3"></a><span class="w"> </span><span class="nt">&quot;updated&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span>
</span><span id="__span-7-4"><a id="__codelineno-7-4" name="__codelineno-7-4" href="#__codelineno-7-4"></a><span class="w"> </span><span class="nt">&quot;stubs&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span>
</span><span id="__span-7-5"><a id="__codelineno-7-5" name="__codelineno-7-5" href="#__codelineno-7-5"></a><span class="p">}</span>
</span></code></pre></div>
<p><strong>Behavior:</strong></p>
<ol>
<li>Scans <code>mkdocs/docs/overrides/</code> recursively for <code>.html</code> files</li>
<li>For untracked files: Creates new CODE-mode page (published=true)</li>
<li>For tracked CODE-mode pages: Updates <code>htmlOutput</code> from disk (disk wins)</li>
<li>For tracked VISUAL-mode pages: Skips (managed by GrapesJS)</li>
<li>Backfills missing <code>.md</code> stubs for published pages</li>
</ol>
<p><strong>Use Cases:</strong></p>
<ul>
<li>Migrate legacy hand-coded landing pages</li>
<li>Import templates from designers</li>
<li>Sync after manual file system edits</li>
</ul>
<h4 id="validate-exports">Validate Exports<a class="headerlink" href="#validate-exports" title="Permanent link">&para;</a></h4>
<div class="language-http highlight"><pre><span></span><code><span id="__span-8-1"><a id="__codelineno-8-1" name="__codelineno-8-1" href="#__codelineno-8-1"></a><span class="err">POST /api/pages/validate</span>
</span></code></pre></div>
<p><strong>Purpose:</strong> Verify MkDocs exports exist on disk, repair if missing.</p>
<p><strong>Response:</strong></p>
<div class="language-json highlight"><pre><span></span><code><span id="__span-9-1"><a id="__codelineno-9-1" name="__codelineno-9-1" href="#__codelineno-9-1"></a><span class="p">{</span>
</span><span id="__span-9-2"><a id="__codelineno-9-2" name="__codelineno-9-2" href="#__codelineno-9-2"></a><span class="w"> </span><span class="nt">&quot;validated&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span>
</span><span id="__span-9-3"><a id="__codelineno-9-3" name="__codelineno-9-3" href="#__codelineno-9-3"></a><span class="w"> </span><span class="nt">&quot;repaired&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span>
</span><span id="__span-9-4"><a id="__codelineno-9-4" name="__codelineno-9-4" href="#__codelineno-9-4"></a><span class="w"> </span><span class="nt">&quot;errors&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
</span><span id="__span-9-5"><a id="__codelineno-9-5" name="__codelineno-9-5" href="#__codelineno-9-5"></a><span class="w"> </span><span class="p">{</span>
</span><span id="__span-9-6"><a id="__codelineno-9-6" name="__codelineno-9-6" href="#__codelineno-9-6"></a><span class="w"> </span><span class="nt">&quot;pageId&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;xyz789&quot;</span><span class="p">,</span>
</span><span id="__span-9-7"><a id="__codelineno-9-7" name="__codelineno-9-7" href="#__codelineno-9-7"></a><span class="w"> </span><span class="nt">&quot;slug&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;broken-page&quot;</span><span class="p">,</span>
</span><span id="__span-9-8"><a id="__codelineno-9-8" name="__codelineno-9-8" href="#__codelineno-9-8"></a><span class="w"> </span><span class="nt">&quot;error&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;EACCES: permission denied&quot;</span>
</span><span id="__span-9-9"><a id="__codelineno-9-9" name="__codelineno-9-9" href="#__codelineno-9-9"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-9-10"><a id="__codelineno-9-10" name="__codelineno-9-10" href="#__codelineno-9-10"></a><span class="w"> </span><span class="p">]</span>
</span><span id="__span-9-11"><a id="__codelineno-9-11" name="__codelineno-9-11" href="#__codelineno-9-11"></a><span class="p">}</span>
</span></code></pre></div>
<p><strong>Behavior:</strong></p>
<ol>
<li>Queries all published, non-skipped pages with <code>mkdocsPath</code></li>
<li>Checks if <code>.html</code> override and <code>.md</code> stub exist</li>
<li>Re-exports if either missing</li>
<li>Updates <code>mkdocsStubPath</code> if changed</li>
<li>Returns error list for manual intervention</li>
</ol>
<p><strong>Use Cases:</strong></p>
<ul>
<li>Recover from accidental file deletion</li>
<li>Fix export state after container restarts</li>
<li>Audit before MkDocs rebuild</li>
</ul>
<h3 id="public-routes">Public Routes<a class="headerlink" href="#public-routes" title="Permanent link">&para;</a></h3>
<p><strong>Prefix:</strong> <code>/api/pages</code></p>
<p><strong>Authentication:</strong> None (public access)</p>
<h4 id="view-published-page">View Published Page<a class="headerlink" href="#view-published-page" title="Permanent link">&para;</a></h4>
<div class="language-http highlight"><pre><span></span><code><span id="__span-10-1"><a id="__codelineno-10-1" name="__codelineno-10-1" href="#__codelineno-10-1"></a><span class="err">GET /api/pages/:slug/view</span>
</span></code></pre></div>
<p><strong>Example:</strong></p>
<div class="language-http highlight"><pre><span></span><code><span id="__span-11-1"><a id="__codelineno-11-1" name="__codelineno-11-1" href="#__codelineno-11-1"></a><span class="err">GET /api/pages/about-us/view</span>
</span></code></pre></div>
<p><strong>Response:</strong></p>
<div class="language-json highlight"><pre><span></span><code><span id="__span-12-1"><a id="__codelineno-12-1" name="__codelineno-12-1" href="#__codelineno-12-1"></a><span class="p">{</span>
</span><span id="__span-12-2"><a id="__codelineno-12-2" name="__codelineno-12-2" href="#__codelineno-12-2"></a><span class="w"> </span><span class="nt">&quot;id&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;abc123&quot;</span><span class="p">,</span>
</span><span id="__span-12-3"><a id="__codelineno-12-3" name="__codelineno-12-3" href="#__codelineno-12-3"></a><span class="w"> </span><span class="nt">&quot;slug&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;about-us&quot;</span><span class="p">,</span>
</span><span id="__span-12-4"><a id="__codelineno-12-4" name="__codelineno-12-4" href="#__codelineno-12-4"></a><span class="w"> </span><span class="nt">&quot;title&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;About Our Campaign&quot;</span><span class="p">,</span>
</span><span id="__span-12-5"><a id="__codelineno-12-5" name="__codelineno-12-5" href="#__codelineno-12-5"></a><span class="w"> </span><span class="nt">&quot;htmlOutput&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;&lt;section&gt;...&lt;/section&gt;&quot;</span><span class="p">,</span>
</span><span id="__span-12-6"><a id="__codelineno-12-6" name="__codelineno-12-6" href="#__codelineno-12-6"></a><span class="w"> </span><span class="nt">&quot;cssOutput&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;section { padding: 40px; }&quot;</span><span class="p">,</span>
</span><span id="__span-12-7"><a id="__codelineno-12-7" name="__codelineno-12-7" href="#__codelineno-12-7"></a><span class="w"> </span><span class="nt">&quot;seoTitle&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;About Us | Campaign 2026&quot;</span><span class="p">,</span>
</span><span id="__span-12-8"><a id="__codelineno-12-8" name="__codelineno-12-8" href="#__codelineno-12-8"></a><span class="w"> </span><span class="nt">&quot;seoDescription&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Join our movement for change.&quot;</span><span class="p">,</span>
</span><span id="__span-12-9"><a id="__codelineno-12-9" name="__codelineno-12-9" href="#__codelineno-12-9"></a><span class="w"> </span><span class="nt">&quot;seoImage&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;https://example.com/og-image.jpg&quot;</span><span class="p">,</span>
</span><span id="__span-12-10"><a id="__codelineno-12-10" name="__codelineno-12-10" href="#__codelineno-12-10"></a><span class="w"> </span><span class="nt">&quot;createdAt&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;2026-01-15T10:00:00Z&quot;</span><span class="p">,</span>
</span><span id="__span-12-11"><a id="__codelineno-12-11" name="__codelineno-12-11" href="#__codelineno-12-11"></a><span class="w"> </span><span class="nt">&quot;updatedAt&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;2026-02-13T14:30:00Z&quot;</span>
</span><span id="__span-12-12"><a id="__codelineno-12-12" name="__codelineno-12-12" href="#__codelineno-12-12"></a><span class="p">}</span>
</span></code></pre></div>
<p><strong>Errors:</strong></p>
<ul>
<li><code>404 PAGE_NOT_FOUND</code> — Page doesn't exist or is unpublished</li>
</ul>
<p><strong>Security:</strong></p>
<ul>
<li>Only returns published pages (<code>published=true</code>)</li>
<li>Omits editor-only fields (<code>blocks</code>, <code>mkdocsPath</code>, etc.)</li>
</ul>
<hr />
<h2 id="configuration">Configuration<a class="headerlink" href="#configuration" title="Permanent link">&para;</a></h2>
<h3 id="environment-variables">Environment Variables<a class="headerlink" href="#environment-variables" title="Permanent link">&para;</a></h3>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-13-1"><a id="__codelineno-13-1" name="__codelineno-13-1" href="#__codelineno-13-1"></a><span class="c1"># MkDocs integration</span>
</span><span id="__span-13-2"><a id="__codelineno-13-2" name="__codelineno-13-2" href="#__codelineno-13-2"></a><span class="nv">MKDOCS_DOCS_PATH</span><span class="o">=</span>/mkdocs/docs
</span><span id="__span-13-3"><a id="__codelineno-13-3" name="__codelineno-13-3" href="#__codelineno-13-3"></a><span class="c1"># Override path: ${MKDOCS_DOCS_PATH}/overrides/</span>
</span><span id="__span-13-4"><a id="__codelineno-13-4" name="__codelineno-13-4" href="#__codelineno-13-4"></a><span class="c1"># Stub path: ${MKDOCS_DOCS_PATH}/ (root of docs)</span>
</span></code></pre></div>
<p><strong>Docker Volume:</strong></p>
<div class="language-yaml highlight"><pre><span></span><code><span id="__span-14-1"><a id="__codelineno-14-1" name="__codelineno-14-1" href="#__codelineno-14-1"></a><span class="nt">volumes</span><span class="p">:</span>
</span><span id="__span-14-2"><a id="__codelineno-14-2" name="__codelineno-14-2" href="#__codelineno-14-2"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">./mkdocs:/mkdocs:rw</span>
</span></code></pre></div>
<p><strong>Note:</strong> API container needs write access to export files.</p>
<h3 id="site-settings">Site Settings<a class="headerlink" href="#site-settings" title="Permanent link">&para;</a></h3>
<p><strong>Feature Flag:</strong> <code>ENABLE_LANDING_PAGES</code></p>
<p><strong>Location:</strong> Admin → Settings → Features → Landing Pages</p>
<p><strong>Default:</strong> <code>true</code></p>
<p><strong>Effect:</strong> Shows/hides "Pages" menu item in admin sidebar</p>
<hr />
<h2 id="admin-workflow">Admin Workflow<a class="headerlink" href="#admin-workflow" title="Permanent link">&para;</a></h2>
<h3 id="creating-a-page">Creating a Page<a class="headerlink" href="#creating-a-page" title="Permanent link">&para;</a></h3>
<ol>
<li><strong>Navigate:</strong> Admin sidebar → Pages</li>
<li><strong>Click:</strong> "Create Page" button</li>
<li><strong>Fill form:</strong></li>
<li>Title: <code>"About Us"</code> (slug auto-generated: <code>about-us</code>)</li>
<li>Description: <code>"Learn about our campaign"</code> (optional)</li>
<li>Editor Mode: <code>VISUAL</code> (default) or <code>CODE</code></li>
<li><strong>Submit:</strong> "Create &amp; Edit" button</li>
<li><strong>Result:</strong> Redirected to full-screen editor</li>
</ol>
<h3 id="visual-editing-visual-mode">Visual Editing (VISUAL Mode)<a class="headerlink" href="#visual-editing-visual-mode" title="Permanent link">&para;</a></h3>
<ol>
<li><strong>Editor opens:</strong> GrapesJS interface with 3 panels:</li>
<li>Left: Block library (drag-and-drop components)</li>
<li>Center: Canvas (preview + inline editing)</li>
<li>Right: Properties panel (configure selected component)</li>
<li><strong>Add blocks:</strong> Drag "Hero Section" from left panel to canvas</li>
<li><strong>Configure:</strong> Click hero → Edit title/subtitle/CTA in right panel</li>
<li><strong>Save:</strong> Press <code>Ctrl+S</code> (or <code>Cmd+S</code> on Mac) → API saves <code>projectData</code>, <code>htmlOutput</code>, <code>cssOutput</code></li>
<li><strong>Close:</strong> Click "X" or "Back to Pages" → Returns to table</li>
</ol>
<h3 id="code-editing-code-mode">Code Editing (CODE Mode)<a class="headerlink" href="#code-editing-code-mode" title="Permanent link">&para;</a></h3>
<ol>
<li><strong>Editor opens:</strong> Split-view Monaco editors:</li>
<li>Left: HTML editor</li>
<li>Right: CSS editor (optional)</li>
<li><strong>Edit HTML:</strong> Write raw HTML with Jinja2 template syntax (for MkDocs)</li>
<li><strong>Save:</strong> Press <code>Ctrl+S</code> → API saves <code>htmlOutput</code>, <code>cssOutput</code></li>
<li><strong>Close:</strong> Click "Back to Pages"</li>
</ol>
<h3 id="publishing-a-page">Publishing a Page<a class="headerlink" href="#publishing-a-page" title="Permanent link">&para;</a></h3>
<p><strong>Option 1: From Table</strong></p>
<ol>
<li>Locate page in table</li>
<li>Click "Publish" button in Actions column</li>
<li>Status tag changes: Draft → Published</li>
<li>Page accessible at <code>/p/{slug}</code></li>
</ol>
<p><strong>Option 2: From Settings Modal</strong></p>
<ol>
<li>Click gear icon (Settings) in Actions column</li>
<li>Settings modal opens</li>
<li>(Field not shown in modal — use table toggle)</li>
</ol>
<p><strong>Side Effects (on publish):</strong></p>
<ul>
<li>If <code>mkdocsSkipExport=false</code>: Exports <code>.html</code> + <code>.md</code> to MkDocs</li>
<li>If <code>mkdocsSkipExport=true</code>: Only accessible via <code>/p/:slug</code> (no MkDocs export)</li>
</ul>
<h3 id="configuring-seo">Configuring SEO<a class="headerlink" href="#configuring-seo" title="Permanent link">&para;</a></h3>
<ol>
<li>Click gear icon (Settings) in Actions column</li>
<li>Fill SEO section:</li>
<li><strong>SEO Title:</strong> Custom title for <code>&lt;title&gt;</code> and Open Graph (defaults to <code>title</code>)</li>
<li><strong>SEO Description:</strong> Meta description for search engines</li>
<li><strong>SEO Image:</strong> Full URL to Open Graph image (e.g., <code>https://cdn.example.com/og.jpg</code>)</li>
<li>Click "Save"</li>
<li>Re-export to MkDocs if already published</li>
</ol>
<h3 id="mkdocs-integration-settings">MkDocs Integration Settings<a class="headerlink" href="#mkdocs-integration-settings" title="Permanent link">&para;</a></h3>
<p><strong>Access:</strong> Page Settings modal → MkDocs Integration section</p>
<p><strong>Fields:</strong></p>
<ol>
<li><strong>Skip MkDocs Export</strong> (checkbox)</li>
<li>When enabled: Page NOT exported to MkDocs site</li>
<li>Use case: Pages meant only for <code>/p/:slug</code> (not documentation)</li>
<li>
<p>Default: <code>false</code> (export enabled)</p>
</li>
<li>
<p><strong>Override Path</strong> (text input)</p>
</li>
<li>Custom filename for override (e.g., <code>custom-about.html</code>)</li>
<li>Default: Auto-generated from slug (<code>{slug}.html</code>)</li>
<li>
<p>Validation: Must end with <code>.html</code>, no path traversal</p>
</li>
<li>
<p><strong>Full page MkDocs</strong> (checkbox)</p>
</li>
<li>When enabled: Exports as STANDALONE (full <code>&lt;!DOCTYPE html&gt;</code> document)</li>
<li>When disabled: Exports as THEMED (wraps in <code>{% extends "main.html" %}</code>)</li>
<li>Default: <code>false</code> (THEMED)</li>
<li>
<p>Use case: Standalone pages with no MkDocs chrome (like <code>lander.html</code>)</p>
</li>
<li>
<p><strong>Hide navigation sidebar</strong> (checkbox, only for THEMED mode)</p>
</li>
<li>Adds <code>hide: [navigation]</code> to <code>.md</code> stub front matter</li>
<li>Hides left sidebar on page</li>
<li>
<p>Default: <code>false</code></p>
</li>
<li>
<p><strong>Hide table of contents</strong> (checkbox, only for THEMED mode)</p>
</li>
<li>Adds <code>hide: [toc]</code> to <code>.md</code> stub front matter</li>
<li>Hides right sidebar on page</li>
<li>Default: <code>false</code></li>
</ol>
<p><strong>Workflow:</strong></p>
<ol>
<li>Edit page settings</li>
<li>Configure MkDocs options</li>
<li>Save settings</li>
<li>If published: API auto-exports with new settings</li>
<li>Rebuild MkDocs: Admin → Pages → "Build Site" button</li>
</ol>
<h3 id="syncing-overrides">Syncing Overrides<a class="headerlink" href="#syncing-overrides" title="Permanent link">&para;</a></h3>
<p><strong>Purpose:</strong> Import hand-coded <code>.html</code> files from disk</p>
<p><strong>Workflow:</strong></p>
<ol>
<li>Place <code>.html</code> files in <code>mkdocs/docs/overrides/</code> (on Docker host)</li>
<li>Admin → Pages → "Sync Overrides" button</li>
<li>API scans directory, imports new files as CODE-mode pages</li>
<li>Table refreshes, new pages appear</li>
<li>Edit pages normally, publish as needed</li>
</ol>
<p><strong>Example:</strong></p>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-15-1"><a id="__codelineno-15-1" name="__codelineno-15-1" href="#__codelineno-15-1"></a><span class="c1"># On Docker host</span>
</span><span id="__span-15-2"><a id="__codelineno-15-2" name="__codelineno-15-2" href="#__codelineno-15-2"></a><span class="nb">echo</span><span class="w"> </span><span class="s1">&#39;&lt;h1&gt;Custom Page&lt;/h1&gt;&#39;</span><span class="w"> </span>&gt;<span class="w"> </span>mkdocs/docs/overrides/custom.html
</span><span id="__span-15-3"><a id="__codelineno-15-3" name="__codelineno-15-3" href="#__codelineno-15-3"></a>
</span><span id="__span-15-4"><a id="__codelineno-15-4" name="__codelineno-15-4" href="#__codelineno-15-4"></a><span class="c1"># In admin panel</span>
</span><span id="__span-15-5"><a id="__codelineno-15-5" name="__codelineno-15-5" href="#__codelineno-15-5"></a><span class="c1"># Click &quot;Sync Overrides&quot; → 1 imported</span>
</span></code></pre></div>
<h3 id="validating-exports">Validating Exports<a class="headerlink" href="#validating-exports" title="Permanent link">&para;</a></h3>
<p><strong>Purpose:</strong> Verify MkDocs files exist, repair if missing</p>
<p><strong>Workflow:</strong></p>
<ol>
<li>Admin → Pages → "Validate Exports" button</li>
<li>API checks all published pages:</li>
<li><code>.html</code> override exists?</li>
<li><code>.md</code> stub exists?</li>
<li>Re-exports if either missing</li>
<li>Shows result: <code>Validated 10 pages: 2 repaired</code></li>
</ol>
<p><strong>Use Cases:</strong></p>
<ul>
<li>After container restart (volume mount issues)</li>
<li>After manual file deletion</li>
<li>Before rebuilding MkDocs site</li>
</ul>
<hr />
<h2 id="public-workflow">Public Workflow<a class="headerlink" href="#public-workflow" title="Permanent link">&para;</a></h2>
<h3 id="viewing-a-published-page">Viewing a Published Page<a class="headerlink" href="#viewing-a-published-page" title="Permanent link">&para;</a></h3>
<ol>
<li><strong>User navigates:</strong> <code>https://yoursite.com/p/about-us</code></li>
<li><strong>React router:</strong> Matches <code>/p/:slug</code> route → Loads <code>LandingPage.tsx</code></li>
<li><strong>API call:</strong> <code>GET /api/pages/about-us/view</code></li>
<li><strong>Response:</strong> Returns <code>htmlOutput</code>, <code>cssOutput</code>, SEO fields</li>
<li><strong>Render:</strong></li>
<li>Sets <code>document.title = seoTitle || title</code></li>
<li>Updates meta description, Open Graph image</li>
<li>Injects <code>cssOutput</code> as <code>&lt;style&gt;</code> tag</li>
<li>Renders <code>htmlOutput</code> via <code>dangerouslySetInnerHTML</code></li>
<li><strong>Video hydration:</strong> Scans for <code>.video-block</code> divs, replaces placeholders with React VideoPlayer components</li>
</ol>
<h3 id="seo-meta-tags">SEO Meta Tags<a class="headerlink" href="#seo-meta-tags" title="Permanent link">&para;</a></h3>
<p><strong>Applied automatically on page load:</strong></p>
<div class="language-html highlight"><pre><span></span><code><span id="__span-16-1"><a id="__codelineno-16-1" name="__codelineno-16-1" href="#__codelineno-16-1"></a><span class="p">&lt;</span><span class="nt">html</span><span class="p">&gt;</span>
</span><span id="__span-16-2"><a id="__codelineno-16-2" name="__codelineno-16-2" href="#__codelineno-16-2"></a><span class="p">&lt;</span><span class="nt">head</span><span class="p">&gt;</span>
</span><span id="__span-16-3"><a id="__codelineno-16-3" name="__codelineno-16-3" href="#__codelineno-16-3"></a> <span class="p">&lt;</span><span class="nt">title</span><span class="p">&gt;</span>About Us | Campaign 2026<span class="p">&lt;/</span><span class="nt">title</span><span class="p">&gt;</span>
</span><span id="__span-16-4"><a id="__codelineno-16-4" name="__codelineno-16-4" href="#__codelineno-16-4"></a> <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">name</span><span class="o">=</span><span class="s">&quot;description&quot;</span> <span class="na">content</span><span class="o">=</span><span class="s">&quot;Join our movement for change.&quot;</span><span class="p">&gt;</span>
</span><span id="__span-16-5"><a id="__codelineno-16-5" name="__codelineno-16-5" href="#__codelineno-16-5"></a> <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">property</span><span class="o">=</span><span class="s">&quot;og:image&quot;</span> <span class="na">content</span><span class="o">=</span><span class="s">&quot;https://example.com/og-image.jpg&quot;</span><span class="p">&gt;</span>
</span><span id="__span-16-6"><a id="__codelineno-16-6" name="__codelineno-16-6" href="#__codelineno-16-6"></a><span class="p">&lt;/</span><span class="nt">head</span><span class="p">&gt;</span>
</span><span id="__span-16-7"><a id="__codelineno-16-7" name="__codelineno-16-7" href="#__codelineno-16-7"></a><span class="p">&lt;</span><span class="nt">body</span><span class="p">&gt;</span>
</span><span id="__span-16-8"><a id="__codelineno-16-8" name="__codelineno-16-8" href="#__codelineno-16-8"></a> <span class="p">&lt;</span><span class="nt">style</span><span class="p">&gt;</span><span class="nt">section</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">padding</span><span class="p">:</span><span class="w"> </span><span class="mi">40</span><span class="kt">px</span><span class="p">;</span><span class="w"> </span><span class="p">}&lt;/</span><span class="nt">style</span><span class="p">&gt;</span>
</span><span id="__span-16-9"><a id="__codelineno-16-9" name="__codelineno-16-9" href="#__codelineno-16-9"></a> <span class="p">&lt;</span><span class="nt">section</span><span class="p">&gt;</span>...<span class="p">&lt;/</span><span class="nt">section</span><span class="p">&gt;</span>
</span><span id="__span-16-10"><a id="__codelineno-16-10" name="__codelineno-16-10" href="#__codelineno-16-10"></a><span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span>
</span><span id="__span-16-11"><a id="__codelineno-16-11" name="__codelineno-16-11" href="#__codelineno-16-11"></a><span class="p">&lt;/</span><span class="nt">html</span><span class="p">&gt;</span>
</span></code></pre></div>
<h3 id="video-embedding">Video Embedding<a class="headerlink" href="#video-embedding" title="Permanent link">&para;</a></h3>
<p><strong>Editor Placeholder:</strong></p>
<div class="language-html highlight"><pre><span></span><code><span id="__span-17-1"><a id="__codelineno-17-1" name="__codelineno-17-1" href="#__codelineno-17-1"></a><span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&quot;video-block&quot;</span>
</span><span id="__span-17-2"><a id="__codelineno-17-2" name="__codelineno-17-2" href="#__codelineno-17-2"></a> <span class="na">data-video-id</span><span class="o">=</span><span class="s">&quot;123&quot;</span>
</span><span id="__span-17-3"><a id="__codelineno-17-3" name="__codelineno-17-3" href="#__codelineno-17-3"></a> <span class="na">data-player-type</span><span class="o">=</span><span class="s">&quot;advanced&quot;</span>
</span><span id="__span-17-4"><a id="__codelineno-17-4" name="__codelineno-17-4" href="#__codelineno-17-4"></a> <span class="na">data-width</span><span class="o">=</span><span class="s">&quot;100%&quot;</span>
</span><span id="__span-17-5"><a id="__codelineno-17-5" name="__codelineno-17-5" href="#__codelineno-17-5"></a> <span class="na">data-autoplay</span><span class="o">=</span><span class="s">&quot;false&quot;</span>
</span><span id="__span-17-6"><a id="__codelineno-17-6" name="__codelineno-17-6" href="#__codelineno-17-6"></a> <span class="na">data-controls</span><span class="o">=</span><span class="s">&quot;true&quot;</span>
</span><span id="__span-17-7"><a id="__codelineno-17-7" name="__codelineno-17-7" href="#__codelineno-17-7"></a> <span class="na">data-show-reactions</span><span class="o">=</span><span class="s">&quot;true&quot;</span><span class="p">&gt;</span>
</span><span id="__span-17-8"><a id="__codelineno-17-8" name="__codelineno-17-8" href="#__codelineno-17-8"></a> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&quot;video-placeholder&quot;</span><span class="p">&gt;</span>
</span><span id="__span-17-9"><a id="__codelineno-17-9" name="__codelineno-17-9" href="#__codelineno-17-9"></a> <span class="cm">&lt;!-- SVG play icon + metadata --&gt;</span>
</span><span id="__span-17-10"><a id="__codelineno-17-10" name="__codelineno-17-10" href="#__codelineno-17-10"></a> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span><span id="__span-17-11"><a id="__codelineno-17-11" name="__codelineno-17-11" href="#__codelineno-17-11"></a><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></code></pre></div>
<p><strong>Runtime Hydration:</strong></p>
<ol>
<li><code>LandingPage.tsx</code> mounts → Scans for <code>.video-block</code> elements</li>
<li>Reads <code>data-*</code> attributes</li>
<li>Creates React root for each block</li>
<li>Renders <code>AdvancedVideoPlayer</code> or <code>VideoPlayer</code> component</li>
<li>Replaces placeholder with live player</li>
</ol>
<p><strong>Supported Attributes:</strong></p>
<ul>
<li><code>data-video-id</code> (required) — Media library video ID</li>
<li><code>data-player-type</code> (<code>"standard"</code> or <code>"advanced"</code>, default: <code>"standard"</code>)</li>
<li><code>data-width</code> (CSS value, default: <code>"100%"</code>)</li>
<li><code>data-height</code> (CSS value, default: <code>"auto"</code>)</li>
<li><code>data-autoplay</code> (<code>"true"</code> or <code>"false"</code>, default: <code>"false"</code>)</li>
<li><code>data-controls</code> (<code>"true"</code> or <code>"false"</code>, default: <code>"true"</code>)</li>
<li><code>data-show-reactions</code> (<code>"true"</code> or <code>"false"</code>, default: <code>"true"</code>, advanced player only)</li>
</ul>
<hr />
<h2 id="code-examples">Code Examples<a class="headerlink" href="#code-examples" title="Permanent link">&para;</a></h2>
<h3 id="creating-a-page-typescript">Creating a Page (TypeScript)<a class="headerlink" href="#creating-a-page-typescript" title="Permanent link">&para;</a></h3>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-18-1"><a id="__codelineno-18-1" name="__codelineno-18-1" href="#__codelineno-18-1"></a><span class="k">import</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">api</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">&#39;@/lib/api&#39;</span><span class="p">;</span>
</span><span id="__span-18-2"><a id="__codelineno-18-2" name="__codelineno-18-2" href="#__codelineno-18-2"></a>
</span><span id="__span-18-3"><a id="__codelineno-18-3" name="__codelineno-18-3" href="#__codelineno-18-3"></a><span class="k">async</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="nx">createAboutPage</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-18-4"><a id="__codelineno-18-4" name="__codelineno-18-4" href="#__codelineno-18-4"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">data</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">api</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="s1">&#39;/pages&#39;</span><span class="p">,</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-18-5"><a id="__codelineno-18-5" name="__codelineno-18-5" href="#__codelineno-18-5"></a><span class="w"> </span><span class="nx">title</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;About Us&#39;</span><span class="p">,</span>
</span><span id="__span-18-6"><a id="__codelineno-18-6" name="__codelineno-18-6" href="#__codelineno-18-6"></a><span class="w"> </span><span class="nx">description</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;Learn about our campaign&#39;</span><span class="p">,</span>
</span><span id="__span-18-7"><a id="__codelineno-18-7" name="__codelineno-18-7" href="#__codelineno-18-7"></a><span class="w"> </span><span class="nx">editorMode</span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;VISUAL&#39;</span><span class="p">,</span>
</span><span id="__span-18-8"><a id="__codelineno-18-8" name="__codelineno-18-8" href="#__codelineno-18-8"></a><span class="w"> </span><span class="p">});</span>
</span><span id="__span-18-9"><a id="__codelineno-18-9" name="__codelineno-18-9" href="#__codelineno-18-9"></a>
</span><span id="__span-18-10"><a id="__codelineno-18-10" name="__codelineno-18-10" href="#__codelineno-18-10"></a><span class="w"> </span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;Created page:&#39;</span><span class="p">,</span><span class="w"> </span><span class="nx">data</span><span class="p">.</span><span class="nx">slug</span><span class="p">);</span><span class="w"> </span><span class="c1">// &quot;about-us&quot;</span>
</span><span id="__span-18-11"><a id="__codelineno-18-11" name="__codelineno-18-11" href="#__codelineno-18-11"></a><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">data</span><span class="p">.</span><span class="nx">id</span><span class="p">;</span>
</span><span id="__span-18-12"><a id="__codelineno-18-12" name="__codelineno-18-12" href="#__codelineno-18-12"></a><span class="p">}</span>
</span></code></pre></div>
<h3 id="saving-editor-state-grapesjs">Saving Editor State (GrapesJS)<a class="headerlink" href="#saving-editor-state-grapesjs" title="Permanent link">&para;</a></h3>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-19-1"><a id="__codelineno-19-1" name="__codelineno-19-1" href="#__codelineno-19-1"></a><span class="c1">// In LandingPageEditor component</span>
</span><span id="__span-19-2"><a id="__codelineno-19-2" name="__codelineno-19-2" href="#__codelineno-19-2"></a><span class="k">import</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">useRef</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">&#39;react&#39;</span><span class="p">;</span>
</span><span id="__span-19-3"><a id="__codelineno-19-3" name="__codelineno-19-3" href="#__codelineno-19-3"></a><span class="k">import</span><span class="w"> </span><span class="nx">GrapesJSEditor</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">GrapesJSEditorHandle</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">&#39;@/components/GrapesJSEditor&#39;</span><span class="p">;</span>
</span><span id="__span-19-4"><a id="__codelineno-19-4" name="__codelineno-19-4" href="#__codelineno-19-4"></a>
</span><span id="__span-19-5"><a id="__codelineno-19-5" name="__codelineno-19-5" href="#__codelineno-19-5"></a><span class="kd">const</span><span class="w"> </span><span class="nx">editorRef</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">useRef</span><span class="o">&lt;</span><span class="nx">GrapesJSEditorHandle</span><span class="o">&gt;</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span>
</span><span id="__span-19-6"><a id="__codelineno-19-6" name="__codelineno-19-6" href="#__codelineno-19-6"></a>
</span><span id="__span-19-7"><a id="__codelineno-19-7" name="__codelineno-19-7" href="#__codelineno-19-7"></a><span class="kd">const</span><span class="w"> </span><span class="nx">handleSave</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">()</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-19-8"><a id="__codelineno-19-8" name="__codelineno-19-8" href="#__codelineno-19-8"></a><span class="w"> </span><span class="nx">editorRef</span><span class="p">.</span><span class="nx">current</span><span class="o">?</span><span class="p">.</span><span class="nx">triggerSave</span><span class="p">();</span><span class="w"> </span><span class="c1">// Calls registered save command</span>
</span><span id="__span-19-9"><a id="__codelineno-19-9" name="__codelineno-19-9" href="#__codelineno-19-9"></a><span class="p">};</span>
</span><span id="__span-19-10"><a id="__codelineno-19-10" name="__codelineno-19-10" href="#__codelineno-19-10"></a>
</span><span id="__span-19-11"><a id="__codelineno-19-11" name="__codelineno-19-11" href="#__codelineno-19-11"></a><span class="kd">const</span><span class="w"> </span><span class="nx">handleEditorSave</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="p">(</span><span class="nx">data</span><span class="o">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">projectData</span><span class="o">:</span><span class="w"> </span><span class="kt">any</span><span class="p">;</span><span class="w"> </span><span class="nx">html</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="p">;</span><span class="w"> </span><span class="nx">css</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="p">})</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-19-12"><a id="__codelineno-19-12" name="__codelineno-19-12" href="#__codelineno-19-12"></a><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">api</span><span class="p">.</span><span class="nx">put</span><span class="p">(</span><span class="sb">`/pages/</span><span class="si">${</span><span class="nx">pageId</span><span class="si">}</span><span class="sb">`</span><span class="p">,</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-19-13"><a id="__codelineno-19-13" name="__codelineno-19-13" href="#__codelineno-19-13"></a><span class="w"> </span><span class="nx">blocks</span><span class="o">:</span><span class="w"> </span><span class="kt">data.projectData</span><span class="p">,</span>
</span><span id="__span-19-14"><a id="__codelineno-19-14" name="__codelineno-19-14" href="#__codelineno-19-14"></a><span class="w"> </span><span class="nx">htmlOutput</span><span class="o">:</span><span class="w"> </span><span class="kt">data.html</span><span class="p">,</span>
</span><span id="__span-19-15"><a id="__codelineno-19-15" name="__codelineno-19-15" href="#__codelineno-19-15"></a><span class="w"> </span><span class="nx">cssOutput</span><span class="o">:</span><span class="w"> </span><span class="kt">data.css</span><span class="p">,</span>
</span><span id="__span-19-16"><a id="__codelineno-19-16" name="__codelineno-19-16" href="#__codelineno-19-16"></a><span class="w"> </span><span class="p">});</span>
</span><span id="__span-19-17"><a id="__codelineno-19-17" name="__codelineno-19-17" href="#__codelineno-19-17"></a><span class="w"> </span><span class="nx">message</span><span class="p">.</span><span class="nx">success</span><span class="p">(</span><span class="s1">&#39;Page saved&#39;</span><span class="p">);</span>
</span><span id="__span-19-18"><a id="__codelineno-19-18" name="__codelineno-19-18" href="#__codelineno-19-18"></a><span class="p">};</span>
</span><span id="__span-19-19"><a id="__codelineno-19-19" name="__codelineno-19-19" href="#__codelineno-19-19"></a>
</span><span id="__span-19-20"><a id="__codelineno-19-20" name="__codelineno-19-20" href="#__codelineno-19-20"></a><span class="k">return</span><span class="w"> </span><span class="p">(</span>
</span><span id="__span-19-21"><a id="__codelineno-19-21" name="__codelineno-19-21" href="#__codelineno-19-21"></a><span class="w"> </span><span class="o">&lt;</span><span class="nx">GrapesJSEditor</span>
</span><span id="__span-19-22"><a id="__codelineno-19-22" name="__codelineno-19-22" href="#__codelineno-19-22"></a><span class="w"> </span><span class="nx">ref</span><span class="o">=</span><span class="p">{</span><span class="nx">editorRef</span><span class="p">}</span>
</span><span id="__span-19-23"><a id="__codelineno-19-23" name="__codelineno-19-23" href="#__codelineno-19-23"></a><span class="w"> </span><span class="nx">initialData</span><span class="o">=</span><span class="p">{</span><span class="nx">page</span><span class="p">.</span><span class="nx">blocks</span><span class="p">}</span>
</span><span id="__span-19-24"><a id="__codelineno-19-24" name="__codelineno-19-24" href="#__codelineno-19-24"></a><span class="w"> </span><span class="nx">onSave</span><span class="o">=</span><span class="p">{</span><span class="nx">handleEditorSave</span><span class="p">}</span>
</span><span id="__span-19-25"><a id="__codelineno-19-25" name="__codelineno-19-25" href="#__codelineno-19-25"></a><span class="w"> </span><span class="o">/&gt;</span>
</span><span id="__span-19-26"><a id="__codelineno-19-26" name="__codelineno-19-26" href="#__codelineno-19-26"></a><span class="p">);</span>
</span></code></pre></div>
<h3 id="fetching-published-page-public-route">Fetching Published Page (Public Route)<a class="headerlink" href="#fetching-published-page-public-route" title="Permanent link">&para;</a></h3>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-20-1"><a id="__codelineno-20-1" name="__codelineno-20-1" href="#__codelineno-20-1"></a><span class="k">import</span><span class="w"> </span><span class="nx">axios</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">&#39;axios&#39;</span><span class="p">;</span>
</span><span id="__span-20-2"><a id="__codelineno-20-2" name="__codelineno-20-2" href="#__codelineno-20-2"></a>
</span><span id="__span-20-3"><a id="__codelineno-20-3" name="__codelineno-20-3" href="#__codelineno-20-3"></a><span class="k">async</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="nx">loadLandingPage</span><span class="p">(</span><span class="nx">slug</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-20-4"><a id="__codelineno-20-4" name="__codelineno-20-4" href="#__codelineno-20-4"></a><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-20-5"><a id="__codelineno-20-5" name="__codelineno-20-5" href="#__codelineno-20-5"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">data</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">axios</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="sb">`/api/pages/</span><span class="si">${</span><span class="nx">slug</span><span class="si">}</span><span class="sb">/view`</span><span class="p">);</span>
</span><span id="__span-20-6"><a id="__codelineno-20-6" name="__codelineno-20-6" href="#__codelineno-20-6"></a>
</span><span id="__span-20-7"><a id="__codelineno-20-7" name="__codelineno-20-7" href="#__codelineno-20-7"></a><span class="w"> </span><span class="c1">// Set SEO</span>
</span><span id="__span-20-8"><a id="__codelineno-20-8" name="__codelineno-20-8" href="#__codelineno-20-8"></a><span class="w"> </span><span class="nb">document</span><span class="p">.</span><span class="nx">title</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">data</span><span class="p">.</span><span class="nx">seoTitle</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="nx">data</span><span class="p">.</span><span class="nx">title</span><span class="p">;</span>
</span><span id="__span-20-9"><a id="__codelineno-20-9" name="__codelineno-20-9" href="#__codelineno-20-9"></a>
</span><span id="__span-20-10"><a id="__codelineno-20-10" name="__codelineno-20-10" href="#__codelineno-20-10"></a><span class="w"> </span><span class="c1">// Inject CSS</span>
</span><span id="__span-20-11"><a id="__codelineno-20-11" name="__codelineno-20-11" href="#__codelineno-20-11"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">style</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s1">&#39;style&#39;</span><span class="p">);</span>
</span><span id="__span-20-12"><a id="__codelineno-20-12" name="__codelineno-20-12" href="#__codelineno-20-12"></a><span class="w"> </span><span class="nx">style</span><span class="p">.</span><span class="nx">textContent</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">data</span><span class="p">.</span><span class="nx">cssOutput</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="s1">&#39;&#39;</span><span class="p">;</span>
</span><span id="__span-20-13"><a id="__codelineno-20-13" name="__codelineno-20-13" href="#__codelineno-20-13"></a><span class="w"> </span><span class="nb">document</span><span class="p">.</span><span class="nx">head</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">style</span><span class="p">);</span>
</span><span id="__span-20-14"><a id="__codelineno-20-14" name="__codelineno-20-14" href="#__codelineno-20-14"></a>
</span><span id="__span-20-15"><a id="__codelineno-20-15" name="__codelineno-20-15" href="#__codelineno-20-15"></a><span class="w"> </span><span class="c1">// Render HTML</span>
</span><span id="__span-20-16"><a id="__codelineno-20-16" name="__codelineno-20-16" href="#__codelineno-20-16"></a><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">data</span><span class="p">.</span><span class="nx">htmlOutput</span><span class="p">;</span>
</span><span id="__span-20-17"><a id="__codelineno-20-17" name="__codelineno-20-17" href="#__codelineno-20-17"></a><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">catch</span><span class="w"> </span><span class="p">(</span><span class="nx">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-20-18"><a id="__codelineno-20-18" name="__codelineno-20-18" href="#__codelineno-20-18"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">axios</span><span class="p">.</span><span class="nx">isAxiosError</span><span class="p">(</span><span class="nx">error</span><span class="p">)</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="nx">error</span><span class="p">.</span><span class="nx">response</span><span class="o">?</span><span class="p">.</span><span class="nx">status</span><span class="w"> </span><span class="o">===</span><span class="w"> </span><span class="mf">404</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-20-19"><a id="__codelineno-20-19" name="__codelineno-20-19" href="#__codelineno-20-19"></a><span class="w"> </span><span class="k">throw</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="ne">Error</span><span class="p">(</span><span class="s1">&#39;Page not found or unpublished&#39;</span><span class="p">);</span>
</span><span id="__span-20-20"><a id="__codelineno-20-20" name="__codelineno-20-20" href="#__codelineno-20-20"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-20-21"><a id="__codelineno-20-21" name="__codelineno-20-21" href="#__codelineno-20-21"></a><span class="w"> </span><span class="k">throw</span><span class="w"> </span><span class="nx">error</span><span class="p">;</span>
</span><span id="__span-20-22"><a id="__codelineno-20-22" name="__codelineno-20-22" href="#__codelineno-20-22"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-20-23"><a id="__codelineno-20-23" name="__codelineno-20-23" href="#__codelineno-20-23"></a><span class="p">}</span>
</span></code></pre></div>
<h3 id="mkdocs-export-logic-backend">MkDocs Export Logic (Backend)<a class="headerlink" href="#mkdocs-export-logic-backend" title="Permanent link">&para;</a></h3>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-21-1"><a id="__codelineno-21-1" name="__codelineno-21-1" href="#__codelineno-21-1"></a><span class="c1">// From pages.service.ts</span>
</span><span id="__span-21-2"><a id="__codelineno-21-2" name="__codelineno-21-2" href="#__codelineno-21-2"></a>
</span><span id="__span-21-3"><a id="__codelineno-21-3" name="__codelineno-21-3" href="#__codelineno-21-3"></a><span class="kd">function</span><span class="w"> </span><span class="nx">wrapInMaterialOverride</span><span class="p">(</span><span class="nx">html</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="nx">css</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="kc">null</span><span class="p">)</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-21-4"><a id="__codelineno-21-4" name="__codelineno-21-4" href="#__codelineno-21-4"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">styleBlock</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">css</span><span class="w"> </span><span class="o">?</span><span class="w"> </span><span class="sb">`&lt;style&gt;\n</span><span class="si">${</span><span class="nx">css</span><span class="si">}</span><span class="sb">\n&lt;/style&gt;`</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;&#39;</span><span class="p">;</span>
</span><span id="__span-21-5"><a id="__codelineno-21-5" name="__codelineno-21-5" href="#__codelineno-21-5"></a><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="sb">`{% extends &quot;main.html&quot; %}</span>
</span><span id="__span-21-6"><a id="__codelineno-21-6" name="__codelineno-21-6" href="#__codelineno-21-6"></a><span class="sb">{% block content %}</span>
</span><span id="__span-21-7"><a id="__codelineno-21-7" name="__codelineno-21-7" href="#__codelineno-21-7"></a><span class="si">${</span><span class="nx">styleBlock</span><span class="si">}</span>
</span><span id="__span-21-8"><a id="__codelineno-21-8" name="__codelineno-21-8" href="#__codelineno-21-8"></a><span class="si">${</span><span class="nx">html</span><span class="si">}</span>
</span><span id="__span-21-9"><a id="__codelineno-21-9" name="__codelineno-21-9" href="#__codelineno-21-9"></a><span class="sb">{% endblock %}</span>
</span><span id="__span-21-10"><a id="__codelineno-21-10" name="__codelineno-21-10" href="#__codelineno-21-10"></a><span class="sb">`</span><span class="p">;</span>
</span><span id="__span-21-11"><a id="__codelineno-21-11" name="__codelineno-21-11" href="#__codelineno-21-11"></a><span class="p">}</span>
</span><span id="__span-21-12"><a id="__codelineno-21-12" name="__codelineno-21-12" href="#__codelineno-21-12"></a>
</span><span id="__span-21-13"><a id="__codelineno-21-13" name="__codelineno-21-13" href="#__codelineno-21-13"></a><span class="k">async</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="nx">exportToMkDocs</span><span class="p">(</span><span class="nx">opts</span><span class="o">:</span><span class="w"> </span><span class="kt">ExportOptions</span><span class="p">)</span><span class="o">:</span><span class="w"> </span><span class="nb">Promise</span><span class="o">&lt;</span><span class="kt">string</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-21-14"><a id="__codelineno-21-14" name="__codelineno-21-14" href="#__codelineno-21-14"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">mkdocsPath</span><span class="p">,</span><span class="w"> </span><span class="nx">html</span><span class="p">,</span><span class="w"> </span><span class="nx">css</span><span class="p">,</span><span class="w"> </span><span class="nx">exportMode</span><span class="p">,</span><span class="w"> </span><span class="nx">title</span><span class="p">,</span><span class="w"> </span><span class="nx">seoTitle</span><span class="p">,</span><span class="w"> </span><span class="nx">seoDescription</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">opts</span><span class="p">;</span>
</span><span id="__span-21-15"><a id="__codelineno-21-15" name="__codelineno-21-15" href="#__codelineno-21-15"></a>
</span><span id="__span-21-16"><a id="__codelineno-21-16" name="__codelineno-21-16" href="#__codelineno-21-16"></a><span class="w"> </span><span class="c1">// Write override template</span>
</span><span id="__span-21-17"><a id="__codelineno-21-17" name="__codelineno-21-17" href="#__codelineno-21-17"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">filePath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">MKDOCS_OVERRIDES</span><span class="p">,</span><span class="w"> </span><span class="nx">mkdocsPath</span><span class="p">);</span>
</span><span id="__span-21-18"><a id="__codelineno-21-18" name="__codelineno-21-18" href="#__codelineno-21-18"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">content</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">exportMode</span><span class="w"> </span><span class="o">===</span><span class="w"> </span><span class="s1">&#39;STANDALONE&#39;</span>
</span><span id="__span-21-19"><a id="__codelineno-21-19" name="__codelineno-21-19" href="#__codelineno-21-19"></a><span class="w"> </span><span class="o">?</span><span class="w"> </span><span class="nx">wrapInStandaloneDocument</span><span class="p">(</span><span class="nx">html</span><span class="p">,</span><span class="w"> </span><span class="nx">css</span><span class="p">,</span><span class="w"> </span><span class="nx">seoTitle</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="nx">title</span><span class="p">,</span><span class="w"> </span><span class="nx">seoDescription</span><span class="p">)</span>
</span><span id="__span-21-20"><a id="__codelineno-21-20" name="__codelineno-21-20" href="#__codelineno-21-20"></a><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="nx">wrapInMaterialOverride</span><span class="p">(</span><span class="nx">html</span><span class="p">,</span><span class="w"> </span><span class="nx">css</span><span class="p">);</span>
</span><span id="__span-21-21"><a id="__codelineno-21-21" name="__codelineno-21-21" href="#__codelineno-21-21"></a>
</span><span id="__span-21-22"><a id="__codelineno-21-22" name="__codelineno-21-22" href="#__codelineno-21-22"></a><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">fs</span><span class="p">.</span><span class="nx">writeFile</span><span class="p">(</span><span class="nx">filePath</span><span class="p">,</span><span class="w"> </span><span class="nx">content</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;utf-8&#39;</span><span class="p">);</span>
</span><span id="__span-21-23"><a id="__codelineno-21-23" name="__codelineno-21-23" href="#__codelineno-21-23"></a>
</span><span id="__span-21-24"><a id="__codelineno-21-24" name="__codelineno-21-24" href="#__codelineno-21-24"></a><span class="w"> </span><span class="c1">// Write .md stub</span>
</span><span id="__span-21-25"><a id="__codelineno-21-25" name="__codelineno-21-25" href="#__codelineno-21-25"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">stubPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">mkdocsPath</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/\.html$/</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;.md&#39;</span><span class="p">);</span>
</span><span id="__span-21-26"><a id="__codelineno-21-26" name="__codelineno-21-26" href="#__codelineno-21-26"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">stubContent</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sb">`---</span>
</span><span id="__span-21-27"><a id="__codelineno-21-27" name="__codelineno-21-27" href="#__codelineno-21-27"></a><span class="sb">template: </span><span class="si">${</span><span class="nx">mkdocsPath</span><span class="si">}</span>
</span><span id="__span-21-28"><a id="__codelineno-21-28" name="__codelineno-21-28" href="#__codelineno-21-28"></a><span class="sb">title: &quot;</span><span class="si">${</span><span class="nx">seoTitle</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="nx">title</span><span class="si">}</span><span class="sb">&quot;</span>
</span><span id="__span-21-29"><a id="__codelineno-21-29" name="__codelineno-21-29" href="#__codelineno-21-29"></a><span class="sb">---</span>
</span><span id="__span-21-30"><a id="__codelineno-21-30" name="__codelineno-21-30" href="#__codelineno-21-30"></a><span class="sb">`</span><span class="p">;</span>
</span><span id="__span-21-31"><a id="__codelineno-21-31" name="__codelineno-21-31" href="#__codelineno-21-31"></a><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">fs</span><span class="p">.</span><span class="nx">writeFile</span><span class="p">(</span><span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">MKDOCS_DOCS_ROOT</span><span class="p">,</span><span class="w"> </span><span class="nx">stubPath</span><span class="p">),</span><span class="w"> </span><span class="nx">stubContent</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;utf-8&#39;</span><span class="p">);</span>
</span><span id="__span-21-32"><a id="__codelineno-21-32" name="__codelineno-21-32" href="#__codelineno-21-32"></a>
</span><span id="__span-21-33"><a id="__codelineno-21-33" name="__codelineno-21-33" href="#__codelineno-21-33"></a><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">stubPath</span><span class="p">;</span>
</span><span id="__span-21-34"><a id="__codelineno-21-34" name="__codelineno-21-34" href="#__codelineno-21-34"></a><span class="p">}</span>
</span></code></pre></div>
<hr />
<h2 id="troubleshooting">Troubleshooting<a class="headerlink" href="#troubleshooting" title="Permanent link">&para;</a></h2>
<h3 id="problem-grapesjs-editor-not-loading">Problem: GrapesJS Editor Not Loading<a class="headerlink" href="#problem-grapesjs-editor-not-loading" title="Permanent link">&para;</a></h3>
<p><strong>Symptoms:</strong></p>
<ul>
<li>Blank screen in editor</li>
<li>Console error: <code>Cannot read property 'init' of undefined</code></li>
</ul>
<p><strong>Causes:</strong></p>
<ul>
<li>GrapesJS package not installed</li>
<li>CSS import missing</li>
<li>Plugin incompatibility</li>
</ul>
<p><strong>Solutions:</strong></p>
<ol>
<li>
<p><strong>Verify installation:</strong>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-22-1"><a id="__codelineno-22-1" name="__codelineno-22-1" href="#__codelineno-22-1"></a><span class="nb">cd</span><span class="w"> </span>admin<span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span>npm<span class="w"> </span>list<span class="w"> </span>grapesjs
</span><span id="__span-22-2"><a id="__codelineno-22-2" name="__codelineno-22-2" href="#__codelineno-22-2"></a><span class="c1"># Should show: grapesjs@0.21.x</span>
</span></code></pre></div></p>
</li>
<li>
<p><strong>Check CSS import:</strong>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-23-1"><a id="__codelineno-23-1" name="__codelineno-23-1" href="#__codelineno-23-1"></a><span class="c1">// In GrapesJSEditor.tsx</span>
</span><span id="__span-23-2"><a id="__codelineno-23-2" name="__codelineno-23-2" href="#__codelineno-23-2"></a><span class="k">import</span><span class="w"> </span><span class="s1">&#39;grapesjs/dist/css/grapes.min.css&#39;</span><span class="p">;</span>
</span></code></pre></div></p>
</li>
<li>
<p><strong>Check browser console:</strong></p>
</li>
<li>Look for <code>grapesjs</code> variable in global scope</li>
<li>
<p>Verify all plugins loaded successfully</p>
</li>
<li>
<p><strong>Clear cache:</strong>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-24-1"><a id="__codelineno-24-1" name="__codelineno-24-1" href="#__codelineno-24-1"></a><span class="c1"># In browser DevTools</span>
</span><span id="__span-24-2"><a id="__codelineno-24-2" name="__codelineno-24-2" href="#__codelineno-24-2"></a><span class="c1"># Right-click Reload → Empty Cache and Hard Reload</span>
</span></code></pre></div></p>
</li>
</ol>
<hr />
<h3 id="problem-published-page-not-rendering">Problem: Published Page Not Rendering<a class="headerlink" href="#problem-published-page-not-rendering" title="Permanent link">&para;</a></h3>
<p><strong>Symptoms:</strong></p>
<ul>
<li>404 error at <code>/p/my-page</code></li>
<li>Page exists in database, <code>published=true</code></li>
</ul>
<p><strong>Causes:</strong></p>
<ul>
<li>React route not registered</li>
<li>Slug mismatch</li>
<li>Public route mounted incorrectly</li>
</ul>
<p><strong>Solutions:</strong></p>
<ol>
<li>
<p><strong>Verify route registration:</strong>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-25-1"><a id="__codelineno-25-1" name="__codelineno-25-1" href="#__codelineno-25-1"></a><span class="c1">// In admin/src/App.tsx</span>
</span><span id="__span-25-2"><a id="__codelineno-25-2" name="__codelineno-25-2" href="#__codelineno-25-2"></a><span class="o">&lt;</span><span class="nx">Route</span><span class="w"> </span><span class="nx">path</span><span class="o">=</span><span class="s2">&quot;/p/:slug&quot;</span><span class="w"> </span><span class="nx">element</span><span class="o">=</span><span class="p">{</span><span class="o">&lt;</span><span class="nx">LandingPage</span><span class="w"> </span><span class="o">/&gt;</span><span class="p">}</span><span class="w"> </span><span class="o">/&gt;</span>
</span></code></pre></div></p>
</li>
<li>
<p><strong>Check slug in URL:</strong></p>
</li>
<li>Slug is case-sensitive: <code>/p/About-Us</code><code>/p/about-us</code></li>
<li>
<p>Use lowercase, hyphenated: <code>/p/about-us</code></p>
</li>
<li>
<p><strong>Test API directly:</strong>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-26-1"><a id="__codelineno-26-1" name="__codelineno-26-1" href="#__codelineno-26-1"></a>curl<span class="w"> </span>http://localhost:4000/api/pages/about-us/view
</span><span id="__span-26-2"><a id="__codelineno-26-2" name="__codelineno-26-2" href="#__codelineno-26-2"></a><span class="c1"># Should return JSON, not 404</span>
</span></code></pre></div></p>
</li>
<li>
<p><strong>Check published status:</strong>
<div class="language-sql highlight"><pre><span></span><code><span id="__span-27-1"><a id="__codelineno-27-1" name="__codelineno-27-1" href="#__codelineno-27-1"></a><span class="k">SELECT</span><span class="w"> </span><span class="n">slug</span><span class="p">,</span><span class="w"> </span><span class="n">published</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">landing_pages</span><span class="w"> </span><span class="k">WHERE</span><span class="w"> </span><span class="n">slug</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;about-us&#39;</span><span class="p">;</span>
</span><span id="__span-27-2"><a id="__codelineno-27-2" name="__codelineno-27-2" href="#__codelineno-27-2"></a><span class="c1">-- published should be true</span>
</span></code></pre></div></p>
</li>
</ol>
<hr />
<h3 id="problem-mobile-warning-shows-on-desktop">Problem: Mobile Warning Shows on Desktop<a class="headerlink" href="#problem-mobile-warning-shows-on-desktop" title="Permanent link">&para;</a></h3>
<p><strong>Symptoms:</strong></p>
<ul>
<li>"Desktop Required" warning displays on 1920px screen</li>
<li>Editor won't load</li>
</ul>
<p><strong>Causes:</strong></p>
<ul>
<li>Browser window width &lt; 768px</li>
<li>Breakpoint detection failure</li>
<li>DevTools docked (reduces viewport width)</li>
</ul>
<p><strong>Solutions:</strong></p>
<ol>
<li>
<p><strong>Check actual viewport width:</strong>
<div class="language-javascript highlight"><pre><span></span><code><span id="__span-28-1"><a id="__codelineno-28-1" name="__codelineno-28-1" href="#__codelineno-28-1"></a><span class="c1">// In browser console</span>
</span><span id="__span-28-2"><a id="__codelineno-28-2" name="__codelineno-28-2" href="#__codelineno-28-2"></a><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">innerWidth</span><span class="p">);</span>
</span><span id="__span-28-3"><a id="__codelineno-28-3" name="__codelineno-28-3" href="#__codelineno-28-3"></a><span class="c1">// Should be &gt; 768 for desktop</span>
</span></code></pre></div></p>
</li>
<li>
<p><strong>Undock DevTools:</strong></p>
</li>
<li>Press F12 → Click ⋮ (three dots) → Dock to right/bottom → Undock</li>
<li>
<p>Increases available viewport width</p>
</li>
<li>
<p><strong>Verify breakpoint hook:</strong>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-29-1"><a id="__codelineno-29-1" name="__codelineno-29-1" href="#__codelineno-29-1"></a><span class="c1">// In PageEditorPage.tsx</span>
</span><span id="__span-29-2"><a id="__codelineno-29-2" name="__codelineno-29-2" href="#__codelineno-29-2"></a><span class="kd">const</span><span class="w"> </span><span class="nx">screens</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">Grid</span><span class="p">.</span><span class="nx">useBreakpoint</span><span class="p">();</span>
</span><span id="__span-29-3"><a id="__codelineno-29-3" name="__codelineno-29-3" href="#__codelineno-29-3"></a><span class="kd">const</span><span class="w"> </span><span class="nx">isMobile</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">!</span><span class="nx">screens</span><span class="p">.</span><span class="nx">md</span><span class="p">;</span><span class="w"> </span><span class="c1">// md = 768px</span>
</span></code></pre></div></p>
</li>
<li>
<p><strong>Test responsive mode:</strong></p>
</li>
<li>F12 → Toggle device toolbar (Ctrl+Shift+M)</li>
<li>Select "Responsive" → Set width to 1024px</li>
</ol>
<hr />
<h3 id="problem-mkdocs-export-not-found">Problem: MkDocs Export Not Found<a class="headerlink" href="#problem-mkdocs-export-not-found" title="Permanent link">&para;</a></h3>
<p><strong>Symptoms:</strong></p>
<ul>
<li>MkDocs site shows 404 for <code>/pages/about-us/</code></li>
<li>Override file missing from <code>mkdocs/docs/overrides/</code></li>
</ul>
<p><strong>Causes:</strong></p>
<ul>
<li>Page not published</li>
<li><code>mkdocsSkipExport=true</code></li>
<li>Export path incorrect</li>
<li>MkDocs not rebuilt</li>
</ul>
<p><strong>Solutions:</strong></p>
<ol>
<li>
<p><strong>Verify publish status:</strong>
<div class="language-sql highlight"><pre><span></span><code><span id="__span-30-1"><a id="__codelineno-30-1" name="__codelineno-30-1" href="#__codelineno-30-1"></a><span class="k">SELECT</span><span class="w"> </span><span class="n">slug</span><span class="p">,</span><span class="w"> </span><span class="n">published</span><span class="p">,</span><span class="w"> </span><span class="n">mkdocs_skip_export</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">landing_pages</span><span class="w"> </span><span class="k">WHERE</span><span class="w"> </span><span class="n">slug</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;about-us&#39;</span><span class="p">;</span>
</span><span id="__span-30-2"><a id="__codelineno-30-2" name="__codelineno-30-2" href="#__codelineno-30-2"></a><span class="c1">-- Both should be true/false appropriately</span>
</span></code></pre></div></p>
</li>
<li>
<p><strong>Check export path:</strong>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-31-1"><a id="__codelineno-31-1" name="__codelineno-31-1" href="#__codelineno-31-1"></a>ls<span class="w"> </span>-la<span class="w"> </span>mkdocs/docs/overrides/about.html
</span><span id="__span-31-2"><a id="__codelineno-31-2" name="__codelineno-31-2" href="#__codelineno-31-2"></a><span class="c1"># Should exist if published and not skipped</span>
</span></code></pre></div></p>
</li>
<li>
<p><strong>Validate exports:</strong></p>
</li>
<li>Admin → Pages → "Validate Exports" button</li>
<li>
<p>Check repair count</p>
</li>
<li>
<p><strong>Rebuild MkDocs:</strong>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-32-1"><a id="__codelineno-32-1" name="__codelineno-32-1" href="#__codelineno-32-1"></a>docker<span class="w"> </span>compose<span class="w"> </span><span class="nb">exec</span><span class="w"> </span>mkdocs<span class="w"> </span>mkdocs<span class="w"> </span>build
</span><span id="__span-32-2"><a id="__codelineno-32-2" name="__codelineno-32-2" href="#__codelineno-32-2"></a><span class="c1"># Or in admin: Pages → &quot;Build Site&quot;</span>
</span></code></pre></div></p>
</li>
<li>
<p><strong>Check template path in stub:</strong>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-33-1"><a id="__codelineno-33-1" name="__codelineno-33-1" href="#__codelineno-33-1"></a>cat<span class="w"> </span>mkdocs/docs/about.md
</span><span id="__span-33-2"><a id="__codelineno-33-2" name="__codelineno-33-2" href="#__codelineno-33-2"></a><span class="c1"># Should show: template: about.html (NOT overrides/about.html)</span>
</span></code></pre></div></p>
</li>
</ol>
<hr />
<h3 id="problem-slug-collision-on-create">Problem: Slug Collision on Create<a class="headerlink" href="#problem-slug-collision-on-create" title="Permanent link">&para;</a></h3>
<p><strong>Symptoms:</strong></p>
<ul>
<li>Create page with title "About Us" → slug becomes <code>about-us-2</code></li>
<li>Expected <code>about-us</code> but already taken</li>
</ul>
<p><strong>Causes:</strong></p>
<ul>
<li>Existing page with same slug (possibly unpublished)</li>
<li>Soft-deleted page (if soft delete implemented)</li>
</ul>
<p><strong>Solutions:</strong></p>
<ol>
<li>
<p><strong>Check existing pages:</strong>
<div class="language-sql highlight"><pre><span></span><code><span id="__span-34-1"><a id="__codelineno-34-1" name="__codelineno-34-1" href="#__codelineno-34-1"></a><span class="k">SELECT</span><span class="w"> </span><span class="n">id</span><span class="p">,</span><span class="w"> </span><span class="n">title</span><span class="p">,</span><span class="w"> </span><span class="n">slug</span><span class="p">,</span><span class="w"> </span><span class="n">published</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">landing_pages</span><span class="w"> </span><span class="k">WHERE</span><span class="w"> </span><span class="n">slug</span><span class="w"> </span><span class="k">LIKE</span><span class="w"> </span><span class="s1">&#39;about-us%&#39;</span><span class="p">;</span>
</span></code></pre></div></p>
</li>
<li>
<p><strong>Delete duplicate:</strong></p>
</li>
<li>If old page is unwanted: Admin → Pages → Delete</li>
<li>
<p>New page can reuse slug</p>
</li>
<li>
<p><strong>Use unique title:</strong></p>
</li>
<li>
<p>Rename new page: "About Us 2026" → slug <code>about-us-2026</code></p>
</li>
<li>
<p><strong>Manual slug override:</strong></p>
</li>
<li>After create: Edit page → Settings → Override Path → <code>about-us-custom.html</code></li>
</ol>
<hr />
<h3 id="problem-video-block-not-hydrating">Problem: Video Block Not Hydrating<a class="headerlink" href="#problem-video-block-not-hydrating" title="Permanent link">&para;</a></h3>
<p><strong>Symptoms:</strong></p>
<ul>
<li>Video placeholder shows on published page</li>
<li>No player renders</li>
<li>Console error: <code>Invalid video ID: PLACEHOLDER</code></li>
</ul>
<p><strong>Causes:</strong></p>
<ul>
<li><code>data-video-id="PLACEHOLDER"</code> not replaced</li>
<li>Video ID not numeric</li>
<li>Hydration script not running</li>
</ul>
<p><strong>Solutions:</strong></p>
<ol>
<li><strong>Check video ID in editor:</strong></li>
<li>Open GrapesJS editor → Select video block</li>
<li>Properties panel → Video ID field should be numeric (e.g., <code>123</code>)</li>
<li>
<p>Not <code>PLACEHOLDER</code></p>
</li>
<li>
<p><strong>Verify HTML output:</strong>
<div class="language-html highlight"><pre><span></span><code><span id="__span-35-1"><a id="__codelineno-35-1" name="__codelineno-35-1" href="#__codelineno-35-1"></a><span class="cm">&lt;!-- Bad --&gt;</span>
</span><span id="__span-35-2"><a id="__codelineno-35-2" name="__codelineno-35-2" href="#__codelineno-35-2"></a><span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&quot;video-block&quot;</span> <span class="na">data-video-id</span><span class="o">=</span><span class="s">&quot;PLACEHOLDER&quot;</span><span class="p">&gt;</span>...<span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span><span id="__span-35-3"><a id="__codelineno-35-3" name="__codelineno-35-3" href="#__codelineno-35-3"></a>
</span><span id="__span-35-4"><a id="__codelineno-35-4" name="__codelineno-35-4" href="#__codelineno-35-4"></a><span class="cm">&lt;!-- Good --&gt;</span>
</span><span id="__span-35-5"><a id="__codelineno-35-5" name="__codelineno-35-5" href="#__codelineno-35-5"></a><span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&quot;video-block&quot;</span> <span class="na">data-video-id</span><span class="o">=</span><span class="s">&quot;42&quot;</span><span class="p">&gt;</span>...<span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></code></pre></div></p>
</li>
<li>
<p><strong>Check hydration script:</strong>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-36-1"><a id="__codelineno-36-1" name="__codelineno-36-1" href="#__codelineno-36-1"></a><span class="c1">// In LandingPage.tsx</span>
</span><span id="__span-36-2"><a id="__codelineno-36-2" name="__codelineno-36-2" href="#__codelineno-36-2"></a><span class="nx">useEffect</span><span class="p">(()</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-36-3"><a id="__codelineno-36-3" name="__codelineno-36-3" href="#__codelineno-36-3"></a><span class="w"> </span><span class="c1">// Should scan for .video-block elements</span>
</span><span id="__span-36-4"><a id="__codelineno-36-4" name="__codelineno-36-4" href="#__codelineno-36-4"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">videoBlocks</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">contentRef</span><span class="p">.</span><span class="nx">current</span><span class="o">?</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="s1">&#39;.video-block&#39;</span><span class="p">);</span>
</span><span id="__span-36-5"><a id="__codelineno-36-5" name="__codelineno-36-5" href="#__codelineno-36-5"></a><span class="w"> </span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;Found video blocks:&#39;</span><span class="p">,</span><span class="w"> </span><span class="nx">videoBlocks</span><span class="o">?</span><span class="p">.</span><span class="nx">length</span><span class="p">);</span>
</span><span id="__span-36-6"><a id="__codelineno-36-6" name="__codelineno-36-6" href="#__codelineno-36-6"></a><span class="p">},</span><span class="w"> </span><span class="p">[</span><span class="nx">page</span><span class="p">]);</span>
</span></code></pre></div></p>
</li>
<li>
<p><strong>Test video ID validity:</strong>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-37-1"><a id="__codelineno-37-1" name="__codelineno-37-1" href="#__codelineno-37-1"></a>curl<span class="w"> </span>http://localhost:4100/api/media/videos/42
</span><span id="__span-37-2"><a id="__codelineno-37-2" name="__codelineno-37-2" href="#__codelineno-37-2"></a><span class="c1"># Should return video metadata, not 404</span>
</span></code></pre></div></p>
</li>
</ol>
<hr />
<h2 id="performance-considerations">Performance Considerations<a class="headerlink" href="#performance-considerations" title="Permanent link">&para;</a></h2>
<h3 id="editor-initialization">Editor Initialization<a class="headerlink" href="#editor-initialization" title="Permanent link">&para;</a></h3>
<p><strong>GrapesJS startup:</strong> ~500ms on modern desktop</p>
<p><strong>Optimization strategies:</strong></p>
<ul>
<li>Lazy load GrapesJS: <code>const GrapesJS = lazy(() =&gt; import('./GrapesJSEditor'))</code></li>
<li>Show loading spinner during init</li>
<li>Preload on hover over "Edit" button</li>
</ul>
<h3 id="large-pages">Large Pages<a class="headerlink" href="#large-pages" title="Permanent link">&para;</a></h3>
<p><strong>Complexity threshold:</strong> 100+ components</p>
<p><strong>Symptoms:</strong></p>
<ul>
<li>Laggy drag-and-drop</li>
<li>Slow save operations</li>
<li>Canvas rendering delay</li>
</ul>
<p><strong>Mitigations:</strong></p>
<ul>
<li>Break into multiple pages (split hero + sections)</li>
<li>Use CODE mode for complex layouts</li>
<li>Minimize nested components</li>
</ul>
<h3 id="htmloutput-storage">htmlOutput Storage<a class="headerlink" href="#htmloutput-storage" title="Permanent link">&para;</a></h3>
<p><strong>Database overhead:</strong> <code>htmlOutput</code> can be 50KB+ for complex pages</p>
<p><strong>Considerations:</strong></p>
<ul>
<li>Indexed by <code>published</code> for public queries (fast)</li>
<li>Not indexed by content (no full-text search on HTML)</li>
<li>Consider external storage for very large pages (future enhancement)</li>
</ul>
<h3 id="public-page-rendering">Public Page Rendering<a class="headerlink" href="#public-page-rendering" title="Permanent link">&para;</a></h3>
<p><strong>React hydration:</strong> Video blocks hydrate after initial render (~100ms delay)</p>
<p><strong>Performance tips:</strong></p>
<ul>
<li>Use <code>dangerouslySetInnerHTML</code> for immediate HTML paint</li>
<li>Defer video hydration to <code>setTimeout(..., 100)</code></li>
<li>Preload video metadata for above-fold players</li>
</ul>
<hr />
<h2 id="security-considerations">Security Considerations<a class="headerlink" href="#security-considerations" title="Permanent link">&para;</a></h2>
<h3 id="admin-authored-html">Admin-Authored HTML<a class="headerlink" href="#admin-authored-html" title="Permanent link">&para;</a></h3>
<p><strong>Risk:</strong> XSS via malicious HTML in editor</p>
<p><strong>Mitigation:</strong></p>
<ul>
<li><strong>Accepted risk:</strong> Only admins can create/edit pages (trusted users)</li>
<li><strong>No user-supplied content:</strong> Public users cannot edit landing pages</li>
<li><strong>Authentication required:</strong> All write endpoints require admin role</li>
</ul>
<p><strong>Comment in code:</strong></p>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-38-1"><a id="__codelineno-38-1" name="__codelineno-38-1" href="#__codelineno-38-1"></a><span class="c1">// HTML/CSS is admin-authored via GrapesJS editor (not user-submitted content).</span>
</span><span id="__span-38-2"><a id="__codelineno-38-2" name="__codelineno-38-2" href="#__codelineno-38-2"></a><span class="c1">// Only authenticated admins can create/edit pages, so XSS risk is accepted.</span>
</span><span id="__span-38-3"><a id="__codelineno-38-3" name="__codelineno-38-3" href="#__codelineno-38-3"></a><span class="k">return</span><span class="w"> </span><span class="o">&lt;</span><span class="nx">div</span><span class="w"> </span><span class="nx">dangerouslySetInnerHTML</span><span class="o">=</span><span class="p">{{</span><span class="w"> </span><span class="nx">__html</span><span class="o">:</span><span class="w"> </span><span class="kt">page.htmlOutput</span><span class="w"> </span><span class="p">}}</span><span class="w"> </span><span class="o">/&gt;</span><span class="p">;</span>
</span></code></pre></div>
<h3 id="slug-validation">Slug Validation<a class="headerlink" href="#slug-validation" title="Permanent link">&para;</a></h3>
<p><strong>Attack vector:</strong> Path traversal via slug injection</p>
<p><strong>Protection:</strong></p>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-39-1"><a id="__codelineno-39-1" name="__codelineno-39-1" href="#__codelineno-39-1"></a><span class="kd">function</span><span class="w"> </span><span class="nx">generateSlug</span><span class="p">(</span><span class="nx">title</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-39-2"><a id="__codelineno-39-2" name="__codelineno-39-2" href="#__codelineno-39-2"></a><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">title</span>
</span><span id="__span-39-3"><a id="__codelineno-39-3" name="__codelineno-39-3" href="#__codelineno-39-3"></a><span class="w"> </span><span class="p">.</span><span class="nx">toLowerCase</span><span class="p">()</span>
</span><span id="__span-39-4"><a id="__codelineno-39-4" name="__codelineno-39-4" href="#__codelineno-39-4"></a><span class="w"> </span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/[^a-z0-9]+/g</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;-&#39;</span><span class="p">)</span><span class="w"> </span><span class="c1">// Alphanumeric + hyphens only</span>
</span><span id="__span-39-5"><a id="__codelineno-39-5" name="__codelineno-39-5" href="#__codelineno-39-5"></a><span class="w"> </span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/^-+|-+$/g</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;&#39;</span><span class="p">)</span><span class="w"> </span><span class="c1">// Trim leading/trailing hyphens</span>
</span><span id="__span-39-6"><a id="__codelineno-39-6" name="__codelineno-39-6" href="#__codelineno-39-6"></a><span class="w"> </span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mf">0</span><span class="p">,</span><span class="w"> </span><span class="mf">80</span><span class="p">);</span><span class="w"> </span><span class="c1">// Max 80 chars</span>
</span><span id="__span-39-7"><a id="__codelineno-39-7" name="__codelineno-39-7" href="#__codelineno-39-7"></a><span class="p">}</span>
</span></code></pre></div>
<p><strong>Safe slugs:</strong> <code>about-us</code>, <code>campaign-2026</code>, <code>contact</code></p>
<p><strong>Rejected:</strong> <code>../etc/passwd</code>, <code>&lt;script&gt;alert(1)&lt;/script&gt;</code>, <code>../../admin</code></p>
<h3 id="mkdocs-path-validation">MkDocs Path Validation<a class="headerlink" href="#mkdocs-path-validation" title="Permanent link">&para;</a></h3>
<p><strong>Attack vector:</strong> Write arbitrary files via path traversal in <code>mkdocsPath</code></p>
<p><strong>Protection:</strong></p>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-40-1"><a id="__codelineno-40-1" name="__codelineno-40-1" href="#__codelineno-40-1"></a><span class="kd">function</span><span class="w"> </span><span class="nx">validateMkdocsPath</span><span class="p">(</span><span class="nx">mkdocsPath</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="o">:</span><span class="w"> </span><span class="ow">void</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-40-2"><a id="__codelineno-40-2" name="__codelineno-40-2" href="#__codelineno-40-2"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">mkdocsPath</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="s1">&#39;\0&#39;</span><span class="p">))</span><span class="w"> </span><span class="k">throw</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="ne">Error</span><span class="p">(</span><span class="s1">&#39;Null byte detected&#39;</span><span class="p">);</span>
</span><span id="__span-40-3"><a id="__codelineno-40-3" name="__codelineno-40-3" href="#__codelineno-40-3"></a>
</span><span id="__span-40-4"><a id="__codelineno-40-4" name="__codelineno-40-4" href="#__codelineno-40-4"></a><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">normalized</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">path</span><span class="p">.</span><span class="nx">normalize</span><span class="p">(</span><span class="nx">mkdocsPath</span><span class="p">);</span>
</span><span id="__span-40-5"><a id="__codelineno-40-5" name="__codelineno-40-5" href="#__codelineno-40-5"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">normalized</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="s1">&#39;..&#39;</span><span class="p">)</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="nx">path</span><span class="p">.</span><span class="nx">isAbsolute</span><span class="p">(</span><span class="nx">normalized</span><span class="p">))</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-40-6"><a id="__codelineno-40-6" name="__codelineno-40-6" href="#__codelineno-40-6"></a><span class="w"> </span><span class="k">throw</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="ne">Error</span><span class="p">(</span><span class="s1">&#39;Path traversal not allowed&#39;</span><span class="p">);</span>
</span><span id="__span-40-7"><a id="__codelineno-40-7" name="__codelineno-40-7" href="#__codelineno-40-7"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-40-8"><a id="__codelineno-40-8" name="__codelineno-40-8" href="#__codelineno-40-8"></a>
</span><span id="__span-40-9"><a id="__codelineno-40-9" name="__codelineno-40-9" href="#__codelineno-40-9"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">mkdocsPath</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="s1">&#39;%2e&#39;</span><span class="p">)</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="nx">mkdocsPath</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="s1">&#39;%2E&#39;</span><span class="p">))</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-40-10"><a id="__codelineno-40-10" name="__codelineno-40-10" href="#__codelineno-40-10"></a><span class="w"> </span><span class="k">throw</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="ne">Error</span><span class="p">(</span><span class="s1">&#39;Encoded path traversal not allowed&#39;</span><span class="p">);</span>
</span><span id="__span-40-11"><a id="__codelineno-40-11" name="__codelineno-40-11" href="#__codelineno-40-11"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-40-12"><a id="__codelineno-40-12" name="__codelineno-40-12" href="#__codelineno-40-12"></a>
</span><span id="__span-40-13"><a id="__codelineno-40-13" name="__codelineno-40-13" href="#__codelineno-40-13"></a><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="nx">mkdocsPath</span><span class="p">.</span><span class="nx">endsWith</span><span class="p">(</span><span class="s1">&#39;.html&#39;</span><span class="p">))</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-40-14"><a id="__codelineno-40-14" name="__codelineno-40-14" href="#__codelineno-40-14"></a><span class="w"> </span><span class="k">throw</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="ne">Error</span><span class="p">(</span><span class="s1">&#39;Path must end with .html&#39;</span><span class="p">);</span>
</span><span id="__span-40-15"><a id="__codelineno-40-15" name="__codelineno-40-15" href="#__codelineno-40-15"></a><span class="w"> </span><span class="p">}</span>
</span><span id="__span-40-16"><a id="__codelineno-40-16" name="__codelineno-40-16" href="#__codelineno-40-16"></a><span class="p">}</span>
</span></code></pre></div>
<p><strong>Safe paths:</strong> <code>about.html</code>, <code>pages/contact.html</code></p>
<p><strong>Rejected:</strong> <code>../../../etc/passwd.html</code>, <code>/etc/shadow.html</code>, <code>%2e%2e/admin.html</code></p>
<h3 id="published-flag-enforcement">Published Flag Enforcement<a class="headerlink" href="#published-flag-enforcement" title="Permanent link">&para;</a></h3>
<p><strong>Attack vector:</strong> Access draft pages via public route</p>
<p><strong>Protection:</strong></p>
<div class="language-typescript highlight"><pre><span></span><code><span id="__span-41-1"><a id="__codelineno-41-1" name="__codelineno-41-1" href="#__codelineno-41-1"></a><span class="c1">// In pagesService.findBySlugPublic()</span>
</span><span id="__span-41-2"><a id="__codelineno-41-2" name="__codelineno-41-2" href="#__codelineno-41-2"></a><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="nx">page</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="o">!</span><span class="nx">page</span><span class="p">.</span><span class="nx">published</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-41-3"><a id="__codelineno-41-3" name="__codelineno-41-3" href="#__codelineno-41-3"></a><span class="w"> </span><span class="k">throw</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="nx">AppError</span><span class="p">(</span><span class="mf">404</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;Page not found&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;PAGE_NOT_FOUND&#39;</span><span class="p">);</span>
</span><span id="__span-41-4"><a id="__codelineno-41-4" name="__codelineno-41-4" href="#__codelineno-41-4"></a><span class="p">}</span>
</span></code></pre></div>
<p><strong>Behavior:</strong></p>
<ul>
<li>Unpublished pages return 404 on public route</li>
<li>Admin routes bypass check (can view drafts)</li>
</ul>
<hr />
<h2 id="related-documentation">Related Documentation<a class="headerlink" href="#related-documentation" title="Permanent link">&para;</a></h2>
<h3 id="frontend-components">Frontend Components<a class="headerlink" href="#frontend-components" title="Permanent link">&para;</a></h3>
<ul>
<li><strong><a href="/v2/frontend/pages/LandingPageEditor">LandingPageEditor</a></strong> — Full-screen editor wrapper</li>
<li><strong><a href="/v2/frontend/pages/LandingPagesPage">LandingPagesPage</a></strong> — Table view + CRUD</li>
<li><strong><a href="/v2/frontend/components/GrapesJSEditor">GrapesJSEditor</a></strong> — GrapesJS wrapper with forwardRef</li>
<li><strong><a href="/v2/frontend/pages/public/LandingPage">PublicLandingPage</a></strong> — Public page renderer</li>
</ul>
<h3 id="backend-modules">Backend Modules<a class="headerlink" href="#backend-modules" title="Permanent link">&para;</a></h3>
<ul>
<li><strong><a href="/v2/backend/modules/pages/pages-admin.routes">pages-admin.routes</a></strong> — Admin CRUD endpoints</li>
<li><strong><a href="/v2/backend/modules/pages/pages-public.routes">pages-public.routes</a></strong> — Public view endpoint</li>
<li><strong><a href="/v2/backend/modules/pages/pages.service">pages.service</a></strong> — Business logic + MkDocs export</li>
<li><strong><a href="/v2/backend/modules/pages/pages.schemas">pages.schemas</a></strong> — Zod validation schemas</li>
</ul>
<h3 id="database">Database<a class="headerlink" href="#database" title="Permanent link">&para;</a></h3>
<ul>
<li><strong><a href="/v2/database/models/pages">LandingPage Model</a></strong> — Schema + relationships</li>
<li><strong><a href="/v2/database/models/pages">PageBlock Model</a></strong> — Block library schema</li>
</ul>
<h3 id="feature-documentation">Feature Documentation<a class="headerlink" href="#feature-documentation" title="Permanent link">&para;</a></h3>
<ul>
<li><strong><a href="../grapes-editor/">GrapesJS Editor Integration</a></strong> — forwardRef pattern + custom blocks</li>
<li><strong><a href="../block-library/">Block Library</a></strong> — Reusable components system</li>
<li><strong><a href="../mkdocs-export/">MkDocs Export</a></strong> — Material theme integration</li>
</ul>
<h3 id="external-resources">External Resources<a class="headerlink" href="#external-resources" title="Permanent link">&para;</a></h3>
<ul>
<li><a href="https://grapesjs.com/docs/">GrapesJS Documentation</a> — Official editor docs</li>
<li><a href="https://grapesjs.com/docs/plugins/">GrapesJS Plugins</a> — Available plugins</li>
<li><a href="https://squidfunk.github.io/mkdocs-material/">MkDocs Material</a> — Theme docs</li>
<li><a href="https://jinja.palletsprojects.com/">Jinja2 Templates</a> — Template syntax</li>
</ul>
</article>
</div>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
<button type="button" class="md-top md-icon" data-md-component="top" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8z"/></svg>
Back to top
</button>
</main>
<footer class="md-footer">
<nav class="md-footer__inner md-grid" aria-label="Footer" >
<a href="../../landing-pages/" class="md-footer__link md-footer__link--prev" aria-label="Previous: Landing Pages (Page Builder)">
<div class="md-footer__button md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</div>
<div class="md-footer__title">
<span class="md-footer__direction">
Previous
</span>
<div class="md-ellipsis">
Landing Pages (Page Builder)
</div>
</div>
</a>
<a href="../grapes-editor/" class="md-footer__link md-footer__link--next" aria-label="Next: GrapesJS Editor">
<div class="md-footer__title">
<span class="md-footer__direction">
Next
</span>
<div class="md-ellipsis">
GrapesJS Editor
</div>
</div>
<div class="md-footer__button md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M4 11v2h12l-5.5 5.5 1.42 1.42L19.84 12l-7.92-7.92L10.5 5.5 16 11z"/></svg>
</div>
</a>
</nav>
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
<div class="md-copyright__highlight">
Copyright &copy; 2024 The Bunker Operations <a href="#__consent">Change cookie settings</a>
</div>
</div>
<div class="md-social">
<a href="https://gitea.bnkops.com/admin" target="_blank" rel="noopener" title="Gitea Repository" class="md-social__link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M173.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6m-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3m44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9M252.8 8C114.1 8 8 113.3 8 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C436.2 457.8 504 362.9 504 252 504 113.3 391.5 8 252.8 8M105.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1m-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7m32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1m-11.4-14.7c-1.6 1-1.6 3.6 0 5.9s4.3 3.3 5.6 2.3c1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2"/></svg>
</a>
<a href="https://listmonk.bnkops.com/subscription/form" target="_blank" rel="noopener" title="Newsletter" class="md-social__link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M536.4-26.3c9.8-3.5 20.6-1 28 6.3s9.8 18.2 6.3 28l-178 496.9c-5 13.9-18.1 23.1-32.8 23.1-14.2 0-27-8.6-32.3-21.7l-64.2-158c-4.5-11-2.5-23.6 5.2-32.6l94.5-112.4c5.1-6.1 4.7-15-.9-20.6s-14.6-6-20.6-.9l-112.4 94.3c-9.1 7.6-21.6 9.6-32.6 5.2L38.1 216.8c-13.1-5.3-21.7-18.1-21.7-32.3 0-14.7 9.2-27.8 23.1-32.8z"/></svg>
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"annotate": null, "base": "../../../..", "features": ["announce.dismiss", "content.action.edit", "content.action.view", "content.code.annotate", "content.code.copy", "content.tooltips", "navigation.expand", "navigation.footer", "navigation.indexes", "navigation.path", "navigation.prune", "navigation.sections", "navigation.tabs", "navigation.tabs.sticky", "navigation.top", "navigation.tracking", "search.highlight", "search.share", "search.suggest", "toc.follow"], "search": "../../../../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
<script src="../../../../assets/javascripts/bundle.79ae519e.min.js"></script>
<script src="../../../../javascripts/home.js"></script>
<script src="../../../../javascripts/github-widget.js"></script>
<script src="../../../../javascripts/gitea-widget.js"></script>
<script src="../../../../assets/js/env-config.js"></script>
<script src="../../../../assets/js/video-player.js"></script>
</body>
</html>