@pixerate/flair 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +254 -2
- package/dist/catalog/index.d.ts +74 -0
- package/dist/catalog/index.js +47 -0
- package/dist/catalog/index.js.map +1 -0
- package/dist/chunk-36DTNSHU.js +160 -0
- package/dist/chunk-36DTNSHU.js.map +1 -0
- package/dist/chunk-3FAUZOGL.js +331 -0
- package/dist/chunk-3FAUZOGL.js.map +1 -0
- package/dist/chunk-CFCLT2XL.js +1 -0
- package/dist/chunk-CFCLT2XL.js.map +1 -0
- package/dist/chunk-EHDJ3CPJ.js +327 -0
- package/dist/chunk-EHDJ3CPJ.js.map +1 -0
- package/dist/chunk-G5FNZDTA.js +536 -0
- package/dist/chunk-G5FNZDTA.js.map +1 -0
- package/dist/chunk-HKXOHRCD.js +763 -0
- package/dist/chunk-HKXOHRCD.js.map +1 -0
- package/dist/chunk-J55UBNAT.js +164 -0
- package/dist/chunk-J55UBNAT.js.map +1 -0
- package/dist/chunk-JWZCPMFG.js +2074 -0
- package/dist/chunk-JWZCPMFG.js.map +1 -0
- package/dist/chunk-MWVTQFV5.js +1894 -0
- package/dist/chunk-MWVTQFV5.js.map +1 -0
- package/dist/chunk-UQQNZSYL.js +594 -0
- package/dist/chunk-UQQNZSYL.js.map +1 -0
- package/dist/chunk-WMZQCUFJ.js +73 -0
- package/dist/chunk-WMZQCUFJ.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +331 -0
- package/dist/cli.js.map +1 -0
- package/dist/clock-BhdXWH-n.d.ts +47 -0
- package/dist/controls-DdXMbovi.d.ts +97 -0
- package/dist/editor/index.d.ts +413 -0
- package/dist/editor/index.js +1009 -0
- package/dist/editor/index.js.map +1 -0
- package/dist/export-FEBPEEO7.js +22 -0
- package/dist/export-FEBPEEO7.js.map +1 -0
- package/dist/index.d.ts +1431 -0
- package/dist/index.js +1263 -0
- package/dist/index.js.map +1 -0
- package/dist/motion-D0TVv2CF.d.ts +148 -0
- package/dist/node.d.ts +24 -0
- package/dist/node.js +10 -0
- package/dist/node.js.map +1 -0
- package/dist/recorder-WJTWMEQN.js +10 -0
- package/dist/recorder-WJTWMEQN.js.map +1 -0
- package/dist/renderer-Oyynz-fs.d.ts +293 -0
- package/dist/types-CkKkavvB.d.ts +71 -0
- package/dist/vector/index.d.ts +83 -0
- package/dist/vector/index.js +39 -0
- package/dist/vector/index.js.map +1 -0
- package/package.json +96 -4
package/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jack James
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,255 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @pixerate/flair
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<p align="center">
|
|
4
|
+
<strong>Visual effects and motion graphics for the web: WebGL2 shader effects, animated vector effects, a catalog of ready-made looks, and a UI-agnostic, agent-ready effect editor.</strong>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://www.npmjs.com/package/@pixerate/flair"><img src="https://img.shields.io/npm/v/@pixerate/flair.svg?style=flat-square&color=violet" alt="npm version" /></a>
|
|
9
|
+
<a href="https://github.com/Pixerate/flair/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/Pixerate/flair/ci.yml?branch=main&style=flat-square" alt="CI build status" /></a>
|
|
10
|
+
<a href="https://pixerate.github.io/flair/"><img src="https://img.shields.io/badge/demo-live-a855f7.svg?style=flat-square" alt="Live demo" /></a>
|
|
11
|
+
<a href="https://github.com/Pixerate/flair/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square" alt="MIT License" /></a>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 🌟 Overview
|
|
17
|
+
|
|
18
|
+
Flair gives products a library of good-looking, configurable visual effects (hero backgrounds, textures, light, image treatments, transitions, loaders, animated icons and charts) and the tools to make more of them, by hand or with an agent.
|
|
19
|
+
|
|
20
|
+
### Core Capabilities
|
|
21
|
+
|
|
22
|
+
- 🎨 **Effect catalog (`@pixerate/flair/catalog`)**: 59 effects, each with controls and presets. There are 44 WebGL2 shaders (gradients, organic textures, patterns, light, space, retro, image effects, transitions) and 15 animated SVG effects (loaders, status icons, charts, counters). [Browse them live](https://pixerate.github.io/flair/).
|
|
23
|
+
- ⚡ **Shader engine**:
|
|
24
|
+
- Shadertoy-style GLSL ES 3.00 fragment shaders, with controls declared in the source (`// @control`), including image inputs.
|
|
25
|
+
- Compile errors come back with line numbers that match the source.
|
|
26
|
+
- Live previews (`ShaderRenderer`) and many animated thumbnails on one context (`ThumbnailPool`).
|
|
27
|
+
- Frames at exact times (`ShaderFrames`), looping GIFs, and standalone HTML pages.
|
|
28
|
+
- ✒️ **Vector motion (`@pixerate/flair/vector`)**: animated SVG described as data. It covers attribute tweens, draw-on strokes, motion along paths, counters and view zooms. Every frame is a pure function of time (`svgFrame`), and `SvgPlayer` plays it.
|
|
29
|
+
- 🎛️ **Effect editor (`@pixerate/flair/editor`)**:
|
|
30
|
+
- A headless editor for an effect's source, values, keyframes, presets and timeline.
|
|
31
|
+
- Built on [`@pixerate/editor`](https://github.com/Pixerate/editor)'s undo history and command registry, with no UI framework assumed.
|
|
32
|
+
- 🤖 **Agent-native, bring your own model**:
|
|
33
|
+
- Every editing action is a command, and the commands are exposed as MCP-shaped tools.
|
|
34
|
+
- Ships instructions for the model. The host supplies the model, keys and agent loop.
|
|
35
|
+
- 🎬 **Directing and interchange**: multi-shot scenes with camera moves, procedural effects, validation and fast headless simulation. Also blend modes, Oklab colour and grading, retiming, timecode, and export to Lottie, Apple Motion, FCP7 XML and CMX 3600 EDL.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 📦 Entry points
|
|
40
|
+
|
|
41
|
+
| Import | What it provides | Runtime dependencies |
|
|
42
|
+
| :--- | :--- | :--- |
|
|
43
|
+
| `@pixerate/flair` | Shader engine, vector motion, time and easing, colour, blending, transitions, scenes, interchange exporters | None (browser-safe) |
|
|
44
|
+
| `@pixerate/flair/catalog` | `EFFECTS`, `getEffect`, `findEffects`, `effectDefaults`, `renderVectorEffect`, GLSL helpers, sample images | None |
|
|
45
|
+
| `@pixerate/flair/vector` | `SvgMotion`, `svgFrame`, `svgView`, `SvgPlayer`, `svgMotionIssues` | None |
|
|
46
|
+
| `@pixerate/flair/editor` | `EffectEditor`, effect documents, keyframe tracks, renderer bindings, agent tools and instructions | `@pixerate/editor` (optional peer) |
|
|
47
|
+
| `@pixerate/flair/node` | `exportMotionGraphic`: renders a shader to GIF/MP4 through headless Chrome and ffmpeg | Node.js |
|
|
48
|
+
|
|
49
|
+
The `flair` CLI inspects shaders, writes standalone pages and directs scenes (see [CLI](#-cli)).
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 🏗️ Architecture
|
|
54
|
+
|
|
55
|
+
```mermaid
|
|
56
|
+
graph TD
|
|
57
|
+
subgraph Effects["Effects (no dependencies)"]
|
|
58
|
+
Catalog["catalog: 59 effects + presets"]
|
|
59
|
+
Shader["shader engine: controls, compile, render, GIF, HTML"]
|
|
60
|
+
Vector["vector motion: SvgMotion, SvgPlayer"]
|
|
61
|
+
Time["time: clocks, easing, keyframes, noise"]
|
|
62
|
+
end
|
|
63
|
+
subgraph Editing["Editing (@pixerate/flair/editor)"]
|
|
64
|
+
Doc["EffectDocument: source, values, tracks, presets"]
|
|
65
|
+
Editor["EffectEditor: state, playhead"]
|
|
66
|
+
Cmds["commands → MCP tools"]
|
|
67
|
+
end
|
|
68
|
+
PE["@pixerate/editor: history, commands"]
|
|
69
|
+
Host["Your app: UI (any framework) + your model"]
|
|
70
|
+
|
|
71
|
+
Catalog --> Shader
|
|
72
|
+
Catalog --> Vector
|
|
73
|
+
Doc --> Editor
|
|
74
|
+
Editor --> Cmds
|
|
75
|
+
PE --> Editor
|
|
76
|
+
PE --> Cmds
|
|
77
|
+
Editor -- bindShaderRenderer / bindSvgView --> Shader
|
|
78
|
+
Editor --> Vector
|
|
79
|
+
Host --> Editor
|
|
80
|
+
Host -- tool calls --> Cmds
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Flair stays free of LLM SDKs and UI frameworks. Your app owns the interface and the model. Flair owns the rendering, the effect model and the tool surface.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 🚀 Quickstart
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
npm install @pixerate/flair
|
|
91
|
+
# For the editor:
|
|
92
|
+
npm install @pixerate/flair @pixerate/editor
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 1. Drop in a catalog effect
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import { ShaderRenderer, SvgPlayer } from "@pixerate/flair";
|
|
99
|
+
import { getEffect, effectDefaults, renderVectorEffect } from "@pixerate/flair/catalog";
|
|
100
|
+
|
|
101
|
+
// A shader background
|
|
102
|
+
const aurora = getEffect("aurora")!;
|
|
103
|
+
if (aurora.kind === "shader") {
|
|
104
|
+
const preview = new ShaderRenderer(document.querySelector("#hero")!);
|
|
105
|
+
preview.setSource(aurora.source);
|
|
106
|
+
preview.setValues(effectDefaults(aurora, { preset: "Ember", values: { uSpeed: 0.5 } }));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// An animated SVG loader, recoloured
|
|
110
|
+
const spinner = getEffect("spinner")!;
|
|
111
|
+
if (spinner.kind === "vector") {
|
|
112
|
+
new SvgPlayer(document.querySelector("#loading")!, renderVectorEffect(spinner, { accent: "#0ea5e9" }), {
|
|
113
|
+
hold: spinner.hold,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Find effects with `findEffects({ text: "glow", category: "light", kind: "shader" })`. Each effect has an `id`, `name`, `description`, `category`, `tags` and optional `presets`. Image effects default to built-in sample pictures, so they look right before you set `uImage` to your own URL.
|
|
119
|
+
|
|
120
|
+
### 2. Write your own shader
|
|
121
|
+
|
|
122
|
+
```glsl
|
|
123
|
+
// Controls are uniforms annotated on the same line.
|
|
124
|
+
uniform vec3 uTint; // @control color default=#77C16A label="Tint"
|
|
125
|
+
uniform float uSpeed; // @control min=0 max=4 default=1 label="Speed"
|
|
126
|
+
uniform sampler2D uPhoto; // @control filter=linear wrap=clamp label="Photo"
|
|
127
|
+
|
|
128
|
+
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
129
|
+
vec2 uv = fragCoord / iResolution.xy;
|
|
130
|
+
vec3 photo = texture(uPhoto, uv + 0.01 * sin(uv.yx * 20.0 + iTime * uSpeed)).rgb;
|
|
131
|
+
fragColor = vec4(photo * uTint, 1.0);
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
import { ShaderRenderer, ShaderFrames, recordGif, standaloneHtml, parseControls } from "@pixerate/flair";
|
|
137
|
+
|
|
138
|
+
const status = renderer.setSource(source); // { ok, errors: [{ line, message }] }
|
|
139
|
+
parseControls(source); // [{ name: "uTint", type: "vec3", color: true, default: [...] }, ...]
|
|
140
|
+
const gif = await recordGif({ source, width: 480, height: 270, fps: 30, duration: 3 });
|
|
141
|
+
const page = standaloneHtml("My effect", source, { uSpeed: 2 });
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### 3. Build an editor (any UI)
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import { createEffectEditor, bindShaderRenderer, bindSvgView } from "@pixerate/flair/editor";
|
|
148
|
+
import { ShaderRenderer } from "@pixerate/flair";
|
|
149
|
+
|
|
150
|
+
const editor = createEffectEditor({ from: "nebula" });
|
|
151
|
+
const renderer = new ShaderRenderer(previewElement);
|
|
152
|
+
bindShaderRenderer(editor, renderer); // source, values at the playhead, time; compile errors flow back
|
|
153
|
+
|
|
154
|
+
editor.subscribe((state) => render(state)); // state: document, controls, values, issues, compile, time, history
|
|
155
|
+
editor.setValue("uDensity", 1.4); // checked and undoable; slider drags merge into one step
|
|
156
|
+
editor.setKeyframe("uSpeed", 2000, 0.2, "ease-in-out");
|
|
157
|
+
editor.applyPreset("Ember");
|
|
158
|
+
editor.play();
|
|
159
|
+
editor.undo();
|
|
160
|
+
|
|
161
|
+
// Menus, shortcuts and command palettes run the same commands by id:
|
|
162
|
+
editor.commands.execute("values.randomize");
|
|
163
|
+
window.addEventListener("keydown", (e) => editor.commands.handleKeyDown(e)); // Space plays/pauses
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`getState()` returns the same object until something changes, so React's `useSyncExternalStore` works as is. Documents are plain JSON (`EffectDocument`), and keyframe tracks use `@pixerate/editor`'s keyframe shape and easing names.
|
|
167
|
+
|
|
168
|
+
### 4. Let an agent drive it
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
import { createEffectToolProvider, toolDefinitions, callTool, EFFECT_AGENT_INSTRUCTIONS } from "@pixerate/flair/editor";
|
|
172
|
+
|
|
173
|
+
const tools = createEffectToolProvider(editor, {
|
|
174
|
+
capture: () => renderer.capture({ type: "image/png" }), // adds a `view_effect` tool so the model can look
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Give your model EFFECT_AGENT_INSTRUCTIONS and toolDefinitions(tools), then for each tool call:
|
|
178
|
+
const result = await callTool(tools, call.name, call.input); // MCP tools/call result
|
|
179
|
+
|
|
180
|
+
// Or register the provider with @pixerate/editor's ToolRegistry, or bridge it to an MCP server.
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
The tools are `list_commands`, `run_command` and `run_commands`, plus `view_effect` when you can capture the preview. The commands they run:
|
|
184
|
+
|
|
185
|
+
- **Inspect and start:** `effect.describe`, `catalog.search`, `effect.load`.
|
|
186
|
+
- **Edit the effect:** `effect.setSource`.
|
|
187
|
+
- **Values and presets:** `values.set`, `values.reset`, `values.randomize`, `preset.apply` and `preset.save`.
|
|
188
|
+
- **Keyframes:** `keyframe.set`, `keyframe.remove`, `keyframe.setEasing` and `keyframe.clear`.
|
|
189
|
+
- **Timeline:** `timeline.setTiming`, `timeline.play` and `timeline.seek`.
|
|
190
|
+
- **History:** `history.undo` and `history.redo`.
|
|
191
|
+
|
|
192
|
+
Every change is one undo step, and refusals come back as messages the model can act on.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## 🎨 The catalog
|
|
197
|
+
|
|
198
|
+
| Category | Effects |
|
|
199
|
+
| :--- | :--- |
|
|
200
|
+
| gradient | mesh gradient, aurora, gradient flow, conic sweep, silk |
|
|
201
|
+
| organic | liquid marble, smoke, lava lamp, plasma, caustics, flames, blob morph |
|
|
202
|
+
| pattern | dot wave, Truchet flow, hex pulse, living cells, moiré rings, kaleidoscope, stripes |
|
|
203
|
+
| light / space | bokeh, god rays, sparkles, neon ripple, warp starfield, nebula, event horizon |
|
|
204
|
+
| retro | synthwave, digital rain, dithered gradient, TV static |
|
|
205
|
+
| image | ripple, glitch, duotone, halftone, pixel reveal, CRT screen, film look |
|
|
206
|
+
| transition | wipe, iris, block dissolve, grid wipe, glass wipe, twister, light leak, cross zoom |
|
|
207
|
+
| loader / icon / data | spinner, bouncing dots, pulse ping, equalizer, progress ring, bar chart, line chart, stat counter, route, success check, error cross, heartbeat, signature, confetti |
|
|
208
|
+
|
|
209
|
+
Every shader is self-contained and compiles on WebGL2. Open [`#check`](https://pixerate.github.io/flair/#check) on the demo to compile the catalog on your own GPU.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## 💻 CLI
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
npx flair controls ./effect.frag # list @control uniforms (--json for JSON)
|
|
217
|
+
npx flair html ./effect.frag --out page.html --open
|
|
218
|
+
npx flair scene validate ./scene.json # layout, framing and timing checks
|
|
219
|
+
npx flair scene review ./scene.json # fast headless simulation + review
|
|
220
|
+
npx flair scene lottie ./scene.json out.json # also: fcp7, edl, motn
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## 🧪 Interactive Browser Demo
|
|
226
|
+
|
|
227
|
+
The [live demo](https://pixerate.github.io/flair/) has three parts:
|
|
228
|
+
|
|
229
|
+
- **Gallery:** the whole catalog, live.
|
|
230
|
+
- **Editor:** controls, presets, keyframes, source and export, built with React on the headless editor.
|
|
231
|
+
- **Agent tools:** the tool surface, plus a sample agent plan you can run.
|
|
232
|
+
|
|
233
|
+
To run it locally:
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
pnpm install
|
|
237
|
+
pnpm dev # http://localhost:3100
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## 🛠️ Development
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
pnpm typecheck && pnpm build && pnpm test
|
|
246
|
+
pnpm changeset # describe consumer-facing changes; merging to main publishes them
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
See [AGENTS.md](./AGENTS.md) for the conventions contributors and coding agents follow.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## 📄 License
|
|
254
|
+
|
|
255
|
+
MIT © Pixerate
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { a as ControlDef, C as ControlValue } from '../controls-DdXMbovi.js';
|
|
2
|
+
import { E as EffectDefinition, a as EffectCategory, S as ShaderEffect } from '../types-CkKkavvB.js';
|
|
3
|
+
export { b as EFFECT_CATEGORIES, c as EffectPreset, V as VectorControl, d as VectorEffect, e as colorControl, n as numberControl, r as renderVectorEffect, v as vectorControlDef } from '../types-CkKkavvB.js';
|
|
4
|
+
import '../motion-D0TVv2CF.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Sample pictures for the catalog's image effects: small SVGs as `data:` URLs,
|
|
8
|
+
* so image controls have something to show with nothing to download.
|
|
9
|
+
*/
|
|
10
|
+
/** An ASCII SVG as a base64 `data:image/svg+xml` URL. */
|
|
11
|
+
declare function svgDataUrl(svg: string): string;
|
|
12
|
+
/** Built-in sample pictures, as image control values. */
|
|
13
|
+
declare const SAMPLE_IMAGES: {
|
|
14
|
+
readonly landscape: string;
|
|
15
|
+
readonly city: string;
|
|
16
|
+
readonly portrait: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* GLSL helpers the catalog's shaders are built from. Each effect's source
|
|
21
|
+
* inlines the helpers it uses, so a source stays self-contained: copy it into
|
|
22
|
+
* any Shadertoy-style host and it compiles.
|
|
23
|
+
*/
|
|
24
|
+
/** Hashes: `hash12` (vec2 → float), `hash22` (vec2 → vec2), both 0–1. */
|
|
25
|
+
declare const HASH: string;
|
|
26
|
+
/** Smooth value noise, 0–1 (needs {@link HASH}). */
|
|
27
|
+
declare const NOISE: string;
|
|
28
|
+
/** Fractal noise: `fbm(p, octaves)`, about 0–1 (needs {@link NOISE}). */
|
|
29
|
+
declare const FBM: string;
|
|
30
|
+
/** Inigo Quilez's cosine palette: `palette(t, a, b, c, d)`. */
|
|
31
|
+
declare const PALETTE: string;
|
|
32
|
+
/** Mixes colours in Oklab, so blends stay bright: `oklabMix(a, b, t)` on sRGB colours. */
|
|
33
|
+
declare const OKLAB_MIX: string;
|
|
34
|
+
/** 2D rotation matrix: `rot(angle)`. */
|
|
35
|
+
declare const ROTATE: string;
|
|
36
|
+
/** Joins helpers and a body into one source, a blank line apart. */
|
|
37
|
+
declare function glsl(...parts: string[]): string;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The effect catalog: ready-made shader and vector effects, each with
|
|
41
|
+
* controls and presets, to drop into a product or start an edit from.
|
|
42
|
+
*
|
|
43
|
+
* - Browsing: `EFFECTS`, `getEffect`, `findEffects`, `EFFECT_CATEGORIES`.
|
|
44
|
+
* - Controls: `effectControls`, `effectDefaults`.
|
|
45
|
+
* - Vector effects: `renderVectorEffect` fills in their controls.
|
|
46
|
+
*
|
|
47
|
+
* @packageDocumentation
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
/** The catalog's shader effects. */
|
|
51
|
+
declare const SHADER_EFFECTS: readonly ShaderEffect[];
|
|
52
|
+
/** Every effect in the catalog, shaders first, grouped by category. */
|
|
53
|
+
declare const EFFECTS: readonly EffectDefinition[];
|
|
54
|
+
/** The effect with this id, or undefined. */
|
|
55
|
+
declare function getEffect(id: string): EffectDefinition | undefined;
|
|
56
|
+
interface EffectQuery {
|
|
57
|
+
/** Words to look for in the name, description, tags and id (all must match). */
|
|
58
|
+
text?: string;
|
|
59
|
+
category?: EffectCategory;
|
|
60
|
+
kind?: EffectDefinition["kind"];
|
|
61
|
+
/** Effects with every one of these tags. */
|
|
62
|
+
tags?: readonly string[];
|
|
63
|
+
}
|
|
64
|
+
/** Effects matching a query, in catalog order. */
|
|
65
|
+
declare function findEffects(query?: EffectQuery): EffectDefinition[];
|
|
66
|
+
/** An effect's controls: a shader's `@control` uniforms, or a vector effect's declared controls. */
|
|
67
|
+
declare function effectControls(effect: EffectDefinition): ControlDef[];
|
|
68
|
+
/** An effect's values: each control's default, overridden by `values` (and a preset by name, when given). */
|
|
69
|
+
declare function effectDefaults(effect: EffectDefinition, options?: {
|
|
70
|
+
preset?: string;
|
|
71
|
+
values?: Record<string, ControlValue>;
|
|
72
|
+
}): Record<string, ControlValue>;
|
|
73
|
+
|
|
74
|
+
export { EFFECTS, EffectCategory, EffectDefinition, type EffectQuery, FBM, HASH, NOISE, OKLAB_MIX, PALETTE, ROTATE, SAMPLE_IMAGES, SHADER_EFFECTS, ShaderEffect, effectControls, effectDefaults, findEffects, getEffect, glsl, svgDataUrl };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EFFECTS,
|
|
3
|
+
EFFECT_CATEGORIES,
|
|
4
|
+
FBM,
|
|
5
|
+
HASH,
|
|
6
|
+
NOISE,
|
|
7
|
+
OKLAB_MIX,
|
|
8
|
+
PALETTE,
|
|
9
|
+
ROTATE,
|
|
10
|
+
SAMPLE_IMAGES,
|
|
11
|
+
SHADER_EFFECTS,
|
|
12
|
+
colorControl,
|
|
13
|
+
effectControls,
|
|
14
|
+
effectDefaults,
|
|
15
|
+
findEffects,
|
|
16
|
+
getEffect,
|
|
17
|
+
glsl,
|
|
18
|
+
numberControl,
|
|
19
|
+
renderVectorEffect,
|
|
20
|
+
svgDataUrl,
|
|
21
|
+
vectorControlDef
|
|
22
|
+
} from "../chunk-MWVTQFV5.js";
|
|
23
|
+
import "../chunk-EHDJ3CPJ.js";
|
|
24
|
+
import "../chunk-J55UBNAT.js";
|
|
25
|
+
export {
|
|
26
|
+
EFFECTS,
|
|
27
|
+
EFFECT_CATEGORIES,
|
|
28
|
+
FBM,
|
|
29
|
+
HASH,
|
|
30
|
+
NOISE,
|
|
31
|
+
OKLAB_MIX,
|
|
32
|
+
PALETTE,
|
|
33
|
+
ROTATE,
|
|
34
|
+
SAMPLE_IMAGES,
|
|
35
|
+
SHADER_EFFECTS,
|
|
36
|
+
colorControl,
|
|
37
|
+
effectControls,
|
|
38
|
+
effectDefaults,
|
|
39
|
+
findEffects,
|
|
40
|
+
getEffect,
|
|
41
|
+
glsl,
|
|
42
|
+
numberControl,
|
|
43
|
+
renderVectorEffect,
|
|
44
|
+
svgDataUrl,
|
|
45
|
+
vectorControlDef
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// src/scene/types.ts
|
|
2
|
+
function isElementCommand(cmd) {
|
|
3
|
+
return cmd.type === "element" && "element" in cmd && !!cmd.element;
|
|
4
|
+
}
|
|
5
|
+
function isUpdateCommand(cmd) {
|
|
6
|
+
return cmd.type === "update" && "id" in cmd && typeof cmd.id === "string";
|
|
7
|
+
}
|
|
8
|
+
function isDeleteCommand(cmd) {
|
|
9
|
+
return cmd.type === "delete" && "id" in cmd && typeof cmd.id === "string";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// src/scene/geometry.ts
|
|
13
|
+
function boundsOf(e) {
|
|
14
|
+
if (e.points?.length) {
|
|
15
|
+
const xs = e.points.map((p) => e.x + p[0]);
|
|
16
|
+
const ys = e.points.map((p) => e.y + p[1]);
|
|
17
|
+
const x = Math.min(...xs);
|
|
18
|
+
const y = Math.min(...ys);
|
|
19
|
+
return {
|
|
20
|
+
x,
|
|
21
|
+
y,
|
|
22
|
+
width: Math.max(1, Math.max(...xs) - x),
|
|
23
|
+
height: Math.max(1, Math.max(...ys) - y)
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return { x: e.x, y: e.y, width: Math.max(1, e.width), height: Math.max(1, e.height) };
|
|
27
|
+
}
|
|
28
|
+
function overlapArea(a, b) {
|
|
29
|
+
const w = Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x);
|
|
30
|
+
const h = Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y);
|
|
31
|
+
return w > 0 && h > 0 ? w * h : 0;
|
|
32
|
+
}
|
|
33
|
+
var areaOf = (r) => r.width * r.height;
|
|
34
|
+
|
|
35
|
+
// src/scene/camera.ts
|
|
36
|
+
var DEFAULT_STAGE = {
|
|
37
|
+
width: 1920,
|
|
38
|
+
height: 1080,
|
|
39
|
+
caption: 0
|
|
40
|
+
};
|
|
41
|
+
var CAMERA_MS = 600;
|
|
42
|
+
var boardHeight = (stage) => stage.height - (stage.caption ?? 0);
|
|
43
|
+
function viewAt(cx, cy, zoom, stage) {
|
|
44
|
+
return {
|
|
45
|
+
zoom,
|
|
46
|
+
scrollX: stage.width / (2 * zoom) - cx,
|
|
47
|
+
scrollY: boardHeight(stage) / (2 * zoom) - cy
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function centreOf(view, stage) {
|
|
51
|
+
return [
|
|
52
|
+
stage.width / (2 * view.zoom) - view.scrollX,
|
|
53
|
+
boardHeight(stage) / (2 * view.zoom) - view.scrollY
|
|
54
|
+
];
|
|
55
|
+
}
|
|
56
|
+
function frame(rect, stage) {
|
|
57
|
+
if (!(rect.width > 0 && rect.height > 0)) {
|
|
58
|
+
throw new Error("A camera rectangle needs a positive width and height");
|
|
59
|
+
}
|
|
60
|
+
const zoom = Math.min(stage.width / rect.width, boardHeight(stage) / rect.height);
|
|
61
|
+
return viewAt(rect.x + rect.width / 2, rect.y + rect.height / 2, zoom, stage);
|
|
62
|
+
}
|
|
63
|
+
function calculateFramingRect(elements, stage, options = {}) {
|
|
64
|
+
const activeElements = elements.filter((e) => !e.isDeleted);
|
|
65
|
+
const targetH = boardHeight(stage);
|
|
66
|
+
const stageAspect = stage.width / Math.max(1, targetH);
|
|
67
|
+
if (activeElements.length === 0) {
|
|
68
|
+
return {
|
|
69
|
+
x: 0,
|
|
70
|
+
y: 0,
|
|
71
|
+
width: stage.width,
|
|
72
|
+
height: targetH
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
let minX = Infinity;
|
|
76
|
+
let minY = Infinity;
|
|
77
|
+
let maxX = -Infinity;
|
|
78
|
+
let maxY = -Infinity;
|
|
79
|
+
for (const el of activeElements) {
|
|
80
|
+
const b = boundsOf(el);
|
|
81
|
+
if (b.x < minX) minX = b.x;
|
|
82
|
+
if (b.y < minY) minY = b.y;
|
|
83
|
+
if (b.x + b.width > maxX) maxX = b.x + b.width;
|
|
84
|
+
if (b.y + b.height > maxY) maxY = b.y + b.height;
|
|
85
|
+
}
|
|
86
|
+
let width = maxX - minX;
|
|
87
|
+
let height = maxY - minY;
|
|
88
|
+
const padding = options.padding ?? 0.15;
|
|
89
|
+
if (padding > 0) {
|
|
90
|
+
const padX = width * padding;
|
|
91
|
+
const padY = height * padding;
|
|
92
|
+
minX -= padX;
|
|
93
|
+
minY -= padY;
|
|
94
|
+
width += padX * 2;
|
|
95
|
+
height += padY * 2;
|
|
96
|
+
}
|
|
97
|
+
if (options.minWidth && width < options.minWidth) {
|
|
98
|
+
const diff = options.minWidth - width;
|
|
99
|
+
minX -= diff / 2;
|
|
100
|
+
width = options.minWidth;
|
|
101
|
+
}
|
|
102
|
+
if (options.minHeight && height < options.minHeight) {
|
|
103
|
+
const diff = options.minHeight - height;
|
|
104
|
+
minY -= diff / 2;
|
|
105
|
+
height = options.minHeight;
|
|
106
|
+
}
|
|
107
|
+
const currentAspect = width / Math.max(1, height);
|
|
108
|
+
if (currentAspect < stageAspect) {
|
|
109
|
+
const targetWidth = height * stageAspect;
|
|
110
|
+
const diff = targetWidth - width;
|
|
111
|
+
minX -= diff / 2;
|
|
112
|
+
width = targetWidth;
|
|
113
|
+
} else if (currentAspect > stageAspect) {
|
|
114
|
+
const targetHeight = width / stageAspect;
|
|
115
|
+
const diff = targetHeight - height;
|
|
116
|
+
minY -= diff / 2;
|
|
117
|
+
height = targetHeight;
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
x: minX,
|
|
121
|
+
y: minY,
|
|
122
|
+
width,
|
|
123
|
+
height
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function between(from, to, t, stage) {
|
|
127
|
+
const [x0, y0] = centreOf(from, stage);
|
|
128
|
+
const [x1, y1] = centreOf(to, stage);
|
|
129
|
+
const zoom = from.zoom * Math.pow(to.zoom / from.zoom, t);
|
|
130
|
+
return viewAt(x0 + (x1 - x0) * t, y0 + (y1 - y0) * t, zoom, stage);
|
|
131
|
+
}
|
|
132
|
+
function visibleRect(view, stage) {
|
|
133
|
+
return {
|
|
134
|
+
x: -view.scrollX,
|
|
135
|
+
y: -view.scrollY,
|
|
136
|
+
width: stage.width / view.zoom,
|
|
137
|
+
height: boardHeight(stage) / view.zoom
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
var easeInOutCubic = (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
|
|
141
|
+
|
|
142
|
+
export {
|
|
143
|
+
isElementCommand,
|
|
144
|
+
isUpdateCommand,
|
|
145
|
+
isDeleteCommand,
|
|
146
|
+
boundsOf,
|
|
147
|
+
overlapArea,
|
|
148
|
+
areaOf,
|
|
149
|
+
DEFAULT_STAGE,
|
|
150
|
+
CAMERA_MS,
|
|
151
|
+
boardHeight,
|
|
152
|
+
viewAt,
|
|
153
|
+
centreOf,
|
|
154
|
+
frame,
|
|
155
|
+
calculateFramingRect,
|
|
156
|
+
between,
|
|
157
|
+
visibleRect,
|
|
158
|
+
easeInOutCubic
|
|
159
|
+
};
|
|
160
|
+
//# sourceMappingURL=chunk-36DTNSHU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/scene/types.ts","../src/scene/geometry.ts","../src/scene/camera.ts"],"sourcesContent":["import type { Rect, SceneElement } from './geometry.js';\nimport type { Stage } from './camera.js';\nimport type { Effect } from './effects.js';\nimport type { Behavior } from './behavior.js';\nimport type { SceneRig } from './rig.js';\n\n/** Shader or external visual stream in the scene. */\nexport interface SceneMedia {\n type: 'shader' | string;\n source?: string;\n values?: Record<string, unknown>;\n [field: string]: unknown;\n}\n\nexport interface ElementCommand {\n type: 'element';\n element: SceneElement;\n}\n\nexport interface UpdateCommand {\n type: 'update';\n id: string;\n props: Partial<SceneElement>;\n}\n\nexport interface DeleteCommand {\n type: 'delete';\n id: string;\n}\n\nexport interface ClearCommand {\n type: 'clear';\n}\n\nexport interface CustomCommand {\n type: string;\n [key: string]: unknown;\n}\n\n/** A command to add, move, or style elements in a shot. */\nexport type SceneCommand =\n | ElementCommand\n | UpdateCommand\n | DeleteCommand\n | ClearCommand\n | CustomCommand;\n\nexport function isElementCommand(cmd: SceneCommand): cmd is ElementCommand {\n return cmd.type === 'element' && 'element' in cmd && !!cmd.element;\n}\n\nexport function isUpdateCommand(cmd: SceneCommand): cmd is UpdateCommand {\n return cmd.type === 'update' && 'id' in cmd && typeof cmd.id === 'string';\n}\n\nexport function isDeleteCommand(cmd: SceneCommand): cmd is DeleteCommand {\n return cmd.type === 'delete' && 'id' in cmd && typeof cmd.id === 'string';\n}\n\nexport interface DynamicFraming {\n /** Element IDs to frame. */\n frame: string[];\n /** Proportional padding (default 0.15 for 15%). */\n padding?: number;\n /** Minimum width in scene units. */\n minWidth?: number;\n /** Minimum height in scene units. */\n minHeight?: number;\n}\n\nexport type ShotCamera = Rect | DynamicFraming;\n\n/** One beat of a scene: camera framing, title, narration/caption, commands, and procedural effects. */\nexport interface Shot {\n /** Narration / caption spoken or displayed during this beat. */\n caption?: string;\n /** High-level title or callout displayed on screen. */\n title?: string;\n /** Camera framing box in scene units, or dynamic auto-framing target. */\n camera?: ShotCamera;\n /** Pause/dwell duration after this shot's actions run (in milliseconds). Default: 800ms. */\n hold?: number;\n /** Drawing or state change commands played during this shot. */\n run?: SceneCommand[];\n /** Procedural visual effects started in this shot. */\n effects?: Effect[];\n /** IDs of active effects to stop/fade at the start of this shot. */\n stopEffects?: string[];\n /** Continuous procedural parameter and physics behaviors played during this shot. */\n behaviors?: Behavior[];\n}\n\n/** Complete animated scene specification. */\nexport interface Scene {\n name: string;\n title?: string;\n description?: string;\n stage?: Partial<Stage>;\n /** Embedded media (e.g. bespoke WebGL fragment shaders). */\n media?: Record<string, SceneMedia>;\n /** Target duration [minSeconds, maxSeconds]. */\n target?: [number, number];\n /** Interactive macro rigs binding high-level controls to uniforms or element properties. */\n rig?: SceneRig;\n shots: Shot[];\n}\n","export type Point = [number, number];\n\nexport interface Rect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/** An element placed or animated in a scene. */\nexport interface SceneElement {\n id: string;\n type: string;\n x: number;\n y: number;\n width: number;\n height: number;\n points?: readonly (readonly [number, number])[];\n fontSize?: number;\n text?: string;\n containerId?: string | null;\n isDeleted?: boolean;\n opacity?: number;\n}\n\n/** Bounds in scene units, ignoring rotation. */\nexport function boundsOf(e: SceneElement): Rect {\n if (e.points?.length) {\n const xs = e.points.map((p) => e.x + p[0]);\n const ys = e.points.map((p) => e.y + p[1]);\n const x = Math.min(...xs);\n const y = Math.min(...ys);\n return {\n x,\n y,\n width: Math.max(1, Math.max(...xs) - x),\n height: Math.max(1, Math.max(...ys) - y),\n };\n }\n return { x: e.x, y: e.y, width: Math.max(1, e.width), height: Math.max(1, e.height) };\n}\n\n/** Area of the overlap of two rectangles (0 when they do not meet). */\nexport function overlapArea(a: Rect, b: Rect): number {\n const w = Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x);\n const h = Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y);\n return w > 0 && h > 0 ? w * h : 0;\n}\n\nexport const areaOf = (r: Rect): number => r.width * r.height;\n","import { type Rect, type SceneElement, boundsOf } from './geometry.js';\n\nexport interface Stage {\n width: number;\n height: number;\n caption?: number;\n}\n\nexport const DEFAULT_STAGE: Stage = {\n width: 1920,\n height: 1080,\n caption: 0,\n};\n\nexport const CAMERA_MS = 600;\n\n/** Camera view into the scene: drawn at (point + scroll) × zoom. */\nexport interface View {\n zoom: number;\n scrollX: number;\n scrollY: number;\n}\n\nexport const boardHeight = (stage: Stage): number => stage.height - (stage.caption ?? 0);\n\n/** View with `(cx, cy)` in the middle of the board at `zoom`. */\nexport function viewAt(cx: number, cy: number, zoom: number, stage: Stage): View {\n return {\n zoom,\n scrollX: stage.width / (2 * zoom) - cx,\n scrollY: boardHeight(stage) / (2 * zoom) - cy,\n };\n}\n\n/** Scene point in the middle of the board. */\nexport function centreOf(view: View, stage: Stage): [number, number] {\n return [\n stage.width / (2 * view.zoom) - view.scrollX,\n boardHeight(stage) / (2 * view.zoom) - view.scrollY,\n ];\n}\n\n/** The view that fits `rect` on the board, centred and with its aspect kept. */\nexport function frame(rect: Rect, stage: Stage): View {\n if (!(rect.width > 0 && rect.height > 0)) {\n throw new Error('A camera rectangle needs a positive width and height');\n }\n const zoom = Math.min(stage.width / rect.width, boardHeight(stage) / rect.height);\n return viewAt(rect.x + rect.width / 2, rect.y + rect.height / 2, zoom, stage);\n}\n\nexport interface FramingOptions {\n /** Proportional padding added to all sides (e.g., 0.15 for 15%). Default: 0.15 */\n padding?: number;\n /** Minimum width in scene units. */\n minWidth?: number;\n /** Minimum height in scene units. */\n minHeight?: number;\n}\n\n/**\n * Calculates a camera framing rectangle that encloses target elements\n * while preserving the stage aspect ratio and applying optional margin padding.\n */\nexport function calculateFramingRect(\n elements: readonly SceneElement[],\n stage: Stage,\n options: FramingOptions = {},\n): Rect {\n const activeElements = elements.filter((e) => !e.isDeleted);\n const targetH = boardHeight(stage);\n const stageAspect = stage.width / Math.max(1, targetH);\n\n if (activeElements.length === 0) {\n return {\n x: 0,\n y: 0,\n width: stage.width,\n height: targetH,\n };\n }\n\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n\n for (const el of activeElements) {\n const b = boundsOf(el);\n if (b.x < minX) minX = b.x;\n if (b.y < minY) minY = b.y;\n if (b.x + b.width > maxX) maxX = b.x + b.width;\n if (b.y + b.height > maxY) maxY = b.y + b.height;\n }\n\n let width = maxX - minX;\n let height = maxY - minY;\n\n // Apply padding (default 15%)\n const padding = options.padding ?? 0.15;\n if (padding > 0) {\n const padX = width * padding;\n const padY = height * padding;\n minX -= padX;\n minY -= padY;\n width += padX * 2;\n height += padY * 2;\n }\n\n // Apply min dimensions\n if (options.minWidth && width < options.minWidth) {\n const diff = options.minWidth - width;\n minX -= diff / 2;\n width = options.minWidth;\n }\n if (options.minHeight && height < options.minHeight) {\n const diff = options.minHeight - height;\n minY -= diff / 2;\n height = options.minHeight;\n }\n\n // Preserve stage aspect ratio centered around midpoint\n const currentAspect = width / Math.max(1, height);\n if (currentAspect < stageAspect) {\n // Taller than stage: expand width\n const targetWidth = height * stageAspect;\n const diff = targetWidth - width;\n minX -= diff / 2;\n width = targetWidth;\n } else if (currentAspect > stageAspect) {\n // Wider than stage: expand height\n const targetHeight = width / stageAspect;\n const diff = targetHeight - height;\n minY -= diff / 2;\n height = targetHeight;\n }\n\n return {\n x: minX,\n y: minY,\n width,\n height,\n };\n}\n\n/**\n * The view part-way (`t` from 0 to 1) from one view to another.\n * Centre moves linearly and zoom scales exponentially for smooth motion.\n */\nexport function between(from: View, to: View, t: number, stage: Stage): View {\n const [x0, y0] = centreOf(from, stage);\n const [x1, y1] = centreOf(to, stage);\n const zoom = from.zoom * Math.pow(to.zoom / from.zoom, t);\n return viewAt(x0 + (x1 - x0) * t, y0 + (y1 - y0) * t, zoom, stage);\n}\n\n/** The part of the scene the camera currently shows. */\nexport function visibleRect(view: View, stage: Stage): Rect {\n return {\n x: -view.scrollX,\n y: -view.scrollY,\n width: stage.width / view.zoom,\n height: boardHeight(stage) / view.zoom,\n };\n}\n\n/** Smooth cubic ease in / ease out. */\nexport const easeInOutCubic = (t: number): number =>\n t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;\n"],"mappings":"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","names":[]}
|