/* style.css — cookbook/1-minimal — `.demo-mini-builder--minimal`.
*
* Self-contained chrome variant (W6.C.B.1). Pair this file with init.js
* (sibling) and dist/builder.css + dist/builder.js, and you have a fully-
* working Builder mount in 4 files. NO shared mini-builder.css. NO
* examples.css. The `.demo-mini-builder--minimal` rules below ARE the
* variant — copy them verbatim into your own stylesheet.
*
* ─── Buyer use case ─────────────────────────────────────────────────
* Embed inside an iframe (or any constrained slot in an existing app)
* where the host already supplies its own chrome. The mini-builder
* just needs a thin top strip for a title or a Save button + the
* canvas itself. No widgets palette, no settings sidebar.
*
* ─── Constructor null-args (W6.A.A.1 null-tolerance) ────────────────
* new Builder({
* mainContainer: '#YourCanvas',
* widgetsContainer: null, // ← no widgets palette
* settingsContainer: null, // ← no settings sidebar
* });
* The engine skips the omitted mounts cleanly — no NPE, no console
* error, regardless of whether DOM nodes exist for them.
*
* ─── Tokens consumed (already exposed by dist/builder.css) ──────────
* --bjs-bg, --bjs-bg-subtle, --bjs-border, --bjs-border-light,
* --bjs-radius-lg, --bjs-text, --bjs-text-secondary,
* --bjs-fs-sm, --bjs-fs-base, --bjs-space-2, --bjs-space-3.
*/
/* ─── Base wrapper (4-slot grid; canvas-only fallback) ─────────────── */
.demo-mini-builder {
display: grid;
width: 100%;
height: 480px;
border: 1px solid var(--bjs-border);
border-radius: var(--bjs-radius-lg);
background: var(--bjs-bg);
color: var(--bjs-text);
font-size: var(--bjs-fs-base);
overflow: hidden;
box-sizing: border-box;
grid-template-areas: "canvas";
grid-template-columns: 1fr;
grid-template-rows: 1fr;
}
.demo-mini-builder *,
.demo-mini-builder *::before,
.demo-mini-builder *::after { box-sizing: border-box; }
.demo-mini-builder__header,
.demo-mini-builder__canvas {
overflow: auto;
min-height: 0;
min-width: 0;
}
.demo-mini-builder__header {
grid-area: header;
background: var(--bjs-bg-subtle);
border-bottom: 1px solid var(--bjs-border-light);
padding: var(--bjs-space-2) var(--bjs-space-3);
display: flex;
align-items: center;
gap: var(--bjs-space-2);
font-size: var(--bjs-fs-sm);
font-weight: 500;
color: var(--bjs-text-secondary);
}
/* Minimal button reset for any <button> inside the header strip.
* Keeps the walkthrough HTML clean (`<button>Save</button>` — no class)
* while still giving the buyer a sensible, token-driven look. */
.demo-mini-builder__header button {
appearance: none;
-webkit-appearance: none;
border: 1px solid var(--bjs-border-light);
background: var(--bjs-bg);
color: var(--bjs-text);
padding: 4px 10px;
border-radius: 6px;
font: inherit;
font-size: var(--bjs-fs-sm);
line-height: 1.2;
cursor: pointer;
transition: background 120ms ease, border-color 120ms ease;
}
.demo-mini-builder__header button:hover {
background: var(--bjs-bg-subtle);
border-color: var(--bjs-border);
}
.demo-mini-builder__header button:focus-visible {
outline: 2px solid var(--bjs-primary);
outline-offset: 2px;
}
.demo-mini-builder__header button:active {
transform: translateY(1px);
}
.demo-mini-builder__canvas {
grid-area: canvas;
background: var(--bjs-bg);
position: relative;
}
/* ─── Variant: --minimal ──── thin top strip + canvas ─────────────── */
.demo-mini-builder--minimal {
grid-template-areas:
"header"
"canvas";
grid-template-rows: 36px 1fr;
}
@media (prefers-reduced-motion: reduce) {
.demo-mini-builder, .demo-mini-builder * { transition: none; }
}
Minimal — canvas + thin top strip
No widgets palette, no settings sidebar — just a 36 px header strip for a title or a Save button, and the editing canvas. Pick this when your host app already supplies the chrome.
Walkthrough
-
Wrap two slots — header + canvas
Drop one
.demo-mini-builder.demo-mini-builder--minimalwrapper anywhere in your page. Inside, two slots:__headerfor your title / Save button, and__canvasfor the editing surface. Give the canvas slot anid;init.jsmounts the Builder there.HTML<div class="demo-mini-builder demo-mini-builder--minimal"> <div class="demo-mini-builder__header"> <span style="margin-right: auto; font-weight: 600;">My builder</span> <button type="button" id="CookbookMinimalSave">Save</button> </div> <div class="demo-mini-builder__canvas" id="CookbookMinimalCanvas"></div> </div> -
Mount with both sidebars set to
nullW6.A.A.1 null-tolerance — passing
nullforwidgetsContainerandsettingsContainermakes the engine skip those sub-system mounts cleanly. No NPE, no console error. That contract is exactly what makes--minimala one-slot wrapper.JavaScriptvar builder = new Builder({ mainContainer: '#CookbookMinimalCanvas', widgetsContainer: null, // <- null-tolerance: engine skips settingsContainer: null, // <- null-tolerance: engine skips historyUI: false }); builder.load( window.THEME_JSON, window.THEME_TEMPLATES, window.THEME_CONFIG_DATA, window.MEDIA_URL ); -
Wire the Save button —
getHtml()+getData()The header strip is yours — title text, Save button, anything you want. The example below pops an
alert()with the round-trip output; production hosts swap the alert for afetch()POST to their backend.getHtml()returns the rendered page (string);getData()returns the page tree (object).JavaScriptdocument.getElementById('CookbookMinimalSave').addEventListener('click', function () { var html = builder.getHtml(); var data = builder.getData(); alert( 'Saved · ' + html.length + ' chars HTML, ' + Object.keys(data).length + ' top-level page-tree keys.' ); });
The whole snippet
Click Copy on any file to grab it byte-identical, or Expand to read inline (capped at ~520 px so the page stays scannable). Multi-file snippets surface a side-by-side grid — copy only the files you need.
// init.js — cookbook/1-minimal.
//
// Mounts a Builder instance into the `.demo-mini-builder--minimal`
// scaffold rendered alongside this file. Reads four named globals
// (BUILDER.md RULE I split-globals convention):
//
// window.THEME_JSON — page tree (the sample JSON)
// window.THEME_TEMPLATES — { templateKey: HTML EJS string }
// window.THEME_CONFIG_DATA — theme's index.json verbatim
// window.MEDIA_URL — base URL for relative asset paths
//
// The host page emits these (server-rendered via PHP `ThemeRegistry::
// resolveBundle()` in this demo; production hosts can hardcode them or
// pre-render from any backend). The engine consumes them in the same
// order as the named globals — no wrapper object.
//
// W6.A.A.1 null-tolerance — passing `null` for `widgetsContainer` and
// `settingsContainer` makes the engine skip those sub-system mounts
// cleanly. No NPE, no console error, regardless of whether the
// corresponding DOM nodes exist. That contract is what makes
// `--minimal` chrome possible — only canvas + header in the buyer's
// HTML, nothing else.
(function () {
'use strict';
var canvas = document.getElementById('CookbookMinimalCanvas');
if (!canvas || typeof window.Builder !== 'function') return;
var builder = new window.Builder({
mainContainer: '#CookbookMinimalCanvas',
widgetsContainer: null,
settingsContainer: null,
historyUI: false
});
builder.load(
window.THEME_JSON,
window.THEME_TEMPLATES,
window.THEME_CONFIG_DATA,
window.MEDIA_URL,
function () {
// Wire the Save button in the header strip. Production
// host replaces the alert with a fetch() POST to its own
// backend. getHtml() returns the rendered email/page;
// getData() returns the page tree for round-trip reload.
var saveBtn = document.getElementById('CookbookMinimalSave');
if (saveBtn) {
saveBtn.addEventListener('click', function () {
var html = builder.getHtml();
var data = builder.getData();
alert(
'Saved · ' + html.length + ' chars HTML, ' +
Object.keys(data).length + ' top-level page-tree keys.'
);
});
}
// Expose for buyer-side debugging + spec assertions.
window.cookbookMinimalBuilder = builder;
}
);
})();
Notes
Why two null arguments? Builder.js's constructor accepts widgetsContainer + settingsContainer as optional. Passing null tells the engine: "don't mount these sub-systems." Internally (W6.A.A.1 null-tolerance, src/includes/Builder.js) the engine guards every reach into those sub-systems with a presence check, so the rest of the runtime — selection, history, save, getHtml — works identically to the full 3-column chrome. No mocks, no orphan <div>s, no warnings.
What happens if I drop the header? You can. Remove the __header slot from the HTML and the wrapper falls back to "canvas"-only via the base .demo-mini-builder rules. If you want a TRULY chrome-less surface (no border, no rounded corners, no padding), pick the 6-canvas-only preset instead — it strips the wrapper border entirely.
Where do the four window.* globals come from? The host page renders them server-side. In this demo, _partials/example.php calls ThemeRegistry::resolveBundle('default', 'master/sample/email/Minimal') and emits the four named globals before init.js loads. In your own project, render them from any backend (PHP, Node, Python, Go) — only the four names matter to the engine. See Quickstart for the canonical pattern.
Save flow? The demo above pops an alert() so the round-trip is visible without a backend. Production hosts replace the alert with a fetch(saveUrl, { method: 'POST', body: JSON.stringify({ html, data }) }). Hello World wires the full POST + response toast pattern.