@pixerate/flair 0.29.0 → 0.30.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.
@@ -28,8 +28,8 @@ function isTextImage(url) {
28
28
  var PATH_DATA = /^\s*[Mm]\s*[-+.\d]/;
29
29
  function textSlot(control) {
30
30
  const value = String(control.default);
31
- const holds2 = control.name === "font" || /\bfont\b/i.test(control.label) ? "font" : PATH_DATA.test(value) ? "path" : control.name === "glyphs" ? "glyphs" : "copy";
32
- return { name: control.name, label: control.label, holds: holds2, default: value };
31
+ const holds3 = control.name === "font" || /\bfont\b/i.test(control.label) ? "font" : PATH_DATA.test(value) ? "path" : control.name === "glyphs" ? "glyphs" : "copy";
32
+ return { name: control.name, label: control.label, holds: holds3, default: value };
33
33
  }
34
34
  var codeOf = (source) => source.replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*/g, "");
35
35
  function shaderInteractions(source) {
@@ -1885,7 +1885,7 @@ function capsule(p, q, width, flatEnd = false) {
1885
1885
  if (flatEnd) pts.push([q[0] + h * Math.cos(a - Math.PI / 2), q[1] + h * Math.sin(a - Math.PI / 2)], [q[0] + h * Math.cos(a + Math.PI / 2), q[1] + h * Math.sin(a + Math.PI / 2)]);
1886
1886
  else cap(q, a - Math.PI / 2);
1887
1887
  cap(p, a + Math.PI / 2);
1888
- return `M${pts.map((pt) => `${r2(pt[0])} ${r2(pt[1])}`).join(" L")} Z`;
1888
+ return `M${pts.map((pt2) => `${r2(pt2[0])} ${r2(pt2[1])}`).join(" L")} Z`;
1889
1889
  }
1890
1890
  var menuMorphs = /* @__PURE__ */ new Map();
1891
1891
  function menuMorph(weight) {
@@ -1973,9 +1973,9 @@ var swapLabels = (outAt, back) => [
1973
1973
  { id: "label", step: 1, attrs: { opacity: [0, 1], transform: ["translate(0 10)", "translate(0 0)"] }, delay: back + 120, ms: 220, ease: "out" },
1974
1974
  snap("done", "transform", "translate(0 10)", back + 200)
1975
1975
  ];
1976
- var checkFlash = (at3, back) => [
1977
- { id: "check", step: 1, attrs: { opacity: [0, 1] }, delay: at3, ms: 40 },
1978
- { id: "check", step: 1, draw: true, delay: at3, ms: 280, ease: "out" },
1976
+ var checkFlash = (at4, back) => [
1977
+ { id: "check", step: 1, attrs: { opacity: [0, 1] }, delay: at4, ms: 40 },
1978
+ { id: "check", step: 1, draw: true, delay: at4, ms: 280, ease: "out" },
1979
1979
  { id: "check", step: 1, attrs: { opacity: [1, 0] }, delay: back, ms: 160 },
1980
1980
  { id: "check", step: 1, draw: [0, 0], delay: back + 200, ms: 0 }
1981
1981
  ];
@@ -2308,10 +2308,10 @@ var sliderScrub = {
2308
2308
  return { size, length };
2309
2309
  },
2310
2310
  motion: (() => {
2311
- const move = (from, to, delay, ms, withCursor) => [
2312
- { id: "knob", step: 1, attrs: { transform: [knobAt(from), knobAt(to)] }, delay, ms, ease: "in-out" },
2313
- { id: "fill", step: 1, attrs: { width: [r1(from - SLIDER.x), r1(to - SLIDER.x)] }, delay, ms, ease: "in-out" },
2314
- ...withCursor ? [{ id: "cursor", step: 1, attrs: { transform: [cursorAt(from + 2), cursorAt(to + 2)] }, delay, ms, ease: "in-out" }] : []
2311
+ const move = (from, to, delay, ms2, withCursor) => [
2312
+ { id: "knob", step: 1, attrs: { transform: [knobAt(from), knobAt(to)] }, delay, ms: ms2, ease: "in-out" },
2313
+ { id: "fill", step: 1, attrs: { width: [r1(from - SLIDER.x), r1(to - SLIDER.x)] }, delay, ms: ms2, ease: "in-out" },
2314
+ ...withCursor ? [{ id: "cursor", step: 1, attrs: { transform: [cursorAt(from + 2), cursorAt(to + 2)] }, delay, ms: ms2, ease: "in-out" }] : []
2315
2315
  ];
2316
2316
  return {
2317
2317
  svg: `<svg viewBox="0 0 360 160"><!-- {{label: text default='Number of results' label='Label'}} {{from: int min=0 max=99999 default=50 label='From value'}} {{to: int min=0 max=99999 default=306 label='To value'}} {{background: color default=#0B0F17 label='Background'}} {{accent: color default=#8B5CF6 label='Accent'}} {{track: color default=#272D3A label='Track'}} {{ink: color default=#F3F4F6 label='Text colour'}} --><rect width="360" height="160" fill="{{background}}"/><text x="${SLIDER.x}" y="58" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="${SANS}" font-size="{{size}}" font-weight="600" fill="{{ink}}">{{label}}</text><text id="value" x="${SLIDER.x + SLIDER.w}" y="58" text-anchor="end" font-family="${SANS}" font-size="16" font-weight="700" fill="{{accent}}">{{from}}</text><rect x="${SLIDER.x}" y="${SLIDER.y - 3}" width="${SLIDER.w}" height="6" rx="3" fill="{{track}}"/><rect id="fill" x="${SLIDER.x}" y="${SLIDER.y - 3}" width="${r1(KNOB_FROM - SLIDER.x)}" height="6" rx="3" fill="{{accent}}"/><g id="knob" transform="${knobAt(KNOB_FROM)}"><circle id="halo" r="11" fill="{{accent}}" opacity="0"/><circle cy="1.5" r="11" fill="#000000" opacity="0.2"/><circle id="dot" r="10" fill="#FFFFFF" stroke="{{accent}}" stroke-width="3"/></g><g id="cursor" transform="${CURSOR_REST}"><path d="M0 0 L0 17 L4.6 12.8 L7.8 19.6 L10.6 18.3 L7.5 11.7 L13.4 11.4 Z" fill="#FFFFFF" stroke="#111827" stroke-width="1.3" stroke-linejoin="round"/></g></svg>`,
@@ -2452,10 +2452,10 @@ var splatStamp = {
2452
2452
  { id: "splat", attrs: { d: ["{{b}}", "{{c}}"] }, delay: 520, ms: 380, ease: "in-out" },
2453
2453
  { id: "gloss", attrs: { opacity: [0, n2("gloss")] }, delay: 600, ms: 400, ease: "out" },
2454
2454
  ...Array.from({ length: DROPS }, (_, i) => {
2455
- const at3 = POP + i * 35;
2455
+ const at4 = POP + i * 35;
2456
2456
  return [
2457
- { id: `drop${i}`, attrs: { cx: [CX, n2(`xm${i}`)], cy: [CY, n2(`ym${i}`)], r: [0, n2(`ro${i}`)] }, delay: at3, ms: 150, ease: "out" },
2458
- { id: `drop${i}`, attrs: { cx: [n2(`xm${i}`), n2(`x${i}`)], cy: [n2(`ym${i}`), n2(`y${i}`)], r: [n2(`ro${i}`), n2(`r${i}`)] }, delay: at3 + 150, ms: 260, ease: "out" },
2457
+ { id: `drop${i}`, attrs: { cx: [CX, n2(`xm${i}`)], cy: [CY, n2(`ym${i}`)], r: [0, n2(`ro${i}`)] }, delay: at4, ms: 150, ease: "out" },
2458
+ { id: `drop${i}`, attrs: { cx: [n2(`xm${i}`), n2(`x${i}`)], cy: [n2(`ym${i}`), n2(`y${i}`)], r: [n2(`ro${i}`), n2(`r${i}`)] }, delay: at4 + 150, ms: 260, ease: "out" },
2459
2459
  { id: `drop${i}`, attrs: { cx: [n2(`x${i}`), CX], cy: [n2(`y${i}`), CY], r: [n2(`r${i}`), 0] }, delay: SETTLE + 2e3, ms: 360, ease: "in-out" }
2460
2460
  ];
2461
2461
  }).flat(),
@@ -2534,10 +2534,10 @@ var chatBubble = {
2534
2534
  ...Object.fromEntries(
2535
2535
  Array.from({ length: BOUNCES * DOTS }, (_, i) => {
2536
2536
  const [k, j] = [Math.floor(i / DOTS), i % DOTS];
2537
- const at3 = reply + 450 + k * BOUNCE_MS + j * 150;
2537
+ const at4 = reply + 450 + k * BOUNCE_MS + j * 150;
2538
2538
  return [
2539
- [`rise${k}_${j}`, at3],
2540
- [`fall${k}_${j}`, at3 + 180]
2539
+ [`rise${k}_${j}`, at4],
2540
+ [`fall${k}_${j}`, at4 + 180]
2541
2541
  ];
2542
2542
  }).flat()
2543
2543
  )
@@ -2606,22 +2606,22 @@ var bubblePopLoader = {
2606
2606
  svg: `<svg viewBox="0 0 240 80"><!-- {{color: color default=#6366F1 label='Bubbles'}} {{accent: color default=#38BDF8 label='Droplets'}} {{background: color default=#FFFFFF label='Background'}} {{width: float min=1 max=4 default=2 step=0.5 label='Outline'}} --><rect width="240" height="80" fill="{{background}}"/>${bubbles}</svg>`,
2607
2607
  animate: LOADER_X.flatMap((_, i) => {
2608
2608
  const t0 = i * LOADER_STAGGER;
2609
- const pop = t0 + POP_AT;
2609
+ const pop2 = t0 + POP_AT;
2610
2610
  const p = pose3(i);
2611
2611
  return [
2612
2612
  // Inflating with a wobble, then bursting.
2613
2613
  { id: `b${i}`, attrs: { transform: [p(0), p(1.1)] }, delay: t0, ms: 450, ease: "out" },
2614
2614
  { id: `b${i}`, attrs: { transform: [p(1.1), p(0.95)] }, delay: t0 + 450, ms: 150, ease: "in-out" },
2615
2615
  { id: `b${i}`, attrs: { transform: [p(0.95), p(1)] }, delay: t0 + 600, ms: 200, ease: "in-out" },
2616
- { id: `b${i}`, attrs: { transform: [p(1), p(1.35)], opacity: [1, 0] }, delay: pop, ms: 90, ease: "out" },
2616
+ { id: `b${i}`, attrs: { transform: [p(1), p(1.35)], opacity: [1, 0] }, delay: pop2, ms: 90, ease: "out" },
2617
2617
  snap2(`ring${i}`, "stroke-width", 0, 0),
2618
- { id: `ring${i}`, attrs: { r: [LOADER_R, LOADER_R * 2], "stroke-width": [3, 0] }, delay: pop, ms: 320, ease: "out" },
2618
+ { id: `ring${i}`, attrs: { r: [LOADER_R, LOADER_R * 2], "stroke-width": [3, 0] }, delay: pop2, ms: 320, ease: "out" },
2619
2619
  ...Array.from({ length: DROPS2 }, (_2, k) => {
2620
2620
  const [x0, y0] = drop(i, k, LOADER_R);
2621
2621
  const [x1, y1] = drop(i, k, LOADER_R * 2.1);
2622
2622
  return [
2623
2623
  snap2(`d${i}_${k}`, "r", 0, 0),
2624
- { id: `d${i}_${k}`, attrs: { cx: [x0, x1], cy: [y0, r12(y1 + 4)], r: [3, 0] }, delay: pop, ms: 420, ease: "out" },
2624
+ { id: `d${i}_${k}`, attrs: { cx: [x0, x1], cy: [y0, r12(y1 + 4)], r: [3, 0] }, delay: pop2, ms: 420, ease: "out" },
2625
2625
  snap2(`d${i}_${k}`, "cx", x0, LOADER_END),
2626
2626
  snap2(`d${i}_${k}`, "cy", y0, LOADER_END)
2627
2627
  ];
@@ -2731,21 +2731,21 @@ var stickerSlap = {
2731
2731
  },
2732
2732
  motion: {
2733
2733
  svg: `<svg viewBox="0 0 240 240"><!-- {{label: text default='NEW!' label='Label'}} ${SHAPE_CONTROL} {{background: color default=#EDE8DF label='Background'}} {{sticker: color default=#FF5A36 label='Sticker'}} {{ink: color default=#FFFFFF label='Label colour'}} {{border: color default=#FFFFFF label='Border'}} ${FONT} --><defs><filter id="slapSoft" x="-50%" y="-50%" width="200%" height="200%"><feGaussianBlur id="shadeBlur" stdDeviation="3"/></filter>${gloss("slapGloss")}</defs><rect width="240" height="240" fill="{{background}}"/><g id="shade" transform="${pose(120, 120, 1, -6, 7)}" opacity="0.32"><path d="{{d}}" fill="#000000" stroke="#000000" stroke-width="16" stroke-linejoin="round" filter="url(#slapSoft)"/></g><g id="sticker" transform="${pose(120, 120, 1, -6)}"><path d="{{d}}" fill="{{border}}" stroke="{{border}}" stroke-width="16" stroke-linejoin="round"/><path d="{{d}}" fill="{{sticker}}"/><text x="120" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="{{size}}" font-weight="900" fill="{{ink}}">{{label}}</text><path d="{{d}}" fill="url(#slapGloss)" stroke="#FFFFFF" stroke-opacity="0.35" stroke-width="16" stroke-linejoin="round"/></g></svg>`,
2734
- animate: SLAP.slice(1).flatMap(([at3, s, r, dy, o, blur], i) => {
2734
+ animate: SLAP.slice(1).flatMap(([at4, s, r, dy, o, blur], i) => {
2735
2735
  const [from, fs, fr, fdy, fo, fblur] = SLAP[i];
2736
- const ms = at3 - from;
2737
- if (ms === 0)
2736
+ const ms2 = at4 - from;
2737
+ if (ms2 === 0)
2738
2738
  return [
2739
- snap3("sticker", "transform", pose(120, 120, s, r), at3),
2740
- snap3("shade", "transform", pose(120, 120, s, r, dy), at3),
2741
- snap3("shade", "opacity", o, at3),
2742
- snap3("shadeBlur", "stdDeviation", blur, at3)
2739
+ snap3("sticker", "transform", pose(120, 120, s, r), at4),
2740
+ snap3("shade", "transform", pose(120, 120, s, r, dy), at4),
2741
+ snap3("shade", "opacity", o, at4),
2742
+ snap3("shadeBlur", "stdDeviation", blur, at4)
2743
2743
  ];
2744
2744
  const ease = i === 0 || i === 3 ? "out" : "in-out";
2745
2745
  return [
2746
- { id: "sticker", step: 1, attrs: { transform: [pose(120, 120, fs, fr), pose(120, 120, s, r)] }, delay: from, ms, ease },
2747
- { id: "shade", step: 1, attrs: { transform: [pose(120, 120, fs, fr, fdy), pose(120, 120, s, r, dy)], opacity: [fo, o] }, delay: from, ms, ease },
2748
- { id: "shadeBlur", step: 1, attrs: { stdDeviation: [fblur, blur] }, delay: from, ms, ease }
2746
+ { id: "sticker", step: 1, attrs: { transform: [pose(120, 120, fs, fr), pose(120, 120, s, r)] }, delay: from, ms: ms2, ease },
2747
+ { id: "shade", step: 1, attrs: { transform: [pose(120, 120, fs, fr, fdy), pose(120, 120, s, r, dy)], opacity: [fo, o] }, delay: from, ms: ms2, ease },
2748
+ { id: "shadeBlur", step: 1, attrs: { stdDeviation: [fblur, blur] }, delay: from, ms: ms2, ease }
2749
2749
  ];
2750
2750
  }).concat([
2751
2751
  // Gone as it flicks up, back in as it comes down.
@@ -2813,7 +2813,7 @@ var holoSticker = {
2813
2813
  const { d, labelY, labelW } = stickerShape(v.shape, 120, 120, 80);
2814
2814
  const { size, length } = fit2(v.label, labelW, 40);
2815
2815
  const period = Math.round(Number(v.speed) * 1e3);
2816
- const glints = Object.fromEntries(GLINTS.flatMap(([, , , at3], k) => [[`g${k}`, Math.round(at3 * period)], [`h${k}`, Math.round(at3 * period) + Math.round(period * 0.08)]]));
2816
+ const glints = Object.fromEntries(GLINTS.flatMap(([, , , at4], k) => [[`g${k}`, Math.round(at4 * period)], [`h${k}`, Math.round(at4 * period) + Math.round(period * 0.08)]]));
2817
2817
  return { d, size, length, base: r13(labelY + size * 0.36), period, sheen: Math.round(period * 0.35), grow: Math.round(period * 0.08), fade: Math.round(period * 0.11), ...glints };
2818
2818
  },
2819
2819
  motion: {
@@ -2883,12 +2883,12 @@ var stickerSheet = {
2883
2883
  ([kind, x, y, r], i) => `<g id="s${i}" transform="${pose(x, y, 0, r - 30)}" opacity="0">${sheetSticker(kind, x, y, SHEET_COLOURS[i])}</g>`
2884
2884
  ).join("")}</svg>`,
2885
2885
  animate: SHEET.flatMap(([, x, y, r], i) => {
2886
- const at3 = 150 + i * POP_GAP;
2886
+ const at4 = 150 + i * POP_GAP;
2887
2887
  const out = SHEET_EXIT + i * 40;
2888
2888
  return [
2889
- { id: `s${i}`, attrs: { opacity: [0, 1] }, delay: at3, ms: 60 },
2890
- { id: `s${i}`, attrs: { transform: [pose(x, y, 0, r - 30), pose(x, y, 1.18, r + 4)] }, delay: at3, ms: 220, ease: "out" },
2891
- { id: `s${i}`, attrs: { transform: [pose(x, y, 1.18, r + 4), pose(x, y, 1, r)] }, delay: at3 + 220, ms: 180, ease: "in-out" },
2889
+ { id: `s${i}`, attrs: { opacity: [0, 1] }, delay: at4, ms: 60 },
2890
+ { id: `s${i}`, attrs: { transform: [pose(x, y, 0, r - 30), pose(x, y, 1.18, r + 4)] }, delay: at4, ms: 220, ease: "out" },
2891
+ { id: `s${i}`, attrs: { transform: [pose(x, y, 1.18, r + 4), pose(x, y, 1, r)] }, delay: at4 + 220, ms: 180, ease: "in-out" },
2892
2892
  { id: `s${i}`, attrs: { transform: [pose(x, y, 1, r), pose(x, y, 0, r - 30)] }, delay: out, ms: 240, ease: "in-out" },
2893
2893
  { id: `s${i}`, attrs: { opacity: [1, 0] }, delay: out + 160, ms: 80 }
2894
2894
  ];
@@ -2949,9 +2949,9 @@ var TORN_LEFT = [3, 0, 5, 1, 4, 0, 6, 2, 3, 0, 4];
2949
2949
  var TORN_RIGHT = [1, 5, 0, 4, 2, 6, 1, 3, 0, 5, 2];
2950
2950
  var TAPE = (() => {
2951
2951
  const h = TAPE_W / 2;
2952
- const at3 = (k) => -h + k * TAPE_W / (TORN_LEFT.length - 1);
2953
- const right = TORN_RIGHT.map((dx, k) => [TAPE_L - dx, at3(k)]);
2954
- const left = TORN_LEFT.map((dx, k) => [dx, at3(k)]).reverse();
2952
+ const at4 = (k) => -h + k * TAPE_W / (TORN_LEFT.length - 1);
2953
+ const right = TORN_RIGHT.map((dx, k) => [TAPE_L - dx, at4(k)]);
2954
+ const left = TORN_LEFT.map((dx, k) => [dx, at4(k)]).reverse();
2955
2955
  return outline([...right, ...left]);
2956
2956
  })();
2957
2957
  var UNROLL_MS = 1500;
@@ -3026,8 +3026,8 @@ var sealBadge = {
3026
3026
  const bottomMid = topMid + top.size / 2 + gap + bottom.size / 2;
3027
3027
  const beats = Object.fromEntries(
3028
3028
  SEAL_PULSES.flatMap((f, i) => {
3029
- const at3 = Math.round(period * f);
3030
- return [...SEAL_BEAT.map(([ms], j) => [`beat${i}_${j}`, at3 + ms]), [`glint${i}`, at3 + 60]];
3029
+ const at4 = Math.round(period * f);
3030
+ return [...SEAL_BEAT.map(([ms2], j) => [`beat${i}_${j}`, at4 + ms2]), [`glint${i}`, at4 + 60]];
3031
3031
  })
3032
3032
  );
3033
3033
  return {
@@ -3049,11 +3049,11 @@ var sealBadge = {
3049
3049
  ...["sealSpin", "sealClipShape"].map((id) => ({ id, attrs: { transform: ["rotate(0 120 120)", "rotate(360 120 120)"] }, ms: n4("period"), ease: "linear" })),
3050
3050
  // Now and then it pulses and a glint sweeps across.
3051
3051
  ...SEAL_PULSES.flatMap((_, i) => [
3052
- ...SEAL_BEAT.slice(1).map(([at3, scale], j) => ({
3052
+ ...SEAL_BEAT.slice(1).map(([at4, scale], j) => ({
3053
3053
  id: "sealPulse",
3054
3054
  attrs: { transform: [pose(120, 120, SEAL_BEAT[j][1], 0), pose(120, 120, scale, 0)] },
3055
3055
  delay: n4(`beat${i}_${j}`),
3056
- ms: at3 - SEAL_BEAT[j][0],
3056
+ ms: at4 - SEAL_BEAT[j][0],
3057
3057
  ease: j === 0 ? "out" : "in-out"
3058
3058
  })),
3059
3059
  { id: "sealGlint", attrs: { transform: ["translate(-14 0) skewX(-20)", "translate(300 0) skewX(-20)"] }, delay: n4(`glint${i}`), ms: SEAL_GLINT_MS, ease: "in-out" }
@@ -3305,14 +3305,14 @@ var snap5 = (id, attr, value, delay, step) => ({
3305
3305
  ms: 0
3306
3306
  });
3307
3307
  function keys(id, attr, frames, options = {}) {
3308
- return frames.slice(1).map(([at3, value], i) => {
3308
+ return frames.slice(1).map(([at4, value], i) => {
3309
3309
  const [from, before] = frames[i];
3310
3310
  return {
3311
3311
  id,
3312
3312
  ...options.step === void 0 ? {} : { step: options.step },
3313
3313
  attrs: { [attr]: [before, value] },
3314
3314
  delay: from,
3315
- ms: at3 - from,
3315
+ ms: at4 - from,
3316
3316
  ease: options.ease ?? "in-out"
3317
3317
  };
3318
3318
  });
@@ -3412,9 +3412,9 @@ var soundEffect = {
3412
3412
  { id: "sfxFlecks", attrs: { transform: [sfxPose(0.7, 0), sfxPose(1.25, 4)] }, delay: 140, ms: 420, ease: "out" },
3413
3413
  { id: "sfxFlecks", attrs: { opacity: [1, 0] }, delay: 300, ms: 320 },
3414
3414
  // Everything shakes from the hit.
3415
- ...SFX_SHAKE.slice(1).map(([at3, dx, dy], i) => {
3415
+ ...SFX_SHAKE.slice(1).map(([at4, dx, dy], i) => {
3416
3416
  const [from, fx, fy] = SFX_SHAKE[i];
3417
- return { id: "sfxShake", attrs: { transform: [`translate(${fx} ${fy})`, `translate(${dx} ${dy})`] }, delay: from, ms: at3 - from, ease: "linear" };
3417
+ return { id: "sfxShake", attrs: { transform: [`translate(${fx} ${fy})`, `translate(${dx} ${dy})`] }, delay: from, ms: at4 - from, ease: "linear" };
3418
3418
  })
3419
3419
  ]
3420
3420
  }
@@ -3500,20 +3500,20 @@ var speechBubble = {
3500
3500
  pop1: about4(tip[0], tip[1], 1)
3501
3501
  };
3502
3502
  trail.forEach((c, i) => Object.assign(out, { [`tx${i}`]: c.x, [`ty${i}`]: c.y, [`tr${i}`]: c.r }));
3503
- let at3 = 480;
3503
+ let at4 = 480;
3504
3504
  for (let i = 0; i < SB_LINES; i++) {
3505
3505
  const line = lines[i] ?? "";
3506
- const ms = Math.max(1, Math.round(Array.from(line).length * pace));
3506
+ const ms2 = Math.max(1, Math.round(Array.from(line).length * pace));
3507
3507
  Object.assign(out, {
3508
3508
  [`line${i}`]: line,
3509
3509
  [`x${i}`]: r15(SB_X - textWidthEm(line) * size / 2),
3510
3510
  [`y${i}`]: r15(top + lineH * (i + 0.78)),
3511
- [`at${i}`]: at3,
3512
- [`ms${i}`]: ms
3511
+ [`at${i}`]: at4,
3512
+ [`ms${i}`]: ms2
3513
3513
  });
3514
- at3 += line ? ms + 80 : 0;
3514
+ at4 += line ? ms2 + 80 : 0;
3515
3515
  }
3516
- out.outAt = at3 + 1600;
3516
+ out.outAt = at4 + 1600;
3517
3517
  return out;
3518
3518
  },
3519
3519
  motion: {
@@ -3612,20 +3612,20 @@ var fightCloud = {
3612
3612
  })
3613
3613
  ),
3614
3614
  // A fist, boot or star jabs out of the cloud and back, and an impact mark flashes at its tip.
3615
- ...FC_POKES.flatMap(([at3, deg], k) => {
3615
+ ...FC_POKES.flatMap(([at4, deg], k) => {
3616
3616
  const hidden = fcPoke(deg, 0);
3617
3617
  const out = fcPoke(deg, fcReach(deg) + 14);
3618
3618
  const a = deg * Math.PI / 180;
3619
3619
  const d = fcReach(deg) + 54;
3620
3620
  const [x, y] = [FC_X + d * Math.cos(a), FC_Y + d * Math.sin(a)];
3621
3621
  return [
3622
- { id: `fcPoke${k}`, attrs: { transform: [hidden, out] }, delay: at3, ms: 110, ease: "out" },
3623
- { id: `fcPoke${k}`, attrs: { transform: [out, hidden] }, delay: at3 + 290, ms: 170, ease: "in-out" },
3622
+ { id: `fcPoke${k}`, attrs: { transform: [hidden, out] }, delay: at4, ms: 110, ease: "out" },
3623
+ { id: `fcPoke${k}`, attrs: { transform: [out, hidden] }, delay: at4 + 290, ms: 170, ease: "in-out" },
3624
3624
  snap5(`fcHit${k}`, "opacity", 0, 0),
3625
- snap5(`fcHit${k}`, "opacity", 1, at3 + 90),
3626
- { id: `fcHit${k}`, attrs: { transform: [about4(x, y, 0.3), about4(x, y, 1.2)] }, delay: at3 + 90, ms: 200, ease: "out" },
3627
- { id: `fcHit${k}`, attrs: { opacity: [1, 0] }, delay: at3 + 200, ms: 200 },
3628
- snap5(`fcHit${k}`, "transform", about4(x, y, 0.3), at3 + 400)
3625
+ snap5(`fcHit${k}`, "opacity", 1, at4 + 90),
3626
+ { id: `fcHit${k}`, attrs: { transform: [about4(x, y, 0.3), about4(x, y, 1.2)] }, delay: at4 + 90, ms: 200, ease: "out" },
3627
+ { id: `fcHit${k}`, attrs: { opacity: [1, 0] }, delay: at4 + 200, ms: 200 },
3628
+ snap5(`fcHit${k}`, "transform", about4(x, y, 0.3), at4 + 400)
3629
3629
  ];
3630
3630
  }),
3631
3631
  // The whole scrap jitters.
@@ -3758,12 +3758,12 @@ var animeEmotes = {
3758
3758
  ]),
3759
3759
  // Sparkles: each twinkles open with a turn and closes again, one after another.
3760
3760
  ...AE_SPARKS.flatMap(
3761
- ([x, y, s, at3], i) => keys(`aeSpark${i}`, "transform", [
3762
- [at3, aeSpark(x, y, 0, 0)],
3763
- [at3 + 260, aeSpark(x, y, s, 45)],
3764
- [at3 + 460, aeSpark(x, y, s * 0.85, 60)],
3765
- [at3 + 760, aeSpark(x, y, 0, 90)]
3766
- ], { ease: "out" }).concat(snap5(`aeSpark${i}`, "transform", aeSpark(x, y, 0, 0), at3 + 780))
3761
+ ([x, y, s, at4], i) => keys(`aeSpark${i}`, "transform", [
3762
+ [at4, aeSpark(x, y, 0, 0)],
3763
+ [at4 + 260, aeSpark(x, y, s, 45)],
3764
+ [at4 + 460, aeSpark(x, y, s * 0.85, 60)],
3765
+ [at4 + 760, aeSpark(x, y, 0, 90)]
3766
+ ], { ease: "out" }).concat(snap5(`aeSpark${i}`, "transform", aeSpark(x, y, 0, 0), at4 + 780))
3767
3767
  ),
3768
3768
  // Exclamation: pops up past its size with stress lines, holds, then ducks away.
3769
3769
  ...keys("aeBang", "transform", [
@@ -4024,12 +4024,12 @@ var RINGS = 3;
4024
4024
  var RING_FROM = 18;
4025
4025
  var RING_TO = 112;
4026
4026
  function ringAnimations(k) {
4027
- const at3 = (phase2) => ({ r: r16(RING_FROM + (RING_TO - RING_FROM) * phase2), o: Math.round((1 - phase2) * 100) / 100 });
4027
+ const at4 = (phase2) => ({ r: r16(RING_FROM + (RING_TO - RING_FROM) * phase2), o: Math.round((1 - phase2) * 100) / 100 });
4028
4028
  const id = `glowRingPulseRing${k}`;
4029
4029
  const phase = k / RINGS;
4030
- const from = at3(phase);
4031
- const end = at3(1);
4032
- const start = at3(0);
4030
+ const from = at4(phase);
4031
+ const end = at4(1);
4032
+ const start = at4(0);
4033
4033
  const out = [
4034
4034
  {
4035
4035
  id,
@@ -4176,7 +4176,7 @@ var explodedLayers = {
4176
4176
  const speed = Math.max(0.1, Number(v.speed));
4177
4177
  const lift = Number(v.lift);
4178
4178
  return {
4179
- ...Object.fromEntries(Object.entries(EL_TIMES).map(([name, ms]) => [`el_${name}`, Math.round(ms / speed)])),
4179
+ ...Object.fromEntries(Object.entries(EL_TIMES).map(([name, ms2]) => [`el_${name}`, Math.round(ms2 / speed)])),
4180
4180
  up: r17(-lift),
4181
4181
  fall: r17(lift * 0.92),
4182
4182
  spread: r17(lift * 0.12)
@@ -4219,7 +4219,7 @@ var pull = (step) => [
4219
4219
  { id: "lampSwitchRig", step, attrs: { transform: [sway(2.5), sway(-1.4)] }, delay: 370, ms: 380, ease: "in-out" },
4220
4220
  { id: "lampSwitchRig", step, attrs: { transform: [sway(-1.4), sway(0)] }, delay: 750, ms: 420, ease: "in-out" }
4221
4221
  ];
4222
- var fade = (step, from, to, delay, ms) => ["lampSwitchCone", "lampSwitchPool", "lampSwitchRoomLit"].map((id) => ({ id, step, attrs: { opacity: [from, to] }, delay, ms, ease: "out" }));
4222
+ var fade = (step, from, to, delay, ms2) => ["lampSwitchCone", "lampSwitchPool", "lampSwitchRoomLit"].map((id) => ({ id, step, attrs: { opacity: [from, to] }, delay, ms: ms2, ease: "out" }));
4223
4223
  var lampSwitch = {
4224
4224
  kind: "vector",
4225
4225
  id: "lamp-switch",
@@ -4282,8 +4282,8 @@ var zapIcon = {
4282
4282
  motion: (() => {
4283
4283
  const sparks = [-55, 25, 160, 215].map((deg, i) => {
4284
4284
  const a = deg * Math.PI / 180;
4285
- const at3 = (r) => [r18(60 + r * Math.cos(a)), r18(60 + r * Math.sin(a))];
4286
- return { id: `s${i}`, from: at3(30), mid: at3(46), to: at3(56) };
4285
+ const at4 = (r) => [r18(60 + r * Math.cos(a)), r18(60 + r * Math.sin(a))];
4286
+ return { id: `s${i}`, from: at4(30), mid: at4(46), to: at4(56) };
4287
4287
  });
4288
4288
  return {
4289
4289
  svg: `<svg viewBox="0 0 120 120"><!-- {{background: color default=#0B1020 label='Background'}} {{bolt: color default=#FACC15 label='Bolt'}} {{glow: color default=#FDE68A label='Glow'}} --><defs><filter id="zapGlow" x="-60%" y="-60%" width="220%" height="220%"><feGaussianBlur stdDeviation="7"/></filter></defs><rect width="120" height="120" fill="{{background}}"/><circle id="burst" cx="60" cy="60" r="20" fill="none" stroke="{{glow}}" stroke-width="6" opacity="0"/><path id="halo" d="${BOLT}" fill="{{glow}}" filter="url(#zapGlow)" opacity="0"/>${sparks.map(({ id, from }) => `<line id="${id}" x1="${from[0]}" y1="${from[1]}" x2="${from[0]}" y2="${from[1]}" stroke="{{glow}}" stroke-width="3" stroke-linecap="round" opacity="0"/>`).join("")}<g id="zap" transform="${about5(60, 60, 1)}"><path id="fill" d="${BOLT}" fill="{{bolt}}" fill-opacity="0"/><path id="outline" d="${BOLT}" fill="none" stroke="{{bolt}}" stroke-width="3.5" stroke-linejoin="round"/></g></svg>`,
@@ -4323,7 +4323,7 @@ var powerButton = {
4323
4323
  ],
4324
4324
  motion: (() => {
4325
4325
  const symbol = (id, stroke2) => `<path id="${id}Arc" d="${POWER_ARC}" fill="none" stroke="${stroke2}" stroke-width="7" stroke-linecap="round"/><path id="${id}Bar" d="${POWER_BAR}" fill="none" stroke="${stroke2}" stroke-width="7" stroke-linecap="round"/>`;
4326
- const press = (step, depth) => [
4326
+ const press2 = (step, depth) => [
4327
4327
  { id: "press", step, attrs: { transform: [about5(80, 80, 1), about5(80, 80, depth)] }, ms: 90, ease: "out" },
4328
4328
  { id: "press", step, attrs: { transform: [about5(80, 80, depth), about5(80, 80, 1)] }, delay: 90, ms: 260, ease: "in-out" }
4329
4329
  ];
@@ -4332,7 +4332,7 @@ var powerButton = {
4332
4332
  svg: `<svg viewBox="0 0 160 160"><!-- {{background: color default=#0B0F19 label='Background'}} {{button: color default=#1A2030 label='Button'}} {{off: color default=#5B6475 label='Off'}} {{on: color default=#22D3EE label='On'}} --><defs><filter id="pbGlow" x="-50%" y="-50%" width="200%" height="200%"><feGaussianBlur stdDeviation="5"/></filter></defs><rect width="160" height="160" fill="{{background}}"/><circle id="halo" cx="80" cy="80" r="52" fill="none" stroke="{{on}}" stroke-width="8" opacity="0" filter="url(#pbGlow)"/><circle id="pulse" cx="80" cy="80" r="50" fill="none" stroke="{{on}}" stroke-width="3" opacity="0"/><g id="press" transform="${about5(80, 80, 1)}"><circle id="disc" cx="80" cy="80" r="48" fill="{{button}}" stroke="{{off}}" stroke-width="2.5"/><g id="symGlow" opacity="0" filter="url(#pbGlow)">${symbol("glow", "{{on}}")}</g>${symbol("sym", "{{off}}")}</g></svg>`,
4333
4333
  animate: [
4334
4334
  // Step 1, on: a press, the symbol and rim light up, the glow ring comes on and a pulse rings out.
4335
- ...press(1, 0.93),
4335
+ ...press2(1, 0.93),
4336
4336
  ...colours(1, "{{off}}", "{{on}}", 60),
4337
4337
  { id: "disc", step: 1, attrs: { stroke: ["{{off}}", "{{on}}"] }, delay: 60, ms: 220 },
4338
4338
  { id: "symGlow", step: 1, attrs: { opacity: [0, 1] }, delay: 60, ms: 260 },
@@ -4341,7 +4341,7 @@ var powerButton = {
4341
4341
  { id: "pulse", step: 1, attrs: { r: [50, 76], opacity: [0.9, 0] }, delay: 80, ms: 560, ease: "out" },
4342
4342
  snap6("pulse", "r", 50, 700, 1),
4343
4343
  // Step 2, off: a press and everything dims back to the start.
4344
- ...press(2, 0.95),
4344
+ ...press2(2, 0.95),
4345
4345
  ...colours(2, "{{on}}", "{{off}}", 40),
4346
4346
  { id: "disc", step: 2, attrs: { stroke: ["{{on}}", "{{off}}"] }, delay: 40, ms: 220 },
4347
4347
  { id: "symGlow", step: 2, attrs: { opacity: [1, 0] }, delay: 40, ms: 220 },
@@ -4498,16 +4498,16 @@ var BURN = 6e3;
4498
4498
  var TOP2 = 92;
4499
4499
  var BURNT = 150;
4500
4500
  var FLICKER = 150;
4501
- var topAt = (ms) => TOP2 + (BURNT - TOP2) * Math.min(ms, BURN) / BURN;
4501
+ var topAt = (ms2) => TOP2 + (BURNT - TOP2) * Math.min(ms2, BURN) / BURN;
4502
4502
  var rand2 = (i, k) => {
4503
4503
  const x = Math.sin(i * 127.1 + k * 311.7) * 43758.5453;
4504
4504
  return x - Math.floor(x);
4505
4505
  };
4506
- var flame = (i, ms) => {
4506
+ var flame = (i, ms2) => {
4507
4507
  const sx = i < 0 ? 1 : 0.9 + 0.16 * rand2(i, 1);
4508
4508
  const sy = i < 0 ? 1 : 0.82 + 0.34 * rand2(i, 2);
4509
4509
  const lean = i < 0 ? 0 : (rand2(i, 3) - 0.5) * 12;
4510
- return `translate(100 ${r28(topAt(ms) - 12)}) skewX(${r28(lean)}) scale(${r28(sx)} ${r28(sy)})`;
4510
+ return `translate(100 ${r28(topAt(ms2) - 12)}) skewX(${r28(lean)}) scale(${r28(sx)} ${r28(sy)})`;
4511
4511
  };
4512
4512
  var FLICKERS = BURN / FLICKER;
4513
4513
  var DRIPS = [
@@ -4658,8 +4658,8 @@ function packGlyph(rows) {
4658
4658
  return `ivec2(${a}, ${b})`;
4659
4659
  }
4660
4660
  var vec3Hex = (hex) => {
4661
- const n12 = parseInt(hex.slice(1), 16);
4662
- const c = [n12 >> 16, n12 >> 8 & 255, n12 & 255].map((v) => +(v / 255).toFixed(3));
4661
+ const n13 = parseInt(hex.slice(1), 16);
4662
+ const c = [n13 >> 16, n13 >> 8 & 255, n13 & 255].map((v) => +(v / 255).toFixed(3));
4663
4663
  return `vec3(${c.map((v) => Number.isInteger(v) ? `${v}.0` : String(v)).join(", ")})`;
4664
4664
  };
4665
4665
  var paletteArray = (name, hexes) => `const vec3 ${name}[${hexes.length}] = vec3[${hexes.length}](
@@ -5592,8 +5592,8 @@ var spaceInvader = {
5592
5592
  const boltA = bitmapPath(BOLT_A, "#", 2);
5593
5593
  const boltB = bitmapPath(BOLT_B, "#", 2);
5594
5594
  const animate = [snap7("bolt", "opacity", 0, 0)];
5595
- MARCH.forEach((at3, k) => {
5596
- animate.push(snap7("invader", "transform", pose3(at3), k * MARCH_MS));
5595
+ MARCH.forEach((at4, k) => {
5596
+ animate.push(snap7("invader", "transform", pose3(at4), k * MARCH_MS));
5597
5597
  animate.push(snap7("body", "d", k % 2 ? frameB : frameA, k * MARCH_MS));
5598
5598
  });
5599
5599
  SHOTS.forEach((k) => {
@@ -5645,19 +5645,19 @@ var pixelHeartHealth = {
5645
5645
  for (let i = 0; i < HEARTS; i++) {
5646
5646
  animate.push(snap7(`f${i}`, "opacity", 1, 0), snap7(`x${i}`, "opacity", 0, 0));
5647
5647
  }
5648
- for (const [i, at3] of LOSSES) {
5648
+ for (const [i, at4] of LOSSES) {
5649
5649
  [0, 1, 0, 1, 0].forEach((on, k) => {
5650
- animate.push(snap7(`x${i}`, "opacity", on ? 0 : 1, at3 + k * 90));
5651
- animate.push(snap7(`f${i}`, "opacity", on, at3 + k * 90));
5650
+ animate.push(snap7(`x${i}`, "opacity", on ? 0 : 1, at4 + k * 90));
5651
+ animate.push(snap7(`f${i}`, "opacity", on, at4 + k * 90));
5652
5652
  });
5653
- animate.push(snap7(`x${i}`, "opacity", 0, at3 + 450));
5654
- animate.push({ id: `h${i}`, attrs: { transform: [heartPose2(i, 1), heartPose2(i, 0.82)] }, delay: at3, ms: 90, ease: "out" });
5655
- animate.push({ id: `h${i}`, attrs: { transform: [heartPose2(i, 0.82), heartPose2(i, 1)] }, delay: at3 + 90, ms: 220, ease: "in-out" });
5653
+ animate.push(snap7(`x${i}`, "opacity", 0, at4 + 450));
5654
+ animate.push({ id: `h${i}`, attrs: { transform: [heartPose2(i, 1), heartPose2(i, 0.82)] }, delay: at4, ms: 90, ease: "out" });
5655
+ animate.push({ id: `h${i}`, attrs: { transform: [heartPose2(i, 0.82), heartPose2(i, 1)] }, delay: at4 + 90, ms: 220, ease: "in-out" });
5656
5656
  }
5657
- for (const [i, at3] of REFILLS) {
5658
- animate.push(snap7(`f${i}`, "opacity", 1, at3));
5659
- animate.push({ id: `h${i}`, attrs: { transform: [heartPose2(i, 1), heartPose2(i, 1.3)] }, delay: at3, ms: 120, ease: "out" });
5660
- animate.push({ id: `h${i}`, attrs: { transform: [heartPose2(i, 1.3), heartPose2(i, 1)] }, delay: at3 + 120, ms: 200, ease: "in-out" });
5657
+ for (const [i, at4] of REFILLS) {
5658
+ animate.push(snap7(`f${i}`, "opacity", 1, at4));
5659
+ animate.push({ id: `h${i}`, attrs: { transform: [heartPose2(i, 1), heartPose2(i, 1.3)] }, delay: at4, ms: 120, ease: "out" });
5660
+ animate.push({ id: `h${i}`, attrs: { transform: [heartPose2(i, 1.3), heartPose2(i, 1)] }, delay: at4 + 120, ms: 200, ease: "in-out" });
5661
5661
  }
5662
5662
  const hearts = Array.from(
5663
5663
  { length: HEARTS },
@@ -5717,7 +5717,7 @@ var chompLoader = {
5717
5717
  var pixelEffects = [pixelText, coinSpin, spaceInvader, pixelHeartHealth, chompLoader];
5718
5718
 
5719
5719
  // src/catalog/pyro.ts
5720
- var round = (n12) => Math.round(n12 * 10) / 10;
5720
+ var round = (n13) => Math.round(n13 * 10) / 10;
5721
5721
  var fireworkBurst = {
5722
5722
  kind: "vector",
5723
5723
  id: "firework-burst",
@@ -5736,19 +5736,19 @@ var fireworkBurst = {
5736
5736
  const a = (i + 0.5) / 10 * Math.PI * 2 - Math.PI / 2;
5737
5737
  return { id: `d${i}`, c: Math.cos(a), s: Math.sin(a), fill: ["{{b}}", "{{c}}", "{{a}}"][i % 3] };
5738
5738
  });
5739
- const at3 = (c, s, r) => [round(c * r), round(s * r)];
5739
+ const at4 = (c, s, r) => [round(c * r), round(s * r)];
5740
5740
  return {
5741
5741
  svg: `<svg viewBox="0 0 200 200"><!-- {{a: color default=#F43F5E label='Colour A'}} {{b: color default=#FACC15 label='Colour B'}} {{c: color default=#38BDF8 label='Colour C'}} {{width: float min=1 max=8 default=4 step=0.5 label='Line width'}} --><g transform="translate(100 100) scale({{size: float min=0.5 max=1.4 default=1 step=0.05 label='Size'}})"><circle id="flash" cx="0" cy="0" r="0" fill="{{b}}" opacity="0"/>${streaks.map(({ id, c, s, stroke: stroke2 }) => {
5742
- const [x, y] = at3(c, s, 12);
5742
+ const [x, y] = at4(c, s, 12);
5743
5743
  return `<line id="${id}" x1="${x}" y1="${y}" x2="${x}" y2="${y}" stroke="${stroke2}" stroke-width="{{width}}" stroke-linecap="round" opacity="0"/>`;
5744
5744
  }).join("")}${dots.map(({ id, c, s, fill }) => `<circle id="${id}" cx="${round(c * 14)}" cy="${round(s * 14)}" r="5" fill="${fill}" opacity="0"/>`).join("")}</g></svg>`,
5745
5745
  animate: [
5746
5746
  { id: "flash", attrs: { r: [0, 22], opacity: [0.9, 0] }, ms: 380, ease: "out" },
5747
5747
  ...streaks.flatMap(({ id, c, s }) => {
5748
- const [x0, y0] = at3(c, s, 12);
5749
- const [x1, y1] = at3(c, s, 40);
5750
- const [x2, y2] = at3(c, s, 62);
5751
- const [x3, y3] = at3(c, s, 74);
5748
+ const [x0, y0] = at4(c, s, 12);
5749
+ const [x1, y1] = at4(c, s, 40);
5750
+ const [x2, y2] = at4(c, s, 62);
5751
+ const [x3, y3] = at4(c, s, 74);
5752
5752
  return [
5753
5753
  { id, attrs: { opacity: [0, 1] }, ms: 40 },
5754
5754
  { id, attrs: { x1: [x0, x1], y1: [y0, y1], x2: [x0, x2], y2: [y0, y2] }, ms: 420, ease: "out" },
@@ -5772,7 +5772,7 @@ var fireworkBurst = {
5772
5772
  var pyroEffects = [fireworkBurst];
5773
5773
 
5774
5774
  // src/catalog/timewarp.ts
5775
- var r19 = (n12) => Math.round(n12 * 10) / 10;
5775
+ var r19 = (n13) => Math.round(n13 * 10) / 10;
5776
5776
  var at2 = (degrees, radius) => {
5777
5777
  const a = degrees * Math.PI / 180;
5778
5778
  return [r19(100 + radius * Math.sin(a)), r19(100 - radius * Math.cos(a))];
@@ -5906,37 +5906,37 @@ function around(c, r, from, to, avoid) {
5906
5906
  if (((avoid - from) % 360 + 360) % 360 < delta) delta -= 360;
5907
5907
  return arc2(c[0], c[1], r, r, from, from + delta);
5908
5908
  }
5909
- function crescent(c1, r111, c2, r2_) {
5909
+ function crescent(c1, r112, c2, r2_) {
5910
5910
  const [dx, dy] = [c2[0] - c1[0], c2[1] - c1[1]];
5911
5911
  const d = Math.hypot(dx, dy);
5912
- const a = (r111 * r111 - r2_ * r2_ + d * d) / (2 * d);
5913
- const h = Math.sqrt(Math.max(0, r111 * r111 - a * a));
5914
- const [px, py] = [c1[0] + a * dx / d, c1[1] + a * dy / d];
5915
- const i1 = [px - h * dy / d, py + h * dx / d];
5916
- const i2 = [px + h * dy / d, py - h * dx / d];
5912
+ const a = (r112 * r112 - r2_ * r2_ + d * d) / (2 * d);
5913
+ const h = Math.sqrt(Math.max(0, r112 * r112 - a * a));
5914
+ const [px2, py2] = [c1[0] + a * dx / d, c1[1] + a * dy / d];
5915
+ const i1 = [px2 - h * dy / d, py2 + h * dx / d];
5916
+ const i2 = [px2 + h * dy / d, py2 - h * dx / d];
5917
5917
  const angle = (c, p) => Math.atan2(p[1] - c[1], p[0] - c[0]) / DEG2;
5918
- const outer = around(c1, r111, angle(c1, i1), angle(c1, i2), angle(c1, c2));
5918
+ const outer = around(c1, r112, angle(c1, i1), angle(c1, i2), angle(c1, c2));
5919
5919
  const inner = around(c2, r2_, angle(c2, i2), angle(c2, i1), angle(c2, c1) + 180);
5920
5920
  return polyline3([...outer, ...inner.slice(1, -1)], true);
5921
5921
  }
5922
5922
  var HEART6 = "M12 20.5 C12 20.5 3 14.8 3 8.8 C3 6 5.2 3.8 8 3.8 C9.7 3.8 11.2 4.7 12 6.1 C12.8 4.7 14.3 3.8 16 3.8 C18.8 3.8 21 6 21 8.8 C21 14.8 12 20.5 12 20.5 Z";
5923
5923
  var placed = (d, cx, cy, s) => mapPath(d, (p) => [cx + (p[0] - 12) * s, cy + (p[1] - 12) * s]);
5924
- function fitted(d, box, points) {
5924
+ function fitted(d, box2, points) {
5925
5925
  const outline4 = morphSequence([d], { points })[0];
5926
5926
  const pts = pointsOf(outline4);
5927
5927
  const xs = pts.map((p) => p[0]);
5928
5928
  const ys = pts.map((p) => p[1]);
5929
5929
  const [x0, y0] = [Math.min(...xs), Math.min(...ys)];
5930
5930
  const [w, h] = [Math.max(...xs) - x0, Math.max(...ys) - y0];
5931
- const s = Math.min(box[2] / Math.max(w, 1e-6), box[3] / Math.max(h, 1e-6));
5932
- const [ox, oy] = [box[0] + (box[2] - w * s) / 2, box[1] + (box[3] - h * s) / 2];
5931
+ const s = Math.min(box2[2] / Math.max(w, 1e-6), box2[3] / Math.max(h, 1e-6));
5932
+ const [ox, oy] = [box2[0] + (box2[2] - w * s) / 2, box2[1] + (box2[3] - h * s) / 2];
5933
5933
  return mapPath(outline4, (p) => [ox + (p[0] - x0) * s, oy + (p[1] - y0) * s]);
5934
5934
  }
5935
- function fittedOr(d, fallback, box, points) {
5935
+ function fittedOr(d, fallback, box2, points) {
5936
5936
  try {
5937
- return fitted(String(d), box, points);
5937
+ return fitted(String(d), box2, points);
5938
5938
  } catch {
5939
- return fitted(fallback, box, points);
5939
+ return fitted(fallback, box2, points);
5940
5940
  }
5941
5941
  }
5942
5942
  var SHAPE_FROM = "M30 12 H70 A18 18 0 0 1 88 30 V70 A18 18 0 0 1 70 88 H30 A18 18 0 0 1 12 70 V30 A18 18 0 0 1 30 12 Z";
@@ -6007,12 +6007,12 @@ function iconMorph(pair2) {
6007
6007
  return iconMorphs.get(pair2);
6008
6008
  }
6009
6009
  var pose2 = (turn, scale) => `translate(60 60) rotate(${turn}) scale(${scale}) translate(-12 -12)`;
6010
- var toggle = (at3, d, fill, turn) => [
6011
- { id: "glyph", attrs: { d }, delay: at3, ms: 420, ease: "in-out" },
6012
- { id: "icon", attrs: { transform: [pose2(0, 2.6), pose2(turn, 2.15)] }, delay: at3, ms: 170, ease: "out" },
6013
- { id: "icon", attrs: { transform: [pose2(turn, 2.15), pose2(-turn * 0.3, 2.85)] }, delay: at3 + 170, ms: 190, ease: "in-out" },
6014
- { id: "icon", attrs: { transform: [pose2(-turn * 0.3, 2.85), pose2(0, 2.6)] }, delay: at3 + 360, ms: 260, ease: "in-out" },
6015
- { id: "button", attrs: { fill }, delay: at3, ms: 320, ease: "in-out" }
6010
+ var toggle = (at4, d, fill, turn) => [
6011
+ { id: "glyph", attrs: { d }, delay: at4, ms: 420, ease: "in-out" },
6012
+ { id: "icon", attrs: { transform: [pose2(0, 2.6), pose2(turn, 2.15)] }, delay: at4, ms: 170, ease: "out" },
6013
+ { id: "icon", attrs: { transform: [pose2(turn, 2.15), pose2(-turn * 0.3, 2.85)] }, delay: at4 + 170, ms: 190, ease: "in-out" },
6014
+ { id: "icon", attrs: { transform: [pose2(-turn * 0.3, 2.85), pose2(0, 2.6)] }, delay: at4 + 360, ms: 260, ease: "in-out" },
6015
+ { id: "button", attrs: { fill }, delay: at4, ms: 320, ease: "in-out" }
6016
6016
  ];
6017
6017
  var iconToggle = {
6018
6018
  kind: "vector",
@@ -6130,8 +6130,8 @@ var particleMorph = {
6130
6130
  ],
6131
6131
  // The dots sit on the morphed outlines: point k of one shape flies to point k of the other.
6132
6132
  derived: (v) => {
6133
- const box = [40, 40, 120, 120];
6134
- const [a, b] = morphPaths(fittedOr(v.from, PARTICLE_FROM, box, 96), fittedOr(v.to, PARTICLE_TO, box, 96), { points: DOTS4 });
6133
+ const box2 = [40, 40, 120, 120];
6134
+ const [a, b] = morphPaths(fittedOr(v.from, PARTICLE_FROM, box2, 96), fittedOr(v.to, PARTICLE_TO, box2, 96), { points: DOTS4 });
6135
6135
  const [pa, pb] = [pointsOf(a), pointsOf(b)];
6136
6136
  const out = { a, b };
6137
6137
  const scatter = Number(v.scatter);
@@ -6154,11 +6154,11 @@ var particleMorph = {
6154
6154
  motion: (() => {
6155
6155
  const back = 3e3;
6156
6156
  const spread = DOTS4 * DOT_STAGGER;
6157
- const leg = (i, at3, from, mid, to, colours) => [
6158
- { id: `p${i}`, attrs: { cx: [n9(`${from}x${i}`), n9(`${mid}x${i}`)], cy: [n9(`${from}y${i}`), n9(`${mid}y${i}`)] }, delay: at3 + i * DOT_STAGGER, ms: 450, ease: "out" },
6159
- { id: `p${i}`, attrs: { cx: [n9(`${mid}x${i}`), n9(`${to}x${i}`)], cy: [n9(`${mid}y${i}`), n9(`${to}y${i}`)], fill: colours }, delay: at3 + 450 + i * DOT_STAGGER, ms: 650, ease: "in-out" }
6157
+ const leg = (i, at4, from, mid, to, colours) => [
6158
+ { id: `p${i}`, attrs: { cx: [n9(`${from}x${i}`), n9(`${mid}x${i}`)], cy: [n9(`${from}y${i}`), n9(`${mid}y${i}`)] }, delay: at4 + i * DOT_STAGGER, ms: 450, ease: "out" },
6159
+ { id: `p${i}`, attrs: { cx: [n9(`${mid}x${i}`), n9(`${to}x${i}`)], cy: [n9(`${mid}y${i}`), n9(`${to}y${i}`)], fill: colours }, delay: at4 + 450 + i * DOT_STAGGER, ms: 650, ease: "in-out" }
6160
6160
  ];
6161
- const fade2 = (id, at3, show) => ({ id, attrs: { opacity: show ? [0, n9("ghost")] : [n9("ghost"), 0] }, delay: at3, ms: 450, ease: "in-out" });
6161
+ const fade2 = (id, at4, show) => ({ id, attrs: { opacity: show ? [0, n9("ghost")] : [n9("ghost"), 0] }, delay: at4, ms: 450, ease: "in-out" });
6162
6162
  return {
6163
6163
  svg: `<svg viewBox="0 0 200 200"><!-- {{from: text default='${PARTICLE_FROM}' label='From path'}} {{to: text default='${PARTICLE_TO}' label='To path'}} {{fromColor: color default=#F472B6 label='From colour'}} {{toColor: color default=#FBBF24 label='To colour'}} {{background: color default=#0C0A1D label='Background'}} {{size: float min=0.8 max=6 default=2.6 step=0.1 label='Dot size'}} {{scatter: float min=0 max=80 default=34 step=1 label='Scatter'}} {{ghost: float min=0 max=1 default=0.16 label='Shape fill'}} --><rect width="200" height="200" fill="{{background}}"/><path id="ghostA" d="{{a}}" fill="{{fromColor}}" fill-rule="evenodd" opacity="{{ghost}}"/><path id="ghostB" d="{{b}}" fill="{{toColor}}" fill-rule="evenodd" opacity="0"/>${Array.from(
6164
6164
  { length: DOTS4 },
@@ -6226,8 +6226,8 @@ var digitMorph = {
6226
6226
  out[`x${j}`] = r210(left + Math.max(0, place) * 22 * scale);
6227
6227
  const glyphs = values.map((x) => {
6228
6228
  const text = String(x);
6229
- const at3 = text.length - (used - place);
6230
- return place >= 0 && at3 >= 0 ? Number(text[at3]) : -1;
6229
+ const at4 = text.length - (used - place);
6230
+ return place >= 0 && at4 >= 0 ? Number(text[at4]) : -1;
6231
6231
  });
6232
6232
  const paths = morphSequence(glyphs.map((g) => g < 0 ? BLANK : DIGITS[g]), { points: 64 });
6233
6233
  for (let k = 0; k <= MAX_STEPS; k++) {
@@ -18442,26 +18442,26 @@ float coverage(vec2 p, float radius) {
18442
18442
  }
18443
18443
  return sum / 16.0;
18444
18444
  }`.trim();
18445
- function goldenTaps(n12) {
18446
- const taps = Array.from({ length: n12 }, (_, i) => {
18447
- const r = Math.sqrt((i + 0.5) / n12);
18445
+ function goldenTaps(n13) {
18446
+ const taps = Array.from({ length: n13 }, (_, i) => {
18447
+ const r = Math.sqrt((i + 0.5) / n13);
18448
18448
  const a = i * 2.39996;
18449
18449
  return `vec2(${(r * Math.cos(a)).toFixed(5)}, ${(r * Math.sin(a)).toFixed(5)})`;
18450
18450
  });
18451
- return `const vec2 GOLDEN${n12}[${n12}] = vec2[${n12}](${taps.join(", ")});`;
18451
+ return `const vec2 GOLDEN${n13}[${n13}] = vec2[${n13}](${taps.join(", ")});`;
18452
18452
  }
18453
- function coverageTaps(name, n12, blur) {
18453
+ function coverageTaps(name, n13, blur) {
18454
18454
  return `
18455
- ${goldenTaps(n12)}
18455
+ ${goldenTaps(n13)}
18456
18456
  float ${name}(vec2 p, float radius) {
18457
18457
  float sum = 0.0;
18458
18458
  if (gMaskPlace.w < 0.5) {
18459
- for (int i = 0; i < ${n12}; i++) sum += shapeAt(p + radius * GOLDEN${n12}[i], radius * ${blur.toFixed(2)});
18459
+ for (int i = 0; i < ${n13}; i++) sum += shapeAt(p + radius * GOLDEN${n13}[i], radius * ${blur.toFixed(2)});
18460
18460
  } else {
18461
18461
  float lod = log2(max(radius * ${blur.toFixed(2)} * gMaskPlace.z, 1.0));
18462
- for (int i = 0; i < ${n12}; i++) sum += shapeLod(p + radius * GOLDEN${n12}[i], lod);
18462
+ for (int i = 0; i < ${n13}; i++) sum += shapeLod(p + radius * GOLDEN${n13}[i], lod);
18463
18463
  }
18464
- return sum / ${n12}.0;
18464
+ return sum / ${n13}.0;
18465
18465
  }`.trim();
18466
18466
  }
18467
18467
  var LITE_TAPS = 10;
@@ -21538,6 +21538,8 @@ var EFFECT_CATEGORIES = [
21538
21538
  "distortion",
21539
21539
  "audio",
21540
21540
  "scroll",
21541
+ "broadcast",
21542
+ "annotation",
21541
21543
  "transition",
21542
21544
  "loader",
21543
21545
  "data",
@@ -21655,7 +21657,7 @@ function numberControl(name, value, min, max, label, step) {
21655
21657
  }
21656
21658
 
21657
21659
  // src/catalog/text.ts
21658
- var r110 = (n12) => Math.round(n12 * 10) / 10;
21660
+ var r110 = (n13) => Math.round(n13 * 10) / 10;
21659
21661
  function fit5(text, width, max, ems = textWidthEm(text)) {
21660
21662
  const e = Math.max(0.6, ems);
21661
21663
  const size = Math.min(max, width / e);
@@ -22023,10 +22025,10 @@ var wordCycle = {
22023
22025
  (_, 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>`
22024
22026
  ).join("")}</g></g><rect id="under" x="{{wx}}" y="{{underY}}" width="{{len0}}" height="4" rx="2" fill="{{accent}}"/></svg>`,
22025
22027
  animate: Array.from({ length: CYCLE_WORDS }, (_, i) => {
22026
- const at3 = { delay: (i + 1) * SHOW_MS + i * ROLL_MS, ms: ROLL_MS, ease: "in-out" };
22028
+ const at4 = { delay: (i + 1) * SHOW_MS + i * ROLL_MS, ms: ROLL_MS, ease: "in-out" };
22027
22029
  return [
22028
- { id: "reel", attrs: { transform: [`translate(0 -{{o${i}}})`, `translate(0 -{{o${i + 1}}})`] }, ...at3 },
22029
- { id: "under", attrs: { width: [n11(`len${i}`), n11(`len${i + 1}`)] }, ...at3 }
22030
+ { id: "reel", attrs: { transform: [`translate(0 -{{o${i}}})`, `translate(0 -{{o${i + 1}}})`] }, ...at4 },
22031
+ { id: "under", attrs: { width: [n11(`len${i}`), n11(`len${i + 1}`)] }, ...at4 }
22030
22032
  ];
22031
22033
  }).flat()
22032
22034
  }
@@ -22151,7 +22153,7 @@ var extrudedText = {
22151
22153
  const { size, length, left } = fit5(String(v.text), room(v, 320), 96);
22152
22154
  const d = 0.36 * Number(v.depth);
22153
22155
  const step = size * 0.03 * Number(v.depth);
22154
- const at3 = (deg, i) => {
22156
+ const at4 = (deg, i) => {
22155
22157
  const a = deg * Math.PI / 180;
22156
22158
  return `translate(${r110(Math.cos(a) * step * i)} ${r110(Math.sin(a) * step * i)})`;
22157
22159
  };
@@ -22165,8 +22167,8 @@ var extrudedText = {
22165
22167
  const i = LAYERS - k;
22166
22168
  return [
22167
22169
  [`fill${i}`, mixHex3(v.side, v.shade, i / LAYERS)],
22168
- [`a${i}`, at3(EXTRUDE_FROM, i)],
22169
- [`b${i}`, at3(EXTRUDE_TO, i)]
22170
+ [`a${i}`, at4(EXTRUDE_FROM, i)],
22171
+ [`b${i}`, at4(EXTRUDE_TO, i)]
22170
22172
  ];
22171
22173
  }).flat()
22172
22174
  )
@@ -22199,6 +22201,1216 @@ var textEffects = [
22199
22201
  extrudedText
22200
22202
  ];
22201
22203
 
22204
+ // src/catalog/broadcast.ts
22205
+ var r211 = (v) => Math.round(v * 100) / 100;
22206
+ var r111 = (v) => Math.round(v * 10) / 10;
22207
+ var n12 = (name) => `{{${name}}}`;
22208
+ var SANS9 = `'Inter', 'Helvetica Neue', Arial, system-ui, sans-serif`;
22209
+ function fit6(text, width, max) {
22210
+ const em = Math.max(0.6, textWidthEm(String(text ?? "")));
22211
+ const size = Math.min(max, width / em);
22212
+ return { size: r111(size), width: r111(em * size) };
22213
+ }
22214
+ var ms = (seconds) => Math.round(Math.max(0, Number(seconds) || 0) * 1e3);
22215
+ var holdControl = (seconds, label = "Hold (s)") => `{{hold: float min=0.5 max=20 default=${seconds} step=0.5 label='${label}'}}`;
22216
+ var after = (k) => n12(`out${k}`);
22217
+ function outTimes(motion, start) {
22218
+ const out = {};
22219
+ for (const [, k] of JSON.stringify(motion).matchAll(/\{\{out(\d+)\}\}/g)) out[`out${k}`] = Math.round(start + Number(k));
22220
+ return out;
22221
+ }
22222
+ var holds2 = (id, attr, keys2) => keys2.map(([delay, value]) => ({ id, attrs: { [attr]: [value, value] }, delay, ms: 0 }));
22223
+ function pop(id, delay, over = 1.12, ms2 = 300) {
22224
+ return [
22225
+ { id, attrs: { transform: ["scale(0)", `scale(${over})`] }, delay, ms: ms2, ease: "out" },
22226
+ { id, attrs: { transform: [`scale(${over})`, "scale(1)"] }, delay: delay + ms2, ms: 160, ease: "in-out" }
22227
+ ];
22228
+ }
22229
+ var MARK = "M12 1.5 L21.5 7 V17 L12 22.5 L2.5 17 V7 Z M12 6.5 L7 9.4 V14.6 L12 17.5 L17 14.6 V9.4 Z";
22230
+ var NUMBER2 = /-?(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?/gi;
22231
+ function placeMark(d, box2, fallback = MARK) {
22232
+ for (const path of [String(d ?? ""), fallback]) {
22233
+ let nums;
22234
+ try {
22235
+ nums = (morphSequence([path], { points: 128 })[0].match(NUMBER2) ?? []).map(Number);
22236
+ } catch {
22237
+ continue;
22238
+ }
22239
+ const xs = nums.filter((_, i) => i % 2 === 0);
22240
+ const ys = nums.filter((_, i) => i % 2 === 1);
22241
+ const [x0, x1, y0, y1] = [Math.min(...xs), Math.max(...xs), Math.min(...ys), Math.max(...ys)];
22242
+ const [w, h] = [x1 - x0, y1 - y0];
22243
+ if (![x0, x1, y0, y1].every(Number.isFinite) || Math.max(w, h) < 1e-6) continue;
22244
+ const s = Math.min(box2[2] / Math.max(w, 1e-6), box2[3] / Math.max(h, 1e-6));
22245
+ const ox = box2[0] + (box2[2] - w * s) / 2;
22246
+ const oy = box2[1] + (box2[3] - h * s) / 2;
22247
+ return { d: path, transform: `translate(${r211(ox)} ${r211(oy)}) scale(${+s.toPrecision(4)}) translate(${r211(-x0)} ${r211(-y0)})`, scale: s };
22248
+ }
22249
+ return { d: fallback, transform: "", scale: 1 };
22250
+ }
22251
+ var markControl = (label = "Logo path") => `{{logo: text default='${MARK}' label='${label}'}}`;
22252
+ var lowerThirdMotion = {
22253
+ svg: `<svg viewBox="0 0 1920 1080"><!-- {{name: text default='Ada Lovelace' label='Name'}} {{title: text default='Mathematician and writer' label='Title'}} {{plate: color default=#0F2742 label='Name bar'}} {{ink: color default=#FFFFFF label='Name colour'}} {{accent: color default=#3B82F6 label='Accent stripe'}} {{titlePlate: color default=#FFFFFF label='Title bar'}} {{titleInk: color default=#0F2742 label='Title colour'}} {{plateOpacity: float min=0 max=1 default=1 step=0.05 label='Bar opacity'}} {{skew: float min=-20 max=20 default=0 step=1 label='Slant'}} {{shadow: float min=0 max=1 default=0.35 step=0.05 label='Shadow'}} ${holdControl(3)} {{font: text default="${SANS9}" label='Font'}} --><defs><clipPath id="lowerThirdNameClip"><rect x="206" y="792" width="1600" height="96"/></clipPath><clipPath id="lowerThirdTitleClip"><rect x="206" y="888" width="1600" height="60"/></clipPath><filter id="lowerThirdShadow" x="-10%" y="-40%" width="120%" height="180%"><feDropShadow dx="0" dy="6" stdDeviation="10" flood-color="#000000" flood-opacity="{{shadow}}"/></filter></defs><g transform="translate(192 870) skewX({{skew}}) translate(-192 -870)" filter="url(#lowerThirdShadow)"><rect id="lowerThirdNameBar" x="206" y="792" width="0" height="96" fill="{{plate}}" fill-opacity="{{plateOpacity}}"/><rect id="lowerThirdTitleBar" x="206" y="888" width="0" height="60" fill="{{titlePlate}}" fill-opacity="{{plateOpacity}}"/><g clip-path="url(#lowerThirdNameClip)"><text id="lowerThirdName" x="238" y="{{nameBase}}" transform="translate({{nameHide}} 0)" font-family="{{font}}" font-size="{{nameSize}}" font-weight="700" textLength="{{nameW}}" lengthAdjust="spacingAndGlyphs" fill="{{ink}}">{{name}}</text></g><g clip-path="url(#lowerThirdTitleClip)"><text id="lowerThirdTitle" x="230" y="{{titleBase}}" transform="translate({{titleHide}} 0)" font-family="{{font}}" font-size="{{titleSize}}" font-weight="500" textLength="{{titleW}}" lengthAdjust="spacingAndGlyphs" fill="{{titleInk}}">{{title}}</text></g><rect id="lowerThirdStripe" x="192" y="870" width="14" height="0" fill="{{accent}}"/></g></svg>`,
22254
+ animate: [
22255
+ { id: "lowerThirdStripe", attrs: { y: [870, 792], height: [0, 156] }, ms: 350, ease: "out" },
22256
+ { id: "lowerThirdNameBar", attrs: { width: [0, n12("nameBar")] }, delay: 150, ms: 500, ease: "out" },
22257
+ { id: "lowerThirdName", attrs: { transform: ["translate({{nameHide}} 0)", "translate(0 0)"] }, delay: 300, ms: 550, ease: "out" },
22258
+ { id: "lowerThirdTitleBar", attrs: { width: [0, n12("titleBar")] }, delay: 400, ms: 450, ease: "out" },
22259
+ { id: "lowerThirdTitle", attrs: { transform: ["translate({{titleHide}} 0)", "translate(0 0)"] }, delay: 500, ms: 500, ease: "out" },
22260
+ { id: "lowerThirdName", attrs: { transform: ["translate(0 0)", "translate({{nameHide}} 0)"] }, delay: after(0), ms: 400 },
22261
+ { id: "lowerThirdTitle", attrs: { transform: ["translate(0 0)", "translate({{titleHide}} 0)"] }, delay: after(80), ms: 400 },
22262
+ { id: "lowerThirdTitleBar", attrs: { width: [n12("titleBar"), 0] }, delay: after(250), ms: 400 },
22263
+ { id: "lowerThirdNameBar", attrs: { width: [n12("nameBar"), 0] }, delay: after(300), ms: 400 },
22264
+ { id: "lowerThirdStripe", attrs: { y: [792, 870], height: [156, 0] }, delay: after(650), ms: 300 }
22265
+ ]
22266
+ };
22267
+ var lowerThird = {
22268
+ kind: "vector",
22269
+ id: "lower-third",
22270
+ name: "Lower third",
22271
+ description: "A name bar and a title bar sliding out from an accent stripe in the lower left, holding, then sliding back.",
22272
+ category: "broadcast",
22273
+ tags: ["broadcast", "lower-third", "name", "caption", "title", "news", "overlay"],
22274
+ hold: 300,
22275
+ presets: [
22276
+ { name: "Corporate", values: { plate: [0.06, 0.15, 0.26], accent: [0.23, 0.51, 0.96], titlePlate: [1, 1, 1], titleInk: [0.06, 0.15, 0.26] } },
22277
+ { name: "News", values: { plate: [0.73, 0.11, 0.11], accent: [0.98, 0.8, 0.08], titlePlate: [0.07, 0.09, 0.15], titleInk: [1, 1, 1] } },
22278
+ { name: "Sport", values: { plate: [0.07, 0.07, 0.07], accent: [0.64, 0.9, 0.21], titlePlate: [0.64, 0.9, 0.21], titleInk: [0.07, 0.07, 0.07], skew: -12 } },
22279
+ { name: "Minimal", values: { plateOpacity: 0, accent: [1, 1, 1], titleInk: [1, 1, 1], shadow: 0.7 } }
22280
+ ],
22281
+ derived: (v) => {
22282
+ const name = fit6(v.name, 1380, 56);
22283
+ const title = fit6(v.title, 1380, 32);
22284
+ return {
22285
+ ...outTimes(lowerThirdMotion, 1e3 + ms(v.hold)),
22286
+ nameSize: name.size,
22287
+ nameW: name.width,
22288
+ nameBase: r111(840 + name.size * 0.35),
22289
+ nameBar: r111(name.width + 64),
22290
+ nameHide: -r111(name.width + 80),
22291
+ titleSize: title.size,
22292
+ titleW: title.width,
22293
+ titleBase: r111(918 + title.size * 0.35),
22294
+ titleBar: r111(title.width + 48),
22295
+ titleHide: -r111(title.width + 60)
22296
+ };
22297
+ },
22298
+ motion: lowerThirdMotion
22299
+ };
22300
+ var lowerThirdStackedMotion = {
22301
+ svg: `<svg viewBox="0 0 1920 1080"><!-- {{name: text default='Grace Hopper' label='Name'}} {{title: text default='Rear Admiral \xB7 Computer scientist' label='Title'}} ${markControl()} {{plate: color default=#FFFFFF label='Panel'}} {{ink: color default=#111827 label='Name colour'}} {{titleInk: color default=#4B5563 label='Title colour'}} {{accent: color default=#7C3AED label='Logo tile'}} {{logoInk: color default=#FFFFFF label='Logo colour'}} {{corner: float min=0 max=40 default=10 step=1 label='Corner radius'}} {{shadow: float min=0 max=1 default=0.3 step=0.05 label='Shadow'}} ${holdControl(3)} {{font: text default="${SANS9}" label='Font'}} --><defs><clipPath id="lowerThirdStackedNameClip"><rect x="360" y="792" width="1400" height="76"/></clipPath><clipPath id="lowerThirdStackedTitleClip"><rect x="360" y="868" width="1400" height="68"/></clipPath><filter id="lowerThirdStackedShadow" x="-10%" y="-40%" width="120%" height="180%"><feDropShadow dx="0" dy="8" stdDeviation="12" flood-color="#000000" flood-opacity="{{shadow}}"/></filter></defs><g filter="url(#lowerThirdStackedShadow)"><rect id="lowerThirdStackedPanel" x="340" y="792" width="0" height="144" rx="{{corner}}" fill="{{plate}}"/><g clip-path="url(#lowerThirdStackedNameClip)"><text id="lowerThirdStackedName" x="396" y="{{nameBase}}" transform="translate(0 80)" font-family="{{font}}" font-size="{{nameSize}}" font-weight="700" textLength="{{nameW}}" lengthAdjust="spacingAndGlyphs" fill="{{ink}}">{{name}}</text></g><line id="lowerThirdStackedRule" x1="396" y1="868" x2="{{ruleEnd}}" y2="868" stroke="{{accent}}" stroke-width="3" stroke-opacity="0.6"/><g clip-path="url(#lowerThirdStackedTitleClip)"><text id="lowerThirdStackedTitle" x="396" y="{{titleBase}}" transform="translate(0 70)" font-family="{{font}}" font-size="{{titleSize}}" font-weight="500" textLength="{{titleW}}" lengthAdjust="spacingAndGlyphs" fill="{{titleInk}}">{{title}}</text></g><g transform="translate(276 864)"><g id="lowerThirdStackedTile" transform="scale(0)"><rect x="-84" y="-84" width="168" height="168" rx="{{corner}}" fill="{{accent}}"/><path d="{{markD}}" transform="{{markT}}" fill="{{logoInk}}" fill-rule="evenodd"/></g></g></g></svg>`,
22302
+ animate: [
22303
+ ...pop("lowerThirdStackedTile", 0, 1.12),
22304
+ { id: "lowerThirdStackedPanel", attrs: { width: [0, n12("panel")] }, delay: 300, ms: 550, ease: "out" },
22305
+ { id: "lowerThirdStackedName", attrs: { transform: ["translate(0 80)", "translate(0 0)"] }, delay: 550, ms: 450, ease: "out" },
22306
+ { id: "lowerThirdStackedRule", draw: true, delay: 650, ms: 450, ease: "out" },
22307
+ { id: "lowerThirdStackedTitle", attrs: { transform: ["translate(0 70)", "translate(0 0)"] }, delay: 700, ms: 450, ease: "out" },
22308
+ { id: "lowerThirdStackedTitle", attrs: { transform: ["translate(0 0)", "translate(0 70)"] }, delay: after(0), ms: 350 },
22309
+ { id: "lowerThirdStackedName", attrs: { transform: ["translate(0 0)", "translate(0 80)"] }, delay: after(80), ms: 350 },
22310
+ { id: "lowerThirdStackedRule", draw: [1, 0], delay: after(100), ms: 300 },
22311
+ { id: "lowerThirdStackedPanel", attrs: { width: [n12("panel"), 0] }, delay: after(300), ms: 450 },
22312
+ { id: "lowerThirdStackedTile", attrs: { transform: ["scale(1)", "scale(0)"] }, delay: after(650), ms: 300 }
22313
+ ]
22314
+ };
22315
+ var lowerThirdStacked = {
22316
+ kind: "vector",
22317
+ id: "lower-third-stacked",
22318
+ name: "Stacked lower third",
22319
+ description: "A logo tile popping in, then a panel sliding out from behind it with a name over a title, divided by a drawn rule.",
22320
+ category: "broadcast",
22321
+ tags: ["broadcast", "lower-third", "name", "logo", "caption", "overlay"],
22322
+ hold: 300,
22323
+ presets: [
22324
+ { name: "Clean", values: { plate: [1, 1, 1], ink: [0.07, 0.09, 0.15], accent: [0.49, 0.23, 0.93] } },
22325
+ { name: "Night", values: { plate: [0.07, 0.09, 0.15], ink: [1, 1, 1], titleInk: [0.62, 0.66, 0.75], accent: [0.13, 0.83, 0.93], logoInk: [0.07, 0.09, 0.15] } },
22326
+ { name: "News", values: { plate: [1, 1, 1], ink: [0.07, 0.07, 0.07], accent: [0.8, 0.1, 0.12], corner: 0 } }
22327
+ ],
22328
+ derived: (v) => {
22329
+ const name = fit6(v.name, 1300, 52);
22330
+ const title = fit6(v.title, 1300, 30);
22331
+ const mark = placeMark(v.logo, [-52, -52, 104, 104]);
22332
+ const inner = Math.max(name.width, title.width, 160);
22333
+ return {
22334
+ ...outTimes(lowerThirdStackedMotion, 1150 + ms(v.hold)),
22335
+ nameSize: name.size,
22336
+ nameW: name.width,
22337
+ nameBase: r111(834 + name.size * 0.35),
22338
+ titleSize: title.size,
22339
+ titleW: title.width,
22340
+ titleBase: r111(902 + title.size * 0.35),
22341
+ ruleEnd: r111(396 + inner),
22342
+ panel: r111(inner + 92),
22343
+ markD: mark.d,
22344
+ markT: mark.transform
22345
+ };
22346
+ },
22347
+ motion: lowerThirdStackedMotion
22348
+ };
22349
+ var titleCardMotion = {
22350
+ svg: `<svg viewBox="0 0 1920 1080"><!-- {{title: text default='The Long Way Home' label='Title'}} {{subtitle: text default='A film about getting there' label='Subtitle'}} {{background: color default=#0B1020 label='Background'}} {{ink: color default=#F8FAFC label='Title colour'}} {{muted: color default=#94A3B8 label='Subtitle colour'}} {{accent: color default=#F59E0B label='Accent'}} {{wipe: bool default=true label='Wipe out (else fade)'}} ${holdControl(3)} {{font: text default="${SANS9}" label='Font'}} --><defs><radialGradient id="titleCardGlow" cx="0.5" cy="0.5" r="0.6"><stop offset="0" stop-color="{{accent}}" stop-opacity="0.18"/><stop offset="1" stop-color="{{accent}}" stop-opacity="0"/></radialGradient></defs><rect width="1920" height="1080" fill="{{background}}"/><rect width="1920" height="1080" fill="url(#titleCardGlow)"/><g id="titleCardAll" opacity="1"><g id="titleCardTitle" opacity="0" transform="translate(0 40)"><text x="960" y="{{titleBase}}" text-anchor="middle" font-family="{{font}}" font-size="{{titleSize}}" font-weight="800" textLength="{{titleW}}" lengthAdjust="spacingAndGlyphs" fill="{{ink}}">{{title}}</text></g><line id="titleCardLineL" x1="960" y1="{{lineY}}" x2="{{lineL}}" y2="{{lineY}}" stroke="{{accent}}" stroke-width="5" stroke-linecap="round"/><line id="titleCardLineR" x1="960" y1="{{lineY}}" x2="{{lineR}}" y2="{{lineY}}" stroke="{{accent}}" stroke-width="5" stroke-linecap="round"/><g id="titleCardSubtitle" opacity="0" transform="translate(0 30)"><text x="960" y="{{subBase}}" text-anchor="middle" font-family="{{font}}" font-size="{{subSize}}" font-weight="400" letter-spacing="1" textLength="{{subW}}" lengthAdjust="spacingAndGlyphs" fill="{{muted}}">{{subtitle}}</text></g></g><g id="titleCardWipe" transform="translate(-2000 0)"><rect x="0" y="0" width="1920" height="1080" fill="{{background}}"/><rect x="1920" y="0" width="80" height="1080" fill="{{accent}}"/></g></svg>`,
22351
+ animate: [
22352
+ { id: "titleCardTitle", attrs: { opacity: [0, 1], transform: ["translate(0 40)", "translate(0 0)"] }, ms: 800, ease: "out" },
22353
+ { id: "titleCardLineL", draw: true, delay: 450, ms: 650, ease: "in-out" },
22354
+ { id: "titleCardLineR", draw: true, delay: 450, ms: 650, ease: "in-out" },
22355
+ { id: "titleCardSubtitle", attrs: { opacity: [0, 1], transform: ["translate(0 30)", "translate(0 0)"] }, delay: 750, ms: 700, ease: "out" },
22356
+ { id: "titleCardWipe", attrs: { transform: ["translate(-2000 0)", "translate({{wipeTo}} 0)"] }, delay: after(0), ms: 700 },
22357
+ { id: "titleCardAll", attrs: { opacity: [1, n12("fadeTo")] }, delay: after(0), ms: 700 }
22358
+ ]
22359
+ };
22360
+ var titleCard = {
22361
+ kind: "vector",
22362
+ id: "title-card",
22363
+ name: "Title card",
22364
+ description: "A big centred title rising in, a line drawing out from the middle beneath it and a subtitle following, then a wipe or fade out.",
22365
+ category: "broadcast",
22366
+ tags: ["broadcast", "title", "card", "intro", "headline", "film"],
22367
+ hold: 300,
22368
+ presets: [
22369
+ { name: "Cinema", values: { background: [0.02, 0.02, 0.03], ink: [0.96, 0.88, 0.7], accent: [0.85, 0.65, 0.25], muted: [0.6, 0.56, 0.5] } },
22370
+ { name: "Clean", values: { background: [0.98, 0.98, 0.97], ink: [0.07, 0.09, 0.15], muted: [0.4, 0.45, 0.52], accent: [0.15, 0.39, 0.92], wipe: false } },
22371
+ { name: "News", values: { background: [0.04, 0.09, 0.2], ink: [1, 1, 1], muted: [0.75, 0.8, 0.9], accent: [0.86, 0.15, 0.15] } },
22372
+ { name: "Sport", values: { background: [0.05, 0.05, 0.05], ink: [1, 1, 1], muted: [0.64, 0.9, 0.21], accent: [0.64, 0.9, 0.21] } }
22373
+ ],
22374
+ derived: (v) => {
22375
+ const title = fit6(v.title, 1440, 132);
22376
+ const sub = fit6(v.subtitle, 1200, 46);
22377
+ const titleBase = title.size * 0.72;
22378
+ const lineY = titleBase + title.size * 0.25 + 36;
22379
+ const subBase = lineY + 40 + sub.size * 0.75;
22380
+ const shift = 540 - (subBase + sub.size * 0.25) / 2;
22381
+ const half = Math.min(600, Math.max(140, Math.max(title.width, sub.width) * 0.3));
22382
+ return {
22383
+ ...outTimes(titleCardMotion, 1450 + ms(v.hold)),
22384
+ titleSize: title.size,
22385
+ titleW: title.width,
22386
+ titleBase: r111(titleBase + shift),
22387
+ subSize: sub.size,
22388
+ subW: sub.width,
22389
+ subBase: r111(subBase + shift),
22390
+ lineY: r111(lineY + shift),
22391
+ lineL: r111(960 - half),
22392
+ lineR: r111(960 + half),
22393
+ wipeTo: v.wipe ? 0 : -2e3,
22394
+ fadeTo: v.wipe ? 1 : 0
22395
+ };
22396
+ },
22397
+ motion: titleCardMotion
22398
+ };
22399
+ var lockupText = (id, extra = "") => `<text${id ? ` id="${id}"` : ""} x="{{wordX}}" y="{{wordBase}}" font-family="{{font}}" font-size="{{wordSize}}" font-weight="800" textLength="{{wordW}}" lengthAdjust="spacingAndGlyphs"${extra}>{{word}}</text>`;
22400
+ function lockup(v, mark, gap, maxWidth, maxSize) {
22401
+ const word = fit6(v.word, maxWidth, maxSize);
22402
+ const total = mark + gap + word.width;
22403
+ const left = 960 - total / 2;
22404
+ const placed2 = placeMark(v.logo, [left, 540 - mark / 2, mark, mark]);
22405
+ return {
22406
+ left: r111(left),
22407
+ right: r111(left + total),
22408
+ wordX: r111(left + mark + gap),
22409
+ wordBase: r111(540 + word.size * 0.36),
22410
+ wordSize: word.size,
22411
+ wordW: word.width,
22412
+ markD: placed2.d,
22413
+ markT: placed2.transform,
22414
+ scale: placed2.scale
22415
+ };
22416
+ }
22417
+ var logoRevealMotion = {
22418
+ svg: `<svg viewBox="0 0 1920 1080"><!-- {{word: text default='Northwind' label='Wordmark'}} {{tagline: text default='Built to move' label='Tagline'}} ${markControl()} {{background: color default=#0A0A0F label='Background'}} {{markColor: color default=#22D3EE label='Mark colour'}} {{ink: color default=#FFFFFF label='Wordmark colour'}} {{muted: color default=#94A3B8 label='Tagline colour'}} {{shine: float min=0 max=1 default=0.7 step=0.05 label='Shine'}} ${holdControl(2)} {{font: text default="${SANS9}" label='Font'}} --><defs><clipPath id="logoRevealWordClip"><rect id="logoRevealWordWipe" x="{{wipeX}}" y="300" width="0" height="480"/></clipPath><clipPath id="logoRevealShineClip"><path d="{{markD}}" transform="{{markT}}" fill-rule="evenodd"/>${lockupText("")}</clipPath><linearGradient id="logoRevealShineFill" x1="0" y1="0" x2="1" y2="0"><stop offset="0" stop-color="#FFFFFF" stop-opacity="0"/><stop offset="0.5" stop-color="#FFFFFF" stop-opacity="{{shine}}"/><stop offset="1" stop-color="#FFFFFF" stop-opacity="0"/></linearGradient></defs><rect width="1920" height="1080" fill="{{background}}"/><g id="logoRevealLockup" transform="translate(960 540) scale(1) translate(-960 -540)" opacity="1"><path id="logoRevealMark" d="{{markD}}" transform="{{markT}}" fill="{{markColor}}" fill-opacity="0" fill-rule="evenodd" stroke="{{markColor}}" stroke-width="{{markStroke}}" stroke-linejoin="round"/><g clip-path="url(#logoRevealWordClip)"><g id="logoRevealWord" transform="translate(-60 0)">${lockupText("", ` fill="{{ink}}"`)}</g></g><g clip-path="url(#logoRevealShineClip)"><rect id="logoRevealShine" x="0" y="300" width="240" height="480" fill="url(#logoRevealShineFill)" transform="translate({{shineFrom}} 0) skewX(-20)"/></g><g id="logoRevealTagline" opacity="0" transform="translate(0 20)"><text x="960" y="{{tagBase}}" text-anchor="middle" font-family="{{font}}" font-size="{{tagSize}}" font-weight="500" letter-spacing="2" textLength="{{tagW}}" lengthAdjust="spacingAndGlyphs" fill="{{muted}}">{{tagline}}</text></g></g></svg>`,
22419
+ animate: [
22420
+ { id: "logoRevealMark", draw: true, ms: 1200, ease: "in-out" },
22421
+ { id: "logoRevealMark", attrs: { "fill-opacity": [0, 1] }, delay: 900, ms: 500, ease: "out" },
22422
+ { id: "logoRevealWordWipe", attrs: { width: [0, n12("wipeW")] }, delay: 1e3, ms: 700, ease: "in-out" },
22423
+ { id: "logoRevealWord", attrs: { transform: ["translate(-60 0)", "translate(0 0)"] }, delay: 1e3, ms: 800, ease: "out" },
22424
+ { id: "logoRevealTagline", attrs: { opacity: [0, 1], transform: ["translate(0 20)", "translate(0 0)"] }, delay: 1500, ms: 500, ease: "out" },
22425
+ { id: "logoRevealShine", attrs: { transform: ["translate({{shineFrom}} 0) skewX(-20)", "translate({{shineTo}} 0) skewX(-20)"] }, delay: 1900, ms: 900, ease: "in-out" },
22426
+ { id: "logoRevealLockup", attrs: { transform: ["translate(960 540) scale(1) translate(-960 -540)", "translate(960 540) scale(0.94) translate(-960 -540)"], opacity: [1, 0] }, delay: after(0), ms: 500 }
22427
+ ]
22428
+ };
22429
+ var logoReveal = {
22430
+ kind: "vector",
22431
+ id: "logo-reveal",
22432
+ name: "Logo reveal",
22433
+ description: "A logo mark tracing its outline on and filling, the wordmark wiping in beside it, a tagline rising, then a shine sweeping across.",
22434
+ category: "broadcast",
22435
+ tags: ["broadcast", "logo", "reveal", "brand", "intro", "draw-on", "shine"],
22436
+ hold: 300,
22437
+ presets: [
22438
+ { name: "Clean", values: { background: [0.98, 0.98, 0.98], markColor: [0.15, 0.39, 0.92], ink: [0.07, 0.09, 0.15], muted: [0.4, 0.45, 0.52], shine: 0.9 } },
22439
+ { name: "Gold", values: { background: [0.03, 0.03, 0.03], markColor: [0.85, 0.65, 0.25], ink: [0.96, 0.88, 0.7] } },
22440
+ { name: "Sport", values: { background: [0.05, 0.05, 0.05], markColor: [0.64, 0.9, 0.21], ink: [1, 1, 1], muted: [0.64, 0.9, 0.21] } }
22441
+ ],
22442
+ derived: (v) => {
22443
+ const l = lockup(v, 200, 56, 1100, 160);
22444
+ const tag = fit6(v.tagline, 1e3, 40);
22445
+ return {
22446
+ ...outTimes(logoRevealMotion, 2800 + ms(v.hold)),
22447
+ ...l,
22448
+ markStroke: r211(4 / l.scale),
22449
+ wipeX: r111(l.wordX - 20),
22450
+ wipeW: r111(l.wordW + 60),
22451
+ shineFrom: r111(l.left - 420),
22452
+ shineTo: r111(l.right + 200),
22453
+ tagSize: tag.size,
22454
+ tagW: tag.width,
22455
+ tagBase: r111(540 + 100 + 60 + tag.size * 0.7)
22456
+ };
22457
+ },
22458
+ motion: logoRevealMotion
22459
+ };
22460
+ var RAYS = 16;
22461
+ var ray = (k) => {
22462
+ const a = (k + 0.5) / RAYS * Math.PI * 2;
22463
+ const [inner, outer] = [210, k % 2 ? 470 : 600];
22464
+ return { x0: r111(960 + Math.cos(a) * inner), y0: r111(540 + Math.sin(a) * inner * 0.8), x1: r111(960 + Math.cos(a) * outer), y1: r111(540 + Math.sin(a) * outer * 0.8) };
22465
+ };
22466
+ var logoStingMotion = {
22467
+ svg: `<svg viewBox="0 0 1920 1080"><!-- {{word: text default='BOLT' label='Wordmark'}} ${markControl()} {{background: color default=#111111 label='Background'}} {{accent: color default=#FACC15 label='Burst'}} {{markColor: color default=#FACC15 label='Mark colour'}} {{ink: color default=#FFFFFF label='Wordmark colour'}} {{flash: float min=0 max=1 default=0.5 step=0.05 label='Flash'}} ${holdControl(1.5)} {{font: text default="${SANS9}" label='Font'}} --><rect width="1920" height="1080" fill="{{background}}"/><circle id="logoStingRing" cx="960" cy="540" r="40" fill="none" stroke="{{accent}}" stroke-width="30" opacity="0"/><g stroke="{{accent}}" stroke-width="10" stroke-linecap="butt">${Array.from({ length: RAYS }, (_, k) => {
22468
+ const p = ray(k);
22469
+ return `<line id="logoStingRay${k}" x1="${p.x0}" y1="${p.y0}" x2="${p.x0}" y2="${p.y0}"/>`;
22470
+ }).join("")}</g><g transform="translate(960 540)"><g id="logoStingPop" transform="scale(0)"><g transform="translate(-960 -540)"><path d="{{markD}}" transform="{{markT}}" fill="{{markColor}}" fill-rule="evenodd"/>${lockupText("", ` fill="{{ink}}"`)}</g></g></g><rect id="logoStingFlash" width="1920" height="1080" fill="#FFFFFF" opacity="0"/></svg>`,
22471
+ animate: [
22472
+ ...pop("logoStingPop", 80, 1.18, 260),
22473
+ { id: "logoStingFlash", attrs: { opacity: [0, n12("flash")] }, delay: 80, ms: 60, ease: "linear" },
22474
+ { id: "logoStingFlash", attrs: { opacity: [n12("flash"), 0] }, delay: 140, ms: 320, ease: "out" },
22475
+ { id: "logoStingRing", attrs: { r: [40, 620], "stroke-width": [30, 0], opacity: [0.9, 0] }, delay: 100, ms: 700, ease: "out" },
22476
+ ...Array.from({ length: RAYS }, (_, k) => {
22477
+ const p = ray(k);
22478
+ const lag = k % 4 * 25;
22479
+ return [
22480
+ { id: `logoStingRay${k}`, attrs: { x2: [p.x0, p.x1], y2: [p.y0, p.y1] }, delay: 120 + lag, ms: 260, ease: "out" },
22481
+ { id: `logoStingRay${k}`, attrs: { x1: [p.x0, p.x1], y1: [p.y0, p.y1] }, delay: 300 + lag, ms: 300, ease: "out" }
22482
+ ];
22483
+ }).flat(),
22484
+ { id: "logoStingPop", attrs: { transform: ["scale(1)", "scale(1.06)"] }, delay: after(0), ms: 140, ease: "out" },
22485
+ { id: "logoStingPop", attrs: { transform: ["scale(1.06)", "scale(0)"] }, delay: after(140), ms: 260 }
22486
+ ]
22487
+ };
22488
+ var logoSting = {
22489
+ kind: "vector",
22490
+ id: "logo-sting",
22491
+ name: "Logo sting",
22492
+ description: "A quick, punchy logo hit: a flash, burst lines and a shock ring firing out as the logo pops in with an overshoot, then snaps away.",
22493
+ category: "broadcast",
22494
+ tags: ["broadcast", "logo", "sting", "brand", "intro", "burst", "punchy"],
22495
+ hold: 200,
22496
+ presets: [
22497
+ { name: "Electric", values: { background: [0.02, 0.03, 0.08], accent: [0.13, 0.83, 0.93], markColor: [0.13, 0.83, 0.93] } },
22498
+ { name: "Clean", values: { background: [1, 1, 1], accent: [0.93, 0.27, 0.27], markColor: [0.93, 0.27, 0.27], ink: [0.07, 0.07, 0.07], flash: 0 } },
22499
+ { name: "Sport", values: { background: [0.05, 0.05, 0.05], accent: [0.64, 0.9, 0.21], markColor: [0.64, 0.9, 0.21] } }
22500
+ ],
22501
+ derived: (v) => ({ ...outTimes(logoStingMotion, 700 + ms(v.hold)), ...lockup(v, 150, 40, 900, 140) }),
22502
+ motion: logoStingMotion
22503
+ };
22504
+ var tile = (key, cx) => `<g transform="translate(${cx} 500)"><g id="endCardTile${key}" transform="scale(0.85)" opacity="0"><rect x="-320" y="-180" width="640" height="360" rx="24" fill="{{tile}}" stroke="{{ink}}" stroke-opacity="0.25" stroke-width="3"/><circle r="52" fill="{{ink}}" fill-opacity="0.14"/><path d="M-16 -26 L28 0 L-16 26 Z" fill="{{ink}}" fill-opacity="0.85"/></g></g>`;
22505
+ var endCardMotion = {
22506
+ svg: `<svg viewBox="0 0 1920 1080"><!-- {{title: text default='Thanks for watching' label='Title'}} {{videoA: text default='Watch next' label='Left video'}} {{videoB: text default='Latest video' label='Right video'}} {{button: text default='Subscribe' label='Button'}} {{background: color default=#0F0F12 label='Background'}} {{tile: color default=#1F2937 label='Video placeholders'}} {{ink: color default=#FFFFFF label='Text colour'}} {{accent: color default=#FF0033 label='Button'}} {{buttonInk: color default=#FFFFFF label='Button text'}} ${holdControl(4)} {{font: text default="${SANS9}" label='Font'}} --><defs><radialGradient id="endCardGlow" cx="0.5" cy="0.35" r="0.7"><stop offset="0" stop-color="{{accent}}" stop-opacity="0.12"/><stop offset="1" stop-color="{{accent}}" stop-opacity="0"/></radialGradient></defs><rect width="1920" height="1080" fill="{{background}}"/><rect width="1920" height="1080" fill="url(#endCardGlow)"/><g id="endCardAll" opacity="1"><g id="endCardTitle" opacity="0" transform="translate(0 -30)"><text x="960" y="{{titleBase}}" text-anchor="middle" font-family="{{font}}" font-size="{{titleSize}}" font-weight="800" textLength="{{titleW}}" lengthAdjust="spacingAndGlyphs" fill="{{ink}}">{{title}}</text></g>${tile("A", 600)}${tile("B", 1320)}<g id="endCardLabels" opacity="0"><text x="600" y="{{labelBase}}" text-anchor="middle" font-family="{{font}}" font-size="{{aSize}}" font-weight="600" textLength="{{aW}}" lengthAdjust="spacingAndGlyphs" fill="{{ink}}">{{videoA}}</text><text x="1320" y="{{labelBase}}" text-anchor="middle" font-family="{{font}}" font-size="{{bSize}}" font-weight="600" textLength="{{bW}}" lengthAdjust="spacingAndGlyphs" fill="{{ink}}">{{videoB}}</text></g><g transform="translate(960 868)"><g id="endCardButton" transform="scale(0)"><rect x="{{buttonX}}" y="-42" width="{{buttonW}}" height="84" rx="42" fill="{{accent}}"/><text y="{{buttonBase}}" text-anchor="middle" font-family="{{font}}" font-size="{{buttonSize}}" font-weight="700" textLength="{{buttonTextW}}" lengthAdjust="spacingAndGlyphs" fill="{{buttonInk}}">{{button}}</text></g></g></g></svg>`,
22507
+ animate: [
22508
+ { id: "endCardTitle", attrs: { opacity: [0, 1], transform: ["translate(0 -30)", "translate(0 0)"] }, ms: 600, ease: "out" },
22509
+ { id: "endCardTileA", attrs: { opacity: [0, 1], transform: ["scale(0.85)", "scale(1)"] }, delay: 300, ms: 550, ease: "out" },
22510
+ { id: "endCardTileB", attrs: { opacity: [0, 1], transform: ["scale(0.85)", "scale(1)"] }, delay: 450, ms: 550, ease: "out" },
22511
+ { id: "endCardLabels", attrs: { opacity: [0, 1] }, delay: 800, ms: 400, ease: "out" },
22512
+ ...pop("endCardButton", 1100, 1.14),
22513
+ { id: "endCardAll", attrs: { opacity: [1, 0] }, delay: after(0), ms: 500 }
22514
+ ]
22515
+ };
22516
+ var endCard = {
22517
+ kind: "vector",
22518
+ id: "end-card",
22519
+ name: "End card",
22520
+ description: "An outro screen: a thank-you title, two video placeholders scaling in for end-screen links, and a subscribe button that pops.",
22521
+ category: "broadcast",
22522
+ tags: ["broadcast", "end-card", "outro", "youtube", "end-screen", "subscribe"],
22523
+ hold: 300,
22524
+ presets: [
22525
+ { name: "Dark", values: { background: [0.06, 0.06, 0.07], tile: [0.12, 0.16, 0.22] } },
22526
+ { name: "Light", values: { background: [0.97, 0.97, 0.98], tile: [0.89, 0.91, 0.94], ink: [0.07, 0.09, 0.15] } },
22527
+ { name: "Brand", values: { background: [0.15, 0.08, 0.35], tile: [0.24, 0.15, 0.5], accent: [0.98, 0.8, 0.08], buttonInk: [0.1, 0.06, 0.2] } }
22528
+ ],
22529
+ derived: (v) => {
22530
+ const title = fit6(v.title, 1400, 88);
22531
+ const a = fit6(v.videoA, 600, 36);
22532
+ const b = fit6(v.videoB, 600, 36);
22533
+ const button = fit6(v.button, 420, 34);
22534
+ const w = Math.max(220, button.width + 96);
22535
+ return {
22536
+ ...outTimes(endCardMotion, 1600 + ms(v.hold)),
22537
+ titleSize: title.size,
22538
+ titleW: title.width,
22539
+ titleBase: r111(250 + title.size * 0.35),
22540
+ aSize: a.size,
22541
+ aW: a.width,
22542
+ bSize: b.size,
22543
+ bW: b.width,
22544
+ labelBase: r111(735 + Math.max(a.size, b.size) * 0.35),
22545
+ buttonW: r111(w),
22546
+ buttonX: -r111(w / 2),
22547
+ buttonSize: button.size,
22548
+ buttonTextW: button.width,
22549
+ buttonBase: r111(button.size * 0.35)
22550
+ };
22551
+ },
22552
+ motion: endCardMotion
22553
+ };
22554
+ var COUNT_MAX = 30;
22555
+ var COUNT_IN = 300;
22556
+ var flipPose = (s) => `translate(960 540) scale(1 ${s}) translate(-960 -540)`;
22557
+ var countdownMotion = {
22558
+ svg: `<svg viewBox="0 0 1920 1080"><!-- {{from: int min=1 max=${COUNT_MAX} default=10 label='Count from'}} {{step: float min=0.25 max=3 default=1 step=0.05 label='Seconds per step'}} {{label: text default='Starting in' label='Label'}} {{background: color default=#0B1020 label='Background'}} {{ring: color default=#F43F5E label='Ring'}} {{track: color default=#1E293B label='Ring track'}} {{ink: color default=#FFFFFF label='Number colour'}} ${holdControl(1, "Hold on zero (s)")} {{font: text default="${SANS9}" label='Font'}} --><rect width="1920" height="1080" fill="{{background}}"/><g id="countdownAll" opacity="0" transform="translate(960 540) scale(0.9) translate(-960 -540)"><circle cx="960" cy="520" r="300" fill="none" stroke="{{track}}" stroke-width="28"/><circle id="countdownRing" cx="960" cy="520" r="300" fill="none" stroke="{{ring}}" stroke-width="28" stroke-linecap="round" transform="rotate(-90 960 520)"/><g id="countdownFlip" transform="${flipPose(0)}"><text id="countdownNumber" x="960" y="{{numberBase}}" text-anchor="middle" font-family="{{font}}" font-size="{{numberSize}}" font-weight="800" fill="{{ink}}">{{d0}}</text></g><text x="960" y="{{labelBase}}" text-anchor="middle" font-family="{{font}}" font-size="{{labelSize}}" font-weight="600" letter-spacing="4" textLength="{{labelW}}" lengthAdjust="spacingAndGlyphs" fill="{{ink}}" fill-opacity="0.7">{{label}}</text></g></svg>`,
22559
+ animate: [
22560
+ { id: "countdownAll", attrs: { opacity: [0, 1], transform: ["translate(960 540) scale(0.9) translate(-960 -540)", "translate(960 540) scale(1) translate(-960 -540)"] }, ms: COUNT_IN, ease: "out" },
22561
+ // A slot per number: the ring drains over its step, the number is written and flips open.
22562
+ ...Array.from({ length: COUNT_MAX + 1 }, (_, k) => [
22563
+ { id: "countdownRing", draw: [n12(`ra${k}`), 0], delay: n12(`t${k}`), ms: n12(`rm${k}`), ease: "linear" },
22564
+ { id: "countdownNumber", write: `{{d${k}}}`, delay: n12(`t${k}`), ms: 0 },
22565
+ { id: "countdownFlip", attrs: { transform: [flipPose(0), flipPose(1)] }, delay: n12(`t${k}`), ms: 260, ease: "out" }
22566
+ ]).flat(),
22567
+ { id: "countdownAll", attrs: { opacity: [1, 0], transform: ["translate(960 540) scale(1) translate(-960 -540)", "translate(960 540) scale(1.08) translate(-960 -540)"] }, delay: after(0), ms: 400 }
22568
+ ]
22569
+ };
22570
+ var countdown = {
22571
+ kind: "vector",
22572
+ id: "countdown",
22573
+ name: "Countdown",
22574
+ description: "A countdown from ten (or any number) to zero, the number flipping over each second as a ring drains around it.",
22575
+ category: "broadcast",
22576
+ tags: ["broadcast", "countdown", "timer", "number", "intro", "live"],
22577
+ hold: 300,
22578
+ presets: [
22579
+ { name: "Live", values: { ring: [0.96, 0.25, 0.37], background: [0.04, 0.06, 0.13] } },
22580
+ { name: "Clean", values: { background: [0.98, 0.98, 0.98], ring: [0.15, 0.39, 0.92], track: [0.89, 0.91, 0.94], ink: [0.07, 0.09, 0.15] } },
22581
+ { name: "Retro film", values: { background: [0.1, 0.09, 0.08], ring: [0.95, 0.9, 0.8], track: [0.25, 0.23, 0.2], ink: [0.95, 0.9, 0.8], from: 5, label: "" } }
22582
+ ],
22583
+ derived: (v) => {
22584
+ const from = Math.max(1, Math.min(COUNT_MAX, Math.round(Number(v.from)) || 1));
22585
+ const step = ms(v.step);
22586
+ const number = fit6(String(from), 380, 300);
22587
+ const label = fit6(v.label, 900, 40);
22588
+ const out = {
22589
+ ...outTimes(countdownMotion, COUNT_IN + from * step + 300 + ms(v.hold)),
22590
+ numberSize: number.size,
22591
+ numberBase: r111(520 + number.size * 0.36),
22592
+ labelSize: label.size,
22593
+ labelW: label.width,
22594
+ labelBase: r111(920 + label.size * 0.35)
22595
+ };
22596
+ for (let k = 0; k <= COUNT_MAX; k++) {
22597
+ const s = Math.min(k, from);
22598
+ out[`t${k}`] = COUNT_IN + s * step;
22599
+ out[`d${k}`] = String(from - s);
22600
+ out[`ra${k}`] = s < from ? 1 : 0;
22601
+ out[`rm${k}`] = s < from ? step : 0;
22602
+ }
22603
+ return out;
22604
+ },
22605
+ motion: countdownMotion
22606
+ };
22607
+ var CURSOR = "M0 0 L0 34 L9.5 25.5 L15.5 39.5 L21.5 37 L15.5 23.5 L27.5 23.5 Z";
22608
+ var BELL = "M12 3 C8.4 3 6 5.8 6 9.6 V14 L4 17.2 H20 L18 14 V9.6 C18 5.8 15.6 3 12 3 Z M9.6 19.4 A2.5 2.5 0 0 0 14.4 19.4";
22609
+ var ring = (deg) => `rotate(${deg} 0 -26)`;
22610
+ var subscribeButtonMotion = {
22611
+ svg: `<svg viewBox="0 0 1920 1080"><!-- {{label: text default='Subscribe' label='Button'}} {{done: text default='Subscribed' label='Button when subscribed'}} {{color: color default=#FF0033 label='Button'}} {{ink: color default=#FFFFFF label='Button text'}} {{doneColor: color default=#E5E7EB label='Subscribed button'}} {{doneInk: color default=#111827 label='Subscribed text'}} ${holdControl(2)} {{font: text default="${SANS9}" label='Font'}} --><g transform="translate(960 860) scale(1.35) translate(-960 -860)"><g id="subscribeButtonAll" opacity="0" transform="translate(0 40)"><rect id="subscribeButtonFace" x="{{buttonX}}" y="816" width="{{buttonW}}" height="88" rx="44" fill="{{color}}"/><text id="subscribeButtonLabel" x="{{buttonCx}}" y="{{textBase}}" text-anchor="middle" font-family="{{font}}" font-size="{{labelSize}}" font-weight="700" textLength="{{labelW}}" lengthAdjust="spacingAndGlyphs" fill="{{ink}}">{{label}}</text><text id="subscribeButtonDone" x="{{buttonCx}}" y="{{doneBase}}" text-anchor="middle" font-family="{{font}}" font-size="{{doneSize}}" font-weight="700" textLength="{{doneW}}" lengthAdjust="spacingAndGlyphs" fill="{{doneInk}}" opacity="0">{{done}}</text><g transform="translate({{bellX}} 860)"><g id="subscribeButtonBell" transform="scale(0)"><circle r="44" fill="{{doneColor}}"/><g id="subscribeButtonSwing" transform="${ring(0)}"><path d="${BELL}" transform="scale(2.4) translate(-12 -11.5)" fill="none" stroke="{{doneInk}}" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></g></g></g><circle id="subscribeButtonRipple" cx="{{clickX}}" cy="872" r="0" fill="#FFFFFF" opacity="0"/><g id="subscribeButtonCursor" transform="translate(1640 1130)"><g id="subscribeButtonPress" transform="scale(1.3)"><path d="${CURSOR}" fill="#FFFFFF" stroke="#111111" stroke-width="2" stroke-linejoin="round"/></g></g></g></g></svg>`,
22612
+ animate: [
22613
+ { id: "subscribeButtonAll", attrs: { opacity: [0, 1], transform: ["translate(0 40)", "translate(0 0)"] }, ms: 450, ease: "out" },
22614
+ { id: "subscribeButtonCursor", attrs: { transform: ["translate(1640 1130)", "translate({{clickX}} 872)"] }, delay: 450, ms: 850, ease: "in-out" },
22615
+ { id: "subscribeButtonPress", attrs: { transform: ["scale(1.3)", "scale(1.1)"] }, delay: 1300, ms: 100, ease: "out" },
22616
+ { id: "subscribeButtonPress", attrs: { transform: ["scale(1.1)", "scale(1.3)"] }, delay: 1400, ms: 140, ease: "out" },
22617
+ { id: "subscribeButtonRipple", attrs: { r: [0, 110], opacity: [0.5, 0] }, delay: 1300, ms: 550, ease: "out" },
22618
+ { id: "subscribeButtonFace", attrs: { fill: ["{{color}}", "{{doneColor}}"] }, delay: 1300, ms: 180, ease: "linear" },
22619
+ { id: "subscribeButtonLabel", attrs: { opacity: [1, 0] }, delay: 1300, ms: 120, ease: "linear" },
22620
+ { id: "subscribeButtonDone", attrs: { opacity: [0, 1] }, delay: 1380, ms: 160, ease: "linear" },
22621
+ ...pop("subscribeButtonBell", 1450, 1.15, 240),
22622
+ ...[[0, 20], [20, -18], [-18, 14], [14, -9], [-9, 5], [5, 0]].map(([a, b], i) => ({ id: "subscribeButtonSwing", attrs: { transform: [ring(a), ring(b)] }, delay: 1700 + i * 110, ms: 110, ease: "in-out" })),
22623
+ { id: "subscribeButtonCursor", attrs: { transform: ["translate({{clickX}} 872)", "translate({{leaveX}} 1000)"] }, delay: 2e3, ms: 700, ease: "in-out" },
22624
+ { id: "subscribeButtonAll", attrs: { opacity: [1, 0], transform: ["translate(0 0)", "translate(0 40)"] }, delay: after(0), ms: 450 }
22625
+ ]
22626
+ };
22627
+ var subscribeButton = {
22628
+ kind: "vector",
22629
+ id: "subscribe-button",
22630
+ name: "Subscribe button",
22631
+ description: "A YouTube-style subscribe button: a cursor glides in and clicks it, it turns grey and reads Subscribed, and a bell pops up and rings.",
22632
+ category: "broadcast",
22633
+ tags: ["broadcast", "subscribe", "youtube", "button", "cursor", "bell", "overlay", "cta"],
22634
+ hold: 300,
22635
+ presets: [
22636
+ { name: "YouTube", values: { color: [1, 0, 0.2], doneColor: [0.9, 0.91, 0.92], doneInk: [0.07, 0.09, 0.15] } },
22637
+ { name: "Dark", values: { color: [1, 1, 1], ink: [0.06, 0.06, 0.06], doneColor: [0.15, 0.15, 0.15], doneInk: [1, 1, 1] } },
22638
+ { name: "Brand", values: { color: [0.49, 0.23, 0.93], doneColor: [0.93, 0.91, 0.99], doneInk: [0.35, 0.15, 0.7], label: "Follow", done: "Following" } }
22639
+ ],
22640
+ derived: (v) => {
22641
+ const label = fit6(v.label, 520, 34);
22642
+ const done = fit6(v.done, 520, 34);
22643
+ const w = Math.max(label.width, done.width) + 88;
22644
+ const total = w + 24 + 88;
22645
+ const left = 960 - total / 2;
22646
+ return {
22647
+ ...outTimes(subscribeButtonMotion, 2700 + ms(v.hold)),
22648
+ buttonX: r111(left),
22649
+ buttonW: r111(w),
22650
+ buttonCx: r111(left + w / 2),
22651
+ labelSize: label.size,
22652
+ labelW: label.width,
22653
+ textBase: r111(860 + label.size * 0.35),
22654
+ doneSize: done.size,
22655
+ doneW: done.width,
22656
+ doneBase: r111(860 + done.size * 0.35),
22657
+ bellX: r111(left + w + 24 + 44),
22658
+ clickX: r111(left + w * 0.55),
22659
+ leaveX: r111(Math.min(1700, left + total + 260))
22660
+ };
22661
+ },
22662
+ motion: subscribeButtonMotion
22663
+ };
22664
+ var TICKER_COPIES = 8;
22665
+ var TICKER_MIN_REPEAT = 400;
22666
+ var BLINKS2 = 24;
22667
+ var newsTickerMotion = {
22668
+ svg: `<svg viewBox="0 0 1920 1080"><!-- {{text: text default='Markets close higher as rates hold steady \xB7 Storm warning lifted along the coast \xB7 City council approves new transit line' label='Ticker text'}} {{label: text default='BREAKING' label='Label'}} {{band: color default=#0B1220 label='Band'}} {{ink: color default=#FFFFFF label='Text colour'}} {{accent: color default=#DC2626 label='Label'}} {{labelInk: color default=#FFFFFF label='Label text'}} {{speed: float min=40 max=600 default=160 step=10 label='Speed (px/s)'}} {{font: text default="${SANS9}" label='Font'}} --><defs><clipPath id="newsTickerClip"><rect x="{{crawlX}}" y="884" width="{{crawlW}}" height="80"/></clipPath></defs><rect x="0" y="884" width="1920" height="80" fill="{{band}}" fill-opacity="0.92"/><rect x="0" y="884" width="1920" height="4" fill="{{accent}}"/><g clip-path="url(#newsTickerClip)"><g id="newsTickerCrawl" transform="translate(0 0)">${Array.from(
22669
+ { length: TICKER_COPIES },
22670
+ (_, k) => `<text x="{{x${k}}}" y="{{textBase}}" font-family="{{font}}" font-size="{{textSize}}" font-weight="500" textLength="{{textW}}" lengthAdjust="spacingAndGlyphs" fill="{{ink}}">{{text}}</text><rect x="{{s${k}}}" y="917" width="14" height="14" transform="rotate(45 {{sc${k}}} 924)" fill="{{accent}}"/>`
22671
+ ).join("")}</g></g><rect x="192" y="884" width="{{labelBox}}" height="80" fill="{{accent}}"/><circle id="newsTickerDot" cx="226" cy="924" r="9" fill="{{labelInk}}"/><text x="250" y="{{labelBase}}" font-family="{{font}}" font-size="{{labelSize}}" font-weight="800" letter-spacing="2" textLength="{{labelW}}" lengthAdjust="spacingAndGlyphs" fill="{{labelInk}}">{{label}}</text></svg>`,
22672
+ animate: [
22673
+ { id: "newsTickerCrawl", attrs: { transform: ["translate(0 0)", "translate({{repeatBack}} 0)"] }, ms: n12("period"), ease: "linear" },
22674
+ ...holds2(
22675
+ "newsTickerDot",
22676
+ "opacity",
22677
+ Array.from({ length: BLINKS2 }, (_, k) => [n12(`b${k}`), k % 2 ? 0.25 : 1])
22678
+ )
22679
+ ]
22680
+ };
22681
+ var newsTicker = {
22682
+ kind: "vector",
22683
+ id: "news-ticker",
22684
+ name: "News ticker",
22685
+ description: "A news band along the bottom with a BREAKING label and a blinking live dot, its headlines crawling past in a seamless loop.",
22686
+ category: "broadcast",
22687
+ tags: ["broadcast", "news", "ticker", "crawl", "marquee", "breaking", "loop", "overlay"],
22688
+ hold: 0,
22689
+ presets: [
22690
+ { name: "Breaking", values: { accent: [0.86, 0.15, 0.15], band: [0.04, 0.07, 0.13] } },
22691
+ { name: "Business", values: { accent: [0.15, 0.39, 0.92], band: [1, 1, 1], ink: [0.07, 0.09, 0.15], label: "MARKETS" } },
22692
+ { name: "Sport", values: { accent: [0.64, 0.9, 0.21], labelInk: [0.05, 0.05, 0.05], band: [0.05, 0.05, 0.05], label: "LATEST" } }
22693
+ ],
22694
+ derived: (v) => {
22695
+ const label = fit6(v.label, 360, 30);
22696
+ const text = fit6(v.text, Infinity, 36);
22697
+ const labelBox = label.width + 92;
22698
+ const crawlX = 192 + labelBox;
22699
+ const repeat = Math.max(TICKER_MIN_REPEAT, text.width + 96);
22700
+ const period = Math.max(1, Math.round(repeat / Math.max(1, Number(v.speed)) * 1e3));
22701
+ const out = {
22702
+ labelSize: label.size,
22703
+ labelW: label.width,
22704
+ labelBase: r111(924 + label.size * 0.35),
22705
+ labelBox: r111(labelBox),
22706
+ crawlX: r111(crawlX),
22707
+ crawlW: r111(1920 - crawlX),
22708
+ textSize: text.size,
22709
+ textW: text.width,
22710
+ textBase: r111(924 + text.size * 0.35),
22711
+ repeatBack: -r111(repeat),
22712
+ period
22713
+ };
22714
+ for (let k = 0; k < TICKER_COPIES; k++) {
22715
+ const x = crawlX + 32 + k * repeat;
22716
+ out[`x${k}`] = r111(x);
22717
+ out[`s${k}`] = r111(x + text.width + 41);
22718
+ out[`sc${k}`] = r111(x + text.width + 48);
22719
+ }
22720
+ for (let k = 0; k < BLINKS2; k++) out[`b${k}`] = Math.round(k * period / BLINKS2);
22721
+ return out;
22722
+ },
22723
+ motion: newsTickerMotion
22724
+ };
22725
+ var TICKS2 = 40;
22726
+ var SCORE_IN = 1400;
22727
+ var clockText = (total) => {
22728
+ const t = Math.max(0, total);
22729
+ return `${Math.floor(t / 60)}:${String(t % 60).padStart(2, "0")}`;
22730
+ };
22731
+ var scoreboardMotion = {
22732
+ svg: `<svg viewBox="0 0 1920 1080"><!-- {{home: text default='LIV' label='Home team'}} {{away: text default='ARS' label='Away team'}} {{homeScore: int min=0 max=199 default=2 label='Home score'}} {{awayScore: int min=0 max=199 default=1 label='Away score'}} {{period: text default='2H' label='Period'}} {{minutes: int min=0 max=120 default=67 label='Clock minutes'}} {{seconds: int min=0 max=59 default=12 label='Clock seconds'}} {{countDown: bool default=false label='Clock counts down'}} {{homeColor: color default=#C8102E label='Home colour'}} {{awayColor: color default=#F5F5F5 label='Away colour'}} {{plate: color default=#101828 label='Team plate'}} {{scorePlate: color default=#FFFFFF label='Score plate'}} {{ink: color default=#FFFFFF label='Team text'}} {{scoreInk: color default=#101828 label='Score text'}} {{accent: color default=#22C55E label='Clock plate'}} {{clockInk: color default=#04130A label='Clock text'}} ${holdControl(6)} {{font: text default="${SANS9}" label='Font'}} --><defs><clipPath id="scoreboardWipe"><rect id="scoreboardWipeRect" x="192" y="100" width="0" height="80"/></clipPath><filter id="scoreboardShadow" x="-10%" y="-50%" width="120%" height="200%"><feDropShadow dx="0" dy="4" stdDeviation="6" flood-color="#000000" flood-opacity="0.35"/></filter></defs><g clip-path="url(#scoreboardWipe)" filter="url(#scoreboardShadow)"><rect x="192" y="108" width="10" height="64" fill="{{homeColor}}"/><rect x="202" y="108" width="180" height="64" fill="{{plate}}"/><text x="292" y="{{homeBase}}" text-anchor="middle" font-family="{{font}}" font-size="{{homeSize}}" font-weight="800" textLength="{{homeW}}" lengthAdjust="spacingAndGlyphs" fill="{{ink}}">{{home}}</text><rect x="382" y="108" width="152" height="64" fill="{{scorePlate}}"/><line x1="458" y1="122" x2="458" y2="158" stroke="{{scoreInk}}" stroke-opacity="0.2" stroke-width="2"/><text id="scoreboardHomeScore" x="420" y="152" text-anchor="middle" font-family="{{font}}" font-size="40" font-weight="800" fill="{{scoreInk}}">0</text><text id="scoreboardAwayScore" x="496" y="152" text-anchor="middle" font-family="{{font}}" font-size="40" font-weight="800" fill="{{scoreInk}}">0</text><rect x="534" y="108" width="180" height="64" fill="{{plate}}"/><text x="624" y="{{awayBase}}" text-anchor="middle" font-family="{{font}}" font-size="{{awaySize}}" font-weight="800" textLength="{{awayW}}" lengthAdjust="spacingAndGlyphs" fill="{{ink}}">{{away}}</text><rect x="714" y="108" width="10" height="64" fill="{{awayColor}}"/><rect x="724" y="108" width="150" height="64" fill="{{accent}}"/><text id="scoreboardClock" x="799" y="152" text-anchor="middle" font-family="{{font}}" font-size="36" font-weight="700" fill="{{clockInk}}">{{c0}}</text><rect x="874" y="108" width="72" height="64" fill="{{plate}}"/><text x="910" y="{{periodBase}}" text-anchor="middle" font-family="{{font}}" font-size="{{periodSize}}" font-weight="700" textLength="{{periodW}}" lengthAdjust="spacingAndGlyphs" fill="{{ink}}">{{period}}</text></g></svg>`,
22733
+ animate: [
22734
+ { id: "scoreboardWipeRect", attrs: { width: [0, 760] }, ms: 600, ease: "out" },
22735
+ { id: "scoreboardHomeScore", count: [0, n12("homeScore")], delay: 500, ms: 900, ease: "out" },
22736
+ { id: "scoreboardAwayScore", count: [0, n12("awayScore")], delay: 600, ms: 800, ease: "out" },
22737
+ ...Array.from({ length: TICKS2 }, (_, k) => ({ id: "scoreboardClock", write: `{{c${k}}}`, delay: n12(`ct${k}`), ms: 0 })),
22738
+ { id: "scoreboardWipeRect", attrs: { width: [760, 0] }, delay: after(0), ms: 500 }
22739
+ ]
22740
+ };
22741
+ var scoreboard = {
22742
+ kind: "vector",
22743
+ id: "scoreboard",
22744
+ name: "Scoreboard",
22745
+ description: "A sports score bug wiping in at the top left: team names and colours, scores counting up, a ticking match clock and the period.",
22746
+ category: "broadcast",
22747
+ tags: ["broadcast", "sport", "scoreboard", "score-bug", "clock", "overlay", "counter"],
22748
+ hold: 300,
22749
+ presets: [
22750
+ { name: "Football", values: { accent: [0.13, 0.77, 0.37], clockInk: [0.02, 0.07, 0.04] } },
22751
+ { name: "Basketball", values: { home: "LAL", away: "BOS", homeScore: 98, awayScore: 101, period: "Q4", minutes: 2, seconds: 30, countDown: true, homeColor: [0.33, 0.15, 0.53], awayColor: [0, 0.48, 0.2], accent: [0.98, 0.45, 0.09], clockInk: [0.1, 0.04, 0] } },
22752
+ { name: "Minimal", values: { plate: [1, 1, 1], ink: [0.07, 0.09, 0.15], scorePlate: [0.07, 0.09, 0.15], scoreInk: [1, 1, 1], accent: [0.95, 0.96, 0.97], clockInk: [0.07, 0.09, 0.15] } }
22753
+ ],
22754
+ derived: (v) => {
22755
+ const home = fit6(v.home, 150, 34);
22756
+ const away = fit6(v.away, 150, 34);
22757
+ const period = fit6(v.period, 56, 28);
22758
+ const hold = ms(v.hold);
22759
+ const start = Math.round(Number(v.minutes)) * 60 + Math.round(Number(v.seconds));
22760
+ const dir = v.countDown ? -1 : 1;
22761
+ const ticks2 = Math.max(1, Math.min(TICKS2, Math.floor((SCORE_IN + hold - 600) / 1e3) + 1));
22762
+ const out = {
22763
+ ...outTimes(scoreboardMotion, SCORE_IN + hold),
22764
+ homeSize: home.size,
22765
+ homeW: home.width,
22766
+ homeBase: r111(140 + home.size * 0.35),
22767
+ awaySize: away.size,
22768
+ awayW: away.width,
22769
+ awayBase: r111(140 + away.size * 0.35),
22770
+ periodSize: period.size,
22771
+ periodW: period.width,
22772
+ periodBase: r111(140 + period.size * 0.35),
22773
+ homeScore: Math.round(Number(v.homeScore)),
22774
+ awayScore: Math.round(Number(v.awayScore))
22775
+ };
22776
+ for (let k = 0; k < TICKS2; k++) {
22777
+ const s = Math.min(k, ticks2 - 1);
22778
+ out[`ct${k}`] = 600 + s * 1e3;
22779
+ out[`c${k}`] = clockText(start + dir * s);
22780
+ }
22781
+ return out;
22782
+ },
22783
+ motion: scoreboardMotion
22784
+ };
22785
+ var chapterMarkerMotion = {
22786
+ svg: `<svg viewBox="0 0 1920 1080"><!-- {{title: text default='The plan' label='Title'}} {{number: int min=0 max=99 default=2 label='Chapter number'}} {{label: text default='Chapter' label='Label'}} {{accent: color default=#F97316 label='Badge'}} {{badgeInk: color default=#FFFFFF label='Number colour'}} {{plate: color default=#111827 label='Panel'}} {{ink: color default=#FFFFFF label='Title colour'}} {{labelInk: color default=#F97316 label='Label colour'}} {{plateOpacity: float min=0 max=1 default=0.92 step=0.05 label='Panel opacity'}} ${holdControl(3)} {{font: text default="${SANS9}" label='Font'}} --><defs><clipPath id="chapterMarkerClip"><rect x="318" y="760" width="1420" height="112"/></clipPath></defs><rect id="chapterMarkerPanel" x="252" y="760" width="0" height="112" rx="56" fill="{{plate}}" fill-opacity="{{plateOpacity}}"/><g clip-path="url(#chapterMarkerClip)"><text id="chapterMarkerLabel" x="340" y="{{labelBase}}" transform="translate(-260 0)" font-family="{{font}}" font-size="{{labelSize}}" font-weight="700" letter-spacing="3" textLength="{{labelW}}" lengthAdjust="spacingAndGlyphs" fill="{{labelInk}}">{{heading}}</text><text id="chapterMarkerTitle" x="340" y="{{titleBase}}" transform="translate(-320 0)" font-family="{{font}}" font-size="{{titleSize}}" font-weight="700" textLength="{{titleW}}" lengthAdjust="spacingAndGlyphs" fill="{{ink}}">{{title}}</text></g><g transform="translate(252 816)"><g id="chapterMarkerBadge" transform="rotate(-120) scale(0)"><circle r="60" fill="{{accent}}"/><circle r="50" fill="none" stroke="{{badgeInk}}" stroke-opacity="0.35" stroke-width="2"/><text y="{{numberBase}}" text-anchor="middle" font-family="{{font}}" font-size="{{numberSize}}" font-weight="800" fill="{{badgeInk}}">{{number}}</text></g></g></svg>`,
22787
+ animate: [
22788
+ { id: "chapterMarkerBadge", attrs: { transform: ["rotate(-120) scale(0)", "rotate(8) scale(1.1)"] }, ms: 380, ease: "out" },
22789
+ { id: "chapterMarkerBadge", attrs: { transform: ["rotate(8) scale(1.1)", "rotate(0) scale(1)"] }, delay: 380, ms: 180, ease: "in-out" },
22790
+ { id: "chapterMarkerPanel", attrs: { width: [0, n12("panel")] }, delay: 250, ms: 550, ease: "out" },
22791
+ { id: "chapterMarkerLabel", attrs: { transform: ["translate(-260 0)", "translate(0 0)"] }, delay: 450, ms: 450, ease: "out" },
22792
+ { id: "chapterMarkerTitle", attrs: { transform: ["translate(-320 0)", "translate(0 0)"] }, delay: 550, ms: 500, ease: "out" },
22793
+ { id: "chapterMarkerTitle", attrs: { transform: ["translate(0 0)", "translate(-320 0)"] }, delay: after(0), ms: 350 },
22794
+ { id: "chapterMarkerLabel", attrs: { transform: ["translate(0 0)", "translate(-260 0)"] }, delay: after(80), ms: 350 },
22795
+ { id: "chapterMarkerPanel", attrs: { width: [n12("panel"), 0] }, delay: after(200), ms: 450 },
22796
+ { id: "chapterMarkerBadge", attrs: { transform: ["rotate(0) scale(1)", "rotate(-120) scale(0)"] }, delay: after(550), ms: 350 }
22797
+ ]
22798
+ };
22799
+ var chapterMarker = {
22800
+ kind: "vector",
22801
+ id: "chapter-marker",
22802
+ name: "Chapter marker",
22803
+ description: "A numbered badge spinning in at the lower left and a panel sliding out of it with the chapter label over its title.",
22804
+ category: "broadcast",
22805
+ tags: ["broadcast", "chapter", "section", "title", "number", "overlay", "youtube"],
22806
+ hold: 300,
22807
+ presets: [
22808
+ { name: "Bold", values: { accent: [0.98, 0.45, 0.09], labelInk: [0.98, 0.45, 0.09] } },
22809
+ { name: "Clean", values: { plate: [1, 1, 1], ink: [0.07, 0.09, 0.15], accent: [0.15, 0.39, 0.92], labelInk: [0.15, 0.39, 0.92], plateOpacity: 1 } },
22810
+ { name: "Minimal", values: { plateOpacity: 0, accent: [1, 1, 1], badgeInk: [0.07, 0.07, 0.07], labelInk: [1, 1, 1] } }
22811
+ ],
22812
+ derived: (v) => {
22813
+ const heading = `${String(v.label).toUpperCase()} ${Math.round(Number(v.number))}`.trim();
22814
+ const label = fit6(heading, 1300, 24);
22815
+ const title = fit6(v.title, 1300, 50);
22816
+ const number = fit6(String(Math.round(Number(v.number))), 80, 56);
22817
+ return {
22818
+ ...outTimes(chapterMarkerMotion, 1050 + ms(v.hold)),
22819
+ heading,
22820
+ labelSize: label.size,
22821
+ labelW: label.width,
22822
+ labelBase: r111(796 + label.size * 0.35),
22823
+ titleSize: title.size,
22824
+ titleW: title.width,
22825
+ titleBase: r111(840 + title.size * 0.35),
22826
+ numberSize: number.size,
22827
+ numberBase: r111(number.size * 0.36),
22828
+ panel: r111(88 + Math.max(label.width, title.width) + 44)
22829
+ };
22830
+ },
22831
+ motion: chapterMarkerMotion
22832
+ };
22833
+ var broadcastEffects = [
22834
+ lowerThird,
22835
+ lowerThirdStacked,
22836
+ titleCard,
22837
+ logoReveal,
22838
+ logoSting,
22839
+ endCard,
22840
+ countdown,
22841
+ subscribeButton,
22842
+ newsTicker,
22843
+ scoreboard,
22844
+ chapterMarker
22845
+ ];
22846
+
22847
+ // src/catalog/annotation.ts
22848
+ var HAND = `'Caveat', 'Patrick Hand', 'Segoe Print', 'Bradley Hand', 'Comic Sans MS', cursive`;
22849
+ var px = (v) => r111(Number(v) / 100 * 1920);
22850
+ var py = (v) => r111(Number(v) / 100 * 1080);
22851
+ var clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
22852
+ var pt = (p) => `${r111(p[0])} ${r111(p[1])}`;
22853
+ var at3 = (name, value, label, axis) => `{{${name}: float min=0 max=100 default=${value} step=0.5 label='${label} ${axis} (%)'}}`;
22854
+ function random2(seed) {
22855
+ let a = (Math.round(Number(seed)) || 0) * 7919 + 1;
22856
+ return () => {
22857
+ a = a + 1831565813 | 0;
22858
+ let t = Math.imul(a ^ a >>> 15, 1 | a);
22859
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
22860
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
22861
+ };
22862
+ }
22863
+ function smooth(pts) {
22864
+ let d = `M${pt(pts[0])}`;
22865
+ for (let i = 0; i < pts.length - 1; i++) {
22866
+ const [p0, p1, p2, p3] = [pts[i - 1] ?? pts[i], pts[i], pts[i + 1], pts[i + 2] ?? pts[i + 1]];
22867
+ const c1 = [p1[0] + (p2[0] - p0[0]) / 6, p1[1] + (p2[1] - p0[1]) / 6];
22868
+ const c2 = [p2[0] - (p3[0] - p1[0]) / 6, p2[1] - (p3[1] - p1[1]) / 6];
22869
+ d += ` C${pt(c1)} ${pt(c2)} ${pt(p2)}`;
22870
+ }
22871
+ return d;
22872
+ }
22873
+ function handLoop(cx, cy, rx, ry, rand3, rough, grow = 1) {
22874
+ const a0 = -2.5 + rand3() * 0.5;
22875
+ const sweep = Math.PI * 2 + 0.4 + rand3() * 0.25;
22876
+ const [p1, p2, p3] = [rand3() * 6.28, rand3() * 6.28, rand3() * 6.28];
22877
+ const tilt = (rand3() - 0.5) * 0.12;
22878
+ const amp = 0.012 + 0.06 * rough;
22879
+ const pts = [];
22880
+ const N = 40;
22881
+ for (let i = 0; i <= N; i++) {
22882
+ const t = i / N;
22883
+ const a = a0 + sweep * t;
22884
+ const r = grow * (1 + amp * (0.55 * Math.sin(2 * a + p1) + 0.3 * Math.sin(3 * a + p2) + 0.15 * Math.sin(5 * a + p3)) + 0.07 * rough * t);
22885
+ const [x, y] = [rx * r * Math.cos(a), ry * r * Math.sin(a)];
22886
+ pts.push([cx + x * Math.cos(tilt) - y * Math.sin(tilt), cy + x * Math.sin(tilt) + y * Math.cos(tilt)]);
22887
+ }
22888
+ return smooth(pts);
22889
+ }
22890
+ var writing = (text, perLetter = 60) => clamp(Array.from(String(text ?? "")).length * perLetter, 200, 1800);
22891
+ var handCircleMotion = {
22892
+ svg: `<svg viewBox="0 0 1920 1080"><!-- ${at3("x", 50, "Centre", "x")} ${at3("y", 50, "Centre", "y")} {{w: float min=2 max=100 default=30 step=0.5 label='Width (%)'}} {{h: float min=2 max=100 default=26 step=0.5 label='Height (%)'}} {{color: color default=#EF4444 label='Ink'}} {{width: float min=1 max=40 default=8 step=0.5 label='Pen width'}} {{alpha: float min=0.1 max=1 default=1 step=0.05 label='Ink opacity'}} {{rough: float min=0 max=1 default=0.5 step=0.05 label='Roughness'}} {{seed: int min=0 max=99 default=3 label='Seed'}} ${holdControl(2)} --><g id="handCircleAll" fill="none" stroke="{{color}}" stroke-linecap="round" stroke-linejoin="round"><path id="handCircleA" d="{{a}}" stroke-width="{{width}}" stroke-opacity="{{alpha}}"/><path id="handCircleB" d="{{b}}" stroke-width="{{thin}}" stroke-opacity="{{alphaB}}"/></g></svg>`,
22893
+ animate: [
22894
+ { id: "handCircleA", draw: true, ms: 750, ease: "in-out" },
22895
+ { id: "handCircleB", draw: true, delay: 350, ms: 700, ease: "in-out" },
22896
+ { id: "handCircleAll", attrs: { opacity: [1, 0] }, delay: after(0), ms: 400 }
22897
+ ]
22898
+ };
22899
+ var handCircle = {
22900
+ kind: "vector",
22901
+ id: "hand-circle",
22902
+ name: "Hand circle",
22903
+ description: "A hand-drawn, slightly wobbly marker circle drawing itself round a region in a rough double stroke, then fading.",
22904
+ category: "annotation",
22905
+ tags: ["annotation", "hand-drawn", "circle", "highlight", "marker", "draw-on", "tutorial"],
22906
+ hold: 300,
22907
+ presets: [
22908
+ { name: "Red marker", values: { color: [0.94, 0.27, 0.27], width: 8 } },
22909
+ { name: "Highlighter", values: { color: [0.98, 0.88, 0.2], width: 26, alpha: 0.55, rough: 0.3 } },
22910
+ { name: "Blue pen", values: { color: [0.15, 0.39, 0.92], width: 4, rough: 0.7 } }
22911
+ ],
22912
+ derived: (v) => {
22913
+ const [cx, cy] = [px(v.x), py(v.y)];
22914
+ const [rx, ry] = [Number(v.w) / 200 * 1920, Number(v.h) / 200 * 1080];
22915
+ const rough = Number(v.rough);
22916
+ return {
22917
+ ...outTimes(handCircleMotion, 1050 + ms(v.hold)),
22918
+ a: handLoop(cx, cy, rx, ry, random2(v.seed), rough),
22919
+ b: handLoop(cx, cy, rx, ry, random2(Number(v.seed) + 101), rough, 1.035),
22920
+ thin: r111(Number(v.width) * 0.6),
22921
+ alphaB: r111(Number(v.alpha) * 0.85 * 100) / 100
22922
+ };
22923
+ },
22924
+ motion: handCircleMotion
22925
+ };
22926
+ var handArrowMotion = {
22927
+ svg: `<svg viewBox="0 0 1920 1080"><!-- {{label: text default='Click here!' label='Label'}} ${at3("lx", 22, "Label", "x")} ${at3("ly", 24, "Label", "y")} ${at3("tx", 52, "Target", "x")} ${at3("ty", 58, "Target", "y")} {{bend: float min=-1 max=1 default=0.45 step=0.05 label='Bend'}} {{color: color default=#EF4444 label='Ink'}} {{width: float min=1 max=24 default=7 step=0.5 label='Pen width'}} {{size: float min=16 max=160 default=76 step=1 label='Label size'}} {{rough: float min=0 max=1 default=0.5 step=0.05 label='Roughness'}} {{seed: int min=0 max=99 default=5 label='Seed'}} ${holdControl(2)} {{font: text default="${HAND}" label='Font'}} --><g id="handArrowAll" fill="none" stroke="{{color}}" stroke-width="{{width}}" stroke-linecap="round" stroke-linejoin="round"><text id="handArrowLabel" x="{{lx}}" y="{{labelBase}}" text-anchor="middle" transform="rotate(-4 {{lx}} {{ly}})" font-family="{{font}}" font-size="{{labelSize}}" font-weight="700" fill="{{color}}" stroke="none">{{label}}</text><path id="handArrowShaft" d="{{shaft}}"/><path id="handArrowHead" d="{{head}}"/></g></svg>`,
22928
+ animate: [
22929
+ { id: "handArrowLabel", write: "{{label}}", ms: n12("writeMs"), ease: "linear" },
22930
+ { id: "handArrowShaft", draw: true, delay: n12("shaftAt"), ms: 700, ease: "in-out" },
22931
+ { id: "handArrowHead", draw: true, delay: n12("headAt"), ms: 250, ease: "out" },
22932
+ { id: "handArrowAll", attrs: { opacity: [1, 0] }, delay: after(0), ms: 400 }
22933
+ ]
22934
+ };
22935
+ var handArrow = {
22936
+ kind: "vector",
22937
+ id: "hand-arrow",
22938
+ name: "Hand arrow",
22939
+ description: "A handwritten label, then a curved, hand-drawn arrow drawing itself from it to a target point.",
22940
+ category: "annotation",
22941
+ tags: ["annotation", "hand-drawn", "arrow", "pointer", "label", "draw-on", "tutorial"],
22942
+ hold: 300,
22943
+ presets: [
22944
+ { name: "Red marker", values: { color: [0.94, 0.27, 0.27] } },
22945
+ { name: "White chalk", values: { color: [0.97, 0.97, 0.95], width: 6, rough: 0.8 } },
22946
+ { name: "Blue pen", values: { color: [0.15, 0.39, 0.92], width: 4, bend: -0.35 } }
22947
+ ],
22948
+ derived: (v) => {
22949
+ const L = [px(v.lx), py(v.ly)];
22950
+ const T = [px(v.tx), py(v.ty)];
22951
+ const label = fit6(v.label, 900, Number(v.size));
22952
+ const empty = String(v.label ?? "").trim() === "";
22953
+ let [dx, dy] = [T[0] - L[0], T[1] - L[1]];
22954
+ let len = Math.hypot(dx, dy);
22955
+ if (len < 1) [dx, dy, len] = [1, 0, 1];
22956
+ const [ux, uy] = [dx / len, dy / len];
22957
+ const [hw, hh] = empty ? [0, 0] : [label.width / 2 + 22, label.size * 0.55 + 18];
22958
+ const edge = Math.min(Math.abs(ux) > 1e-6 ? hw / Math.abs(ux) : Infinity, Math.abs(uy) > 1e-6 ? hh / Math.abs(uy) : Infinity, Math.max(0, len - 40));
22959
+ const S = [L[0] + ux * edge, L[1] + uy * edge];
22960
+ const E = [T[0] - ux * 14, T[1] - uy * 14];
22961
+ const span = Math.max(10, Math.hypot(E[0] - S[0], E[1] - S[1]));
22962
+ const perp = [-uy, ux];
22963
+ const bend = Number(v.bend) * span * 0.5;
22964
+ const C = [(S[0] + E[0]) / 2 + perp[0] * bend, (S[1] + E[1]) / 2 + perp[1] * bend];
22965
+ const rand3 = random2(v.seed);
22966
+ const phase = rand3() * 6.28;
22967
+ const wobble = Number(v.rough) * Math.min(14, span * 0.03);
22968
+ const curve = (t) => [
22969
+ (1 - t) ** 2 * S[0] + 2 * (1 - t) * t * C[0] + t * t * E[0],
22970
+ (1 - t) ** 2 * S[1] + 2 * (1 - t) * t * C[1] + t * t * E[1]
22971
+ ];
22972
+ const pts = Array.from({ length: 18 }, (_, i) => {
22973
+ const t = i / 17;
22974
+ const [x, y] = curve(t);
22975
+ const j = wobble * Math.sin(Math.PI * t * 3 + phase) * 4 * t * (1 - t);
22976
+ return [x + perp[0] * j, y + perp[1] * j];
22977
+ });
22978
+ const before = curve(0.9);
22979
+ const angle = Math.atan2(E[1] - before[1], E[0] - before[0]);
22980
+ const barb = 22 + Number(v.width) * 2.2;
22981
+ const wing = (s) => [E[0] + barb * Math.cos(angle + Math.PI + s * (0.5 + rand3() * 0.08)), E[1] + barb * Math.sin(angle + Math.PI + s * (0.5 + rand3() * 0.08))];
22982
+ const writeMs = empty ? 0 : writing(v.label);
22983
+ return {
22984
+ ...outTimes(handArrowMotion, writeMs + 950 + ms(v.hold)),
22985
+ lx: L[0],
22986
+ ly: L[1],
22987
+ labelSize: label.size,
22988
+ labelBase: r111(L[1] + label.size * 0.32),
22989
+ shaft: smooth(pts),
22990
+ head: `M${pt(wing(1))} L${pt(E)} L${pt(wing(-1))}`,
22991
+ writeMs,
22992
+ shaftAt: Math.max(0, writeMs - 100),
22993
+ headAt: writeMs + 550
22994
+ };
22995
+ },
22996
+ motion: handArrowMotion
22997
+ };
22998
+ var calloutMotion = {
22999
+ svg: `<svg viewBox="0 0 1920 1080"><!-- {{label: text default='New: dark mode' label='Label'}} ${at3("tx", 38, "Target", "x")} ${at3("ty", 46, "Target", "y")} ${at3("bx", 64, "Label", "x")} ${at3("by", 26, "Label", "y")} {{accent: color default=#6366F1 label='Accent'}} {{box: color default=#FFFFFF label='Label box'}} {{ink: color default=#111827 label='Label colour'}} {{size: float min=14 max=80 default=34 step=1 label='Label size'}} ${holdControl(2.5)} {{font: text default="${SANS9}" label='Font'}} --><defs><filter id="calloutShadow" x="-20%" y="-40%" width="140%" height="180%"><feDropShadow dx="0" dy="6" stdDeviation="10" flood-color="#000000" flood-opacity="0.25"/></filter></defs><g id="calloutAll"><path id="calloutLeader" d="{{leader}}" fill="none" stroke="{{accent}}" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><g transform="translate({{tx}} {{ty}})"><circle id="calloutPulse" r="11" fill="none" stroke="{{accent}}" stroke-width="4" opacity="0"/><g id="calloutDot" transform="scale(0)"><circle r="16" fill="{{accent}}" fill-opacity="0.25"/><circle r="9" fill="{{accent}}"/></g></g><g transform="translate({{bx}} {{by}})"><g id="calloutBox" transform="scale(0)" filter="url(#calloutShadow)"><rect x="{{boxX}}" y="{{boxY}}" width="{{boxW}}" height="{{boxH}}" rx="14" fill="{{box}}" stroke="{{accent}}" stroke-width="3"/><text y="{{labelBase}}" text-anchor="middle" font-family="{{font}}" font-size="{{labelSize}}" font-weight="600" textLength="{{labelW}}" lengthAdjust="spacingAndGlyphs" fill="{{ink}}">{{label}}</text></g></g></g></svg>`,
23000
+ animate: [
23001
+ ...pop("calloutDot", 0, 1.3, 260),
23002
+ { id: "calloutPulse", attrs: { r: [11, 46], opacity: [0.9, 0] }, delay: 150, ms: 700, ease: "out" },
23003
+ { id: "calloutLeader", draw: true, delay: 250, ms: 500, ease: "in-out" },
23004
+ ...pop("calloutBox", 650, 1.08, 280),
23005
+ { id: "calloutPulse", attrs: { r: [11, 46], opacity: [0.9, 0] }, delay: 1300, ms: 700, ease: "out" },
23006
+ { id: "calloutAll", attrs: { opacity: [1, 0] }, delay: after(0), ms: 400 }
23007
+ ]
23008
+ };
23009
+ var callout = {
23010
+ kind: "vector",
23011
+ id: "callout",
23012
+ name: "Callout",
23013
+ description: "A dot pops on a target and pulses, a leader line draws out from it and a label box pops in at its end.",
23014
+ category: "annotation",
23015
+ tags: ["annotation", "callout", "label", "pointer", "product", "tutorial", "ui"],
23016
+ hold: 300,
23017
+ presets: [
23018
+ { name: "Indigo", values: { accent: [0.39, 0.4, 0.95] } },
23019
+ { name: "Dark", values: { accent: [0.13, 0.83, 0.93], box: [0.07, 0.09, 0.15], ink: [1, 1, 1] } },
23020
+ { name: "Alert", values: { accent: [0.94, 0.27, 0.27], box: [1, 0.95, 0.95], ink: [0.5, 0.1, 0.1] } }
23021
+ ],
23022
+ derived: (v) => {
23023
+ const T = [px(v.tx), py(v.ty)];
23024
+ const B = [px(v.bx), py(v.by)];
23025
+ const label = fit6(v.label, 760, Number(v.size));
23026
+ const [bw, bh] = [label.width + 56, label.size + 40];
23027
+ const side = B[0] >= T[0] ? -1 : 1;
23028
+ const A = [B[0] + side * bw / 2, B[1]];
23029
+ const elbow = [A[0] + side * 40, B[1]];
23030
+ return {
23031
+ ...outTimes(calloutMotion, 1150 + ms(v.hold)),
23032
+ tx: T[0],
23033
+ ty: T[1],
23034
+ bx: B[0],
23035
+ by: B[1],
23036
+ leader: `M${pt(T)} L${pt(elbow)} L${pt(A)}`,
23037
+ boxX: -r111(bw / 2),
23038
+ boxY: -r111(bh / 2),
23039
+ boxW: r111(bw),
23040
+ boxH: r111(bh),
23041
+ labelSize: label.size,
23042
+ labelW: label.width,
23043
+ labelBase: r111(label.size * 0.35)
23044
+ };
23045
+ },
23046
+ motion: calloutMotion
23047
+ };
23048
+ var bracketNoteMotion = {
23049
+ svg: `<svg viewBox="0 0 1920 1080"><!-- {{note: text default='All of these ship today' label='Note'}} ${at3("x", 62, "Bracket", "x")} {{top: float min=0 max=100 default=24 step=0.5 label='Top (%)'}} {{bottom: float min=0 max=100 default=76 step=0.5 label='Bottom (%)'}} {{flip: bool default=false label='Note on the left'}} {{color: color default=#EF4444 label='Ink'}} {{width: float min=1 max=20 default=6 step=0.5 label='Pen width'}} {{depth: float min=10 max=120 default=40 step=1 label='Bracket depth'}} {{size: float min=16 max=140 default=68 step=1 label='Note size'}} ${holdControl(2.5)} {{font: text default="${HAND}" label='Font'}} --><g id="bracketNoteAll"><path id="bracketNoteBrace" d="{{brace}}" fill="none" stroke="{{color}}" stroke-width="{{width}}" stroke-linecap="round" stroke-linejoin="round"/><text id="bracketNoteText" x="{{noteX}}" y="{{noteBase}}" text-anchor="{{anchor}}" font-family="{{font}}" font-size="{{noteSize}}" font-weight="700" fill="{{color}}">{{note}}</text></g></svg>`,
23050
+ animate: [
23051
+ { id: "bracketNoteBrace", draw: true, ms: 700, ease: "in-out" },
23052
+ { id: "bracketNoteText", write: "{{note}}", delay: 600, ms: n12("writeMs"), ease: "linear" },
23053
+ { id: "bracketNoteAll", attrs: { opacity: [1, 0] }, delay: after(0), ms: 400 }
23054
+ ]
23055
+ };
23056
+ var bracketNote = {
23057
+ kind: "vector",
23058
+ id: "bracket-note",
23059
+ name: "Bracket note",
23060
+ description: "A curly bracket drawing itself down the side of a region, then a handwritten note beside its point.",
23061
+ category: "annotation",
23062
+ tags: ["annotation", "hand-drawn", "bracket", "note", "group", "draw-on", "tutorial"],
23063
+ hold: 300,
23064
+ presets: [
23065
+ { name: "Red marker", values: { color: [0.94, 0.27, 0.27] } },
23066
+ { name: "Blue pen", values: { color: [0.15, 0.39, 0.92], width: 4 } },
23067
+ { name: "Chalk", values: { color: [0.97, 0.97, 0.95] } }
23068
+ ],
23069
+ derived: (v) => {
23070
+ const x = px(v.x);
23071
+ const [y1, y2] = [py(v.top), py(v.bottom)].sort((a, b) => a - b);
23072
+ const ym = (y1 + y2) / 2;
23073
+ const dir = v.flip ? -1 : 1;
23074
+ const w = dir * Math.max(4, Math.min(Number(v.depth), (y2 - y1) / 4));
23075
+ const h = w / 2;
23076
+ const ah = Math.abs(h);
23077
+ const brace = `M${r111(x)} ${r111(y1)} Q${r111(x + h)} ${r111(y1)} ${r111(x + h)} ${r111(y1 + ah)} L${r111(x + h)} ${r111(ym - ah)} Q${r111(x + h)} ${r111(ym)} ${r111(x + w)} ${r111(ym)} Q${r111(x + h)} ${r111(ym)} ${r111(x + h)} ${r111(ym + ah)} L${r111(x + h)} ${r111(y2 - ah)} Q${r111(x + h)} ${r111(y2)} ${r111(x)} ${r111(y2)}`;
23078
+ const noteX = x + w + dir * 24;
23079
+ const room2 = v.flip ? noteX - 96 : 1824 - noteX;
23080
+ const note = fit6(v.note, Math.max(200, room2), Number(v.size));
23081
+ const writeMs = writing(v.note);
23082
+ return {
23083
+ ...outTimes(bracketNoteMotion, 600 + writeMs + 200 + ms(v.hold)),
23084
+ brace,
23085
+ noteX: r111(noteX),
23086
+ noteBase: r111(ym + note.size * 0.3),
23087
+ noteSize: note.size,
23088
+ anchor: v.flip ? "end" : "start",
23089
+ writeMs
23090
+ };
23091
+ },
23092
+ motion: bracketNoteMotion
23093
+ };
23094
+ var scribbleOutMotion = {
23095
+ svg: `<svg viewBox="0 0 1920 1080"><!-- {{word: text default='Tuesday' label='Word'}} {{replacement: text default='Friday!' label='Replacement'}} ${at3("x", 42, "Word", "x")} ${at3("y", 50, "Word", "y")} {{size: float min=16 max=200 default=96 step=1 label='Word size'}} {{showWord: bool default=true label='Draw the word'}} {{ink: color default=#111827 label='Word colour'}} {{color: color default=#EF4444 label='Scribble'}} {{fix: color default=#2563EB label='Replacement colour'}} {{width: float min=1 max=24 default=8 step=0.5 label='Pen width'}} {{seed: int min=0 max=99 default=7 label='Seed'}} ${holdControl(2.5)} {{font: text default="${SANS9}" label='Font'}} {{handFont: text default="${HAND}" label='Handwriting font'}} --><g id="scribbleOutAll"><text id="scribbleOutWord" x="{{x}}" y="{{wordBase}}" text-anchor="middle" font-family="{{font}}" font-size="{{wordSize}}" font-weight="700" textLength="{{wordW}}" lengthAdjust="spacingAndGlyphs" fill="{{ink}}" opacity="0">{{word}}</text><path id="scribbleOutScribble" d="{{scribble}}" fill="none" stroke="{{color}}" stroke-width="{{width}}" stroke-linecap="round" stroke-linejoin="round"/><text id="scribbleOutFix" x="{{fixX}}" y="{{fixY}}" text-anchor="{{fixAnchor}}" transform="rotate(-5 {{fixX}} {{fixY}})" font-family="{{handFont}}" font-size="{{fixSize}}" font-weight="700" fill="{{fix}}">{{replacement}}</text></g></svg>`,
23096
+ animate: [
23097
+ { id: "scribbleOutWord", attrs: { opacity: [0, n12("wordOpacity")] }, ms: 300, ease: "out" },
23098
+ { id: "scribbleOutScribble", draw: true, delay: 300, ms: 1e3, ease: "in-out" },
23099
+ { id: "scribbleOutFix", write: "{{replacement}}", delay: 1300, ms: n12("writeMs"), ease: "linear" },
23100
+ { id: "scribbleOutAll", attrs: { opacity: [1, 0] }, delay: after(0), ms: 400 }
23101
+ ]
23102
+ };
23103
+ var scribbleOut = {
23104
+ kind: "vector",
23105
+ id: "scribble-out",
23106
+ name: "Scribble out",
23107
+ description: "A word scribbled out in a back-and-forth cross-hatch, then a replacement handwritten beside it.",
23108
+ category: "annotation",
23109
+ tags: ["annotation", "hand-drawn", "scribble", "strike", "correction", "draw-on", "edit"],
23110
+ hold: 300,
23111
+ presets: [
23112
+ { name: "Red and blue", values: { color: [0.94, 0.27, 0.27], fix: [0.15, 0.39, 0.92] } },
23113
+ { name: "Over a screenshot", values: { showWord: false, color: [0.94, 0.27, 0.27], fix: [0.94, 0.27, 0.27] } },
23114
+ { name: "Black marker", values: { color: [0.07, 0.07, 0.07], width: 10, fix: [0.86, 0.15, 0.15] } }
23115
+ ],
23116
+ derived: (v) => {
23117
+ const [X, Y] = [px(v.x), py(v.y)];
23118
+ const word = fit6(v.word, 1400, Number(v.size));
23119
+ const rand3 = random2(v.seed);
23120
+ const [left, right] = [X - word.width / 2 - 10, X + word.width / 2 + 10];
23121
+ const [top, bottom] = [Y - word.size * 0.46, Y + word.size * 0.32];
23122
+ const step = Math.max(6, word.size * 0.12);
23123
+ const jitter = () => (rand3() - 0.5) * word.size * 0.1;
23124
+ const pts = [];
23125
+ for (let x = left, k = 0; x <= right + step / 2; x += step, k++) pts.push([Math.min(x, right) + (k % 2 ? step * 0.4 : 0), (k % 2 ? top : bottom) + jitter()]);
23126
+ for (let x = right, k = 0; x >= left - step / 2; x -= step * 1.1, k++) pts.push([Math.max(x, left) - (k % 2 ? step * 0.4 : 0), (k % 2 ? bottom : top) + jitter() + word.size * 0.04]);
23127
+ const roomRight = 1824 - (X + word.width / 2 + 24);
23128
+ const beside = roomRight >= 280;
23129
+ const fix = fit6(v.replacement, beside ? roomRight : 1600, word.size * 0.9);
23130
+ return {
23131
+ ...outTimes(scribbleOutMotion, 1300 + writing(v.replacement, 70) + 150 + ms(v.hold)),
23132
+ x: X,
23133
+ wordSize: word.size,
23134
+ wordW: word.width,
23135
+ wordBase: r111(Y + word.size * 0.35),
23136
+ wordOpacity: v.showWord ? 1 : 0,
23137
+ scribble: `M${pts.map(pt).join(" L")}`,
23138
+ fixX: r111(beside ? X + word.width / 2 + 24 : X),
23139
+ fixY: r111(beside ? Y - word.size * 0.5 : Y - word.size * 0.75),
23140
+ fixAnchor: beside ? "start" : "middle",
23141
+ fixSize: fix.size,
23142
+ writeMs: writing(v.replacement, 70)
23143
+ };
23144
+ },
23145
+ motion: scribbleOutMotion
23146
+ };
23147
+ var STEPS = 4;
23148
+ var STEP_GAP = 450;
23149
+ var STEP_SPOTS = [[24, 30], [58, 24], [36, 64], [72, 70]];
23150
+ var STEP_LABELS = ["Open settings", "Pick a theme", "Hit save", "Share it"];
23151
+ var stepBadgesMotion = {
23152
+ svg: `<svg viewBox="0 0 1920 1080"><!-- {{count: int min=1 max=${STEPS} default=3 label='Steps'}} ${Array.from({ length: STEPS }, (_, i) => `{{l${i + 1}: text default='${STEP_LABELS[i]}' label='Step ${i + 1}'}} ${at3(`x${i + 1}`, STEP_SPOTS[i][0], `Step ${i + 1}`, "x")} ${at3(`y${i + 1}`, STEP_SPOTS[i][1], `Step ${i + 1}`, "y")}`).join(" ")} {{accent: color default=#F97316 label='Badge'}} {{badgeInk: color default=#FFFFFF label='Number colour'}} {{plate: color default=#111827 label='Label box'}} {{labelInk: color default=#FFFFFF label='Label colour'}} ${holdControl(2.5)} {{font: text default="${SANS9}" label='Font'}} --><defs><filter id="stepBadgesShadow" x="-30%" y="-60%" width="160%" height="220%"><feDropShadow dx="0" dy="4" stdDeviation="6" flood-color="#000000" flood-opacity="0.3"/></filter></defs><g id="stepBadgesAll" filter="url(#stepBadgesShadow)">${Array.from({ length: STEPS }, (_, i) => {
23153
+ const k = i + 1;
23154
+ return `<g transform="translate({{bx${k}}} {{by${k}}})" opacity="{{v${k}}}"><g id="stepBadgesLabel${k}" opacity="0" transform="translate({{lo${k}}} 0)"><rect x="{{lr${k}}}" y="-28" width="{{lw${k}}}" height="56" rx="28" fill="{{plate}}"/><text x="{{lt${k}}}" y="{{lb${k}}}" text-anchor="middle" font-family="{{font}}" font-size="{{ls${k}}}" font-weight="600" textLength="{{ll${k}}}" lengthAdjust="spacingAndGlyphs" fill="{{labelInk}}">{{l${k}}}</text></g><g id="stepBadgesBadge${k}" transform="scale(0)"><circle r="36" fill="{{accent}}"/><circle r="36" fill="none" stroke="{{badgeInk}}" stroke-width="4"/><text y="14" text-anchor="middle" font-family="{{font}}" font-size="38" font-weight="800" fill="{{badgeInk}}">${k}</text></g></g>`;
23155
+ }).join("")}</g></svg>`,
23156
+ animate: [
23157
+ ...Array.from({ length: STEPS }, (_, i) => [
23158
+ ...pop(`stepBadgesBadge${i + 1}`, i * STEP_GAP, 1.18, 260),
23159
+ { id: `stepBadgesLabel${i + 1}`, attrs: { opacity: [0, 1], transform: [`translate({{lo${i + 1}}} 0)`, "translate(0 0)"] }, delay: i * STEP_GAP + 220, ms: 380, ease: "out" }
23160
+ ]).flat(),
23161
+ { id: "stepBadgesAll", attrs: { opacity: [1, 0] }, delay: after(0), ms: 400 }
23162
+ ]
23163
+ };
23164
+ var stepBadges = {
23165
+ kind: "vector",
23166
+ id: "step-badges",
23167
+ name: "Step badges",
23168
+ description: "Numbered step badges popping in one after another at points on a screen, each with a label sliding out beside it.",
23169
+ category: "annotation",
23170
+ tags: ["annotation", "steps", "badges", "numbers", "tutorial", "onboarding", "product"],
23171
+ hold: 300,
23172
+ presets: [
23173
+ { name: "Orange", values: { accent: [0.98, 0.45, 0.09] } },
23174
+ { name: "Light", values: { accent: [0.15, 0.39, 0.92], plate: [1, 1, 1], labelInk: [0.07, 0.09, 0.15] } },
23175
+ { name: "Four steps", values: { count: 4 } }
23176
+ ],
23177
+ derived: (v) => {
23178
+ const count = clamp(Math.round(Number(v.count)) || 1, 1, STEPS);
23179
+ const out = { ...outTimes(stepBadgesMotion, (count - 1) * STEP_GAP + 700 + ms(v.hold)) };
23180
+ for (let k = 1; k <= STEPS; k++) {
23181
+ const [x, y] = [px(v[`x${k}`]), py(v[`y${k}`])];
23182
+ const label = fit6(v[`l${k}`], 520, 28);
23183
+ const w = label.width + 44;
23184
+ const left = x + 46 + w > 1824;
23185
+ out[`bx${k}`] = x;
23186
+ out[`by${k}`] = y;
23187
+ out[`v${k}`] = k <= count ? 1 : 0;
23188
+ out[`lw${k}`] = r111(w);
23189
+ out[`lr${k}`] = left ? -r111(46 + w) : 46;
23190
+ out[`lt${k}`] = left ? -r111(46 + w / 2) : r111(46 + w / 2);
23191
+ out[`lo${k}`] = left ? 24 : -24;
23192
+ out[`ls${k}`] = label.size;
23193
+ out[`ll${k}`] = label.width;
23194
+ out[`lb${k}`] = r111(label.size * 0.35);
23195
+ }
23196
+ return out;
23197
+ },
23198
+ motion: stepBadgesMotion
23199
+ };
23200
+ var FOCUS = [[30, 32, 30, 24], [70, 40, 34, 30], [50, 76, 60, 18]];
23201
+ var box = (k) => ["x", "y", "width", "height"].map((a) => `{{${a}${k}}}`);
23202
+ var between = (from, to) => Object.fromEntries(["x", "y", "width", "height"].map((a, i) => [a, [box(from)[i], box(to)[i]]]));
23203
+ var focusBoxMotion = {
23204
+ svg: `<svg viewBox="0 0 1920 1080"><!-- {{stops: int min=2 max=3 default=3 label='Stops'}} ${FOCUS.map(([x, y, w, h], i) => `${at3(`cx${i + 1}`, x, `Stop ${i + 1}`, "x")} ${at3(`cy${i + 1}`, y, `Stop ${i + 1}`, "y")} {{w${i + 1}: float min=2 max=100 default=${w} step=0.5 label='Stop ${i + 1} width (%)'}} {{h${i + 1}: float min=2 max=100 default=${h} step=0.5 label='Stop ${i + 1} height (%)'}}`).join(" ")} {{dim: color default=#000000 label='Dim'}} {{dimOpacity: float min=0 max=1 default=0.62 step=0.02 label='Dim opacity'}} {{ring: color default=#FACC15 label='Outline'}} {{ringWidth: float min=0 max=16 default=4 step=0.5 label='Outline width'}} {{radius: float min=0 max=80 default=18 step=1 label='Corner radius'}} ${holdControl(1.5, "Hold at each stop (s)")} --><defs><mask id="focusBoxMask" maskUnits="userSpaceOnUse" x="0" y="0" width="1920" height="1080"><rect width="1920" height="1080" fill="#FFFFFF"/><rect id="focusBoxHole" x="{{x1}}" y="{{y1}}" width="{{width1}}" height="{{height1}}" rx="{{radius}}" fill="#000000"/></mask></defs><rect id="focusBoxDim" width="1920" height="1080" fill="{{dim}}" opacity="0" mask="url(#focusBoxMask)"/><rect id="focusBoxRing" x="{{x1}}" y="{{y1}}" width="{{width1}}" height="{{height1}}" rx="{{radius}}" fill="none" stroke="{{ring}}" stroke-width="{{ringWidth}}" opacity="0"/></svg>`,
23205
+ animate: [
23206
+ { id: "focusBoxDim", attrs: { opacity: [0, n12("dimOpacity")] }, ms: 500, ease: "out" },
23207
+ { id: "focusBoxRing", attrs: { opacity: [0, 1] }, delay: 200, ms: 400, ease: "out" },
23208
+ ...["focusBoxHole", "focusBoxRing"].flatMap((id) => [
23209
+ { id, attrs: between(1, 2), delay: n12("move2"), ms: 700, ease: "in-out" },
23210
+ { id, attrs: between(2, 3), delay: n12("move3"), ms: n12("move3ms"), ease: "in-out" }
23211
+ ]),
23212
+ { id: "focusBoxRing", attrs: { opacity: [1, 0] }, delay: after(0), ms: 300 },
23213
+ { id: "focusBoxDim", attrs: { opacity: [n12("dimOpacity"), 0] }, delay: after(100), ms: 450 }
23214
+ ]
23215
+ };
23216
+ var focusBox = {
23217
+ kind: "vector",
23218
+ id: "focus-box",
23219
+ name: "Focus box",
23220
+ description: "A spotlight dimming a screen everywhere but a rounded box, which glides from one region to the next and the next.",
23221
+ category: "annotation",
23222
+ tags: ["annotation", "spotlight", "focus", "highlight", "dim", "tutorial", "product", "walkthrough"],
23223
+ hold: 300,
23224
+ presets: [
23225
+ { name: "Yellow outline", values: { ring: [0.98, 0.8, 0.08] } },
23226
+ { name: "Soft", values: { dimOpacity: 0.4, ringWidth: 0, radius: 32 } },
23227
+ { name: "Light", values: { dim: [1, 1, 1], dimOpacity: 0.7, ring: [0.15, 0.39, 0.92] } }
23228
+ ],
23229
+ derived: (v) => {
23230
+ const stops = Math.round(Number(v.stops)) >= 3 ? 3 : 2;
23231
+ const hold = ms(v.hold);
23232
+ const move2 = 500 + hold;
23233
+ const move3 = move2 + 700 + (stops === 3 ? hold : 0);
23234
+ const out = {
23235
+ ...outTimes(focusBoxMotion, (stops === 3 ? move3 + 700 : move2 + 700) + hold),
23236
+ move2,
23237
+ move3,
23238
+ move3ms: stops === 3 ? 700 : 0
23239
+ };
23240
+ for (let k = 1; k <= 3; k++) {
23241
+ const s = stops === 2 && k === 3 ? 2 : k;
23242
+ const [w, h] = [px(v[`w${s}`]), py(v[`h${s}`])];
23243
+ out[`x${k}`] = r111(px(v[`cx${s}`]) - w / 2);
23244
+ out[`y${k}`] = r111(py(v[`cy${s}`]) - h / 2);
23245
+ out[`width${k}`] = w;
23246
+ out[`height${k}`] = h;
23247
+ }
23248
+ return out;
23249
+ },
23250
+ motion: focusBoxMotion
23251
+ };
23252
+ var press = (s) => `scale(${s})`;
23253
+ var fakeCursorMotion = {
23254
+ svg: `<svg viewBox="0 0 1920 1080"><!-- {{typed: text default='hello@flair.dev' label='Typed text'}} ${at3("sx", 82, "Start", "x")} ${at3("sy", 86, "Start", "y")} ${at3("cx", 28, "Click", "x")} ${at3("cy", 30, "Click", "y")} ${at3("dx", 52, "Drag to", "x")} ${at3("dy", 52, "Drag to", "y")} ${at3("fx", 62, "Text field", "x")} ${at3("fy", 74, "Text field", "y")} {{field: bool default=true label='Draw the text field'}} {{accent: color default=#3B82F6 label='Accent'}} {{fieldColor: color default=#FFFFFF label='Field'}} {{fieldBorder: color default=#CBD5E1 label='Field border'}} {{ink: color default=#0F172A label='Text colour'}} {{pointer: color default=#FFFFFF label='Cursor'}} {{size: float min=14 max=64 default=30 step=1 label='Text size'}} ${holdControl(2)} {{font: text default="${SANS9}" label='Font'}} --><g id="fakeCursorAll"><rect id="fakeCursorMarquee" x="{{cx}}" y="{{cy}}" width="0" height="0" fill="{{accent}}" fill-opacity="0.15" stroke="{{accent}}" stroke-width="2" stroke-dasharray="8 6"/><rect id="fakeCursorField" x="{{fieldX}}" y="{{fieldY}}" width="{{fieldW}}" height="{{fieldH}}" rx="10" fill="{{fieldColor}}" stroke="{{fieldBorder}}" stroke-width="3" opacity="{{fieldOpacity}}"/><text id="fakeCursorTyped" x="{{textX}}" y="{{textBase}}" font-family="{{font}}" font-size="{{size}}" fill="{{ink}}" xml:space="preserve">{{typed}}</text><circle id="fakeCursorRipple1" cx="{{cx}}" cy="{{cy}}" r="0" fill="{{accent}}" opacity="0"/><circle id="fakeCursorRipple2" cx="{{kx}}" cy="{{ky}}" r="0" fill="{{accent}}" opacity="0"/><g id="fakeCursorPos" transform="translate({{sx}} {{sy}})" opacity="0"><g id="fakeCursorPress" transform="${press(1.3)}"><path d="${CURSOR}" fill="{{pointer}}" stroke="#111111" stroke-width="2" stroke-linejoin="round"/></g></g></g></svg>`,
23255
+ animate: [
23256
+ { id: "fakeCursorPos", attrs: { opacity: [0, 1] }, ms: 300, ease: "out" },
23257
+ { id: "fakeCursorPos", attrs: { transform: ["translate({{sx}} {{sy}})", "translate({{cx}} {{cy}})"] }, delay: 300, ms: 800, ease: "in-out" },
23258
+ // Click.
23259
+ { id: "fakeCursorPress", attrs: { transform: [press(1.3), press(1.1)] }, delay: 1100, ms: 100, ease: "out" },
23260
+ { id: "fakeCursorPress", attrs: { transform: [press(1.1), press(1.3)] }, delay: 1200, ms: 120, ease: "out" },
23261
+ { id: "fakeCursorRipple1", attrs: { r: [0, 60], opacity: [0.45, 0] }, delay: 1100, ms: 500, ease: "out" },
23262
+ // Press, drag out a selection, let go.
23263
+ { id: "fakeCursorPress", attrs: { transform: [press(1.3), press(1.1)] }, delay: 1500, ms: 100, ease: "out" },
23264
+ { id: "fakeCursorPos", attrs: { transform: ["translate({{cx}} {{cy}})", "translate({{dx}} {{dy}})"] }, delay: 1600, ms: 1e3, ease: "in-out" },
23265
+ { id: "fakeCursorMarquee", attrs: { x: [n12("cx"), n12("mx")], y: [n12("cy"), n12("my")], width: [0, n12("mw")], height: [0, n12("mh")] }, delay: 1600, ms: 1e3, ease: "in-out" },
23266
+ { id: "fakeCursorPress", attrs: { transform: [press(1.1), press(1.3)] }, delay: 2600, ms: 120, ease: "out" },
23267
+ { id: "fakeCursorMarquee", attrs: { opacity: [1, 0] }, delay: 2800, ms: 300, ease: "linear" },
23268
+ // Click into the field and type.
23269
+ { id: "fakeCursorPos", attrs: { transform: ["translate({{dx}} {{dy}})", "translate({{kx}} {{ky}})"] }, delay: 2900, ms: 800, ease: "in-out" },
23270
+ { id: "fakeCursorPress", attrs: { transform: [press(1.3), press(1.1)] }, delay: 3700, ms: 100, ease: "out" },
23271
+ { id: "fakeCursorPress", attrs: { transform: [press(1.1), press(1.3)] }, delay: 3800, ms: 120, ease: "out" },
23272
+ { id: "fakeCursorRipple2", attrs: { r: [0, 60], opacity: [0.45, 0] }, delay: 3700, ms: 500, ease: "out" },
23273
+ { id: "fakeCursorField", attrs: { stroke: ["{{fieldBorder}}", "{{accent}}"] }, delay: 3700, ms: 200, ease: "linear" },
23274
+ // The caret shows once the field has focus, and the cursor steps aside to let the text be read.
23275
+ { id: "fakeCursorTyped", attrs: { opacity: [0, 0] }, ms: 0 },
23276
+ { id: "fakeCursorTyped", attrs: { opacity: [1, 1] }, delay: 3800, ms: 0 },
23277
+ { id: "fakeCursorPos", attrs: { transform: ["translate({{kx}} {{ky}})", "translate({{ax}} {{ay}})"] }, delay: 3950, ms: 500, ease: "in-out" },
23278
+ { id: "fakeCursorTyped", write: "{{typed}}", caret: "|", delay: 4e3, ms: n12("typeMs"), ease: "linear" },
23279
+ { id: "fakeCursorAll", attrs: { opacity: [1, 0] }, delay: after(0), ms: 400 }
23280
+ ]
23281
+ };
23282
+ var fakeCursor = {
23283
+ kind: "vector",
23284
+ id: "fake-cursor",
23285
+ name: "Fake cursor",
23286
+ description: "A product-demo cursor over a screen: it glides to a point and clicks with a ripple, drags out a selection, then clicks a text field and types.",
23287
+ category: "annotation",
23288
+ tags: ["annotation", "cursor", "pointer", "demo", "product", "click", "typing", "walkthrough"],
23289
+ hold: 300,
23290
+ presets: [
23291
+ { name: "Light UI", values: { accent: [0.23, 0.51, 0.96] } },
23292
+ { name: "Dark UI", values: { fieldColor: [0.07, 0.09, 0.15], fieldBorder: [0.2, 0.25, 0.33], ink: [0.95, 0.96, 0.98], accent: [0.13, 0.83, 0.93] } },
23293
+ { name: "Over a screenshot", values: { field: false } }
23294
+ ],
23295
+ derived: (v) => {
23296
+ const [cx, cy, dx, dy, fx, fy] = [px(v.cx), py(v.cy), px(v.dx), py(v.dy), px(v.fx), py(v.fy)];
23297
+ const size = Number(v.size);
23298
+ const text = fit6(v.typed, 1400, size);
23299
+ const fieldW = Math.max(360, text.width + 72);
23300
+ const fieldX = clamp(fx - fieldW / 2, 24, Math.max(24, 1896 - fieldW));
23301
+ const fieldH = size + 40;
23302
+ return {
23303
+ ...outTimes(fakeCursorMotion, 4e3 + writing(v.typed, 65) + 300 + ms(v.hold)),
23304
+ sx: px(v.sx),
23305
+ sy: py(v.sy),
23306
+ cx,
23307
+ cy,
23308
+ dx,
23309
+ dy,
23310
+ mx: Math.min(cx, dx),
23311
+ my: Math.min(cy, dy),
23312
+ mw: r111(Math.abs(dx - cx)),
23313
+ mh: r111(Math.abs(dy - cy)),
23314
+ fieldX: r111(fieldX),
23315
+ fieldY: r111(fy - fieldH / 2),
23316
+ fieldW: r111(fieldW),
23317
+ fieldH: r111(fieldH),
23318
+ fieldOpacity: v.field ? 1 : 0,
23319
+ textX: r111(fieldX + 28),
23320
+ textBase: r111(fy + size * 0.35),
23321
+ kx: r111(fieldX + 40),
23322
+ ky: fy,
23323
+ ax: r111(fieldX + 40 + 50),
23324
+ ay: r111(fy + fieldH / 2 + 50),
23325
+ typeMs: writing(v.typed, 65)
23326
+ };
23327
+ },
23328
+ motion: fakeCursorMotion
23329
+ };
23330
+ var TOUR = [[22, 22], [70, 34], [44, 74]];
23331
+ var TOUR_TITLES = ["Search anything from here", "Your projects live here", "Invite your team"];
23332
+ var TOUR_IN = 400;
23333
+ var TOUR_MOVE = 600;
23334
+ var tooltipTourMotion = {
23335
+ svg: `<svg viewBox="0 0 1920 1080"><!-- ${TOUR.map(([x, y], i) => `{{t${i + 1}: text default='${TOUR_TITLES[i]}' label='Step ${i + 1}'}} ${at3(`x${i + 1}`, x, `Target ${i + 1}`, "x")} ${at3(`y${i + 1}`, y, `Target ${i + 1}`, "y")}`).join(" ")} {{radius: float min=10 max=200 default=54 step=1 label='Highlight radius'}} {{accent: color default=#8B5CF6 label='Accent'}} {{plate: color default=#FFFFFF label='Tooltip'}} {{ink: color default=#111827 label='Text colour'}} {{muted: color default=#6B7280 label='Counter colour'}} {{size: float min=16 max=48 default=28 step=1 label='Text size'}} ${holdControl(1.8, "Hold at each step (s)")} {{font: text default="${SANS9}" label='Font'}} --><defs><filter id="tooltipTourShadow" x="-20%" y="-30%" width="140%" height="170%"><feDropShadow dx="0" dy="8" stdDeviation="12" flood-color="#000000" flood-opacity="0.28"/></filter></defs><g id="tooltipTourAll" opacity="0"><circle id="tooltipTourHalo" cx="{{hx1}}" cy="{{hy1}}" r="0" fill="{{accent}}" fill-opacity="0.16" stroke="{{accent}}" stroke-width="4"/><g filter="url(#tooltipTourShadow)"><path id="tooltipTourArrow" d="{{arrow1}}" fill="{{plate}}"/><g id="tooltipTourTip" transform="translate({{tx1}} {{ty1}})"><rect width="{{tipW}}" height="{{tipH}}" rx="14" fill="{{plate}}"/>${[1, 2, 3].map((k) => `<text id="tooltipTourTitle${k}" x="28" y="{{titleBase}}" font-family="{{font}}" font-size="{{s${k}}}" font-weight="700" textLength="{{w${k}}}" lengthAdjust="spacingAndGlyphs" fill="{{ink}}" opacity="${k === 1 ? 1 : 0}">{{t${k}}}</text>`).join("")}<text id="tooltipTourCount" x="28" y="{{countBase}}" font-family="{{font}}" font-size="22" font-weight="600" fill="{{muted}}">1 / 3</text>${[0, 1, 2].map((k) => `<circle cx="{{dot${k}}}" cy="{{dotY}}" r="5" fill="{{muted}}" fill-opacity="0.35"/>`).join("")}<rect id="tooltipTourActive" x="{{pill0}}" y="{{pillY}}" width="22" height="10" rx="5" fill="{{accent}}"/></g></g></g></svg>`,
23336
+ animate: [
23337
+ { id: "tooltipTourAll", attrs: { opacity: [0, 1] }, ms: TOUR_IN, ease: "out" },
23338
+ { id: "tooltipTourHalo", attrs: { r: [0, n12("radius")] }, ms: TOUR_IN, ease: "out" },
23339
+ ...[2, 3].flatMap((k) => {
23340
+ const t = n12(`m${k}`);
23341
+ return [
23342
+ { id: "tooltipTourTip", attrs: { transform: [`translate({{tx${k - 1}}} {{ty${k - 1}}})`, `translate({{tx${k}}} {{ty${k}}})`] }, delay: t, ms: TOUR_MOVE, ease: "in-out" },
23343
+ { id: "tooltipTourArrow", attrs: { d: [`{{arrow${k - 1}}}`, `{{arrow${k}}}`] }, delay: t, ms: TOUR_MOVE, ease: "in-out" },
23344
+ { id: "tooltipTourHalo", attrs: { cx: [n12(`hx${k - 1}`), n12(`hx${k}`)], cy: [n12(`hy${k - 1}`), n12(`hy${k}`)] }, delay: t, ms: TOUR_MOVE, ease: "in-out" },
23345
+ { id: `tooltipTourTitle${k - 1}`, attrs: { opacity: [1, 0] }, delay: t, ms: 200, ease: "linear" },
23346
+ { id: `tooltipTourTitle${k}`, attrs: { opacity: [0, 1] }, delay: n12(`f${k}`), ms: 250, ease: "linear" },
23347
+ { id: "tooltipTourCount", write: `${k} / 3`, delay: n12(`f${k}`), ms: 0 },
23348
+ { id: "tooltipTourActive", attrs: { x: [n12(`pill${k - 2}`), n12(`pill${k - 1}`)] }, delay: t, ms: 400, ease: "in-out" }
23349
+ ];
23350
+ }),
23351
+ { id: "tooltipTourAll", attrs: { opacity: [1, 0] }, delay: after(0), ms: 400 }
23352
+ ]
23353
+ };
23354
+ var tooltipTour = {
23355
+ kind: "vector",
23356
+ id: "tooltip-tour",
23357
+ name: "Tooltip tour",
23358
+ description: "A guided-tour tooltip moving between three highlighted spots on a screen, its text changing with a 1/3, 2/3, 3/3 counter and a progress dot.",
23359
+ category: "annotation",
23360
+ tags: ["annotation", "tooltip", "tour", "onboarding", "walkthrough", "product", "ui"],
23361
+ hold: 300,
23362
+ presets: [
23363
+ { name: "Violet", values: { accent: [0.55, 0.36, 0.96] } },
23364
+ { name: "Dark", values: { plate: [0.07, 0.09, 0.15], ink: [1, 1, 1], muted: [0.6, 0.64, 0.7], accent: [0.13, 0.83, 0.93] } },
23365
+ { name: "Blue", values: { accent: [0.15, 0.39, 0.92] } }
23366
+ ],
23367
+ derived: (v) => {
23368
+ const size = Number(v.size);
23369
+ const r = Number(v.radius);
23370
+ const titles = [1, 2, 3].map((k) => fit6(v[`t${k}`], 700, size));
23371
+ const w = Math.max(340, ...titles.map((t) => t.width + 56));
23372
+ const h = size + 92;
23373
+ const hold = ms(v.hold);
23374
+ const m2 = TOUR_IN + hold;
23375
+ const m3 = m2 + TOUR_MOVE + hold;
23376
+ const out = {
23377
+ ...outTimes(tooltipTourMotion, m3 + TOUR_MOVE + hold),
23378
+ m2,
23379
+ m3,
23380
+ f2: m2 + 300,
23381
+ f3: m3 + 300,
23382
+ radius: r,
23383
+ tipW: r111(w),
23384
+ tipH: r111(h),
23385
+ titleBase: r111(26 + size * 0.8),
23386
+ countBase: r111(h - 24),
23387
+ dotY: r111(h - 31),
23388
+ pillY: r111(h - 36)
23389
+ };
23390
+ [1, 2, 3].forEach((k, i) => {
23391
+ const [x, y] = [px(v[`x${k}`]), py(v[`y${k}`])];
23392
+ const left = clamp(x - w / 2, 96, Math.max(96, 1824 - w));
23393
+ const below = y + r + 22 + h <= 1e3;
23394
+ const top = below ? y + r + 22 : y - r - 22 - h;
23395
+ const ax = clamp(x, left + 28, left + w - 28);
23396
+ const edge = below ? top : top + h;
23397
+ const tip = below ? edge - 14 : edge + 14;
23398
+ out[`s${k}`] = titles[i].size;
23399
+ out[`w${k}`] = titles[i].width;
23400
+ out[`hx${k}`] = x;
23401
+ out[`hy${k}`] = y;
23402
+ out[`tx${k}`] = r111(left);
23403
+ out[`ty${k}`] = r111(top);
23404
+ out[`arrow${k}`] = `M${r111(ax - 14)} ${r111(edge)} L${r111(ax)} ${r111(tip)} L${r111(ax + 14)} ${r111(edge)} Z`;
23405
+ out[`dot${i}`] = r111(w - 28 - (2 - i) * 24);
23406
+ out[`pill${i}`] = r111(w - 28 - (2 - i) * 24 - 11);
23407
+ });
23408
+ return out;
23409
+ },
23410
+ motion: tooltipTourMotion
23411
+ };
23412
+ var annotationEffects = [handCircle, handArrow, callout, bracketNote, scribbleOut, stepBadges, focusBox, fakeCursor, tooltipTour];
23413
+
22202
23414
  // src/catalog/vector.ts
22203
23415
  var spinner = {
22204
23416
  kind: "vector",
@@ -22644,7 +23856,7 @@ var dripText = {
22644
23856
  const left = 200 - width / 2;
22645
23857
  const runs = Math.min(DRIP_RUNS, Math.max(1, Math.round(width / DRIP_RUN)));
22646
23858
  const run = width / runs;
22647
- const r111 = (n12) => Math.round(n12 * 10) / 10;
23859
+ const r112 = (n13) => Math.round(n13 * 10) / 10;
22648
23860
  const edge = Number(v.stroke) / 2 / DRIP_SIZE;
22649
23861
  const tip = (DRIP_TOP + Math.max(...DRIPS2.map((d) => d.length)) * Number(v.drip) + 13 * 0.72 - DRIP_BASELINE) / DRIP_SIZE;
22650
23862
  const overhang = [edge, edge, Math.max(0.05, tip) + edge, edge].map((e) => Math.round(e * 100) / 100).join(" ");
@@ -22658,15 +23870,15 @@ var dripText = {
22658
23870
  overhang,
22659
23871
  ...Object.fromEntries(
22660
23872
  DRIP_SLOTS.flatMap((d) => {
22661
- const x = r111(left + (d.run + d.at) * run);
22662
- const length = r111(d.length * Number(v.drip));
23873
+ const x = r112(left + (d.run + d.at) * run);
23874
+ const length = r112(d.length * Number(v.drip));
22663
23875
  return [
22664
23876
  [`show${d.i}`, d.run < runs ? "inline" : "none"],
22665
23877
  [`x${d.i}`, x],
22666
- [`left${d.i}`, r111(x - d.width / 2)],
22667
- [`shine${d.i}`, r111(x - d.width * 0.25)],
23878
+ [`left${d.i}`, r112(x - d.width / 2)],
23879
+ [`shine${d.i}`, r112(x - d.width * 0.25)],
22668
23880
  [`len${d.i}`, length],
22669
- [`tip${d.i}`, r111(DRIP_TOP + length)]
23881
+ [`tip${d.i}`, r112(DRIP_TOP + length)]
22670
23882
  ];
22671
23883
  })
22672
23884
  )
@@ -22678,23 +23890,23 @@ var dripText = {
22678
23890
  (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>`
22679
23891
  ).join("");
22680
23892
  const outline4 = `fill="{{outline}}" stroke="{{outline}}" stroke-width="{{stroke}}"`;
22681
- const pop = (from, to) => [`translate(200 100) scale(${from}) translate(-200 -100)`, `translate(200 100) scale(${to}) translate(-200 -100)`];
23893
+ const pop2 = (from, to) => [`translate(200 100) scale(${from}) translate(-200 -100)`, `translate(200 100) scale(${to}) translate(-200 -100)`];
22682
23894
  return {
22683
- 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", outline4)}${word} fill="none" stroke="{{outline}}" stroke-width="{{stroke}}">{{word}}</text>${drips("f", `fill="{{fill}}"`)}${word} fill="{{fill}}">{{word}}</text>${DRIP_SLOTS.map(
23895
+ 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="${pop2(0.6, 1)[0]}" opacity="0">${drips("o", outline4)}${word} fill="none" stroke="{{outline}}" stroke-width="{{stroke}}">{{word}}</text>${drips("f", `fill="{{fill}}"`)}${word} fill="{{fill}}">{{word}}</text>${DRIP_SLOTS.map(
22684
23896
  (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"/>`
22685
23897
  ).join("")}</g></g></svg>`,
22686
23898
  animate: [
22687
- { id: "word", attrs: { transform: pop(0.6, 1.08), opacity: [0, 1] }, ms: 260, ease: "out" },
22688
- { id: "word", attrs: { transform: pop(1.08, 1) }, delay: 260, ms: 200, ease: "in-out" },
23899
+ { id: "word", attrs: { transform: pop2(0.6, 1.08), opacity: [0, 1] }, ms: 260, ease: "out" },
23900
+ { id: "word", attrs: { transform: pop2(1.08, 1) }, delay: 260, ms: 200, ease: "in-out" },
22689
23901
  ...DRIP_SLOTS.flatMap((d) => {
22690
- const at3 = { delay: 400 + d.delay, ms: d.ms, ease: "in-out" };
23902
+ const at4 = { delay: 400 + d.delay, ms: d.ms, ease: "in-out" };
22691
23903
  const tip = `{{tip${d.i}}}`;
22692
23904
  return [
22693
23905
  ...["o", "f"].flatMap((layer) => [
22694
- { id: `${layer}r${d.i}`, attrs: { height: [0, `{{len${d.i}}}`] }, ...at3 },
22695
- { id: `${layer}c${d.i}`, attrs: { cy: [DRIP_TOP, tip] }, ...at3 }
23906
+ { id: `${layer}r${d.i}`, attrs: { height: [0, `{{len${d.i}}}`] }, ...at4 },
23907
+ { id: `${layer}c${d.i}`, attrs: { cy: [DRIP_TOP, tip] }, ...at4 }
22696
23908
  ]),
22697
- { id: `s${d.i}`, attrs: { cy: [DRIP_TOP, tip], opacity: [0, 0.9] }, ...at3 }
23909
+ { id: `s${d.i}`, attrs: { cy: [DRIP_TOP, tip], opacity: [0, 0.9] }, ...at4 }
22698
23910
  ];
22699
23911
  })
22700
23912
  ]
@@ -22879,7 +24091,7 @@ var SHADER_EFFECTS = [
22879
24091
  ...scrollShaders,
22880
24092
  ...transitions
22881
24093
  ];
22882
- var EFFECTS = [...SHADER_EFFECTS, ...vectorEffects, ...textEffects, ...morphEffects, ...interactionEffects, ...pyroEffects, ...timewarpEffects, ...meltEffects, ...electricEffects, ...pixelEffects, ...splatterEffects, ...bubbleEffects, ...stickerEffects, ...trailEffects, ...impactEffects, ...comicEffects, ...glowEffects, ...shadowEffects, ...lightingEffects, ...scrollEffects].sort(
24094
+ var EFFECTS = [...SHADER_EFFECTS, ...vectorEffects, ...textEffects, ...morphEffects, ...interactionEffects, ...pyroEffects, ...timewarpEffects, ...meltEffects, ...electricEffects, ...pixelEffects, ...splatterEffects, ...bubbleEffects, ...stickerEffects, ...trailEffects, ...impactEffects, ...comicEffects, ...glowEffects, ...shadowEffects, ...lightingEffects, ...scrollEffects, ...broadcastEffects, ...annotationEffects].sort(
22883
24095
  (a, b) => EFFECT_CATEGORIES.indexOf(a.category) - EFFECT_CATEGORIES.indexOf(b.category)
22884
24096
  );
22885
24097
  var byId = new Map(EFFECTS.map((e) => [e.id, e]));
@@ -22955,4 +24167,4 @@ export {
22955
24167
  effectControls,
22956
24168
  effectDefaults
22957
24169
  };
22958
- //# sourceMappingURL=chunk-6GFOLHCT.js.map
24170
+ //# sourceMappingURL=chunk-UIODYJ6B.js.map