@pixerate/flair 0.28.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 +19 -0
- package/README.md +54 -6
- package/dist/catalog/index.d.ts +11 -6
- package/dist/catalog/index.js +1 -1
- package/dist/{chunk-JL6TI6II.js → chunk-7KYQ4R76.js} +2 -2
- package/dist/chunk-7KYQ4R76.js.map +1 -0
- package/dist/{chunk-STKEKXCK.js → chunk-AUH3PEFW.js} +2 -2
- package/dist/{chunk-PFGGPIMT.js → chunk-DA4UUSDO.js} +58 -3
- package/dist/chunk-DA4UUSDO.js.map +1 -0
- package/dist/{chunk-RDU63CDO.js → chunk-HDYBY3PQ.js} +380 -1
- package/dist/chunk-HDYBY3PQ.js.map +1 -0
- package/dist/{chunk-GXP4YLAP.js → chunk-HSYDTVTD.js} +9 -9
- package/dist/chunk-HSYDTVTD.js.map +1 -0
- package/dist/{chunk-VLQLRKGW.js → chunk-MAYKC3RF.js} +260 -33
- package/dist/chunk-MAYKC3RF.js.map +1 -0
- package/dist/{chunk-H6VDFBIO.js → chunk-SMR2MJXB.js} +156 -5
- package/dist/chunk-SMR2MJXB.js.map +1 -0
- package/dist/{chunk-4LKAPRFM.js → chunk-UIODYJ6B.js} +2274 -176
- package/dist/chunk-UIODYJ6B.js.map +1 -0
- package/dist/{chunk-YBGRXVAE.js → chunk-X3C2VFTM.js} +3 -3
- package/dist/{chunk-CK6EV37I.js → chunk-ZULFHADW.js} +69 -6
- package/dist/chunk-ZULFHADW.js.map +1 -0
- package/dist/cli.js +6 -6
- package/dist/editor/index.d.ts +3 -3
- package/dist/editor/index.js +3 -3
- package/dist/{effect-GGP66JI6.js → effect-RGABT3BK.js} +10 -10
- package/dist/embed/index.d.ts +36 -8
- package/dist/embed/index.js +5 -5
- package/dist/{frames-VEcqa40g.d.ts → frames-CGJudAbu.d.ts} +11 -1
- package/dist/index.d.ts +69 -6
- package/dist/index.js +35 -7
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +4 -4
- package/dist/node.js +9 -9
- package/dist/react/index.d.ts +4 -4
- package/dist/react/index.js +7 -7
- package/dist/react/index.js.map +1 -1
- package/dist/{recorder-CDPPTJMG.js → recorder-TYMEZHOT.js} +3 -3
- package/dist/{renderer-teaa98RE.d.ts → renderer-tk1nbVkW.d.ts} +252 -2
- package/dist/svelte/index.d.ts +4 -4
- package/dist/svelte/index.js +7 -7
- package/dist/svelte/index.js.map +1 -1
- package/dist/{svg-BhRboN9q.d.ts → svg-DaKRPA9v.d.ts} +39 -7
- package/dist/{types-DUOMxZaq.d.ts → types-DuGVdJbt.d.ts} +2 -2
- package/dist/vector/index.d.ts +2 -2
- package/dist/vector/index.js +5 -3
- package/package.json +1 -1
- package/dist/chunk-4LKAPRFM.js.map +0 -1
- package/dist/chunk-CK6EV37I.js.map +0 -1
- package/dist/chunk-GXP4YLAP.js.map +0 -1
- package/dist/chunk-H6VDFBIO.js.map +0 -1
- package/dist/chunk-JL6TI6II.js.map +0 -1
- package/dist/chunk-PFGGPIMT.js.map +0 -1
- package/dist/chunk-RDU63CDO.js.map +0 -1
- package/dist/chunk-VLQLRKGW.js.map +0 -1
- /package/dist/{chunk-STKEKXCK.js.map → chunk-AUH3PEFW.js.map} +0 -0
- /package/dist/{chunk-YBGRXVAE.js.map → chunk-X3C2VFTM.js.map} +0 -0
- /package/dist/{effect-GGP66JI6.js.map → effect-RGABT3BK.js.map} +0 -0
- /package/dist/{recorder-CDPPTJMG.js.map → recorder-TYMEZHOT.js.map} +0 -0
|
@@ -15,8 +15,8 @@ import {
|
|
|
15
15
|
} from "./chunk-ORAFCHX3.js";
|
|
16
16
|
|
|
17
17
|
// src/catalog/inputs.ts
|
|
18
|
-
var EFFECT_INPUTS = ["image", "video", "svg", "text", "mask", "path"];
|
|
19
|
-
var EFFECT_INTERACTIONS = ["hover", "press", "click", "trail", "pointer"];
|
|
18
|
+
var EFFECT_INPUTS = ["image", "video", "svg", "text", "mask", "path", "audio"];
|
|
19
|
+
var EFFECT_INTERACTIONS = ["hover", "press", "click", "trail", "pointer", "scroll"];
|
|
20
20
|
function isTextImage(url) {
|
|
21
21
|
if (!url.startsWith("data:image/svg+xml;charset=utf-8,")) return false;
|
|
22
22
|
try {
|
|
@@ -28,17 +28,19 @@ 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
|
+
var codeOf = (source) => source.replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*/g, "");
|
|
34
35
|
function shaderInteractions(source) {
|
|
35
|
-
const code = source
|
|
36
|
+
const code = codeOf(source);
|
|
36
37
|
const found = /* @__PURE__ */ new Set();
|
|
37
38
|
if (/\biClicks\b/.test(code)) found.add("click");
|
|
38
39
|
if (/\biTrail\b/.test(code)) found.add("trail");
|
|
39
40
|
if (/\biPointer\s*\.\s*z\b/.test(code)) found.add("hover");
|
|
40
41
|
if (/\biPointer\s*\.\s*w\b/.test(code) || /\biMouse\s*\.\s*z\b/.test(code)) found.add("press");
|
|
41
42
|
if (/\biPointer\s*\.\s*xy?\b/.test(code) || /\biMouse\s*\.\s*xy?\b/.test(code)) found.add("pointer");
|
|
43
|
+
if (/\biScroll\b/.test(code)) found.add("scroll");
|
|
42
44
|
return EFFECT_INTERACTIONS.filter((i) => found.has(i));
|
|
43
45
|
}
|
|
44
46
|
function inputsOf(effect, controls) {
|
|
@@ -64,6 +66,7 @@ function inputsOf(effect, controls) {
|
|
|
64
66
|
if (slot.holds === "path") accepts.add("path");
|
|
65
67
|
}
|
|
66
68
|
}
|
|
69
|
+
if (effect.kind === "shader" && /\bi(Audio|Spectrum|Beat)\b/.test(codeOf(effect.source))) accepts.add("audio");
|
|
67
70
|
const interactions = effect.kind === "vector" ? effect.trigger ? [effect.trigger] : [] : shaderInteractions(effect.source);
|
|
68
71
|
return {
|
|
69
72
|
accepts: EFFECT_INPUTS.filter((i) => accepts.has(i)),
|
|
@@ -1882,7 +1885,7 @@ function capsule(p, q, width, flatEnd = false) {
|
|
|
1882
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)]);
|
|
1883
1886
|
else cap(q, a - Math.PI / 2);
|
|
1884
1887
|
cap(p, a + Math.PI / 2);
|
|
1885
|
-
return `M${pts.map((
|
|
1888
|
+
return `M${pts.map((pt2) => `${r2(pt2[0])} ${r2(pt2[1])}`).join(" L")} Z`;
|
|
1886
1889
|
}
|
|
1887
1890
|
var menuMorphs = /* @__PURE__ */ new Map();
|
|
1888
1891
|
function menuMorph(weight) {
|
|
@@ -1970,9 +1973,9 @@ var swapLabels = (outAt, back) => [
|
|
|
1970
1973
|
{ id: "label", step: 1, attrs: { opacity: [0, 1], transform: ["translate(0 10)", "translate(0 0)"] }, delay: back + 120, ms: 220, ease: "out" },
|
|
1971
1974
|
snap("done", "transform", "translate(0 10)", back + 200)
|
|
1972
1975
|
];
|
|
1973
|
-
var checkFlash = (
|
|
1974
|
-
{ id: "check", step: 1, attrs: { opacity: [0, 1] }, delay:
|
|
1975
|
-
{ 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" },
|
|
1976
1979
|
{ id: "check", step: 1, attrs: { opacity: [1, 0] }, delay: back, ms: 160 },
|
|
1977
1980
|
{ id: "check", step: 1, draw: [0, 0], delay: back + 200, ms: 0 }
|
|
1978
1981
|
];
|
|
@@ -2305,10 +2308,10 @@ var sliderScrub = {
|
|
|
2305
2308
|
return { size, length };
|
|
2306
2309
|
},
|
|
2307
2310
|
motion: (() => {
|
|
2308
|
-
const move = (from, to, delay,
|
|
2309
|
-
{ id: "knob", step: 1, attrs: { transform: [knobAt(from), knobAt(to)] }, delay, ms, ease: "in-out" },
|
|
2310
|
-
{ id: "fill", step: 1, attrs: { width: [r1(from - SLIDER.x), r1(to - SLIDER.x)] }, delay, ms, ease: "in-out" },
|
|
2311
|
-
...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" }] : []
|
|
2312
2315
|
];
|
|
2313
2316
|
return {
|
|
2314
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>`,
|
|
@@ -2449,10 +2452,10 @@ var splatStamp = {
|
|
|
2449
2452
|
{ id: "splat", attrs: { d: ["{{b}}", "{{c}}"] }, delay: 520, ms: 380, ease: "in-out" },
|
|
2450
2453
|
{ id: "gloss", attrs: { opacity: [0, n2("gloss")] }, delay: 600, ms: 400, ease: "out" },
|
|
2451
2454
|
...Array.from({ length: DROPS }, (_, i) => {
|
|
2452
|
-
const
|
|
2455
|
+
const at4 = POP + i * 35;
|
|
2453
2456
|
return [
|
|
2454
|
-
{ id: `drop${i}`, attrs: { cx: [CX, n2(`xm${i}`)], cy: [CY, n2(`ym${i}`)], r: [0, n2(`ro${i}`)] }, delay:
|
|
2455
|
-
{ 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" },
|
|
2456
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" }
|
|
2457
2460
|
];
|
|
2458
2461
|
}).flat(),
|
|
@@ -2531,10 +2534,10 @@ var chatBubble = {
|
|
|
2531
2534
|
...Object.fromEntries(
|
|
2532
2535
|
Array.from({ length: BOUNCES * DOTS }, (_, i) => {
|
|
2533
2536
|
const [k, j] = [Math.floor(i / DOTS), i % DOTS];
|
|
2534
|
-
const
|
|
2537
|
+
const at4 = reply + 450 + k * BOUNCE_MS + j * 150;
|
|
2535
2538
|
return [
|
|
2536
|
-
[`rise${k}_${j}`,
|
|
2537
|
-
[`fall${k}_${j}`,
|
|
2539
|
+
[`rise${k}_${j}`, at4],
|
|
2540
|
+
[`fall${k}_${j}`, at4 + 180]
|
|
2538
2541
|
];
|
|
2539
2542
|
}).flat()
|
|
2540
2543
|
)
|
|
@@ -2603,22 +2606,22 @@ var bubblePopLoader = {
|
|
|
2603
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>`,
|
|
2604
2607
|
animate: LOADER_X.flatMap((_, i) => {
|
|
2605
2608
|
const t0 = i * LOADER_STAGGER;
|
|
2606
|
-
const
|
|
2609
|
+
const pop2 = t0 + POP_AT;
|
|
2607
2610
|
const p = pose3(i);
|
|
2608
2611
|
return [
|
|
2609
2612
|
// Inflating with a wobble, then bursting.
|
|
2610
2613
|
{ id: `b${i}`, attrs: { transform: [p(0), p(1.1)] }, delay: t0, ms: 450, ease: "out" },
|
|
2611
2614
|
{ id: `b${i}`, attrs: { transform: [p(1.1), p(0.95)] }, delay: t0 + 450, ms: 150, ease: "in-out" },
|
|
2612
2615
|
{ id: `b${i}`, attrs: { transform: [p(0.95), p(1)] }, delay: t0 + 600, ms: 200, ease: "in-out" },
|
|
2613
|
-
{ 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" },
|
|
2614
2617
|
snap2(`ring${i}`, "stroke-width", 0, 0),
|
|
2615
|
-
{ 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" },
|
|
2616
2619
|
...Array.from({ length: DROPS2 }, (_2, k) => {
|
|
2617
2620
|
const [x0, y0] = drop(i, k, LOADER_R);
|
|
2618
2621
|
const [x1, y1] = drop(i, k, LOADER_R * 2.1);
|
|
2619
2622
|
return [
|
|
2620
2623
|
snap2(`d${i}_${k}`, "r", 0, 0),
|
|
2621
|
-
{ 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" },
|
|
2622
2625
|
snap2(`d${i}_${k}`, "cx", x0, LOADER_END),
|
|
2623
2626
|
snap2(`d${i}_${k}`, "cy", y0, LOADER_END)
|
|
2624
2627
|
];
|
|
@@ -2728,21 +2731,21 @@ var stickerSlap = {
|
|
|
2728
2731
|
},
|
|
2729
2732
|
motion: {
|
|
2730
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>`,
|
|
2731
|
-
animate: SLAP.slice(1).flatMap(([
|
|
2734
|
+
animate: SLAP.slice(1).flatMap(([at4, s, r, dy, o, blur], i) => {
|
|
2732
2735
|
const [from, fs, fr, fdy, fo, fblur] = SLAP[i];
|
|
2733
|
-
const
|
|
2734
|
-
if (
|
|
2736
|
+
const ms2 = at4 - from;
|
|
2737
|
+
if (ms2 === 0)
|
|
2735
2738
|
return [
|
|
2736
|
-
snap3("sticker", "transform", pose(120, 120, s, r),
|
|
2737
|
-
snap3("shade", "transform", pose(120, 120, s, r, dy),
|
|
2738
|
-
snap3("shade", "opacity", o,
|
|
2739
|
-
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)
|
|
2740
2743
|
];
|
|
2741
2744
|
const ease = i === 0 || i === 3 ? "out" : "in-out";
|
|
2742
2745
|
return [
|
|
2743
|
-
{ id: "sticker", step: 1, attrs: { transform: [pose(120, 120, fs, fr), pose(120, 120, s, r)] }, delay: from, ms, ease },
|
|
2744
|
-
{ 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 },
|
|
2745
|
-
{ 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 }
|
|
2746
2749
|
];
|
|
2747
2750
|
}).concat([
|
|
2748
2751
|
// Gone as it flicks up, back in as it comes down.
|
|
@@ -2810,7 +2813,7 @@ var holoSticker = {
|
|
|
2810
2813
|
const { d, labelY, labelW } = stickerShape(v.shape, 120, 120, 80);
|
|
2811
2814
|
const { size, length } = fit2(v.label, labelW, 40);
|
|
2812
2815
|
const period = Math.round(Number(v.speed) * 1e3);
|
|
2813
|
-
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)]]));
|
|
2814
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 };
|
|
2815
2818
|
},
|
|
2816
2819
|
motion: {
|
|
@@ -2880,12 +2883,12 @@ var stickerSheet = {
|
|
|
2880
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>`
|
|
2881
2884
|
).join("")}</svg>`,
|
|
2882
2885
|
animate: SHEET.flatMap(([, x, y, r], i) => {
|
|
2883
|
-
const
|
|
2886
|
+
const at4 = 150 + i * POP_GAP;
|
|
2884
2887
|
const out = SHEET_EXIT + i * 40;
|
|
2885
2888
|
return [
|
|
2886
|
-
{ id: `s${i}`, attrs: { opacity: [0, 1] }, delay:
|
|
2887
|
-
{ id: `s${i}`, attrs: { transform: [pose(x, y, 0, r - 30), pose(x, y, 1.18, r + 4)] }, delay:
|
|
2888
|
-
{ 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" },
|
|
2889
2892
|
{ id: `s${i}`, attrs: { transform: [pose(x, y, 1, r), pose(x, y, 0, r - 30)] }, delay: out, ms: 240, ease: "in-out" },
|
|
2890
2893
|
{ id: `s${i}`, attrs: { opacity: [1, 0] }, delay: out + 160, ms: 80 }
|
|
2891
2894
|
];
|
|
@@ -2946,9 +2949,9 @@ var TORN_LEFT = [3, 0, 5, 1, 4, 0, 6, 2, 3, 0, 4];
|
|
|
2946
2949
|
var TORN_RIGHT = [1, 5, 0, 4, 2, 6, 1, 3, 0, 5, 2];
|
|
2947
2950
|
var TAPE = (() => {
|
|
2948
2951
|
const h = TAPE_W / 2;
|
|
2949
|
-
const
|
|
2950
|
-
const right = TORN_RIGHT.map((dx, k) => [TAPE_L - dx,
|
|
2951
|
-
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();
|
|
2952
2955
|
return outline([...right, ...left]);
|
|
2953
2956
|
})();
|
|
2954
2957
|
var UNROLL_MS = 1500;
|
|
@@ -3023,8 +3026,8 @@ var sealBadge = {
|
|
|
3023
3026
|
const bottomMid = topMid + top.size / 2 + gap + bottom.size / 2;
|
|
3024
3027
|
const beats = Object.fromEntries(
|
|
3025
3028
|
SEAL_PULSES.flatMap((f, i) => {
|
|
3026
|
-
const
|
|
3027
|
-
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]];
|
|
3028
3031
|
})
|
|
3029
3032
|
);
|
|
3030
3033
|
return {
|
|
@@ -3046,11 +3049,11 @@ var sealBadge = {
|
|
|
3046
3049
|
...["sealSpin", "sealClipShape"].map((id) => ({ id, attrs: { transform: ["rotate(0 120 120)", "rotate(360 120 120)"] }, ms: n4("period"), ease: "linear" })),
|
|
3047
3050
|
// Now and then it pulses and a glint sweeps across.
|
|
3048
3051
|
...SEAL_PULSES.flatMap((_, i) => [
|
|
3049
|
-
...SEAL_BEAT.slice(1).map(([
|
|
3052
|
+
...SEAL_BEAT.slice(1).map(([at4, scale], j) => ({
|
|
3050
3053
|
id: "sealPulse",
|
|
3051
3054
|
attrs: { transform: [pose(120, 120, SEAL_BEAT[j][1], 0), pose(120, 120, scale, 0)] },
|
|
3052
3055
|
delay: n4(`beat${i}_${j}`),
|
|
3053
|
-
ms:
|
|
3056
|
+
ms: at4 - SEAL_BEAT[j][0],
|
|
3054
3057
|
ease: j === 0 ? "out" : "in-out"
|
|
3055
3058
|
})),
|
|
3056
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" }
|
|
@@ -3302,14 +3305,14 @@ var snap5 = (id, attr, value, delay, step) => ({
|
|
|
3302
3305
|
ms: 0
|
|
3303
3306
|
});
|
|
3304
3307
|
function keys(id, attr, frames, options = {}) {
|
|
3305
|
-
return frames.slice(1).map(([
|
|
3308
|
+
return frames.slice(1).map(([at4, value], i) => {
|
|
3306
3309
|
const [from, before] = frames[i];
|
|
3307
3310
|
return {
|
|
3308
3311
|
id,
|
|
3309
3312
|
...options.step === void 0 ? {} : { step: options.step },
|
|
3310
3313
|
attrs: { [attr]: [before, value] },
|
|
3311
3314
|
delay: from,
|
|
3312
|
-
ms:
|
|
3315
|
+
ms: at4 - from,
|
|
3313
3316
|
ease: options.ease ?? "in-out"
|
|
3314
3317
|
};
|
|
3315
3318
|
});
|
|
@@ -3409,9 +3412,9 @@ var soundEffect = {
|
|
|
3409
3412
|
{ id: "sfxFlecks", attrs: { transform: [sfxPose(0.7, 0), sfxPose(1.25, 4)] }, delay: 140, ms: 420, ease: "out" },
|
|
3410
3413
|
{ id: "sfxFlecks", attrs: { opacity: [1, 0] }, delay: 300, ms: 320 },
|
|
3411
3414
|
// Everything shakes from the hit.
|
|
3412
|
-
...SFX_SHAKE.slice(1).map(([
|
|
3415
|
+
...SFX_SHAKE.slice(1).map(([at4, dx, dy], i) => {
|
|
3413
3416
|
const [from, fx, fy] = SFX_SHAKE[i];
|
|
3414
|
-
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" };
|
|
3415
3418
|
})
|
|
3416
3419
|
]
|
|
3417
3420
|
}
|
|
@@ -3497,20 +3500,20 @@ var speechBubble = {
|
|
|
3497
3500
|
pop1: about4(tip[0], tip[1], 1)
|
|
3498
3501
|
};
|
|
3499
3502
|
trail.forEach((c, i) => Object.assign(out, { [`tx${i}`]: c.x, [`ty${i}`]: c.y, [`tr${i}`]: c.r }));
|
|
3500
|
-
let
|
|
3503
|
+
let at4 = 480;
|
|
3501
3504
|
for (let i = 0; i < SB_LINES; i++) {
|
|
3502
3505
|
const line = lines[i] ?? "";
|
|
3503
|
-
const
|
|
3506
|
+
const ms2 = Math.max(1, Math.round(Array.from(line).length * pace));
|
|
3504
3507
|
Object.assign(out, {
|
|
3505
3508
|
[`line${i}`]: line,
|
|
3506
3509
|
[`x${i}`]: r15(SB_X - textWidthEm(line) * size / 2),
|
|
3507
3510
|
[`y${i}`]: r15(top + lineH * (i + 0.78)),
|
|
3508
|
-
[`at${i}`]:
|
|
3509
|
-
[`ms${i}`]:
|
|
3511
|
+
[`at${i}`]: at4,
|
|
3512
|
+
[`ms${i}`]: ms2
|
|
3510
3513
|
});
|
|
3511
|
-
|
|
3514
|
+
at4 += line ? ms2 + 80 : 0;
|
|
3512
3515
|
}
|
|
3513
|
-
out.outAt =
|
|
3516
|
+
out.outAt = at4 + 1600;
|
|
3514
3517
|
return out;
|
|
3515
3518
|
},
|
|
3516
3519
|
motion: {
|
|
@@ -3609,20 +3612,20 @@ var fightCloud = {
|
|
|
3609
3612
|
})
|
|
3610
3613
|
),
|
|
3611
3614
|
// A fist, boot or star jabs out of the cloud and back, and an impact mark flashes at its tip.
|
|
3612
|
-
...FC_POKES.flatMap(([
|
|
3615
|
+
...FC_POKES.flatMap(([at4, deg], k) => {
|
|
3613
3616
|
const hidden = fcPoke(deg, 0);
|
|
3614
3617
|
const out = fcPoke(deg, fcReach(deg) + 14);
|
|
3615
3618
|
const a = deg * Math.PI / 180;
|
|
3616
3619
|
const d = fcReach(deg) + 54;
|
|
3617
3620
|
const [x, y] = [FC_X + d * Math.cos(a), FC_Y + d * Math.sin(a)];
|
|
3618
3621
|
return [
|
|
3619
|
-
{ id: `fcPoke${k}`, attrs: { transform: [hidden, out] }, delay:
|
|
3620
|
-
{ 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" },
|
|
3621
3624
|
snap5(`fcHit${k}`, "opacity", 0, 0),
|
|
3622
|
-
snap5(`fcHit${k}`, "opacity", 1,
|
|
3623
|
-
{ id: `fcHit${k}`, attrs: { transform: [about4(x, y, 0.3), about4(x, y, 1.2)] }, delay:
|
|
3624
|
-
{ id: `fcHit${k}`, attrs: { opacity: [1, 0] }, delay:
|
|
3625
|
-
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)
|
|
3626
3629
|
];
|
|
3627
3630
|
}),
|
|
3628
3631
|
// The whole scrap jitters.
|
|
@@ -3755,12 +3758,12 @@ var animeEmotes = {
|
|
|
3755
3758
|
]),
|
|
3756
3759
|
// Sparkles: each twinkles open with a turn and closes again, one after another.
|
|
3757
3760
|
...AE_SPARKS.flatMap(
|
|
3758
|
-
([x, y, s,
|
|
3759
|
-
[
|
|
3760
|
-
[
|
|
3761
|
-
[
|
|
3762
|
-
[
|
|
3763
|
-
], { 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))
|
|
3764
3767
|
),
|
|
3765
3768
|
// Exclamation: pops up past its size with stress lines, holds, then ducks away.
|
|
3766
3769
|
...keys("aeBang", "transform", [
|
|
@@ -4021,12 +4024,12 @@ var RINGS = 3;
|
|
|
4021
4024
|
var RING_FROM = 18;
|
|
4022
4025
|
var RING_TO = 112;
|
|
4023
4026
|
function ringAnimations(k) {
|
|
4024
|
-
const
|
|
4027
|
+
const at4 = (phase2) => ({ r: r16(RING_FROM + (RING_TO - RING_FROM) * phase2), o: Math.round((1 - phase2) * 100) / 100 });
|
|
4025
4028
|
const id = `glowRingPulseRing${k}`;
|
|
4026
4029
|
const phase = k / RINGS;
|
|
4027
|
-
const from =
|
|
4028
|
-
const end =
|
|
4029
|
-
const start =
|
|
4030
|
+
const from = at4(phase);
|
|
4031
|
+
const end = at4(1);
|
|
4032
|
+
const start = at4(0);
|
|
4030
4033
|
const out = [
|
|
4031
4034
|
{
|
|
4032
4035
|
id,
|
|
@@ -4173,7 +4176,7 @@ var explodedLayers = {
|
|
|
4173
4176
|
const speed = Math.max(0.1, Number(v.speed));
|
|
4174
4177
|
const lift = Number(v.lift);
|
|
4175
4178
|
return {
|
|
4176
|
-
...Object.fromEntries(Object.entries(EL_TIMES).map(([name,
|
|
4179
|
+
...Object.fromEntries(Object.entries(EL_TIMES).map(([name, ms2]) => [`el_${name}`, Math.round(ms2 / speed)])),
|
|
4177
4180
|
up: r17(-lift),
|
|
4178
4181
|
fall: r17(lift * 0.92),
|
|
4179
4182
|
spread: r17(lift * 0.12)
|
|
@@ -4216,7 +4219,7 @@ var pull = (step) => [
|
|
|
4216
4219
|
{ id: "lampSwitchRig", step, attrs: { transform: [sway(2.5), sway(-1.4)] }, delay: 370, ms: 380, ease: "in-out" },
|
|
4217
4220
|
{ id: "lampSwitchRig", step, attrs: { transform: [sway(-1.4), sway(0)] }, delay: 750, ms: 420, ease: "in-out" }
|
|
4218
4221
|
];
|
|
4219
|
-
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" }));
|
|
4220
4223
|
var lampSwitch = {
|
|
4221
4224
|
kind: "vector",
|
|
4222
4225
|
id: "lamp-switch",
|
|
@@ -4279,8 +4282,8 @@ var zapIcon = {
|
|
|
4279
4282
|
motion: (() => {
|
|
4280
4283
|
const sparks = [-55, 25, 160, 215].map((deg, i) => {
|
|
4281
4284
|
const a = deg * Math.PI / 180;
|
|
4282
|
-
const
|
|
4283
|
-
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) };
|
|
4284
4287
|
});
|
|
4285
4288
|
return {
|
|
4286
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>`,
|
|
@@ -4320,7 +4323,7 @@ var powerButton = {
|
|
|
4320
4323
|
],
|
|
4321
4324
|
motion: (() => {
|
|
4322
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"/>`;
|
|
4323
|
-
const
|
|
4326
|
+
const press2 = (step, depth) => [
|
|
4324
4327
|
{ id: "press", step, attrs: { transform: [about5(80, 80, 1), about5(80, 80, depth)] }, ms: 90, ease: "out" },
|
|
4325
4328
|
{ id: "press", step, attrs: { transform: [about5(80, 80, depth), about5(80, 80, 1)] }, delay: 90, ms: 260, ease: "in-out" }
|
|
4326
4329
|
];
|
|
@@ -4329,7 +4332,7 @@ var powerButton = {
|
|
|
4329
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>`,
|
|
4330
4333
|
animate: [
|
|
4331
4334
|
// Step 1, on: a press, the symbol and rim light up, the glow ring comes on and a pulse rings out.
|
|
4332
|
-
...
|
|
4335
|
+
...press2(1, 0.93),
|
|
4333
4336
|
...colours(1, "{{off}}", "{{on}}", 60),
|
|
4334
4337
|
{ id: "disc", step: 1, attrs: { stroke: ["{{off}}", "{{on}}"] }, delay: 60, ms: 220 },
|
|
4335
4338
|
{ id: "symGlow", step: 1, attrs: { opacity: [0, 1] }, delay: 60, ms: 260 },
|
|
@@ -4338,7 +4341,7 @@ var powerButton = {
|
|
|
4338
4341
|
{ id: "pulse", step: 1, attrs: { r: [50, 76], opacity: [0.9, 0] }, delay: 80, ms: 560, ease: "out" },
|
|
4339
4342
|
snap6("pulse", "r", 50, 700, 1),
|
|
4340
4343
|
// Step 2, off: a press and everything dims back to the start.
|
|
4341
|
-
...
|
|
4344
|
+
...press2(2, 0.95),
|
|
4342
4345
|
...colours(2, "{{on}}", "{{off}}", 40),
|
|
4343
4346
|
{ id: "disc", step: 2, attrs: { stroke: ["{{on}}", "{{off}}"] }, delay: 40, ms: 220 },
|
|
4344
4347
|
{ id: "symGlow", step: 2, attrs: { opacity: [1, 0] }, delay: 40, ms: 220 },
|
|
@@ -4495,16 +4498,16 @@ var BURN = 6e3;
|
|
|
4495
4498
|
var TOP2 = 92;
|
|
4496
4499
|
var BURNT = 150;
|
|
4497
4500
|
var FLICKER = 150;
|
|
4498
|
-
var topAt = (
|
|
4501
|
+
var topAt = (ms2) => TOP2 + (BURNT - TOP2) * Math.min(ms2, BURN) / BURN;
|
|
4499
4502
|
var rand2 = (i, k) => {
|
|
4500
4503
|
const x = Math.sin(i * 127.1 + k * 311.7) * 43758.5453;
|
|
4501
4504
|
return x - Math.floor(x);
|
|
4502
4505
|
};
|
|
4503
|
-
var flame = (i,
|
|
4506
|
+
var flame = (i, ms2) => {
|
|
4504
4507
|
const sx = i < 0 ? 1 : 0.9 + 0.16 * rand2(i, 1);
|
|
4505
4508
|
const sy = i < 0 ? 1 : 0.82 + 0.34 * rand2(i, 2);
|
|
4506
4509
|
const lean = i < 0 ? 0 : (rand2(i, 3) - 0.5) * 12;
|
|
4507
|
-
return `translate(100 ${r28(topAt(
|
|
4510
|
+
return `translate(100 ${r28(topAt(ms2) - 12)}) skewX(${r28(lean)}) scale(${r28(sx)} ${r28(sy)})`;
|
|
4508
4511
|
};
|
|
4509
4512
|
var FLICKERS = BURN / FLICKER;
|
|
4510
4513
|
var DRIPS = [
|
|
@@ -4655,8 +4658,8 @@ function packGlyph(rows) {
|
|
|
4655
4658
|
return `ivec2(${a}, ${b})`;
|
|
4656
4659
|
}
|
|
4657
4660
|
var vec3Hex = (hex) => {
|
|
4658
|
-
const
|
|
4659
|
-
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));
|
|
4660
4663
|
return `vec3(${c.map((v) => Number.isInteger(v) ? `${v}.0` : String(v)).join(", ")})`;
|
|
4661
4664
|
};
|
|
4662
4665
|
var paletteArray = (name, hexes) => `const vec3 ${name}[${hexes.length}] = vec3[${hexes.length}](
|
|
@@ -5589,8 +5592,8 @@ var spaceInvader = {
|
|
|
5589
5592
|
const boltA = bitmapPath(BOLT_A, "#", 2);
|
|
5590
5593
|
const boltB = bitmapPath(BOLT_B, "#", 2);
|
|
5591
5594
|
const animate = [snap7("bolt", "opacity", 0, 0)];
|
|
5592
|
-
MARCH.forEach((
|
|
5593
|
-
animate.push(snap7("invader", "transform", pose3(
|
|
5595
|
+
MARCH.forEach((at4, k) => {
|
|
5596
|
+
animate.push(snap7("invader", "transform", pose3(at4), k * MARCH_MS));
|
|
5594
5597
|
animate.push(snap7("body", "d", k % 2 ? frameB : frameA, k * MARCH_MS));
|
|
5595
5598
|
});
|
|
5596
5599
|
SHOTS.forEach((k) => {
|
|
@@ -5642,19 +5645,19 @@ var pixelHeartHealth = {
|
|
|
5642
5645
|
for (let i = 0; i < HEARTS; i++) {
|
|
5643
5646
|
animate.push(snap7(`f${i}`, "opacity", 1, 0), snap7(`x${i}`, "opacity", 0, 0));
|
|
5644
5647
|
}
|
|
5645
|
-
for (const [i,
|
|
5648
|
+
for (const [i, at4] of LOSSES) {
|
|
5646
5649
|
[0, 1, 0, 1, 0].forEach((on, k) => {
|
|
5647
|
-
animate.push(snap7(`x${i}`, "opacity", on ? 0 : 1,
|
|
5648
|
-
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));
|
|
5649
5652
|
});
|
|
5650
|
-
animate.push(snap7(`x${i}`, "opacity", 0,
|
|
5651
|
-
animate.push({ id: `h${i}`, attrs: { transform: [heartPose2(i, 1), heartPose2(i, 0.82)] }, delay:
|
|
5652
|
-
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" });
|
|
5653
5656
|
}
|
|
5654
|
-
for (const [i,
|
|
5655
|
-
animate.push(snap7(`f${i}`, "opacity", 1,
|
|
5656
|
-
animate.push({ id: `h${i}`, attrs: { transform: [heartPose2(i, 1), heartPose2(i, 1.3)] }, delay:
|
|
5657
|
-
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" });
|
|
5658
5661
|
}
|
|
5659
5662
|
const hearts = Array.from(
|
|
5660
5663
|
{ length: HEARTS },
|
|
@@ -5714,7 +5717,7 @@ var chompLoader = {
|
|
|
5714
5717
|
var pixelEffects = [pixelText, coinSpin, spaceInvader, pixelHeartHealth, chompLoader];
|
|
5715
5718
|
|
|
5716
5719
|
// src/catalog/pyro.ts
|
|
5717
|
-
var round = (
|
|
5720
|
+
var round = (n13) => Math.round(n13 * 10) / 10;
|
|
5718
5721
|
var fireworkBurst = {
|
|
5719
5722
|
kind: "vector",
|
|
5720
5723
|
id: "firework-burst",
|
|
@@ -5733,19 +5736,19 @@ var fireworkBurst = {
|
|
|
5733
5736
|
const a = (i + 0.5) / 10 * Math.PI * 2 - Math.PI / 2;
|
|
5734
5737
|
return { id: `d${i}`, c: Math.cos(a), s: Math.sin(a), fill: ["{{b}}", "{{c}}", "{{a}}"][i % 3] };
|
|
5735
5738
|
});
|
|
5736
|
-
const
|
|
5739
|
+
const at4 = (c, s, r) => [round(c * r), round(s * r)];
|
|
5737
5740
|
return {
|
|
5738
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 }) => {
|
|
5739
|
-
const [x, y] =
|
|
5742
|
+
const [x, y] = at4(c, s, 12);
|
|
5740
5743
|
return `<line id="${id}" x1="${x}" y1="${y}" x2="${x}" y2="${y}" stroke="${stroke2}" stroke-width="{{width}}" stroke-linecap="round" opacity="0"/>`;
|
|
5741
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>`,
|
|
5742
5745
|
animate: [
|
|
5743
5746
|
{ id: "flash", attrs: { r: [0, 22], opacity: [0.9, 0] }, ms: 380, ease: "out" },
|
|
5744
5747
|
...streaks.flatMap(({ id, c, s }) => {
|
|
5745
|
-
const [x0, y0] =
|
|
5746
|
-
const [x1, y1] =
|
|
5747
|
-
const [x2, y2] =
|
|
5748
|
-
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);
|
|
5749
5752
|
return [
|
|
5750
5753
|
{ id, attrs: { opacity: [0, 1] }, ms: 40 },
|
|
5751
5754
|
{ id, attrs: { x1: [x0, x1], y1: [y0, y1], x2: [x0, x2], y2: [y0, y2] }, ms: 420, ease: "out" },
|
|
@@ -5769,7 +5772,7 @@ var fireworkBurst = {
|
|
|
5769
5772
|
var pyroEffects = [fireworkBurst];
|
|
5770
5773
|
|
|
5771
5774
|
// src/catalog/timewarp.ts
|
|
5772
|
-
var r19 = (
|
|
5775
|
+
var r19 = (n13) => Math.round(n13 * 10) / 10;
|
|
5773
5776
|
var at2 = (degrees, radius) => {
|
|
5774
5777
|
const a = degrees * Math.PI / 180;
|
|
5775
5778
|
return [r19(100 + radius * Math.sin(a)), r19(100 - radius * Math.cos(a))];
|
|
@@ -5903,37 +5906,37 @@ function around(c, r, from, to, avoid) {
|
|
|
5903
5906
|
if (((avoid - from) % 360 + 360) % 360 < delta) delta -= 360;
|
|
5904
5907
|
return arc2(c[0], c[1], r, r, from, from + delta);
|
|
5905
5908
|
}
|
|
5906
|
-
function crescent(c1,
|
|
5909
|
+
function crescent(c1, r112, c2, r2_) {
|
|
5907
5910
|
const [dx, dy] = [c2[0] - c1[0], c2[1] - c1[1]];
|
|
5908
5911
|
const d = Math.hypot(dx, dy);
|
|
5909
|
-
const a = (
|
|
5910
|
-
const h = Math.sqrt(Math.max(0,
|
|
5911
|
-
const [
|
|
5912
|
-
const i1 = [
|
|
5913
|
-
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];
|
|
5914
5917
|
const angle = (c, p) => Math.atan2(p[1] - c[1], p[0] - c[0]) / DEG2;
|
|
5915
|
-
const outer = around(c1,
|
|
5918
|
+
const outer = around(c1, r112, angle(c1, i1), angle(c1, i2), angle(c1, c2));
|
|
5916
5919
|
const inner = around(c2, r2_, angle(c2, i2), angle(c2, i1), angle(c2, c1) + 180);
|
|
5917
5920
|
return polyline3([...outer, ...inner.slice(1, -1)], true);
|
|
5918
5921
|
}
|
|
5919
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";
|
|
5920
5923
|
var placed = (d, cx, cy, s) => mapPath(d, (p) => [cx + (p[0] - 12) * s, cy + (p[1] - 12) * s]);
|
|
5921
|
-
function fitted(d,
|
|
5924
|
+
function fitted(d, box2, points) {
|
|
5922
5925
|
const outline4 = morphSequence([d], { points })[0];
|
|
5923
5926
|
const pts = pointsOf(outline4);
|
|
5924
5927
|
const xs = pts.map((p) => p[0]);
|
|
5925
5928
|
const ys = pts.map((p) => p[1]);
|
|
5926
5929
|
const [x0, y0] = [Math.min(...xs), Math.min(...ys)];
|
|
5927
5930
|
const [w, h] = [Math.max(...xs) - x0, Math.max(...ys) - y0];
|
|
5928
|
-
const s = Math.min(
|
|
5929
|
-
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];
|
|
5930
5933
|
return mapPath(outline4, (p) => [ox + (p[0] - x0) * s, oy + (p[1] - y0) * s]);
|
|
5931
5934
|
}
|
|
5932
|
-
function fittedOr(d, fallback,
|
|
5935
|
+
function fittedOr(d, fallback, box2, points) {
|
|
5933
5936
|
try {
|
|
5934
|
-
return fitted(String(d),
|
|
5937
|
+
return fitted(String(d), box2, points);
|
|
5935
5938
|
} catch {
|
|
5936
|
-
return fitted(fallback,
|
|
5939
|
+
return fitted(fallback, box2, points);
|
|
5937
5940
|
}
|
|
5938
5941
|
}
|
|
5939
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";
|
|
@@ -6004,12 +6007,12 @@ function iconMorph(pair2) {
|
|
|
6004
6007
|
return iconMorphs.get(pair2);
|
|
6005
6008
|
}
|
|
6006
6009
|
var pose2 = (turn, scale) => `translate(60 60) rotate(${turn}) scale(${scale}) translate(-12 -12)`;
|
|
6007
|
-
var toggle = (
|
|
6008
|
-
{ id: "glyph", attrs: { d }, delay:
|
|
6009
|
-
{ id: "icon", attrs: { transform: [pose2(0, 2.6), pose2(turn, 2.15)] }, delay:
|
|
6010
|
-
{ id: "icon", attrs: { transform: [pose2(turn, 2.15), pose2(-turn * 0.3, 2.85)] }, delay:
|
|
6011
|
-
{ id: "icon", attrs: { transform: [pose2(-turn * 0.3, 2.85), pose2(0, 2.6)] }, delay:
|
|
6012
|
-
{ 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" }
|
|
6013
6016
|
];
|
|
6014
6017
|
var iconToggle = {
|
|
6015
6018
|
kind: "vector",
|
|
@@ -6127,8 +6130,8 @@ var particleMorph = {
|
|
|
6127
6130
|
],
|
|
6128
6131
|
// The dots sit on the morphed outlines: point k of one shape flies to point k of the other.
|
|
6129
6132
|
derived: (v) => {
|
|
6130
|
-
const
|
|
6131
|
-
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 });
|
|
6132
6135
|
const [pa, pb] = [pointsOf(a), pointsOf(b)];
|
|
6133
6136
|
const out = { a, b };
|
|
6134
6137
|
const scatter = Number(v.scatter);
|
|
@@ -6151,11 +6154,11 @@ var particleMorph = {
|
|
|
6151
6154
|
motion: (() => {
|
|
6152
6155
|
const back = 3e3;
|
|
6153
6156
|
const spread = DOTS4 * DOT_STAGGER;
|
|
6154
|
-
const leg = (i,
|
|
6155
|
-
{ id: `p${i}`, attrs: { cx: [n9(`${from}x${i}`), n9(`${mid}x${i}`)], cy: [n9(`${from}y${i}`), n9(`${mid}y${i}`)] }, delay:
|
|
6156
|
-
{ 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" }
|
|
6157
6160
|
];
|
|
6158
|
-
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" });
|
|
6159
6162
|
return {
|
|
6160
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(
|
|
6161
6164
|
{ length: DOTS4 },
|
|
@@ -6223,8 +6226,8 @@ var digitMorph = {
|
|
|
6223
6226
|
out[`x${j}`] = r210(left + Math.max(0, place) * 22 * scale);
|
|
6224
6227
|
const glyphs = values.map((x) => {
|
|
6225
6228
|
const text = String(x);
|
|
6226
|
-
const
|
|
6227
|
-
return place >= 0 &&
|
|
6229
|
+
const at4 = text.length - (used - place);
|
|
6230
|
+
return place >= 0 && at4 >= 0 ? Number(text[at4]) : -1;
|
|
6228
6231
|
});
|
|
6229
6232
|
const paths = morphSequence(glyphs.map((g) => g < 0 ? BLANK : DIGITS[g]), { points: 64 });
|
|
6230
6233
|
for (let k = 0; k <= MAX_STEPS; k++) {
|
|
@@ -18439,26 +18442,26 @@ float coverage(vec2 p, float radius) {
|
|
|
18439
18442
|
}
|
|
18440
18443
|
return sum / 16.0;
|
|
18441
18444
|
}`.trim();
|
|
18442
|
-
function goldenTaps(
|
|
18443
|
-
const taps = Array.from({ length:
|
|
18444
|
-
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);
|
|
18445
18448
|
const a = i * 2.39996;
|
|
18446
18449
|
return `vec2(${(r * Math.cos(a)).toFixed(5)}, ${(r * Math.sin(a)).toFixed(5)})`;
|
|
18447
18450
|
});
|
|
18448
|
-
return `const vec2 GOLDEN${
|
|
18451
|
+
return `const vec2 GOLDEN${n13}[${n13}] = vec2[${n13}](${taps.join(", ")});`;
|
|
18449
18452
|
}
|
|
18450
|
-
function coverageTaps(name,
|
|
18453
|
+
function coverageTaps(name, n13, blur) {
|
|
18451
18454
|
return `
|
|
18452
|
-
${goldenTaps(
|
|
18455
|
+
${goldenTaps(n13)}
|
|
18453
18456
|
float ${name}(vec2 p, float radius) {
|
|
18454
18457
|
float sum = 0.0;
|
|
18455
18458
|
if (gMaskPlace.w < 0.5) {
|
|
18456
|
-
for (int i = 0; i < ${
|
|
18459
|
+
for (int i = 0; i < ${n13}; i++) sum += shapeAt(p + radius * GOLDEN${n13}[i], radius * ${blur.toFixed(2)});
|
|
18457
18460
|
} else {
|
|
18458
18461
|
float lod = log2(max(radius * ${blur.toFixed(2)} * gMaskPlace.z, 1.0));
|
|
18459
|
-
for (int i = 0; i < ${
|
|
18462
|
+
for (int i = 0; i < ${n13}; i++) sum += shapeLod(p + radius * GOLDEN${n13}[i], lod);
|
|
18460
18463
|
}
|
|
18461
|
-
return sum / ${
|
|
18464
|
+
return sum / ${n13}.0;
|
|
18462
18465
|
}`.trim();
|
|
18463
18466
|
}
|
|
18464
18467
|
var LITE_TAPS = 10;
|
|
@@ -20624,6 +20627,885 @@ var lightingShaders = [
|
|
|
20624
20627
|
partyLights
|
|
20625
20628
|
];
|
|
20626
20629
|
|
|
20630
|
+
// src/catalog/shaders/audio.ts
|
|
20631
|
+
var SOUND = `
|
|
20632
|
+
bool soundDemo() { return uDemo && iBeat == 0.0 && dot(iAudio, iAudio) == 0.0; }
|
|
20633
|
+
// The demo: a kick every half second, a hat between, and a gently wandering pink-ish spectrum.
|
|
20634
|
+
float demoKick() { return exp(-mod(iTime, 0.5) * 7.0); }
|
|
20635
|
+
float demoHat() { return exp(-mod(iTime + 0.25, 0.5) * 16.0); }
|
|
20636
|
+
vec4 sound() {
|
|
20637
|
+
if (!soundDemo()) return iAudio;
|
|
20638
|
+
float k = demoKick();
|
|
20639
|
+
float mid = 0.4 + 0.12 * sin(iTime * 1.7) + 0.1 * sin(iTime * 3.1);
|
|
20640
|
+
return vec4(0.45 + 0.3 * k + 0.1 * mid, 0.3 + 0.65 * k, mid, 0.25 + 0.4 * demoHat() + 0.08 * sin(iTime * 5.3));
|
|
20641
|
+
}
|
|
20642
|
+
float beat() { return soundDemo() ? mod(iTime, 0.5) : iBeat; }
|
|
20643
|
+
float spectrum(float x) {
|
|
20644
|
+
x = clamp(x, 0.0, 1.0);
|
|
20645
|
+
if (soundDemo()) {
|
|
20646
|
+
float tilt = 0.8 - 0.5 * x;
|
|
20647
|
+
float wander = 0.6 + 0.25 * sin(iTime * 0.7 + x * 5.0) + 0.1 * sin(x * 23.0 + iTime * 2.3) + 0.07 * sin(x * 47.0 - iTime * 3.7);
|
|
20648
|
+
return clamp(tilt * wander + 0.55 * demoKick() * exp(-x * 7.0) + 0.35 * demoHat() * smoothstep(0.55, 0.9, x), 0.0, 1.0);
|
|
20649
|
+
}
|
|
20650
|
+
float f = x * 63.0;
|
|
20651
|
+
int i = int(f);
|
|
20652
|
+
int j = min(i + 1, 63);
|
|
20653
|
+
return mix(iSpectrum[i / 4][i % 4], iSpectrum[j / 4][j % 4], f - float(i));
|
|
20654
|
+
}`.trim();
|
|
20655
|
+
var GLOW8 = `
|
|
20656
|
+
vec4 glow(vec3 c) {
|
|
20657
|
+
c = 1.0 - exp(-max(c, 0.0));
|
|
20658
|
+
float a = max(c.r, max(c.g, c.b));
|
|
20659
|
+
return vec4(c / max(a, 1e-4), a);
|
|
20660
|
+
}`.trim();
|
|
20661
|
+
var DEMO = `uniform bool uDemo; // @control default=true label="Demo when silent"`;
|
|
20662
|
+
var spectrumBars = {
|
|
20663
|
+
kind: "shader",
|
|
20664
|
+
id: "spectrum-bars",
|
|
20665
|
+
name: "Spectrum bars",
|
|
20666
|
+
description: "Classic equaliser bars dancing to the music, with peak caps kicked up on each beat and an optional mirrored layout.",
|
|
20667
|
+
category: "audio",
|
|
20668
|
+
tags: ["audio", "music", "equalizer", "spectrum", "bars", "visualizer", "beat"],
|
|
20669
|
+
source: glsl(
|
|
20670
|
+
`// Spectrum bars: the spectrum as bars, low to high, coloured by height, with caps that jump on beats.
|
|
20671
|
+
uniform int uBars; // @control min=8 max=64 default=32 label="Bars"
|
|
20672
|
+
uniform float uGap; // @control min=0 max=0.8 default=0.3 label="Gap"
|
|
20673
|
+
uniform float uGain; // @control min=0.3 max=2.5 default=1.1 label="Gain"
|
|
20674
|
+
uniform vec3 uColorLow; // @control color default=#22D3EE label="Low colour"
|
|
20675
|
+
uniform vec3 uColorHigh; // @control color default=#F43F5E label="High colour"
|
|
20676
|
+
uniform vec3 uBackground; // @control color default=#070B16 label="Background"
|
|
20677
|
+
uniform bool uMirror; // @control default=false label="Mirrored"
|
|
20678
|
+
uniform bool uPeaks; // @control default=true label="Peak caps"
|
|
20679
|
+
uniform float uRound; // @control min=0 max=1 default=0.5 label="Rounded"
|
|
20680
|
+
${DEMO}`,
|
|
20681
|
+
SOUND,
|
|
20682
|
+
`float box(vec2 p, vec2 hs, float r) {
|
|
20683
|
+
vec2 q = abs(p) - hs + r;
|
|
20684
|
+
return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - r;
|
|
20685
|
+
}
|
|
20686
|
+
|
|
20687
|
+
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
20688
|
+
vec2 uv = fragCoord / iResolution.xy;
|
|
20689
|
+
float px = 1.0 / iResolution.y;
|
|
20690
|
+
float n = float(uBars);
|
|
20691
|
+
// Margins round the bars, and a floor a little up from the bottom (the middle when mirrored).
|
|
20692
|
+
float x = (uv.x - 0.06) / 0.88;
|
|
20693
|
+
float base = uMirror ? 0.5 : 0.12;
|
|
20694
|
+
float room = uMirror ? 0.4 : 0.78;
|
|
20695
|
+
float cell = floor(x * n);
|
|
20696
|
+
float within = fract(x * n);
|
|
20697
|
+
vec3 col = uBackground * (1.0 + 0.25 * (1.0 - uv.y));
|
|
20698
|
+
if (x >= 0.0 && x < 1.0) {
|
|
20699
|
+
float v = clamp(spectrum((cell + 0.5) / n) * uGain, 0.0, 1.0);
|
|
20700
|
+
float h = max(v * room, 2.0 * px);
|
|
20701
|
+
// The bar, in pixels from its centre, so corners round alike at any size.
|
|
20702
|
+
float barW = (1.0 - uGap) * 0.88 * iResolution.x / n;
|
|
20703
|
+
vec2 p = vec2((within - 0.5) * 0.88 * iResolution.x / n, (uv.y - base) * iResolution.y);
|
|
20704
|
+
float sideY = uMirror ? abs(p.y) : p.y;
|
|
20705
|
+
float r = uRound * barW * 0.5;
|
|
20706
|
+
float d = box(vec2(p.x, sideY - h * 0.5 * iResolution.y), vec2(barW * 0.5, h * 0.5 * iResolution.y), r);
|
|
20707
|
+
float body = clamp(0.5 - d, 0.0, 1.0);
|
|
20708
|
+
float along = clamp(sideY / (room * iResolution.y), 0.0, 1.0);
|
|
20709
|
+
vec3 bar = mix(uColorLow, uColorHigh, along);
|
|
20710
|
+
// A soft glow under each bar, brighter as it rises.
|
|
20711
|
+
float halo = exp(-max(d, 0.0) / (barW * 0.6)) * 0.25 * v;
|
|
20712
|
+
col += bar * halo;
|
|
20713
|
+
col = mix(col, bar * (0.85 + 0.3 * v), body);
|
|
20714
|
+
if (uPeaks) {
|
|
20715
|
+
// The cap rides just above the bar, kicked up on each beat and falling back under gravity.
|
|
20716
|
+
float b = beat();
|
|
20717
|
+
float kick = b >= 0.0 ? max(0.0, 1.0 - b * 3.0) : 0.0;
|
|
20718
|
+
float lift = 0.035 + 0.12 * v * kick * kick;
|
|
20719
|
+
float capY = (h + lift * room) * iResolution.y;
|
|
20720
|
+
float cap = box(vec2(p.x, sideY - capY), vec2(barW * 0.5, max(2.0, 0.008 * iResolution.y)), r * 0.4);
|
|
20721
|
+
col = mix(col, mix(bar, vec3(1.0), 0.6), clamp(0.5 - cap, 0.0, 1.0));
|
|
20722
|
+
}
|
|
20723
|
+
// A faint reflection on the floor.
|
|
20724
|
+
if (!uMirror && p.y < 0.0) {
|
|
20725
|
+
float rd = box(vec2(p.x, -p.y - h * 0.5 * iResolution.y), vec2(barW * 0.5, h * 0.5 * iResolution.y), r);
|
|
20726
|
+
col = mix(col, bar, clamp(0.5 - rd, 0.0, 1.0) * 0.18 * exp(p.y / (0.06 * iResolution.y)));
|
|
20727
|
+
}
|
|
20728
|
+
}
|
|
20729
|
+
fragColor = vec4(col, 1.0);
|
|
20730
|
+
}`
|
|
20731
|
+
),
|
|
20732
|
+
presets: [
|
|
20733
|
+
{ name: "Mirrored neon", values: { uMirror: true, uBars: 48, uGap: 0.4, uColorLow: [0.6, 0.2, 1], uColorHigh: [0.2, 1, 0.8] } },
|
|
20734
|
+
{ name: "Retro LED", values: { uBars: 16, uGap: 0.15, uRound: 0, uColorLow: [0.2, 1, 0.3], uColorHigh: [1, 0.2, 0.1], uBackground: [0, 0, 0] } },
|
|
20735
|
+
{ name: "Minimal", values: { uBars: 64, uGap: 0.5, uRound: 1, uPeaks: false, uColorLow: [1, 1, 1], uColorHigh: [1, 1, 1], uBackground: [0.06, 0.06, 0.07] } }
|
|
20736
|
+
]
|
|
20737
|
+
};
|
|
20738
|
+
var circularSpectrum = {
|
|
20739
|
+
kind: "shader",
|
|
20740
|
+
id: "circular-spectrum",
|
|
20741
|
+
name: "Circular spectrum",
|
|
20742
|
+
description: "A ring of spectrum bars radiating round a centre picture or colour, which swells on the bass.",
|
|
20743
|
+
category: "audio",
|
|
20744
|
+
video: "filter",
|
|
20745
|
+
tags: ["audio", "music", "spectrum", "radial", "ring", "visualizer", "album art"],
|
|
20746
|
+
source: glsl(
|
|
20747
|
+
`// Circular spectrum: bars radiating from a ring, the spectrum mirrored round it, a picture in the middle.
|
|
20748
|
+
uniform sampler2D uImage; // @control filter=mipmap wrap=clamp default=${SAMPLE_IMAGES.portrait} label="Centre picture"
|
|
20749
|
+
uniform bool uShowImage; // @control default=true label="Show the picture"
|
|
20750
|
+
uniform vec3 uCenter; // @control color default=#1E1B4B label="Centre colour"
|
|
20751
|
+
uniform float uRadius; // @control min=0.1 max=0.4 default=0.22 label="Radius"
|
|
20752
|
+
uniform int uBars; // @control min=24 max=180 default=96 label="Bars"
|
|
20753
|
+
uniform float uLength; // @control min=0.05 max=0.4 default=0.2 label="Bar length"
|
|
20754
|
+
uniform vec3 uColorA; // @control color default=#38BDF8 label="Colour 1"
|
|
20755
|
+
uniform vec3 uColorB; // @control color default=#E879F9 label="Colour 2"
|
|
20756
|
+
uniform vec3 uBackground; // @control color default=#05060F label="Background"
|
|
20757
|
+
uniform float uBackgroundAlpha; // @control min=0 max=1 default=1 label="Background opacity"
|
|
20758
|
+
uniform float uSpin; // @control min=-1 max=1 default=0.1 label="Spin"
|
|
20759
|
+
${BACKDROP_CONTROLS}
|
|
20760
|
+
${DEMO}`,
|
|
20761
|
+
PICTURE,
|
|
20762
|
+
SOUND,
|
|
20763
|
+
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
20764
|
+
float px = 1.0 / iResolution.y;
|
|
20765
|
+
vec2 p = (fragCoord - 0.5 * iResolution.xy) * px;
|
|
20766
|
+
vec4 s = sound();
|
|
20767
|
+
float pulse = 1.0 + 0.12 * s.y * s.y;
|
|
20768
|
+
float r0 = uRadius * pulse;
|
|
20769
|
+
float r = length(p);
|
|
20770
|
+
float a = atan(p.x, p.y) + iTime * uSpin;
|
|
20771
|
+
// Bars round the ring: the spectrum low to high from the top down each side, so it is symmetric.
|
|
20772
|
+
float n = float(uBars);
|
|
20773
|
+
float t = fract(a / 6.28318);
|
|
20774
|
+
float cell = floor(t * n);
|
|
20775
|
+
float mirrored = abs((cell + 0.5) / n * 2.0 - 1.0);
|
|
20776
|
+
float v = spectrum(1.0 - mirrored);
|
|
20777
|
+
float len = uLength * (0.08 + v);
|
|
20778
|
+
float within = abs(fract(t * n) - 0.5) * 6.28318 * r / n;
|
|
20779
|
+
float width = 0.45 * 6.28318 * r0 / n;
|
|
20780
|
+
float radial = max(r0 + 0.01 - r, r - (r0 + 0.01 + len));
|
|
20781
|
+
float bar = clamp(0.5 - max(radial, within - width) / px, 0.0, 1.0);
|
|
20782
|
+
vec3 tint = mix(uColorA, uColorB, mirrored);
|
|
20783
|
+
vec4 acc = vec4(uBackground, uBackgroundAlpha);
|
|
20784
|
+
// Glow round the ring with the level.
|
|
20785
|
+
float halo = exp(-abs(r - r0 - 0.02) / (0.04 + 0.06 * s.x)) * (0.3 + 0.7 * s.x);
|
|
20786
|
+
vec3 glowCol = tint * halo * 0.6;
|
|
20787
|
+
acc.rgb += glowCol;
|
|
20788
|
+
acc.a = max(acc.a, clamp(halo * 0.6, 0.0, 1.0));
|
|
20789
|
+
acc = mix(acc, vec4(tint * (0.8 + 0.5 * v), 1.0), bar);
|
|
20790
|
+
// The centre: the picture (or the colour) in a disc, with a thin ring.
|
|
20791
|
+
float disc = clamp((r0 - r) / px, 0.0, 1.0);
|
|
20792
|
+
if (disc > 0.0) {
|
|
20793
|
+
vec2 uv = p / (2.0 * r0) + 0.5;
|
|
20794
|
+
vec4 pic = uShowImage ? pictureRGBA(uImage, vec2(uv.x, uv.y)) : vec4(uCenter, 1.0);
|
|
20795
|
+
vec3 c = mix(uCenter, pic.rgb, pic.a) * (0.9 + 0.2 * s.y);
|
|
20796
|
+
acc = mix(acc, vec4(c, 1.0), disc);
|
|
20797
|
+
}
|
|
20798
|
+
float rim = clamp(1.0 - abs(r - r0) / (1.5 * px), 0.0, 1.0);
|
|
20799
|
+
acc = mix(acc, vec4(mix(tint, vec3(1.0), 0.5), 1.0), rim * 0.8);
|
|
20800
|
+
fragColor = acc;
|
|
20801
|
+
}`
|
|
20802
|
+
),
|
|
20803
|
+
presets: [
|
|
20804
|
+
{ name: "Colour disc", values: { uShowImage: false, uCenter: [0.95, 0.3, 0.45], uColorA: [1, 0.8, 0.3], uColorB: [1, 0.3, 0.5] } },
|
|
20805
|
+
{ name: "Overlay", values: { uBackgroundAlpha: 0, uShowImage: false, uCenter: [0, 0, 0], uBars: 140, uLength: 0.3 } },
|
|
20806
|
+
{ name: "Dense", values: { uBars: 180, uLength: 0.32, uRadius: 0.18, uColorA: [0.3, 1, 0.7], uColorB: [0.2, 0.5, 1] } }
|
|
20807
|
+
]
|
|
20808
|
+
};
|
|
20809
|
+
var waveformLine = {
|
|
20810
|
+
kind: "shader",
|
|
20811
|
+
id: "waveform-line",
|
|
20812
|
+
name: "Waveform line",
|
|
20813
|
+
description: "A smooth glowing oscilloscope line drawn from the music's spectrum, with fainter echoes trailing it.",
|
|
20814
|
+
category: "audio",
|
|
20815
|
+
tags: ["audio", "music", "waveform", "oscilloscope", "line", "visualizer", "overlay"],
|
|
20816
|
+
transparent: true,
|
|
20817
|
+
source: glsl(
|
|
20818
|
+
`// Waveform line: a line built from sines whose sizes are the spectrum's bands, glowing, with echoes.
|
|
20819
|
+
uniform vec3 uColor; // @control color default=#5EEAD4 label="Colour"
|
|
20820
|
+
uniform vec3 uEcho; // @control color default=#A78BFA label="Echo colour"
|
|
20821
|
+
uniform float uAmplitude; // @control min=0.05 max=0.5 default=0.22 label="Height"
|
|
20822
|
+
uniform float uThickness; // @control min=0.5 max=8 default=2.5 label="Thickness (px)"
|
|
20823
|
+
uniform float uGlow; // @control min=0 max=2 default=1 label="Glow"
|
|
20824
|
+
uniform int uEchoes; // @control min=0 max=3 default=2 label="Echoes"
|
|
20825
|
+
uniform float uSpeed; // @control min=0 max=3 default=1 label="Speed"
|
|
20826
|
+
uniform vec3 uBackground; // @control color default=#03050C label="Background"
|
|
20827
|
+
uniform float uBackgroundAlpha; // @control min=0 max=1 default=0 label="Background opacity"
|
|
20828
|
+
${DEMO}`,
|
|
20829
|
+
SOUND,
|
|
20830
|
+
GLOW8,
|
|
20831
|
+
`// The line's height at x (0\u20131) and its slope, from 12 bands of the spectrum, fading out at the ends.
|
|
20832
|
+
vec2 wave(float x, float t) {
|
|
20833
|
+
float y = 0.0;
|
|
20834
|
+
float dy = 0.0;
|
|
20835
|
+
for (int k = 0; k < 12; k++) {
|
|
20836
|
+
float fk = float(k);
|
|
20837
|
+
float amp = spectrum((fk + 0.5) / 12.0) / (1.0 + fk * 0.35);
|
|
20838
|
+
float freq = 6.28318 * (1.5 + fk * 1.7);
|
|
20839
|
+
float ph = t * (1.3 + fk * 0.6) * (mod(fk, 2.0) < 0.5 ? 1.0 : -1.0) + fk * 1.7;
|
|
20840
|
+
y += amp * sin(freq * x + ph);
|
|
20841
|
+
dy += amp * freq * cos(freq * x + ph);
|
|
20842
|
+
}
|
|
20843
|
+
float env = sin(3.14159 * clamp(x, 0.0, 1.0));
|
|
20844
|
+
float denv = 3.14159 * cos(3.14159 * clamp(x, 0.0, 1.0));
|
|
20845
|
+
return vec2(y * env, dy * env + y * denv) * 0.35;
|
|
20846
|
+
}
|
|
20847
|
+
|
|
20848
|
+
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
20849
|
+
vec2 uv = fragCoord / iResolution.xy;
|
|
20850
|
+
float aspect = iResolution.x / iResolution.y;
|
|
20851
|
+
vec4 s = sound();
|
|
20852
|
+
vec3 light = vec3(0.0);
|
|
20853
|
+
for (int e = 3; e >= 0; e--) {
|
|
20854
|
+
if (e > uEchoes) continue;
|
|
20855
|
+
float lag = float(e) * 0.12;
|
|
20856
|
+
vec2 w = wave(uv.x, iTime * uSpeed - lag) * uAmplitude * (1.0 + 0.6 * s.y) * (1.0 - 0.18 * float(e));
|
|
20857
|
+
// Distance to the line in pixels, corrected for its slope.
|
|
20858
|
+
float slope = w.y / aspect;
|
|
20859
|
+
float d = abs(uv.y - 0.5 - w.x) * iResolution.y / sqrt(1.0 + slope * slope);
|
|
20860
|
+
float core = clamp(uThickness * 0.5 + 0.5 - d, 0.0, 1.0);
|
|
20861
|
+
float halo = exp(-d / (6.0 + 18.0 * s.x)) * uGlow * 0.5;
|
|
20862
|
+
vec3 c = e == 0 ? uColor : uEcho;
|
|
20863
|
+
float fade = e == 0 ? 1.0 : 0.45 / float(e);
|
|
20864
|
+
light += c * (core * 1.6 + halo) * fade;
|
|
20865
|
+
}
|
|
20866
|
+
vec4 g = glow(light);
|
|
20867
|
+
vec3 col = mix(uBackground, g.rgb, g.a);
|
|
20868
|
+
float alpha = max(g.a, uBackgroundAlpha);
|
|
20869
|
+
fragColor = vec4(mix(g.rgb, col, uBackgroundAlpha), alpha);
|
|
20870
|
+
}`
|
|
20871
|
+
),
|
|
20872
|
+
presets: [
|
|
20873
|
+
{ name: "On black", values: { uBackgroundAlpha: 1 } },
|
|
20874
|
+
{ name: "Laser", values: { uColor: [1, 0.15, 0.25], uEcho: [1, 0.6, 0.2], uThickness: 1.5, uGlow: 1.8 } },
|
|
20875
|
+
{ name: "Clean", values: { uEchoes: 0, uGlow: 0.3, uColor: [1, 1, 1], uThickness: 3 } }
|
|
20876
|
+
]
|
|
20877
|
+
};
|
|
20878
|
+
var bassPulse = {
|
|
20879
|
+
kind: "shader",
|
|
20880
|
+
id: "bass-pulse",
|
|
20881
|
+
name: "Bass pulse",
|
|
20882
|
+
description: "A picture or logo thumping to the bass: it swells and glows on every kick, with a colour split on the beat.",
|
|
20883
|
+
category: "audio",
|
|
20884
|
+
video: "filter",
|
|
20885
|
+
tags: ["audio", "music", "bass", "pulse", "logo", "beat", "glow"],
|
|
20886
|
+
source: glsl(
|
|
20887
|
+
`// Bass pulse: the picture scaled by the bass, a glow from its edges, a quick colour split on each beat.
|
|
20888
|
+
uniform sampler2D uImage; // @control filter=mipmap wrap=clamp default=${SAMPLE_IMAGES.portrait} label="Picture or logo"
|
|
20889
|
+
uniform float uSize; // @control min=0.2 max=1 default=0.55 label="Size"
|
|
20890
|
+
uniform float uPulse; // @control min=0 max=0.5 default=0.14 label="Pulse"
|
|
20891
|
+
uniform vec3 uGlowColor; // @control color default=#F472B6 label="Glow"
|
|
20892
|
+
uniform float uGlow; // @control min=0 max=2 default=1 label="Glow strength"
|
|
20893
|
+
uniform float uSplit; // @control min=0 max=0.03 default=0.012 label="Colour split on the beat"
|
|
20894
|
+
uniform float uCorner; // @control min=0 max=0.5 default=0.08 label="Corner rounding"
|
|
20895
|
+
uniform vec3 uBackground; // @control color default=#0B0614 label="Background"
|
|
20896
|
+
uniform float uBackgroundAlpha; // @control min=0 max=1 default=1 label="Background opacity"
|
|
20897
|
+
${BACKDROP_CONTROLS}
|
|
20898
|
+
${DEMO}`,
|
|
20899
|
+
PICTURE,
|
|
20900
|
+
SOUND,
|
|
20901
|
+
`// The picture's coverage at p (centred, in heights), and its colour: inside a rounded box, through its own alpha.
|
|
20902
|
+
vec4 card(vec2 p, vec2 hs) {
|
|
20903
|
+
float corner = uCorner * min(hs.x, hs.y);
|
|
20904
|
+
vec2 q = abs(p) - hs + corner;
|
|
20905
|
+
float d = length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - corner;
|
|
20906
|
+
float inside = clamp(0.5 - d * iResolution.y, 0.0, 1.0);
|
|
20907
|
+
if (inside <= 0.0) return vec4(0.0);
|
|
20908
|
+
vec4 c = pictureRGBA(uImage, p / (2.0 * hs) + 0.5);
|
|
20909
|
+
return vec4(c.rgb, c.a * inside);
|
|
20910
|
+
}
|
|
20911
|
+
|
|
20912
|
+
// The glow reaching p: falling off from the picture's box, shaped by its alpha blurred (a coarse mip) nearby.
|
|
20913
|
+
float glowAt(vec2 p, vec2 hs, float spread) {
|
|
20914
|
+
float corner = uCorner * min(hs.x, hs.y);
|
|
20915
|
+
vec2 q = abs(p) - hs + corner;
|
|
20916
|
+
float d = length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - corner;
|
|
20917
|
+
float box = exp(-max(d, 0.0) / spread);
|
|
20918
|
+
if (textureSize(uImage, 0).x <= 1 || uBackdropAlpha >= 1.0) return box;
|
|
20919
|
+
// A logo on a clear backdrop: two coarse mips of its alpha, a soft halo round its own shape.
|
|
20920
|
+
vec2 uv = p / (2.0 * hs) + 0.5;
|
|
20921
|
+
float lod = log2(float(textureSize(uImage, 0).x));
|
|
20922
|
+
float blurred = min(1.0, 0.6 * textureLod(uImage, uv, lod - 4.5).a + 0.8 * textureLod(uImage, uv, lod - 3.0).a);
|
|
20923
|
+
float inside = step(abs(uv.x - 0.5), 0.5) * step(abs(uv.y - 0.5), 0.5);
|
|
20924
|
+
return mix(blurred * inside, box, uBackdropAlpha);
|
|
20925
|
+
}
|
|
20926
|
+
|
|
20927
|
+
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
20928
|
+
vec2 p = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
|
|
20929
|
+
vec4 s = sound();
|
|
20930
|
+
float b = beat();
|
|
20931
|
+
float hit = b >= 0.0 ? exp(-b * 6.0) : 0.0;
|
|
20932
|
+
float bass = s.y * s.y;
|
|
20933
|
+
float scale = 1.0 + uPulse * (bass * 0.7 + hit * 0.5);
|
|
20934
|
+
// The picture's own shape: square for a 1 \xD7 1 placeholder, else its aspect.
|
|
20935
|
+
vec2 size = vec2(textureSize(uImage, 0));
|
|
20936
|
+
float aspect = size.x > 1.0 ? size.x / size.y : 16.0 / 9.0;
|
|
20937
|
+
vec2 hs = 0.5 * uSize * scale * vec2(aspect, 1.0);
|
|
20938
|
+
// Narrower than the frame, whatever the picture's shape.
|
|
20939
|
+
hs *= min(1.0, 0.45 * iResolution.x / iResolution.y / hs.x);
|
|
20940
|
+
// A colour split along x on the beat.
|
|
20941
|
+
vec2 off = vec2(uSplit * hit, 0.0);
|
|
20942
|
+
vec4 c = card(p, hs);
|
|
20943
|
+
vec4 cr = card(p - off, hs);
|
|
20944
|
+
vec4 cb = card(p + off, hs);
|
|
20945
|
+
vec3 pic = vec3(cr.r, c.g, cb.b);
|
|
20946
|
+
float cover = max(c.a, max(cr.a, cb.a));
|
|
20947
|
+
// Glow round the picture, wider and brighter with the bass.
|
|
20948
|
+
float spread = 0.02 + 0.05 * bass + 0.03 * hit;
|
|
20949
|
+
float g = glowAt(p, hs, spread) * (0.45 + 0.9 * bass + 0.6 * hit) * uGlow;
|
|
20950
|
+
vec3 col = uBackground + uGlowColor * g * (1.0 - cover);
|
|
20951
|
+
float alpha = max(uBackgroundAlpha, clamp(g, 0.0, 1.0));
|
|
20952
|
+
col = mix(col, pic * (1.0 + 0.25 * hit), cover);
|
|
20953
|
+
fragColor = vec4(col, max(alpha, cover));
|
|
20954
|
+
}`
|
|
20955
|
+
),
|
|
20956
|
+
presets: [
|
|
20957
|
+
{ name: "Logo", values: { uImage: SAMPLE_IMAGES.logo, uBackdropAlpha: 0, uCorner: 0, uSize: 0.6, uGlowColor: [0.5, 0.5, 1] } },
|
|
20958
|
+
{ name: "Heart beat", values: { uImage: SAMPLE_IMAGES.heart, uBackdropAlpha: 0, uCorner: 0, uPulse: 0.3, uGlowColor: [1, 0.3, 0.4] } },
|
|
20959
|
+
{ name: "Overlay", values: { uBackgroundAlpha: 0, uSplit: 0.02 } }
|
|
20960
|
+
]
|
|
20961
|
+
};
|
|
20962
|
+
var audioParticles = {
|
|
20963
|
+
kind: "shader",
|
|
20964
|
+
id: "audio-particles",
|
|
20965
|
+
name: "Audio particles",
|
|
20966
|
+
description: "Sparks bursting outward from the centre on every beat, trailing and fading, over a core that breathes with the level.",
|
|
20967
|
+
category: "audio",
|
|
20968
|
+
tags: ["audio", "music", "particles", "beat", "burst", "overlay", "visualizer"],
|
|
20969
|
+
transparent: true,
|
|
20970
|
+
source: glsl(
|
|
20971
|
+
`// Audio particles: a burst of sparks on each beat, flying out and slowing, plus a core glowing with the level.
|
|
20972
|
+
uniform int uCount; // @control min=8 max=64 default=40 label="Particles"
|
|
20973
|
+
uniform float uSpeed; // @control min=0.2 max=2 default=0.9 label="Burst speed"
|
|
20974
|
+
uniform float uSize; // @control min=0.002 max=0.02 default=0.007 label="Size"
|
|
20975
|
+
uniform float uLife; // @control min=0.2 max=2 default=0.9 label="Lifetime"
|
|
20976
|
+
uniform vec3 uColorA; // @control color default=#FDE047 label="Colour 1"
|
|
20977
|
+
uniform vec3 uColorB; // @control color default=#FB7185 label="Colour 2"
|
|
20978
|
+
uniform vec3 uColorC; // @control color default=#60A5FA label="Colour 3"
|
|
20979
|
+
uniform float uCore; // @control min=0 max=1 default=0.6 label="Core glow"
|
|
20980
|
+
${DEMO}`,
|
|
20981
|
+
HASH,
|
|
20982
|
+
SOUND,
|
|
20983
|
+
GLOW8,
|
|
20984
|
+
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
20985
|
+
vec2 p = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
|
|
20986
|
+
vec4 s = sound();
|
|
20987
|
+
float b = beat();
|
|
20988
|
+
vec3 light = vec3(0.0);
|
|
20989
|
+
// The core breathes with the level.
|
|
20990
|
+
float r = length(p);
|
|
20991
|
+
light += mix(uColorA, uColorB, 0.5) * uCore * (0.2 + 0.8 * s.x) * exp(-r / (0.04 + 0.08 * s.y)) * 0.8;
|
|
20992
|
+
if (b >= 0.0 && b < uLife) {
|
|
20993
|
+
// Each burst is seeded by when it began (the beat's time on the iTime clock), so bursts differ.
|
|
20994
|
+
float seed = floor((iTime - b) * 8.0);
|
|
20995
|
+
float life = b / uLife;
|
|
20996
|
+
float fade = (1.0 - life) * (1.0 - life);
|
|
20997
|
+
float energy = 0.6 + 0.6 * s.y;
|
|
20998
|
+
for (int i = 0; i < 64; i++) {
|
|
20999
|
+
if (i >= uCount) break;
|
|
21000
|
+
vec2 h = hash22(vec2(float(i) * 1.91 + 0.3, seed));
|
|
21001
|
+
float ang = 6.28318 * (float(i) + h.x) / float(uCount);
|
|
21002
|
+
float v = uSpeed * energy * (0.35 + 0.65 * h.y);
|
|
21003
|
+
// Out fast, slowed by drag, the slow ones drooping a little.
|
|
21004
|
+
float dist = v * (1.0 - exp(-b * 3.5)) / 3.5;
|
|
21005
|
+
vec2 at = vec2(sin(ang), cos(ang)) * dist - vec2(0.0, 0.08 * b * b);
|
|
21006
|
+
vec2 dir = normalize(vec2(sin(ang), cos(ang)) + 1e-4);
|
|
21007
|
+
// A short streak behind each spark, along its direction.
|
|
21008
|
+
vec2 d = p - at;
|
|
21009
|
+
float along = clamp(dot(d, -dir), 0.0, 0.04 * exp(-b * 3.0) + 0.002);
|
|
21010
|
+
float dd = length(d + dir * along);
|
|
21011
|
+
float size = uSize * (1.0 - 0.5 * life);
|
|
21012
|
+
float spark = exp(-dd * dd / (size * size)) + 0.25 * exp(-dd / (size * 4.0));
|
|
21013
|
+
vec3 c = h.x < 0.33 ? uColorA : h.x < 0.66 ? uColorB : uColorC;
|
|
21014
|
+
light += c * spark * fade * 1.4;
|
|
21015
|
+
}
|
|
21016
|
+
}
|
|
21017
|
+
// Fine glitter that shimmers with the treble.
|
|
21018
|
+
vec2 cell = floor(p * 40.0);
|
|
21019
|
+
float g = hash12(cell + floor(iTime * 12.0));
|
|
21020
|
+
float twinkle = step(0.985, g) * s.w * exp(-length(fract(p * 40.0) - 0.5) * 8.0);
|
|
21021
|
+
light += uColorC * twinkle;
|
|
21022
|
+
fragColor = glow(light);
|
|
21023
|
+
}`
|
|
21024
|
+
),
|
|
21025
|
+
presets: [
|
|
21026
|
+
{ name: "Gold", values: { uColorA: [1, 0.85, 0.4], uColorB: [1, 0.6, 0.2], uColorC: [1, 1, 0.8], uCount: 56 } },
|
|
21027
|
+
{ name: "Ice", values: { uColorA: [0.6, 0.9, 1], uColorB: [0.3, 0.6, 1], uColorC: [1, 1, 1], uSpeed: 1.3, uLife: 1.2 } }
|
|
21028
|
+
]
|
|
21029
|
+
};
|
|
21030
|
+
var audioPlasma = {
|
|
21031
|
+
kind: "shader",
|
|
21032
|
+
id: "audio-plasma",
|
|
21033
|
+
name: "Audio plasma",
|
|
21034
|
+
description: "A flowing plasma whose swirl, scale and colours follow the bass, mids and highs, flaring on each beat.",
|
|
21035
|
+
category: "audio",
|
|
21036
|
+
tags: ["audio", "music", "plasma", "gradient", "psychedelic", "background", "visualizer"],
|
|
21037
|
+
source: glsl(
|
|
21038
|
+
`// Audio plasma: layered sines warped by noise; the bass pushes the flow, the mids tilt the palette, the highs add grain.
|
|
21039
|
+
uniform float uScale; // @control min=0.5 max=6 default=2.4 label="Scale"
|
|
21040
|
+
uniform float uFlow; // @control min=0 max=2 default=0.5 label="Flow"
|
|
21041
|
+
uniform float uReact; // @control min=0 max=2 default=1 label="Reactivity"
|
|
21042
|
+
uniform vec3 uPhase; // @control color default=#1A66B3 label="Palette phase"
|
|
21043
|
+
uniform float uContrast; // @control min=0.5 max=2 default=1.1 label="Contrast"
|
|
21044
|
+
${DEMO}`,
|
|
21045
|
+
HASH,
|
|
21046
|
+
NOISE,
|
|
21047
|
+
PALETTE,
|
|
21048
|
+
SOUND,
|
|
21049
|
+
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
21050
|
+
vec2 p = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
|
|
21051
|
+
vec4 s = sound() * uReact;
|
|
21052
|
+
float b = beat();
|
|
21053
|
+
float hit = b >= 0.0 ? exp(-b * 5.0) * uReact : 0.0;
|
|
21054
|
+
float t = iTime * uFlow;
|
|
21055
|
+
// The bass pushes the pattern on (a nudge that settles back, not a jump in time).
|
|
21056
|
+
float push = 0.8 * s.y + 0.5 * hit;
|
|
21057
|
+
vec2 q = p * uScale * (1.0 - 0.1 * s.y);
|
|
21058
|
+
vec2 w = vec2(vnoise(q * 0.7 + t * 0.3), vnoise(q * 0.7 - t * 0.27 + 5.0)) - 0.5;
|
|
21059
|
+
q += w * (1.2 + 1.5 * s.z);
|
|
21060
|
+
float v = sin(q.x + t + push);
|
|
21061
|
+
v += sin(q.y * 1.3 - t * 0.8 + push * 0.7);
|
|
21062
|
+
v += sin((q.x + q.y) * 0.8 + t * 0.6);
|
|
21063
|
+
v += sin(length(q + vec2(sin(t * 0.4), cos(t * 0.3))) * 2.0 - t * 1.2 - push * 1.5);
|
|
21064
|
+
v = v * 0.25 * uContrast;
|
|
21065
|
+
// The mids turn the palette; the highs add a fine shimmer.
|
|
21066
|
+
vec3 col = palette(v * 0.7 + 0.15 * s.z + 0.05 * t, vec3(0.45, 0.4, 0.5), vec3(0.4, 0.35, 0.4), vec3(0.8, 0.7, 0.6), uPhase + vec3(0.0, 0.1 * s.w, 0.2 * s.y));
|
|
21067
|
+
col *= 0.75 + 0.35 * s.x + 0.4 * hit;
|
|
21068
|
+
col += (hash12(fragCoord + floor(iTime * 30.0)) - 0.5) * 0.06 * s.w;
|
|
21069
|
+
fragColor = vec4(clamp(col, 0.0, 1.0), 1.0);
|
|
21070
|
+
}`
|
|
21071
|
+
),
|
|
21072
|
+
presets: [
|
|
21073
|
+
{ name: "Sunset", values: { uPhase: [0, 0.15, 0.3], uScale: 1.6 } },
|
|
21074
|
+
{ name: "Acid", values: { uPhase: [0.3, 0.6, 0.1], uScale: 4, uContrast: 1.6, uFlow: 1 } },
|
|
21075
|
+
{ name: "Calm", values: { uReact: 0.4, uFlow: 0.25, uScale: 1.2 } }
|
|
21076
|
+
]
|
|
21077
|
+
};
|
|
21078
|
+
var vinyl = {
|
|
21079
|
+
kind: "shader",
|
|
21080
|
+
id: "vinyl",
|
|
21081
|
+
name: "Vinyl",
|
|
21082
|
+
description: "A record spinning on a turntable, its label a picture, its grooves catching the light with the music, the tonearm riding the beat.",
|
|
21083
|
+
category: "audio",
|
|
21084
|
+
video: "stills",
|
|
21085
|
+
tags: ["audio", "music", "vinyl", "record", "turntable", "retro", "album art"],
|
|
21086
|
+
source: glsl(
|
|
21087
|
+
`// Vinyl: a spinning record; each groove's sheen follows the spectrum at its radius, the label is a picture.
|
|
21088
|
+
uniform sampler2D uLabel; // @control filter=mipmap wrap=clamp default=${SAMPLE_IMAGES.portrait} label="Label picture"
|
|
21089
|
+
uniform float uRpm; // @control min=0 max=78 default=33.3 label="Speed (rpm)"
|
|
21090
|
+
uniform vec3 uRecord; // @control color default=#101014 label="Record"
|
|
21091
|
+
uniform vec3 uSheen; // @control color default=#C4B5FD label="Sheen"
|
|
21092
|
+
uniform float uShimmer; // @control min=0 max=2 default=1 label="Shimmer"
|
|
21093
|
+
uniform vec3 uBackground; // @control color default=#2A211C label="Platter"
|
|
21094
|
+
uniform float uBackgroundAlpha; // @control min=0 max=1 default=1 label="Platter opacity"
|
|
21095
|
+
uniform bool uArm; // @control default=true label="Tonearm"
|
|
21096
|
+
${BACKDROP_CONTROLS}
|
|
21097
|
+
${DEMO}`,
|
|
21098
|
+
PICTURE,
|
|
21099
|
+
SOUND,
|
|
21100
|
+
ROTATE,
|
|
21101
|
+
`float segment(vec2 p, vec2 a, vec2 b) {
|
|
21102
|
+
vec2 ab = b - a;
|
|
21103
|
+
float t = clamp(dot(p - a, ab) / dot(ab, ab), 0.0, 1.0);
|
|
21104
|
+
return length(p - a - ab * t);
|
|
21105
|
+
}
|
|
21106
|
+
|
|
21107
|
+
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
21108
|
+
float px = 1.0 / iResolution.y;
|
|
21109
|
+
vec2 p = (fragCoord - 0.5 * iResolution.xy) * px;
|
|
21110
|
+
vec2 c = p + vec2(0.12, 0.0);
|
|
21111
|
+
vec4 s = sound();
|
|
21112
|
+
float b = beat();
|
|
21113
|
+
float hit = b >= 0.0 ? exp(-b * 8.0) : 0.0;
|
|
21114
|
+
float R = 0.44;
|
|
21115
|
+
float r = length(c);
|
|
21116
|
+
float spin = iTime * uRpm / 60.0 * 6.28318;
|
|
21117
|
+
vec2 q = rot(spin) * c;
|
|
21118
|
+
float ang = atan(q.y, q.x);
|
|
21119
|
+
// The platter, with a soft shadow under the record.
|
|
21120
|
+
vec3 col = uBackground * (0.85 + 0.15 * smoothstep(0.9, 0.0, length(p)));
|
|
21121
|
+
float shadow = 0.5 * exp(-max(r - R, 0.0) / 0.02) * step(R, r);
|
|
21122
|
+
col *= 1.0 - shadow;
|
|
21123
|
+
float alpha = max(uBackgroundAlpha, shadow);
|
|
21124
|
+
float disc = clamp((R - r) / px, 0.0, 1.0);
|
|
21125
|
+
if (disc > 0.0) {
|
|
21126
|
+
// Grooves: fine rings; their sheen is the spectrum at that radius (bass outside, treble in), in two
|
|
21127
|
+
// bright wedges opposite the light, as on a real record.
|
|
21128
|
+
float band = clamp((R - r) / (R - 0.14), 0.0, 1.0);
|
|
21129
|
+
float level = spectrum(band);
|
|
21130
|
+
float grooves = 0.5 + 0.5 * sin(r * 900.0);
|
|
21131
|
+
float wedge = pow(abs(cos(atan(c.y, c.x) - 0.8)), 24.0);
|
|
21132
|
+
float sheen = wedge * (0.25 + uShimmer * level) * (0.6 + 0.4 * grooves);
|
|
21133
|
+
vec3 rec = uRecord * (0.9 + 0.2 * grooves) + uSheen * sheen;
|
|
21134
|
+
// A faint swirl that shows the spin.
|
|
21135
|
+
rec += 0.03 * sin(ang * 2.0 + r * 40.0);
|
|
21136
|
+
// Lead-in and run-out rings.
|
|
21137
|
+
rec *= 1.0 - 0.4 * (smoothstep(0.004, 0.0, abs(r - R + 0.012)) + smoothstep(0.004, 0.0, abs(r - 0.15)));
|
|
21138
|
+
col = mix(col, rec, disc);
|
|
21139
|
+
// The label: the picture, turning with the record.
|
|
21140
|
+
float label = clamp((0.13 - r) / px, 0.0, 1.0);
|
|
21141
|
+
vec4 pic = pictureRGBA(uLabel, q / 0.26 + 0.5);
|
|
21142
|
+
vec3 lab = mix(uRecord, pic.rgb, pic.a) * (0.95 + 0.1 * s.y);
|
|
21143
|
+
col = mix(col, lab, label);
|
|
21144
|
+
// The spindle.
|
|
21145
|
+
col = mix(col, vec3(0.8), clamp((0.008 - r) / px, 0.0, 1.0));
|
|
21146
|
+
}
|
|
21147
|
+
if (uArm) {
|
|
21148
|
+
// The tonearm: from a pivot at the top right to the stylus on the outer grooves, nudged by the beat.
|
|
21149
|
+
vec2 pivot = vec2(0.42, 0.34);
|
|
21150
|
+
float swing = 0.32 + 0.004 * hit;
|
|
21151
|
+
vec2 head = pivot + vec2(-sin(swing + 0.9), -cos(swing + 0.9)) * 0.5;
|
|
21152
|
+
float arm = segment(p, pivot, head);
|
|
21153
|
+
float shade = clamp((0.009 - arm) / px, 0.0, 1.0);
|
|
21154
|
+
col = mix(col * (1.0 - 0.35 * exp(-segment(p + vec2(-0.01, 0.015), pivot, head) / 0.015)), vec3(0.78, 0.78, 0.82), shade);
|
|
21155
|
+
float cart = segment(p, head, head + vec2(-0.03, -0.012));
|
|
21156
|
+
col = mix(col, vec3(0.15), clamp((0.016 - cart) / px, 0.0, 1.0));
|
|
21157
|
+
float base = length(p - pivot);
|
|
21158
|
+
float foot = clamp((0.035 - base) / px, 0.0, 1.0);
|
|
21159
|
+
col = mix(col, vec3(0.6), foot);
|
|
21160
|
+
col = mix(col, vec3(0.3), clamp((0.015 - base) / px, 0.0, 1.0));
|
|
21161
|
+
alpha = max(alpha, max(shade, max(foot, clamp((0.016 - cart) / px, 0.0, 1.0))));
|
|
21162
|
+
}
|
|
21163
|
+
fragColor = vec4(col, max(alpha, disc));
|
|
21164
|
+
}`
|
|
21165
|
+
),
|
|
21166
|
+
presets: [
|
|
21167
|
+
{ name: "Red vinyl", values: { uRecord: [0.45, 0.04, 0.06], uSheen: [1, 0.6, 0.6] } },
|
|
21168
|
+
{ name: "45 rpm", values: { uRpm: 45, uLabel: SAMPLE_IMAGES.logo, uBackdrop: [0.95, 0.9, 0.8] } }
|
|
21169
|
+
]
|
|
21170
|
+
};
|
|
21171
|
+
var audioShaders = [spectrumBars, circularSpectrum, waveformLine, bassPulse, audioParticles, audioPlasma, vinyl];
|
|
21172
|
+
|
|
21173
|
+
// src/catalog/shaders/scroll.ts
|
|
21174
|
+
var SCROLL = `
|
|
21175
|
+
bool scrollDemo() { return uDemo && dot(iScroll, iScroll) == 0.0; }
|
|
21176
|
+
vec4 scroll() {
|
|
21177
|
+
if (!scrollDemo()) return iScroll;
|
|
21178
|
+
// Down and back up every 6 s, resting a moment at each end.
|
|
21179
|
+
float ph = fract(iTime / 6.0);
|
|
21180
|
+
float u = clamp(ph < 0.5 ? ph * 2.4 - 0.1 : (1.0 - ph) * 2.4 - 0.1, 0.0, 1.0);
|
|
21181
|
+
float x = u * u * (3.0 - 2.0 * u);
|
|
21182
|
+
float moving = u > 0.0 && u < 1.0 ? 1.0 : 0.0;
|
|
21183
|
+
// dx/dt in viewport heights a second (progress 0 to 1 being about two viewport heights): u moves 2.4 per 6 s.
|
|
21184
|
+
float v = moving * 6.0 * u * (1.0 - u) * (2.4 / 6.0) * 2.0 * (ph < 0.5 ? 1.0 : -1.0);
|
|
21185
|
+
return vec4(x, x, v, moving);
|
|
21186
|
+
}`.trim();
|
|
21187
|
+
var DEMO2 = `uniform bool uDemo; // @control default=true label="Demo when not scrolled"`;
|
|
21188
|
+
var scrollReveal = {
|
|
21189
|
+
kind: "shader",
|
|
21190
|
+
id: "scroll-reveal",
|
|
21191
|
+
name: "Scroll reveal",
|
|
21192
|
+
description: "A picture revealed by a soft wipe, dissolve, iris or blinds as it scrolls into view, settling from a slight zoom.",
|
|
21193
|
+
category: "scroll",
|
|
21194
|
+
video: "filter",
|
|
21195
|
+
transparent: true,
|
|
21196
|
+
tags: ["interactive", "scroll", "reveal", "wipe", "dissolve", "image", "landing page"],
|
|
21197
|
+
source: glsl(
|
|
21198
|
+
`// Scroll reveal: the picture's coverage follows how far the canvas has come into view.
|
|
21199
|
+
uniform sampler2D uImage; // @control filter=mipmap wrap=clamp default=${SAMPLE_IMAGES.landscape} label="Image"
|
|
21200
|
+
uniform int uStyle; // @control min=0 max=3 default=0 label="Style (wipe, dissolve, iris, blinds)"
|
|
21201
|
+
uniform float uStart; // @control min=0 max=1 default=0.1 label="Starts at"
|
|
21202
|
+
uniform float uEnd; // @control min=0 max=1 default=0.5 label="Done at"
|
|
21203
|
+
uniform float uSoftness; // @control min=0 max=0.4 default=0.12 label="Edge softness"
|
|
21204
|
+
uniform float uZoom; // @control min=0 max=0.3 default=0.08 label="Settle zoom"
|
|
21205
|
+
uniform vec3 uEdge; // @control color default=#FFFFFF label="Edge glow"
|
|
21206
|
+
uniform float uEdgeGlow; // @control min=0 max=1 default=0.35 label="Edge glow strength"
|
|
21207
|
+
${BACKDROP_CONTROLS}
|
|
21208
|
+
${DEMO2}`,
|
|
21209
|
+
PICTURE,
|
|
21210
|
+
HASH,
|
|
21211
|
+
NOISE,
|
|
21212
|
+
SCROLL,
|
|
21213
|
+
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
21214
|
+
vec2 uv = fragCoord / iResolution.xy;
|
|
21215
|
+
vec4 s = scroll();
|
|
21216
|
+
float r = smoothstep(uStart, max(uEnd, uStart + 1e-3), s.x);
|
|
21217
|
+
// The field the reveal sweeps across, 0\u20131, and how far it has swept.
|
|
21218
|
+
float field;
|
|
21219
|
+
if (uStyle == 0) field = 1.0 - uv.y + 0.04 * (vnoise(vec2(uv.x * 9.0, 0.0)) - 0.5);
|
|
21220
|
+
else if (uStyle == 1) field = vnoise(fragCoord / iResolution.y * 18.0) * 0.6 + vnoise(fragCoord / iResolution.y * 60.0) * 0.4;
|
|
21221
|
+
else if (uStyle == 2) field = length((uv - 0.5) * vec2(iResolution.x / iResolution.y, 1.0)) / 0.9;
|
|
21222
|
+
else field = fract(uv.y * 8.0) * 0.9 + uv.y * 0.1;
|
|
21223
|
+
float soft = max(uSoftness, 1e-3);
|
|
21224
|
+
float edge = r * (1.0 + soft) - soft;
|
|
21225
|
+
float shown = smoothstep(edge + soft, edge, field);
|
|
21226
|
+
float rim = exp(-pow((field - edge - soft * 0.5) / (soft * 0.35 + 0.01), 2.0)) * (1.0 - step(0.999, r)) * step(0.001, r);
|
|
21227
|
+
// The picture settles from a slight zoom as it is revealed.
|
|
21228
|
+
vec2 at = (uv - 0.5) / (1.0 + uZoom * (1.0 - r)) + 0.5;
|
|
21229
|
+
vec4 pic = pictureRGBA(uImage, at);
|
|
21230
|
+
vec3 col = pic.rgb + uEdge * rim * uEdgeGlow;
|
|
21231
|
+
float alpha = pic.a * shown;
|
|
21232
|
+
fragColor = vec4(col, clamp(max(alpha, rim * uEdgeGlow * pic.a), 0.0, 1.0));
|
|
21233
|
+
}`
|
|
21234
|
+
),
|
|
21235
|
+
presets: [
|
|
21236
|
+
{ name: "Dissolve", values: { uStyle: 1, uSoftness: 0.2 } },
|
|
21237
|
+
{ name: "Iris", values: { uStyle: 2, uSoftness: 0.05, uImage: SAMPLE_IMAGES.portrait } },
|
|
21238
|
+
{ name: "Blinds", values: { uStyle: 3, uSoftness: 0.05, uEdgeGlow: 0, uImage: SAMPLE_IMAGES.city } }
|
|
21239
|
+
]
|
|
21240
|
+
};
|
|
21241
|
+
var parallaxLayers = {
|
|
21242
|
+
kind: "shader",
|
|
21243
|
+
id: "parallax-layers",
|
|
21244
|
+
name: "Parallax layers",
|
|
21245
|
+
description: "Layered hills under a sky, the near ones sliding further than the far ones as the page scrolls, for depth.",
|
|
21246
|
+
category: "scroll",
|
|
21247
|
+
tags: ["interactive", "scroll", "parallax", "landscape", "hills", "depth", "hero", "background"],
|
|
21248
|
+
source: glsl(
|
|
21249
|
+
`// Parallax layers: five ridgelines, each shifted by the scroll in proportion to how near it is.
|
|
21250
|
+
uniform vec3 uSkyTop; // @control color default=#1E3A8A label="Sky top"
|
|
21251
|
+
uniform vec3 uSkyBottom; // @control color default=#FDBA74 label="Sky horizon"
|
|
21252
|
+
uniform vec3 uFar; // @control color default=#A78BFA label="Far hills"
|
|
21253
|
+
uniform vec3 uNear; // @control color default=#1E1B4B label="Near hills"
|
|
21254
|
+
uniform vec3 uSun; // @control color default=#FFF7D6 label="Sun"
|
|
21255
|
+
uniform float uDepth; // @control min=0 max=1.5 default=0.6 label="Parallax depth"
|
|
21256
|
+
uniform float uRough; // @control min=0 max=1 default=0.5 label="Ruggedness"
|
|
21257
|
+
uniform float uDrift; // @control min=0 max=0.2 default=0.02 label="Drift"
|
|
21258
|
+
${DEMO2}`,
|
|
21259
|
+
HASH,
|
|
21260
|
+
NOISE,
|
|
21261
|
+
SCROLL,
|
|
21262
|
+
`float ridge(float x, float seed) {
|
|
21263
|
+
float h = vnoise(vec2(x * 1.3, seed)) * 0.6 + vnoise(vec2(x * 3.1, seed + 7.0)) * 0.3 * (0.5 + uRough) + vnoise(vec2(x * 8.0, seed + 3.0)) * 0.1 * uRough;
|
|
21264
|
+
return h;
|
|
21265
|
+
}
|
|
21266
|
+
|
|
21267
|
+
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
21268
|
+
vec2 uv = fragCoord / iResolution.xy;
|
|
21269
|
+
float aspect = iResolution.x / iResolution.y;
|
|
21270
|
+
vec4 s = scroll();
|
|
21271
|
+
float shift = (s.x - 0.5) * uDepth;
|
|
21272
|
+
// The sky (and sun) move least: further away.
|
|
21273
|
+
float skyY = uv.y + shift * 0.15;
|
|
21274
|
+
vec3 col = mix(uSkyBottom, uSkyTop, smoothstep(0.1, 1.0, skyY));
|
|
21275
|
+
vec2 sun = vec2(0.68 * aspect, 0.62 - shift * 0.2);
|
|
21276
|
+
float d = length(vec2(uv.x * aspect, uv.y) - sun);
|
|
21277
|
+
col = mix(col, uSun, smoothstep(0.075, 0.07, d));
|
|
21278
|
+
col += uSun * exp(-d * 6.0) * 0.25;
|
|
21279
|
+
for (int i = 0; i < 5; i++) {
|
|
21280
|
+
float k = float(i) / 4.0;
|
|
21281
|
+
// Nearer layers sit lower, rise higher with the scroll, and drift faster.
|
|
21282
|
+
float base = 0.55 - 0.1 * float(i) + shift * (0.15 + 0.85 * k * k) ;
|
|
21283
|
+
float x = uv.x * aspect * (0.6 + 0.5 * k) + iTime * uDrift * (0.3 + k) + float(i) * 11.3;
|
|
21284
|
+
float top = base + ridge(x, float(i) * 5.0) * (0.18 + 0.05 * k);
|
|
21285
|
+
float layer = smoothstep(top + 1.5 / iResolution.y, top, uv.y);
|
|
21286
|
+
vec3 tint = mix(mix(uFar, uSkyBottom, 0.35), uNear, k);
|
|
21287
|
+
// Mist at each layer's foot.
|
|
21288
|
+
tint = mix(tint, uSkyBottom, 0.25 * (1.0 - k) * smoothstep(top - 0.25, top, uv.y) * 0.5);
|
|
21289
|
+
col = mix(col, tint, layer);
|
|
21290
|
+
}
|
|
21291
|
+
fragColor = vec4(col, 1.0);
|
|
21292
|
+
}`
|
|
21293
|
+
),
|
|
21294
|
+
presets: [
|
|
21295
|
+
{ name: "Night", values: { uSkyTop: [0.02, 0.03, 0.1], uSkyBottom: [0.15, 0.2, 0.4], uFar: [0.2, 0.25, 0.45], uNear: [0.02, 0.02, 0.06], uSun: [0.9, 0.92, 1] } },
|
|
21296
|
+
{ name: "Forest", values: { uSkyTop: [0.55, 0.78, 0.95], uSkyBottom: [0.9, 0.95, 0.9], uFar: [0.45, 0.65, 0.55], uNear: [0.05, 0.2, 0.12], uRough: 0.9 } },
|
|
21297
|
+
{ name: "Desert", values: { uSkyTop: [0.95, 0.55, 0.3], uSkyBottom: [1, 0.85, 0.6], uFar: [0.85, 0.55, 0.4], uNear: [0.45, 0.2, 0.12], uRough: 0.2 } }
|
|
21298
|
+
]
|
|
21299
|
+
};
|
|
21300
|
+
var scrollProgressGlow = {
|
|
21301
|
+
kind: "shader",
|
|
21302
|
+
id: "scroll-progress-glow",
|
|
21303
|
+
name: "Scroll progress glow",
|
|
21304
|
+
description: "A reading-progress bar along the top of the page, filling as you scroll, its glowing head flaring while you move.",
|
|
21305
|
+
category: "scroll",
|
|
21306
|
+
transparent: true,
|
|
21307
|
+
tags: ["interactive", "scroll", "progress", "reading", "bar", "overlay", "ui"],
|
|
21308
|
+
source: glsl(
|
|
21309
|
+
`// Scroll progress glow: a bar as long as the page's scroll, with a glow under it and a bright head.
|
|
21310
|
+
uniform vec3 uColorA; // @control color default=#22D3EE label="Start colour"
|
|
21311
|
+
uniform vec3 uColorB; // @control color default=#A855F7 label="End colour"
|
|
21312
|
+
uniform float uHeight; // @control min=1 max=16 default=4 label="Thickness (px)"
|
|
21313
|
+
uniform float uGlow; // @control min=0 max=2 default=1 label="Glow"
|
|
21314
|
+
uniform bool uBottom; // @control default=false label="Along the bottom"
|
|
21315
|
+
uniform vec3 uTrack; // @control color default=#FFFFFF label="Track"
|
|
21316
|
+
uniform float uTrackAlpha; // @control min=0 max=1 default=0.08 label="Track opacity"
|
|
21317
|
+
${DEMO2}`,
|
|
21318
|
+
SCROLL,
|
|
21319
|
+
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
21320
|
+
vec4 s = scroll();
|
|
21321
|
+
float progress = clamp(s.y, 0.0, 1.0);
|
|
21322
|
+
float x = fragCoord.x;
|
|
21323
|
+
// Pixels from the bar's edge, into the page.
|
|
21324
|
+
float y = uBottom ? fragCoord.y : iResolution.y - fragCoord.y;
|
|
21325
|
+
float end = progress * iResolution.x;
|
|
21326
|
+
float inBar = clamp(uHeight - y + 0.5, 0.0, 1.0);
|
|
21327
|
+
float filled = clamp(end - x + 0.5, 0.0, 1.0);
|
|
21328
|
+
vec3 tint = mix(uColorA, uColorB, x / iResolution.x);
|
|
21329
|
+
float lit = 0.35 + 0.65 * s.w;
|
|
21330
|
+
// A glow spilling down from the filled part, and a flare round the head while scrolling.
|
|
21331
|
+
float under = filled * exp(-max(y - uHeight, 0.0) / (6.0 + 10.0 * s.w)) * 0.5 * uGlow * lit;
|
|
21332
|
+
float head = exp(-length(vec2(x - end, max(y - uHeight * 0.5, 0.0))) / (8.0 + 20.0 * s.w)) * uGlow * (0.4 + 0.9 * s.w) * step(0.001, progress);
|
|
21333
|
+
vec3 light = tint * (under + head * 1.4);
|
|
21334
|
+
vec4 bar = vec4(mix(uTrack, tint * (1.0 + 0.3 * s.w), filled), inBar * mix(uTrackAlpha, 1.0, filled));
|
|
21335
|
+
// Light over the bar, as straight colour with alpha.
|
|
21336
|
+
vec3 g = 1.0 - exp(-light);
|
|
21337
|
+
float ga = max(g.r, max(g.g, g.b));
|
|
21338
|
+
float alpha = bar.a + ga * (1.0 - bar.a);
|
|
21339
|
+
vec3 col = (bar.rgb * bar.a + g * (1.0 - bar.a)) / max(alpha, 1e-4);
|
|
21340
|
+
fragColor = vec4(col, alpha);
|
|
21341
|
+
}`
|
|
21342
|
+
),
|
|
21343
|
+
presets: [
|
|
21344
|
+
{ name: "Fire", values: { uColorA: [1, 0.8, 0.2], uColorB: [1, 0.2, 0.1], uHeight: 3 } },
|
|
21345
|
+
{ name: "Thick, bottom", values: { uBottom: true, uHeight: 10, uGlow: 0.5, uTrackAlpha: 0.15 } }
|
|
21346
|
+
]
|
|
21347
|
+
};
|
|
21348
|
+
var scrollVelocityBlur = {
|
|
21349
|
+
kind: "shader",
|
|
21350
|
+
id: "scroll-velocity-blur",
|
|
21351
|
+
name: "Scroll velocity blur",
|
|
21352
|
+
description: "A picture smeared with motion blur and a colour split as fast as the page scrolls, sharp again when it stops.",
|
|
21353
|
+
category: "scroll",
|
|
21354
|
+
video: "filter",
|
|
21355
|
+
tags: ["interactive", "scroll", "motion blur", "speed", "image", "chromatic"],
|
|
21356
|
+
source: glsl(
|
|
21357
|
+
`// Scroll velocity blur: samples along the scroll direction, as many viewport heights as it is moving per second.
|
|
21358
|
+
uniform sampler2D uImage; // @control filter=mipmap wrap=clamp default=${SAMPLE_IMAGES.city} label="Image"
|
|
21359
|
+
uniform float uAmount; // @control min=0 max=0.3 default=0.08 label="Blur per speed"
|
|
21360
|
+
uniform float uMax; // @control min=0.01 max=0.4 default=0.15 label="Most blur"
|
|
21361
|
+
uniform float uSplit; // @control min=0 max=1 default=0.4 label="Colour split"
|
|
21362
|
+
uniform float uStretch; // @control min=0 max=0.2 default=0.04 label="Stretch"
|
|
21363
|
+
${BACKDROP_CONTROLS}
|
|
21364
|
+
${DEMO2}`,
|
|
21365
|
+
PICTURE,
|
|
21366
|
+
SCROLL,
|
|
21367
|
+
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
21368
|
+
vec2 uv = fragCoord / iResolution.xy;
|
|
21369
|
+
vec4 s = scroll();
|
|
21370
|
+
float v = s.z;
|
|
21371
|
+
float len = clamp(abs(v) * uAmount, 0.0, uMax) * sign(v);
|
|
21372
|
+
// A slight stretch along the scroll while moving.
|
|
21373
|
+
uv.y = (uv.y - 0.5) / (1.0 + uStretch * abs(len) / max(uMax, 1e-3)) + 0.5;
|
|
21374
|
+
vec4 acc = vec4(0.0);
|
|
21375
|
+
vec3 split = vec3(0.0);
|
|
21376
|
+
for (int i = 0; i < 16; i++) {
|
|
21377
|
+
float t = (float(i) + 0.5) / 16.0 - 0.5;
|
|
21378
|
+
float w = 1.0 - abs(t) * 1.2;
|
|
21379
|
+
vec4 c = pictureRGBA(uImage, uv + vec2(0.0, t * len));
|
|
21380
|
+
acc += c * w;
|
|
21381
|
+
// Red trails, blue leads: a colour split along the motion.
|
|
21382
|
+
split += vec3(c.r * max(-t, 0.0), 0.0, c.b * max(t, 0.0)) * w;
|
|
21383
|
+
}
|
|
21384
|
+
acc /= 6.4;
|
|
21385
|
+
split /= 6.4;
|
|
21386
|
+
vec3 col = acc.rgb + (split * 2.0 - vec3(acc.r, 0.0, acc.b) * 0.5) * uSplit * clamp(abs(len) * 10.0, 0.0, 1.0);
|
|
21387
|
+
fragColor = vec4(clamp(col, 0.0, 1.0), clamp(acc.a, 0.0, 1.0));
|
|
21388
|
+
}`
|
|
21389
|
+
),
|
|
21390
|
+
presets: [
|
|
21391
|
+
{ name: "Subtle", values: { uAmount: 0.03, uSplit: 0.1, uStretch: 0 } },
|
|
21392
|
+
{ name: "Warp speed", values: { uAmount: 0.2, uMax: 0.35, uSplit: 1, uStretch: 0.15 } }
|
|
21393
|
+
]
|
|
21394
|
+
};
|
|
21395
|
+
var scrollZoom = {
|
|
21396
|
+
kind: "shader",
|
|
21397
|
+
id: "scroll-zoom",
|
|
21398
|
+
name: "Scroll zoom",
|
|
21399
|
+
description: "A picture zooming in (or out) and drifting as it passes through the viewport, with an optional turn and vignette.",
|
|
21400
|
+
category: "scroll",
|
|
21401
|
+
video: "filter",
|
|
21402
|
+
tags: ["interactive", "scroll", "zoom", "ken burns", "image", "hero"],
|
|
21403
|
+
source: glsl(
|
|
21404
|
+
`// Scroll zoom: the picture's scale, pan and turn follow its progress through the viewport.
|
|
21405
|
+
uniform sampler2D uImage; // @control filter=mipmap wrap=clamp default=${SAMPLE_IMAGES.landscape} label="Image"
|
|
21406
|
+
uniform float uFrom; // @control min=0.5 max=3 default=1 label="Zoom entering"
|
|
21407
|
+
uniform float uTo; // @control min=0.5 max=3 default=1.6 label="Zoom leaving"
|
|
21408
|
+
uniform vec2 uFocus; // @control min=0 max=1 default=0.62,0.5 label="Zoom towards"
|
|
21409
|
+
uniform float uPan; // @control min=-0.3 max=0.3 default=0.06 label="Vertical drift"
|
|
21410
|
+
uniform float uTurn; // @control min=-30 max=30 default=0 label="Turn (degrees)"
|
|
21411
|
+
uniform float uVignette; // @control min=0 max=1 default=0.3 label="Vignette"
|
|
21412
|
+
${BACKDROP_CONTROLS}
|
|
21413
|
+
${DEMO2}`,
|
|
21414
|
+
PICTURE,
|
|
21415
|
+
SCROLL,
|
|
21416
|
+
`void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
21417
|
+
vec2 uv = fragCoord / iResolution.xy;
|
|
21418
|
+
vec4 s = scroll();
|
|
21419
|
+
float k = s.x * s.x * (3.0 - 2.0 * s.x);
|
|
21420
|
+
float zoom = mix(uFrom, uTo, k);
|
|
21421
|
+
float a = radians(uTurn) * (k - 0.5);
|
|
21422
|
+
vec2 aspect = vec2(iResolution.x / iResolution.y, 1.0);
|
|
21423
|
+
vec2 p = (uv - uFocus) * aspect;
|
|
21424
|
+
p = mat2(cos(a), sin(a), -sin(a), cos(a)) * p / zoom;
|
|
21425
|
+
vec2 at = p / aspect + uFocus + vec2(0.0, uPan * (k - 0.5));
|
|
21426
|
+
vec4 pic = pictureRGBA(uImage, at);
|
|
21427
|
+
float vig = 1.0 - uVignette * smoothstep(0.35, 0.95, length((uv - 0.5) * aspect) / length(aspect * 0.5));
|
|
21428
|
+
fragColor = vec4(pic.rgb * vig, pic.a);
|
|
21429
|
+
}`
|
|
21430
|
+
),
|
|
21431
|
+
presets: [
|
|
21432
|
+
{ name: "Zoom out", values: { uFrom: 1.8, uTo: 1 } },
|
|
21433
|
+
{ name: "Dramatic turn", values: { uTo: 2.2, uTurn: 18, uVignette: 0.6, uImage: SAMPLE_IMAGES.city } }
|
|
21434
|
+
]
|
|
21435
|
+
};
|
|
21436
|
+
var scrollShaders = [scrollReveal, parallaxLayers, scrollProgressGlow, scrollVelocityBlur, scrollZoom];
|
|
21437
|
+
|
|
21438
|
+
// src/catalog/scroll.ts
|
|
21439
|
+
var n10 = (name) => `{{${name}}}`;
|
|
21440
|
+
var SANS7 = `system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif`;
|
|
21441
|
+
var scrollDraw = {
|
|
21442
|
+
kind: "vector",
|
|
21443
|
+
id: "scroll-draw",
|
|
21444
|
+
name: "Scroll draw",
|
|
21445
|
+
description: "A line drawing itself along its path as the page scrolls, a dot riding its tip, and undrawing as you scroll back.",
|
|
21446
|
+
category: "scroll",
|
|
21447
|
+
trigger: "scroll",
|
|
21448
|
+
tags: ["interactive", "scroll", "line", "path", "draw", "route", "storytelling"],
|
|
21449
|
+
motion: {
|
|
21450
|
+
svg: `<svg viewBox="0 0 400 200" fill="none"><!-- {{accent: color default=#6366F1 label='Line'}} {{track: color default=#CBD5E1 label='Track'}} {{width: float min=1 max=16 default=6 step=1 label='Width'}} --><path d="{{path: text default='M24 160 C84 20 150 24 196 100 S318 186 376 44' label='Path'}}" stroke="{{track}}" stroke-width="2" stroke-dasharray="2 8" stroke-linecap="round"/><path id="scrollDrawLine" d="{{path}}" stroke="{{accent}}" stroke-width="{{width}}" stroke-linecap="round" stroke-linejoin="round" pathLength="1" stroke-dasharray="1 1" stroke-dashoffset="1"/><circle id="scrollDrawDot" cx="0" cy="0" r="{{dot}}" fill="{{accent}}" stroke="#FFFFFF" stroke-width="3"/></svg>`,
|
|
21451
|
+
animate: [
|
|
21452
|
+
{ id: "scrollDrawLine", draw: true, ms: 1e3, ease: "linear" },
|
|
21453
|
+
{ id: "scrollDrawDot", along: "scrollDrawLine", ms: 1e3, ease: "linear" }
|
|
21454
|
+
]
|
|
21455
|
+
},
|
|
21456
|
+
derived: (v) => ({ dot: Math.round((Number(v.width) * 0.9 + 4) * 10) / 10 }),
|
|
21457
|
+
presets: [
|
|
21458
|
+
{ name: "Signature", values: { path: "M30 140 C60 60 90 60 100 120 C110 170 140 70 170 90 C200 110 190 150 220 130 C250 110 270 80 300 110 C320 130 350 120 370 90", width: 4, accent: [0.07, 0.09, 0.15] } },
|
|
21459
|
+
{ name: "Bold", values: { width: 12, accent: [0.93, 0.27, 0.27] } }
|
|
21460
|
+
]
|
|
21461
|
+
};
|
|
21462
|
+
var scrollCounter = {
|
|
21463
|
+
kind: "vector",
|
|
21464
|
+
id: "scroll-counter",
|
|
21465
|
+
name: "Scroll counter",
|
|
21466
|
+
description: "A big number counting up as it scrolls into view, with its label and a line growing under it.",
|
|
21467
|
+
category: "scroll",
|
|
21468
|
+
trigger: "scroll",
|
|
21469
|
+
tags: ["interactive", "scroll", "counter", "number", "stat", "kpi", "landing page"],
|
|
21470
|
+
motion: {
|
|
21471
|
+
svg: `<svg viewBox="0 0 400 200" font-family="${SANS7}"><!-- {{accent: color default=#0EA5E9 label='Accent'}} {{ink: color default=#0F172A label='Number'}} {{muted: color default=#64748B label='Label colour'}} {{value: float min=0 max=1000000 default=2400 step=1 label='Number'}} {{decimals: int min=0 max=3 default=0 label='Decimals'}} --><text id="scrollCounterValue" x="200" y="112" text-anchor="middle" font-size="76" font-weight="800" fill="{{ink}}" style="font-variant-numeric: tabular-nums">0</text><rect id="scrollCounterLine" x="140" y="132" width="0" height="6" rx="3" fill="{{accent}}"/><text x="200" y="170" text-anchor="middle" font-size="20" font-weight="500" fill="{{muted}}">{{label: text default='happy customers' label='Label'}}</text></svg>`,
|
|
21472
|
+
animate: [
|
|
21473
|
+
{ id: "scrollCounterValue", count: [0, n10("value")], format: "{{prefix: text default='' label='Before the number'}}{n}{{suffix: text default='+' label='After the number'}}", decimals: n10("decimals"), ms: 1e3, ease: "out" },
|
|
21474
|
+
{ id: "scrollCounterLine", attrs: { width: [0, 120] }, ms: 1e3, ease: "out" }
|
|
21475
|
+
]
|
|
21476
|
+
},
|
|
21477
|
+
presets: [
|
|
21478
|
+
{ name: "Revenue", values: { value: 12.5, decimals: 1, prefix: "$", suffix: "M", label: "raised so far" } },
|
|
21479
|
+
{ name: "Percent", values: { value: 98, suffix: "%", label: "uptime", accent: [0.13, 0.77, 0.37] } }
|
|
21480
|
+
]
|
|
21481
|
+
};
|
|
21482
|
+
var STEP_X = [60, 160, 260, 360];
|
|
21483
|
+
var scrollSteps = {
|
|
21484
|
+
kind: "vector",
|
|
21485
|
+
id: "scroll-steps",
|
|
21486
|
+
name: "Scroll steps",
|
|
21487
|
+
description: "Four step markers joined by a line that fills as you scroll, each marker lighting up as the line reaches it.",
|
|
21488
|
+
category: "scroll",
|
|
21489
|
+
trigger: "scroll",
|
|
21490
|
+
tags: ["interactive", "scroll", "steps", "progress", "timeline", "onboarding", "ui"],
|
|
21491
|
+
motion: {
|
|
21492
|
+
svg: `<svg viewBox="0 0 420 160" font-family="${SANS7}"><!-- {{accent: color default=#8B5CF6 label='Accent'}} {{track: color default=#E2E8F0 label='Track'}} {{text: color default=#334155 label='Text'}} --><line x1="60" y1="64" x2="360" y2="64" stroke="{{track}}" stroke-width="6" stroke-linecap="round"/><line id="scrollStepsLine" x1="60" y1="64" x2="360" y2="64" stroke="{{accent}}" stroke-width="6" stroke-linecap="round" pathLength="1" stroke-dasharray="1 1" stroke-dashoffset="1"/>${STEP_X.map(
|
|
21493
|
+
(x, i) => `<circle cx="${x}" cy="64" r="20" fill="#FFFFFF" stroke="{{track}}" stroke-width="4"/><circle id="scrollStepsDot${i}" cx="${x}" cy="64" r="0" fill="{{accent}}"/><text id="scrollStepsNum${i}" x="${x}" y="71" text-anchor="middle" font-size="19" font-weight="700" fill="#94A3B8">${i + 1}</text><text x="${x}" y="118" text-anchor="middle" font-size="15" font-weight="600" fill="{{text}}">{{step${i + 1}: text default='${["Sign up", "Connect", "Customise", "Launch"][i]}' label='Step ${i + 1}'}}</text>`
|
|
21494
|
+
).join("")}</svg>`,
|
|
21495
|
+
animate: [
|
|
21496
|
+
{ id: "scrollStepsLine", draw: true, ms: 1e3, ease: "linear" },
|
|
21497
|
+
...STEP_X.flatMap((_, i) => [
|
|
21498
|
+
{ id: `scrollStepsDot${i}`, attrs: { r: [0, 22] }, delay: Math.round(i * 1e3 / 3) - (i ? 60 : 0), ms: 160, ease: "out" },
|
|
21499
|
+
{ id: `scrollStepsNum${i}`, attrs: { fill: ["#94A3B8", "#FFFFFF"] }, delay: Math.round(i * 1e3 / 3) - (i ? 60 : 0), ms: 160, ease: "out" }
|
|
21500
|
+
])
|
|
21501
|
+
]
|
|
21502
|
+
},
|
|
21503
|
+
presets: [
|
|
21504
|
+
{ name: "Checkout", values: { step1: "Cart", step2: "Details", step3: "Payment", step4: "Done", accent: [0.06, 0.73, 0.51] } }
|
|
21505
|
+
]
|
|
21506
|
+
};
|
|
21507
|
+
var scrollEffects = [scrollDraw, scrollCounter, scrollSteps];
|
|
21508
|
+
|
|
20627
21509
|
// src/catalog/types.ts
|
|
20628
21510
|
var EFFECT_CATEGORIES = [
|
|
20629
21511
|
"gradient",
|
|
@@ -20654,6 +21536,10 @@ var EFFECT_CATEGORIES = [
|
|
|
20654
21536
|
"glow",
|
|
20655
21537
|
"shadow",
|
|
20656
21538
|
"distortion",
|
|
21539
|
+
"audio",
|
|
21540
|
+
"scroll",
|
|
21541
|
+
"broadcast",
|
|
21542
|
+
"annotation",
|
|
20657
21543
|
"transition",
|
|
20658
21544
|
"loader",
|
|
20659
21545
|
"data",
|
|
@@ -20771,7 +21657,7 @@ function numberControl(name, value, min, max, label, step) {
|
|
|
20771
21657
|
}
|
|
20772
21658
|
|
|
20773
21659
|
// src/catalog/text.ts
|
|
20774
|
-
var r110 = (
|
|
21660
|
+
var r110 = (n13) => Math.round(n13 * 10) / 10;
|
|
20775
21661
|
function fit5(text, width, max, ems = textWidthEm(text)) {
|
|
20776
21662
|
const e = Math.max(0.6, ems);
|
|
20777
21663
|
const size = Math.min(max, width / e);
|
|
@@ -20788,11 +21674,11 @@ var mixHex3 = (a, b, t) => {
|
|
|
20788
21674
|
return rgbToHex([0, 1, 2].map((i) => x[i] + (y[i] - x[i]) * t));
|
|
20789
21675
|
};
|
|
20790
21676
|
var holds = (id, attr, keys2) => keys2.map(([delay, value]) => ({ id, attrs: { [attr]: [value, value] }, delay, ms: 0 }));
|
|
20791
|
-
var
|
|
21677
|
+
var n11 = (name) => `{{${name}}}`;
|
|
20792
21678
|
var MONO = `ui-monospace, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace`;
|
|
20793
21679
|
var MONO_EM = 0.6;
|
|
20794
21680
|
var HEAVY2 = `'Arial Black', 'Helvetica Neue', Impact, system-ui, sans-serif`;
|
|
20795
|
-
var
|
|
21681
|
+
var SANS8 = `system-ui, 'Helvetica Neue', Arial, sans-serif`;
|
|
20796
21682
|
var SCRIPT = `'Pacifico', 'Brush Script MT', 'Segoe Script', 'Snell Roundhand', cursive`;
|
|
20797
21683
|
var CAP = 0.72;
|
|
20798
21684
|
var typeface = (font, weight) => `{{font: text default="${font}" label='Font'}} {{weight: int min=100 max=900 default=${weight} step=100 label='Weight'}} {{inline: bool default=false label='Inline'}}`;
|
|
@@ -20836,9 +21722,9 @@ var typewriter = {
|
|
|
20836
21722
|
motion: {
|
|
20837
21723
|
svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='Hello, world.' label='Text'}} {{paper: color default=#111827 label='Background'}} {{ink: color default=#F9FAFB label='Text colour'}} {{pace: float min=30 max=300 default=90 step=5 label='Ms per letter'}} ${typeface(MONO, 400)} --><rect width="400" height="200" fill="{{paper}}" opacity="{{backdrop}}"/><text id="typed" x="{{left}}" y="{{base}}" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" fill="{{ink}}" xml:space="preserve">{{text}}</text><text id="caret" x="{{end}}" y="{{base}}" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" fill="{{ink}}" opacity="0">\u258C</text></svg>`,
|
|
20838
21724
|
animate: [
|
|
20839
|
-
{ id: "typed", write: "{{text}}", caret: "\u258C", ms:
|
|
21725
|
+
{ id: "typed", write: "{{text}}", caret: "\u258C", ms: n11("typing"), ease: "linear" },
|
|
20840
21726
|
// Hidden while the writer shows its own caret, then blinking where it stopped.
|
|
20841
|
-
...holds("caret", "opacity", [[0, 0], ...Array.from({ length: BLINKS }, (_, k) => [
|
|
21727
|
+
...holds("caret", "opacity", [[0, 0], ...Array.from({ length: BLINKS }, (_, k) => [n11(`blink${k}`), k % 2 ? 0 : 1])])
|
|
20842
21728
|
]
|
|
20843
21729
|
}
|
|
20844
21730
|
};
|
|
@@ -20865,8 +21751,8 @@ var scrambleText = {
|
|
|
20865
21751
|
svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='ACCESS GRANTED' label='Text'}} {{glyphs: text default='ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#%&@$*+' label='Scramble glyphs'}} {{background: color default=#05070D label='Background'}} {{ink: color default=#E2E8F0 label='Text colour'}} {{accent: color default=#22D3EE label='Accent'}} {{pace: float min=20 max=250 default=70 step=5 label='Ms per letter'}} {{glow: float min=0 max=6 default=2 step=0.5 label='Glow'}} ${typeface(MONO, 700)} --><defs><filter id="glow" x="-20%" y="-50%" width="140%" height="200%"><feGaussianBlur stdDeviation="{{glowWide}}" result="blur"/><feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge></filter></defs><rect width="400" height="200" fill="{{background}}" opacity="{{backdrop}}"/><text id="word" x="200" y="{{base}}" text-anchor="middle" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" fill="{{ink}}" filter="url(#glow)" xml:space="preserve">{{text}}</text><rect id="bar" x="{{barX}}" y="{{barY}}" width="0" height="3" fill="{{accent}}"/></svg>`,
|
|
20866
21752
|
animate: [
|
|
20867
21753
|
// Pure noise for a moment (the scramble runs before the change starts), then it decodes.
|
|
20868
|
-
{ id: "word", write: "{{text}}", scramble: "{{glyphs}}", delay: 500, ms:
|
|
20869
|
-
{ id: "bar", attrs: { width: [0,
|
|
21754
|
+
{ id: "word", write: "{{text}}", scramble: "{{glyphs}}", delay: 500, ms: n11("decode"), ease: "linear" },
|
|
21755
|
+
{ id: "bar", attrs: { width: [0, n11("barW")] }, delay: 500, ms: n11("decode"), ease: "linear" }
|
|
20870
21756
|
]
|
|
20871
21757
|
}
|
|
20872
21758
|
};
|
|
@@ -20923,13 +21809,13 @@ var kineticStagger = {
|
|
|
20923
21809
|
{
|
|
20924
21810
|
id: `l${i}`,
|
|
20925
21811
|
attrs: { transform: [`translate({{x${i}}} {{drop}}) rotate(-28) scale(0.3)`, `translate({{x${i}}} {{rise}}) rotate(5) scale(1.12)`], opacity: [0, 1] },
|
|
20926
|
-
delay:
|
|
21812
|
+
delay: n11(`d${i}`),
|
|
20927
21813
|
ms: 380,
|
|
20928
21814
|
ease: "out"
|
|
20929
21815
|
},
|
|
20930
|
-
{ id: `l${i}`, attrs: { transform: [`translate({{x${i}}} {{rise}}) rotate(5) scale(1.12)`, `translate({{x${i}}} {{base}}) rotate(0) scale(1)`] }, delay:
|
|
21816
|
+
{ id: `l${i}`, attrs: { transform: [`translate({{x${i}}} {{rise}}) rotate(5) scale(1.12)`, `translate({{x${i}}} {{base}}) rotate(0) scale(1)`] }, delay: n11(`e${i}`), ms: 260, ease: "in-out" }
|
|
20931
21817
|
]).flat(),
|
|
20932
|
-
{ id: "line", attrs: { width: [0,
|
|
21818
|
+
{ id: "line", attrs: { width: [0, n11("lineW")] }, delay: n11("lineAt"), ms: 500, ease: "in-out" }
|
|
20933
21819
|
]
|
|
20934
21820
|
}
|
|
20935
21821
|
};
|
|
@@ -21001,7 +21887,7 @@ var strokeDrawText = {
|
|
|
21001
21887
|
svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='Draw' label='Text'}} {{background: color default=#FFFBF5 label='Background'}} {{outline: color default=#1E293B label='Outline'}} {{fill: color default=#F97316 label='Fill'}} {{width: float min=0.5 max=6 default=2 step=0.5 label='Outline width'}} {{fillOpacity: float min=0 max=1 default=1 label='Fill opacity'}} ${typeface(HEAVY2, 900)} --><rect width="400" height="200" fill="{{background}}" opacity="{{backdrop}}"/><text id="word" x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" fill="{{fill}}" fill-opacity="0" stroke="{{outline}}" stroke-width="{{width}}" stroke-linejoin="round" stroke-dasharray="0 {{dash}}">{{text}}</text></svg>`,
|
|
21002
21888
|
animate: [
|
|
21003
21889
|
{ id: "word", attrs: { "stroke-dasharray": ["0 {{dash}}", "{{dash}} 0"] }, ms: 2e3, ease: "in-out" },
|
|
21004
|
-
{ id: "word", attrs: { "fill-opacity": [0,
|
|
21890
|
+
{ id: "word", attrs: { "fill-opacity": [0, n11("fillOpacity")] }, delay: 1500, ms: 700, ease: "out" }
|
|
21005
21891
|
]
|
|
21006
21892
|
}
|
|
21007
21893
|
};
|
|
@@ -21033,7 +21919,7 @@ var splitReveal = {
|
|
|
21033
21919
|
motion: (() => {
|
|
21034
21920
|
const word = `<text x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" fill="{{ink}}">{{text}}</text>`;
|
|
21035
21921
|
return {
|
|
21036
|
-
svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='Chapter One' label='Text'}} {{caption: text default='The beginning' label='Caption'}} {{background: color default=#0F172A label='Background'}} {{ink: color default=#F8FAFC label='Text colour'}} {{line: color default=#38BDF8 label='Line'}} ${typeface(
|
|
21922
|
+
svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='Chapter One' label='Text'}} {{caption: text default='The beginning' label='Caption'}} {{background: color default=#0F172A label='Background'}} {{ink: color default=#F8FAFC label='Text colour'}} {{line: color default=#38BDF8 label='Line'}} ${typeface(SANS8, 800)} --><defs><clipPath id="above"><rect x="-5000" y="-5000" width="10400" height="5100"/></clipPath><clipPath id="below"><rect x="-5000" y="100" width="10400" height="5100"/></clipPath></defs><rect width="400" height="200" fill="{{background}}" opacity="{{backdrop}}"/><g clip-path="url(#above)"><g id="top" transform="translate(0 {{shift}})">${word}</g></g><g clip-path="url(#below)"><g id="bottom" transform="translate(0 -{{shift}})">${word}</g></g><path id="line" d="M{{lineX1}} 100 H{{lineX2}}" stroke="{{line}}" stroke-width="2.5" stroke-linecap="round"/><text id="caption" x="200" y="{{captionY}}" text-anchor="middle" font-family="{{font}}" font-size="13" letter-spacing="3" fill="{{ink}}" opacity="0">{{caption}}</text></svg>`,
|
|
21037
21923
|
animate: [
|
|
21038
21924
|
{ id: "line", draw: true, ms: 600, ease: "in-out" },
|
|
21039
21925
|
{ id: "top", attrs: { transform: ["translate(0 {{shift}})", "translate(0 0)"] }, delay: 450, ms: 800, ease: "out" },
|
|
@@ -21076,7 +21962,7 @@ var highlighter = {
|
|
|
21076
21962
|
};
|
|
21077
21963
|
},
|
|
21078
21964
|
motion: {
|
|
21079
|
-
svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='Ship it faster' label='Phrase'}} {{background: color default=#FFFFFF label='Background'}} {{ink: color default=#111827 label='Text colour'}} {{marker: color default=#FDE047 label='Marker'}} ${typeface(
|
|
21965
|
+
svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='Ship it faster' label='Phrase'}} {{background: color default=#FFFFFF label='Background'}} {{ink: color default=#111827 label='Text colour'}} {{marker: color default=#FDE047 label='Marker'}} ${typeface(SANS8, 700)} --><rect width="400" height="200" fill="{{background}}" opacity="{{backdrop}}"/><g transform="rotate(-1.2 200 100)" opacity="0.9"><path id="swipe" d="{{swipe}}" fill="none" stroke="{{marker}}" stroke-width="{{thick}}"/><path id="ragged" d="{{ragged}}" fill="none" stroke="{{marker}}" stroke-width="{{thin}}" stroke-linecap="round"/></g><text id="phrase" x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" fill="{{ink}}" opacity="0">{{text}}</text></svg>`,
|
|
21080
21966
|
animate: [
|
|
21081
21967
|
{ id: "phrase", attrs: { opacity: [0, 1] }, ms: 300, ease: "out" },
|
|
21082
21968
|
{ id: "swipe", draw: true, delay: 350, ms: 650, ease: "in-out" },
|
|
@@ -21134,15 +22020,15 @@ var wordCycle = {
|
|
|
21134
22020
|
};
|
|
21135
22021
|
},
|
|
21136
22022
|
motion: {
|
|
21137
|
-
svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{prefix: text default='We build' label='Lead-in'}} {{word1: text default=apps label='Word 1'}} {{word2: text default=brands label='Word 2'}} {{word3: text default=websites label='Word 3'}} {{word4: text default=games label='Word 4'}} {{background: color default=#FFFFFF label='Background'}} {{ink: color default=#0F172A label='Text colour'}} {{accent: color default=#6366F1 label='Accent'}} ${typeface(
|
|
22023
|
+
svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{prefix: text default='We build' label='Lead-in'}} {{word1: text default=apps label='Word 1'}} {{word2: text default=brands label='Word 2'}} {{word3: text default=websites label='Word 3'}} {{word4: text default=games label='Word 4'}} {{background: color default=#FFFFFF label='Background'}} {{ink: color default=#0F172A label='Text colour'}} {{accent: color default=#6366F1 label='Accent'}} ${typeface(SANS8, 700)} --><defs><clipPath id="window"><rect x="{{clipX}}" y="{{clipY}}" width="{{clipW}}" height="{{clipH}}"/></clipPath></defs><rect width="400" height="200" fill="{{background}}" opacity="{{backdrop}}"/><text x="{{px}}" y="{{base}}" textLength="{{plen}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" fill="{{ink}}">{{prefix}}</text><g clip-path="url(#window)"><g id="reel" transform="translate(0 -0)">${Array.from(
|
|
21138
22024
|
{ length: CYCLE_WORDS + 1 },
|
|
21139
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>`
|
|
21140
22026
|
).join("")}</g></g><rect id="under" x="{{wx}}" y="{{underY}}" width="{{len0}}" height="4" rx="2" fill="{{accent}}"/></svg>`,
|
|
21141
22027
|
animate: Array.from({ length: CYCLE_WORDS }, (_, i) => {
|
|
21142
|
-
const
|
|
22028
|
+
const at4 = { delay: (i + 1) * SHOW_MS + i * ROLL_MS, ms: ROLL_MS, ease: "in-out" };
|
|
21143
22029
|
return [
|
|
21144
|
-
{ id: "reel", attrs: { transform: [`translate(0 -{{o${i}}})`, `translate(0 -{{o${i + 1}}})`] }, ...
|
|
21145
|
-
{ id: "under", attrs: { width: [
|
|
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 }
|
|
21146
22032
|
];
|
|
21147
22033
|
}).flat()
|
|
21148
22034
|
}
|
|
@@ -21267,7 +22153,7 @@ var extrudedText = {
|
|
|
21267
22153
|
const { size, length, left } = fit5(String(v.text), room(v, 320), 96);
|
|
21268
22154
|
const d = 0.36 * Number(v.depth);
|
|
21269
22155
|
const step = size * 0.03 * Number(v.depth);
|
|
21270
|
-
const
|
|
22156
|
+
const at4 = (deg, i) => {
|
|
21271
22157
|
const a = deg * Math.PI / 180;
|
|
21272
22158
|
return `translate(${r110(Math.cos(a) * step * i)} ${r110(Math.sin(a) * step * i)})`;
|
|
21273
22159
|
};
|
|
@@ -21281,8 +22167,8 @@ var extrudedText = {
|
|
|
21281
22167
|
const i = LAYERS - k;
|
|
21282
22168
|
return [
|
|
21283
22169
|
[`fill${i}`, mixHex3(v.side, v.shade, i / LAYERS)],
|
|
21284
|
-
[`a${i}`,
|
|
21285
|
-
[`b${i}`,
|
|
22170
|
+
[`a${i}`, at4(EXTRUDE_FROM, i)],
|
|
22171
|
+
[`b${i}`, at4(EXTRUDE_TO, i)]
|
|
21286
22172
|
];
|
|
21287
22173
|
}).flat()
|
|
21288
22174
|
)
|
|
@@ -21315,6 +22201,1216 @@ var textEffects = [
|
|
|
21315
22201
|
extrudedText
|
|
21316
22202
|
];
|
|
21317
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
|
+
|
|
21318
23414
|
// src/catalog/vector.ts
|
|
21319
23415
|
var spinner = {
|
|
21320
23416
|
kind: "vector",
|
|
@@ -21760,7 +23856,7 @@ var dripText = {
|
|
|
21760
23856
|
const left = 200 - width / 2;
|
|
21761
23857
|
const runs = Math.min(DRIP_RUNS, Math.max(1, Math.round(width / DRIP_RUN)));
|
|
21762
23858
|
const run = width / runs;
|
|
21763
|
-
const
|
|
23859
|
+
const r112 = (n13) => Math.round(n13 * 10) / 10;
|
|
21764
23860
|
const edge = Number(v.stroke) / 2 / DRIP_SIZE;
|
|
21765
23861
|
const tip = (DRIP_TOP + Math.max(...DRIPS2.map((d) => d.length)) * Number(v.drip) + 13 * 0.72 - DRIP_BASELINE) / DRIP_SIZE;
|
|
21766
23862
|
const overhang = [edge, edge, Math.max(0.05, tip) + edge, edge].map((e) => Math.round(e * 100) / 100).join(" ");
|
|
@@ -21774,15 +23870,15 @@ var dripText = {
|
|
|
21774
23870
|
overhang,
|
|
21775
23871
|
...Object.fromEntries(
|
|
21776
23872
|
DRIP_SLOTS.flatMap((d) => {
|
|
21777
|
-
const x =
|
|
21778
|
-
const length =
|
|
23873
|
+
const x = r112(left + (d.run + d.at) * run);
|
|
23874
|
+
const length = r112(d.length * Number(v.drip));
|
|
21779
23875
|
return [
|
|
21780
23876
|
[`show${d.i}`, d.run < runs ? "inline" : "none"],
|
|
21781
23877
|
[`x${d.i}`, x],
|
|
21782
|
-
[`left${d.i}`,
|
|
21783
|
-
[`shine${d.i}`,
|
|
23878
|
+
[`left${d.i}`, r112(x - d.width / 2)],
|
|
23879
|
+
[`shine${d.i}`, r112(x - d.width * 0.25)],
|
|
21784
23880
|
[`len${d.i}`, length],
|
|
21785
|
-
[`tip${d.i}`,
|
|
23881
|
+
[`tip${d.i}`, r112(DRIP_TOP + length)]
|
|
21786
23882
|
];
|
|
21787
23883
|
})
|
|
21788
23884
|
)
|
|
@@ -21794,23 +23890,23 @@ var dripText = {
|
|
|
21794
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>`
|
|
21795
23891
|
).join("");
|
|
21796
23892
|
const outline4 = `fill="{{outline}}" stroke="{{outline}}" stroke-width="{{stroke}}"`;
|
|
21797
|
-
const
|
|
23893
|
+
const pop2 = (from, to) => [`translate(200 100) scale(${from}) translate(-200 -100)`, `translate(200 100) scale(${to}) translate(-200 -100)`];
|
|
21798
23894
|
return {
|
|
21799
|
-
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(
|
|
21800
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"/>`
|
|
21801
23897
|
).join("")}</g></g></svg>`,
|
|
21802
23898
|
animate: [
|
|
21803
|
-
{ id: "word", attrs: { transform:
|
|
21804
|
-
{ 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" },
|
|
21805
23901
|
...DRIP_SLOTS.flatMap((d) => {
|
|
21806
|
-
const
|
|
23902
|
+
const at4 = { delay: 400 + d.delay, ms: d.ms, ease: "in-out" };
|
|
21807
23903
|
const tip = `{{tip${d.i}}}`;
|
|
21808
23904
|
return [
|
|
21809
23905
|
...["o", "f"].flatMap((layer) => [
|
|
21810
|
-
{ id: `${layer}r${d.i}`, attrs: { height: [0, `{{len${d.i}}}`] }, ...
|
|
21811
|
-
{ 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 }
|
|
21812
23908
|
]),
|
|
21813
|
-
{ 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 }
|
|
21814
23910
|
];
|
|
21815
23911
|
})
|
|
21816
23912
|
]
|
|
@@ -21991,9 +24087,11 @@ var SHADER_EFFECTS = [
|
|
|
21991
24087
|
...glowShaders,
|
|
21992
24088
|
...shadowShaders,
|
|
21993
24089
|
...distortionShaders,
|
|
24090
|
+
...audioShaders,
|
|
24091
|
+
...scrollShaders,
|
|
21994
24092
|
...transitions
|
|
21995
24093
|
];
|
|
21996
|
-
var EFFECTS = [...SHADER_EFFECTS, ...vectorEffects, ...textEffects, ...morphEffects, ...interactionEffects, ...pyroEffects, ...timewarpEffects, ...meltEffects, ...electricEffects, ...pixelEffects, ...splatterEffects, ...bubbleEffects, ...stickerEffects, ...trailEffects, ...impactEffects, ...comicEffects, ...glowEffects, ...shadowEffects, ...lightingEffects].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(
|
|
21997
24095
|
(a, b) => EFFECT_CATEGORIES.indexOf(a.category) - EFFECT_CATEGORIES.indexOf(b.category)
|
|
21998
24096
|
);
|
|
21999
24097
|
var byId = new Map(EFFECTS.map((e) => [e.id, e]));
|
|
@@ -22069,4 +24167,4 @@ export {
|
|
|
22069
24167
|
effectControls,
|
|
22070
24168
|
effectDefaults
|
|
22071
24169
|
};
|
|
22072
|
-
//# sourceMappingURL=chunk-
|
|
24170
|
+
//# sourceMappingURL=chunk-UIODYJ6B.js.map
|