@koda-sl/baker-cli 0.393.0-dev.799d3f88c → 0.395.0-dev.f8ecbaa42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -1
- package/canvas/static-ad-composition/index.html +154 -3
- package/canvas/static-ad-composition/meta.json +1 -1
- package/dist/{chunk-JLKUHDBB.js → chunk-Q3LMLQAI.js} +271 -100
- package/dist/chunk-Q3LMLQAI.js.map +1 -0
- package/dist/cli.js +8512 -7419
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.d.ts +6 -0
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-JLKUHDBB.js.map +0 -1
package/README.md
CHANGED
|
@@ -4066,8 +4066,37 @@ baker canvas catalog --node video_generate
|
|
|
4066
4066
|
# multi-line / accented copy; the edit is re-validated before it is written back)
|
|
4067
4067
|
baker canvas set-prompt my-canvas.json s0_start --text "Close-up, flushed and breathless"
|
|
4068
4068
|
baker canvas set-prompt my-canvas.json s0_start --text-file ./hook-frame.txt
|
|
4069
|
+
|
|
4070
|
+
# 6. Set a static ad's words without editing prompt.json by hand. No flags lists
|
|
4071
|
+
# every line with its role and layout region; flags change them in the copy
|
|
4072
|
+
# and the layout together and keep everything else in prompt.json.
|
|
4073
|
+
baker canvas copy spring-offer
|
|
4074
|
+
baker canvas copy spring-offer --headline "Nine days, not 31." --cta "Book your free survey" \
|
|
4075
|
+
--line body="Scaffolding included" --line body="MCS certificate included" \
|
|
4076
|
+
--line eyebrow@header="ROOFTOP SOLAR" --treatment cta=pill --remove caption --remove-logo
|
|
4069
4077
|
```
|
|
4070
4078
|
|
|
4079
|
+
`baker canvas copy <slug>` flags:
|
|
4080
|
+
|
|
4081
|
+
| Flag | What it does |
|
|
4082
|
+
|---|---|
|
|
4083
|
+
| `--headline`, `--sub`, `--cta` | Shorthand for `--line headline=…`, `--line subheadline=…`, `--line cta=…` |
|
|
4084
|
+
| `--line <role>[@<region>]=<text>` | Repeatable. A role you name gets exactly the lines you give, in order; old extra ones are dropped. A new line goes to the region of its role's last line, or the region you name. Roles: `headline`, `subheadline`, `body`, `cta`, `caption`, `fine_print`, `eyebrow` |
|
|
4085
|
+
| `--remove <role>` | Repeatable. Drops every line of that role |
|
|
4086
|
+
| `--treatment <role>=<kind>` | `plain`, `underline`, `arrow`, `button`, `pill`, `sticker`, `highlight` |
|
|
4087
|
+
| `--color <role>=<#rrggbb>` | The ink of that role's lines |
|
|
4088
|
+
| `--remove-logo` | Draws the ad without a logo |
|
|
4089
|
+
| `--mechanism "<verbal\|visual\|structural>: <device>"` | Repeatable. Replaces the reference's winning mechanisms with ours |
|
|
4090
|
+
| `--alternate "<hook>"` | Repeatable. Records a hook alternate on the creative's `_definition.md` |
|
|
4091
|
+
| `--spec <file.json>` | The same fields as JSON, for long copy |
|
|
4092
|
+
| `--dry-run` | Prints the changes without writing |
|
|
4093
|
+
|
|
4094
|
+
It refuses, before writing anything, a role it does not know, a region the layout does not have, a new
|
|
4095
|
+
line with no region to go to, and copy that says the same line twice. Replacing the headline that is
|
|
4096
|
+
the creative's recorded `hook` keeps the old one in `hookAlternates` and makes the new one the hook.
|
|
4097
|
+
The output lists each change, the resulting copy, what was recorded, and the next command
|
|
4098
|
+
(`baker canvas run <slug> --draft`).
|
|
4099
|
+
|
|
4071
4100
|
#### The brand, the ending and the cast are drawn, not generated
|
|
4072
4101
|
|
|
4073
4102
|
`baker canvas scaffold-ad` renders everything with a known, fixed appearance locally,
|
|
@@ -5513,6 +5542,9 @@ Takes a creative's slug (`baker canvas run spring-offer`) or a canvas path, reso
|
|
|
5513
5542
|
| `--remote-cache <on\|off>` | `on` (or `BAKER_CANVAS_REMOTE_CACHE`) | Company-scoped remote cache + durable asset persistence. |
|
|
5514
5543
|
| `--max-credits <n>` | uncapped (or `BAKER_CANVAS_MAX_CREDITS`) | Credit ceiling: aborts before billing when the estimate exceeds it, and at layer boundaries once actual spend does. Completed nodes stay cached, so retrying with a higher cap loses nothing. |
|
|
5515
5544
|
| `--no-record` | records | Skip posting the durable run-history record (and its live progress). |
|
|
5545
|
+
| `--draft` | off | A static ad only: render one picture at `high` quality with no judge, then draw and review every format exactly as the final does. Iterate on the copy and the layout with it, and run once without it for the final (three pictures at the best quality, judged). The canvas file is not changed. |
|
|
5546
|
+
|
|
5547
|
+
`stats.total_credits` is what the credits ledger debits for the run: each node's charge as the backend billed it (an estimate only when it reported none), plus the review's own model calls. `validate`'s figure is an estimate.
|
|
5516
5548
|
|
|
5517
5549
|
**Run history streams live.** The run posts its plan (every node + its dependency edges) the moment validation passes, then re-posts a progress snapshot as each node starts and settles — the dashboard's creative workflow graph shows nodes flipping pending → running → done in real time, with each node's outputs attached as they land. A failed run keeps its per-node trail (what completed, what died). All best-effort: an unreachable backend never changes the run's outcome.
|
|
5518
5550
|
|
|
@@ -5664,7 +5696,7 @@ baker canvas run spring-offer
|
|
|
5664
5696
|
| `--layout-model <id>` | registry default (`~google/gemini-flash-latest`) | Override the global-layout `text_generate` model. |
|
|
5665
5697
|
| `--gen-model <id>` | `openai/gpt-image-2.5-sunburst` | Override the plate model. |
|
|
5666
5698
|
| `--takes <n>` | `3` | Plates rendered and judged (1-3); `1` skips the judge. A text-only ad renders none. |
|
|
5667
|
-
| `--text-only` | on when the reference pictures nothing but copy | Draw a typographic ad: no picture generated
|
|
5699
|
+
| `--text-only` | on when the reference pictures nothing but copy (a note, a letter, a comparison with no photo) | Draw a typographic ad: no picture generated; each line in the colour, weight and size prompt.json gives it, on the layout's background colour, with its cards, dividers, icons, tick and cross marks and app chrome. `--no-text-only` renders a picture anyway. |
|
|
5668
5700
|
| `--platform <channel>` | `meta` | `_definition.md` channel **and** the default placement set the hero fans out to (`meta`\|`google`\|`linkedin`\|`tiktok`\|`youtube`\|`x`\|`other`; `x`/`other` fan out to nothing). Any other value is refused. |
|
|
5669
5701
|
| `--placements <preset\|none>` | derived from `--platform` | Override the fan-out set: `meta_feed`\|`meta_feed_stories`\|`meta_all`\|`linkedin`\|`google_demandgen`\|`tiktok`\|`youtube`, or `none` for a single base ad. |
|
|
5670
5702
|
| `--aspect <ratio>` | inferred from the image, else `9:16` | Force the lead format (it leads the placement set when the set has it): `1:1`, `4:5`, `9:16`, `16:9`, `4:3`, `3:4`, `2:3`, `3:2` or `21:9`. |
|
|
@@ -20,10 +20,18 @@
|
|
|
20
20
|
.block { font-family: 'AdBody', 'Helvetica Neue', Arial, sans-serif; overflow-wrap: break-word; text-wrap: pretty; }
|
|
21
21
|
.block.headline, .block.subheadline, .block.cta { font-family: 'AdHeadline', 'AdBody', 'Helvetica Neue', Arial, sans-serif; }
|
|
22
22
|
.block.headline { text-wrap: balance; letter-spacing: -0.01em; }
|
|
23
|
-
.fill { display: inline-block; line-height: 1.1; padding: 0.5em 1.2em; }
|
|
23
|
+
.fill { display: inline-block; line-height: 1.1; padding: 0.5em 1.2em; white-space: nowrap; }
|
|
24
24
|
.fill.highlight { padding: 0.12em 0.4em; box-decoration-break: clone; -webkit-box-decoration-break: clone; }
|
|
25
25
|
.fill.sticker { transform: rotate(-3deg); }
|
|
26
|
+
.fill.outline { background: transparent !important; border: 0.09em solid currentColor; }
|
|
26
27
|
.logo { display: block; width: auto; object-fit: contain; }
|
|
28
|
+
.shape { position: absolute; pointer-events: none; }
|
|
29
|
+
.shape svg { display: block; height: 100%; width: auto; }
|
|
30
|
+
.shape.chrome { display: flex; align-items: center; justify-content: space-between; }
|
|
31
|
+
.chrome-back { display: flex; align-items: center; gap: 0.2em; font-family: 'AdBody', 'Helvetica Neue', Arial, sans-serif; }
|
|
32
|
+
.chrome-more { display: flex; align-items: center; justify-content: center; border-radius: 999px; }
|
|
33
|
+
.block.mark { display: flex; align-items: flex-start; gap: 0.45em; }
|
|
34
|
+
.block.mark svg { flex: none; width: 1.05em; height: 1.05em; margin-top: 0.1em; }
|
|
27
35
|
</style>
|
|
28
36
|
</head>
|
|
29
37
|
<body>
|
|
@@ -122,6 +130,8 @@
|
|
|
122
130
|
el.style.minHeight = ((at.y[1] - at.y[0]) / 100) * H + "px";
|
|
123
131
|
el.style.justifyContent = "center";
|
|
124
132
|
el.style.gap = 1.2 * U + "px";
|
|
133
|
+
// A card's lines keep off its edges.
|
|
134
|
+
if (g.inset) el.style.padding = 2.6 * U + "px " + 3.2 * U + "px";
|
|
125
135
|
// Copy rewritten for our brand runs longer than the source's: allow some growth
|
|
126
136
|
// past the region before the type shrinks.
|
|
127
137
|
el.dataset.max = String(((at.y[1] - at.y[0]) / 100) * H * 1.35);
|
|
@@ -146,6 +156,91 @@
|
|
|
146
156
|
else el.insertBefore(img, el.firstChild);
|
|
147
157
|
}
|
|
148
158
|
|
|
159
|
+
// The structures of a text-only ad, each at its layout region's box on this format:
|
|
160
|
+
// cards and panels, dividers, icons and an app's chrome. Drawn before the copy, under it.
|
|
161
|
+
var ICON_PATHS = {
|
|
162
|
+
check: '<path d="M5 12.5l4.5 4.5L19 7.5" fill="none" stroke="C" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>',
|
|
163
|
+
cross: '<path d="M6.5 6.5l11 11M17.5 6.5l-11 11" fill="none" stroke="C" stroke-width="3" stroke-linecap="round"/>',
|
|
164
|
+
dots: '<circle cx="5" cy="12" r="2" fill="C"/><circle cx="12" cy="12" r="2" fill="C"/><circle cx="19" cy="12" r="2" fill="C"/>',
|
|
165
|
+
chevron_left: '<path d="M15 4.5L7.5 12 15 19.5" fill="none" stroke="C" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/>',
|
|
166
|
+
arrow_right: '<path d="M4 12h15M13 6l6 6-6 6" fill="none" stroke="C" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/>',
|
|
167
|
+
star: '<path d="M12 3l2.7 5.6 6.1.9-4.4 4.3 1 6.1L12 17l-5.4 2.9 1-6.1-4.4-4.3 6.1-.9z" fill="C"/>',
|
|
168
|
+
heart: '<path d="M12 20s-7-4.4-7-10a4 4 0 017-2.6A4 4 0 0119 10c0 5.6-7 10-7 10z" fill="C"/>',
|
|
169
|
+
};
|
|
170
|
+
function icon(name, color) {
|
|
171
|
+
var path = ICON_PATHS[name] || "";
|
|
172
|
+
return '<svg viewBox="0 0 24 24" aria-hidden="true">' + path.split('"C"').join('"' + color + '"') + "</svg>";
|
|
173
|
+
}
|
|
174
|
+
var SHAPE_RADIUS = { pill: "999px", rounded: 2.4 * U + "px", square: "0" };
|
|
175
|
+
function addShape(s) {
|
|
176
|
+
var at = s.at && s.at[FRAME.format];
|
|
177
|
+
if (!at) return;
|
|
178
|
+
var left = (at.x[0] / 100) * W;
|
|
179
|
+
var top = (at.y[0] / 100) * H;
|
|
180
|
+
var width = ((at.x[1] - at.x[0]) / 100) * W;
|
|
181
|
+
var height = ((at.y[1] - at.y[0]) / 100) * H;
|
|
182
|
+
var el = document.createElement("div");
|
|
183
|
+
el.className = "shape " + s.kind;
|
|
184
|
+
el.setAttribute("data-layout-ignore", "");
|
|
185
|
+
el.style.left = left + "px";
|
|
186
|
+
el.style.top = top + "px";
|
|
187
|
+
el.style.width = width + "px";
|
|
188
|
+
el.style.height = height + "px";
|
|
189
|
+
if (s.kind === "box") {
|
|
190
|
+
el.style.background = s.fill || "transparent";
|
|
191
|
+
if (s.border) el.style.border = Math.max(2, 0.28 * U) + "px solid " + s.border;
|
|
192
|
+
el.style.borderRadius = SHAPE_RADIUS[s.radius] || SHAPE_RADIUS.rounded;
|
|
193
|
+
if (s.shadow) el.style.boxShadow = "0 " + 0.8 * U + "px " + 2.6 * U + "px rgba(0,0,0,0.18)";
|
|
194
|
+
} else if (s.kind === "rule") {
|
|
195
|
+
var thick = Math.max(2, 0.32 * U);
|
|
196
|
+
el.style.background = s.color;
|
|
197
|
+
if (width >= height) {
|
|
198
|
+
el.style.top = top + height / 2 - thick / 2 + "px";
|
|
199
|
+
el.style.height = thick + "px";
|
|
200
|
+
} else {
|
|
201
|
+
el.style.left = left + width / 2 - thick / 2 + "px";
|
|
202
|
+
el.style.width = thick + "px";
|
|
203
|
+
}
|
|
204
|
+
} else if (s.kind === "icon") {
|
|
205
|
+
el.style.display = "flex";
|
|
206
|
+
el.style.justifyContent = "center";
|
|
207
|
+
el.style.alignItems = "center";
|
|
208
|
+
if (s.ring) {
|
|
209
|
+
// A circled icon (a note's "more" button): the ring is as tall as the region.
|
|
210
|
+
var ring = Math.min(width, height);
|
|
211
|
+
var circle = document.createElement("div");
|
|
212
|
+
circle.className = "chrome-more";
|
|
213
|
+
circle.style.width = ring + "px";
|
|
214
|
+
circle.style.height = ring + "px";
|
|
215
|
+
circle.style.border = Math.max(2, ring * 0.07) + "px solid " + s.color;
|
|
216
|
+
circle.innerHTML = '<span style="display:block;height:' + ring * 0.6 + 'px">' + icon(s.icon, s.color) + "</span>";
|
|
217
|
+
el.appendChild(circle);
|
|
218
|
+
} else {
|
|
219
|
+
el.innerHTML = icon(s.icon, s.color);
|
|
220
|
+
}
|
|
221
|
+
} else if (s.kind === "chrome") {
|
|
222
|
+
var size = Math.min(height * 0.62, 5 * U);
|
|
223
|
+
var back = document.createElement("div");
|
|
224
|
+
back.className = "chrome-back";
|
|
225
|
+
if (!facesStaged) back.style.fontFamily = FALLBACK_FACE;
|
|
226
|
+
back.style.color = s.color;
|
|
227
|
+
back.style.fontSize = size + "px";
|
|
228
|
+
back.innerHTML = '<span style="display:block;height:1.1em">' + icon("chevron_left", s.color) + "</span>";
|
|
229
|
+
var label = document.createElement("span");
|
|
230
|
+
label.textContent = s.label;
|
|
231
|
+
back.appendChild(label);
|
|
232
|
+
var more = document.createElement("div");
|
|
233
|
+
more.className = "chrome-more";
|
|
234
|
+
more.style.width = size * 1.35 + "px";
|
|
235
|
+
more.style.height = size * 1.35 + "px";
|
|
236
|
+
more.style.border = Math.max(2, size * 0.09) + "px solid " + s.color;
|
|
237
|
+
more.innerHTML = '<span style="display:block;height:' + size * 0.8 + 'px">' + icon("dots", s.color) + "</span>";
|
|
238
|
+
el.appendChild(back);
|
|
239
|
+
if (s.more) el.appendChild(more);
|
|
240
|
+
}
|
|
241
|
+
root.appendChild(el);
|
|
242
|
+
}
|
|
243
|
+
|
|
149
244
|
function addBlock(b) {
|
|
150
245
|
var el = document.createElement("div");
|
|
151
246
|
el.className = "block " + b.role;
|
|
@@ -154,6 +249,11 @@
|
|
|
154
249
|
el.dataset.base = String(size);
|
|
155
250
|
el.style.fontSize = size * U + "px";
|
|
156
251
|
el.style.fontWeight = String(WEIGHT[b.weight] || (b.role === "cta" ? 700 : 400));
|
|
252
|
+
// A staged face is one weight, so a line prompt.json sets bold (a bold paragraph, a
|
|
253
|
+
// sign-off) is drawn in the heavy face whatever its role.
|
|
254
|
+
if (facesStaged && WEIGHT[b.weight] >= 600) {
|
|
255
|
+
el.style.fontFamily = "'AdHeadline', 'AdBody', 'Helvetica Neue', Arial, sans-serif";
|
|
256
|
+
}
|
|
157
257
|
el.style.lineHeight = b.role === "headline" ? "1.04" : "1.22";
|
|
158
258
|
el.style.textTransform = CASE[b.case] || "none";
|
|
159
259
|
el.style.textAlign = b.align || "center";
|
|
@@ -169,15 +269,29 @@
|
|
|
169
269
|
fill.style.background = t.background || (AD.cta && AD.cta.background) || "#111111";
|
|
170
270
|
fill.style.color = t.color || (AD.cta && AD.cta.color) || "#ffffff";
|
|
171
271
|
fill.style.borderRadius = RADIUS[t.shape] || RADIUS.rounded;
|
|
272
|
+
if (t.outline) fill.classList.add("outline");
|
|
172
273
|
el.appendChild(fill);
|
|
173
274
|
} else {
|
|
174
|
-
|
|
275
|
+
if (t.kind === "mark") {
|
|
276
|
+
// A tick or cross list item: the mark in its own colour, then the line.
|
|
277
|
+
el.classList.add("mark");
|
|
278
|
+
el.innerHTML = icon(t.mark, t.color);
|
|
279
|
+
var words = document.createElement("span");
|
|
280
|
+
words.textContent = text;
|
|
281
|
+
el.appendChild(words);
|
|
282
|
+
} else {
|
|
283
|
+
el.textContent = text;
|
|
284
|
+
}
|
|
175
285
|
el.style.color = ink;
|
|
176
286
|
if (t.kind === "underline") {
|
|
177
287
|
el.style.textDecoration = "underline";
|
|
178
288
|
el.style.textDecorationThickness = "0.08em";
|
|
179
289
|
el.style.textUnderlineOffset = "0.18em";
|
|
180
290
|
}
|
|
291
|
+
if (t.kind === "strike") {
|
|
292
|
+
el.style.textDecoration = "line-through";
|
|
293
|
+
el.style.textDecorationThickness = "0.08em";
|
|
294
|
+
}
|
|
181
295
|
var dark = (luma(ink) || 255) < 110;
|
|
182
296
|
el.dataset.dark = dark ? "1" : "0";
|
|
183
297
|
if (hasPlate) el.style.textShadow = dark ? "0 1px 10px rgba(255,255,255,0.35)" : "0 2px 14px rgba(0,0,0,0.45)";
|
|
@@ -186,6 +300,7 @@
|
|
|
186
300
|
}
|
|
187
301
|
|
|
188
302
|
var drawCopy = FRAME.draw_copy !== false;
|
|
303
|
+
(hasPlate ? [] : AD.shapes || []).forEach(addShape);
|
|
189
304
|
(drawCopy ? AD.copy || [] : []).forEach(addBlock);
|
|
190
305
|
if (AD.logo) addLogo(AD.logo);
|
|
191
306
|
|
|
@@ -198,7 +313,9 @@
|
|
|
198
313
|
// layout is written.
|
|
199
314
|
function fit(el) {
|
|
200
315
|
var max = Number(el.dataset.max);
|
|
201
|
-
|
|
316
|
+
// The room inside a card's padding, not the card's outer width.
|
|
317
|
+
var pad = getComputedStyle(el);
|
|
318
|
+
var width = el.getBoundingClientRect().width - parseFloat(pad.paddingLeft) - parseFloat(pad.paddingRight);
|
|
202
319
|
var scale = 1;
|
|
203
320
|
for (var i = 0; i < 40; i++) {
|
|
204
321
|
var tooTall = el.scrollHeight > max;
|
|
@@ -209,10 +326,42 @@
|
|
|
209
326
|
scale *= 0.94;
|
|
210
327
|
Array.prototype.forEach.call(el.querySelectorAll(".block"), function (c) {
|
|
211
328
|
c.style.fontSize = Number(c.dataset.base) * U * scale + "px";
|
|
329
|
+
c.dataset.scale = String(scale);
|
|
212
330
|
});
|
|
213
331
|
}
|
|
214
332
|
}
|
|
215
333
|
|
|
334
|
+
// A region's copy may run past its box, but never into the next region down the same
|
|
335
|
+
// column: the room it has ends where that one starts.
|
|
336
|
+
function keepClearOfNext(el) {
|
|
337
|
+
var r = el.getBoundingClientRect();
|
|
338
|
+
var room = Infinity;
|
|
339
|
+
Array.prototype.forEach.call(root.querySelectorAll(".group.placed"), function (other) {
|
|
340
|
+
if (other === el) return;
|
|
341
|
+
var q = other.getBoundingClientRect();
|
|
342
|
+
var sameColumn = Math.min(r.right, q.right) - Math.max(r.left, q.left) > 0;
|
|
343
|
+
if (sameColumn && q.top > r.top + 1) room = Math.min(room, q.top - r.top - 0.8 * U);
|
|
344
|
+
});
|
|
345
|
+
// Two regions the format already stacks on each other are left as they are: holding
|
|
346
|
+
// one to less than its own box would only shrink its type to nothing.
|
|
347
|
+
if (room === Infinity || room < parseFloat(el.style.minHeight)) return;
|
|
348
|
+
el.dataset.max = String(Math.min(Number(el.dataset.max), room));
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Lines set at one size in prompt.json stay one size in the ad: every one is drawn at
|
|
352
|
+
// the smallest scale any of them needed to fit.
|
|
353
|
+
function evenSizes() {
|
|
354
|
+
var least = {};
|
|
355
|
+
var blocks = Array.prototype.slice.call(root.querySelectorAll(".block"));
|
|
356
|
+
blocks.forEach(function (b) {
|
|
357
|
+
var s = Number(b.dataset.scale || 1);
|
|
358
|
+
least[b.dataset.base] = Math.min(least[b.dataset.base] || 1, s);
|
|
359
|
+
});
|
|
360
|
+
blocks.forEach(function (b) {
|
|
361
|
+
b.style.fontSize = Number(b.dataset.base) * U * least[b.dataset.base] + "px";
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
216
365
|
// A soft scrim behind a band of light type over the picture (or a light one behind
|
|
217
366
|
// dark type), so copy with no position of its own reads on any plate. Copy placed
|
|
218
367
|
// by the layout sits on the calm area the plate kept for it.
|
|
@@ -243,7 +392,9 @@
|
|
|
243
392
|
|
|
244
393
|
var ready = document.fonts && document.fonts.ready ? document.fonts.ready : Promise.resolve();
|
|
245
394
|
ready.then(function () {
|
|
395
|
+
Array.prototype.forEach.call(root.querySelectorAll(".group.placed"), keepClearOfNext);
|
|
246
396
|
containers().forEach(fit);
|
|
397
|
+
evenSizes();
|
|
247
398
|
["top", "bottom"].forEach(scrim);
|
|
248
399
|
root.setAttribute("data-ready", "1");
|
|
249
400
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "static-ad",
|
|
3
|
-
"title": "Static ad: the picture (or the
|
|
3
|
+
"title": "Static ad: the picture (or, text-only, the layout drawn from prompt.json) with what the concept holds drawn on top",
|
|
4
4
|
"description": "Written by `baker canvas scaffold-static-ad`, one copy per delivered format. Draws only what prompt.compose.json (derived from prompt.json) holds: each line of copy where the source layout placed it, set the way the reference set it (plain, underlined, with an arrow, as a button or sticker), in the staged fonts, and the logo file only when the concept shows one. The plate is the image model's clean picture; a text-only ad has none and sits on the brand colour. Width and height are this format's delivered size.",
|
|
5
5
|
"width": 1080,
|
|
6
6
|
"height": 1350,
|