@pixerate/flair 0.22.0 → 0.23.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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @pixerate/flair
2
2
 
3
+ ## 0.23.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 69cdb60: `effectInputs(effect)` says what an effect takes and how it answers the pointer: what it `accepts` (image, video, svg, text, mask, path), its image controls and whether each wants a picture, a mask or text, its text controls (copy, font, path, glyphs), its video use, its `interactions` (hover, press, click, trail, pointer) and whether it is a transparent overlay. It is worked out from the effect's controls, source and fields, so it never drifts. `findEffects` and the `catalog.search` command filter on `accepts`, `interaction` and `transparent`, and the search lists each effect's inputs. `silhouette-morph` now says `video: "none"` (it takes silhouettes, not photos), and the fireworks, explosion and bullet-time effects are tagged `interactive`.
8
+
3
9
  ## 0.22.0
4
10
 
5
11
  ### Minor Changes
package/README.md CHANGED
@@ -150,6 +150,33 @@ new SvgPlayer(host, motion, { loop: false });
150
150
 
151
151
  Find effects with `findEffects({ text: "glow", category: "light", kind: "shader" })`. Each effect has an `id`, `name`, `description`, `category`, `tags` and optional `presets`. Image effects default to built-in sample pictures, so they look right before you set `uImage` to your own URL.
152
152
 
153
+ #### What an effect takes
154
+
155
+ `effectInputs(effect)` says what you can give an effect and how it answers the pointer, worked out from the effect itself:
156
+
157
+ ```ts
158
+ import { effectInputs, findEffects, getEffect, textImage } from "@pixerate/flair/catalog";
159
+
160
+ effectInputs(getEffect("image-duotone")!);
161
+ // { accepts: ["image", "video", "svg"], images: [{ name: "uImage", wants: "picture" }], video: "filter", interactions: [], transparent: false, … }
162
+ effectInputs(getEffect("typewriter")!);
163
+ // { accepts: ["text"], texts: [{ name: "text", holds: "copy" }, { name: "font", holds: "font" }], interactions: [], … }
164
+ effectInputs(getEffect("click-ripple")!).interactions; // ["click"]
165
+
166
+ findEffects({ accepts: ["video"] }); // effects that run on video
167
+ findEffects({ accepts: ["text"], kind: "vector" }); // animated titles and labels
168
+ findEffects({ accepts: ["image"], interaction: ["hover"] }); // pictures that react to hovering
169
+ findEffects({ transparent: true }); // overlays to lay over a page, picture or video
170
+ ```
171
+
172
+ - `accepts`: `image` (any picture URL), `video` (a live `<video>`, with `setLiveSource`), `svg` (an SVG URL, drawn with its transparency), `text` (words for a text control, or for a text mask through `textImage("…")`), `mask` (a shape with transparency whose silhouette the effect uses: an icon, a logo, a word), `path` (SVG path data for a shape control).
173
+ - `images`: each image control's name, and whether it `wants` a `picture`, a `mask` or `text`.
174
+ - `texts`: each text control's name, and whether it `holds` `copy`, a `font`, a `path` or `glyphs`.
175
+ - `interactions`: `hover`, `press`, `click`, `trail` (follows where the pointer has been) or `pointer` (follows where it is), from the pointer built-ins a shader reads or a vector effect's `trigger`.
176
+ - `transparent`, `video` and `trigger`, as on the effect.
177
+
178
+ `findEffects` filters on `accepts` (all of them), `interaction` (any of them; `[]` for effects that don't answer the pointer) and `transparent`, and the `catalog.search` command takes the same filters and lists each effect's `accepts` and `interactions`. The `interactive` tag is kept in step with `interactions`.
179
+
153
180
  Effects that show a picture (image, transition, morph, time-warp and the like) take transparent pictures too, such as SVG icons (Lucide and similar sets draw black `currentColor` strokes on a clear background). They lay the picture over a backdrop: `uBackdrop` (white by default) shows through its clear parts. Set `uBackdropAlpha` to 0 to keep the picture's alpha instead, and render with `{ alpha: true }` to lay the result over the page. `SAMPLE_IMAGES.icon` is a Lucide-style sample icon. SVGs are rasterised at 2048 px on the long edge, so icons stay sharp.
154
181
 
155
182
  #### Effects on video
@@ -1,7 +1,76 @@
1
1
  import { a as ControlDef, C as ControlValue } from '../controls-Ec6P0Vpy.js';
2
- import { E as EffectDefinition, a as EffectCategory, b as VideoUse, S as ShaderEffect } from '../types-Cy5EihZ8.js';
3
- export { C as CURRENT_COLOR, c as EFFECT_CATEGORIES, d as EffectPreset, e as VECTOR_CONTROL_TYPES, f as VIDEO_USES, g as VectorControl, V as VectorEffect, h as colorControl, i as isCurrentColor, n as numberControl, r as renderVectorEffect, v as vectorControlDef, j as vectorControlIssues, k as vectorControls, l as vectorEffectControls } from '../types-Cy5EihZ8.js';
4
- import '../svg-BEOJriIW.js';
2
+ import { a as SvgTrigger } from '../svg-BhRboN9q.js';
3
+ import { a as VideoUse, E as EffectDefinition, b as EffectCategory, S as ShaderEffect } from '../types-DLkbOX8d.js';
4
+ export { C as CURRENT_COLOR, c as EFFECT_CATEGORIES, d as EffectPreset, e as VECTOR_CONTROL_TYPES, f as VIDEO_USES, g as VectorControl, V as VectorEffect, h as colorControl, i as isCurrentColor, n as numberControl, r as renderVectorEffect, v as vectorControlDef, j as vectorControlIssues, k as vectorControls, l as vectorEffectControls } from '../types-DLkbOX8d.js';
5
+
6
+ /**
7
+ * What an effect takes and how it answers the pointer, worked out from the
8
+ * effect itself (its controls, its `video` use, the pointer built-ins its
9
+ * source reads, its `trigger`), so a consumer can pair a picture, a video,
10
+ * an SVG, a word or a path with effects that suit it.
11
+ */
12
+
13
+ /**
14
+ * Something you can give an effect:
15
+ * - `image`: a photo or picture (any URL or `data:` image) for an image control.
16
+ * - `video`: a live `<video>` (or canvas) for an image control, with `setLiveSource`.
17
+ * - `svg`: an SVG file or `data:` URL for an image control (drawn as a picture, keeping its transparency).
18
+ * - `text`: words, for a text control, or for a text mask through `textImage`.
19
+ * - `mask`: a shape with transparency (an icon, a logo, `textImage`) whose silhouette the effect uses.
20
+ * - `path`: SVG path data (`d`), for a shape control.
21
+ */
22
+ type EffectInput = "image" | "video" | "svg" | "text" | "mask" | "path";
23
+ /** Every {@link EffectInput}. */
24
+ declare const EFFECT_INPUTS: readonly EffectInput[];
25
+ /**
26
+ * How an effect answers the pointer:
27
+ * - `hover`: changes while the pointer is over it.
28
+ * - `press`: changes while the pointer is held down (or dragged).
29
+ * - `click`: answers each click or tap.
30
+ * - `trail`: follows where the pointer has just been.
31
+ * - `pointer`: follows where the pointer is.
32
+ */
33
+ type EffectInteraction = "hover" | "press" | "click" | "trail" | "pointer";
34
+ /** Every {@link EffectInteraction}. */
35
+ declare const EFFECT_INTERACTIONS: readonly EffectInteraction[];
36
+ /** A shader's image control and what it wants. */
37
+ interface ImageSlot {
38
+ /** The control's name (a `sampler2D` uniform): set it with `setValues` or `setLiveSource`. */
39
+ name: string;
40
+ label: string;
41
+ /**
42
+ * What it is for: `picture` (a photo, video or SVG shown as it is), `mask`
43
+ * (a shape whose transparency the effect uses: an icon, a logo, a
44
+ * silhouette), or `text` (a word drawn by `textImage`).
45
+ */
46
+ wants: "picture" | "mask" | "text";
47
+ }
48
+ /** A vector effect's text control and what it holds. */
49
+ interface TextSlot {
50
+ /** The control's name: set it with `renderVectorEffect(effect, { [name]: … })`. */
51
+ name: string;
52
+ label: string;
53
+ /** `copy` (words shown), `font` (a CSS font family), `path` (SVG path data) or `glyphs` (characters to draw from). */
54
+ holds: "copy" | "font" | "path" | "glyphs";
55
+ default: string;
56
+ }
57
+ /** What an effect takes and how it answers the pointer (see {@link effectInputs}). */
58
+ interface EffectInputs {
59
+ /** Everything it can be given, from its image and text controls. Empty: it makes its picture from nothing. */
60
+ accepts: EffectInput[];
61
+ /** Its image controls (shader effects). */
62
+ images: ImageSlot[];
63
+ /** Its text controls (vector effects). */
64
+ texts: TextSlot[];
65
+ /** How it suits video, for shaders with an image control (see `VideoUse`). */
66
+ video?: VideoUse;
67
+ /** How it answers the pointer; empty when it does not. */
68
+ interactions: EffectInteraction[];
69
+ /** A vector effect's trigger: what makes it play. */
70
+ trigger?: SvgTrigger;
71
+ /** Draws over other content (render it with `alpha: true`, over a page or picture). */
72
+ transparent: boolean;
73
+ }
5
74
 
6
75
  /**
7
76
  * Sample pictures for the catalog's image effects: small SVGs as `data:` URLs,
@@ -71,6 +140,7 @@ declare function glsl(...parts: string[]): string;
71
140
  * - Video: shader effects with image controls say how they suit video
72
141
  * (`video`, see `VideoUse`); `findEffects({ video: ["filter"] })` finds them.
73
142
  * - Controls: `effectControls`, `effectDefaults`.
143
+ * - Inputs: `effectInputs` (what an effect takes, how it answers the pointer).
74
144
  * - Vector effects: their pictures declare their controls (`vectorControls`),
75
145
  * and `renderVectorEffect` fills them in.
76
146
  *
@@ -83,6 +153,17 @@ declare const SHADER_EFFECTS: readonly ShaderEffect[];
83
153
  declare const EFFECTS: readonly EffectDefinition[];
84
154
  /** The effect with this id, or undefined. */
85
155
  declare function getEffect(id: string): EffectDefinition | undefined;
156
+
157
+ /**
158
+ * What an effect takes and how it answers the pointer: what you can give it
159
+ * (`accepts`: image, video, svg, text, mask, path), its image controls and
160
+ * whether each wants a picture, a mask or text, its text controls (copy,
161
+ * font, path, glyphs), how it suits video, how it answers the pointer
162
+ * (`interactions`: hover, press, click, trail, pointer) and whether it draws
163
+ * over other content. Worked out from the effect itself, so it always agrees
164
+ * with its controls and source.
165
+ */
166
+ declare function effectInputs(effect: EffectDefinition): EffectInputs;
86
167
  interface EffectQuery {
87
168
  /** Words to look for in the name, description, tags and id (all must match). */
88
169
  text?: string;
@@ -92,6 +173,12 @@ interface EffectQuery {
92
173
  tags?: readonly string[];
93
174
  /** Effects that suit video in one of these ways (shader effects with image controls only). */
94
175
  video?: readonly VideoUse[];
176
+ /** Effects that can be given every one of these (see {@link effectInputs}): `["image"]`, `["text"]`, `["svg"]`… */
177
+ accepts?: readonly EffectInput[];
178
+ /** Effects that answer the pointer in at least one of these ways; `[]` for effects that don't answer it at all. */
179
+ interaction?: readonly EffectInteraction[];
180
+ /** Overlays (`true`), which draw over other content, or effects that fill their frame (`false`). */
181
+ transparent?: boolean;
95
182
  }
96
183
  /** Effects matching a query, in catalog order. */
97
184
  declare function findEffects(query?: EffectQuery): EffectDefinition[];
@@ -103,4 +190,4 @@ declare function effectDefaults(effect: EffectDefinition, options?: {
103
190
  values?: Record<string, ControlValue>;
104
191
  }): Record<string, ControlValue>;
105
192
 
106
- export { EFFECTS, EffectCategory, EffectDefinition, type EffectQuery, FBM, HASH, NOISE, OKLAB_MIX, PALETTE, ROTATE, SAMPLE_IMAGES, SHADER_EFFECTS, ShaderEffect, VideoUse, effectControls, effectDefaults, findEffects, getEffect, glsl, svgDataUrl, textImage };
193
+ export { EFFECTS, EFFECT_INPUTS, EFFECT_INTERACTIONS, EffectCategory, EffectDefinition, type EffectInput, type EffectInputs, type EffectInteraction, type EffectQuery, FBM, HASH, type ImageSlot, NOISE, OKLAB_MIX, PALETTE, ROTATE, SAMPLE_IMAGES, SHADER_EFFECTS, ShaderEffect, type TextSlot, VideoUse, effectControls, effectDefaults, effectInputs, findEffects, getEffect, glsl, svgDataUrl, textImage };
@@ -2,6 +2,8 @@ import {
2
2
  CURRENT_COLOR,
3
3
  EFFECTS,
4
4
  EFFECT_CATEGORIES,
5
+ EFFECT_INPUTS,
6
+ EFFECT_INTERACTIONS,
5
7
  FBM,
6
8
  HASH,
7
9
  NOISE,
@@ -15,6 +17,7 @@ import {
15
17
  colorControl,
16
18
  effectControls,
17
19
  effectDefaults,
20
+ effectInputs,
18
21
  findEffects,
19
22
  getEffect,
20
23
  glsl,
@@ -27,7 +30,7 @@ import {
27
30
  vectorControlIssues,
28
31
  vectorControls,
29
32
  vectorEffectControls
30
- } from "../chunk-3RKDS64M.js";
33
+ } from "../chunk-4F4KL4KU.js";
31
34
  import "../chunk-OHW7UG6D.js";
32
35
  import "../chunk-EHDJ3CPJ.js";
33
36
  import "../chunk-VYOMXE6I.js";
@@ -35,6 +38,8 @@ export {
35
38
  CURRENT_COLOR,
36
39
  EFFECTS,
37
40
  EFFECT_CATEGORIES,
41
+ EFFECT_INPUTS,
42
+ EFFECT_INTERACTIONS,
38
43
  FBM,
39
44
  HASH,
40
45
  NOISE,
@@ -48,6 +53,7 @@ export {
48
53
  colorControl,
49
54
  effectControls,
50
55
  effectDefaults,
56
+ effectInputs,
51
57
  findEffects,
52
58
  getEffect,
53
59
  glsl,
@@ -14,6 +14,68 @@ import {
14
14
  rgbToHex
15
15
  } from "./chunk-VYOMXE6I.js";
16
16
 
17
+ // src/catalog/inputs.ts
18
+ var EFFECT_INPUTS = ["image", "video", "svg", "text", "mask", "path"];
19
+ var EFFECT_INTERACTIONS = ["hover", "press", "click", "trail", "pointer"];
20
+ function isTextImage(url) {
21
+ if (!url.startsWith("data:image/svg+xml;charset=utf-8,")) return false;
22
+ try {
23
+ return decodeURIComponent(url.slice(url.indexOf(",") + 1)).includes("<text");
24
+ } catch {
25
+ return false;
26
+ }
27
+ }
28
+ var PATH_DATA = /^\s*[Mm]\s*[-+.\d]/;
29
+ function textSlot(control) {
30
+ const value = String(control.default);
31
+ const holds2 = control.name === "font" || /\bfont\b/i.test(control.label) ? "font" : PATH_DATA.test(value) ? "path" : control.name === "glyphs" ? "glyphs" : "copy";
32
+ return { name: control.name, label: control.label, holds: holds2, default: value };
33
+ }
34
+ function shaderInteractions(source) {
35
+ const code = source.replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*/g, "");
36
+ const found = /* @__PURE__ */ new Set();
37
+ if (/\biClicks\b/.test(code)) found.add("click");
38
+ if (/\biTrail\b/.test(code)) found.add("trail");
39
+ if (/\biPointer\s*\.\s*z\b/.test(code)) found.add("hover");
40
+ if (/\biPointer\s*\.\s*w\b/.test(code) || /\biMouse\s*\.\s*z\b/.test(code)) found.add("press");
41
+ if (/\biPointer\s*\.\s*xy?\b/.test(code) || /\biMouse\s*\.\s*xy?\b/.test(code)) found.add("pointer");
42
+ return EFFECT_INTERACTIONS.filter((i) => found.has(i));
43
+ }
44
+ function inputsOf(effect, controls) {
45
+ const accepts = /* @__PURE__ */ new Set();
46
+ const images = [];
47
+ const texts = [];
48
+ for (const control of controls) {
49
+ if (control.type === "sampler2D") {
50
+ const value = String(control.default);
51
+ const video = effect.kind === "shader" ? effect.video : void 0;
52
+ const wants = isTextImage(value) ? "text" : video === "none" ? "mask" : "picture";
53
+ images.push({ name: control.name, label: control.label, wants });
54
+ if (wants === "picture") {
55
+ accepts.add("image").add("svg");
56
+ if (video && video !== "none") accepts.add("video");
57
+ } else {
58
+ accepts.add("mask").add("svg").add("text").add("image");
59
+ }
60
+ } else if (control.type === "text") {
61
+ const slot = textSlot(control);
62
+ texts.push(slot);
63
+ if (slot.holds === "copy") accepts.add("text");
64
+ if (slot.holds === "path") accepts.add("path");
65
+ }
66
+ }
67
+ const interactions = effect.kind === "vector" ? effect.trigger ? [effect.trigger] : [] : shaderInteractions(effect.source);
68
+ return {
69
+ accepts: EFFECT_INPUTS.filter((i) => accepts.has(i)),
70
+ images,
71
+ texts,
72
+ ...effect.kind === "shader" && effect.video ? { video: effect.video } : {},
73
+ interactions,
74
+ ...effect.kind === "vector" && effect.trigger ? { trigger: effect.trigger } : {},
75
+ transparent: effect.kind === "shader" ? Boolean(effect.transparent) : false
76
+ };
77
+ }
78
+
17
79
  // src/catalog/glsl.ts
18
80
  var HASH = `
19
81
  float hash12(vec2 p) {
@@ -10242,7 +10304,7 @@ var fireworks = {
10242
10304
  name: "Fireworks",
10243
10305
  description: "Shells rising on faint trails and bursting into peonies, chrysanthemums, gold willows and rings that droop and twinkle out; clicks fire more.",
10244
10306
  category: "pyrotechnics",
10245
- tags: ["pyro", "overlay", "fireworks", "celebration", "festive", "night", "click"],
10307
+ tags: ["pyro", "overlay", "fireworks", "celebration", "festive", "night", "click", "interactive"],
10246
10308
  transparent: true,
10247
10309
  source: glsl(
10248
10310
  `// Fireworks: staggered shells rising and bursting into peony, chrysanthemum, willow and ring patterns.
@@ -10461,7 +10523,7 @@ var explosion = {
10461
10523
  name: "Explosion",
10462
10524
  description: "A fireball cooling from white-hot to orange to rolling dark smoke, with a shockwave ring and flying debris; it loops and also goes off at each click.",
10463
10525
  category: "pyrotechnics",
10464
- tags: ["pyro", "overlay", "explosion", "fire", "smoke", "action", "click"],
10526
+ tags: ["pyro", "overlay", "explosion", "fire", "smoke", "action", "click", "interactive"],
10465
10527
  transparent: true,
10466
10528
  loop: 3,
10467
10529
  source: glsl(
@@ -11308,7 +11370,7 @@ var bulletTime = {
11308
11370
  description: "A shockwave sweeping out from the centre or a click, freezing the picture grey and zoomed behind it while the rest moves on.",
11309
11371
  category: "timewarp",
11310
11372
  video: "stills",
11311
- tags: ["time", "image", "freeze", "shockwave", "click", "cinematic"],
11373
+ tags: ["time", "image", "freeze", "shockwave", "click", "cinematic", "interactive"],
11312
11374
  loop: 8,
11313
11375
  source: glsl(
11314
11376
  `// Bullet time: every 4 s (or on a click) a wave freezes the picture behind it, then lets go.
@@ -11659,7 +11721,7 @@ var silhouetteMorph = {
11659
11721
  name: "Silhouette morph",
11660
11722
  description: "One silhouette or logo flowing into another as a gooey blob, its colour blending from the first shape's to the second's.",
11661
11723
  category: "morph",
11662
- video: "stills",
11724
+ video: "none",
11663
11725
  tags: ["morph", "shape", "logo", "goo", "blob", "text"],
11664
11726
  loop: 4,
11665
11727
  source: glsl(
@@ -20128,6 +20190,12 @@ var byId = new Map(EFFECTS.map((e) => [e.id, e]));
20128
20190
  function getEffect(id) {
20129
20191
  return byId.get(id);
20130
20192
  }
20193
+ var inputsCache = /* @__PURE__ */ new WeakMap();
20194
+ function effectInputs(effect) {
20195
+ let inputs = inputsCache.get(effect);
20196
+ if (!inputs) inputsCache.set(effect, inputs = inputsOf(effect, effectControls(effect)));
20197
+ return inputs;
20198
+ }
20131
20199
  function findEffects(query = {}) {
20132
20200
  const words = (query.text ?? "").toLowerCase().split(/\s+/).filter(Boolean);
20133
20201
  return EFFECTS.filter((e) => {
@@ -20135,6 +20203,15 @@ function findEffects(query = {}) {
20135
20203
  if (query.kind && e.kind !== query.kind) return false;
20136
20204
  if (query.tags && !query.tags.every((t) => e.tags.includes(t))) return false;
20137
20205
  if (query.video && !(e.kind === "shader" && e.video && query.video.includes(e.video))) return false;
20206
+ if (query.accepts || query.interaction || query.transparent !== void 0) {
20207
+ const inputs = effectInputs(e);
20208
+ if (query.accepts && !query.accepts.every((i) => inputs.accepts.includes(i))) return false;
20209
+ if (query.interaction) {
20210
+ const answers = query.interaction.length === 0 ? inputs.interactions.length === 0 : query.interaction.some((i) => inputs.interactions.includes(i));
20211
+ if (!answers) return false;
20212
+ }
20213
+ if (query.transparent !== void 0 && inputs.transparent !== query.transparent) return false;
20214
+ }
20138
20215
  const haystack = [e.id, e.name, e.description, e.category, ...e.tags].join(" ").toLowerCase();
20139
20216
  return words.every((w) => haystack.includes(w));
20140
20217
  });
@@ -20150,6 +20227,8 @@ function effectDefaults(effect, options = {}) {
20150
20227
  }
20151
20228
 
20152
20229
  export {
20230
+ EFFECT_INPUTS,
20231
+ EFFECT_INTERACTIONS,
20153
20232
  HASH,
20154
20233
  NOISE,
20155
20234
  FBM,
@@ -20175,8 +20254,9 @@ export {
20175
20254
  SHADER_EFFECTS,
20176
20255
  EFFECTS,
20177
20256
  getEffect,
20257
+ effectInputs,
20178
20258
  findEffects,
20179
20259
  effectControls,
20180
20260
  effectDefaults
20181
20261
  };
20182
- //# sourceMappingURL=chunk-3RKDS64M.js.map
20262
+ //# sourceMappingURL=chunk-4F4KL4KU.js.map