@vosjs/cli 0.14.0 → 0.15.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/dist/{chunk-MMKDYE57.js → chunk-HGLKXIXH.js} +166 -25
- package/dist/chunk-HGLKXIXH.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{run-R5P44XNR.js → run-37GVK3VS.js} +2 -2
- package/package.json +4 -4
- package/dist/chunk-MMKDYE57.js.map +0 -1
- /package/dist/{run-R5P44XNR.js.map → run-37GVK3VS.js.map} +0 -0
|
@@ -3950,8 +3950,9 @@ function resolveFace(family, weights) {
|
|
|
3950
3950
|
url: fontFaceUrl(entry.slug, w),
|
|
3951
3951
|
weight: w
|
|
3952
3952
|
}));
|
|
3953
|
-
return { stack: fontStack(entry), fonts };
|
|
3953
|
+
return { stack: fontStack(entry), category: entry.category, fonts };
|
|
3954
3954
|
}
|
|
3955
|
+
var HOUSE_SERIF = "Fraunces";
|
|
3955
3956
|
function posterValues(brand, words) {
|
|
3956
3957
|
const b = brand ?? {};
|
|
3957
3958
|
const bgA = b.bgA && HEX.test(b.bgA) ? b.bgA : null;
|
|
@@ -3981,9 +3982,10 @@ function posterValues(brand, words) {
|
|
|
3981
3982
|
values.kicker = kicker ? kicker : [wordmark, release].filter(Boolean).join(" ").toUpperCase();
|
|
3982
3983
|
const fonts = [];
|
|
3983
3984
|
const display = resolveFace(b.fontDisplay, [600, 700]);
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3985
|
+
const serif = display && display.category === "serif" ? display : resolveFace(HOUSE_SERIF, [600, 700]);
|
|
3986
|
+
if (serif) {
|
|
3987
|
+
values.fontDisplay = serif.stack;
|
|
3988
|
+
fonts.push(...serif.fonts);
|
|
3987
3989
|
}
|
|
3988
3990
|
const body = resolveFace(b.fontBody, [700]);
|
|
3989
3991
|
if (body) {
|
|
@@ -3993,6 +3995,89 @@ function posterValues(brand, words) {
|
|
|
3993
3995
|
return { values, fonts, lightGround };
|
|
3994
3996
|
}
|
|
3995
3997
|
|
|
3998
|
+
// src/plugin/stages.ts
|
|
3999
|
+
var HOUSE_SERIF2 = "Fraunces";
|
|
4000
|
+
var str = (v, fallback) => typeof v === "string" && v.trim() ? v.trim() : fallback;
|
|
4001
|
+
var firstFamily = (stack) => stack.split(",")[0].replace(/['"]/g, "").trim();
|
|
4002
|
+
function stageSplitCover(input) {
|
|
4003
|
+
const R = input.size.w / Math.max(1, input.size.h);
|
|
4004
|
+
const portrait = R < 0.87;
|
|
4005
|
+
const square = !portrait && R <= 1.15;
|
|
4006
|
+
const v = input.values;
|
|
4007
|
+
const ground = `linear-gradient(135deg, ${str(v.bgA, "#8a3d2a")}, ${str(v.bgC, str(v.bgB, "#5c5a2e"))})`;
|
|
4008
|
+
const ink = str(v.ink, "#fff6ec");
|
|
4009
|
+
const inkSoft = str(v.inkSoft, ink);
|
|
4010
|
+
const serif = firstFamily(str(v.fontDisplay, HOUSE_SERIF2));
|
|
4011
|
+
const body = firstFamily(str(v.fontBody, "Lexend"));
|
|
4012
|
+
const headline = str(v.headline, "");
|
|
4013
|
+
const kicker = str(v.kicker, "");
|
|
4014
|
+
const brand = str(v.brand, "");
|
|
4015
|
+
const inset = portrait ? { left: 0.08, right: -0.08, top: 0.44, bottom: -0.1 } : square ? { left: 0.1, right: -0.1, top: 0.5, bottom: -0.12 } : { left: 0.44, right: -0.06, top: 0.3, bottom: -0.12 };
|
|
4016
|
+
const shot = {
|
|
4017
|
+
x: inset.left,
|
|
4018
|
+
y: inset.top,
|
|
4019
|
+
w: 1 - inset.left - inset.right,
|
|
4020
|
+
h: 1 - inset.top - inset.bottom
|
|
4021
|
+
};
|
|
4022
|
+
const set = [
|
|
4023
|
+
`frame.background=${JSON.stringify(ground)}`,
|
|
4024
|
+
"frame.backgroundMedia=null",
|
|
4025
|
+
"frame.fit=cover",
|
|
4026
|
+
`frame.inset=${JSON.stringify(inset)}`,
|
|
4027
|
+
'frame.focus={"cx":0,"cy":0}',
|
|
4028
|
+
"frame.radius=16",
|
|
4029
|
+
"frame.shadow=0.45",
|
|
4030
|
+
"frame.shadowContact=0.25",
|
|
4031
|
+
"frame.border=0",
|
|
4032
|
+
"zoom=[]",
|
|
4033
|
+
`tilt=[{"id":"stage","in":0,"out":${input.sourceSeconds.toFixed(3)},"rx":3,"ry":${portrait || square ? 0 : 10}}]`,
|
|
4034
|
+
"cursor.visible=false",
|
|
4035
|
+
"cursor.clickFx.style=none"
|
|
4036
|
+
];
|
|
4037
|
+
const designW = 1080 * input.size.w / input.size.h;
|
|
4038
|
+
const boxes = [];
|
|
4039
|
+
const clips = [];
|
|
4040
|
+
const column = portrait || square ? 0.84 : 0.36;
|
|
4041
|
+
const left = portrait || square ? 0.08 : 0.07;
|
|
4042
|
+
const size = portrait ? 60 : square ? 68 : 84;
|
|
4043
|
+
const word = (id, text, preset, family, px, y, color, extra, role) => {
|
|
4044
|
+
if (!text) return;
|
|
4045
|
+
const lines = text.split("\n");
|
|
4046
|
+
const longest = Math.max(...lines.map((l) => l.length));
|
|
4047
|
+
const widest = Math.min(column, longest * px * (preset === "title" ? 0.5 : 0.62) / designW);
|
|
4048
|
+
const lh = preset === "title" ? 1.05 : 1.2;
|
|
4049
|
+
const h = lines.length * px * lh / 1080;
|
|
4050
|
+
clips.push({
|
|
4051
|
+
id,
|
|
4052
|
+
kind: "text",
|
|
4053
|
+
text,
|
|
4054
|
+
preset,
|
|
4055
|
+
family,
|
|
4056
|
+
size: px,
|
|
4057
|
+
color,
|
|
4058
|
+
align: "left",
|
|
4059
|
+
maxWidth: column,
|
|
4060
|
+
lineHeight: lh,
|
|
4061
|
+
transform: { x: left + widest / 2, y, scale: 1, rotation: 0 },
|
|
4062
|
+
shadow: "none",
|
|
4063
|
+
start: 0,
|
|
4064
|
+
duration: +input.outputSeconds.toFixed(3),
|
|
4065
|
+
enter: "none",
|
|
4066
|
+
exit: "none",
|
|
4067
|
+
...extra
|
|
4068
|
+
});
|
|
4069
|
+
boxes.push({ x: left, y: y - h / 2, w: widest, h, color, role, label: text.length > 24 ? `${text.slice(0, 24)}\u2026` : text });
|
|
4070
|
+
};
|
|
4071
|
+
const kickerY = portrait ? 0.1 : square ? 0.12 : 0.24;
|
|
4072
|
+
const titleY = portrait ? 0.24 : square ? 0.28 : 0.46;
|
|
4073
|
+
const brandY = portrait ? 0.94 : square ? 0.94 : 0.86;
|
|
4074
|
+
word("stage-kicker", kicker, "label", "JetBrains Mono", 19, kickerY, inkSoft, { letterSpacing: 6, weight: 400 }, "body");
|
|
4075
|
+
word("stage-title", headline, "title", serif, size, titleY, ink, { weight: 600 }, "headline");
|
|
4076
|
+
word("stage-brand", brand, "label", body, 25, brandY, ink, { weight: 700, letterSpacing: 1 }, "body");
|
|
4077
|
+
set.push(`overlays=${JSON.stringify(clips)}`);
|
|
4078
|
+
return { set, text: boxes, shot };
|
|
4079
|
+
}
|
|
4080
|
+
|
|
3996
4081
|
// src/plugin/motionPlan.ts
|
|
3997
4082
|
import { ratedSegments as ratedSegments4, spanOutputExtent as spanOutputExtent4 } from "@vosjs/studio-core";
|
|
3998
4083
|
var SOUND_DESTINATIONS = /* @__PURE__ */ new Set([
|
|
@@ -4218,6 +4303,7 @@ function templateForCard(d, opts) {
|
|
|
4218
4303
|
}
|
|
4219
4304
|
const config = templateByName(name);
|
|
4220
4305
|
if (!config) return null;
|
|
4306
|
+
if (name === "split-cover") return { config, from: "stage split-cover", note, stage: true };
|
|
4221
4307
|
return { config, from: `template ${name}`, note };
|
|
4222
4308
|
}
|
|
4223
4309
|
function lookOverrides(look, placement, size, video, opts) {
|
|
@@ -4453,6 +4539,53 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4453
4539
|
await writeFile6(join5(serveDir, "shot.png"), encodePng(baked));
|
|
4454
4540
|
const shotAspect = raw.w / raw.h;
|
|
4455
4541
|
for (const { d, plan } of cardPlans) {
|
|
4542
|
+
if (plan.stage) {
|
|
4543
|
+
const staged = stageSplitCover({
|
|
4544
|
+
size: d.px,
|
|
4545
|
+
values: fill.values,
|
|
4546
|
+
sourceSeconds: meta.durationMs / 1e3,
|
|
4547
|
+
outputSeconds: duration
|
|
4548
|
+
});
|
|
4549
|
+
opts.onPhase?.(`${d.channel} ${d.asset} (${specWords(d)}) from ${plan.from}`);
|
|
4550
|
+
const shotDir = await mkdtemp(join5(tmpdir(), "vos-stage-"));
|
|
4551
|
+
try {
|
|
4552
|
+
const captured = await framesTake(browser, dir, {
|
|
4553
|
+
times: [heroTime],
|
|
4554
|
+
width: d.px.w,
|
|
4555
|
+
height: d.px.h,
|
|
4556
|
+
outDir: shotDir,
|
|
4557
|
+
overrides: {
|
|
4558
|
+
...opts.overrides,
|
|
4559
|
+
set: [...staged.set, ...opts.overrides?.set ?? []]
|
|
4560
|
+
}
|
|
4561
|
+
});
|
|
4562
|
+
const to2 = join5(outDir, `${d.id}.png`);
|
|
4563
|
+
await rename4(captured.frames[0].file, to2);
|
|
4564
|
+
const bytes2 = (await stat(to2)).size;
|
|
4565
|
+
if (d.maxBytes !== void 0 && bytes2 > d.maxBytes) {
|
|
4566
|
+
skipped.push(`${d.channel} ${d.asset}: ${overCeiling(bytes2, d.maxBytes)} (kept at ${to2})`);
|
|
4567
|
+
continue;
|
|
4568
|
+
}
|
|
4569
|
+
assets.push({
|
|
4570
|
+
channel: d.channel,
|
|
4571
|
+
asset: d.asset,
|
|
4572
|
+
destination: d.id,
|
|
4573
|
+
path: relative(outDir, to2),
|
|
4574
|
+
w: d.px.w,
|
|
4575
|
+
h: d.px.h,
|
|
4576
|
+
bytes: bytes2,
|
|
4577
|
+
seconds: null,
|
|
4578
|
+
frameTime: heroTime,
|
|
4579
|
+
source: "stage",
|
|
4580
|
+
template: "split-cover-stage",
|
|
4581
|
+
text: staged.text,
|
|
4582
|
+
shot: staged.shot
|
|
4583
|
+
});
|
|
4584
|
+
} finally {
|
|
4585
|
+
await rm3(shotDir, { recursive: true, force: true });
|
|
4586
|
+
}
|
|
4587
|
+
continue;
|
|
4588
|
+
}
|
|
4456
4589
|
const problems = templateProblems(plan.config);
|
|
4457
4590
|
if (problems.length) {
|
|
4458
4591
|
skipped.push(
|
|
@@ -4677,7 +4810,8 @@ var RUBRIC = [
|
|
|
4677
4810
|
"Name three ways this asset acknowledges THIS product (its data, its state, its brand), not a generic window."
|
|
4678
4811
|
];
|
|
4679
4812
|
function rolesFor(asset) {
|
|
4680
|
-
if (asset.source === "poster"
|
|
4813
|
+
if ((asset.source === "poster" || asset.source === "stage") && asset.template)
|
|
4814
|
+
return [asset.template.replace(/-stage$/, "")];
|
|
4681
4815
|
const spec = DESTINATIONS2.find((d) => d.id === asset.destination);
|
|
4682
4816
|
if (spec?.kind === "video") {
|
|
4683
4817
|
const portrait = spec.px.w < spec.px.h;
|
|
@@ -4844,9 +4978,11 @@ async function judgeKit(kitPath, manifestPath, outDir) {
|
|
|
4844
4978
|
return { sheets, skipped, outDir: out, verdictFile };
|
|
4845
4979
|
}
|
|
4846
4980
|
function winRate(verdicts) {
|
|
4847
|
-
const
|
|
4848
|
-
const wins =
|
|
4849
|
-
|
|
4981
|
+
const judgedList = verdicts.filter((v) => v.win !== null || (v.reasons?.length ?? 0) > 0);
|
|
4982
|
+
const wins = judgedList.filter((v) => v.win === true).length;
|
|
4983
|
+
const ties = judgedList.filter((v) => v.win === null).length;
|
|
4984
|
+
const judged = judgedList.length;
|
|
4985
|
+
return { wins, ties, judged, rate: judged ? (wins + ties / 2) / judged : null };
|
|
4850
4986
|
}
|
|
4851
4987
|
|
|
4852
4988
|
// src/plugin/kitPicture.ts
|
|
@@ -5069,13 +5205,16 @@ function duplicateFindings(stills) {
|
|
|
5069
5205
|
}
|
|
5070
5206
|
if (g.length > 1) groups.push(g);
|
|
5071
5207
|
}
|
|
5072
|
-
return groups.map((g) =>
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5208
|
+
return groups.map((g) => {
|
|
5209
|
+
const composed = g.every((s) => stills.find((x) => x.destination === s.destination)?.composed);
|
|
5210
|
+
return {
|
|
5211
|
+
code: "duplicate",
|
|
5212
|
+
severity: composed ? "info" : g.length >= 3 ? "error" : "warning",
|
|
5213
|
+
asset: g.map((s) => s.destination).join(", "),
|
|
5214
|
+
message: composed ? `${g.length} channels share one cover: ${g.map((s) => s.destination).join(", ")}` : `${g.length} assets share one frame${g[0].time !== null ? ` (${g[0].time.toFixed(2)}s)` : ""}: ${g.map((s) => s.destination).join(", ")}`,
|
|
5215
|
+
fixHint: "a kit is many moments: let deliver pick from the step timeline, or pass --times with one step per still; a poster template composes each card differently from one shot"
|
|
5216
|
+
};
|
|
5217
|
+
});
|
|
5079
5218
|
}
|
|
5080
5219
|
async function videoFrame(file, at2, ffmpeg) {
|
|
5081
5220
|
try {
|
|
@@ -5124,7 +5263,8 @@ async function pictureChecks(assets) {
|
|
|
5124
5263
|
destination: a.destination,
|
|
5125
5264
|
hash: m.hash,
|
|
5126
5265
|
time: null,
|
|
5127
|
-
genre: a.spec?.genre
|
|
5266
|
+
genre: a.spec?.genre,
|
|
5267
|
+
composed: !!a.shot
|
|
5128
5268
|
});
|
|
5129
5269
|
continue;
|
|
5130
5270
|
}
|
|
@@ -5328,7 +5468,7 @@ async function validateKit(kitPath, opts = {}) {
|
|
|
5328
5468
|
text: a.text,
|
|
5329
5469
|
seconds,
|
|
5330
5470
|
composed: a.composed,
|
|
5331
|
-
shot: a.source === "poster" ? a.shot : void 0
|
|
5471
|
+
shot: a.source === "poster" || a.source === "stage" ? a.shot : void 0
|
|
5332
5472
|
});
|
|
5333
5473
|
if (!spec) {
|
|
5334
5474
|
if (a.channel !== "demo")
|
|
@@ -7900,7 +8040,7 @@ function mostCommon(values) {
|
|
|
7900
8040
|
}
|
|
7901
8041
|
return best;
|
|
7902
8042
|
}
|
|
7903
|
-
function
|
|
8043
|
+
function firstFamily2(fontFamily) {
|
|
7904
8044
|
const first = fontFamily.split(",")[0]?.trim() ?? "";
|
|
7905
8045
|
return first.replace(/^["']|["']$/g, "");
|
|
7906
8046
|
}
|
|
@@ -8032,10 +8172,10 @@ function composeBrand(input) {
|
|
|
8032
8172
|
const bgC = mixHex2(bgA, accent, 0.14);
|
|
8033
8173
|
p.bgC = `bgA tinted 14% toward the accent (a highlight ground)`;
|
|
8034
8174
|
p.ink = w.h1 ? `the h1's colour` : `the body's colour`;
|
|
8035
|
-
const fontDisplay = design?.fonts[0] ?? (w.h1 ?
|
|
8175
|
+
const fontDisplay = design?.fonts[0] ?? (w.h1 ? firstFamily2(w.h1.fontFamily) : firstFamily2(w.body.fontFamily));
|
|
8036
8176
|
p.fontDisplay = design?.fonts[0] ? `named in design.md` : w.h1 ? `the h1's computed face` : `the body's computed face (no h1)`;
|
|
8037
8177
|
const designBody = design?.fonts.slice(1).find((f) => !/\bmono\b/i.test(f));
|
|
8038
|
-
const fontBody = designBody ??
|
|
8178
|
+
const fontBody = designBody ?? firstFamily2(w.body.fontFamily);
|
|
8039
8179
|
p.fontBody = designBody ? `named in design.md` : `the body's computed face`;
|
|
8040
8180
|
const logoUrl = design?.logos.find((u) => /wordmark|logo/i.test(u)) ?? null;
|
|
8041
8181
|
const iconUrl = w.icons.find((u) => /apple-touch/i.test(u)) ?? (w.icons.length ? w.icons[0] : null);
|
|
@@ -8847,9 +8987,10 @@ ground (a paper site is a plate, a dark site is dark), and with no brand
|
|
|
8847
8987
|
the house gradient. Screenshot-genre stills never take a look.
|
|
8848
8988
|
Card-genre destinations (OG, LinkedIn, X, YouTube thumbnail, the CWS
|
|
8849
8989
|
tile + marquee, GitHub social preview) COMPOSE by default: each renders
|
|
8850
|
-
from its destination's poster TEMPLATE (split-cover
|
|
8851
|
-
|
|
8852
|
-
|
|
8990
|
+
from its destination's poster TEMPLATE (split-cover is a STAGE, the
|
|
8991
|
+
take's own card leaning in perspective with its chrome and shadow beside
|
|
8992
|
+
a serif headline column, on the brand's ground; card-on-gradient is the
|
|
8993
|
+
shot alone on a mesh, the store's tile rule), filled with BRAND.md's colours and faces and the
|
|
8853
8994
|
release's words, the shot baked as an object (padded, rounded, shadowed,
|
|
8854
8995
|
a hairline on a light ground), PNG at exact pixels; kit.json records the
|
|
8855
8996
|
template and the text boxes. The headline is LAUNCH.md's headline role
|
|
@@ -9717,7 +9858,7 @@ async function cmdJudge(argv) {
|
|
|
9717
9858
|
${lines.join("\n ")}` : "") + (result.skipped.length ? `
|
|
9718
9859
|
skipped: ${result.skipped.join("\n skipped: ")}` : "") + `
|
|
9719
9860
|
Judge each pair both ways (the rubric is beside it) and fill ${result.verdictFile}` + (rate.judged ? `
|
|
9720
|
-
Win rate so far: ${rate.wins}
|
|
9861
|
+
Win rate so far: ${rate.wins} win(s), ${rate.ties} tie(s) of ${rate.judged} judged (${(rate.rate * 100).toFixed(0)}%; a tie counts a half); parity with the references is 50%, the marketability bar is 40%` : "")
|
|
9721
9862
|
);
|
|
9722
9863
|
return EXIT_OK;
|
|
9723
9864
|
}
|
|
@@ -10008,4 +10149,4 @@ export {
|
|
|
10008
10149
|
convertAgentBrowser,
|
|
10009
10150
|
run
|
|
10010
10151
|
};
|
|
10011
|
-
//# sourceMappingURL=chunk-
|
|
10152
|
+
//# sourceMappingURL=chunk-HGLKXIXH.js.map
|