@vibes.diy/prompts 2.2.17 → 2.2.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  [![Join our
2
- Discord](https://discord-badge.selem.workers.dev/i/tzMUkwFK.svg)](https://discord.gg/tzMUkwFK)
2
+ Discord](https://discord-badge.selem.workers.dev/i/weSgWt684k.svg)](https://discord.gg/weSgWt684k)
3
3
 
4
4
  # ✨ Build Mini Apps with AI Magic
5
5
 
package/json-docs.d.ts CHANGED
@@ -12,6 +12,7 @@ export interface JsonDocs {
12
12
  "three-js.json": JsonDoc;
13
13
  "use-viewer.json": JsonDoc;
14
14
  "web-audio.json": JsonDoc;
15
+ "webxr.json": JsonDoc;
15
16
  [key: string]: JsonDoc;
16
17
  }
17
18
  export declare function getLlmCatalogNames(): Promise<Set<string>>;
package/json-docs.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"json-docs.js","sourceRoot":"","sources":["../jsr/json-docs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAuB7C,MAAM,UAAU,kBAAkB;IAChC,OAAO,aAAa,EAAE,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAChF,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO,eAAe,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;AAClE,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,WAAW,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;QACjC,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,IAAuB,EAAE;IAC5D,MAAM,CAAC,GAAa,EAAc,CAAC;IAGnC,KAAK,MAAM,MAAM,IAAI,UAAU,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,GAAG,MAAM,CAAC,IAAI,OAAO,CAAC;QACvC,CAAC,CAAC,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC;IAChD,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"json-docs.js","sourceRoot":"","sources":["../jsr/json-docs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAwB7C,MAAM,UAAU,kBAAkB;IAChC,OAAO,aAAa,EAAE,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAChF,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO,eAAe,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;AAClE,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,WAAW,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;QACjC,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,IAAuB,EAAE;IAC5D,MAAM,CAAC,GAAa,EAAc,CAAC;IAGnC,KAAK,MAAM,MAAM,IAAI,UAAU,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,GAAG,MAAM,CAAC,IAAI,OAAO,CAAC;QACvC,CAAC,CAAC,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC;IAChD,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC,CAAC,CAAC"}
package/llms/index.d.ts CHANGED
@@ -4,6 +4,7 @@ export { imageGenConfig } from "./image-gen.js";
4
4
  export { webAudioConfig } from "./web-audio.js";
5
5
  export { d3Config } from "./d3.js";
6
6
  export { threeJsConfig } from "./three-js.js";
7
+ export { webxrConfig } from "./webxr.js";
7
8
  export { useViewerConfig } from "./use-viewer.js";
8
9
  export type { LlmConfig } from "./types.js";
9
- export declare const allConfigs: readonly [import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig];
10
+ export declare const allConfigs: readonly [import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig];
package/llms/index.js CHANGED
@@ -4,6 +4,7 @@ import { imageGenConfig } from "./image-gen.js";
4
4
  import { webAudioConfig } from "./web-audio.js";
5
5
  import { d3Config } from "./d3.js";
6
6
  import { threeJsConfig } from "./three-js.js";
7
+ import { webxrConfig } from "./webxr.js";
7
8
  import { useViewerConfig } from "./use-viewer.js";
8
9
  export { callaiConfig } from "./callai.js";
9
10
  export { fireproofConfig } from "./fireproof.js";
@@ -11,6 +12,7 @@ export { imageGenConfig } from "./image-gen.js";
11
12
  export { webAudioConfig } from "./web-audio.js";
12
13
  export { d3Config } from "./d3.js";
13
14
  export { threeJsConfig } from "./three-js.js";
15
+ export { webxrConfig } from "./webxr.js";
14
16
  export { useViewerConfig } from "./use-viewer.js";
15
17
  export const allConfigs = [
16
18
  callaiConfig,
@@ -19,6 +21,7 @@ export const allConfigs = [
19
21
  d3Config,
20
22
  threeJsConfig,
21
23
  fireproofConfig,
24
+ webxrConfig,
22
25
  useViewerConfig,
23
26
  ];
24
27
  //# sourceMappingURL=index.js.map
package/llms/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../jsr/llms/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAElD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAIlD,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,YAAY;IACZ,cAAc;IACd,cAAc;IACd,QAAQ;IACR,aAAa;IACb,eAAe;IACf,eAAe;CACP,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../jsr/llms/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAElD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAIlD,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,YAAY;IACZ,cAAc;IACd,cAAc;IACd,QAAQ;IACR,aAAa;IACb,eAAe;IACf,WAAW;IACX,eAAe;CACP,CAAC"}
package/llms/three-js.js CHANGED
@@ -2,7 +2,7 @@ export const threeJsConfig = {
2
2
  name: "three-js",
3
3
  label: "Three.js",
4
4
  module: "three-js",
5
- description: "Three.js 3D graphics library for WebGL 3D rendering, mesh geometry, materials, lighting, animation, scenes, cameras, textures, shaders, models, WebXR, physics, particle systems, post-processing, visual effects, 3js",
5
+ description: "Three.js 3D graphics library for WebGL rendering: mesh geometry, materials, lighting, animation, cameras, textures, GLSL shaders, GLTF/OBJ/FBX model loading, physics, particle systems, post-processing, visual effects, 3js",
6
6
  importModule: "three",
7
7
  importName: "THREE",
8
8
  importType: "namespace",
@@ -1 +1 @@
1
- {"version":3,"file":"three-js.js","sourceRoot":"","sources":["../../jsr/llms/three-js.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,aAAa,GAAc;IACtC,IAAI,EAAE,UAAU;IAChB,KAAK,EAAE,UAAU;IACjB,MAAM,EAAE,UAAU;IAClB,WAAW,EACT,wNAAwN;IAC1N,YAAY,EAAE,OAAO;IACrB,UAAU,EAAE,OAAO;IACnB,UAAU,EAAE,WAAW;CACxB,CAAC"}
1
+ {"version":3,"file":"three-js.js","sourceRoot":"","sources":["../../jsr/llms/three-js.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,aAAa,GAAc;IACtC,IAAI,EAAE,UAAU;IAChB,KAAK,EAAE,UAAU;IACjB,MAAM,EAAE,UAAU;IAClB,WAAW,EACT,+NAA+N;IACjO,YAAY,EAAE,OAAO;IACrB,UAAU,EAAE,OAAO;IACnB,UAAU,EAAE,WAAW;CACxB,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { LlmConfig } from "./types.js";
2
+ export declare const webxrConfig: LlmConfig;
package/llms/webxr.js ADDED
@@ -0,0 +1,14 @@
1
+ export const webxrConfig = {
2
+ name: "webxr",
3
+ label: "Babylon.js WebXR",
4
+ module: "webxr",
5
+ description: "Babylon.js 3D engine with first-class WebXR: immersive VR and AR passthrough, " +
6
+ "hit-testing, surface anchors, particle systems, SolidParticleSystem, custom GLSL shaders, " +
7
+ "controller and hand-tracking events, PBR materials, Quest performance patterns. " +
8
+ "babylon, babylonjs, WebXR, VR, AR, spatial computing, immersive, mixed reality, xr, " +
9
+ "augmented reality, virtual reality, Quest, Vision Pro",
10
+ importModule: "@babylonjs/core",
11
+ importName: "BABYLON",
12
+ importType: "namespace",
13
+ };
14
+ //# sourceMappingURL=webxr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webxr.js","sourceRoot":"","sources":["../../jsr/llms/webxr.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,WAAW,GAAc;IACpC,IAAI,EAAE,OAAO;IACb,KAAK,EAAE,kBAAkB;IACzB,MAAM,EAAE,OAAO;IACf,WAAW,EACT,gFAAgF;QAChF,4FAA4F;QAC5F,kFAAkF;QAClF,sFAAsF;QACtF,uDAAuD;IACzD,YAAY,EAAE,iBAAiB;IAC/B,UAAU,EAAE,SAAS;IACrB,UAAU,EAAE,WAAW;CACxB,CAAC"}
package/llms/webxr.md ADDED
@@ -0,0 +1,751 @@
1
+ # Babylon.js WebXR Reference for Coding Agents
2
+
3
+ _Babylon.js is a full-featured 3D engine with first-class WebXR support. Import it as a bare specifier — the platform resolves it via esm.sh._
4
+
5
+ ## Import
6
+
7
+ ```javascript
8
+ import * as BABYLON from "@babylonjs/core";
9
+ ```
10
+
11
+ ## Core Setup
12
+
13
+ ```javascript
14
+ const canvas = document.getElementById("renderCanvas");
15
+ const engine = new BABYLON.Engine(canvas, true, {
16
+ preserveDrawingBuffer: true,
17
+ stencil: true,
18
+ });
19
+
20
+ const scene = new BABYLON.Scene(engine);
21
+
22
+ // Required resize handler
23
+ window.addEventListener("resize", () => engine.resize());
24
+
25
+ // Render loop — put your per-frame logic in scene.onBeforeRenderObservable
26
+ engine.runRenderLoop(() => scene.render());
27
+ ```
28
+
29
+ ### Minimal Scene with Lighting
30
+
31
+ ```javascript
32
+ // Camera (required even in VR — sets the non-XR fallback view)
33
+ const camera = new BABYLON.FreeCamera("cam", new BABYLON.Vector3(0, 1.7, -5), scene);
34
+ camera.setTarget(BABYLON.Vector3.Zero());
35
+ camera.attachControl(canvas, true);
36
+
37
+ // Lights
38
+ const ambient = new BABYLON.HemisphericLight("ambient", new BABYLON.Vector3(0, 1, 0), scene);
39
+ ambient.intensity = 0.4;
40
+
41
+ const sun = new BABYLON.DirectionalLight("sun", new BABYLON.Vector3(-1, -2, -1), scene);
42
+ sun.position = new BABYLON.Vector3(5, 10, 5);
43
+ sun.intensity = 0.8;
44
+ ```
45
+
46
+ ---
47
+
48
+ ## VR Mode
49
+
50
+ ```javascript
51
+ // createDefaultXRExperienceAsync is async — always await it
52
+ const xrHelper = await scene.createDefaultXRExperienceAsync({
53
+ floorMeshes: [ground], // meshes user can teleport onto
54
+ disableTeleportation: false, // set true to disable built-in teleport
55
+ });
56
+
57
+ // Access the base XR session
58
+ const sessionManager = xrHelper.baseExperience.sessionManager;
59
+
60
+ // Check if currently in XR
61
+ if (xrHelper.baseExperience.state === BABYLON.WebXRState.IN_XR) {
62
+ console.log("In VR");
63
+ }
64
+
65
+ // React to entering/leaving XR
66
+ xrHelper.baseExperience.onStateChangedObservable.add((state) => {
67
+ if (state === BABYLON.WebXRState.IN_XR) {
68
+ // User entered VR — hide 2D UI, etc.
69
+ }
70
+ if (state === BABYLON.WebXRState.NOT_IN_XR) {
71
+ // User exited VR
72
+ }
73
+ });
74
+ ```
75
+
76
+ ### VR Button
77
+
78
+ `createDefaultXRExperienceAsync` adds the "Enter VR" button automatically. To add it manually:
79
+
80
+ ```javascript
81
+ const vrButton = BABYLON.WebXRDefaultExperience.CreateAsync(scene, {
82
+ uiOptions: { sessionMode: "immersive-vr" },
83
+ });
84
+ ```
85
+
86
+ ---
87
+
88
+ ## AR Mode (Passthrough)
89
+
90
+ AR mode requires HTTPS. The Quest Browser and Chrome on Android support it. Safari/Vision Pro does not yet support AR passthrough via WebXR.
91
+
92
+ ```javascript
93
+ const xrHelper = await scene.createDefaultXRExperienceAsync({
94
+ uiOptions: {
95
+ sessionMode: "immersive-ar",
96
+ referenceSpaceType: "local-floor",
97
+ },
98
+ optionalFeatures: true, // enables hit-test, anchors if supported
99
+ });
100
+ ```
101
+
102
+ ### Hit Testing (Tap to Place)
103
+
104
+ ```javascript
105
+ const hitTest = xrHelper.featuresManager.enableFeature(BABYLON.WebXRHitTest, "latest");
106
+
107
+ // Reusable indicator mesh (a thin ring)
108
+ const indicator = BABYLON.MeshBuilder.CreateTorus(
109
+ "indicator",
110
+ {
111
+ diameter: 0.3,
112
+ thickness: 0.01,
113
+ tessellation: 32,
114
+ },
115
+ scene
116
+ );
117
+ indicator.isPickable = false;
118
+
119
+ hitTest.onHitTestResultObservable.add((results) => {
120
+ if (results.length > 0) {
121
+ indicator.isVisible = true;
122
+ results[0].transformationMatrix.decompose(undefined, indicator.rotationQuaternion, indicator.position);
123
+ } else {
124
+ indicator.isVisible = false;
125
+ }
126
+ });
127
+ ```
128
+
129
+ ### Anchors (Persist Placed Objects)
130
+
131
+ ```javascript
132
+ const anchors = xrHelper.featuresManager.enableFeature(BABYLON.WebXRAnchorSystem, "latest");
133
+
134
+ // Place an object anchored to a real-world surface
135
+ async function placeAnchor(hitTestResult) {
136
+ const anchor = await anchors.addAnchorPointUsingHitTestResultAsync(hitTestResult);
137
+ const mesh = BABYLON.MeshBuilder.CreateSphere("orb", { diameter: 0.15 }, scene);
138
+ anchor.attachedNode = mesh; // mesh follows the anchor
139
+ }
140
+ ```
141
+
142
+ ### Plane Detection
143
+
144
+ ```javascript
145
+ const planes = xrHelper.featuresManager.enableFeature(BABYLON.WebXRPlaneDetector, "latest");
146
+
147
+ planes.onPlaneAddedObservable.add((plane) => {
148
+ // plane.xrPlane.polygon — array of DOMPointReadOnly vertices
149
+ // plane.mesh — auto-generated Babylon mesh for the detected plane
150
+ plane.mesh.material = planeMaterial;
151
+ });
152
+ ```
153
+
154
+ ---
155
+
156
+ ## Generative Art Patterns
157
+
158
+ ### Particle System (Billboarded Sprites)
159
+
160
+ ```javascript
161
+ const particles = new BABYLON.ParticleSystem("stars", 3000, scene);
162
+
163
+ // Texture — use a built-in or a canvas texture
164
+ particles.particleTexture = new BABYLON.Texture("https://playground.babylonjs.com/textures/flare.png", scene);
165
+
166
+ particles.emitter = new BABYLON.Vector3(0, 1, 0); // world position
167
+ particles.minEmitBox = new BABYLON.Vector3(-5, -5, -5);
168
+ particles.maxEmitBox = new BABYLON.Vector3(5, 5, 5);
169
+
170
+ particles.color1 = new BABYLON.Color4(0.3, 0.8, 1, 1);
171
+ particles.color2 = new BABYLON.Color4(1, 0.3, 0.8, 0.8);
172
+ particles.colorDead = new BABYLON.Color4(0, 0, 0, 0);
173
+
174
+ particles.minSize = 0.02;
175
+ particles.maxSize = 0.08;
176
+ particles.minLifeTime = 2;
177
+ particles.maxLifeTime = 6;
178
+ particles.emitRate = 200;
179
+ particles.blendMode = BABYLON.ParticleSystem.BLENDMODE_ADD;
180
+ particles.gravity = new BABYLON.Vector3(0, -0.1, 0);
181
+ particles.minAngularSpeed = -Math.PI;
182
+ particles.maxAngularSpeed = Math.PI;
183
+
184
+ particles.start();
185
+ ```
186
+
187
+ ### Solid Particle System (Performant Many-Mesh)
188
+
189
+ Use `SolidParticleSystem` when you need 3D geometry (not billboards) for thousands of instances:
190
+
191
+ ```javascript
192
+ const sps = new BABYLON.SolidParticleSystem("sps", scene);
193
+ const sphere = BABYLON.MeshBuilder.CreateSphere("tmp", { diameter: 0.1 }, scene);
194
+ sps.addShape(sphere, 500); // 500 sphere instances
195
+ sphere.dispose();
196
+
197
+ const mesh = sps.buildMesh();
198
+
199
+ // Position particles on init
200
+ sps.initParticles = () => {
201
+ for (let i = 0; i < sps.nbParticles; i++) {
202
+ const p = sps.particles[i];
203
+ p.position.x = (Math.random() - 0.5) * 10;
204
+ p.position.y = (Math.random() - 0.5) * 10;
205
+ p.position.z = (Math.random() - 0.5) * 10;
206
+ p.color = new BABYLON.Color4(Math.random(), Math.random(), Math.random(), 1);
207
+ }
208
+ };
209
+
210
+ sps.initParticles();
211
+ sps.setParticles();
212
+
213
+ // Animate each frame
214
+ scene.onBeforeRenderObservable.add(() => {
215
+ const t = performance.now() / 1000;
216
+ for (let i = 0; i < sps.nbParticles; i++) {
217
+ const p = sps.particles[i];
218
+ p.rotation.y += 0.01;
219
+ p.position.y += Math.sin(t + i * 0.1) * 0.001;
220
+ }
221
+ sps.setParticles();
222
+ });
223
+ ```
224
+
225
+ ### Procedural Geometry
226
+
227
+ ```javascript
228
+ // Primitives via MeshBuilder
229
+ const box = BABYLON.MeshBuilder.CreateBox("box", { size: 1 }, scene);
230
+ const sphere = BABYLON.MeshBuilder.CreateSphere("s", { diameter: 1, segments: 32 }, scene);
231
+ const torus = BABYLON.MeshBuilder.CreateTorus("t", { diameter: 2, thickness: 0.3 }, scene);
232
+ const ribbon = BABYLON.MeshBuilder.CreateRibbon("r", { pathArray: paths }, scene);
233
+ const tube = BABYLON.MeshBuilder.CreateTube("tube", { path: points, radius: 0.1 }, scene);
234
+
235
+ // Custom vertex data
236
+ const positions = new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]);
237
+ const indices = new Uint32Array([0, 1, 2]);
238
+ const normals = [];
239
+ const vertexData = new BABYLON.VertexData();
240
+ vertexData.positions = positions;
241
+ vertexData.indices = indices;
242
+ BABYLON.VertexData.ComputeNormals(positions, indices, normals);
243
+ vertexData.normals = normals;
244
+ const customMesh = new BABYLON.Mesh("custom", scene);
245
+ vertexData.applyToMesh(customMesh);
246
+ ```
247
+
248
+ ### Custom GLSL Shaders
249
+
250
+ ```javascript
251
+ BABYLON.Effect.ShadersStore["colorCycleVertexShader"] = `
252
+ precision highp float;
253
+ attribute vec3 position;
254
+ attribute vec2 uv;
255
+ uniform mat4 worldViewProjection;
256
+ varying vec2 vUv;
257
+ void main() {
258
+ vUv = uv;
259
+ gl_Position = worldViewProjection * vec4(position, 1.0);
260
+ }
261
+ `;
262
+
263
+ BABYLON.Effect.ShadersStore["colorCycleFragmentShader"] = `
264
+ precision highp float;
265
+ varying vec2 vUv;
266
+ uniform float time;
267
+ uniform float hueShift;
268
+ vec3 hsl2rgb(float h, float s, float l) {
269
+ float c = (1.0 - abs(2.0 * l - 1.0)) * s;
270
+ float x = c * (1.0 - abs(mod(h / 60.0, 2.0) - 1.0));
271
+ float m = l - c / 2.0;
272
+ vec3 rgb;
273
+ if (h < 60.0) rgb = vec3(c, x, 0.0);
274
+ else if (h < 120.0) rgb = vec3(x, c, 0.0);
275
+ else if (h < 180.0) rgb = vec3(0.0, c, x);
276
+ else if (h < 240.0) rgb = vec3(0.0, x, c);
277
+ else if (h < 300.0) rgb = vec3(x, 0.0, c);
278
+ else rgb = vec3(c, 0.0, x);
279
+ return rgb + m;
280
+ }
281
+ void main() {
282
+ float hue = mod(hueShift + vUv.x * 120.0 + time * 30.0, 360.0);
283
+ vec3 color = hsl2rgb(hue, 0.8, 0.5 + sin(time + vUv.y * 6.28) * 0.15);
284
+ gl_FragColor = vec4(color, 1.0);
285
+ }
286
+ `;
287
+
288
+ const shaderMat = new BABYLON.ShaderMaterial("colorCycle", scene, "colorCycle", {
289
+ attributes: ["position", "uv"],
290
+ uniforms: ["worldViewProjection", "time", "hueShift"],
291
+ });
292
+ shaderMat.setFloat("hueShift", 0);
293
+
294
+ // Update uniforms each frame
295
+ const startTime = performance.now();
296
+ scene.onBeforeRenderObservable.add(() => {
297
+ shaderMat.setFloat("time", (performance.now() - startTime) / 1000);
298
+ });
299
+ ```
300
+
301
+ ---
302
+
303
+ ## Controller & Hand Tracking
304
+
305
+ ### Motion Controllers
306
+
307
+ ```javascript
308
+ const xrInput = xrHelper.input;
309
+
310
+ xrInput.onControllerAddedObservable.add((controller) => {
311
+ controller.onMotionControllerInitObservable.add((motionController) => {
312
+ const triggerComponent = motionController.getComponent(BABYLON.WebXRControllerComponent.TRIGGER_TYPE);
313
+ if (triggerComponent) {
314
+ triggerComponent.onButtonStateChangedObservable.add((component) => {
315
+ if (component.pressed) {
316
+ console.log("Trigger pressed on", motionController.handedness);
317
+ }
318
+ });
319
+ }
320
+
321
+ // Controller mesh position/rotation
322
+ const grip = controller.grip; // the physical grip space
323
+ // grip.position, grip.rotationQuaternion update every frame
324
+ });
325
+ });
326
+ ```
327
+
328
+ ### Hand Tracking
329
+
330
+ ```javascript
331
+ const handTracking = xrHelper.featuresManager.enableFeature(BABYLON.WebXRHandTracking, "latest", { xrInput: xrHelper.input });
332
+
333
+ handTracking.onHandAddedObservable.add((hand) => {
334
+ // Get index fingertip position every frame
335
+ scene.onBeforeRenderObservable.add(() => {
336
+ const tip = hand.getJointMesh(BABYLON.WebXRHandJoint.INDEX_FINGER_TIP);
337
+ if (tip) {
338
+ // tip.position is the world-space fingertip position
339
+ }
340
+ });
341
+ });
342
+ ```
343
+
344
+ ---
345
+
346
+ ## Materials & PBR
347
+
348
+ ```javascript
349
+ // Standard PBR material
350
+ const pbr = new BABYLON.PBRMaterial("pbr", scene);
351
+ pbr.albedoColor = new BABYLON.Color3(0.2, 0.6, 1.0);
352
+ pbr.metallic = 0.8;
353
+ pbr.roughness = 0.2;
354
+
355
+ // Emissive glow
356
+ pbr.emissiveColor = new BABYLON.Color3(0.1, 0.4, 0.9);
357
+ pbr.emissiveIntensity = 1.5;
358
+
359
+ // Glass
360
+ const glass = new BABYLON.PBRMaterial("glass", scene);
361
+ glass.alpha = 0.3;
362
+ glass.metallic = 0;
363
+ glass.roughness = 0;
364
+ glass.subSurface.isRefractionEnabled = true;
365
+ glass.subSurface.refractionIntensity = 0.8;
366
+
367
+ // Unlit (good for AR overlays — no lighting calculation)
368
+ const unlit = new BABYLON.StandardMaterial("unlit", scene);
369
+ unlit.emissiveColor = new BABYLON.Color3(1, 0.5, 0);
370
+ unlit.disableLighting = true;
371
+ ```
372
+
373
+ ---
374
+
375
+ ## Performance in VR
376
+
377
+ - Target **72 fps on Quest** — keep draw calls under ~100 per frame
378
+ - Use `mesh.freezeWorldMatrix()` for any mesh that never moves
379
+ - Use `scene.freezeMaterials()` after all materials are set up
380
+ - Prefer `SolidParticleSystem` over individual meshes for 100+ repeated objects
381
+ - Use `BABYLON.InstancedMesh` for identical geometry:
382
+
383
+ ```javascript
384
+ const source = BABYLON.MeshBuilder.CreateSphere("source", { diameter: 0.1 }, scene);
385
+ source.isVisible = false;
386
+
387
+ for (let i = 0; i < 200; i++) {
388
+ const instance = source.createInstance(`orb_${i}`);
389
+ instance.position.set((Math.random() - 0.5) * 10, (Math.random() - 0.5) * 10, (Math.random() - 0.5) * 10);
390
+ }
391
+ ```
392
+
393
+ ---
394
+
395
+ ## Common Gotchas
396
+
397
+ - **Import from `@babylonjs/core`, not `babylonjs`** — the legacy `babylonjs` package times out on esm.sh; the scoped package resolves correctly. `import * as BABYLON from "@babylonjs/core"` gives the same `BABYLON.*` namespace.
398
+ - **`createDefaultXRExperienceAsync` is async** — always `await` it or chain `.then()`. Calling it without await silently skips XR setup.
399
+ - **AR requires HTTPS** — `localhost` works for dev; plain `http://` will fail on device.
400
+ - **Quest AR setting** — `immersive-ar` may require "WebXR Passthrough" enabled in Quest Browser settings (about://flags).
401
+ - **Apple Vision Pro** — `immersive-vr` works via visionOS Safari WebXR; AR passthrough is not yet available.
402
+ - **Always call `engine.resize()`** on window resize AND when XR display geometry changes.
403
+ - **`rotationQuaternion` vs `rotation`** — once Babylon sets `rotationQuaternion` on a mesh (e.g. via XR tracking), `rotation` is ignored. Use `rotationQuaternion` consistently.
404
+ - **Dispose properly** — call `scene.dispose()` and `engine.dispose()` on React component unmount to prevent memory leaks.
405
+
406
+ ---
407
+
408
+ ## Real-World Example 1: VR Generative Art — Particle Galaxy
409
+
410
+ A floating galaxy of 2000 particles with a custom-shader core sphere, navigable in VR. Session data stored in Fireproof.
411
+
412
+ ```javascript
413
+ import * as BABYLON from "@babylonjs/core";
414
+ import React, { useEffect, useRef } from "react";
415
+ import { useFireproof } from "use-fireproof";
416
+
417
+ // ── Pure Babylon functions (no React, no Fireproof) ────────────────────────
418
+
419
+ function buildScene(canvas) {
420
+ const engine = new BABYLON.Engine(canvas, true);
421
+ const scene = new BABYLON.Scene(engine);
422
+ scene.clearColor = new BABYLON.Color4(0, 0, 0.02, 1);
423
+
424
+ const camera = new BABYLON.FreeCamera("cam", new BABYLON.Vector3(0, 1.7, -8), scene);
425
+ camera.setTarget(BABYLON.Vector3.Zero());
426
+ camera.attachControl(canvas, true);
427
+
428
+ new BABYLON.HemisphericLight("amb", new BABYLON.Vector3(0, 1, 0), scene).intensity = 0.1;
429
+
430
+ engine.runRenderLoop(() => scene.render());
431
+ return { engine, scene };
432
+ }
433
+
434
+ function buildGalaxy(scene) {
435
+ const galaxy = new BABYLON.ParticleSystem("galaxy", 2000, scene);
436
+ galaxy.particleTexture = new BABYLON.Texture("https://playground.babylonjs.com/textures/flare.png", scene);
437
+ galaxy.emitter = new BABYLON.Vector3(0, 0, 0);
438
+ galaxy.minEmitBox = new BABYLON.Vector3(-6, -0.5, -6);
439
+ galaxy.maxEmitBox = new BABYLON.Vector3(6, 0.5, 6);
440
+ galaxy.color1 = new BABYLON.Color4(0.4, 0.7, 1, 1);
441
+ galaxy.color2 = new BABYLON.Color4(1, 0.4, 0.8, 0.6);
442
+ galaxy.colorDead = new BABYLON.Color4(0, 0, 0, 0);
443
+ galaxy.minSize = 0.03;
444
+ galaxy.maxSize = 0.12;
445
+ galaxy.minLifeTime = 4;
446
+ galaxy.maxLifeTime = 10;
447
+ galaxy.emitRate = 80;
448
+ galaxy.blendMode = BABYLON.ParticleSystem.BLENDMODE_ADD;
449
+ galaxy.gravity = new BABYLON.Vector3(0, 0, 0);
450
+ galaxy.minAngularSpeed = -0.5;
451
+ galaxy.maxAngularSpeed = 0.5;
452
+ galaxy.start();
453
+ return galaxy;
454
+ }
455
+
456
+ function buildCoreShader(scene) {
457
+ if (!BABYLON.Effect.ShadersStore["galaxyCoreVertexShader"]) {
458
+ BABYLON.Effect.ShadersStore["galaxyCoreVertexShader"] = `
459
+ precision highp float;
460
+ attribute vec3 position; attribute vec2 uv;
461
+ uniform mat4 worldViewProjection;
462
+ varying vec2 vUv;
463
+ void main() { vUv = uv; gl_Position = worldViewProjection * vec4(position, 1.0); }
464
+ `;
465
+ BABYLON.Effect.ShadersStore["galaxyCoreFragmentShader"] = `
466
+ precision highp float;
467
+ varying vec2 vUv; uniform float time;
468
+ void main() {
469
+ vec2 p = vUv - 0.5;
470
+ float r = length(p);
471
+ float glow = 0.05 / max(r, 0.01);
472
+ float hue = mod(atan(p.y, p.x) / 6.283 + time * 0.1, 1.0) * 360.0;
473
+ float c = mod(hue / 60.0, 2.0) - 1.0;
474
+ vec3 col = vec3(
475
+ step(hue, 60.0) * (1.0 - abs(c)) + step(300.0, hue),
476
+ step(60.0, hue) * step(hue, 180.0) * (1.0 - abs(mod(hue / 60.0, 2.0) - 1.0)),
477
+ step(180.0, hue) * step(hue, 300.0) * (1.0 - abs(mod(hue / 60.0, 2.0) - 1.0))
478
+ );
479
+ gl_FragColor = vec4(col * glow * 2.0, min(glow, 1.0));
480
+ }
481
+ `;
482
+ }
483
+ const mat = new BABYLON.ShaderMaterial("galaxyCore", scene, "galaxyCore", {
484
+ attributes: ["position", "uv"],
485
+ uniforms: ["worldViewProjection", "time"],
486
+ });
487
+ mat.backFaceCulling = false;
488
+ mat.alphaMode = BABYLON.Constants.ALPHA_ADD;
489
+
490
+ const sphere = BABYLON.MeshBuilder.CreateSphere("core", { diameter: 1.5, segments: 32 }, scene);
491
+ sphere.material = mat;
492
+
493
+ const t0 = performance.now();
494
+ scene.onBeforeRenderObservable.add(() => {
495
+ mat.setFloat("time", (performance.now() - t0) / 1000);
496
+ });
497
+ return sphere;
498
+ }
499
+
500
+ async function enableVR(scene) {
501
+ const ground = BABYLON.MeshBuilder.CreateGround("ground", { width: 20, height: 20 }, scene);
502
+ ground.isVisible = false;
503
+ return scene.createDefaultXRExperienceAsync({ floorMeshes: [ground] });
504
+ }
505
+
506
+ // ── React container component ──────────────────────────────────────────────
507
+
508
+ export default function App() {
509
+ const { database, useLiveQuery } = useFireproof("galaxy-sessions");
510
+ const canvasRef = useRef(null);
511
+ const { docs: sessions } = useLiveQuery("type", { key: "session" });
512
+
513
+ useEffect(() => {
514
+ if (!canvasRef.current) return;
515
+ const { engine, scene } = buildScene(canvasRef.current);
516
+ const onResize = () => engine.resize();
517
+ window.addEventListener("resize", onResize);
518
+ buildGalaxy(scene);
519
+ buildCoreShader(scene);
520
+ enableVR(scene).then(() => {
521
+ database.put({ type: "session", startedAt: Date.now() });
522
+ });
523
+ return () => {
524
+ window.removeEventListener("resize", onResize);
525
+ scene.dispose();
526
+ engine.dispose();
527
+ };
528
+ }, []);
529
+
530
+ return (
531
+ <div className="relative w-full h-screen bg-black">
532
+ <canvas ref={canvasRef} className="w-full h-full" />
533
+ <div className="absolute top-4 left-4 text-white text-sm opacity-70">Sessions: {sessions.length}</div>
534
+ </div>
535
+ );
536
+ }
537
+ ```
538
+
539
+ ---
540
+
541
+ ## Real-World Example 2: AR Passthrough — Tap to Place Glowing Orbs
542
+
543
+ Tap a real-world surface to plant a pulsing orb anchored in place. Orb positions are stored in Fireproof so they survive page reload.
544
+
545
+ On devices without AR support (desktop, unsupported browsers) the app automatically enters **fixture mode**: a background photo stands in for the passthrough feed, and mouse clicks place orbs via ray-picking. The 3D scene is identical in both modes — only the background source differs.
546
+
547
+ ```javascript
548
+ import * as BABYLON from "@babylonjs/core";
549
+ import React, { useEffect, useRef, useState } from "react";
550
+ import { useFireproof } from "use-fireproof";
551
+
552
+ const FIXTURE_BG = "https://picsum.photos/seed/indoor-room/1456/816";
553
+
554
+ // ── Pure Babylon functions ─────────────────────────────────────────────────
555
+
556
+ // Always alpha:true so orbs composite over AR passthrough or fixture img
557
+ function buildScene(canvas) {
558
+ const engine = new BABYLON.Engine(canvas, true, { alpha: true });
559
+ const scene = new BABYLON.Scene(engine);
560
+ scene.clearColor = new BABYLON.Color4(0, 0, 0, 0);
561
+
562
+ const camera = new BABYLON.FreeCamera("cam", new BABYLON.Vector3(0, 1.6, 0), scene);
563
+ camera.minZ = 0.01;
564
+ camera.setTarget(new BABYLON.Vector3(0, 1.6, 3));
565
+
566
+ engine.runRenderLoop(() => scene.render());
567
+ return { engine, scene, camera };
568
+ }
569
+
570
+ function makeOrbMaterial(scene) {
571
+ if (!BABYLON.Effect.ShadersStore["orbVertexShader"]) {
572
+ BABYLON.Effect.ShadersStore["orbVertexShader"] = `
573
+ precision highp float;
574
+ attribute vec3 position; attribute vec2 uv;
575
+ uniform mat4 worldViewProjection;
576
+ varying vec2 vUv;
577
+ void main() { vUv = uv; gl_Position = worldViewProjection * vec4(position, 1.0); }
578
+ `;
579
+ BABYLON.Effect.ShadersStore["orbFragmentShader"] = `
580
+ precision highp float;
581
+ varying vec2 vUv; uniform float time; uniform vec3 baseColor;
582
+ void main() {
583
+ vec2 p = vUv - 0.5;
584
+ float r = length(p);
585
+ float rim = smoothstep(0.5, 0.35, r) - smoothstep(0.35, 0.2, r);
586
+ float core = smoothstep(0.2, 0.0, r);
587
+ float pulse = 0.7 + 0.3 * sin(time * 3.0);
588
+ vec3 col = baseColor * (rim * 0.6 + core * 2.0) * pulse;
589
+ gl_FragColor = vec4(col, (rim + core) * 0.9);
590
+ }
591
+ `;
592
+ }
593
+ const mat = new BABYLON.ShaderMaterial("orb", scene, "orb", {
594
+ attributes: ["position", "uv"],
595
+ uniforms: ["worldViewProjection", "time", "baseColor"],
596
+ });
597
+ mat.backFaceCulling = false;
598
+ mat.alphaMode = BABYLON.Constants.ALPHA_ADD;
599
+ return mat;
600
+ }
601
+
602
+ function spawnOrb(scene, position, orbMat) {
603
+ const mesh = BABYLON.MeshBuilder.CreateSphere("orb", { diameter: 0.15, segments: 16 }, scene);
604
+ mesh.position.copyFrom(position);
605
+ const mat = orbMat.clone("orbInst_" + Date.now());
606
+ const hue = Math.random();
607
+ mat.setVector3("baseColor", new BABYLON.Vector3(hue, 0.5, 1 - hue));
608
+ mesh.material = mat;
609
+ return mesh;
610
+ }
611
+
612
+ // Fixture mode: ray-pick through mouse click, place orb 3m along ray
613
+ function enableFixtureClicks(scene, camera, onPlace) {
614
+ scene.onPointerObservable.add((pointerInfo) => {
615
+ if (pointerInfo.type !== BABYLON.PointerEventTypes.POINTERDOWN) return;
616
+ const ray = scene.createPickingRay(scene.pointerX, scene.pointerY, BABYLON.Matrix.Identity(), camera);
617
+ onPlace(ray.origin.add(ray.direction.scale(3)));
618
+ });
619
+ }
620
+
621
+ async function enableAR(scene, onPlace) {
622
+ const xrHelper = await scene.createDefaultXRExperienceAsync({
623
+ uiOptions: { sessionMode: "immersive-ar", referenceSpaceType: "local-floor" },
624
+ optionalFeatures: true,
625
+ });
626
+
627
+ const hitTest = xrHelper.featuresManager.enableFeature(BABYLON.WebXRHitTest, "latest");
628
+
629
+ const indicator = BABYLON.MeshBuilder.CreateTorus("indicator", { diameter: 0.25, thickness: 0.008, tessellation: 32 }, scene);
630
+ const indicatorMat = new BABYLON.StandardMaterial("indMat", scene);
631
+ indicatorMat.emissiveColor = new BABYLON.Color3(0.4, 1, 0.8);
632
+ indicatorMat.disableLighting = true;
633
+ indicator.material = indicatorMat;
634
+ indicator.isVisible = false;
635
+
636
+ let latestHit = null;
637
+ hitTest.onHitTestResultObservable.add((results) => {
638
+ if (results.length > 0) {
639
+ indicator.isVisible = true;
640
+ latestHit = results[0];
641
+ results[0].transformationMatrix.decompose(
642
+ undefined,
643
+ indicator.rotationQuaternion || (indicator.rotationQuaternion = new BABYLON.Quaternion()),
644
+ indicator.position
645
+ );
646
+ } else {
647
+ indicator.isVisible = false;
648
+ latestHit = null;
649
+ }
650
+ });
651
+
652
+ scene.onPointerObservable.add((pointerInfo) => {
653
+ if (pointerInfo.type === BABYLON.PointerEventTypes.POINTERDOWN && latestHit) {
654
+ onPlace(indicator.position.clone());
655
+ }
656
+ });
657
+
658
+ return xrHelper;
659
+ }
660
+
661
+ // ── React container ────────────────────────────────────────────────────────
662
+
663
+ export default function App() {
664
+ const { database, useLiveQuery } = useFireproof("ar-orbs");
665
+ const canvasRef = useRef(null);
666
+ const sceneRef = useRef(null);
667
+ const orbMatRef = useRef(null);
668
+ const spawnedIdsRef = useRef(new Set());
669
+ const [orbCount, setOrbCount] = useState(0);
670
+ const [mode, setMode] = useState("checking"); // checking | ar | fixture
671
+ const [arError, setArError] = useState(null);
672
+ const { docs: savedOrbs } = useLiveQuery("type", { key: "orb" });
673
+
674
+ useEffect(() => {
675
+ if (!canvasRef.current) return;
676
+ let engine, scene, camera;
677
+
678
+ const onResize = () => engine?.resize();
679
+ window.addEventListener("resize", onResize);
680
+
681
+ async function init() {
682
+ const arSupported = await navigator.xr?.isSessionSupported("immersive-ar").catch(() => false);
683
+ ({ engine, scene, camera } = buildScene(canvasRef.current));
684
+ sceneRef.current = scene;
685
+ orbMatRef.current = makeOrbMaterial(scene);
686
+
687
+ const t0 = performance.now();
688
+ scene.onBeforeRenderObservable.add(() => {
689
+ const t = (performance.now() - t0) / 1000;
690
+ scene.meshes.forEach((m) => {
691
+ if (m.material?.getClassName?.() === "ShaderMaterial" && m.name.startsWith("orb")) {
692
+ m.material.setFloat("time", t);
693
+ }
694
+ });
695
+ });
696
+
697
+ const handlePlace = async (pos) => {
698
+ spawnOrb(scene, pos, orbMatRef.current);
699
+ setOrbCount((n) => n + 1);
700
+ const { id } = await database.put({ type: "orb", x: pos.x, y: pos.y, z: pos.z, ts: Date.now() });
701
+ spawnedIdsRef.current.add(id);
702
+ };
703
+
704
+ if (arSupported) {
705
+ setMode("ar");
706
+ try {
707
+ await enableAR(scene, handlePlace);
708
+ } catch (e) {
709
+ setArError(e?.message ?? String(e));
710
+ }
711
+ } else {
712
+ setMode("fixture");
713
+ enableFixtureClicks(scene, camera, handlePlace);
714
+ }
715
+ }
716
+
717
+ init();
718
+ return () => {
719
+ window.removeEventListener("resize", onResize);
720
+ scene?.dispose();
721
+ engine?.dispose();
722
+ };
723
+ }, []);
724
+
725
+ useEffect(() => {
726
+ if (!sceneRef.current || !orbMatRef.current || savedOrbs.length === 0) return;
727
+ savedOrbs.forEach((doc) => {
728
+ if (spawnedIdsRef.current.has(doc._id)) return;
729
+ spawnedIdsRef.current.add(doc._id);
730
+ spawnOrb(sceneRef.current, new BABYLON.Vector3(doc.x, doc.y, doc.z), orbMatRef.current);
731
+ });
732
+ }, [savedOrbs.length]);
733
+
734
+ return (
735
+ <div className="relative w-full h-screen overflow-hidden">
736
+ {mode === "fixture" && <img src={FIXTURE_BG} className="absolute inset-0 w-full h-full object-cover" alt="" />}
737
+ <canvas ref={canvasRef} className="absolute inset-0 w-full h-full" style={{ background: "transparent" }} />
738
+ <div className="absolute top-4 left-4 bg-black/40 text-white px-3 py-2 rounded-lg text-sm">
739
+ {mode === "checking" && "Checking AR support…"}
740
+ {mode === "ar" && `Tap a surface to place an orb · ${orbCount} placed`}
741
+ {mode === "fixture" && `Desktop preview — click to place orbs · ${orbCount} placed`}
742
+ </div>
743
+ {arError && (
744
+ <div className="absolute bottom-4 left-4 right-4 bg-red-900/80 text-white px-3 py-2 rounded-lg text-sm">
745
+ AR error: {arError}
746
+ </div>
747
+ )}
748
+ </div>
749
+ );
750
+ }
751
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibes.diy/prompts",
3
- "version": "2.2.17",
3
+ "version": "2.2.19",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "description": "",
@@ -30,8 +30,8 @@
30
30
  "@fireproof/core-types-base": "~0.24.19",
31
31
  "@fireproof/core-types-protocols-cloud": "~0.24.19",
32
32
  "@fireproof/use-fireproof": "~0.24.19",
33
- "@vibes.diy/call-ai-v2": "^2.2.17",
34
- "@vibes.diy/use-vibes-types": "^2.2.17",
33
+ "@vibes.diy/call-ai-v2": "^2.2.19",
34
+ "@vibes.diy/use-vibes-types": "^2.2.19",
35
35
  "arktype": "~2.2.0",
36
36
  "json-schema-faker": "~0.6.1"
37
37
  },
package/prompts.d.ts CHANGED
@@ -38,11 +38,11 @@ export declare const preAllocSchema: {
38
38
  };
39
39
  readonly iconDescription: {
40
40
  readonly type: "string";
41
- readonly description: "A short, vivid one-line description of what an icon for this app should depict \u2014 what it shows, not how it's drawn. Examples: 'a fox on a record player', 'a sailboat on a calm lake', 'a chef whisking eggs'. Don't mention colors, line weights, letters, numbers, or framing \u2014 those are added separately by the renderer.";
41
+ readonly description: "A short, vivid one-line description of what an icon for this app should depict what it shows, not how it's drawn. Examples: 'a fox on a record player', 'a sailboat on a calm lake', 'a chef whisking eggs'. Don't mention colors, line weights, letters, numbers, or framing those are added separately by the renderer.";
42
42
  };
43
43
  readonly theme: {
44
44
  readonly type: "string";
45
- readonly description: "Theme slug from the theme catalog above. Pick the one whose mood best fits the app \u2014 playful apps lean playful themes, focus/utility apps lean clean themes, retro apps lean retro themes, etc. Always pick something rather than leaving empty; the catalog is broad enough to cover most app moods. Only omit if the request is so abstract that every theme would feel arbitrary.";
45
+ readonly description: "Theme slug from the theme catalog above. Pick the one whose mood best fits the app playful apps lean playful themes, focus/utility apps lean clean themes, retro apps lean retro themes, etc. Always pick something rather than leaving empty; the catalog is broad enough to cover most app moods. Only omit if the request is so abstract that every theme would feel arbitrary.";
46
46
  };
47
47
  readonly enrichedPrompt: {
48
48
  readonly type: "string";
package/system-prompt.md CHANGED
@@ -9,7 +9,7 @@ You are an AI assistant tasked with creating React components. You should create
9
9
  - Avoid using external libraries unless they are essential for the component to function
10
10
  - Always use ES module imports at the top of the file (e.g. `import React, { useState } from "react"`). Never reference React or other libraries as globals.
11
11
  - Your file MUST use `export default function App()` — the runtime loads it as an ES module and imports the default export.
12
- - Structure your component code in this order: (1) hooks and document shapes, (2) event handlers, (3) classNames object, (4) JSX return. ClassNames go right before JSX so they are close to where they are used.
12
+ - Structure your component code in this order: (1) hooks and document shapes, (2) event handlers, (3) classNames object, (4) JSX return. ClassNames go right before JSX so they are close to where they are used. Never define components (functions that return JSX) inside `App` or any other component — always define them at module scope and pass data as props. Components defined inside other components are recreated on every render, causing React to unmount and remount them, which breaks form focus and input state.
13
13
  - Use Fireproof for data persistence
14
14
  - Use `callAI` to fetch AI, use schema like this: `JSON.parse(await callAI(prompt, { schema: { properties: { todos: { type: 'array', items: { type: 'string' } } } } }))` and save final responses as individual Fireproof documents.
15
15
  - Always show loading states during any async operation (callAI, fetch, database queries): use a useState boolean (e.g. `isLoading`), set it true before the call and false in .finally(). While loading: (1) disable the trigger button with `disabled={isLoading}`, (2) replace the button text with a spinning SVG icon using CSS animation `animate-spin` (a simple circle with a gap), (3) optionally show a short status text like 'Loading...' near the button. Never leave the user clicking a button with no visual feedback. Pattern: `setIsLoading(true); try { await callAI(...); } finally { setIsLoading(false); }`