@cliangdev/conductor 0.27.0 → 0.28.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.
- package/assets/claude/agents/conductor-content-art-director.md +16 -4
- package/assets/claude/agents/conductor-content-scriptwriter.md +4 -0
- package/assets/claude/commands/conductor/creative.md +2 -1
- package/assets/claude/skills/conductor-content-studio/SKILL.md +39 -17
- package/assets/claude/skills/conductor-content-studio/references/capabilities.md +11 -2
- package/assets/claude/skills/conductor-content-studio/references/compliance-checklist.md +1 -0
- package/assets/claude/skills/conductor-content-studio/references/idea-template.md +6 -1
- package/assets/claude/skills/conductor-content-studio/references/publish-pack-template.md +3 -1
- package/assets/claude/skills/conductor-content-studio/references/script-template.md +1 -1
- package/assets/claude/skills/conductor-creative/SKILL.md +37 -8
- package/assets/cli-manifest.json +8 -0
- package/dist/commands/doctor.d.ts +5 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +20 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/creative/assertions.js +57 -7
- package/dist/creative/font.js +29 -0
- package/dist/creative/frame.css +2 -0
- package/dist/creative/frame.js +7 -4
- package/dist/creative/layouts/bleed/layout.css +14 -7
- package/dist/creative/motion.js +61 -19
- package/dist/creative/render.js +21 -2
- package/dist/creative/sheet.js +7 -2
- package/dist/creative/styles.css +16 -7
- package/dist/lib/codex-image.d.ts +51 -0
- package/dist/lib/codex-image.d.ts.map +1 -0
- package/dist/lib/codex-image.js +329 -0
- package/dist/lib/codex-image.js.map +1 -0
- package/dist/lib/media-probe.d.ts +4 -0
- package/dist/lib/media-probe.d.ts.map +1 -1
- package/dist/lib/media-probe.js +5 -2
- package/dist/lib/media-probe.js.map +1 -1
- package/dist/lib/provenance-sidecar.d.ts +19 -0
- package/dist/lib/provenance-sidecar.d.ts.map +1 -0
- package/dist/lib/provenance-sidecar.js +39 -0
- package/dist/lib/provenance-sidecar.js.map +1 -0
- package/dist/mcp/index.d.ts +531 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +34 -2
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/tools/creative-drafts.d.ts +12 -0
- package/dist/mcp/tools/creative-drafts.d.ts.map +1 -1
- package/dist/mcp/tools/creative-drafts.js +36 -7
- package/dist/mcp/tools/creative-drafts.js.map +1 -1
- package/dist/mcp/tools/creative-images.d.ts +39 -0
- package/dist/mcp/tools/creative-images.d.ts.map +1 -0
- package/dist/mcp/tools/creative-images.js +89 -0
- package/dist/mcp/tools/creative-images.js.map +1 -0
- package/dist/mcp/tools/creatives.d.ts.map +1 -1
- package/dist/mcp/tools/creatives.js +7 -0
- package/dist/mcp/tools/creatives.js.map +1 -1
- package/package.json +1 -1
package/dist/creative/frame.css
CHANGED
|
@@ -87,6 +87,8 @@
|
|
|
87
87
|
}
|
|
88
88
|
.cc-board--light .cc-headline { color: var(--cc-ink); }
|
|
89
89
|
.cc-headline em { font-style: italic; color: var(--cc-accent); }
|
|
90
|
+
.cc-headline em.cc-accent-after-space { margin-left: 0.07em; }
|
|
91
|
+
.cc-headline em.cc-accent-before-space { margin-right: 0.04em; }
|
|
90
92
|
/* Word spans the motion presets animate: inline-block so transforms apply; the spaces between them stay
|
|
91
93
|
* plain text, so lines wrap exactly as they do without the spans. */
|
|
92
94
|
.cc-word { display: inline-block; }
|
package/dist/creative/frame.js
CHANGED
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
import { resolveAd, resolveSequence, renderBoard, fitBoard } from './render.js';
|
|
38
38
|
import { placements as DEFAULT_PLACEMENTS } from './placements.js';
|
|
39
39
|
import { layouts as DEFAULT_LAYOUTS } from './layouts/index.js';
|
|
40
|
-
import { ensureBrandFont } from './font.js';
|
|
41
|
-
import { runAssertions } from './assertions.js';
|
|
40
|
+
import { ensureBrandFont, settleImages } from './font.js';
|
|
41
|
+
import { runAssertions, expectedElements } from './assertions.js';
|
|
42
42
|
import { applyMotion, seekBackgroundVideo } from './motion.js';
|
|
43
43
|
|
|
44
44
|
function finish(result) {
|
|
@@ -83,10 +83,13 @@ async function main() {
|
|
|
83
83
|
|
|
84
84
|
await document.fonts.ready;
|
|
85
85
|
await Promise.all([...document.images].map((img) => (img.decode ? img.decode().catch(() => {}) : null)));
|
|
86
|
+
await settleImages(board); // also the bleed photo layer, a CSS background `document.images` does not list
|
|
86
87
|
// Motion presets restructure the headline (word spans): build that structure, in its final state,
|
|
87
88
|
// before fitting, so the type is sized for the layout every captured frame will have.
|
|
88
89
|
if (spec.motion) applyMotion(board, spec.motion, spec.motion.durationSec || 8, { durationSec: spec.motion.durationSec || 8 });
|
|
89
90
|
fitBoard(board, placementsReg);
|
|
91
|
+
// What this spec requires on the board: a required element that did not render fails the checks.
|
|
92
|
+
const expect = expectedElements(ad, spec.brand);
|
|
90
93
|
|
|
91
94
|
if (spec.motion) {
|
|
92
95
|
const durationSec = spec.motion.durationSec || 8;
|
|
@@ -102,13 +105,13 @@ async function main() {
|
|
|
102
105
|
return true;
|
|
103
106
|
};
|
|
104
107
|
// A motion frame is captured at 1x (video), so the photo only needs to cover the placement itself.
|
|
105
|
-
window.__assertBoard = async () => runAssertions(board, placement, spec.brand || {}, { outputScale: 1 });
|
|
108
|
+
window.__assertBoard = async () => runAssertions(board, placement, spec.brand || {}, { outputScale: 1, expect });
|
|
106
109
|
|
|
107
110
|
finish({ ok: true, width: placement.w, height: placement.h });
|
|
108
111
|
return;
|
|
109
112
|
}
|
|
110
113
|
|
|
111
|
-
const { errors, warnings, checks, width, height } = await runAssertions(board, placement, spec.brand || {});
|
|
114
|
+
const { errors, warnings, checks, width, height } = await runAssertions(board, placement, spec.brand || {}, { expect });
|
|
112
115
|
finish({ ok: errors.length === 0, errors, warnings, checks, width, height });
|
|
113
116
|
} catch (err) {
|
|
114
117
|
finish({ ok: false, error: String((err && err.message) || err) });
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
/* bleed — photo covers the whole artboard, text sits over a scrim. */
|
|
2
2
|
|
|
3
3
|
.cc-board--bleed {
|
|
4
|
-
background-image: var(--cc-photo);
|
|
5
|
-
/* motion.js's Ken Burns pan/zoom (a MOTION creative only) sets --cc-bg-size/--cc-bg-pos on the
|
|
6
|
-
board; a background-image has no `transform`, so the zoom/pan is expressed as a growing
|
|
7
|
-
background-size and a shifting background-position instead. Unset for STILL: falls back to the
|
|
8
|
-
original fixed cover/focal. */
|
|
9
|
-
background-size: var(--cc-bg-size, cover);
|
|
10
|
-
background-position: var(--cc-bg-pos, var(--cc-focal, 50% 50%));
|
|
11
4
|
justify-content: flex-end;
|
|
12
5
|
}
|
|
13
6
|
|
|
7
|
+
/* The photo: a real layer under the copy (render.js adds it first, so plain DOM order keeps the panel and
|
|
8
|
+
any clip video above it). `cover` + `no-repeat` mean it can never tile or leave a strip uncovered;
|
|
9
|
+
the focal point is its background-position. motion.js's Ken Burns pan/zoom (a MOTION creative only)
|
|
10
|
+
is a transform on this layer, kept inside the overhang a scale >= 1 gives it; unset for STILL. */
|
|
11
|
+
.cc-bg-photo {
|
|
12
|
+
position: absolute; inset: 0;
|
|
13
|
+
background-image: var(--cc-photo);
|
|
14
|
+
background-repeat: no-repeat;
|
|
15
|
+
background-size: cover;
|
|
16
|
+
background-position: var(--cc-focal, 50% 50%);
|
|
17
|
+
transform: var(--cc-bg-transform, none);
|
|
18
|
+
transform-origin: center center;
|
|
19
|
+
}
|
|
20
|
+
|
|
14
21
|
/* Fill variant (9x16, 4x5, story): the scrim covers the artboard and the
|
|
15
22
|
copy is distributed from top to bottom over it. */
|
|
16
23
|
.cc-board__panel--fill {
|
package/dist/creative/motion.js
CHANGED
|
@@ -136,6 +136,45 @@ function wrapWordsInPlace(container) {
|
|
|
136
136
|
container.appendChild(frag);
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
/* ── accent-pop: how far a word may grow without eating the spaces around it ─────────────────────
|
|
140
|
+
* A scaled inline-block keeps its layout box, so growing it moves its ink into the neighbouring spaces:
|
|
141
|
+
* at 1.18x a long accent word swallowed the space before it ("slightdelay") for the first half second.
|
|
142
|
+
* The pop is therefore limited so each side grows by at most POP_GAP_EM of the font size (two accent words share one space, so each takes only a sliver of it), and only on a
|
|
143
|
+
* side that HAS a space (a word glued to punctuation, like "delay" in "delay.", does not grow towards it);
|
|
144
|
+
* the origin is placed so the growth lands on the open sides. Layout-less environments (a DOM shim)
|
|
145
|
+
* report no width and get the plain, uncapped pop. */
|
|
146
|
+
|
|
147
|
+
const POP_PEAK = 1.18;
|
|
148
|
+
const POP_GAP_EM = 0.04;
|
|
149
|
+
|
|
150
|
+
function hasGap(word, direction) {
|
|
151
|
+
const prop = direction < 0 ? 'previousSibling' : 'nextSibling';
|
|
152
|
+
let node = word[prop];
|
|
153
|
+
// The first/last word of the accent <em> neighbours whatever sits beside the <em> itself.
|
|
154
|
+
if (!node && word.parentNode && word.parentNode.tagName === 'EM') node = word.parentNode[prop];
|
|
155
|
+
if (!node) return true; // a line edge: nothing to run into
|
|
156
|
+
if (node.nodeType !== 3) return false;
|
|
157
|
+
return direction < 0 ? /\s$/.test(node.textContent) : /^\s/.test(node.textContent);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Pure-ish (reads the word's measured width and font size): `{ peak, originX }` — the largest pop scale
|
|
161
|
+
* that keeps the word clear of its neighbours, and the transform-origin x (percent) that puts the growth
|
|
162
|
+
* on the sides that have a space. */
|
|
163
|
+
function popLimits(word) {
|
|
164
|
+
const width = word.offsetWidth;
|
|
165
|
+
const view = word.ownerDocument && word.ownerDocument.defaultView;
|
|
166
|
+
const size = view && view.getComputedStyle ? parseFloat(view.getComputedStyle(word).fontSize) : NaN;
|
|
167
|
+
if (!(width > 0) || !(size > 0)) return { peak: POP_PEAK, originX: 50 };
|
|
168
|
+
const budget = POP_GAP_EM * size;
|
|
169
|
+
const left = hasGap(word, -1) ? budget : 0;
|
|
170
|
+
const right = hasGap(word, 1) ? budget : 0;
|
|
171
|
+
if (left + right === 0) return { peak: 1, originX: 50 };
|
|
172
|
+
return {
|
|
173
|
+
peak: Math.min(POP_PEAK, 1 + (left + right) / width),
|
|
174
|
+
originX: round2((100 * left) / (left + right)),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
139
178
|
/* ── headline (preset-dependent) ──────────────────────────────────────────── */
|
|
140
179
|
|
|
141
180
|
function applyHeadline(board, preset, tSec, inEndCard) {
|
|
@@ -189,11 +228,12 @@ function applyHeadline(board, preset, tSec, inEndCard) {
|
|
|
189
228
|
const fadeProgress = windowProgress(tSec, ACCENT_POP_FADE_WINDOW[0], ACCENT_POP_FADE_WINDOW[1]);
|
|
190
229
|
setFade(headline, fadeProgress, null);
|
|
191
230
|
const popProgress = windowProgress(tSec, ACCENT_POP_POP_WINDOW[0], ACCENT_POP_POP_WINDOW[1]);
|
|
192
|
-
// Scale settles
|
|
193
|
-
const scale = round2(lerp(1.18, 1, popProgress));
|
|
231
|
+
// Scale settles peak → 1; brightness pulses up and back to 1, so the end card starts with no jump.
|
|
194
232
|
const brightness = round2(1 + 0.3 * Math.sin(Math.PI * popProgress));
|
|
195
233
|
accentWords.forEach((w) => {
|
|
196
|
-
|
|
234
|
+
const { peak, originX } = popLimits(w);
|
|
235
|
+
const scale = round2(lerp(peak, 1, popProgress));
|
|
236
|
+
w.style.transformOrigin = `${originX}% 70%`;
|
|
197
237
|
w.style.transform = scale === 1 ? 'none' : `scale(${scale})`;
|
|
198
238
|
w.style.filter = brightness === 1 ? 'none' : `brightness(${brightness})`;
|
|
199
239
|
});
|
|
@@ -243,26 +283,28 @@ export function backgroundMotionState(motion, tSec, durationSec) {
|
|
|
243
283
|
return { scale: 1, panPct: 0 }; // 'none'
|
|
244
284
|
}
|
|
245
285
|
|
|
246
|
-
|
|
247
|
-
*
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
286
|
+
/** Pure: the rect (relative to the board/band/card it fills) the background layer occupies once
|
|
287
|
+
* `backgroundMotionState`'s transform is applied to a layer that exactly fills a `w` x `h` box. The
|
|
288
|
+
* transform is `translateX(panPct%) scale(scale)` (translate first, so the pan is in the BOX's own
|
|
289
|
+
* pixels, not the scaled layer's): a scale >= 1 overhangs each side by (scale - 1) / 2, and the pan is
|
|
290
|
+
* kept inside that overhang, so the layer covers the box at every `t`. Exported for tests. */
|
|
291
|
+
export function backgroundLayerRect(w, h, state) {
|
|
292
|
+
const { scale, panPct } = state;
|
|
293
|
+
const width = w * scale;
|
|
294
|
+
const height = h * scale;
|
|
295
|
+
const left = (w - width) / 2 + (panPct / 100) * w;
|
|
296
|
+
const top = (h - height) / 2;
|
|
297
|
+
return { left, top, right: left + width, bottom: top + height, width, height };
|
|
252
298
|
}
|
|
253
299
|
|
|
254
300
|
function applyBackgroundMotion(board, motion, tSec, durationSec) {
|
|
255
301
|
const { scale, panPct } = backgroundMotionState(motion, tSec, durationSec);
|
|
256
|
-
//
|
|
257
|
-
// never calls applyMotion at all) is untouched:
|
|
258
|
-
//
|
|
259
|
-
//
|
|
260
|
-
//
|
|
261
|
-
|
|
262
|
-
board.style.setProperty('--cc-bg-transform', `scale(${scale}) translateX(${panPct}%)`);
|
|
263
|
-
board.style.setProperty('--cc-bg-size', `${round2(scale * 100)}%`);
|
|
264
|
-
const focal = board.style.getPropertyValue('--cc-focal') || '50% 50%';
|
|
265
|
-
board.style.setProperty('--cc-bg-pos', panBackgroundPosition(focal, panPct));
|
|
302
|
+
// One CSS var, consumed by every layout's photo layer, with a no-op fallback so a STILL board (which
|
|
303
|
+
// never calls applyMotion at all) is untouched: `.cc-bg-photo` (bleed's photo layer),
|
|
304
|
+
// `.cc-board__band img` / `.cc-board__card img` and `.cc-bg-video`. The layer is `cover`-sized, never
|
|
305
|
+
// scaled below 1 and never panned past its overhang, so no edge or repeat can show (see
|
|
306
|
+
// backgroundLayerRect). The focal point stays the layer's own object-/background-position.
|
|
307
|
+
board.style.setProperty('--cc-bg-transform', `translateX(${panPct}%) scale(${scale})`);
|
|
266
308
|
}
|
|
267
309
|
|
|
268
310
|
/* ── the public entry point ───────────────────────────────────────────────── */
|
package/dist/creative/render.js
CHANGED
|
@@ -159,7 +159,11 @@ export function resolveSequence(raw, placements, layouts) {
|
|
|
159
159
|
if (!raw.sequenceKind || !Array.isArray(seq) || !seq.length) return [];
|
|
160
160
|
const last = seq.length - 1;
|
|
161
161
|
return seq.map((frame, i) => {
|
|
162
|
-
|
|
162
|
+
// A beat inherits the creative for anything it leaves out, and the API sends an unset beat field as
|
|
163
|
+
// null: a null must not overwrite the creative's own value (a beat with `photoUrl: null` rendered
|
|
164
|
+
// with no photo at all).
|
|
165
|
+
const own = Object.fromEntries(Object.entries(frame).filter(([, v]) => v != null));
|
|
166
|
+
const merged = resolveAd({ ...raw, ...own, sequence: undefined, sequenceKind: undefined }, placements, layouts);
|
|
163
167
|
// null and undefined both mean "not set" (the API sends null): the CTA then lands on the last frame only.
|
|
164
168
|
merged.showCta = frame.cta != null ? frame.cta : i === last;
|
|
165
169
|
// Body is per-frame opt-in: a hook frame can be a headline and nothing else.
|
|
@@ -260,7 +264,13 @@ function buildHeadline(ad, parent) {
|
|
|
260
264
|
return h;
|
|
261
265
|
}
|
|
262
266
|
if (m[1]) h.appendChild(document.createTextNode(m[1]));
|
|
263
|
-
el('em', null, h)
|
|
267
|
+
const em = el('em', null, h);
|
|
268
|
+
em.textContent = m[2];
|
|
269
|
+
// Italic correction: a slanted accent leans into the upright word before it (and its last letter
|
|
270
|
+
// overhangs the word after), so a plain word space reads as no space at all ("slightdelay").
|
|
271
|
+
// Widen only the gaps that are real spaces, never one against glued punctuation.
|
|
272
|
+
if (/\s$/.test(m[1])) em.classList.add('cc-accent-after-space');
|
|
273
|
+
if (/^\s/.test(m[3])) em.classList.add('cc-accent-before-space');
|
|
264
274
|
if (m[3]) h.appendChild(document.createTextNode(m[3]));
|
|
265
275
|
return h;
|
|
266
276
|
}
|
|
@@ -405,6 +415,7 @@ export function renderBoard(ad, placementKey, placements, layouts, brand, opts)
|
|
|
405
415
|
(ad.isSequence ? ' cc-board--seq' : '') +
|
|
406
416
|
(ad.isStory ? ' cc-board--story' : ''));
|
|
407
417
|
board.dataset.ratio = placementKey;
|
|
418
|
+
board.dataset.layout = kind;
|
|
408
419
|
|
|
409
420
|
applyBrandTokens(board, brand);
|
|
410
421
|
board.style.setProperty('--cc-board-w', placement.w + 'px');
|
|
@@ -435,6 +446,11 @@ export function renderBoard(ad, placementKey, placements, layouts, brand, opts)
|
|
|
435
446
|
let panel;
|
|
436
447
|
if (photoType === 'background') {
|
|
437
448
|
board.style.setProperty('--cc-photo', ad.photoUrl ? 'url("' + ad.photoUrl + '")' : 'none');
|
|
449
|
+
// The photo is its own layer, not the board's background: a MOTION creative's Ken Burns zoom/pan is a
|
|
450
|
+
// transform on this layer, which always covers the board (cover, no-repeat) whatever the zoom. A
|
|
451
|
+
// background-size percentage on the board did not: it was relative to the photo's WIDTH, so a
|
|
452
|
+
// landscape photo on a tall board ended up shorter than the board and tiled (see motion.js).
|
|
453
|
+
if (ad.photoUrl) el('div', 'cc-bg-photo', board).setAttribute('aria-hidden', 'true');
|
|
438
454
|
// The video (when present) must land BEFORE the panel, so the panel's own
|
|
439
455
|
// stacking (position:relative, later in the DOM) renders above it.
|
|
440
456
|
if (ad.backgroundVideoUrl) buildBgVideo(board, ad.backgroundVideoUrl);
|
|
@@ -474,6 +490,9 @@ export function renderBoard(ad, placementKey, placements, layouts, brand, opts)
|
|
|
474
490
|
if (ad.body && !dropBody) {
|
|
475
491
|
el('p', 'cc-body', panel).textContent = ad.body;
|
|
476
492
|
}
|
|
493
|
+
// A layout may not have room for a body line at some placements (stacked at 4x5): keep that behaviour,
|
|
494
|
+
// but record it on the board so runAssertions can warn that supplied copy is not on the artwork.
|
|
495
|
+
if (ad.body && dropBody) board.dataset.bodyDropped = 'true';
|
|
477
496
|
if (ad.showCta !== false) buildCta(brand, panel, placement);
|
|
478
497
|
|
|
479
498
|
if (opts.safe && placement.safe.bottom) {
|
package/dist/creative/sheet.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* // motionKeyTimes()): one ROW per time, each row
|
|
16
16
|
* // holding every placement in placementKeys at
|
|
17
17
|
* // that time. Ignored for a sequence creative
|
|
18
|
-
* // (MOTION
|
|
18
|
+
* // (the backend refuses a MOTION creative with a sequence).
|
|
19
19
|
* }
|
|
20
20
|
*
|
|
21
21
|
* Result contract: window.__RENDER_RESULT = { ok: true, width, height } |
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
import { resolveAd, resolveSequence, renderBoard, fitAll } from './render.js';
|
|
26
26
|
import { placements as DEFAULT_PLACEMENTS } from './placements.js';
|
|
27
27
|
import { layouts as DEFAULT_LAYOUTS } from './layouts/index.js';
|
|
28
|
-
import { ensureBrandFont } from './font.js';
|
|
28
|
+
import { ensureBrandFont, settleImages } from './font.js';
|
|
29
29
|
import { applyMotion } from './motion.js';
|
|
30
30
|
|
|
31
31
|
const SHEET_SCALE = 0.34;
|
|
@@ -105,6 +105,11 @@ async function main() {
|
|
|
105
105
|
keys.forEach((key) => addCell(ad, key, (placementsReg[key] && placementsReg[key].label) || key));
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
// Every photo and logo must have arrived before fitting (a logo's width shapes the layout) and
|
|
109
|
+
// before the job screenshots the sheet: a sheet captured mid-load showed boards with no photo and no
|
|
110
|
+
// logo, which read as a render that had dropped them.
|
|
111
|
+
await settleImages(sheet);
|
|
112
|
+
|
|
108
113
|
// Fit every board at its TRUE, unscaled size first (a CSS transform
|
|
109
114
|
// shrinks the measured rect and would throw fitBoard's line-count math
|
|
110
115
|
// off), then apply the display scale — the same order mount.js uses.
|
package/dist/creative/styles.css
CHANGED
|
@@ -134,6 +134,8 @@
|
|
|
134
134
|
}
|
|
135
135
|
.cc-board--light .cc-headline { color: var(--cc-ink); }
|
|
136
136
|
.cc-headline em { font-style: italic; color: var(--cc-accent); }
|
|
137
|
+
.cc-headline em.cc-accent-after-space { margin-left: 0.07em; }
|
|
138
|
+
.cc-headline em.cc-accent-before-space { margin-right: 0.04em; }
|
|
137
139
|
/* Word spans the motion presets animate: inline-block so transforms apply; the spaces between them stay
|
|
138
140
|
* plain text, so lines wrap exactly as they do without the spans. */
|
|
139
141
|
.cc-word { display: inline-block; }
|
|
@@ -245,16 +247,23 @@
|
|
|
245
247
|
/* bleed — photo covers the whole artboard, text sits over a scrim. */
|
|
246
248
|
|
|
247
249
|
.cc-board--bleed {
|
|
248
|
-
background-image: var(--cc-photo);
|
|
249
|
-
/* motion.js's Ken Burns pan/zoom (a MOTION creative only) sets --cc-bg-size/--cc-bg-pos on the
|
|
250
|
-
board; a background-image has no `transform`, so the zoom/pan is expressed as a growing
|
|
251
|
-
background-size and a shifting background-position instead. Unset for STILL: falls back to the
|
|
252
|
-
original fixed cover/focal. */
|
|
253
|
-
background-size: var(--cc-bg-size, cover);
|
|
254
|
-
background-position: var(--cc-bg-pos, var(--cc-focal, 50% 50%));
|
|
255
250
|
justify-content: flex-end;
|
|
256
251
|
}
|
|
257
252
|
|
|
253
|
+
/* The photo: a real layer under the copy (render.js adds it first, so plain DOM order keeps the panel and
|
|
254
|
+
any clip video above it). `cover` + `no-repeat` mean it can never tile or leave a strip uncovered;
|
|
255
|
+
the focal point is its background-position. motion.js's Ken Burns pan/zoom (a MOTION creative only)
|
|
256
|
+
is a transform on this layer, kept inside the overhang a scale >= 1 gives it; unset for STILL. */
|
|
257
|
+
.cc-bg-photo {
|
|
258
|
+
position: absolute; inset: 0;
|
|
259
|
+
background-image: var(--cc-photo);
|
|
260
|
+
background-repeat: no-repeat;
|
|
261
|
+
background-size: cover;
|
|
262
|
+
background-position: var(--cc-focal, 50% 50%);
|
|
263
|
+
transform: var(--cc-bg-transform, none);
|
|
264
|
+
transform-origin: center center;
|
|
265
|
+
}
|
|
266
|
+
|
|
258
267
|
/* Fill variant (9x16, 4x5, story): the scrim covers the artboard and the
|
|
259
268
|
copy is distributed from top to bottom over it. */
|
|
260
269
|
.cc-board__panel--fill {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export type ImageAspect = 'portrait' | 'square' | 'landscape';
|
|
2
|
+
export declare const ASPECT_SIZES: Record<ImageAspect, string>;
|
|
3
|
+
export declare const NO_CODEX_HINT = "Codex CLI not found \u2014 install it (npm i -g @openai/codex) and run `codex login`, or set CONDUCTOR_CODEX_PATH.";
|
|
4
|
+
export declare const NOT_LOGGED_IN_HINT = "Codex CLI is not logged in \u2014 run `codex login` (see `conductor doctor`).";
|
|
5
|
+
export interface GenerateImagesParams {
|
|
6
|
+
prompt: string;
|
|
7
|
+
aspect?: ImageAspect;
|
|
8
|
+
count?: number;
|
|
9
|
+
referenceImagePaths?: string[];
|
|
10
|
+
outDir: string;
|
|
11
|
+
onProgress?: (message: string) => void;
|
|
12
|
+
timeoutMs?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface GeneratedImage {
|
|
15
|
+
path: string;
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
}
|
|
19
|
+
export interface GenerateImagesResult {
|
|
20
|
+
files: GeneratedImage[];
|
|
21
|
+
provenance: {
|
|
22
|
+
source: string;
|
|
23
|
+
licence: string;
|
|
24
|
+
aiGenerated: true;
|
|
25
|
+
};
|
|
26
|
+
revisedPrompt: string;
|
|
27
|
+
outDir: string;
|
|
28
|
+
/** JPEG (or small PNG) of the first image for inline display; absent when none fits ~1 MB. */
|
|
29
|
+
preview?: {
|
|
30
|
+
data: Buffer;
|
|
31
|
+
mimeType: string;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** `CONDUCTOR_CODEX_PATH`, else `codex` on PATH. Cached for the process lifetime. */
|
|
35
|
+
export declare function resolveCodexPath(): Promise<string>;
|
|
36
|
+
/** Test-only: the resolved path is cached, so tests that vary availability reset it. */
|
|
37
|
+
export declare function resetCodexPathCache(): void;
|
|
38
|
+
export type CodexStatus = {
|
|
39
|
+
state: 'ready';
|
|
40
|
+
version: string;
|
|
41
|
+
} | {
|
|
42
|
+
state: 'logged-out';
|
|
43
|
+
version: string;
|
|
44
|
+
} | {
|
|
45
|
+
state: 'missing';
|
|
46
|
+
};
|
|
47
|
+
/** For `conductor doctor`: never throws. */
|
|
48
|
+
export declare function checkCodexCli(): Promise<CodexStatus>;
|
|
49
|
+
export declare function buildPrompt(scene: string, aspect: ImageAspect, count: number, hasReferences: boolean): string;
|
|
50
|
+
export declare function generateImages(params: GenerateImagesParams): Promise<GenerateImagesResult>;
|
|
51
|
+
//# sourceMappingURL=codex-image.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-image.d.ts","sourceRoot":"","sources":["../../src/lib/codex-image.ts"],"names":[],"mappings":"AAkBA,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,QAAQ,GAAG,WAAW,CAAA;AAE7D,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAIpD,CAAA;AAED,eAAO,MAAM,aAAa,uHACuF,CAAA;AACjH,eAAO,MAAM,kBAAkB,kFAA6E,CAAA;AAe5G,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IACtC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,cAAc,EAAE,CAAA;IACvB,UAAU,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,IAAI,CAAA;KAAE,CAAA;IAClE,aAAa,EAAE,MAAM,CAAA;IACrB,MAAM,EAAE,MAAM,CAAA;IACd,8FAA8F;IAC9F,OAAO,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;CAC7C;AAID,qFAAqF;AACrF,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC,CAQxD;AAED,wFAAwF;AACxF,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AA4BD,MAAM,MAAM,WAAW,GACnB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,KAAK,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACxC;IAAE,KAAK,EAAE,SAAS,CAAA;CAAE,CAAA;AAExB,4CAA4C;AAC5C,wBAAsB,aAAa,IAAI,OAAO,CAAC,WAAW,CAAC,CAoB1D;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,GAAG,MAAM,CAa7G;AA0ID,wBAAsB,cAAc,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA+EhG"}
|