@pixerate/flair 0.28.0 → 0.29.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 (59) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +48 -6
  3. package/dist/catalog/index.d.ts +11 -6
  4. package/dist/catalog/index.js +1 -1
  5. package/dist/{chunk-4LKAPRFM.js → chunk-6GFOLHCT.js} +920 -34
  6. package/dist/chunk-6GFOLHCT.js.map +1 -0
  7. package/dist/{chunk-JL6TI6II.js → chunk-7KYQ4R76.js} +2 -2
  8. package/dist/chunk-7KYQ4R76.js.map +1 -0
  9. package/dist/{chunk-STKEKXCK.js → chunk-AUH3PEFW.js} +2 -2
  10. package/dist/{chunk-PFGGPIMT.js → chunk-DA4UUSDO.js} +58 -3
  11. package/dist/chunk-DA4UUSDO.js.map +1 -0
  12. package/dist/{chunk-RDU63CDO.js → chunk-HDYBY3PQ.js} +380 -1
  13. package/dist/chunk-HDYBY3PQ.js.map +1 -0
  14. package/dist/{chunk-VLQLRKGW.js → chunk-KZYZX3V5.js} +260 -33
  15. package/dist/chunk-KZYZX3V5.js.map +1 -0
  16. package/dist/{chunk-GXP4YLAP.js → chunk-MBKNTXQX.js} +9 -9
  17. package/dist/chunk-MBKNTXQX.js.map +1 -0
  18. package/dist/{chunk-H6VDFBIO.js → chunk-SMR2MJXB.js} +156 -5
  19. package/dist/chunk-SMR2MJXB.js.map +1 -0
  20. package/dist/{chunk-YBGRXVAE.js → chunk-X3C2VFTM.js} +3 -3
  21. package/dist/{chunk-CK6EV37I.js → chunk-ZULFHADW.js} +69 -6
  22. package/dist/chunk-ZULFHADW.js.map +1 -0
  23. package/dist/cli.js +6 -6
  24. package/dist/editor/index.d.ts +3 -3
  25. package/dist/editor/index.js +3 -3
  26. package/dist/{effect-GGP66JI6.js → effect-YBWDNIYG.js} +10 -10
  27. package/dist/embed/index.d.ts +36 -8
  28. package/dist/embed/index.js +5 -5
  29. package/dist/{frames-VEcqa40g.d.ts → frames-CGJudAbu.d.ts} +11 -1
  30. package/dist/index.d.ts +69 -6
  31. package/dist/index.js +35 -7
  32. package/dist/index.js.map +1 -1
  33. package/dist/node.d.ts +4 -4
  34. package/dist/node.js +9 -9
  35. package/dist/react/index.d.ts +4 -4
  36. package/dist/react/index.js +7 -7
  37. package/dist/react/index.js.map +1 -1
  38. package/dist/{recorder-CDPPTJMG.js → recorder-TYMEZHOT.js} +3 -3
  39. package/dist/{renderer-teaa98RE.d.ts → renderer-tk1nbVkW.d.ts} +252 -2
  40. package/dist/svelte/index.d.ts +4 -4
  41. package/dist/svelte/index.js +7 -7
  42. package/dist/svelte/index.js.map +1 -1
  43. package/dist/{svg-BhRboN9q.d.ts → svg-DaKRPA9v.d.ts} +39 -7
  44. package/dist/{types-DUOMxZaq.d.ts → types-wh28fXW4.d.ts} +2 -2
  45. package/dist/vector/index.d.ts +2 -2
  46. package/dist/vector/index.js +5 -3
  47. package/package.json +1 -1
  48. package/dist/chunk-4LKAPRFM.js.map +0 -1
  49. package/dist/chunk-CK6EV37I.js.map +0 -1
  50. package/dist/chunk-GXP4YLAP.js.map +0 -1
  51. package/dist/chunk-H6VDFBIO.js.map +0 -1
  52. package/dist/chunk-JL6TI6II.js.map +0 -1
  53. package/dist/chunk-PFGGPIMT.js.map +0 -1
  54. package/dist/chunk-RDU63CDO.js.map +0 -1
  55. package/dist/chunk-VLQLRKGW.js.map +0 -1
  56. /package/dist/{chunk-STKEKXCK.js.map → chunk-AUH3PEFW.js.map} +0 -0
  57. /package/dist/{chunk-YBGRXVAE.js.map → chunk-X3C2VFTM.js.map} +0 -0
  58. /package/dist/{effect-GGP66JI6.js.map → effect-YBWDNIYG.js.map} +0 -0
  59. /package/dist/{recorder-CDPPTJMG.js.map → recorder-TYMEZHOT.js.map} +0 -0
@@ -2,22 +2,22 @@ import {
2
2
  svgFrameMarkup,
3
3
  svgInlineLayout,
4
4
  svgViewBox
5
- } from "../chunk-STKEKXCK.js";
5
+ } from "../chunk-AUH3PEFW.js";
6
6
  import {
7
7
  mountEffect
8
- } from "../chunk-VLQLRKGW.js";
9
- import "../chunk-H6VDFBIO.js";
10
- import "../chunk-RDU63CDO.js";
8
+ } from "../chunk-KZYZX3V5.js";
9
+ import "../chunk-SMR2MJXB.js";
10
+ import "../chunk-HDYBY3PQ.js";
11
11
  import {
12
12
  effectDefaults,
13
13
  getEffect,
14
14
  renderVectorEffect
15
- } from "../chunk-4LKAPRFM.js";
15
+ } from "../chunk-6GFOLHCT.js";
16
16
  import "../chunk-EHDJ3CPJ.js";
17
17
  import "../chunk-VYOMXE6I.js";
18
18
  import {
19
19
  SvgPlayer
20
- } from "../chunk-PFGGPIMT.js";
20
+ } from "../chunk-DA4UUSDO.js";
21
21
  import "../chunk-WMZQCUFJ.js";
22
22
  import "../chunk-ORAFCHX3.js";
23
23
 
@@ -75,7 +75,7 @@ function SvgPlayerView(props) {
75
75
  const player = useRef(void 0);
76
76
  const callbacks = useRef({ onPick, onPlayer });
77
77
  callbacks.current = { onPick, onPlayer };
78
- const at = poster ?? (trigger === "hover" || trigger === "press" ? "first" : "last");
78
+ const at = poster ?? (trigger === "hover" || trigger === "press" || trigger === "scroll" ? "first" : "last");
79
79
  const markup = useMemo(() => svgFrameMarkup(motion, { at, steps }) ?? "", [motion, at, steps]);
80
80
  const stepsKey = steps?.join(",");
81
81
  const enter = playOnEnter ?? (loop === false && !trigger);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/react/index.tsx"],"sourcesContent":["/**\n * React components for Flair: `<FlairEffect>` runs any catalog effect (shader\n * or vector) applied to a picture, video, SVG or words; `<VectorEffect>` plays\n * a catalog vector effect by id, `<SvgPlayerView>` plays any {@link SvgMotion}.\n *\n * Both render a still of the picture on the server (its last frame, by\n * default), so a page hydrates with the picture in place rather than a blank\n * box that jumps; the live `SvgPlayer` takes over after mount and is disposed\n * on unmount. Under `prefers-reduced-motion: reduce` the still stays.\n *\n * Needs React 18 or later (an optional peer dependency of the package).\n *\n * @packageDocumentation\n */\n\nimport {\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n type CSSProperties,\n type HTMLAttributes,\n} from \"react\";\nimport { getEffect, effectDefaults, type EffectDefinition } from \"../catalog/index.js\";\nimport { mountEffect, type EffectSubject, type MountedEffect } from \"../embed/index.js\";\nimport { renderVectorEffect, type VectorEffect as VectorEffectDefinition } from \"../catalog/types.js\";\nimport type { ControlValue } from \"../shader/controls.js\";\nimport type { Clock } from \"../time/clock.js\";\nimport type { SvgMotion } from \"../vector/motion.js\";\nimport { svgFrameMarkup, svgInlineLayout, svgViewBox } from \"../vector/static.js\";\nimport { SvgPlayer, type SvgTrigger } from \"../vector/svg.js\";\n\n// Layout effects swap the still for the player before paint; on a server neither runs.\nconst useIsomorphicLayoutEffect = typeof window === \"undefined\" ? useEffect : useLayoutEffect;\n\n/** Cap height of a heavy sans, as a share of its font size: what an inline word is framed to. */\nconst CAP_HEIGHT = \"0.72em\";\n\n/** When to leave the still in place of the animation. */\nexport type ReducedMotion = \"user\" | \"always\" | \"never\";\n\nexport interface SvgPlayerViewProps extends Omit<HTMLAttributes<HTMLSpanElement>, \"children\"> {\n /** The picture and its animations. */\n motion: SvgMotion;\n /** Start again at the end (default `true`); `false` plays once and stays on the last frame. */\n loop?: boolean;\n /** Start playing at once (default `true`). */\n autoplay?: boolean;\n /** Playback speed (default 1). */\n speed?: number;\n /** Ms to rest on the last frame before looping (default 600). */\n hold?: number;\n /** Loop length in ms (default: the picture's own, plus `hold`). */\n duration?: number;\n /** Step start times in ms, for pictures that change on steps. */\n steps?: readonly number[];\n /** Plays in answer to the pointer (see `SvgTrigger`). */\n trigger?: SvgTrigger;\n /** Pause while scrolled out of view. */\n pauseWhenHidden?: boolean;\n /**\n * Wait to start until scrolled into view. Default `true` when it plays once\n * (`loop={false}`, no trigger), so a one-shot further down the page does not\n * finish before anyone sees it; `false` plays at mount.\n */\n playOnEnter?: boolean;\n /** Hide the picture while paused or reset. */\n hideWhenPaused?: boolean;\n /** Time source (default animation frames); a `ManualClock` steps frames yourself. */\n clock?: Clock;\n /** Called with the id of the innermost element with an id that is clicked. */\n onPick?: (id: string) => void;\n /** Called with the player once it is made, and `undefined` when it is disposed. */\n onPlayer?: (player: SvgPlayer | undefined) => void;\n /**\n * The still shown before the player starts (server render, hydration) and\n * instead of it under reduced motion: `\"last\"` (default; the rest frame,\n * `\"first\"` for a hover or press trigger), `\"first\"` or a time in ms.\n */\n poster?: \"first\" | \"last\" | number;\n /**\n * `\"user\"` (default) shows the still when the visitor prefers reduced\n * motion, `\"always\"` always does, `\"never\"` always plays.\n */\n reducedMotion?: ReducedMotion;\n /**\n * Size it as a word in a line of text: `display: inline-block` with the\n * viewBox's aspect ratio and `height` 0.72em (the cap height of a heavy\n * sans; pass a CSS length for another). Meant for pictures framed from cap\n * height to baseline, like drip text's `inline`. Otherwise it is a block\n * as wide as its container with the viewBox's aspect ratio.\n */\n inline?: boolean | string;\n /**\n * With `inline`, keep room for what spills past the word (descenders,\n * outlines, glows, drips): a margin of the picture's `data-overhang` (see\n * `svgInlineLayout`), so the lines around it make space instead of being\n * painted over. Default `false`.\n */\n contain?: boolean;\n}\n\nfunction prefersReducedMotion(): boolean {\n return typeof matchMedia === \"function\" && matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n}\n\n/** The host's size from the picture's viewBox, so the still and the player take the same room. */\nfunction hostStyle(svg: string, inline: boolean | string | undefined, contain: boolean | undefined, style?: CSSProperties): CSSProperties {\n const box = svgViewBox(svg);\n const aspectRatio = box ? `${box[2]} / ${box[3]}` : undefined;\n const spill = inline && contain ? svgInlineLayout(svg)?.overhang : undefined;\n const base: CSSProperties = inline\n ? {\n display: \"inline-block\",\n height: typeof inline === \"string\" ? inline : CAP_HEIGHT,\n aspectRatio,\n ...(spill && { margin: `${spill.top}em ${spill.right}em ${spill.bottom}em ${spill.left}em` }),\n }\n : { display: \"block\", aspectRatio };\n return { position: \"relative\", ...base, ...style };\n}\n\nconst LAYER: CSSProperties = { position: \"absolute\", inset: 0, display: \"block\" };\n\n/**\n * Plays an {@link SvgMotion}: a still on the server and before mount, then\n * an `SvgPlayer` (remade when the motion or its options change, disposed on\n * unmount). Pass a memoised `motion`: a new object each render restarts it.\n */\nexport function SvgPlayerView(props: SvgPlayerViewProps) {\n const {\n motion,\n loop,\n autoplay,\n speed,\n hold,\n duration,\n steps,\n trigger,\n pauseWhenHidden,\n playOnEnter,\n hideWhenPaused,\n clock,\n onPick,\n onPlayer,\n poster,\n reducedMotion = \"user\",\n inline,\n contain,\n style,\n ...rest\n } = props;\n const still = useRef<HTMLSpanElement>(null);\n const mount = useRef<HTMLSpanElement>(null);\n const player = useRef<SvgPlayer | undefined>(undefined);\n // Callbacks change every render; the player calls the latest without being remade.\n const callbacks = useRef({ onPick, onPlayer });\n callbacks.current = { onPick, onPlayer };\n\n const at = poster ?? (trigger === \"hover\" || trigger === \"press\" ? \"first\" : \"last\");\n const markup = useMemo(() => svgFrameMarkup(motion, { at, steps }) ?? \"\", [motion, at, steps]);\n const stepsKey = steps?.join(\",\");\n // A one-shot waits until it is seen.\n const enter = playOnEnter ?? (loop === false && !trigger);\n\n useIsomorphicLayoutEffect(() => {\n const host = mount.current;\n if (!host) return;\n if (reducedMotion === \"always\" || (reducedMotion === \"user\" && prefersReducedMotion())) return;\n const p = new SvgPlayer(host, motion, {\n loop,\n autoplay,\n speed,\n hold,\n duration,\n steps,\n trigger,\n pauseWhenHidden,\n playOnEnter: enter,\n hideWhenPaused,\n clock,\n onPick: (id) => callbacks.current.onPick?.(id),\n });\n player.current = p;\n // Set directly, not through props, so React leaves it be on later renders.\n if (still.current) still.current.style.display = \"none\";\n callbacks.current.onPlayer?.(p);\n return () => {\n p.dispose();\n player.current = undefined;\n if (still.current) still.current.style.display = \"block\";\n callbacks.current.onPlayer?.(undefined);\n };\n // `speed` is applied below without remaking; `steps` by value.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [motion, loop, autoplay, hold, duration, stepsKey, trigger, pauseWhenHidden, enter, hideWhenPaused, clock, reducedMotion]);\n\n useEffect(() => {\n player.current?.setSpeed(speed ?? 1);\n }, [speed]);\n\n return (\n <span {...rest} style={hostStyle(motion.svg, inline, contain, style)}>\n <span ref={still} aria-hidden=\"true\" style={LAYER} dangerouslySetInnerHTML={{ __html: markup }} />\n <span ref={mount} style={LAYER} />\n </span>\n );\n}\n\nexport interface VectorEffectProps extends Omit<SvgPlayerViewProps, \"motion\" | \"id\"> {\n /** A vector effect's catalog id (`\"drip-text\"`), or the effect itself. */\n id: string | VectorEffectDefinition;\n /** Control values; missing ones take the preset's, then the defaults. */\n values?: Record<string, ControlValue>;\n /** A preset's name to start from. */\n preset?: string;\n /** The host element's own `id`. */\n elementId?: string;\n}\n\n/**\n * Plays a catalog vector effect: `<VectorEffect id=\"drip-text\" values={{ word: \"Slop\" }} loop={false} />`.\n * Its `hold` and `trigger` are used unless given. With an `inline` control\n * set (drip text's), it is sized as a word in the line (see `inline`).\n * Throws when `id` is not a vector effect.\n */\nexport function VectorEffect(props: VectorEffectProps) {\n const { id, values, preset, elementId, ...rest } = props;\n const effect = typeof id === \"string\" ? getEffect(id) : id;\n if (!effect || effect.kind !== \"vector\")\n throw new Error(`\"${typeof id === \"string\" ? id : effect?.id}\" is not a vector effect; pick one from findEffects({ kind: \"vector\" })`);\n const valuesKey = JSON.stringify(values ?? {});\n const motion = useMemo(\n () => renderVectorEffect(effect, effectDefaults(effect, { preset, values })),\n // Values by content, so an inline object literal does not restart it.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [effect, preset, valuesKey],\n );\n return (\n <SvgPlayerView\n id={elementId}\n motion={motion}\n hold={effect.hold}\n trigger={effect.trigger}\n inline={values?.inline === true}\n {...rest}\n />\n );\n}\n\nexport type { SvgPlayer, SvgMotion, SvgTrigger };\n\nexport interface FlairEffectProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /** A catalog effect, by id or definition: shader or vector. */\n effect: string | EffectDefinition;\n /** What to apply it to: a picture, a video, an SVG, words or a path (see `EffectSubject`). */\n subject?: EffectSubject | readonly EffectSubject[];\n /** A preset by name, under `values`. */\n preset?: string;\n /** Control values over the effect's defaults (and preset). */\n values?: Record<string, ControlValue>;\n /** Start playing at once (default `true`). */\n autoplay?: boolean;\n /** Pause while scrolled out of view (default `true`). */\n pauseWhenHidden?: boolean;\n}\n\n/**\n * Any catalog effect, shader or vector, applied to a subject: a picture, a\n * video, an SVG, words or a path (see `mountEffect` in\n * `@pixerate/flair/embed`). Fills its element: size it with CSS. A new\n * subject or values update the running effect; a new effect, preset or\n * playback option starts it again. It renders nothing on the server.\n *\n * ```tsx\n * <FlairEffect effect=\"image-duotone\" subject={{ image: \"/team.jpg\" }} preset=\"Spotify\" style={{ aspectRatio: \"16 / 9\" }} />\n * <FlairEffect effect=\"typewriter\" subject={{ text: \"Hello there\" }} />\n * ```\n */\nexport function FlairEffect(props: FlairEffectProps) {\n const { effect, subject, preset, values, autoplay, pauseWhenHidden, style, ...rest } = props;\n const host = useRef<HTMLDivElement>(null);\n const mounted = useRef<MountedEffect | undefined>(undefined);\n const valuesKey = JSON.stringify(values ?? {});\n // What the running effect last took, so a re-render with the same subject or values changes nothing.\n const applied = useRef<{ subject?: FlairEffectProps[\"subject\"]; valuesKey: string }>({ valuesKey: \"\" });\n const latest = useRef({ subject, values, valuesKey });\n latest.current = { subject, values, valuesKey };\n useEffect(() => {\n const now = latest.current;\n const m = mountEffect(host.current!, effect, {\n ...(now.subject ? { subject: now.subject } : {}),\n ...(preset ? { preset } : {}),\n ...(now.values ? { values: now.values } : {}),\n autoplay: autoplay ?? true,\n pauseWhenHidden: pauseWhenHidden ?? true,\n });\n m.element.style.display = \"block\";\n m.element.style.width = \"100%\";\n m.element.style.height = \"100%\";\n mounted.current = m;\n applied.current = { subject: now.subject, valuesKey: now.valuesKey };\n return () => {\n m.dispose();\n mounted.current = undefined;\n };\n }, [effect, preset, autoplay, pauseWhenHidden]);\n useEffect(() => {\n if (!mounted.current || !subject || subject === applied.current.subject) return;\n mounted.current.setSubject(subject);\n applied.current.subject = subject;\n }, [subject]);\n useEffect(() => {\n if (!mounted.current || !values || valuesKey === applied.current.valuesKey) return;\n mounted.current.setValues(values);\n applied.current.valuesKey = valuesKey;\n }, [valuesKey]);\n return <div ref={host} style={{ position: \"relative\", ...style }} {...rest} />;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/react/index.tsx"],"sourcesContent":["/**\n * React components for Flair: `<FlairEffect>` runs any catalog effect (shader\n * or vector) applied to a picture, video, SVG or words; `<VectorEffect>` plays\n * a catalog vector effect by id, `<SvgPlayerView>` plays any {@link SvgMotion}.\n *\n * Both render a still of the picture on the server (its last frame, by\n * default), so a page hydrates with the picture in place rather than a blank\n * box that jumps; the live `SvgPlayer` takes over after mount and is disposed\n * on unmount. Under `prefers-reduced-motion: reduce` the still stays.\n *\n * Needs React 18 or later (an optional peer dependency of the package).\n *\n * @packageDocumentation\n */\n\nimport {\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n type CSSProperties,\n type HTMLAttributes,\n} from \"react\";\nimport { getEffect, effectDefaults, type EffectDefinition } from \"../catalog/index.js\";\nimport { mountEffect, type EffectSubject, type MountedEffect } from \"../embed/index.js\";\nimport { renderVectorEffect, type VectorEffect as VectorEffectDefinition } from \"../catalog/types.js\";\nimport type { ControlValue } from \"../shader/controls.js\";\nimport type { Clock } from \"../time/clock.js\";\nimport type { SvgMotion } from \"../vector/motion.js\";\nimport { svgFrameMarkup, svgInlineLayout, svgViewBox } from \"../vector/static.js\";\nimport { SvgPlayer, type SvgTrigger } from \"../vector/svg.js\";\n\n// Layout effects swap the still for the player before paint; on a server neither runs.\nconst useIsomorphicLayoutEffect = typeof window === \"undefined\" ? useEffect : useLayoutEffect;\n\n/** Cap height of a heavy sans, as a share of its font size: what an inline word is framed to. */\nconst CAP_HEIGHT = \"0.72em\";\n\n/** When to leave the still in place of the animation. */\nexport type ReducedMotion = \"user\" | \"always\" | \"never\";\n\nexport interface SvgPlayerViewProps extends Omit<HTMLAttributes<HTMLSpanElement>, \"children\"> {\n /** The picture and its animations. */\n motion: SvgMotion;\n /** Start again at the end (default `true`); `false` plays once and stays on the last frame. */\n loop?: boolean;\n /** Start playing at once (default `true`). */\n autoplay?: boolean;\n /** Playback speed (default 1). */\n speed?: number;\n /** Ms to rest on the last frame before looping (default 600). */\n hold?: number;\n /** Loop length in ms (default: the picture's own, plus `hold`). */\n duration?: number;\n /** Step start times in ms, for pictures that change on steps. */\n steps?: readonly number[];\n /** Plays in answer to the pointer (see `SvgTrigger`). */\n trigger?: SvgTrigger;\n /** Pause while scrolled out of view. */\n pauseWhenHidden?: boolean;\n /**\n * Wait to start until scrolled into view. Default `true` when it plays once\n * (`loop={false}`, no trigger), so a one-shot further down the page does not\n * finish before anyone sees it; `false` plays at mount.\n */\n playOnEnter?: boolean;\n /** Hide the picture while paused or reset. */\n hideWhenPaused?: boolean;\n /** Time source (default animation frames); a `ManualClock` steps frames yourself. */\n clock?: Clock;\n /** Called with the id of the innermost element with an id that is clicked. */\n onPick?: (id: string) => void;\n /** Called with the player once it is made, and `undefined` when it is disposed. */\n onPlayer?: (player: SvgPlayer | undefined) => void;\n /**\n * The still shown before the player starts (server render, hydration) and\n * instead of it under reduced motion: `\"last\"` (default; the rest frame,\n * `\"first\"` for a hover or press trigger), `\"first\"` or a time in ms.\n */\n poster?: \"first\" | \"last\" | number;\n /**\n * `\"user\"` (default) shows the still when the visitor prefers reduced\n * motion, `\"always\"` always does, `\"never\"` always plays.\n */\n reducedMotion?: ReducedMotion;\n /**\n * Size it as a word in a line of text: `display: inline-block` with the\n * viewBox's aspect ratio and `height` 0.72em (the cap height of a heavy\n * sans; pass a CSS length for another). Meant for pictures framed from cap\n * height to baseline, like drip text's `inline`. Otherwise it is a block\n * as wide as its container with the viewBox's aspect ratio.\n */\n inline?: boolean | string;\n /**\n * With `inline`, keep room for what spills past the word (descenders,\n * outlines, glows, drips): a margin of the picture's `data-overhang` (see\n * `svgInlineLayout`), so the lines around it make space instead of being\n * painted over. Default `false`.\n */\n contain?: boolean;\n}\n\nfunction prefersReducedMotion(): boolean {\n return typeof matchMedia === \"function\" && matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n}\n\n/** The host's size from the picture's viewBox, so the still and the player take the same room. */\nfunction hostStyle(svg: string, inline: boolean | string | undefined, contain: boolean | undefined, style?: CSSProperties): CSSProperties {\n const box = svgViewBox(svg);\n const aspectRatio = box ? `${box[2]} / ${box[3]}` : undefined;\n const spill = inline && contain ? svgInlineLayout(svg)?.overhang : undefined;\n const base: CSSProperties = inline\n ? {\n display: \"inline-block\",\n height: typeof inline === \"string\" ? inline : CAP_HEIGHT,\n aspectRatio,\n ...(spill && { margin: `${spill.top}em ${spill.right}em ${spill.bottom}em ${spill.left}em` }),\n }\n : { display: \"block\", aspectRatio };\n return { position: \"relative\", ...base, ...style };\n}\n\nconst LAYER: CSSProperties = { position: \"absolute\", inset: 0, display: \"block\" };\n\n/**\n * Plays an {@link SvgMotion}: a still on the server and before mount, then\n * an `SvgPlayer` (remade when the motion or its options change, disposed on\n * unmount). Pass a memoised `motion`: a new object each render restarts it.\n */\nexport function SvgPlayerView(props: SvgPlayerViewProps) {\n const {\n motion,\n loop,\n autoplay,\n speed,\n hold,\n duration,\n steps,\n trigger,\n pauseWhenHidden,\n playOnEnter,\n hideWhenPaused,\n clock,\n onPick,\n onPlayer,\n poster,\n reducedMotion = \"user\",\n inline,\n contain,\n style,\n ...rest\n } = props;\n const still = useRef<HTMLSpanElement>(null);\n const mount = useRef<HTMLSpanElement>(null);\n const player = useRef<SvgPlayer | undefined>(undefined);\n // Callbacks change every render; the player calls the latest without being remade.\n const callbacks = useRef({ onPick, onPlayer });\n callbacks.current = { onPick, onPlayer };\n\n const at = poster ?? (trigger === \"hover\" || trigger === \"press\" || trigger === \"scroll\" ? \"first\" : \"last\");\n const markup = useMemo(() => svgFrameMarkup(motion, { at, steps }) ?? \"\", [motion, at, steps]);\n const stepsKey = steps?.join(\",\");\n // A one-shot waits until it is seen.\n const enter = playOnEnter ?? (loop === false && !trigger);\n\n useIsomorphicLayoutEffect(() => {\n const host = mount.current;\n if (!host) return;\n if (reducedMotion === \"always\" || (reducedMotion === \"user\" && prefersReducedMotion())) return;\n const p = new SvgPlayer(host, motion, {\n loop,\n autoplay,\n speed,\n hold,\n duration,\n steps,\n trigger,\n pauseWhenHidden,\n playOnEnter: enter,\n hideWhenPaused,\n clock,\n onPick: (id) => callbacks.current.onPick?.(id),\n });\n player.current = p;\n // Set directly, not through props, so React leaves it be on later renders.\n if (still.current) still.current.style.display = \"none\";\n callbacks.current.onPlayer?.(p);\n return () => {\n p.dispose();\n player.current = undefined;\n if (still.current) still.current.style.display = \"block\";\n callbacks.current.onPlayer?.(undefined);\n };\n // `speed` is applied below without remaking; `steps` by value.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [motion, loop, autoplay, hold, duration, stepsKey, trigger, pauseWhenHidden, enter, hideWhenPaused, clock, reducedMotion]);\n\n useEffect(() => {\n player.current?.setSpeed(speed ?? 1);\n }, [speed]);\n\n return (\n <span {...rest} style={hostStyle(motion.svg, inline, contain, style)}>\n <span ref={still} aria-hidden=\"true\" style={LAYER} dangerouslySetInnerHTML={{ __html: markup }} />\n <span ref={mount} style={LAYER} />\n </span>\n );\n}\n\nexport interface VectorEffectProps extends Omit<SvgPlayerViewProps, \"motion\" | \"id\"> {\n /** A vector effect's catalog id (`\"drip-text\"`), or the effect itself. */\n id: string | VectorEffectDefinition;\n /** Control values; missing ones take the preset's, then the defaults. */\n values?: Record<string, ControlValue>;\n /** A preset's name to start from. */\n preset?: string;\n /** The host element's own `id`. */\n elementId?: string;\n}\n\n/**\n * Plays a catalog vector effect: `<VectorEffect id=\"drip-text\" values={{ word: \"Slop\" }} loop={false} />`.\n * Its `hold` and `trigger` are used unless given. With an `inline` control\n * set (drip text's), it is sized as a word in the line (see `inline`).\n * Throws when `id` is not a vector effect.\n */\nexport function VectorEffect(props: VectorEffectProps) {\n const { id, values, preset, elementId, ...rest } = props;\n const effect = typeof id === \"string\" ? getEffect(id) : id;\n if (!effect || effect.kind !== \"vector\")\n throw new Error(`\"${typeof id === \"string\" ? id : effect?.id}\" is not a vector effect; pick one from findEffects({ kind: \"vector\" })`);\n const valuesKey = JSON.stringify(values ?? {});\n const motion = useMemo(\n () => renderVectorEffect(effect, effectDefaults(effect, { preset, values })),\n // Values by content, so an inline object literal does not restart it.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [effect, preset, valuesKey],\n );\n return (\n <SvgPlayerView\n id={elementId}\n motion={motion}\n hold={effect.hold}\n trigger={effect.trigger}\n inline={values?.inline === true}\n {...rest}\n />\n );\n}\n\nexport type { SvgPlayer, SvgMotion, SvgTrigger };\n\nexport interface FlairEffectProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /** A catalog effect, by id or definition: shader or vector. */\n effect: string | EffectDefinition;\n /** What to apply it to: a picture, a video, an SVG, words or a path (see `EffectSubject`). */\n subject?: EffectSubject | readonly EffectSubject[];\n /** A preset by name, under `values`. */\n preset?: string;\n /** Control values over the effect's defaults (and preset). */\n values?: Record<string, ControlValue>;\n /** Start playing at once (default `true`). */\n autoplay?: boolean;\n /** Pause while scrolled out of view (default `true`). */\n pauseWhenHidden?: boolean;\n}\n\n/**\n * Any catalog effect, shader or vector, applied to a subject: a picture, a\n * video, an SVG, words or a path (see `mountEffect` in\n * `@pixerate/flair/embed`). Fills its element: size it with CSS. A new\n * subject or values update the running effect; a new effect, preset or\n * playback option starts it again. It renders nothing on the server.\n *\n * ```tsx\n * <FlairEffect effect=\"image-duotone\" subject={{ image: \"/team.jpg\" }} preset=\"Spotify\" style={{ aspectRatio: \"16 / 9\" }} />\n * <FlairEffect effect=\"typewriter\" subject={{ text: \"Hello there\" }} />\n * ```\n */\nexport function FlairEffect(props: FlairEffectProps) {\n const { effect, subject, preset, values, autoplay, pauseWhenHidden, style, ...rest } = props;\n const host = useRef<HTMLDivElement>(null);\n const mounted = useRef<MountedEffect | undefined>(undefined);\n const valuesKey = JSON.stringify(values ?? {});\n // What the running effect last took, so a re-render with the same subject or values changes nothing.\n const applied = useRef<{ subject?: FlairEffectProps[\"subject\"]; valuesKey: string }>({ valuesKey: \"\" });\n const latest = useRef({ subject, values, valuesKey });\n latest.current = { subject, values, valuesKey };\n useEffect(() => {\n const now = latest.current;\n const m = mountEffect(host.current!, effect, {\n ...(now.subject ? { subject: now.subject } : {}),\n ...(preset ? { preset } : {}),\n ...(now.values ? { values: now.values } : {}),\n autoplay: autoplay ?? true,\n pauseWhenHidden: pauseWhenHidden ?? true,\n });\n m.element.style.display = \"block\";\n m.element.style.width = \"100%\";\n m.element.style.height = \"100%\";\n mounted.current = m;\n applied.current = { subject: now.subject, valuesKey: now.valuesKey };\n return () => {\n m.dispose();\n mounted.current = undefined;\n };\n }, [effect, preset, autoplay, pauseWhenHidden]);\n useEffect(() => {\n if (!mounted.current || !subject || subject === applied.current.subject) return;\n mounted.current.setSubject(subject);\n applied.current.subject = subject;\n }, [subject]);\n useEffect(() => {\n if (!mounted.current || !values || valuesKey === applied.current.valuesKey) return;\n mounted.current.setValues(values);\n applied.current.valuesKey = valuesKey;\n }, [valuesKey]);\n return <div ref={host} style={{ position: \"relative\", ...style }} {...rest} />;\n}\n"],"mappings":"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","names":[]}
@@ -11,8 +11,8 @@ import {
11
11
  runRender,
12
12
  scriptedPointer,
13
13
  sequencePath
14
- } from "./chunk-JL6TI6II.js";
15
- import "./chunk-RDU63CDO.js";
14
+ } from "./chunk-7KYQ4R76.js";
15
+ import "./chunk-HDYBY3PQ.js";
16
16
  import "./chunk-VYOMXE6I.js";
17
17
  export {
18
18
  exportMotionGraphic,
@@ -28,4 +28,4 @@ export {
28
28
  scriptedPointer,
29
29
  sequencePath
30
30
  };
31
- //# sourceMappingURL=recorder-CDPPTJMG.js.map
31
+ //# sourceMappingURL=recorder-TYMEZHOT.js.map
@@ -168,6 +168,23 @@ declare const BUILTIN_UNIFORMS: readonly [{
168
168
  readonly type: "vec4";
169
169
  readonly size: 32;
170
170
  readonly doc: "where the pointer has just been over the canvas, newest first (about 30 points a second, so a second of path): xy = where (pixels), z = when (iTime seconds), w = 1 for a point, 0 for an empty slot";
171
+ }, {
172
+ readonly name: "iAudio";
173
+ readonly type: "vec4";
174
+ readonly doc: "the sound (see setAudioSource): x = level (loudness 0–1), y = bass, z = mid, w = treble (0–1, smoothed); all zero with no audio";
175
+ }, {
176
+ readonly name: "iSpectrum";
177
+ readonly type: "vec4";
178
+ readonly size: number;
179
+ readonly doc: "64 log-spaced frequency bins (about 30 Hz to 16 kHz), 0–1, four to a vec4, low to high: bin i is iSpectrum[i / 4][i % 4]";
180
+ }, {
181
+ readonly name: "iBeat";
182
+ readonly type: "float";
183
+ readonly doc: "seconds since the last beat (an onset on the bass), -1 before the first; 0 like every audio uniform when there is no audio at all";
184
+ }, {
185
+ readonly name: "iScroll";
186
+ readonly type: "vec4";
187
+ readonly doc: "scrolling: x = the canvas's progress through the viewport (0 as its top enters at the bottom, 1 as its bottom leaves at the top), y = page scroll (0–1), z = velocity (viewport heights per second, + down, smoothed), w = 1 while scrolling, easing to 0; all zero where nothing scrolls";
171
188
  }];
172
189
  /** A full-screen triangle from `gl_VertexID` alone: no buffers or attributes. */
173
190
  declare const VERTEX_SOURCE = "#version 300 es\nvoid main() {\n vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));\n gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);\n}\n";
@@ -209,7 +226,7 @@ declare function resetGlCaches(): void;
209
226
  * which uniform locations to look up.
210
227
  */
211
228
  declare function compileShader(gl: WebGL2RenderingContext, source: string, controls: ControlDef[]): CompileResult;
212
- /** What one frame shows: its size in pixels, its time and the pointer. */
229
+ /** What one frame shows: its size in pixels, its time, the pointer, the sound and the scroll. */
213
230
  interface FrameState {
214
231
  width: number;
215
232
  height: number;
@@ -227,6 +244,14 @@ interface FrameState {
227
244
  clicks?: Float32Array;
228
245
  /** `iTrail`: {@link MAX_TRAIL} × (x, y, time, 1), newest first (default none; see `trailUniform`). */
229
246
  trail?: Float32Array;
247
+ /** `iAudio`: level, bass, mid, treble (default all zero: no audio). */
248
+ audio?: readonly [number, number, number, number];
249
+ /** `iSpectrum`: {@link SPECTRUM_BINS} values, 0–1, low to high (default all zero). */
250
+ spectrum?: Float32Array;
251
+ /** `iBeat`: seconds since the last beat, -1 before the first (default 0, with no audio at all). */
252
+ beat?: number;
253
+ /** `iScroll`: progress, page, velocity, scrolling (default all zero: nothing scrolls it). */
254
+ scroll?: readonly [number, number, number, number];
230
255
  }
231
256
  /**
232
257
  * Draws one frame of a compiled shader into the bound framebuffer, from (0, 0).
@@ -244,6 +269,193 @@ declare function drawFrame(gl: WebGL2RenderingContext, shader: CompiledShader, c
244
269
  */
245
270
  declare function createContext(canvas: HTMLCanvasElement | OffscreenCanvas, preserveDrawingBuffer?: boolean, alpha?: boolean): WebGL2RenderingContext | null;
246
271
 
272
+ /**
273
+ * Sound as shaders see it: `iAudio` (level, bass, mid and treble, 0–1,
274
+ * smoothed), `iSpectrum[16]` (64 log-spaced frequency bins, 0–1, four to a
275
+ * `vec4`, low to high) and `iBeat` (seconds since the last beat on the bass,
276
+ * -1 before the first).
277
+ *
278
+ * {@link AudioAnalysis} turns frequency data (an `AnalyserNode`'s, or an
279
+ * FFT's) into those uniforms, deterministically for given data and times.
280
+ * {@link LiveAudio} feeds it from a playing media element, a microphone or
281
+ * any Web Audio node (what `ShaderRenderer.setAudioSource` uses), and
282
+ * {@link analyseAudioBuffer} from a decoded `AudioBuffer` offline, frame-exact
283
+ * for recordings.
284
+ *
285
+ * With no audio at all every audio uniform is zero, `iBeat` included, so an
286
+ * audio-reactive effect can tell silence from "nothing connected" (where
287
+ * `iBeat` is -1 or above 0) and play an idle demo instead.
288
+ */
289
+ /** How many frequency bins `iSpectrum` holds (16 `vec4`s). */
290
+ declare const SPECTRUM_BINS = 64;
291
+ /** One frame of the audio uniforms. */
292
+ interface AudioFrame {
293
+ /** `iAudio`: level (loudness, 0–1), bass, mid, treble (0–1), smoothed. */
294
+ audio: readonly [number, number, number, number];
295
+ /** `iSpectrum`: {@link SPECTRUM_BINS} values, 0–1, low to high (log-spaced). */
296
+ spectrum: Float32Array;
297
+ /** `iBeat`: seconds since the last beat, or -1 before the first. */
298
+ beat: number;
299
+ }
300
+ interface AudioAnalysisOptions {
301
+ /** The sample rate of the audio analysed, in Hz (default 48000). */
302
+ sampleRate?: number;
303
+ /** The FFT size the frequency data came from (default 2048: `fftSize / 2` values). */
304
+ fftSize?: number;
305
+ /** Decibels that read as 0 and 1 for float (dB) frequency data (default -100 and -30, as `AnalyserNode`). */
306
+ minDecibels?: number;
307
+ maxDecibels?: number;
308
+ /** Seconds a value takes to rise most of the way to a louder reading (default 0.03). */
309
+ attack?: number;
310
+ /** Seconds a value takes to fall most of the way to a quieter one (default 0.25). */
311
+ release?: number;
312
+ /** The lowest and highest frequencies the spectrum covers, in Hz (default 30 and 16000). */
313
+ minHz?: number;
314
+ maxHz?: number;
315
+ /**
316
+ * How far the bass must jump over its recent average to count as a beat,
317
+ * 0–1 (default 0.12). Lower finds more beats.
318
+ */
319
+ beatThreshold?: number;
320
+ /** The least time between two beats, in seconds (default 0.22: about 270 beats a minute). */
321
+ beatGap?: number;
322
+ }
323
+ /** The bands `iAudio` averages, in Hz. */
324
+ declare const AUDIO_BANDS: {
325
+ readonly bass: readonly [20, 250];
326
+ readonly mid: readonly [250, 2000];
327
+ readonly treble: readonly [2000, 16000];
328
+ };
329
+ /**
330
+ * Turns frequency data into the audio uniforms: 64 log-spaced bins, band
331
+ * averages, attack and release smoothing, a loudness level, and simple beat
332
+ * detection (onsets on the bass band, over its recent average). Feed it
333
+ * frequency data with the time it was read, in seconds, increasing; read
334
+ * {@link AudioAnalysis.frame} for the uniforms. The same data at the same
335
+ * times always gives the same uniforms.
336
+ *
337
+ * ```ts
338
+ * const analysis = new AudioAnalysis({ sampleRate: ctx.sampleRate, fftSize: analyser.fftSize });
339
+ * analyser.getFloatFrequencyData(freq);
340
+ * analysis.update(freq, performance.now() / 1000);
341
+ * const { audio, spectrum, beat } = analysis.frame(performance.now() / 1000);
342
+ * ```
343
+ */
344
+ declare class AudioAnalysis {
345
+ readonly sampleRate: number;
346
+ readonly fftSize: number;
347
+ private readonly minDb;
348
+ private readonly maxDb;
349
+ private readonly attack;
350
+ private readonly release;
351
+ private readonly beatThreshold;
352
+ private readonly beatGap;
353
+ /** Each spectrum bin's range of FFT bins, [from, to), fractional. */
354
+ private readonly ranges;
355
+ private readonly bands;
356
+ private readonly raw;
357
+ private readonly smoothed;
358
+ private values;
359
+ private last;
360
+ private bassAverage;
361
+ private armed;
362
+ private lastBeat;
363
+ private normalised;
364
+ constructor(options?: AudioAnalysisOptions);
365
+ /** Forgets everything heard: the next update starts afresh. */
366
+ reset(): void;
367
+ /**
368
+ * Takes one reading at `time` seconds: `frequency` is `fftSize / 2`
369
+ * values, bytes 0–255 (`getByteFrequencyData`) or decibels
370
+ * (`getFloatFrequencyData`). `waveform` (time-domain samples, -1–1, or
371
+ * bytes centred on 128) gives the level its RMS; without it the level
372
+ * comes from the spectrum.
373
+ */
374
+ update(frequency: Uint8Array | Float32Array, time: number, waveform?: Uint8Array | Float32Array): void;
375
+ /** The uniforms after the latest update, with `iBeat` measured to `time` (default the latest update's). */
376
+ frame(time?: number): AudioFrame;
377
+ }
378
+ /** Loudness from an RMS amplitude: 0 at -60 dBFS (or below), 1 at full scale. */
379
+ declare function rmsLevel(rms: number): number;
380
+ /**
381
+ * An in-place radix-2 FFT of `re` and `im` (their length a power of two).
382
+ * Forward, unscaled: `X[k] = Σ x[n] e^(-2πikn/N)`.
383
+ */
384
+ declare function fft(re: Float32Array | Float64Array, im: Float32Array | Float64Array): void;
385
+ /**
386
+ * The decibel spectrum of the `fftSize` samples of `samples` ending at
387
+ * `end` (exclusive; samples before the start are silence), as
388
+ * `AnalyserNode.getFloatFrequencyData` gives it with no smoothing: a
389
+ * Blackman window, an FFT, magnitudes over `fftSize`, in dB. Writes
390
+ * `fftSize / 2` values into `out`, and the window's samples into `waveform`
391
+ * when given.
392
+ */
393
+ declare function frequencyData(samples: Float32Array, end: number, fftSize: number, out: Float32Array, waveform?: Float32Array): void;
394
+ /** What {@link analyseAudioBuffer} reads: an `AudioBuffer`, or anything shaped like one. */
395
+ interface AudioSamples {
396
+ sampleRate: number;
397
+ length: number;
398
+ numberOfChannels: number;
399
+ getChannelData(channel: number): Float32Array;
400
+ }
401
+ interface AudioBufferAnalysisOptions extends Omit<AudioAnalysisOptions, "sampleRate"> {
402
+ /** Readings per second (default 120). Recordings sample the reading at or just before each frame. */
403
+ rate?: number;
404
+ }
405
+ /** The audio uniforms of a whole decoded track, at any time (see {@link analyseAudioBuffer}). */
406
+ interface AudioBufferAnalysis {
407
+ /** The track's length in seconds. */
408
+ readonly duration: number;
409
+ /** The uniforms at `time` seconds into the track (silence before 0 and after the end). */
410
+ at(time: number): AudioFrame;
411
+ }
412
+ /**
413
+ * Analyses a decoded track offline, so a recording of an audio-reactive
414
+ * effect follows the sound frame for frame: the uniforms at time `t` come
415
+ * from an FFT of the samples just before `t`, as a live `AnalyserNode`
416
+ * would hear them, through an {@link AudioAnalysis} stepped at a fixed
417
+ * `rate` from the start of the track. Readings are worked out as far as
418
+ * asked and kept, so asking for frames in any order gives the same answer.
419
+ *
420
+ * ```ts
421
+ * const track = analyseAudioBuffer(await ctx.decodeAudioData(bytes));
422
+ * frames.render(t, { audio: track.at(t) });
423
+ * ```
424
+ */
425
+ declare function analyseAudioBuffer(buffer: AudioSamples, options?: AudioBufferAnalysisOptions): AudioBufferAnalysis;
426
+ /** What `ShaderRenderer.setAudioSource` listens to: a media element (it keeps playing to the speakers), a stream (a microphone: not played) or any Web Audio node. */
427
+ type AudioSource = HTMLMediaElement | MediaStream | AudioNode;
428
+ /**
429
+ * The `AudioContext` Flair listens with, made on first use and shared by
430
+ * every renderer (browsers limit how many a page may have). Make nodes in it
431
+ * to pass to `setAudioSource` (an oscillator, a decoded buffer). Browsers
432
+ * start it suspended until the page has had a click or key press: Flair
433
+ * resumes it on the first one, or call `resume()` from your own gesture.
434
+ * Closing it is allowed: the next use makes a new one.
435
+ */
436
+ declare function sharedAudioContext(): AudioContext;
437
+ /**
438
+ * Listens to a live {@link AudioSource} through an `AnalyserNode` (in the
439
+ * shared context, or the node's own) and reads the audio uniforms from it.
440
+ * A media element keeps playing to the speakers through Web Audio; a stream
441
+ * (a microphone) is only listened to. A media element from another site
442
+ * needs `crossOrigin = "anonymous"` and CORS, or the browser feeds silence.
443
+ */
444
+ declare class LiveAudio {
445
+ readonly context: BaseAudioContext;
446
+ readonly analyser: AnalyserNode;
447
+ private readonly analysis;
448
+ private readonly input;
449
+ private readonly ownsInput;
450
+ private readonly freq;
451
+ private readonly wave;
452
+ constructor(source: AudioSource, options?: AudioAnalysisOptions);
453
+ /** Reads the analyser now and returns the uniforms, `time` in seconds (a steady clock, such as `performance.now() / 1000`). */
454
+ read(time: number): AudioFrame;
455
+ /** Stops listening. A media element goes on playing to the speakers. */
456
+ dispose(): void;
457
+ }
458
+
247
459
  /** Whether the latest source compiled, and if not, why. */
248
460
  interface CompileStatus {
249
461
  ok: boolean;
@@ -304,6 +516,17 @@ declare class ShaderRenderer {
304
516
  private mouse;
305
517
  /** `iPointer` and `iClicks`. */
306
518
  private pointer;
519
+ /** `iAudio`, `iSpectrum` and `iBeat`, from `setAudioSource`. */
520
+ private audio;
521
+ private audioFrame;
522
+ /** `iScroll`. */
523
+ private scrollTracker;
524
+ private scrollOverride;
525
+ private scrollMoved;
526
+ /** The page clock's latest reading, in seconds, for `iScroll`'s easing. */
527
+ private scrollNow;
528
+ /** `iScroll` as last drawn, so a paused preview redraws only when it changes. */
529
+ private drawnScroll;
307
530
  private disposed;
308
531
  /** The latest source's status. */
309
532
  status: CompileStatus;
@@ -346,6 +569,30 @@ declare class ShaderRenderer {
346
569
  * agents keep seeing the URL.
347
570
  */
348
571
  setLiveSource(name: string, source: LiveSource | null): void;
572
+ /**
573
+ * Feeds `iAudio`, `iSpectrum` and `iBeat` from a sound (`null` stops):
574
+ * - an `<audio>` or `<video>` element: it keeps playing to the speakers,
575
+ * now through Web Audio (an element can only be wired in once, so the
576
+ * same node is reused by every renderer that listens to it);
577
+ * - a `MediaStream`, such as a microphone from `getUserMedia`: listened
578
+ * to, not played;
579
+ * - any Web Audio node (an oscillator, a gain, a player's output), in its
580
+ * own context.
581
+ *
582
+ * The `AudioContext` and `AnalyserNode` are made on first use (see
583
+ * `sharedAudioContext`). Browsers keep a new context suspended until the
584
+ * page has a click or key press (autoplay policy): call this from one, or
585
+ * the context resumes on the first. While a source is set, a paused
586
+ * preview still follows the sound. A media element from another site needs
587
+ * `crossOrigin = "anonymous"` and CORS, or the browser feeds silence.
588
+ */
589
+ setAudioSource(source: AudioSource | null): void;
590
+ /**
591
+ * Pins `iScroll` to a scroll position of your own (0–1, for x and y; the
592
+ * velocity and "scrolling" follow its changes), for scrubbers and hosts
593
+ * that scroll in their own way; `null` goes back to following the page.
594
+ */
595
+ setScrollProgress(progress: number | null): void;
349
596
  /**
350
597
  * Settles once the images the current controls show have loaded, and
351
598
  * every live video has a frame. Rejects with an {@link ImageLoadError}
@@ -379,6 +626,9 @@ declare class ShaderRenderer {
379
626
  private syncTextures;
380
627
  private tick;
381
628
  private emit;
629
+ /** Where the canvas is in the viewport (or the pinned progress), into the scroll tracker. */
630
+ private sampleScroll;
631
+ private onScroll;
382
632
  /** Pointer position in shader pixels (origin bottom-left, like gl_FragCoord). */
383
633
  private toPixels;
384
634
  private onPointerDown;
@@ -390,4 +640,4 @@ declare class ShaderRenderer {
390
640
  private onRestored;
391
641
  }
392
642
 
393
- export { BUILTIN_UNIFORMS as B, type CompileError as C, type FrameState as F, ImageLoadError as I, type LiveSource as L, type RendererStats as R, ShaderRenderer as S, TextureCache as T, VERTEX_SOURCE as V, type CompileStatus as a, type CompileResult as b, type CompiledShader as c, SVG_EDGE as d, ShaderCompileError as e, type ShaderRendererOptions as f, type TextureSource as g, applyTextureSettings as h, compileShader as i, createContext as j, drawFrame as k, formatCompileError as l, isSvgUrl as m, loadImage as n, preloadImages as o, parseCompileLog as p, resetGlCaches as q, releaseImages as r, seekVideo as s, sizeSvg as t, wrapSource as w };
643
+ export { type AudioSource as A, BUILTIN_UNIFORMS as B, type CompileError as C, parseCompileLog as D, preloadImages as E, type FrameState as F, releaseImages as G, resetGlCaches as H, ImageLoadError as I, rmsLevel as J, seekVideo as K, type LiveSource as L, sharedAudioContext as M, sizeSvg as N, wrapSource as O, type RendererStats as R, ShaderRenderer as S, TextureCache as T, VERTEX_SOURCE as V, type AudioFrame as a, type CompileStatus as b, AUDIO_BANDS as c, AudioAnalysis as d, type AudioAnalysisOptions as e, type AudioBufferAnalysis as f, type AudioBufferAnalysisOptions as g, type AudioSamples as h, type CompileResult as i, type CompiledShader as j, LiveAudio as k, SPECTRUM_BINS as l, SVG_EDGE as m, ShaderCompileError as n, type ShaderRendererOptions as o, type TextureSource as p, analyseAudioBuffer as q, applyTextureSettings as r, compileShader as s, createContext as t, drawFrame as u, fft as v, formatCompileError as w, frequencyData as x, isSvgUrl as y, loadImage as z };
@@ -1,10 +1,10 @@
1
1
  import { ActionReturn } from 'svelte/action';
2
2
  import { C as ControlValue } from '../controls-Ec6P0Vpy.js';
3
- import { a as SvgTrigger, C as Clock, b as SvgPlayer, S as SvgMotion } from '../svg-BhRboN9q.js';
4
- import { E as EffectDefinition, V as VectorEffect } from '../types-DUOMxZaq.js';
3
+ import { a as SvgTrigger, C as Clock, b as SvgPlayer, S as SvgMotion } from '../svg-DaKRPA9v.js';
4
+ import { E as EffectDefinition, V as VectorEffect } from '../types-wh28fXW4.js';
5
5
  import { EffectSubject } from '../embed/index.js';
6
- import '../renderer-teaa98RE.js';
7
- import '../frames-VEcqa40g.js';
6
+ import '../renderer-tk1nbVkW.js';
7
+ import '../frames-CGJudAbu.js';
8
8
 
9
9
  /**
10
10
  * Svelte 5 bindings for Flair, written as plain TypeScript so they need no
@@ -2,22 +2,22 @@ import {
2
2
  svgFrameMarkup,
3
3
  svgInlineLayout,
4
4
  svgViewBox
5
- } from "../chunk-STKEKXCK.js";
5
+ } from "../chunk-AUH3PEFW.js";
6
6
  import {
7
7
  mountEffect
8
- } from "../chunk-VLQLRKGW.js";
9
- import "../chunk-H6VDFBIO.js";
10
- import "../chunk-RDU63CDO.js";
8
+ } from "../chunk-KZYZX3V5.js";
9
+ import "../chunk-SMR2MJXB.js";
10
+ import "../chunk-HDYBY3PQ.js";
11
11
  import {
12
12
  effectDefaults,
13
13
  getEffect,
14
14
  renderVectorEffect
15
- } from "../chunk-4LKAPRFM.js";
15
+ } from "../chunk-6GFOLHCT.js";
16
16
  import "../chunk-EHDJ3CPJ.js";
17
17
  import "../chunk-VYOMXE6I.js";
18
18
  import {
19
19
  SvgPlayer
20
- } from "../chunk-PFGGPIMT.js";
20
+ } from "../chunk-DA4UUSDO.js";
21
21
  import "../chunk-WMZQCUFJ.js";
22
22
  import "../chunk-ORAFCHX3.js";
23
23
 
@@ -62,7 +62,7 @@ function hostStyle(svg, inline, contain) {
62
62
  }
63
63
  var enters = (r, p) => p.playOnEnter ?? (p.loop === false && !r.trigger);
64
64
  function stillMarkup(r, params) {
65
- const at = params.poster ?? (r.trigger === "hover" || r.trigger === "press" ? "first" : "last");
65
+ const at = params.poster ?? (r.trigger === "hover" || r.trigger === "press" || r.trigger === "scroll" ? "first" : "last");
66
66
  return svgFrameMarkup(r.motion, { at, steps: params.steps }) ?? "";
67
67
  }
68
68
  function vectorEffectStill(params) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/svelte/index.ts"],"sourcesContent":["/**\n * Svelte 5 bindings for Flair, written as plain TypeScript so they need no\n * Svelte compiler: the `flairEffect` action runs any catalog effect (shader or\n * vector) applied to a picture, video, SVG or words; the `vectorEffect` action plays a catalog\n * effect (or any {@link SvgMotion}) in an element, and `vectorEffectStill`\n * gives the markup and style of its still for server rendering.\n *\n * ```svelte\n * <script>\n * import { vectorEffect, vectorEffectStill } from \"@pixerate/flair/svelte\";\n * const params = { id: \"drip-text\", values: { word: \"Slop\" }, loop: false };\n * const still = vectorEffectStill(params);\n * </script>\n *\n * <span use:vectorEffect={params} style={still.style}>{@html still.markup}</span>\n * ```\n *\n * The still is in place from the first paint; the live `SvgPlayer` takes over\n * after mount and is disposed on destroy. Under `prefers-reduced-motion:\n * reduce` the still stays.\n *\n * Needs Svelte 5 (an optional peer dependency of the package); only its types\n * are imported.\n *\n * @packageDocumentation\n */\n\nimport type { ActionReturn } from \"svelte/action\";\nimport { getEffect, effectDefaults, type EffectDefinition } from \"../catalog/index.js\";\nimport { mountEffect, type EffectSubject } from \"../embed/index.js\";\nimport { renderVectorEffect, type VectorEffect as VectorEffectDefinition } from \"../catalog/types.js\";\nimport type { ControlValue } from \"../shader/controls.js\";\nimport type { Clock } from \"../time/clock.js\";\nimport type { SvgMotion } from \"../vector/motion.js\";\nimport { svgFrameMarkup, svgInlineLayout, svgViewBox } from \"../vector/static.js\";\nimport { SvgPlayer, type SvgTrigger } from \"../vector/svg.js\";\n\n/** Cap height of a heavy sans, as a share of its font size: what an inline word is framed to. */\nconst CAP_HEIGHT = \"0.72em\";\n\n/** Marks the still's layer, so the action can find it (and hide it while the player runs). */\nconst STILL_ATTR = \"data-flair-still\";\n\nconst LAYER = \"position:absolute;inset:0;display:block\";\n\n/** When to leave the still in place of the animation. */\nexport type ReducedMotion = \"user\" | \"always\" | \"never\";\n\n/** Options shared by an effect by id and a motion of your own. */\nexport interface SvgPlayerParams {\n /** Start again at the end (default `true`); `false` plays once and stays on the last frame. */\n loop?: boolean;\n /** Start playing at once (default `true`). */\n autoplay?: boolean;\n /** Playback speed (default 1); changed without remaking the player. */\n speed?: number;\n /** Ms to rest on the last frame before looping (default 600, or the effect's). */\n hold?: number;\n /** Loop length in ms (default: the picture's own, plus `hold`). */\n duration?: number;\n /** Step start times in ms, for pictures that change on steps. */\n steps?: readonly number[];\n /** Plays in answer to the pointer (see `SvgTrigger`; default the effect's). */\n trigger?: SvgTrigger;\n /** Pause while scrolled out of view. */\n pauseWhenHidden?: boolean;\n /**\n * Wait to start until scrolled into view. Default `true` when it plays once\n * (`loop: false`, no trigger), so a one-shot further down the page does not\n * finish before anyone sees it; `false` plays at mount.\n */\n playOnEnter?: boolean;\n /** Hide the picture while paused or reset. */\n hideWhenPaused?: boolean;\n /** Time source (default animation frames); a `ManualClock` steps frames yourself. */\n clock?: Clock;\n /** Called with the id of the innermost element with an id that is clicked. */\n onPick?: (id: string) => void;\n /** Called with the player once it is made, and `undefined` when it is disposed. */\n onPlayer?: (player: SvgPlayer | undefined) => void;\n /**\n * The still shown before the player starts and instead of it under reduced\n * motion: `\"last\"` (default; the rest frame, `\"first\"` for a hover or press\n * trigger), `\"first\"` or a time in ms.\n */\n poster?: \"first\" | \"last\" | number;\n /**\n * `\"user\"` (default) shows the still when the visitor prefers reduced\n * motion, `\"always\"` always does, `\"never\"` always plays.\n */\n reducedMotion?: ReducedMotion;\n /**\n * Size it as a word in a line of text: `display: inline-block` with the\n * viewBox's aspect ratio and `height` 0.72em (the cap height of a heavy\n * sans; pass a CSS length for another). Default: the effect's `inline`\n * control. Otherwise it is a block as wide as its container with the\n * viewBox's aspect ratio.\n */\n inline?: boolean | string;\n /**\n * With `inline`, keep room for what spills past the word (descenders,\n * outlines, glows, drips): a margin of the picture's `data-overhang` (see\n * `svgInlineLayout`), so the lines around it make space instead of being\n * painted over. Default `false`.\n */\n contain?: boolean;\n}\n\n/** A catalog vector effect by id (or the effect itself), with its values. */\nexport interface VectorEffectByIdParams extends SvgPlayerParams {\n /** A vector effect's catalog id (`\"drip-text\"`), or the effect itself. */\n id: string | VectorEffectDefinition;\n /** Control values; missing ones take the preset's, then the defaults. Compared by content. */\n values?: Record<string, ControlValue>;\n /** A preset's name to start from. */\n preset?: string;\n motion?: never;\n}\n\n/** Any {@link SvgMotion}; a new object restarts the player. */\nexport interface SvgMotionParams extends SvgPlayerParams {\n /** The picture and its animations. */\n motion: SvgMotion;\n id?: never;\n values?: never;\n preset?: never;\n}\n\n/** What `vectorEffect` and `vectorEffectStill` take: an effect by `id`, or a `motion`. */\nexport type VectorEffectParams = VectorEffectByIdParams | SvgMotionParams;\n\n/** A still of the picture, for `{@html markup}` inside an element with `style`. */\nexport interface VectorEffectStill {\n /** The still's layer: safe `<svg>` markup (see `svgFrameMarkup`) in an absolutely placed `<span>`. */\n markup: string;\n /** The host's inline style: its position and size from the viewBox. */\n style: string;\n}\n\ninterface Resolved {\n motion: SvgMotion;\n hold?: number;\n trigger?: SvgTrigger;\n inline?: boolean | string;\n contain?: boolean;\n}\n\n/** Remembers the last motion made from an effect, so values compared by content do not remake it. */\ntype MotionCache = { key: string; effect: VectorEffectDefinition; motion: SvgMotion };\n\nfunction resolve(params: VectorEffectParams, cache?: { current?: MotionCache }): Resolved {\n if (params.motion) return { motion: params.motion, hold: params.hold, trigger: params.trigger, inline: params.inline, contain: params.contain };\n const { id, values, preset } = params as VectorEffectByIdParams;\n const effect = typeof id === \"string\" ? getEffect(id) : id;\n if (!effect || effect.kind !== \"vector\")\n throw new Error(\n `\"${typeof id === \"string\" ? id : (effect?.id ?? id)}\" is not a vector effect; pick one from findEffects({ kind: \"vector\" }), or pass a motion`,\n );\n const key = JSON.stringify([preset ?? null, values ?? {}]);\n let motion: SvgMotion;\n if (cache?.current && cache.current.effect === effect && cache.current.key === key) motion = cache.current.motion;\n else {\n motion = renderVectorEffect(effect, effectDefaults(effect, { preset, values }));\n if (cache) cache.current = { key, effect, motion };\n }\n return {\n motion,\n hold: params.hold ?? effect.hold,\n trigger: params.trigger ?? effect.trigger,\n inline: params.inline ?? values?.inline === true,\n contain: params.contain,\n };\n}\n\n/** The host's sizing from the picture's viewBox, so the still and the player take the same room. */\nfunction hostStyle(svg: string, inline: boolean | string | undefined, contain?: boolean): [string, string][] {\n const box = svgViewBox(svg);\n const style: [string, string][] = [\n [\"position\", \"relative\"],\n [\"display\", inline ? \"inline-block\" : \"block\"],\n ];\n if (inline) style.push([\"height\", typeof inline === \"string\" ? inline : CAP_HEIGHT]);\n if (box) style.push([\"aspect-ratio\", `${box[2]} / ${box[3]}`]);\n const spill = inline && contain ? svgInlineLayout(svg)?.overhang : undefined;\n if (spill) style.push([\"margin\", `${spill.top}em ${spill.right}em ${spill.bottom}em ${spill.left}em`]);\n return style;\n}\n\n/** Whether to wait until scrolled into view: as asked, else when it plays once. */\nconst enters = (r: Resolved, p: SvgPlayerParams) => p.playOnEnter ?? (p.loop === false && !r.trigger);\n\nfunction stillMarkup(r: Resolved, params: SvgPlayerParams): string {\n const at = params.poster ?? (r.trigger === \"hover\" || r.trigger === \"press\" ? \"first\" : \"last\");\n return svgFrameMarkup(r.motion, { at, steps: params.steps }) ?? \"\";\n}\n\n/**\n * The still of an effect or motion, for server rendering: put `markup` in the\n * element that has the `vectorEffect` action, with `style` as its style, so\n * the page shows the picture before the player starts (and instead of it\n * under reduced motion), in the room the player will take.\n * Throws when `id` is not a vector effect.\n */\nexport function vectorEffectStill(params: VectorEffectParams): VectorEffectStill {\n const r = resolve(params);\n return {\n markup: `<span ${STILL_ATTR} aria-hidden=\"true\" style=\"${LAYER}\">${stillMarkup(r, params)}</span>`,\n style: hostStyle(r.motion.svg, r.inline, r.contain)\n .map(([k, v]) => `${k}: ${v}`)\n .join(\"; \"),\n };\n}\n\nfunction prefersReducedMotion(): boolean {\n return typeof matchMedia === \"function\" && matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n}\n\n/** The options whose change remakes the player (`speed` is set on it instead). */\nfunction playerKey(r: Resolved, p: SvgPlayerParams): unknown[] {\n return [\n r.motion,\n p.loop,\n p.autoplay,\n r.hold,\n p.duration,\n p.steps?.join(\",\"),\n r.trigger,\n p.pauseWhenHidden,\n enters(r, p),\n p.hideWhenPaused,\n p.clock,\n p.reducedMotion ?? \"user\",\n ];\n}\n\n/**\n * A Svelte action that plays a vector effect in its element:\n * `<span use:vectorEffect={{ id: \"drip-text\", values: { word: \"Slop\" }, loop: false }} />`.\n *\n * It makes an `SvgPlayer` in a layer of its own, remakes it when the params\n * change (values by content; `speed` without remaking) and disposes it on\n * destroy. A still from {@link vectorEffectStill} in the element is hidden\n * while the player runs and shown again after; without one, the action makes\n * its own (so reduced motion still shows the picture) and sizes the element\n * from the viewBox, leaving alone any of `position`, `display`, `height`,\n * `aspect-ratio` and `margin` the element's style already sets.\n *\n * For an attachment, `{@attach fromAction(vectorEffect, () => params)}`\n * (`fromAction` is from `svelte/attachments`). Throws when `id` is not a\n * vector effect.\n */\nexport function vectorEffect(node: HTMLElement, initial: VectorEffectParams): ActionReturn<VectorEffectParams> {\n const cache: { current?: MotionCache } = {};\n const doc = node.ownerDocument;\n const mount = doc.createElement(\"span\");\n mount.style.cssText = LAYER;\n node.appendChild(mount);\n\n let params = initial;\n let resolved = resolve(params, cache);\n let player: SvgPlayer | undefined;\n let key: unknown[] = [];\n /** The still the action made itself, when the element had none. */\n let ownStill: HTMLElement | undefined;\n /** Sizing properties the action set (the element's own style had them empty). */\n const sized = new Set<string>();\n\n const stills = () => node.querySelectorAll<HTMLElement>(`:scope > [${STILL_ATTR}]`);\n const showStills = (show: boolean) => {\n for (const s of stills()) s.style.display = show ? \"block\" : \"none\";\n };\n // When the params change Svelte replaces a server still (`{@html}`), and when\n // it is the element's only child, by setting `innerHTML`, which takes the\n // player's layer out too: put the layer back, and keep a new still hidden.\n const reattach = () => {\n if (mount.parentNode !== node) node.appendChild(mount);\n if (player) showStills(false);\n };\n const observer = typeof MutationObserver === \"function\" ? new MutationObserver(reattach) : undefined;\n observer?.observe(node, { childList: true });\n\n const size = () => {\n const style = hostStyle(resolved.motion.svg, resolved.inline, resolved.contain);\n for (const [prop, value] of style) {\n if (!sized.has(prop) && node.style.getPropertyValue(prop)) continue;\n node.style.setProperty(prop, value);\n sized.add(prop);\n }\n // A property the new sizing drops (`height` when no longer inline) is cleared.\n for (const prop of sized)\n if (!style.some(([p]) => p === prop)) {\n node.style.removeProperty(prop);\n sized.delete(prop);\n }\n };\n\n if (stills().length === 0) {\n ownStill = doc.createElement(\"span\");\n ownStill.setAttribute(STILL_ATTR, \"\");\n ownStill.setAttribute(\"aria-hidden\", \"true\");\n ownStill.style.cssText = LAYER;\n node.insertBefore(ownStill, mount);\n size();\n }\n\n const stop = () => {\n if (!player) return;\n player.dispose();\n player = undefined;\n showStills(true);\n params.onPlayer?.(undefined);\n };\n\n const start = () => {\n const reduced = params.reducedMotion ?? \"user\";\n if (reduced === \"always\" || (reduced === \"user\" && prefersReducedMotion())) return;\n player = new SvgPlayer(mount, resolved.motion, {\n loop: params.loop,\n autoplay: params.autoplay,\n speed: params.speed,\n hold: resolved.hold,\n duration: params.duration,\n steps: params.steps,\n trigger: resolved.trigger,\n pauseWhenHidden: params.pauseWhenHidden,\n playOnEnter: enters(resolved, params),\n hideWhenPaused: params.hideWhenPaused,\n clock: params.clock,\n // The latest params' callback, without remaking the player.\n onPick: (id) => params.onPick?.(id),\n });\n showStills(false);\n params.onPlayer?.(player);\n };\n\n const apply = () => {\n reattach();\n if (ownStill) {\n ownStill.innerHTML = stillMarkup(resolved, params);\n size();\n }\n const next = playerKey(resolved, params);\n if (player && next.every((v, i) => v === key[i])) {\n player.setSpeed(params.speed ?? 1);\n return;\n }\n key = next;\n stop();\n start();\n };\n\n apply();\n\n return {\n update(next) {\n params = next;\n resolved = resolve(params, cache);\n apply();\n },\n destroy() {\n observer?.disconnect();\n stop();\n mount.remove();\n ownStill?.remove();\n for (const prop of sized) node.style.removeProperty(prop);\n },\n };\n}\n\nexport type { SvgPlayer, SvgMotion, SvgTrigger };\n\n/** What the `flairEffect` action runs: any catalog effect, applied to a subject. */\nexport interface FlairEffectParams {\n /** A catalog effect, by id or definition: shader or vector. */\n effect: string | EffectDefinition;\n /** What to apply it to: a picture, a video, an SVG, words or a path (see `EffectSubject`). */\n subject?: EffectSubject | readonly EffectSubject[];\n preset?: string;\n values?: Record<string, ControlValue>;\n /** Start playing at once (default `true`). */\n autoplay?: boolean;\n /** Pause while scrolled out of view (default `true`). */\n pauseWhenHidden?: boolean;\n}\n\n/**\n * Runs any catalog effect, shader or vector, in an element, applied to a\n * subject (see `mountEffect` in `@pixerate/flair/embed`); it fills the\n * element, so size it with CSS. A new subject or values update the running\n * effect; a new effect, preset or playback option starts it again.\n *\n * ```svelte\n * <div use:flairEffect={{ effect: \"image-duotone\", subject: { image: \"/team.jpg\" } }} style=\"aspect-ratio: 16 / 9\"></div>\n * ```\n */\nexport function flairEffect(node: HTMLElement, initial: FlairEffectParams): ActionReturn<FlairEffectParams> {\n let params = initial;\n if (!node.style.position) node.style.position = \"relative\";\n const start = () => {\n const m = mountEffect(node, params.effect, {\n ...(params.subject ? { subject: params.subject } : {}),\n ...(params.preset ? { preset: params.preset } : {}),\n ...(params.values ? { values: params.values } : {}),\n autoplay: params.autoplay ?? true,\n pauseWhenHidden: params.pauseWhenHidden ?? true,\n });\n m.element.style.display = \"block\";\n m.element.style.width = \"100%\";\n m.element.style.height = \"100%\";\n return m;\n };\n let mounted = start();\n return {\n update(next) {\n const restart =\n next.effect !== params.effect || next.preset !== params.preset || next.autoplay !== params.autoplay || next.pauseWhenHidden !== params.pauseWhenHidden;\n const subjectChanged = next.subject !== params.subject;\n const valuesChanged = JSON.stringify(next.values ?? {}) !== JSON.stringify(params.values ?? {});\n params = next;\n if (restart) {\n mounted.dispose();\n mounted = start();\n return;\n }\n if (subjectChanged && next.subject) mounted.setSubject(next.subject);\n if (valuesChanged && next.values) mounted.setValues(next.values);\n },\n destroy() {\n mounted.dispose();\n },\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/svelte/index.ts"],"sourcesContent":["/**\n * Svelte 5 bindings for Flair, written as plain TypeScript so they need no\n * Svelte compiler: the `flairEffect` action runs any catalog effect (shader or\n * vector) applied to a picture, video, SVG or words; the `vectorEffect` action plays a catalog\n * effect (or any {@link SvgMotion}) in an element, and `vectorEffectStill`\n * gives the markup and style of its still for server rendering.\n *\n * ```svelte\n * <script>\n * import { vectorEffect, vectorEffectStill } from \"@pixerate/flair/svelte\";\n * const params = { id: \"drip-text\", values: { word: \"Slop\" }, loop: false };\n * const still = vectorEffectStill(params);\n * </script>\n *\n * <span use:vectorEffect={params} style={still.style}>{@html still.markup}</span>\n * ```\n *\n * The still is in place from the first paint; the live `SvgPlayer` takes over\n * after mount and is disposed on destroy. Under `prefers-reduced-motion:\n * reduce` the still stays.\n *\n * Needs Svelte 5 (an optional peer dependency of the package); only its types\n * are imported.\n *\n * @packageDocumentation\n */\n\nimport type { ActionReturn } from \"svelte/action\";\nimport { getEffect, effectDefaults, type EffectDefinition } from \"../catalog/index.js\";\nimport { mountEffect, type EffectSubject } from \"../embed/index.js\";\nimport { renderVectorEffect, type VectorEffect as VectorEffectDefinition } from \"../catalog/types.js\";\nimport type { ControlValue } from \"../shader/controls.js\";\nimport type { Clock } from \"../time/clock.js\";\nimport type { SvgMotion } from \"../vector/motion.js\";\nimport { svgFrameMarkup, svgInlineLayout, svgViewBox } from \"../vector/static.js\";\nimport { SvgPlayer, type SvgTrigger } from \"../vector/svg.js\";\n\n/** Cap height of a heavy sans, as a share of its font size: what an inline word is framed to. */\nconst CAP_HEIGHT = \"0.72em\";\n\n/** Marks the still's layer, so the action can find it (and hide it while the player runs). */\nconst STILL_ATTR = \"data-flair-still\";\n\nconst LAYER = \"position:absolute;inset:0;display:block\";\n\n/** When to leave the still in place of the animation. */\nexport type ReducedMotion = \"user\" | \"always\" | \"never\";\n\n/** Options shared by an effect by id and a motion of your own. */\nexport interface SvgPlayerParams {\n /** Start again at the end (default `true`); `false` plays once and stays on the last frame. */\n loop?: boolean;\n /** Start playing at once (default `true`). */\n autoplay?: boolean;\n /** Playback speed (default 1); changed without remaking the player. */\n speed?: number;\n /** Ms to rest on the last frame before looping (default 600, or the effect's). */\n hold?: number;\n /** Loop length in ms (default: the picture's own, plus `hold`). */\n duration?: number;\n /** Step start times in ms, for pictures that change on steps. */\n steps?: readonly number[];\n /** Plays in answer to the pointer (see `SvgTrigger`; default the effect's). */\n trigger?: SvgTrigger;\n /** Pause while scrolled out of view. */\n pauseWhenHidden?: boolean;\n /**\n * Wait to start until scrolled into view. Default `true` when it plays once\n * (`loop: false`, no trigger), so a one-shot further down the page does not\n * finish before anyone sees it; `false` plays at mount.\n */\n playOnEnter?: boolean;\n /** Hide the picture while paused or reset. */\n hideWhenPaused?: boolean;\n /** Time source (default animation frames); a `ManualClock` steps frames yourself. */\n clock?: Clock;\n /** Called with the id of the innermost element with an id that is clicked. */\n onPick?: (id: string) => void;\n /** Called with the player once it is made, and `undefined` when it is disposed. */\n onPlayer?: (player: SvgPlayer | undefined) => void;\n /**\n * The still shown before the player starts and instead of it under reduced\n * motion: `\"last\"` (default; the rest frame, `\"first\"` for a hover or press\n * trigger), `\"first\"` or a time in ms.\n */\n poster?: \"first\" | \"last\" | number;\n /**\n * `\"user\"` (default) shows the still when the visitor prefers reduced\n * motion, `\"always\"` always does, `\"never\"` always plays.\n */\n reducedMotion?: ReducedMotion;\n /**\n * Size it as a word in a line of text: `display: inline-block` with the\n * viewBox's aspect ratio and `height` 0.72em (the cap height of a heavy\n * sans; pass a CSS length for another). Default: the effect's `inline`\n * control. Otherwise it is a block as wide as its container with the\n * viewBox's aspect ratio.\n */\n inline?: boolean | string;\n /**\n * With `inline`, keep room for what spills past the word (descenders,\n * outlines, glows, drips): a margin of the picture's `data-overhang` (see\n * `svgInlineLayout`), so the lines around it make space instead of being\n * painted over. Default `false`.\n */\n contain?: boolean;\n}\n\n/** A catalog vector effect by id (or the effect itself), with its values. */\nexport interface VectorEffectByIdParams extends SvgPlayerParams {\n /** A vector effect's catalog id (`\"drip-text\"`), or the effect itself. */\n id: string | VectorEffectDefinition;\n /** Control values; missing ones take the preset's, then the defaults. Compared by content. */\n values?: Record<string, ControlValue>;\n /** A preset's name to start from. */\n preset?: string;\n motion?: never;\n}\n\n/** Any {@link SvgMotion}; a new object restarts the player. */\nexport interface SvgMotionParams extends SvgPlayerParams {\n /** The picture and its animations. */\n motion: SvgMotion;\n id?: never;\n values?: never;\n preset?: never;\n}\n\n/** What `vectorEffect` and `vectorEffectStill` take: an effect by `id`, or a `motion`. */\nexport type VectorEffectParams = VectorEffectByIdParams | SvgMotionParams;\n\n/** A still of the picture, for `{@html markup}` inside an element with `style`. */\nexport interface VectorEffectStill {\n /** The still's layer: safe `<svg>` markup (see `svgFrameMarkup`) in an absolutely placed `<span>`. */\n markup: string;\n /** The host's inline style: its position and size from the viewBox. */\n style: string;\n}\n\ninterface Resolved {\n motion: SvgMotion;\n hold?: number;\n trigger?: SvgTrigger;\n inline?: boolean | string;\n contain?: boolean;\n}\n\n/** Remembers the last motion made from an effect, so values compared by content do not remake it. */\ntype MotionCache = { key: string; effect: VectorEffectDefinition; motion: SvgMotion };\n\nfunction resolve(params: VectorEffectParams, cache?: { current?: MotionCache }): Resolved {\n if (params.motion) return { motion: params.motion, hold: params.hold, trigger: params.trigger, inline: params.inline, contain: params.contain };\n const { id, values, preset } = params as VectorEffectByIdParams;\n const effect = typeof id === \"string\" ? getEffect(id) : id;\n if (!effect || effect.kind !== \"vector\")\n throw new Error(\n `\"${typeof id === \"string\" ? id : (effect?.id ?? id)}\" is not a vector effect; pick one from findEffects({ kind: \"vector\" }), or pass a motion`,\n );\n const key = JSON.stringify([preset ?? null, values ?? {}]);\n let motion: SvgMotion;\n if (cache?.current && cache.current.effect === effect && cache.current.key === key) motion = cache.current.motion;\n else {\n motion = renderVectorEffect(effect, effectDefaults(effect, { preset, values }));\n if (cache) cache.current = { key, effect, motion };\n }\n return {\n motion,\n hold: params.hold ?? effect.hold,\n trigger: params.trigger ?? effect.trigger,\n inline: params.inline ?? values?.inline === true,\n contain: params.contain,\n };\n}\n\n/** The host's sizing from the picture's viewBox, so the still and the player take the same room. */\nfunction hostStyle(svg: string, inline: boolean | string | undefined, contain?: boolean): [string, string][] {\n const box = svgViewBox(svg);\n const style: [string, string][] = [\n [\"position\", \"relative\"],\n [\"display\", inline ? \"inline-block\" : \"block\"],\n ];\n if (inline) style.push([\"height\", typeof inline === \"string\" ? inline : CAP_HEIGHT]);\n if (box) style.push([\"aspect-ratio\", `${box[2]} / ${box[3]}`]);\n const spill = inline && contain ? svgInlineLayout(svg)?.overhang : undefined;\n if (spill) style.push([\"margin\", `${spill.top}em ${spill.right}em ${spill.bottom}em ${spill.left}em`]);\n return style;\n}\n\n/** Whether to wait until scrolled into view: as asked, else when it plays once. */\nconst enters = (r: Resolved, p: SvgPlayerParams) => p.playOnEnter ?? (p.loop === false && !r.trigger);\n\nfunction stillMarkup(r: Resolved, params: SvgPlayerParams): string {\n const at = params.poster ?? (r.trigger === \"hover\" || r.trigger === \"press\" || r.trigger === \"scroll\" ? \"first\" : \"last\");\n return svgFrameMarkup(r.motion, { at, steps: params.steps }) ?? \"\";\n}\n\n/**\n * The still of an effect or motion, for server rendering: put `markup` in the\n * element that has the `vectorEffect` action, with `style` as its style, so\n * the page shows the picture before the player starts (and instead of it\n * under reduced motion), in the room the player will take.\n * Throws when `id` is not a vector effect.\n */\nexport function vectorEffectStill(params: VectorEffectParams): VectorEffectStill {\n const r = resolve(params);\n return {\n markup: `<span ${STILL_ATTR} aria-hidden=\"true\" style=\"${LAYER}\">${stillMarkup(r, params)}</span>`,\n style: hostStyle(r.motion.svg, r.inline, r.contain)\n .map(([k, v]) => `${k}: ${v}`)\n .join(\"; \"),\n };\n}\n\nfunction prefersReducedMotion(): boolean {\n return typeof matchMedia === \"function\" && matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n}\n\n/** The options whose change remakes the player (`speed` is set on it instead). */\nfunction playerKey(r: Resolved, p: SvgPlayerParams): unknown[] {\n return [\n r.motion,\n p.loop,\n p.autoplay,\n r.hold,\n p.duration,\n p.steps?.join(\",\"),\n r.trigger,\n p.pauseWhenHidden,\n enters(r, p),\n p.hideWhenPaused,\n p.clock,\n p.reducedMotion ?? \"user\",\n ];\n}\n\n/**\n * A Svelte action that plays a vector effect in its element:\n * `<span use:vectorEffect={{ id: \"drip-text\", values: { word: \"Slop\" }, loop: false }} />`.\n *\n * It makes an `SvgPlayer` in a layer of its own, remakes it when the params\n * change (values by content; `speed` without remaking) and disposes it on\n * destroy. A still from {@link vectorEffectStill} in the element is hidden\n * while the player runs and shown again after; without one, the action makes\n * its own (so reduced motion still shows the picture) and sizes the element\n * from the viewBox, leaving alone any of `position`, `display`, `height`,\n * `aspect-ratio` and `margin` the element's style already sets.\n *\n * For an attachment, `{@attach fromAction(vectorEffect, () => params)}`\n * (`fromAction` is from `svelte/attachments`). Throws when `id` is not a\n * vector effect.\n */\nexport function vectorEffect(node: HTMLElement, initial: VectorEffectParams): ActionReturn<VectorEffectParams> {\n const cache: { current?: MotionCache } = {};\n const doc = node.ownerDocument;\n const mount = doc.createElement(\"span\");\n mount.style.cssText = LAYER;\n node.appendChild(mount);\n\n let params = initial;\n let resolved = resolve(params, cache);\n let player: SvgPlayer | undefined;\n let key: unknown[] = [];\n /** The still the action made itself, when the element had none. */\n let ownStill: HTMLElement | undefined;\n /** Sizing properties the action set (the element's own style had them empty). */\n const sized = new Set<string>();\n\n const stills = () => node.querySelectorAll<HTMLElement>(`:scope > [${STILL_ATTR}]`);\n const showStills = (show: boolean) => {\n for (const s of stills()) s.style.display = show ? \"block\" : \"none\";\n };\n // When the params change Svelte replaces a server still (`{@html}`), and when\n // it is the element's only child, by setting `innerHTML`, which takes the\n // player's layer out too: put the layer back, and keep a new still hidden.\n const reattach = () => {\n if (mount.parentNode !== node) node.appendChild(mount);\n if (player) showStills(false);\n };\n const observer = typeof MutationObserver === \"function\" ? new MutationObserver(reattach) : undefined;\n observer?.observe(node, { childList: true });\n\n const size = () => {\n const style = hostStyle(resolved.motion.svg, resolved.inline, resolved.contain);\n for (const [prop, value] of style) {\n if (!sized.has(prop) && node.style.getPropertyValue(prop)) continue;\n node.style.setProperty(prop, value);\n sized.add(prop);\n }\n // A property the new sizing drops (`height` when no longer inline) is cleared.\n for (const prop of sized)\n if (!style.some(([p]) => p === prop)) {\n node.style.removeProperty(prop);\n sized.delete(prop);\n }\n };\n\n if (stills().length === 0) {\n ownStill = doc.createElement(\"span\");\n ownStill.setAttribute(STILL_ATTR, \"\");\n ownStill.setAttribute(\"aria-hidden\", \"true\");\n ownStill.style.cssText = LAYER;\n node.insertBefore(ownStill, mount);\n size();\n }\n\n const stop = () => {\n if (!player) return;\n player.dispose();\n player = undefined;\n showStills(true);\n params.onPlayer?.(undefined);\n };\n\n const start = () => {\n const reduced = params.reducedMotion ?? \"user\";\n if (reduced === \"always\" || (reduced === \"user\" && prefersReducedMotion())) return;\n player = new SvgPlayer(mount, resolved.motion, {\n loop: params.loop,\n autoplay: params.autoplay,\n speed: params.speed,\n hold: resolved.hold,\n duration: params.duration,\n steps: params.steps,\n trigger: resolved.trigger,\n pauseWhenHidden: params.pauseWhenHidden,\n playOnEnter: enters(resolved, params),\n hideWhenPaused: params.hideWhenPaused,\n clock: params.clock,\n // The latest params' callback, without remaking the player.\n onPick: (id) => params.onPick?.(id),\n });\n showStills(false);\n params.onPlayer?.(player);\n };\n\n const apply = () => {\n reattach();\n if (ownStill) {\n ownStill.innerHTML = stillMarkup(resolved, params);\n size();\n }\n const next = playerKey(resolved, params);\n if (player && next.every((v, i) => v === key[i])) {\n player.setSpeed(params.speed ?? 1);\n return;\n }\n key = next;\n stop();\n start();\n };\n\n apply();\n\n return {\n update(next) {\n params = next;\n resolved = resolve(params, cache);\n apply();\n },\n destroy() {\n observer?.disconnect();\n stop();\n mount.remove();\n ownStill?.remove();\n for (const prop of sized) node.style.removeProperty(prop);\n },\n };\n}\n\nexport type { SvgPlayer, SvgMotion, SvgTrigger };\n\n/** What the `flairEffect` action runs: any catalog effect, applied to a subject. */\nexport interface FlairEffectParams {\n /** A catalog effect, by id or definition: shader or vector. */\n effect: string | EffectDefinition;\n /** What to apply it to: a picture, a video, an SVG, words or a path (see `EffectSubject`). */\n subject?: EffectSubject | readonly EffectSubject[];\n preset?: string;\n values?: Record<string, ControlValue>;\n /** Start playing at once (default `true`). */\n autoplay?: boolean;\n /** Pause while scrolled out of view (default `true`). */\n pauseWhenHidden?: boolean;\n}\n\n/**\n * Runs any catalog effect, shader or vector, in an element, applied to a\n * subject (see `mountEffect` in `@pixerate/flair/embed`); it fills the\n * element, so size it with CSS. A new subject or values update the running\n * effect; a new effect, preset or playback option starts it again.\n *\n * ```svelte\n * <div use:flairEffect={{ effect: \"image-duotone\", subject: { image: \"/team.jpg\" } }} style=\"aspect-ratio: 16 / 9\"></div>\n * ```\n */\nexport function flairEffect(node: HTMLElement, initial: FlairEffectParams): ActionReturn<FlairEffectParams> {\n let params = initial;\n if (!node.style.position) node.style.position = \"relative\";\n const start = () => {\n const m = mountEffect(node, params.effect, {\n ...(params.subject ? { subject: params.subject } : {}),\n ...(params.preset ? { preset: params.preset } : {}),\n ...(params.values ? { values: params.values } : {}),\n autoplay: params.autoplay ?? true,\n pauseWhenHidden: params.pauseWhenHidden ?? true,\n });\n m.element.style.display = \"block\";\n m.element.style.width = \"100%\";\n m.element.style.height = \"100%\";\n return m;\n };\n let mounted = start();\n return {\n update(next) {\n const restart =\n next.effect !== params.effect || next.preset !== params.preset || next.autoplay !== params.autoplay || next.pauseWhenHidden !== params.pauseWhenHidden;\n const subjectChanged = next.subject !== params.subject;\n const valuesChanged = JSON.stringify(next.values ?? {}) !== JSON.stringify(params.values ?? {});\n params = next;\n if (restart) {\n mounted.dispose();\n mounted = start();\n return;\n }\n if (subjectChanged && next.subject) mounted.setSubject(next.subject);\n if (valuesChanged && next.values) mounted.setValues(next.values);\n },\n destroy() {\n mounted.dispose();\n },\n };\n}\n"],"mappings":"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","names":[]}