@koda-sl/baker-cli 0.396.0-dev.88120240 → 0.396.0

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.
Files changed (29) hide show
  1. package/README.md +181 -83
  2. package/canvas/static-ad-composition/index.html +119 -63
  3. package/canvas/static-ad-composition/meta.json +6 -6
  4. package/dist/{chunk-7OVZ6SOA.js → chunk-BRIJ4OWT.js} +12 -1
  5. package/dist/{chunk-7OVZ6SOA.js.map → chunk-BRIJ4OWT.js.map} +1 -1
  6. package/dist/{chunk-EYN7G4KF.js → chunk-EEHNRAFA.js} +4 -4
  7. package/dist/{chunk-MNSF5SQ3.js → chunk-LHJ5IXBN.js} +3902 -2432
  8. package/dist/chunk-LHJ5IXBN.js.map +1 -0
  9. package/dist/{chunk-Z7WHG6TA.js → chunk-Q7RQ3H6Y.js} +3 -3
  10. package/dist/{chunk-J76AGFBH.js → chunk-QP7637HG.js} +3 -3
  11. package/dist/{chunk-TWSWUIDI.js → chunk-ZWMATKNS.js} +2 -2
  12. package/dist/cli.js +10907 -9258
  13. package/dist/cli.js.map +1 -1
  14. package/dist/{client-YUWLIYLF.js → client-4P6U75EX.js} +4 -4
  15. package/dist/engine/index.d.ts +178 -117
  16. package/dist/engine/index.js +3 -3
  17. package/dist/{env-RQGMYIQN.js → env-LJHSZSNA.js} +2 -2
  18. package/dist/{output-POG3LVBW.js → output-HVBFJX4I.js} +5 -5
  19. package/dist/{shared-ZAEUTUNU.js → shared-7ZEF4DIX.js} +6 -6
  20. package/package.json +1 -1
  21. package/dist/chunk-MNSF5SQ3.js.map +0 -1
  22. /package/dist/{chunk-EYN7G4KF.js.map → chunk-EEHNRAFA.js.map} +0 -0
  23. /package/dist/{chunk-Z7WHG6TA.js.map → chunk-Q7RQ3H6Y.js.map} +0 -0
  24. /package/dist/{chunk-J76AGFBH.js.map → chunk-QP7637HG.js.map} +0 -0
  25. /package/dist/{chunk-TWSWUIDI.js.map → chunk-ZWMATKNS.js.map} +0 -0
  26. /package/dist/{client-YUWLIYLF.js.map → client-4P6U75EX.js.map} +0 -0
  27. /package/dist/{env-RQGMYIQN.js.map → env-LJHSZSNA.js.map} +0 -0
  28. /package/dist/{output-POG3LVBW.js.map → output-HVBFJX4I.js.map} +0 -0
  29. /package/dist/{shared-ZAEUTUNU.js.map → shared-7ZEF4DIX.js.map} +0 -0
@@ -5,8 +5,10 @@
5
5
  <meta name="viewport" content="width=__WIDTH__, height=__HEIGHT__" />
6
6
  <script src="./gsap.min.js"></script>
7
7
  <style>
8
- /* The brand's own faces, staged beside this file by the render. One range per face,
9
- so a single-weight file is used as it is instead of being faked bolder. */
8
+ /* The ad's own faces, staged beside this file by the render (a brand file or a web font
9
+ file). One range per face, so a single-weight file is used as it is instead of being
10
+ faked bolder. The browser fetches a face only when text uses it, and text uses it
11
+ only when a face was staged (see FRAME.fonts below). */
10
12
  @font-face { font-family: 'AdHeadline'; src: url('./headline.font'); font-weight: 100 900; font-display: block; }
11
13
  @font-face { font-family: 'AdBody'; src: url('./body.font'); font-weight: 100 900; font-display: block; }
12
14
  * { margin: 0; padding: 0; box-sizing: border-box; }
@@ -14,22 +16,20 @@
14
16
  #root { position: absolute; inset: 0; overflow: hidden; }
15
17
  .plate { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: 50% 50%; }
16
18
  .scrim { position: absolute; left: 0; right: 0; pointer-events: none; display: none; }
17
- .band { position: absolute; display: flex; flex-direction: column; gap: 0; }
19
+ .group { position: absolute; display: flex; flex-direction: column; gap: 0; }
18
20
  .block { font-family: 'AdBody', 'Helvetica Neue', Arial, sans-serif; overflow-wrap: break-word; text-wrap: pretty; }
19
21
  .block.headline, .block.subheadline, .block.cta { font-family: 'AdHeadline', 'AdBody', 'Helvetica Neue', Arial, sans-serif; }
20
22
  .block.headline { text-wrap: balance; letter-spacing: -0.01em; }
21
- .cta-pill { display: inline-block; border-radius: 999px; font-weight: 700; line-height: 1.1; }
23
+ .fill { display: inline-block; line-height: 1.1; padding: 0.5em 1.2em; }
24
+ .fill.highlight { padding: 0.12em 0.4em; box-decoration-break: clone; -webkit-box-decoration-break: clone; }
25
+ .fill.sticker { transform: rotate(-3deg); }
22
26
  .logo { display: block; width: auto; object-fit: contain; }
23
27
  </style>
24
28
  </head>
25
29
  <body>
26
30
  <div id="root" data-composition-id="main" data-start="0" data-duration="1" data-width="__WIDTH__" data-height="__HEIGHT__">
27
- <img class="plate" id="plate" src="./plate.png" alt="" />
28
31
  <div class="scrim" id="scrim-top"></div>
29
32
  <div class="scrim" id="scrim-bottom"></div>
30
- <div class="band" id="band-top" data-role="copy-top"></div>
31
- <div class="band" id="band-middle" data-role="copy-middle"></div>
32
- <div class="band" id="band-bottom" data-role="copy-bottom"></div>
33
33
  </div>
34
34
 
35
35
  <script>
@@ -43,11 +43,31 @@
43
43
  var box = { x: safe.left * W, y: safe.top * H, w: W * (1 - safe.left - safe.right), h: H * (1 - safe.top - safe.bottom) };
44
44
  // One unit per percent of the shorter side, so type reads the same size on every format.
45
45
  var U = Math.min(W, H) / 100;
46
+ // A text-only ad has no picture (`plate: null`): the copy sits on the brand's ground.
47
+ var hasPlate = FRAME.plate !== null;
48
+ var ground = AD.background || { color: "#111111", ink: "#ffffff" };
46
49
 
47
50
  var SIZE = { x_large: 8.6, large: 6.6, medium: 4.4, small: 3.0 };
48
51
  var WEIGHT = { bold: 800, semibold: 600, regular: 400 };
49
52
  var CASE = { upper: "uppercase", title: "capitalize", sentence: "none" };
50
53
  var MAX_SHARE = { top: 0.44, middle: 0.3, bottom: 0.36 };
54
+ var RADIUS = { pill: "999px", rounded: "0.35em", square: "0" };
55
+
56
+ // With no face staged, the text never names the ad's faces, so the browser never asks
57
+ // for a file that is not there (a 404 the pre-render check refuses).
58
+ var FALLBACK_FACE = "'Helvetica Neue', Arial, sans-serif";
59
+ var facesStaged = FRAME.fonts !== false;
60
+
61
+ if (hasPlate) {
62
+ var plate = document.createElement("img");
63
+ plate.className = "plate";
64
+ plate.id = "plate";
65
+ plate.src = "./plate.png";
66
+ plate.alt = "";
67
+ root.insertBefore(plate, root.firstChild);
68
+ } else {
69
+ root.style.background = ground.color;
70
+ }
51
71
 
52
72
  function luma(hex) {
53
73
  var m = /^#?([0-9a-f]{6})$/i.exec(hex || "");
@@ -56,18 +76,23 @@
56
76
  return 0.2126 * ((v >> 16) & 255) + 0.7152 * ((v >> 8) & 255) + 0.0722 * (v & 255);
57
77
  }
58
78
 
59
- var bands = { top: [], middle: [], bottom: [] };
60
- var drawCopy = FRAME.draw_copy !== false;
61
- (drawCopy ? AD.copy || [] : []).forEach(function (b) {
62
- (bands[b.band] || bands.top).push(b);
79
+ function alignSelf(align) {
80
+ return align === "left" ? "flex-start" : align === "right" ? "flex-end" : "center";
81
+ }
82
+
83
+ // Every line is drawn in its group: the layout region the reference put it in (placed
84
+ // at that region's box on this format), or a band when the layout gave no position.
85
+ var groups = {};
86
+ var bands = { top: null, middle: null, bottom: null };
87
+ (AD.groups || []).forEach(function (g) {
88
+ groups[g.id] = g;
63
89
  });
64
90
 
65
91
  function bandEl(name) {
66
- return document.getElementById("band-" + name);
67
- }
68
-
69
- function place(name) {
70
- var el = bandEl(name);
92
+ if (bands[name]) return bands[name];
93
+ var el = document.createElement("div");
94
+ el.className = "group band";
95
+ el.dataset.band = name;
71
96
  el.style.left = box.x + "px";
72
97
  el.style.width = box.w + "px";
73
98
  el.style.gap = 1.6 * U + "px";
@@ -77,80 +102,110 @@
77
102
  el.style.top = box.y + box.h / 2 + "px";
78
103
  el.style.transform = "translateY(-50%)";
79
104
  }
105
+ el.dataset.max = String(box.h * MAX_SHARE[name]);
106
+ root.appendChild(el);
107
+ bands[name] = el;
108
+ return el;
80
109
  }
81
110
 
82
- function alignSelf(align) {
83
- return align === "left" ? "flex-start" : align === "right" ? "flex-end" : "center";
111
+ var placed = {};
112
+ function containerFor(groupId, band) {
113
+ var g = groups[groupId];
114
+ var at = g && g.at && g.at[FRAME.format];
115
+ if (!at) return bandEl((g && g.band) || band || "top");
116
+ if (placed[groupId]) return placed[groupId];
117
+ var el = document.createElement("div");
118
+ el.className = "group placed";
119
+ el.style.left = (at.x[0] / 100) * W + "px";
120
+ el.style.width = ((at.x[1] - at.x[0]) / 100) * W + "px";
121
+ el.style.top = (at.y[0] / 100) * H + "px";
122
+ el.style.minHeight = ((at.y[1] - at.y[0]) / 100) * H + "px";
123
+ el.style.justifyContent = "center";
124
+ el.style.gap = 1.2 * U + "px";
125
+ // Copy rewritten for our brand runs longer than the source's: allow some growth
126
+ // past the region before the type shrinks.
127
+ el.dataset.max = String(((at.y[1] - at.y[0]) / 100) * H * 1.35);
128
+ root.appendChild(el);
129
+ placed[groupId] = el;
130
+ return el;
84
131
  }
85
132
 
86
- function addLogo(name, align) {
133
+ function addLogo(logo) {
87
134
  var img = document.createElement("img");
88
135
  img.className = "logo";
89
136
  img.src = "./logo.png";
90
137
  img.alt = "";
91
138
  img.style.height = 5.4 * U + "px";
92
139
  img.style.maxWidth = box.w * 0.42 + "px";
93
- img.style.alignSelf = alignSelf(align);
140
+ img.style.alignSelf = alignSelf(logo.align);
94
141
  img.onerror = function () {
95
142
  img.remove();
96
143
  };
97
- var el = bandEl(name);
98
- if (name === "bottom") el.appendChild(img);
144
+ var el = containerFor(logo.group, logo.band);
145
+ if (el.dataset.band === "bottom") el.appendChild(img);
99
146
  else el.insertBefore(img, el.firstChild);
100
147
  }
101
148
 
102
- function addBlock(name, b) {
149
+ function addBlock(b) {
103
150
  var el = document.createElement("div");
104
151
  el.className = "block " + b.role;
105
- el.dataset.size = String(SIZE[b.size] || SIZE.medium);
106
- var size = b.role === "fine_print" ? 2.4 : b.role === "cta" ? 4.2 : SIZE[b.size] || SIZE.medium;
152
+ if (!facesStaged) el.style.fontFamily = FALLBACK_FACE;
153
+ var size = b.role === "fine_print" ? 2.4 : SIZE[b.size] || SIZE.medium;
107
154
  el.dataset.base = String(size);
108
155
  el.style.fontSize = size * U + "px";
109
- el.style.fontWeight = String(b.role === "cta" ? 700 : WEIGHT[b.weight] || 400);
156
+ el.style.fontWeight = String(WEIGHT[b.weight] || (b.role === "cta" ? 700 : 400));
110
157
  el.style.lineHeight = b.role === "headline" ? "1.04" : "1.22";
111
158
  el.style.textTransform = CASE[b.case] || "none";
112
159
  el.style.textAlign = b.align || "center";
113
160
  el.style.alignSelf = alignSelf(b.align);
114
- if (b.role === "cta") {
115
- var pill = document.createElement("span");
116
- pill.className = "cta-pill";
117
- pill.textContent = b.text;
118
- pill.style.background = AD.cta.background;
119
- pill.style.color = AD.cta.color;
120
- pill.style.padding = "0.55em 1.4em";
121
- el.appendChild(pill);
161
+ var t = b.treatment || { kind: b.role === "cta" && !AD.groups ? "button" : "plain" };
162
+ var ink = b.color || (hasPlate ? "#ffffff" : ground.ink);
163
+ var text = b.text;
164
+ if (t.kind === "arrow" && !/[→›»]\s*$/.test(text)) text = text + " →";
165
+ if (t.kind === "button" || t.kind === "sticker" || t.kind === "highlight") {
166
+ var fill = document.createElement("span");
167
+ fill.className = "fill " + t.kind;
168
+ fill.textContent = text;
169
+ fill.style.background = t.background || (AD.cta && AD.cta.background) || "#111111";
170
+ fill.style.color = t.color || (AD.cta && AD.cta.color) || "#ffffff";
171
+ fill.style.borderRadius = RADIUS[t.shape] || RADIUS.rounded;
172
+ el.appendChild(fill);
122
173
  } else {
123
- el.textContent = b.text;
124
- var ink = b.color || "#ffffff";
174
+ el.textContent = text;
125
175
  el.style.color = ink;
176
+ if (t.kind === "underline") {
177
+ el.style.textDecoration = "underline";
178
+ el.style.textDecorationThickness = "0.08em";
179
+ el.style.textUnderlineOffset = "0.18em";
180
+ }
126
181
  var dark = (luma(ink) || 255) < 110;
127
- el.style.textShadow = dark ? "0 1px 10px rgba(255,255,255,0.35)" : "0 2px 14px rgba(0,0,0,0.45)";
128
182
  el.dataset.dark = dark ? "1" : "0";
183
+ if (hasPlate) el.style.textShadow = dark ? "0 1px 10px rgba(255,255,255,0.35)" : "0 2px 14px rgba(0,0,0,0.45)";
129
184
  }
130
- bandEl(name).appendChild(el);
185
+ containerFor(b.group, b.band).appendChild(el);
131
186
  }
132
187
 
133
- ["top", "middle", "bottom"].forEach(function (name) {
134
- place(name);
135
- bands[name].forEach(function (b) {
136
- addBlock(name, b);
137
- });
138
- });
139
- if (AD.logo) addLogo(AD.logo.band, AD.logo.align);
140
-
141
- // Shrink a band's type until it fits its share of the safe area and no line runs
142
- // past the edge. The copy is whatever prompt.json says today, so its length is
143
- // not known when the layout is written.
144
- function fit(name) {
145
- var el = bandEl(name);
146
- var max = box.h * MAX_SHARE[name];
188
+ var drawCopy = FRAME.draw_copy !== false;
189
+ (drawCopy ? AD.copy || [] : []).forEach(addBlock);
190
+ if (AD.logo) addLogo(AD.logo);
191
+
192
+ function containers() {
193
+ return Array.prototype.slice.call(root.querySelectorAll(".group"));
194
+ }
195
+
196
+ // Shrink a group's type until it fits its room and no line runs past its edge. The
197
+ // copy is whatever prompt.json says today, so its length is not known when the
198
+ // layout is written.
199
+ function fit(el) {
200
+ var max = Number(el.dataset.max);
201
+ var width = el.getBoundingClientRect().width;
147
202
  var scale = 1;
148
203
  for (var i = 0; i < 40; i++) {
149
204
  var tooTall = el.scrollHeight > max;
150
205
  var tooWide = Array.prototype.some.call(el.children, function (c) {
151
- return c.scrollWidth > box.w + 1;
206
+ return c.scrollWidth > width + 1;
152
207
  });
153
- if (!tooTall && !tooWide) break;
208
+ if ((!tooTall && !tooWide) || scale < 0.45) break;
154
209
  scale *= 0.94;
155
210
  Array.prototype.forEach.call(el.querySelectorAll(".block"), function (c) {
156
211
  c.style.fontSize = Number(c.dataset.base) * U * scale + "px";
@@ -158,15 +213,16 @@
158
213
  }
159
214
  }
160
215
 
161
- // A soft scrim behind a band of light type (or a light one behind dark type), so the
162
- // copy reads on any plate without boxing it in.
216
+ // A soft scrim behind a band of light type over the picture (or a light one behind
217
+ // dark type), so copy with no position of its own reads on any plate. Copy placed
218
+ // by the layout sits on the calm area the plate kept for it.
163
219
  function scrim(name) {
164
- if (name === "middle" || bands[name].length === 0) return;
165
- var el = bandEl(name);
220
+ var el = bands[name];
221
+ if (!hasPlate || !el) return;
166
222
  var s = document.getElementById("scrim-" + name);
167
- var firstText = el.querySelector(".block:not(.cta)");
168
- var dark = firstText && firstText.dataset.dark === "1";
169
- var tint = dark ? "255,255,255" : "0,0,0";
223
+ var firstText = el.querySelector(".block[data-dark]");
224
+ if (!firstText) return;
225
+ var tint = firstText.dataset.dark === "1" ? "255,255,255" : "0,0,0";
170
226
  var rect = el.getBoundingClientRect();
171
227
  var reach = name === "top" ? rect.bottom + 6 * U : H - rect.top + 6 * U;
172
228
  s.style.display = "block";
@@ -187,7 +243,7 @@
187
243
 
188
244
  var ready = document.fonts && document.fonts.ready ? document.fonts.ready : Promise.resolve();
189
245
  ready.then(function () {
190
- ["top", "middle", "bottom"].forEach(fit);
246
+ containers().forEach(fit);
191
247
  ["top", "bottom"].forEach(scrim);
192
248
  root.setAttribute("data-ready", "1");
193
249
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "static-ad",
3
- "title": "Static ad: a rendered plate with the copy, the call to action and the logo drawn on top",
4
- "description": "Written by `baker canvas scaffold-static-ad`, one copy per delivered format. The plate is the image model's clean picture; the copy, the CTA and the logo are drawn here from prompt.compose.json (derived from prompt.json) in the brand's own fonts and logo file. Width and height are this format's delivered size.",
3
+ "title": "Static ad: the picture (or the brand colours) with what the concept holds drawn on top",
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,
7
7
  "fps": 1,
@@ -9,15 +9,15 @@
9
9
  "inputs": {
10
10
  "plate": {
11
11
  "kind": "image",
12
- "required": true,
12
+ "required": false,
13
13
  "staged_as": "plate.png",
14
- "description": "The picture, with no text and no logo."
14
+ "description": "The picture, with no overlay text and no logo. Absent for a text-only ad."
15
15
  },
16
16
  "logo": {
17
17
  "kind": "image",
18
18
  "required": false,
19
19
  "staged_as": "logo.png",
20
- "description": "The brand's logo file."
20
+ "description": "The brand logo file, wired only when the concept shows a logo."
21
21
  },
22
22
  "font_headline": {
23
23
  "kind": "font",
@@ -47,7 +47,7 @@
47
47
  "frame": {
48
48
  "kind": "json",
49
49
  "required": true,
50
- "description": "This format's safe area and whether the copy is drawn here."
50
+ "description": "This format: its safe area, whether copy is drawn, the picture it is cut from (plate: null for none), and whether fonts are staged."
51
51
  }
52
52
  }
53
53
  }
@@ -947,6 +947,12 @@ function round2(value) {
947
947
  function asRecord(value) {
948
948
  return value && typeof value === "object" ? value : {};
949
949
  }
950
+ function bakerUses(value) {
951
+ const uses = asRecord(value);
952
+ const companies = typeof uses.companies === "number" ? uses.companies : 0;
953
+ if (companies === 0) return {};
954
+ return { baker_uses: companies, ...uses.ours === true ? { already_adapted: true } : {} };
955
+ }
950
956
  function compactWinningAd(record) {
951
957
  const dna = asRecord(record.dna);
952
958
  return {
@@ -959,6 +965,10 @@ function compactWinningAd(record) {
959
965
  advertiser_id: String(record.advertiser_id ?? ""),
960
966
  ad_id: String(record.ad_id ?? ""),
961
967
  media_url: String(record.media_url ?? ""),
968
+ ...typeof dna.visual === "string" && dna.visual ? { visual: dna.visual } : {},
969
+ ...typeof dna.container === "string" && dna.container ? { container: dna.container } : {},
970
+ ...typeof dna.mechanism === "string" && dna.mechanism ? { mechanism: dna.mechanism } : {},
971
+ ...bakerUses(record.baker_uses),
962
972
  ...record.source === "followed" || record.source === "library" ? { source: record.source } : {}
963
973
  };
964
974
  }
@@ -976,6 +986,7 @@ function fullWinningAd(record) {
976
986
  awareness_stage: dna.awareness_stage ?? null,
977
987
  target_persona: dna.target_persona ?? null,
978
988
  hook_archetype: dna.hook_archetype ?? null,
989
+ mechanism: dna.mechanism ?? null,
979
990
  industry: dna.industry ?? null
980
991
  };
981
992
  }
@@ -1300,4 +1311,4 @@ export {
1300
1311
  captureVimeoCookie,
1301
1312
  childEnvWith
1302
1313
  };
1303
- //# sourceMappingURL=chunk-7OVZ6SOA.js.map
1314
+ //# sourceMappingURL=chunk-BRIJ4OWT.js.map