@vibes.diy/prompts 0.1.1

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.
@@ -0,0 +1,2232 @@
1
+ # Three.js API
2
+
3
+ _Essential classes, methods, and patterns for Three.js development_
4
+
5
+ ## Core Setup
6
+
7
+ ### Scene Graph Hierarchy
8
+
9
+ ```javascript
10
+ import * as THREE from "three";
11
+
12
+ // Core trinity
13
+ const scene = new THREE.Scene();
14
+ const camera = new THREE.PerspectiveCamera(75, aspect, 0.1, 1000);
15
+ const renderer = new THREE.WebGLRenderer({ antialias: true });
16
+
17
+ // Everything is an Object3D
18
+ scene.add(mesh); // Mesh extends Object3D
19
+ group.add(light); // Light extends Object3D
20
+ parent.add(child); // Hierarchical transforms
21
+ ```
22
+
23
+ ## Essential Classes
24
+
25
+ ### Cameras
26
+
27
+ ```javascript
28
+ // Perspective (most common)
29
+ const camera = new THREE.PerspectiveCamera(
30
+ 75, // field of view
31
+ aspect, // aspect ratio
32
+ 0.1, // near plane
33
+ 1000, // far plane
34
+ );
35
+
36
+ // Orthographic (2D/technical)
37
+ const camera = new THREE.OrthographicCamera(
38
+ left,
39
+ right,
40
+ top,
41
+ bottom,
42
+ near,
43
+ far,
44
+ );
45
+
46
+ // Camera controls
47
+ camera.position.set(x, y, z);
48
+ camera.lookAt(target);
49
+ camera.updateProjectionMatrix(); // After changing properties
50
+ ```
51
+
52
+ ### Geometries
53
+
54
+ ```javascript
55
+ // Primitive geometries
56
+ const box = new THREE.BoxGeometry(1, 1, 1);
57
+ const sphere = new THREE.SphereGeometry(1, 32, 32);
58
+ const plane = new THREE.PlaneGeometry(1, 1);
59
+ const cylinder = new THREE.CylinderGeometry(1, 1, 2, 32);
60
+
61
+ // Custom geometry
62
+ const geometry = new THREE.BufferGeometry();
63
+ geometry.setAttribute("position", new THREE.BufferAttribute(vertices, 3));
64
+ geometry.setAttribute("normal", new THREE.BufferAttribute(normals, 3));
65
+ geometry.setAttribute("uv", new THREE.BufferAttribute(uvs, 2));
66
+ geometry.setIndex(indices);
67
+ ```
68
+
69
+ ### Materials
70
+
71
+ ```javascript
72
+ // Basic materials
73
+ const basic = new THREE.MeshBasicMaterial({ color: 0xff0000 });
74
+ const lambert = new THREE.MeshLambertMaterial({ color: 0x00ff00 });
75
+ const phong = new THREE.MeshPhongMaterial({ color: 0x0000ff });
76
+
77
+ // PBR materials (most realistic)
78
+ const standard = new THREE.MeshStandardMaterial({
79
+ color: 0xffffff,
80
+ metalness: 0.5,
81
+ roughness: 0.5,
82
+ map: texture,
83
+ normalMap: normalTexture,
84
+ envMap: environmentTexture,
85
+ });
86
+
87
+ const physical = new THREE.MeshPhysicalMaterial({
88
+ ...standard,
89
+ clearcoat: 1.0,
90
+ transmission: 0.5,
91
+ thickness: 1.0,
92
+ });
93
+ ```
94
+
95
+ ### Lights
96
+
97
+ ```javascript
98
+ // Ambient (global illumination)
99
+ const ambient = new THREE.AmbientLight(0xffffff, 0.6);
100
+
101
+ // Directional (sun-like)
102
+ const directional = new THREE.DirectionalLight(0xffffff, 1);
103
+ directional.position.set(1, 1, 1);
104
+ directional.castShadow = true;
105
+
106
+ // Point (bulb-like)
107
+ const point = new THREE.PointLight(0xffffff, 1, 100);
108
+ point.position.set(0, 10, 0);
109
+
110
+ // Spot (flashlight-like)
111
+ const spot = new THREE.SpotLight(0xffffff, 1, 100, Math.PI / 4);
112
+ ```
113
+
114
+ ### Textures
115
+
116
+ ```javascript
117
+ // Texture loading
118
+ const loader = new THREE.TextureLoader();
119
+ const texture = loader.load("path/to/texture.jpg");
120
+
121
+ // Texture properties
122
+ texture.wrapS = THREE.RepeatWrapping;
123
+ texture.wrapT = THREE.RepeatWrapping;
124
+ texture.repeat.set(2, 2);
125
+ texture.flipY = false;
126
+
127
+ // HDR textures
128
+ const hdrLoader = new THREE.HDRLoader();
129
+ const envMap = hdrLoader.load("environment.hdr");
130
+ envMap.mapping = THREE.EquirectangularReflectionMapping;
131
+ ```
132
+
133
+ ## Object3D Fundamentals
134
+
135
+ ### Transform Properties
136
+
137
+ ```javascript
138
+ // Position
139
+ object.position.set(x, y, z);
140
+ object.position.copy(otherObject.position);
141
+ object.translateX(distance);
142
+
143
+ // Rotation (Euler angles)
144
+ object.rotation.set(x, y, z);
145
+ object.rotation.y = Math.PI / 4;
146
+ object.rotateY(Math.PI / 4);
147
+
148
+ // Scale
149
+ object.scale.set(2, 2, 2);
150
+ object.scale.multiplyScalar(0.5);
151
+
152
+ // Quaternion (preferred for animations)
153
+ object.quaternion.setFromAxisAngle(axis, angle);
154
+ object.lookAt(target);
155
+ ```
156
+
157
+ ### Hierarchy Operations
158
+
159
+ ```javascript
160
+ // Adding/removing children
161
+ parent.add(child);
162
+ parent.remove(child);
163
+ scene.add(mesh, light, helper);
164
+
165
+ // Traversal
166
+ object.traverse((child) => {
167
+ if (child.isMesh) {
168
+ child.material.wireframe = true;
169
+ }
170
+ });
171
+
172
+ // Finding objects
173
+ const found = scene.getObjectByName("myObject");
174
+ const found = scene.getObjectById(id);
175
+ ```
176
+
177
+ ## Math Utilities
178
+
179
+ ### Vectors
180
+
181
+ ```javascript
182
+ // Vector3 (most common)
183
+ const v = new THREE.Vector3(1, 2, 3);
184
+ v.add(otherVector);
185
+ v.multiplyScalar(2);
186
+ v.normalize();
187
+ v.cross(otherVector);
188
+ v.dot(otherVector);
189
+ v.distanceTo(otherVector);
190
+
191
+ // Vector2 (UV coordinates)
192
+ const uv = new THREE.Vector2(0.5, 0.5);
193
+ ```
194
+
195
+ ### Matrices
196
+
197
+ ```javascript
198
+ // Matrix4 (transformations)
199
+ const matrix = new THREE.Matrix4();
200
+ matrix.makeTranslation(x, y, z);
201
+ matrix.makeRotationY(angle);
202
+ matrix.makeScale(x, y, z);
203
+ matrix.multiply(otherMatrix);
204
+
205
+ // Apply to object
206
+ object.applyMatrix4(matrix);
207
+ ```
208
+
209
+ ### Colors
210
+
211
+ ```javascript
212
+ const color = new THREE.Color();
213
+ color.set(0xff0000); // hex
214
+ color.setRGB(1, 0, 0); // RGB values 0-1
215
+ color.setHSL(0, 1, 0.5); // HSL values
216
+ color.lerp(targetColor, 0.1); // interpolation
217
+ ```
218
+
219
+ ## Raycasting (Mouse Interaction)
220
+
221
+ ```javascript
222
+ const raycaster = new THREE.Raycaster();
223
+ const mouse = new THREE.Vector2();
224
+
225
+ function onMouseClick(event) {
226
+ // Normalize mouse coordinates (-1 to +1)
227
+ mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
228
+ mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
229
+
230
+ // Cast ray from camera through mouse position
231
+ raycaster.setFromCamera(mouse, camera);
232
+
233
+ // Find intersections
234
+ const intersects = raycaster.intersectObjects(scene.children, true);
235
+
236
+ if (intersects.length > 0) {
237
+ const object = intersects[0].object;
238
+ const point = intersects[0].point;
239
+ // Handle intersection
240
+ }
241
+ }
242
+ ```
243
+
244
+ ## Animation System
245
+
246
+ ### Animation Mixer
247
+
248
+ ```javascript
249
+ // For GLTF animations
250
+ const mixer = new THREE.AnimationMixer(model);
251
+ const action = mixer.clipAction(animationClip);
252
+ action.play();
253
+
254
+ // Update in render loop
255
+ function animate() {
256
+ const delta = clock.getDelta();
257
+ mixer.update(delta);
258
+ renderer.render(scene, camera);
259
+ }
260
+ ```
261
+
262
+ ### Manual Animation
263
+
264
+ ```javascript
265
+ const clock = new THREE.Clock();
266
+
267
+ function animate() {
268
+ const time = clock.getElapsedTime();
269
+
270
+ // Rotate object
271
+ mesh.rotation.y = time * 0.5;
272
+
273
+ // Oscillate position
274
+ mesh.position.y = Math.sin(time) * 2;
275
+
276
+ renderer.render(scene, camera);
277
+ }
278
+ renderer.setAnimationLoop(animate);
279
+ ```
280
+
281
+ ## Loading Assets
282
+
283
+ ### GLTF Models (Recommended)
284
+
285
+ ```javascript
286
+ import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
287
+
288
+ const loader = new GLTFLoader();
289
+ loader.load("model.gltf", (gltf) => {
290
+ const model = gltf.scene;
291
+ scene.add(model);
292
+
293
+ // Access animations
294
+ if (gltf.animations.length > 0) {
295
+ const mixer = new THREE.AnimationMixer(model);
296
+ gltf.animations.forEach((clip) => {
297
+ mixer.clipAction(clip).play();
298
+ });
299
+ }
300
+ });
301
+ ```
302
+
303
+ ### Other Loaders
304
+
305
+ ```javascript
306
+ // OBJ files
307
+ import { OBJLoader } from "three/addons/loaders/OBJLoader.js";
308
+
309
+ // FBX files
310
+ import { FBXLoader } from "three/addons/loaders/FBXLoader.js";
311
+
312
+ // Textures
313
+ const textureLoader = new THREE.TextureLoader();
314
+ const cubeLoader = new THREE.CubeTextureLoader();
315
+ ```
316
+
317
+ ## Renderer Configuration
318
+
319
+ ### Basic Setup
320
+
321
+ ```javascript
322
+ const renderer = new THREE.WebGLRenderer({
323
+ canvas: canvasElement, // Existing canvas
324
+ antialias: true, // Smooth edges
325
+ alpha: true, // Transparent background
326
+ powerPreference: "high-performance",
327
+ });
328
+
329
+ renderer.setSize(width, height);
330
+ renderer.setPixelRatio(window.devicePixelRatio);
331
+ renderer.setClearColor(0x000000, 1);
332
+ ```
333
+
334
+ ### Advanced Settings
335
+
336
+ ```javascript
337
+ // Shadows
338
+ renderer.shadowMap.enabled = true;
339
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap;
340
+
341
+ // Tone mapping (HDR)
342
+ renderer.toneMapping = THREE.ACESFilmicToneMapping;
343
+ renderer.toneMappingExposure = 1.0;
344
+
345
+ // Color space
346
+ renderer.outputColorSpace = THREE.SRGBColorSpace;
347
+
348
+ // Performance
349
+ renderer.setAnimationLoop(animate); // Preferred over requestAnimationFrame
350
+ ```
351
+
352
+ ## Common Patterns
353
+
354
+ ### Responsive Canvas
355
+
356
+ ```javascript
357
+ function onWindowResize() {
358
+ camera.aspect = window.innerWidth / window.innerHeight;
359
+ camera.updateProjectionMatrix();
360
+ renderer.setSize(window.innerWidth, window.innerHeight);
361
+ }
362
+ window.addEventListener("resize", onWindowResize);
363
+ ```
364
+
365
+ ### Performance Optimization
366
+
367
+ ```javascript
368
+ // Frustum culling
369
+ object.frustumCulled = true;
370
+
371
+ // LOD (Level of Detail)
372
+ const lod = new THREE.LOD();
373
+ lod.addLevel(highDetailMesh, 0);
374
+ lod.addLevel(lowDetailMesh, 100);
375
+
376
+ // Instancing for many objects
377
+ const instancedMesh = new THREE.InstancedMesh(geometry, material, count);
378
+ const matrix = new THREE.Matrix4();
379
+ for (let i = 0; i < count; i++) {
380
+ matrix.setPosition(x, y, z);
381
+ instancedMesh.setMatrixAt(i, matrix);
382
+ }
383
+ instancedMesh.instanceMatrix.needsUpdate = true;
384
+ ```
385
+
386
+ ### Dispose Pattern (Memory Management)
387
+
388
+ ```javascript
389
+ // Clean up resources
390
+ geometry.dispose();
391
+ material.dispose();
392
+ texture.dispose();
393
+ renderer.dispose();
394
+
395
+ // Traverse and dispose
396
+ object.traverse((child) => {
397
+ if (child.geometry) child.geometry.dispose();
398
+ if (child.material) {
399
+ if (Array.isArray(child.material)) {
400
+ child.material.forEach((m) => m.dispose());
401
+ } else {
402
+ child.material.dispose();
403
+ }
404
+ }
405
+ });
406
+ ```
407
+
408
+ ## Buffer Attributes (Advanced)
409
+
410
+ ### Custom Geometry Data
411
+
412
+ ```javascript
413
+ const geometry = new THREE.BufferGeometry();
414
+
415
+ // Vertex positions (required)
416
+ const positions = new Float32Array([
417
+ -1,
418
+ -1,
419
+ 0, // vertex 0
420
+ 1,
421
+ -1,
422
+ 0, // vertex 1
423
+ 0,
424
+ 1,
425
+ 0, // vertex 2
426
+ ]);
427
+ geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
428
+
429
+ // Vertex colors
430
+ const colors = new Float32Array([
431
+ 1,
432
+ 0,
433
+ 0, // red
434
+ 0,
435
+ 1,
436
+ 0, // green
437
+ 0,
438
+ 0,
439
+ 1, // blue
440
+ ]);
441
+ geometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));
442
+
443
+ // Custom attributes for shaders
444
+ const customData = new Float32Array(vertexCount);
445
+ geometry.setAttribute(
446
+ "customAttribute",
447
+ new THREE.BufferAttribute(customData, 1),
448
+ );
449
+ ```
450
+
451
+ ## Events and Interaction
452
+
453
+ ### Event Dispatcher
454
+
455
+ ```javascript
456
+ // Custom events
457
+ const emitter = new THREE.EventDispatcher();
458
+
459
+ emitter.addEventListener("customEvent", (event) => {
460
+ console.log("Event fired:", event.data);
461
+ });
462
+
463
+ emitter.dispatchEvent({ type: "customEvent", data: "hello" });
464
+ ```
465
+
466
+ ### Built-in Events
467
+
468
+ ```javascript
469
+ // Loading progress
470
+ loader.onProgress = (progress) => {
471
+ console.log(`Loading: ${(progress.loaded / progress.total) * 100}%`);
472
+ };
473
+
474
+ // Window resize
475
+ window.addEventListener("resize", onWindowResize);
476
+
477
+ // Mouse events
478
+ canvas.addEventListener("click", onMouseClick);
479
+ canvas.addEventListener("mousemove", onMouseMove);
480
+ ```
481
+
482
+ ## Constants Reference
483
+
484
+ ### Material Constants
485
+
486
+ ```javascript
487
+ // Blending modes
488
+ THREE.NormalBlending;
489
+ THREE.AdditiveBlending;
490
+ THREE.SubtractiveBlending;
491
+ THREE.MultiplyBlending;
492
+
493
+ // Culling
494
+ THREE.FrontSide;
495
+ THREE.BackSide;
496
+ THREE.DoubleSide;
497
+
498
+ // Depth modes
499
+ THREE.NeverDepth;
500
+ THREE.AlwaysDepth;
501
+ THREE.LessDepth;
502
+ THREE.LessEqualDepth;
503
+ ```
504
+
505
+ ### Texture Constants
506
+
507
+ ```javascript
508
+ // Wrapping
509
+ THREE.RepeatWrapping;
510
+ THREE.ClampToEdgeWrapping;
511
+ THREE.MirroredRepeatWrapping;
512
+
513
+ // Filtering
514
+ THREE.NearestFilter;
515
+ THREE.LinearFilter;
516
+ THREE.NearestMipmapNearestFilter;
517
+ THREE.LinearMipmapLinearFilter;
518
+
519
+ // Formats
520
+ THREE.RGBAFormat;
521
+ THREE.RGBFormat;
522
+ THREE.RedFormat;
523
+ ```
524
+
525
+ ### Rendering Constants
526
+
527
+ ```javascript
528
+ // Shadow types
529
+ THREE.BasicShadowMap;
530
+ THREE.PCFShadowMap;
531
+ THREE.PCFSoftShadowMap;
532
+ THREE.VSMShadowMap;
533
+
534
+ // Tone mapping
535
+ THREE.NoToneMapping;
536
+ THREE.LinearToneMapping;
537
+ THREE.ReinhardToneMapping;
538
+ THREE.CineonToneMapping;
539
+ THREE.ACESFilmicToneMapping;
540
+ ```
541
+
542
+ ## Common Gotchas
543
+
544
+ ### Matrix Updates
545
+
546
+ ```javascript
547
+ // Force matrix update after transform changes
548
+ object.updateMatrix();
549
+ object.updateMatrixWorld();
550
+
551
+ // Automatic updates (default: true)
552
+ object.matrixAutoUpdate = false; // Manual control
553
+ ```
554
+
555
+ ### Geometry Modifications
556
+
557
+ ```javascript
558
+ // After modifying geometry attributes
559
+ geometry.attributes.position.needsUpdate = true;
560
+ geometry.computeBoundingSphere();
561
+ geometry.computeBoundingBox();
562
+ ```
563
+
564
+ ### Material Updates
565
+
566
+ ```javascript
567
+ // After changing material properties
568
+ material.needsUpdate = true;
569
+
570
+ // Texture updates
571
+ texture.needsUpdate = true;
572
+ ```
573
+
574
+ ## Performance Tips
575
+
576
+ ### Efficient Rendering
577
+
578
+ ```javascript
579
+ // Batch similar objects
580
+ const geometry = new THREE.InstancedBufferGeometry();
581
+ const material = new THREE.MeshStandardMaterial();
582
+ const instancedMesh = new THREE.InstancedMesh(geometry, material, 1000);
583
+
584
+ // Freeze objects that don't move
585
+ object.matrixAutoUpdate = false;
586
+ object.updateMatrix();
587
+
588
+ // Use appropriate geometry detail
589
+ const sphere = new THREE.SphereGeometry(1, 8, 6); // Low poly
590
+ const sphere = new THREE.SphereGeometry(1, 32, 32); // High poly
591
+ ```
592
+
593
+ ### Memory Management
594
+
595
+ ```javascript
596
+ // Remove from scene
597
+ scene.remove(object);
598
+
599
+ // Dispose resources
600
+ object.traverse((child) => {
601
+ if (child.geometry) child.geometry.dispose();
602
+ if (child.material) child.material.dispose();
603
+ });
604
+
605
+ // Clear references
606
+ object = null;
607
+ ```
608
+
609
+ ## Quick Reference
610
+
611
+ ### Essential Imports
612
+
613
+ ```javascript
614
+ // Core
615
+ import * as THREE from "three";
616
+
617
+ // Controls
618
+ import { OrbitControls } from "three/addons/controls/OrbitControls.js";
619
+ import { FlyControls } from "three/addons/controls/FlyControls.js";
620
+
621
+ // Loaders
622
+ import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
623
+ import { OBJLoader } from "three/addons/loaders/OBJLoader.js";
624
+
625
+ // Post-processing
626
+ import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";
627
+
628
+ // Helpers
629
+ import { GUI } from "three/addons/libs/lil-gui.module.min.js";
630
+ import Stats from "three/addons/libs/stats.module.js";
631
+ ```
632
+
633
+ ### Minimal Working Example
634
+
635
+ ```javascript
636
+ import * as THREE from "three";
637
+
638
+ const scene = new THREE.Scene();
639
+ const camera = new THREE.PerspectiveCamera(
640
+ 75,
641
+ window.innerWidth / window.innerHeight,
642
+ );
643
+ const renderer = new THREE.WebGLRenderer();
644
+
645
+ renderer.setSize(window.innerWidth, window.innerHeight);
646
+ document.body.appendChild(renderer.domElement);
647
+
648
+ const geometry = new THREE.BoxGeometry();
649
+ const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
650
+ const cube = new THREE.Mesh(geometry, material);
651
+ scene.add(cube);
652
+
653
+ camera.position.z = 5;
654
+
655
+ function animate() {
656
+ cube.rotation.x += 0.01;
657
+ cube.rotation.y += 0.01;
658
+ renderer.render(scene, camera);
659
+ }
660
+ renderer.setAnimationLoop(animate);
661
+ ```
662
+
663
+ ---
664
+
665
+ # Three.js Condensed Guide: Most Impressive Examples
666
+
667
+ _A curated collection of Three.js's most visually stunning and technically advanced examples_
668
+
669
+ ## Quick Start Template
670
+
671
+ ```javascript
672
+ import * as THREE from "three";
673
+ import { OrbitControls } from "three/addons/controls/OrbitControls.js";
674
+
675
+ // Basic setup
676
+ const scene = new THREE.Scene();
677
+ const camera = new THREE.PerspectiveCamera(
678
+ 75,
679
+ window.innerWidth / window.innerHeight,
680
+ 0.1,
681
+ 1000,
682
+ );
683
+ const renderer = new THREE.WebGLRenderer({ antialias: true });
684
+ renderer.setSize(window.innerWidth, window.innerHeight);
685
+ document.body.appendChild(renderer.domElement);
686
+
687
+ // Controls
688
+ const controls = new OrbitControls(camera, renderer.domElement);
689
+ camera.position.set(5, 5, 5);
690
+ controls.update();
691
+
692
+ // Animation loop
693
+ function animate() {
694
+ controls.update();
695
+ renderer.render(scene, camera);
696
+ }
697
+ renderer.setAnimationLoop(animate);
698
+ ```
699
+
700
+ ## 1. Spectacular Visual Effects
701
+
702
+ ### Galaxy Generator (WebGPU + TSL)
703
+
704
+ Creates a procedural spiral galaxy with thousands of animated particles.
705
+
706
+ ```javascript
707
+ import * as THREE from "three/webgpu";
708
+ import { color, cos, sin, time, uniform, range, vec3, PI2 } from "three/tsl";
709
+
710
+ const material = new THREE.SpriteNodeMaterial({
711
+ depthWrite: false,
712
+ blending: THREE.AdditiveBlending,
713
+ });
714
+
715
+ // Procedural galaxy structure
716
+ const radiusRatio = range(0, 1);
717
+ const radius = radiusRatio.pow(1.5).mul(5);
718
+ const branches = 3;
719
+ const branchAngle = range(0, branches).floor().mul(PI2.div(branches));
720
+ const angle = branchAngle.add(time.mul(radiusRatio.oneMinus()));
721
+
722
+ const position = vec3(cos(angle), 0, sin(angle)).mul(radius);
723
+ material.positionNode = position.add(randomOffset);
724
+
725
+ // Dynamic colors
726
+ const colorInside = uniform(color("#ffa575"));
727
+ const colorOutside = uniform(color("#311599"));
728
+ material.colorNode = mix(colorInside, colorOutside, radiusRatio);
729
+
730
+ const galaxy = new THREE.InstancedMesh(
731
+ new THREE.PlaneGeometry(1, 1),
732
+ material,
733
+ 20000,
734
+ );
735
+ ```
736
+
737
+ ### Ocean Shaders
738
+
739
+ Realistic water simulation with dynamic waves and sky reflections.
740
+
741
+ ```javascript
742
+ import { Water } from "three/addons/objects/Water.js";
743
+ import { Sky } from "three/addons/objects/Sky.js";
744
+
745
+ const waterGeometry = new THREE.PlaneGeometry(10000, 10000);
746
+ const water = new Water(waterGeometry, {
747
+ textureWidth: 512,
748
+ textureHeight: 512,
749
+ waterNormals: new THREE.TextureLoader().load("textures/waternormals.jpg"),
750
+ sunDirection: new THREE.Vector3(),
751
+ sunColor: 0xffffff,
752
+ waterColor: 0x001e0f,
753
+ distortionScale: 3.7,
754
+ });
755
+
756
+ // Sky system
757
+ const sky = new Sky();
758
+ sky.scale.setScalar(10000);
759
+ const skyUniforms = sky.material.uniforms;
760
+ skyUniforms["turbidity"].value = 10;
761
+ skyUniforms["rayleigh"].value = 2;
762
+ ```
763
+
764
+ ### Unreal Bloom Effect
765
+
766
+ Cinematic glow and HDR post-processing.
767
+
768
+ ```javascript
769
+ import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";
770
+ import { RenderPass } from "three/addons/postprocessing/RenderPass.js";
771
+ import { UnrealBloomPass } from "three/addons/postprocessing/UnrealBloomPass.js";
772
+
773
+ const composer = new EffectComposer(renderer);
774
+ const renderPass = new RenderPass(scene, camera);
775
+ composer.addPass(renderPass);
776
+
777
+ const bloomPass = new UnrealBloomPass(
778
+ new THREE.Vector2(window.innerWidth, window.innerHeight),
779
+ 1.5, // strength
780
+ 0.4, // radius
781
+ 0.85, // threshold
782
+ );
783
+ composer.addPass(bloomPass);
784
+
785
+ // Render with bloom
786
+ composer.render();
787
+ ```
788
+
789
+ ## 2. Advanced GPU Computing
790
+
791
+ ### Flocking Birds (GPGPU)
792
+
793
+ GPU-accelerated boid simulation with emergent flocking behavior.
794
+
795
+ ```javascript
796
+ // Position computation shader
797
+ const fragmentShaderPosition = `
798
+ uniform float time;
799
+ uniform float delta;
800
+
801
+ void main() {
802
+ vec2 uv = gl_FragCoord.xy / resolution.xy;
803
+ vec4 tmpPos = texture2D(texturePosition, uv);
804
+ vec3 position = tmpPos.xyz;
805
+ vec3 velocity = texture2D(textureVelocity, uv).xyz;
806
+
807
+ gl_FragColor = vec4(position + velocity * delta * 15.0, tmpPos.w);
808
+ }`;
809
+
810
+ // Velocity computation (separation, alignment, cohesion)
811
+ const fragmentShaderVelocity = `
812
+ uniform float separationDistance;
813
+ uniform float alignmentDistance;
814
+ uniform float cohesionDistance;
815
+ uniform vec3 predator;
816
+
817
+ void main() {
818
+ // Boid algorithm implementation
819
+ // ...separation, alignment, cohesion logic
820
+ }`;
821
+ ```
822
+
823
+ ### Cloth Physics (WebGPU Compute)
824
+
825
+ Real-time fabric simulation using compute shaders.
826
+
827
+ ```javascript
828
+ import { Fn, uniform, attribute, Loop } from "three/tsl";
829
+
830
+ // Verlet integration in compute shader
831
+ const computeVertexForces = Fn(() => {
832
+ const position = attribute("position");
833
+ const velocity = attribute("velocity");
834
+
835
+ // Spring forces, wind, gravity
836
+ const force = uniform("wind").add(uniform("gravity"));
837
+
838
+ // Verlet integration
839
+ const newPosition = position.add(velocity.mul(uniform("deltaTime")));
840
+
841
+ return newPosition;
842
+ })();
843
+
844
+ const clothMaterial = new THREE.MeshPhysicalMaterial({
845
+ color: 0x204080,
846
+ roughness: 0.8,
847
+ transmission: 0.2,
848
+ sheen: 0.5,
849
+ });
850
+ ```
851
+
852
+ ## 3. Impressive 3D Scenes
853
+
854
+ ### Photorealistic Car
855
+
856
+ Advanced PBR materials with interactive customization.
857
+
858
+ ```javascript
859
+ import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
860
+ import { HDRLoader } from "three/addons/loaders/HDRLoader.js";
861
+
862
+ // Environment setup
863
+ scene.environment = new HDRLoader().load(
864
+ "textures/equirectangular/venice_sunset_1k.hdr",
865
+ );
866
+ renderer.toneMapping = THREE.ACESFilmicToneMapping;
867
+ renderer.toneMappingExposure = 0.85;
868
+
869
+ // Load car model
870
+ const loader = new GLTFLoader();
871
+ const gltf = await loader.loadAsync("models/gltf/ferrari.glb");
872
+
873
+ // Material customization
874
+ gltf.scene.traverse((child) => {
875
+ if (child.isMesh && child.material.name === "body") {
876
+ child.material.color.setHex(bodyColor);
877
+ child.material.metalness = 1.0;
878
+ child.material.roughness = 0.5;
879
+ child.material.clearcoat = 1.0;
880
+ }
881
+ });
882
+ ```
883
+
884
+ ### Minecraft World Generator
885
+
886
+ Procedural voxel terrain with optimized geometry merging.
887
+
888
+ ```javascript
889
+ import { ImprovedNoise } from "three/addons/math/ImprovedNoise.js";
890
+ import * as BufferGeometryUtils from "three/addons/utils/BufferGeometryUtils.js";
891
+
892
+ function generateTerrain(width, depth) {
893
+ const noise = new ImprovedNoise();
894
+ const data = [];
895
+
896
+ for (let x = 0; x < width; x++) {
897
+ for (let z = 0; z < depth; z++) {
898
+ // Multi-octave noise
899
+ const height =
900
+ noise.noise(x / 100, z / 100, 0) * 50 +
901
+ noise.noise(x / 50, z / 50, 0) * 25;
902
+ data.push(Math.floor(height));
903
+ }
904
+ }
905
+
906
+ return data;
907
+ }
908
+
909
+ // Merge geometries for performance
910
+ const geometries = [];
911
+ // ...create individual cube geometries
912
+ const mergedGeometry = BufferGeometryUtils.mergeGeometries(geometries);
913
+ ```
914
+
915
+ ## 4. Interactive Experiences
916
+
917
+ ### VR Painting
918
+
919
+ Virtual reality 3D painting with hand tracking.
920
+
921
+ ```javascript
922
+ // WebXR setup
923
+ renderer.xr.enabled = true;
924
+ document.body.appendChild(VRButton.createButton(renderer));
925
+
926
+ // Hand input
927
+ const controller1 = renderer.xr.getController(0);
928
+ const controller2 = renderer.xr.getController(1);
929
+
930
+ controller1.addEventListener("selectstart", onSelectStart);
931
+ controller1.addEventListener("selectend", onSelectEnd);
932
+
933
+ function onSelectStart(event) {
934
+ // Start painting stroke
935
+ const geometry = new THREE.BufferGeometry();
936
+ const material = new THREE.LineBasicMaterial({
937
+ color: currentColor,
938
+ linewidth: brushSize,
939
+ });
940
+ const line = new THREE.Line(geometry, material);
941
+ scene.add(line);
942
+ }
943
+ ```
944
+
945
+ ### Physics Vehicle Controller
946
+
947
+ Real-time vehicle physics with Rapier.js integration.
948
+
949
+ ```javascript
950
+ import { World } from "@dimforge/rapier3d-compat";
951
+
952
+ // Physics world
953
+ const world = new World({ x: 0, y: -9.81, z: 0 });
954
+
955
+ // Vehicle setup
956
+ const vehicleDesc = world.createRigidBody({
957
+ type: "dynamic",
958
+ translation: { x: 0, y: 1, z: 0 },
959
+ });
960
+
961
+ // Wheel constraints
962
+ wheels.forEach((wheel, index) => {
963
+ const wheelJoint = world.createImpulseJoint(
964
+ vehicleDesc,
965
+ wheel.body,
966
+ wheelConstraints[index],
967
+ );
968
+ });
969
+ ```
970
+
971
+ ## 5. Cutting-Edge WebGPU Features
972
+
973
+ ### Path Tracing
974
+
975
+ Realistic ray-traced lighting with global illumination.
976
+
977
+ ```javascript
978
+ import { PathTracingRenderer } from "three/addons/renderers/PathTracingRenderer.js";
979
+
980
+ const ptRenderer = new PathTracingRenderer(renderer);
981
+ ptRenderer.setSize(window.innerWidth, window.innerHeight);
982
+
983
+ // Progressive rendering
984
+ let sampleCount = 0;
985
+ function animate() {
986
+ if (sampleCount < 1000) {
987
+ ptRenderer.update();
988
+ sampleCount++;
989
+ }
990
+ }
991
+ ```
992
+
993
+ ### TSL (Three.js Shading Language)
994
+
995
+ Modern node-based shader programming.
996
+
997
+ ```javascript
998
+ import { mix, noise, time, uv, vec3, sin, cos } from "three/tsl";
999
+
1000
+ // Procedural materials with TSL
1001
+ const proceduralMaterial = new THREE.MeshStandardNodeMaterial();
1002
+
1003
+ // Animated noise texture
1004
+ const noiseValue = noise(uv().mul(10).add(time.mul(0.1)));
1005
+ const colorA = vec3(1, 0.5, 0.2);
1006
+ const colorB = vec3(0.2, 0.5, 1);
1007
+
1008
+ proceduralMaterial.colorNode = mix(colorA, colorB, noiseValue);
1009
+ proceduralMaterial.roughnessNode = noiseValue.mul(0.5).add(0.3);
1010
+ ```
1011
+
1012
+ ## Performance Tips for Impressive Results
1013
+
1014
+ ### Instancing for Massive Scenes
1015
+
1016
+ ```javascript
1017
+ const instancedMesh = new THREE.InstancedMesh(geometry, material, 100000);
1018
+ const matrix = new THREE.Matrix4();
1019
+
1020
+ for (let i = 0; i < instancedMesh.count; i++) {
1021
+ matrix.setPosition(
1022
+ Math.random() * 2000 - 1000,
1023
+ Math.random() * 2000 - 1000,
1024
+ Math.random() * 2000 - 1000,
1025
+ );
1026
+ instancedMesh.setMatrixAt(i, matrix);
1027
+ }
1028
+ ```
1029
+
1030
+ ### LOD for Complex Models
1031
+
1032
+ ```javascript
1033
+ const lod = new THREE.LOD();
1034
+ lod.addLevel(highDetailMesh, 0);
1035
+ lod.addLevel(mediumDetailMesh, 50);
1036
+ lod.addLevel(lowDetailMesh, 200);
1037
+ ```
1038
+
1039
+ ### Render Targets for Effects
1040
+
1041
+ ```javascript
1042
+ const renderTarget = new THREE.WebGLRenderTarget(1024, 1024);
1043
+ renderer.setRenderTarget(renderTarget);
1044
+ renderer.render(effectScene, effectCamera);
1045
+ renderer.setRenderTarget(null);
1046
+
1047
+ // Use render target as texture
1048
+ material.map = renderTarget.texture;
1049
+ ```
1050
+
1051
+ ## Essential Setup for Maximum Impact
1052
+
1053
+ ### HDR Environment
1054
+
1055
+ ```javascript
1056
+ import { HDRLoader } from "three/addons/loaders/HDRLoader.js";
1057
+
1058
+ const hdrTexture = new HDRLoader().load("environment.hdr");
1059
+ hdrTexture.mapping = THREE.EquirectangularReflectionMapping;
1060
+ scene.environment = hdrTexture;
1061
+ scene.background = hdrTexture;
1062
+ ```
1063
+
1064
+ ### Tone Mapping
1065
+
1066
+ ```javascript
1067
+ renderer.toneMapping = THREE.ACESFilmicToneMapping;
1068
+ renderer.toneMappingExposure = 1.0;
1069
+ renderer.outputColorSpace = THREE.SRGBColorSpace;
1070
+ ```
1071
+
1072
+ ### Post-Processing Chain
1073
+
1074
+ ```javascript
1075
+ import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";
1076
+
1077
+ const composer = new EffectComposer(renderer);
1078
+ composer.addPass(new RenderPass(scene, camera));
1079
+ composer.addPass(new UnrealBloomPass(resolution, strength, radius, threshold));
1080
+ composer.addPass(new OutputPass());
1081
+ ```
1082
+
1083
+ ---
1084
+
1085
+ _This guide focuses on Three.js's most impressive capabilities. Each example demonstrates advanced techniques that create visually stunning results with minimal code complexity._
1086
+
1087
+ # Real world example
1088
+
1089
+ ```javascript
1090
+ import React, { useState, useEffect, useRef, useCallback } from "react";
1091
+ import { useFireproof } from "use-fireproof";
1092
+ import * as THREE from "three";
1093
+
1094
+ export default function SkyGlider() {
1095
+ const { database, useLiveQuery } = useFireproof("sky-glider-scores");
1096
+ const canvasRef = useRef(null);
1097
+ const gameStateRef = useRef({
1098
+ scene: null,
1099
+ camera: null,
1100
+ renderer: null,
1101
+ glider: null,
1102
+ clouds: [],
1103
+ coins: [],
1104
+ glowEffects: [],
1105
+ smokeTrail: [],
1106
+ lastSmokeTime: 0,
1107
+ score: 0,
1108
+ gameRunning: false,
1109
+ keys: {},
1110
+ velocity: { x: 0, y: 0, z: 0 },
1111
+ heading: 0,
1112
+ forwardSpeed: 0,
1113
+ pitch: 0,
1114
+ roll: 0,
1115
+ });
1116
+
1117
+ const [currentScore, setCurrentScore] = useState(0);
1118
+ const { docs: scoreData } = useLiveQuery("type", { key: "score" }) || {
1119
+ docs: [],
1120
+ };
1121
+
1122
+ const saveScore = useCallback(
1123
+ async (score) => {
1124
+ await database.put({
1125
+ _id: `score-${Date.now()}`,
1126
+ type: "score",
1127
+ value: score,
1128
+ timestamp: Date.now(),
1129
+ });
1130
+ },
1131
+ [database],
1132
+ );
1133
+
1134
+ const createGlowEffect = useCallback((position) => {
1135
+ const state = gameStateRef.current;
1136
+ if (!state.scene) return;
1137
+
1138
+ const glowSphere = new THREE.Mesh(
1139
+ new THREE.SphereGeometry(8, 16, 16),
1140
+ new THREE.MeshBasicMaterial({
1141
+ color: 0xffd670,
1142
+ transparent: true,
1143
+ opacity: 0.8,
1144
+ }),
1145
+ );
1146
+
1147
+ glowSphere.position.copy(position);
1148
+ state.scene.add(glowSphere);
1149
+
1150
+ const glowEffect = {
1151
+ mesh: glowSphere,
1152
+ createdAt: Date.now(),
1153
+ scale: 1,
1154
+ };
1155
+
1156
+ state.glowEffects.push(glowEffect);
1157
+
1158
+ // Remove after animation
1159
+ setTimeout(() => {
1160
+ state.scene.remove(glowSphere);
1161
+ const index = state.glowEffects.indexOf(glowEffect);
1162
+ if (index > -1) state.glowEffects.splice(index, 1);
1163
+ }, 1000);
1164
+ }, []);
1165
+
1166
+ const createSmokeCloud = useCallback((position) => {
1167
+ const state = gameStateRef.current;
1168
+ if (!state.scene) return;
1169
+
1170
+ const smokeGeometry = new THREE.SphereGeometry(
1171
+ 0.1 + Math.random() * 0.05,
1172
+ 4,
1173
+ 3,
1174
+ );
1175
+ const smokeMaterial = new THREE.MeshLambertMaterial({
1176
+ color: 0x242424,
1177
+ transparent: true,
1178
+ opacity: 0.7 + Math.random() * 0.2,
1179
+ });
1180
+ const smokeCloud = new THREE.Mesh(smokeGeometry, smokeMaterial);
1181
+
1182
+ // Position behind the glider
1183
+ const heading = state.heading;
1184
+ const offsetX = Math.sin(heading) * -4;
1185
+ const offsetZ = Math.cos(heading) * -4;
1186
+
1187
+ smokeCloud.position.set(
1188
+ position.x + offsetX + (Math.random() - 0.5) * 0.2,
1189
+ position.y - 0.2 + (Math.random() - 0.5) * 0.1,
1190
+ position.z + offsetZ + Math.random() * 0.3,
1191
+ );
1192
+
1193
+ state.scene.add(smokeCloud);
1194
+ state.smokeTrail.push({
1195
+ mesh: smokeCloud,
1196
+ createdAt: Date.now(),
1197
+ });
1198
+
1199
+ // Keep trail manageable
1200
+ while (state.smokeTrail.length > 100) {
1201
+ const oldSmoke = state.smokeTrail.shift();
1202
+ state.scene.remove(oldSmoke.mesh);
1203
+ }
1204
+ }, []);
1205
+
1206
+ const createTexturedCoin = useCallback((scene, position) => {
1207
+ // Create procedural gold texture
1208
+ const canvas = document.createElement("canvas");
1209
+ canvas.width = canvas.height = 128;
1210
+ const ctx = canvas.getContext("2d");
1211
+
1212
+ // Gold gradient
1213
+ const gradient = ctx.createRadialGradient(64, 64, 20, 64, 64, 64);
1214
+ gradient.addColorStop(0, "#ffd670");
1215
+ gradient.addColorStop(0.5, "#ff9770");
1216
+ gradient.addColorStop(1, "#ffb347");
1217
+ ctx.fillStyle = gradient;
1218
+ ctx.fillRect(0, 0, 128, 128);
1219
+
1220
+ // Add metallic shine lines
1221
+ ctx.strokeStyle = "#ffffff";
1222
+ ctx.lineWidth = 2;
1223
+ ctx.setLineDash([5, 3]);
1224
+ for (let i = 0; i < 8; i++) {
1225
+ const angle = (i / 8) * Math.PI * 2;
1226
+ ctx.beginPath();
1227
+ ctx.moveTo(64 + Math.cos(angle) * 30, 64 + Math.sin(angle) * 30);
1228
+ ctx.lineTo(64 + Math.cos(angle) * 50, 64 + Math.sin(angle) * 50);
1229
+ ctx.stroke();
1230
+ }
1231
+
1232
+ const texture = new THREE.CanvasTexture(canvas);
1233
+ const coin = new THREE.Mesh(
1234
+ new THREE.CylinderGeometry(2, 2, 0.3, 16),
1235
+ new THREE.MeshStandardMaterial({
1236
+ map: texture,
1237
+ metalness: 0.8,
1238
+ roughness: 0.2,
1239
+ }),
1240
+ );
1241
+
1242
+ coin.position.copy(position);
1243
+ coin.rotation.z = Math.PI / 2;
1244
+ scene.add(coin);
1245
+
1246
+ return {
1247
+ mesh: coin,
1248
+ collected: false,
1249
+ rotation: Math.random() * 0.02 + 0.01,
1250
+ };
1251
+ }, []);
1252
+
1253
+ const initThreeJS = useCallback(() => {
1254
+ if (!canvasRef.current) return;
1255
+
1256
+ const scene = new THREE.Scene();
1257
+ scene.background = new THREE.Color(0x70d6ff);
1258
+ scene.fog = new THREE.Fog(0x70d6ff, 50, 300);
1259
+
1260
+ const camera = new THREE.PerspectiveCamera(
1261
+ 75,
1262
+ window.innerWidth / window.innerHeight,
1263
+ 0.1,
1264
+ 1000,
1265
+ );
1266
+ camera.position.set(0, 10, 20);
1267
+
1268
+ const renderer = new THREE.WebGLRenderer({
1269
+ canvas: canvasRef.current,
1270
+ antialias: true,
1271
+ });
1272
+ renderer.setSize(window.innerWidth, window.innerHeight);
1273
+
1274
+ // Lighting
1275
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
1276
+ scene.add(ambientLight);
1277
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
1278
+ directionalLight.position.set(50, 100, 50);
1279
+ scene.add(directionalLight);
1280
+
1281
+ // Glider
1282
+ const glider = new THREE.Group();
1283
+ const body = new THREE.Mesh(
1284
+ new THREE.ConeGeometry(2, 8, 3),
1285
+ new THREE.MeshLambertMaterial({ color: 0xff70a6 }),
1286
+ );
1287
+ body.rotation.x = Math.PI / 2;
1288
+ glider.add(body);
1289
+
1290
+ glider.position.set(0, 10, 0);
1291
+ scene.add(glider);
1292
+
1293
+ // Create simple clouds
1294
+ const clouds = [];
1295
+ for (let i = 0; i < 30; i++) {
1296
+ const cloud = new THREE.Mesh(
1297
+ new THREE.SphereGeometry(Math.random() * 5 + 3, 8, 6),
1298
+ new THREE.MeshLambertMaterial({
1299
+ color: 0xffffff,
1300
+ transparent: true,
1301
+ opacity: 0.7,
1302
+ }),
1303
+ );
1304
+ cloud.position.set(
1305
+ (Math.random() - 0.5) * 400,
1306
+ Math.random() * 30 + 10,
1307
+ (Math.random() - 0.5) * 400,
1308
+ );
1309
+ scene.add(cloud);
1310
+ clouds.push({
1311
+ mesh: cloud,
1312
+ drift: {
1313
+ x: (Math.random() - 0.5) * 0.01,
1314
+ y: 0,
1315
+ z: (Math.random() - 0.5) * 0.01,
1316
+ },
1317
+ });
1318
+ }
1319
+
1320
+ // Create initial coins
1321
+ const coins = [];
1322
+ for (let i = 0; i < 20; i++) {
1323
+ const coin = createTexturedCoin(
1324
+ scene,
1325
+ new THREE.Vector3(
1326
+ (Math.random() - 0.5) * 200,
1327
+ Math.random() * 40 + 10,
1328
+ (Math.random() - 0.5) * 200,
1329
+ ),
1330
+ );
1331
+ coins.push(coin);
1332
+ }
1333
+
1334
+ gameStateRef.current = {
1335
+ ...gameStateRef.current,
1336
+ scene,
1337
+ camera,
1338
+ renderer,
1339
+ glider,
1340
+ clouds,
1341
+ coins,
1342
+ lastSmokeTime: Date.now(),
1343
+ heading: 0,
1344
+ forwardSpeed: 0.1,
1345
+ pitch: 0,
1346
+ roll: 0,
1347
+ };
1348
+
1349
+ const gameLoop = () => {
1350
+ if (gameStateRef.current.gameRunning) {
1351
+ updateGame();
1352
+ requestAnimationFrame(gameLoop);
1353
+ }
1354
+ };
1355
+ gameStateRef.current.gameRunning = true;
1356
+ gameLoop();
1357
+ }, [createTexturedCoin]);
1358
+
1359
+ const checkCoinCollisions = useCallback(() => {
1360
+ const state = gameStateRef.current;
1361
+ if (!state.glider) return;
1362
+
1363
+ state.coins.forEach((coin) => {
1364
+ if (!coin.collected) {
1365
+ const distance = state.glider.position.distanceTo(coin.mesh.position);
1366
+ if (distance < 4) {
1367
+ coin.collected = true;
1368
+ coin.mesh.visible = false;
1369
+ createGlowEffect(coin.mesh.position);
1370
+ state.score += 1;
1371
+ setCurrentScore(state.score);
1372
+
1373
+ // Respawn coin at random location
1374
+ setTimeout(() => {
1375
+ coin.mesh.position.set(
1376
+ (Math.random() - 0.5) * 200,
1377
+ Math.random() * 40 + 10,
1378
+ (Math.random() - 0.5) * 200,
1379
+ );
1380
+ coin.mesh.visible = true;
1381
+ coin.collected = false;
1382
+ }, 5000);
1383
+ }
1384
+ }
1385
+ });
1386
+ }, [createGlowEffect]);
1387
+
1388
+ const handleKeyDown = useCallback((event) => {
1389
+ if (event.code === "Space") event.preventDefault();
1390
+ gameStateRef.current.keys[event.code] = true;
1391
+ }, []);
1392
+
1393
+ const handleKeyUp = useCallback((event) => {
1394
+ if (event.code === "Space") event.preventDefault();
1395
+ gameStateRef.current.keys[event.code] = false;
1396
+ }, []);
1397
+
1398
+ const updateGame = useCallback(() => {
1399
+ const state = gameStateRef.current;
1400
+ if (!state.gameRunning || !state.glider) return;
1401
+
1402
+ const { keys, glider } = state;
1403
+
1404
+ // Controls
1405
+ if (keys["ArrowLeft"] || keys["KeyA"]) state.heading += 0.03;
1406
+ if (keys["ArrowRight"] || keys["KeyD"]) state.heading -= 0.03;
1407
+ if (keys["ArrowUp"] || keys["KeyW"]) state.pitch += 0.01;
1408
+ if (keys["ArrowDown"] || keys["KeyS"]) state.pitch -= 0.01;
1409
+ if (keys["Space"])
1410
+ state.forwardSpeed = Math.min(0.3, state.forwardSpeed + 0.005);
1411
+
1412
+ // Physics
1413
+ state.forwardSpeed = Math.max(0.05, state.forwardSpeed * 0.995);
1414
+ state.velocity.x =
1415
+ Math.sin(state.heading) * Math.cos(state.pitch) * state.forwardSpeed;
1416
+ state.velocity.y = Math.sin(-state.pitch) * state.forwardSpeed;
1417
+ state.velocity.z =
1418
+ Math.cos(state.heading) * Math.cos(state.pitch) * state.forwardSpeed;
1419
+
1420
+ glider.position.add(
1421
+ new THREE.Vector3(state.velocity.x, state.velocity.y, state.velocity.z),
1422
+ );
1423
+
1424
+ // Point glider in thrust vector direction
1425
+ const thrustDirection = new THREE.Vector3(
1426
+ state.velocity.x,
1427
+ state.velocity.y,
1428
+ state.velocity.z,
1429
+ ).normalize();
1430
+ if (thrustDirection.length() > 0) {
1431
+ glider.lookAt(glider.position.clone().add(thrustDirection));
1432
+ }
1433
+
1434
+ // Camera follow
1435
+ const cameraDistance = 15;
1436
+ state.camera.position.set(
1437
+ glider.position.x - Math.sin(state.heading) * cameraDistance,
1438
+ glider.position.y + 10,
1439
+ glider.position.z - Math.cos(state.heading) * cameraDistance,
1440
+ );
1441
+ state.camera.lookAt(glider.position);
1442
+
1443
+ // Create smoke trail
1444
+ const currentTime = Date.now();
1445
+ const timeSinceLastSmoke = currentTime - state.lastSmokeTime;
1446
+ const smokeInterval = 150 + Math.random() * 200;
1447
+
1448
+ if (timeSinceLastSmoke > smokeInterval) {
1449
+ createSmokeCloud(glider.position);
1450
+ state.lastSmokeTime = currentTime;
1451
+ }
1452
+
1453
+ // Animate
1454
+ checkCoinCollisions();
1455
+ state.coins.forEach((coin) => {
1456
+ if (!coin.collected) coin.mesh.rotation.y += coin.rotation;
1457
+ });
1458
+ state.clouds.forEach((cloud) => {
1459
+ cloud.mesh.position.add(
1460
+ new THREE.Vector3(cloud.drift.x, cloud.drift.y, cloud.drift.z),
1461
+ );
1462
+ });
1463
+
1464
+ // Animate glow effects
1465
+ state.glowEffects.forEach((effect) => {
1466
+ const age = Date.now() - effect.createdAt;
1467
+ const progress = age / 1000;
1468
+ effect.scale = 1 + progress * 2;
1469
+ effect.mesh.scale.setScalar(effect.scale);
1470
+ effect.mesh.material.opacity = 0.8 * (1 - progress);
1471
+ });
1472
+
1473
+ // Fade smoke trail
1474
+ state.smokeTrail.forEach((smoke) => {
1475
+ const age = currentTime - smoke.createdAt;
1476
+ const maxAge = 15000;
1477
+ if (age > maxAge) {
1478
+ smoke.mesh.material.opacity = 0;
1479
+ } else if (age > 7500) {
1480
+ const fadeProgress = (age - 7500) / 7500;
1481
+ smoke.mesh.material.opacity =
1482
+ (0.7 + Math.random() * 0.2) * (1 - fadeProgress);
1483
+ }
1484
+ });
1485
+
1486
+ state.renderer.render(state.scene, state.camera);
1487
+ }, [checkCoinCollisions, createSmokeCloud]);
1488
+
1489
+ useEffect(() => {
1490
+ initThreeJS();
1491
+ window.addEventListener("keydown", handleKeyDown);
1492
+ window.addEventListener("keyup", handleKeyUp);
1493
+ return () => {
1494
+ window.removeEventListener("keydown", handleKeyDown);
1495
+ window.removeEventListener("keyup", handleKeyUp);
1496
+ gameStateRef.current.gameRunning = false;
1497
+ };
1498
+ }, [initThreeJS, handleKeyDown, handleKeyUp]);
1499
+
1500
+ return (
1501
+ <div className="relative h-screen w-full bg-sky-200">
1502
+ <canvas ref={canvasRef} className="absolute inset-0" />
1503
+ <div className="absolute top-4 left-4 rounded bg-white p-4 shadow">
1504
+ <h2 className="text-lg font-bold">Sky Glider</h2>
1505
+ <p>Score: {currentScore}</p>
1506
+ <p className="mt-2 text-sm">WASD/Arrows: Fly, Space: Thrust</p>
1507
+ </div>
1508
+ {scoreData.length > 0 && (
1509
+ <div className="absolute top-4 right-4 rounded bg-white p-4 shadow">
1510
+ <h3 className="font-bold">High Scores</h3>
1511
+ {scoreData
1512
+ .sort((a, b) => b.value - a.value)
1513
+ .slice(0, 3)
1514
+ .map((score, i) => (
1515
+ <div key={score._id} className="text-sm">
1516
+ #{i + 1}: {score.value}
1517
+ </div>
1518
+ ))}
1519
+ </div>
1520
+ )}
1521
+ </div>
1522
+ );
1523
+ }
1524
+ ```
1525
+
1526
+ # Visual effects example
1527
+
1528
+ ```javascript
1529
+ import React, { useState, useEffect, useRef, useCallback } from "react";
1530
+ import { useFireproof } from "use-fireproof";
1531
+ import * as THREE from "three";
1532
+ import { OrbitControls } from "three/addons/controls/OrbitControls.js";
1533
+ import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";
1534
+ import { RenderPass } from "three/addons/postprocessing/RenderPass.js";
1535
+ import { HalftonePass } from "three/addons/postprocessing/HalftonePass.js";
1536
+
1537
+ export default function HalftoneArtStudio() {
1538
+ const { database, useLiveQuery } = useFireproof("halftone-studio");
1539
+ const canvasRef = useRef(null);
1540
+ const sceneRef = useRef(null);
1541
+ const [currentPreset, setCurrentPreset] = useState(null);
1542
+ const [presetName, setPresetName] = useState("");
1543
+ const [isGenerating, setIsGenerating] = useState(false);
1544
+ const [showParameters, setShowParameters] = useState(false);
1545
+
1546
+ const { docs: presets } = useLiveQuery("type", { key: "preset" }) || {
1547
+ docs: [],
1548
+ };
1549
+ const { docs: parameterHistory } = useLiveQuery("type", {
1550
+ key: "parameter-state",
1551
+ }) || {
1552
+ docs: [],
1553
+ };
1554
+
1555
+ const [parameters, setParameters] = useState({
1556
+ shape: 1, // 1=Dot, 2=Ellipse, 3=Line, 4=Square
1557
+ radius: 4,
1558
+ rotateR: 15,
1559
+ rotateG: 30,
1560
+ rotateB: 45,
1561
+ scatter: 0,
1562
+ blending: 1,
1563
+ blendingMode: 1, // 1=Linear, 2=Multiply, 3=Add, 4=Lighter, 5=Darker
1564
+ greyscale: false,
1565
+ disable: false,
1566
+ objectCount: 25,
1567
+ rotationSpeed: 1,
1568
+ colorTheme: 0, // 0=Rainbow, 1=Warm, 2=Cool, 3=Monochrome
1569
+ });
1570
+
1571
+ const saveParameterState = useCallback(
1572
+ async (params, action = "manual") => {
1573
+ await database.put({
1574
+ _id: `param-state-${Date.now()}`,
1575
+ type: "parameter-state",
1576
+ parameters: { ...params },
1577
+ action,
1578
+ timestamp: Date.now(),
1579
+ });
1580
+ },
1581
+ [database],
1582
+ );
1583
+
1584
+ const savePreset = useCallback(async () => {
1585
+ if (!presetName.trim()) return;
1586
+
1587
+ await database.put({
1588
+ _id: `preset-${Date.now()}`,
1589
+ type: "preset",
1590
+ name: presetName,
1591
+ parameters: { ...parameters },
1592
+ timestamp: Date.now(),
1593
+ });
1594
+
1595
+ setPresetName("");
1596
+ }, [database, presetName, parameters]);
1597
+
1598
+ const loadPreset = useCallback((preset) => {
1599
+ setParameters({ ...preset.parameters });
1600
+ setCurrentPreset(preset);
1601
+ }, []);
1602
+
1603
+ const loadParameterState = useCallback((state) => {
1604
+ setParameters({ ...state.parameters });
1605
+ }, []);
1606
+
1607
+ const generateRandomScene = useCallback(async () => {
1608
+ setIsGenerating(true);
1609
+
1610
+ // Save current state before randomizing
1611
+ await saveParameterState(parameters, "before-randomize");
1612
+
1613
+ // Generate random parameters
1614
+ const newParams = {
1615
+ shape: Math.floor(Math.random() * 4) + 1,
1616
+ radius: Math.random() * 20 + 2,
1617
+ rotateR: Math.random() * 90,
1618
+ rotateG: Math.random() * 90,
1619
+ rotateB: Math.random() * 90,
1620
+ scatter: Math.random(),
1621
+ blending: Math.random(),
1622
+ blendingMode: Math.floor(Math.random() * 5) + 1,
1623
+ greyscale: Math.random() > 0.7,
1624
+ disable: false,
1625
+ objectCount: Math.floor(Math.random() * 40) + 10,
1626
+ rotationSpeed: Math.random() * 3 + 0.5,
1627
+ colorTheme: Math.floor(Math.random() * 4),
1628
+ };
1629
+
1630
+ setParameters(newParams);
1631
+
1632
+ // Save the new randomized state
1633
+ setTimeout(async () => {
1634
+ await saveParameterState(newParams, "randomized");
1635
+ setIsGenerating(false);
1636
+ }, 500);
1637
+ }, [parameters, saveParameterState]);
1638
+
1639
+ // Save parameter changes for history
1640
+ useEffect(() => {
1641
+ const timeoutId = setTimeout(() => {
1642
+ saveParameterState(parameters, "manual");
1643
+ }, 1000);
1644
+
1645
+ return () => clearTimeout(timeoutId);
1646
+ }, [parameters, saveParameterState]);
1647
+
1648
+ useEffect(() => {
1649
+ if (!canvasRef.current) return;
1650
+
1651
+ // Scene setup
1652
+ const scene = new THREE.Scene();
1653
+ scene.background = new THREE.Color(0x242424);
1654
+
1655
+ const camera = new THREE.PerspectiveCamera(
1656
+ 75,
1657
+ window.innerWidth / window.innerHeight,
1658
+ 1,
1659
+ 1000,
1660
+ );
1661
+ camera.position.z = 12;
1662
+
1663
+ const renderer = new THREE.WebGLRenderer({
1664
+ canvas: canvasRef.current,
1665
+ antialias: true,
1666
+ preserveDrawingBuffer: true,
1667
+ });
1668
+ renderer.setPixelRatio(window.devicePixelRatio);
1669
+ renderer.setSize(window.innerWidth, window.innerHeight);
1670
+
1671
+ // Controls
1672
+ const controls = new OrbitControls(camera, renderer.domElement);
1673
+ controls.enableDamping = true;
1674
+ controls.dampingFactor = 0.05;
1675
+ controls.autoRotate = true;
1676
+ controls.autoRotateSpeed = 0.5;
1677
+
1678
+ // Group for all objects
1679
+ const group = new THREE.Group();
1680
+ scene.add(group);
1681
+
1682
+ // Lighting
1683
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
1684
+ scene.add(ambientLight);
1685
+
1686
+ const pointLight = new THREE.PointLight(0xffffff, 1, 100);
1687
+ pointLight.position.set(10, 10, 10);
1688
+ scene.add(pointLight);
1689
+
1690
+ // Post-processing
1691
+ const composer = new EffectComposer(renderer);
1692
+ const renderPass = new RenderPass(scene, camera);
1693
+ composer.addPass(renderPass);
1694
+
1695
+ const halftonePass = new HalftonePass({
1696
+ shape: parameters.shape,
1697
+ radius: parameters.radius,
1698
+ rotateR: parameters.rotateR * (Math.PI / 180),
1699
+ rotateG: parameters.rotateG * (Math.PI / 180),
1700
+ rotateB: parameters.rotateB * (Math.PI / 180),
1701
+ scatter: parameters.scatter,
1702
+ blending: parameters.blending,
1703
+ blendingMode: parameters.blendingMode,
1704
+ greyscale: parameters.greyscale,
1705
+ disable: parameters.disable,
1706
+ });
1707
+ composer.addPass(halftonePass);
1708
+
1709
+ // Store refs
1710
+ sceneRef.current = {
1711
+ scene,
1712
+ camera,
1713
+ renderer,
1714
+ composer,
1715
+ halftonePass,
1716
+ group,
1717
+ controls,
1718
+ objects: [],
1719
+ };
1720
+
1721
+ // Create initial objects
1722
+ const createObjects = () => {
1723
+ // Clear existing objects
1724
+ sceneRef.current.objects.forEach((obj) => {
1725
+ group.remove(obj);
1726
+ });
1727
+ sceneRef.current.objects = [];
1728
+
1729
+ // Color themes
1730
+ const colorThemes = [
1731
+ [0xff70a6, 0x70d6ff, 0xffd670, 0xe9ff70, 0xff9770], // Rainbow
1732
+ [0xff9770, 0xffd670, 0xff70a6], // Warm
1733
+ [0x70d6ff, 0xe9ff70, 0x242424], // Cool
1734
+ [0xffffff, 0x242424], // Monochrome
1735
+ ];
1736
+
1737
+ const colors = colorThemes[parameters.colorTheme] || colorThemes[0];
1738
+
1739
+ // Shader material for interesting effects
1740
+ const material = new THREE.ShaderMaterial({
1741
+ uniforms: {
1742
+ time: { value: 0 },
1743
+ },
1744
+ vertexShader: `
1745
+ varying vec2 vUv;
1746
+ varying vec3 vNormal;
1747
+ varying vec3 vPosition;
1748
+ uniform float time;
1749
+
1750
+ void main() {
1751
+ vUv = uv;
1752
+ vNormal = normalize(normalMatrix * normal);
1753
+ vPosition = position;
1754
+
1755
+ vec3 pos = position;
1756
+ pos += sin(pos * 2.0 + time) * 0.1;
1757
+
1758
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
1759
+ }
1760
+ `,
1761
+ fragmentShader: `
1762
+ varying vec2 vUv;
1763
+ varying vec3 vNormal;
1764
+ varying vec3 vPosition;
1765
+ uniform float time;
1766
+
1767
+ void main() {
1768
+ vec3 color = abs(vNormal) + vec3(vUv, sin(time + vPosition.x));
1769
+ color = mix(color, vec3(1.0, 0.4, 0.6), sin(time + vPosition.y) * 0.5 + 0.5);
1770
+ gl_FragColor = vec4(color, 1.0);
1771
+ }
1772
+ `,
1773
+ });
1774
+
1775
+ // Create various geometric shapes
1776
+ const geometries = [
1777
+ new THREE.BoxGeometry(2, 2, 2),
1778
+ new THREE.SphereGeometry(1.2, 16, 16),
1779
+ new THREE.ConeGeometry(1, 2, 8),
1780
+ new THREE.CylinderGeometry(0.8, 0.8, 2, 8),
1781
+ new THREE.OctahedronGeometry(1.2),
1782
+ new THREE.TetrahedronGeometry(1.5),
1783
+ new THREE.DodecahedronGeometry(1),
1784
+ new THREE.IcosahedronGeometry(1.2),
1785
+ ];
1786
+
1787
+ for (let i = 0; i < parameters.objectCount; i++) {
1788
+ const geometry =
1789
+ geometries[Math.floor(Math.random() * geometries.length)];
1790
+ const basicMaterial = new THREE.MeshPhongMaterial({
1791
+ color: colors[Math.floor(Math.random() * colors.length)],
1792
+ shininess: 100,
1793
+ transparent: true,
1794
+ opacity: 0.8 + Math.random() * 0.2,
1795
+ });
1796
+
1797
+ const mesh = new THREE.Mesh(
1798
+ geometry,
1799
+ Math.random() > 0.3 ? basicMaterial : material,
1800
+ );
1801
+
1802
+ mesh.position.set(
1803
+ (Math.random() - 0.5) * 20,
1804
+ (Math.random() - 0.5) * 20,
1805
+ (Math.random() - 0.5) * 20,
1806
+ );
1807
+
1808
+ mesh.rotation.set(
1809
+ Math.random() * Math.PI * 2,
1810
+ Math.random() * Math.PI * 2,
1811
+ Math.random() * Math.PI * 2,
1812
+ );
1813
+
1814
+ mesh.scale.setScalar(0.5 + Math.random() * 1.5);
1815
+
1816
+ group.add(mesh);
1817
+ sceneRef.current.objects.push(mesh);
1818
+ }
1819
+ };
1820
+
1821
+ createObjects();
1822
+
1823
+ // Animation loop
1824
+ const clock = new THREE.Clock();
1825
+ const animate = () => {
1826
+ const delta = clock.getDelta();
1827
+ const elapsed = clock.getElapsedTime();
1828
+
1829
+ // Update material uniforms
1830
+ sceneRef.current.objects.forEach((obj) => {
1831
+ if (obj.material.uniforms && obj.material.uniforms.time) {
1832
+ obj.material.uniforms.time.value = elapsed;
1833
+ }
1834
+
1835
+ // Animate objects
1836
+ obj.rotation.x += delta * parameters.rotationSpeed * 0.2;
1837
+ obj.rotation.y += delta * parameters.rotationSpeed * 0.3;
1838
+ obj.rotation.z += delta * parameters.rotationSpeed * 0.1;
1839
+ });
1840
+
1841
+ controls.update();
1842
+ composer.render();
1843
+ requestAnimationFrame(animate);
1844
+ };
1845
+
1846
+ animate();
1847
+
1848
+ // Handle resize
1849
+ const handleResize = () => {
1850
+ camera.aspect = window.innerWidth / window.innerHeight;
1851
+ camera.updateProjectionMatrix();
1852
+ renderer.setSize(window.innerWidth, window.innerHeight);
1853
+ composer.setSize(window.innerWidth, window.innerHeight);
1854
+ };
1855
+
1856
+ window.addEventListener("resize", handleResize);
1857
+
1858
+ return () => {
1859
+ window.removeEventListener("resize", handleResize);
1860
+ renderer.dispose();
1861
+ };
1862
+ }, [parameters]);
1863
+
1864
+ // Update halftone parameters
1865
+ useEffect(() => {
1866
+ if (sceneRef.current?.halftonePass) {
1867
+ const pass = sceneRef.current.halftonePass;
1868
+ pass.uniforms.shape.value = parameters.shape;
1869
+ pass.uniforms.radius.value = parameters.radius;
1870
+ pass.uniforms.rotateR.value = parameters.rotateR * (Math.PI / 180);
1871
+ pass.uniforms.rotateG.value = parameters.rotateG * (Math.PI / 180);
1872
+ pass.uniforms.rotateB.value = parameters.rotateB * (Math.PI / 180);
1873
+ pass.uniforms.scatter.value = parameters.scatter;
1874
+ pass.uniforms.blending.value = parameters.blending;
1875
+ pass.uniforms.blendingMode.value = parameters.blendingMode;
1876
+ pass.uniforms.greyscale.value = parameters.greyscale;
1877
+ pass.uniforms.disable.value = parameters.disable;
1878
+ }
1879
+ }, [parameters]);
1880
+
1881
+ const shapeName =
1882
+ ["", "Dot", "Ellipse", "Line", "Square"][parameters.shape] || "Dot";
1883
+ const blendModeName =
1884
+ ["", "Linear", "Multiply", "Add", "Lighter", "Darker"][
1885
+ parameters.blendingMode
1886
+ ] || "Linear";
1887
+ const actionNames = {
1888
+ "before-randomize": "🎲 Before Random",
1889
+ randomized: "✨ Randomized",
1890
+ manual: "✏️ Manual Edit",
1891
+ };
1892
+
1893
+ return (
1894
+ <div className="relative h-screen w-full overflow-hidden bg-[#242424]">
1895
+ {/* Background pattern */}
1896
+ <div
1897
+ className="absolute inset-0 opacity-10"
1898
+ style={{
1899
+ backgroundImage: `radial-gradient(circle at 25px 25px, #70d6ff 2px, transparent 2px)`,
1900
+ backgroundSize: "50px 50px",
1901
+ }}
1902
+ />
1903
+
1904
+ <canvas ref={canvasRef} className="absolute inset-0" />
1905
+
1906
+ {/* Main Control Panel */}
1907
+ <div
1908
+ className={`absolute top-4 left-4 max-h-[calc(100vh-2rem)] overflow-y-auto rounded-lg border-4 border-[#242424] bg-[#ffffff] p-4 shadow-lg transition-all duration-300 ${showParameters ? "w-80" : "w-64"}`}
1909
+ >
1910
+ <h2 className="mb-4 text-lg font-bold text-[#242424]">
1911
+ RGB Halftone Studio
1912
+ </h2>
1913
+
1914
+ {/* Always visible controls */}
1915
+ <div className="mb-4 space-y-3">
1916
+ <button
1917
+ onClick={generateRandomScene}
1918
+ disabled={isGenerating}
1919
+ className="w-full rounded border-2 border-[#242424] bg-[#ff70a6] px-4 py-3 font-bold text-[#242424] hover:bg-[#ff9770] disabled:opacity-50"
1920
+ >
1921
+ {isGenerating ? "Generating..." : "🎲 Random Art"}
1922
+ </button>
1923
+
1924
+ <button
1925
+ onClick={() => setShowParameters(!showParameters)}
1926
+ className="w-full rounded border-2 border-[#242424] bg-[#70d6ff] px-4 py-2 font-bold text-[#242424] hover:bg-[#e9ff70]"
1927
+ >
1928
+ {showParameters ? "🔼 Hide Controls" : "🔽 Show Controls"}
1929
+ </button>
1930
+ </div>
1931
+
1932
+ {/* Expandable parameter controls */}
1933
+ {showParameters && (
1934
+ <div className="space-y-4">
1935
+ {/* Shape Controls */}
1936
+ <div>
1937
+ <label className="mb-2 block text-sm font-bold text-[#242424]">
1938
+ Shape: {shapeName}
1939
+ </label>
1940
+ <select
1941
+ value={parameters.shape}
1942
+ onChange={(e) =>
1943
+ setParameters((prev) => ({
1944
+ ...prev,
1945
+ shape: parseInt(e.target.value),
1946
+ }))
1947
+ }
1948
+ className="w-full rounded border-2 border-[#242424] p-2 text-[#242424]"
1949
+ >
1950
+ <option value={1}>Dot</option>
1951
+ <option value={2}>Ellipse</option>
1952
+ <option value={3}>Line</option>
1953
+ <option value={4}>Square</option>
1954
+ </select>
1955
+ </div>
1956
+
1957
+ {/* Size Controls */}
1958
+ <div>
1959
+ <label className="mb-2 block text-sm font-bold text-[#242424]">
1960
+ Size: {parameters.radius.toFixed(1)}
1961
+ </label>
1962
+ <input
1963
+ type="range"
1964
+ min="1"
1965
+ max="25"
1966
+ step="0.5"
1967
+ value={parameters.radius}
1968
+ onChange={(e) =>
1969
+ setParameters((prev) => ({
1970
+ ...prev,
1971
+ radius: parseFloat(e.target.value),
1972
+ }))
1973
+ }
1974
+ className="w-full"
1975
+ />
1976
+ </div>
1977
+
1978
+ {/* Color Rotation */}
1979
+ <div className="grid grid-cols-3 gap-2">
1980
+ <div>
1981
+ <label className="mb-1 block text-xs font-bold text-[#ff70a6]">
1982
+ Red: {parameters.rotateR.toFixed(0)}°
1983
+ </label>
1984
+ <input
1985
+ type="range"
1986
+ min="0"
1987
+ max="90"
1988
+ value={parameters.rotateR}
1989
+ onChange={(e) =>
1990
+ setParameters((prev) => ({
1991
+ ...prev,
1992
+ rotateR: parseFloat(e.target.value),
1993
+ }))
1994
+ }
1995
+ className="w-full"
1996
+ />
1997
+ </div>
1998
+ <div>
1999
+ <label className="mb-1 block text-xs font-bold text-[#e9ff70]">
2000
+ Green: {parameters.rotateG.toFixed(0)}°
2001
+ </label>
2002
+ <input
2003
+ type="range"
2004
+ min="0"
2005
+ max="90"
2006
+ value={parameters.rotateG}
2007
+ onChange={(e) =>
2008
+ setParameters((prev) => ({
2009
+ ...prev,
2010
+ rotateG: parseFloat(e.target.value),
2011
+ }))
2012
+ }
2013
+ className="w-full"
2014
+ />
2015
+ </div>
2016
+ <div>
2017
+ <label className="mb-1 block text-xs font-bold text-[#70d6ff]">
2018
+ Blue: {parameters.rotateB.toFixed(0)}°
2019
+ </label>
2020
+ <input
2021
+ type="range"
2022
+ min="0"
2023
+ max="90"
2024
+ value={parameters.rotateB}
2025
+ onChange={(e) =>
2026
+ setParameters((prev) => ({
2027
+ ...prev,
2028
+ rotateB: parseFloat(e.target.value),
2029
+ }))
2030
+ }
2031
+ className="w-full"
2032
+ />
2033
+ </div>
2034
+ </div>
2035
+
2036
+ {/* Effects */}
2037
+ <div>
2038
+ <label className="mb-2 block text-sm font-bold text-[#242424]">
2039
+ Scatter: {(parameters.scatter * 100).toFixed(0)}%
2040
+ </label>
2041
+ <input
2042
+ type="range"
2043
+ min="0"
2044
+ max="1"
2045
+ step="0.01"
2046
+ value={parameters.scatter}
2047
+ onChange={(e) =>
2048
+ setParameters((prev) => ({
2049
+ ...prev,
2050
+ scatter: parseFloat(e.target.value),
2051
+ }))
2052
+ }
2053
+ className="w-full"
2054
+ />
2055
+ </div>
2056
+
2057
+ <div>
2058
+ <label className="mb-2 block text-sm font-bold text-[#242424]">
2059
+ Blend: {(parameters.blending * 100).toFixed(0)}%
2060
+ </label>
2061
+ <input
2062
+ type="range"
2063
+ min="0"
2064
+ max="1"
2065
+ step="0.01"
2066
+ value={parameters.blending}
2067
+ onChange={(e) =>
2068
+ setParameters((prev) => ({
2069
+ ...prev,
2070
+ blending: parseFloat(e.target.value),
2071
+ }))
2072
+ }
2073
+ className="w-full"
2074
+ />
2075
+ </div>
2076
+
2077
+ <div>
2078
+ <label className="mb-2 block text-sm font-bold text-[#242424]">
2079
+ Blend Mode: {blendModeName}
2080
+ </label>
2081
+ <select
2082
+ value={parameters.blendingMode}
2083
+ onChange={(e) =>
2084
+ setParameters((prev) => ({
2085
+ ...prev,
2086
+ blendingMode: parseInt(e.target.value),
2087
+ }))
2088
+ }
2089
+ className="w-full rounded border-2 border-[#242424] p-2 text-[#242424]"
2090
+ >
2091
+ <option value={1}>Linear</option>
2092
+ <option value={2}>Multiply</option>
2093
+ <option value={3}>Add</option>
2094
+ <option value={4}>Lighter</option>
2095
+ <option value={5}>Darker</option>
2096
+ </select>
2097
+ </div>
2098
+
2099
+ {/* Toggles */}
2100
+ <div className="space-y-2">
2101
+ <label className="flex items-center">
2102
+ <input
2103
+ type="checkbox"
2104
+ checked={parameters.greyscale}
2105
+ onChange={(e) =>
2106
+ setParameters((prev) => ({
2107
+ ...prev,
2108
+ greyscale: e.target.checked,
2109
+ }))
2110
+ }
2111
+ className="mr-2"
2112
+ />
2113
+ <span className="text-sm font-bold text-[#242424]">
2114
+ Greyscale
2115
+ </span>
2116
+ </label>
2117
+
2118
+ <label className="flex items-center">
2119
+ <input
2120
+ type="checkbox"
2121
+ checked={parameters.disable}
2122
+ onChange={(e) =>
2123
+ setParameters((prev) => ({
2124
+ ...prev,
2125
+ disable: e.target.checked,
2126
+ }))
2127
+ }
2128
+ className="mr-2"
2129
+ />
2130
+ <span className="text-sm font-bold text-[#242424]">
2131
+ Disable Effect
2132
+ </span>
2133
+ </label>
2134
+ </div>
2135
+
2136
+ {/* Save Preset */}
2137
+ <div>
2138
+ <input
2139
+ type="text"
2140
+ placeholder="Preset name..."
2141
+ value={presetName}
2142
+ onChange={(e) => setPresetName(e.target.value)}
2143
+ className="mb-2 w-full rounded border-2 border-[#242424] p-2 text-[#242424]"
2144
+ />
2145
+ <button
2146
+ onClick={savePreset}
2147
+ disabled={!presetName.trim()}
2148
+ className="w-full rounded border-2 border-[#242424] bg-[#ffd670] px-4 py-2 font-bold text-[#242424] hover:bg-[#e9ff70] disabled:opacity-50"
2149
+ >
2150
+ 💾 Save Preset
2151
+ </button>
2152
+ </div>
2153
+
2154
+ {/* Saved Presets */}
2155
+ {presets.length > 0 && (
2156
+ <div>
2157
+ <h4 className="mb-2 text-sm font-bold text-[#242424]">
2158
+ 💾 Saved Presets
2159
+ </h4>
2160
+ <div className="max-h-32 space-y-2 overflow-y-auto">
2161
+ {presets
2162
+ .sort((a, b) => b.timestamp - a.timestamp)
2163
+ .map((preset) => (
2164
+ <div
2165
+ key={preset._id}
2166
+ className={`cursor-pointer rounded border-2 p-2 transition-colors ${
2167
+ currentPreset?._id === preset._id
2168
+ ? "border-[#242424] bg-[#ff70a6]"
2169
+ : "border-[#242424] bg-[#ffffff] hover:bg-[#e9ff70]"
2170
+ }`}
2171
+ onClick={() => loadPreset(preset)}
2172
+ >
2173
+ <div className="text-xs font-bold text-[#242424]">
2174
+ {preset.name}
2175
+ </div>
2176
+ <div className="text-xs text-[#242424] opacity-75">
2177
+ {
2178
+ ["", "Dot", "Ellipse", "Line", "Square"][
2179
+ preset.parameters.shape
2180
+ ]
2181
+ }{" "}
2182
+ • {preset.parameters.greyscale ? "B&W" : "Color"}
2183
+ </div>
2184
+ </div>
2185
+ ))}
2186
+ </div>
2187
+ </div>
2188
+ )}
2189
+
2190
+ {/* Parameter History */}
2191
+ {parameterHistory.length > 0 && (
2192
+ <div>
2193
+ <h4 className="mb-2 text-sm font-bold text-[#242424]">
2194
+ 📜 Parameter History
2195
+ </h4>
2196
+ <div className="max-h-40 space-y-2 overflow-y-auto">
2197
+ {parameterHistory
2198
+ .sort((a, b) => b.timestamp - a.timestamp)
2199
+ .slice(0, 10)
2200
+ .map((state) => (
2201
+ <div
2202
+ key={state._id}
2203
+ className="cursor-pointer rounded border-2 border-[#242424] p-2 transition-colors hover:bg-[#e9ff70]"
2204
+ onClick={() => loadParameterState(state)}
2205
+ >
2206
+ <div className="text-xs font-bold text-[#242424]">
2207
+ {actionNames[state.action] || "⚙️ Unknown"}
2208
+ </div>
2209
+ <div className="text-xs text-[#242424] opacity-75">
2210
+ {
2211
+ ["", "Dot", "Ellipse", "Line", "Square"][
2212
+ state.parameters.shape
2213
+ ]
2214
+ }{" "}
2215
+ • Size: {state.parameters.radius.toFixed(1)} •{" "}
2216
+ {state.parameters.greyscale ? "B&W" : "Color"}
2217
+ </div>
2218
+ <div className="text-xs text-[#242424] opacity-50">
2219
+ {new Date(state.timestamp).toLocaleTimeString()}
2220
+ </div>
2221
+ </div>
2222
+ ))}
2223
+ </div>
2224
+ </div>
2225
+ )}
2226
+ </div>
2227
+ )}
2228
+ </div>
2229
+ </div>
2230
+ );
2231
+ }
2232
+ ```