@pixerate/flair 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +38 -7
  3. package/dist/catalog/index.d.ts +8 -4
  4. package/dist/catalog/index.js +8 -2
  5. package/dist/{chunk-YLJWC4N2.js → chunk-HA6FIMH2.js} +108 -5
  6. package/dist/chunk-HA6FIMH2.js.map +1 -0
  7. package/dist/{chunk-UNLCGDCB.js → chunk-HP7CYHS5.js} +46 -11
  8. package/dist/chunk-HP7CYHS5.js.map +1 -0
  9. package/dist/{chunk-DVVQRZJ5.js → chunk-MILF5MRS.js} +11 -2
  10. package/dist/chunk-MILF5MRS.js.map +1 -0
  11. package/dist/{chunk-4RGHZWQC.js → chunk-RM3I6WZH.js} +2 -2
  12. package/dist/{chunk-4RGHZWQC.js.map → chunk-RM3I6WZH.js.map} +1 -1
  13. package/dist/{chunk-KWNFZ5NC.js → chunk-UI5X4D63.js} +292 -185
  14. package/dist/chunk-UI5X4D63.js.map +1 -0
  15. package/dist/{chunk-FIJOMGPG.js → chunk-UP6QQVRS.js} +2 -2
  16. package/dist/{chunk-WC3VADZC.js → chunk-VYOMXE6I.js} +46 -4
  17. package/dist/chunk-VYOMXE6I.js.map +1 -0
  18. package/dist/cli.js +4 -4
  19. package/dist/{controls-Syr6drzO.d.ts → controls-Ec6P0Vpy.d.ts} +9 -2
  20. package/dist/editor/index.d.ts +3 -3
  21. package/dist/editor/index.js +20 -6
  22. package/dist/editor/index.js.map +1 -1
  23. package/dist/index.d.ts +19 -7
  24. package/dist/index.js +11 -5
  25. package/dist/index.js.map +1 -1
  26. package/dist/node.d.ts +1 -1
  27. package/dist/node.js +2 -2
  28. package/dist/react/index.d.ts +14 -3
  29. package/dist/react/index.js +18 -9
  30. package/dist/react/index.js.map +1 -1
  31. package/dist/recorder-HKFFHQES.js +10 -0
  32. package/dist/{renderer-SNYZu__R.d.ts → renderer-Cn4_d5Qs.d.ts} +64 -8
  33. package/dist/svelte/index.d.ts +16 -5
  34. package/dist/svelte/index.js +16 -11
  35. package/dist/svelte/index.js.map +1 -1
  36. package/dist/{types-9J6NWaN3.d.ts → types-kAFjaq2N.d.ts} +28 -3
  37. package/dist/vector/index.d.ts +27 -1
  38. package/dist/vector/index.js +4 -2
  39. package/package.json +1 -1
  40. package/dist/chunk-DVVQRZJ5.js.map +0 -1
  41. package/dist/chunk-KWNFZ5NC.js.map +0 -1
  42. package/dist/chunk-UNLCGDCB.js.map +0 -1
  43. package/dist/chunk-WC3VADZC.js.map +0 -1
  44. package/dist/chunk-YLJWC4N2.js.map +0 -1
  45. package/dist/recorder-KEX3U35D.js +0 -10
  46. /package/dist/{chunk-FIJOMGPG.js.map → chunk-UP6QQVRS.js.map} +0 -0
  47. /package/dist/{recorder-KEX3U35D.js.map → recorder-HKFFHQES.js.map} +0 -0
@@ -12,7 +12,7 @@ import {
12
12
  hexToRgb,
13
13
  parseControls,
14
14
  rgbToHex
15
- } from "./chunk-WC3VADZC.js";
15
+ } from "./chunk-VYOMXE6I.js";
16
16
 
17
17
  // src/catalog/glsl.ts
18
18
  var HASH = `
@@ -390,6 +390,7 @@ var imageRipple = {
390
390
  name: "Ripple",
391
391
  description: "An image rippling as if seen through water, from the pointer or the centre.",
392
392
  category: "image",
393
+ video: "filter",
393
394
  tags: ["image", "water", "interactive", "distortion"],
394
395
  source: glsl(
395
396
  `// Ripple: an image rippling as if under water.
@@ -419,6 +420,7 @@ var imageGlitch = {
419
420
  name: "Glitch",
420
421
  description: "Digital glitch on an image: RGB split, slice displacement and blocky corruption in bursts.",
421
422
  category: "image",
423
+ video: "filter",
422
424
  tags: ["image", "glitch", "cyber", "transition"],
423
425
  source: glsl(
424
426
  `// Glitch: RGB split, slice displacement and blocky corruption in bursts.
@@ -456,6 +458,7 @@ var imageDuotone = {
456
458
  name: "Duotone",
457
459
  description: "An image mapped onto two colours, with a slow light sweep.",
458
460
  category: "image",
461
+ video: "filter",
459
462
  tags: ["image", "brand", "editorial", "colour"],
460
463
  source: glsl(
461
464
  `// Duotone: an image mapped onto two colours, with a light sweep.
@@ -487,6 +490,7 @@ var imagePixelate = {
487
490
  name: "Pixel reveal",
488
491
  description: "An image resolving from big blocks to full detail, then back, in a loop.",
489
492
  category: "image",
493
+ video: "filter",
490
494
  tags: ["image", "reveal", "transition", "8-bit"],
491
495
  loop: 4,
492
496
  source: glsl(
@@ -511,6 +515,7 @@ var imageHalftone = {
511
515
  name: "Halftone",
512
516
  description: "An image printed as rotated halftone dots, breathing slightly.",
513
517
  category: "image",
518
+ video: "filter",
514
519
  tags: ["image", "print", "comic", "pop-art"],
515
520
  source: glsl(
516
521
  `// Halftone: an image as rotated print dots.
@@ -542,6 +547,7 @@ var crt = {
542
547
  name: "CRT screen",
543
548
  description: "An image on a curved CRT: scanlines, shadow mask, bloom and flicker.",
544
549
  category: "image",
550
+ video: "filter",
545
551
  tags: ["image", "retro", "screen", "arcade"],
546
552
  source: glsl(
547
553
  `// CRT screen: an image on a curved tube with scanlines and a shadow mask.
@@ -580,6 +586,7 @@ var filmGrain = {
580
586
  name: "Film look",
581
587
  description: "An image graded like film: grain, gate weave, halation and vignette.",
582
588
  category: "image",
589
+ video: "filter",
583
590
  tags: ["image", "cinematic", "grain", "vintage"],
584
591
  source: glsl(
585
592
  `// Film look: grain, gate weave, halation and vignette.
@@ -626,6 +633,7 @@ var transitions = TRANSITIONS.map(({ type, id, name, description }) => ({
626
633
  name,
627
634
  description: `${description} Loops back and forth; or turn Animate off and set Progress.`,
628
635
  category: "transition",
636
+ video: "transition",
629
637
  tags: ["transition", "image", "slideshow"],
630
638
  loop: 4,
631
639
  source: glsl(
@@ -1748,9 +1756,9 @@ var checkFlash = (at2, back) => [
1748
1756
  { id: "check", step: 1, attrs: { opacity: [1, 0] }, delay: back, ms: 160 },
1749
1757
  { id: "check", step: 1, draw: [0, 0], delay: back + 200, ms: 0 }
1750
1758
  ];
1751
- var buttonLabels = (a, b, room, max) => {
1759
+ var buttonLabels = (a, b, room2, max) => {
1752
1760
  const ems = [a, b].map((t) => Math.max(0.6, textWidthEm(String(t))));
1753
- const size = Math.min(max, room / Math.max(...ems));
1761
+ const size = Math.min(max, room2 / Math.max(...ems));
1754
1762
  return { size: r1(size), base: r1(50 + size * 0.35), labelLen: r1(ems[0] * size), doneLen: r1(ems[1] * size) };
1755
1763
  };
1756
1764
  var labelTexts = (x) => `<text id="label" x="${x}" y="{{base}}" textLength="{{labelLen}}" lengthAdjust="spacingAndGlyphs" transform="translate(0 0)" font-family="${SANS}" font-size="{{size}}" font-weight="600" fill="{{ink}}">{{label}}</text><text id="done" x="${x}" y="{{base}}" textLength="{{doneLen}}" lengthAdjust="spacingAndGlyphs" transform="translate(0 10)" opacity="0" font-family="${SANS}" font-size="{{size}}" font-weight="600" fill="{{ink}}">{{doneText}}</text>`;
@@ -2452,6 +2460,7 @@ var image8bit = {
2452
2460
  name: "8-bit",
2453
2461
  description: "An image on a chunky pixel grid, reduced to a classic console or computer palette with ordered Bayer dithering.",
2454
2462
  category: "retro",
2463
+ video: "filter",
2455
2464
  tags: ["pixel", "retro", "image", "8-bit", "dither", "palette", "game boy", "pico-8"],
2456
2465
  source: glsl(
2457
2466
  `// 8-bit: an image on a chunky pixel grid in a classic palette, Bayer-dithered.
@@ -2527,6 +2536,7 @@ var gameboyScreen = {
2527
2536
  name: "Game Boy screen",
2528
2537
  description: "An image on a handheld's green dot-matrix LCD: four shades, a visible pixel grid and smeary ghosting, inset in an olive bezel.",
2529
2538
  category: "retro",
2539
+ video: "filter",
2530
2540
  tags: ["pixel", "retro", "image", "game boy", "lcd", "handheld", "screen"],
2531
2541
  source: glsl(
2532
2542
  `// Game Boy screen: an image on a 4-shade green LCD with a pixel grid, ghosting and a bezel.
@@ -2969,6 +2979,7 @@ var rasterBars = {
2969
2979
  name: "Raster bars",
2970
2980
  description: "Demoscene copper bars bouncing on sine paths behind a sine-scrolling greeting, all in chunky pixels.",
2971
2981
  category: "retro",
2982
+ video: "none",
2972
2983
  tags: ["pixel", "retro", "demoscene", "amiga", "c64", "scroller", "text", "copper"],
2973
2984
  source: glsl(
2974
2985
  `// Raster bars: copper bars on sine paths behind a sine scroller.
@@ -3154,6 +3165,7 @@ var pixelDissolve = {
3154
3165
  name: "Pixel dissolve",
3155
3166
  description: "The picture pixelates up to big blocks, the blocks swap to the next image in a random order, then it resolves into the new picture. Loops back and forth; or turn Animate off and set Progress.",
3156
3167
  category: "transition",
3168
+ video: "transition",
3157
3169
  tags: ["transition", "pixel", "image", "retro", "8-bit", "slideshow"],
3158
3170
  loop: 4,
3159
3171
  source: glsl(
@@ -4215,6 +4227,7 @@ var hoverLiquid = {
4215
4227
  name: "Liquid hover",
4216
4228
  description: "An image that swells and ripples like liquid around the pointer while it hovers.",
4217
4229
  category: "interaction",
4230
+ video: "interactive",
4218
4231
  tags: ["interactive", "hover", "image", "liquid", "distortion"],
4219
4232
  source: glsl(
4220
4233
  `// Liquid hover: an image bulging and rippling around the pointer, as strongly as it is hovered.
@@ -4261,6 +4274,7 @@ var hoverReveal = {
4261
4274
  name: "X-ray reveal",
4262
4275
  description: "A second image showing through a soft lens around the pointer that grows as it hovers and pulses on click.",
4263
4276
  category: "interaction",
4277
+ video: "interactive",
4264
4278
  tags: ["interactive", "hover", "click", "image", "reveal", "x-ray"],
4265
4279
  source: glsl(
4266
4280
  `// X-ray reveal: a second image seen through a soft lens at the pointer.
@@ -4312,6 +4326,7 @@ var magnifier = {
4312
4326
  name: "Magnifier",
4313
4327
  description: "A round magnifying glass over an image at the pointer, with a bright rim and a fringe of colour at its edge.",
4314
4328
  category: "interaction",
4329
+ video: "interactive",
4315
4330
  tags: ["interactive", "hover", "image", "lens", "zoom"],
4316
4331
  source: glsl(
4317
4332
  `// Magnifier: a round lens over an image at the pointer, showing while hovered.
@@ -4366,6 +4381,7 @@ var holoCard = {
4366
4381
  name: "Holo card",
4367
4382
  description: "A holographic foil trading card whose rainbow sheen and glare shift as the pointer tilts it.",
4368
4383
  category: "interaction",
4384
+ video: "interactive",
4369
4385
  tags: ["interactive", "hover", "card", "holographic", "foil", "iridescent"],
4370
4386
  transparent: true,
4371
4387
  source: glsl(
@@ -4520,6 +4536,7 @@ var hoverSwap = {
4520
4536
  name: "Hover swap",
4521
4537
  description: "One image burning away into another from the pointer as it hovers, along a glowing ragged edge.",
4522
4538
  category: "interaction",
4539
+ video: "interactive",
4523
4540
  tags: ["interactive", "hover", "image", "dissolve", "transition"],
4524
4541
  source: glsl(
4525
4542
  `// Hover swap: image A dissolves into image B as hover goes 0 \u2192 1, spreading from the pointer.
@@ -5044,6 +5061,7 @@ var electricText = {
5044
5061
  name: "Electric text",
5045
5062
  description: "A word with electricity crawling over its outlines and arcs leaping between its letters, flickering on a dark background.",
5046
5063
  category: "electric",
5064
+ video: "none",
5047
5065
  tags: ["electric", "text", "title", "glow", "sci-fi"],
5048
5066
  source: glsl(
5049
5067
  `// Electric text: electricity crawling along the letters' outlines, arcs jumping between them.
@@ -5306,6 +5324,7 @@ var powerSurge = {
5306
5324
  name: "Power surge",
5307
5325
  description: "A picture jolted by a power surge: a flash, an arc tearing across, scanlines and colour split, then a brownout before it settles.",
5308
5326
  category: "electric",
5327
+ video: "filter",
5309
5328
  tags: ["electric", "image", "glitch", "surge", "flash"],
5310
5329
  source: glsl(
5311
5330
  `// Power surge: an image jolted by a surge, then settling, on a schedule.
@@ -5487,6 +5506,7 @@ var dripMelt = {
5487
5506
  name: "Drip melt",
5488
5507
  description: "An image melting from the top edge down: columns slump, thick glossy drips run down and pool in a puddle, then it all reforms.",
5489
5508
  category: "melt",
5509
+ video: "filter",
5490
5510
  tags: ["melt", "image", "drip", "liquid", "glossy", "loop"],
5491
5511
  loop: 8,
5492
5512
  source: glsl(
@@ -5595,6 +5615,7 @@ var heatHaze = {
5595
5615
  name: "Heat haze",
5596
5616
  description: "A mirage shimmer rising off a hot image: wavering refraction strongest near the ground, with a warm tint and a faint reflection.",
5597
5617
  category: "melt",
5618
+ video: "filter",
5598
5619
  tags: ["melt", "image", "heat", "mirage", "shimmer", "desert", "distortion"],
5599
5620
  source: glsl(
5600
5621
  `// Heat haze: rising, wavering refraction over a picture, strongest near the bottom.
@@ -5654,6 +5675,7 @@ var waxMelt = {
5654
5675
  name: "Wax melt",
5655
5676
  description: "A silhouette softening like warm wax: its edges round off, its top sags and it slumps into a glossy puddle, then sets again.",
5656
5677
  category: "melt",
5678
+ video: "none",
5657
5679
  tags: ["melt", "wax", "text", "silhouette", "glossy", "loop"],
5658
5680
  loop: 8,
5659
5681
  source: glsl(
@@ -5754,6 +5776,7 @@ var meltingText = {
5754
5776
  name: "Melting text",
5755
5777
  description: "Letters sagging and stretching downwards as long glossy drips run from their bottoms and droplets break off and fall.",
5756
5778
  category: "melt",
5779
+ video: "none",
5757
5780
  tags: ["melt", "text", "title", "drip", "liquid", "glossy", "loop"],
5758
5781
  loop: 8,
5759
5782
  transparent: true,
@@ -5871,6 +5894,7 @@ var liquify = {
5871
5894
  name: "Liquify",
5872
5895
  description: "An image pushed and smeared like wet paint by a moving brush (or the pointer), the smears slowly relaxing back.",
5873
5896
  category: "melt",
5897
+ video: "filter",
5874
5898
  tags: ["melt", "image", "liquify", "smear", "paint", "interactive", "hover"],
5875
5899
  source: glsl(
5876
5900
  `// Liquify: a brush pushes the picture around; its recent path leaves smears that relax over time.
@@ -6946,6 +6970,7 @@ var timeEcho = {
6946
6970
  name: "Time echo",
6947
6971
  description: "A swaying picture trailed by fading, tinted ghost echoes of where it just was.",
6948
6972
  category: "timewarp",
6973
+ video: "stills",
6949
6974
  tags: ["time", "image", "echo", "trail", "ghost"],
6950
6975
  loop: 8,
6951
6976
  source: glsl(
@@ -6993,6 +7018,7 @@ var chronoSplit = {
6993
7018
  name: "Chrono split",
6994
7019
  description: "Red, green and blue each lagging a little further behind in time, so movement leaves colour fringes.",
6995
7020
  category: "timewarp",
7021
+ video: "stills",
6996
7022
  tags: ["time", "image", "rgb", "chromatic", "echo"],
6997
7023
  loop: 8,
6998
7024
  source: glsl(
@@ -7026,6 +7052,7 @@ var timeSlice = {
7026
7052
  name: "Time slice",
7027
7053
  description: "Bands of a moving picture each showing a different moment, staggered like a time-displacement.",
7028
7054
  category: "timewarp",
7055
+ video: "stills",
7029
7056
  tags: ["time", "image", "slices", "stagger", "displacement"],
7030
7057
  loop: 8,
7031
7058
  source: glsl(
@@ -7090,6 +7117,7 @@ var rewind = {
7090
7117
  name: "VHS rewind",
7091
7118
  description: "A tape playing, then rewinding fast with tracking noise, jittering lines, colour bleed and a \u25C0\u25C0 display.",
7092
7119
  category: "timewarp",
7120
+ video: "stills",
7093
7121
  tags: ["time", "image", "vhs", "retro", "rewind", "tape"],
7094
7122
  loop: 6,
7095
7123
  source: glsl(
@@ -7196,6 +7224,7 @@ var bulletTime = {
7196
7224
  name: "Bullet time",
7197
7225
  description: "A shockwave sweeping out from the centre or a click, freezing the picture grey and zoomed behind it while the rest moves on.",
7198
7226
  category: "timewarp",
7227
+ video: "stills",
7199
7228
  tags: ["time", "image", "freeze", "shockwave", "click", "cinematic"],
7200
7229
  loop: 8,
7201
7230
  source: glsl(
@@ -7255,6 +7284,7 @@ var timeTunnel = {
7255
7284
  name: "Time tunnel",
7256
7285
  description: "A picture wrapped round a spiralling tunnel rushing at the viewer, with light streaks and a blazing core.",
7257
7286
  category: "timewarp",
7287
+ video: "filter",
7258
7288
  tags: ["time", "image", "tunnel", "vortex", "sci-fi", "warp"],
7259
7289
  loop: 8,
7260
7290
  source: glsl(
@@ -7313,6 +7343,7 @@ var speedRamp = {
7313
7343
  name: "Speed ramp",
7314
7344
  description: "A panning picture lurching from a crawl to a blur and back, motion-blurred in proportion to its speed.",
7315
7345
  category: "timewarp",
7346
+ video: "stills",
7316
7347
  tags: ["time", "image", "speed", "motion blur", "cinematic"],
7317
7348
  loop: 4,
7318
7349
  source: glsl(
@@ -7360,6 +7391,7 @@ var stutterFreeze = {
7360
7391
  name: "Stutter freeze",
7361
7392
  description: "A moving picture that catches in irregular bursts, holding or stuttering frames with a flash on each freeze.",
7362
7393
  category: "timewarp",
7394
+ video: "stills",
7363
7395
  tags: ["time", "image", "freeze", "stutter", "glitch"],
7364
7396
  source: glsl(
7365
7397
  `// Stutter freeze: some moments end in a hold (or a skipping stutter) before cutting back to live.
@@ -7445,6 +7477,7 @@ var featureMorph = {
7445
7477
  name: "Feature morph",
7446
7478
  description: "The classic face morph: two pictures warp so five matching points (eyes, mouth, chin, hairline) meet while they cross-fade.",
7447
7479
  category: "morph",
7480
+ video: "stills",
7448
7481
  tags: ["morph", "face", "warp", "image", "transition"],
7449
7482
  loop: 4,
7450
7483
  source: glsl(
@@ -7543,6 +7576,7 @@ var silhouetteMorph = {
7543
7576
  name: "Silhouette morph",
7544
7577
  description: "One silhouette or logo flowing into another as a gooey blob, its colour blending from the first shape's to the second's.",
7545
7578
  category: "morph",
7579
+ video: "stills",
7546
7580
  tags: ["morph", "shape", "logo", "goo", "blob", "text"],
7547
7581
  loop: 4,
7548
7582
  source: glsl(
@@ -7623,6 +7657,7 @@ var transitionLiquidMorph = {
7623
7657
  name: "Liquid morph",
7624
7658
  description: "Both images swept along a flowing, swirling current that peaks half way, dissolving one into the other like ink in water.",
7625
7659
  category: "transition",
7660
+ video: "transition",
7626
7661
  tags: ["transition", "morph", "liquid", "image", "flow"],
7627
7662
  loop: 4,
7628
7663
  source: glsl(
@@ -7664,6 +7699,7 @@ var transitionDisplacement = {
7664
7699
  name: "Displacement",
7665
7700
  description: "Each image's brightness pushes it out in one direction as the next slides in, warped by its own light and dark.",
7666
7701
  category: "transition",
7702
+ video: "transition",
7667
7703
  tags: ["transition", "morph", "displacement", "image", "warp"],
7668
7704
  loop: 4,
7669
7705
  source: glsl(
@@ -7696,6 +7732,7 @@ var transitionLuma = {
7696
7732
  name: "Luma fade",
7697
7733
  description: "Pixels switch to the next image in order of brightness, shadows first or highlights first, along a soft edge.",
7698
7734
  category: "transition",
7735
+ video: "transition",
7699
7736
  tags: ["transition", "morph", "luma", "image", "dissolve"],
7700
7737
  loop: 4,
7701
7738
  source: glsl(
@@ -7730,6 +7767,7 @@ var transitionParticleSwap = {
7730
7767
  name: "Particle swap",
7731
7768
  description: "The image breaks into square particles that tumble along curved, staggered paths and land as the next image.",
7732
7769
  category: "transition",
7770
+ video: "transition",
7733
7771
  tags: ["transition", "morph", "particles", "image", "mosaic"],
7734
7772
  loop: 4,
7735
7773
  source: glsl(
@@ -7813,6 +7851,7 @@ var transitionInkBleed = {
7813
7851
  name: "Ink bleed",
7814
7852
  description: "The next image bleeds through the first like wet ink on paper, spreading from a few seeds with dark, pooled edges.",
7815
7853
  category: "transition",
7854
+ video: "transition",
7816
7855
  tags: ["transition", "morph", "ink", "watercolour", "image", "organic"],
7817
7856
  loop: 4,
7818
7857
  source: glsl(
@@ -8498,6 +8537,7 @@ var shaderText = {
8498
8537
  name: "Shader text",
8499
8538
  description: "A title filled with a living texture (plasma, fire, caustics or liquid chrome), with a soft glow and outline.",
8500
8539
  category: "text",
8540
+ video: "none",
8501
8541
  tags: ["text", "title", "plasma", "fire", "chrome", "glow"],
8502
8542
  source: glsl(
8503
8543
  `// Shader text: the letters of a text image filled with an animated pattern.
@@ -8601,6 +8641,7 @@ var imageKenBurns = {
8601
8641
  name: "Ken Burns",
8602
8642
  description: "A slow documentary-style pan and zoom across an image, easing in and out and back again.",
8603
8643
  category: "image",
8644
+ video: "filter",
8604
8645
  tags: ["image", "camera", "documentary", "slideshow"],
8605
8646
  loop: 12,
8606
8647
  source: glsl(
@@ -8641,6 +8682,7 @@ var imageAscii = {
8641
8682
  name: "ASCII art",
8642
8683
  description: "An image drawn as text characters picked by brightness, in one ink or the picture's own colours.",
8643
8684
  category: "image",
8685
+ video: "filter",
8644
8686
  tags: ["image", "ascii", "text", "terminal", "retro"],
8645
8687
  source: glsl(
8646
8688
  `// ASCII art: each cell of the image becomes a character, denser for brighter cells.
@@ -8700,6 +8742,7 @@ var imageShatter = {
8700
8742
  name: "Shatter",
8701
8743
  description: "An image breaking into glass-like shards that fly apart, spin and reassemble in a loop.",
8702
8744
  category: "image",
8745
+ video: "filter",
8703
8746
  tags: ["image", "shatter", "transition", "voronoi", "explode"],
8704
8747
  loop: 5,
8705
8748
  source: glsl(
@@ -8783,6 +8826,7 @@ var imageMelt = {
8783
8826
  name: "Melt",
8784
8827
  description: "An image melting and dripping downwards in slow, noisy runs.",
8785
8828
  category: "image",
8829
+ video: "filter",
8786
8830
  tags: ["image", "melt", "drip", "psychedelic", "distortion"],
8787
8831
  source: glsl(
8788
8832
  `// Melt: noise-driven runs drag the image downwards.
@@ -8821,6 +8865,7 @@ var imageRisograph = {
8821
8865
  name: "Risograph",
8822
8866
  description: "An image printed in two posterized, slightly misregistered inks on grainy paper.",
8823
8867
  category: "image",
8868
+ video: "filter",
8824
8869
  tags: ["image", "print", "riso", "editorial", "poster"],
8825
8870
  source: glsl(
8826
8871
  `// Risograph: two posterized ink layers, out of register, on grainy paper.
@@ -8871,6 +8916,7 @@ var imageThermal = {
8871
8916
  name: "Thermal",
8872
8917
  description: "An image seen through a heat camera or night-vision goggles.",
8873
8918
  category: "image",
8919
+ video: "filter",
8874
8920
  tags: ["image", "thermal", "night-vision", "surveillance", "colour"],
8875
8921
  source: glsl(
8876
8922
  `// Thermal: brightness mapped to a heat palette, or to green night-vision phosphor.
@@ -8924,6 +8970,7 @@ var imageEdgeGlow = {
8924
8970
  name: "Edge glow",
8925
8971
  description: "An image's edges traced as glowing neon lines over a dimmed picture, with a travelling pulse.",
8926
8972
  category: "image",
8973
+ video: "filter",
8927
8974
  tags: ["image", "neon", "edges", "outline", "cyber"],
8928
8975
  loop: 2,
8929
8976
  source: glsl(
@@ -8984,6 +9031,7 @@ var imageTiltShift = {
8984
9031
  name: "Tilt-shift",
8985
9032
  description: "An image blurred outside a sharp horizontal band, with punchy colour, so it looks like a miniature.",
8986
9033
  category: "image",
9034
+ video: "filter",
8987
9035
  tags: ["image", "blur", "miniature", "photography", "depth"],
8988
9036
  source: glsl(
8989
9037
  `// Tilt-shift: blur away from a focus band, plus saturation, for the miniature look.
@@ -9026,6 +9074,7 @@ var imageSlitScan = {
9026
9074
  name: "Slit-scan",
9027
9075
  description: "An image smeared into time-stretched streaks, like a slit-scan photograph.",
9028
9076
  category: "image",
9077
+ video: "stills",
9029
9078
  tags: ["image", "slit-scan", "smear", "experimental", "distortion"],
9030
9079
  source: glsl(
9031
9080
  `// Slit-scan: rows (or columns) squeeze towards a moving slit and smear into streaks.
@@ -9070,6 +9119,7 @@ var imageMosaic = {
9070
9119
  name: "Mosaic flip",
9071
9120
  description: "Hexagonal or triangular tiles flipping over from the left to reveal an image, then back.",
9072
9121
  category: "image",
9122
+ video: "filter",
9073
9123
  tags: ["image", "reveal", "transition", "tiles", "hexagon"],
9074
9124
  loop: 6,
9075
9125
  source: glsl(
@@ -9155,6 +9205,7 @@ var imageChromaticLens = {
9155
9205
  name: "Chromatic lens",
9156
9206
  description: "An image through a cheap wide lens: barrel distortion and colour fringes growing towards the edges.",
9157
9207
  category: "image",
9208
+ video: "filter",
9158
9209
  tags: ["image", "lens", "aberration", "distortion", "vintage"],
9159
9210
  source: glsl(
9160
9211
  `// Chromatic lens: barrel distortion with red, green and blue bent by different amounts.
@@ -9195,6 +9246,7 @@ var imageOilPaint = {
9195
9246
  name: "Oil paint",
9196
9247
  description: "An image repainted in flat, edge-keeping brush dabs, like an oil painting.",
9197
9248
  category: "image",
9249
+ video: "filter",
9198
9250
  tags: ["image", "painterly", "artistic", "kuwahara", "filter"],
9199
9251
  source: glsl(
9200
9252
  `// Oil paint: a four-sector Kuwahara filter, each pixel takes the calmest neighbourhood's colour.
@@ -9265,6 +9317,139 @@ var imageTreatments = [
9265
9317
  imageOilPaint
9266
9318
  ];
9267
9319
 
9320
+ // src/catalog/types.ts
9321
+ var EFFECT_CATEGORIES = [
9322
+ "gradient",
9323
+ "organic",
9324
+ "pattern",
9325
+ "light",
9326
+ "space",
9327
+ "retro",
9328
+ "image",
9329
+ "overlay",
9330
+ "text",
9331
+ "morph",
9332
+ "interaction",
9333
+ "pyrotechnics",
9334
+ "timewarp",
9335
+ "melt",
9336
+ "electric",
9337
+ "transition",
9338
+ "loader",
9339
+ "data",
9340
+ "icon"
9341
+ ];
9342
+ var VIDEO_USES = ["filter", "transition", "interactive", "stills", "none"];
9343
+ var VECTOR_CONTROL_TYPES = ["float", "int", "bool", "color", "text"];
9344
+ var PLACEHOLDER = /\{\{\s*([A-Za-z_]\w*)\s*(?::\s*([A-Za-z0-9]*)([^}]*))?\}\}/g;
9345
+ function motionStrings(motion) {
9346
+ const out = [];
9347
+ const walk = (v) => {
9348
+ if (typeof v === "string") out.push(v);
9349
+ else if (Array.isArray(v)) v.forEach(walk);
9350
+ else if (v && typeof v === "object") Object.values(v).forEach(walk);
9351
+ };
9352
+ walk(motion);
9353
+ return out;
9354
+ }
9355
+ function vectorControls(motion) {
9356
+ const controls = [];
9357
+ const seen = /* @__PURE__ */ new Set();
9358
+ for (const text of motionStrings(motion)) {
9359
+ for (const [, name, type, attributes] of text.matchAll(PLACEHOLDER)) {
9360
+ if (type === void 0 || seen.has(name) || !VECTOR_CONTROL_TYPES.includes(type)) continue;
9361
+ seen.add(name);
9362
+ const colour = type === "color";
9363
+ controls.push(controlFromDeclaration(name, colour ? "vec3" : type, `${attributes}${colour ? " color" : ""}`, controls.length + 1));
9364
+ }
9365
+ }
9366
+ return controls;
9367
+ }
9368
+ function vectorEffectControls(effect) {
9369
+ const declared = vectorControls(effect.motion);
9370
+ const names = new Set(declared.map((c) => c.name));
9371
+ const listed = (effect.controls ?? []).filter((c) => !names.has(c.name));
9372
+ return [...declared, ...listed.map((c, i) => vectorControlDef(c, declared.length + i + 1))];
9373
+ }
9374
+ function vectorControlIssues(motion) {
9375
+ const issues = [];
9376
+ const declarations = /* @__PURE__ */ new Map();
9377
+ for (const text of motionStrings(motion)) {
9378
+ for (const [whole, name, type] of text.matchAll(PLACEHOLDER)) {
9379
+ if (type === void 0) continue;
9380
+ if (!VECTOR_CONTROL_TYPES.includes(type))
9381
+ issues.push(`${whole}: a control's type is one of ${VECTOR_CONTROL_TYPES.join(", ")}`);
9382
+ else if (declarations.has(name) && declarations.get(name) !== whole)
9383
+ issues.push(`"${name}" is declared twice; declare it once and write {{${name}}} everywhere else`);
9384
+ else declarations.set(name, whole);
9385
+ }
9386
+ }
9387
+ return issues;
9388
+ }
9389
+ function written(control, value) {
9390
+ if (control.color && Array.isArray(value)) return rgbToHex(value);
9391
+ if (Array.isArray(value)) return value.join(" ");
9392
+ return value;
9393
+ }
9394
+ var escapeMarkup = (text) => text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
9395
+ function vectorControlDef(control, line = 0) {
9396
+ const min = control.min ?? 0;
9397
+ return {
9398
+ name: control.name,
9399
+ type: control.type,
9400
+ label: control.label ?? control.name,
9401
+ color: control.color ?? false,
9402
+ min,
9403
+ max: control.max ?? Math.max(min + 1, 1),
9404
+ step: control.step ?? (control.type === "int" ? 1 : 0.01),
9405
+ default: control.default,
9406
+ line
9407
+ };
9408
+ }
9409
+ var CURRENT_COLOR = "currentColor";
9410
+ function isCurrentColor(value) {
9411
+ return typeof value === "string" && value.trim().toLowerCase() === "currentcolor";
9412
+ }
9413
+ function renderVectorEffect(effect, values = {}) {
9414
+ const filled = {};
9415
+ const placeholders = /* @__PURE__ */ new Map();
9416
+ for (const control of vectorEffectControls(effect)) {
9417
+ const given = values[control.name];
9418
+ if (control.color && isCurrentColor(given)) {
9419
+ filled[control.name] = CURRENT_COLOR;
9420
+ placeholders.set(control.name, CURRENT_COLOR);
9421
+ continue;
9422
+ }
9423
+ const check = given === void 0 ? void 0 : checkControlValue(control, given);
9424
+ filled[control.name] = check?.ok ? check.value : control.default;
9425
+ placeholders.set(control.name, written(control, filled[control.name]));
9426
+ }
9427
+ for (const [name, value] of Object.entries(effect.derived?.(filled) ?? {})) placeholders.set(name, value);
9428
+ const fill = (text, markup) => {
9429
+ const whole = /^\{\{\s*([A-Za-z_]\w*)\s*(?::[^}]*)?\}\}$/.exec(text);
9430
+ if (!markup && whole && placeholders.has(whole[1])) return placeholders.get(whole[1]);
9431
+ return text.replace(PLACEHOLDER, (match, name) => {
9432
+ if (!placeholders.has(name)) return match;
9433
+ const value = String(placeholders.get(name));
9434
+ return markup ? escapeMarkup(value) : value;
9435
+ });
9436
+ };
9437
+ const walk = (v) => {
9438
+ if (typeof v === "string") return fill(v, false);
9439
+ if (Array.isArray(v)) return v.map(walk);
9440
+ if (v && typeof v === "object") return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, walk(x)]));
9441
+ return v;
9442
+ };
9443
+ const { svg, ...rest } = effect.motion;
9444
+ return { svg: fill(svg.replace(/<!--[\s\S]*?-->/g, ""), true), ...walk(rest) };
9445
+ }
9446
+ function colorControl(name, hex, label) {
9447
+ return { name, type: "vec3", color: true, default: hexToRgb(hex) ?? [1, 1, 1], label: label ?? name };
9448
+ }
9449
+ function numberControl(name, value, min, max, label, step) {
9450
+ return { name, type: "float", default: value, min, max, label: label ?? name, step };
9451
+ }
9452
+
9268
9453
  // src/catalog/text.ts
9269
9454
  var r14 = (n5) => Math.round(n5 * 10) / 10;
9270
9455
  function fit2(text, width, max, ems = textWidthEm(text)) {
@@ -9274,6 +9459,10 @@ function fit2(text, width, max, ems = textWidthEm(text)) {
9274
9459
  }
9275
9460
  var lettersOf = (text) => Array.from(String(text));
9276
9461
  var mixHex2 = (a, b, t) => {
9462
+ if (isCurrentColor(a) || isCurrentColor(b)) {
9463
+ const css = (c) => isCurrentColor(c) ? CURRENT_COLOR : rgbToHex(Array.isArray(c) ? c : [1, 1, 1]);
9464
+ return `color-mix(in srgb, ${css(a)}, ${css(b)} ${Math.round(t * 100)}%)`;
9465
+ }
9277
9466
  const x = Array.isArray(a) ? a : [1, 1, 1];
9278
9467
  const y = Array.isArray(b) ? b : [1, 1, 1];
9279
9468
  return rgbToHex([0, 1, 2].map((i) => x[i] + (y[i] - x[i]) * t));
@@ -9284,6 +9473,16 @@ var MONO = `ui-monospace, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospa
9284
9473
  var MONO_EM = 0.6;
9285
9474
  var HEAVY = `'Arial Black', 'Helvetica Neue', Impact, system-ui, sans-serif`;
9286
9475
  var SANS3 = `system-ui, 'Helvetica Neue', Arial, sans-serif`;
9476
+ var SCRIPT = `'Pacifico', 'Brush Script MT', 'Segoe Script', 'Snell Roundhand', cursive`;
9477
+ var CAP = 0.72;
9478
+ 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'}}`;
9479
+ function frame(v, left, width, base, size, overhang = [0.05, 0, DESCENT, 0]) {
9480
+ const spill = overhang.map((e) => Math.round(Math.max(0, e) * 100) / 100).join(" ");
9481
+ return v.inline ? { viewBox: `${r14(left)} ${r14(base - size * CAP)} ${r14(width)} ${r14(size * CAP)}`, overflow: "visible", backdrop: 0, overhang: spill } : { viewBox: "0 0 400 200", overflow: "hidden", backdrop: 1, overhang: spill };
9482
+ }
9483
+ var DESCENT = 0.25;
9484
+ var glowing = (overhang, by) => overhang.map((e) => e + by);
9485
+ var room = (v, width) => v.inline ? Infinity : width;
9287
9486
  var BLINKS = 7;
9288
9487
  var typewriter = {
9289
9488
  kind: "vector",
@@ -9300,20 +9499,22 @@ var typewriter = {
9300
9499
  // Monospace, so the caret lands after the last letter however long the text is.
9301
9500
  derived: (v) => {
9302
9501
  const count = Math.max(1, lettersOf(v.text).length);
9303
- const size = r14(Math.min(44, 340 / ((count + 1) * MONO_EM)));
9502
+ const size = r14(Math.min(44, room(v, 340) / ((count + 1) * MONO_EM)));
9304
9503
  const left = r14(200 - (count + 1) * MONO_EM * size / 2);
9305
9504
  const typing = Math.round(count * Number(v.pace));
9505
+ const base = r14(100 + size * 0.35);
9306
9506
  return {
9507
+ ...frame(v, left, (count + 1) * MONO_EM * size, base, size, [0.08, 0, DESCENT, 0]),
9307
9508
  size,
9308
9509
  left,
9309
- base: r14(100 + size * 0.35),
9510
+ base,
9310
9511
  end: r14(left + count * MONO_EM * size),
9311
9512
  typing,
9312
9513
  ...Object.fromEntries(Array.from({ length: BLINKS }, (_, k) => [`blink${k}`, typing + k * 500]))
9313
9514
  };
9314
9515
  },
9315
9516
  motion: {
9316
- svg: `<svg viewBox="0 0 400 200"><!-- {{text: text default='Hello, world.' label='Text'}} {{paper: color default=#111827 label='Background'}} {{ink: color default=#F9FAFB label='Text colour'}} {{pace: float min=30 max=300 default=90 step=5 label='Ms per letter'}} --><rect width="400" height="200" fill="{{paper}}"/><text id="typed" x="{{left}}" y="{{base}}" font-family="${MONO}" font-size="{{size}}" fill="{{ink}}" xml:space="preserve">{{text}}</text><text id="caret" x="{{end}}" y="{{base}}" font-family="${MONO}" font-size="{{size}}" fill="{{ink}}" opacity="0">\u258C</text></svg>`,
9517
+ svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='Hello, world.' label='Text'}} {{paper: color default=#111827 label='Background'}} {{ink: color default=#F9FAFB label='Text colour'}} {{pace: float min=30 max=300 default=90 step=5 label='Ms per letter'}} ${typeface(MONO, 400)} --><rect width="400" height="200" fill="{{paper}}" opacity="{{backdrop}}"/><text id="typed" x="{{left}}" y="{{base}}" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" fill="{{ink}}" xml:space="preserve">{{text}}</text><text id="caret" x="{{end}}" y="{{base}}" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" fill="{{ink}}" opacity="0">\u258C</text></svg>`,
9317
9518
  animate: [
9318
9519
  { id: "typed", write: "{{text}}", caret: "\u258C", ms: n4("typing"), ease: "linear" },
9319
9520
  // Hidden while the writer shows its own caret, then blinking where it stopped.
@@ -9335,13 +9536,13 @@ var scrambleText = {
9335
9536
  ],
9336
9537
  derived: (v) => {
9337
9538
  const count = Math.max(1, lettersOf(v.text).length);
9338
- const size = r14(Math.min(60, 360 / (count * MONO_EM)));
9539
+ const size = r14(Math.min(60, room(v, 360) / (count * MONO_EM)));
9339
9540
  const width = r14(count * MONO_EM * size);
9340
9541
  const base = r14(100 + size * 0.35);
9341
- return { size, base, barX: r14(200 - width / 2), barY: r14(base + size * 0.3), barW: width, decode: Math.round(count * Number(v.pace)), glowWide: Number(v.glow) * 2 };
9542
+ return { size, base, barX: r14(200 - width / 2), barY: r14(base + size * 0.3), barW: width, decode: Math.round(count * Number(v.pace)), glowWide: Number(v.glow) * 2, ...frame(v, 200 - width / 2, width, base, size, glowing([0.08, 0, Math.max(DESCENT, 0.3 + 3 / size), 0], Number(v.glow) * 4 / size)) };
9342
9543
  },
9343
9544
  motion: {
9344
- svg: `<svg viewBox="0 0 400 200"><!-- {{text: text default='ACCESS GRANTED' label='Text'}} {{glyphs: text default='ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#%&@$*+' label='Scramble glyphs'}} {{background: color default=#05070D label='Background'}} {{ink: color default=#E2E8F0 label='Text colour'}} {{accent: color default=#22D3EE label='Accent'}} {{pace: float min=20 max=250 default=70 step=5 label='Ms per letter'}} {{glow: float min=0 max=6 default=2 step=0.5 label='Glow'}} --><defs><filter id="glow" x="-20%" y="-50%" width="140%" height="200%"><feGaussianBlur stdDeviation="{{glowWide}}" result="blur"/><feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge></filter></defs><rect width="400" height="200" fill="{{background}}"/><text id="word" x="200" y="{{base}}" text-anchor="middle" font-family="${MONO}" font-size="{{size}}" font-weight="700" fill="{{ink}}" filter="url(#glow)" xml:space="preserve">{{text}}</text><rect id="bar" x="{{barX}}" y="{{barY}}" width="0" height="3" fill="{{accent}}"/></svg>`,
9545
+ svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='ACCESS GRANTED' label='Text'}} {{glyphs: text default='ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#%&@$*+' label='Scramble glyphs'}} {{background: color default=#05070D label='Background'}} {{ink: color default=#E2E8F0 label='Text colour'}} {{accent: color default=#22D3EE label='Accent'}} {{pace: float min=20 max=250 default=70 step=5 label='Ms per letter'}} {{glow: float min=0 max=6 default=2 step=0.5 label='Glow'}} ${typeface(MONO, 700)} --><defs><filter id="glow" x="-20%" y="-50%" width="140%" height="200%"><feGaussianBlur stdDeviation="{{glowWide}}" result="blur"/><feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge></filter></defs><rect width="400" height="200" fill="{{background}}" opacity="{{backdrop}}"/><text id="word" x="200" y="{{base}}" text-anchor="middle" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" fill="{{ink}}" filter="url(#glow)" xml:space="preserve">{{text}}</text><rect id="bar" x="{{barX}}" y="{{barY}}" width="0" height="3" fill="{{accent}}"/></svg>`,
9345
9546
  animate: [
9346
9547
  // Pure noise for a moment (the scramble runs before the change starts), then it decodes.
9347
9548
  { id: "word", write: "{{text}}", scramble: "{{glyphs}}", delay: 500, ms: n4("decode"), ease: "linear" },
@@ -9367,10 +9568,11 @@ var kineticStagger = {
9367
9568
  const letters = lettersOf(v.text).slice(0, SLOTS2);
9368
9569
  const widths = letters.map((c) => textWidthEm(c));
9369
9570
  const ems = Math.max(0.6, widths.reduce((a, b) => a + b, 0));
9370
- const size = Math.min(92, 370 / ems);
9571
+ const size = Math.min(92, room(v, 370) / ems);
9371
9572
  const base = 100 + size * 0.35;
9372
9573
  const stagger = Number(v.stagger);
9373
9574
  const out = {
9575
+ ...frame(v, 200 - ems * size / 2, ems * size, base, size, [0.15, 0, Math.max(DESCENT, 0.2 + 6 / size), 0]),
9374
9576
  size: r14(size),
9375
9577
  base: r14(base),
9376
9578
  rise: r14(base - size * 0.1),
@@ -9392,9 +9594,9 @@ var kineticStagger = {
9392
9594
  return out;
9393
9595
  },
9394
9596
  motion: {
9395
- svg: `<svg viewBox="0 0 400 200"><!-- {{text: text default='Kinetic' label='Text'}} {{background: color default=#F8FAFC label='Background'}} {{ink: color default=#0F172A label='Text colour'}} {{accent: color default=#F43F5E label='Underline'}} {{stagger: float min=20 max=200 default=55 step=5 label='Ms between letters'}} --><rect width="400" height="200" fill="{{background}}"/>${Array.from(
9597
+ svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='Kinetic' label='Text'}} {{background: color default=#F8FAFC label='Background'}} {{ink: color default=#0F172A label='Text colour'}} {{accent: color default=#F43F5E label='Underline'}} {{stagger: float min=20 max=200 default=55 step=5 label='Ms between letters'}} ${typeface(HEAVY, 900)} --><rect width="400" height="200" fill="{{background}}" opacity="{{backdrop}}"/>${Array.from(
9396
9598
  { length: SLOTS2 },
9397
- (_, i) => `<text id="l${i}" text-anchor="middle" transform="translate({{x${i}}} {{drop}}) rotate(-28) scale(0.3)" opacity="0" font-family="${HEAVY}" font-size="{{size}}" font-weight="900" fill="{{ink}}">{{c${i}}}</text>`
9599
+ (_, i) => `<text id="l${i}" text-anchor="middle" transform="translate({{x${i}}} {{drop}}) rotate(-28) scale(0.3)" opacity="0" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" fill="{{ink}}">{{c${i}}}</text>`
9398
9600
  ).join("")}<rect id="line" x="{{lineX}}" y="{{lineY}}" width="0" height="6" rx="3" fill="{{accent}}"/></svg>`,
9399
9601
  animate: [
9400
9602
  ...Array.from({ length: SLOTS2 }, (_, i) => [
@@ -9428,8 +9630,10 @@ var neonSign = {
9428
9630
  derived: (v) => {
9429
9631
  const letters = lettersOf(v.text);
9430
9632
  const k = Math.min(Math.max(0, Math.round(Number(v.faulty)) - 1), Math.max(0, letters.length - 1));
9431
- const { size, length } = fit2(String(v.text), 340, 84);
9633
+ const { size, length, left } = fit2(String(v.text), room(v, 340), 84);
9432
9634
  return {
9635
+ ...frame(v, left, length, 100 + size * 0.3, size, glowing([0.1, 0, DESCENT, 0], Number(v.glow) * 6 / size)),
9636
+ hazeX: r14(Math.max(170, length / 2 + 30)),
9433
9637
  head: letters.slice(0, k).join(""),
9434
9638
  mid: letters[k] ?? "",
9435
9639
  tail: letters.slice(k + 1).join(""),
@@ -9441,12 +9645,12 @@ var neonSign = {
9441
9645
  };
9442
9646
  },
9443
9647
  motion: (() => {
9444
- const word = `x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="'Pacifico', 'Brush Script MT', 'Segoe Script', 'Snell Roundhand', cursive" font-size="{{size}}"`;
9648
+ const word = `x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}"`;
9445
9649
  const runs = (id) => `<tspan>{{head}}</tspan><tspan id="${id}">{{mid}}</tspan><tspan>{{tail}}</tspan>`;
9446
9650
  const startUp = [[0, 0], [150, 0.85], [210, 0], [330, 1], [390, 0.15], [450, 1], [620, 0.35], [680, 1]];
9447
9651
  const buzz = [[0, 1], [1300, 0.15], [1350, 1], [1420, 0.2], [1520, 1], [2100, 0.1], [2160, 1], [2230, 0.25], [2280, 1]];
9448
9652
  return {
9449
- svg: `<svg viewBox="0 0 400 200"><!-- {{text: text default='Open late' label='Text'}} {{tube: color default=#FF3DA5 label='Tube'}} {{wall: color default=#120612 label='Wall'}} {{glow: float min=0.5 max=8 default=3.5 step=0.5 label='Glow'}} {{faulty: int min=1 max=24 default=6 label='Buzzing letter'}} --><defs><filter id="neon" x="-30%" y="-80%" width="160%" height="260%"><feGaussianBlur in="SourceGraphic" stdDeviation="{{glow}}" result="near"/><feGaussianBlur in="SourceGraphic" stdDeviation="{{far}}" result="far"/><feMerge><feMergeNode in="far"/><feMergeNode in="far"/><feMergeNode in="near"/><feMergeNode in="SourceGraphic"/></feMerge></filter><filter id="haze" x="-50%" y="-100%" width="200%" height="300%"><feGaussianBlur stdDeviation="30"/></filter></defs><rect width="400" height="200" fill="{{wall}}"/><g id="sign" opacity="0"><ellipse cx="200" cy="100" rx="170" ry="55" fill="{{tube}}" opacity="0.18" filter="url(#haze)"/><text ${word} fill="{{tube}}" stroke="{{tube}}" stroke-width="1.5" filter="url(#neon)">${runs("buzzGlow")}</text><text ${word} fill="{{core}}">${runs("buzzCore")}</text></g></svg>`,
9653
+ svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='Open late' label='Text'}} {{tube: color default=#FF3DA5 label='Tube'}} {{wall: color default=#120612 label='Wall'}} {{glow: float min=0.5 max=8 default=3.5 step=0.5 label='Glow'}} {{faulty: int min=1 max=24 default=6 label='Buzzing letter'}} ${typeface(SCRIPT, 400)} --><defs><filter id="neon" x="-30%" y="-80%" width="160%" height="260%"><feGaussianBlur in="SourceGraphic" stdDeviation="{{glow}}" result="near"/><feGaussianBlur in="SourceGraphic" stdDeviation="{{far}}" result="far"/><feMerge><feMergeNode in="far"/><feMergeNode in="far"/><feMergeNode in="near"/><feMergeNode in="SourceGraphic"/></feMerge></filter><filter id="haze" x="-50%" y="-100%" width="200%" height="300%"><feGaussianBlur stdDeviation="30"/></filter></defs><rect width="400" height="200" fill="{{wall}}" opacity="{{backdrop}}"/><g id="sign" opacity="0"><ellipse cx="200" cy="100" rx="{{hazeX}}" ry="55" fill="{{tube}}" opacity="0.18" filter="url(#haze)"/><text ${word} fill="{{tube}}" stroke="{{tube}}" stroke-width="1.5" filter="url(#neon)">${runs("buzzGlow")}</text><text ${word} fill="{{core}}">${runs("buzzCore")}</text></g></svg>`,
9450
9654
  animate: [
9451
9655
  ...holds("sign", "opacity", startUp),
9452
9656
  // fill-opacity rather than opacity: browsers ignore opacity on a <tspan>.
@@ -9469,11 +9673,12 @@ var strokeDrawText = {
9469
9673
  ],
9470
9674
  // <text> takes no pathLength, so `draw` cannot measure it: the dash grows instead, longer than any letter's outline.
9471
9675
  derived: (v) => {
9472
- const { size, length } = fit2(String(v.text), 360, 100);
9473
- return { size, length, base: r14(100 + size * 0.35), dash: Math.round(size * 6) };
9676
+ const { size, length, left } = fit2(String(v.text), room(v, 360), 100);
9677
+ const edge = Number(v.width) / 2 / size;
9678
+ return { size, length, base: r14(100 + size * 0.35), dash: Math.round(size * 6), ...frame(v, left, length, 100 + size * 0.35, size, glowing([0.05, 0, DESCENT, 0], edge)) };
9474
9679
  },
9475
9680
  motion: {
9476
- svg: `<svg viewBox="0 0 400 200"><!-- {{text: text default='Draw' label='Text'}} {{background: color default=#FFFBF5 label='Background'}} {{outline: color default=#1E293B label='Outline'}} {{fill: color default=#F97316 label='Fill'}} {{width: float min=0.5 max=6 default=2 step=0.5 label='Outline width'}} {{fillOpacity: float min=0 max=1 default=1 label='Fill opacity'}} --><rect width="400" height="200" fill="{{background}}"/><text id="word" x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="${HEAVY}" font-size="{{size}}" font-weight="900" fill="{{fill}}" fill-opacity="0" stroke="{{outline}}" stroke-width="{{width}}" stroke-linejoin="round" stroke-dasharray="0 {{dash}}">{{text}}</text></svg>`,
9681
+ svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='Draw' label='Text'}} {{background: color default=#FFFBF5 label='Background'}} {{outline: color default=#1E293B label='Outline'}} {{fill: color default=#F97316 label='Fill'}} {{width: float min=0.5 max=6 default=2 step=0.5 label='Outline width'}} {{fillOpacity: float min=0 max=1 default=1 label='Fill opacity'}} ${typeface(HEAVY, 900)} --><rect width="400" height="200" fill="{{background}}" opacity="{{backdrop}}"/><text id="word" x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" fill="{{fill}}" fill-opacity="0" stroke="{{outline}}" stroke-width="{{width}}" stroke-linejoin="round" stroke-dasharray="0 {{dash}}">{{text}}</text></svg>`,
9477
9682
  animate: [
9478
9683
  { id: "word", attrs: { "stroke-dasharray": ["0 {{dash}}", "{{dash}} 0"] }, ms: 2e3, ease: "in-out" },
9479
9684
  { id: "word", attrs: { "fill-opacity": [0, n4("fillOpacity")] }, delay: 1500, ms: 700, ease: "out" }
@@ -9493,8 +9698,9 @@ var splitReveal = {
9493
9698
  { name: "Gold on black", values: { background: [0.02, 0.02, 0.02], ink: [0.96, 0.84, 0.55], line: [0.96, 0.84, 0.55] } }
9494
9699
  ],
9495
9700
  derived: (v) => {
9496
- const { size, length, left } = fit2(String(v.text), 360, 88);
9701
+ const { size, length, left } = fit2(String(v.text), room(v, 360), 88);
9497
9702
  return {
9703
+ ...frame(v, left, length, 100 + size * 0.36, size, [0.05, 10 / size, Math.max(DESCENT, 34 / size), 10 / size]),
9498
9704
  size,
9499
9705
  length,
9500
9706
  base: r14(100 + size * 0.36),
@@ -9505,9 +9711,9 @@ var splitReveal = {
9505
9711
  };
9506
9712
  },
9507
9713
  motion: (() => {
9508
- const word = `<text x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="${SANS3}" font-size="{{size}}" font-weight="800" fill="{{ink}}">{{text}}</text>`;
9714
+ const word = `<text x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" fill="{{ink}}">{{text}}</text>`;
9509
9715
  return {
9510
- svg: `<svg viewBox="0 0 400 200"><!-- {{text: text default='Chapter One' label='Text'}} {{caption: text default='The beginning' label='Caption'}} {{background: color default=#0F172A label='Background'}} {{ink: color default=#F8FAFC label='Text colour'}} {{line: color default=#38BDF8 label='Line'}} --><defs><clipPath id="above"><rect width="400" height="100"/></clipPath><clipPath id="below"><rect y="100" width="400" height="100"/></clipPath></defs><rect width="400" height="200" fill="{{background}}"/><g clip-path="url(#above)"><g id="top" transform="translate(0 {{shift}})">${word}</g></g><g clip-path="url(#below)"><g id="bottom" transform="translate(0 -{{shift}})">${word}</g></g><path id="line" d="M{{lineX1}} 100 H{{lineX2}}" stroke="{{line}}" stroke-width="2.5" stroke-linecap="round"/><text id="caption" x="200" y="{{captionY}}" text-anchor="middle" font-family="${SANS3}" font-size="13" letter-spacing="3" fill="{{ink}}" opacity="0">{{caption}}</text></svg>`,
9716
+ svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='Chapter One' label='Text'}} {{caption: text default='The beginning' label='Caption'}} {{background: color default=#0F172A label='Background'}} {{ink: color default=#F8FAFC label='Text colour'}} {{line: color default=#38BDF8 label='Line'}} ${typeface(SANS3, 800)} --><defs><clipPath id="above"><rect x="-5000" y="-5000" width="10400" height="5100"/></clipPath><clipPath id="below"><rect x="-5000" y="100" width="10400" height="5100"/></clipPath></defs><rect width="400" height="200" fill="{{background}}" opacity="{{backdrop}}"/><g clip-path="url(#above)"><g id="top" transform="translate(0 {{shift}})">${word}</g></g><g clip-path="url(#below)"><g id="bottom" transform="translate(0 -{{shift}})">${word}</g></g><path id="line" d="M{{lineX1}} 100 H{{lineX2}}" stroke="{{line}}" stroke-width="2.5" stroke-linecap="round"/><text id="caption" x="200" y="{{captionY}}" text-anchor="middle" font-family="{{font}}" font-size="13" letter-spacing="3" fill="{{ink}}" opacity="0">{{caption}}</text></svg>`,
9511
9717
  animate: [
9512
9718
  { id: "line", draw: true, ms: 600, ease: "in-out" },
9513
9719
  { id: "top", attrs: { transform: ["translate(0 {{shift}})", "translate(0 0)"] }, delay: 450, ms: 800, ease: "out" },
@@ -9533,12 +9739,13 @@ var highlighter = {
9533
9739
  ],
9534
9740
  // The swipe runs a little past the phrase at both ends, across its lower half, with a ragged second pass.
9535
9741
  derived: (v) => {
9536
- const { size, length, left } = fit2(String(v.text), 330, 56);
9742
+ const { size, length, left } = fit2(String(v.text), room(v, 330), 56);
9537
9743
  const base = 100 + size * 0.35;
9538
9744
  const x0 = left - size * 0.25;
9539
9745
  const x1 = 400 - left + size * 0.2;
9540
9746
  const y = base - size * 0.3;
9541
9747
  return {
9748
+ ...frame(v, left, length, base, size, [0.05, 0.2, DESCENT, 0.25]),
9542
9749
  size,
9543
9750
  length,
9544
9751
  base: r14(base),
@@ -9549,7 +9756,7 @@ var highlighter = {
9549
9756
  };
9550
9757
  },
9551
9758
  motion: {
9552
- svg: `<svg viewBox="0 0 400 200"><!-- {{text: text default='Ship it faster' label='Phrase'}} {{background: color default=#FFFFFF label='Background'}} {{ink: color default=#111827 label='Text colour'}} {{marker: color default=#FDE047 label='Marker'}} --><rect width="400" height="200" fill="{{background}}"/><g transform="rotate(-1.2 200 100)" opacity="0.9"><path id="swipe" d="{{swipe}}" fill="none" stroke="{{marker}}" stroke-width="{{thick}}"/><path id="ragged" d="{{ragged}}" fill="none" stroke="{{marker}}" stroke-width="{{thin}}" stroke-linecap="round"/></g><text id="phrase" x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="${SANS3}" font-size="{{size}}" font-weight="700" fill="{{ink}}" opacity="0">{{text}}</text></svg>`,
9759
+ svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='Ship it faster' label='Phrase'}} {{background: color default=#FFFFFF label='Background'}} {{ink: color default=#111827 label='Text colour'}} {{marker: color default=#FDE047 label='Marker'}} ${typeface(SANS3, 700)} --><rect width="400" height="200" fill="{{background}}" opacity="{{backdrop}}"/><g transform="rotate(-1.2 200 100)" opacity="0.9"><path id="swipe" d="{{swipe}}" fill="none" stroke="{{marker}}" stroke-width="{{thick}}"/><path id="ragged" d="{{ragged}}" fill="none" stroke="{{marker}}" stroke-width="{{thin}}" stroke-linecap="round"/></g><text id="phrase" x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" fill="{{ink}}" opacity="0">{{text}}</text></svg>`,
9553
9760
  animate: [
9554
9761
  { id: "phrase", attrs: { opacity: [0, 1] }, ms: 300, ease: "out" },
9555
9762
  { id: "swipe", draw: true, delay: 350, ms: 650, ease: "in-out" },
@@ -9578,12 +9785,14 @@ var wordCycle = {
9578
9785
  const lead = textWidthEm(String(v.prefix));
9579
9786
  const ems = words.map((w) => Math.max(0.6, textWidthEm(w)));
9580
9787
  const total = lead + 0.3 + Math.max(...ems);
9581
- const size = Math.min(52, 370 / total);
9788
+ const size = Math.min(52, room(v, 370) / total);
9582
9789
  const left = 200 - total * size / 2;
9583
9790
  const base = 100 + size * 0.35;
9584
9791
  const step = size * 1.3;
9585
9792
  const wx = left + (lead + 0.3) * size;
9586
9793
  return {
9794
+ ...frame(v, left, total * size, base, size, [0.08, 0, Math.max(DESCENT, 0.18 + 4 / size), 0]),
9795
+ bold: Math.min(900, Number(v.weight) + 100),
9587
9796
  size: r14(size),
9588
9797
  base: r14(base),
9589
9798
  px: r14(left),
@@ -9605,9 +9814,9 @@ var wordCycle = {
9605
9814
  };
9606
9815
  },
9607
9816
  motion: {
9608
- svg: `<svg viewBox="0 0 400 200"><!-- {{prefix: text default='We build' label='Lead-in'}} {{word1: text default=apps label='Word 1'}} {{word2: text default=brands label='Word 2'}} {{word3: text default=websites label='Word 3'}} {{word4: text default=games label='Word 4'}} {{background: color default=#FFFFFF label='Background'}} {{ink: color default=#0F172A label='Text colour'}} {{accent: color default=#6366F1 label='Accent'}} --><defs><clipPath id="window"><rect x="{{clipX}}" y="{{clipY}}" width="{{clipW}}" height="{{clipH}}"/></clipPath></defs><rect width="400" height="200" fill="{{background}}"/><text x="{{px}}" y="{{base}}" textLength="{{plen}}" lengthAdjust="spacingAndGlyphs" font-family="${SANS3}" font-size="{{size}}" font-weight="700" fill="{{ink}}">{{prefix}}</text><g clip-path="url(#window)"><g id="reel" transform="translate(0 -0)">${Array.from(
9817
+ svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{prefix: text default='We build' label='Lead-in'}} {{word1: text default=apps label='Word 1'}} {{word2: text default=brands label='Word 2'}} {{word3: text default=websites label='Word 3'}} {{word4: text default=games label='Word 4'}} {{background: color default=#FFFFFF label='Background'}} {{ink: color default=#0F172A label='Text colour'}} {{accent: color default=#6366F1 label='Accent'}} ${typeface(SANS3, 700)} --><defs><clipPath id="window"><rect x="{{clipX}}" y="{{clipY}}" width="{{clipW}}" height="{{clipH}}"/></clipPath></defs><rect width="400" height="200" fill="{{background}}" opacity="{{backdrop}}"/><text x="{{px}}" y="{{base}}" textLength="{{plen}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" fill="{{ink}}">{{prefix}}</text><g clip-path="url(#window)"><g id="reel" transform="translate(0 -0)">${Array.from(
9609
9818
  { length: CYCLE_WORDS + 1 },
9610
- (_, k) => `<text x="{{wx}}" y="{{y${k}}}" textLength="{{len${k}}}" lengthAdjust="spacingAndGlyphs" font-family="${SANS3}" font-size="{{size}}" font-weight="800" fill="{{accent}}">{{w${k % CYCLE_WORDS}}}</text>`
9819
+ (_, k) => `<text x="{{wx}}" y="{{y${k}}}" textLength="{{len${k}}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="{{size}}" font-weight="{{bold}}" fill="{{accent}}">{{w${k % CYCLE_WORDS}}}</text>`
9611
9820
  ).join("")}</g></g><rect id="under" x="{{wx}}" y="{{underY}}" width="{{len0}}" height="4" rx="2" fill="{{accent}}"/></svg>`,
9612
9821
  animate: Array.from({ length: CYCLE_WORDS }, (_, i) => {
9613
9822
  const at2 = { delay: (i + 1) * SHOW_MS + i * ROLL_MS, ms: ROLL_MS, ease: "in-out" };
@@ -9618,7 +9827,7 @@ var wordCycle = {
9618
9827
  }).flat()
9619
9828
  }
9620
9829
  };
9621
- var WAVE = `M-200 0${" q25 -8 50 0 t50 0".repeat(8)} V320 H-200Z`;
9830
+ var WAVE = `M-2000 0${" q25 -8 50 0 t50 0".repeat(46)} V320 H-2000Z`;
9622
9831
  var liquidFill = {
9623
9832
  kind: "vector",
9624
9833
  id: "liquid-fill",
@@ -9634,14 +9843,14 @@ var liquidFill = {
9634
9843
  ],
9635
9844
  // The surface starts under the letters and ends above their caps.
9636
9845
  derived: (v) => {
9637
- const { size, length } = fit2(String(v.text), 360, 110);
9846
+ const { size, length, left } = fit2(String(v.text), room(v, 360), 110);
9638
9847
  const base = 100 + size * 0.36;
9639
- return { size, length, base: r14(base), low: r14(base + 12), high: r14(base - size * 0.82 - 12) };
9848
+ return { ...frame(v, left, length, base, size), size, length, base: r14(base), low: r14(base + 12), high: r14(base - size * 0.82 - 12) };
9640
9849
  },
9641
9850
  motion: (() => {
9642
- const word = `x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="${HEAVY}" font-size="{{size}}" font-weight="900"`;
9851
+ const word = `x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}"`;
9643
9852
  return {
9644
- svg: `<svg viewBox="0 0 400 200"><!-- {{text: text default='FILL' label='Text'}} {{background: color default=#FFFFFF label='Background'}} {{empty: color default=#E0F2FE label='Empty'}} {{liquid: color default=#0284C7 label='Liquid'}} {{liquid2: color default=#38BDF8 label='Liquid sweep'}} --><defs><clipPath id="letters"><text ${word}>{{text}}</text></clipPath><linearGradient id="liq" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="400" y2="0" spreadMethod="repeat"><stop offset="0" stop-color="{{liquid}}"/><stop offset="0.5" stop-color="{{liquid2}}"/><stop offset="1" stop-color="{{liquid}}"/></linearGradient></defs><rect width="400" height="200" fill="{{background}}"/><text ${word} fill="{{empty}}" stroke="{{liquid}}" stroke-width="1.5" stroke-opacity="0.5">{{text}}</text><g clip-path="url(#letters)"><g id="level" transform="translate(0 {{low}})"><path id="back" d="${WAVE}" fill="{{liquid}}" opacity="0.45" transform="translate(-50 -5)"/><path id="wave" d="${WAVE}" fill="url(#liq)" transform="translate(0 0)"/></g></g></svg>`,
9853
+ svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='FILL' label='Text'}} {{background: color default=#FFFFFF label='Background'}} {{empty: color default=#E0F2FE label='Empty'}} {{liquid: color default=#0284C7 label='Liquid'}} {{liquid2: color default=#38BDF8 label='Liquid sweep'}} ${typeface(HEAVY, 900)} --><defs><clipPath id="letters"><text ${word}>{{text}}</text></clipPath><linearGradient id="liq" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="400" y2="0" spreadMethod="repeat"><stop offset="0" stop-color="{{liquid}}"/><stop offset="0.5" stop-color="{{liquid2}}"/><stop offset="1" stop-color="{{liquid}}"/></linearGradient></defs><rect width="400" height="200" fill="{{background}}" opacity="{{backdrop}}"/><text ${word} fill="{{empty}}" stroke="{{liquid}}" stroke-width="1.5" stroke-opacity="0.5">{{text}}</text><g clip-path="url(#letters)"><g id="level" transform="translate(0 {{low}})"><path id="back" d="${WAVE}" fill="{{liquid}}" opacity="0.45" transform="translate(-50 -5)"/><path id="wave" d="${WAVE}" fill="url(#liq)" transform="translate(0 0)"/></g></g></svg>`,
9645
9854
  animate: [
9646
9855
  { id: "level", attrs: { transform: ["translate(0 {{low}})", "translate(0 {{high}})"] }, ms: 2800, ease: "out" },
9647
9856
  { id: "wave", attrs: { transform: ["translate(0 0)", "translate(-200 0)"] }, ms: 3e3, ease: "linear" },
@@ -9683,13 +9892,15 @@ var glitchText = {
9683
9892
  { name: "Vapor", values: { left: [1, 0.3, 0.85], right: [0.4, 0.6, 1], background: [0.08, 0.02, 0.12], amount: 1.4 } }
9684
9893
  ],
9685
9894
  derived: (v) => {
9686
- const { size, length } = fit2(String(v.text), 340, 100);
9895
+ const { size, length, left } = fit2(String(v.text), room(v, 340), 100);
9896
+ const a = Number(v.amount);
9687
9897
  const base = 100 + size * 0.36;
9688
9898
  const top = base - size * 0.78;
9689
- const cuts = [0, ...BANDS.map((f) => r14(top + f * (base - top))), 200];
9899
+ const cuts = [-1e3, ...BANDS.map((f) => r14(top + f * (base - top))), 1200];
9690
9900
  const unit = size * Number(v.amount);
9691
9901
  const shift = ([x, y]) => `translate(${r14(x * unit)} ${r14(y * unit)})`;
9692
9902
  return {
9903
+ ...frame(v, left, length, base, size, [0.05 + 0.04 * a, 0.25 * a, DESCENT + 0.04 * a, 0.25 * a]),
9693
9904
  size,
9694
9905
  length,
9695
9906
  base: r14(base),
@@ -9704,10 +9915,10 @@ var glitchText = {
9704
9915
  };
9705
9916
  },
9706
9917
  motion: (() => {
9707
- const word = (paint) => `<text x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="${HEAVY}" font-size="{{size}}" font-weight="900" ${paint}>{{text}}</text>`;
9918
+ const word = (paint) => `<text x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" ${paint}>{{text}}</text>`;
9708
9919
  const jumps = (id, value) => GLITCH.map((key, k) => ({ id, attrs: { transform: [value(k), value(k)] }, delay: key.at, ms: 0 }));
9709
9920
  return {
9710
- svg: `<svg viewBox="0 0 400 200"><!-- {{text: text default='GLITCH' label='Text'}} {{background: color default=#07070B label='Background'}} {{ink: color default=#F5F5F5 label='Text colour'}} {{left: color default=#FF0040 label='Red copy'}} {{right: color default=#00F0FF label='Cyan copy'}} {{amount: float min=0 max=3 default=1 step=0.1 label='Intensity'}} --><defs>${[0, 1, 2, 3].map((b) => `<clipPath id="slice${b}"><rect x="-100" y="{{cut${b}}}" width="600" height="{{band${b}}}"/></clipPath>`).join("")}</defs><rect width="400" height="200" fill="{{background}}"/><g id="red" style="mix-blend-mode:screen">${word(`fill="{{left}}"`)}</g><g id="cyan" style="mix-blend-mode:screen">${word(`fill="{{right}}"`)}</g>${[0, 1, 2, 3].map((b) => `<g clip-path="url(#slice${b})"><g id="s${b}">${word(`fill="{{ink}}"`)}</g></g>`).join("")}</svg>`,
9921
+ svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='GLITCH' label='Text'}} {{background: color default=#07070B label='Background'}} {{ink: color default=#F5F5F5 label='Text colour'}} {{left: color default=#FF0040 label='Red copy'}} {{right: color default=#00F0FF label='Cyan copy'}} {{amount: float min=0 max=3 default=1 step=0.1 label='Intensity'}} ${typeface(HEAVY, 900)} --><defs>${[0, 1, 2, 3].map((b) => `<clipPath id="slice${b}"><rect x="-5000" y="{{cut${b}}}" width="10400" height="{{band${b}}}"/></clipPath>`).join("")}</defs><rect width="400" height="200" fill="{{background}}" opacity="{{backdrop}}"/><g id="red" style="mix-blend-mode:screen">${word(`fill="{{left}}"`)}</g><g id="cyan" style="mix-blend-mode:screen">${word(`fill="{{right}}"`)}</g>${[0, 1, 2, 3].map((b) => `<g clip-path="url(#slice${b})"><g id="s${b}">${word(`fill="{{ink}}"`)}</g></g>`).join("")}</svg>`,
9711
9922
  animate: [
9712
9923
  ...jumps("red", (k) => `{{red${k}}}`),
9713
9924
  ...jumps("cyan", (k) => `{{cyan${k}}}`),
@@ -9733,13 +9944,15 @@ var extrudedText = {
9733
9944
  ],
9734
9945
  // Each layer sits a step further back along the extrude direction, shaded from the side colour to the shadow.
9735
9946
  derived: (v) => {
9736
- const { size, length } = fit2(String(v.text), 320, 96);
9947
+ const { size, length, left } = fit2(String(v.text), room(v, 320), 96);
9948
+ const d = 0.36 * Number(v.depth);
9737
9949
  const step = size * 0.03 * Number(v.depth);
9738
9950
  const at2 = (deg, i) => {
9739
9951
  const a = deg * Math.PI / 180;
9740
9952
  return `translate(${r14(Math.cos(a) * step * i)} ${r14(Math.sin(a) * step * i)})`;
9741
9953
  };
9742
9954
  return {
9955
+ ...frame(v, left, length, 92 + size * 0.35, size, [0.05, 0.77 * d, DESCENT + d, 0.77 * d]),
9743
9956
  size,
9744
9957
  length,
9745
9958
  base: r14(92 + size * 0.35),
@@ -9756,10 +9969,10 @@ var extrudedText = {
9756
9969
  };
9757
9970
  },
9758
9971
  motion: (() => {
9759
- const word = (paint) => `x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="${HEAVY}" font-size="{{size}}" font-weight="900" stroke-linejoin="round" ${paint}`;
9972
+ const word = (paint) => `x="200" y="{{base}}" text-anchor="middle" textLength="{{length}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="{{size}}" font-weight="{{weight}}" stroke-linejoin="round" ${paint}`;
9760
9973
  const layers = Array.from({ length: LAYERS }, (_, k) => LAYERS - k);
9761
9974
  return {
9762
- svg: `<svg viewBox="0 0 400 200"><!-- {{text: text default='RETRO' label='Text'}} {{background: color default=#FFE8C2 label='Background'}} {{face: color default=#FFD23F label='Face'}} {{outline: color default=#1A1423 label='Outline'}} {{side: color default=#EE4266 label='Extrude'}} {{shade: color default=#3B1F2B label='Shadow'}} {{depth: float min=0.2 max=2 default=1 step=0.1 label='Depth'}} --><rect width="400" height="200" fill="{{background}}"/>${layers.map((i) => `<text id="e${i}" transform="translate(0 0)" ${word(`fill="{{fill${i}}}" stroke="{{fill${i}}}" stroke-width="2"`)}>{{text}}</text>`).join("")}<text ${word(`fill="{{face}}" stroke="{{outline}}" stroke-width="2.5" paint-order="stroke"`)}>{{text}}</text></svg>`,
9975
+ svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{text: text default='RETRO' label='Text'}} {{background: color default=#FFE8C2 label='Background'}} {{face: color default=#FFD23F label='Face'}} {{outline: color default=#1A1423 label='Outline'}} {{side: color default=#EE4266 label='Extrude'}} {{shade: color default=#3B1F2B label='Shadow'}} {{depth: float min=0.2 max=2 default=1 step=0.1 label='Depth'}} ${typeface(HEAVY, 900)} --><rect width="400" height="200" fill="{{background}}" opacity="{{backdrop}}"/>${layers.map((i) => `<text id="e${i}" transform="translate(0 0)" ${word(`fill="{{fill${i}}}" stroke="{{fill${i}}}" stroke-width="2"`)}>{{text}}</text>`).join("")}<text ${word(`fill="{{face}}" stroke="{{outline}}" stroke-width="2.5" paint-order="stroke"`)}>{{text}}</text></svg>`,
9763
9976
  animate: layers.flatMap((i) => [
9764
9977
  { id: `e${i}`, attrs: { transform: ["translate(0 0)", `{{a${i}}}`] }, ms: 700, ease: "out" },
9765
9978
  { id: `e${i}`, attrs: { transform: [`{{a${i}}}`, `{{b${i}}}`] }, delay: 1200, ms: 1500, ease: "in-out" },
@@ -9782,129 +9995,6 @@ var textEffects = [
9782
9995
  extrudedText
9783
9996
  ];
9784
9997
 
9785
- // src/catalog/types.ts
9786
- var EFFECT_CATEGORIES = [
9787
- "gradient",
9788
- "organic",
9789
- "pattern",
9790
- "light",
9791
- "space",
9792
- "retro",
9793
- "image",
9794
- "overlay",
9795
- "text",
9796
- "morph",
9797
- "interaction",
9798
- "pyrotechnics",
9799
- "timewarp",
9800
- "melt",
9801
- "electric",
9802
- "transition",
9803
- "loader",
9804
- "data",
9805
- "icon"
9806
- ];
9807
- var VECTOR_CONTROL_TYPES = ["float", "int", "bool", "color", "text"];
9808
- var PLACEHOLDER = /\{\{\s*([A-Za-z_]\w*)\s*(?::\s*([A-Za-z0-9]*)([^}]*))?\}\}/g;
9809
- function motionStrings(motion) {
9810
- const out = [];
9811
- const walk = (v) => {
9812
- if (typeof v === "string") out.push(v);
9813
- else if (Array.isArray(v)) v.forEach(walk);
9814
- else if (v && typeof v === "object") Object.values(v).forEach(walk);
9815
- };
9816
- walk(motion);
9817
- return out;
9818
- }
9819
- function vectorControls(motion) {
9820
- const controls = [];
9821
- const seen = /* @__PURE__ */ new Set();
9822
- for (const text of motionStrings(motion)) {
9823
- for (const [, name, type, attributes] of text.matchAll(PLACEHOLDER)) {
9824
- if (type === void 0 || seen.has(name) || !VECTOR_CONTROL_TYPES.includes(type)) continue;
9825
- seen.add(name);
9826
- const colour = type === "color";
9827
- controls.push(controlFromDeclaration(name, colour ? "vec3" : type, `${attributes}${colour ? " color" : ""}`, controls.length + 1));
9828
- }
9829
- }
9830
- return controls;
9831
- }
9832
- function vectorEffectControls(effect) {
9833
- const declared = vectorControls(effect.motion);
9834
- const names = new Set(declared.map((c) => c.name));
9835
- const listed = (effect.controls ?? []).filter((c) => !names.has(c.name));
9836
- return [...declared, ...listed.map((c, i) => vectorControlDef(c, declared.length + i + 1))];
9837
- }
9838
- function vectorControlIssues(motion) {
9839
- const issues = [];
9840
- const declarations = /* @__PURE__ */ new Map();
9841
- for (const text of motionStrings(motion)) {
9842
- for (const [whole, name, type] of text.matchAll(PLACEHOLDER)) {
9843
- if (type === void 0) continue;
9844
- if (!VECTOR_CONTROL_TYPES.includes(type))
9845
- issues.push(`${whole}: a control's type is one of ${VECTOR_CONTROL_TYPES.join(", ")}`);
9846
- else if (declarations.has(name) && declarations.get(name) !== whole)
9847
- issues.push(`"${name}" is declared twice; declare it once and write {{${name}}} everywhere else`);
9848
- else declarations.set(name, whole);
9849
- }
9850
- }
9851
- return issues;
9852
- }
9853
- function written(control, value) {
9854
- if (control.color && Array.isArray(value)) return rgbToHex(value);
9855
- if (Array.isArray(value)) return value.join(" ");
9856
- return value;
9857
- }
9858
- var escapeMarkup = (text) => text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
9859
- function vectorControlDef(control, line = 0) {
9860
- const min = control.min ?? 0;
9861
- return {
9862
- name: control.name,
9863
- type: control.type,
9864
- label: control.label ?? control.name,
9865
- color: control.color ?? false,
9866
- min,
9867
- max: control.max ?? Math.max(min + 1, 1),
9868
- step: control.step ?? (control.type === "int" ? 1 : 0.01),
9869
- default: control.default,
9870
- line
9871
- };
9872
- }
9873
- function renderVectorEffect(effect, values = {}) {
9874
- const filled = {};
9875
- const placeholders = /* @__PURE__ */ new Map();
9876
- for (const control of vectorEffectControls(effect)) {
9877
- const given = values[control.name];
9878
- const check = given === void 0 ? void 0 : checkControlValue(control, given);
9879
- filled[control.name] = check?.ok ? check.value : control.default;
9880
- placeholders.set(control.name, written(control, filled[control.name]));
9881
- }
9882
- for (const [name, value] of Object.entries(effect.derived?.(filled) ?? {})) placeholders.set(name, value);
9883
- const fill = (text, markup) => {
9884
- const whole = /^\{\{\s*([A-Za-z_]\w*)\s*(?::[^}]*)?\}\}$/.exec(text);
9885
- if (!markup && whole && placeholders.has(whole[1])) return placeholders.get(whole[1]);
9886
- return text.replace(PLACEHOLDER, (match, name) => {
9887
- if (!placeholders.has(name)) return match;
9888
- const value = String(placeholders.get(name));
9889
- return markup ? escapeMarkup(value) : value;
9890
- });
9891
- };
9892
- const walk = (v) => {
9893
- if (typeof v === "string") return fill(v, false);
9894
- if (Array.isArray(v)) return v.map(walk);
9895
- if (v && typeof v === "object") return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, walk(x)]));
9896
- return v;
9897
- };
9898
- const { svg, ...rest } = effect.motion;
9899
- return { svg: fill(svg.replace(/<!--[\s\S]*?-->/g, ""), true), ...walk(rest) };
9900
- }
9901
- function colorControl(name, hex, label) {
9902
- return { name, type: "vec3", color: true, default: hexToRgb(hex) ?? [1, 1, 1], label: label ?? name };
9903
- }
9904
- function numberControl(name, value, min, max, label, step) {
9905
- return { name, type: "float", default: value, min, max, label: label ?? name, step };
9906
- }
9907
-
9908
9998
  // src/catalog/vector.ts
9909
9999
  var spinner = {
9910
10000
  kind: "vector",
@@ -10317,6 +10407,7 @@ var flowNetwork = {
10317
10407
  }
10318
10408
  };
10319
10409
  var DRIP_BASELINE = 120;
10410
+ var DRIP_SIZE = 104;
10320
10411
  var DRIP_CAP = 75;
10321
10412
  var DRIP_TOP = 106;
10322
10413
  var DRIPS2 = [
@@ -10328,6 +10419,9 @@ var DRIPS2 = [
10328
10419
  { at: 0.693, width: 12, length: 42, delay: 60, ms: 1700 },
10329
10420
  { at: 0.793, width: 11, length: 32, delay: 300, ms: 1500 }
10330
10421
  ];
10422
+ var DRIP_RUN = 300;
10423
+ var DRIP_RUNS = 8;
10424
+ var DRIP_SLOTS = Array.from({ length: DRIP_RUNS }, (_, run) => DRIPS2.map((d, k) => ({ ...d, i: run * DRIPS2.length + k, run, delay: d.delay + run * 90 }))).flat();
10331
10425
  var dripText = {
10332
10426
  kind: "vector",
10333
10427
  id: "drip-text",
@@ -10340,55 +10434,63 @@ var dripText = {
10340
10434
  { name: "Slime", values: { fill: [0.55, 0.93, 0.25], outline: [0.06, 0.2, 0.04], shine: [0.85, 1, 0.7] } },
10341
10435
  { name: "Molten gold", values: { fill: [1, 0.78, 0.2], outline: [0.35, 0.16, 0], shine: [1, 0.96, 0.75], drip: 1.5 } }
10342
10436
  ],
10343
- // The word is stretched to a width set by its length (textLength), so the drips land under its letters in any font.
10437
+ // The word is drawn at its own width (textLength from its estimated width), so the drips land under its letters in any font.
10344
10438
  derived: (v) => {
10345
- const letters = Math.max(1, Array.from(String(v.word).trim()).length);
10346
- const width = Math.min(380, letters * 75);
10439
+ const width = Math.round(Math.max(0.6, textWidthEm(String(v.word).trim())) * DRIP_SIZE);
10347
10440
  const left = 200 - width / 2;
10441
+ const runs = Math.min(DRIP_RUNS, Math.max(1, Math.round(width / DRIP_RUN)));
10442
+ const run = width / runs;
10443
+ const r15 = (n5) => Math.round(n5 * 10) / 10;
10444
+ const edge = Number(v.stroke) / 2 / DRIP_SIZE;
10445
+ const tip = (DRIP_TOP + Math.max(...DRIPS2.map((d) => d.length)) * Number(v.drip) + 13 * 0.72 - DRIP_BASELINE) / DRIP_SIZE;
10446
+ const overhang = [edge, edge, Math.max(0.05, tip) + edge, edge].map((e) => Math.round(e * 100) / 100).join(" ");
10348
10447
  return {
10349
10448
  width,
10449
+ fit: v.inline ? 1 : Math.round(Math.min(1, 380 / width) * 1e3) / 1e3,
10350
10450
  // Inline, the picture frames just the word's capitals, from cap height to baseline, so it sits on
10351
10451
  // the line like a letter would; the pop, outline and drips spill out over the text around it.
10352
10452
  viewBox: v.inline ? `${left} ${DRIP_BASELINE - DRIP_CAP} ${width} ${DRIP_CAP}` : "0 0 400 200",
10353
10453
  overflow: v.inline ? "visible" : "hidden",
10454
+ overhang,
10354
10455
  ...Object.fromEntries(
10355
- DRIPS2.flatMap((d, i) => {
10356
- const x = Math.round((left + d.at * width) * 10) / 10;
10357
- const length = Math.round(d.length * Number(v.drip) * 10) / 10;
10456
+ DRIP_SLOTS.flatMap((d) => {
10457
+ const x = r15(left + (d.run + d.at) * run);
10458
+ const length = r15(d.length * Number(v.drip));
10358
10459
  return [
10359
- [`x${i}`, x],
10360
- [`left${i}`, x - d.width / 2],
10361
- [`shine${i}`, x - d.width * 0.25],
10362
- [`len${i}`, length],
10363
- [`tip${i}`, DRIP_TOP + length]
10460
+ [`show${d.i}`, d.run < runs ? "inline" : "none"],
10461
+ [`x${d.i}`, x],
10462
+ [`left${d.i}`, r15(x - d.width / 2)],
10463
+ [`shine${d.i}`, r15(x - d.width * 0.25)],
10464
+ [`len${d.i}`, length],
10465
+ [`tip${d.i}`, r15(DRIP_TOP + length)]
10364
10466
  ];
10365
10467
  })
10366
10468
  )
10367
10469
  };
10368
10470
  },
10369
10471
  motion: (() => {
10370
- const word = `<text x="200" y="${DRIP_BASELINE}" text-anchor="middle" textLength="{{width}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="104" font-weight="{{weight}}" stroke-linejoin="round"`;
10371
- const drips = (layer, paint) => DRIPS2.map(
10372
- (d, i) => `<rect id="${layer}r${i}" x="{{left${i}}}" y="${DRIP_TOP}" width="${d.width}" height="0" rx="${d.width / 2}" ${paint}/><circle id="${layer}c${i}" cx="{{x${i}}}" cy="${DRIP_TOP}" r="${d.width * 0.72}" ${paint}/>`
10472
+ const word = `<text x="200" y="${DRIP_BASELINE}" text-anchor="middle" textLength="{{width}}" lengthAdjust="spacingAndGlyphs" font-family="{{font}}" font-size="${DRIP_SIZE}" font-weight="{{weight}}" stroke-linejoin="round"`;
10473
+ const drips = (layer, paint) => DRIP_SLOTS.map(
10474
+ (d) => `<g display="{{show${d.i}}}"><rect id="${layer}r${d.i}" x="{{left${d.i}}}" y="${DRIP_TOP}" width="${d.width}" height="0" rx="${d.width / 2}" ${paint}/><circle id="${layer}c${d.i}" cx="{{x${d.i}}}" cy="${DRIP_TOP}" r="${d.width * 0.72}" ${paint}/></g>`
10373
10475
  ).join("");
10374
10476
  const outline = `fill="{{outline}}" stroke="{{outline}}" stroke-width="{{stroke}}"`;
10375
10477
  const pop = (from, to) => [`translate(200 100) scale(${from}) translate(-200 -100)`, `translate(200 100) scale(${to}) translate(-200 -100)`];
10376
10478
  return {
10377
- svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}"><!-- {{word: text default=SLOP label='Word'}} {{font: text default='Arial Black, Impact, system-ui, sans-serif' label='Font'}} {{weight: int min=100 max=900 default=900 step=100 label='Weight'}} {{fill: color default=#FF7EB6 label='Fill'}} {{outline: color default=#3A0A2C label='Outline'}} {{shine: color default=#FFD1E6 label='Shine'}} {{drip: float min=0.2 max=2 default=1 label='Drip length'}} {{stroke: float min=0 max=14 default=7 step=0.5 label='Outline width'}} {{inline: bool default=false label='Inline'}} --><g id="word" transform="${pop(0.6, 1)[0]}" opacity="0">${drips("o", outline)}${word} fill="none" stroke="{{outline}}" stroke-width="{{stroke}}">{{word}}</text>${drips("f", `fill="{{fill}}"`)}${word} fill="{{fill}}">{{word}}</text>${DRIPS2.map(
10378
- (d, i) => `<circle id="s${i}" cx="{{shine${i}}}" cy="${DRIP_TOP}" r="${d.width * 0.2}" fill="{{shine}}" opacity="0"/>`
10379
- ).join("")}</g></svg>`,
10479
+ svg: `<svg viewBox="{{viewBox}}" overflow="{{overflow}}" data-overhang="{{overhang}}"><!-- {{word: text default=SLOP label='Word'}} {{font: text default='Arial Black, Impact, system-ui, sans-serif' label='Font'}} {{weight: int min=100 max=900 default=900 step=100 label='Weight'}} {{fill: color default=#FF7EB6 label='Fill'}} {{outline: color default=#3A0A2C label='Outline'}} {{shine: color default=#FFD1E6 label='Shine'}} {{drip: float min=0.2 max=2 default=1 label='Drip length'}} {{stroke: float min=0 max=14 default=7 step=0.5 label='Outline width'}} {{inline: bool default=false label='Inline'}} --><g transform="translate(200 100) scale({{fit}}) translate(-200 -100)"><g id="word" transform="${pop(0.6, 1)[0]}" opacity="0">${drips("o", outline)}${word} fill="none" stroke="{{outline}}" stroke-width="{{stroke}}">{{word}}</text>${drips("f", `fill="{{fill}}"`)}${word} fill="{{fill}}">{{word}}</text>${DRIP_SLOTS.map(
10480
+ (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"/>`
10481
+ ).join("")}</g></g></svg>`,
10380
10482
  animate: [
10381
10483
  { id: "word", attrs: { transform: pop(0.6, 1.08), opacity: [0, 1] }, ms: 260, ease: "out" },
10382
10484
  { id: "word", attrs: { transform: pop(1.08, 1) }, delay: 260, ms: 200, ease: "in-out" },
10383
- ...DRIPS2.flatMap((d, i) => {
10485
+ ...DRIP_SLOTS.flatMap((d) => {
10384
10486
  const at2 = { delay: 400 + d.delay, ms: d.ms, ease: "in-out" };
10385
- const tip = `{{tip${i}}}`;
10487
+ const tip = `{{tip${d.i}}}`;
10386
10488
  return [
10387
10489
  ...["o", "f"].flatMap((layer) => [
10388
- { id: `${layer}r${i}`, attrs: { height: [0, `{{len${i}}}`] }, ...at2 },
10389
- { id: `${layer}c${i}`, attrs: { cy: [DRIP_TOP, tip] }, ...at2 }
10490
+ { id: `${layer}r${d.i}`, attrs: { height: [0, `{{len${d.i}}}`] }, ...at2 },
10491
+ { id: `${layer}c${d.i}`, attrs: { cy: [DRIP_TOP, tip] }, ...at2 }
10390
10492
  ]),
10391
- { id: `s${i}`, attrs: { cy: [DRIP_TOP, tip], opacity: [0, 0.9] }, ...at2 }
10493
+ { id: `s${d.i}`, attrs: { cy: [DRIP_TOP, tip], opacity: [0, 0.9] }, ...at2 }
10392
10494
  ];
10393
10495
  })
10394
10496
  ]
@@ -10508,6 +10610,7 @@ function findEffects(query = {}) {
10508
10610
  if (query.category && e.category !== query.category) return false;
10509
10611
  if (query.kind && e.kind !== query.kind) return false;
10510
10612
  if (query.tags && !query.tags.every((t) => e.tags.includes(t))) return false;
10613
+ if (query.video && !(e.kind === "shader" && e.video && query.video.includes(e.video))) return false;
10511
10614
  const haystack = [e.id, e.name, e.description, e.category, ...e.tags].join(" ").toLowerCase();
10512
10615
  return words.every((w) => haystack.includes(w));
10513
10616
  });
@@ -10518,7 +10621,8 @@ function effectControls(effect) {
10518
10621
  function effectDefaults(effect, options = {}) {
10519
10622
  const preset = options.preset ? effect.presets?.find((p) => p.name === options.preset)?.values : void 0;
10520
10623
  const merged = { ...preset, ...options.values };
10521
- return Object.fromEntries(effectControls(effect).map((c) => [c.name, controlValue(c, merged)]));
10624
+ const keep = (c) => effect.kind === "vector" && c.color && isCurrentColor(merged[c.name]);
10625
+ return Object.fromEntries(effectControls(effect).map((c) => [c.name, keep(c) ? CURRENT_COLOR : controlValue(c, merged)]));
10522
10626
  }
10523
10627
 
10524
10628
  export {
@@ -10533,11 +10637,14 @@ export {
10533
10637
  SAMPLE_IMAGES,
10534
10638
  textImage,
10535
10639
  EFFECT_CATEGORIES,
10640
+ VIDEO_USES,
10536
10641
  VECTOR_CONTROL_TYPES,
10537
10642
  vectorControls,
10538
10643
  vectorEffectControls,
10539
10644
  vectorControlIssues,
10540
10645
  vectorControlDef,
10646
+ CURRENT_COLOR,
10647
+ isCurrentColor,
10541
10648
  renderVectorEffect,
10542
10649
  colorControl,
10543
10650
  numberControl,
@@ -10548,4 +10655,4 @@ export {
10548
10655
  effectControls,
10549
10656
  effectDefaults
10550
10657
  };
10551
- //# sourceMappingURL=chunk-KWNFZ5NC.js.map
10658
+ //# sourceMappingURL=chunk-UI5X4D63.js.map