@viji-dev/core 0.3.24 → 0.3.25

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.
@@ -1,37 +1,4 @@
1
1
  // JavaScript IntelliSense Support - Auto-generated
2
2
  // This file provides type hints for JavaScript without requiring @ts-check
3
-
4
- // Global Viji API available in all scenes
5
- /**
6
- * @fileoverview Viji Artist API - JavaScript IntelliSense Support
7
- * Auto-generated from TypeScript definitions - DO NOT EDIT MANUALLY
8
- */
9
-
10
- /**
11
- * Global viji object available in artist code
12
- * @type {VijiAPI}
13
- */
14
- declare const viji;
15
-
16
- /**
17
- * Type alias for render function
18
- * @typedef {function(VijiAPI): void} Render
19
- */
20
-
21
- /**
22
- * Type alias for setup function
23
- * @typedef {function(VijiAPI): void} Setup
24
- */
25
-
26
- /**
27
- * Artist render function - called every frame
28
- * @param {VijiAPI} viji - The viji API object with all capabilities
29
- */
30
- declare function render(viji): void;
31
-
32
- /**
33
- * Artist setup function - called once at initialization
34
- * @param {VijiAPI} viji - The viji API object with all capabilities
35
- */
36
- declare function setup(viji): void;
37
-
3
+ // All types are defined in artist-jsdoc.d.ts — this file re-exports them.
4
+ /// <reference path="artist-jsdoc.d.ts" />
@@ -4,29 +4,422 @@
4
4
  */
5
5
 
6
6
  /**
7
- * Global viji object available in artist code
8
- * @type {VijiAPI}
7
+ * Configuration for slider parameters
8
+ * @typedef {Object} SliderConfig
9
+ * @property {number} [min] - min property
10
+ * @property {number} [max] - max property
11
+ * @property {number} [step] - step property
12
+ * @property {string} label - label property
13
+ * @property {string} [description] - description property
14
+ * @property {string} [group] - group property
15
+ * @property {ParameterCategory} [category] - category property
9
16
  */
10
- declare const viji;
11
17
 
12
18
  /**
13
- * Type alias for render function
14
- * @typedef {function(VijiAPI): void} Render
19
+ * Configuration for color parameters
20
+ * @typedef {Object} ColorConfig
21
+ * @property {string} label - label property
22
+ * @property {string} [description] - description property
23
+ * @property {string} [group] - group property
24
+ * @property {ParameterCategory} [category] - category property
15
25
  */
16
26
 
17
27
  /**
18
- * Type alias for setup function
19
- * @typedef {function(VijiAPI): void} Setup
28
+ * Configuration for toggle parameters
29
+ * @typedef {Object} ToggleConfig
30
+ * @property {string} label - label property
31
+ * @property {string} [description] - description property
32
+ * @property {string} [group] - group property
33
+ * @property {ParameterCategory} [category] - category property
34
+ */
35
+
36
+ /**
37
+ * Configuration for select parameters
38
+ * @typedef {Object} SelectConfig
39
+ * @property {string[] | number[]} options - options property
40
+ * @property {string} label - label property
41
+ * @property {string} [description] - description property
42
+ * @property {string} [group] - group property
43
+ * @property {ParameterCategory} [category] - category property
44
+ */
45
+
46
+ /**
47
+ * Configuration for text parameters
48
+ * @typedef {Object} TextConfig
49
+ * @property {string} label - label property
50
+ * @property {string} [description] - description property
51
+ * @property {string} [group] - group property
52
+ * @property {ParameterCategory} [category] - category property
53
+ * @property {number} [maxLength] - maxLength property
54
+ */
55
+
56
+ /**
57
+ * Configuration for number parameters
58
+ * @typedef {Object} NumberConfig
59
+ * @property {number} [min] - min property
60
+ * @property {number} [max] - max property
61
+ * @property {number} [step] - step property
62
+ * @property {string} label - label property
63
+ * @property {string} [description] - description property
64
+ * @property {string} [group] - group property
65
+ * @property {ParameterCategory} [category] - category property
66
+ */
67
+
68
+ /**
69
+ * Configuration for image parameters
70
+ * @typedef {Object} ImageConfig
71
+ * @property {string} label - label property
72
+ * @property {string} [description] - description property
73
+ * @property {string} [group] - group property
74
+ * @property {ParameterCategory} [category] - category property
75
+ */
76
+
77
+ /**
78
+ * Configuration for button parameters
79
+ * @typedef {Object} ButtonConfig
80
+ * @property {string} label - label property
81
+ * @property {string} [description] - description property
82
+ * @property {string} [group] - group property
83
+ * @property {ParameterCategory} [category] - category property
84
+ */
85
+
86
+ /**
87
+ * Parameter object for slider parameters
88
+ * @typedef {Object} SliderParameter
89
+ * @property {number} value - value property
90
+ * @property {number} min - min property
91
+ * @property {number} max - max property
92
+ * @property {number} step - step property
93
+ * @property {string} label - label property
94
+ * @property {string} [description] - description property
95
+ * @property {string} group - group property
96
+ * @property {ParameterCategory} category - category property
97
+ */
98
+
99
+ /**
100
+ * Parameter object for color parameters
101
+ * @typedef {Object} ColorParameter
102
+ * @property {string} value - value property
103
+ * @property {string} label - label property
104
+ * @property {string} [description] - description property
105
+ * @property {string} group - group property
106
+ * @property {ParameterCategory} category - category property
20
107
  */
21
108
 
22
109
  /**
23
- * Artist render function - called every frame
24
- * @param {VijiAPI} viji - The viji API object with all capabilities
110
+ * Parameter object for toggle parameters
111
+ * @typedef {Object} ToggleParameter
112
+ * @property {boolean} value - value property
113
+ * @property {string} label - label property
114
+ * @property {string} [description] - description property
115
+ * @property {string} group - group property
116
+ * @property {ParameterCategory} category - category property
25
117
  */
26
- declare function render(viji): void;
27
118
 
28
119
  /**
29
- * Artist setup function - called once at initialization
30
- * @param {VijiAPI} viji - The viji API object with all capabilities
120
+ * Parameter object for select parameters
121
+ * @typedef {Object} SelectParameter
122
+ * @property {string | number} value - value property
123
+ * @property {string[] | number[]} options - options property
124
+ * @property {string} label - label property
125
+ * @property {string} [description] - description property
126
+ * @property {string} group - group property
127
+ * @property {ParameterCategory} category - category property
31
128
  */
32
- declare function setup(viji): void;
129
+
130
+ /**
131
+ * Parameter object for text parameters
132
+ * @typedef {Object} TextParameter
133
+ * @property {string} value - value property
134
+ * @property {number} [maxLength] - maxLength property
135
+ * @property {string} label - label property
136
+ * @property {string} [description] - description property
137
+ * @property {string} group - group property
138
+ * @property {ParameterCategory} category - category property
139
+ */
140
+
141
+ /**
142
+ * Parameter object for number parameters
143
+ * @typedef {Object} NumberParameter
144
+ * @property {number} value - value property
145
+ * @property {number} min - min property
146
+ * @property {number} max - max property
147
+ * @property {number} step - step property
148
+ * @property {string} label - label property
149
+ * @property {string} [description] - description property
150
+ * @property {string} group - group property
151
+ * @property {ParameterCategory} category - category property
152
+ */
153
+
154
+ /**
155
+ * Parameter object for image parameters
156
+ * @typedef {Object} ImageParameter
157
+ * @property {ImageBitmap |null} value - value property
158
+ * @property {string} label - label property
159
+ * @property {string} [description] - description property
160
+ * @property {string} group - group property
161
+ * @property {ParameterCategory} category - category property
162
+ */
163
+
164
+ /**
165
+ * Parameter object for button parameters
166
+ * @typedef {Object} ButtonParameter
167
+ * @property {boolean} value - value property
168
+ * @property {string} label - label property
169
+ * @property {string} [description] - description property
170
+ * @property {string} group - group property
171
+ * @property {ParameterCategory} category - category property
172
+ */
173
+
174
+ /**
175
+ * Audio analysis API - provides real-time audio data and frequency analysis
176
+ * @typedef {Object} AudioAPI
177
+ * @property {boolean} isConnected - isConnected property
178
+ * @property {Object} volume - volume property
179
+ * @property {number} volume.current - current property
180
+ * @property {number} volume.peak - peak property
181
+ * @property {number} volume.smoothed - smoothed property
182
+ */
183
+
184
+ /**
185
+ * Video frame API - provides access to video stream and computer vision data
186
+ * @typedef {Object} VideoAPI
187
+ * @property {boolean} isConnected - isConnected property
188
+ * @property {OffscreenCanvas | ImageBitmap |null} currentFrame - currentFrame property
189
+ * @property {number} frameWidth - frameWidth property
190
+ * @property {number} frameHeight - frameHeight property
191
+ * @property {number} frameRate - frameRate property
192
+ * @property {() => ImageData |null} getFrameData - getFrameData property
193
+ * @property {FaceData[]} faces - faces property
194
+ * @property {HandData[]} hands - hands property
195
+ * @property {PoseData |null} pose - pose property
196
+ * @property {SegmentationData |null} segmentation - segmentation property
197
+ * @property {Object} cv - cv property
198
+ */
199
+
200
+ /**
201
+ * Mouse interaction API
202
+ * @typedef {Object} MouseAPI
203
+ * @property {number} x - x property
204
+ * @property {number} y - y property
205
+ * @property {boolean} isInCanvas - isInCanvas property
206
+ * @property {boolean} isPressed - isPressed property
207
+ * @property {boolean} leftButton - leftButton property
208
+ * @property {boolean} rightButton - rightButton property
209
+ * @property {boolean} middleButton - middleButton property
210
+ * @property {number} deltaX - deltaX property
211
+ * @property {number} deltaY - deltaY property
212
+ * @property {number} wheelDelta - wheelDelta property
213
+ * @property {number} wheelX - wheelX property
214
+ * @property {number} wheelY - wheelY property
215
+ * @property {boolean} wasPressed - wasPressed property
216
+ * @property {boolean} wasReleased - wasReleased property
217
+ * @property {boolean} wasMoved - wasMoved property
218
+ */
219
+
220
+ /**
221
+ * Keyboard interaction API
222
+ * @typedef {Object} KeyboardAPI
223
+ * @property {Set<string>} activeKeys - activeKeys property
224
+ * @property {Set<string>} pressedThisFrame - pressedThisFrame property
225
+ * @property {Set<string>} releasedThisFrame - releasedThisFrame property
226
+ * @property {string} lastKeyPressed - lastKeyPressed property
227
+ * @property {string} lastKeyReleased - lastKeyReleased property
228
+ * @property {boolean} shift - shift property
229
+ * @property {boolean} ctrl - ctrl property
230
+ * @property {boolean} alt - alt property
231
+ * @property {boolean} meta - meta property
232
+ * @property {Uint8Array} textureData - textureData property
233
+ */
234
+
235
+ /**
236
+ * Touch interaction API
237
+ * @typedef {Object} TouchAPI
238
+ * @property {TouchPoint[]} points - points property
239
+ * @property {number} count - count property
240
+ * @property {TouchPoint[]} started - started property
241
+ * @property {TouchPoint[]} moved - moved property
242
+ * @property {TouchPoint[]} ended - ended property
243
+ * @property {TouchPoint |null} primary - primary property
244
+ */
245
+
246
+ /**
247
+ * Main Viji Artist API - provides access to canvas, timing, audio, video, and interactions
248
+ * @typedef {Object} VijiAPI
249
+ * @property {OffscreenCanvas} canvas - canvas property
250
+ * @property {OffscreenCanvasRenderingContext2D} [ctx] - ctx property
251
+ * @property {WebGLRenderingContext | WebGL2RenderingContext} [gl] - gl property
252
+ * @property {number} width - width property
253
+ * @property {number} height - height property
254
+ * @property {number} time - time property
255
+ * @property {number} deltaTime - deltaTime property
256
+ * @property {number} frameCount - frameCount property
257
+ * @property {number} fps - fps property
258
+ * @property {AudioAPI} audio - audio property
259
+ * @property {VideoAPI} video - video property
260
+ * @property {VideoAPI[]} streams - streams property
261
+ * @property {MouseAPI} mouse - mouse property
262
+ * @property {KeyboardAPI} keyboard - keyboard property
263
+ * @property {TouchAPI} touches - touches property
264
+ * @property {PointerAPI} pointer - pointer property
265
+ * @property {DeviceSensorState} device - device property
266
+ * @property {DeviceState[]} devices - devices property
267
+ * @property {(defaultValue: number, config: SliderConfig) => SliderParameter} slider - slider property
268
+ * @property {(defaultValue: string, config: ColorConfig) => ColorParameter} color - color property
269
+ * @property {(defaultValue: boolean, config: ToggleConfig) => ToggleParameter} toggle - toggle property
270
+ * @property {(defaultValue: string | number, config: SelectConfig) => SelectParameter} select - select property
271
+ * @property {(defaultValue: string, config: TextConfig) => TextParameter} text - text property
272
+ * @property {(defaultValue: number, config: NumberConfig) => NumberParameter} number - number property
273
+ * @property {(defaultValue: null, config: ImageConfig) => ImageParameter} image - image property
274
+ * @property {(config: ButtonConfig) => ButtonParameter} button - button property
275
+ */
276
+
277
+ /**
278
+ * FaceData interface
279
+ * @typedef {Object} FaceData
280
+ * @property {number} id - id property
281
+ * @property {Object} bounds - bounds property
282
+ * @property {number} bounds.x - x property
283
+ * @property {number} bounds.y - y property
284
+ * @property {number} bounds.width - width property
285
+ * @property {number} bounds.height - height property
286
+ */
287
+
288
+ /**
289
+ * FaceBlendshapes interface
290
+ * @typedef {Object} FaceBlendshapes
291
+ * @property {number} browDownLeft - browDownLeft property
292
+ * @property {number} browDownRight - browDownRight property
293
+ * @property {number} browInnerUp - browInnerUp property
294
+ * @property {number} browOuterUpLeft - browOuterUpLeft property
295
+ * @property {number} browOuterUpRight - browOuterUpRight property
296
+ * @property {number} cheekPuff - cheekPuff property
297
+ * @property {number} cheekSquintLeft - cheekSquintLeft property
298
+ * @property {number} cheekSquintRight - cheekSquintRight property
299
+ * @property {number} eyeBlinkLeft - eyeBlinkLeft property
300
+ * @property {number} eyeBlinkRight - eyeBlinkRight property
301
+ * @property {number} eyeLookDownLeft - eyeLookDownLeft property
302
+ * @property {number} eyeLookDownRight - eyeLookDownRight property
303
+ * @property {number} eyeLookInLeft - eyeLookInLeft property
304
+ * @property {number} eyeLookInRight - eyeLookInRight property
305
+ * @property {number} eyeLookOutLeft - eyeLookOutLeft property
306
+ * @property {number} eyeLookOutRight - eyeLookOutRight property
307
+ * @property {number} eyeLookUpLeft - eyeLookUpLeft property
308
+ * @property {number} eyeLookUpRight - eyeLookUpRight property
309
+ * @property {number} eyeSquintLeft - eyeSquintLeft property
310
+ * @property {number} eyeSquintRight - eyeSquintRight property
311
+ * @property {number} eyeWideLeft - eyeWideLeft property
312
+ * @property {number} eyeWideRight - eyeWideRight property
313
+ * @property {number} jawForward - jawForward property
314
+ * @property {number} jawLeft - jawLeft property
315
+ * @property {number} jawOpen - jawOpen property
316
+ * @property {number} jawRight - jawRight property
317
+ * @property {number} mouthClose - mouthClose property
318
+ * @property {number} mouthDimpleLeft - mouthDimpleLeft property
319
+ * @property {number} mouthDimpleRight - mouthDimpleRight property
320
+ * @property {number} mouthFrownLeft - mouthFrownLeft property
321
+ * @property {number} mouthFrownRight - mouthFrownRight property
322
+ * @property {number} mouthFunnel - mouthFunnel property
323
+ * @property {number} mouthLeft - mouthLeft property
324
+ * @property {number} mouthLowerDownLeft - mouthLowerDownLeft property
325
+ * @property {number} mouthLowerDownRight - mouthLowerDownRight property
326
+ * @property {number} mouthPressLeft - mouthPressLeft property
327
+ * @property {number} mouthPressRight - mouthPressRight property
328
+ * @property {number} mouthPucker - mouthPucker property
329
+ * @property {number} mouthRight - mouthRight property
330
+ * @property {number} mouthRollLower - mouthRollLower property
331
+ * @property {number} mouthRollUpper - mouthRollUpper property
332
+ * @property {number} mouthShrugLower - mouthShrugLower property
333
+ * @property {number} mouthShrugUpper - mouthShrugUpper property
334
+ * @property {number} mouthSmileLeft - mouthSmileLeft property
335
+ * @property {number} mouthSmileRight - mouthSmileRight property
336
+ * @property {number} mouthStretchLeft - mouthStretchLeft property
337
+ * @property {number} mouthStretchRight - mouthStretchRight property
338
+ * @property {number} mouthUpperUpLeft - mouthUpperUpLeft property
339
+ * @property {number} mouthUpperUpRight - mouthUpperUpRight property
340
+ * @property {number} noseSneerLeft - noseSneerLeft property
341
+ * @property {number} noseSneerRight - noseSneerRight property
342
+ * @property {number} tongueOut - tongueOut property
343
+ */
344
+
345
+ /**
346
+ * HandData interface
347
+ * @typedef {Object} HandData
348
+ * @property {number} id - id property
349
+ * @property {'left' | 'right'} handedness - handedness property
350
+ * @property {number} confidence - confidence property
351
+ * @property {Object} bounds - bounds property
352
+ * @property {number} bounds.x - x property
353
+ * @property {number} bounds.y - y property
354
+ * @property {number} bounds.width - width property
355
+ * @property {number} bounds.height - height property
356
+ */
357
+
358
+ /**
359
+ * PoseData interface
360
+ * @typedef {Object} PoseData
361
+ * @property {number} confidence - confidence property
362
+ * @property {Object} landmarks - landmarks property
363
+ * @property {number} landmarks.x - x property
364
+ * @property {number} landmarks.y - y property
365
+ * @property {number} landmarks.z - z property
366
+ * @property {number} landmarks.visibility - visibility property
367
+ */
368
+
369
+ /**
370
+ * SegmentationData interface
371
+ * @typedef {Object} SegmentationData
372
+ * @property {Uint8Array} mask - mask property
373
+ * @property {number} width - width property
374
+ * @property {number} height - height property
375
+ */
376
+
377
+ /**
378
+ * DeviceMotionData interface
379
+ * @typedef {Object} DeviceMotionData
380
+ * @property {Object} acceleration - Acceleration without gravity (m/s²)
381
+ * @property {number |null} acceleration.x - x property
382
+ * @property {number |null} acceleration.y - y property
383
+ * @property {number |null} acceleration.z - z property
384
+ */
385
+
386
+ /**
387
+ * DeviceOrientationData interface
388
+ * @typedef {Object} DeviceOrientationData
389
+ * @property {number |null} alpha - Rotation around Z-axis (0-360 degrees, compass heading)
390
+ * @property {number |null} beta - Rotation around X-axis (-180 to 180 degrees, front-to-back tilt)
391
+ * @property {number |null} gamma - Rotation around Y-axis (-90 to 90 degrees, left-to-right tilt)
392
+ * @property {boolean} absolute - True if using magnetometer (compass) for absolute orientation
393
+ */
394
+
395
+ /**
396
+ * DeviceSensorState interface
397
+ * @typedef {Object} DeviceSensorState
398
+ * @property {DeviceMotionData |null} motion - motion property
399
+ * @property {DeviceOrientationData |null} orientation - orientation property
400
+ */
401
+
402
+ /**
403
+ * DeviceState interface
404
+ * @typedef {Object} DeviceState
405
+ * @property {string} id - Unique device identifier
406
+ * @property {string} name - User-friendly device name
407
+ * @property {VideoAPI |null} video - Device camera video (null if not available)
408
+ */
409
+
410
+ /**
411
+ * @typedef {function(VijiAPI): void} Render
412
+ */
413
+
414
+ /**
415
+ * @typedef {function(VijiAPI): void} Setup
416
+ */
417
+
418
+ /** Global viji object available in artist code */
419
+ declare const viji: VijiAPI;
420
+
421
+ /** Artist render function - called every frame */
422
+ declare function render(viji: VijiAPI): void;
423
+
424
+ /** Artist setup function - called once at initialization */
425
+ declare function setup(viji: VijiAPI): void;
@@ -40,9 +40,16 @@ export interface LiveExampleBlock {
40
40
  description?: string;
41
41
  sceneCode: string;
42
42
  sceneFile: string;
43
+ capabilities?: LiveExampleCapabilities;
43
44
  assets?: LiveExampleAssets;
44
45
  }
45
46
 
47
+ export interface LiveExampleCapabilities {
48
+ audio?: true;
49
+ video?: true;
50
+ interaction?: true;
51
+ }
52
+
46
53
  export interface LiveExampleAssets {
47
54
  audio?: string;
48
55
  video?: string;
package/dist/docs-api.js CHANGED
@@ -1,7 +1,7 @@
1
1
  export const docsApi = {
2
2
  "version": "1.0.0",
3
- "coreVersion": "0.3.23",
4
- "generatedAt": "2026-03-25T18:17:02.150Z",
3
+ "coreVersion": "0.3.24",
4
+ "generatedAt": "2026-03-27T11:44:58.441Z",
5
5
  "navigation": [
6
6
  {
7
7
  "id": "getting-started",
@@ -846,7 +846,7 @@ export const docsApi = {
846
846
  },
847
847
  {
848
848
  "type": "text",
849
- "markdown": "> [!NOTE]\n> The Viji shader renderer automatically injects `precision mediump float;` and all `uniform` declarations. Write only your helper functions and `void main() { ... }`. Do NOT redeclare `precision` or built-in uniforms — they will conflict.\n\n**Key characteristics:**\n\n- **Fragment shader only.** Viji renders a fullscreen quad; your shader defines the color of every pixel.\n- **GLSL ES 1.00 by default.** If you add `#version 300 es` as the first line, Viji switches to WebGL 2. Note that ES 3.00 requires `out vec4` for output instead of `gl_FragColor`, and `texture()` instead of `texture2D()`. ES 1.00 is recommended for maximum compatibility.\n- **Built-in uniforms** like `u_time`, `u_resolution`, `u_mouse`, `u_audioVolume`, `u_video`, and many more are always available — no declaration needed.\n- **Parameters via comments.** Declare parameters with `// @viji-TYPE:uniformName key:value` syntax. They become uniforms automatically.\n- **Accumulators for smooth animation.** Use `// @viji-accumulator:phase rate:speed` instead of `u_time * speed` — the value grows smoothly without jumping when the rate parameter changes.\n- **No `u_` prefix for your parameters.** The `u_` prefix is reserved for Viji's built-in uniforms. Name your parameters descriptively: `speed`, `colorMix`, `intensity`.\n\nIf you have existing Shadertoy shaders, see [Shadertoy Compatibility](/shader/shadertoy) for a compatibility layer that lets you paste code with minimal changes.\n\n---\n\n## Comparison\n\n| | Native | P5.js | Shader |\n|---|--------|-------|--------|\n| **Language** | JavaScript / TypeScript | JavaScript with P5 API | GLSL ES 1.00 (or 3.00 with `#version 300 es`) |\n| **Directive** | None (default) | `// @renderer p5` | `// @renderer shader` |\n| **Entry point** | `render(viji)` | `render(viji, p5)` | `void main()` |\n| **Setup** | Top-level code + `await` | Optional `setup(viji, p5)` | N/A |\n| **Canvas access** | [`viji.useContext('2d'/'webgl'/'webgl2')`](/native/canvas-context) | P5 drawing functions | Automatic fullscreen quad |\n| **External libraries** | Yes (`await import(...)`) | P5.js only | No |\n| **Best for** | Full control, WebGL, Three.js | Familiar P5 workflows | GPU effects, raymarching |\n| **Parameters** | [`viji.slider()`](/native/parameters/slider), etc. | [`viji.slider()`](/native/parameters/slider), etc. | `// @viji-slider:name ...` |\n\n## Next Steps\n\n- [Native Quick Start](/native/quickstart) — build your first native scene\n- [P5 Quick Start](/p5/quickstart) — build your first P5.js scene\n- [Shader Quick Start](/shader/quickstart) — build your first shader\n- [Best Practices](../best-practices/) — essential patterns all artists should follow"
849
+ "markdown": "> [!NOTE]\n> The Viji shader renderer automatically injects `precision mediump float;` and all `uniform` declarations — both built-in uniforms (`u_resolution`, `u_time`, etc.) and parameter uniforms from `@viji-*` directives. Write only your helper functions and `void main() { ... }`. Do NOT redeclare `precision` or any uniforms — they will conflict.\n\n**Key characteristics:**\n\n- **Fragment shader only.** Viji renders a fullscreen quad; your shader defines the color of every pixel.\n- **GLSL ES 1.00 by default.** If you add `#version 300 es` as the first line, Viji switches to WebGL 2. Note that ES 3.00 requires `out vec4` for output instead of `gl_FragColor`, and `texture()` instead of `texture2D()`. ES 1.00 is recommended for maximum compatibility.\n- **Built-in uniforms** like `u_time`, `u_resolution`, `u_mouse`, `u_audioVolume`, `u_video`, and many more are always available — no declaration needed.\n- **Parameters via comments.** Declare parameters with `// @viji-TYPE:uniformName key:value` syntax. They become uniforms automatically.\n- **Accumulators for smooth animation.** Use `// @viji-accumulator:phase rate:speed` instead of `u_time * speed` — the value grows smoothly without jumping when the rate parameter changes.\n- **No `u_` prefix for your parameters.** The `u_` prefix is reserved for Viji's built-in uniforms. Name your parameters descriptively: `speed`, `colorMix`, `intensity`.\n\nIf you have existing Shadertoy shaders, see [Shadertoy Compatibility](/shader/shadertoy) for a compatibility layer that lets you paste code with minimal changes.\n\n---\n\n## Comparison\n\n| | Native | P5.js | Shader |\n|---|--------|-------|--------|\n| **Language** | JavaScript / TypeScript | JavaScript with P5 API | GLSL ES 1.00 (or 3.00 with `#version 300 es`) |\n| **Directive** | None (default) | `// @renderer p5` | `// @renderer shader` |\n| **Entry point** | `render(viji)` | `render(viji, p5)` | `void main()` |\n| **Setup** | Top-level code + `await` | Optional `setup(viji, p5)` | N/A |\n| **Canvas access** | [`viji.useContext('2d'/'webgl'/'webgl2')`](/native/canvas-context) | P5 drawing functions | Automatic fullscreen quad |\n| **External libraries** | Yes (`await import(...)`) | P5.js only | No |\n| **Best for** | Full control, WebGL, Three.js | Familiar P5 workflows | GPU effects, raymarching |\n| **Parameters** | [`viji.slider()`](/native/parameters/slider), etc. | [`viji.slider()`](/native/parameters/slider), etc. | `// @viji-slider:name ...` |\n\n## Next Steps\n\n- [Native Quick Start](/native/quickstart) — build your first native scene\n- [P5 Quick Start](/p5/quickstart) — build your first P5.js scene\n- [Shader Quick Start](/shader/quickstart) — build your first shader\n- [Best Practices](../best-practices/) — essential patterns all artists should follow"
850
850
  }
851
851
  ]
852
852
  },
@@ -868,7 +868,7 @@ export const docsApi = {
868
868
  "content": [
869
869
  {
870
870
  "type": "text",
871
- "markdown": "# Common Mistakes\r\n\r\nThis page collects the most frequent mistakes artists make when writing Viji scenes. Each section shows the wrong approach and the correct alternative.\r\n\r\n---\r\n\r\n## Using DOM APIs\r\n\r\nScenes run in a Web Worker. There is no DOM.\r\n\r\n```javascript\r\n// Wrong — DOM APIs don't exist in workers\r\nconst img = new Image();\r\nimg.src = 'photo.jpg';\r\n\r\ndocument.createElement('canvas');\r\nwindow.innerWidth;\r\nlocalStorage.setItem('key', 'value');\r\n```\r\n\r\n```javascript\r\n// Right — use Viji's API for inputs\r\nconst photo = viji.image(null, { label: 'Photo' });\r\n\r\n// Use viji.canvas, viji.width, viji.height instead\r\n// Use fetch() for loading external data:\r\nconst data = await fetch('https://cdn.example.com/data.json').then(r => r.json());\r\n```\r\n\r\n---\r\n\r\n## Declaring Parameters Inside `render()`\r\n\r\nParameter functions register UI controls with the host. Calling them in `render()` re-registers the parameter every frame, resetting its value to the default and making user changes ineffective.\r\n\r\n```javascript\r\n// Wrong — re-registers the slider every frame, resetting its value\r\nfunction render(viji) {\r\n const speed = viji.slider(1, { min: 0, max: 5, label: 'Speed' });\r\n // ...\r\n}\r\n```\r\n\r\n```javascript\r\n// Right — declare once at top level, read .value in render()\r\nconst speed = viji.slider(1, { min: 0, max: 5, label: 'Speed' });\r\n\r\nfunction render(viji) {\r\n const s = speed.value;\r\n // ...\r\n}\r\n```\r\n\r\n---\r\n\r\n## Forgetting `.value` on Parameters\r\n\r\nParameter objects are not raw values. You need to access `.value` to get the current value.\r\n\r\n```javascript\r\n// Wrong — uses the parameter object, not its value\r\nconst radius = viji.slider(50, { min: 10, max: 200, label: 'Radius' });\r\n\r\nfunction render(viji) {\r\n ctx.arc(x, y, radius, 0, Math.PI * 2); // radius is an object, not a number\r\n}\r\n```\r\n\r\n```javascript\r\n// Right — access .value\r\nfunction render(viji) {\r\n ctx.arc(x, y, radius.value, 0, Math.PI * 2);\r\n}\r\n```\r\n\r\n---\r\n\r\n## Hardcoding Pixel Values\r\n\r\nThe host controls your scene's resolution. Hardcoded values break at different sizes.\r\n\r\n```javascript\r\n// Wrong — only looks right at one specific resolution\r\nfunction render(viji) {\r\n ctx.arc(960, 540, 50, 0, Math.PI * 2);\r\n}\r\n```\r\n\r\n```javascript\r\n// Right — adapts to any resolution\r\nfunction render(viji) {\r\n const cx = viji.width / 2;\r\n const cy = viji.height / 2;\r\n const r = Math.min(viji.width, viji.height) * 0.05;\r\n ctx.arc(cx, cy, r, 0, Math.PI * 2);\r\n}\r\n```\r\n\r\n---\r\n\r\n## Frame-Rate-Dependent Animation\r\n\r\nCounting frames or using fixed increments makes animation speed depend on the device's frame rate.\r\n\r\n```javascript\r\n// Wrong — faster on 120Hz displays, slower on 30Hz\r\nlet angle = 0;\r\nfunction render(viji) {\r\n angle += 0.02;\r\n}\r\n```\r\n\r\n```javascript\r\n// Right — use viji.time for consistent speed regardless of FPS\r\nfunction render(viji) {\r\n const angle = viji.time * speed.value;\r\n}\r\n\r\n// Or use viji.deltaTime for accumulation\r\nlet position = 0;\r\nfunction render(viji) {\r\n position += velocity * viji.deltaTime;\r\n}\r\n```\r\n\r\n---\r\n\r\n## Allocating Objects in `render()`\r\n\r\nCreating new objects every frame causes garbage collection pauses.\r\n\r\n```javascript\r\n// Wrong — new object every frame\r\nfunction render(viji) {\r\n const particles = [];\r\n for (let i = 0; i < 100; i++) {\r\n particles.push({ x: Math.random() * viji.width, y: Math.random() * viji.height });\r\n }\r\n}\r\n```\r\n\r\n```javascript\r\n// Right — pre-allocate and reuse\r\nconst particles = Array.from({ length: 100 }, () => ({ x: 0, y: 0 }));\r\n\r\nfunction render(viji) {\r\n for (const p of particles) {\r\n p.x = Math.random() * viji.width;\r\n p.y = Math.random() * viji.height;\r\n }\r\n}\r\n```\r\n\r\n---\r\n\r\n## P5: Missing the `p5.` Prefix\r\n\r\nViji runs P5 in **instance mode**. All P5 functions must be called on the `p5` object.\r\n\r\n```javascript\r\n// @renderer p5\r\n\r\n// Wrong — global P5 functions don't exist\r\nfunction render(viji, p5) {\r\n background(0); // ReferenceError\r\n fill(255, 0, 0); // ReferenceError\r\n circle(width / 2, height / 2, 100); // ReferenceError\r\n}\r\n```\r\n\r\n```javascript\r\n// @renderer p5\r\n\r\n// Right — use p5. prefix for P5 functions, viji.* for dimensions\r\nfunction render(viji, p5) {\r\n p5.background(0);\r\n p5.fill(255, 0, 0);\r\n p5.circle(viji.width / 2, viji.height / 2, 100);\r\n}\r\n```\r\n\r\n---\r\n\r\n## P5: Using `draw()` Instead of `render()`\r\n\r\nP5's built-in draw loop is disabled in Viji. Your function must be named `render`, not `draw`.\r\n\r\n```javascript\r\n// @renderer p5\r\n\r\n// Wrong — Viji never calls draw()\r\nfunction draw(viji, p5) {\r\n p5.background(0);\r\n}\r\n```\r\n\r\n```javascript\r\n// @renderer p5\r\n\r\n// Right — Viji calls render() every frame\r\nfunction render(viji, p5) {\r\n p5.background(0);\r\n}\r\n```\r\n\r\n---\r\n\r\n## P5: Calling `createCanvas()`\r\n\r\nThe canvas is created and managed by Viji. Calling `createCanvas()` creates a second canvas that won't be displayed.\r\n\r\n```javascript\r\n// @renderer p5\r\n\r\n// Wrong — creates a separate, invisible canvas\r\nfunction setup(viji, p5) {\r\n p5.createCanvas(800, 600);\r\n}\r\n```\r\n\r\n```javascript\r\n// @renderer p5\r\n\r\n// Right — canvas is already provided, just configure settings\r\nfunction setup(viji, p5) {\r\n p5.colorMode(p5.HSB);\r\n}\r\n```\r\n\r\n---\r\n\r\n## P5: Using Event Callbacks\r\n\r\nP5 event callbacks like `mousePressed()`, `keyPressed()`, `touchStarted()` do not work in Viji's worker environment. Use Viji's interaction APIs instead.\r\n\r\n```javascript\r\n// @renderer p5\r\n\r\n// Wrong — these callbacks are never called\r\nfunction mousePressed() {\r\n console.log('clicked');\r\n}\r\n```\r\n\r\n```javascript\r\n// @renderer p5\r\n\r\n// Right — check Viji's interaction state in render()\r\nfunction render(viji, p5) {\r\n if (viji.pointer.wasPressed) {\r\n console.log('clicked');\r\n }\r\n}\r\n```\r\n\r\n---\r\n\r\n## Shader: Redeclaring Auto-Injected Code\r\n\r\nViji auto-injects `precision` and all built-in uniform declarations. Redeclaring them causes conflicts.\r\n\r\n```glsl\r\n// @renderer shader\r\n\r\n// Wrong — these are already injected by Viji\r\nprecision mediump float;\r\nuniform vec2 u_resolution;\r\nuniform float u_time;\r\n\r\nvoid main() {\r\n vec2 uv = gl_FragCoord.xy / u_resolution;\r\n gl_FragColor = vec4(uv, sin(u_time), 1.0);\r\n}\r\n```\r\n\r\n```glsl\r\n// @renderer shader\r\n\r\n// Right — just write your code, uniforms are available automatically\r\nvoid main() {\r\n vec2 uv = gl_FragCoord.xy / u_resolution;\r\n gl_FragColor = vec4(uv, sin(u_time), 1.0);\r\n}\r\n```\r\n\r\n---\r\n\r\n## Shader: Using `u_` Prefix for Parameters\r\n\r\nThe `u_` prefix is reserved for Viji's built-in uniforms. Using it for your parameters risks naming collisions.\r\n\r\n```glsl\r\n// Wrong — u_ prefix is reserved\r\n// @viji-slider:u_speed label:\"Speed\" default:1.0\r\n```\r\n\r\n```glsl\r\n// Right — use descriptive names without u_ prefix\r\n// @viji-slider:speed label:\"Speed\" default:1.0\r\n```\r\n\r\n---\r\n\r\n## Shader: Missing `@renderer shader`\r\n\r\nWithout the directive, your GLSL code is treated as JavaScript and will throw syntax errors.\r\n\r\n```glsl\r\n// Wrong — no directive, treated as JavaScript\r\nvoid main() {\r\n gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\r\n}\r\n```\r\n\r\n```glsl\r\n// Right — directive tells Viji to use the shader renderer\r\n// @renderer shader\r\n\r\nvoid main() {\r\n gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\r\n}\r\n```\r\n\r\n---\r\n\r\n## Shader: Using Block Comments for `@viji-*` Parameters\r\n\r\nThe `@viji-*` parameter declarations only work with single-line `//` comments. Block comments `/* */` are silently ignored.\r\n\r\n```glsl\r\n// @renderer shader\r\n\r\n// Wrong — block comments are not parsed for parameters\r\n/* @viji-slider:speed label:\"Speed\" default:1.0 min:0.0 max:5.0 */\r\n\r\nvoid main() {\r\n gl_FragColor = vec4(speed, 0.0, 0.0, 1.0); // speed is undefined\r\n}\r\n```\r\n\r\n```glsl\r\n// @renderer shader\r\n\r\n// Right — use single-line comments for parameter declarations\r\n// @viji-slider:speed label:\"Speed\" default:1.0 min:0.0 max:5.0\r\n\r\nvoid main() {\r\n gl_FragColor = vec4(speed, 0.0, 0.0, 1.0);\r\n}\r\n```\r\n\r\n> [!NOTE]\r\n> The `@renderer` directive supports both `//` and `/* */` styles, but `@viji-*` parameter declarations require `//`.\r\n\r\n---\r\n\r\n## Shader: Using `u_time * speed` for Parameter-Driven Animation\r\n\r\nMultiplying `u_time` by a parameter causes the entire phase to jump when the slider moves, because the full history is recalculated instantly.\r\n\r\n```glsl\r\n// Wrong — animation jumps when speed slider changes\r\n// @viji-slider:speed label:\"Speed\" default:1.0 min:0.1 max:5.0\r\nvoid main() {\r\n float wave = sin(u_time * speed);\r\n gl_FragColor = vec4(vec3(wave * 0.5 + 0.5), 1.0);\r\n}\r\n```\r\n\r\n```glsl\r\n// Right — accumulator integrates smoothly, no jumps\r\n// @viji-slider:speed label:\"Speed\" default:1.0 min:0.1 max:5.0\r\n// @viji-accumulator:phase rate:speed\r\nvoid main() {\r\n float wave = sin(phase);\r\n gl_FragColor = vec4(vec3(wave * 0.5 + 0.5), 1.0);\r\n}\r\n```\r\n\r\n---\r\n\r\n## Not Checking `isConnected` for Audio/Video\r\n\r\nAudio and video streams may not be available. Accessing their properties without checking `isConnected` gives meaningless zero values with no indication that something is missing.\r\n\r\n```javascript\r\n// Wrong — no guard, silently uses zero values\r\nfunction render(viji) {\r\n const bass = viji.audio.bands.low;\r\n ctx.drawImage(viji.video.currentFrame, 0, 0);\r\n}\r\n```\r\n\r\n```javascript\r\n// Right — check connection state first\r\nfunction render(viji) {\r\n if (viji.audio.isConnected) {\r\n const bass = viji.audio.bands.low;\r\n // ... react to audio\r\n }\r\n\r\n if (viji.video.isConnected && viji.video.currentFrame) {\r\n ctx.drawImage(viji.video.currentFrame, 0, 0, viji.width, viji.height);\r\n }\r\n}\r\n```\r\n\r\n---\r\n\r\n## Enabling All CV Features by Default\r\n\r\nEnabling CV features without user consent wastes resources on devices that can't handle it, and risks WebGL context loss.\r\n\r\n```javascript\r\n// Wrong — activates expensive CV on every device\r\nawait viji.video.cv.enableFaceDetection(true);\r\nawait viji.video.cv.enableHandTracking(true);\r\nawait viji.video.cv.enablePoseDetection(true);\r\nawait viji.video.cv.enableBodySegmentation(true);\r\n```\r\n\r\n```javascript\r\n// Right — let the user opt in\r\nconst useFace = viji.toggle(false, { label: 'Enable Face Tracking', category: 'video' });\r\nconst useHands = viji.toggle(false, { label: 'Enable Hand Tracking', category: 'video' });\r\n\r\nfunction render(viji) {\r\n if (useFace.value) await viji.video.cv.enableFaceDetection(true);\r\n else await viji.video.cv.enableFaceDetection(false);\r\n\r\n if (useHands.value) await viji.video.cv.enableHandTracking(true);\r\n else await viji.video.cv.enableHandTracking(false);\r\n}\r\n```\r\n\r\n---\r\n\r\n## Related\r\n\r\n- [Best Practices](../best-practices/) — positive guidance for writing robust scenes\r\n- [Performance](/advanced/performance) — deep dive into optimization\r\n- [Renderers Overview](../renderers-overview/) — choosing the right renderer"
871
+ "markdown": "# Common Mistakes\r\n\r\nThis page collects the most frequent mistakes artists make when writing Viji scenes. Each section shows the wrong approach and the correct alternative.\r\n\r\n---\r\n\r\n## Using DOM APIs\r\n\r\nScenes run in a Web Worker. There is no DOM.\r\n\r\n```javascript\r\n// Wrong — DOM APIs don't exist in workers\r\nconst img = new Image();\r\nimg.src = 'photo.jpg';\r\n\r\ndocument.createElement('canvas');\r\nwindow.innerWidth;\r\nlocalStorage.setItem('key', 'value');\r\n```\r\n\r\n```javascript\r\n// Right — use Viji's API for inputs\r\nconst photo = viji.image(null, { label: 'Photo' });\r\n\r\n// Use viji.canvas, viji.width, viji.height instead\r\n// Use fetch() for loading external data:\r\nconst data = await fetch('https://cdn.example.com/data.json').then(r => r.json());\r\n```\r\n\r\n---\r\n\r\n## Declaring Parameters Inside `render()`\r\n\r\nParameter functions register UI controls with the host. Calling them in `render()` re-registers the parameter every frame, resetting its value to the default and making user changes ineffective.\r\n\r\n```javascript\r\n// Wrong — re-registers the slider every frame, resetting its value\r\nfunction render(viji) {\r\n const speed = viji.slider(1, { min: 0, max: 5, label: 'Speed' });\r\n // ...\r\n}\r\n```\r\n\r\n```javascript\r\n// Right — declare once at top level, read .value in render()\r\nconst speed = viji.slider(1, { min: 0, max: 5, label: 'Speed' });\r\n\r\nfunction render(viji) {\r\n const s = speed.value;\r\n // ...\r\n}\r\n```\r\n\r\n---\r\n\r\n## Forgetting `.value` on Parameters\r\n\r\nParameter objects are not raw values. You need to access `.value` to get the current value.\r\n\r\n```javascript\r\n// Wrong — uses the parameter object, not its value\r\nconst radius = viji.slider(50, { min: 10, max: 200, label: 'Radius' });\r\n\r\nfunction render(viji) {\r\n ctx.arc(x, y, radius, 0, Math.PI * 2); // radius is an object, not a number\r\n}\r\n```\r\n\r\n```javascript\r\n// Right — access .value\r\nfunction render(viji) {\r\n ctx.arc(x, y, radius.value, 0, Math.PI * 2);\r\n}\r\n```\r\n\r\n---\r\n\r\n## Hardcoding Pixel Values\r\n\r\nThe host controls your scene's resolution. Hardcoded values break at different sizes.\r\n\r\n```javascript\r\n// Wrong — only looks right at one specific resolution\r\nfunction render(viji) {\r\n ctx.arc(960, 540, 50, 0, Math.PI * 2);\r\n}\r\n```\r\n\r\n```javascript\r\n// Right — adapts to any resolution\r\nfunction render(viji) {\r\n const cx = viji.width / 2;\r\n const cy = viji.height / 2;\r\n const r = Math.min(viji.width, viji.height) * 0.05;\r\n ctx.arc(cx, cy, r, 0, Math.PI * 2);\r\n}\r\n```\r\n\r\n---\r\n\r\n## Frame-Rate-Dependent Animation\r\n\r\nCounting frames or using fixed increments makes animation speed depend on the device's frame rate.\r\n\r\n```javascript\r\n// Wrong — faster on 120Hz displays, slower on 30Hz\r\nlet angle = 0;\r\nfunction render(viji) {\r\n angle += 0.02;\r\n}\r\n```\r\n\r\n```javascript\r\n// Right — use viji.time for consistent speed regardless of FPS\r\nfunction render(viji) {\r\n const angle = viji.time * speed.value;\r\n}\r\n\r\n// Or use viji.deltaTime for accumulation\r\nlet position = 0;\r\nfunction render(viji) {\r\n position += velocity * viji.deltaTime;\r\n}\r\n```\r\n\r\n---\r\n\r\n## Allocating Objects in `render()`\r\n\r\nCreating new objects every frame causes garbage collection pauses.\r\n\r\n```javascript\r\n// Wrong — new object every frame\r\nfunction render(viji) {\r\n const particles = [];\r\n for (let i = 0; i < 100; i++) {\r\n particles.push({ x: Math.random() * viji.width, y: Math.random() * viji.height });\r\n }\r\n}\r\n```\r\n\r\n```javascript\r\n// Right — pre-allocate and reuse\r\nconst particles = Array.from({ length: 100 }, () => ({ x: 0, y: 0 }));\r\n\r\nfunction render(viji) {\r\n for (const p of particles) {\r\n p.x = Math.random() * viji.width;\r\n p.y = Math.random() * viji.height;\r\n }\r\n}\r\n```\r\n\r\n---\r\n\r\n## P5: Missing the `p5.` Prefix\r\n\r\nViji runs P5 in **instance mode**. All P5 functions must be called on the `p5` object.\r\n\r\n```javascript\r\n// @renderer p5\r\n\r\n// Wrong — global P5 functions don't exist\r\nfunction render(viji, p5) {\r\n background(0); // ReferenceError\r\n fill(255, 0, 0); // ReferenceError\r\n circle(width / 2, height / 2, 100); // ReferenceError\r\n}\r\n```\r\n\r\n```javascript\r\n// @renderer p5\r\n\r\n// Right — use p5. prefix for P5 functions, viji.* for dimensions\r\nfunction render(viji, p5) {\r\n p5.background(0);\r\n p5.fill(255, 0, 0);\r\n p5.circle(viji.width / 2, viji.height / 2, 100);\r\n}\r\n```\r\n\r\n---\r\n\r\n## P5: Using `draw()` Instead of `render()`\r\n\r\nP5's built-in draw loop is disabled in Viji. Your function must be named `render`, not `draw`.\r\n\r\n```javascript\r\n// @renderer p5\r\n\r\n// Wrong — Viji never calls draw()\r\nfunction draw(viji, p5) {\r\n p5.background(0);\r\n}\r\n```\r\n\r\n```javascript\r\n// @renderer p5\r\n\r\n// Right — Viji calls render() every frame\r\nfunction render(viji, p5) {\r\n p5.background(0);\r\n}\r\n```\r\n\r\n---\r\n\r\n## P5: Calling `createCanvas()`\r\n\r\nThe canvas is created and managed by Viji. Calling `createCanvas()` creates a second canvas that won't be displayed.\r\n\r\n```javascript\r\n// @renderer p5\r\n\r\n// Wrong — creates a separate, invisible canvas\r\nfunction setup(viji, p5) {\r\n p5.createCanvas(800, 600);\r\n}\r\n```\r\n\r\n```javascript\r\n// @renderer p5\r\n\r\n// Right — canvas is already provided, just configure settings\r\nfunction setup(viji, p5) {\r\n p5.colorMode(p5.HSB);\r\n}\r\n```\r\n\r\n---\r\n\r\n## P5: Using Event Callbacks\r\n\r\nP5 event callbacks like `mousePressed()`, `keyPressed()`, `touchStarted()` do not work in Viji's worker environment. Use Viji's interaction APIs instead.\r\n\r\n```javascript\r\n// @renderer p5\r\n\r\n// Wrong — these callbacks are never called\r\nfunction mousePressed() {\r\n console.log('clicked');\r\n}\r\n```\r\n\r\n```javascript\r\n// @renderer p5\r\n\r\n// Right — check Viji's interaction state in render()\r\nfunction render(viji, p5) {\r\n if (viji.pointer.wasPressed) {\r\n console.log('clicked');\r\n }\r\n}\r\n```\r\n\r\n---\r\n\r\n## Shader: Redeclaring Auto-Injected Code\r\n\r\nViji auto-injects `precision`, all built-in uniform declarations, and all parameter uniforms from `@viji-*` directives. Redeclaring any of them causes compilation errors.\r\n\r\n```glsl\r\n// @renderer shader\r\n\r\n// Wrong — these are already injected by Viji\r\nprecision mediump float;\r\nuniform vec2 u_resolution;\r\nuniform float u_time;\r\n\r\nvoid main() {\r\n vec2 uv = gl_FragCoord.xy / u_resolution;\r\n gl_FragColor = vec4(uv, sin(u_time), 1.0);\r\n}\r\n```\r\n\r\n```glsl\r\n// @renderer shader\r\n\r\n// Right — just write your code, uniforms are available automatically\r\nvoid main() {\r\n vec2 uv = gl_FragCoord.xy / u_resolution;\r\n gl_FragColor = vec4(uv, sin(u_time), 1.0);\r\n}\r\n```\r\n\r\n---\r\n\r\n## Shader: Using `u_` Prefix for Parameters\r\n\r\nThe `u_` prefix is reserved for Viji's built-in uniforms. Using it for your parameters risks naming collisions.\r\n\r\n```glsl\r\n// Wrong — u_ prefix is reserved\r\n// @viji-slider:u_speed label:\"Speed\" default:1.0\r\n```\r\n\r\n```glsl\r\n// Right — use descriptive names without u_ prefix\r\n// @viji-slider:speed label:\"Speed\" default:1.0\r\n```\r\n\r\n---\r\n\r\n## Shader: Missing `@renderer shader`\r\n\r\nWithout the directive, your GLSL code is treated as JavaScript and will throw syntax errors.\r\n\r\n```glsl\r\n// Wrong — no directive, treated as JavaScript\r\nvoid main() {\r\n gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\r\n}\r\n```\r\n\r\n```glsl\r\n// Right — directive tells Viji to use the shader renderer\r\n// @renderer shader\r\n\r\nvoid main() {\r\n gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\r\n}\r\n```\r\n\r\n---\r\n\r\n## Shader: Using Block Comments for `@viji-*` Parameters\r\n\r\nThe `@viji-*` parameter declarations only work with single-line `//` comments. Block comments `/* */` are silently ignored.\r\n\r\n```glsl\r\n// @renderer shader\r\n\r\n// Wrong — block comments are not parsed for parameters\r\n/* @viji-slider:speed label:\"Speed\" default:1.0 min:0.0 max:5.0 */\r\n\r\nvoid main() {\r\n gl_FragColor = vec4(speed, 0.0, 0.0, 1.0); // speed is undefined\r\n}\r\n```\r\n\r\n```glsl\r\n// @renderer shader\r\n\r\n// Right — use single-line comments for parameter declarations\r\n// @viji-slider:speed label:\"Speed\" default:1.0 min:0.0 max:5.0\r\n\r\nvoid main() {\r\n gl_FragColor = vec4(speed, 0.0, 0.0, 1.0);\r\n}\r\n```\r\n\r\n> [!NOTE]\r\n> The `@renderer` directive supports both `//` and `/* */` styles, but `@viji-*` parameter declarations require `//`.\r\n\r\n---\r\n\r\n## Shader: Using `u_time * speed` for Parameter-Driven Animation\r\n\r\nMultiplying `u_time` by a parameter causes the entire phase to jump when the slider moves, because the full history is recalculated instantly.\r\n\r\n```glsl\r\n// Wrong — animation jumps when speed slider changes\r\n// @viji-slider:speed label:\"Speed\" default:1.0 min:0.1 max:5.0\r\nvoid main() {\r\n float wave = sin(u_time * speed);\r\n gl_FragColor = vec4(vec3(wave * 0.5 + 0.5), 1.0);\r\n}\r\n```\r\n\r\n```glsl\r\n// Right — accumulator integrates smoothly, no jumps\r\n// @viji-slider:speed label:\"Speed\" default:1.0 min:0.1 max:5.0\r\n// @viji-accumulator:phase rate:speed\r\nvoid main() {\r\n float wave = sin(phase);\r\n gl_FragColor = vec4(vec3(wave * 0.5 + 0.5), 1.0);\r\n}\r\n```\r\n\r\n---\r\n\r\n## Not Checking `isConnected` for Audio/Video\r\n\r\nAudio and video streams may not be available. Accessing their properties without checking `isConnected` gives meaningless zero values with no indication that something is missing.\r\n\r\n```javascript\r\n// Wrong — no guard, silently uses zero values\r\nfunction render(viji) {\r\n const bass = viji.audio.bands.low;\r\n ctx.drawImage(viji.video.currentFrame, 0, 0);\r\n}\r\n```\r\n\r\n```javascript\r\n// Right — check connection state first\r\nfunction render(viji) {\r\n if (viji.audio.isConnected) {\r\n const bass = viji.audio.bands.low;\r\n // ... react to audio\r\n }\r\n\r\n if (viji.video.isConnected && viji.video.currentFrame) {\r\n ctx.drawImage(viji.video.currentFrame, 0, 0, viji.width, viji.height);\r\n }\r\n}\r\n```\r\n\r\n---\r\n\r\n## Enabling All CV Features by Default\r\n\r\nEnabling CV features without user consent wastes resources on devices that can't handle it, and risks WebGL context loss.\r\n\r\n```javascript\r\n// Wrong — activates expensive CV on every device\r\nawait viji.video.cv.enableFaceDetection(true);\r\nawait viji.video.cv.enableHandTracking(true);\r\nawait viji.video.cv.enablePoseDetection(true);\r\nawait viji.video.cv.enableBodySegmentation(true);\r\n```\r\n\r\n```javascript\r\n// Right — let the user opt in\r\nconst useFace = viji.toggle(false, { label: 'Enable Face Tracking', category: 'video' });\r\nconst useHands = viji.toggle(false, { label: 'Enable Hand Tracking', category: 'video' });\r\n\r\nfunction render(viji) {\r\n if (useFace.value) await viji.video.cv.enableFaceDetection(true);\r\n else await viji.video.cv.enableFaceDetection(false);\r\n\r\n if (useHands.value) await viji.video.cv.enableHandTracking(true);\r\n else await viji.video.cv.enableHandTracking(false);\r\n}\r\n```\r\n\r\n---\r\n\r\n## Related\r\n\r\n- [Best Practices](../best-practices/) — positive guidance for writing robust scenes\r\n- [Performance](/advanced/performance) — deep dive into optimization\r\n- [Renderers Overview](../renderers-overview/) — choosing the right renderer"
872
872
  }
873
873
  ]
874
874
  },
@@ -1222,7 +1222,11 @@ export const docsApi = {
1222
1222
  "type": "live-example",
1223
1223
  "title": "Parameter Categories",
1224
1224
  "sceneCode": "const baseColor = viji.color('#4488ff', { label: 'Base Color', category: 'general' });\r\nconst pulseAmount = viji.slider(0.3, { min: 0, max: 1, step: 0.01, label: 'Audio Pulse', category: 'audio' });\r\nconst showMouse = viji.toggle(true, { label: 'Mouse Trail', category: 'interaction' });\r\n\r\nlet angle = 0;\r\nlet mouseTrailX = 0;\r\nlet mouseTrailY = 0;\r\n\r\nfunction render(viji) {\r\n const ctx = viji.useContext('2d');\r\n const w = viji.width;\r\n const h = viji.height;\r\n\r\n ctx.fillStyle = 'rgba(10, 10, 30, 0.15)';\r\n ctx.fillRect(0, 0, w, h);\r\n\r\n angle += viji.deltaTime;\r\n\r\n const r = parseInt(baseColor.value.slice(1, 3), 16);\r\n const g = parseInt(baseColor.value.slice(3, 5), 16);\r\n const b = parseInt(baseColor.value.slice(5, 7), 16);\r\n\r\n let pulse = 0;\r\n if (viji.audio.isConnected) {\r\n pulse = viji.audio.volume.current * pulseAmount.value;\r\n }\r\n\r\n const baseR = Math.min(w, h) * (0.1 + pulse * 0.15);\r\n const cx = w / 2 + Math.cos(angle) * w * 0.2;\r\n const cy = h / 2 + Math.sin(angle * 0.7) * h * 0.2;\r\n\r\n ctx.beginPath();\r\n ctx.arc(cx, cy, baseR, 0, Math.PI * 2);\r\n ctx.fillStyle = `rgb(${r}, ${g}, ${b})`;\r\n ctx.fill();\r\n\r\n if (showMouse.value && viji.mouse.isInCanvas) {\r\n mouseTrailX += (viji.mouse.x - mouseTrailX) * 0.1;\r\n mouseTrailY += (viji.mouse.y - mouseTrailY) * 0.1;\r\n ctx.beginPath();\r\n ctx.arc(mouseTrailX, mouseTrailY, Math.min(w, h) * 0.02, 0, Math.PI * 2);\r\n ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';\r\n ctx.fill();\r\n }\r\n}\r\n",
1225
- "sceneFile": "categories-demo.scene.js"
1225
+ "sceneFile": "categories-demo.scene.js",
1226
+ "capabilities": {
1227
+ "audio": true,
1228
+ "interaction": true
1229
+ }
1226
1230
  },
1227
1231
  {
1228
1232
  "type": "text",
@@ -1243,7 +1247,10 @@ export const docsApi = {
1243
1247
  "type": "live-example",
1244
1248
  "title": "Pointer — Drag Trail",
1245
1249
  "sceneCode": "const ctx = viji.useContext('2d');\nconst trail = [];\nconst maxTrail = 80;\n\nfunction render(viji) {\n const w = viji.width, h = viji.height;\n const p = viji.pointer;\n\n ctx.fillStyle = 'rgba(10, 10, 30, 0.15)';\n ctx.fillRect(0, 0, w, h);\n\n if (p.isDown) {\n trail.push({ x: p.x, y: p.y });\n if (trail.length > maxTrail) trail.shift();\n } else if (trail.length > 0) {\n trail.shift();\n }\n\n for (let i = 0; i < trail.length; i++) {\n const t = i / trail.length;\n const radius = 3 + t * 12;\n ctx.beginPath();\n ctx.arc(trail[i].x, trail[i].y, radius, 0, Math.PI * 2);\n ctx.fillStyle = `hsla(${200 + t * 60}, 80%, 65%, ${t * 0.8})`;\n ctx.fill();\n }\n\n const size = Math.min(w, h);\n ctx.fillStyle = p.isDown ? 'rgba(100, 220, 255, 0.9)' : 'rgba(200, 200, 200, 0.5)';\n ctx.beginPath();\n ctx.arc(p.x, p.y, size * 0.015, 0, Math.PI * 2);\n ctx.fill();\n\n ctx.fillStyle = 'rgba(255,255,255,0.5)';\n ctx.font = `${size * 0.025}px monospace`;\n ctx.textAlign = 'left';\n ctx.fillText(`pointer: (${Math.round(p.x)}, ${Math.round(p.y)}) type: ${p.type}`, size * 0.03, h - size * 0.03);\n ctx.fillText(`isDown: ${p.isDown} inCanvas: ${p.isInCanvas}`, size * 0.03, h - size * 0.06);\n}\n",
1246
- "sceneFile": "pointer-demo.scene.js"
1250
+ "sceneFile": "pointer-demo.scene.js",
1251
+ "capabilities": {
1252
+ "interaction": true
1253
+ }
1247
1254
  },
1248
1255
  {
1249
1256
  "type": "text",
@@ -1264,7 +1271,10 @@ export const docsApi = {
1264
1271
  "type": "live-example",
1265
1272
  "title": "Mouse — Buttons & Wheel",
1266
1273
  "sceneCode": "const ctx = viji.useContext('2d');\nlet hue = 200;\nlet zoom = 1;\nlet prevRight = false;\n\nfunction render(viji) {\n const w = viji.width, h = viji.height;\n const m = viji.mouse;\n const size = Math.min(w, h);\n\n if (m.rightButton && !prevRight) hue = (hue + 50) % 360;\n prevRight = m.rightButton;\n\n zoom -= m.wheelDelta * 0.001;\n zoom = Math.max(0.3, Math.min(5, zoom));\n\n ctx.fillStyle = 'rgba(10, 10, 30, 0.2)';\n ctx.fillRect(0, 0, w, h);\n\n const speed = Math.sqrt(m.deltaX ** 2 + m.deltaY ** 2);\n const radius = (size * 0.02 + speed * 1.5) * zoom;\n\n if (m.isInCanvas) {\n ctx.beginPath();\n ctx.arc(m.x, m.y, radius, 0, Math.PI * 2);\n const alpha = m.isPressed ? 0.9 : 0.4;\n ctx.fillStyle = `hsla(${hue}, 80%, 65%, ${alpha})`;\n ctx.fill();\n\n if (m.leftButton) {\n ctx.strokeStyle = `hsla(${hue}, 80%, 75%, 0.6)`;\n ctx.lineWidth = 2;\n ctx.stroke();\n }\n }\n\n ctx.fillStyle = 'rgba(255,255,255,0.5)';\n ctx.font = `${size * 0.022}px monospace`;\n ctx.textAlign = 'left';\n const y0 = h - size * 0.15;\n ctx.fillText(`pos: (${Math.round(m.x)}, ${Math.round(m.y)}) inCanvas: ${m.isInCanvas}`, size * 0.03, y0);\n ctx.fillText(`buttons: L[${m.leftButton ? '\\u25A0' : '\\u25A1'}] R[${m.rightButton ? '\\u25A0' : '\\u25A1'}] M[${m.middleButton ? '\\u25A0' : '\\u25A1'}]`, size * 0.03, y0 + size * 0.03);\n ctx.fillText(`delta: (${m.deltaX.toFixed(0)}, ${m.deltaY.toFixed(0)}) wheel: ${m.wheelDelta.toFixed(1)}`, size * 0.03, y0 + size * 0.06);\n ctx.fillText(`zoom: ${zoom.toFixed(2)} hue: ${hue}`, size * 0.03, y0 + size * 0.09);\n}\n",
1267
- "sceneFile": "mouse-demo.scene.js"
1274
+ "sceneFile": "mouse-demo.scene.js",
1275
+ "capabilities": {
1276
+ "interaction": true
1277
+ }
1268
1278
  },
1269
1279
  {
1270
1280
  "type": "text",
@@ -1285,7 +1295,10 @@ export const docsApi = {
1285
1295
  "type": "live-example",
1286
1296
  "title": "Keyboard — Movement & State",
1287
1297
  "sceneCode": "const ctx = viji.useContext('2d');\nlet px, py;\n\nfunction render(viji) {\n const w = viji.width, h = viji.height;\n const size = Math.min(w, h);\n const kb = viji.keyboard;\n\n if (px === undefined) { px = w / 2; py = h / 2; }\n\n const speed = size * 0.4 * viji.deltaTime * (kb.shift ? 2.5 : 1);\n if (kb.isPressed('w') || kb.isPressed('arrowup')) py -= speed;\n if (kb.isPressed('s') || kb.isPressed('arrowdown')) py += speed;\n if (kb.isPressed('a') || kb.isPressed('arrowleft')) px -= speed;\n if (kb.isPressed('d') || kb.isPressed('arrowright')) px += speed;\n px = Math.max(0, Math.min(w, px));\n py = Math.max(0, Math.min(h, py));\n\n ctx.fillStyle = 'rgba(10, 10, 30, 0.15)';\n ctx.fillRect(0, 0, w, h);\n\n const r = size * 0.03;\n ctx.beginPath();\n ctx.arc(px, py, r, 0, Math.PI * 2);\n ctx.fillStyle = `hsl(${(viji.time * 40) % 360}, 80%, 65%)`;\n ctx.fill();\n\n ctx.fillStyle = 'rgba(255,255,255,0.5)';\n ctx.font = `${size * 0.022}px monospace`;\n ctx.textAlign = 'left';\n const y0 = h - size * 0.12;\n const keys = [...kb.activeKeys];\n ctx.fillText(`active: [${keys.join(', ')}]`, size * 0.03, y0);\n ctx.fillText(`mods: ${kb.shift ? '[Shift] ' : ''}${kb.ctrl ? '[Ctrl] ' : ''}${kb.alt ? '[Alt] ' : ''}${kb.meta ? '[Meta]' : ''}${!kb.shift && !kb.ctrl && !kb.alt && !kb.meta ? 'none' : ''}`, size * 0.03, y0 + size * 0.03);\n ctx.fillText(`last pressed: \"${kb.lastKeyPressed}\" released: \"${kb.lastKeyReleased}\"`, size * 0.03, y0 + size * 0.06);\n\n ctx.fillStyle = 'rgba(255,255,255,0.3)';\n ctx.textAlign = 'center';\n ctx.fillText('WASD / Arrows to move \\u2022 Shift for speed', w / 2, size * 0.04);\n}\n",
1288
- "sceneFile": "keyboard-demo.scene.js"
1298
+ "sceneFile": "keyboard-demo.scene.js",
1299
+ "capabilities": {
1300
+ "interaction": true
1301
+ }
1289
1302
  },
1290
1303
  {
1291
1304
  "type": "text",
@@ -1306,7 +1319,10 @@ export const docsApi = {
1306
1319
  "type": "live-example",
1307
1320
  "title": "Touch — Multi-Point Tracker",
1308
1321
  "sceneCode": "const ctx = viji.useContext('2d');\nconst ripples = [];\n\nfunction render(viji) {\n const w = viji.width, h = viji.height;\n const size = Math.min(w, h);\n const touch = viji.touches;\n const dt = viji.deltaTime;\n\n for (const pt of touch.started) {\n ripples.push({ x: pt.x, y: pt.y, r: 0, alpha: 1 });\n }\n\n ctx.fillStyle = 'rgba(10, 10, 30, 0.2)';\n ctx.fillRect(0, 0, w, h);\n\n for (let i = ripples.length - 1; i >= 0; i--) {\n const rp = ripples[i];\n rp.r += size * 0.3 * dt;\n rp.alpha -= dt * 0.8;\n if (rp.alpha <= 0) { ripples.splice(i, 1); continue; }\n ctx.beginPath();\n ctx.arc(rp.x, rp.y, rp.r, 0, Math.PI * 2);\n ctx.strokeStyle = `hsla(200, 80%, 70%, ${rp.alpha})`;\n ctx.lineWidth = 2;\n ctx.stroke();\n }\n\n for (let i = 0; i < touch.count; i++) {\n const pt = touch.points[i];\n const r = size * 0.02 + pt.pressure * size * 0.04;\n\n ctx.beginPath();\n ctx.arc(pt.x, pt.y, r, 0, Math.PI * 2);\n ctx.fillStyle = `hsla(${120 + i * 60}, 80%, 65%, 0.8)`;\n ctx.fill();\n\n const speed = Math.sqrt(pt.velocity.x ** 2 + pt.velocity.y ** 2);\n if (speed > 10) {\n const len = Math.min(speed * 0.05, size * 0.08);\n const angle = Math.atan2(pt.velocity.y, pt.velocity.x);\n ctx.beginPath();\n ctx.moveTo(pt.x, pt.y);\n ctx.lineTo(pt.x + Math.cos(angle) * len, pt.y + Math.sin(angle) * len);\n ctx.strokeStyle = `hsla(${120 + i * 60}, 80%, 75%, 0.5)`;\n ctx.lineWidth = 2;\n ctx.stroke();\n }\n\n ctx.fillStyle = 'rgba(255,255,255,0.6)';\n ctx.font = `${size * 0.02}px monospace`;\n ctx.textAlign = 'center';\n ctx.fillText(`T${pt.id}`, pt.x, pt.y - r - size * 0.01);\n }\n\n ctx.fillStyle = 'rgba(255,255,255,0.4)';\n ctx.font = `${size * 0.022}px monospace`;\n ctx.textAlign = 'left';\n ctx.fillText(`touches: ${touch.count} primary: ${touch.primary ? 'T' + touch.primary.id : '-'}`, size * 0.03, h - size * 0.03);\n}\n",
1309
- "sceneFile": "touch-demo.scene.js"
1322
+ "sceneFile": "touch-demo.scene.js",
1323
+ "capabilities": {
1324
+ "interaction": true
1325
+ }
1310
1326
  },
1311
1327
  {
1312
1328
  "type": "text",
@@ -1632,7 +1648,11 @@ export const docsApi = {
1632
1648
  "type": "live-example",
1633
1649
  "title": "P5 Parameter Categories",
1634
1650
  "sceneCode": "// @renderer p5\r\n\r\nconst baseColor = viji.color('#4488ff', { label: 'Base Color', category: 'general' });\r\nconst pulseAmount = viji.slider(0.3, { min: 0, max: 1, step: 0.01, label: 'Audio Pulse', category: 'audio' });\r\nconst showMouse = viji.toggle(true, { label: 'Mouse Dot', category: 'interaction' });\r\n\r\nlet angle = 0;\r\n\r\nfunction render(viji, p5) {\r\n p5.background(10, 10, 30, 40);\r\n\r\n angle += viji.deltaTime;\r\n\r\n const r = parseInt(baseColor.value.slice(1, 3), 16);\r\n const g = parseInt(baseColor.value.slice(3, 5), 16);\r\n const b = parseInt(baseColor.value.slice(5, 7), 16);\r\n\r\n let pulse = 0;\r\n if (viji.audio.isConnected) {\r\n pulse = viji.audio.volume.current * pulseAmount.value;\r\n }\r\n\r\n const baseR = Math.min(viji.width, viji.height) * (0.1 + pulse * 0.15);\r\n const cx = viji.width / 2 + p5.cos(angle) * viji.width * 0.2;\r\n const cy = viji.height / 2 + p5.sin(angle * 0.7) * viji.height * 0.2;\r\n\r\n p5.noStroke();\r\n p5.fill(r, g, b);\r\n p5.circle(cx, cy, baseR * 2);\r\n\r\n if (showMouse.value && viji.mouse.isInCanvas) {\r\n p5.fill(255, 255, 255, 200);\r\n p5.circle(viji.mouse.x, viji.mouse.y, Math.min(viji.width, viji.height) * 0.04);\r\n }\r\n}\r\n",
1635
- "sceneFile": "categories-demo.scene.js"
1651
+ "sceneFile": "categories-demo.scene.js",
1652
+ "capabilities": {
1653
+ "audio": true,
1654
+ "interaction": true
1655
+ }
1636
1656
  },
1637
1657
  {
1638
1658
  "type": "text",
@@ -1653,7 +1673,10 @@ export const docsApi = {
1653
1673
  "type": "live-example",
1654
1674
  "title": "Pointer — Drag Trail",
1655
1675
  "sceneCode": "// @renderer p5\n\nconst trail = [];\nconst maxTrail = 80;\n\nfunction setup(viji, p5) {\n p5.colorMode(p5.HSB, 360, 100, 100, 100);\n}\n\nfunction render(viji, p5) {\n const ptr = viji.pointer;\n\n p5.background(240, 10, 8, 15);\n\n if (ptr.isDown) {\n trail.push({ x: ptr.x, y: ptr.y });\n if (trail.length > maxTrail) trail.shift();\n } else if (trail.length > 0) {\n trail.shift();\n }\n\n p5.noStroke();\n for (let i = 0; i < trail.length; i++) {\n const t = i / trail.length;\n const r = 3 + t * 12;\n p5.fill(200 + t * 60, 80, 65, t * 80);\n p5.ellipse(trail[i].x, trail[i].y, r * 2);\n }\n\n const cursorSize = Math.min(viji.width, viji.height) * 0.03;\n p5.fill(ptr.isDown ? p5.color(200, 80, 90) : p5.color(0, 0, 80, 50));\n p5.ellipse(ptr.x, ptr.y, cursorSize);\n\n p5.fill(0, 0, 100, 50);\n p5.textSize(Math.min(viji.width, viji.height) * 0.025);\n p5.textAlign(p5.LEFT);\n p5.text(`pointer: (${Math.round(ptr.x)}, ${Math.round(ptr.y)}) type: ${ptr.type}`, viji.width * 0.03, viji.height - viji.height * 0.06);\n p5.text(`isDown: ${ptr.isDown} inCanvas: ${ptr.isInCanvas}`, viji.width * 0.03, viji.height - viji.height * 0.03);\n}\n",
1656
- "sceneFile": "pointer-p5-demo.scene.js"
1676
+ "sceneFile": "pointer-p5-demo.scene.js",
1677
+ "capabilities": {
1678
+ "interaction": true
1679
+ }
1657
1680
  },
1658
1681
  {
1659
1682
  "type": "text",
@@ -1674,7 +1697,10 @@ export const docsApi = {
1674
1697
  "type": "live-example",
1675
1698
  "title": "Mouse — Buttons & Wheel",
1676
1699
  "sceneCode": "// @renderer p5\n\nlet hue = 200;\nlet zoom = 1;\nlet prevRight = false;\n\nfunction setup(viji, p5) {\n p5.colorMode(p5.HSB, 360, 100, 100, 100);\n}\n\nfunction render(viji, p5) {\n const m = viji.mouse;\n const size = Math.min(viji.width, viji.height);\n\n if (m.rightButton && !prevRight) hue = (hue + 50) % 360;\n prevRight = m.rightButton;\n\n zoom -= m.wheelDelta * 0.001;\n zoom = Math.max(0.3, Math.min(5, zoom));\n\n p5.background(240, 10, 8, 20);\n\n const speed = Math.sqrt(m.deltaX ** 2 + m.deltaY ** 2);\n const radius = (size * 0.02 + speed * 1.5) * zoom;\n\n if (m.isInCanvas) {\n p5.noStroke();\n p5.fill(hue, 80, 65, m.isPressed ? 90 : 40);\n p5.ellipse(m.x, m.y, radius * 2);\n\n if (m.leftButton) {\n p5.noFill();\n p5.stroke(hue, 80, 75, 60);\n p5.strokeWeight(2);\n p5.ellipse(m.x, m.y, radius * 2);\n }\n }\n\n p5.noStroke();\n p5.fill(0, 0, 100, 50);\n p5.textSize(size * 0.022);\n p5.textFont('monospace');\n p5.textAlign(p5.LEFT);\n const y0 = viji.height - size * 0.12;\n p5.text(`pos: (${Math.round(m.x)}, ${Math.round(m.y)}) inCanvas: ${m.isInCanvas}`, size * 0.03, y0);\n p5.text(`buttons: L[${m.leftButton ? '\\u25A0' : '\\u25A1'}] R[${m.rightButton ? '\\u25A0' : '\\u25A1'}] M[${m.middleButton ? '\\u25A0' : '\\u25A1'}]`, size * 0.03, y0 + size * 0.03);\n p5.text(`wheel: ${m.wheelDelta.toFixed(1)} zoom: ${zoom.toFixed(2)}`, size * 0.03, y0 + size * 0.06);\n}\n",
1677
- "sceneFile": "mouse-p5-demo.scene.js"
1700
+ "sceneFile": "mouse-p5-demo.scene.js",
1701
+ "capabilities": {
1702
+ "interaction": true
1703
+ }
1678
1704
  },
1679
1705
  {
1680
1706
  "type": "text",
@@ -1695,7 +1721,10 @@ export const docsApi = {
1695
1721
  "type": "live-example",
1696
1722
  "title": "Keyboard — Movement & State",
1697
1723
  "sceneCode": "// @renderer p5\n\nlet px, py;\n\nfunction setup(viji, p5) {\n p5.colorMode(p5.HSB, 360, 100, 100, 100);\n}\n\nfunction render(viji, p5) {\n const w = viji.width, h = viji.height;\n const size = Math.min(w, h);\n const kb = viji.keyboard;\n\n if (px === undefined) { px = w / 2; py = h / 2; }\n\n const speed = size * 0.4 * viji.deltaTime * (kb.shift ? 2.5 : 1);\n if (kb.isPressed('w') || kb.isPressed('arrowup')) py -= speed;\n if (kb.isPressed('s') || kb.isPressed('arrowdown')) py += speed;\n if (kb.isPressed('a') || kb.isPressed('arrowleft')) px -= speed;\n if (kb.isPressed('d') || kb.isPressed('arrowright')) px += speed;\n px = Math.max(0, Math.min(w, px));\n py = Math.max(0, Math.min(h, py));\n\n p5.background(240, 10, 8, 15);\n\n const r = size * 0.03;\n p5.noStroke();\n p5.fill((viji.time * 40) % 360, 80, 65);\n p5.ellipse(px, py, r * 2);\n\n p5.fill(0, 0, 100, 50);\n p5.textSize(size * 0.022);\n p5.textFont('monospace');\n p5.textAlign(p5.LEFT);\n const y0 = h - size * 0.09;\n p5.text(`active: [${[...kb.activeKeys].join(', ')}]`, size * 0.03, y0);\n p5.text(`last: \"${kb.lastKeyPressed}\" mods: ${kb.shift ? 'Shift ' : ''}${kb.ctrl ? 'Ctrl ' : ''}${kb.alt ? 'Alt ' : ''}${!kb.shift && !kb.ctrl && !kb.alt ? 'none' : ''}`, size * 0.03, y0 + size * 0.03);\n\n p5.fill(0, 0, 100, 30);\n p5.textAlign(p5.CENTER);\n p5.text('WASD / Arrows to move \\u2022 Shift for speed', w / 2, size * 0.04);\n}\n",
1698
- "sceneFile": "keyboard-p5-demo.scene.js"
1724
+ "sceneFile": "keyboard-p5-demo.scene.js",
1725
+ "capabilities": {
1726
+ "interaction": true
1727
+ }
1699
1728
  },
1700
1729
  {
1701
1730
  "type": "text",
@@ -1716,7 +1745,10 @@ export const docsApi = {
1716
1745
  "type": "live-example",
1717
1746
  "title": "Touch — Multi-Point Tracker",
1718
1747
  "sceneCode": "// @renderer p5\n\nconst ripples = [];\n\nfunction setup(viji, p5) {\n p5.colorMode(p5.HSB, 360, 100, 100, 100);\n}\n\nfunction render(viji, p5) {\n const size = Math.min(viji.width, viji.height);\n const touch = viji.touches;\n const dt = viji.deltaTime;\n\n for (const pt of touch.started) {\n ripples.push({ x: pt.x, y: pt.y, r: 0, alpha: 100 });\n }\n\n p5.background(240, 10, 8, 20);\n\n p5.noFill();\n for (let i = ripples.length - 1; i >= 0; i--) {\n const rp = ripples[i];\n rp.r += size * 0.3 * dt;\n rp.alpha -= dt * 80;\n if (rp.alpha <= 0) { ripples.splice(i, 1); continue; }\n p5.stroke(200, 80, 70, rp.alpha);\n p5.strokeWeight(2);\n p5.ellipse(rp.x, rp.y, rp.r * 2);\n }\n\n p5.noStroke();\n for (let i = 0; i < touch.count; i++) {\n const pt = touch.points[i];\n const r = size * 0.02 + pt.pressure * size * 0.04;\n\n p5.fill(120 + i * 60, 80, 65, 80);\n p5.ellipse(pt.x, pt.y, r * 2);\n\n const speed = Math.sqrt(pt.velocity.x ** 2 + pt.velocity.y ** 2);\n if (speed > 10) {\n const len = Math.min(speed * 0.05, size * 0.08);\n const angle = Math.atan2(pt.velocity.y, pt.velocity.x);\n p5.stroke(120 + i * 60, 80, 75, 50);\n p5.strokeWeight(2);\n p5.line(pt.x, pt.y, pt.x + Math.cos(angle) * len, pt.y + Math.sin(angle) * len);\n p5.noStroke();\n }\n\n p5.fill(0, 0, 100, 60);\n p5.textSize(size * 0.02);\n p5.textAlign(p5.CENTER);\n p5.text('T' + pt.id, pt.x, pt.y - r - size * 0.01);\n }\n\n p5.noStroke();\n p5.fill(0, 0, 100, 40);\n p5.textSize(size * 0.022);\n p5.textFont('monospace');\n p5.textAlign(p5.LEFT);\n p5.text(`touches: ${touch.count} primary: ${touch.primary ? 'T' + touch.primary.id : '-'}`, size * 0.03, viji.height - size * 0.03);\n}\n",
1719
- "sceneFile": "touch-p5-demo.scene.js"
1748
+ "sceneFile": "touch-p5-demo.scene.js",
1749
+ "capabilities": {
1750
+ "interaction": true
1751
+ }
1720
1752
  },
1721
1753
  {
1722
1754
  "type": "text",
@@ -1741,7 +1773,7 @@ export const docsApi = {
1741
1773
  },
1742
1774
  {
1743
1775
  "type": "text",
1744
- "markdown": "### What's Happening\r\n\r\n**Comment directives — parsed before compilation:**\r\n\r\n- `// @renderer shader` tells Viji to use the shader renderer.\r\n- `// @viji-slider:speed ...` declares a parameter. Viji generates a `uniform float speed;` automatically.\r\n- `// @viji-color:tint ...` declares a color parameter. Viji generates a `uniform vec3 tint;`.\r\n- `// @viji-accumulator:phase rate:speed` creates a CPU-side accumulator that adds `speed × deltaTime` every frame. The result is a `uniform float phase;` that increases smoothly — no jumps when the slider changes.\r\n\r\n**`void main()` — runs for every pixel, every frame:**\r\n\r\n- `gl_FragCoord.xy / u_resolution` gives normalized UV coordinates (0–1).\r\n- `phase` is the accumulator — use it instead of `u_time * speed` for smooth, slider-driven animation.\r\n- `speed`, `scale`, `tint` are your parameter uniforms — updated live as the user adjusts controls.\r\n- `gl_FragColor` sets the output color for each pixel.\r\n\r\n> [!NOTE]\r\n> Parameter declarations use **single-line `//` comments only**. Block comments `/* */` are not parsed for `@viji-*` directives.\r\n\r\n## Scene Structure\r\n\r\nA shader scene is a GLSL fragment shader with comment directives:\r\n\r\n```glsl\r\n// @renderer shader\r\n// @viji-slider:brightness label:\"Brightness\" default:1.0 min:0.0 max:2.0\r\n\r\nvoid main() {\r\n vec2 uv = gl_FragCoord.xy / u_resolution;\r\n gl_FragColor = vec4(uv * brightness, 0.5, 1.0);\r\n}\r\n```\r\n\r\n- **No `precision` or `uniform` declarations needed.** Viji auto-injects `precision mediump float;` and all uniform declarations.\r\n- **No vertex shader.** Viji renders a fullscreen quad; your fragment shader defines the color of every pixel.\r\n- **Parameters become uniforms.** `// @viji-slider:name` becomes `uniform float name;` automatically.\r\n\r\n> [!NOTE]\r\n> The Viji shader renderer automatically injects `precision mediump float;` and all `uniform` declarations. Write only your helper functions and `void main() { ... }`. Do NOT redeclare `precision` or built-in uniforms — they will conflict. If you use `#version 300 es`, Viji will handle its placement automatically.\r\n\r\n## Parameter Types\r\n\r\nDeclare parameters with `// @viji-TYPE:uniformName key:value` syntax:\r\n\r\n| Type | Uniform | Example |\r\n|------|---------|---------|\r\n| `slider` | `float` | `// @viji-slider:speed label:\"Speed\" default:1.0 min:0.0 max:5.0` |\r\n| `number` | `float` | `// @viji-number:count label:\"Count\" default:10.0 min:1.0 max:100.0` |\r\n| `color` | `vec3` | `// @viji-color:tint label:\"Tint\" default:#ff6600` |\r\n| `toggle` | `bool` | `// @viji-toggle:invert label:\"Invert\" default:false` |\r\n| `select` | `int` | `// @viji-select:mode label:\"Mode\" default:0 options:[\"Wave\",\"Spiral\",\"Grid\"]` |\r\n| `image` | `sampler2D` | `// @viji-image:tex label:\"Texture\"` |\r\n| `button` | `bool` | `// @viji-button:trigger label:\"Trigger\"` |\r\n| `accumulator` | `float` | `// @viji-accumulator:phase rate:speed` |\r\n\r\nConfig keys: `label`, `default`, `min`, `max`, `step`, `description`, `group`, `category`.\r\n\r\n### Accumulators\r\n\r\nAccumulators solve the \"jumping animation\" problem. When you write `u_time * speed`, changing the `speed` slider causes a visible jump because the entire phase is recalculated instantly. Accumulators integrate the rate over time on the CPU side:\r\n\r\n```glsl\r\n// @viji-slider:speed label:\"Speed\" default:1.0 min:0.1 max:5.0\r\n// @viji-accumulator:phase rate:speed\r\n```\r\n\r\n- `phase` increases by `speed × deltaTime` each frame — changing `speed` only affects future growth, never jumps backward.\r\n- `rate` can reference any declared parameter name or be a numeric constant (e.g., `rate:1.5`).\r\n- Accumulators have no UI control — they are internal uniform values.\r\n- Optional `default` sets the starting value (defaults to 0).\r\n\r\n> [!WARNING]\r\n> Do not use the `u_` prefix for your parameter uniform names — it is reserved for built-in Viji uniforms. Use descriptive names like `speed`, `colorMix`, `intensity` instead.\r\n\r\n## Built-in Uniforms\r\n\r\nThese are always available — no declaration needed:\r\n\r\n| Uniform | Type | Description |\r\n|---------|------|-------------|\r\n| `u_resolution` | `vec2` | Canvas width and height in pixels |\r\n| `u_time` | `float` | Elapsed seconds since scene start |\r\n| `u_deltaTime` | `float` | Seconds since last frame |\r\n| `u_frame` | `int` | Current frame number |\r\n| `u_pointer` | `vec2` | Unified input position — mouse or touch (pixels) |\r\n| `u_pointerDown` | `bool` | Unified input active (left-click or touch) |\r\n| `u_mouse` | `vec2` | Mouse position in pixels |\r\n| `u_mousePressed` | `bool` | Any mouse button is pressed |\r\n| `u_audioVolume` | `float` | Overall audio volume (0–1) |\r\n| `u_audioLow` | `float` | Low frequency energy (0–1) |\r\n| `u_audioMid` | `float` | Mid frequency energy (0–1) |\r\n| `u_audioHigh` | `float` | High frequency energy (0–1) |\r\n| `u_audioKick` | `float` | Kick beat detection (0–1) |\r\n| `u_video` | `sampler2D` | Current video frame |\r\n\r\nSee [API Reference](/shader/api-reference) for the complete list of 100+ built-in uniforms.\r\n\r\n## Essential Patterns\r\n\r\n**Normalized coordinates:**\r\n\r\n```glsl\r\nvec2 uv = gl_FragCoord.xy / u_resolution; // 0..1\r\nvec2 centered = uv - 0.5; // -0.5..0.5\r\ncentered.x *= u_resolution.x / u_resolution.y; // aspect-corrected\r\n```\r\n\r\n**Distance fields:**\r\n\r\n```glsl\r\nfloat d = length(centered); // distance from center\r\nfloat circle = smoothstep(0.3, 0.29, d); // anti-aliased circle\r\n```\r\n\r\n> [!NOTE]\r\n> Always use `u_resolution` for positioning and sizing and `u_time` / `u_deltaTime` for animation. This keeps your shader resolution-agnostic and frame-rate-independent.\r\n\r\n## GLSL Version\r\n\r\nBy default, shaders use **GLSL ES 1.00** (WebGL 1). If you need WebGL 2 features, add `#version 300 es` as the first line:\r\n\r\n```glsl\r\n#version 300 es\r\n// @renderer shader\r\n\r\n// ES 3.00 requires explicit output declaration\r\nout vec4 fragColor;\r\n\r\nvoid main() {\r\n vec2 uv = gl_FragCoord.xy / u_resolution;\r\n fragColor = vec4(uv, sin(u_time), 1.0);\r\n}\r\n```\r\n\r\nES 3.00 differences: `gl_FragColor` → `out vec4`, `texture2D()` → `texture()`. Use ES 1.00 for maximum compatibility.\r\n\r\n## Backbuffer (Previous Frame)\r\n\r\nViji gives you access to the previous frame as a texture — just reference `backbuffer` in your code and it's automatically enabled:\r\n\r\n```glsl\r\nvoid main() {\r\n vec2 uv = gl_FragCoord.xy / u_resolution;\r\n vec4 prev = texture2D(backbuffer, uv); // previous frame\r\n vec3 current = vec3(/* ... your effect ... */);\r\n gl_FragColor = vec4(mix(prev.rgb, current, 0.1), 1.0); // 90% trail\r\n}\r\n```\r\n\r\nThis enables feedback effects, trails, motion blur, and accumulation buffers. No setup needed — Viji detects the `backbuffer` reference and creates the ping-pong framebuffers automatically.\r\n\r\nSee [Backbuffer](/shader/backbuffer) for detailed patterns and techniques.\r\n\r\n## Shadertoy Compatibility\r\n\r\nIf you have existing Shadertoy shaders, see [Shadertoy Compatibility](/shader/shadertoy) for a mapping of Shadertoy uniforms to Viji equivalents.\r\n\r\n## Next Steps\r\n\r\n- [Shader Basics](/shader/basics) — uniforms, coordinate systems, techniques\r\n- [Parameters](/shader/parameters) — all parameter types for shaders\r\n- [Audio Uniforms](/shader/audio) — react to music in GLSL\r\n- [Backbuffer](/shader/backbuffer) — feedback effects using the previous frame\r\n- [API Reference](/shader/api-reference) — complete list of built-in uniforms\r\n- [Best Practices](/getting-started/best-practices) — essential patterns for all renderers"
1776
+ "markdown": "### What's Happening\r\n\r\n**Comment directives — parsed before compilation:**\r\n\r\n- `// @renderer shader` tells Viji to use the shader renderer.\r\n- `// @viji-slider:speed ...` declares a parameter. Viji generates a `uniform float speed;` automatically.\r\n- `// @viji-color:tint ...` declares a color parameter. Viji generates a `uniform vec3 tint;`.\r\n- `// @viji-accumulator:phase rate:speed` creates a CPU-side accumulator that adds `speed × deltaTime` every frame. The result is a `uniform float phase;` that increases smoothly — no jumps when the slider changes.\r\n\r\n**`void main()` — runs for every pixel, every frame:**\r\n\r\n- `gl_FragCoord.xy / u_resolution` gives normalized UV coordinates (0–1).\r\n- `phase` is the accumulator — use it instead of `u_time * speed` for smooth, slider-driven animation.\r\n- `speed`, `scale`, `tint` are your parameter uniforms — updated live as the user adjusts controls.\r\n- `gl_FragColor` sets the output color for each pixel.\r\n\r\n> [!NOTE]\r\n> Parameter declarations use **single-line `//` comments only**. Block comments `/* */` are not parsed for `@viji-*` directives.\r\n\r\n## Scene Structure\r\n\r\nA shader scene is a GLSL fragment shader with comment directives:\r\n\r\n```glsl\r\n// @renderer shader\r\n// @viji-slider:brightness label:\"Brightness\" default:1.0 min:0.0 max:2.0\r\n\r\nvoid main() {\r\n vec2 uv = gl_FragCoord.xy / u_resolution;\r\n gl_FragColor = vec4(uv * brightness, 0.5, 1.0);\r\n}\r\n```\r\n\r\n- **No `precision` or `uniform` declarations needed.** Viji auto-injects `precision mediump float;` and all uniform declarations.\r\n- **No vertex shader.** Viji renders a fullscreen quad; your fragment shader defines the color of every pixel.\r\n- **Parameters become uniforms.** `// @viji-slider:name` becomes `uniform float name;` automatically.\r\n\r\n> [!NOTE]\r\n> The Viji shader renderer automatically injects `precision mediump float;` and all `uniform` declarations — both built-in uniforms (`u_resolution`, `u_time`, etc.) and parameter uniforms from `@viji-*` directives. Write only your helper functions and `void main() { ... }`. Do NOT redeclare `precision` or any uniforms — they will conflict. If you use `#version 300 es`, Viji will handle its placement automatically.\r\n\r\n## Parameter Types\r\n\r\nDeclare parameters with `// @viji-TYPE:uniformName key:value` syntax:\r\n\r\n| Type | Uniform | Example |\r\n|------|---------|---------|\r\n| `slider` | `float` | `// @viji-slider:speed label:\"Speed\" default:1.0 min:0.0 max:5.0` |\r\n| `number` | `float` | `// @viji-number:count label:\"Count\" default:10.0 min:1.0 max:100.0` |\r\n| `color` | `vec3` | `// @viji-color:tint label:\"Tint\" default:#ff6600` |\r\n| `toggle` | `bool` | `// @viji-toggle:invert label:\"Invert\" default:false` |\r\n| `select` | `int` | `// @viji-select:mode label:\"Mode\" default:0 options:[\"Wave\",\"Spiral\",\"Grid\"]` |\r\n| `image` | `sampler2D` | `// @viji-image:tex label:\"Texture\"` |\r\n| `button` | `bool` | `// @viji-button:trigger label:\"Trigger\"` |\r\n| `accumulator` | `float` | `// @viji-accumulator:phase rate:speed` |\r\n\r\nConfig keys: `label`, `default`, `min`, `max`, `step`, `description`, `group`, `category`.\r\n\r\n### Accumulators\r\n\r\nAccumulators solve the \"jumping animation\" problem. When you write `u_time * speed`, changing the `speed` slider causes a visible jump because the entire phase is recalculated instantly. Accumulators integrate the rate over time on the CPU side:\r\n\r\n```glsl\r\n// @viji-slider:speed label:\"Speed\" default:1.0 min:0.1 max:5.0\r\n// @viji-accumulator:phase rate:speed\r\n```\r\n\r\n- `phase` increases by `speed × deltaTime` each frame — changing `speed` only affects future growth, never jumps backward.\r\n- `rate` can reference any declared parameter name or be a numeric constant (e.g., `rate:1.5`).\r\n- Accumulators have no UI control — they are internal uniform values.\r\n- Optional `default` sets the starting value (defaults to 0).\r\n\r\n> [!WARNING]\r\n> Do not use the `u_` prefix for your parameter uniform names — it is reserved for built-in Viji uniforms. Use descriptive names like `speed`, `colorMix`, `intensity` instead.\r\n\r\n## Built-in Uniforms\r\n\r\nThese are always available — no declaration needed:\r\n\r\n| Uniform | Type | Description |\r\n|---------|------|-------------|\r\n| `u_resolution` | `vec2` | Canvas width and height in pixels |\r\n| `u_time` | `float` | Elapsed seconds since scene start |\r\n| `u_deltaTime` | `float` | Seconds since last frame |\r\n| `u_frame` | `int` | Current frame number |\r\n| `u_pointer` | `vec2` | Unified input position — mouse or touch (pixels) |\r\n| `u_pointerDown` | `bool` | Unified input active (left-click or touch) |\r\n| `u_mouse` | `vec2` | Mouse position in pixels |\r\n| `u_mousePressed` | `bool` | Any mouse button is pressed |\r\n| `u_audioVolume` | `float` | Overall audio volume (0–1) |\r\n| `u_audioLow` | `float` | Low frequency energy (0–1) |\r\n| `u_audioMid` | `float` | Mid frequency energy (0–1) |\r\n| `u_audioHigh` | `float` | High frequency energy (0–1) |\r\n| `u_audioKick` | `float` | Kick beat detection (0–1) |\r\n| `u_video` | `sampler2D` | Current video frame |\r\n\r\nSee [API Reference](/shader/api-reference) for the complete list of 100+ built-in uniforms.\r\n\r\n## Essential Patterns\r\n\r\n**Normalized coordinates:**\r\n\r\n```glsl\r\nvec2 uv = gl_FragCoord.xy / u_resolution; // 0..1\r\nvec2 centered = uv - 0.5; // -0.5..0.5\r\ncentered.x *= u_resolution.x / u_resolution.y; // aspect-corrected\r\n```\r\n\r\n**Distance fields:**\r\n\r\n```glsl\r\nfloat d = length(centered); // distance from center\r\nfloat circle = smoothstep(0.3, 0.29, d); // anti-aliased circle\r\n```\r\n\r\n> [!NOTE]\r\n> Always use `u_resolution` for positioning and sizing and `u_time` / `u_deltaTime` for animation. This keeps your shader resolution-agnostic and frame-rate-independent.\r\n\r\n## GLSL Version\r\n\r\nBy default, shaders use **GLSL ES 1.00** (WebGL 1). If you need WebGL 2 features, add `#version 300 es` as the first line:\r\n\r\n```glsl\r\n#version 300 es\r\n// @renderer shader\r\n\r\n// ES 3.00 requires explicit output declaration\r\nout vec4 fragColor;\r\n\r\nvoid main() {\r\n vec2 uv = gl_FragCoord.xy / u_resolution;\r\n fragColor = vec4(uv, sin(u_time), 1.0);\r\n}\r\n```\r\n\r\nES 3.00 differences: `gl_FragColor` → `out vec4`, `texture2D()` → `texture()`. Use ES 1.00 for maximum compatibility.\r\n\r\n## Backbuffer (Previous Frame)\r\n\r\nViji gives you access to the previous frame as a texture — just reference `backbuffer` in your code and it's automatically enabled:\r\n\r\n```glsl\r\nvoid main() {\r\n vec2 uv = gl_FragCoord.xy / u_resolution;\r\n vec4 prev = texture2D(backbuffer, uv); // previous frame\r\n vec3 current = vec3(/* ... your effect ... */);\r\n gl_FragColor = vec4(mix(prev.rgb, current, 0.1), 1.0); // 90% trail\r\n}\r\n```\r\n\r\nThis enables feedback effects, trails, motion blur, and accumulation buffers. No setup needed — Viji detects the `backbuffer` reference and creates the ping-pong framebuffers automatically.\r\n\r\nSee [Backbuffer](/shader/backbuffer) for detailed patterns and techniques.\r\n\r\n## Shadertoy Compatibility\r\n\r\nIf you have existing Shadertoy shaders, see [Shadertoy Compatibility](/shader/shadertoy) for a mapping of Shadertoy uniforms to Viji equivalents.\r\n\r\n## Next Steps\r\n\r\n- [Shader Basics](/shader/basics) — uniforms, coordinate systems, techniques\r\n- [Parameters](/shader/parameters) — all parameter types for shaders\r\n- [Audio Uniforms](/shader/audio) — react to music in GLSL\r\n- [Backbuffer](/shader/backbuffer) — feedback effects using the previous frame\r\n- [API Reference](/shader/api-reference) — complete list of built-in uniforms\r\n- [Best Practices](/getting-started/best-practices) — essential patterns for all renderers"
1745
1777
  }
1746
1778
  ]
1747
1779
  },
@@ -1762,7 +1794,7 @@ export const docsApi = {
1762
1794
  },
1763
1795
  {
1764
1796
  "type": "text",
1765
- "markdown": "## Auto-Injection\n\n> [!NOTE]\n> The Viji shader renderer automatically injects `precision mediump float;` and all `uniform` declarations. Write only your helper functions and `void main() { ... }`. Do NOT redeclare `precision` or built-in uniforms — they will conflict. If you use `#version 300 es`, Viji will handle its placement automatically.\n\nWhen your shader is compiled, Viji prepends the following before your code:\n\n1. `#extension GL_OES_standard_derivatives : enable` — only if your code uses `fwidth` (GLSL ES 1.00 only)\n2. `precision mediump float;`\n3. All built-in uniform declarations (`u_resolution`, `u_time`, `u_deltaTime`, etc.)\n4. All `@viji-*` parameter uniform declarations\n\n**You must not redeclare any of these.** Writing `precision mediump float;` or `uniform vec2 u_resolution;` in your code will cause a compilation error.\n\n### What You Write vs What Viji Adds\n\n```glsl\n// What you write:\n// @renderer shader\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n gl_FragColor = vec4(uv, sin(u_time), 1.0);\n}\n\n// What Viji compiles (conceptual):\nprecision mediump float;\nuniform vec2 u_resolution;\nuniform float u_time;\nuniform float u_deltaTime;\nuniform int u_frame;\nuniform float u_fps;\n// ... (100+ more built-in uniforms)\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n gl_FragColor = vec4(uv, sin(u_time), 1.0);\n}\n```\n\n## Key Built-in Uniforms\n\nThese are always available — a brief overview (each has a dedicated page):\n\n| Uniform | Type | Description | Details |\n|---------|------|-------------|---------|\n| [`u_resolution`](/shader/resolution) | `vec2` | Canvas width and height in pixels | [Resolution & Coordinates](/shader/resolution) |\n| [`u_time`](/shader/timing) | `float` | Elapsed seconds since scene start | [Timing & Animation](/shader/timing) |\n| [`u_deltaTime`](/shader/timing) | `float` | Seconds since last frame | [Timing & Animation](/shader/timing) |\n| [`u_frame`](/shader/timing) | `int` | Current frame number | [Timing & Animation](/shader/timing) |\n| [`u_fps`](/shader/timing) | `float` | Target FPS | [Timing & Animation](/shader/timing) |\n| `u_mouse` | `vec2` | Mouse position in pixels | [Mouse Uniforms](/shader/mouse) |\n\nSee [API Reference](/shader/api-reference) for the complete list of 100+ built-in uniforms.\n\n## GLSL Versions\n\n### GLSL ES 1.00 (Default)\n\nThe default. No `#version` declaration needed. Maximum browser compatibility. Uses `gl_FragColor` for output and `texture2D()` for texture sampling:\n\n```glsl\n// @renderer shader\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n gl_FragColor = vec4(uv, 0.5, 1.0);\n}\n```\n\n### GLSL ES 3.00 (WebGL 2)\n\nFor WebGL 2 features, add `#version 300 es` as the very first line (before the `@renderer` directive). Viji extracts it, places it at the top of the compiled output, and requests a WebGL 2 context:"
1797
+ "markdown": "## Auto-Injection\n\n> [!NOTE]\n> The Viji shader renderer automatically injects `precision mediump float;` and all `uniform` declarations — both built-in uniforms (`u_resolution`, `u_time`, etc.) and parameter uniforms from `@viji-*` directives. Write only your helper functions and `void main() { ... }`. Do NOT redeclare `precision` or any uniforms — they will conflict. If you use `#version 300 es`, Viji will handle its placement automatically.\n\nWhen your shader is compiled, Viji prepends the following before your code:\n\n1. `#extension GL_OES_standard_derivatives : enable` — only if your code uses `fwidth` (GLSL ES 1.00 only)\n2. `precision mediump float;`\n3. All built-in uniform declarations (`u_resolution`, `u_time`, `u_deltaTime`, etc.)\n4. All `@viji-*` parameter uniform declarations\n\n**You must not redeclare any of these.** Writing `precision mediump float;` or `uniform vec2 u_resolution;` in your code will cause a compilation error.\n\n### What You Write vs What Viji Adds\n\n```glsl\n// What you write:\n// @renderer shader\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n gl_FragColor = vec4(uv, sin(u_time), 1.0);\n}\n\n// What Viji compiles (conceptual):\nprecision mediump float;\nuniform vec2 u_resolution;\nuniform float u_time;\nuniform float u_deltaTime;\nuniform int u_frame;\nuniform float u_fps;\n// ... (100+ more built-in uniforms)\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n gl_FragColor = vec4(uv, sin(u_time), 1.0);\n}\n```\n\n## Key Built-in Uniforms\n\nThese are always available — a brief overview (each has a dedicated page):\n\n| Uniform | Type | Description | Details |\n|---------|------|-------------|---------|\n| [`u_resolution`](/shader/resolution) | `vec2` | Canvas width and height in pixels | [Resolution & Coordinates](/shader/resolution) |\n| [`u_time`](/shader/timing) | `float` | Elapsed seconds since scene start | [Timing & Animation](/shader/timing) |\n| [`u_deltaTime`](/shader/timing) | `float` | Seconds since last frame | [Timing & Animation](/shader/timing) |\n| [`u_frame`](/shader/timing) | `int` | Current frame number | [Timing & Animation](/shader/timing) |\n| [`u_fps`](/shader/timing) | `float` | Target FPS | [Timing & Animation](/shader/timing) |\n| `u_mouse` | `vec2` | Mouse position in pixels | [Mouse Uniforms](/shader/mouse) |\n\nSee [API Reference](/shader/api-reference) for the complete list of 100+ built-in uniforms.\n\n## GLSL Versions\n\n### GLSL ES 1.00 (Default)\n\nThe default. No `#version` declaration needed. Maximum browser compatibility. Uses `gl_FragColor` for output and `texture2D()` for texture sampling:\n\n```glsl\n// @renderer shader\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n gl_FragColor = vec4(uv, 0.5, 1.0);\n}\n```\n\n### GLSL ES 3.00 (WebGL 2)\n\nFor WebGL 2 features, add `#version 300 es` as the very first line (before the `@renderer` directive). Viji extracts it, places it at the top of the compiled output, and requests a WebGL 2 context:"
1766
1798
  },
1767
1799
  {
1768
1800
  "type": "live-example",
@@ -1856,17 +1888,17 @@ export const docsApi = {
1856
1888
  "content": [
1857
1889
  {
1858
1890
  "type": "text",
1859
- "markdown": "# @viji-slider\n\n```glsl\n// @viji-slider:speed label:\"Speed\" default:1 min:0 max:5 step:0.1\nuniform float speed;\n```\n\nDeclares a numeric slider parameter. The host renders it as a draggable slider control. The value is injected as a `uniform float`.\n\n## Directive Syntax\n\n```\n// @viji-slider:uniformName key:value key:value ...\n```\n\n| Key | Required | Default | Description |\n|-----|----------|---------|-------------|\n| `label` | Yes | — | Display name in the parameter UI |\n| `default` | Yes | — | Initial value |\n| `min` | No | `0` | Minimum allowed value |\n| `max` | No | `100` | Maximum allowed value |\n| `step` | No | `1` | Increment between values |\n| `description` | No | — | Tooltip text (use quotes) |\n| `group` | No | `general` | Group name (use quotes) |\n| `category` | No | `general` | Visibility category |\n\n## Uniform Type\n\nThe slider value is always injected as a `float`:\n\n```glsl\n// @viji-slider:count label:\"Count\" default:8 min:1 max:20 step:1\nuniform float count; // always float, use int(count) if needed\n```\n\n## Usage\n\n```glsl\n// @renderer shader\n// @viji-slider:zoom label:\"Zoom\" default:1 min:0.1 max:5 step:0.1\n// @viji-slider:rotation label:\"Rotation\" default:0 min:0 max:6.2832 step:0.01\n\nuniform float zoom;\nuniform float rotation;\nuniform vec2 u_resolution;\nuniform float u_time;\n\nvoid main() {\n vec2 uv = (gl_FragCoord.xy / u_resolution - 0.5) * zoom;\n float c = cos(rotation), s = sin(rotation);\n uv = mat2(c, -s, s, c) * uv;\n float d = length(uv);\n float ring = sin(d * 20.0 - u_time * 3.0) * 0.5 + 0.5;\n gl_FragColor = vec4(vec3(ring), 1.0);\n}\n```\n\n> [!WARNING]\n> The directive must use `//` comments only. Block comments (`/* */`) are not parsed.\n\n> [!WARNING]\n> The uniform name in the directive (`@viji-slider:speed`) must exactly match the `uniform float speed;` declaration. Viji does not auto-generate uniform declarations."
1891
+ "markdown": "# @viji-slider\n\n```glsl\n// @viji-slider:speed label:\"Speed\" default:1 min:0 max:5 step:0.1\nuniform float speed;\n```\n\nDeclares a numeric slider parameter. The host renders it as a draggable slider control. The value is injected as a `uniform float`.\n\n## Directive Syntax\n\n```\n// @viji-slider:uniformName key:value key:value ...\n```\n\n| Key | Required | Default | Description |\n|-----|----------|---------|-------------|\n| `label` | Yes | — | Display name in the parameter UI |\n| `default` | Yes | — | Initial value |\n| `min` | No | `0` | Minimum allowed value |\n| `max` | No | `100` | Maximum allowed value |\n| `step` | No | `1` | Increment between values |\n| `description` | No | — | Tooltip text (use quotes) |\n| `group` | No | `general` | Group name (use quotes) |\n| `category` | No | `general` | Visibility category |\n\n## Uniform Type\n\nThe slider value is always injected as a `float`:\n\n```glsl\n// @viji-slider:count label:\"Count\" default:8 min:1 max:20 step:1\nuniform float count; // always float, use int(count) if needed\n```\n\n## Usage\n\n```glsl\n// @renderer shader\n// @viji-slider:zoom label:\"Zoom\" default:1 min:0.1 max:5 step:0.1\n// @viji-slider:rotation label:\"Rotation\" default:0 min:0 max:6.2832 step:0.01\n\nvoid main() {\n vec2 uv = (gl_FragCoord.xy / u_resolution - 0.5) * zoom;\n float c = cos(rotation), s = sin(rotation);\n uv = mat2(c, -s, s, c) * uv;\n float d = length(uv);\n float ring = sin(d * 20.0 - u_time * 3.0) * 0.5 + 0.5;\n gl_FragColor = vec4(vec3(ring), 1.0);\n}\n```\n\n> [!WARNING]\n> The directive must use `//` comments only. Block comments (`/* */`) are not parsed.\n\n> [!NOTE]\n> Viji auto-injects all `uniform` declarations — both built-in uniforms and parameter uniforms from directives. Do **not** redeclare them in your shader code; duplicate declarations cause compilation errors."
1860
1892
  },
1861
1893
  {
1862
1894
  "type": "live-example",
1863
1895
  "title": "Slider Controls",
1864
- "sceneCode": "// @renderer shader\n// @viji-slider:zoom label:\"Zoom\" default:1 min:0.1 max:5 step:0.1\n// @viji-slider:speed label:\"Speed\" default:3 min:0 max:10 step:0.5\n// @viji-color:ringColor label:\"Color\" default:#44ddff\n\nuniform float zoom;\nuniform float speed;\nuniform vec3 ringColor;\nuniform vec2 u_resolution;\nuniform float u_time;\n\nvoid main() {\n vec2 uv = (gl_FragCoord.xy / u_resolution - 0.5) * zoom;\n float aspect = u_resolution.x / u_resolution.y;\n uv.x *= aspect;\n\n float d = length(uv);\n float ring = sin(d * 20.0 - u_time * speed) * 0.5 + 0.5;\n vec3 col = ringColor * ring;\n\n gl_FragColor = vec4(col, 1.0);\n}\n",
1896
+ "sceneCode": "// @renderer shader\n// @viji-slider:zoom label:\"Zoom\" default:1 min:0.1 max:5 step:0.1\n// @viji-slider:speed label:\"Speed\" default:3 min:0 max:10 step:0.5\n// @viji-color:ringColor label:\"Color\" default:#44ddff\n\nvoid main() {\n vec2 uv = (gl_FragCoord.xy / u_resolution - 0.5) * zoom;\n float aspect = u_resolution.x / u_resolution.y;\n uv.x *= aspect;\n\n float d = length(uv);\n float ring = sin(d * 20.0 - u_time * speed) * 0.5 + 0.5;\n vec3 col = ringColor * ring;\n\n gl_FragColor = vec4(col, 1.0);\n}\n",
1865
1897
  "sceneFile": "slider-shader.scene.glsl"
1866
1898
  },
1867
1899
  {
1868
1900
  "type": "text",
1869
- "markdown": "## Slider vs Number in Shaders\n\nBoth `@viji-slider` and `@viji-number` produce a `uniform float` and accept the same config keys (`min`, `max`, `step`, `default`). The only difference is the host UI:\n\n| | @viji-slider | @viji-number |\n|--|--------------|--------------|\n| UI | Draggable track | Text input field |\n| Best for | Continuous ranges, visual tuning | Precise values, integer counts |\n\n## Smooth Animation with Accumulators\n\nA common pattern is using a slider to control animation speed:\n\n```glsl\nfloat phase = u_time * speed; // jumps when speed changes mid-animation\n```\n\nThe problem: if the user changes `speed` from `1.0` to `3.0` at `u_time = 10`, the phase jumps from `10` to `30` instantly. The [`@viji-accumulator`](../accumulator/) solves this by integrating the rate over time — changing the rate only affects future growth, never jumps:\n\n```glsl\n// @viji-slider:speed label:\"Speed\" default:1 min:0 max:5 step:0.1\n// @viji-accumulator:phase rate:speed\n\nuniform float speed;\nuniform float phase; // grows by speed × deltaTime each frame, no jumps\n```\n\nSee [Accumulator](../accumulator/) for full details and examples.\n\n## Rules\n\n- Numeric values have no quotes: `default:1`, `min:0`, `max:5`\n- String values use quotes: `label:\"Speed\"`\n- The `label` and `default` keys are required\n\n## Related\n\n- [Shader Basics](/shader/basics) — shader file structure and directives\n- [Number](../number/) — numeric input `uniform float`\n- [Color](../color/) — color picker `uniform vec3`\n- [Toggle](../toggle/) — boolean `uniform bool`\n- [Select](../select/) — dropdown `uniform int`\n- [Accumulator](../accumulator/) — frame-persistent state driven by slider values\n- [Native Slider](/native/parameters/slider) — equivalent for the Native renderer\n- [P5 Slider](/p5/parameters/slider) — equivalent for the P5 renderer"
1901
+ "markdown": "## Slider vs Number in Shaders\n\nBoth `@viji-slider` and `@viji-number` produce a `uniform float` and accept the same config keys (`min`, `max`, `step`, `default`). The only difference is the host UI:\n\n| | @viji-slider | @viji-number |\n|--|--------------|--------------|\n| UI | Draggable track | Text input field |\n| Best for | Continuous ranges, visual tuning | Precise values, integer counts |\n\n## Smooth Animation with Accumulators\n\nA common pattern is using a slider to control animation speed:\n\n```glsl\nfloat phase = u_time * speed; // jumps when speed changes mid-animation\n```\n\nThe problem: if the user changes `speed` from `1.0` to `3.0` at `u_time = 10`, the phase jumps from `10` to `30` instantly. The [`@viji-accumulator`](../accumulator/) solves this by integrating the rate over time — changing the rate only affects future growth, never jumps:\n\n```glsl\n// @viji-slider:speed label:\"Speed\" default:1 min:0 max:5 step:0.1\n// @viji-accumulator:phase rate:speed\n// Generates: uniform float speed; and uniform float phase;\n// phase grows by speed × deltaTime each frame, no jumps\n```\n\nSee [Accumulator](../accumulator/) for full details and examples.\n\n## Rules\n\n- Numeric values have no quotes: `default:1`, `min:0`, `max:5`\n- String values use quotes: `label:\"Speed\"`\n- The `label` and `default` keys are required\n\n## Related\n\n- [Shader Basics](/shader/basics) — shader file structure and directives\n- [Number](../number/) — numeric input `uniform float`\n- [Color](../color/) — color picker `uniform vec3`\n- [Toggle](../toggle/) — boolean `uniform bool`\n- [Select](../select/) — dropdown `uniform int`\n- [Accumulator](../accumulator/) — frame-persistent state driven by slider values\n- [Native Slider](/native/parameters/slider) — equivalent for the Native renderer\n- [P5 Slider](/p5/parameters/slider) — equivalent for the P5 renderer"
1870
1902
  }
1871
1903
  ]
1872
1904
  },
@@ -1877,12 +1909,12 @@ export const docsApi = {
1877
1909
  "content": [
1878
1910
  {
1879
1911
  "type": "text",
1880
- "markdown": "# @viji-color\n\n```glsl\n// @viji-color:myColor label:\"Color\" default:#ff6600\nuniform vec3 myColor;\n```\n\nDeclares a color picker parameter. The host renders a color swatch that opens a full picker when clicked. The hex value is converted to a `vec3` uniform with RGB components normalized to `0.0–1.0`.\n\n## Directive Syntax\n\n```\n// @viji-color:uniformName key:value key:value ...\n```\n\n| Key | Required | Default | Description |\n|-----|----------|---------|-------------|\n| `label` | Yes | — | Display name in the parameter UI |\n| `default` | Yes | — | Initial hex color (e.g., `#ff6600`) — **no quotes** |\n| `description` | No | — | Tooltip text (use quotes: `description:\"Help text\"`) |\n| `group` | No | `general` | Group name (use quotes: `group:\"colors\"`) |\n| `category` | No | `general` | Visibility category |\n\n## Uniform Type\n\nThe color is injected as a `vec3`:\n\n| Component | Value | Range |\n|-----------|-------|-------|\n| `.r` | Red channel | 0.0 – 1.0 |\n| `.g` | Green channel | 0.0 – 1.0 |\n| `.b` | Blue channel | 0.0 – 1.0 |\n\nFor hex `#ff8040`: `.r = 1.0`, `.g ≈ 0.502`, `.b ≈ 0.251`.\n\n## Usage\n\n```glsl\n// @renderer shader\n// @viji-color:bgColor label:\"Background\" default:#0f0f1a\n// @viji-color:accent label:\"Accent\" default:#ff4488\n\nuniform vec3 bgColor;\nuniform vec3 accent;\nuniform vec2 u_resolution;\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n float d = distance(uv, vec2(0.5));\n vec3 col = mix(accent, bgColor, smoothstep(0.1, 0.4, d));\n gl_FragColor = vec4(col, 1.0);\n}\n```\n\n> [!WARNING]\n> The directive must use `//` comments only. Block comments (`/* */`) are not parsed.\n\n> [!WARNING]\n> The uniform name in the directive (`@viji-color:myColor`) must exactly match the `uniform vec3 myColor;` declaration. Viji does not auto-generate uniform declarations."
1912
+ "markdown": "# @viji-color\n\n```glsl\n// @viji-color:myColor label:\"Color\" default:#ff6600\nuniform vec3 myColor;\n```\n\nDeclares a color picker parameter. The host renders a color swatch that opens a full picker when clicked. The hex value is converted to a `vec3` uniform with RGB components normalized to `0.0–1.0`.\n\n## Directive Syntax\n\n```\n// @viji-color:uniformName key:value key:value ...\n```\n\n| Key | Required | Default | Description |\n|-----|----------|---------|-------------|\n| `label` | Yes | — | Display name in the parameter UI |\n| `default` | Yes | — | Initial hex color (e.g., `#ff6600`) — **no quotes** |\n| `description` | No | — | Tooltip text (use quotes: `description:\"Help text\"`) |\n| `group` | No | `general` | Group name (use quotes: `group:\"colors\"`) |\n| `category` | No | `general` | Visibility category |\n\n## Uniform Type\n\nThe color is injected as a `vec3`:\n\n| Component | Value | Range |\n|-----------|-------|-------|\n| `.r` | Red channel | 0.0 – 1.0 |\n| `.g` | Green channel | 0.0 – 1.0 |\n| `.b` | Blue channel | 0.0 – 1.0 |\n\nFor hex `#ff8040`: `.r = 1.0`, `.g ≈ 0.502`, `.b ≈ 0.251`.\n\n## Usage\n\n```glsl\n// @renderer shader\n// @viji-color:bgColor label:\"Background\" default:#0f0f1a\n// @viji-color:accent label:\"Accent\" default:#ff4488\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n float d = distance(uv, vec2(0.5));\n vec3 col = mix(accent, bgColor, smoothstep(0.1, 0.4, d));\n gl_FragColor = vec4(col, 1.0);\n}\n```\n\n> [!WARNING]\n> The directive must use `//` comments only. Block comments (`/* */`) are not parsed.\n\n> [!NOTE]\n> Viji auto-injects all `uniform` declarations — both built-in uniforms and parameter uniforms from directives. Do **not** redeclare them in your shader code; duplicate declarations cause compilation errors."
1881
1913
  },
1882
1914
  {
1883
1915
  "type": "live-example",
1884
1916
  "title": "Color Blending",
1885
- "sceneCode": "// @renderer shader\n// @viji-color:bgColor label:\"Background\" default:#0f0f1a\n// @viji-color:color1 label:\"Color 1\" default:#ff4488 group:\"colors\"\n// @viji-color:color2 label:\"Color 2\" default:#4488ff group:\"colors\"\n\nuniform vec3 bgColor;\nuniform vec3 color1;\nuniform vec3 color2;\nuniform vec2 u_resolution;\nuniform float u_time;\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n float t = sin(uv.x * 6.2832 + u_time) * 0.5 + 0.5;\n vec3 gradient = mix(color1, color2, t);\n float d = distance(uv, vec2(0.5));\n vec3 col = mix(gradient, bgColor, smoothstep(0.2, 0.5, d));\n gl_FragColor = vec4(col, 1.0);\n}\n",
1917
+ "sceneCode": "// @renderer shader\n// @viji-color:bgColor label:\"Background\" default:#0f0f1a\n// @viji-color:color1 label:\"Color 1\" default:#ff4488 group:\"colors\"\n// @viji-color:color2 label:\"Color 2\" default:#4488ff group:\"colors\"\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n float t = sin(uv.x * 6.2832 + u_time) * 0.5 + 0.5;\n vec3 gradient = mix(color1, color2, t);\n float d = distance(uv, vec2(0.5));\n vec3 col = mix(gradient, bgColor, smoothstep(0.2, 0.5, d));\n gl_FragColor = vec4(col, 1.0);\n}\n",
1886
1918
  "sceneFile": "color-shader.scene.glsl"
1887
1919
  },
1888
1920
  {
@@ -1898,12 +1930,12 @@ export const docsApi = {
1898
1930
  "content": [
1899
1931
  {
1900
1932
  "type": "text",
1901
- "markdown": "# @viji-toggle\n\n```glsl\n// @viji-toggle:showGrid label:\"Show Grid\" default:true\nuniform bool showGrid;\n```\n\nDeclares a boolean toggle parameter. The host renders it as an on/off switch. The value is injected as a `uniform bool`.\n\n## Directive Syntax\n\n```\n// @viji-toggle:uniformName key:value key:value ...\n```\n\n| Key | Required | Default | Description |\n|-----|----------|---------|-------------|\n| `label` | Yes | — | Display name in the parameter UI |\n| `default` | Yes | — | Initial state: `true` or `false` |\n| `description` | No | — | Tooltip text (use quotes: `description:\"Help text\"`) |\n| `group` | No | `general` | Group name (use quotes: `group:\"effects\"`) |\n| `category` | No | `general` | Visibility category |\n\n## Uniform Type\n\nThe toggle is injected as a `bool`:\n\n| Value | GLSL |\n|-------|------|\n| On | `true` |\n| Off | `false` |\n\n## Usage\n\n```glsl\n// @renderer shader\n// @viji-toggle:invert label:\"Invert Colors\" default:false\n\nuniform bool invert;\nuniform vec2 u_resolution;\nuniform float u_time;\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n vec3 col = vec3(uv.x, uv.y, sin(u_time) * 0.5 + 0.5);\n if (invert) {\n col = 1.0 - col;\n }\n gl_FragColor = vec4(col, 1.0);\n}\n```\n\n> [!WARNING]\n> The directive must use `//` comments only. Block comments (`/* */`) are not parsed.\n\n> [!WARNING]\n> The uniform name in the directive (`@viji-toggle:myToggle`) must exactly match the `uniform bool myToggle;` declaration. Viji does not auto-generate uniform declarations."
1933
+ "markdown": "# @viji-toggle\n\n```glsl\n// @viji-toggle:showGrid label:\"Show Grid\" default:true\nuniform bool showGrid;\n```\n\nDeclares a boolean toggle parameter. The host renders it as an on/off switch. The value is injected as a `uniform bool`.\n\n## Directive Syntax\n\n```\n// @viji-toggle:uniformName key:value key:value ...\n```\n\n| Key | Required | Default | Description |\n|-----|----------|---------|-------------|\n| `label` | Yes | — | Display name in the parameter UI |\n| `default` | Yes | — | Initial state: `true` or `false` |\n| `description` | No | — | Tooltip text (use quotes: `description:\"Help text\"`) |\n| `group` | No | `general` | Group name (use quotes: `group:\"effects\"`) |\n| `category` | No | `general` | Visibility category |\n\n## Uniform Type\n\nThe toggle is injected as a `bool`:\n\n| Value | GLSL |\n|-------|------|\n| On | `true` |\n| Off | `false` |\n\n## Usage\n\n```glsl\n// @renderer shader\n// @viji-toggle:invert label:\"Invert Colors\" default:false\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n vec3 col = vec3(uv.x, uv.y, sin(u_time) * 0.5 + 0.5);\n if (invert) {\n col = 1.0 - col;\n }\n gl_FragColor = vec4(col, 1.0);\n}\n```\n\n> [!WARNING]\n> The directive must use `//` comments only. Block comments (`/* */`) are not parsed.\n\n> [!NOTE]\n> Viji auto-injects all `uniform` declarations — both built-in uniforms and parameter uniforms from directives. Do **not** redeclare them in your shader code; duplicate declarations cause compilation errors."
1902
1934
  },
1903
1935
  {
1904
1936
  "type": "live-example",
1905
1937
  "title": "Toggle Inversion",
1906
- "sceneCode": "// @renderer shader\n// @viji-toggle:invert label:\"Invert\" default:false\n// @viji-toggle:animate label:\"Animate\" default:true\n// @viji-color:baseColor label:\"Base Color\" default:#4488ff\n\nuniform bool invert;\nuniform bool animate;\nuniform vec3 baseColor;\nuniform vec2 u_resolution;\nuniform float u_time;\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n float t = animate ? u_time : 0.0;\n float pattern = sin(uv.x * 10.0 + t * 2.0) * sin(uv.y * 10.0 + t * 1.5);\n vec3 col = baseColor * (pattern * 0.5 + 0.5);\n if (invert) {\n col = 1.0 - col;\n }\n gl_FragColor = vec4(col, 1.0);\n}\n",
1938
+ "sceneCode": "// @renderer shader\n// @viji-toggle:invert label:\"Invert\" default:false\n// @viji-toggle:animate label:\"Animate\" default:true\n// @viji-color:baseColor label:\"Base Color\" default:#4488ff\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n float t = animate ? u_time : 0.0;\n float pattern = sin(uv.x * 10.0 + t * 2.0) * sin(uv.y * 10.0 + t * 1.5);\n vec3 col = baseColor * (pattern * 0.5 + 0.5);\n if (invert) {\n col = 1.0 - col;\n }\n gl_FragColor = vec4(col, 1.0);\n}\n",
1907
1939
  "sceneFile": "toggle-shader.scene.glsl"
1908
1940
  },
1909
1941
  {
@@ -1919,12 +1951,12 @@ export const docsApi = {
1919
1951
  "content": [
1920
1952
  {
1921
1953
  "type": "text",
1922
- "markdown": "# @viji-select\n\n```glsl\n// @viji-select:mode label:\"Mode\" options:[\"Wave\",\"Circles\",\"Grid\"] default:0\nuniform int mode;\n```\n\nDeclares a dropdown selection parameter. The host renders it as a dropdown menu. The selected option's **index** is injected as a `uniform int`.\n\n## Directive Syntax\n\n```\n// @viji-select:uniformName key:value key:value ...\n```\n\n| Key | Required | Default | Description |\n|-----|----------|---------|-------------|\n| `label` | Yes | — | Display name in the parameter UI |\n| `options` | Yes | — | Array of string choices: `options:[\"A\",\"B\",\"C\"]` |\n| `default` | Yes | — | Initially selected index (0-based integer) |\n| `description` | No | — | Tooltip text (use quotes) |\n| `group` | No | `general` | Group name (use quotes) |\n| `category` | No | `general` | Visibility category |\n\n## Uniform Type\n\nThe select value is injected as an `int` — the **zero-based index** of the selected option:\n\n| Selected Option | GLSL Value |\n|-----------------|------------|\n| First option | `0` |\n| Second option | `1` |\n| Third option | `2` |\n\nThis differs from Native and P5 renderers, where `.value` is the option string itself.\n\n## Usage\n\n```glsl\n// @renderer shader\n// @viji-select:pattern label:\"Pattern\" options:[\"Stripes\",\"Dots\",\"Checker\"] default:0\n\nuniform int pattern;\nuniform vec2 u_resolution;\nuniform float u_time;\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n float v = 0.0;\n\n if (pattern == 0) {\n v = step(0.5, fract(uv.x * 10.0 + u_time));\n } else if (pattern == 1) {\n v = 1.0 - step(0.3, length(fract(uv * 5.0) - 0.5));\n } else if (pattern == 2) {\n v = mod(floor(uv.x * 8.0) + floor(uv.y * 8.0), 2.0);\n }\n\n gl_FragColor = vec4(vec3(v), 1.0);\n}\n```\n\n> [!WARNING]\n> The directive must use `//` comments only. Block comments (`/* */`) are not parsed.\n\n> [!WARNING]\n> The uniform name in the directive (`@viji-select:mode`) must exactly match the `uniform int mode;` declaration. Viji does not auto-generate uniform declarations."
1954
+ "markdown": "# @viji-select\n\n```glsl\n// @viji-select:mode label:\"Mode\" options:[\"Wave\",\"Circles\",\"Grid\"] default:0\nuniform int mode;\n```\n\nDeclares a dropdown selection parameter. The host renders it as a dropdown menu. The selected option's **index** is injected as a `uniform int`.\n\n## Directive Syntax\n\n```\n// @viji-select:uniformName key:value key:value ...\n```\n\n| Key | Required | Default | Description |\n|-----|----------|---------|-------------|\n| `label` | Yes | — | Display name in the parameter UI |\n| `options` | Yes | — | Array of string choices: `options:[\"A\",\"B\",\"C\"]` |\n| `default` | Yes | — | Initially selected index (0-based integer) |\n| `description` | No | — | Tooltip text (use quotes) |\n| `group` | No | `general` | Group name (use quotes) |\n| `category` | No | `general` | Visibility category |\n\n## Uniform Type\n\nThe select value is injected as an `int` — the **zero-based index** of the selected option:\n\n| Selected Option | GLSL Value |\n|-----------------|------------|\n| First option | `0` |\n| Second option | `1` |\n| Third option | `2` |\n\nThis differs from Native and P5 renderers, where `.value` is the option string itself.\n\n## Usage\n\n```glsl\n// @renderer shader\n// @viji-select:pattern label:\"Pattern\" options:[\"Stripes\",\"Dots\",\"Checker\"] default:0\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n float v = 0.0;\n\n if (pattern == 0) {\n v = step(0.5, fract(uv.x * 10.0 + u_time));\n } else if (pattern == 1) {\n v = 1.0 - step(0.3, length(fract(uv * 5.0) - 0.5));\n } else if (pattern == 2) {\n v = mod(floor(uv.x * 8.0) + floor(uv.y * 8.0), 2.0);\n }\n\n gl_FragColor = vec4(vec3(v), 1.0);\n}\n```\n\n> [!WARNING]\n> The directive must use `//` comments only. Block comments (`/* */`) are not parsed.\n\n> [!NOTE]\n> Viji auto-injects all `uniform` declarations — both built-in uniforms and parameter uniforms from directives. Do **not** redeclare them in your shader code; duplicate declarations cause compilation errors."
1923
1955
  },
1924
1956
  {
1925
1957
  "type": "live-example",
1926
1958
  "title": "Pattern Selector",
1927
- "sceneCode": "// @renderer shader\n// @viji-select:pattern label:\"Pattern\" options:[\"Stripes\",\"Dots\",\"Checker\"] default:0\n// @viji-color:color1 label:\"Color\" default:#ff4488\n\nuniform int pattern;\nuniform vec3 color1;\nuniform vec2 u_resolution;\nuniform float u_time;\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n float v = 0.0;\n\n if (pattern == 0) {\n v = step(0.5, fract(uv.x * 10.0 + u_time));\n } else if (pattern == 1) {\n v = 1.0 - step(0.3, length(fract(uv * 5.0) - 0.5));\n } else {\n v = mod(floor(uv.x * 8.0) + floor(uv.y * 8.0), 2.0);\n }\n\n vec3 col = mix(vec3(0.06), color1, v);\n gl_FragColor = vec4(col, 1.0);\n}\n",
1959
+ "sceneCode": "// @renderer shader\n// @viji-select:pattern label:\"Pattern\" options:[\"Stripes\",\"Dots\",\"Checker\"] default:0\n// @viji-color:color1 label:\"Color\" default:#ff4488\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n float v = 0.0;\n\n if (pattern == 0) {\n v = step(0.5, fract(uv.x * 10.0 + u_time));\n } else if (pattern == 1) {\n v = 1.0 - step(0.3, length(fract(uv * 5.0) - 0.5));\n } else {\n v = mod(floor(uv.x * 8.0) + floor(uv.y * 8.0), 2.0);\n }\n\n vec3 col = mix(vec3(0.06), color1, v);\n gl_FragColor = vec4(col, 1.0);\n}\n",
1928
1960
  "sceneFile": "select-shader.scene.glsl"
1929
1961
  },
1930
1962
  {
@@ -1940,17 +1972,17 @@ export const docsApi = {
1940
1972
  "content": [
1941
1973
  {
1942
1974
  "type": "text",
1943
- "markdown": "# @viji-number\n\n```glsl\n// @viji-number:density label:\"Density\" default:5 min:1 max:20 step:1\nuniform float density;\n```\n\nDeclares a numeric input parameter. The host renders it as a direct number input field. The value is injected as a `uniform float`.\n\n## Directive Syntax\n\n```\n// @viji-number:uniformName key:value key:value ...\n```\n\n| Key | Required | Default | Description |\n|-----|----------|---------|-------------|\n| `label` | Yes | — | Display name in the parameter UI |\n| `default` | Yes | — | Initial value |\n| `min` | No | `0` | Minimum allowed value |\n| `max` | No | `100` | Maximum allowed value |\n| `step` | No | `1` | Increment between values |\n| `description` | No | — | Tooltip text (use quotes) |\n| `group` | No | `general` | Group name (use quotes) |\n| `category` | No | `general` | Visibility category |\n\n## Uniform Type\n\nThe number is always injected as a `float`, even when configured with integer steps:\n\n```glsl\n// @viji-number:count label:\"Count\" default:8 min:1 max:20 step:1\nuniform float count; // always float, use int(count) if needed\n```\n\n## Usage\n\n```glsl\n// @renderer shader\n// @viji-number:rings label:\"Rings\" default:5 min:1 max:15 step:1\n// @viji-color:ringColor label:\"Color\" default:#44ddff\n\nuniform float rings;\nuniform vec3 ringColor;\nuniform vec2 u_resolution;\nuniform float u_time;\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n vec2 center = uv - 0.5;\n float aspect = u_resolution.x / u_resolution.y;\n center.x *= aspect;\n\n float d = length(center);\n float wave = sin(d * rings * 6.2832 - u_time * 3.0) * 0.5 + 0.5;\n vec3 col = ringColor * wave;\n\n gl_FragColor = vec4(col, 1.0);\n}\n```\n\n> [!WARNING]\n> The directive must use `//` comments only. Block comments (`/* */`) are not parsed.\n\n> [!WARNING]\n> The uniform name in the directive (`@viji-number:density`) must exactly match the `uniform float density;` declaration. Viji does not auto-generate uniform declarations."
1975
+ "markdown": "# @viji-number\n\n```glsl\n// @viji-number:density label:\"Density\" default:5 min:1 max:20 step:1\nuniform float density;\n```\n\nDeclares a numeric input parameter. The host renders it as a direct number input field. The value is injected as a `uniform float`.\n\n## Directive Syntax\n\n```\n// @viji-number:uniformName key:value key:value ...\n```\n\n| Key | Required | Default | Description |\n|-----|----------|---------|-------------|\n| `label` | Yes | — | Display name in the parameter UI |\n| `default` | Yes | — | Initial value |\n| `min` | No | `0` | Minimum allowed value |\n| `max` | No | `100` | Maximum allowed value |\n| `step` | No | `1` | Increment between values |\n| `description` | No | — | Tooltip text (use quotes) |\n| `group` | No | `general` | Group name (use quotes) |\n| `category` | No | `general` | Visibility category |\n\n## Uniform Type\n\nThe number is always injected as a `float`, even when configured with integer steps:\n\n```glsl\n// @viji-number:count label:\"Count\" default:8 min:1 max:20 step:1\nuniform float count; // always float, use int(count) if needed\n```\n\n## Usage\n\n```glsl\n// @renderer shader\n// @viji-number:rings label:\"Rings\" default:5 min:1 max:15 step:1\n// @viji-color:ringColor label:\"Color\" default:#44ddff\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n vec2 center = uv - 0.5;\n float aspect = u_resolution.x / u_resolution.y;\n center.x *= aspect;\n\n float d = length(center);\n float wave = sin(d * rings * 6.2832 - u_time * 3.0) * 0.5 + 0.5;\n vec3 col = ringColor * wave;\n\n gl_FragColor = vec4(col, 1.0);\n}\n```\n\n> [!WARNING]\n> The directive must use `//` comments only. Block comments (`/* */`) are not parsed.\n\n> [!NOTE]\n> Viji auto-injects all `uniform` declarations — both built-in uniforms and parameter uniforms from directives. Do **not** redeclare them in your shader code; duplicate declarations cause compilation errors."
1944
1976
  },
1945
1977
  {
1946
1978
  "type": "live-example",
1947
1979
  "title": "Ring Count",
1948
- "sceneCode": "// @renderer shader\n// @viji-number:rings label:\"Rings\" default:5 min:1 max:15 step:1\n// @viji-number:speed label:\"Speed\" default:3 min:0 max:10 step:0.5\n// @viji-color:ringColor label:\"Color\" default:#44ddff\n\nuniform float rings;\nuniform float speed;\nuniform vec3 ringColor;\nuniform vec2 u_resolution;\nuniform float u_time;\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n vec2 center = uv - 0.5;\n float aspect = u_resolution.x / u_resolution.y;\n center.x *= aspect;\n\n float d = length(center);\n float wave = sin(d * rings * 6.2832 - u_time * speed) * 0.5 + 0.5;\n vec3 col = ringColor * wave;\n\n gl_FragColor = vec4(col, 1.0);\n}\n",
1980
+ "sceneCode": "// @renderer shader\n// @viji-number:rings label:\"Rings\" default:5 min:1 max:15 step:1\n// @viji-number:speed label:\"Speed\" default:3 min:0 max:10 step:0.5\n// @viji-color:ringColor label:\"Color\" default:#44ddff\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n vec2 center = uv - 0.5;\n float aspect = u_resolution.x / u_resolution.y;\n center.x *= aspect;\n\n float d = length(center);\n float wave = sin(d * rings * 6.2832 - u_time * speed) * 0.5 + 0.5;\n vec3 col = ringColor * wave;\n\n gl_FragColor = vec4(col, 1.0);\n}\n",
1949
1981
  "sceneFile": "number-shader.scene.glsl"
1950
1982
  },
1951
1983
  {
1952
1984
  "type": "text",
1953
- "markdown": "## Number vs Slider in Shaders\n\nBoth `@viji-number` and `@viji-slider` produce a `uniform float` and accept the same config keys (`min`, `max`, `step`, `default`). The only difference is the host UI:\n\n| | @viji-slider | @viji-number |\n|--|--------------|--------------|\n| UI | Draggable track | Text input field |\n| Best for | Continuous ranges, visual tuning | Precise values, integer counts |\n\n## Smooth Animation with Accumulators\n\nWhen a number parameter controls animation speed, multiplying by `u_time` directly causes jumps when the value changes. The [`@viji-accumulator`](../accumulator/) integrates the rate over time for smooth transitions:\n\n```glsl\n// @viji-number:bpm label:\"BPM\" default:120 min:30 max:300 step:1\n// @viji-accumulator:beat rate:bpm\n\nuniform float bpm;\nuniform float beat; // grows by bpm × deltaTime each frame, no jumps\n```\n\nSee [Accumulator](../accumulator/) for full details and examples.\n\n## Rules\n\n- Numeric values have no quotes: `default:5`, `min:1`, `max:20`\n- String values use quotes: `label:\"Density\"`\n- The `label` and `default` keys are required\n\n## Related\n\n- [Shader Basics](/shader/basics) — shader file structure and directives\n- [Slider](/shader/parameters/slider) — numeric slider `uniform float`\n- [Toggle](../toggle/) — boolean `uniform bool`\n- [Select](../select/) — dropdown `uniform int`\n- [Accumulator](../accumulator/) — frame-persistent state driven by numeric values\n- [Native Number](/native/parameters/number) — equivalent for the Native renderer\n- [P5 Number](/p5/parameters/number) — equivalent for the P5 renderer"
1985
+ "markdown": "## Number vs Slider in Shaders\n\nBoth `@viji-number` and `@viji-slider` produce a `uniform float` and accept the same config keys (`min`, `max`, `step`, `default`). The only difference is the host UI:\n\n| | @viji-slider | @viji-number |\n|--|--------------|--------------|\n| UI | Draggable track | Text input field |\n| Best for | Continuous ranges, visual tuning | Precise values, integer counts |\n\n## Smooth Animation with Accumulators\n\nWhen a number parameter controls animation speed, multiplying by `u_time` directly causes jumps when the value changes. The [`@viji-accumulator`](../accumulator/) integrates the rate over time for smooth transitions:\n\n```glsl\n// @viji-number:bpm label:\"BPM\" default:120 min:30 max:300 step:1\n// @viji-accumulator:beat rate:bpm\n// Generates: uniform float bpm; and uniform float beat;\n// beat grows by bpm × deltaTime each frame, no jumps\n```\n\nSee [Accumulator](../accumulator/) for full details and examples.\n\n## Rules\n\n- Numeric values have no quotes: `default:5`, `min:1`, `max:20`\n- String values use quotes: `label:\"Density\"`\n- The `label` and `default` keys are required\n\n## Related\n\n- [Shader Basics](/shader/basics) — shader file structure and directives\n- [Slider](/shader/parameters/slider) — numeric slider `uniform float`\n- [Toggle](../toggle/) — boolean `uniform bool`\n- [Select](../select/) — dropdown `uniform int`\n- [Accumulator](../accumulator/) — frame-persistent state driven by numeric values\n- [Native Number](/native/parameters/number) — equivalent for the Native renderer\n- [P5 Number](/p5/parameters/number) — equivalent for the P5 renderer"
1954
1986
  }
1955
1987
  ]
1956
1988
  },
@@ -1961,12 +1993,12 @@ export const docsApi = {
1961
1993
  "content": [
1962
1994
  {
1963
1995
  "type": "text",
1964
- "markdown": "# @viji-image\n\n```glsl\n// @viji-image:tex label:\"Texture\"\nuniform sampler2D tex;\n```\n\nDeclares an image upload parameter. The host renders a file picker or drag-and-drop area. The uploaded image is bound as a WebGL texture and accessed via a `uniform sampler2D`.\n\n## Directive Syntax\n\n```\n// @viji-image:uniformName key:value key:value ...\n```\n\n| Key | Required | Default | Description |\n|-----|----------|---------|-------------|\n| `label` | Yes | — | Display name in the parameter UI |\n| `description` | No | — | Tooltip text (use quotes) |\n| `group` | No | `general` | Group name (use quotes) |\n| `category` | No | `general` | Visibility category |\n\nImage parameters do not have a `default` key — the default is always \"no image\" until the user uploads one.\n\n## Uniform Type\n\nThe image is injected as a `sampler2D`. When no image is uploaded, the texture contains a single transparent black pixel (`vec4(0.0)`).\n\n## Usage\n\n```glsl\n// @renderer shader\n// @viji-image:tex label:\"Texture\"\n\nuniform sampler2D tex;\nuniform vec2 u_resolution;\nuniform float u_time;\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n vec4 img = texture2D(tex, uv);\n gl_FragColor = img;\n}\n```\n\n> [!WARNING]\n> The directive must use `//` comments only. Block comments (`/* */`) are not parsed.\n\n> [!WARNING]\n> The uniform name in the directive (`@viji-image:tex`) must exactly match the `uniform sampler2D tex;` declaration. Viji does not auto-generate uniform declarations."
1996
+ "markdown": "# @viji-image\n\n```glsl\n// @viji-image:tex label:\"Texture\"\nuniform sampler2D tex;\n```\n\nDeclares an image upload parameter. The host renders a file picker or drag-and-drop area. The uploaded image is bound as a WebGL texture and accessed via a `uniform sampler2D`.\n\n## Directive Syntax\n\n```\n// @viji-image:uniformName key:value key:value ...\n```\n\n| Key | Required | Default | Description |\n|-----|----------|---------|-------------|\n| `label` | Yes | — | Display name in the parameter UI |\n| `description` | No | — | Tooltip text (use quotes) |\n| `group` | No | `general` | Group name (use quotes) |\n| `category` | No | `general` | Visibility category |\n\nImage parameters do not have a `default` key — the default is always \"no image\" until the user uploads one.\n\n## Uniform Type\n\nThe image is injected as a `sampler2D`. When no image is uploaded, the texture contains a single transparent black pixel (`vec4(0.0)`).\n\n## Usage\n\n```glsl\n// @renderer shader\n// @viji-image:tex label:\"Texture\"\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n vec4 img = texture2D(tex, uv);\n gl_FragColor = img;\n}\n```\n\n> [!WARNING]\n> The directive must use `//` comments only. Block comments (`/* */`) are not parsed.\n\n> [!NOTE]\n> Viji auto-injects all `uniform` declarations — both built-in uniforms and parameter uniforms from directives. Do **not** redeclare them in your shader code; duplicate declarations cause compilation errors."
1965
1997
  },
1966
1998
  {
1967
1999
  "type": "live-example",
1968
2000
  "title": "Image Texture",
1969
- "sceneCode": "// @renderer shader\n// @viji-image:tex label:\"Texture\"\n// @viji-slider:distort label:\"Distortion\" default:0.02 min:0 max:0.1 step:0.005\n// @viji-color:tint label:\"Tint\" default:#ffffff\n\nuniform sampler2D tex;\nuniform float distort;\nuniform vec3 tint;\nuniform vec2 u_resolution;\nuniform float u_time;\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n vec2 d = vec2(\n sin(uv.y * 10.0 + u_time * 2.0) * distort,\n cos(uv.x * 10.0 + u_time * 2.0) * distort\n );\n vec4 img = texture2D(tex, uv + d);\n\n float hasImage = step(0.001, img.a);\n vec3 fallback = vec3(uv, sin(u_time) * 0.5 + 0.5);\n vec3 col = mix(fallback, img.rgb * tint, hasImage);\n\n gl_FragColor = vec4(col, 1.0);\n}\n",
2001
+ "sceneCode": "// @renderer shader\n// @viji-image:tex label:\"Texture\"\n// @viji-slider:distort label:\"Distortion\" default:0.02 min:0 max:0.1 step:0.005\n// @viji-color:tint label:\"Tint\" default:#ffffff\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n vec2 d = vec2(\n sin(uv.y * 10.0 + u_time * 2.0) * distort,\n cos(uv.x * 10.0 + u_time * 2.0) * distort\n );\n vec4 img = texture2D(tex, uv + d);\n\n float hasImage = step(0.001, img.a);\n vec3 fallback = vec3(uv, sin(u_time) * 0.5 + 0.5);\n vec3 col = mix(fallback, img.rgb * tint, hasImage);\n\n gl_FragColor = vec4(col, 1.0);\n}\n",
1970
2002
  "sceneFile": "image-shader.scene.glsl"
1971
2003
  },
1972
2004
  {
@@ -2061,7 +2093,11 @@ export const docsApi = {
2061
2093
  "type": "live-example",
2062
2094
  "title": "Shader Parameter Categories",
2063
2095
  "sceneCode": "// @renderer shader\r\n// @viji-color:tint label:\"Color\" default:#4488ff category:general\r\n// @viji-slider:audioPulse label:\"Audio Pulse\" default:0.3 min:0.0 max:1.0 category:audio\r\n// @viji-slider:mouseSize label:\"Mouse Glow\" default:0.15 min:0.0 max:0.5 category:interaction\r\n// @viji-accumulator:phase rate:1.0\r\n\r\nvoid main() {\r\n vec2 uv = (2.0 * gl_FragCoord.xy - u_resolution) / u_resolution.y;\r\n float d = length(uv);\r\n\r\n float pulse = u_audioVolume * audioPulse;\r\n float wave = sin(d * 15.0 - phase * 3.0) * 0.5 + 0.5;\r\n vec3 col = tint * wave * (1.0 + pulse);\r\n\r\n vec2 mouseUV = (2.0 * u_mouse - u_resolution) / u_resolution.y;\r\n float mouseDist = length(uv - mouseUV);\r\n float glow = mouseSize / (mouseDist + 0.05);\r\n col += vec3(glow * 0.3);\r\n\r\n col *= smoothstep(1.5, 0.3, d);\r\n gl_FragColor = vec4(col, 1.0);\r\n}\r\n",
2064
- "sceneFile": "categories-demo.scene.glsl"
2096
+ "sceneFile": "categories-demo.scene.glsl",
2097
+ "capabilities": {
2098
+ "audio": true,
2099
+ "interaction": true
2100
+ }
2065
2101
  },
2066
2102
  {
2067
2103
  "type": "text",
@@ -2082,7 +2118,10 @@ export const docsApi = {
2082
2118
  "type": "live-example",
2083
2119
  "title": "Pointer — Click Flash & Glow",
2084
2120
  "sceneCode": "// @renderer shader\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n vec2 pNorm = u_pointer / u_resolution;\n\n vec3 col = vec3(0.04, 0.04, 0.08);\n\n float dist = length(uv - pNorm);\n float glow = 0.015 / (dist + 0.01);\n vec3 glowColor = u_pointerDown\n ? vec3(0.3, 0.7, 1.0)\n : vec3(0.5, 0.5, 0.6);\n col += glow * glowColor;\n\n float flash = u_pointerWasPressed ? 1.0 : 0.0;\n col += vec3(0.2, 0.5, 0.8) * flash * smoothstep(0.3, 0.0, dist);\n\n float release = u_pointerWasReleased ? 1.0 : 0.0;\n float ring = smoothstep(0.002, 0.0, abs(dist - 0.15)) * release;\n col += vec3(0.8, 0.4, 0.2) * ring;\n\n if (!u_pointerInCanvas) {\n col *= 0.5;\n }\n\n gl_FragColor = vec4(col, 1.0);\n}\n",
2085
- "sceneFile": "pointer-shader-demo.scene.glsl"
2121
+ "sceneFile": "pointer-shader-demo.scene.glsl",
2122
+ "capabilities": {
2123
+ "interaction": true
2124
+ }
2086
2125
  },
2087
2126
  {
2088
2127
  "type": "text",
@@ -2103,7 +2142,10 @@ export const docsApi = {
2103
2142
  "type": "live-example",
2104
2143
  "title": "Mouse — Glow & Button States",
2105
2144
  "sceneCode": "// @renderer shader\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n vec2 mNorm = u_mouse / u_resolution;\n\n vec3 col = vec3(0.04, 0.04, 0.08);\n\n float dist = length(uv - mNorm);\n float glow = 0.012 / (dist + 0.01);\n\n vec3 btnColor = vec3(0.5);\n if (u_mouseLeft) btnColor = vec3(0.3, 0.8, 1.0);\n if (u_mouseRight) btnColor = vec3(1.0, 0.4, 0.3);\n if (u_mouseMiddle) btnColor = vec3(0.3, 1.0, 0.5);\n col += glow * btnColor;\n\n float flash = u_mouseWasPressed ? 1.0 : 0.0;\n col += vec3(0.3) * flash * smoothstep(0.2, 0.0, dist);\n\n float speed = length(u_mouseDelta) / max(u_resolution.x, 1.0);\n col += vec3(0.0, 0.2, 0.5) * speed * 5.0 * smoothstep(0.3, 0.0, dist);\n\n float wheelVis = clamp(u_mouseWheel * 0.001, -1.0, 1.0);\n col.r += abs(wheelVis) * 0.15 * smoothstep(0.5, 0.0, dist);\n\n if (!u_mouseInCanvas) {\n col *= 0.4;\n }\n\n gl_FragColor = vec4(col, 1.0);\n}\n",
2106
- "sceneFile": "mouse-shader-demo.scene.glsl"
2145
+ "sceneFile": "mouse-shader-demo.scene.glsl",
2146
+ "capabilities": {
2147
+ "interaction": true
2148
+ }
2107
2149
  },
2108
2150
  {
2109
2151
  "type": "text",
@@ -2124,7 +2166,10 @@ export const docsApi = {
2124
2166
  "type": "live-example",
2125
2167
  "title": "Keyboard — WASD Movement",
2126
2168
  "sceneCode": "// @renderer shader\n\n// Smooth WASD/arrow movement via accumulators\n// @viji-accumulator:moveRight rate:u_keyD\n// @viji-accumulator:moveLeft rate:u_keyA\n// @viji-accumulator:moveUp rate:u_keyW\n// @viji-accumulator:moveDown rate:u_keyS\n// @viji-accumulator:arRight rate:u_keyRight\n// @viji-accumulator:arLeft rate:u_keyLeft\n// @viji-accumulator:arUp rate:u_keyUp\n// @viji-accumulator:arDown rate:u_keyDown\n\nvoid main() {\n vec2 uv = (gl_FragCoord.xy - 0.5 * u_resolution) / min(u_resolution.x, u_resolution.y);\n\n float speed = u_keyShift ? 0.5 : 0.2;\n vec2 offset = vec2(\n (moveRight + arRight - moveLeft - arLeft) * speed,\n (moveUp + arUp - moveDown - arDown) * speed\n );\n\n vec2 p = uv - offset;\n\n float d = length(p) - 0.1;\n vec3 col = vec3(0.04, 0.04, 0.08);\n\n vec3 dotColor = u_keySpace ? vec3(1.0, 0.5, 0.2) : vec3(0.3, 0.7, 1.0);\n col += dotColor * smoothstep(0.01, 0.0, d);\n\n float glow = 0.005 / (abs(d) + 0.005);\n col += dotColor * glow * 0.3;\n\n float grid = step(0.98, fract(uv.x * 10.0)) + step(0.98, fract(uv.y * 10.0));\n col += grid * 0.03;\n\n gl_FragColor = vec4(col, 1.0);\n}\n",
2127
- "sceneFile": "keyboard-shader-demo.scene.glsl"
2169
+ "sceneFile": "keyboard-shader-demo.scene.glsl",
2170
+ "capabilities": {
2171
+ "interaction": true
2172
+ }
2128
2173
  },
2129
2174
  {
2130
2175
  "type": "text",
@@ -2145,7 +2190,10 @@ export const docsApi = {
2145
2190
  "type": "live-example",
2146
2191
  "title": "Touch — Multi-Point Glow",
2147
2192
  "sceneCode": "// @renderer shader\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy;\n\n vec3 col = vec3(0.04, 0.04, 0.08);\n\n vec3 colors[5];\n colors[0] = vec3(0.3, 0.7, 1.0);\n colors[1] = vec3(0.3, 1.0, 0.5);\n colors[2] = vec3(1.0, 0.5, 0.3);\n colors[3] = vec3(1.0, 0.8, 0.2);\n colors[4] = vec3(0.8, 0.3, 1.0);\n\n for (int i = 0; i < 5; i++) {\n if (i >= u_touchCount) break;\n vec2 tp;\n if (i == 0) tp = u_touch0;\n else if (i == 1) tp = u_touch1;\n else if (i == 2) tp = u_touch2;\n else if (i == 3) tp = u_touch3;\n else tp = u_touch4;\n\n float d = length(uv - tp);\n col += colors[i] * 20.0 / (d + 8.0);\n\n float ring = smoothstep(2.0, 0.0, abs(d - 50.0));\n col += colors[i] * ring * 0.5;\n }\n\n if (u_touchCount == 0) {\n vec2 center = u_resolution * 0.5;\n float pulse = 0.5 + 0.5 * sin(u_time * 2.0);\n float d = length(uv - center);\n col += vec3(0.2, 0.3, 0.5) * pulse * 10.0 / (d + 20.0);\n }\n\n if (u_touchCount >= 2) {\n float dist = length(u_touch0 - u_touch1);\n vec2 mid = (u_touch0 + u_touch1) * 0.5;\n float dMid = length(uv - mid);\n float ring = smoothstep(2.0, 0.0, abs(dMid - dist * 0.5));\n col += vec3(0.5, 0.5, 0.8) * ring * 0.4;\n }\n\n gl_FragColor = vec4(col, 1.0);\n}\n",
2148
- "sceneFile": "touch-shader-demo.scene.glsl"
2193
+ "sceneFile": "touch-shader-demo.scene.glsl",
2194
+ "capabilities": {
2195
+ "interaction": true
2196
+ }
2149
2197
  },
2150
2198
  {
2151
2199
  "type": "text",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viji-dev/core",
3
- "version": "0.3.24",
3
+ "version": "0.3.25",
4
4
  "description": "Universal execution engine for Viji Creative scenes",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",