Просмотр исходного кода

docs: add Layout Blueprint, Recipes, and Deployment pages

Layout Blueprint: explains the .app-wrapper / .app-header / .app-sidebar
/ .app-main / .app-footer regions with an ASCII diagram, region-by-
region reference, body-level layout modifier table, responsive
behaviour notes, and common pitfalls. This is the page that was
missing for developers asking "how does the page structure actually
work?".

Recipes: copy-paste solutions for the patterns that appear in every
admin dashboard -- multi-level sidebar with badges + headers, active
state highlighting from JS, responsive tables, user dropdown,
small-box KPI cards, breadcrumbs, timelines, direct-chat widget,
chart-resize-on-sidebar-collapse hook, fetch-into-card, and a
confirm-destructive-action modal.

Deployment: production-ready guidance covering minified vs dev
builds, compression, cache headers, CDN integrity hashes, script
loading order (the failure mode for getting it wrong was previously
undocumented), defer vs async, critical CSS, preload hints,
service-worker offline support, and rsync excludes that strip
source maps and the Astro intermediate output.
Aigars Silkalns 12 часов назад
Родитель
Сommit
9460ccab34

+ 225 - 0
src/html/components/docs/deployment.mdx

@@ -0,0 +1,225 @@
+AdminLTE is a static HTML/CSS/JS template — there's nothing exotic about deploying it. But there are a handful of choices that meaningfully change page weight, time-to-interactive, and CDN reliability. This page documents the ones worth getting right.
+
+##### What to deploy
+
+The deployable artefact is everything under `dist/` plus your own HTML / server-rendered templates:
+
+```text
+dist/
+├── css/                ← stylesheets (LTR + RTL, dev + minified)
+│   ├── adminlte.css
+│   ├── adminlte.min.css
+│   ├── adminlte.rtl.css
+│   └── adminlte.rtl.min.css
+├── js/                 ← JavaScript bundle (dev + minified)
+│   ├── adminlte.js
+│   └── adminlte.min.js
+└── assets/             ← demo images / fonts (replace with your own)
+```
+
+You don't ship `src/`, `node_modules/`, or the demo HTML if you're embedding AdminLTE into your own app.
+
+##### Always use the `.min` variants in production
+
+The minified builds are the same code with whitespace stripped and identifiers shortened. For `adminlte.css` the saving is roughly 15-20%; for `adminlte.js` it's bigger (about 30% after gzip). The non-minified files are useful in development for readable source-map debugging — never serve them to users.
+
+| Asset | Dev | Production |
+|---|---|---|
+| Stylesheet | `dist/css/adminlte.css` | **`dist/css/adminlte.min.css`** |
+| Script | `dist/js/adminlte.js` | **`dist/js/adminlte.min.js`** |
+| RTL stylesheet | `dist/css/adminlte.rtl.css` | **`dist/css/adminlte.rtl.min.css`** |
+
+##### Compression at the edge
+
+Make sure your web server / CDN gzips (or brotlis) responses for the relevant content types:
+
+```nginx
+# nginx
+gzip on;
+gzip_types text/css application/javascript text/html image/svg+xml;
+gzip_comp_level 6;
+```
+
+```apache
+# Apache
+AddOutputFilterByType DEFLATE text/css application/javascript text/html image/svg+xml
+```
+
+Cloudflare, Vercel, Netlify, and most managed hosting do this automatically. Verify with `curl -I -H "Accept-Encoding: gzip" your-page-url` and look for `content-encoding: gzip` in the response.
+
+##### Cache headers
+
+The dist artefacts have content hashes in their filenames *only if you wire it up via your bundler* — AdminLTE itself ships fixed filenames. Two reasonable strategies:
+
+1. **Short cache + version query string** (simple, works without a build pipeline):
+
+   ```html
+   <link rel="stylesheet" href="/dist/css/adminlte.min.css?v=4.0.0" />
+   <script src="/dist/js/adminlte.min.js?v=4.0.0"></script>
+   ```
+
+   Set `Cache-Control: public, max-age=86400` (1 day) at the server. Bump `?v=` on every release to force cache invalidation.
+
+2. **Fingerprinted filenames** (production-grade, requires a bundler):
+
+   Let your bundler emit `adminlte.[hash].min.css` and reference that hash in your HTML. Set `Cache-Control: public, max-age=31536000, immutable` (1 year). This is what Vite/Webpack/Rollup do by default if you import AdminLTE through them.
+
+##### CDN integrity hashes
+
+If you load AdminLTE from a public CDN (jsDelivr, unpkg), include a Subresource Integrity (SRI) hash so a compromised CDN can't serve malicious JavaScript silently:
+
+```html
+<link
+  rel="stylesheet"
+  href="https://cdn.jsdelivr.net/npm/admin-lte@4.0.0-rc7/dist/css/adminlte.min.css"
+  integrity="sha384-PASTE_THE_HASH_HERE"
+  crossorigin="anonymous"
+/>
+```
+
+You can get the hash from the [jsDelivr file detail page](https://www.jsdelivr.com/package/npm/admin-lte) — click any file and copy the "SRI" line. Do the same for Bootstrap, Popper, OverlayScrollbars, and Bootstrap Icons.
+
+For private-hosted assets, generate the hash yourself:
+
+```bash
+openssl dgst -sha384 -binary dist/css/adminlte.min.css | openssl base64 -A
+```
+
+##### Loading order matters
+
+Get the script order right or things break silently:
+
+```html
+<head>
+  <!-- 1. Bootstrap Icons (font CSS) — load first so icons render with the page -->
+  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.min.css" />
+
+  <!-- 2. OverlayScrollbars (optional, for the sidebar scroller) -->
+  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/overlayscrollbars@2.11.0/styles/overlayscrollbars.min.css" />
+
+  <!-- 3. AdminLTE — includes Bootstrap CSS via its imports -->
+  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/admin-lte@4.0.0-rc7/dist/css/adminlte.min.css" />
+</head>
+
+<body>
+  …
+
+  <!-- Scripts at the END of body -->
+  <!-- 4. Popper (Bootstrap dependency for dropdowns/tooltips/popovers) -->
+  <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js"></script>
+
+  <!-- 5. Bootstrap -->
+  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.min.js"></script>
+
+  <!-- 6. OverlayScrollbars -->
+  <script src="https://cdn.jsdelivr.net/npm/overlayscrollbars@2.11.0/browser/overlayscrollbars.browser.es6.min.js"></script>
+
+  <!-- 7. AdminLTE — must load AFTER Bootstrap -->
+  <script src="https://cdn.jsdelivr.net/npm/admin-lte@4.0.0-rc7/dist/js/adminlte.min.js"></script>
+</body>
+```
+
+##### Defer or async?
+
+The AdminLTE bundle waits for `DOMContentLoaded` before wiring its data API, so `defer` is safe and recommended:
+
+```html
+<script defer src="/dist/js/adminlte.min.js"></script>
+```
+
+Don't use `async` — the script could execute before its dependencies (Bootstrap, Popper) have parsed, and listeners would attach to elements that don't exist yet.
+
+##### Critical CSS
+
+For sub-1-second first paint, inline the styles that the above-the-fold layout uses (header + brand + first visible content) and load the rest of `adminlte.min.css` asynchronously:
+
+```html
+<style>/* extracted critical CSS for /dashboard */</style>
+
+<link
+  rel="preload"
+  as="style"
+  href="/dist/css/adminlte.min.css"
+  onload="this.onload=null;this.rel='stylesheet'"
+/>
+<noscript><link rel="stylesheet" href="/dist/css/adminlte.min.css" /></noscript>
+```
+
+Extract critical CSS with a tool like [Critters](https://github.com/GoogleChromeLabs/critters) or [Penthouse](https://github.com/pocketjoso/penthouse) as part of your build. For dashboards behind a login (where first-load speed matters less than for marketing pages), this is usually overkill.
+
+##### Preload key assets
+
+Push critical resources to the browser before it discovers them in the HTML:
+
+```html
+<link rel="preload" as="style" href="/dist/css/adminlte.min.css" />
+<link rel="preload" as="font" href="/fonts/source-sans-3.woff2" type="font/woff2" crossorigin />
+<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
+```
+
+Only preload what's used on every page. Preloading per-route assets in a shared layout wastes bandwidth.
+
+##### Lazy-loading sub-pages
+
+If your dashboard has many routes and the JS is server-rendered HTML per route, prefer per-route CSS imports rather than one mega-stylesheet. AdminLTE's core CSS is small (~44KB gzipped) but page-specific styles (charts, calendars, tables) can add up — only load them where they're used.
+
+For SPAs, code-split heavy integrations:
+
+```js
+// Don't bundle the calendar app into the main chunk
+const Calendar = () => import("./Calendar.vue")
+```
+
+##### Bundle size budget
+
+The current published `dist/` artefacts gzip to:
+
+| Asset | Size (gzip) |
+|---|---|
+| `adminlte.min.css` | ~40 KB |
+| `adminlte.rtl.min.css` | ~40 KB |
+| `adminlte.min.js` | ~5 KB |
+
+The full first-page payload (HTML + Bootstrap + AdminLTE + Bootstrap Icons + Popper + OverlayScrollbars) is around **180 KB gzipped** when loaded from CDN. The CI build enforces a `bundlewatch` check that fails if any asset crosses its budget.
+
+##### Service workers / offline support
+
+AdminLTE is just static files — caching them in a service worker works without changes. A minimal Workbox config:
+
+```js
+import { precacheAndRoute } from "workbox-precaching"
+
+precacheAndRoute([
+  { url: "/dist/css/adminlte.min.css", revision: "4.0.0" },
+  { url: "/dist/js/adminlte.min.js", revision: "4.0.0" },
+  // ...your own pages
+])
+```
+
+##### Removing dev-only files before deploy
+
+If you're deploying the demo `dist/` folder verbatim, strip these patterns from the upload:
+
+- `*.map` (source maps — leak source code to anyone who opens devtools)
+- `**/html/` (the Astro intermediate build, if present)
+- `**/.astro/` (Astro cache)
+
+For `rclone` / `rsync`:
+
+```bash
+rsync -avz --delete \
+  --exclude='*.map' \
+  --exclude='html/' \
+  --exclude='.astro/' \
+  dist/ user@server:/var/www/your-app/
+```
+
+##### A note on Cloudflare cache
+
+Cloudflare's default Browser Cache TTL is 4 hours. If you push a new release and want users to see it immediately, [purge the affected paths](https://developers.cloudflare.com/cache/how-to/purge-cache/) from the Cloudflare dashboard or API. Otherwise, users on cached pages will see your old CSS / JS for up to four hours.
+
+##### Where to next
+
+- [Customization](customization.html) — change colours, sidebar width, breakpoints before you deploy
+- [Recommended Integrations](integrations.html) — pick lightweight libraries for the things AdminLTE doesn't bundle
+- [Accessibility](javascript/accessibility.html) — make sure your deployed pages keep their a11y guarantees

+ 161 - 0
src/html/components/docs/layout-blueprint.mdx

@@ -0,0 +1,161 @@
+Every AdminLTE page follows the same structural blueprint: a wrapper that contains a header, a sidebar, a main content area, and (optionally) a footer. Understanding the four region classes is the difference between fighting the framework and using it productively.
+
+##### The four regions
+
+```text
+┌──────────────────────────────────────────┐
+│  .app-wrapper                            │
+│  ┌────────────────────────────────────┐  │
+│  │  .app-header                       │  │ ← topbar / navbar
+│  ├──────────┬─────────────────────────┤  │
+│  │          │                         │  │
+│  │  .app-   │   .app-main             │  │ ← page content
+│  │ sidebar  │                         │  │
+│  │          │                         │  │
+│  ├──────────┴─────────────────────────┤  │
+│  │  .app-footer (optional)            │  │
+│  └────────────────────────────────────┘  │
+└──────────────────────────────────────────┘
+```
+
+The wrapper is a CSS grid container, the four children are grid areas. You don't have to know that to use it — the markup pattern alone is sufficient.
+
+##### Minimal markup
+
+```html
+<body class="layout-fixed sidebar-expand-lg bg-body-tertiary">
+  <div class="app-wrapper">
+    <nav class="app-header navbar navbar-expand bg-body">…</nav>
+    <aside class="app-sidebar bg-body-secondary shadow" data-bs-theme="dark">…</aside>
+    <main class="app-main">…</main>
+    <footer class="app-footer">…</footer>
+  </div>
+</body>
+```
+
+Order inside `.app-wrapper` doesn't matter — the grid template positions each child by its class. Use whatever order reads cleanly.
+
+##### Region reference
+
+###### `.app-wrapper`
+
+The root container. Defines the grid template that positions header, sidebar, main, and footer. Always lives directly inside `<body>`.
+
+You can add `.compact-mode` here (or anywhere up the tree) to reduce padding throughout the layout — useful for data-dense dashboards.
+
+###### `.app-header`
+
+The top bar. Use Bootstrap's navbar classes (`navbar navbar-expand bg-body`) for the visual. The header is where the sidebar toggle button lives — wire it with `data-lte-toggle="sidebar"`:
+
+```html
+<a class="nav-link" data-lte-toggle="sidebar" href="#" role="button">
+  <i class="bi bi-list"></i>
+</a>
+```
+
+By default the header scrolls with the page. Add `.fixed-header` to `<body>` to pin it.
+
+###### `.app-sidebar`
+
+The side rail. Always contains two children: `.sidebar-brand` (logo / app name) and `.sidebar-wrapper` (the scrollable nav region).
+
+```html
+<aside class="app-sidebar bg-body-secondary shadow" data-bs-theme="dark">
+  <div class="sidebar-brand">
+    <a href="/" class="brand-link">
+      <span class="brand-text fw-light">My Dashboard</span>
+    </a>
+  </div>
+  <div class="sidebar-wrapper">
+    <nav class="mt-2">
+      <ul class="nav sidebar-menu flex-column" data-lte-toggle="treeview" role="menu">
+        <!-- nav items -->
+      </ul>
+    </nav>
+  </div>
+</aside>
+```
+
+The `data-bs-theme="dark"` on the sidebar gives you the dark-sidebar-on-light-page look from the demos. Drop it for a light sidebar.
+
+###### `.app-main`
+
+The page content region. Almost always contains two children:
+
+```html
+<main class="app-main">
+  <div class="app-content-header">
+    <!-- page title + breadcrumb -->
+  </div>
+  <div class="app-content">
+    <!-- page body, cards, tables, charts, etc. -->
+  </div>
+</main>
+```
+
+`.app-content-header` is the title strip — typically an `<h3>` and a breadcrumb in a row. `.app-content` is everything below it. Both should wrap their children in `.container-fluid` (or `.container` for capped width).
+
+###### `.app-footer` (optional)
+
+A footer strip at the bottom of the wrapper:
+
+```html
+<footer class="app-footer">
+  <div class="float-end d-none d-sm-inline">Version 4.0.0</div>
+  <strong>&copy; 2026 Your Company</strong>
+</footer>
+```
+
+Add `.fixed-footer` to `<body>` to pin it to the bottom of the viewport.
+
+##### Body-level layout modifiers
+
+Layout behaviour is mostly controlled by classes on `<body>`, not `.app-wrapper`. The supported modifiers:
+
+| Class | What it does |
+|---|---|
+| `layout-fixed` | Sidebar gets its own scrollbar; only `.app-main` scrolls with the page |
+| `fixed-header` | Sticky `.app-header` at the top; sidebar also pins (since v4.0.0) |
+| `fixed-footer` | Sticky `.app-footer` at the bottom |
+| `sidebar-expand-{sm,md,lg,xl,xxl}` | Breakpoint at which the sidebar transitions from off-canvas (mobile) to inline (desktop). `lg` is the default in the demos. |
+| `sidebar-mini` | Collapses sidebar to icon-only mini state |
+| `sidebar-mini sidebar-collapse` | Starts the page with the mini sidebar already collapsed |
+| `sidebar-without-hover` | Disables the auto-expand-on-hover behaviour for the mini sidebar |
+| `layout-rtl` | Mirrors the layout for right-to-left languages (also requires `.rtl.css` and `dir="rtl"` — see [RTL Support](rtl.html)) |
+
+These compose. The demo dashboards use `class="layout-fixed sidebar-expand-lg bg-body-tertiary"` — fixed-height with a desktop sidebar breakpoint at the `lg` (≥992px) boundary.
+
+###### Layout variants demo
+
+The Layout section of the demo site (`/layout/*.html` files) ships eleven pre-built combinations:
+
+- [Fixed Sidebar](../layout/fixed-sidebar.html), [Fixed Header](../layout/fixed-header.html), [Fixed Footer](../layout/fixed-footer.html), [Fixed Complete](../layout/fixed-complete.html)
+- [Unfixed Sidebar](../layout/unfixed-sidebar.html), [Sidebar Mini](../layout/sidebar-mini.html), [Collapsed Sidebar](../layout/collapsed-sidebar.html), [Collapsed without hover](../layout/collapsed-sidebar-without-hover.html)
+- [Layout Custom Area](../layout/layout-custom-area.html), [Logo Switch](../layout/logo-switch.html), [Layout RTL](../layout/layout-rtl.html)
+
+##### Responsive behaviour
+
+At and above your `sidebar-expand-*` breakpoint, the sidebar is **inline** — visible in the grid alongside `.app-main`. Below the breakpoint, it becomes **off-canvas** — hidden by default and slid in from the left when the user clicks the hamburger toggle.
+
+Mobile-specific classes:
+
+| Class | When applied | Effect |
+|---|---|---|
+| `sidebar-open` | Body — when the user explicitly opens the mobile sidebar | Slides the sidebar in over the content with an overlay |
+| `sidebar-collapse` | Body — set automatically when the user closes the mobile sidebar | Hides the sidebar |
+
+These are managed by the [PushMenu plugin](javascript/pushmenu.html) — you don't write them yourself.
+
+##### Common pitfalls
+
+- **Forgetting `.container-fluid` inside `.app-content`** — the content lands flush against the screen edge. Wrap your content cards in a container.
+- **Putting the sidebar toggle outside `.app-header`** — it'll still work, but the styling expects the navbar context.
+- **Mixing `.app-*` and `.main-*` classes** — `.main-*` is the AdminLTE 3 naming. Don't combine them. See [Migration from v3](migration.html).
+- **Using `position: fixed` on something inside `.app-main`** — `layout-fixed` already gives `.app-main` `overflow: auto`. Children with `position: fixed` may behave unexpectedly. Use sticky positioning instead.
+- **Hardcoding sidebar width in custom CSS** — the sidebar width is exposed via the SCSS variable `$lte-sidebar-width`. If you need to know it from JavaScript, read the rendered `.app-sidebar` element's computed width.
+
+##### Where to next
+
+- [Layout reference](layout.html) — the lower-level CSS classes and what they do
+- [PushMenu plugin](javascript/pushmenu.html) — the sidebar toggle implementation
+- [Customization](customization.html) — how to change sidebar width, breakpoint, and colours

+ 350 - 0
src/html/components/docs/recipes.mdx

@@ -0,0 +1,350 @@
+Copy-paste solutions for the patterns that show up in every admin dashboard. Each recipe assumes you've followed [Getting Started](getting-started.html) and have a working AdminLTE page to build on.
+
+##### A multi-level sidebar with icons, badges, and headers
+
+The demo dashboards use this exact pattern. Group sections with `.nav-header`, indicate item depth with nested `.nav-treeview` lists, and use `.navbar-badge` for counts.
+
+```html
+<aside class="app-sidebar bg-body-secondary shadow" data-bs-theme="dark">
+  <div class="sidebar-brand">
+    <a href="/" class="brand-link">
+      <span class="brand-text fw-light">My Dashboard</span>
+    </a>
+  </div>
+  <div class="sidebar-wrapper">
+    <nav class="mt-2">
+      <ul class="nav sidebar-menu flex-column" data-lte-toggle="treeview" role="menu">
+
+        <li class="nav-header">MAIN</li>
+        <li class="nav-item">
+          <a href="/" class="nav-link active">
+            <i class="nav-icon bi bi-speedometer"></i>
+            <p>Dashboard</p>
+          </a>
+        </li>
+
+        <li class="nav-item">
+          <a href="#" class="nav-link">
+            <i class="nav-icon bi bi-folder"></i>
+            <p>
+              Projects
+              <span class="badge text-bg-info ms-2">12</span>
+              <i class="nav-arrow bi bi-chevron-right"></i>
+            </p>
+          </a>
+          <ul class="nav nav-treeview">
+            <li class="nav-item">
+              <a href="#" class="nav-link"><i class="nav-icon bi bi-circle"></i><p>Active</p></a>
+            </li>
+            <li class="nav-item">
+              <a href="#" class="nav-link"><i class="nav-icon bi bi-circle"></i><p>Archived</p></a>
+            </li>
+          </ul>
+        </li>
+
+        <li class="nav-header">REPORTS</li>
+        <li class="nav-item">
+          <a href="#" class="nav-link">
+            <i class="nav-icon bi bi-bar-chart"></i>
+            <p>Sales</p>
+            <span class="badge text-bg-danger ms-auto">3</span>
+          </a>
+        </li>
+
+      </ul>
+    </nav>
+  </div>
+</aside>
+```
+
+To mark the current page as active, server-render the `.active` class onto the matching `.nav-link`. For SPA frameworks, add it on route change.
+
+##### Highlighting the active sidebar item from JavaScript
+
+If you're rendering the sidebar once and want client-side route highlighting:
+
+```js
+function setActiveSidebarItem(href) {
+  document.querySelectorAll(".sidebar-menu .nav-link").forEach((el) => {
+    el.classList.toggle("active", el.getAttribute("href") === href)
+  })
+}
+
+// Call on route change
+setActiveSidebarItem(window.location.pathname)
+```
+
+##### Responsive data tables
+
+Wrap a `<table>` in `.table-responsive` so it gets a horizontal scrollbar on narrow viewports instead of overflowing the card.
+
+```html
+<div class="card">
+  <div class="card-header">
+    <h3 class="card-title">Recent orders</h3>
+  </div>
+  <div class="card-body p-0">
+    <div class="table-responsive">
+      <table class="table table-striped align-middle mb-0">
+        <thead>
+          <tr>
+            <th>#</th>
+            <th>Customer</th>
+            <th>Total</th>
+            <th>Status</th>
+            <th class="text-end">Actions</th>
+          </tr>
+        </thead>
+        <tbody>
+          <!-- rows -->
+        </tbody>
+      </table>
+    </div>
+  </div>
+</div>
+```
+
+For interactive sorting/filtering/pagination, use [Tabulator](integrations.html#tabulator) — `tables/data.html` in the demo has a full example.
+
+##### A user dropdown in the topbar
+
+```html
+<li class="nav-item dropdown user-menu">
+  <a href="#" class="nav-link dropdown-toggle" data-bs-toggle="dropdown">
+    <span class="d-none d-md-inline me-1">Jane Doe</span>
+    <i class="bi bi-person-circle fs-5"></i>
+  </a>
+  <ul class="dropdown-menu dropdown-menu-end">
+    <li>
+      <a href="/profile" class="dropdown-item">
+        <i class="bi bi-person me-2"></i>Profile
+      </a>
+    </li>
+    <li>
+      <a href="/settings" class="dropdown-item">
+        <i class="bi bi-gear me-2"></i>Settings
+      </a>
+    </li>
+    <li><hr class="dropdown-divider"></li>
+    <li>
+      <a href="/logout" class="dropdown-item text-danger">
+        <i class="bi bi-box-arrow-right me-2"></i>Sign out
+      </a>
+    </li>
+  </ul>
+</li>
+```
+
+Put this inside the `<ul class="navbar-nav ms-auto">` in `.app-header`.
+
+##### A KPI card (small-box)
+
+The bright-coloured stat boxes at the top of most dashboards:
+
+```html
+<div class="row g-3">
+  <div class="col-lg-3 col-6">
+    <div class="small-box text-bg-primary">
+      <div class="inner">
+        <h3>150</h3>
+        <p>New orders</p>
+      </div>
+      <i class="bi bi-cart small-box-icon" aria-hidden="true"></i>
+      <a href="#" class="small-box-footer link-light">
+        More info <i class="bi bi-arrow-right"></i>
+      </a>
+    </div>
+  </div>
+  <!-- repeat 3 more times with different values + colours -->
+</div>
+```
+
+Swap `text-bg-primary` for `text-bg-success`, `text-bg-warning`, `text-bg-danger`, etc.
+
+##### A breadcrumb + page-title row
+
+This is the standard `.app-content-header` pattern used across every demo page:
+
+```html
+<div class="app-content-header">
+  <div class="container-fluid">
+    <div class="row">
+      <div class="col-sm-6">
+        <h3 class="mb-0">Page Title</h3>
+      </div>
+      <div class="col-sm-6">
+        <ol class="breadcrumb float-sm-end">
+          <li class="breadcrumb-item"><a href="/">Home</a></li>
+          <li class="breadcrumb-item"><a href="/section">Section</a></li>
+          <li class="breadcrumb-item active" aria-current="page">Current page</li>
+        </ol>
+      </div>
+    </div>
+  </div>
+</div>
+```
+
+##### A timeline
+
+For activity feeds, audit logs, or roadmap views:
+
+```html
+<div class="timeline">
+  <div>
+    <i class="bi bi-envelope bg-info" aria-hidden="true"></i>
+    <div class="timeline-item">
+      <span class="time"><i class="bi bi-clock"></i> 12:05</span>
+      <h3 class="timeline-header">
+        <a href="#">Olivia</a> sent you a message
+      </h3>
+      <div class="timeline-body">
+        Hey, can we review the Q4 numbers tomorrow at 10?
+      </div>
+      <div class="timeline-footer">
+        <a class="btn btn-primary btn-sm">Read more</a>
+      </div>
+    </div>
+  </div>
+  <!-- repeat -->
+</div>
+```
+
+The timeline component lives in `_timeline.scss` and has variants. See [`/UI/timeline.html`](../UI/timeline.html) for the full set.
+
+##### A direct-chat widget
+
+The chat-style card used for live customer support, team chat, etc. Includes contact toggle and message bubbles.
+
+```html
+<div class="card direct-chat direct-chat-primary">
+  <div class="card-header">
+    <h3 class="card-title">Direct Chat</h3>
+    <div class="card-tools">
+      <span class="badge text-bg-primary">3 new</span>
+      <button type="button" class="btn btn-tool" data-lte-toggle="chat-pane" title="Contacts">
+        <i class="bi bi-people-fill"></i>
+      </button>
+    </div>
+  </div>
+  <div class="card-body">
+    <div class="direct-chat-messages">
+      <div class="direct-chat-msg">
+        <div class="direct-chat-infos clearfix">
+          <span class="direct-chat-name float-start">Olivia</span>
+          <span class="direct-chat-timestamp float-end">12:05</span>
+        </div>
+        <img class="direct-chat-img" src="/avatars/olivia.jpg" alt="" />
+        <div class="direct-chat-text">Hey, ready for the review?</div>
+      </div>
+      <div class="direct-chat-msg right">
+        <div class="direct-chat-infos clearfix">
+          <span class="direct-chat-name float-end">You</span>
+          <span class="direct-chat-timestamp float-start">12:06</span>
+        </div>
+        <img class="direct-chat-img" src="/avatars/me.jpg" alt="" />
+        <div class="direct-chat-text">Yes, on my way.</div>
+      </div>
+    </div>
+    <div class="direct-chat-contacts">
+      <!-- contacts list, shown when chat-pane toggled -->
+    </div>
+  </div>
+  <div class="card-footer">
+    <form>
+      <div class="input-group">
+        <input type="text" class="form-control" placeholder="Message…" />
+        <button type="submit" class="btn btn-primary">
+          <i class="bi bi-send"></i>
+        </button>
+      </div>
+    </form>
+  </div>
+</div>
+```
+
+For a full-page chat application, see `/pages/chat.html` in the demo.
+
+##### Reacting to layout/sidebar changes
+
+Listen for plugin events to coordinate with your own code — e.g., refit a chart when the sidebar collapses (so the chart re-measures its container width):
+
+```js
+let chart = /* your ApexCharts instance */
+
+document.addEventListener("collapse.lte.push-menu", () => {
+  // wait for the transition to finish (~300ms by default)
+  setTimeout(() => chart.updateOptions({}), 350)
+})
+
+document.addEventListener("open.lte.push-menu", () => {
+  setTimeout(() => chart.updateOptions({}), 350)
+})
+```
+
+The same pattern works for Tabulator, FullCalendar, and any other library that reads its container width on init.
+
+##### Loading content into a card via fetch
+
+```html
+<div class="card" id="users-card">
+  <div class="card-header">
+    <h3 class="card-title">Users</h3>
+    <div class="card-tools">
+      <span class="spinner-border spinner-border-sm text-secondary d-none" id="users-spinner" role="status"></span>
+    </div>
+  </div>
+  <div class="card-body p-0">
+    <div id="users-body" class="text-secondary p-3">Loading&hellip;</div>
+  </div>
+</div>
+
+<script>
+  const spinner = document.getElementById("users-spinner")
+  const body = document.getElementById("users-body")
+
+  spinner.classList.remove("d-none")
+  fetch("/api/users")
+    .then((r) => r.text())
+    .then((html) => { body.innerHTML = html })
+    .catch(() => { body.textContent = "Failed to load." })
+    .finally(() => spinner.classList.add("d-none"))
+</script>
+```
+
+##### Confirming destructive actions before submission
+
+Bootstrap modal + a tiny confirmation pattern:
+
+```html
+<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#confirmDelete">
+  Delete account
+</button>
+
+<div class="modal fade" id="confirmDelete" tabindex="-1">
+  <div class="modal-dialog">
+    <div class="modal-content">
+      <div class="modal-header">
+        <h5 class="modal-title">Delete account?</h5>
+        <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
+      </div>
+      <div class="modal-body">
+        This permanently deletes your account and all associated data. This cannot be undone.
+      </div>
+      <div class="modal-footer">
+        <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
+        <form action="/account/delete" method="POST" class="d-inline">
+          <button type="submit" class="btn btn-danger">Yes, delete it</button>
+        </form>
+      </div>
+    </div>
+  </div>
+</div>
+```
+
+The [Color Mode](color-mode.html), [Accessibility](javascript/accessibility.html), and the bundled JS handle Escape-to-close, focus trap, and keyboard navigation inside the modal automatically.
+
+##### Where to next
+
+- [JavaScript Plugins Overview](javascript/plugins-overview.html) — every plugin's event names and methods
+- [Recommended Integrations](integrations.html) — when you need more than what's in this list
+- [Customization](customization.html) — change colours, sidebar width, breakpoints

+ 60 - 0
src/html/pages/docs/deployment.astro

@@ -0,0 +1,60 @@
+---
+import Head from "@components/_head.astro"
+import Footer from "@components/dashboard/_footer.astro"
+import Topbar from "@components/dashboard/_topbar.astro"
+import Deployment from "@components/docs/deployment.mdx"
+import Sidenav from "@components/dashboard/_sidenav.astro"
+import Scripts from "@components/_scripts.astro"
+
+const title = "Deployment & Performance | AdminLTE 4"
+const path = "../../../dist"
+const mainPage = "docs"
+const page = "deployment";
+---
+
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <Head title={title} path={path} />
+  </head>
+  <body class="layout-fixed sidebar-expand-lg bg-body-tertiary">
+    <div class="app-wrapper">
+      <Topbar path={path} />
+      <Sidenav path={path} mainPage={mainPage} page={page} />
+      <main class="app-main">
+        <div class="app-content-header">
+          <div class="container-fluid">
+            <div class="row">
+              <div class="col-sm-6">
+                <h3 class="mb-0">Deployment &amp; Performance</h3>
+              </div>
+              <div class="col-sm-6">
+                <ol class="breadcrumb float-sm-end">
+                  <li class="breadcrumb-item"><a href="#">Docs</a></li>
+                  <li class="breadcrumb-item active" aria-current="page">
+                    Deployment
+                  </li>
+                </ol>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div class="app-content">
+          <div class="container-fluid">
+            <div class="row">
+              <div class="col-12">
+                <div class="card">
+                  <div class="card-body">
+                    <Deployment />
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </main>
+      <Footer />
+    </div>
+    <Scripts path={path} />
+  </body>
+</html>

+ 60 - 0
src/html/pages/docs/layout-blueprint.astro

@@ -0,0 +1,60 @@
+---
+import Head from "@components/_head.astro"
+import Footer from "@components/dashboard/_footer.astro"
+import Topbar from "@components/dashboard/_topbar.astro"
+import LayoutBlueprint from "@components/docs/layout-blueprint.mdx"
+import Sidenav from "@components/dashboard/_sidenav.astro"
+import Scripts from "@components/_scripts.astro"
+
+const title = "Layout Blueprint | AdminLTE 4"
+const path = "../../../dist"
+const mainPage = "docs"
+const page = "layout-blueprint";
+---
+
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <Head title={title} path={path} />
+  </head>
+  <body class="layout-fixed sidebar-expand-lg bg-body-tertiary">
+    <div class="app-wrapper">
+      <Topbar path={path} />
+      <Sidenav path={path} mainPage={mainPage} page={page} />
+      <main class="app-main">
+        <div class="app-content-header">
+          <div class="container-fluid">
+            <div class="row">
+              <div class="col-sm-6">
+                <h3 class="mb-0">Layout Blueprint</h3>
+              </div>
+              <div class="col-sm-6">
+                <ol class="breadcrumb float-sm-end">
+                  <li class="breadcrumb-item"><a href="#">Docs</a></li>
+                  <li class="breadcrumb-item active" aria-current="page">
+                    Layout Blueprint
+                  </li>
+                </ol>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div class="app-content">
+          <div class="container-fluid">
+            <div class="row">
+              <div class="col-12">
+                <div class="card">
+                  <div class="card-body">
+                    <LayoutBlueprint />
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </main>
+      <Footer />
+    </div>
+    <Scripts path={path} />
+  </body>
+</html>

+ 60 - 0
src/html/pages/docs/recipes.astro

@@ -0,0 +1,60 @@
+---
+import Head from "@components/_head.astro"
+import Footer from "@components/dashboard/_footer.astro"
+import Topbar from "@components/dashboard/_topbar.astro"
+import Recipes from "@components/docs/recipes.mdx"
+import Sidenav from "@components/dashboard/_sidenav.astro"
+import Scripts from "@components/_scripts.astro"
+
+const title = "Common Patterns & Recipes | AdminLTE 4"
+const path = "../../../dist"
+const mainPage = "docs"
+const page = "recipes";
+---
+
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <Head title={title} path={path} />
+  </head>
+  <body class="layout-fixed sidebar-expand-lg bg-body-tertiary">
+    <div class="app-wrapper">
+      <Topbar path={path} />
+      <Sidenav path={path} mainPage={mainPage} page={page} />
+      <main class="app-main">
+        <div class="app-content-header">
+          <div class="container-fluid">
+            <div class="row">
+              <div class="col-sm-6">
+                <h3 class="mb-0">Common Patterns &amp; Recipes</h3>
+              </div>
+              <div class="col-sm-6">
+                <ol class="breadcrumb float-sm-end">
+                  <li class="breadcrumb-item"><a href="#">Docs</a></li>
+                  <li class="breadcrumb-item active" aria-current="page">
+                    Recipes
+                  </li>
+                </ol>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div class="app-content">
+          <div class="container-fluid">
+            <div class="row">
+              <div class="col-12">
+                <div class="card">
+                  <div class="card-body">
+                    <Recipes />
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </main>
+      <Footer />
+    </div>
+    <Scripts path={path} />
+  </body>
+</html>