/* style.css — cookbook/8-card — `.demo-mini-builder--card`.
*
* Self-contained chrome variant (W6.C.B.8). Pair this file with init.js
* (sibling) and dist/builder.css + dist/builder.js, and you have a fully-
* working card-shaped Builder mount in 4 files. NO shared mini-builder.css.
* NO examples.css. The `.demo-mini-builder--card` rules below ARE the
* variant — copy them verbatim into your own stylesheet.
*
* ─── Buyer use case ─────────────────────────────────────────────────
* Builder embedded as a dashboard widget tile alongside other small
* cards (analytics widgets, status panels, quick-action buttons).
* Fixed 360 × 420 footprint + rounded corners + drop shadow makes it
* read as one card among many — not as the page's primary surface.
* Pick this when:
*
* - Your host app's main surface is a dashboard / cards grid where
* the Builder is one of N tiles, not the page-defining feature.
* - You need a predictable, small footprint that doesn't fight for
* attention with surrounding components.
* - Buyers preview / quick-edit a single email or page block here
* and "expand" elsewhere for the full editor.
*
* The defining property: explicit `width: 360px; height: 420px` — the
* ONLY variant with hardcoded pixel dimensions (every other cookbook
* variant uses `width: 100%` and a flexible height). The card's
* dimensions are part of its identity; locked by the cookbook spec's
* variant-defining-property gate (`card has fixed width + height`).
*
* ─── Constructor null-args (W6.A.A.1 null-tolerance) ────────────────
* new Builder({
* mainContainer: '#YourCardCanvas',
* 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. Same pattern
* as `cookbook/1-minimal/` and `cookbook/6-canvas-only/`.
*
* ─── Tokens consumed (already exposed by dist/builder.css) ──────────
* --bjs-bg, --bjs-bg-subtle, --bjs-border, --bjs-border-light,
* --bjs-radius-md, --bjs-radius-lg, --bjs-shadow,
* --bjs-text, --bjs-text-secondary, --bjs-primary,
* --bjs-fs-sm, --bjs-fs-base, --bjs-space-2, --bjs-space-3.
*/
/* ─── Base wrapper (header + canvas grid) ─────────────────────────── */
.demo-mini-builder {
display: grid;
border: 1px solid var(--bjs-border);
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);
}
.demo-mini-builder__canvas {
grid-area: canvas;
background: var(--bjs-bg);
position: relative;
}
/* ─── Variant: --card ──── 360 × 420 dashboard tile ───────────────── *
* Explicit pixel dimensions are the variant's defining property — the
* cookbook spec's `card has fixed width + height` test asserts the
* computed `width` is `360px` and `height` is `420px`. Don't change
* these without bumping the spec; the dimensions ARE the contract.
*
* The 40 px header row is just enough for a tile title + a small
* primary action (e.g. Save). Drop-shadow makes the card lift off
* the dashboard background — same visual language as analytics tiles.
*/
.demo-mini-builder--card {
width: 360px;
height: 420px;
border-radius: var(--bjs-radius-lg);
box-shadow: 0 4px 16px var(--bjs-shadow);
grid-template-areas:
"header"
"canvas";
grid-template-rows: 40px 1fr;
}
/* ─── Card title + Save button (in-wrapper header) ─────────────────
* Title takes the leading flex space; Save is pushed to the trailing
* edge by `margin-left: auto`. Save uses the engine's primary-tone
* tokens so the card reads as "primary action lives here" without
* needing a custom palette.
*/
.demo-mini-builder--card .demo-mini-builder__header {
padding: 0 var(--bjs-space-3);
}
.demo-mini-builder--card .demo-card-title {
color: var(--bjs-text);
font-weight: 600;
font-size: var(--bjs-fs-sm);
letter-spacing: 0.01em;
}
.demo-mini-builder--card .demo-card-save {
appearance: none;
-webkit-appearance: none;
margin-left: auto;
border: 1px solid var(--bjs-primary);
background: var(--bjs-primary);
color: var(--bjs-text-on-primary, #fff);
padding: 4px 12px;
border-radius: var(--bjs-radius-md);
font: inherit;
font-size: var(--bjs-fs-sm);
font-weight: 500;
line-height: 1.2;
cursor: pointer;
transition: background 120ms ease, border-color 120ms ease, transform 120ms ease;
}
.demo-mini-builder--card .demo-card-save:hover {
filter: brightness(1.06);
}
.demo-mini-builder--card .demo-card-save:focus-visible {
outline: 2px solid var(--bjs-primary);
outline-offset: 2px;
}
.demo-mini-builder--card .demo-card-save:active {
transform: translateY(1px);
}
@media (prefers-reduced-motion: reduce) {
.demo-mini-builder, .demo-mini-builder *,
.demo-mini-builder--card .demo-card-save { transition: none; }
}
Card — dashboard tile (360 × 420 fixed)
Builder embedded as a small card alongside other dashboard widgets. Fixed <code>360 × 420</code> footprint + rounded corners + drop shadow — the only cookbook variant with explicit pixel dimensions. DOUBLE null-arg pattern (no widgets palette, no settings sidebar): the card has room for a 40-px header + canvas only. Pick this when the Builder is one of N tiles on a dashboard, not the page-defining feature.
Walkthrough
-
Wrap the two slots — in-wrapper header (title + Save) + canvas
The
--cardvariant has TWO slots:__header(40 px row at the top, hosts a tile title + the primary Save action) and__canvas(everything else). Same in-wrapper-header pattern as1-minimal(in contrast to the host-toolbar-above-wrapper pattern shared by2-compact-2col/3-compact-3col/5-sidebar-only/6-canvas-only/7-mobile) — keeping Save inside the card preserves the "this card is its own little app" feel.HTML<div class="demo-mini-builder demo-mini-builder--card"> <div class="demo-mini-builder__header"> <span class="demo-card-title">Email tile</span> <button type="button" class="demo-card-save" id="CookbookCardSave">Save</button> </div> <div class="demo-mini-builder__canvas" id="CookbookCardCanvas"></div> </div> -
Mount canvas only — DOUBLE null-arg
Pass
nullfor bothwidgetsContainerandsettingsContainer— the W6.A.A.1 null-tolerance contract makes the engine skip those mounts cleanly. Same shape as1-minimal+6-canvas-only. The engine boots in'preview'mode (clean WYSIWYG, no debug overlays) — perfect for a dashboard tile that shows the rendered page in-place. Buyers wanting click-to-edit inside the tile uncomment thesetMode('design')line.JavaScriptvar builder = new Builder({ mainContainer: '#CookbookCardCanvas', widgetsContainer: null, settingsContainer: null, historyUI: false }); builder.load( window.THEME_JSON, window.THEME_TEMPLATES, window.THEME_CONFIG_DATA, window.MEDIA_URL, function () { // builder.setMode('design'); // opt-in: click-to-edit in the tile wireSave(builder); } ); -
Lock the dimensions —
width: 360px; height: 420px;The card variant's defining property is its explicit pixel dimensions — every other cookbook variant uses
width: 100%and a flexible height. The 360 × 420 footprint reads as one tile among many on a dashboard grid; widening or stretching it changes the whole feel. Drop-shadow + rounded corners reinforce the card identity. The cookbook spec'scard has fixed width + heighttest asserts the computed values — don't change them without bumping the spec.CSS.demo-mini-builder--card { width: 360px; height: 420px; border-radius: var(--bjs-radius-lg); box-shadow: 0 4px 16px var(--bjs-shadow); grid-template-areas: "header" "canvas"; grid-template-rows: 40px 1fr; } -
Wire
Save—getHtml()+getData()round-tripSame Save pattern as every other cookbook entry — the alert pops with the round-trip output; production hosts swap it for a
fetch()POST. The Save button lives inside the card's header (in-wrapper) so the tile reads as self-contained — buyers don't need a parent-page action to save the card's content.JavaScriptfunction wireSave(b) { document.getElementById('CookbookCardSave').addEventListener('click', function () { var html = b.getHtml(); var data = b.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/8-card.
//
// Mounts a Builder instance into the `.demo-mini-builder--card` 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
//
// W6.A.A.1 null-tolerance — passing `null` for both `widgetsContainer`
// and `settingsContainer` makes the engine skip those sub-system mounts
// cleanly. Same DOUBLE null-arg pattern as `cookbook/1-minimal/` and
// `cookbook/6-canvas-only/`. The card variant adds a 40-px in-wrapper
// `__header` slot for a title + Save button (vs minimal's 36-px
// header, vs canvas-only's host toolbar above the wrapper).
//
// Engine boots in 'preview' mode → call `setMode('design')` after
// `load()` so the canvas accepts edits the moment the buyer clicks
// inside the tile. (See `cookbook/4-rich-3col-header/` for the
// discovery context.)
(function () {
'use strict';
var canvas = document.getElementById('CookbookCardCanvas');
if (!canvas || typeof window.Builder !== 'function') return;
var builder = new window.Builder({
mainContainer: '#CookbookCardCanvas',
widgetsContainer: null,
settingsContainer: null,
historyUI: false
});
builder.load(
window.THEME_JSON,
window.THEME_TEMPLATES,
window.THEME_CONFIG_DATA,
window.MEDIA_URL,
function () {
// 2026-05-09 — demo defaults to 'preview' (engine default)
// per the "no debug overlays unless this example IS the
// mode-toggle showcase" rule. Card chrome is the lesson; mode
// is orthogonal. Buyer wanting design-on-boot calls
// `builder.setMode('design')` here.
// Save lives INSIDE the wrapper's `__header` slot (same
// pattern as `cookbook/1-minimal/`, contrast with the host-
// toolbar pattern shared by `2-compact-2col` / `3-compact-
// 3col` / `5-sidebar-only` / `6-canvas-only` / `7-mobile`).
// Production hosts swap the alert for a `fetch()` POST.
var saveBtn = document.getElementById('CookbookCardSave');
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.cookbookCardBuilder = builder;
}
);
})();
Notes
Why fixed pixel dimensions, not width: 100%? Cards on a dashboard grid have predictable footprints — the parent grid's column track decides the tile width, but the tile itself "knows" its own size and shape. A card that stretches to fill its parent breaks the visual cadence of a multi-card layout (analytics widget, status panel, builder tile, quick-action — they need to read as a row). 360 × 420 is the contract; the spec asserts the computed values so a regression that drops the rule sails past every other gate (slot count is the same as 1-minimal; null-arg pair is the same as 6-canvas-only).
Why DOUBLE null-arg (no widgets, no settings)? 360 × 420 minus the 40-px header leaves 380 px of canvas height — barely enough for one or two blocks of email content. Adding a widgets palette (~120 px wide) or settings sidebar (~200 px wide) inside the card would crush the canvas to 40 px and 160 px respectively. Card-shaped Builders are for preview / quick-edit, not full authoring — buyers click "Open in editor" or "Expand" to land on the full 4-rich-3col-header chrome elsewhere. The card's job is to render and accept simple text edits in place, then defer to a bigger surface for structural work.
Why is Save inside the card's header, not a host toolbar above? Same answer as 1-minimal: the variant has a real __header slot, so primary actions belong there. A host toolbar above the wrapper would orphan the Save action — the buyer's eye would have to leave the tile to find it. Inside the header, Save reads as "this tile's own button" — exactly the contract a card-grid host wants. Variants WITHOUT a __header slot (2-compact-2col / 3-compact-3col / 5-sidebar-only / 6-canvas-only / 7-mobile) use the host-toolbar pattern instead.
Why setMode('design') after load()? The engine boots in 'preview' mode (read-only WYSIWYG). Without the flip, the buyer's first click on a heading or paragraph wouldn't open the inline text editor — same discovery as 4-rich-3col-header + 6-canvas-only + 7-mobile. Setting design mode keeps card-tile editability consistent with every other cookbook entry's expectation.
Sample pick — why SimpleText? The card's 360 × 420 viewport is the smallest of any cookbook entry. Layout-rich samples (newsletter, lineup, multi-column) overflow horizontally inside that footprint and look broken at boot. SimpleText is a minimal email layout (header link → centered heading → two paragraphs → signoff) that reads cleanly at 360 px width and fits within the 380-px-of-canvas height with a small scrolling tail — the canvas's own scroll affordance handles the overflow gracefully. Pick samples that respect the card's footprint when adapting this preset to your own theme.
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/SimpleText') and emits the four named globals before init.js loads. In your own project, render them from any backend — only the four names matter to the engine. See Quickstart for the canonical pattern.
What about a different size? The 360 × 420 dimensions are a starting point, not a religion — your dashboard's column track may want 320, 400, or 480 instead. Pick a size that matches your tile cadence; just keep the W:H ratio close to 0.85 so the canvas doesn't read as too-narrow or too-tall, and update the cookbook spec's expected values if you fork the dimensions in your own tree (the spec asserts the exact pixel values today). The 40-px header row is a minimum — anything shorter clips the Save button's focus ring.