react-native-webrtc-kaleidoscope 2.7.4 → 2.7.6

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/README.md CHANGED
@@ -46,7 +46,7 @@ What you get:
46
46
 
47
47
  - **Four simple functions.** `bindKaleidoscope(track, { presets })` hands back [`kaleidoscope`, `transform`, `mask`, and `dispose`](#the-four-verbs); that is the whole runtime API.
48
48
  - **Agent-first setup.** Point a coding agent at [`llms.txt`](./llms.txt) and it [installs the package, writes the config plugin, and gets an effect on screen](#with-an-agent) without you babysitting it.
49
- - **Turnkey implementation.** [Drop-in components](#quick-start), a picker, a live editor, and a persistence provider, render [dozens of presets](#presets) over your camera; wire a callback, ship it.
49
+ - **Turnkey implementation.** [Drop-in components](#quick-start), a picker, a live editor, and a persistence provider, render [63 presets](#presets) over your camera; wire a callback, ship it.
50
50
  - **Cost and tooling.** Every shader carries a [measured GPU cost](#performance) you can read before you ship, and the [bench, meter, and thumbnail tools](#authoring-tooling) come in the box.
51
51
  - **Won't bloat your binary.** Per-asset subpath exports and `sideEffects: false` mean you [ship only the presets you reference](#only-ship-what-you-use); web tree-shakes and native bundles just the assets your book names.
52
52
  - **Built for extension.** A new shader is one folder that codegens to every platform, [clear by construction](#architecture); remix the compositor to [build your own worlds](#make-your-own-presets).
@@ -58,30 +58,18 @@ The demo book ships the gallery below; **every tile is a live link**, so click o
58
58
  <!-- PRESET-WAFFLE:START -->
59
59
 
60
60
  <table cellspacing="0" cellpadding="2">
61
- <tr><td align="right" valign="middle"><sub><b>Blur</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=blur-low" title="Low"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/blur-low.thumb.webp" alt="Low" title="Low" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=blur-medium" title="Medium"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/blur-medium.thumb.webp" alt="Medium" title="Medium" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=blur-high" title="High"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/blur-high.thumb.webp" alt="High" title="High" width="58" /></a></td></tr>
62
- <tr><td align="right" valign="middle"><sub><b>Wizard Tower</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=wizard-tower" title="Wizard Tower"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/catalog/composites/wizard-tower/wizard-tower.thumb.webp" alt="Wizard Tower" title="Wizard Tower" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=wizard-tower-night" title="Night"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/catalog/composites/wizard-tower-night/wizard-tower-night.thumb.webp" alt="Night" title="Night" width="58" /></a></td></tr>
63
- <tr><td align="right" valign="middle"><sub><b>Spaceship</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=observation-deck" title="Observation Deck"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/catalog/composites/observation-deck/observation-deck.thumb.webp" alt="Observation Deck" title="Observation Deck" width="58" /></a></td></tr>
64
- <tr><td align="right" valign="middle"><sub><b>Fairy Cave</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=fairy-cave" title="Fairy Cave"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/catalog/composites/fairy-cave/fairy-cave.thumb.webp" alt="Fairy Cave" title="Fairy Cave" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=fairy-grotto" title="Grotto"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/catalog/composites/fairy-grotto/fairy-grotto.thumb.webp" alt="Grotto" title="Grotto" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=fairy-hollow" title="Hollow"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/catalog/composites/fairy-hollow/fairy-hollow.thumb.webp" alt="Hollow" title="Hollow" width="58" /></a></td></tr>
65
- <tr><td align="right" valign="middle"><sub><b>Ocean</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=underwater" title="Underwater"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/catalog/composites/underwater/underwater.thumb.webp" alt="Underwater" title="Underwater" width="58" /></a></td></tr>
66
- <tr><td align="right" valign="middle"><sub><b>Corporate</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=corporate-blobs" title="Blobs"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/catalog/composites/corporate-blobs/corporate-blobs.thumb.webp" alt="Blobs" title="Blobs" width="58" /></a></td></tr>
67
- <tr><td align="right" valign="middle"><sub><b>Simiancraft</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=simiancraft-light" title="Simiancraft Light"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/simiancraft-light.thumb.webp" alt="Simiancraft Light" title="Simiancraft Light" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=simiancraft-dark" title="Simiancraft Dark"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/simiancraft-dark.thumb.webp" alt="Simiancraft Dark" title="Simiancraft Dark" width="58" /></a></td></tr>
68
- <tr><td align="right" valign="middle"><sub><b>Office</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=office-dark" title="Dark Office"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/office-dark.thumb.webp" alt="Dark Office" title="Dark Office" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=office-light" title="Light Office"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/office-light.thumb.webp" alt="Light Office" title="Light Office" width="58" /></a></td></tr>
69
- <tr><td align="right" valign="middle"><sub><b>Nature</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=landscape-light" title="Nature Light"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/landscape-light.thumb.webp" alt="Nature Light" title="Nature Light" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=landscape-dark" title="Nature Dark"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/landscape-dark.thumb.webp" alt="Nature Dark" title="Nature Dark" width="58" /></a></td></tr>
70
- <tr><td align="right" valign="middle"><sub><b>Home</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=home-light" title="Home Light"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/home-light.thumb.webp" alt="Home Light" title="Home Light" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=home-dark" title="Home Dark"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/home-dark.thumb.webp" alt="Home Dark" title="Home Dark" width="58" /></a></td></tr>
71
- <tr><td align="right" valign="middle"><sub><b>Sci-Fi</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=sci-fi-light" title="Landscape"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/sci-fi-light.thumb.webp" alt="Landscape" title="Landscape" width="58" /></a></td></tr>
72
- <tr><td align="right" valign="middle"><sub><b>Ocean</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=oceanscape-dark" title="Underwater"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/oceanscape-dark.thumb.webp" alt="Underwater" title="Underwater" width="58" /></a></td></tr>
73
- <tr><td align="right" valign="middle"><sub><b>Debug</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=debug-resolutions" title="Resolutions"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/debug-resolutions.thumb.webp" alt="Resolutions" title="Resolutions" width="58" /></a></td></tr>
74
- <tr><td align="right" valign="middle"><sub><b>Sky</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=clouds" title="Daytime"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/catalog/composites/clouds/clouds.thumb.webp" alt="Daytime" title="Daytime" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=clouds-dawn" title="Dawn"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/clouds-dawn.thumb.webp" alt="Dawn" title="Dawn" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=clouds-dusk" title="Dusk"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/clouds-dusk.thumb.webp" alt="Dusk" title="Dusk" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=clouds-night" title="Night"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/clouds-night.thumb.webp" alt="Night" title="Night" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=clouds-otherworld" title="Otherworld"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/clouds-otherworld.thumb.webp" alt="Otherworld" title="Otherworld" width="58" /></a></td></tr>
75
- <tr><td align="right" valign="middle"><sub><b>Plasma</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=plasma-ocean" title="Ocean"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/plasma-ocean.thumb.webp" alt="Ocean" title="Ocean" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=plasma-sunset" title="Sunset"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/plasma-sunset.thumb.webp" alt="Sunset" title="Sunset" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=plasma-mint" title="Mint"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/plasma-mint.thumb.webp" alt="Mint" title="Mint" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=plasma-fast" title="Fast"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/plasma-fast.thumb.webp" alt="Fast" title="Fast" width="58" /></a></td></tr>
76
- <tr><td align="right" valign="middle"><sub><b>Kaleidoscope</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=kaleidoscope-stained-glass" title="Stained Glass"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/kaleidoscope-stained-glass.thumb.webp" alt="Stained Glass" title="Stained Glass" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=kaleidoscope-mandala" title="Mandala"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/kaleidoscope-mandala.thumb.webp" alt="Mandala" title="Mandala" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=kaleidoscope-prism" title="Prism"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/kaleidoscope-prism.thumb.webp" alt="Prism" title="Prism" width="58" /></a></td></tr>
77
- <tr><td align="right" valign="middle"><sub><b>Neo-Memphis</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=neo-memphis-jazz-cup" title="Jazz Cup"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/neo-memphis-jazz-cup.thumb.webp" alt="Jazz Cup" title="Jazz Cup" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=neo-memphis-bauhaus" title="Bauhaus"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/neo-memphis-bauhaus.thumb.webp" alt="Bauhaus" title="Bauhaus" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=neo-memphis-confetti" title="Confetti"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/neo-memphis-confetti.thumb.webp" alt="Confetti" title="Confetti" width="58" /></a></td></tr>
78
- <tr><td align="right" valign="middle"><sub><b>Halftone</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=halftone-boardroom" title="Boardroom"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/halftone-boardroom.thumb.webp" alt="Boardroom" title="Boardroom" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=halftone-press" title="Press"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/halftone-press.thumb.webp" alt="Press" title="Press" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=halftone-ripple" title="Ripple"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/halftone-ripple.thumb.webp" alt="Ripple" title="Ripple" width="58" /></a></td></tr>
79
- <tr><td align="right" valign="middle"><sub><b>Aurora</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=aurora-corporate-silk" title="Corporate Silk"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/aurora-corporate-silk.thumb.webp" alt="Corporate Silk" title="Corporate Silk" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=aurora-dusk" title="Dusk"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/aurora-dusk.thumb.webp" alt="Dusk" title="Dusk" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=aurora-polar" title="Polar"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/aurora-polar.thumb.webp" alt="Polar" title="Polar" width="58" /></a></td></tr>
80
- <tr><td align="right" valign="middle"><sub><b>Outrun</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=outrun-classic" title="Classic"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/outrun-classic.thumb.webp" alt="Classic" title="Classic" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=outrun-miami" title="Miami"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/outrun-miami.thumb.webp" alt="Miami" title="Miami" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=outrun-circuit" title="Circuit"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/outrun-circuit.thumb.webp" alt="Circuit" title="Circuit" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=outrun-acid" title="Acid"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/outrun-acid.thumb.webp" alt="Acid" title="Acid" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=outrun-vapor" title="Vapor"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/outrun-vapor.thumb.webp" alt="Vapor" title="Vapor" width="58" /></a></td></tr>
81
- <tr><td align="right" valign="middle"><sub><b>Nebula</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=nebula" title="Nebula"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/catalog/composites/nebula/nebula.thumb.webp" alt="Nebula" title="Nebula" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=nebula-ember" title="Ember"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/nebula-ember.thumb.webp" alt="Ember" title="Ember" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=nebula-drift" title="Drift"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/nebula-drift.thumb.webp" alt="Drift" title="Drift" width="58" /></a></td></tr>
82
- <tr><td align="right" valign="middle"><sub><b>Simianlights</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=simianlights" title="Simianlights"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/catalog/composites/simianlights/simianlights.thumb.webp" alt="Simianlights" title="Simianlights" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=simianlights-glacier" title="Glacier"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/simianlights-glacier.thumb.webp" alt="Glacier" title="Glacier" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=simianlights-hearth" title="Hearth"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/simianlights-hearth.thumb.webp" alt="Hearth" title="Hearth" width="58" /></a></td></tr>
83
- <tr><td align="right" valign="middle"><sub><b>Interior</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=interior-home" title="Home"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/interior-home.thumb.webp" alt="Home" title="Home" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=interior-office" title="Office"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/interior-office.thumb.webp" alt="Office" title="Office" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=interior-ab-shaft" title="A/B 1-shaft"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/interior-ab-shaft.thumb.webp" alt="A/B 1-shaft" title="A/B 1-shaft" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=interior-ab-3beam" title="A/B 3-beam"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/interior-ab-3beam.thumb.webp" alt="A/B 3-beam" title="A/B 3-beam" width="58" /></a></td></tr>
84
- <tr><td align="right" valign="middle"><sub><b>Data-Mesh</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=data-mesh-datafield" title="Datafield"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/data-mesh-datafield.thumb.webp" alt="Datafield" title="Datafield" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=data-mesh-boardroom" title="Boardroom"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/data-mesh-boardroom.thumb.webp" alt="Boardroom" title="Boardroom" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=data-mesh-acid" title="Acid"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/data-mesh-acid.thumb.webp" alt="Acid" title="Acid" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=data-mesh-cobalt" title="Cobalt"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/data-mesh-cobalt.thumb.webp" alt="Cobalt" title="Cobalt" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=data-mesh-slate" title="Slate"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/data-mesh-slate.thumb.webp" alt="Slate" title="Slate" width="58" /></a></td></tr>
61
+ <tr><td align="right" valign="middle"><sub><b>Blur</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=blur-low" title="Low"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/blur-low.thumb.webp" alt="Low" title="Low" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=blur-medium" title="Medium"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/blur-medium.thumb.webp" alt="Medium" title="Medium" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=blur-high" title="High"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/blur-high.thumb.webp" alt="High" title="High" width="58" /></a></td><td align="right" valign="middle"><sub><b>Debug</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=debug-resolutions" title="Resolutions"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/debug-resolutions.thumb.webp" alt="Resolutions" title="Resolutions" width="58" /></a></td></tr>
62
+ <tr><td align="right" valign="middle"><sub><b>Wizard Tower</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=wizard-tower" title="Wizard Tower"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/catalog/composites/wizard-tower/wizard-tower.thumb.webp" alt="Wizard Tower" title="Wizard Tower" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=wizard-tower-night" title="Night"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/catalog/composites/wizard-tower-night/wizard-tower-night.thumb.webp" alt="Night" title="Night" width="58" /></a></td><td align="right" valign="middle"><sub><b>Sky</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=clouds" title="Daytime"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/catalog/composites/clouds/clouds.thumb.webp" alt="Daytime" title="Daytime" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=clouds-dawn" title="Dawn"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/clouds-dawn.thumb.webp" alt="Dawn" title="Dawn" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=clouds-dusk" title="Dusk"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/clouds-dusk.thumb.webp" alt="Dusk" title="Dusk" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=clouds-night" title="Night"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/clouds-night.thumb.webp" alt="Night" title="Night" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=clouds-otherworld" title="Otherworld"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/clouds-otherworld.thumb.webp" alt="Otherworld" title="Otherworld" width="58" /></a></td></tr>
63
+ <tr><td align="right" valign="middle"><sub><b>Spaceship</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=observation-deck" title="Observation Deck"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/catalog/composites/observation-deck/observation-deck.thumb.webp" alt="Observation Deck" title="Observation Deck" width="58" /></a></td><td align="right" valign="middle"><sub><b>Plasma</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=plasma-ocean" title="Ocean"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/plasma-ocean.thumb.webp" alt="Ocean" title="Ocean" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=plasma-sunset" title="Sunset"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/plasma-sunset.thumb.webp" alt="Sunset" title="Sunset" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=plasma-mint" title="Mint"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/plasma-mint.thumb.webp" alt="Mint" title="Mint" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=plasma-fast" title="Fast"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/plasma-fast.thumb.webp" alt="Fast" title="Fast" width="58" /></a></td></tr>
64
+ <tr><td align="right" valign="middle"><sub><b>Fairy Cave</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=fairy-cave" title="Fairy Cave"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/catalog/composites/fairy-cave/fairy-cave.thumb.webp" alt="Fairy Cave" title="Fairy Cave" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=fairy-grotto" title="Grotto"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/catalog/composites/fairy-grotto/fairy-grotto.thumb.webp" alt="Grotto" title="Grotto" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=fairy-hollow" title="Hollow"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/catalog/composites/fairy-hollow/fairy-hollow.thumb.webp" alt="Hollow" title="Hollow" width="58" /></a></td><td align="right" valign="middle"><sub><b>Kaleidoscope</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=kaleidoscope-stained-glass" title="Stained Glass"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/kaleidoscope-stained-glass.thumb.webp" alt="Stained Glass" title="Stained Glass" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=kaleidoscope-mandala" title="Mandala"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/kaleidoscope-mandala.thumb.webp" alt="Mandala" title="Mandala" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=kaleidoscope-prism" title="Prism"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/kaleidoscope-prism.thumb.webp" alt="Prism" title="Prism" width="58" /></a></td></tr>
65
+ <tr><td align="right" valign="middle"><sub><b>Ocean</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=underwater" title="Underwater"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/catalog/composites/underwater/underwater.thumb.webp" alt="Underwater" title="Underwater" width="58" /></a></td><td align="right" valign="middle"><sub><b>Neo-Memphis</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=neo-memphis-jazz-cup" title="Jazz Cup"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/neo-memphis-jazz-cup.thumb.webp" alt="Jazz Cup" title="Jazz Cup" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=neo-memphis-bauhaus" title="Bauhaus"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/neo-memphis-bauhaus.thumb.webp" alt="Bauhaus" title="Bauhaus" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=neo-memphis-confetti" title="Confetti"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/neo-memphis-confetti.thumb.webp" alt="Confetti" title="Confetti" width="58" /></a></td></tr>
66
+ <tr><td align="right" valign="middle"><sub><b>Corporate</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=corporate-blobs" title="Blobs"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/catalog/composites/corporate-blobs/corporate-blobs.thumb.webp" alt="Blobs" title="Blobs" width="58" /></a></td><td align="right" valign="middle"><sub><b>Halftone</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=halftone-boardroom" title="Boardroom"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/halftone-boardroom.thumb.webp" alt="Boardroom" title="Boardroom" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=halftone-press" title="Press"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/halftone-press.thumb.webp" alt="Press" title="Press" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=halftone-ripple" title="Ripple"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/halftone-ripple.thumb.webp" alt="Ripple" title="Ripple" width="58" /></a></td></tr>
67
+ <tr><td align="right" valign="middle"><sub><b>Simiancraft</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=simiancraft-light" title="Simiancraft Light"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/simiancraft-light.thumb.webp" alt="Simiancraft Light" title="Simiancraft Light" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=simiancraft-dark" title="Simiancraft Dark"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/simiancraft-dark.thumb.webp" alt="Simiancraft Dark" title="Simiancraft Dark" width="58" /></a></td><td align="right" valign="middle"><sub><b>Aurora</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=aurora-corporate-silk" title="Corporate Silk"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/aurora-corporate-silk.thumb.webp" alt="Corporate Silk" title="Corporate Silk" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=aurora-dusk" title="Dusk"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/aurora-dusk.thumb.webp" alt="Dusk" title="Dusk" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=aurora-polar" title="Polar"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/aurora-polar.thumb.webp" alt="Polar" title="Polar" width="58" /></a></td></tr>
68
+ <tr><td align="right" valign="middle"><sub><b>Office</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=office-dark" title="Dark Office"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/office-dark.thumb.webp" alt="Dark Office" title="Dark Office" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=office-light" title="Light Office"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/office-light.thumb.webp" alt="Light Office" title="Light Office" width="58" /></a></td><td align="right" valign="middle"><sub><b>Outrun</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=outrun-classic" title="Classic"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/outrun-classic.thumb.webp" alt="Classic" title="Classic" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=outrun-miami" title="Miami"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/outrun-miami.thumb.webp" alt="Miami" title="Miami" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=outrun-circuit" title="Circuit"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/outrun-circuit.thumb.webp" alt="Circuit" title="Circuit" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=outrun-acid" title="Acid"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/outrun-acid.thumb.webp" alt="Acid" title="Acid" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=outrun-vapor" title="Vapor"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/outrun-vapor.thumb.webp" alt="Vapor" title="Vapor" width="58" /></a></td></tr>
69
+ <tr><td align="right" valign="middle"><sub><b>Nature</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=landscape-light" title="Nature Light"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/landscape-light.thumb.webp" alt="Nature Light" title="Nature Light" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=landscape-dark" title="Nature Dark"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/landscape-dark.thumb.webp" alt="Nature Dark" title="Nature Dark" width="58" /></a></td><td align="right" valign="middle"><sub><b>Nebula</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=nebula" title="Nebula"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/catalog/composites/nebula/nebula.thumb.webp" alt="Nebula" title="Nebula" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=nebula-ember" title="Ember"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/nebula-ember.thumb.webp" alt="Ember" title="Ember" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=nebula-drift" title="Drift"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/nebula-drift.thumb.webp" alt="Drift" title="Drift" width="58" /></a></td></tr>
70
+ <tr><td align="right" valign="middle"><sub><b>Home</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=home-light" title="Home Light"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/home-light.thumb.webp" alt="Home Light" title="Home Light" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=home-dark" title="Home Dark"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/home-dark.thumb.webp" alt="Home Dark" title="Home Dark" width="58" /></a></td><td align="right" valign="middle"><sub><b>Simianlights</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=simianlights" title="Simianlights"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/catalog/composites/simianlights/simianlights.thumb.webp" alt="Simianlights" title="Simianlights" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=simianlights-glacier" title="Glacier"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/simianlights-glacier.thumb.webp" alt="Glacier" title="Glacier" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=simianlights-hearth" title="Hearth"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/simianlights-hearth.thumb.webp" alt="Hearth" title="Hearth" width="58" /></a></td></tr>
71
+ <tr><td align="right" valign="middle"><sub><b>Sci-Fi</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=sci-fi-light" title="Landscape"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/sci-fi-light.thumb.webp" alt="Landscape" title="Landscape" width="58" /></a></td><td align="right" valign="middle"><sub><b>Interior</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=interior-home" title="Home"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/interior-home.thumb.webp" alt="Home" title="Home" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=interior-office" title="Office"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/interior-office.thumb.webp" alt="Office" title="Office" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=interior-ab-shaft" title="A/B 1-shaft"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/interior-ab-shaft.thumb.webp" alt="A/B 1-shaft" title="A/B 1-shaft" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=interior-ab-3beam" title="A/B 3-beam"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/interior-ab-3beam.thumb.webp" alt="A/B 3-beam" title="A/B 3-beam" width="58" /></a></td></tr>
72
+ <tr><td align="right" valign="middle"><sub><b>Oceanscape</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=oceanscape-dark" title="Underwater"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/oceanscape-dark.thumb.webp" alt="Underwater" title="Underwater" width="58" /></a></td><td align="right" valign="middle"><sub><b>Data-Mesh</b></sub></td><td valign="middle"><a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=data-mesh-datafield" title="Datafield"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/data-mesh-datafield.thumb.webp" alt="Datafield" title="Datafield" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=data-mesh-boardroom" title="Boardroom"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/data-mesh-boardroom.thumb.webp" alt="Boardroom" title="Boardroom" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=data-mesh-acid" title="Acid"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/data-mesh-acid.thumb.webp" alt="Acid" title="Acid" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=data-mesh-cobalt" title="Cobalt"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/data-mesh-cobalt.thumb.webp" alt="Cobalt" title="Cobalt" width="58" /></a> <a href="https://simiancraft.github.io/react-native-webrtc-kaleidoscope/?preset=data-mesh-slate" title="Slate"><img src="https://raw.githubusercontent.com/simiancraft/react-native-webrtc-kaleidoscope/main/demo/assets/thumbnails/data-mesh-slate.thumb.webp" alt="Slate" title="Slate" width="58" /></a></td></tr>
85
73
  </table>
86
74
 
87
75
  <sub>63 presets across 4 families (Effects, Worlds, Backgrounds, Shaders); click any to open it live, or <a href="#make-your-own-presets">make your own</a> in a few lines.</sub>
@@ -171,6 +159,8 @@ kaleidoscope('wizard-tower'); // autocompletes from your book
171
159
  // call dispose() on unmount to release the track
172
160
  ```
173
161
 
162
+ <sub>Strict TypeScript setups that pull in the DOM lib may need `track as unknown as MediaStreamTrack` here; `react-native-webrtc`'s track type is structurally narrower than the DOM one, the same cast the demo uses.</sub>
163
+
174
164
  For a ready-made gallery, drop in the picker; it reads your book directly:
175
165
 
176
166
  ```tsx
@@ -204,15 +194,16 @@ On web, LiveKit owns the `RTCRtpSender`, so you cannot swap the track yourself;
204
194
  import { KaleidoscopeProcessor, setMaskTuning } from 'react-native-webrtc-kaleidoscope/livekit';
205
195
 
206
196
  // A processor effect is a `composite` spec: the same layer stack a preset projects into.
207
- const composite = {
208
- name: 'composite',
209
- layers: [
210
- { id: 'bg', shader: 'blur', uniforms: { sigma: 8 } },
211
- { id: 'you', shader: 'direct', target: 'subject' },
212
- ],
213
- };
214
197
  await localVideoTrack.setProcessor(
215
- new KaleidoscopeProcessor([composite]),
198
+ new KaleidoscopeProcessor([
199
+ {
200
+ name: 'composite',
201
+ layers: [
202
+ { id: 'bg', shader: 'blur', uniforms: { sigma: 8 } },
203
+ { id: 'you', shader: 'direct', target: 'subject' },
204
+ ],
205
+ },
206
+ ]),
216
207
  true,
217
208
  );
218
209
  setMaskTuning({ hardness: 0.2, threshold: 0.85 }); // the processor-path twin of `mask`
@@ -256,7 +247,7 @@ const { kaleidoscope, transform, mask, dispose } =
256
247
 
257
248
  kaleidoscope('wizard-tower'); // a preset id
258
249
  kaleidoscope('blur-soft', [ // patch a layer live, by id
259
- { id: 'bg', shader: 'blur', uniforms: { sigma: 9 } },
250
+ { id: 'bg', uniforms: { sigma: 9 } },
260
251
  ]);
261
252
  kaleidoscope(null); // clear the art
262
253
 
@@ -267,7 +258,9 @@ mask({ hardness: 0.5, threshold: 0.5 });
267
258
  dispose(); // on unmount
268
259
  ```
269
260
 
270
- Many uniforms are normalized `0..1`; others (`sigma`, scales, counts) carry natural units, and JSDoc documents each range. `mask` defaults to `0.5 / 0.5`; nudge it to match your camera and lighting.
261
+ Many uniforms are normalized `0..1`; others carry natural units (blur's `sigma` runs `0.5..10`; scales and counts vary), and JSDoc documents each range. `mask` defaults to `0.5 / 0.5`; nudge it to match your camera and lighting.
262
+
263
+ On native today, `dispose()` is a no-op (the bound track is mutated in place, never torn down) and a `kaleidoscope` patch bakes into the next stack rebuild instead of tuning live; both are fully live on web.
271
264
 
272
265
  ## Make your own presets
273
266
 
@@ -279,8 +272,8 @@ A preset is a composition: **every preset is a back-to-front stack of N layers**
279
272
  name: 'Aurora night',
280
273
  taxonomy: ['Shaders', 'Aurora'],
281
274
  layers: [
282
- { id: 'sky', shader: 'clouds', target: 'background', uniforms: { coverage: 0.4 } },
283
- { id: 'glow', shader: 'godrays', target: 'background', blend: 'additive' },
275
+ { id: 'sky', shader: 'clouds', target: 'background', uniforms: { uCoverage: 0.4 } },
276
+ { id: 'glow', shader: 'godrays', target: 'background', blend: 'additive', uniforms: { uRayIntensity: 0.6 } },
284
277
  { id: 'you', shader: 'direct', target: 'subject' },
285
278
  ],
286
279
  },
@@ -346,8 +339,11 @@ import { useKaleidoscopeState } from 'react-native-webrtc-kaleidoscope/persisten
346
339
  const { hydrated, presetId, mask, setPreset, setMask, setPatch, patchesFor, reset } =
347
340
  useKaleidoscopeState<typeof presets>();
348
341
 
342
+ // `controls` is the binding from bindKaleidoscope(track, { presets }); see Quick start.
349
343
  useEffect(() => {
350
344
  if (!hydrated || !controls) return; // wait: don't flash the default over the restored preset
345
+ // patchesFor reads the active preset's patches from this render; live edits go
346
+ // through onPatch, not this re-apply, so it stays out of the deps.
351
347
  if (presetId) controls.kaleidoscope(presetId, patchesFor(presetId));
352
348
  else controls.kaleidoscope(null);
353
349
  }, [hydrated, controls, presetId]);
@@ -357,10 +353,10 @@ Route the picker's `onSelect` into `setPreset`, the editor's `onPatch` into `set
357
353
 
358
354
  ## Performance
359
355
 
360
- The product is the masked-background composite, and its cost is something you can see, not guess.
356
+ **Highly performant.** The lightweight presets (the plasma family and the simpler shaders) run at a high frame rate even on an iPhone X. Absolute frame rate is device-dependent, so the kit ships **relative** per-shader cost: each shader is annotated against a cheap baseline, so you can compare effects and keep the heavy ones in budget.
361
357
 
362
358
  - **Annotated shader cost.** Each generative shader's `.ts` carries a measured GPU cost annotation (relative to `plasma` as the cheap baseline), so you know what a preset spends before you ship it.
363
- - **One resolution knob.** Raw shader compute scales with output resolution, handled by the resolution tier (`targetShortSide`), not by per-effect orientation tricks. Drop the tier on weak GPUs; the mask and composite logic are unchanged.
359
+ - **A segmentation-cost knob.** The mask is produced from the camera downscaled to `targetShortSide`; lower it on weak GPUs to cut segmentation cost, at a slightly softer mask edge. A heavy shader is its own cost; reach for a lighter preset if a device struggles.
364
360
  - **Bounded work per frame.** Compositing is per-layer through a single mask stencil; a new shader inherits the pipeline's frame budget rather than adding a pass of its own.
365
361
 
366
362
  ## Authoring tooling
@@ -8,8 +8,8 @@ export type { KaleidoscopeBinding, KaleidoscopeBindOptions, MaskInput, PatchesFo
8
8
  export type { KaleidoscopeBlendMode, KaleidoscopeControls, KaleidoscopeLayer, KaleidoscopeLayerTarget, KaleidoscopePreset, KaleidoscopePresetBook, KaleidoscopePresetEntry, KaleidoscopeTaxonomy, } from './kaleidoscope.preset-book.types';
9
9
  export type { RGB } from './lib/primitives.types';
10
10
  /**
11
- * Bind a track and a preset book; get the three verbs back
12
- * (`{ kaleidoscope, transform, mask }`). On native the track is mutated in
11
+ * Bind a track and a preset book; get the four verbs back
12
+ * (`{ kaleidoscope, transform, mask, dispose }`). On native the track is mutated in
13
13
  * place, so `controls.track` is the bound track and `onTrack` fires with it
14
14
  * after each `kaleidoscope` preset switch and `transform` command. `mask`
15
15
  * updates the segmentation edge the per-frame processors read.
package/dist/src/index.js CHANGED
@@ -92,7 +92,7 @@ const specToNativeName = (spec) => {
92
92
  // entry be collected when the track is.
93
93
  const lastAppliedSignatureByTrack = new WeakMap();
94
94
  // The lower-level native primitive: route a spec array through the upstream
95
- // `_setVideoEffects`. Internal now (the public surface is the three verbs);
95
+ // `_setVideoEffects`. Internal now (the public surface is the four verbs);
96
96
  // `bindKaleidoscope`'s reconcile drives it.
97
97
  const applyVideoEffects = (track, effects) => {
98
98
  const t = track;
@@ -157,8 +157,8 @@ const applyVideoEffects = (track, effects) => {
157
157
  return track;
158
158
  };
159
159
  /**
160
- * Bind a track and a preset book; get the three verbs back
161
- * (`{ kaleidoscope, transform, mask }`). On native the track is mutated in
160
+ * Bind a track and a preset book; get the four verbs back
161
+ * (`{ kaleidoscope, transform, mask, dispose }`). On native the track is mutated in
162
162
  * place, so `controls.track` is the bound track and `onTrack` fires with it
163
163
  * after each `kaleidoscope` preset switch and `transform` command. `mask`
164
164
  * updates the segmentation edge the per-frame processors read.
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["// Native entry point. Metro picks this up via package.json \"react-native\"\n// and the \".\" subpath export's \"react-native\" condition.\n//\n// Thin facade over `track._setVideoEffects(names)` from react-native-webrtc.\n// Native frame processors are registered at app boot by the Expo Module's\n// OnCreate hook (see android/.../KaleidoscopeModule.kt and ios/.../KaleidoscopeModule.swift);\n// this facade just dispatches into the existing upstream registry.\n\nimport { requireNativeModule } from 'expo-modules-core';\nimport { Platform } from 'react-native';\nimport {\n createControls,\n type Reconcile,\n type ResetLayerUniforms,\n type SetLayerUniforms,\n type SetMask,\n} from './kaleidoscope/controls';\nimport { toEffectSpec } from './kaleidoscope/effect';\nimport type { ApplyVideoEffects, CompositeSpec } from './kaleidoscope/effect.types';\nimport type { KaleidoscopeBinding, KaleidoscopeBindOptions } from './kaleidoscope/types';\nimport type { KaleidoscopeLayer, KaleidoscopePresetBook } from './kaleidoscope.preset-book.types';\n\n// The native module's tuning functions. Only the three the JS layer drives are\n// declared: blur sigma (from a blur preset's options) and the mask edge (from\n// the mask() verb). The native module also exposes segmentation/debug/reset\n// functions, but nothing in JS calls them anymore, so they're not declared here.\ninterface KaleidoscopeNativeModule {\n setMaskHardness: (value: number) => void;\n setMaskThreshold: (value: number) => void;\n // KaleidoscopePreset layer-stack channel. Optional: a native build predating the\n // compositor won't expose it, so callers guard with `?.`. JS sends the active\n // composite's ordered layer stack as a JSON string; native parses + composites\n // it. Blur sigma and generative uniforms now ride inside each layer's\n // `uniforms`, so there is no separate setBlurSigma/setShaderUniforms channel.\n setCompositeLayers?: (json: string) => void;\n}\n\n// Lazy because the module is not available during pure-JS tests; the\n// getter throws if you call a setter outside a real native runtime, which\n// is the right failure mode.\nconst nativeModule = (): KaleidoscopeNativeModule =>\n requireNativeModule<KaleidoscopeNativeModule>('RnWebrtcKaleidoscope');\n\n// The native tuning functions (setMaskHardness / setMaskThreshold) remain on the\n// native module and are called internally: the mask edge flows from the mask()\n// verb (see bindKaleidoscope). The composite layer stack flows through\n// setCompositeLayers. The old global set* JS exports are gone; effects are driven by\n// kaleidoscope / transform / mask, not loose setters.\n\nexport type { CatalogImageId } from '../catalog/images';\nexport type {\n AnamorphicLensFlareUniforms,\n BlurUniforms,\n CloudsUniforms,\n CorporateBlobsUniforms,\n FirefliesUniforms,\n GodraysUniforms,\n LayerShaderName,\n LayerShaderOptions,\n LightBeamsAndMotesUniforms,\n NebulaUniforms,\n PatchableShaderName,\n PlasmaUniforms,\n ShaderUniformsMap,\n SimianlightsUniforms,\n UniformControl,\n} from '../catalog/shaders';\n// Per-shader control descriptors (platform-agnostic data). Imported individually\n// per the shader a preset's layer uses; there is no all-shaders aggregate.\nexport {\n ANAMORPHIC_LENSFLARE_CONTROLS,\n BLUR_CONTROLS,\n CLOUDS_CONTROLS,\n CORPORATE_BLOBS_CONTROLS,\n defaultUniforms,\n FIREFLIES_CONTROLS,\n GODRAYS_CONTROLS,\n LIGHT_BEAMS_AND_MOTES_CONTROLS,\n NEBULA_CONTROLS,\n PLASMA_CONTROLS,\n SIMIANLIGHTS_CONTROLS,\n} from '../catalog/shaders';\nexport type {\n CompositeSpec,\n EffectInput,\n EffectName,\n EffectSpec,\n TransformName,\n TransformSpec,\n} from './kaleidoscope/effect.types';\nexport type {\n KaleidoscopeBinding,\n KaleidoscopeBindOptions,\n MaskInput,\n PatchesFor,\n PatchFor,\n TransformInput,\n} from './kaleidoscope/types';\nexport type {\n KaleidoscopeBlendMode,\n KaleidoscopeControls,\n KaleidoscopeLayer,\n KaleidoscopeLayerTarget,\n KaleidoscopePreset,\n KaleidoscopePresetBook,\n KaleidoscopePresetEntry,\n KaleidoscopeTaxonomy,\n} from './kaleidoscope.preset-book.types';\nexport type { RGB } from './lib/primitives.types';\n\ninterface WebRTCTrackExtensions {\n remote?: boolean;\n // Upstream's typed signature is `string[]`, but the platforms diverge on\n // how to clear effects:\n // - Android: passing `null` takes the\n // `videoSource.setVideoProcessor(null)` branch (the only correct clear\n // path); passing `[]` crashes EglRenderer.\n // - iOS: the Obj-C method declares `names` as `nonnull NSArray<NSString *>`,\n // so `null` violates the bridge contract; `[]` is the supported clear\n // value (iOS's `VideoEffectProcessor` with no processors is a\n // passthrough).\n // We type the parameter as the union so the facade can platform-split at\n // the call site. See `applyVideoEffects` below.\n _setVideoEffects?: (names: ReadonlyArray<string> | null) => void;\n}\n\n// Effect names registered native-side in Registration.kt / Registration.swift.\n// Anything not in this list gets filtered out before reaching upstream,\n// because rn-webrtc's setVideoEffects calls ProcessorProvider.getProcessor()\n// and filters nulls into an empty list, which then hits the\n// VideoEffectProcessor empty-processors-list bug (refcount goes negative,\n// EglRenderer crashes one frame later). Dropping an unregistered name here is\n// the safe behavior.\n//\n// The art axis is now one registered \"composite\" compositor: blur, images, and\n// generative shaders are all layers inside it, delivered out-of-band via\n// setCompositeLayers. The four geometric transform ops share one native processor\n// per platform (TransformFactory on Android, TransformProcessor on iOS); each is\n// a flat name. iOS registers the same set via ios/.../Registration.swift.\nconst TRANSFORM_EFFECTS: readonly string[] = ['flip-x', 'flip-y', 'rotate-cw', 'rotate-ccw'];\n\n// Android (Registration.kt) and iOS (Registration.swift) install an identical\n// effect set, so one list covers both natives. If the platforms ever diverge,\n// split this back into per-platform lists keyed off Platform.OS.\nconst NATIVE_REGISTERED_EFFECTS_LIST: readonly string[] = [...TRANSFORM_EFFECTS, 'composite'];\n\nconst registeredForPlatform = (): readonly string[] =>\n Platform.OS === 'android' || Platform.OS === 'ios' ? NATIVE_REGISTERED_EFFECTS_LIST : [];\n\nconst NATIVE_REGISTERED_EFFECTS: ReadonlySet<string> = new Set(registeredForPlatform());\n\n// Serialize a composite's layer stack to the JSON shape the native CompositeLayers\n// channel parses: an array of { id, shader, target, blend?, source?, uniforms? }.\n// Every layer carries its `id`. An `image` layer's native `source` IS its `id`\n// (the bundled WebP basename the prebuild plugin copied under that name); all\n// other layers carry their uniforms.\nconst serializeCompositeLayers = (layers: ReadonlyArray<KaleidoscopeLayer>): string => {\n const wire = layers.map((layer) => {\n const base: Record<string, unknown> = {\n id: layer.id,\n shader: layer.shader,\n target: layer.target ?? 'background',\n };\n if (layer.blend != null) base.blend = layer.blend;\n if (layer.shader === 'image') {\n // The layer id is the image id (the bundled WebP basename); the native\n // compositor resolves assets/images/<id>.webp from it.\n base.source = layer.id;\n } else if ('uniforms' in layer) {\n base.uniforms = layer.uniforms;\n }\n return base;\n });\n return JSON.stringify(wire);\n};\n\nconst specToNativeName = (spec: ReturnType<typeof toEffectSpec>): string => {\n // The composite runs through the single registered \"composite\" compositor; its\n // layer stack is delivered out-of-band via setCompositeLayers (see applyVideoEffects).\n if (spec.name === 'composite') {\n return 'composite';\n }\n return spec.name;\n};\n\n// Last effect set applied to each track, as a stable signature. Used to skip\n// redundant native calls: rn-webrtc rebuilds the native frame processors on\n// EVERY _setVideoEffects call (Android constructs a fresh processor per call),\n// so re-issuing an unchanged set (a React re-render, an idempotent effect\n// hook) churns GL + segmentation resources for no reason. The WeakMap lets the\n// entry be collected when the track is.\nconst lastAppliedSignatureByTrack = new WeakMap<object, string>();\n\n// The lower-level native primitive: route a spec array through the upstream\n// `_setVideoEffects`. Internal now (the public surface is the three verbs);\n// `bindKaleidoscope`'s reconcile drives it.\nconst applyVideoEffects: ApplyVideoEffects = (track, effects) => {\n const t = track as MediaStreamTrack & WebRTCTrackExtensions;\n if (t.remote) {\n throw new Error('kaleidoscope: cannot apply effects to remote tracks');\n }\n if (typeof t._setVideoEffects !== 'function') {\n throw new Error(\n 'kaleidoscope: track has no _setVideoEffects method (is react-native-webrtc >=124 installed?)',\n );\n }\n const specs = effects.map(toEffectSpec);\n // Deliver the composite's layer stack out-of-band before the \"composite\" name\n // is dispatched. Blur sigma and generative uniforms ride inside each layer's\n // `uniforms`. Guarded: a native build without the compositor lacks the function\n // (and drops the \"composite\" name below if not registered).\n for (const spec of specs) {\n if (spec.name === 'composite') {\n nativeModule().setCompositeLayers?.(serializeCompositeLayers((spec as CompositeSpec).layers));\n }\n }\n // The composite name is book-driven: the prebuild copied the images and native\n // registration installed the one \"composite\" compositor, so it passes the\n // crash-guard. Transforms must be in the static set (always are).\n const mapped = specs.map((spec) => ({\n name: specToNativeName(spec),\n trusted: spec.name === 'composite',\n }));\n const names = mapped\n .filter((m) => m.trusted || NATIVE_REGISTERED_EFFECTS.has(m.name))\n .map((m) => m.name);\n\n // Dedup against the last set applied to this track. Order is significant\n // (effects chain in array order), so the signature preserves it. Skip the\n // native call when nothing changed; the first call for any given set always\n // proceeds (no prior entry).\n const signature = names.join('\\n');\n if (lastAppliedSignatureByTrack.get(track) === signature) {\n return track;\n }\n lastAppliedSignatureByTrack.set(track, signature);\n\n const dropped = mapped\n .filter((m) => !m.trusted && !NATIVE_REGISTERED_EFFECTS.has(m.name))\n .map((m) => m.name);\n if (dropped.length > 0) {\n console.warn(\n `kaleidoscope: dropping effects not registered on this native platform: ${dropped.join(', ')}. ` +\n 'Web has its own registry; this is a native-only filter.',\n );\n }\n // Platforms diverge on how to clear effects:\n // - Android: rn-webrtc 124 has a bug where passing `[]` installs a\n // VideoEffectProcessor with an empty processors list, whose\n // onFrameCaptured then double-releases the input frame (retain once,\n // release twice) and crashes EglRenderer one frame later. The only\n // correct clear is `null`, which takes the upstream else-branch that\n // resets the processor via `videoSource.setVideoProcessor(null)`.\n // - iOS: the upstream Obj-C `_setVideoEffects` method declares\n // `names` as `(nonnull NSArray<NSString *> *)`, so passing `null`\n // violates the React Native bridge's nonnull contract. The supported\n // clear value is `[]`, which iOS's VideoEffectProcessor treats as a\n // passthrough (no double-release bug on this platform).\n // The explicit type annotation is required; without it TS widens the\n // empty-array literal to `never[] | null`.\n const clearValue: ReadonlyArray<string> | null = Platform.OS === 'ios' ? [] : null;\n t._setVideoEffects(names.length === 0 ? clearValue : names);\n return track;\n};\n\n/**\n * Bind a track and a preset book; get the three verbs back\n * (`{ kaleidoscope, transform, mask }`). On native the track is mutated in\n * place, so `controls.track` is the bound track and `onTrack` fires with it\n * after each `kaleidoscope` preset switch and `transform` command. `mask`\n * updates the segmentation edge the per-frame processors read.\n */\nexport const bindKaleidoscope = <P extends KaleidoscopePresetBook>(\n track: MediaStreamTrack,\n options: KaleidoscopeBindOptions<P>,\n): KaleidoscopeBinding<P> => {\n const reconcile: Reconcile = {\n apply: (specs) => applyVideoEffects(track, specs),\n dispose: () => {},\n };\n const setMask: SetMask = (hardness, threshold) => {\n nativeModule().setMaskHardness(hardness);\n nativeModule().setMaskThreshold(threshold);\n };\n // Native has no live per-layer uniform channel yet (Phase B): a patch of the\n // active preset is a no-op here, so the verb's patch path is inert on native\n // until the compositor reads layer-id-keyed overrides. The verb still drives\n // preset switches through reconcile.\n const setLayerUniforms: SetLayerUniforms = () => {};\n // Native re-sends the full layer stack (with baked uniforms) on every preset\n // switch via setCompositeLayers, so there is no stale override to clear; no-op for\n // parity with the inert setLayerUniforms above.\n const resetLayerUniforms: ResetLayerUniforms = () => {};\n return createControls(track, options, reconcile, setMask, setLayerUniforms, resetLayerUniforms);\n};\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["// Native entry point. Metro picks this up via package.json \"react-native\"\n// and the \".\" subpath export's \"react-native\" condition.\n//\n// Thin facade over `track._setVideoEffects(names)` from react-native-webrtc.\n// Native frame processors are registered at app boot by the Expo Module's\n// OnCreate hook (see android/.../KaleidoscopeModule.kt and ios/.../KaleidoscopeModule.swift);\n// this facade just dispatches into the existing upstream registry.\n\nimport { requireNativeModule } from 'expo-modules-core';\nimport { Platform } from 'react-native';\nimport {\n createControls,\n type Reconcile,\n type ResetLayerUniforms,\n type SetLayerUniforms,\n type SetMask,\n} from './kaleidoscope/controls';\nimport { toEffectSpec } from './kaleidoscope/effect';\nimport type { ApplyVideoEffects, CompositeSpec } from './kaleidoscope/effect.types';\nimport type { KaleidoscopeBinding, KaleidoscopeBindOptions } from './kaleidoscope/types';\nimport type { KaleidoscopeLayer, KaleidoscopePresetBook } from './kaleidoscope.preset-book.types';\n\n// The native module's tuning functions. Only the three the JS layer drives are\n// declared: blur sigma (from a blur preset's options) and the mask edge (from\n// the mask() verb). The native module also exposes segmentation/debug/reset\n// functions, but nothing in JS calls them anymore, so they're not declared here.\ninterface KaleidoscopeNativeModule {\n setMaskHardness: (value: number) => void;\n setMaskThreshold: (value: number) => void;\n // KaleidoscopePreset layer-stack channel. Optional: a native build predating the\n // compositor won't expose it, so callers guard with `?.`. JS sends the active\n // composite's ordered layer stack as a JSON string; native parses + composites\n // it. Blur sigma and generative uniforms now ride inside each layer's\n // `uniforms`, so there is no separate setBlurSigma/setShaderUniforms channel.\n setCompositeLayers?: (json: string) => void;\n}\n\n// Lazy because the module is not available during pure-JS tests; the\n// getter throws if you call a setter outside a real native runtime, which\n// is the right failure mode.\nconst nativeModule = (): KaleidoscopeNativeModule =>\n requireNativeModule<KaleidoscopeNativeModule>('RnWebrtcKaleidoscope');\n\n// The native tuning functions (setMaskHardness / setMaskThreshold) remain on the\n// native module and are called internally: the mask edge flows from the mask()\n// verb (see bindKaleidoscope). The composite layer stack flows through\n// setCompositeLayers. The old global set* JS exports are gone; effects are driven by\n// kaleidoscope / transform / mask, not loose setters.\n\nexport type { CatalogImageId } from '../catalog/images';\nexport type {\n AnamorphicLensFlareUniforms,\n BlurUniforms,\n CloudsUniforms,\n CorporateBlobsUniforms,\n FirefliesUniforms,\n GodraysUniforms,\n LayerShaderName,\n LayerShaderOptions,\n LightBeamsAndMotesUniforms,\n NebulaUniforms,\n PatchableShaderName,\n PlasmaUniforms,\n ShaderUniformsMap,\n SimianlightsUniforms,\n UniformControl,\n} from '../catalog/shaders';\n// Per-shader control descriptors (platform-agnostic data). Imported individually\n// per the shader a preset's layer uses; there is no all-shaders aggregate.\nexport {\n ANAMORPHIC_LENSFLARE_CONTROLS,\n BLUR_CONTROLS,\n CLOUDS_CONTROLS,\n CORPORATE_BLOBS_CONTROLS,\n defaultUniforms,\n FIREFLIES_CONTROLS,\n GODRAYS_CONTROLS,\n LIGHT_BEAMS_AND_MOTES_CONTROLS,\n NEBULA_CONTROLS,\n PLASMA_CONTROLS,\n SIMIANLIGHTS_CONTROLS,\n} from '../catalog/shaders';\nexport type {\n CompositeSpec,\n EffectInput,\n EffectName,\n EffectSpec,\n TransformName,\n TransformSpec,\n} from './kaleidoscope/effect.types';\nexport type {\n KaleidoscopeBinding,\n KaleidoscopeBindOptions,\n MaskInput,\n PatchesFor,\n PatchFor,\n TransformInput,\n} from './kaleidoscope/types';\nexport type {\n KaleidoscopeBlendMode,\n KaleidoscopeControls,\n KaleidoscopeLayer,\n KaleidoscopeLayerTarget,\n KaleidoscopePreset,\n KaleidoscopePresetBook,\n KaleidoscopePresetEntry,\n KaleidoscopeTaxonomy,\n} from './kaleidoscope.preset-book.types';\nexport type { RGB } from './lib/primitives.types';\n\ninterface WebRTCTrackExtensions {\n remote?: boolean;\n // Upstream's typed signature is `string[]`, but the platforms diverge on\n // how to clear effects:\n // - Android: passing `null` takes the\n // `videoSource.setVideoProcessor(null)` branch (the only correct clear\n // path); passing `[]` crashes EglRenderer.\n // - iOS: the Obj-C method declares `names` as `nonnull NSArray<NSString *>`,\n // so `null` violates the bridge contract; `[]` is the supported clear\n // value (iOS's `VideoEffectProcessor` with no processors is a\n // passthrough).\n // We type the parameter as the union so the facade can platform-split at\n // the call site. See `applyVideoEffects` below.\n _setVideoEffects?: (names: ReadonlyArray<string> | null) => void;\n}\n\n// Effect names registered native-side in Registration.kt / Registration.swift.\n// Anything not in this list gets filtered out before reaching upstream,\n// because rn-webrtc's setVideoEffects calls ProcessorProvider.getProcessor()\n// and filters nulls into an empty list, which then hits the\n// VideoEffectProcessor empty-processors-list bug (refcount goes negative,\n// EglRenderer crashes one frame later). Dropping an unregistered name here is\n// the safe behavior.\n//\n// The art axis is now one registered \"composite\" compositor: blur, images, and\n// generative shaders are all layers inside it, delivered out-of-band via\n// setCompositeLayers. The four geometric transform ops share one native processor\n// per platform (TransformFactory on Android, TransformProcessor on iOS); each is\n// a flat name. iOS registers the same set via ios/.../Registration.swift.\nconst TRANSFORM_EFFECTS: readonly string[] = ['flip-x', 'flip-y', 'rotate-cw', 'rotate-ccw'];\n\n// Android (Registration.kt) and iOS (Registration.swift) install an identical\n// effect set, so one list covers both natives. If the platforms ever diverge,\n// split this back into per-platform lists keyed off Platform.OS.\nconst NATIVE_REGISTERED_EFFECTS_LIST: readonly string[] = [...TRANSFORM_EFFECTS, 'composite'];\n\nconst registeredForPlatform = (): readonly string[] =>\n Platform.OS === 'android' || Platform.OS === 'ios' ? NATIVE_REGISTERED_EFFECTS_LIST : [];\n\nconst NATIVE_REGISTERED_EFFECTS: ReadonlySet<string> = new Set(registeredForPlatform());\n\n// Serialize a composite's layer stack to the JSON shape the native CompositeLayers\n// channel parses: an array of { id, shader, target, blend?, source?, uniforms? }.\n// Every layer carries its `id`. An `image` layer's native `source` IS its `id`\n// (the bundled WebP basename the prebuild plugin copied under that name); all\n// other layers carry their uniforms.\nconst serializeCompositeLayers = (layers: ReadonlyArray<KaleidoscopeLayer>): string => {\n const wire = layers.map((layer) => {\n const base: Record<string, unknown> = {\n id: layer.id,\n shader: layer.shader,\n target: layer.target ?? 'background',\n };\n if (layer.blend != null) base.blend = layer.blend;\n if (layer.shader === 'image') {\n // The layer id is the image id (the bundled WebP basename); the native\n // compositor resolves assets/images/<id>.webp from it.\n base.source = layer.id;\n } else if ('uniforms' in layer) {\n base.uniforms = layer.uniforms;\n }\n return base;\n });\n return JSON.stringify(wire);\n};\n\nconst specToNativeName = (spec: ReturnType<typeof toEffectSpec>): string => {\n // The composite runs through the single registered \"composite\" compositor; its\n // layer stack is delivered out-of-band via setCompositeLayers (see applyVideoEffects).\n if (spec.name === 'composite') {\n return 'composite';\n }\n return spec.name;\n};\n\n// Last effect set applied to each track, as a stable signature. Used to skip\n// redundant native calls: rn-webrtc rebuilds the native frame processors on\n// EVERY _setVideoEffects call (Android constructs a fresh processor per call),\n// so re-issuing an unchanged set (a React re-render, an idempotent effect\n// hook) churns GL + segmentation resources for no reason. The WeakMap lets the\n// entry be collected when the track is.\nconst lastAppliedSignatureByTrack = new WeakMap<object, string>();\n\n// The lower-level native primitive: route a spec array through the upstream\n// `_setVideoEffects`. Internal now (the public surface is the four verbs);\n// `bindKaleidoscope`'s reconcile drives it.\nconst applyVideoEffects: ApplyVideoEffects = (track, effects) => {\n const t = track as MediaStreamTrack & WebRTCTrackExtensions;\n if (t.remote) {\n throw new Error('kaleidoscope: cannot apply effects to remote tracks');\n }\n if (typeof t._setVideoEffects !== 'function') {\n throw new Error(\n 'kaleidoscope: track has no _setVideoEffects method (is react-native-webrtc >=124 installed?)',\n );\n }\n const specs = effects.map(toEffectSpec);\n // Deliver the composite's layer stack out-of-band before the \"composite\" name\n // is dispatched. Blur sigma and generative uniforms ride inside each layer's\n // `uniforms`. Guarded: a native build without the compositor lacks the function\n // (and drops the \"composite\" name below if not registered).\n for (const spec of specs) {\n if (spec.name === 'composite') {\n nativeModule().setCompositeLayers?.(serializeCompositeLayers((spec as CompositeSpec).layers));\n }\n }\n // The composite name is book-driven: the prebuild copied the images and native\n // registration installed the one \"composite\" compositor, so it passes the\n // crash-guard. Transforms must be in the static set (always are).\n const mapped = specs.map((spec) => ({\n name: specToNativeName(spec),\n trusted: spec.name === 'composite',\n }));\n const names = mapped\n .filter((m) => m.trusted || NATIVE_REGISTERED_EFFECTS.has(m.name))\n .map((m) => m.name);\n\n // Dedup against the last set applied to this track. Order is significant\n // (effects chain in array order), so the signature preserves it. Skip the\n // native call when nothing changed; the first call for any given set always\n // proceeds (no prior entry).\n const signature = names.join('\\n');\n if (lastAppliedSignatureByTrack.get(track) === signature) {\n return track;\n }\n lastAppliedSignatureByTrack.set(track, signature);\n\n const dropped = mapped\n .filter((m) => !m.trusted && !NATIVE_REGISTERED_EFFECTS.has(m.name))\n .map((m) => m.name);\n if (dropped.length > 0) {\n console.warn(\n `kaleidoscope: dropping effects not registered on this native platform: ${dropped.join(', ')}. ` +\n 'Web has its own registry; this is a native-only filter.',\n );\n }\n // Platforms diverge on how to clear effects:\n // - Android: rn-webrtc 124 has a bug where passing `[]` installs a\n // VideoEffectProcessor with an empty processors list, whose\n // onFrameCaptured then double-releases the input frame (retain once,\n // release twice) and crashes EglRenderer one frame later. The only\n // correct clear is `null`, which takes the upstream else-branch that\n // resets the processor via `videoSource.setVideoProcessor(null)`.\n // - iOS: the upstream Obj-C `_setVideoEffects` method declares\n // `names` as `(nonnull NSArray<NSString *> *)`, so passing `null`\n // violates the React Native bridge's nonnull contract. The supported\n // clear value is `[]`, which iOS's VideoEffectProcessor treats as a\n // passthrough (no double-release bug on this platform).\n // The explicit type annotation is required; without it TS widens the\n // empty-array literal to `never[] | null`.\n const clearValue: ReadonlyArray<string> | null = Platform.OS === 'ios' ? [] : null;\n t._setVideoEffects(names.length === 0 ? clearValue : names);\n return track;\n};\n\n/**\n * Bind a track and a preset book; get the four verbs back\n * (`{ kaleidoscope, transform, mask, dispose }`). On native the track is mutated in\n * place, so `controls.track` is the bound track and `onTrack` fires with it\n * after each `kaleidoscope` preset switch and `transform` command. `mask`\n * updates the segmentation edge the per-frame processors read.\n */\nexport const bindKaleidoscope = <P extends KaleidoscopePresetBook>(\n track: MediaStreamTrack,\n options: KaleidoscopeBindOptions<P>,\n): KaleidoscopeBinding<P> => {\n const reconcile: Reconcile = {\n apply: (specs) => applyVideoEffects(track, specs),\n dispose: () => {},\n };\n const setMask: SetMask = (hardness, threshold) => {\n nativeModule().setMaskHardness(hardness);\n nativeModule().setMaskThreshold(threshold);\n };\n // Native has no live per-layer uniform channel yet (Phase B): a patch of the\n // active preset is a no-op here, so the verb's patch path is inert on native\n // until the compositor reads layer-id-keyed overrides. The verb still drives\n // preset switches through reconcile.\n const setLayerUniforms: SetLayerUniforms = () => {};\n // Native re-sends the full layer stack (with baked uniforms) on every preset\n // switch via setCompositeLayers, so there is no stale override to clear; no-op for\n // parity with the inert setLayerUniforms above.\n const resetLayerUniforms: ResetLayerUniforms = () => {};\n return createControls(track, options, reconcile, setMask, setLayerUniforms, resetLayerUniforms);\n};\n"]}
@@ -19,8 +19,8 @@ export type { RGB } from './lib/primitives.types';
19
19
  */
20
20
  export declare const applyVideoEffectsDisposable: (track: MediaStreamTrack, effects: ReadonlyArray<EffectInput>) => DisposablePipeline;
21
21
  /**
22
- * Bind a track and a preset book; get the three verbs back
23
- * (`{ kaleidoscope, transform, mask }`). Presets live in the consumer's
22
+ * Bind a track and a preset book; get the four verbs back
23
+ * (`{ kaleidoscope, transform, mask, dispose }`). Presets live in the consumer's
24
24
  * project; these verbs drive them. On web each `kaleidoscope` preset switch and
25
25
  * each `transform` command rebuilds the Insertable-Streams pipeline and yields a
26
26
  * new output track, so read the live track from `onTrack` (or `controls.track`);
@@ -77,8 +77,8 @@ const applyVideoEffectsDisposable = (track, effects) => {
77
77
  };
78
78
  exports.applyVideoEffectsDisposable = applyVideoEffectsDisposable;
79
79
  /**
80
- * Bind a track and a preset book; get the three verbs back
81
- * (`{ kaleidoscope, transform, mask }`). Presets live in the consumer's
80
+ * Bind a track and a preset book; get the four verbs back
81
+ * (`{ kaleidoscope, transform, mask, dispose }`). Presets live in the consumer's
82
82
  * project; these verbs drive them. On web each `kaleidoscope` preset switch and
83
83
  * each `transform` command rebuilds the Insertable-Streams pipeline and yields a
84
84
  * new output track, so read the live track from `onTrack` (or `controls.track`);
@@ -1 +1 @@
1
- {"version":3,"file":"index.web.js","sourceRoot":"","sources":["../../src/index.web.ts"],"names":[],"mappings":"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","sourcesContent":["// Web entry point (source). This builds to `dist/index.web.js`, which web\n// bundlers resolve through the package's `browser` export condition. With an\n// `exports` map present, that condition supersedes platform-extension\n// resolution; the `.web.ts` suffix here is just our source convention, not what\n// selects this file at consume time.\n//\n// Every visual effect is one composite (a layer stack) or one transform. A\n// composite wires an Insertable-Streams stage that runs the whole layer stack\n// through the compositor and returns a new MediaStreamTrack carrying the\n// transformed frames. Pass the returned track to a `<video>` element or to\n// `RTCRtpSender.replaceTrack(...)`.\n\nimport {\n applyEffectToTrack,\n type DisposablePipeline,\n type FrameTransform,\n makeComposite,\n makeTransform,\n resetLayerUniforms as resetCompositeLayerUniforms,\n setLayerUniforms as setCompositeLayerUniforms,\n tuning,\n} from '../web-driver';\nimport {\n createControls,\n type Reconcile,\n type ResetLayerUniforms,\n type SetLayerUniforms,\n type SetMask,\n} from './kaleidoscope/controls';\nimport { toEffectSpec } from './kaleidoscope/effect';\nimport type { EffectInput, EffectSpec } from './kaleidoscope/effect.types';\nimport type { KaleidoscopeBinding, KaleidoscopeBindOptions } from './kaleidoscope/types';\nimport type { KaleidoscopePresetBook } from './kaleidoscope.preset-book.types';\n\n// The tuning channel (tuning.*) is internal: the mask edge flows from the mask()\n// verb (see bindKaleidoscope). Per-layer uniform tuning flows from the\n// kaleidoscope verb's patch path into the composite compositor's live channel. The\n// old global set* exports are gone; effects are driven by kaleidoscope /\n// transform / mask, not loose setters.\n\nexport type { CatalogImageId } from '../catalog/images';\nexport type {\n AnamorphicLensFlareUniforms,\n BlurUniforms,\n CloudsUniforms,\n CorporateBlobsUniforms,\n FirefliesUniforms,\n GodraysUniforms,\n LayerShaderName,\n LayerShaderOptions,\n LightBeamsAndMotesUniforms,\n NebulaUniforms,\n PatchableShaderName,\n PlasmaUniforms,\n ShaderUniformsMap,\n SimianlightsUniforms,\n UniformControl,\n} from '../catalog/shaders';\nexport {\n ANAMORPHIC_LENSFLARE_CONTROLS,\n BLUR_CONTROLS,\n CLOUDS_CONTROLS,\n CORPORATE_BLOBS_CONTROLS,\n defaultUniforms,\n FIREFLIES_CONTROLS,\n GODRAYS_CONTROLS,\n LIGHT_BEAMS_AND_MOTES_CONTROLS,\n NEBULA_CONTROLS,\n PLASMA_CONTROLS,\n SIMIANLIGHTS_CONTROLS,\n} from '../catalog/shaders';\nexport type {\n CompositeSpec,\n EffectInput,\n EffectName,\n EffectSpec,\n TransformName,\n TransformSpec,\n} from './kaleidoscope/effect.types';\nexport type {\n KaleidoscopeBinding,\n KaleidoscopeBindOptions,\n MaskInput,\n PatchesFor,\n PatchFor,\n TransformInput,\n} from './kaleidoscope/types';\nexport type {\n KaleidoscopeBlendMode,\n KaleidoscopeControls,\n KaleidoscopeLayer,\n KaleidoscopeLayerTarget,\n KaleidoscopePreset,\n KaleidoscopePresetBook,\n KaleidoscopePresetEntry,\n KaleidoscopeTaxonomy,\n} from './kaleidoscope.preset-book.types';\nexport type { RGB } from './lib/primitives.types';\n\nconst specToTransform = (spec: EffectSpec): FrameTransform => {\n switch (spec.name) {\n case 'flip-x':\n case 'flip-y':\n case 'rotate-cw':\n case 'rotate-ccw':\n return makeTransform(spec.name);\n case 'composite':\n // The layer stack runs as a single compositor stage (painter's order,\n // per-layer blend), not a serial chain of replace-stages. Blur, image, and\n // generative layers all live inside it.\n return makeComposite(spec.layers);\n }\n};\n\n/**\n * Like `applyVideoEffects`, but also returns a `dispose()` that tears down every\n * Insertable-Streams stage (stops each generator and aborts each pipe). The\n * LiveKit adapter (`react-native-webrtc-kaleidoscope/livekit`) uses this so a\n * camera flip (restart) or unpublish (destroy) does not leak generators. The\n * page-shared segmenter and WebGL state are module singletons reused across\n * stages, so they are intentionally NOT torn down here.\n */\nexport const applyVideoEffectsDisposable = (\n track: MediaStreamTrack,\n effects: ReadonlyArray<EffectInput>,\n): DisposablePipeline => {\n if (!track || track.kind !== 'video') {\n throw new Error('kaleidoscope: applyVideoEffects requires a video MediaStreamTrack');\n }\n if (effects.length === 0) {\n return { track, dispose: () => {} };\n }\n\n let current = track;\n const disposers: Array<() => void> = [];\n for (const input of effects) {\n const spec = toEffectSpec(input);\n const transform = specToTransform(spec);\n const stage = applyEffectToTrack(current, transform);\n current = stage.track;\n disposers.push(stage.dispose);\n }\n return {\n track: current,\n dispose: () => {\n // Tear down in reverse so downstream stages stop before their sources.\n for (const dispose of disposers.reverse()) {\n dispose();\n }\n },\n };\n};\n\n/**\n * Bind a track and a preset book; get the three verbs back\n * (`{ kaleidoscope, transform, mask }`). Presets live in the consumer's\n * project; these verbs drive them. On web each `kaleidoscope` preset switch and\n * each `transform` command rebuilds the Insertable-Streams pipeline and yields a\n * new output track, so read the live track from `onTrack` (or `controls.track`);\n * the prior pipeline is disposed each command and on `dispose()`. A `kaleidoscope`\n * patch of the active preset and `mask` both update what the running pipeline\n * reads per frame (no rebuild).\n */\nexport const bindKaleidoscope = <P extends KaleidoscopePresetBook>(\n track: MediaStreamTrack,\n options: KaleidoscopeBindOptions<P>,\n): KaleidoscopeBinding<P> => {\n let prevDispose = (): void => {};\n const reconcile: Reconcile = {\n apply: (specs) => {\n // Rebuild the whole pipeline from the base track each command, disposing\n // the previous one (generators/pipes) so stages don't leak.\n prevDispose();\n const { track: out, dispose } = applyVideoEffectsDisposable(track, specs);\n prevDispose = dispose;\n return out;\n },\n dispose: () => prevDispose(),\n };\n const setMask: SetMask = (hardness, threshold) => {\n tuning.setMaskHardness(hardness);\n tuning.setMaskThreshold(threshold);\n };\n // Live per-layer uniform channel: a patch of the active preset writes here\n // (keyed by layer id) and the running compositor merges it each frame.\n const setLayerUniforms: SetLayerUniforms = (id, uniforms) => {\n setCompositeLayerUniforms(id, uniforms);\n };\n // A preset switch drops every live override so reused layer ids revert to the\n // new preset's baked uniforms (see createControls).\n const resetLayerUniforms: ResetLayerUniforms = () => {\n resetCompositeLayerUniforms();\n };\n return createControls(track, options, reconcile, setMask, setLayerUniforms, resetLayerUniforms);\n};\n"]}
1
+ {"version":3,"file":"index.web.js","sourceRoot":"","sources":["../../src/index.web.ts"],"names":[],"mappings":";AAAA,0EAA0E;AAC1E,6EAA6E;AAC7E,sEAAsE;AACtE,gFAAgF;AAChF,qCAAqC;AACrC,EAAE;AACF,2EAA2E;AAC3E,8EAA8E;AAC9E,yEAAyE;AACzE,2EAA2E;AAC3E,oCAAoC;;;AAEpC,8CASuB;AACvB,sDAMiC;AACjC,kDAAqD;AA6BrD,8CAY4B;AAX1B,wHAAA,6BAA6B,OAAA;AAC7B,wGAAA,aAAa,OAAA;AACb,0GAAA,eAAe,OAAA;AACf,mHAAA,wBAAwB,OAAA;AACxB,0GAAA,eAAe,OAAA;AACf,6GAAA,kBAAkB,OAAA;AAClB,2GAAA,gBAAgB,OAAA;AAChB,yHAAA,8BAA8B,OAAA;AAC9B,0GAAA,eAAe,OAAA;AACf,0GAAA,eAAe,OAAA;AACf,gHAAA,qBAAqB,OAAA;AA8BvB,MAAM,eAAe,GAAG,CAAC,IAAgB,EAAkB,EAAE;IAC3D,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,KAAK,QAAQ,CAAC;QACd,KAAK,QAAQ,CAAC;QACd,KAAK,WAAW,CAAC;QACjB,KAAK,YAAY;YACf,OAAO,IAAA,0BAAa,EAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClC,KAAK,WAAW;YACd,sEAAsE;YACtE,2EAA2E;YAC3E,wCAAwC;YACxC,OAAO,IAAA,0BAAa,EAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC;AACH,CAAC,CAAC;AAEF;;;;;;;GAOG;AACI,MAAM,2BAA2B,GAAG,CACzC,KAAuB,EACvB,OAAmC,EACf,EAAE;IACtB,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CAAC,mEAAmE,CAAC,CAAC;IACvF,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,CAAC;IACtC,CAAC;IAED,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,MAAM,SAAS,GAAsB,EAAE,CAAC;IACxC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,IAAA,qBAAY,EAAC,KAAK,CAAC,CAAC;QACjC,MAAM,SAAS,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;QACxC,MAAM,KAAK,GAAG,IAAA,+BAAkB,EAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QACrD,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC;QACtB,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAChC,CAAC;IACD,OAAO;QACL,KAAK,EAAE,OAAO;QACd,OAAO,EAAE,GAAG,EAAE;YACZ,uEAAuE;YACvE,KAAK,MAAM,OAAO,IAAI,SAAS,CAAC,OAAO,EAAE,EAAE,CAAC;gBAC1C,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC,CAAC;AA7BW,QAAA,2BAA2B,GAA3B,2BAA2B,CA6BtC;AAEF;;;;;;;;;GASG;AACI,MAAM,gBAAgB,GAAG,CAC9B,KAAuB,EACvB,OAAmC,EACX,EAAE;IAC1B,IAAI,WAAW,GAAG,GAAS,EAAE,GAAE,CAAC,CAAC;IACjC,MAAM,SAAS,GAAc;QAC3B,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE;YACf,yEAAyE;YACzE,4DAA4D;YAC5D,WAAW,EAAE,CAAC;YACd,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,IAAA,QAAA,2BAA2B,EAAC,KAAK,EAAE,KAAK,CAAC,CAAC;YAC1E,WAAW,GAAG,OAAO,CAAC;YACtB,OAAO,GAAG,CAAC;QACb,CAAC;QACD,OAAO,EAAE,GAAG,EAAE,CAAC,WAAW,EAAE;KAC7B,CAAC;IACF,MAAM,OAAO,GAAY,CAAC,QAAQ,EAAE,SAAS,EAAE,EAAE;QAC/C,mBAAM,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;QACjC,mBAAM,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC;IACrC,CAAC,CAAC;IACF,2EAA2E;IAC3E,uEAAuE;IACvE,MAAM,gBAAgB,GAAqB,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE;QAC1D,IAAA,6BAAyB,EAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;IAC1C,CAAC,CAAC;IACF,8EAA8E;IAC9E,oDAAoD;IACpD,MAAM,kBAAkB,GAAuB,GAAG,EAAE;QAClD,IAAA,+BAA2B,GAAE,CAAC;IAChC,CAAC,CAAC;IACF,OAAO,IAAA,yBAAc,EAAC,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,CAAC,CAAC;AAClG,CAAC,CAAC;AA/BW,QAAA,gBAAgB,GAAhB,gBAAgB,CA+B3B","sourcesContent":["// Web entry point (source). This builds to `dist/index.web.js`, which web\n// bundlers resolve through the package's `browser` export condition. With an\n// `exports` map present, that condition supersedes platform-extension\n// resolution; the `.web.ts` suffix here is just our source convention, not what\n// selects this file at consume time.\n//\n// Every visual effect is one composite (a layer stack) or one transform. A\n// composite wires an Insertable-Streams stage that runs the whole layer stack\n// through the compositor and returns a new MediaStreamTrack carrying the\n// transformed frames. Pass the returned track to a `<video>` element or to\n// `RTCRtpSender.replaceTrack(...)`.\n\nimport {\n applyEffectToTrack,\n type DisposablePipeline,\n type FrameTransform,\n makeComposite,\n makeTransform,\n resetLayerUniforms as resetCompositeLayerUniforms,\n setLayerUniforms as setCompositeLayerUniforms,\n tuning,\n} from '../web-driver';\nimport {\n createControls,\n type Reconcile,\n type ResetLayerUniforms,\n type SetLayerUniforms,\n type SetMask,\n} from './kaleidoscope/controls';\nimport { toEffectSpec } from './kaleidoscope/effect';\nimport type { EffectInput, EffectSpec } from './kaleidoscope/effect.types';\nimport type { KaleidoscopeBinding, KaleidoscopeBindOptions } from './kaleidoscope/types';\nimport type { KaleidoscopePresetBook } from './kaleidoscope.preset-book.types';\n\n// The tuning channel (tuning.*) is internal: the mask edge flows from the mask()\n// verb (see bindKaleidoscope). Per-layer uniform tuning flows from the\n// kaleidoscope verb's patch path into the composite compositor's live channel. The\n// old global set* exports are gone; effects are driven by kaleidoscope /\n// transform / mask, not loose setters.\n\nexport type { CatalogImageId } from '../catalog/images';\nexport type {\n AnamorphicLensFlareUniforms,\n BlurUniforms,\n CloudsUniforms,\n CorporateBlobsUniforms,\n FirefliesUniforms,\n GodraysUniforms,\n LayerShaderName,\n LayerShaderOptions,\n LightBeamsAndMotesUniforms,\n NebulaUniforms,\n PatchableShaderName,\n PlasmaUniforms,\n ShaderUniformsMap,\n SimianlightsUniforms,\n UniformControl,\n} from '../catalog/shaders';\nexport {\n ANAMORPHIC_LENSFLARE_CONTROLS,\n BLUR_CONTROLS,\n CLOUDS_CONTROLS,\n CORPORATE_BLOBS_CONTROLS,\n defaultUniforms,\n FIREFLIES_CONTROLS,\n GODRAYS_CONTROLS,\n LIGHT_BEAMS_AND_MOTES_CONTROLS,\n NEBULA_CONTROLS,\n PLASMA_CONTROLS,\n SIMIANLIGHTS_CONTROLS,\n} from '../catalog/shaders';\nexport type {\n CompositeSpec,\n EffectInput,\n EffectName,\n EffectSpec,\n TransformName,\n TransformSpec,\n} from './kaleidoscope/effect.types';\nexport type {\n KaleidoscopeBinding,\n KaleidoscopeBindOptions,\n MaskInput,\n PatchesFor,\n PatchFor,\n TransformInput,\n} from './kaleidoscope/types';\nexport type {\n KaleidoscopeBlendMode,\n KaleidoscopeControls,\n KaleidoscopeLayer,\n KaleidoscopeLayerTarget,\n KaleidoscopePreset,\n KaleidoscopePresetBook,\n KaleidoscopePresetEntry,\n KaleidoscopeTaxonomy,\n} from './kaleidoscope.preset-book.types';\nexport type { RGB } from './lib/primitives.types';\n\nconst specToTransform = (spec: EffectSpec): FrameTransform => {\n switch (spec.name) {\n case 'flip-x':\n case 'flip-y':\n case 'rotate-cw':\n case 'rotate-ccw':\n return makeTransform(spec.name);\n case 'composite':\n // The layer stack runs as a single compositor stage (painter's order,\n // per-layer blend), not a serial chain of replace-stages. Blur, image, and\n // generative layers all live inside it.\n return makeComposite(spec.layers);\n }\n};\n\n/**\n * Like `applyVideoEffects`, but also returns a `dispose()` that tears down every\n * Insertable-Streams stage (stops each generator and aborts each pipe). The\n * LiveKit adapter (`react-native-webrtc-kaleidoscope/livekit`) uses this so a\n * camera flip (restart) or unpublish (destroy) does not leak generators. The\n * page-shared segmenter and WebGL state are module singletons reused across\n * stages, so they are intentionally NOT torn down here.\n */\nexport const applyVideoEffectsDisposable = (\n track: MediaStreamTrack,\n effects: ReadonlyArray<EffectInput>,\n): DisposablePipeline => {\n if (!track || track.kind !== 'video') {\n throw new Error('kaleidoscope: applyVideoEffects requires a video MediaStreamTrack');\n }\n if (effects.length === 0) {\n return { track, dispose: () => {} };\n }\n\n let current = track;\n const disposers: Array<() => void> = [];\n for (const input of effects) {\n const spec = toEffectSpec(input);\n const transform = specToTransform(spec);\n const stage = applyEffectToTrack(current, transform);\n current = stage.track;\n disposers.push(stage.dispose);\n }\n return {\n track: current,\n dispose: () => {\n // Tear down in reverse so downstream stages stop before their sources.\n for (const dispose of disposers.reverse()) {\n dispose();\n }\n },\n };\n};\n\n/**\n * Bind a track and a preset book; get the four verbs back\n * (`{ kaleidoscope, transform, mask, dispose }`). Presets live in the consumer's\n * project; these verbs drive them. On web each `kaleidoscope` preset switch and\n * each `transform` command rebuilds the Insertable-Streams pipeline and yields a\n * new output track, so read the live track from `onTrack` (or `controls.track`);\n * the prior pipeline is disposed each command and on `dispose()`. A `kaleidoscope`\n * patch of the active preset and `mask` both update what the running pipeline\n * reads per frame (no rebuild).\n */\nexport const bindKaleidoscope = <P extends KaleidoscopePresetBook>(\n track: MediaStreamTrack,\n options: KaleidoscopeBindOptions<P>,\n): KaleidoscopeBinding<P> => {\n let prevDispose = (): void => {};\n const reconcile: Reconcile = {\n apply: (specs) => {\n // Rebuild the whole pipeline from the base track each command, disposing\n // the previous one (generators/pipes) so stages don't leak.\n prevDispose();\n const { track: out, dispose } = applyVideoEffectsDisposable(track, specs);\n prevDispose = dispose;\n return out;\n },\n dispose: () => prevDispose(),\n };\n const setMask: SetMask = (hardness, threshold) => {\n tuning.setMaskHardness(hardness);\n tuning.setMaskThreshold(threshold);\n };\n // Live per-layer uniform channel: a patch of the active preset writes here\n // (keyed by layer id) and the running compositor merges it each frame.\n const setLayerUniforms: SetLayerUniforms = (id, uniforms) => {\n setCompositeLayerUniforms(id, uniforms);\n };\n // A preset switch drops every live override so reused layer ids revert to the\n // new preset's baked uniforms (see createControls).\n const resetLayerUniforms: ResetLayerUniforms = () => {\n resetCompositeLayerUniforms();\n };\n return createControls(track, options, reconcile, setMask, setLayerUniforms, resetLayerUniforms);\n};\n"]}
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- // The three-verb controls: shared composite-state machine, platform-agnostic.
2
+ // The four-verb controls: shared composite-state machine, platform-agnostic.
3
3
  //
4
4
  // Holds the art effect (one composite) and the transform op list, reconciles
5
5
  // them into an ordered EffectSpec array (art FIRST so segmentation sees the
@@ -1 +1 @@
1
- {"version":3,"file":"controls.js","sourceRoot":"","sources":["../../../src/kaleidoscope/controls.ts"],"names":[],"mappings":"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","sourcesContent":["// The three-verb controls: shared composite-state machine, platform-agnostic.\n//\n// Holds the art effect (one composite) and the transform op list, reconciles\n// them into an ordered EffectSpec array (art FIRST so segmentation sees the\n// upright frame, transform LAST so it reorients the finished composite), and\n// applies them through an injected platform `reconcile`. Web rebuilds the\n// pipeline and yields a new track (disposing the prior one); native mutates in\n// place. `mask` writes the segmentation edge through an injected `setMask`; the\n// running composite reads it per frame, so it needs no rebuild.\n//\n// The art verb is rebuild-aware: switching to a different preset rebuilds, but\n// patching the currently-active preset routes through an injected live\n// layer-uniform channel (`setLayerUniforms`, keyed by layer id) so a slider drag\n// updates the running composite without a rebuild.\n\nimport type { KaleidoscopePreset, KaleidoscopePresetBook } from '../kaleidoscope.preset-book.types';\nimport type { EffectSpec, TransformName } from './effect.types';\nimport { compositeToEffectSpec } from './shader-to-spec';\nimport type { KaleidoscopeBinding, KaleidoscopeBindOptions, TransformInput } from './types';\n\n/** Apply the ordered specs to the base track and return the output. */\nexport type Reconcile = {\n apply: (specs: ReadonlyArray<EffectSpec>) => MediaStreamTrack;\n dispose: () => void;\n};\n\n/** Write the segmentation mask edge (platform tuning channel). */\nexport type SetMask = (hardness: number, threshold: number) => void;\n\n/**\n * Write a live per-layer uniform override (platform tuning channel), keyed by\n * layer id. The running composite merges these over the layer's baked uniforms\n * each frame, with no rebuild. Mirrors `setMask`.\n */\nexport type SetLayerUniforms = (\n id: string,\n uniforms: Readonly<Record<string, number | readonly number[]>>,\n) => void;\n\n/**\n * Drop every live per-layer override (platform tuning channel). A preset switch\n * calls this so a reused layer id reverts to the new preset's baked uniforms\n * rather than inheriting a stale override from the prior preset.\n */\nexport type ResetLayerUniforms = () => void;\n\n// Decompose an absolute transform into the discrete ops the pipeline already\n// runs (reused on web and native). Flips first, then rotation; rotation snaps to\n// the nearest 90°. 180° is two CW steps; 270° is one CCW step.\nconst decomposeTransform = (t?: TransformInput): EffectSpec[] => {\n if (!t) return [];\n const names: TransformName[] = [];\n if (t.flip?.x) names.push('flip-x');\n if (t.flip?.y) names.push('flip-y');\n const deg = (((Math.round((t.rotate ?? 0) / 90) * 90) % 360) + 360) % 360;\n if (deg === 90) names.push('rotate-cw');\n else if (deg === 180) names.push('rotate-cw', 'rotate-cw');\n else if (deg === 270) names.push('rotate-ccw');\n return names.map((name) => ({ name }));\n};\n\nexport const createControls = <P extends KaleidoscopePresetBook>(\n baseTrack: MediaStreamTrack,\n { presets, onTrack }: KaleidoscopeBindOptions<P>,\n reconcile: Reconcile,\n setMask: SetMask,\n setLayerUniforms: SetLayerUniforms,\n resetLayerUniforms: ResetLayerUniforms,\n): KaleidoscopeBinding<P> => {\n let art: EffectSpec | null = null;\n let transformOps: EffectSpec[] = [];\n let current = baseTrack;\n // The id of the active preset (null when cleared). A patch of THIS id routes\n // through the live channel; any other cmd rebuilds.\n let activeId: keyof P | null = null;\n\n const apply = (): void => {\n const specs: EffectSpec[] = [];\n if (art) specs.push(art);\n specs.push(...transformOps);\n current = reconcile.apply(specs);\n onTrack?.(current);\n };\n\n return {\n kaleidoscope: (\n cmd: keyof P | null,\n patches?: ReadonlyArray<{\n readonly id: string;\n readonly uniforms: Readonly<Record<string, number | readonly number[]>>;\n }>,\n ) => {\n // Patch the currently-active preset: route through the live no-rebuild\n // channel, keyed by layer id. The `shader` field on a patch is only for\n // narrowing; the channel resolves by `id`.\n if (cmd != null && cmd === activeId && patches && patches.length > 0) {\n for (const patch of patches) {\n setLayerUniforms(patch.id, patch.uniforms);\n }\n return;\n }\n // Switch the preset (or clear): rebuild. Drop every live override first so\n // a reused layer id (e.g. 'blur', shared by the low/medium/high blur\n // presets) takes the new preset's baked uniforms instead of carrying a\n // stale slider override across. Patches given WITH the switch (e.g. a\n // persisted selection's overrides) merge into the rebuilt stack itself,\n // so they land on every platform, native included. A transform rebuild\n // does NOT pass through here, so slider tweaks survive flips/rotations\n // of the active preset.\n activeId = cmd;\n art = cmd == null ? null : compositeToEffectSpec(presets[cmd] as KaleidoscopePreset, patches);\n resetLayerUniforms();\n apply();\n },\n transform: (t) => {\n transformOps = decomposeTransform(t);\n apply();\n },\n mask: (m) => {\n // Updates the edge the per-frame composite reads; no pipeline rebuild.\n setMask(m.hardness, m.threshold);\n },\n get track() {\n return current;\n },\n dispose: () => {\n reconcile.dispose();\n },\n };\n};\n"]}
1
+ {"version":3,"file":"controls.js","sourceRoot":"","sources":["../../../src/kaleidoscope/controls.ts"],"names":[],"mappings":"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","sourcesContent":["// The four-verb controls: shared composite-state machine, platform-agnostic.\n//\n// Holds the art effect (one composite) and the transform op list, reconciles\n// them into an ordered EffectSpec array (art FIRST so segmentation sees the\n// upright frame, transform LAST so it reorients the finished composite), and\n// applies them through an injected platform `reconcile`. Web rebuilds the\n// pipeline and yields a new track (disposing the prior one); native mutates in\n// place. `mask` writes the segmentation edge through an injected `setMask`; the\n// running composite reads it per frame, so it needs no rebuild.\n//\n// The art verb is rebuild-aware: switching to a different preset rebuilds, but\n// patching the currently-active preset routes through an injected live\n// layer-uniform channel (`setLayerUniforms`, keyed by layer id) so a slider drag\n// updates the running composite without a rebuild.\n\nimport type { KaleidoscopePreset, KaleidoscopePresetBook } from '../kaleidoscope.preset-book.types';\nimport type { EffectSpec, TransformName } from './effect.types';\nimport { compositeToEffectSpec } from './shader-to-spec';\nimport type { KaleidoscopeBinding, KaleidoscopeBindOptions, TransformInput } from './types';\n\n/** Apply the ordered specs to the base track and return the output. */\nexport type Reconcile = {\n apply: (specs: ReadonlyArray<EffectSpec>) => MediaStreamTrack;\n dispose: () => void;\n};\n\n/** Write the segmentation mask edge (platform tuning channel). */\nexport type SetMask = (hardness: number, threshold: number) => void;\n\n/**\n * Write a live per-layer uniform override (platform tuning channel), keyed by\n * layer id. The running composite merges these over the layer's baked uniforms\n * each frame, with no rebuild. Mirrors `setMask`.\n */\nexport type SetLayerUniforms = (\n id: string,\n uniforms: Readonly<Record<string, number | readonly number[]>>,\n) => void;\n\n/**\n * Drop every live per-layer override (platform tuning channel). A preset switch\n * calls this so a reused layer id reverts to the new preset's baked uniforms\n * rather than inheriting a stale override from the prior preset.\n */\nexport type ResetLayerUniforms = () => void;\n\n// Decompose an absolute transform into the discrete ops the pipeline already\n// runs (reused on web and native). Flips first, then rotation; rotation snaps to\n// the nearest 90°. 180° is two CW steps; 270° is one CCW step.\nconst decomposeTransform = (t?: TransformInput): EffectSpec[] => {\n if (!t) return [];\n const names: TransformName[] = [];\n if (t.flip?.x) names.push('flip-x');\n if (t.flip?.y) names.push('flip-y');\n const deg = (((Math.round((t.rotate ?? 0) / 90) * 90) % 360) + 360) % 360;\n if (deg === 90) names.push('rotate-cw');\n else if (deg === 180) names.push('rotate-cw', 'rotate-cw');\n else if (deg === 270) names.push('rotate-ccw');\n return names.map((name) => ({ name }));\n};\n\nexport const createControls = <P extends KaleidoscopePresetBook>(\n baseTrack: MediaStreamTrack,\n { presets, onTrack }: KaleidoscopeBindOptions<P>,\n reconcile: Reconcile,\n setMask: SetMask,\n setLayerUniforms: SetLayerUniforms,\n resetLayerUniforms: ResetLayerUniforms,\n): KaleidoscopeBinding<P> => {\n let art: EffectSpec | null = null;\n let transformOps: EffectSpec[] = [];\n let current = baseTrack;\n // The id of the active preset (null when cleared). A patch of THIS id routes\n // through the live channel; any other cmd rebuilds.\n let activeId: keyof P | null = null;\n\n const apply = (): void => {\n const specs: EffectSpec[] = [];\n if (art) specs.push(art);\n specs.push(...transformOps);\n current = reconcile.apply(specs);\n onTrack?.(current);\n };\n\n return {\n kaleidoscope: (\n cmd: keyof P | null,\n patches?: ReadonlyArray<{\n readonly id: string;\n readonly uniforms: Readonly<Record<string, number | readonly number[]>>;\n }>,\n ) => {\n // Patch the currently-active preset: route through the live no-rebuild\n // channel, keyed by layer id. The `shader` field on a patch is only for\n // narrowing; the channel resolves by `id`.\n if (cmd != null && cmd === activeId && patches && patches.length > 0) {\n for (const patch of patches) {\n setLayerUniforms(patch.id, patch.uniforms);\n }\n return;\n }\n // Switch the preset (or clear): rebuild. Drop every live override first so\n // a reused layer id (e.g. 'blur', shared by the low/medium/high blur\n // presets) takes the new preset's baked uniforms instead of carrying a\n // stale slider override across. Patches given WITH the switch (e.g. a\n // persisted selection's overrides) merge into the rebuilt stack itself,\n // so they land on every platform, native included. A transform rebuild\n // does NOT pass through here, so slider tweaks survive flips/rotations\n // of the active preset.\n activeId = cmd;\n art = cmd == null ? null : compositeToEffectSpec(presets[cmd] as KaleidoscopePreset, patches);\n resetLayerUniforms();\n apply();\n },\n transform: (t) => {\n transformOps = decomposeTransform(t);\n apply();\n },\n mask: (m) => {\n // Updates the edge the per-frame composite reads; no pipeline rebuild.\n setMask(m.hardness, m.threshold);\n },\n get track() {\n return current;\n },\n dispose: () => {\n reconcile.dispose();\n },\n };\n};\n"]}
@@ -64,7 +64,7 @@ export type KaleidoscopeBindOptions<P extends KaleidoscopePresetBook> = {
64
64
  */
65
65
  type KaleidoscopeCommand<P extends KaleidoscopePresetBook> = <K extends keyof P>(cmd: K | null, patches?: PatchesFor<P, K>) => void;
66
66
  /**
67
- * The three verbs for one bound track and book, plus the live track and a
67
+ * The four verbs for one bound track and book, plus the live track and a
68
68
  * teardown. `kaleidoscope` (preset switch) and `transform` rebuild the composite
69
69
  * (web yields a new track via onTrack); a `kaleidoscope` patch of the active
70
70
  * preset and `mask` both update what the running composite reads each frame, so
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/kaleidoscope/types.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AACpF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAEhF;;;;;;GAMG;AACH,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,CAAC,SAAS;IAClC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC,SAAS,MAAM,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,SAAS,mBAAmB,CAAC;CACtD,GACG;IAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAA;CAAE,GACpE,KAAK,CAAC;AAEV;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,sBAAsB,EAAE,CAAC,SAAS,MAAM,CAAC,IAAI,aAAa,CACzF,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CACjC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,oCAAoC;IACpC,QAAQ,CAAC,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC/D,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,+EAA+E;AAC/E,MAAM,MAAM,SAAS,GAAG;IACtB,yDAAyD;IACzD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,+EAA+E;IAC/E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,uBAAuB,CAAC,CAAC,SAAS,sBAAsB,IAAI;IACtE,0FAA0F;IAC1F,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;CACtD,CAAC;AAEF;;;;;;;GAOG;AACH,KAAK,mBAAmB,CAAC,CAAC,SAAS,sBAAsB,IAAI,CAAC,CAAC,SAAS,MAAM,CAAC,EAC7E,GAAG,EAAE,CAAC,GAAG,IAAI,EACb,OAAO,CAAC,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,KACvB,IAAI,CAAC;AAEV;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB,CAAC,CAAC,SAAS,sBAAsB;IACnE,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC;IAC9C,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;IACjD,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,SAAS,KAAK,IAAI,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;CAC9B"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/kaleidoscope/types.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AACpF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAEhF;;;;;;GAMG;AACH,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,CAAC,SAAS;IAClC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC,SAAS,MAAM,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,SAAS,mBAAmB,CAAC;CACtD,GACG;IAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAA;CAAE,GACpE,KAAK,CAAC;AAEV;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,sBAAsB,EAAE,CAAC,SAAS,MAAM,CAAC,IAAI,aAAa,CACzF,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CACjC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,oCAAoC;IACpC,QAAQ,CAAC,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC/D,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,+EAA+E;AAC/E,MAAM,MAAM,SAAS,GAAG;IACtB,yDAAyD;IACzD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,+EAA+E;IAC/E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,uBAAuB,CAAC,CAAC,SAAS,sBAAsB,IAAI;IACtE,0FAA0F;IAC1F,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;CACtD,CAAC;AAEF;;;;;;;GAOG;AACH,KAAK,mBAAmB,CAAC,CAAC,SAAS,sBAAsB,IAAI,CAAC,CAAC,SAAS,MAAM,CAAC,EAC7E,GAAG,EAAE,CAAC,GAAG,IAAI,EACb,OAAO,CAAC,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,KACvB,IAAI,CAAC;AAEV;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB,CAAC,CAAC,SAAS,sBAAsB;IACnE,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC;IAC9C,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;IACjD,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,SAAS,KAAK,IAAI,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;CAC9B"}
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
- // The three-verb surface: types.
2
+ // The four-verb surface: types.
3
3
  //
4
- // Bind a track and a preset book once; get three typed verbs back:
4
+ // Bind a track and a preset book once; get four typed verbs back:
5
5
  // - kaleidoscope(cmd, patches?) the art axis: which composite (layer stack)
6
6
  // fills the frame. cmd is a preset id from the book (narrowed), or null to
7
7
  // clear. patches optionally merge per-layer uniform overrides (addressed by
@@ -9,6 +9,7 @@
9
9
  // no-rebuild channel, so sliders stay smooth.
10
10
  // - transform(t?) the geometry axis: absolute flips + 90° rotation.
11
11
  // - mask(m) the segmentation edge shared by every art effect.
12
+ // - dispose() tear down the pipeline; release the bound track.
12
13
  //
13
14
  // Shaders live in the library; consumers add presets (composites) over them,
14
15
  // never new shaders. Per shader-world convention, numeric uniforms are