@appinternalleads/ui 0.4.1 → 0.4.2
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 +92 -0
- package/dist/cjs/motion/typesurface/TypeSurfaceCanvas.js +25 -2
- package/dist/cjs/motion/typesurface/TypeSurfaceCanvas.js.map +1 -1
- package/dist/cjs/motion/typesurface/config.js +21 -1
- package/dist/cjs/motion/typesurface/config.js.map +1 -1
- package/dist/cjs/motion/typesurface/construct.js +875 -0
- package/dist/cjs/motion/typesurface/construct.js.map +1 -0
- package/dist/cjs/motion/typesurface/engine.js +28 -4
- package/dist/cjs/motion/typesurface/engine.js.map +1 -1
- package/dist/cjs/motion/typesurface/index.js +9 -1
- package/dist/cjs/motion/typesurface/index.js.map +1 -1
- package/dist/cjs/motion/typesurface/presets.js +69 -2
- package/dist/cjs/motion/typesurface/presets.js.map +1 -1
- package/dist/cjs/motion/typesurface/renderer.js +81 -15
- package/dist/cjs/motion/typesurface/renderer.js.map +1 -1
- package/dist/cjs/motion/typesurface/shaders.js +23 -1
- package/dist/cjs/motion/typesurface/shaders.js.map +1 -1
- package/dist/cjs/motion/typesurface/surfaces.js +69 -0
- package/dist/cjs/motion/typesurface/surfaces.js.map +1 -1
- package/dist/esm/motion/typesurface/TypeSurfaceCanvas.d.ts +11 -1
- package/dist/esm/motion/typesurface/TypeSurfaceCanvas.d.ts.map +1 -1
- package/dist/esm/motion/typesurface/TypeSurfaceCanvas.js +25 -2
- package/dist/esm/motion/typesurface/TypeSurfaceCanvas.js.map +1 -1
- package/dist/esm/motion/typesurface/config.d.ts +13 -2
- package/dist/esm/motion/typesurface/config.d.ts.map +1 -1
- package/dist/esm/motion/typesurface/config.js +21 -1
- package/dist/esm/motion/typesurface/config.js.map +1 -1
- package/dist/esm/motion/typesurface/construct.d.ts +284 -0
- package/dist/esm/motion/typesurface/construct.d.ts.map +1 -0
- package/dist/esm/motion/typesurface/construct.js +866 -0
- package/dist/esm/motion/typesurface/construct.js.map +1 -0
- package/dist/esm/motion/typesurface/engine.d.ts +8 -0
- package/dist/esm/motion/typesurface/engine.d.ts.map +1 -1
- package/dist/esm/motion/typesurface/engine.js +28 -4
- package/dist/esm/motion/typesurface/engine.js.map +1 -1
- package/dist/esm/motion/typesurface/index.d.ts +7 -3
- package/dist/esm/motion/typesurface/index.d.ts.map +1 -1
- package/dist/esm/motion/typesurface/index.js +6 -2
- package/dist/esm/motion/typesurface/index.js.map +1 -1
- package/dist/esm/motion/typesurface/presets.d.ts +18 -0
- package/dist/esm/motion/typesurface/presets.d.ts.map +1 -1
- package/dist/esm/motion/typesurface/presets.js +68 -3
- package/dist/esm/motion/typesurface/presets.js.map +1 -1
- package/dist/esm/motion/typesurface/renderer.d.ts +10 -0
- package/dist/esm/motion/typesurface/renderer.d.ts.map +1 -1
- package/dist/esm/motion/typesurface/renderer.js +82 -16
- package/dist/esm/motion/typesurface/renderer.js.map +1 -1
- package/dist/esm/motion/typesurface/shaders.d.ts +1 -1
- package/dist/esm/motion/typesurface/shaders.d.ts.map +1 -1
- package/dist/esm/motion/typesurface/shaders.js +23 -1
- package/dist/esm/motion/typesurface/shaders.js.map +1 -1
- package/dist/esm/motion/typesurface/surfaces.d.ts +28 -1
- package/dist/esm/motion/typesurface/surfaces.d.ts.map +1 -1
- package/dist/esm/motion/typesurface/surfaces.js +67 -0
- package/dist/esm/motion/typesurface/surfaces.js.map +1 -1
- package/docs/TYPE-SURFACE.md +183 -0
- package/package.json +4 -2
- package/src/motion/typesurface/TypeSurfaceCanvas.tsx +30 -2
- package/src/motion/typesurface/config.ts +29 -3
- package/src/motion/typesurface/construct.ts +828 -0
- package/src/motion/typesurface/engine.ts +31 -5
- package/src/motion/typesurface/index.ts +7 -3
- package/src/motion/typesurface/presets.ts +75 -3
- package/src/motion/typesurface/renderer.ts +77 -17
- package/src/motion/typesurface/shaders.ts +23 -1
- package/src/motion/typesurface/surfaces.ts +78 -1
- package/tools/ask/dist/components.mjs +1138 -57
- package/tools/ask/dist/neon.js +69 -0
- package/tools/ask/mcp.mjs +90 -5
- package/tools/ask/neon-render.mjs +143 -0
|
@@ -1767,10 +1767,10 @@ function validateExportFormat(c) {
|
|
|
1767
1767
|
if (!Array.isArray(c.formats)) return [{ path: "formats", message: "formats must be a list." }];
|
|
1768
1768
|
const issues = [];
|
|
1769
1769
|
const values = /* @__PURE__ */ new Set();
|
|
1770
|
-
c.formats.forEach((
|
|
1771
|
-
if (values.has(
|
|
1772
|
-
values.add(
|
|
1773
|
-
if (!
|
|
1770
|
+
c.formats.forEach((f2, i) => {
|
|
1771
|
+
if (values.has(f2.value)) issues.push({ path: `formats[${i}].value`, message: `The value \u201C${f2.value}\u201D is used twice.` });
|
|
1772
|
+
values.add(f2.value);
|
|
1773
|
+
if (!f2.ext) issues.push({ path: `formats[${i}].ext`, message: "Every format needs an extension." });
|
|
1774
1774
|
});
|
|
1775
1775
|
if (c.formats.length && !values.has(c.defaultFormat)) issues.push({ path: "defaultFormat", message: `\u201C${c.defaultFormat}\u201D is not one of the formats.` });
|
|
1776
1776
|
return issues;
|
|
@@ -6198,8 +6198,8 @@ var FILE_SHARED_PRESETS = presets_default25;
|
|
|
6198
6198
|
function validateFileShared(c) {
|
|
6199
6199
|
if (!Array.isArray(c.files)) return [{ path: "files", message: "files must be a list." }];
|
|
6200
6200
|
const issues = [];
|
|
6201
|
-
c.files.forEach((
|
|
6202
|
-
if (!
|
|
6201
|
+
c.files.forEach((f2, i) => {
|
|
6202
|
+
if (!f2 || !f2.name) issues.push({ path: `files[${i}].name`, message: "Every file needs a name." });
|
|
6203
6203
|
});
|
|
6204
6204
|
if (!(Number.isInteger(c.failIndex) && c.failIndex >= 0)) issues.push({ path: "failIndex", message: "failIndex is a whole number from 0." });
|
|
6205
6205
|
return issues;
|
|
@@ -19839,13 +19839,13 @@ function validateReceiptPrinter(c) {
|
|
|
19839
19839
|
});
|
|
19840
19840
|
const keys = /* @__PURE__ */ new Set();
|
|
19841
19841
|
if (fields.length > RECEIPT_MAX_FIELDS) issues.push({ path: "fields", message: `At most ${RECEIPT_MAX_FIELDS} fields \u2014 this has ${fields.length}.` });
|
|
19842
|
-
else fields.forEach((
|
|
19843
|
-
const key =
|
|
19842
|
+
else fields.forEach((f2, i) => {
|
|
19843
|
+
const key = f2?.key;
|
|
19844
19844
|
if (typeof key !== "string" || !FIELD_KEY.test(key)) issues.push({ path: `fields[${i}].key`, message: "A key starts with a letter and uses only letters, digits and _, like bookName." });
|
|
19845
19845
|
else if (keys.has(key)) issues.push({ path: `fields[${i}].key`, message: `${key} is used twice.` });
|
|
19846
19846
|
else keys.add(key);
|
|
19847
|
-
if (!isText(
|
|
19848
|
-
if (
|
|
19847
|
+
if (!isText(f2?.label)) issues.push({ path: `fields[${i}].label`, message: "Each field needs a label." });
|
|
19848
|
+
if (f2?.value !== void 0 && typeof f2.value !== "string") issues.push({ path: `fields[${i}].value`, message: "A value is text." });
|
|
19849
19849
|
});
|
|
19850
19850
|
if (items.length > RECEIPT_MAX_ITEMS) issues.push({ path: "items", message: `A receipt prints at most ${RECEIPT_MAX_ITEMS} lines \u2014 this has ${items.length}.` });
|
|
19851
19851
|
else items.forEach((it, i) => {
|
|
@@ -21143,8 +21143,8 @@ function validateThresholdFlow(doc) {
|
|
|
21143
21143
|
if (typeof d.key !== "string" || !KEY.test(d.key)) issues.push({ code: "schema", message: "key must match ^[a-z][a-z0-9_]*$." });
|
|
21144
21144
|
const p = d.props;
|
|
21145
21145
|
if (!p || typeof p !== "object") return [...issues, { code: "schema", message: "props is required." }];
|
|
21146
|
-
for (const
|
|
21147
|
-
if (typeof p[
|
|
21146
|
+
for (const f2 of ["title", "valueLabel"]) {
|
|
21147
|
+
if (typeof p[f2] !== "string" || !p[f2]) issues.push({ code: "schema", message: `props.${f2} is required.` });
|
|
21148
21148
|
}
|
|
21149
21149
|
if (!Array.isArray(p.defaultItems)) return [...issues, { code: "schema", message: "props.defaultItems must be an array." }];
|
|
21150
21150
|
if (p.type !== void 0 && !DATA_TYPES.includes(p.type)) issues.push({ code: "unknown-type", message: `Unknown type "${String(p.type)}".` });
|
|
@@ -21254,8 +21254,8 @@ function checkValue(spec, v, path, c) {
|
|
|
21254
21254
|
if (spec.maxItems !== void 0 && v.length > spec.maxItems) push(c, path, "MAX_ITEMS", layer, `${path} holds at most ${spec.maxItems} (has ${v.length}).`, `\u2264 ${spec.maxItems} items`);
|
|
21255
21255
|
v.forEach((x, i) => checkValue(spec.item, x, `${path}[${i}]`, c));
|
|
21256
21256
|
if (spec.item.type === "object") {
|
|
21257
|
-
for (const [k,
|
|
21258
|
-
if (
|
|
21257
|
+
for (const [k, f2] of Object.entries(spec.item.fields)) {
|
|
21258
|
+
if (f2.type !== "text" || !f2.unique) continue;
|
|
21259
21259
|
const seen = /* @__PURE__ */ new Set();
|
|
21260
21260
|
v.forEach((x, i) => {
|
|
21261
21261
|
const val = x?.[k];
|
|
@@ -21290,15 +21290,15 @@ function checkFields(fields, v, path, c) {
|
|
|
21290
21290
|
for (const k of Object.keys(v)) {
|
|
21291
21291
|
if (!(k in fields)) push(c, `${path}.${k}`, "UNKNOWN_KEY", "schema", `${path} has no "${k}". It takes: ${expected.join(", ") || "nothing"}.`, expected);
|
|
21292
21292
|
}
|
|
21293
|
-
for (const [k,
|
|
21294
|
-
if (
|
|
21295
|
-
else checkValue(
|
|
21293
|
+
for (const [k, f2] of Object.entries(fields)) {
|
|
21294
|
+
if (f2.required && v[k] === void 0) push(c, `${path}.${k}`, "REQUIRED", c.section === "data" ? "data" : "configuration", `${path}.${k} is required: ${f2.description}`);
|
|
21295
|
+
else checkValue(f2, v[k], `${path}.${k}`, c);
|
|
21296
21296
|
}
|
|
21297
21297
|
}
|
|
21298
21298
|
function checkOwners(fields, v, path, c) {
|
|
21299
|
-
for (const [k,
|
|
21300
|
-
if (v[k] !== void 0 &&
|
|
21301
|
-
push(c, `${path}.${k}`, "NOT_ALLOWED", "configuration", `${path}.${k} is set by the ${
|
|
21299
|
+
for (const [k, f2] of Object.entries(fields)) {
|
|
21300
|
+
if (v[k] !== void 0 && f2.owners && !f2.owners.includes("agent")) {
|
|
21301
|
+
push(c, `${path}.${k}`, "NOT_ALLOWED", "configuration", `${path}.${k} is set by the ${f2.owners.join(" or ")}, not by an agent.`, f2.owners);
|
|
21302
21302
|
}
|
|
21303
21303
|
}
|
|
21304
21304
|
}
|
|
@@ -21315,22 +21315,22 @@ function valuesAt(root, ref) {
|
|
|
21315
21315
|
return cur;
|
|
21316
21316
|
}
|
|
21317
21317
|
function checkRefs(fields, v, path, root, c) {
|
|
21318
|
-
for (const [k,
|
|
21318
|
+
for (const [k, f2] of Object.entries(fields)) visitRef(f2, v?.[k], `${path}.${k}`, root, c);
|
|
21319
21319
|
}
|
|
21320
|
-
function visitRef(
|
|
21320
|
+
function visitRef(f2, v, path, root, c) {
|
|
21321
21321
|
if (v === void 0 || v === null) return;
|
|
21322
|
-
if (
|
|
21323
|
-
const allowed = valuesAt(root,
|
|
21322
|
+
if (f2.ref && (typeof v === "string" || typeof v === "number")) {
|
|
21323
|
+
const allowed = valuesAt(root, f2.ref).filter((x) => typeof x === "string" || typeof x === "number");
|
|
21324
21324
|
if (!allowed.includes(v)) {
|
|
21325
|
-
push(c, path, "REFERENCE", c.section === "data" ? "data" : "configuration", `${path} must name one of ${
|
|
21325
|
+
push(c, path, "REFERENCE", c.section === "data" ? "data" : "configuration", `${path} must name one of ${f2.ref} (got "${v}").`, allowed.map(String));
|
|
21326
21326
|
}
|
|
21327
21327
|
}
|
|
21328
|
-
if (
|
|
21329
|
-
if (
|
|
21328
|
+
if (f2.type === "list" && Array.isArray(v)) v.forEach((x, i) => visitRef(f2.item, x, `${path}[${i}]`, root, c));
|
|
21329
|
+
if (f2.type === "object" && v && typeof v === "object") for (const [k, g] of Object.entries(f2.fields)) visitRef(g, v[k], `${path}.${k}`, root, c);
|
|
21330
21330
|
}
|
|
21331
21331
|
function withDefaults(fields, v = {}) {
|
|
21332
21332
|
const out = { ...v };
|
|
21333
|
-
for (const [k,
|
|
21333
|
+
for (const [k, f2] of Object.entries(fields)) if (out[k] === void 0 && f2.default !== void 0) out[k] = structuredClone(f2.default);
|
|
21334
21334
|
return out;
|
|
21335
21335
|
}
|
|
21336
21336
|
var PLACEHOLDER = /\{(\w+)\}/g;
|
|
@@ -22724,8 +22724,8 @@ function check(value, shape, path, out) {
|
|
|
22724
22724
|
if (k === "array" && shape.item) value.forEach((v, i) => check(v, shape.item, `${path}[${i}]`, out));
|
|
22725
22725
|
if (k === "object" && shape.fields) {
|
|
22726
22726
|
for (const [key, v] of Object.entries(value)) {
|
|
22727
|
-
const
|
|
22728
|
-
if (
|
|
22727
|
+
const f2 = shape.fields.get(key);
|
|
22728
|
+
if (f2) check(v, f2, path ? `${path}.${key}` : key, out);
|
|
22729
22729
|
}
|
|
22730
22730
|
}
|
|
22731
22731
|
}
|
|
@@ -22745,8 +22745,8 @@ var word = (v, fallback2) => typeof v === "string" && v.trim() ? v : fallback2;
|
|
|
22745
22745
|
var blankItem = () => ({ name: "", quantity: "1", amount: "" });
|
|
22746
22746
|
function fieldDrafts(c) {
|
|
22747
22747
|
const defs = (Array.isArray(c.fields) ? c.fields : []).slice(0, RECEIPT_MAX_FIELDS);
|
|
22748
|
-
const first = defs.findIndex((
|
|
22749
|
-
return defs.map((
|
|
22748
|
+
const first = defs.findIndex((f2) => f2?.heading === true);
|
|
22749
|
+
return defs.map((f2, i) => ({ key: text2(f2?.key), label: text2(f2?.label), value: text2(f2?.value), heading: i === first, keyTouched: true }));
|
|
22750
22750
|
}
|
|
22751
22751
|
function initialDraft(c) {
|
|
22752
22752
|
const currencies = currenciesOf(c);
|
|
@@ -22775,15 +22775,15 @@ function formErrors(draft, c) {
|
|
|
22775
22775
|
const m = messagesOf(c);
|
|
22776
22776
|
if (draft.message.trim().length > RECEIPT_MAX_LENGTH.message) e.message = fill(m.tooLong, { max: RECEIPT_MAX_LENGTH.message });
|
|
22777
22777
|
const keys = /* @__PURE__ */ new Set();
|
|
22778
|
-
draft.fields.forEach((
|
|
22779
|
-
const label =
|
|
22778
|
+
draft.fields.forEach((f2, i) => {
|
|
22779
|
+
const label = f2.label.trim();
|
|
22780
22780
|
if (!label) e[`fields.${i}.label`] = m.labelMissing;
|
|
22781
22781
|
else if (label.length > RECEIPT_MAX_LENGTH.fieldLabel) e[`fields.${i}.label`] = fill(m.labelTooLong, { max: RECEIPT_MAX_LENGTH.fieldLabel });
|
|
22782
|
-
if (!FIELD_KEY.test(
|
|
22783
|
-
else if (keys.has(
|
|
22784
|
-
keys.add(
|
|
22785
|
-
const v =
|
|
22786
|
-
const max =
|
|
22782
|
+
if (!FIELD_KEY.test(f2.key)) e[`fields.${i}.key`] = m.keyInvalid;
|
|
22783
|
+
else if (keys.has(f2.key)) e[`fields.${i}.key`] = fill(m.keyTaken, { key: f2.key });
|
|
22784
|
+
keys.add(f2.key);
|
|
22785
|
+
const v = f2.value.trim();
|
|
22786
|
+
const max = f2.heading ? RECEIPT_MAX_LENGTH.heading : RECEIPT_MAX_LENGTH.fieldValue;
|
|
22787
22787
|
if (!v) e[`fields.${i}.value`] = label ? fill(m.valueMissing, { label: label.toLowerCase() }) : m.valueMissingNoLabel;
|
|
22788
22788
|
else if (v.length > max) e[`fields.${i}.value`] = fill(m.tooLong, { max });
|
|
22789
22789
|
});
|
|
@@ -22810,7 +22810,7 @@ function formErrors(draft, c) {
|
|
|
22810
22810
|
}
|
|
22811
22811
|
function valuesOf(draft) {
|
|
22812
22812
|
return {
|
|
22813
|
-
fields: Object.fromEntries(draft.fields.map((
|
|
22813
|
+
fields: Object.fromEntries(draft.fields.map((f2) => [f2.key, f2.value.trim()])),
|
|
22814
22814
|
items: draft.items.map((it) => ({ name: it.name.trim(), quantity: Number(it.quantity.trim()), amount: Number(it.amount.trim()) })),
|
|
22815
22815
|
currency: draft.currency,
|
|
22816
22816
|
paymentMethod: draft.paymentMethod,
|
|
@@ -22833,14 +22833,14 @@ function receiptLines(v, c, defs = fieldDrafts(c)) {
|
|
|
22833
22833
|
const minor = 10 ** (currency?.decimals ?? 2);
|
|
22834
22834
|
const total = v.items.reduce((sum, it) => sum + Math.round(it.quantity * it.amount * minor), 0) / minor;
|
|
22835
22835
|
const paid = word(c.paidLabel, "") ? ` (${c.paidLabel.toUpperCase()})` : "";
|
|
22836
|
-
const heading = defs.find((
|
|
22836
|
+
const heading = defs.find((f2) => f2.heading);
|
|
22837
22837
|
const title = heading ? v.fields[heading.key] ?? "" : "";
|
|
22838
22838
|
return {
|
|
22839
22839
|
title: title.toUpperCase(),
|
|
22840
22840
|
badge: (title.match(/[\p{L}\p{N}]/u)?.[0] ?? "\u2022").toUpperCase(),
|
|
22841
22841
|
total: formatMoney(total, currency),
|
|
22842
22842
|
meta: `${receiptDate(v.date, c.monthNames?.length === 12 ? c.monthNames : RECEIPT_MONTHS)} | ${method.toUpperCase()}${paid}`,
|
|
22843
|
-
details: defs.filter((
|
|
22843
|
+
details: defs.filter((f2) => !f2.heading).map((f2) => ({ label: f2.label, value: v.fields[f2.key] ?? "" })),
|
|
22844
22844
|
items: v.items.map((it) => ({ line: fill(messagesOf(c).itemLine, { quantity: it.quantity, name: it.name }), amount: formatMoney(it.quantity * it.amount, currency) })),
|
|
22845
22845
|
paymentMethod: method,
|
|
22846
22846
|
message: v.message.toUpperCase(),
|
|
@@ -25147,10 +25147,10 @@ function controlKit(groupOf3, easingNames) {
|
|
|
25147
25147
|
...o.unit ? { unit: o.unit } : {},
|
|
25148
25148
|
...o.step !== void 0 ? { step: o.step } : {}
|
|
25149
25149
|
});
|
|
25150
|
-
const
|
|
25150
|
+
const num10 = (path, def, min, max, description, o = {}) => ({ path, group: groupOf3(path), type: "number", default: def, min, max, description, ...base4({ step: 0.01, ...o }) });
|
|
25151
25151
|
const int4 = (path, def, min, max, description, o = {}) => ({ path, group: groupOf3(path), type: "integer", default: def, min, max, description, ...base4({ step: 1, ...o }) });
|
|
25152
25152
|
const k = {
|
|
25153
|
-
num:
|
|
25153
|
+
num: num10,
|
|
25154
25154
|
int: int4,
|
|
25155
25155
|
frames: (path, def, max, description, min = 0) => int4(path, def, min, max, description, { unit: "frames" }),
|
|
25156
25156
|
bool: (path, def, description, o = {}) => ({ path, group: groupOf3(path), type: "boolean", default: def, description, ...base4(o) }),
|
|
@@ -25169,12 +25169,12 @@ function controlKit(groupOf3, easingNames) {
|
|
|
25169
25169
|
}),
|
|
25170
25170
|
easing: (path, def, description) => ({ path, group: groupOf3(path), type: "easing", default: def, options: easingNames, description, ...base4() }),
|
|
25171
25171
|
asset: (path, description) => ({ path, group: groupOf3(path), type: "asset", default: "", maxLength: 2048, description, ...base4() }),
|
|
25172
|
-
opacity: (path, def, description) =>
|
|
25173
|
-
du: (path, def, min, max, description) =>
|
|
25174
|
-
pu: (path, def, min, max, description) =>
|
|
25175
|
-
norm: (path, def, min, max, description) =>
|
|
25176
|
-
deg: (path, def, min, max, description) =>
|
|
25177
|
-
factor: (path, def, min, max, description) =>
|
|
25172
|
+
opacity: (path, def, description) => num10(path, def, 0, 1, description, { unit: "fraction" }),
|
|
25173
|
+
du: (path, def, min, max, description) => num10(path, def, min, max, description, { step: 1, unit: "du" }),
|
|
25174
|
+
pu: (path, def, min, max, description) => num10(path, def, min, max, description, { step: 1, unit: "pu" }),
|
|
25175
|
+
norm: (path, def, min, max, description) => num10(path, def, min, max, description, { step: 5e-3, unit: "normalized" }),
|
|
25176
|
+
deg: (path, def, min, max, description) => num10(path, def, min, max, description, { step: 0.5, unit: "deg" }),
|
|
25177
|
+
factor: (path, def, min, max, description) => num10(path, def, min, max, description, { step: 0.01, unit: "factor" })
|
|
25178
25178
|
};
|
|
25179
25179
|
return k;
|
|
25180
25180
|
}
|
|
@@ -29202,7 +29202,7 @@ var NOT_CONFIGURABLE = [
|
|
|
29202
29202
|
];
|
|
29203
29203
|
function motionOutputFormats(t) {
|
|
29204
29204
|
const { fps } = t.motion;
|
|
29205
|
-
const canvases = (t.motion.formats ?? [{ id: "default", aspectRatio: "", width: t.motion.width, height: t.motion.height }]).map((
|
|
29205
|
+
const canvases = (t.motion.formats ?? [{ id: "default", aspectRatio: "", width: t.motion.width, height: t.motion.height }]).map((f2) => `${f2.width}\xD7${f2.height}${f2.aspectRatio ? ` (${f2.aspectRatio})` : ""}`).join(" or ");
|
|
29206
29206
|
const composition = t.id.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
29207
29207
|
return [
|
|
29208
29208
|
{
|
|
@@ -29230,24 +29230,24 @@ function listMotionTemplates() {
|
|
|
29230
29230
|
title: t.title,
|
|
29231
29231
|
description: t.description,
|
|
29232
29232
|
canvas: { width: t.motion.width, height: t.motion.height },
|
|
29233
|
-
...t.motion.formats ? { aspectRatios: t.motion.formats.map((
|
|
29233
|
+
...t.motion.formats ? { aspectRatios: t.motion.formats.map((f2) => f2.aspectRatio) } : {},
|
|
29234
29234
|
...t.motion.sequence ? { kind: "sequence" } : {},
|
|
29235
29235
|
seconds: seconds(t, t.motion.durationInFrames),
|
|
29236
29236
|
slots: t.slots.length,
|
|
29237
|
-
formats: motionOutputFormats(t).map((
|
|
29237
|
+
formats: motionOutputFormats(t).map((f2) => f2.format)
|
|
29238
29238
|
})).concat(Object.values(CONFIGURABLE_MOTION_TEMPLATES).map((t) => ({
|
|
29239
29239
|
template: t.id,
|
|
29240
29240
|
version: t.version,
|
|
29241
29241
|
title: t.title,
|
|
29242
29242
|
description: t.description,
|
|
29243
29243
|
canvas: { width: t.formats[0].width, height: t.formats[0].height },
|
|
29244
|
-
aspectRatios: t.formats.map((
|
|
29244
|
+
aspectRatios: t.formats.map((f2) => f2.aspectRatio),
|
|
29245
29245
|
kind: "configurableMotion",
|
|
29246
29246
|
renderer: t.renderer,
|
|
29247
29247
|
seconds: Math.round(t.durationInFrames / t.fps * 100) / 100,
|
|
29248
29248
|
slots: 0,
|
|
29249
29249
|
controls: t.controls.length,
|
|
29250
|
-
formats: configurableOutputFormats(t).map((
|
|
29250
|
+
formats: configurableOutputFormats(t).map((f2) => f2.format)
|
|
29251
29251
|
})));
|
|
29252
29252
|
}
|
|
29253
29253
|
function getConfigurableMotionTemplate(id) {
|
|
@@ -29260,7 +29260,7 @@ var CONFIG_API = {
|
|
|
29260
29260
|
};
|
|
29261
29261
|
var LIVE_PLAYER = { "promo-product-showcase": "PromoShowcasePlayer", "paper-fold-promo": "PaperFoldPlayer" };
|
|
29262
29262
|
function configurableOutputFormats(t) {
|
|
29263
|
-
const canvases = t.formats.map((
|
|
29263
|
+
const canvases = t.formats.map((f2) => `${f2.width}\xD7${f2.height} (${f2.aspectRatio})`).join(", ");
|
|
29264
29264
|
if (t.renderer === "remotion") {
|
|
29265
29265
|
return [
|
|
29266
29266
|
{ output: "live", format: "HTML + CSS (DOM)", detail: `Any size, ${t.fps} fps; drawn procedurally from the config by the package's renderer (${LIVE_PLAYER[t.id] ?? "its player"} on the web). Not a video file.` },
|
|
@@ -29377,7 +29377,7 @@ function describeDom(id) {
|
|
|
29377
29377
|
}])),
|
|
29378
29378
|
theme: Object.fromEntries(t.theme.map((c) => [`theme.${c.token}`, { type: "hex colour", default: c.default, description: c.description }])),
|
|
29379
29379
|
optionalScenes: t.optionalScenes.map((id2) => ({ scene: id2, effect: `leave "${id2}" out of content (or set it to null) to ${t.motion.sequence ? "hide it" : "end the video before it"}` })),
|
|
29380
|
-
...t.motion.formats ? { aspectRatio: { field: "aspectRatio", values: t.motion.formats.map((
|
|
29380
|
+
...t.motion.formats ? { aspectRatio: { field: "aspectRatio", values: t.motion.formats.map((f2) => ({ value: f2.aspectRatio, width: f2.width, height: f2.height })), default: t.motion.formats[0].aspectRatio, effect: "the same animation, laid out for that canvas" } } : {},
|
|
29381
29381
|
...t.motion.sequence ? { sequence: describeSequence(t) } : {},
|
|
29382
29382
|
notConfigurable: NOT_CONFIGURABLE,
|
|
29383
29383
|
example: { schema: MOTION_REQUEST_SCHEMA, template: t.id, content: t.exampleContent },
|
|
@@ -29429,6 +29429,1079 @@ function motionPreviewUrl(request, origin = "https://comppack.vercel.app") {
|
|
|
29429
29429
|
return `${origin.replace(/\/+$/, "")}/animations/${encodeURIComponent(request.template)}?request=${b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "")}`;
|
|
29430
29430
|
}
|
|
29431
29431
|
|
|
29432
|
+
// src/motion/neon/config.ts
|
|
29433
|
+
var NEON_CONSTRUCTIONS = ["outline", "monoline"];
|
|
29434
|
+
var NEON_ALIGNS = ["start", "middle", "end"];
|
|
29435
|
+
var DEFAULT_NEON_CONFIG = {
|
|
29436
|
+
text: "CAFE",
|
|
29437
|
+
typography: {
|
|
29438
|
+
fontFamily: "Oswald, 'Arial Narrow', 'Helvetica Neue', Arial, sans-serif",
|
|
29439
|
+
fontSize: 220,
|
|
29440
|
+
fontWeight: 500,
|
|
29441
|
+
fontStyle: "normal",
|
|
29442
|
+
letterSpacing: 4,
|
|
29443
|
+
lineHeight: 1.15,
|
|
29444
|
+
strokeWidth: 8,
|
|
29445
|
+
construction: "outline",
|
|
29446
|
+
align: "middle"
|
|
29447
|
+
},
|
|
29448
|
+
neon: {
|
|
29449
|
+
color: "#FF4655",
|
|
29450
|
+
coreColor: "#FFF5F5",
|
|
29451
|
+
tubeColor: "#FF4655",
|
|
29452
|
+
glowColor: "#FF1838",
|
|
29453
|
+
bloomColor: "#D4002F",
|
|
29454
|
+
intensity: 1,
|
|
29455
|
+
bloomIntensity: 1,
|
|
29456
|
+
tubeBrightness: 1,
|
|
29457
|
+
coreBrightness: 1,
|
|
29458
|
+
glowRadius: 20,
|
|
29459
|
+
bloomRadius: 70,
|
|
29460
|
+
coreWidth: 0.47,
|
|
29461
|
+
glassOpacity: 0.16,
|
|
29462
|
+
shadow: { enabled: true, color: "#000000", opacity: 0.5, offsetX: 0.035, offsetY: 0.045, blur: 6 }
|
|
29463
|
+
},
|
|
29464
|
+
animation: {
|
|
29465
|
+
enabled: true,
|
|
29466
|
+
speed: 1,
|
|
29467
|
+
seed: 7356951,
|
|
29468
|
+
flickerEnabled: true,
|
|
29469
|
+
flickerIntensity: 0.15,
|
|
29470
|
+
flickerFrequency: 0.08,
|
|
29471
|
+
flickerSpeed: 1,
|
|
29472
|
+
flickerRandomness: 0.6,
|
|
29473
|
+
flickerRecovery: 0.7,
|
|
29474
|
+
flickerMinBrightness: 0.05,
|
|
29475
|
+
glitchEnabled: true,
|
|
29476
|
+
glitchIntensity: 0.15,
|
|
29477
|
+
glitchFrequency: 0.04,
|
|
29478
|
+
glitchDuration: 180,
|
|
29479
|
+
glitchDisplacement: 1,
|
|
29480
|
+
glitchRgbSplit: 1,
|
|
29481
|
+
glitchSlices: 1,
|
|
29482
|
+
glitchGlowSeparation: 1
|
|
29483
|
+
},
|
|
29484
|
+
background: null
|
|
29485
|
+
};
|
|
29486
|
+
var NEON_COLOR_PRESETS = {
|
|
29487
|
+
red: { color: "#FF3B4D", coreColor: "#FFF5F5", tubeColor: "#FF3B4D", glowColor: "#FF1744", bloomColor: "#C40030" },
|
|
29488
|
+
blue: { color: "#00D9FF", coreColor: "#F5FFFF", tubeColor: "#00D9FF", glowColor: "#009DFF", bloomColor: "#0055FF" },
|
|
29489
|
+
purple: { color: "#D66BFF", coreColor: "#FFF5FF", tubeColor: "#D66BFF", glowColor: "#9C27FF", bloomColor: "#5E00B8" },
|
|
29490
|
+
green: { color: "#39FF88", coreColor: "#F5FFF5", tubeColor: "#39FF88", glowColor: "#00D95F", bloomColor: "#008F3D" }
|
|
29491
|
+
};
|
|
29492
|
+
var NEON_TEXT_PRESETS = {
|
|
29493
|
+
cafe: { text: "CAFE", typography: { construction: "outline", fontFamily: DEFAULT_NEON_CONFIG.typography.fontFamily, fontWeight: 500, strokeWidth: 8, letterSpacing: 4 } },
|
|
29494
|
+
open: { text: "OPEN", typography: { construction: "outline", fontFamily: DEFAULT_NEON_CONFIG.typography.fontFamily, fontWeight: 500, strokeWidth: 8, letterSpacing: 10 } },
|
|
29495
|
+
night: { text: "NIGHT", typography: { construction: "outline", fontFamily: DEFAULT_NEON_CONFIG.typography.fontFamily, fontWeight: 500, strokeWidth: 8, letterSpacing: 6 } },
|
|
29496
|
+
"24/7": { text: "24/7", typography: { construction: "monoline", fontFamily: "Quicksand, 'Arial Rounded MT Bold', 'Helvetica Neue', Arial, sans-serif", fontWeight: 500, strokeWidth: 10, letterSpacing: 2 } },
|
|
29497
|
+
hello: { text: "Hello", typography: { construction: "monoline", fontFamily: "Sacramento, 'Snell Roundhand', 'Brush Script MT', cursive", fontWeight: 400, strokeWidth: 7, letterSpacing: 0 } }
|
|
29498
|
+
};
|
|
29499
|
+
var HEX7 = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
|
|
29500
|
+
var isHexColor = (v) => typeof v === "string" && HEX7.test(v);
|
|
29501
|
+
function hexToRgb(hex) {
|
|
29502
|
+
const h = hex.length === 4 ? hex.slice(1).split("").map((c) => c + c).join("") : hex.slice(1);
|
|
29503
|
+
return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];
|
|
29504
|
+
}
|
|
29505
|
+
var rgbToHex = (rgb) => "#" + rgb.map((v) => Math.round(Math.max(0, Math.min(255, v))).toString(16).padStart(2, "0")).join("").toUpperCase();
|
|
29506
|
+
function mixHex(a, b, t) {
|
|
29507
|
+
const x = hexToRgb(a);
|
|
29508
|
+
const y = hexToRgb(b);
|
|
29509
|
+
return rgbToHex(x.map((v, i) => v + (y[i] - v) * t));
|
|
29510
|
+
}
|
|
29511
|
+
function toHsl([r, g, b]) {
|
|
29512
|
+
const [R, G2, B] = [r / 255, g / 255, b / 255];
|
|
29513
|
+
const max = Math.max(R, G2, B);
|
|
29514
|
+
const min = Math.min(R, G2, B);
|
|
29515
|
+
const l = (max + min) / 2;
|
|
29516
|
+
if (max === min) return [0, 0, l];
|
|
29517
|
+
const d = max - min;
|
|
29518
|
+
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
29519
|
+
const h = max === R ? (G2 - B) / d + (G2 < B ? 6 : 0) : max === G2 ? (B - R) / d + 2 : (R - G2) / d + 4;
|
|
29520
|
+
return [h * 60, s, l];
|
|
29521
|
+
}
|
|
29522
|
+
function fromHsl(h, s, l) {
|
|
29523
|
+
const k = (n) => (n + h / 30) % 12;
|
|
29524
|
+
const a = s * Math.min(l, 1 - l);
|
|
29525
|
+
const f2 = (n) => l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
|
|
29526
|
+
return rgbToHex([f2(0) * 255, f2(8) * 255, f2(4) * 255]);
|
|
29527
|
+
}
|
|
29528
|
+
function deriveNeonPalette(color4) {
|
|
29529
|
+
const base4 = isHexColor(color4) ? color4 : DEFAULT_NEON_CONFIG.neon.color;
|
|
29530
|
+
const [h, s, l] = toHsl(hexToRgb(base4));
|
|
29531
|
+
const drift = (deg3) => s < 0.08 ? h : (h + deg3 + 360) % 360;
|
|
29532
|
+
return {
|
|
29533
|
+
coreColor: mixHex(base4, "#FFFFFF", 0.94),
|
|
29534
|
+
tubeColor: rgbToHex(hexToRgb(base4)),
|
|
29535
|
+
glowColor: fromHsl(drift(-6), Math.min(1, s + 0.15), Math.max(0.3, l * 0.84)),
|
|
29536
|
+
bloomColor: fromHsl(drift(-10), Math.min(1, s + 0.2), Math.max(0.18, l * 0.6))
|
|
29537
|
+
};
|
|
29538
|
+
}
|
|
29539
|
+
var isObj5 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
29540
|
+
var NEON_CONTROLS = [
|
|
29541
|
+
{ path: "text", label: "Text", group: "Text", type: "text", description: "What the sign says. A new line (\\n) starts a second row." },
|
|
29542
|
+
{ path: "typography.fontFamily", label: "Font", group: "Typography", type: "text", description: "A CSS font-family list; the page must load the font." },
|
|
29543
|
+
{ path: "typography.fontSize", label: "Font size", group: "Typography", type: "number", min: 12, max: 1e3, step: 1, unit: "units", description: "The em size, in SVG user units." },
|
|
29544
|
+
{ path: "typography.fontWeight", label: "Font weight", group: "Typography", type: "number", min: 100, max: 900, step: 100, description: "CSS font-weight." },
|
|
29545
|
+
{ path: "typography.fontStyle", label: "Font style", group: "Typography", type: "enum", options: ["normal", "italic"], description: "Upright or italic." },
|
|
29546
|
+
{ path: "typography.letterSpacing", label: "Letter spacing", group: "Typography", type: "number", min: -50, max: 200, step: 1, unit: "units", description: "Extra space between letters." },
|
|
29547
|
+
{ path: "typography.lineHeight", label: "Line height", group: "Typography", type: "number", min: 0.6, max: 3, step: 0.05, unit: "\xD7 size", description: "Row pitch for multi-line text." },
|
|
29548
|
+
{ path: "typography.strokeWidth", label: "Tube width", group: "Typography", type: "number", min: 0.5, max: 80, step: 0.5, unit: "units", description: "The glass tube. The reference is \u2248 0.033 \xD7 font size." },
|
|
29549
|
+
{ path: "typography.construction", label: "Construction", group: "Typography", type: "enum", options: NEON_CONSTRUCTIONS, description: "outline: the tube traces each letter (CAFE). monoline: the letter is the tube (24/7)." },
|
|
29550
|
+
{ path: "typography.align", label: "Align", group: "Typography", type: "enum", options: NEON_ALIGNS, description: "Row alignment for multi-line text." },
|
|
29551
|
+
{ path: "neon.color", label: "Neon colour", group: "Colour", type: "color", description: "The primary colour; the others follow it unless set." },
|
|
29552
|
+
{ path: "neon.tubeColor", label: "Tube colour", group: "Colour", type: "color-or-null", description: "The lit glass. null follows the neon colour." },
|
|
29553
|
+
{ path: "neon.coreColor", label: "Core colour", group: "Colour", type: "color-or-null", description: "The hot centre line. null follows the neon colour." },
|
|
29554
|
+
{ path: "neon.glowColor", label: "Glow colour", group: "Colour", type: "color-or-null", description: "The tight halo. null follows the neon colour." },
|
|
29555
|
+
{ path: "neon.bloomColor", label: "Bloom colour", group: "Colour", type: "color-or-null", description: "The light on the wall. null follows the neon colour." },
|
|
29556
|
+
{ path: "neon.intensity", label: "Glow intensity", group: "Light", type: "number", min: 0, max: 3, step: 0.05, description: "0 is a bare tube; 1 the reference." },
|
|
29557
|
+
{ path: "neon.bloomIntensity", label: "Bloom intensity", group: "Light", type: "number", min: 0, max: 3, step: 0.05, description: "The wall bloom\u2019s own strength." },
|
|
29558
|
+
{ path: "neon.tubeBrightness", label: "Tube brightness", group: "Light", type: "number", min: 0, max: 1.5, step: 0.01, description: "How bright the lit glass is." },
|
|
29559
|
+
{ path: "neon.coreBrightness", label: "Core brightness", group: "Light", type: "number", min: 0, max: 1.5, step: 0.01, description: "How bright the hot centre line is." },
|
|
29560
|
+
{ path: "neon.glowRadius", label: "Glow radius", group: "Light", type: "number", min: 0, max: 200, step: 1, unit: "units", description: "Reach of the tight halo." },
|
|
29561
|
+
{ path: "neon.bloomRadius", label: "Bloom radius", group: "Light", type: "number", min: 0, max: 400, step: 1, unit: "units", description: "Reach of the wall bloom." },
|
|
29562
|
+
{ path: "neon.coreWidth", label: "Core width", group: "Light", type: "number", min: 0, max: 1, step: 0.01, unit: "\xD7 tube", description: "The hot core, as a share of the tube." },
|
|
29563
|
+
{ path: "neon.glassOpacity", label: "Unlit glass", group: "Light", type: "number", min: 0, max: 1, step: 0.01, description: "How much dark glass shows when the tube dims." },
|
|
29564
|
+
{ path: "neon.shadow.enabled", label: "Shadow", group: "Light", type: "boolean", description: "The tube\u2019s dark cast shadow on the wall." },
|
|
29565
|
+
{ path: "neon.shadow.color", label: "Shadow colour", group: "Light", type: "color", description: "Shadow colour." },
|
|
29566
|
+
{ path: "neon.shadow.opacity", label: "Shadow opacity", group: "Light", type: "number", min: 0, max: 1, step: 0.01, description: "Shadow strength." },
|
|
29567
|
+
{ path: "neon.shadow.offsetX", label: "Shadow x", group: "Light", type: "number", min: -0.5, max: 0.5, step: 5e-3, unit: "\xD7 size", description: "Shadow offset, across." },
|
|
29568
|
+
{ path: "neon.shadow.offsetY", label: "Shadow y", group: "Light", type: "number", min: -0.5, max: 0.5, step: 5e-3, unit: "\xD7 size", description: "Shadow offset, down." },
|
|
29569
|
+
{ path: "neon.shadow.blur", label: "Shadow blur", group: "Light", type: "number", min: 0, max: 100, step: 0.5, unit: "units", description: "Shadow softness." },
|
|
29570
|
+
{ path: "animation.enabled", label: "Animation", group: "Animation", type: "boolean", description: "Flicker and glitch on or off." },
|
|
29571
|
+
{ path: "animation.speed", label: "Animation speed", group: "Animation", type: "number", min: 0.1, max: 5, step: 0.05, unit: "\xD7", description: "Plays everything faster or slower." },
|
|
29572
|
+
{ path: "animation.seed", label: "Seed", group: "Animation", type: "number", min: 0, max: 4294967295, step: 1, description: "Same seed, same performance." },
|
|
29573
|
+
{ path: "animation.flickerEnabled", label: "Flicker", group: "Flicker", type: "boolean", description: "Brightness flicker on or off." },
|
|
29574
|
+
{ path: "animation.flickerIntensity", label: "Flicker intensity", group: "Flicker", type: "number", min: 0, max: 1, step: 0.01, description: "0.25 plays the full hardware ranges." },
|
|
29575
|
+
{ path: "animation.flickerFrequency", label: "Flicker frequency", group: "Flicker", type: "number", min: 0, max: 5, step: 0.01, unit: "/ s", description: "Noticeable flicker events per second." },
|
|
29576
|
+
{ path: "animation.flickerSpeed", label: "Flicker speed", group: "Flicker", type: "number", min: 0.1, max: 5, step: 0.05, unit: "\xD7", description: "How fast each flicker event plays." },
|
|
29577
|
+
{ path: "animation.flickerRandomness", label: "Randomness", group: "Flicker", type: "number", min: 0, max: 1, step: 0.01, description: "How much events vary in depth and length." },
|
|
29578
|
+
{ path: "animation.flickerRecovery", label: "Recovery speed", group: "Flicker", type: "number", min: 0, max: 1, step: 0.01, description: "How fast the tube comes back after a dip or failure." },
|
|
29579
|
+
{ path: "animation.flickerMinBrightness", label: "Minimum brightness", group: "Flicker", type: "number", min: 0, max: 1, step: 0.01, description: "The tube never falls below this." },
|
|
29580
|
+
{ path: "animation.glitchEnabled", label: "Glitch", group: "Glitch", type: "boolean", description: "Occasional glitches on or off." },
|
|
29581
|
+
{ path: "animation.glitchIntensity", label: "Glitch intensity", group: "Glitch", type: "number", min: 0, max: 1, step: 0.01, description: "How far slices shift and colours split." },
|
|
29582
|
+
{ path: "animation.glitchFrequency", label: "Glitch frequency", group: "Glitch", type: "number", min: 0, max: 5, step: 0.01, unit: "/ s", description: "Glitches per second." },
|
|
29583
|
+
{ path: "animation.glitchDuration", label: "Glitch duration", group: "Glitch", type: "number", min: 20, max: 2e3, step: 10, unit: "ms", description: "A glitch\u2019s typical length." },
|
|
29584
|
+
{ path: "animation.glitchDisplacement", label: "Horizontal displacement", group: "Glitch", type: "number", min: 0, max: 2, step: 0.05, unit: "\xD7", description: "The whole-sign sideways jump." },
|
|
29585
|
+
{ path: "animation.glitchRgbSplit", label: "RGB separation", group: "Glitch", type: "number", min: 0, max: 2, step: 0.05, unit: "\xD7", description: "The red/cyan split." },
|
|
29586
|
+
{ path: "animation.glitchSlices", label: "Slice displacement", group: "Glitch", type: "number", min: 0, max: 2, step: 0.05, unit: "\xD7", description: "How far the sliced bands shift." },
|
|
29587
|
+
{ path: "animation.glitchGlowSeparation", label: "Glow separation", group: "Glitch", type: "number", min: 0, max: 2, step: 0.05, unit: "\xD7", description: "How far the glow slips from the tube." },
|
|
29588
|
+
{ path: "background", label: "Background", group: "Colour", type: "color-or-null", description: "A colour behind the sign, or null for transparent." }
|
|
29589
|
+
];
|
|
29590
|
+
function getPath4(obj2, path) {
|
|
29591
|
+
return path.split(".").reduce((o, k) => isObj5(o) ? o[k] : void 0, obj2);
|
|
29592
|
+
}
|
|
29593
|
+
var MAX_NEON_TEXT_LENGTH = 200;
|
|
29594
|
+
function validateNeonConfig(config) {
|
|
29595
|
+
const errors = [];
|
|
29596
|
+
if (!isObj5(config)) return [{ path: "", message: "The config must be an object." }];
|
|
29597
|
+
for (const c of NEON_CONTROLS) {
|
|
29598
|
+
const v = getPath4(config, c.path);
|
|
29599
|
+
const where = c.path;
|
|
29600
|
+
if (c.type === "text") {
|
|
29601
|
+
if (typeof v !== "string") errors.push({ path: where, message: "Must be text." });
|
|
29602
|
+
else if (c.path === "text" && v.length > MAX_NEON_TEXT_LENGTH) errors.push({ path: where, message: `At most ${MAX_NEON_TEXT_LENGTH} characters.` });
|
|
29603
|
+
else if (c.path !== "text" && !v.trim()) errors.push({ path: where, message: "Must not be empty." });
|
|
29604
|
+
} else if (c.type === "number") {
|
|
29605
|
+
if (typeof v !== "number" || !Number.isFinite(v)) errors.push({ path: where, message: "Must be a number." });
|
|
29606
|
+
else if (c.min !== void 0 && v < c.min || c.max !== void 0 && v > c.max) errors.push({ path: where, message: `Must be between ${c.min} and ${c.max}.` });
|
|
29607
|
+
} else if (c.type === "boolean") {
|
|
29608
|
+
if (typeof v !== "boolean") errors.push({ path: where, message: "Must be true or false." });
|
|
29609
|
+
} else if (c.type === "enum") {
|
|
29610
|
+
if (!c.options.includes(v)) errors.push({ path: where, message: `Must be one of: ${c.options.join(", ")}.` });
|
|
29611
|
+
} else if (c.type === "color") {
|
|
29612
|
+
if (!isHexColor(v)) errors.push({ path: where, message: "Must be a hex colour like #FF3B4D." });
|
|
29613
|
+
} else if (v !== null && !isHexColor(v)) {
|
|
29614
|
+
errors.push({ path: where, message: "Must be a hex colour like #FF3B4D, or null." });
|
|
29615
|
+
}
|
|
29616
|
+
}
|
|
29617
|
+
return errors;
|
|
29618
|
+
}
|
|
29619
|
+
|
|
29620
|
+
// src/motion/neon/artwork.ts
|
|
29621
|
+
var SHAPES = /* @__PURE__ */ new Set(["path", "circle", "ellipse", "rect", "line", "polyline", "polygon"]);
|
|
29622
|
+
var CONTAINERS = /* @__PURE__ */ new Set(["g", "a", "switch"]);
|
|
29623
|
+
var SKIP = /* @__PURE__ */ new Set(["defs", "style", "script", "title", "desc", "metadata", "clippath", "mask", "pattern", "lineargradient", "radialgradient", "filter", "symbol", "marker", "text", "foreignobject", "image", "use"]);
|
|
29624
|
+
var ATTRS = /* @__PURE__ */ new Set(["d", "cx", "cy", "r", "rx", "ry", "x", "y", "width", "height", "x1", "y1", "x2", "y2", "points", "transform", "fill-rule"]);
|
|
29625
|
+
var SAFE_VALUE = /^[\s\d.,eE+\-MmLlHhVvCcSsQqTtAaZz()a-z%]*$/;
|
|
29626
|
+
var SAFE_TRANSFORM = /^\s*((matrix|translate|scale|rotate|skewX|skewY)\s*\([\d\s.,eE+-]*\)\s*,?\s*)*$/;
|
|
29627
|
+
function parseAttrs(src) {
|
|
29628
|
+
const out = {};
|
|
29629
|
+
for (const m of src.matchAll(/([a-zA-Z_:][\w:.-]*)\s*=\s*("([^"]*)"|'([^']*)')/g)) out[m[1].toLowerCase()] = m[3] ?? m[4] ?? "";
|
|
29630
|
+
return out;
|
|
29631
|
+
}
|
|
29632
|
+
var MAX_SVG_BYTES = 5e5;
|
|
29633
|
+
var MAX_NODES = 2e3;
|
|
29634
|
+
function parseSvgArtwork(svg) {
|
|
29635
|
+
if (typeof svg !== "string" || !svg.trim()) throw new Error("The SVG is empty.");
|
|
29636
|
+
if (svg.length > MAX_SVG_BYTES) throw new Error(`The SVG is larger than ${MAX_SVG_BYTES / 1e3} kB.`);
|
|
29637
|
+
const clean = svg.replace(/<!--[\s\S]*?-->/g, "").replace(/<!\[CDATA\[[\s\S]*?\]\]>/g, "").replace(/<![^>]*>/g, "").replace(/<\?[\s\S]*?\?>/g, "");
|
|
29638
|
+
const root = { tag: "g", attrs: {}, children: [] };
|
|
29639
|
+
const stack = [{ node: root, tag: "#root" }];
|
|
29640
|
+
let viewBox = null;
|
|
29641
|
+
let count = 0;
|
|
29642
|
+
let shapes = 0;
|
|
29643
|
+
for (const m of clean.matchAll(/<(\/?)([a-zA-Z][\w:.-]*)((?:\s+[^\s=>/]+\s*=\s*(?:"[^"]*"|'[^']*'))*)\s*(\/?)>/g)) {
|
|
29644
|
+
const [, close, rawTag, rawAttrs, selfClose] = m;
|
|
29645
|
+
const tag = rawTag.toLowerCase().replace(/^svg:/, "");
|
|
29646
|
+
if (close) {
|
|
29647
|
+
while (stack.length > 1) {
|
|
29648
|
+
const top = stack.pop();
|
|
29649
|
+
if (top.tag === tag) break;
|
|
29650
|
+
}
|
|
29651
|
+
continue;
|
|
29652
|
+
}
|
|
29653
|
+
const parent = stack[stack.length - 1].node;
|
|
29654
|
+
const attrs = parseAttrs(rawAttrs ?? "");
|
|
29655
|
+
if (tag === "svg" && !viewBox) {
|
|
29656
|
+
const vb = (attrs.viewbox ?? "").trim().split(/[\s,]+/).map(Number);
|
|
29657
|
+
if (vb.length === 4 && vb.every(Number.isFinite) && vb[2] > 0 && vb[3] > 0) viewBox = vb;
|
|
29658
|
+
else {
|
|
29659
|
+
const w = parseFloat(attrs.width ?? "");
|
|
29660
|
+
const h = parseFloat(attrs.height ?? "");
|
|
29661
|
+
if (w > 0 && h > 0) viewBox = [0, 0, w, h];
|
|
29662
|
+
}
|
|
29663
|
+
}
|
|
29664
|
+
if (tag === "svg") {
|
|
29665
|
+
if (!selfClose) stack.push({ node: parent, tag });
|
|
29666
|
+
continue;
|
|
29667
|
+
}
|
|
29668
|
+
let node = null;
|
|
29669
|
+
if (parent && !SKIP.has(tag) && (SHAPES.has(tag) || CONTAINERS.has(tag)) && count < MAX_NODES) {
|
|
29670
|
+
const kept = {};
|
|
29671
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
29672
|
+
if (!ATTRS.has(k)) continue;
|
|
29673
|
+
if (k === "transform" ? !SAFE_TRANSFORM.test(v) : !SAFE_VALUE.test(v)) continue;
|
|
29674
|
+
if (CONTAINERS.has(tag) && k !== "transform") continue;
|
|
29675
|
+
kept[k] = v;
|
|
29676
|
+
}
|
|
29677
|
+
node = { tag: SHAPES.has(tag) ? tag : "g", attrs: kept, children: [] };
|
|
29678
|
+
parent.children.push(node);
|
|
29679
|
+
count++;
|
|
29680
|
+
if (SHAPES.has(tag)) shapes++;
|
|
29681
|
+
}
|
|
29682
|
+
if (!selfClose && !SHAPES.has(tag)) stack.push({ node: SKIP.has(tag) ? null : node, tag });
|
|
29683
|
+
else if (!selfClose && SHAPES.has(tag)) stack.push({ node: null, tag });
|
|
29684
|
+
}
|
|
29685
|
+
if (!shapes) throw new Error("The SVG has no shapes to bend into tubes (paths, circles, rects, lines or polygons).");
|
|
29686
|
+
return { viewBox: viewBox ?? [0, 0, 100, 100], nodes: root.children, shapes };
|
|
29687
|
+
}
|
|
29688
|
+
var NEON_ARTWORK_LIBRARY = {
|
|
29689
|
+
heart: { name: "Heart", svg: '<svg viewBox="0 0 100 100"><path d="M50 84 C22 64 8 48 12 32 C16 17 35 13 45 24 C48 27 49 30 50 33 C51 30 52 27 55 24 C65 13 84 17 88 32 C92 48 78 64 50 84 Z"/></svg>' },
|
|
29690
|
+
star: { name: "Star", svg: '<svg viewBox="0 0 100 100"><polygon points="50,9 61,37 91,38 67,57 76,87 50,70 24,87 33,57 9,38 39,37"/></svg>' },
|
|
29691
|
+
bolt: { name: "Lightning", svg: '<svg viewBox="0 0 100 100"><path d="M60 7 L26 55 L49 55 L40 93 L75 42 L52 42 Z"/></svg>' },
|
|
29692
|
+
moon: { name: "Moon", svg: '<svg viewBox="0 0 100 100"><path d="M64 12 C40 16 26 35 28 55 C30 76 50 90 72 87 C56 80 45 66 45 49 C45 33 52 20 64 12 Z"/></svg>' },
|
|
29693
|
+
sparkle: { name: "Sparkle", svg: '<svg viewBox="0 0 100 100"><path d="M50 8 C54 38 62 46 92 50 C62 54 54 62 50 92 C46 62 38 54 8 50 C38 46 46 38 50 8 Z"/></svg>' },
|
|
29694
|
+
note: { name: "Music note", svg: '<svg viewBox="0 0 100 100"><path d="M38 74 C38 83 21 86 19 77 C17 69 33 64 38 71 L38 20 L80 11 L80 64 C80 73 63 76 61 67 C59 59 75 54 80 61"/><path d="M38 31 L80 22"/></svg>' },
|
|
29695
|
+
arrow: { name: "Arrow", svg: '<svg viewBox="0 0 100 100"><path d="M10 50 L84 50"/><path d="M62 28 L86 50 L62 72"/></svg>' },
|
|
29696
|
+
cocktail: { name: "Cocktail", svg: '<svg viewBox="0 0 100 100"><path d="M18 18 L82 18 L50 54 Z"/><path d="M50 54 L50 86 M32 86 L68 86"/><circle cx="70" cy="12" r="7"/></svg>' },
|
|
29697
|
+
rocket: { name: "Rocket", svg: '<svg viewBox="0 0 100 100"><path d="M50 8 C66 22 70 44 64 66 L36 66 C30 44 34 22 50 8 Z"/><circle cx="50" cy="36" r="7"/><path d="M36 54 L24 72 L36 68 M64 54 L76 72 L64 68 M44 74 L50 90 L56 74"/></svg>' },
|
|
29698
|
+
crown: { name: "Crown", svg: '<svg viewBox="0 0 100 100"><path d="M14 74 L10 30 L32 50 L50 20 L68 50 L90 30 L86 74 Z"/><path d="M16 84 L84 84"/></svg>' }
|
|
29699
|
+
};
|
|
29700
|
+
|
|
29701
|
+
// src/motion/neon/fonts.ts
|
|
29702
|
+
var f = (id, name, category, fallback2, google, weight3, construction, stroke) => ({ id, name, category, family: google ? `'${name}', ${fallback2}` : fallback2, google, weight: weight3, construction, stroke });
|
|
29703
|
+
var NEON_FONTS = [
|
|
29704
|
+
f("tilt-neon", "Tilt Neon", "neon tube", "Quicksand, sans-serif", "Tilt+Neon", 400, "monoline", 0.04),
|
|
29705
|
+
f("bebas-neue", "Bebas Neue", "condensed", "Oswald, 'Arial Narrow', sans-serif", "Bebas+Neue", 400, "outline", 0.033),
|
|
29706
|
+
f("anton", "Anton", "bold", "'Arial Narrow', Impact, sans-serif", "Anton", 400, "outline", 0.03),
|
|
29707
|
+
f("oswald", "Oswald", "condensed", "'Arial Narrow', 'Helvetica Neue', Arial, sans-serif", "Oswald:wght@300;400;500;600;700", 500, "outline", 0.036),
|
|
29708
|
+
f("space-grotesk", "Space Grotesk", "geometric", "'Helvetica Neue', Arial, sans-serif", "Space+Grotesk:wght@400;500;700", 500, "outline", 0.033),
|
|
29709
|
+
f("orbitron", "Orbitron", "futuristic", "'Eurostile', sans-serif", "Orbitron:wght@400;600;800", 600, "outline", 0.03),
|
|
29710
|
+
f("audiowide", "Audiowide", "futuristic", "sans-serif", "Audiowide", 400, "outline", 0.03),
|
|
29711
|
+
f("russo-one", "Russo One", "signage", "Impact, 'Arial Black', sans-serif", "Russo+One", 400, "outline", 0.03),
|
|
29712
|
+
f("rajdhani", "Rajdhani", "industrial", "'Arial Narrow', sans-serif", "Rajdhani:wght@500;600;700", 600, "outline", 0.033),
|
|
29713
|
+
f("exo-2", "Exo 2", "futuristic", "sans-serif", "Exo+2:wght@300;500;700", 500, "outline", 0.033),
|
|
29714
|
+
f("montserrat", "Montserrat", "geometric", "'Helvetica Neue', Arial, sans-serif", "Montserrat:wght@300;500;700", 500, "outline", 0.033),
|
|
29715
|
+
f("inter", "Inter", "geometric", "system-ui, 'Helvetica Neue', Arial, sans-serif", "Inter:wght@300;500;700", 500, "outline", 0.033),
|
|
29716
|
+
f("archivo", "Archivo", "signage", "'Helvetica Neue', Arial, sans-serif", "Archivo:wght@400;600;800", 600, "outline", 0.033),
|
|
29717
|
+
f("barlow-condensed", "Barlow Condensed", "condensed", "'Arial Narrow', sans-serif", "Barlow+Condensed:wght@400;600", 600, "outline", 0.034),
|
|
29718
|
+
f("sora", "Sora", "geometric", "sans-serif", "Sora:wght@300;500;700", 500, "outline", 0.033),
|
|
29719
|
+
f("raleway", "Raleway", "thin", "'Helvetica Neue', sans-serif", "Raleway:wght@200;300;500", 300, "monoline", 0.03),
|
|
29720
|
+
f("press-start-2p", "Press Start 2P", "arcade", "'Courier New', monospace", "Press+Start+2P", 400, "outline", 0.03),
|
|
29721
|
+
f("pacifico", "Pacifico", "handwritten", "'Brush Script MT', cursive", "Pacifico", 400, "outline", 0.026),
|
|
29722
|
+
f("dm-sans", "DM Sans", "rounded", "'Helvetica Neue', Arial, sans-serif", "DM+Sans:wght@300;500;700", 500, "outline", 0.033),
|
|
29723
|
+
f("quicksand", "Quicksand", "rounded", "'Arial Rounded MT Bold', 'Helvetica Neue', Arial, sans-serif", "Quicksand:wght@300;400;500;600;700", 500, "monoline", 0.045),
|
|
29724
|
+
f("sacramento", "Sacramento", "handwritten", "'Snell Roundhand', 'Brush Script MT', cursive", "Sacramento", 400, "monoline", 0.03),
|
|
29725
|
+
f("righteous", "Righteous", "retro", "sans-serif", "Righteous", 400, "outline", 0.03),
|
|
29726
|
+
f("space-mono", "Space Mono", "monospace", "'Courier New', monospace", "Space+Mono:wght@400;700", 400, "monoline", 0.03),
|
|
29727
|
+
f("great-vibes", "Great Vibes", "elegant", "'Snell Roundhand', cursive", "Great+Vibes", 400, "monoline", 0.022),
|
|
29728
|
+
f("black-ops-one", "Black Ops One", "industrial", "Impact, sans-serif", "Black+Ops+One", 400, "outline", 0.026),
|
|
29729
|
+
f("monoton", "Monoton", "display", "sans-serif", "Monoton", 400, "monoline", 8e-3),
|
|
29730
|
+
f("system", "System", "system", "system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif", null, 600, "outline", 0.033)
|
|
29731
|
+
];
|
|
29732
|
+
var NEON_EMOJI_FONT = f("noto-emoji", "Noto Emoji", "display", "sans-serif", "Noto+Emoji:wght@400;500;700", 500, "outline", 0.03);
|
|
29733
|
+
function findNeonFont(query) {
|
|
29734
|
+
const q = query.trim().toLowerCase().replace(/[-_]+/g, " ");
|
|
29735
|
+
if (!q) return void 0;
|
|
29736
|
+
return NEON_FONTS.find((x) => x.id.replace(/-/g, " ") === q || x.name.toLowerCase() === q) ?? NEON_FONTS.find((x) => x.name.toLowerCase().startsWith(q) || x.family.toLowerCase().startsWith(`'${q}`)) ?? NEON_FONTS.find((x) => x.name.toLowerCase().includes(q));
|
|
29737
|
+
}
|
|
29738
|
+
|
|
29739
|
+
// src/motion/neon/material.ts
|
|
29740
|
+
function materialPad(neon, t) {
|
|
29741
|
+
const shadow = neon.shadow.enabled ? Math.hypot(neon.shadow.offsetX, neon.shadow.offsetY) * t.fontSize + neon.shadow.blur * 2 : 0;
|
|
29742
|
+
const light2 = neon.intensity > 0 ? Math.max(neon.bloomRadius * 1.25, neon.glowRadius * 1.6) : 0;
|
|
29743
|
+
return Math.ceil(Math.max(shadow, light2) + t.strokeWidth * 2 + t.fontSize * 0.04);
|
|
29744
|
+
}
|
|
29745
|
+
|
|
29746
|
+
// src/motion/neon/typography.ts
|
|
29747
|
+
var NARROW = /* @__PURE__ */ new Set([..."iIl.,:;!|'`jft1"]);
|
|
29748
|
+
var WIDE = /* @__PURE__ */ new Set([..."MWmw@%"]);
|
|
29749
|
+
var CAPS = /[A-Z0-9]/;
|
|
29750
|
+
var estimateTextWidth = (text6, t) => {
|
|
29751
|
+
const condensed = /oswald|bebas|narrow|condensed|compressed|league gothic|anton/i.test(t.fontFamily) ? 0.8 : 1;
|
|
29752
|
+
const weight3 = 0.92 + (t.fontWeight - 400) / 2500;
|
|
29753
|
+
let em = 0;
|
|
29754
|
+
for (const ch of text6) {
|
|
29755
|
+
if (ch === " ") em += 0.28;
|
|
29756
|
+
else if (NARROW.has(ch)) em += 0.3;
|
|
29757
|
+
else if (WIDE.has(ch)) em += 0.86;
|
|
29758
|
+
else if (ch === "/") em += 0.4;
|
|
29759
|
+
else if (CAPS.test(ch)) em += 0.64;
|
|
29760
|
+
else em += 0.54;
|
|
29761
|
+
}
|
|
29762
|
+
return em * condensed * weight3 * t.fontSize;
|
|
29763
|
+
};
|
|
29764
|
+
function textRows(text6) {
|
|
29765
|
+
const rows = String(text6 ?? "").replace(/\\n/g, "\n").replace(/\r\n?/g, "\n").replace(/\t/g, " ").split("\n");
|
|
29766
|
+
return rows.length && rows.some((r) => r.length) ? rows : [" "];
|
|
29767
|
+
}
|
|
29768
|
+
var CAP = 0.74;
|
|
29769
|
+
var DESCENT = 0.24;
|
|
29770
|
+
function layoutText(text6, t, pad, measure = estimateTextWidth) {
|
|
29771
|
+
const rows = textRows(text6);
|
|
29772
|
+
const pitch = t.fontSize * t.lineHeight;
|
|
29773
|
+
const measured = rows.map((r, i) => {
|
|
29774
|
+
const glyphs = [...r].length;
|
|
29775
|
+
let w;
|
|
29776
|
+
try {
|
|
29777
|
+
w = measure(r, t);
|
|
29778
|
+
} catch {
|
|
29779
|
+
w = estimateTextWidth(r, t);
|
|
29780
|
+
}
|
|
29781
|
+
if (!Number.isFinite(w) || w < 0) w = estimateTextWidth(r, t);
|
|
29782
|
+
return { text: r, y: i * pitch, width: w + t.letterSpacing * Math.max(0, glyphs - 1) };
|
|
29783
|
+
});
|
|
29784
|
+
const width = Math.max(t.fontSize * 0.3, ...measured.map((r) => r.width));
|
|
29785
|
+
const textAnchor = t.align;
|
|
29786
|
+
const anchorX = textAnchor === "start" ? -width / 2 : textAnchor === "end" ? width / 2 : 0;
|
|
29787
|
+
const top = -t.fontSize * CAP - t.strokeWidth;
|
|
29788
|
+
const bottom = (rows.length - 1) * pitch + t.fontSize * DESCENT + t.strokeWidth;
|
|
29789
|
+
const box = { x: -width / 2 - t.strokeWidth, y: top, width: width + 2 * t.strokeWidth, height: bottom - top };
|
|
29790
|
+
return {
|
|
29791
|
+
rows: measured,
|
|
29792
|
+
anchorX,
|
|
29793
|
+
textAnchor,
|
|
29794
|
+
box,
|
|
29795
|
+
viewBox: { x: box.x - pad, y: box.y - pad, width: box.width + 2 * pad, height: box.height + 2 * pad },
|
|
29796
|
+
pad
|
|
29797
|
+
};
|
|
29798
|
+
}
|
|
29799
|
+
|
|
29800
|
+
// src/motion/neon/wall.ts
|
|
29801
|
+
var DEFAULT_NEON_WALL = {
|
|
29802
|
+
enabled: true,
|
|
29803
|
+
seed: 7356951,
|
|
29804
|
+
brickWidth: 150,
|
|
29805
|
+
brickHeight: 52,
|
|
29806
|
+
mortar: 4,
|
|
29807
|
+
brickColor: "#0D1230",
|
|
29808
|
+
mortarColor: "#030409",
|
|
29809
|
+
variation: 0.5,
|
|
29810
|
+
texture: 0.5,
|
|
29811
|
+
ambientColor: "#1F2FD0",
|
|
29812
|
+
ambient: 0.55,
|
|
29813
|
+
vignette: 0.85,
|
|
29814
|
+
lightSpill: 1,
|
|
29815
|
+
spillRadius: 260,
|
|
29816
|
+
glitchAffectsWall: true
|
|
29817
|
+
};
|
|
29818
|
+
|
|
29819
|
+
// src/motion/neon/scene.ts
|
|
29820
|
+
var DEFAULT_NEON_AUDIO = {
|
|
29821
|
+
enabled: false,
|
|
29822
|
+
muted: false,
|
|
29823
|
+
master: 0.7,
|
|
29824
|
+
hum: 0.45,
|
|
29825
|
+
flicker: 0.45,
|
|
29826
|
+
dip: 0.55,
|
|
29827
|
+
failure: 0.6,
|
|
29828
|
+
glitch: 0.3,
|
|
29829
|
+
recovery: 0.45,
|
|
29830
|
+
scrub: true,
|
|
29831
|
+
scrubVolume: 0.5
|
|
29832
|
+
};
|
|
29833
|
+
var common = (id, seed) => ({
|
|
29834
|
+
id,
|
|
29835
|
+
x: 800,
|
|
29836
|
+
y: 500,
|
|
29837
|
+
scale: 1,
|
|
29838
|
+
rotation: 0,
|
|
29839
|
+
opacity: 1,
|
|
29840
|
+
visible: true,
|
|
29841
|
+
locked: false,
|
|
29842
|
+
neon: { ...DEFAULT_NEON_CONFIG.neon, shadow: { ...DEFAULT_NEON_CONFIG.neon.shadow } },
|
|
29843
|
+
animation: { ...DEFAULT_NEON_CONFIG.animation, seed }
|
|
29844
|
+
});
|
|
29845
|
+
var DEFAULT_NEON_ELEMENTS = {
|
|
29846
|
+
text: { ...common("text-1", 7356951), type: "text", text: "CAFE", typography: { ...DEFAULT_NEON_CONFIG.typography, fontSize: 300, strokeWidth: 10.5, letterSpacing: 6 } },
|
|
29847
|
+
emoji: { ...common("emoji-1", 7356952), type: "emoji", emoji: "\u2615", size: 240, strokeWidth: 8, construction: "outline", fontWeight: 500, fontFamily: NEON_EMOJI_FONT.family },
|
|
29848
|
+
artwork: { ...common("artwork-1", 7356953), type: "artwork", artwork: { kind: "library", id: "heart" }, size: 260, strokeWidth: 8, construction: "outline" }
|
|
29849
|
+
};
|
|
29850
|
+
var DEFAULT_NEON_SCENE = {
|
|
29851
|
+
type: "neonScene",
|
|
29852
|
+
version: 1,
|
|
29853
|
+
stage: { width: 1600, height: 1e3 },
|
|
29854
|
+
elements: [DEFAULT_NEON_ELEMENTS.text],
|
|
29855
|
+
wall: DEFAULT_NEON_WALL,
|
|
29856
|
+
audio: DEFAULT_NEON_AUDIO,
|
|
29857
|
+
timeline: { duration: 8e3, loop: true, triggers: [] }
|
|
29858
|
+
};
|
|
29859
|
+
var isObj6 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
29860
|
+
function merge(base4, over) {
|
|
29861
|
+
if (!isObj6(base4) || !isObj6(over)) return over === void 0 ? base4 : over;
|
|
29862
|
+
const out = { ...base4 };
|
|
29863
|
+
for (const [k, v] of Object.entries(over)) {
|
|
29864
|
+
if (v === void 0) continue;
|
|
29865
|
+
out[k] = isObj6(out[k]) && isObj6(v) ? merge(out[k], v) : v;
|
|
29866
|
+
}
|
|
29867
|
+
return out;
|
|
29868
|
+
}
|
|
29869
|
+
function withDerivedColours(el, input) {
|
|
29870
|
+
const n = isObj6(input) && isObj6(input.neon) ? input.neon : void 0;
|
|
29871
|
+
const primary = n && typeof n.color === "string" ? n.color : n && typeof n.tubeColor === "string" ? n.tubeColor : void 0;
|
|
29872
|
+
if (!n || !isHexColor(primary)) return el;
|
|
29873
|
+
const d = deriveNeonPalette(primary);
|
|
29874
|
+
const neon = { ...el.neon, color: primary };
|
|
29875
|
+
for (const k of ["coreColor", "tubeColor", "glowColor", "bloomColor"]) if (n[k] === void 0) neon[k] = d[k];
|
|
29876
|
+
return { ...el, neon };
|
|
29877
|
+
}
|
|
29878
|
+
function resolveNeonElement(input, index = 0) {
|
|
29879
|
+
const type = isObj6(input) && (input.type === "emoji" || input.type === "artwork") ? input.type : "text";
|
|
29880
|
+
const base4 = DEFAULT_NEON_ELEMENTS[type];
|
|
29881
|
+
const seeded = { ...base4, id: `${type}-${index + 1}`, animation: { ...base4.animation, seed: base4.animation.seed + index * 101 } };
|
|
29882
|
+
const el = merge(seeded, isObj6(input) ? input : {});
|
|
29883
|
+
return withDerivedColours({ ...el, type }, input);
|
|
29884
|
+
}
|
|
29885
|
+
function resolveNeonScene(input) {
|
|
29886
|
+
const i = isObj6(input) ? input : {};
|
|
29887
|
+
const elements = Array.isArray(i.elements) ? i.elements.map((e, n) => resolveNeonElement(e, n)) : DEFAULT_NEON_SCENE.elements;
|
|
29888
|
+
return {
|
|
29889
|
+
...DEFAULT_NEON_SCENE,
|
|
29890
|
+
stage: merge(DEFAULT_NEON_SCENE.stage, i.stage),
|
|
29891
|
+
wall: merge(DEFAULT_NEON_SCENE.wall, i.wall),
|
|
29892
|
+
audio: merge(DEFAULT_NEON_SCENE.audio, i.audio),
|
|
29893
|
+
timeline: merge(DEFAULT_NEON_SCENE.timeline, i.timeline),
|
|
29894
|
+
elements
|
|
29895
|
+
};
|
|
29896
|
+
}
|
|
29897
|
+
var num9 = (v, min, max) => typeof v === "number" && Number.isFinite(v) && v >= min && v <= max;
|
|
29898
|
+
var WALL_RANGES = [
|
|
29899
|
+
["brickWidth", 20, 1e3],
|
|
29900
|
+
["brickHeight", 10, 500],
|
|
29901
|
+
["mortar", 0, 40],
|
|
29902
|
+
["variation", 0, 1],
|
|
29903
|
+
["texture", 0, 1],
|
|
29904
|
+
["ambient", 0, 1],
|
|
29905
|
+
["vignette", 0, 1],
|
|
29906
|
+
["lightSpill", 0, 2],
|
|
29907
|
+
["spillRadius", 0, 2e3],
|
|
29908
|
+
["seed", 0, 4294967295]
|
|
29909
|
+
];
|
|
29910
|
+
var AUDIO_KEYS = ["master", "hum", "flicker", "dip", "failure", "glitch", "recovery", "scrubVolume"];
|
|
29911
|
+
function textLike(el) {
|
|
29912
|
+
if (el.type === "text") return { text: el.text, typography: el.typography };
|
|
29913
|
+
return {
|
|
29914
|
+
text: emojiText(el.emoji),
|
|
29915
|
+
typography: { fontFamily: el.fontFamily, fontSize: el.size, fontWeight: el.fontWeight, fontStyle: "normal", letterSpacing: 0, lineHeight: 1, strokeWidth: el.strokeWidth, construction: el.construction, align: "middle" }
|
|
29916
|
+
};
|
|
29917
|
+
}
|
|
29918
|
+
var emojiText = (e) => `${String(e ?? "").replace(/️/g, "")}\uFE0E`;
|
|
29919
|
+
function validateNeonElement(el, at = "") {
|
|
29920
|
+
if (!isObj6(el)) return [{ path: at, message: "An element must be an object." }];
|
|
29921
|
+
const errors = [];
|
|
29922
|
+
const p = (k) => at ? `${at}.${k}` : k;
|
|
29923
|
+
if (!["text", "emoji", "artwork"].includes(el.type)) errors.push({ path: p("type"), message: "Must be text, emoji or artwork." });
|
|
29924
|
+
if (typeof el.id !== "string" || !el.id) errors.push({ path: p("id"), message: "Must be a non-empty id." });
|
|
29925
|
+
for (const [k, lo, hi] of [["x", -5e3, 1e4], ["y", -5e3, 1e4], ["scale", 0.01, 20], ["rotation", -360, 360], ["opacity", 0, 1]]) {
|
|
29926
|
+
if (!num9(el[k], lo, hi)) errors.push({ path: p(k), message: `Must be a number between ${lo} and ${hi}.` });
|
|
29927
|
+
}
|
|
29928
|
+
if (typeof el.visible !== "boolean") errors.push({ path: p("visible"), message: "Must be true or false." });
|
|
29929
|
+
if (typeof el.locked !== "boolean") errors.push({ path: p("locked"), message: "Must be true or false." });
|
|
29930
|
+
const asText = { ...DEFAULT_NEON_CONFIG, neon: el.neon, animation: el.animation };
|
|
29931
|
+
for (const e of validateNeonConfig(asText)) if (e.path.startsWith("neon") || e.path.startsWith("animation")) errors.push({ path: p(e.path), message: e.message });
|
|
29932
|
+
if (el.type === "text") {
|
|
29933
|
+
for (const e of validateNeonConfig({ ...DEFAULT_NEON_CONFIG, text: el.text, typography: el.typography })) if (e.path === "text" || e.path.startsWith("typography")) errors.push({ path: p(e.path), message: e.message });
|
|
29934
|
+
} else if (el.type === "emoji" || el.type === "artwork") {
|
|
29935
|
+
if (!num9(el.size, 8, 2e3)) errors.push({ path: p("size"), message: "Must be a number between 8 and 2000." });
|
|
29936
|
+
if (!num9(el.strokeWidth, 0.5, 80)) errors.push({ path: p("strokeWidth"), message: "Must be a number between 0.5 and 80." });
|
|
29937
|
+
if (el.construction !== "outline" && el.construction !== "monoline") errors.push({ path: p("construction"), message: "Must be outline or monoline." });
|
|
29938
|
+
if (el.type === "emoji") {
|
|
29939
|
+
if (typeof el.emoji !== "string" || !el.emoji.trim() || [...el.emoji].length > 12) errors.push({ path: p("emoji"), message: "Must be one emoji (or a few characters)." });
|
|
29940
|
+
if (!num9(el.fontWeight, 100, 900)) errors.push({ path: p("fontWeight"), message: "Must be between 100 and 900." });
|
|
29941
|
+
if (typeof el.fontFamily !== "string" || !el.fontFamily.trim()) errors.push({ path: p("fontFamily"), message: "Must be a font-family." });
|
|
29942
|
+
} else {
|
|
29943
|
+
const a = el.artwork;
|
|
29944
|
+
if (!isObj6(a)) errors.push({ path: p("artwork"), message: "Must be { kind: library | svg | raster, \u2026 }." });
|
|
29945
|
+
else if (a.kind === "library") {
|
|
29946
|
+
if (!NEON_ARTWORK_LIBRARY[a.id]) errors.push({ path: p("artwork.id"), message: `Must be one of: ${Object.keys(NEON_ARTWORK_LIBRARY).join(", ")}.` });
|
|
29947
|
+
} else if (a.kind === "svg") {
|
|
29948
|
+
try {
|
|
29949
|
+
parseSvgArtwork(a.svg);
|
|
29950
|
+
} catch (err) {
|
|
29951
|
+
errors.push({ path: p("artwork.svg"), message: err.message });
|
|
29952
|
+
}
|
|
29953
|
+
} else if (a.kind === "raster") {
|
|
29954
|
+
if (typeof a.src !== "string" || !/^(data:image\/(png|webp|jpeg|gif);base64,|https?:\/\/|\/)/.test(a.src)) errors.push({ path: p("artwork.src"), message: "Must be a PNG/WebP data URL or an image URL." });
|
|
29955
|
+
if (!num9(a.width, 1, 2e4) || !num9(a.height, 1, 2e4)) errors.push({ path: p("artwork.width"), message: "Width and height must be the image\u2019s pixel size." });
|
|
29956
|
+
if (a.mode !== "alpha" && a.mode !== "luminance") errors.push({ path: p("artwork.mode"), message: "Must be alpha or luminance." });
|
|
29957
|
+
if (!num9(a.threshold, 0, 1)) errors.push({ path: p("artwork.threshold"), message: "Must be between 0 and 1." });
|
|
29958
|
+
} else errors.push({ path: p("artwork.kind"), message: "Must be library, svg or raster." });
|
|
29959
|
+
}
|
|
29960
|
+
}
|
|
29961
|
+
return errors;
|
|
29962
|
+
}
|
|
29963
|
+
function validateNeonScene(scene) {
|
|
29964
|
+
if (!isObj6(scene)) return [{ path: "", message: "The scene must be an object." }];
|
|
29965
|
+
const errors = [];
|
|
29966
|
+
const st = scene.stage;
|
|
29967
|
+
if (!isObj6(st) || !num9(st.width, 100, 8e3) || !num9(st.height, 100, 8e3)) errors.push({ path: "stage", message: "width and height must be 100\u20268000." });
|
|
29968
|
+
if (!Array.isArray(scene.elements)) errors.push({ path: "elements", message: "Must be a list." });
|
|
29969
|
+
else {
|
|
29970
|
+
if (scene.elements.length > 40) errors.push({ path: "elements", message: "At most 40 elements." });
|
|
29971
|
+
const ids = /* @__PURE__ */ new Set();
|
|
29972
|
+
scene.elements.forEach((el, i) => {
|
|
29973
|
+
errors.push(...validateNeonElement(el, `elements.${i}`));
|
|
29974
|
+
if (isObj6(el) && typeof el.id === "string") {
|
|
29975
|
+
if (ids.has(el.id)) errors.push({ path: `elements.${i}.id`, message: "Ids must be unique." });
|
|
29976
|
+
ids.add(el.id);
|
|
29977
|
+
}
|
|
29978
|
+
});
|
|
29979
|
+
}
|
|
29980
|
+
const w = scene.wall;
|
|
29981
|
+
if (!isObj6(w)) errors.push({ path: "wall", message: "Must be an object." });
|
|
29982
|
+
else {
|
|
29983
|
+
if (typeof w.enabled !== "boolean") errors.push({ path: "wall.enabled", message: "Must be true or false." });
|
|
29984
|
+
for (const [k, lo, hi] of WALL_RANGES) if (!num9(w[k], lo, hi)) errors.push({ path: `wall.${k}`, message: `Must be a number between ${lo} and ${hi}.` });
|
|
29985
|
+
for (const k of ["brickColor", "mortarColor", "ambientColor"]) if (!isHexColor(w[k])) errors.push({ path: `wall.${k}`, message: "Must be a hex colour." });
|
|
29986
|
+
}
|
|
29987
|
+
const a = scene.audio;
|
|
29988
|
+
if (!isObj6(a)) errors.push({ path: "audio", message: "Must be an object." });
|
|
29989
|
+
else {
|
|
29990
|
+
for (const k of ["enabled", "muted", "scrub"]) if (typeof a[k] !== "boolean") errors.push({ path: `audio.${k}`, message: "Must be true or false." });
|
|
29991
|
+
for (const k of AUDIO_KEYS) if (!num9(a[k], 0, 1)) errors.push({ path: `audio.${k}`, message: "Must be a number between 0 and 1." });
|
|
29992
|
+
}
|
|
29993
|
+
const t = scene.timeline;
|
|
29994
|
+
if (!isObj6(t) || !num9(t.duration, 500, 6e5) || typeof t.loop !== "boolean" || !Array.isArray(t.triggers)) errors.push({ path: "timeline", message: "Needs duration (500\u2026600000 ms), loop and a triggers list." });
|
|
29995
|
+
else t.triggers.forEach((tr, i) => {
|
|
29996
|
+
if (!isObj6(tr) || !["sag", "flicker", "dip", "failure", "glitch", "normal"].includes(tr.kind) || !num9(tr.at, 0, 6e5)) errors.push({ path: `timeline.triggers.${i}`, message: "Needs kind (sag, flicker, dip, failure, glitch, normal) and at (ms)." });
|
|
29997
|
+
});
|
|
29998
|
+
return errors;
|
|
29999
|
+
}
|
|
30000
|
+
var r3 = (v) => Math.round(v * 1e3) / 1e3;
|
|
30001
|
+
function localNeon(neon, k) {
|
|
30002
|
+
return { ...neon, glowRadius: neon.glowRadius * k, bloomRadius: neon.bloomRadius * k, shadow: { ...neon.shadow, blur: neon.shadow.blur * k } };
|
|
30003
|
+
}
|
|
30004
|
+
function elementGeometry(el, measure = estimateTextWidth) {
|
|
30005
|
+
let shape;
|
|
30006
|
+
let box;
|
|
30007
|
+
let unit2 = 1;
|
|
30008
|
+
let em;
|
|
30009
|
+
let metrics;
|
|
30010
|
+
if (el.type === "text" || el.type === "emoji") {
|
|
30011
|
+
const { text: text6, typography } = textLike(el);
|
|
30012
|
+
const layout = layoutText(text6, typography, 0, el.type === "emoji" ? () => typography.fontSize * 1.05 : measure);
|
|
30013
|
+
shape = { kind: "text", layout, typography, text: text6 };
|
|
30014
|
+
box = layout.box;
|
|
30015
|
+
em = typography.fontSize;
|
|
30016
|
+
metrics = { fontSize: typography.fontSize, strokeWidth: typography.strokeWidth, construction: typography.construction };
|
|
30017
|
+
} else {
|
|
30018
|
+
let vb = [0, 0, 100, 100];
|
|
30019
|
+
const a = el.artwork;
|
|
30020
|
+
try {
|
|
30021
|
+
if (a.kind === "raster") {
|
|
30022
|
+
const w = Math.max(1, el.size);
|
|
30023
|
+
const h = w * a.height / Math.max(1, a.width);
|
|
30024
|
+
vb = [0, 0, a.width >= a.height ? w : w * a.width / a.height, a.width >= a.height ? h : w];
|
|
30025
|
+
shape = { kind: "raster", src: a.src, width: vb[2], height: vb[3], mode: a.mode, threshold: a.threshold };
|
|
30026
|
+
} else {
|
|
30027
|
+
const art = parseSvgArtwork(a.kind === "library" ? (NEON_ARTWORK_LIBRARY[a.id] ?? NEON_ARTWORK_LIBRARY.heart).svg : a.svg);
|
|
30028
|
+
vb = art.viewBox;
|
|
30029
|
+
shape = { kind: "svg", art };
|
|
30030
|
+
}
|
|
30031
|
+
} catch (err) {
|
|
30032
|
+
shape = { kind: "none", reason: err.message };
|
|
30033
|
+
}
|
|
30034
|
+
const side = Math.max(vb[2], vb[3]);
|
|
30035
|
+
unit2 = side / Math.max(1, el.size);
|
|
30036
|
+
box = { x: vb[0], y: vb[1], width: vb[2], height: vb[3] };
|
|
30037
|
+
em = side;
|
|
30038
|
+
metrics = { fontSize: side, strokeWidth: el.strokeWidth * unit2, construction: el.construction };
|
|
30039
|
+
}
|
|
30040
|
+
const neon = unit2 === 1 ? el.neon : localNeon(el.neon, unit2);
|
|
30041
|
+
const pad = materialPad(neon, metrics);
|
|
30042
|
+
const cx = box.x + box.width / 2;
|
|
30043
|
+
const cy = box.y + box.height / 2;
|
|
30044
|
+
const s = el.scale / unit2;
|
|
30045
|
+
const transform = `translate(${r3(el.x)} ${r3(el.y)}) rotate(${r3(el.rotation)}) scale(${r3(s)}) translate(${r3(-cx)} ${r3(-cy)})`;
|
|
30046
|
+
const hw = box.width / 2 * s;
|
|
30047
|
+
const hh = box.height / 2 * s;
|
|
30048
|
+
const rad = el.rotation * Math.PI / 180;
|
|
30049
|
+
const bw = Math.abs(hw * Math.cos(rad)) + Math.abs(hh * Math.sin(rad));
|
|
30050
|
+
const bh = Math.abs(hw * Math.sin(rad)) + Math.abs(hh * Math.cos(rad));
|
|
30051
|
+
return { shape, box, unit: unit2, em, metrics, neon, pad, transform, bounds: { x: el.x - bw, y: el.y - bh, width: 2 * bw, height: 2 * bh } };
|
|
30052
|
+
}
|
|
30053
|
+
var fontFor = (id) => NEON_FONTS.find((x) => x.id === id);
|
|
30054
|
+
function textEl(id, text6, fontId, color4, over = {}) {
|
|
30055
|
+
const font = fontFor(fontId);
|
|
30056
|
+
return {
|
|
30057
|
+
id,
|
|
30058
|
+
type: "text",
|
|
30059
|
+
text: text6,
|
|
30060
|
+
typography: { fontFamily: font.family, fontWeight: font.weight, construction: font.construction, fontSize: 300, strokeWidth: Math.round(300 * font.stroke * 2) / 2, ...over.typography ?? {} },
|
|
30061
|
+
neon: color4,
|
|
30062
|
+
...Object.fromEntries(Object.entries(over).filter(([k]) => k !== "typography"))
|
|
30063
|
+
};
|
|
30064
|
+
}
|
|
30065
|
+
var anim = (o) => o;
|
|
30066
|
+
var NEON_SCENE_PRESETS = [
|
|
30067
|
+
{ id: "cafe-red", name: "CAFE RED", description: "The reference: red outline CAFE on a navy brick wall.", scene: { elements: [textEl("text-1", "CAFE", "oswald", NEON_COLOR_PRESETS.red, { animation: anim({ flickerIntensity: 0.18, flickerFrequency: 0.25, glitchFrequency: 0.1 }) })] } },
|
|
30068
|
+
{ id: "cafe-blue", name: "CAFE BLUE", description: "CAFE in ice blue.", scene: { elements: [textEl("text-1", "CAFE", "oswald", NEON_COLOR_PRESETS.blue)] } },
|
|
30069
|
+
{ id: "cafe-purple", name: "CAFE PURPLE", description: "CAFE in violet.", scene: { elements: [textEl("text-1", "CAFE", "oswald", NEON_COLOR_PRESETS.purple)] } },
|
|
30070
|
+
{ id: "cafe-green", name: "CAFE GREEN", description: "CAFE in acid green.", scene: { elements: [textEl("text-1", "CAFE", "oswald", NEON_COLOR_PRESETS.green)] } },
|
|
30071
|
+
{
|
|
30072
|
+
id: "retro-red",
|
|
30073
|
+
name: "RETRO RED",
|
|
30074
|
+
description: "A 1950s diner sign: Righteous, warm red, a tired transformer.",
|
|
30075
|
+
scene: {
|
|
30076
|
+
elements: [
|
|
30077
|
+
textEl("text-1", "DINER", "righteous", { color: "#FF3040" }, { y: 470, animation: anim({ flickerIntensity: 0.3, flickerFrequency: 0.45, flickerRandomness: 0.8, glitchEnabled: false }) }),
|
|
30078
|
+
{ id: "artwork-1", type: "artwork", artwork: { kind: "library", id: "star" }, size: 170, x: 1370, y: 300, rotation: 12, neon: { color: "#FFC24A" }, animation: { seed: 91 } }
|
|
30079
|
+
],
|
|
30080
|
+
wall: { ambientColor: "#3A1F7A", ambient: 0.45, lightSpill: 1.2 },
|
|
30081
|
+
audio: { hum: 0.55, failure: 0.7 }
|
|
30082
|
+
}
|
|
30083
|
+
},
|
|
30084
|
+
{
|
|
30085
|
+
id: "cyber-blue",
|
|
30086
|
+
name: "CYBER BLUE",
|
|
30087
|
+
description: "Orbitron in electric blue with frequent digital glitches.",
|
|
30088
|
+
scene: {
|
|
30089
|
+
elements: [textEl("text-1", "CYBER", "orbitron", NEON_COLOR_PRESETS.blue, { animation: anim({ flickerIntensity: 0.12, flickerFrequency: 0.15, glitchIntensity: 0.6, glitchFrequency: 0.35, glitchRgbSplit: 1.6 }) })],
|
|
30090
|
+
wall: { ambientColor: "#0D3DD8", ambient: 0.6 },
|
|
30091
|
+
audio: { glitch: 0.5 }
|
|
30092
|
+
}
|
|
30093
|
+
},
|
|
30094
|
+
{
|
|
30095
|
+
id: "purple-night",
|
|
30096
|
+
name: "PURPLE NIGHT",
|
|
30097
|
+
description: "A handwritten \u201Cnight\u201D under a neon moon.",
|
|
30098
|
+
scene: {
|
|
30099
|
+
elements: [
|
|
30100
|
+
textEl("text-1", "night", "sacramento", NEON_COLOR_PRESETS.purple, { y: 540, typography: { fontSize: 420, strokeWidth: 9 } }),
|
|
30101
|
+
{ id: "emoji-1", type: "emoji", emoji: "\u{1F319}", size: 200, x: 1200, y: 290, neon: { color: "#E0B3FF" }, animation: { seed: 7356951 } }
|
|
30102
|
+
],
|
|
30103
|
+
wall: { ambientColor: "#4A1FB8", ambient: 0.5 }
|
|
30104
|
+
}
|
|
30105
|
+
},
|
|
30106
|
+
{
|
|
30107
|
+
id: "electric-green",
|
|
30108
|
+
name: "ELECTRIC GREEN",
|
|
30109
|
+
description: "Audiowide \u201C24/7\u201D with a bolt, both on one circuit.",
|
|
30110
|
+
scene: {
|
|
30111
|
+
elements: [
|
|
30112
|
+
textEl("text-1", "24/7", "audiowide", NEON_COLOR_PRESETS.green, { x: 700, animation: anim({ seed: 4242, flickerIntensity: 0.22, flickerFrequency: 0.3 }) }),
|
|
30113
|
+
{ id: "emoji-1", type: "emoji", emoji: "\u26A1", size: 230, x: 1300, y: 480, neon: { color: "#F4FF3A" }, animation: { seed: 4242, flickerIntensity: 0.22, flickerFrequency: 0.3 } }
|
|
30114
|
+
],
|
|
30115
|
+
wall: { ambientColor: "#123A7A", ambient: 0.5 }
|
|
30116
|
+
}
|
|
30117
|
+
},
|
|
30118
|
+
{
|
|
30119
|
+
id: "warm-white",
|
|
30120
|
+
name: "WARM WHITE",
|
|
30121
|
+
description: "Tilt Neon \u201CHello\u201D in warm white; nearly steady.",
|
|
30122
|
+
scene: {
|
|
30123
|
+
elements: [textEl("text-1", "Hello", "tilt-neon", { color: "#FFE3B8", coreColor: "#FFFFFF", tubeColor: "#FFE3B8", glowColor: "#FFC98A", bloomColor: "#C98A3A" }, { typography: { fontSize: 340, strokeWidth: 12 }, animation: anim({ flickerIntensity: 0.06, flickerFrequency: 0.05, glitchEnabled: false }) })],
|
|
30124
|
+
wall: { ambientColor: "#2A2A6A", ambient: 0.4, lightSpill: 1.3 },
|
|
30125
|
+
audio: { hum: 0.35 }
|
|
30126
|
+
}
|
|
30127
|
+
}
|
|
30128
|
+
];
|
|
30129
|
+
var NEON_NAMED_COLORS = {
|
|
30130
|
+
red: NEON_COLOR_PRESETS.red.color,
|
|
30131
|
+
blue: NEON_COLOR_PRESETS.blue.color,
|
|
30132
|
+
purple: NEON_COLOR_PRESETS.purple.color,
|
|
30133
|
+
green: NEON_COLOR_PRESETS.green.color,
|
|
30134
|
+
pink: "#FF4FB8",
|
|
30135
|
+
magenta: "#FF2BD6",
|
|
30136
|
+
cyan: "#2BF7FF",
|
|
30137
|
+
teal: "#1FFFD0",
|
|
30138
|
+
amber: "#FFB300",
|
|
30139
|
+
orange: "#FF7A1A",
|
|
30140
|
+
yellow: "#F4FF3A",
|
|
30141
|
+
white: "#F2F6FF",
|
|
30142
|
+
"warm white": "#FFE3B8",
|
|
30143
|
+
"cool white": "#DDEBFF",
|
|
30144
|
+
violet: "#9C5BFF",
|
|
30145
|
+
lime: "#B6FF2B"
|
|
30146
|
+
};
|
|
30147
|
+
var NEON_FLICKER_LEVELS = {
|
|
30148
|
+
none: { flickerEnabled: false, flickerIntensity: 0, flickerFrequency: 0 },
|
|
30149
|
+
subtle: { flickerEnabled: true, flickerIntensity: 0.1, flickerFrequency: 0.06 },
|
|
30150
|
+
medium: { flickerEnabled: true, flickerIntensity: 0.2, flickerFrequency: 0.2 },
|
|
30151
|
+
heavy: { flickerEnabled: true, flickerIntensity: 0.3, flickerFrequency: 0.5 },
|
|
30152
|
+
failing: { flickerEnabled: true, flickerIntensity: 0.6, flickerFrequency: 1.2 }
|
|
30153
|
+
};
|
|
30154
|
+
var NEON_GLITCH_LEVELS = {
|
|
30155
|
+
none: { glitchEnabled: false, glitchIntensity: 0, glitchFrequency: 0 },
|
|
30156
|
+
rare: { glitchEnabled: true, glitchIntensity: 0.15, glitchFrequency: 0.03 },
|
|
30157
|
+
occasional: { glitchEnabled: true, glitchIntensity: 0.3, glitchFrequency: 0.1 },
|
|
30158
|
+
frequent: { glitchEnabled: true, glitchIntensity: 0.5, glitchFrequency: 0.35 }
|
|
30159
|
+
};
|
|
30160
|
+
function neonColor(nameOrHex) {
|
|
30161
|
+
if (isHexColor(nameOrHex)) return nameOrHex.toUpperCase();
|
|
30162
|
+
return NEON_NAMED_COLORS[String(nameOrHex).trim().toLowerCase().replace(/[-_]+/g, " ")];
|
|
30163
|
+
}
|
|
30164
|
+
function neonElementFromSpec(spec, index = 0) {
|
|
30165
|
+
const type = spec.type ?? (spec.emoji ? "emoji" : spec.artwork ? "artwork" : "text");
|
|
30166
|
+
const out = { type, id: spec.id ?? `${type}-${index + 1}` };
|
|
30167
|
+
const color4 = spec.color ? neonColor(spec.color) : void 0;
|
|
30168
|
+
if (color4) out.neon = { color: color4 };
|
|
30169
|
+
const flicker = spec.flicker ? NEON_FLICKER_LEVELS[spec.flicker] : void 0;
|
|
30170
|
+
const glitch = spec.glitch ? NEON_GLITCH_LEVELS[spec.glitch] : void 0;
|
|
30171
|
+
if (flicker || glitch) out.animation = { ...flicker, ...glitch };
|
|
30172
|
+
if (type === "text") {
|
|
30173
|
+
out.text = spec.text ?? "OPEN";
|
|
30174
|
+
const font = spec.font ? findNeonFont(spec.font) : void 0;
|
|
30175
|
+
const size = spec.size ?? 300;
|
|
30176
|
+
out.typography = { fontSize: size, ...font ? { fontFamily: font.family, fontWeight: font.weight, construction: font.construction, strokeWidth: Math.max(2, Math.round(size * font.stroke * 2) / 2) } : {} };
|
|
30177
|
+
} else if (type === "emoji") {
|
|
30178
|
+
out.emoji = spec.emoji ?? "\u2B50";
|
|
30179
|
+
out.size = spec.size ?? 240;
|
|
30180
|
+
} else {
|
|
30181
|
+
const art = spec.artwork ?? "heart";
|
|
30182
|
+
out.artwork = NEON_ARTWORK_LIBRARY[art] ? { kind: "library", id: art } : { kind: "svg", svg: art };
|
|
30183
|
+
out.size = spec.size ?? 260;
|
|
30184
|
+
}
|
|
30185
|
+
return out;
|
|
30186
|
+
}
|
|
30187
|
+
|
|
30188
|
+
// tools/ask/neon.ts
|
|
30189
|
+
var NEON_TEMPLATE = "neon-sign";
|
|
30190
|
+
var NEON_RENDER_TOOL = "render_neon_sign";
|
|
30191
|
+
var NEON_FORMATS = ["mp4", "webm", "gif"];
|
|
30192
|
+
var STAGE_WIDTH = 1600;
|
|
30193
|
+
var DEFAULT_FONT = "oswald";
|
|
30194
|
+
var DEFAULT_SEED = 7356951;
|
|
30195
|
+
var COLOR_NAMES = Object.keys(NEON_NAMED_COLORS);
|
|
30196
|
+
var FONT_IDS = NEON_FONTS.map((f2) => f2.id);
|
|
30197
|
+
var FLICKER = Object.keys(NEON_FLICKER_LEVELS);
|
|
30198
|
+
var GLITCH = Object.keys(NEON_GLITCH_LEVELS);
|
|
30199
|
+
var ARTWORK = Object.keys(NEON_ARTWORK_LIBRARY);
|
|
30200
|
+
var TRIGGER_KINDS = ["sag", "flicker", "dip", "failure", "glitch", "normal"];
|
|
30201
|
+
var colorField = (what) => ({ type: "string", description: `${what} A hex colour (#FF4FB8) or a name: ${COLOR_NAMES.join(", ")}.` });
|
|
30202
|
+
var LOOK = {
|
|
30203
|
+
color: colorField("The neon colour; core, tube, glow and bloom derive from it unless given."),
|
|
30204
|
+
tubeColor: colorField("The lit glass."),
|
|
30205
|
+
coreColor: colorField("The hot centre line."),
|
|
30206
|
+
glowColor: colorField("The tight halo around the tube."),
|
|
30207
|
+
bloomColor: colorField("The wide light thrown on the wall."),
|
|
30208
|
+
font: { type: "string", enum: FONT_IDS, description: "A neon font by id." },
|
|
30209
|
+
fontSize: { type: "number", description: "Letter height in stage units (the stage is 1600 wide). Default 300. Signs wider than the stage are scaled to fit." },
|
|
30210
|
+
flicker: { type: "string", enum: FLICKER, description: "How much the tube flickers." },
|
|
30211
|
+
glitch: { type: "string", enum: GLITCH, description: "How often the sign glitches." }
|
|
30212
|
+
};
|
|
30213
|
+
var NEON_INPUT_SCHEMA = {
|
|
30214
|
+
type: "object",
|
|
30215
|
+
properties: {
|
|
30216
|
+
text: { type: "string", description: 'The sign, when it is one piece in one colour ("OPEN"; "\\n" starts a new line). For several pieces or colours use `elements`.' },
|
|
30217
|
+
elements: {
|
|
30218
|
+
type: "array",
|
|
30219
|
+
description: 'The pieces of the sign, in reading order. Each has its own colour, font and animation, so "EG in pink, LAB in green" is two text elements. Pieces without x/y are placed side by side (see `layout`, `gap`) and centred. The scene contains exactly these and nothing else.',
|
|
30220
|
+
items: {
|
|
30221
|
+
type: "object",
|
|
30222
|
+
properties: {
|
|
30223
|
+
type: { type: "string", enum: ["text", "emoji", "artwork"], description: "Default: text, or emoji / artwork when that field is given." },
|
|
30224
|
+
text: { type: "string", description: "The letters of a text element." },
|
|
30225
|
+
emoji: { type: "string", description: "One emoji, drawn as a neon outline." },
|
|
30226
|
+
artwork: { type: "string", description: `A library shape (${ARTWORK.join(", ")}) or SVG markup.` },
|
|
30227
|
+
...LOOK,
|
|
30228
|
+
size: { type: "number", description: "Emoji or artwork size in stage units." },
|
|
30229
|
+
x: { type: "number", description: "Centre x in stage units (0\u20261600). Omit to place automatically." },
|
|
30230
|
+
y: { type: "number", description: "Centre y in stage units. Omit to place automatically." },
|
|
30231
|
+
scale: { type: "number" },
|
|
30232
|
+
rotation: { type: "number", description: "Degrees, clockwise." },
|
|
30233
|
+
opacity: { type: "number", description: "0\u20261." },
|
|
30234
|
+
id: { type: "string", description: "A name for `triggers[].target`." },
|
|
30235
|
+
neon: { type: "object", description: "Advanced material overrides: intensity (0\u20263), bloomIntensity, tubeBrightness, coreBrightness, glowRadius, bloomRadius, coreWidth, glassOpacity, shadow { enabled, color, opacity, offsetX, offsetY, blur }." },
|
|
30236
|
+
animation: { type: "object", description: "Advanced animation overrides: speed, seed, flickerEnabled, flickerIntensity (0\u20261), flickerFrequency (events per second), flickerSpeed, flickerRandomness, flickerRecovery, flickerMinBrightness, glitchEnabled, glitchIntensity (0\u20261), glitchFrequency, glitchDuration (ms), glitchDisplacement, glitchRgbSplit, glitchSlices, glitchGlowSeparation." },
|
|
30237
|
+
typography: { type: "object", description: "Advanced letter overrides: letterSpacing, lineHeight, strokeWidth, fontWeight, fontStyle (normal | italic), construction (outline | monoline), align (start | middle | end)." }
|
|
30238
|
+
}
|
|
30239
|
+
}
|
|
30240
|
+
},
|
|
30241
|
+
...LOOK,
|
|
30242
|
+
layout: { type: "string", enum: ["row", "column"], description: "How automatically placed elements are arranged. Default row." },
|
|
30243
|
+
gap: { type: "number", description: "Space between neighbouring elements in stage units. Default 0 in a row: adjacent text elements sit a letter apart and read as one word; about 0.3 \xD7 fontSize is a word space." },
|
|
30244
|
+
sync: { type: "boolean", description: "true (default): every element flickers together, as one sign on one circuit. false: each flickers on its own." },
|
|
30245
|
+
seed: { type: "number", description: "Same seed, same performance. Change it for a different, equally natural one." },
|
|
30246
|
+
background: {
|
|
30247
|
+
type: "object",
|
|
30248
|
+
description: "What is behind the sign. Default: a dark navy brick wall lit by the neon.",
|
|
30249
|
+
properties: {
|
|
30250
|
+
type: { type: "string", enum: ["brick-wall", "none"], description: "none: a flat `color` and no wall." },
|
|
30251
|
+
color: { type: "string", description: "Hex. The flat background when type is none. Default #000000." },
|
|
30252
|
+
brickColor: { type: "string", description: "Hex. Darker reads as a darker wall." },
|
|
30253
|
+
mortarColor: { type: "string", description: "Hex." },
|
|
30254
|
+
ambientColor: { type: "string", description: "Hex. The room's own light on the wall." },
|
|
30255
|
+
ambient: { type: "number", description: "0\u20261. Lower is a darker room." },
|
|
30256
|
+
lightSpill: { type: "number", description: "0\u20262. How strongly the neon lights the wall." },
|
|
30257
|
+
spillRadius: { type: "number", description: "How far that light reaches, stage units." },
|
|
30258
|
+
vignette: { type: "number", description: "0\u20261. Edge darkening." },
|
|
30259
|
+
texture: { type: "number", description: "0\u20261. Surface grain." },
|
|
30260
|
+
variation: { type: "number", description: "0\u20261. Brick-to-brick shade variation." },
|
|
30261
|
+
brickWidth: { type: "number" },
|
|
30262
|
+
brickHeight: { type: "number" },
|
|
30263
|
+
mortar: { type: "number" },
|
|
30264
|
+
seed: { type: "number" }
|
|
30265
|
+
}
|
|
30266
|
+
},
|
|
30267
|
+
triggers: {
|
|
30268
|
+
type: "array",
|
|
30269
|
+
description: "Scripted moments on top of the natural flicker.",
|
|
30270
|
+
items: {
|
|
30271
|
+
type: "object",
|
|
30272
|
+
properties: {
|
|
30273
|
+
kind: { type: "string", enum: TRIGGER_KINDS },
|
|
30274
|
+
at: { type: "number", description: "Seconds from the start." },
|
|
30275
|
+
target: { type: "string", description: "An element id; omit for every element." }
|
|
30276
|
+
},
|
|
30277
|
+
required: ["kind", "at"]
|
|
30278
|
+
}
|
|
30279
|
+
},
|
|
30280
|
+
duration: { type: "number", description: "Seconds, 0.5\u202660. Default 6." },
|
|
30281
|
+
fps: { type: "number", description: "Frames per second, 1\u202660. Default 30." },
|
|
30282
|
+
format: { type: "string", enum: [...NEON_FORMATS], description: "Default mp4 (H.264)." },
|
|
30283
|
+
width: { type: "number", description: "Output width in pixels, 320\u20263840. Default 1920." },
|
|
30284
|
+
height: { type: "number", description: "Output height in pixels, 180\u20262160. Default 1080." }
|
|
30285
|
+
}
|
|
30286
|
+
};
|
|
30287
|
+
var TOP_KEYS = Object.keys(NEON_INPUT_SCHEMA.properties);
|
|
30288
|
+
var ELEMENT_KEYS = Object.keys(NEON_INPUT_SCHEMA.properties.elements.items.properties);
|
|
30289
|
+
var BACKGROUND_KEYS = Object.keys(NEON_INPUT_SCHEMA.properties.background.properties);
|
|
30290
|
+
var WALL_NUMBERS = ["ambient", "lightSpill", "spillRadius", "vignette", "texture", "variation", "brickWidth", "brickHeight", "mortar", "seed"];
|
|
30291
|
+
var WALL_COLORS = ["brickColor", "mortarColor", "ambientColor"];
|
|
30292
|
+
var COLOR_KEYS2 = ["color", "tubeColor", "coreColor", "glowColor", "bloomColor"];
|
|
30293
|
+
var isObj7 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
30294
|
+
var even = (n) => Math.max(2, Math.round(n / 2) * 2);
|
|
30295
|
+
var r32 = (v) => Math.round(v * 1e3) / 1e3;
|
|
30296
|
+
function elementInput(spec, defaults, i, at, sync, seed, errors) {
|
|
30297
|
+
const before = errors.length;
|
|
30298
|
+
const bad = (key, message) => errors.push({ path: `${at}.${key}`, message });
|
|
30299
|
+
for (const k of Object.keys(spec)) if (!ELEMENT_KEYS.includes(k)) bad(k, `Unknown key. An element takes: ${ELEMENT_KEYS.join(", ")}.`);
|
|
30300
|
+
const type = spec.type ?? (spec.emoji !== void 0 ? "emoji" : spec.artwork !== void 0 ? "artwork" : "text");
|
|
30301
|
+
if (!["text", "emoji", "artwork"].includes(type)) {
|
|
30302
|
+
bad("type", "Must be text, emoji or artwork.");
|
|
30303
|
+
return null;
|
|
30304
|
+
}
|
|
30305
|
+
const content = spec[type];
|
|
30306
|
+
if (typeof content !== "string" || !content.trim()) bad(type, type === "text" ? "A text element needs its text." : type === "emoji" ? "An emoji element needs its emoji." : `An artwork element needs a library shape (${ARTWORK.join(", ")}) or SVG markup.`);
|
|
30307
|
+
const pick = (k) => spec[k] ?? defaults[k];
|
|
30308
|
+
const colors = {};
|
|
30309
|
+
for (const k of COLOR_KEYS2) {
|
|
30310
|
+
const v = pick(k);
|
|
30311
|
+
if (v === void 0) continue;
|
|
30312
|
+
const hex = typeof v === "string" ? neonColor(v) : void 0;
|
|
30313
|
+
if (hex) colors[k] = hex;
|
|
30314
|
+
else errors.push({ path: `${spec[k] !== void 0 ? `${at}.` : ""}${k}`, message: `Must be a hex colour like #FF4FB8 or one of: ${COLOR_NAMES.join(", ")}.` });
|
|
30315
|
+
}
|
|
30316
|
+
const fontQuery = pick("font") ?? DEFAULT_FONT;
|
|
30317
|
+
const font = typeof fontQuery === "string" ? findNeonFont(fontQuery) : void 0;
|
|
30318
|
+
if (!font) errors.push({ path: `${spec.font !== void 0 ? `${at}.` : ""}font`, message: `Must be one of: ${FONT_IDS.join(", ")}.` });
|
|
30319
|
+
const flicker = pick("flicker");
|
|
30320
|
+
if (flicker !== void 0 && !FLICKER.includes(flicker)) errors.push({ path: `${spec.flicker !== void 0 ? `${at}.` : ""}flicker`, message: `Must be one of: ${FLICKER.join(", ")}.` });
|
|
30321
|
+
const glitch = pick("glitch");
|
|
30322
|
+
if (glitch !== void 0 && !GLITCH.includes(glitch)) errors.push({ path: `${spec.glitch !== void 0 ? `${at}.` : ""}glitch`, message: `Must be one of: ${GLITCH.join(", ")}.` });
|
|
30323
|
+
for (const k of ["size", "x", "y", "scale", "rotation", "opacity"]) if (spec[k] !== void 0 && (typeof spec[k] !== "number" || !Number.isFinite(spec[k]))) bad(k, "Must be a number.");
|
|
30324
|
+
const fontSize = pick("fontSize");
|
|
30325
|
+
if (fontSize !== void 0 && (typeof fontSize !== "number" || !Number.isFinite(fontSize))) errors.push({ path: `${spec.fontSize !== void 0 ? `${at}.` : ""}fontSize`, message: "Must be a number." });
|
|
30326
|
+
for (const k of ["neon", "animation", "typography"]) if (spec[k] !== void 0 && !isObj7(spec[k])) bad(k, "Must be an object.");
|
|
30327
|
+
if (spec.id !== void 0 && (typeof spec.id !== "string" || !spec.id)) bad("id", "Must be a non-empty id.");
|
|
30328
|
+
if (errors.length > before) return null;
|
|
30329
|
+
const base4 = neonElementFromSpec({
|
|
30330
|
+
type,
|
|
30331
|
+
[type]: content,
|
|
30332
|
+
id: spec.id,
|
|
30333
|
+
font: font.id,
|
|
30334
|
+
flicker,
|
|
30335
|
+
glitch,
|
|
30336
|
+
size: type === "text" ? fontSize : spec.size
|
|
30337
|
+
}, i);
|
|
30338
|
+
const out = { ...base4 };
|
|
30339
|
+
out.neon = { ...colors, ...spec.neon };
|
|
30340
|
+
out.animation = { ...base4.animation, ...sync ? { seed } : {}, ...spec.animation };
|
|
30341
|
+
if (type === "text") out.typography = { ...base4.typography, ...spec.typography };
|
|
30342
|
+
for (const k of ["x", "y", "scale", "rotation", "opacity"]) if (spec[k] !== void 0) out[k] = spec[k];
|
|
30343
|
+
return out;
|
|
30344
|
+
}
|
|
30345
|
+
function arrange(scene, auto, column, gap, measure) {
|
|
30346
|
+
const placed = scene.elements.filter((e) => auto.has(e.id));
|
|
30347
|
+
if (!placed.length) return scene;
|
|
30348
|
+
const { width: W, height: H } = scene.stage;
|
|
30349
|
+
const boxes = placed.map((e) => elementGeometry({ ...e, x: 0, y: 0 }, measure).bounds);
|
|
30350
|
+
const em = Math.max(...placed.map((e) => (e.type === "text" ? e.typography.fontSize : e.size) * e.scale));
|
|
30351
|
+
const g = gap ?? (column ? em * 0.12 : 0);
|
|
30352
|
+
const along = (b) => column ? b.height : b.width;
|
|
30353
|
+
const across = (b) => column ? b.width : b.height;
|
|
30354
|
+
const total = boxes.reduce((s, b) => s + along(b), 0) + g * (boxes.length - 1);
|
|
30355
|
+
const room = column ? H * 0.8 : W * 0.86;
|
|
30356
|
+
const roomAcross = column ? W * 0.86 : H * 0.72;
|
|
30357
|
+
const k = Math.min(1, room / Math.max(1, total), roomAcross / Math.max(1, ...boxes.map(across)));
|
|
30358
|
+
let cursor = ((column ? H : W) - total * k) / 2;
|
|
30359
|
+
const at = /* @__PURE__ */ new Map();
|
|
30360
|
+
placed.forEach((e, n) => {
|
|
30361
|
+
const centre = cursor + along(boxes[n]) * k / 2;
|
|
30362
|
+
cursor += (along(boxes[n]) + g) * k;
|
|
30363
|
+
at.set(e.id, column ? { x: W / 2, y: r32(centre), scale: r32(e.scale * k) } : { x: r32(centre), y: H / 2, scale: r32(e.scale * k) });
|
|
30364
|
+
});
|
|
30365
|
+
return { ...scene, elements: scene.elements.map((e) => at.has(e.id) ? { ...e, ...at.get(e.id) } : e) };
|
|
30366
|
+
}
|
|
30367
|
+
function buildNeonScene(content, measure = estimateTextWidth) {
|
|
30368
|
+
if (!isObj7(content)) return { valid: false, errors: [{ path: "", message: "Must be an object with `text` or `elements`." }] };
|
|
30369
|
+
const errors = [];
|
|
30370
|
+
for (const k of Object.keys(content)) if (!TOP_KEYS.includes(k)) errors.push({ path: k, message: `Unknown key. A neon sign takes: ${TOP_KEYS.join(", ")}.` });
|
|
30371
|
+
const specs = [];
|
|
30372
|
+
if (content.text !== void 0 && typeof content.text !== "string") errors.push({ path: "text", message: "Must be text." });
|
|
30373
|
+
else if (typeof content.text === "string" && content.text.trim()) specs.push({ spec: { text: content.text }, at: "text" });
|
|
30374
|
+
if (content.elements !== void 0 && !Array.isArray(content.elements)) errors.push({ path: "elements", message: "Must be a list." });
|
|
30375
|
+
else content.elements?.forEach((spec, i) => specs.push({ spec, at: `elements.${i}` }));
|
|
30376
|
+
if (!specs.length && !errors.length) errors.push({ path: "elements", message: "Nothing to draw: give `text`, or `elements` with at least one piece." });
|
|
30377
|
+
const num10 = (k, lo, hi, fallback2) => {
|
|
30378
|
+
const v = content[k];
|
|
30379
|
+
if (v === void 0) return fallback2;
|
|
30380
|
+
if (typeof v !== "number" || !Number.isFinite(v) || v < lo || v > hi) {
|
|
30381
|
+
errors.push({ path: k, message: `Must be a number between ${lo} and ${hi}.` });
|
|
30382
|
+
return fallback2;
|
|
30383
|
+
}
|
|
30384
|
+
return v;
|
|
30385
|
+
};
|
|
30386
|
+
const durationSeconds = num10("duration", 0.5, 60, 6);
|
|
30387
|
+
const fps = Math.round(num10("fps", 1, 60, 30));
|
|
30388
|
+
const width = even(num10("width", 320, 3840, 1920));
|
|
30389
|
+
const height = even(num10("height", 180, 2160, 1080));
|
|
30390
|
+
const seed = num10("seed", 0, 4294967295, DEFAULT_SEED);
|
|
30391
|
+
const gap = content.gap === void 0 ? void 0 : num10("gap", -2e3, 4e3, 0);
|
|
30392
|
+
const format = content.format ?? "mp4";
|
|
30393
|
+
if (!NEON_FORMATS.includes(format)) errors.push({ path: "format", message: `Must be one of: ${NEON_FORMATS.join(", ")}.` });
|
|
30394
|
+
if (content.layout !== void 0 && content.layout !== "row" && content.layout !== "column") errors.push({ path: "layout", message: "Must be row or column." });
|
|
30395
|
+
if (content.sync !== void 0 && typeof content.sync !== "boolean") errors.push({ path: "sync", message: "Must be true or false." });
|
|
30396
|
+
const wall = {};
|
|
30397
|
+
let backgroundColor = "#000000";
|
|
30398
|
+
const bg = content.background;
|
|
30399
|
+
if (bg !== void 0 && !isObj7(bg)) errors.push({ path: "background", message: "Must be an object." });
|
|
30400
|
+
else if (isObj7(bg)) {
|
|
30401
|
+
for (const k of Object.keys(bg)) if (!BACKGROUND_KEYS.includes(k)) errors.push({ path: `background.${k}`, message: `Unknown key. A background takes: ${BACKGROUND_KEYS.join(", ")}.` });
|
|
30402
|
+
if (bg.type !== void 0 && bg.type !== "brick-wall" && bg.type !== "none") errors.push({ path: "background.type", message: "Must be brick-wall or none." });
|
|
30403
|
+
wall.enabled = bg.type !== "none";
|
|
30404
|
+
if (bg.color !== void 0) {
|
|
30405
|
+
if (isHexColor(bg.color)) backgroundColor = bg.color;
|
|
30406
|
+
else errors.push({ path: "background.color", message: "Must be a hex colour." });
|
|
30407
|
+
}
|
|
30408
|
+
for (const k of [...WALL_COLORS, ...WALL_NUMBERS]) if (bg[k] !== void 0) wall[k] = bg[k];
|
|
30409
|
+
}
|
|
30410
|
+
const triggers = [];
|
|
30411
|
+
if (content.triggers !== void 0 && !Array.isArray(content.triggers)) errors.push({ path: "triggers", message: "Must be a list." });
|
|
30412
|
+
else content.triggers?.forEach((t, i) => {
|
|
30413
|
+
if (!isObj7(t) || !TRIGGER_KINDS.includes(t.kind) || typeof t.at !== "number" || t.at < 0 || t.at > durationSeconds) errors.push({ path: `triggers.${i}`, message: `Needs kind (${TRIGGER_KINDS.join(", ")}) and at (seconds, within the duration).` });
|
|
30414
|
+
else triggers.push({ kind: t.kind, at: Math.round(t.at * 1e3), ...typeof t.target === "string" ? { target: t.target } : {} });
|
|
30415
|
+
});
|
|
30416
|
+
const auto = /* @__PURE__ */ new Set();
|
|
30417
|
+
const inputs = [];
|
|
30418
|
+
specs.forEach(({ spec, at }, i) => {
|
|
30419
|
+
if (!isObj7(spec)) {
|
|
30420
|
+
errors.push({ path: at, message: "An element must be an object." });
|
|
30421
|
+
return;
|
|
30422
|
+
}
|
|
30423
|
+
const input = elementInput(spec, content, i, at, content.sync !== false, seed, errors);
|
|
30424
|
+
if (!input) return;
|
|
30425
|
+
if (spec.x === void 0 && spec.y === void 0) auto.add(String(input.id));
|
|
30426
|
+
inputs.push(input);
|
|
30427
|
+
});
|
|
30428
|
+
if (errors.length) return { valid: false, errors };
|
|
30429
|
+
const stage2 = { width: STAGE_WIDTH, height: Math.round(STAGE_WIDTH * height / width) };
|
|
30430
|
+
let scene = resolveNeonScene({ stage: stage2, elements: inputs, wall, timeline: { duration: Math.round(durationSeconds * 1e3), loop: false, triggers } });
|
|
30431
|
+
scene = arrange(scene, auto, content.layout === "column", gap, measure);
|
|
30432
|
+
const sceneErrors = validateNeonScene(scene).map((e) => ({ path: e.path.replace(/^wall\./, "background."), message: e.message }));
|
|
30433
|
+
if (sceneErrors.length) return { valid: false, errors: sceneErrors };
|
|
30434
|
+
return { valid: true, errors: [], scene, backgroundColor, output: { format, width, height, durationSeconds, fps, frameCount: Math.max(1, Math.round(durationSeconds * fps)) } };
|
|
30435
|
+
}
|
|
30436
|
+
var neonSceneSummary = (scene) => scene.elements.map((e) => ({
|
|
30437
|
+
id: e.id,
|
|
30438
|
+
type: e.type,
|
|
30439
|
+
content: e.type === "text" ? e.text : e.type === "emoji" ? e.emoji : e.artwork.kind === "library" ? e.artwork.id : "svg",
|
|
30440
|
+
color: e.neon.color,
|
|
30441
|
+
x: e.x,
|
|
30442
|
+
y: e.y,
|
|
30443
|
+
scale: e.scale
|
|
30444
|
+
}));
|
|
30445
|
+
var EXAMPLE = {
|
|
30446
|
+
elements: [{ text: "HELLO", color: "cyan" }, { artwork: "heart", color: "pink" }],
|
|
30447
|
+
font: "tilt-neon",
|
|
30448
|
+
flicker: "medium",
|
|
30449
|
+
glitch: "rare",
|
|
30450
|
+
gap: 60,
|
|
30451
|
+
background: { type: "brick-wall" },
|
|
30452
|
+
duration: 6,
|
|
30453
|
+
fps: 30,
|
|
30454
|
+
format: "mp4"
|
|
30455
|
+
};
|
|
30456
|
+
function listNeonAnimation() {
|
|
30457
|
+
return {
|
|
30458
|
+
template: NEON_TEMPLATE,
|
|
30459
|
+
version: 1,
|
|
30460
|
+
title: "Neon sign",
|
|
30461
|
+
description: `A neon sign on a brick wall: any text, emoji or artwork, each piece with its own colour and font, with natural flicker and glitches. Renders to a video file with ${NEON_RENDER_TOOL}.`,
|
|
30462
|
+
canvas: { width: 1920, height: 1080 },
|
|
30463
|
+
kind: "neonScene",
|
|
30464
|
+
renderer: NEON_RENDER_TOOL,
|
|
30465
|
+
seconds: 6,
|
|
30466
|
+
slots: 0,
|
|
30467
|
+
formats: ["MP4 (H.264)", "WebM (VP9)", "GIF", "JSON (motion/1)"]
|
|
30468
|
+
};
|
|
30469
|
+
}
|
|
30470
|
+
function describeNeonAnimation() {
|
|
30471
|
+
return {
|
|
30472
|
+
template: NEON_TEMPLATE,
|
|
30473
|
+
version: 1,
|
|
30474
|
+
kind: "neonScene",
|
|
30475
|
+
title: "Neon sign",
|
|
30476
|
+
description: listNeonAnimation().description,
|
|
30477
|
+
render: { tool: NEON_RENDER_TOOL, arguments: "The `content` below, plus an optional outputPath. Returns the video file's path." },
|
|
30478
|
+
request: { schema: "motion/1", template: NEON_TEMPLATE, content: "<object: see inputSchema>" },
|
|
30479
|
+
output: { width: 1920, height: 1080, fps: 30, seconds: 6, formats: [...NEON_FORMATS] },
|
|
30480
|
+
rules: [
|
|
30481
|
+
"The scene contains exactly the elements you send. Nothing is added.",
|
|
30482
|
+
"One colour per element: text in two colours is two text elements, in reading order.",
|
|
30483
|
+
"Elements without x/y are placed side by side and centred, a letter apart, so neighbouring text reads as one word; set `gap` to separate them.",
|
|
30484
|
+
"Sizes and positions are stage units on a stage 1600 wide, whatever the output resolution.",
|
|
30485
|
+
"Unknown keys, colours, fonts and levels are refused, never guessed."
|
|
30486
|
+
],
|
|
30487
|
+
inputSchema: NEON_INPUT_SCHEMA,
|
|
30488
|
+
colors: NEON_NAMED_COLORS,
|
|
30489
|
+
fonts: NEON_FONTS.map((f2) => ({ id: f2.id, name: f2.name, category: f2.category })),
|
|
30490
|
+
flicker: FLICKER,
|
|
30491
|
+
glitch: GLITCH,
|
|
30492
|
+
artwork: ARTWORK,
|
|
30493
|
+
example: { schema: "motion/1", template: NEON_TEMPLATE, content: EXAMPLE }
|
|
30494
|
+
};
|
|
30495
|
+
}
|
|
30496
|
+
function validateNeonRequest(request) {
|
|
30497
|
+
if (!isObj7(request) || request.schema !== "motion/1" || request.template !== NEON_TEMPLATE) {
|
|
30498
|
+
return { valid: false, errors: [{ path: "", message: `Must be { schema: "motion/1", template: "${NEON_TEMPLATE}", content }.` }] };
|
|
30499
|
+
}
|
|
30500
|
+
const b = buildNeonScene(request.content);
|
|
30501
|
+
if (!b.valid) return { valid: false, errors: b.errors };
|
|
30502
|
+
return { valid: true, seconds: b.output.durationSeconds, output: b.output, elements: neonSceneSummary(b.scene), request };
|
|
30503
|
+
}
|
|
30504
|
+
|
|
29432
30505
|
// tools/ask/components.ts
|
|
29433
30506
|
function listComponents() {
|
|
29434
30507
|
return [
|
|
@@ -29455,17 +30528,25 @@ function checkConfig(name, preset, config) {
|
|
|
29455
30528
|
}
|
|
29456
30529
|
export {
|
|
29457
30530
|
AGENT_COMPONENTS,
|
|
30531
|
+
NEON_INPUT_SCHEMA,
|
|
30532
|
+
NEON_RENDER_TOOL,
|
|
30533
|
+
NEON_TEMPLATE,
|
|
29458
30534
|
agentAvailability,
|
|
30535
|
+
buildNeonScene,
|
|
29459
30536
|
checkConfig,
|
|
29460
30537
|
describeComponent,
|
|
29461
30538
|
describeMotionTemplate,
|
|
30539
|
+
describeNeonAnimation,
|
|
29462
30540
|
isAgentComponent,
|
|
29463
30541
|
isComponent,
|
|
29464
30542
|
listAgentComponents,
|
|
29465
30543
|
listComponents,
|
|
29466
30544
|
listMotionTemplates,
|
|
30545
|
+
listNeonAnimation,
|
|
29467
30546
|
motionPreviewUrl,
|
|
30547
|
+
neonSceneSummary,
|
|
29468
30548
|
searchComponents,
|
|
29469
30549
|
validateMotionRequest,
|
|
30550
|
+
validateNeonRequest,
|
|
29470
30551
|
validateRenderRequest
|
|
29471
30552
|
};
|