@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.
- package/CHANGELOG.md +6 -0
- package/README.md +6 -0
- package/dist/catalog/index.d.ts +2 -2
- package/dist/catalog/index.js +1 -1
- package/dist/{chunk-MBKNTXQX.js → chunk-HSYDTVTD.js} +3 -3
- package/dist/{chunk-KZYZX3V5.js → chunk-MAYKC3RF.js} +2 -2
- package/dist/{chunk-6GFOLHCT.js → chunk-UIODYJ6B.js} +1372 -160
- package/dist/chunk-UIODYJ6B.js.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/editor/index.d.ts +1 -1
- package/dist/editor/index.js +1 -1
- package/dist/{effect-YBWDNIYG.js → effect-RGABT3BK.js} +4 -4
- package/dist/embed/index.d.ts +1 -1
- package/dist/embed/index.js +2 -2
- package/dist/node.d.ts +1 -1
- package/dist/node.js +3 -3
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +2 -2
- package/dist/svelte/index.d.ts +1 -1
- package/dist/svelte/index.js +2 -2
- package/dist/{types-wh28fXW4.d.ts → types-DuGVdJbt.d.ts} +1 -1
- package/package.json +1 -1
- package/dist/chunk-6GFOLHCT.js.map +0 -1
- /package/dist/{chunk-MBKNTXQX.js.map → chunk-HSYDTVTD.js.map} +0 -0
- /package/dist/{chunk-KZYZX3V5.js.map → chunk-MAYKC3RF.js.map} +0 -0
- /package/dist/{effect-YBWDNIYG.js.map → effect-RGABT3BK.js.map} +0 -0
|
@@ -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
|
|
32
|
-
return { name: control.name, label: control.label, holds:
|
|
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((
|
|
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 = (
|
|
1977
|
-
{ id: "check", step: 1, attrs: { opacity: [0, 1] }, delay:
|
|
1978
|
-
{ id: "check", step: 1, draw: true, delay:
|
|
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,
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
2537
|
+
const at4 = reply + 450 + k * BOUNCE_MS + j * 150;
|
|
2538
2538
|
return [
|
|
2539
|
-
[`rise${k}_${j}`,
|
|
2540
|
-
[`fall${k}_${j}`,
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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(([
|
|
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
|
|
2737
|
-
if (
|
|
2736
|
+
const ms2 = at4 - from;
|
|
2737
|
+
if (ms2 === 0)
|
|
2738
2738
|
return [
|
|
2739
|
-
snap3("sticker", "transform", pose(120, 120, s, r),
|
|
2740
|
-
snap3("shade", "transform", pose(120, 120, s, r, dy),
|
|
2741
|
-
snap3("shade", "opacity", o,
|
|
2742
|
-
snap3("shadeBlur", "stdDeviation", blur,
|
|
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(([, , ,
|
|
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
|
|
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:
|
|
2890
|
-
{ id: `s${i}`, attrs: { transform: [pose(x, y, 0, r - 30), pose(x, y, 1.18, r + 4)] }, delay:
|
|
2891
|
-
{ id: `s${i}`, attrs: { transform: [pose(x, y, 1.18, r + 4), pose(x, y, 1, r)] }, delay:
|
|
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
|
|
2953
|
-
const right = TORN_RIGHT.map((dx, k) => [TAPE_L - dx,
|
|
2954
|
-
const left = TORN_LEFT.map((dx, k) => [dx,
|
|
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
|
|
3030
|
-
return [...SEAL_BEAT.map(([
|
|
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(([
|
|
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:
|
|
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(([
|
|
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:
|
|
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(([
|
|
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:
|
|
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
|
|
3503
|
+
let at4 = 480;
|
|
3504
3504
|
for (let i = 0; i < SB_LINES; i++) {
|
|
3505
3505
|
const line = lines[i] ?? "";
|
|
3506
|
-
const
|
|
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}`]:
|
|
3512
|
-
[`ms${i}`]:
|
|
3511
|
+
[`at${i}`]: at4,
|
|
3512
|
+
[`ms${i}`]: ms2
|
|
3513
3513
|
});
|
|
3514
|
-
|
|
3514
|
+
at4 += line ? ms2 + 80 : 0;
|
|
3515
3515
|
}
|
|
3516
|
-
out.outAt =
|
|
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(([
|
|
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:
|
|
3623
|
-
{ id: `fcPoke${k}`, attrs: { transform: [out, hidden] }, delay:
|
|
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,
|
|
3626
|
-
{ id: `fcHit${k}`, attrs: { transform: [about4(x, y, 0.3), about4(x, y, 1.2)] }, delay:
|
|
3627
|
-
{ id: `fcHit${k}`, attrs: { opacity: [1, 0] }, delay:
|
|
3628
|
-
snap5(`fcHit${k}`, "transform", about4(x, y, 0.3),
|
|
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,
|
|
3762
|
-
[
|
|
3763
|
-
[
|
|
3764
|
-
[
|
|
3765
|
-
[
|
|
3766
|
-
], { ease: "out" }).concat(snap5(`aeSpark${i}`, "transform", aeSpark(x, y, 0, 0),
|
|
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
|
|
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 =
|
|
4031
|
-
const end =
|
|
4032
|
-
const start =
|
|
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,
|
|
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,
|
|
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
|
|
4286
|
-
return { id: `s${i}`, from:
|
|
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
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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 = (
|
|
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,
|
|
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(
|
|
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
|
|
4662
|
-
const c = [
|
|
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((
|
|
5596
|
-
animate.push(snap7("invader", "transform", pose3(
|
|
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,
|
|
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,
|
|
5651
|
-
animate.push(snap7(`f${i}`, "opacity", on,
|
|
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,
|
|
5654
|
-
animate.push({ id: `h${i}`, attrs: { transform: [heartPose2(i, 1), heartPose2(i, 0.82)] }, delay:
|
|
5655
|
-
animate.push({ id: `h${i}`, attrs: { transform: [heartPose2(i, 0.82), heartPose2(i, 1)] }, delay:
|
|
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,
|
|
5658
|
-
animate.push(snap7(`f${i}`, "opacity", 1,
|
|
5659
|
-
animate.push({ id: `h${i}`, attrs: { transform: [heartPose2(i, 1), heartPose2(i, 1.3)] }, delay:
|
|
5660
|
-
animate.push({ id: `h${i}`, attrs: { transform: [heartPose2(i, 1.3), heartPose2(i, 1)] }, delay:
|
|
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 = (
|
|
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
|
|
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] =
|
|
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] =
|
|
5749
|
-
const [x1, y1] =
|
|
5750
|
-
const [x2, y2] =
|
|
5751
|
-
const [x3, y3] =
|
|
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 = (
|
|
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,
|
|
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 = (
|
|
5913
|
-
const h = Math.sqrt(Math.max(0,
|
|
5914
|
-
const [
|
|
5915
|
-
const i1 = [
|
|
5916
|
-
const i2 = [
|
|
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,
|
|
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,
|
|
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(
|
|
5932
|
-
const [ox, oy] = [
|
|
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,
|
|
5935
|
+
function fittedOr(d, fallback, box2, points) {
|
|
5936
5936
|
try {
|
|
5937
|
-
return fitted(String(d),
|
|
5937
|
+
return fitted(String(d), box2, points);
|
|
5938
5938
|
} catch {
|
|
5939
|
-
return fitted(fallback,
|
|
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 = (
|
|
6011
|
-
{ id: "glyph", attrs: { d }, delay:
|
|
6012
|
-
{ id: "icon", attrs: { transform: [pose2(0, 2.6), pose2(turn, 2.15)] }, delay:
|
|
6013
|
-
{ id: "icon", attrs: { transform: [pose2(turn, 2.15), pose2(-turn * 0.3, 2.85)] }, delay:
|
|
6014
|
-
{ id: "icon", attrs: { transform: [pose2(-turn * 0.3, 2.85), pose2(0, 2.6)] }, delay:
|
|
6015
|
-
{ id: "button", attrs: { fill }, delay:
|
|
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
|
|
6134
|
-
const [a, b] = morphPaths(fittedOr(v.from, PARTICLE_FROM,
|
|
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,
|
|
6158
|
-
{ id: `p${i}`, attrs: { cx: [n9(`${from}x${i}`), n9(`${mid}x${i}`)], cy: [n9(`${from}y${i}`), n9(`${mid}y${i}`)] }, delay:
|
|
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:
|
|
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,
|
|
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
|
|
6230
|
-
return place >= 0 &&
|
|
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(
|
|
18446
|
-
const taps = Array.from({ length:
|
|
18447
|
-
const r = Math.sqrt((i + 0.5) /
|
|
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${
|
|
18451
|
+
return `const vec2 GOLDEN${n13}[${n13}] = vec2[${n13}](${taps.join(", ")});`;
|
|
18452
18452
|
}
|
|
18453
|
-
function coverageTaps(name,
|
|
18453
|
+
function coverageTaps(name, n13, blur) {
|
|
18454
18454
|
return `
|
|
18455
|
-
${goldenTaps(
|
|
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 < ${
|
|
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 < ${
|
|
18462
|
+
for (int i = 0; i < ${n13}; i++) sum += shapeLod(p + radius * GOLDEN${n13}[i], lod);
|
|
18463
18463
|
}
|
|
18464
|
-
return sum / ${
|
|
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 = (
|
|
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
|
|
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}}})`] }, ...
|
|
22029
|
-
{ id: "under", attrs: { width: [n11(`len${i}`), n11(`len${i + 1}`)] }, ...
|
|
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
|
|
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}`,
|
|
22169
|
-
[`b${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
|
|
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 =
|
|
22662
|
-
const length =
|
|
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}`,
|
|
22667
|
-
[`shine${d.i}`,
|
|
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}`,
|
|
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
|
|
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="${
|
|
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:
|
|
22688
|
-
{ id: "word", attrs: { transform:
|
|
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
|
|
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}}}`] }, ...
|
|
22695
|
-
{ id: `${layer}c${d.i}`, attrs: { cy: [DRIP_TOP, tip] }, ...
|
|
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] }, ...
|
|
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-
|
|
24170
|
+
//# sourceMappingURL=chunk-UIODYJ6B.js.map
|