Cookbook · 8 of 8

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

  1. Wrap the two slots — in-wrapper header (title + Save) + canvas

    The --card variant 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 as 1-minimal (in contrast to the host-toolbar-above-wrapper pattern shared by 2-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>
  2. Mount canvas only — DOUBLE null-arg

    Pass null for both widgetsContainer and settingsContainer — the W6.A.A.1 null-tolerance contract makes the engine skip those mounts cleanly. Same shape as 1-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 the setMode('design') line.

    JavaScript
    var 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);
        }
    );
  3. 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's card has fixed width + height test 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;
    }
  4. Wire SavegetHtml() + getData() round-trip

    Same 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.

    JavaScript
    function 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.'
            );
        });
    }

Live demo

demo-mini-builder--card
Email tile

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.

2 files · 230 lines · 8.8 KB
/* 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; }
}
// 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.