@pixerate/flair 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +33 -6
  3. package/dist/catalog/index.d.ts +3 -3
  4. package/dist/catalog/index.js +6 -2
  5. package/dist/{chunk-SHHAIWUR.js → chunk-LKQM3GSQ.js} +12 -12
  6. package/dist/{chunk-KWNFZ5NC.js → chunk-LURJMWYM.js} +237 -185
  7. package/dist/{chunk-KWNFZ5NC.js.map → chunk-LURJMWYM.js.map} +1 -1
  8. package/dist/{chunk-DVVQRZJ5.js → chunk-MILF5MRS.js} +11 -2
  9. package/dist/chunk-MILF5MRS.js.map +1 -0
  10. package/dist/{chunk-YLJWC4N2.js → chunk-PMVJP5I4.js} +2 -2
  11. package/dist/{chunk-4RGHZWQC.js → chunk-RM3I6WZH.js} +2 -2
  12. package/dist/{chunk-4RGHZWQC.js.map → chunk-RM3I6WZH.js.map} +1 -1
  13. package/dist/{chunk-FIJOMGPG.js → chunk-UP6QQVRS.js} +2 -2
  14. package/dist/{chunk-WC3VADZC.js → chunk-VYOMXE6I.js} +46 -4
  15. package/dist/chunk-VYOMXE6I.js.map +1 -0
  16. package/dist/cli.js +7 -7
  17. package/dist/{controls-Syr6drzO.d.ts → controls-Ec6P0Vpy.d.ts} +9 -2
  18. package/dist/editor/index.d.ts +3 -3
  19. package/dist/editor/index.js +7 -7
  20. package/dist/index.d.ts +4 -4
  21. package/dist/index.js +80 -76
  22. package/dist/index.js.map +1 -1
  23. package/dist/node.d.ts +1 -1
  24. package/dist/node.js +2 -2
  25. package/dist/react/index.d.ts +14 -3
  26. package/dist/react/index.js +18 -9
  27. package/dist/react/index.js.map +1 -1
  28. package/dist/recorder-HKFFHQES.js +10 -0
  29. package/dist/{renderer-SNYZu__R.d.ts → renderer-Bm0oQ7Bw.d.ts} +1 -1
  30. package/dist/svelte/index.d.ts +146 -0
  31. package/dist/svelte/index.js +195 -0
  32. package/dist/svelte/index.js.map +1 -0
  33. package/dist/{types-9J6NWaN3.d.ts → types-C7g8-lTX.d.ts} +9 -3
  34. package/dist/vector/index.d.ts +27 -1
  35. package/dist/vector/index.js +4 -2
  36. package/package.json +13 -3
  37. package/dist/chunk-DVVQRZJ5.js.map +0 -1
  38. package/dist/chunk-WC3VADZC.js.map +0 -1
  39. package/dist/recorder-KEX3U35D.js +0 -10
  40. /package/dist/{chunk-SHHAIWUR.js.map → chunk-LKQM3GSQ.js.map} +0 -0
  41. /package/dist/{chunk-YLJWC4N2.js.map → chunk-PMVJP5I4.js.map} +0 -0
  42. /package/dist/{chunk-FIJOMGPG.js.map → chunk-UP6QQVRS.js.map} +0 -0
  43. /package/dist/{recorder-KEX3U35D.js.map → recorder-HKFFHQES.js.map} +0 -0
@@ -12,7 +12,7 @@ import {
12
12
  hexToRgb,
13
13
  parseControls,
14
14
  rgbToHex
15
- } from "./chunk-WC3VADZC.js";
15
+ } from "./chunk-VYOMXE6I.js";
16
16
 
17
17
  // src/catalog/glsl.ts
18
18
  var HASH = `
@@ -1748,9 +1748,9 @@ var checkFlash = (at2, back) => [
1748
1748
  { id: "check", step: 1, attrs: { opacity: [1, 0] }, delay: back, ms: 160 },
1749
1749
  { id: "check", step: 1, draw: [0, 0], delay: back + 200, ms: 0 }
1750
1750
  ];
1751
- var buttonLabels = (a, b, room, max) => {
1751
+ var buttonLabels = (a, b, room2, max) => {
1752
1752
  const ems = [a, b].map((t) => Math.max(0.6, textWidthEm(String(t))));
1753
- const size = Math.min(max, room / Math.max(...ems));
1753
+ const size = Math.min(max, room2 / Math.max(...ems));
1754
1754
  return { size: r1(size), base: r1(50 + size * 0.35), labelLen: r1(ems[0] * size), doneLen: r1(ems[1] * size) };
1755
1755
  };
1756
1756
  var labelTexts = (x) => `<text id="label" x="${x}" y="{{base}}" textLength="{{labelLen}}" lengthAdjust="spacingAndGlyphs" transform="translate(0 0)" font-family="${SANS}" font-size="{{size}}" font-weight="600" fill="{{ink}}">{{label}}</text><text id="done" x="${x}" y="{{base}}" textLength="{{doneLen}}" lengthAdjust="spacingAndGlyphs" transform="translate(0 10)" opacity="0" font-family="${SANS}" font-size="{{size}}" font-weight="600" fill="{{ink}}">{{doneText}}</text>`;
@@ -9265,6 +9265,138 @@ var imageTreatments = [
9265
9265
  imageOilPaint
9266
9266
  ];
9267
9267
 
9268
+ // src/catalog/types.ts
9269
+ var EFFECT_CATEGORIES = [
9270
+ "gradient",
9271
+ "organic",
9272
+ "pattern",
9273
+ "light",
9274
+ "space",
9275
+ "retro",
9276
+ "image",
9277
+ "overlay",
9278
+ "text",
9279
+ "morph",
9280
+ "interaction",
9281
+ "pyrotechnics",
9282
+ "timewarp",
9283
+ "melt",
9284
+ "electric",
9285
+ "transition",
9286
+ "loader",
9287
+ "data",
9288
+ "icon"
9289
+ ];
9290
+ var VECTOR_CONTROL_TYPES = ["float", "int", "bool", "color", "text"];
9291
+ var PLACEHOLDER = /\{\{\s*([A-Za-z_]\w*)\s*(?::\s*([A-Za-z0-9]*)([^}]*))?\}\}/g;
9292
+ function motionStrings(motion) {
9293
+ const out = [];
9294
+ const walk = (v) => {
9295
+ if (typeof v === "string") out.push(v);
9296
+ else if (Array.isArray(v)) v.forEach(walk);
9297
+ else if (v && typeof v === "object") Object.values(v).forEach(walk);
9298
+ };
9299
+ walk(motion);
9300
+ return out;
9301
+ }
9302
+ function vectorControls(motion) {
9303
+ const controls = [];
9304
+ const seen = /* @__PURE__ */ new Set();
9305
+ for (const text of motionStrings(motion)) {
9306
+ for (const [, name, type, attributes] of text.matchAll(PLACEHOLDER)) {
9307
+ if (type === void 0 || seen.has(name) || !VECTOR_CONTROL_TYPES.includes(type)) continue;
9308
+ seen.add(name);
9309
+ const colour = type === "color";
9310
+ controls.push(controlFromDeclaration(name, colour ? "vec3" : type, `${attributes}${colour ? " color" : ""}`, controls.length + 1));
9311
+ }
9312
+ }
9313
+ return controls;
9314
+ }
9315
+ function vectorEffectControls(effect) {
9316
+ const declared = vectorControls(effect.motion);
9317
+ const names = new Set(declared.map((c) => c.name));
9318
+ const listed = (effect.controls ?? []).filter((c) => !names.has(c.name));
9319
+ return [...declared, ...listed.map((c, i) => vectorControlDef(c, declared.length + i + 1))];
9320
+ }
9321
+ function vectorControlIssues(motion) {
9322
+ const issues = [];
9323
+ const declarations = /* @__PURE__ */ new Map();
9324
+ for (const text of motionStrings(motion)) {
9325
+ for (const [whole, name, type] of text.matchAll(PLACEHOLDER)) {
9326
+ if (type === void 0) continue;
9327
+ if (!VECTOR_CONTROL_TYPES.includes(type))
9328
+ issues.push(`${whole}: a control's type is one of ${VECTOR_CONTROL_TYPES.join(", ")}`);
9329
+ else if (declarations.has(name) && declarations.get(name) !== whole)
9330
+ issues.push(`"${name}" is declared twice; declare it once and write {{${name}}} everywhere else`);
9331
+ else declarations.set(name, whole);
9332
+ }
9333
+ }
9334
+ return issues;
9335
+ }
9336
+ function written(control, value) {
9337
+ if (control.color && Array.isArray(value)) return rgbToHex(value);
9338
+ if (Array.isArray(value)) return value.join(" ");
9339
+ return value;
9340
+ }
9341
+ var escapeMarkup = (text) => text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
9342
+ function vectorControlDef(control, line = 0) {
9343
+ const min = control.min ?? 0;
9344
+ return {
9345
+ name: control.name,
9346
+ type: control.type,
9347
+ label: control.label ?? control.name,
9348
+ color: control.color ?? false,
9349
+ min,
9350
+ max: control.max ?? Math.max(min + 1, 1),
9351
+ step: control.step ?? (control.type === "int" ? 1 : 0.01),
9352
+ default: control.default,
9353
+ line
9354
+ };
9355
+ }
9356
+ var CURRENT_COLOR = "currentColor";
9357
+ function isCurrentColor(value) {
9358
+ return typeof value === "string" && value.trim().toLowerCase() === "currentcolor";
9359
+ }
9360
+ function renderVectorEffect(effect, values = {}) {
9361
+ const filled = {};
9362
+ const placeholders = /* @__PURE__ */ new Map();
9363
+ for (const control of vectorEffectControls(effect)) {
9364
+ const given = values[control.name];
9365
+ if (control.color && isCurrentColor(given)) {
9366
+ filled[control.name] = CURRENT_COLOR;
9367
+ placeholders.set(control.name, CURRENT_COLOR);
9368
+ continue;
9369
+ }
9370
+ const check = given === void 0 ? void 0 : checkControlValue(control, given);
9371
+ filled[control.name] = check?.ok ? check.value : control.default;
9372
+ placeholders.set(control.name, written(control, filled[control.name]));
9373
+ }
9374
+ for (const [name, value] of Object.entries(effect.derived?.(filled) ?? {})) placeholders.set(name, value);
9375
+ const fill = (text, markup) => {
9376
+ const whole = /^\{\{\s*([A-Za-z_]\w*)\s*(?::[^}]*)?\}\}$/.exec(text);
9377
+ if (!markup && whole && placeholders.has(whole[1])) return placeholders.get(whole[1]);
9378
+ return text.replace(PLACEHOLDER, (match, name) => {
9379
+ if (!placeholders.has(name)) return match;
9380
+ const value = String(placeholders.get(name));
9381
+ return markup ? escapeMarkup(value) : value;
9382
+ });
9383
+ };
9384
+ const walk = (v) => {
9385
+ if (typeof v === "string") return fill(v, false);
9386
+ if (Array.isArray(v)) return v.map(walk);
9387
+ if (v && typeof v === "object") return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, walk(x)]));
9388
+ return v;
9389
+ };
9390
+ const { svg, ...rest } = effect.motion;
9391
+ return { svg: fill(svg.replace(/<!--[\s\S]*?-->/g, ""), true), ...walk(rest) };
9392
+ }
9393
+ function colorControl(name, hex, label) {
9394
+ return { name, type: "vec3", color: true, default: hexToRgb(hex) ?? [1, 1, 1], label: label ?? name };
9395
+ }
9396
+ function numberControl(name, value, min, max, label, step) {
9397
+ return { name, type: "float", default: value, min, max, label: label ?? name, step };
9398
+ }
9399
+
9268
9400
  // src/catalog/text.ts
9269
9401
  var r14 = (n5) => Math.round(n5 * 10) / 10;
9270
9402
  function fit2(text, width, max, ems = textWidthEm(text)) {
@@ -9274,6 +9406,10 @@ function fit2(text, width, max, ems = textWidthEm(text)) {
9274
9406
  }
9275
9407
  var lettersOf = (text) => Array.from(String(text));
9276
9408
  var mixHex2 = (a, b, t) => {
9409
+ if (isCurrentColor(a) || isCurrentColor(b)) {
9410
+ const css = (c) => isCurrentColor(c) ? CURRENT_COLOR : rgbToHex(Array.isArray(c) ? c : [1, 1, 1]);
9411
+ return `color-mix(in srgb, ${css(a)}, ${css(b)} ${Math.round(t * 100)}%)`;
9412
+ }
9277
9413
  const x = Array.isArray(a) ? a : [1, 1, 1];
9278
9414
  const y = Array.isArray(b) ? b : [1, 1, 1];
9279
9415
  return rgbToHex([0, 1, 2].map((i) => x[i] + (y[i] - x[i]) * t));
@@ -9284,6 +9420,16 @@ var MONO = `ui-monospace, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospa
9284
9420
  var MONO_EM = 0.6;
9285
9421
  var HEAVY = `'Arial Black', 'Helvetica Neue', Impact, system-ui, sans-serif`;
9286
9422
  var SANS3 = `system-ui, 'Helvetica Neue', Arial, sans-serif`;
9423
+ var SCRIPT = `'Pacifico', 'Brush Script MT', 'Segoe Script', 'Snell Roundhand', cursive`;
9424
+ var CAP = 0.72;
9425
+ var typeface = (font, weight) => `{{font: text default="${font}" label='Font'}} {{weight: int min=100 max=900 default=${weight} step=100 label='Weight'}} {{inline: bool default=false label='Inline'}}`;
9426
+ function frame(v, left, width, base, size, overhang = [0.05, 0, DESCENT, 0]) {
9427
+ const spill = overhang.map((e) => Math.round(Math.max(0, e) * 100) / 100).join(" ");
9428
+ return v.inline ? { viewBox: `${r14(left)} ${r14(base - size * CAP)} ${r14(width)} ${r14(size * CAP)}`, overflow: "visible", backdrop: 0, overhang: spill } : { viewBox: "0 0 400 200", overflow: "hidden", backdrop: 1, overhang: spill };
9429
+ }
9430
+ var DESCENT = 0.25;
9431
+ var glowing = (overhang, by) => overhang.map((e) => e + by);
9432
+ var room = (v, width) => v.inline ? Infinity : width;
9287
9433
  var BLINKS = 7;
9288
9434
  var typewriter = {
9289
9435
  kind: "vector",
@@ -9300,20 +9446,22 @@ var typewriter = {
9300
9446
  // Monospace, so the caret lands after the last letter however long the text is.
9301
9447
  derived: (v) => {
9302
9448
  const count = Math.max(1, lettersOf(v.text).length);
9303
- const size = r14(Math.min(44, 340 / ((count + 1) * MONO_EM)));
9449
+ const size = r14(Math.min(44, room(v, 340) / ((count + 1) * MONO_EM)));
9304
9450
  const left = r14(200 - (count + 1) * MONO_EM * size / 2);
9305
9451
  const typing = Math.round(count * Number(v.pace));
9452
+ const base = r14(100 + size * 0.35);
9306
9453
  return {
9454
+ ...frame(v, left, (count + 1) * MONO_EM * size, base, size, [0.08, 0, DESCENT, 0]),
9307
9455
  size,
9308
9456
  left,
9309
- base: r14(100 + size * 0.35),
9457
+ base,
9310
9458
  end: r14(left + count * MONO_EM * size),
9311
9459
  typing,
9312
9460
  ...Object.fromEntries(Array.from({ length: BLINKS }, (_, k) => [`blink${k}`, typing + k * 500]))
9313
9461
  };
9314
9462
  },
9315
9463
  motion: {
9316
- svg: `<svg viewBox="0 0 400 200"><!-- {{text: text default='Hello, world.' label='Text'}} {{paper: color default=#111827 label='Background'}} {{ink: color default=#F9FAFB label='Text colour'}} {{pace: float min=30 max=300 default=90 step=5 label='Ms per letter'}} --><rect width="400" height="200" fill="{{paper}}"/><text id="typed" x="{{left}}" y="{{base}}" font-family="${MONO}" font-size="{{size}}" fill="{{ink}}" xml:space="preserve">{{text}}</text><text id="caret" x="{{end}}" y="{{base}}" font-family="${MONO}" font-size="{{size}}" fill="{{ink}}" opacity="0">\u258C</text></svg>`,
9464
+ svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='Hello, world.' label='Text'}} {{paper: color default=#111827 label='Background'}} {{ink: color default=#F9FAFB label='Text colour'}} {{pace: float min=30 max=300 default=90 step=5 label='Ms per letter'}} ${typeface(MONO, 400)} --><rect width="400" height="200" fill="{{paper}}" opacity="{{backdrop}}"/><text id="typed" x="{{left}}" y="{{base}}" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" fill="{{ink}}" xml:space="preserve">{{text}}</text><text id="caret" x="{{end}}" y="{{base}}" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" fill="{{ink}}" opacity="0">\u258C</text></svg>`,
9317
9465
  animate: [
9318
9466
  { id: "typed", write: "{{text}}", caret: "\u258C", ms: n4("typing"), ease: "linear" },
9319
9467
  // Hidden while the writer shows its own caret, then blinking where it stopped.
@@ -9335,13 +9483,13 @@ var scrambleText = {
9335
9483
  ],
9336
9484
  derived: (v) => {
9337
9485
  const count = Math.max(1, lettersOf(v.text).length);
9338
- const size = r14(Math.min(60, 360 / (count * MONO_EM)));
9486
+ const size = r14(Math.min(60, room(v, 360) / (count * MONO_EM)));
9339
9487
  const width = r14(count * MONO_EM * size);
9340
9488
  const base = r14(100 + size * 0.35);
9341
- return { size, base, barX: r14(200 - width / 2), barY: r14(base + size * 0.3), barW: width, decode: Math.round(count * Number(v.pace)), glowWide: Number(v.glow) * 2 };
9489
+ return { size, base, barX: r14(200 - width / 2), barY: r14(base + size * 0.3), barW: width, decode: Math.round(count * Number(v.pace)), glowWide: Number(v.glow) * 2, ...frame(v, 200 - width / 2, width, base, size, glowing([0.08, 0, Math.max(DESCENT, 0.3 + 3 / size), 0], Number(v.glow) * 4 / size)) };
9342
9490
  },
9343
9491
  motion: {
9344
- svg: `<svg viewBox="0 0 400 200"><!-- {{text: text default='ACCESS GRANTED' label='Text'}} {{glyphs: text default='ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#%&@$*+' label='Scramble glyphs'}} {{background: color default=#05070D label='Background'}} {{ink: color default=#E2E8F0 label='Text colour'}} {{accent: color default=#22D3EE label='Accent'}} {{pace: float min=20 max=250 default=70 step=5 label='Ms per letter'}} {{glow: float min=0 max=6 default=2 step=0.5 label='Glow'}} --><defs><filter id="glow" x="-20%" y="-50%" width="140%" height="200%"><feGaussianBlur stdDeviation="{{glowWide}}" result="blur"/><feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge></filter></defs><rect width="400" height="200" fill="{{background}}"/><text id="word" x="200" y="{{base}}" text-anchor="middle" font-family="${MONO}" font-size="{{size}}" font-weight="700" fill="{{ink}}" filter="url(#glow)" xml:space="preserve">{{text}}</text><rect id="bar" x="{{barX}}" y="{{barY}}" width="0" height="3" fill="{{accent}}"/></svg>`,
9492
+ svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='ACCESS GRANTED' label='Text'}} {{glyphs: text default='ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#%&@$*+' label='Scramble glyphs'}} {{background: color default=#05070D label='Background'}} {{ink: color default=#E2E8F0 label='Text colour'}} {{accent: color default=#22D3EE label='Accent'}} {{pace: float min=20 max=250 default=70 step=5 label='Ms per letter'}} {{glow: float min=0 max=6 default=2 step=0.5 label='Glow'}} ${typeface(MONO, 700)} --><defs><filter id="glow" x="-20%" y="-50%" width="140%" height="200%"><feGaussianBlur stdDeviation="{{glowWide}}" result="blur"/><feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge></filter></defs><rect width="400" height="200" fill="{{background}}" opacity="{{backdrop}}"/><text id="word" x="200" y="{{base}}" text-anchor="middle" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" fill="{{ink}}" filter="url(#glow)" xml:space="preserve">{{text}}</text><rect id="bar" x="{{barX}}" y="{{barY}}" width="0" height="3" fill="{{accent}}"/></svg>`,
9345
9493
  animate: [
9346
9494
  // Pure noise for a moment (the scramble runs before the change starts), then it decodes.
9347
9495
  { id: "word", write: "{{text}}", scramble: "{{glyphs}}", delay: 500, ms: n4("decode"), ease: "linear" },
@@ -9367,10 +9515,11 @@ var kineticStagger = {
9367
9515
  const letters = lettersOf(v.text).slice(0, SLOTS2);
9368
9516
  const widths = letters.map((c) => textWidthEm(c));
9369
9517
  const ems = Math.max(0.6, widths.reduce((a, b) => a + b, 0));
9370
- const size = Math.min(92, 370 / ems);
9518
+ const size = Math.min(92, room(v, 370) / ems);
9371
9519
  const base = 100 + size * 0.35;
9372
9520
  const stagger = Number(v.stagger);
9373
9521
  const out = {
9522
+ ...frame(v, 200 - ems * size / 2, ems * size, base, size, [0.15, 0, Math.max(DESCENT, 0.2 + 6 / size), 0]),
9374
9523
  size: r14(size),
9375
9524
  base: r14(base),
9376
9525
  rise: r14(base - size * 0.1),
@@ -9392,9 +9541,9 @@ var kineticStagger = {
9392
9541
  return out;
9393
9542
  },
9394
9543
  motion: {
9395
- svg: `<svg viewBox="0 0 400 200"><!-- {{text: text default='Kinetic' label='Text'}} {{background: color default=#F8FAFC label='Background'}} {{ink: color default=#0F172A label='Text colour'}} {{accent: color default=#F43F5E label='Underline'}} {{stagger: float min=20 max=200 default=55 step=5 label='Ms between letters'}} --><rect width="400" height="200" fill="{{background}}"/>${Array.from(
9544
+ svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='Kinetic' label='Text'}} {{background: color default=#F8FAFC label='Background'}} {{ink: color default=#0F172A label='Text colour'}} {{accent: color default=#F43F5E label='Underline'}} {{stagger: float min=20 max=200 default=55 step=5 label='Ms between letters'}} ${typeface(HEAVY, 900)} --><rect width="400" height="200" fill="{{background}}" opacity="{{backdrop}}"/>${Array.from(
9396
9545
  { length: SLOTS2 },
9397
- (_, i) => `<text id="l${i}" text-anchor="middle" transform="translate({{x${i}}} {{drop}}) rotate(-28) scale(0.3)" opacity="0" font-family="${HEAVY}" font-size="{{size}}" font-weight="900" fill="{{ink}}">{{c${i}}}</text>`
9546
+ (_, i) => `<text id="l${i}" text-anchor="middle" transform="translate({{x${i}}} {{drop}}) rotate(-28) scale(0.3)" opacity="0" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" fill="{{ink}}">{{c${i}}}</text>`
9398
9547
  ).join("")}<rect id="line" x="{{lineX}}" y="{{lineY}}" width="0" height="6" rx="3" fill="{{accent}}"/></svg>`,
9399
9548
  animate: [
9400
9549
  ...Array.from({ length: SLOTS2 }, (_, i) => [
@@ -9428,8 +9577,10 @@ var neonSign = {
9428
9577
  derived: (v) => {
9429
9578
  const letters = lettersOf(v.text);
9430
9579
  const k = Math.min(Math.max(0, Math.round(Number(v.faulty)) - 1), Math.max(0, letters.length - 1));
9431
- const { size, length } = fit2(String(v.text), 340, 84);
9580
+ const { size, length, left } = fit2(String(v.text), room(v, 340), 84);
9432
9581
  return {
9582
+ ...frame(v, left, length, 100 + size * 0.3, size, glowing([0.1, 0, DESCENT, 0], Number(v.glow) * 6 / size)),
9583
+ hazeX: r14(Math.max(170, length / 2 + 30)),
9433
9584
  head: letters.slice(0, k).join(""),
9434
9585
  mid: letters[k] ?? "",
9435
9586
  tail: letters.slice(k + 1).join(""),
@@ -9441,12 +9592,12 @@ var neonSign = {
9441
9592
  };
9442
9593
  },
9443
9594
  motion: (() => {
9444
- const word = `x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="'Pacifico', 'Brush Script MT', 'Segoe Script', 'Snell Roundhand', cursive" font-size="{{size}}"`;
9595
+ const word = `x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}"`;
9445
9596
  const runs = (id) => `<tspan>{{head}}</tspan><tspan id="${id}">{{mid}}</tspan><tspan>{{tail}}</tspan>`;
9446
9597
  const startUp = [[0, 0], [150, 0.85], [210, 0], [330, 1], [390, 0.15], [450, 1], [620, 0.35], [680, 1]];
9447
9598
  const buzz = [[0, 1], [1300, 0.15], [1350, 1], [1420, 0.2], [1520, 1], [2100, 0.1], [2160, 1], [2230, 0.25], [2280, 1]];
9448
9599
  return {
9449
- svg: `<svg viewBox="0 0 400 200"><!-- {{text: text default='Open late' label='Text'}} {{tube: color default=#FF3DA5 label='Tube'}} {{wall: color default=#120612 label='Wall'}} {{glow: float min=0.5 max=8 default=3.5 step=0.5 label='Glow'}} {{faulty: int min=1 max=24 default=6 label='Buzzing letter'}} --><defs><filter id="neon" x="-30%" y="-80%" width="160%" height="260%"><feGaussianBlur in="SourceGraphic" stdDeviation="{{glow}}" result="near"/><feGaussianBlur in="SourceGraphic" stdDeviation="{{far}}" result="far"/><feMerge><feMergeNode in="far"/><feMergeNode in="far"/><feMergeNode in="near"/><feMergeNode in="SourceGraphic"/></feMerge></filter><filter id="haze" x="-50%" y="-100%" width="200%" height="300%"><feGaussianBlur stdDeviation="30"/></filter></defs><rect width="400" height="200" fill="{{wall}}"/><g id="sign" opacity="0"><ellipse cx="200" cy="100" rx="170" ry="55" fill="{{tube}}" opacity="0.18" filter="url(#haze)"/><text ${word} fill="{{tube}}" stroke="{{tube}}" stroke-width="1.5" filter="url(#neon)">${runs("buzzGlow")}</text><text ${word} fill="{{core}}">${runs("buzzCore")}</text></g></svg>`,
9600
+ svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='Open late' label='Text'}} {{tube: color default=#FF3DA5 label='Tube'}} {{wall: color default=#120612 label='Wall'}} {{glow: float min=0.5 max=8 default=3.5 step=0.5 label='Glow'}} {{faulty: int min=1 max=24 default=6 label='Buzzing letter'}} ${typeface(SCRIPT, 400)} --><defs><filter id="neon" x="-30%" y="-80%" width="160%" height="260%"><feGaussianBlur in="SourceGraphic" stdDeviation="{{glow}}" result="near"/><feGaussianBlur in="SourceGraphic" stdDeviation="{{far}}" result="far"/><feMerge><feMergeNode in="far"/><feMergeNode in="far"/><feMergeNode in="near"/><feMergeNode in="SourceGraphic"/></feMerge></filter><filter id="haze" x="-50%" y="-100%" width="200%" height="300%"><feGaussianBlur stdDeviation="30"/></filter></defs><rect width="400" height="200" fill="{{wall}}" opacity="{{backdrop}}"/><g id="sign" opacity="0"><ellipse cx="200" cy="100" rx="{{hazeX}}" ry="55" fill="{{tube}}" opacity="0.18" filter="url(#haze)"/><text ${word} fill="{{tube}}" stroke="{{tube}}" stroke-width="1.5" filter="url(#neon)">${runs("buzzGlow")}</text><text ${word} fill="{{core}}">${runs("buzzCore")}</text></g></svg>`,
9450
9601
  animate: [
9451
9602
  ...holds("sign", "opacity", startUp),
9452
9603
  // fill-opacity rather than opacity: browsers ignore opacity on a <tspan>.
@@ -9469,11 +9620,12 @@ var strokeDrawText = {
9469
9620
  ],
9470
9621
  // <text> takes no pathLength, so `draw` cannot measure it: the dash grows instead, longer than any letter's outline.
9471
9622
  derived: (v) => {
9472
- const { size, length } = fit2(String(v.text), 360, 100);
9473
- return { size, length, base: r14(100 + size * 0.35), dash: Math.round(size * 6) };
9623
+ const { size, length, left } = fit2(String(v.text), room(v, 360), 100);
9624
+ const edge = Number(v.width) / 2 / size;
9625
+ return { size, length, base: r14(100 + size * 0.35), dash: Math.round(size * 6), ...frame(v, left, length, 100 + size * 0.35, size, glowing([0.05, 0, DESCENT, 0], edge)) };
9474
9626
  },
9475
9627
  motion: {
9476
- svg: `<svg viewBox="0 0 400 200"><!-- {{text: text default='Draw' label='Text'}} {{background: color default=#FFFBF5 label='Background'}} {{outline: color default=#1E293B label='Outline'}} {{fill: color default=#F97316 label='Fill'}} {{width: float min=0.5 max=6 default=2 step=0.5 label='Outline width'}} {{fillOpacity: float min=0 max=1 default=1 label='Fill opacity'}} --><rect width="400" height="200" fill="{{background}}"/><text id="word" x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="${HEAVY}" font-size="{{size}}" font-weight="900" fill="{{fill}}" fill-opacity="0" stroke="{{outline}}" stroke-width="{{width}}" stroke-linejoin="round" stroke-dasharray="0 {{dash}}">{{text}}</text></svg>`,
9628
+ svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='Draw' label='Text'}} {{background: color default=#FFFBF5 label='Background'}} {{outline: color default=#1E293B label='Outline'}} {{fill: color default=#F97316 label='Fill'}} {{width: float min=0.5 max=6 default=2 step=0.5 label='Outline width'}} {{fillOpacity: float min=0 max=1 default=1 label='Fill opacity'}} ${typeface(HEAVY, 900)} --><rect width="400" height="200" fill="{{background}}" opacity="{{backdrop}}"/><text id="word" x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" fill="{{fill}}" fill-opacity="0" stroke="{{outline}}" stroke-width="{{width}}" stroke-linejoin="round" stroke-dasharray="0 {{dash}}">{{text}}</text></svg>`,
9477
9629
  animate: [
9478
9630
  { id: "word", attrs: { "stroke-dasharray": ["0 {{dash}}", "{{dash}} 0"] }, ms: 2e3, ease: "in-out" },
9479
9631
  { id: "word", attrs: { "fill-opacity": [0, n4("fillOpacity")] }, delay: 1500, ms: 700, ease: "out" }
@@ -9493,8 +9645,9 @@ var splitReveal = {
9493
9645
  { name: "Gold on black", values: { background: [0.02, 0.02, 0.02], ink: [0.96, 0.84, 0.55], line: [0.96, 0.84, 0.55] } }
9494
9646
  ],
9495
9647
  derived: (v) => {
9496
- const { size, length, left } = fit2(String(v.text), 360, 88);
9648
+ const { size, length, left } = fit2(String(v.text), room(v, 360), 88);
9497
9649
  return {
9650
+ ...frame(v, left, length, 100 + size * 0.36, size, [0.05, 10 / size, Math.max(DESCENT, 34 / size), 10 / size]),
9498
9651
  size,
9499
9652
  length,
9500
9653
  base: r14(100 + size * 0.36),
@@ -9505,9 +9658,9 @@ var splitReveal = {
9505
9658
  };
9506
9659
  },
9507
9660
  motion: (() => {
9508
- const word = `<text x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="${SANS3}" font-size="{{size}}" font-weight="800" fill="{{ink}}">{{text}}</text>`;
9661
+ const word = `<text x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" fill="{{ink}}">{{text}}</text>`;
9509
9662
  return {
9510
- svg: `<svg viewBox="0 0 400 200"><!-- {{text: text default='Chapter One' label='Text'}} {{caption: text default='The beginning' label='Caption'}} {{background: color default=#0F172A label='Background'}} {{ink: color default=#F8FAFC label='Text colour'}} {{line: color default=#38BDF8 label='Line'}} --><defs><clipPath id="above"><rect width="400" height="100"/></clipPath><clipPath id="below"><rect y="100" width="400" height="100"/></clipPath></defs><rect width="400" height="200" fill="{{background}}"/><g clip-path="url(#above)"><g id="top" transform="translate(0 {{shift}})">${word}</g></g><g clip-path="url(#below)"><g id="bottom" transform="translate(0 -{{shift}})">${word}</g></g><path id="line" d="M{{lineX1}} 100 H{{lineX2}}" stroke="{{line}}" stroke-width="2.5" stroke-linecap="round"/><text id="caption" x="200" y="{{captionY}}" text-anchor="middle" font-family="${SANS3}" font-size="13" letter-spacing="3" fill="{{ink}}" opacity="0">{{caption}}</text></svg>`,
9663
+ svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='Chapter One' label='Text'}} {{caption: text default='The beginning' label='Caption'}} {{background: color default=#0F172A label='Background'}} {{ink: color default=#F8FAFC label='Text colour'}} {{line: color default=#38BDF8 label='Line'}} ${typeface(SANS3, 800)} --><defs><clipPath id="above"><rect x="-5000" y="-5000" width="10400" height="5100"/></clipPath><clipPath id="below"><rect x="-5000" y="100" width="10400" height="5100"/></clipPath></defs><rect width="400" height="200" fill="{{background}}" opacity="{{backdrop}}"/><g clip-path="url(#above)"><g id="top" transform="translate(0 {{shift}})">${word}</g></g><g clip-path="url(#below)"><g id="bottom" transform="translate(0 -{{shift}})">${word}</g></g><path id="line" d="M{{lineX1}} 100 H{{lineX2}}" stroke="{{line}}" stroke-width="2.5" stroke-linecap="round"/><text id="caption" x="200" y="{{captionY}}" text-anchor="middle" font-family="{{font}}" font-size="13" letter-spacing="3" fill="{{ink}}" opacity="0">{{caption}}</text></svg>`,
9511
9664
  animate: [
9512
9665
  { id: "line", draw: true, ms: 600, ease: "in-out" },
9513
9666
  { id: "top", attrs: { transform: ["translate(0 {{shift}})", "translate(0 0)"] }, delay: 450, ms: 800, ease: "out" },
@@ -9533,12 +9686,13 @@ var highlighter = {
9533
9686
  ],
9534
9687
  // The swipe runs a little past the phrase at both ends, across its lower half, with a ragged second pass.
9535
9688
  derived: (v) => {
9536
- const { size, length, left } = fit2(String(v.text), 330, 56);
9689
+ const { size, length, left } = fit2(String(v.text), room(v, 330), 56);
9537
9690
  const base = 100 + size * 0.35;
9538
9691
  const x0 = left - size * 0.25;
9539
9692
  const x1 = 400 - left + size * 0.2;
9540
9693
  const y = base - size * 0.3;
9541
9694
  return {
9695
+ ...frame(v, left, length, base, size, [0.05, 0.2, DESCENT, 0.25]),
9542
9696
  size,
9543
9697
  length,
9544
9698
  base: r14(base),
@@ -9549,7 +9703,7 @@ var highlighter = {
9549
9703
  };
9550
9704
  },
9551
9705
  motion: {
9552
- svg: `<svg viewBox="0 0 400 200"><!-- {{text: text default='Ship it faster' label='Phrase'}} {{background: color default=#FFFFFF label='Background'}} {{ink: color default=#111827 label='Text colour'}} {{marker: color default=#FDE047 label='Marker'}} --><rect width="400" height="200" fill="{{background}}"/><g transform="rotate(-1.2 200 100)" opacity="0.9"><path id="swipe" d="{{swipe}}" fill="none" stroke="{{marker}}" stroke-width="{{thick}}"/><path id="ragged" d="{{ragged}}" fill="none" stroke="{{marker}}" stroke-width="{{thin}}" stroke-linecap="round"/></g><text id="phrase" x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="${SANS3}" font-size="{{size}}" font-weight="700" fill="{{ink}}" opacity="0">{{text}}</text></svg>`,
9706
+ svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='Ship it faster' label='Phrase'}} {{background: color default=#FFFFFF label='Background'}} {{ink: color default=#111827 label='Text colour'}} {{marker: color default=#FDE047 label='Marker'}} ${typeface(SANS3, 700)} --><rect width="400" height="200" fill="{{background}}" opacity="{{backdrop}}"/><g transform="rotate(-1.2 200 100)" opacity="0.9"><path id="swipe" d="{{swipe}}" fill="none" stroke="{{marker}}" stroke-width="{{thick}}"/><path id="ragged" d="{{ragged}}" fill="none" stroke="{{marker}}" stroke-width="{{thin}}" stroke-linecap="round"/></g><text id="phrase" x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" fill="{{ink}}" opacity="0">{{text}}</text></svg>`,
9553
9707
  animate: [
9554
9708
  { id: "phrase", attrs: { opacity: [0, 1] }, ms: 300, ease: "out" },
9555
9709
  { id: "swipe", draw: true, delay: 350, ms: 650, ease: "in-out" },
@@ -9578,12 +9732,14 @@ var wordCycle = {
9578
9732
  const lead = textWidthEm(String(v.prefix));
9579
9733
  const ems = words.map((w) => Math.max(0.6, textWidthEm(w)));
9580
9734
  const total = lead + 0.3 + Math.max(...ems);
9581
- const size = Math.min(52, 370 / total);
9735
+ const size = Math.min(52, room(v, 370) / total);
9582
9736
  const left = 200 - total * size / 2;
9583
9737
  const base = 100 + size * 0.35;
9584
9738
  const step = size * 1.3;
9585
9739
  const wx = left + (lead + 0.3) * size;
9586
9740
  return {
9741
+ ...frame(v, left, total * size, base, size, [0.08, 0, Math.max(DESCENT, 0.18 + 4 / size), 0]),
9742
+ bold: Math.min(900, Number(v.weight) + 100),
9587
9743
  size: r14(size),
9588
9744
  base: r14(base),
9589
9745
  px: r14(left),
@@ -9605,9 +9761,9 @@ var wordCycle = {
9605
9761
  };
9606
9762
  },
9607
9763
  motion: {
9608
- svg: `<svg viewBox="0 0 400 200"><!-- {{prefix: text default='We build' label='Lead-in'}} {{word1: text default=apps label='Word 1'}} {{word2: text default=brands label='Word 2'}} {{word3: text default=websites label='Word 3'}} {{word4: text default=games label='Word 4'}} {{background: color default=#FFFFFF label='Background'}} {{ink: color default=#0F172A label='Text colour'}} {{accent: color default=#6366F1 label='Accent'}} --><defs><clipPath id="window"><rect x="{{clipX}}" y="{{clipY}}" width="{{clipW}}" height="{{clipH}}"/></clipPath></defs><rect width="400" height="200" fill="{{background}}"/><text x="{{px}}" y="{{base}}" textLength="{{plen}}" lengthAdjust="spacingAndGlyphs" font-family="${SANS3}" font-size="{{size}}" font-weight="700" fill="{{ink}}">{{prefix}}</text><g clip-path="url(#window)"><g id="reel" transform="translate(0 -0)">${Array.from(
9764
+ svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{prefix: text default='We build' label='Lead-in'}} {{word1: text default=apps label='Word 1'}} {{word2: text default=brands label='Word 2'}} {{word3: text default=websites label='Word 3'}} {{word4: text default=games label='Word 4'}} {{background: color default=#FFFFFF label='Background'}} {{ink: color default=#0F172A label='Text colour'}} {{accent: color default=#6366F1 label='Accent'}} ${typeface(SANS3, 700)} --><defs><clipPath id="window"><rect x="{{clipX}}" y="{{clipY}}" width="{{clipW}}" height="{{clipH}}"/></clipPath></defs><rect width="400" height="200" fill="{{background}}" opacity="{{backdrop}}"/><text x="{{px}}" y="{{base}}" textLength="{{plen}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" fill="{{ink}}">{{prefix}}</text><g clip-path="url(#window)"><g id="reel" transform="translate(0 -0)">${Array.from(
9609
9765
  { length: CYCLE_WORDS + 1 },
9610
- (_, k) => `<text x="{{wx}}" y="{{y${k}}}" textLength="{{len${k}}}" lengthAdjust="spacingAndGlyphs" font-family="${SANS3}" font-size="{{size}}" font-weight="800" fill="{{accent}}">{{w${k % CYCLE_WORDS}}}</text>`
9766
+ (_, k) => `<text x="{{wx}}" y="{{y${k}}}" textLength="{{len${k}}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="{{size}}" font-weight="{{bold}}" fill="{{accent}}">{{w${k % CYCLE_WORDS}}}</text>`
9611
9767
  ).join("")}</g></g><rect id="under" x="{{wx}}" y="{{underY}}" width="{{len0}}" height="4" rx="2" fill="{{accent}}"/></svg>`,
9612
9768
  animate: Array.from({ length: CYCLE_WORDS }, (_, i) => {
9613
9769
  const at2 = { delay: (i + 1) * SHOW_MS + i * ROLL_MS, ms: ROLL_MS, ease: "in-out" };
@@ -9618,7 +9774,7 @@ var wordCycle = {
9618
9774
  }).flat()
9619
9775
  }
9620
9776
  };
9621
- var WAVE = `M-200 0${" q25 -8 50 0 t50 0".repeat(8)} V320 H-200Z`;
9777
+ var WAVE = `M-2000 0${" q25 -8 50 0 t50 0".repeat(46)} V320 H-2000Z`;
9622
9778
  var liquidFill = {
9623
9779
  kind: "vector",
9624
9780
  id: "liquid-fill",
@@ -9634,14 +9790,14 @@ var liquidFill = {
9634
9790
  ],
9635
9791
  // The surface starts under the letters and ends above their caps.
9636
9792
  derived: (v) => {
9637
- const { size, length } = fit2(String(v.text), 360, 110);
9793
+ const { size, length, left } = fit2(String(v.text), room(v, 360), 110);
9638
9794
  const base = 100 + size * 0.36;
9639
- return { size, length, base: r14(base), low: r14(base + 12), high: r14(base - size * 0.82 - 12) };
9795
+ return { ...frame(v, left, length, base, size), size, length, base: r14(base), low: r14(base + 12), high: r14(base - size * 0.82 - 12) };
9640
9796
  },
9641
9797
  motion: (() => {
9642
- const word = `x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="${HEAVY}" font-size="{{size}}" font-weight="900"`;
9798
+ const word = `x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}"`;
9643
9799
  return {
9644
- svg: `<svg viewBox="0 0 400 200"><!-- {{text: text default='FILL' label='Text'}} {{background: color default=#FFFFFF label='Background'}} {{empty: color default=#E0F2FE label='Empty'}} {{liquid: color default=#0284C7 label='Liquid'}} {{liquid2: color default=#38BDF8 label='Liquid sweep'}} --><defs><clipPath id="letters"><text ${word}>{{text}}</text></clipPath><linearGradient id="liq" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="400" y2="0" spreadMethod="repeat"><stop offset="0" stop-color="{{liquid}}"/><stop offset="0.5" stop-color="{{liquid2}}"/><stop offset="1" stop-color="{{liquid}}"/></linearGradient></defs><rect width="400" height="200" fill="{{background}}"/><text ${word} fill="{{empty}}" stroke="{{liquid}}" stroke-width="1.5" stroke-opacity="0.5">{{text}}</text><g clip-path="url(#letters)"><g id="level" transform="translate(0 {{low}})"><path id="back" d="${WAVE}" fill="{{liquid}}" opacity="0.45" transform="translate(-50 -5)"/><path id="wave" d="${WAVE}" fill="url(#liq)" transform="translate(0 0)"/></g></g></svg>`,
9800
+ svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='FILL' label='Text'}} {{background: color default=#FFFFFF label='Background'}} {{empty: color default=#E0F2FE label='Empty'}} {{liquid: color default=#0284C7 label='Liquid'}} {{liquid2: color default=#38BDF8 label='Liquid sweep'}} ${typeface(HEAVY, 900)} --><defs><clipPath id="letters"><text ${word}>{{text}}</text></clipPath><linearGradient id="liq" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="400" y2="0" spreadMethod="repeat"><stop offset="0" stop-color="{{liquid}}"/><stop offset="0.5" stop-color="{{liquid2}}"/><stop offset="1" stop-color="{{liquid}}"/></linearGradient></defs><rect width="400" height="200" fill="{{background}}" opacity="{{backdrop}}"/><text ${word} fill="{{empty}}" stroke="{{liquid}}" stroke-width="1.5" stroke-opacity="0.5">{{text}}</text><g clip-path="url(#letters)"><g id="level" transform="translate(0 {{low}})"><path id="back" d="${WAVE}" fill="{{liquid}}" opacity="0.45" transform="translate(-50 -5)"/><path id="wave" d="${WAVE}" fill="url(#liq)" transform="translate(0 0)"/></g></g></svg>`,
9645
9801
  animate: [
9646
9802
  { id: "level", attrs: { transform: ["translate(0 {{low}})", "translate(0 {{high}})"] }, ms: 2800, ease: "out" },
9647
9803
  { id: "wave", attrs: { transform: ["translate(0 0)", "translate(-200 0)"] }, ms: 3e3, ease: "linear" },
@@ -9683,13 +9839,15 @@ var glitchText = {
9683
9839
  { name: "Vapor", values: { left: [1, 0.3, 0.85], right: [0.4, 0.6, 1], background: [0.08, 0.02, 0.12], amount: 1.4 } }
9684
9840
  ],
9685
9841
  derived: (v) => {
9686
- const { size, length } = fit2(String(v.text), 340, 100);
9842
+ const { size, length, left } = fit2(String(v.text), room(v, 340), 100);
9843
+ const a = Number(v.amount);
9687
9844
  const base = 100 + size * 0.36;
9688
9845
  const top = base - size * 0.78;
9689
- const cuts = [0, ...BANDS.map((f) => r14(top + f * (base - top))), 200];
9846
+ const cuts = [-1e3, ...BANDS.map((f) => r14(top + f * (base - top))), 1200];
9690
9847
  const unit = size * Number(v.amount);
9691
9848
  const shift = ([x, y]) => `translate(${r14(x * unit)} ${r14(y * unit)})`;
9692
9849
  return {
9850
+ ...frame(v, left, length, base, size, [0.05 + 0.04 * a, 0.25 * a, DESCENT + 0.04 * a, 0.25 * a]),
9693
9851
  size,
9694
9852
  length,
9695
9853
  base: r14(base),
@@ -9704,10 +9862,10 @@ var glitchText = {
9704
9862
  };
9705
9863
  },
9706
9864
  motion: (() => {
9707
- const word = (paint) => `<text x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="${HEAVY}" font-size="{{size}}" font-weight="900" ${paint}>{{text}}</text>`;
9865
+ const word = (paint) => `<text x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" ${paint}>{{text}}</text>`;
9708
9866
  const jumps = (id, value) => GLITCH.map((key, k) => ({ id, attrs: { transform: [value(k), value(k)] }, delay: key.at, ms: 0 }));
9709
9867
  return {
9710
- svg: `<svg viewBox="0 0 400 200"><!-- {{text: text default='GLITCH' label='Text'}} {{background: color default=#07070B label='Background'}} {{ink: color default=#F5F5F5 label='Text colour'}} {{left: color default=#FF0040 label='Red copy'}} {{right: color default=#00F0FF label='Cyan copy'}} {{amount: float min=0 max=3 default=1 step=0.1 label='Intensity'}} --><defs>${[0, 1, 2, 3].map((b) => `<clipPath id="slice${b}"><rect x="-100" y="{{cut${b}}}" width="600" height="{{band${b}}}"/></clipPath>`).join("")}</defs><rect width="400" height="200" fill="{{background}}"/><g id="red" style="mix-blend-mode:screen">${word(`fill="{{left}}"`)}</g><g id="cyan" style="mix-blend-mode:screen">${word(`fill="{{right}}"`)}</g>${[0, 1, 2, 3].map((b) => `<g clip-path="url(#slice${b})"><g id="s${b}">${word(`fill="{{ink}}"`)}</g></g>`).join("")}</svg>`,
9868
+ svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='GLITCH' label='Text'}} {{background: color default=#07070B label='Background'}} {{ink: color default=#F5F5F5 label='Text colour'}} {{left: color default=#FF0040 label='Red copy'}} {{right: color default=#00F0FF label='Cyan copy'}} {{amount: float min=0 max=3 default=1 step=0.1 label='Intensity'}} ${typeface(HEAVY, 900)} --><defs>${[0, 1, 2, 3].map((b) => `<clipPath id="slice${b}"><rect x="-5000" y="{{cut${b}}}" width="10400" height="{{band${b}}}"/></clipPath>`).join("")}</defs><rect width="400" height="200" fill="{{background}}" opacity="{{backdrop}}"/><g id="red" style="mix-blend-mode:screen">${word(`fill="{{left}}"`)}</g><g id="cyan" style="mix-blend-mode:screen">${word(`fill="{{right}}"`)}</g>${[0, 1, 2, 3].map((b) => `<g clip-path="url(#slice${b})"><g id="s${b}">${word(`fill="{{ink}}"`)}</g></g>`).join("")}</svg>`,
9711
9869
  animate: [
9712
9870
  ...jumps("red", (k) => `{{red${k}}}`),
9713
9871
  ...jumps("cyan", (k) => `{{cyan${k}}}`),
@@ -9733,13 +9891,15 @@ var extrudedText = {
9733
9891
  ],
9734
9892
  // Each layer sits a step further back along the extrude direction, shaded from the side colour to the shadow.
9735
9893
  derived: (v) => {
9736
- const { size, length } = fit2(String(v.text), 320, 96);
9894
+ const { size, length, left } = fit2(String(v.text), room(v, 320), 96);
9895
+ const d = 0.36 * Number(v.depth);
9737
9896
  const step = size * 0.03 * Number(v.depth);
9738
9897
  const at2 = (deg, i) => {
9739
9898
  const a = deg * Math.PI / 180;
9740
9899
  return `translate(${r14(Math.cos(a) * step * i)} ${r14(Math.sin(a) * step * i)})`;
9741
9900
  };
9742
9901
  return {
9902
+ ...frame(v, left, length, 92 + size * 0.35, size, [0.05, 0.77 * d, DESCENT + d, 0.77 * d]),
9743
9903
  size,
9744
9904
  length,
9745
9905
  base: r14(92 + size * 0.35),
@@ -9756,10 +9916,10 @@ var extrudedText = {
9756
9916
  };
9757
9917
  },
9758
9918
  motion: (() => {
9759
- const word = (paint) => `x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="${HEAVY}" font-size="{{size}}" font-weight="900" stroke-linejoin="round" ${paint}`;
9919
+ const word = (paint) => `x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" stroke-linejoin="round" ${paint}`;
9760
9920
  const layers = Array.from({ length: LAYERS }, (_, k) => LAYERS - k);
9761
9921
  return {
9762
- svg: `<svg viewBox="0 0 400 200"><!-- {{text: text default='RETRO' label='Text'}} {{background: color default=#FFE8C2 label='Background'}} {{face: color default=#FFD23F label='Face'}} {{outline: color default=#1A1423 label='Outline'}} {{side: color default=#EE4266 label='Extrude'}} {{shade: color default=#3B1F2B label='Shadow'}} {{depth: float min=0.2 max=2 default=1 step=0.1 label='Depth'}} --><rect width="400" height="200" fill="{{background}}"/>${layers.map((i) => `<text id="e${i}" transform="translate(0 0)" ${word(`fill="{{fill${i}}}" stroke="{{fill${i}}}" stroke-width="2"`)}>{{text}}</text>`).join("")}<text ${word(`fill="{{face}}" stroke="{{outline}}" stroke-width="2.5" paint-order="stroke"`)}>{{text}}</text></svg>`,
9922
+ svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='RETRO' label='Text'}} {{background: color default=#FFE8C2 label='Background'}} {{face: color default=#FFD23F label='Face'}} {{outline: color default=#1A1423 label='Outline'}} {{side: color default=#EE4266 label='Extrude'}} {{shade: color default=#3B1F2B label='Shadow'}} {{depth: float min=0.2 max=2 default=1 step=0.1 label='Depth'}} ${typeface(HEAVY, 900)} --><rect width="400" height="200" fill="{{background}}" opacity="{{backdrop}}"/>${layers.map((i) => `<text id="e${i}" transform="translate(0 0)" ${word(`fill="{{fill${i}}}" stroke="{{fill${i}}}" stroke-width="2"`)}>{{text}}</text>`).join("")}<text ${word(`fill="{{face}}" stroke="{{outline}}" stroke-width="2.5" paint-order="stroke"`)}>{{text}}</text></svg>`,
9763
9923
  animate: layers.flatMap((i) => [
9764
9924
  { id: `e${i}`, attrs: { transform: ["translate(0 0)", `{{a${i}}}`] }, ms: 700, ease: "out" },
9765
9925
  { id: `e${i}`, attrs: { transform: [`{{a${i}}}`, `{{b${i}}}`] }, delay: 1200, ms: 1500, ease: "in-out" },
@@ -9782,129 +9942,6 @@ var textEffects = [
9782
9942
  extrudedText
9783
9943
  ];
9784
9944
 
9785
- // src/catalog/types.ts
9786
- var EFFECT_CATEGORIES = [
9787
- "gradient",
9788
- "organic",
9789
- "pattern",
9790
- "light",
9791
- "space",
9792
- "retro",
9793
- "image",
9794
- "overlay",
9795
- "text",
9796
- "morph",
9797
- "interaction",
9798
- "pyrotechnics",
9799
- "timewarp",
9800
- "melt",
9801
- "electric",
9802
- "transition",
9803
- "loader",
9804
- "data",
9805
- "icon"
9806
- ];
9807
- var VECTOR_CONTROL_TYPES = ["float", "int", "bool", "color", "text"];
9808
- var PLACEHOLDER = /\{\{\s*([A-Za-z_]\w*)\s*(?::\s*([A-Za-z0-9]*)([^}]*))?\}\}/g;
9809
- function motionStrings(motion) {
9810
- const out = [];
9811
- const walk = (v) => {
9812
- if (typeof v === "string") out.push(v);
9813
- else if (Array.isArray(v)) v.forEach(walk);
9814
- else if (v && typeof v === "object") Object.values(v).forEach(walk);
9815
- };
9816
- walk(motion);
9817
- return out;
9818
- }
9819
- function vectorControls(motion) {
9820
- const controls = [];
9821
- const seen = /* @__PURE__ */ new Set();
9822
- for (const text of motionStrings(motion)) {
9823
- for (const [, name, type, attributes] of text.matchAll(PLACEHOLDER)) {
9824
- if (type === void 0 || seen.has(name) || !VECTOR_CONTROL_TYPES.includes(type)) continue;
9825
- seen.add(name);
9826
- const colour = type === "color";
9827
- controls.push(controlFromDeclaration(name, colour ? "vec3" : type, `${attributes}${colour ? " color" : ""}`, controls.length + 1));
9828
- }
9829
- }
9830
- return controls;
9831
- }
9832
- function vectorEffectControls(effect) {
9833
- const declared = vectorControls(effect.motion);
9834
- const names = new Set(declared.map((c) => c.name));
9835
- const listed = (effect.controls ?? []).filter((c) => !names.has(c.name));
9836
- return [...declared, ...listed.map((c, i) => vectorControlDef(c, declared.length + i + 1))];
9837
- }
9838
- function vectorControlIssues(motion) {
9839
- const issues = [];
9840
- const declarations = /* @__PURE__ */ new Map();
9841
- for (const text of motionStrings(motion)) {
9842
- for (const [whole, name, type] of text.matchAll(PLACEHOLDER)) {
9843
- if (type === void 0) continue;
9844
- if (!VECTOR_CONTROL_TYPES.includes(type))
9845
- issues.push(`${whole}: a control's type is one of ${VECTOR_CONTROL_TYPES.join(", ")}`);
9846
- else if (declarations.has(name) && declarations.get(name) !== whole)
9847
- issues.push(`"${name}" is declared twice; declare it once and write {{${name}}} everywhere else`);
9848
- else declarations.set(name, whole);
9849
- }
9850
- }
9851
- return issues;
9852
- }
9853
- function written(control, value) {
9854
- if (control.color && Array.isArray(value)) return rgbToHex(value);
9855
- if (Array.isArray(value)) return value.join(" ");
9856
- return value;
9857
- }
9858
- var escapeMarkup = (text) => text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
9859
- function vectorControlDef(control, line = 0) {
9860
- const min = control.min ?? 0;
9861
- return {
9862
- name: control.name,
9863
- type: control.type,
9864
- label: control.label ?? control.name,
9865
- color: control.color ?? false,
9866
- min,
9867
- max: control.max ?? Math.max(min + 1, 1),
9868
- step: control.step ?? (control.type === "int" ? 1 : 0.01),
9869
- default: control.default,
9870
- line
9871
- };
9872
- }
9873
- function renderVectorEffect(effect, values = {}) {
9874
- const filled = {};
9875
- const placeholders = /* @__PURE__ */ new Map();
9876
- for (const control of vectorEffectControls(effect)) {
9877
- const given = values[control.name];
9878
- const check = given === void 0 ? void 0 : checkControlValue(control, given);
9879
- filled[control.name] = check?.ok ? check.value : control.default;
9880
- placeholders.set(control.name, written(control, filled[control.name]));
9881
- }
9882
- for (const [name, value] of Object.entries(effect.derived?.(filled) ?? {})) placeholders.set(name, value);
9883
- const fill = (text, markup) => {
9884
- const whole = /^\{\{\s*([A-Za-z_]\w*)\s*(?::[^}]*)?\}\}$/.exec(text);
9885
- if (!markup && whole && placeholders.has(whole[1])) return placeholders.get(whole[1]);
9886
- return text.replace(PLACEHOLDER, (match, name) => {
9887
- if (!placeholders.has(name)) return match;
9888
- const value = String(placeholders.get(name));
9889
- return markup ? escapeMarkup(value) : value;
9890
- });
9891
- };
9892
- const walk = (v) => {
9893
- if (typeof v === "string") return fill(v, false);
9894
- if (Array.isArray(v)) return v.map(walk);
9895
- if (v && typeof v === "object") return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, walk(x)]));
9896
- return v;
9897
- };
9898
- const { svg, ...rest } = effect.motion;
9899
- return { svg: fill(svg.replace(/<!--[\s\S]*?-->/g, ""), true), ...walk(rest) };
9900
- }
9901
- function colorControl(name, hex, label) {
9902
- return { name, type: "vec3", color: true, default: hexToRgb(hex) ?? [1, 1, 1], label: label ?? name };
9903
- }
9904
- function numberControl(name, value, min, max, label, step) {
9905
- return { name, type: "float", default: value, min, max, label: label ?? name, step };
9906
- }
9907
-
9908
9945
  // src/catalog/vector.ts
9909
9946
  var spinner = {
9910
9947
  kind: "vector",
@@ -10317,6 +10354,7 @@ var flowNetwork = {
10317
10354
  }
10318
10355
  };
10319
10356
  var DRIP_BASELINE = 120;
10357
+ var DRIP_SIZE = 104;
10320
10358
  var DRIP_CAP = 75;
10321
10359
  var DRIP_TOP = 106;
10322
10360
  var DRIPS2 = [
@@ -10328,6 +10366,9 @@ var DRIPS2 = [
10328
10366
  { at: 0.693, width: 12, length: 42, delay: 60, ms: 1700 },
10329
10367
  { at: 0.793, width: 11, length: 32, delay: 300, ms: 1500 }
10330
10368
  ];
10369
+ var DRIP_RUN = 300;
10370
+ var DRIP_RUNS = 8;
10371
+ var DRIP_SLOTS = Array.from({ length: DRIP_RUNS }, (_, run) => DRIPS2.map((d, k) => ({ ...d, i: run * DRIPS2.length + k, run, delay: d.delay + run * 90 }))).flat();
10331
10372
  var dripText = {
10332
10373
  kind: "vector",
10333
10374
  id: "drip-text",
@@ -10340,55 +10381,63 @@ var dripText = {
10340
10381
  { name: "Slime", values: { fill: [0.55, 0.93, 0.25], outline: [0.06, 0.2, 0.04], shine: [0.85, 1, 0.7] } },
10341
10382
  { name: "Molten gold", values: { fill: [1, 0.78, 0.2], outline: [0.35, 0.16, 0], shine: [1, 0.96, 0.75], drip: 1.5 } }
10342
10383
  ],
10343
- // The word is stretched to a width set by its length (textLength), so the drips land under its letters in any font.
10384
+ // The word is drawn at its own width (textLength from its estimated width), so the drips land under its letters in any font.
10344
10385
  derived: (v) => {
10345
- const letters = Math.max(1, Array.from(String(v.word).trim()).length);
10346
- const width = Math.min(380, letters * 75);
10386
+ const width = Math.round(Math.max(0.6, textWidthEm(String(v.word).trim())) * DRIP_SIZE);
10347
10387
  const left = 200 - width / 2;
10388
+ const runs = Math.min(DRIP_RUNS, Math.max(1, Math.round(width / DRIP_RUN)));
10389
+ const run = width / runs;
10390
+ const r15 = (n5) => Math.round(n5 * 10) / 10;
10391
+ const edge = Number(v.stroke) / 2 / DRIP_SIZE;
10392
+ const tip = (DRIP_TOP + Math.max(...DRIPS2.map((d) => d.length)) * Number(v.drip) + 13 * 0.72 - DRIP_BASELINE) / DRIP_SIZE;
10393
+ const overhang = [edge, edge, Math.max(0.05, tip) + edge, edge].map((e) => Math.round(e * 100) / 100).join(" ");
10348
10394
  return {
10349
10395
  width,
10396
+ fit: v.inline ? 1 : Math.round(Math.min(1, 380 / width) * 1e3) / 1e3,
10350
10397
  // Inline, the picture frames just the word's capitals, from cap height to baseline, so it sits on
10351
10398
  // the line like a letter would; the pop, outline and drips spill out over the text around it.
10352
10399
  viewBox: v.inline ? `${left} ${DRIP_BASELINE - DRIP_CAP} ${width} ${DRIP_CAP}` : "0 0 400 200",
10353
10400
  overflow: v.inline ? "visible" : "hidden",
10401
+ overhang,
10354
10402
  ...Object.fromEntries(
10355
- DRIPS2.flatMap((d, i) => {
10356
- const x = Math.round((left + d.at * width) * 10) / 10;
10357
- const length = Math.round(d.length * Number(v.drip) * 10) / 10;
10403
+ DRIP_SLOTS.flatMap((d) => {
10404
+ const x = r15(left + (d.run + d.at) * run);
10405
+ const length = r15(d.length * Number(v.drip));
10358
10406
  return [
10359
- [`x${i}`, x],
10360
- [`left${i}`, x - d.width / 2],
10361
- [`shine${i}`, x - d.width * 0.25],
10362
- [`len${i}`, length],
10363
- [`tip${i}`, DRIP_TOP + length]
10407
+ [`show${d.i}`, d.run < runs ? "inline" : "none"],
10408
+ [`x${d.i}`, x],
10409
+ [`left${d.i}`, r15(x - d.width / 2)],
10410
+ [`shine${d.i}`, r15(x - d.width * 0.25)],
10411
+ [`len${d.i}`, length],
10412
+ [`tip${d.i}`, r15(DRIP_TOP + length)]
10364
10413
  ];
10365
10414
  })
10366
10415
  )
10367
10416
  };
10368
10417
  },
10369
10418
  motion: (() => {
10370
- const word = `<text x="200" y="${DRIP_BASELINE}" text-anchor="middle" textLength="{{width}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="104" font-weight="{{weight}}" stroke-linejoin="round"`;
10371
- const drips = (layer, paint) => DRIPS2.map(
10372
- (d, i) => `<rect id="${layer}r${i}" x="{{left${i}}}" y="${DRIP_TOP}" width="${d.width}" height="0" rx="${d.width / 2}" ${paint}/><circle id="${layer}c${i}" cx="{{x${i}}}" cy="${DRIP_TOP}" r="${d.width * 0.72}" ${paint}/>`
10419
+ const word = `<text x="200" y="${DRIP_BASELINE}" text-anchor="middle" textLength="{{width}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="${DRIP_SIZE}" font-weight="{{weight}}" stroke-linejoin="round"`;
10420
+ const drips = (layer, paint) => DRIP_SLOTS.map(
10421
+ (d) => `<g display="{{show${d.i}}}"><rect id="${layer}r${d.i}" x="{{left${d.i}}}" y="${DRIP_TOP}" width="${d.width}" height="0" rx="${d.width / 2}" ${paint}/><circle id="${layer}c${d.i}" cx="{{x${d.i}}}" cy="${DRIP_TOP}" r="${d.width * 0.72}" ${paint}/></g>`
10373
10422
  ).join("");
10374
10423
  const outline = `fill="{{outline}}" stroke="{{outline}}" stroke-width="{{stroke}}"`;
10375
10424
  const pop = (from, to) => [`translate(200 100) scale(${from}) translate(-200 -100)`, `translate(200 100) scale(${to}) translate(-200 -100)`];
10376
10425
  return {
10377
- svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}"><!-- {{word: text default=SLOP label='Word'}} {{font: text default='Arial Black, Impact, system-ui, sans-serif' label='Font'}} {{weight: int min=100 max=900 default=900 step=100 label='Weight'}} {{fill: color default=#FF7EB6 label='Fill'}} {{outline: color default=#3A0A2C label='Outline'}} {{shine: color default=#FFD1E6 label='Shine'}} {{drip: float min=0.2 max=2 default=1 label='Drip length'}} {{stroke: float min=0 max=14 default=7 step=0.5 label='Outline width'}} {{inline: bool default=false label='Inline'}} --><g id="word" transform="${pop(0.6, 1)[0]}" opacity="0">${drips("o", outline)}${word} fill="none" stroke="{{outline}}" stroke-width="{{stroke}}">{{word}}</text>${drips("f", `fill="{{fill}}"`)}${word} fill="{{fill}}">{{word}}</text>${DRIPS2.map(
10378
- (d, i) => `<circle id="s${i}" cx="{{shine${i}}}" cy="${DRIP_TOP}" r="${d.width * 0.2}" fill="{{shine}}" opacity="0"/>`
10379
- ).join("")}</g></svg>`,
10426
+ svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{word: text default=SLOP label='Word'}} {{font: text default='Arial Black, Impact, system-ui, sans-serif' label='Font'}} {{weight: int min=100 max=900 default=900 step=100 label='Weight'}} {{fill: color default=#FF7EB6 label='Fill'}} {{outline: color default=#3A0A2C label='Outline'}} {{shine: color default=#FFD1E6 label='Shine'}} {{drip: float min=0.2 max=2 default=1 label='Drip length'}} {{stroke: float min=0 max=14 default=7 step=0.5 label='Outline width'}} {{inline: bool default=false label='Inline'}} --><g transform="translate(200 100) scale({{fit}}) translate(-200 -100)"><g id="word" transform="${pop(0.6, 1)[0]}" opacity="0">${drips("o", outline)}${word} fill="none" stroke="{{outline}}" stroke-width="{{stroke}}">{{word}}</text>${drips("f", `fill="{{fill}}"`)}${word} fill="{{fill}}">{{word}}</text>${DRIP_SLOTS.map(
10427
+ (d) => `<circle id="s${d.i}" display="{{show${d.i}}}" cx="{{shine${d.i}}}" cy="${DRIP_TOP}" r="${d.width * 0.2}" fill="{{shine}}" opacity="0"/>`
10428
+ ).join("")}</g></g></svg>`,
10380
10429
  animate: [
10381
10430
  { id: "word", attrs: { transform: pop(0.6, 1.08), opacity: [0, 1] }, ms: 260, ease: "out" },
10382
10431
  { id: "word", attrs: { transform: pop(1.08, 1) }, delay: 260, ms: 200, ease: "in-out" },
10383
- ...DRIPS2.flatMap((d, i) => {
10432
+ ...DRIP_SLOTS.flatMap((d) => {
10384
10433
  const at2 = { delay: 400 + d.delay, ms: d.ms, ease: "in-out" };
10385
- const tip = `{{tip${i}}}`;
10434
+ const tip = `{{tip${d.i}}}`;
10386
10435
  return [
10387
10436
  ...["o", "f"].flatMap((layer) => [
10388
- { id: `${layer}r${i}`, attrs: { height: [0, `{{len${i}}}`] }, ...at2 },
10389
- { id: `${layer}c${i}`, attrs: { cy: [DRIP_TOP, tip] }, ...at2 }
10437
+ { id: `${layer}r${d.i}`, attrs: { height: [0, `{{len${d.i}}}`] }, ...at2 },
10438
+ { id: `${layer}c${d.i}`, attrs: { cy: [DRIP_TOP, tip] }, ...at2 }
10390
10439
  ]),
10391
- { id: `s${i}`, attrs: { cy: [DRIP_TOP, tip], opacity: [0, 0.9] }, ...at2 }
10440
+ { id: `s${d.i}`, attrs: { cy: [DRIP_TOP, tip], opacity: [0, 0.9] }, ...at2 }
10392
10441
  ];
10393
10442
  })
10394
10443
  ]
@@ -10518,7 +10567,8 @@ function effectControls(effect) {
10518
10567
  function effectDefaults(effect, options = {}) {
10519
10568
  const preset = options.preset ? effect.presets?.find((p) => p.name === options.preset)?.values : void 0;
10520
10569
  const merged = { ...preset, ...options.values };
10521
- return Object.fromEntries(effectControls(effect).map((c) => [c.name, controlValue(c, merged)]));
10570
+ const keep = (c) => effect.kind === "vector" && c.color && isCurrentColor(merged[c.name]);
10571
+ return Object.fromEntries(effectControls(effect).map((c) => [c.name, keep(c) ? CURRENT_COLOR : controlValue(c, merged)]));
10522
10572
  }
10523
10573
 
10524
10574
  export {
@@ -10538,6 +10588,8 @@ export {
10538
10588
  vectorEffectControls,
10539
10589
  vectorControlIssues,
10540
10590
  vectorControlDef,
10591
+ CURRENT_COLOR,
10592
+ isCurrentColor,
10541
10593
  renderVectorEffect,
10542
10594
  colorControl,
10543
10595
  numberControl,
@@ -10548,4 +10600,4 @@ export {
10548
10600
  effectControls,
10549
10601
  effectDefaults
10550
10602
  };
10551
- //# sourceMappingURL=chunk-KWNFZ5NC.js.map
10603
+ //# sourceMappingURL=chunk-LURJMWYM.js.map