JamesToth commited on
Commit
c754822
·
verified ·
1 Parent(s): 0dbf181

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +165 -373
index.html CHANGED
@@ -3,487 +3,280 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Omni-Hand Particle Engine</title>
7
  <style>
8
  body {
9
- margin: 0; overflow: hidden; background-color: #000;
10
  font-family: 'Segoe UI', monospace; user-select: none;
11
  }
12
-
13
- /* HUD UI */
14
  #hud {
15
  position: absolute; top: 20px; left: 20px;
16
- width: 300px; padding: 20px;
17
- background: rgba(10,12,16, 0.85);
18
- backdrop-filter: blur(12px);
19
- border: 1px solid rgba(255,255,255,0.15);
20
- border-left: 4px solid #00ffcc;
21
- border-radius: 8px;
22
- color: #fff; pointer-events: none; z-index: 10;
23
  }
24
-
25
- h1 { margin: 0 0 10px 0; font-size: 16px; letter-spacing: 1px; color: #00ffcc; text-transform: uppercase; }
26
 
27
- .row { display: flex; justify-content: space-between; font-size: 11px; margin-bottom: 5px; color: #aaa; }
28
- .val { font-family: 'Courier New', monospace; color: #fff; font-weight: bold; }
29
- .highlight { color: #00ffcc; }
 
 
 
 
 
 
30
 
31
- #detected-gesture {
32
- margin-top: 15px; padding-top: 15px; border-top: 1px solid rgba(255,255,255,0.1);
33
- font-size: 18px; text-align: center; color: #fff; font-weight: 300;
 
 
 
 
 
 
 
 
 
 
34
  }
35
- #detected-gesture span { display: block; font-size: 10px; color: #666; margin-top: 4px; letter-spacing: 2px; text-transform: uppercase;}
36
-
37
- #loading { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #00ffcc; font-size: 14px; animation: blink 1s infinite; }
38
- @keyframes blink { 50% { opacity: 0.5; } }
39
 
40
- /* Toast Notification for Shape/Color Changes */
41
- #toast {
42
- position: absolute; bottom: 100px; left: 0; width: 100%;
43
- text-align: center; font-size: 24px; color: white;
44
- text-shadow: 0 0 20px #00ffcc; opacity: 0; transition: opacity 0.5s;
45
- pointer-events: none;
46
  }
47
-
48
- video { display: none; }
49
  </style>
50
-
51
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
52
  <script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js"></script>
53
  <script src="https://cdn.jsdelivr.net/npm/@mediapipe/control_utils/control_utils.js"></script>
 
54
  <script src="https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js"></script>
55
  </head>
56
  <body>
57
 
 
 
58
  <div id="hud">
59
- <h1>OMNI-CONTROL ENGINE</h1>
60
-
61
  <div class="row"><span>SHAPE</span><span id="ui-shape" class="val">GALAXY</span></div>
62
- <div class="row"><span>COLOR</span><span id="ui-color" class="val">CYBER</span></div>
63
- <div class="row"><span>SIZE</span><span id="ui-size" class="val">1.0x</span></div>
64
- <div class="row"><span>FPS</span><span id="ui-fps" class="val">60</span></div>
65
-
66
- <div id="detected-gesture">
67
- WAITING FOR HAND...
68
- <span>SYSTEM STATUS</span>
69
- </div>
70
  </div>
71
 
72
- <div id="toast">MODE CHANGE</div>
73
- <div id="loading">INITIALIZING NEURAL NET...</div>
74
- <video id="input-video"></video>
 
 
75
 
76
  <script>
77
- // ==========================================
78
- // 1. SYSTEM CONFIG
79
- // ==========================================
80
  const CONFIG = {
81
- count: 15000,
82
  camWidth: 640,
83
- camHeight: 480,
84
- throttleAI: 2 // Save CPU
85
  };
86
 
87
- const COLORS = [
88
- { name: 'CYBER', base: 0x00aaff, high: 0xffffff },
89
- { name: 'INFERNO', base: 0xff3300, high: 0xffaa00 },
90
- { name: 'POISON', base: 0x8800ff, high: 0x00ff00 },
91
- { name: 'MATRIX', base: 0x003300, high: 0x00ff00 },
92
- { name: 'COTTON', base: 0xffaabb, high: 0xaabbff },
93
- ];
94
-
95
  const State = {
96
  active: false,
 
97
  gesture: 'NONE',
98
- hand: { x: 0, y: 0, z: 0, angle: 0 },
99
- params: {
100
- shapeIdx: 0,
101
- colorIdx: 0,
102
- size: 0.15,
103
- autoSpin: true,
104
- freeze: false,
105
- turbulence: 0,
106
- trail: false,
107
- reset: false
108
- },
109
- cooldown: 0
110
  };
111
 
112
- // ==========================================
113
- // 2. VISUAL ENGINE (THREE.JS)
114
- // ==========================================
115
  const scene = new THREE.Scene();
116
- scene.fog = new THREE.FogExp2(0x000000, 0.03);
117
 
118
  const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 100);
119
  camera.position.z = 10;
120
 
121
- const renderer = new THREE.WebGLRenderer({ antialias: false, powerPreference: "high-performance" });
122
  renderer.setSize(window.innerWidth, window.innerHeight);
123
- renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
124
  document.body.appendChild(renderer.domElement);
125
 
126
- // Texture Gen
127
- const canvas = document.createElement('canvas');
128
- canvas.width = 32; canvas.height = 32;
129
- const ctx = canvas.getContext('2d');
130
- const grad = ctx.createRadialGradient(16,16,0,16,16,16);
131
- grad.addColorStop(0, 'white');
132
- grad.addColorStop(1, 'rgba(255,255,255,0)');
133
- ctx.fillStyle = grad; ctx.fillRect(0,0,32,32);
134
- const texture = new THREE.CanvasTexture(canvas);
135
-
136
  // Particles
137
  const geo = new THREE.BufferGeometry();
138
  const pos = new Float32Array(CONFIG.count * 3);
139
  const tar = new Float32Array(CONFIG.count * 3);
140
- const vel = new Float32Array(CONFIG.count * 3);
141
- const col = new Float32Array(CONFIG.count * 3);
142
- const extra = new Float32Array(CONFIG.count); // For trails/size variation
143
 
144
  for(let i=0; i<CONFIG.count*3; i++) {
145
- pos[i] = (Math.random()-0.5)*50;
146
  tar[i] = pos[i];
147
  vel[i] = 0;
148
- col[i] = 1;
149
  }
150
-
151
  geo.setAttribute('position', new THREE.BufferAttribute(pos, 3));
152
- geo.setAttribute('color', new THREE.BufferAttribute(col, 3));
 
 
 
 
 
 
 
153
 
154
  const mat = new THREE.PointsMaterial({
155
- size: State.params.size,
156
- map: texture,
157
- vertexColors: true,
158
- transparent: true,
159
- opacity: 0.8,
160
- blending: THREE.AdditiveBlending,
161
- depthWrite: false
162
  });
163
-
164
  const particles = new THREE.Points(geo, mat);
165
  scene.add(particles);
166
 
167
- // ==========================================
168
- // 3. SHAPE LIBRARY
169
- // ==========================================
170
  const Shapes = [
171
- { name: 'GALAXY', func: (i) => {
172
- const r = (i/CONFIG.count)*8;
173
- const a = (i/CONFIG.count)*Math.PI*8;
174
- return { x: Math.cos(a)*r, y: (Math.random()-0.5), z: Math.sin(a)*r };
175
- }},
176
- { name: 'SPHERE', func: (i) => {
177
- const phi = Math.acos(-1 + (2*i)/CONFIG.count);
178
- const theta = Math.sqrt(CONFIG.count * Math.PI) * phi;
179
- return { x: 5*Math.cos(theta)*Math.sin(phi), y: 5*Math.sin(theta)*Math.sin(phi), z: 5*Math.cos(phi) };
180
- }},
181
- { name: 'HEART', func: (i) => {
182
- const t = (i/CONFIG.count)*Math.PI*2;
183
- const x = 16*Math.pow(Math.sin(t),3);
184
- const y = 13*Math.cos(t)-5*Math.cos(2*t)-2*Math.cos(3*t)-Math.cos(4*t);
185
- return { x: x*0.3, y: y*0.3, z: (Math.random()-0.5)*2 };
186
- }},
187
- { name: 'CUBE', func: (i) => {
188
- const s = 3; // size
189
- const ax = i % 3; // 0=x, 1=y, 2=z plane logic simplified
190
- return { x: (Math.random()-0.5)*10, y: (Math.random()-0.5)*10, z: (Math.random()-0.5)*10 };
191
- }},
192
- { name: 'RING', func: (i) => {
193
- const a = (i/CONFIG.count)*Math.PI*2;
194
- return { x: Math.cos(a)*7, y: (Math.random()-0.5)*2, z: Math.sin(a)*7 };
195
- }}
196
  ];
197
 
198
- function setShape(idx) {
199
- const s = Shapes[idx];
200
- document.getElementById('ui-shape').innerText = s.name;
201
- showToast(s.name);
202
  for(let i=0; i<CONFIG.count; i++) {
203
- const p = s.func(i);
204
  tar[i*3] = p.x; tar[i*3+1] = p.y; tar[i*3+2] = p.z;
205
  }
206
  }
207
- setShape(0);
208
 
209
- function setColor(idx) {
210
- document.getElementById('ui-color').innerText = COLORS[idx].name;
211
- showToast("COLOR: " + COLORS[idx].name);
212
- }
213
-
214
- function showToast(msg) {
215
- const t = document.getElementById('toast');
216
- t.innerText = msg; t.style.opacity = 1;
217
- setTimeout(() => t.style.opacity = 0, 1000);
218
- }
219
-
220
- // ==========================================
221
- // 4. GESTURE RECOGNITION LOGIC
222
- // ==========================================
223
- const Gesture = {
224
- isFist: (lm) => {
225
- // Tips (8,12,16,20) closer to wrist(0) than PIP joints (6,10,14,18)
226
- return lm[8].y > lm[6].y && lm[12].y > lm[10].y && lm[16].y > lm[14].y && lm[20].y > lm[18].y;
227
- },
228
- isIndex: (lm) => lm[8].y < lm[6].y && lm[12].y > lm[10].y && lm[16].y > lm[14].y,
229
- isPinky: (lm) => lm[20].y < lm[18].y && lm[16].y > lm[14].y && lm[8].y > lm[6].y,
230
- isOkay: (lm) => {
231
- const dist = Math.hypot(lm[4].x - lm[8].x, lm[4].y - lm[8].y);
232
- return dist < 0.05 && lm[12].y < lm[10].y; // Tips touch + Middle up
233
- },
234
- isVictory: (lm) => lm[8].y < lm[6].y && lm[12].y < lm[10].y && lm[16].y > lm[14].y,
235
- isThree: (lm) => lm[8].y < lm[6].y && lm[12].y < lm[10].y && lm[16].y < lm[14].y && lm[20].y > lm[18].y,
236
- isFour: (lm) => lm[8].y < lm[6].y && lm[12].y < lm[10].y && lm[16].y < lm[14].y && lm[20].y < lm[18].y && lm[4].x < lm[3].x, // Thumb tucked
237
- isOpen: (lm) => lm[8].y < lm[6].y && lm[12].y < lm[10].y && lm[16].y < lm[14].y && lm[20].y < lm[18].y,
238
- isRock: (lm) => lm[8].y < lm[6].y && lm[20].y < lm[18].y && lm[12].y > lm[10].y && lm[16].y > lm[14].y,
239
- isShaka: (lm) => lm[4].x > lm[3].x && lm[20].y < lm[18].y && lm[8].y > lm[6].y, // Thumb out, pinky up, others down
240
- };
241
-
242
- function processGesture(lm, velX) {
243
- let g = "UNKNOWN";
244
 
245
- // 1. Swipe Check (High Priority)
246
- if(Math.abs(velX) > 0.8 && State.cooldown <= 0) {
247
- State.params.shapeIdx = (State.params.shapeIdx + 1) % Shapes.length;
248
- setShape(State.params.shapeIdx);
249
- State.cooldown = 20;
250
- return "SWIPE CHANGE";
251
- }
252
-
253
- // 2. Pinch Check (Scaling)
254
  const pinchDist = Math.hypot(lm[4].x - lm[8].x, lm[4].y - lm[8].y);
255
- if(pinchDist < 0.1 && !Gesture.isFist(lm)) {
256
- // Map Pinch Y position to scale
257
- // Higher hand = bigger scale
258
- const newSize = THREE.MathUtils.mapLinear(lm[4].y, 0, 1, 2.0, 0.5);
259
- State.params.scaleTarget = newSize; // We'll lerp to this
260
- g = "PINCH ZOOM";
261
- }
262
 
263
- // 3. Static Poses
264
- if (Gesture.isFist(lm)) g = "FIST (GRAVITY)";
265
- else if (Gesture.isOkay(lm)) g = "OKAY (FREEZE)";
266
- else if (Gesture.isRock(lm)) g = "ROCK (TRAILS)";
267
- else if (Gesture.isVictory(lm)) g = "VICTORY (CHAOS)";
268
- else if (Gesture.isShaka(lm)) g = "SHAKA (RESET)";
269
- else if (Gesture.isThree(lm)) {
270
- if(State.cooldown <= 0) {
271
- State.params.colorIdx = (State.params.colorIdx + 1) % COLORS.length;
272
- setColor(State.params.colorIdx);
273
- State.cooldown = 30;
274
- }
275
- g = "3-FINGER (COLOR)";
276
- }
277
- else if (Gesture.isFour(lm)) {
278
- if(State.cooldown <= 0) {
279
- State.params.autoSpin = !State.params.autoSpin;
280
- showToast(State.params.autoSpin ? "SPIN: ON" : "SPIN: OFF");
281
- State.cooldown = 30;
282
- }
283
- g = "4-FINGER (SPIN)";
284
- }
285
- else if (Gesture.isPinky(lm)) g = "PINKY (VORTEX)";
286
- else if (Gesture.isIndex(lm)) g = "INDEX (CURSOR)";
287
- else if (Gesture.isOpen(lm)) g = "PALM (SHIELD)";
288
-
289
- // 4. Thumb Orientation (Size)
290
- // Check relative y of thumb tip vs IP joint
291
- if(g === "UNKNOWN" || g === "FIST (GRAVITY)") {
292
- const dy = lm[4].y - lm[3].y;
293
- if(dy < -0.05) { // Thumb pointing up
294
- State.params.size = Math.min(State.params.size + 0.005, 0.4);
295
- g = "THUMB UP (GROW)";
296
- } else if (dy > 0.05) {
297
- State.params.size = Math.max(State.params.size - 0.005, 0.02);
298
- g = "THUMB DOWN (SHRINK)";
299
- }
300
- }
301
 
 
 
 
 
 
 
 
 
 
302
  return g;
303
  }
304
 
305
- // ==========================================
306
- // 5. MEDIA PIPE LOOP
307
- // ==========================================
308
- const videoElem = document.getElementById('input-video');
309
- const hudGesture = document.getElementById('detected-gesture');
310
- let lastHandX = 0;
311
- let frames = 0;
312
-
313
- const hands = new Hands({locateFile: (file) => `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`});
314
- hands.setOptions({maxNumHands: 1, minDetectionConfidence: 0.6, minTrackingConfidence: 0.6});
315
 
316
- hands.onResults(results => {
 
 
 
317
  document.getElementById('loading').style.display = 'none';
318
 
319
- if(results.multiHandLandmarks.length > 0) {
 
 
 
 
320
  State.active = true;
 
 
 
321
  const lm = results.multiHandLandmarks[0];
322
 
323
- // Map coordinates (Mirror X)
324
- const x = (1 - lm[9].x) * 20 - 10;
325
- const y = -(lm[9].y - 0.5) * 15;
326
- const z = (lm[9].z) * 20; // Depth approx
327
-
328
- const velX = x - lastHandX;
329
- lastHandX = x;
330
 
331
- // Detect Gesture
332
- const g = processGesture(lm, velX);
333
- State.gesture = g;
334
-
335
- // Update HUD
336
- hudGesture.innerHTML = `${g}<span>DETECTED</span>`;
337
- if(g !== "NONE") hudGesture.style.borderColor = "#00ffcc";
338
 
339
- // Update State Hands
340
- State.hand.x += (x - State.hand.x) * 0.3;
341
- State.hand.y += (y - State.hand.y) * 0.3;
342
- State.hand.z = z;
 
 
 
 
 
 
 
 
 
 
343
 
344
  } else {
345
  State.active = false;
346
- State.gesture = "SEARCHING";
347
- hudGesture.innerHTML = `SEARCHING<span>SHOW HAND</span>`;
348
- hudGesture.style.borderColor = "#555";
349
  }
350
- });
 
 
 
 
 
351
 
352
  const cam = new Camera(videoElem, {
353
- onFrame: async () => {
354
- frames++;
355
- if(frames % CONFIG.throttleAI === 0) await hands.send({image: videoElem});
356
- },
357
- width: CONFIG.camWidth, height: CONFIG.camHeight
358
  });
359
  cam.start();
360
 
361
- // ==========================================
362
- // 6. PHYSICS ENGINE
363
- // ==========================================
364
- const clock = new THREE.Clock();
365
-
366
  function animate() {
367
  requestAnimationFrame(animate);
368
 
369
- const dt = clock.getDelta();
370
- const time = clock.getElapsedTime();
371
-
372
- if(State.cooldown > 0) State.cooldown--;
373
-
374
- // Update Size from gesture
375
- mat.size = State.params.size;
376
- document.getElementById('ui-size').innerText = State.params.size.toFixed(2) + "x";
377
-
378
- // Global Params
379
- let damping = 0.92;
380
- let scale = 1.0;
381
-
382
- // --- GESTURE APPLICATION ---
383
- if(State.active) {
384
- if(State.gesture.includes("PINCH")) {
385
- // Lerp global scale
386
- // Note: we'd need a global scaler, applying it to targets for now
387
- }
388
- if(State.gesture.includes("FREEZE")) damping = 0.5; // High friction
389
- }
390
-
391
- const cTheme = COLORS[State.params.colorIdx];
392
- const rBase = (cTheme.base >> 16 & 255) / 255;
393
- const gBase = (cTheme.base >> 8 & 255) / 255;
394
- const bBase = (cTheme.base & 255) / 255;
395
-
396
  for(let i=0; i<CONFIG.count; i++) {
397
  const i3 = i*3;
398
  let tx = tar[i3]; let ty = tar[i3+1]; let tz = tar[i3+2];
399
 
400
- // Interaction Physics
401
  if(State.active) {
402
  const dx = pos[i3] - State.hand.x;
403
  const dy = pos[i3+1] - State.hand.y;
404
- const dz = pos[i3+2] - State.hand.z * 2; // Depth
405
- const distSq = dx*dx + dy*dy + dz*dz;
406
- const dist = Math.sqrt(distSq);
407
 
408
- // 1. SHIELD (Repulsion)
409
- if(State.gesture.includes("PALM") && dist < 5) {
410
- const f = (5 - dist) * 2.0;
411
- tx += (dx/dist) * f;
412
- ty += (dy/dist) * f;
413
- tz += (dz/dist) * f;
414
- }
415
-
416
- // 2. GRAVITY (Fist/Index)
417
- if(State.gesture.includes("FIST") && dist < 12) {
418
- tx -= dx * 0.8; // Strong pull
419
- ty -= dy * 0.8;
420
- tz -= dz * 0.8;
421
- } else if(State.gesture.includes("INDEX") && dist < 8) {
422
- tx -= dx * 0.2; // Weak pull
423
- ty -= dy * 0.2;
424
  }
425
-
426
- // 3. VORTEX (Pinky)
427
- if(State.gesture.includes("PINKY")) {
428
- // Tangent force
429
- tx += -dy * 0.05;
430
- ty += dx * 0.05;
431
- }
432
-
433
- // 4. TURBULENCE (Victory)
434
- if(State.gesture.includes("VICTORY")) {
435
- tx += (Math.random()-0.5)*2;
436
- ty += (Math.random()-0.5)*2;
437
- }
438
-
439
- // 5. RESET (Shaka)
440
- if(State.gesture.includes("SHAKA")) {
441
- tx = (Math.random()-0.5)*50;
442
- ty = (Math.random()-0.5)*50;
443
- }
444
- }
445
-
446
- // Spring Physics
447
- if(!State.gesture.includes("FREEZE")) {
448
- const ax = (tx - pos[i3]) * 0.05;
449
- const ay = (ty - pos[i3+1]) * 0.05;
450
- const az = (tz - pos[i3+2]) * 0.05;
451
-
452
- vel[i3] += ax; vel[i3+1] += ay; vel[i3+2] += az;
453
- vel[i3] *= damping; vel[i3+1] *= damping; vel[i3+2] *= damping;
454
-
455
- pos[i3] += vel[i3]; pos[i3+1] += vel[i3+1]; pos[i3+2] += vel[i3+2];
456
  }
457
 
458
- // Coloring (Speed based)
459
- const speed = Math.abs(vel[i3]) + Math.abs(vel[i3+1]);
460
- const hot = Math.min(speed*2, 1);
461
 
462
- // Trail logic check
463
- if(State.gesture.includes("TRAILS")) {
464
- col[i3] = 1; col[i3+1] = 1; col[i3+2] = 1; // All white
465
- } else {
466
- col[i3] = rBase + hot;
467
- col[i3+1] = gBase + hot;
468
- col[i3+2] = bBase + hot;
469
- }
470
- }
471
-
472
- // Auto Rotation
473
- if(State.params.autoSpin && !State.gesture.includes("PINKY")) {
474
- particles.rotation.y = time * 0.15;
475
- particles.rotation.z = Math.sin(time*0.2) * 0.1;
476
  }
477
-
478
- // Camera Z-Roll for fun
479
- camera.rotation.z = (State.hand.x * -0.02);
480
-
481
  geo.attributes.position.needsUpdate = true;
482
- geo.attributes.color.needsUpdate = true;
483
  renderer.render(scene, camera);
484
-
485
- // FPS Counter (approx)
486
- document.getElementById('ui-fps').innerText = Math.round(1/dt);
487
  }
488
  animate();
489
 
@@ -492,7 +285,6 @@
492
  camera.updateProjectionMatrix();
493
  renderer.setSize(window.innerWidth, window.innerHeight);
494
  };
495
-
496
  </script>
497
  </body>
498
  </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Omni-Hand Diagnostic Mode</title>
7
  <style>
8
  body {
9
+ margin: 0; overflow: hidden; background-color: #050505;
10
  font-family: 'Segoe UI', monospace; user-select: none;
11
  }
12
+
13
+ /* HUD - Top Left */
14
  #hud {
15
  position: absolute; top: 20px; left: 20px;
16
+ width: 280px; padding: 20px;
17
+ background: rgba(20, 20, 20, 0.9);
18
+ border-left: 4px solid #00ff88;
19
+ border-radius: 4px;
20
+ color: #fff; z-index: 10;
 
 
21
  }
 
 
22
 
23
+ .row { display: flex; justify-content: space-between; font-size: 12px; margin-bottom: 8px; color: #ccc; }
24
+ .val { font-weight: bold; color: #fff; }
25
+ .highlight { color: #00ff88; text-shadow: 0 0 10px rgba(0,255,136,0.5); }
26
+
27
+ #gesture-display {
28
+ font-size: 20px; text-align: center; margin-top: 15px;
29
+ padding-top: 15px; border-top: 1px solid #333;
30
+ color: #666; font-weight: 300;
31
+ }
32
 
33
+ /* DEBUG VIEW - Bottom Right */
34
+ #debug-container {
35
+ position: absolute; bottom: 20px; right: 20px;
36
+ width: 320px; height: 240px;
37
+ background: #000;
38
+ border: 2px solid #333;
39
+ z-index: 100;
40
+ }
41
+ #debug-video { width: 100%; height: 100%; object-fit: cover; transform: scaleX(-1); opacity: 0.6; }
42
+ #debug-canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; transform: scaleX(-1); }
43
+ #debug-label {
44
+ position: absolute; top: 0; left: 0; background: red; color: white;
45
+ font-size: 10px; padding: 2px 5px;
46
  }
 
 
 
 
47
 
48
+ #loading {
49
+ position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
50
+ color: #00ff88; font-size: 24px; text-transform: uppercase; letter-spacing: 4px;
51
+ text-shadow: 0 0 20px #00ff88;
 
 
52
  }
 
 
53
  </style>
54
+
55
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
56
  <script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js"></script>
57
  <script src="https://cdn.jsdelivr.net/npm/@mediapipe/control_utils/control_utils.js"></script>
58
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.js"></script>
59
  <script src="https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js"></script>
60
  </head>
61
  <body>
62
 
63
+ <div id="loading">Loading AI Engine...</div>
64
+
65
  <div id="hud">
66
+ <div style="font-size: 10px; color: #888; margin-bottom: 10px;">DIAGNOSTIC MODE</div>
67
+ <div class="row"><span>HAND TRACKING</span><span id="ui-track" class="val" style="color:red">OFFLINE</span></div>
68
  <div class="row"><span>SHAPE</span><span id="ui-shape" class="val">GALAXY</span></div>
69
+ <div class="row"><span>PARTICLES</span><span class="val">12,000</span></div>
70
+ <div id="gesture-display">WAITING...</div>
 
 
 
 
 
 
71
  </div>
72
 
73
+ <div id="debug-container">
74
+ <div id="debug-label">CAMERA FEED</div>
75
+ <video id="debug-video" playsinline></video>
76
+ <canvas id="debug-canvas" width="320" height="240"></canvas>
77
+ </div>
78
 
79
  <script>
80
+ // --- CONFIG ---
 
 
81
  const CONFIG = {
82
+ count: 12000,
83
  camWidth: 640,
84
+ camHeight: 480
 
85
  };
86
 
 
 
 
 
 
 
 
 
87
  const State = {
88
  active: false,
89
+ hand: { x: 0, y: 0, z: 0 },
90
  gesture: 'NONE',
91
+ shapeIdx: 0,
92
+ lastGestureTime: 0
 
 
 
 
 
 
 
 
 
 
93
  };
94
 
95
+ // --- THREE.JS SETUP ---
 
 
96
  const scene = new THREE.Scene();
97
+ scene.fog = new THREE.FogExp2(0x050505, 0.04);
98
 
99
  const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 100);
100
  camera.position.z = 10;
101
 
102
+ const renderer = new THREE.WebGLRenderer({ antialias: false });
103
  renderer.setSize(window.innerWidth, window.innerHeight);
 
104
  document.body.appendChild(renderer.domElement);
105
 
 
 
 
 
 
 
 
 
 
 
106
  // Particles
107
  const geo = new THREE.BufferGeometry();
108
  const pos = new Float32Array(CONFIG.count * 3);
109
  const tar = new Float32Array(CONFIG.count * 3);
110
+ const vel = new Float32Array(CONFIG.count * 3); // Velocity
 
 
111
 
112
  for(let i=0; i<CONFIG.count*3; i++) {
113
+ pos[i] = (Math.random()-0.5)*30;
114
  tar[i] = pos[i];
115
  vel[i] = 0;
 
116
  }
 
117
  geo.setAttribute('position', new THREE.BufferAttribute(pos, 3));
118
+
119
+ // Texture
120
+ const cvs = document.createElement('canvas'); cvs.width=32; cvs.height=32;
121
+ const ctx = cvs.getContext('2d');
122
+ const grd = ctx.createRadialGradient(16,16,0,16,16,16);
123
+ grd.addColorStop(0,'white'); grd.addColorStop(1,'transparent');
124
+ ctx.fillStyle = grd; ctx.fillRect(0,0,32,32);
125
+ const tex = new THREE.CanvasTexture(cvs);
126
 
127
  const mat = new THREE.PointsMaterial({
128
+ size: 0.15, map: tex, color: 0x00ff88,
129
+ transparent: true, opacity: 0.8, blending: THREE.AdditiveBlending, depthWrite: false
 
 
 
 
 
130
  });
 
131
  const particles = new THREE.Points(geo, mat);
132
  scene.add(particles);
133
 
134
+ // --- SHAPES ---
 
 
135
  const Shapes = [
136
+ (i) => { // Galaxy
137
+ const r = (i/CONFIG.count)*8; const a = (i/CONFIG.count)*20;
138
+ return {x: Math.cos(a)*r, y: (Math.random()-0.5), z: Math.sin(a)*r};
139
+ },
140
+ (i) => { // Sphere
141
+ const p = Math.acos(-1+(2*i)/CONFIG.count); const t = Math.sqrt(CONFIG.count*Math.PI)*p;
142
+ return {x: 4*Math.sin(p)*Math.cos(t), y: 4*Math.sin(p)*Math.sin(t), z: 4*Math.cos(p)};
143
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  ];
145
 
146
+ function updateTargets() {
147
+ const func = Shapes[State.shapeIdx];
 
 
148
  for(let i=0; i<CONFIG.count; i++) {
149
+ const p = func(i);
150
  tar[i*3] = p.x; tar[i*3+1] = p.y; tar[i*3+2] = p.z;
151
  }
152
  }
153
+ updateTargets();
154
 
155
+ // --- GESTURE LOGIC (RELAXED) ---
156
+ function detectGesture(lm) {
157
+ // Tips: 8(Idx), 12(Mid), 16(Rng), 20(Pnk) | PIPs: 6, 10, 14, 18
158
+ // 0 = Wrist
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
 
160
+ // Helper: Is finger extended? (Tip higher than PIP)
161
+ const idxUp = lm[8].y < lm[6].y;
162
+ const midUp = lm[12].y < lm[10].y;
163
+ const rngUp = lm[16].y < lm[14].y;
164
+ const pnkUp = lm[20].y < lm[18].y;
165
+
166
+ // Helper: Distance
 
 
167
  const pinchDist = Math.hypot(lm[4].x - lm[8].x, lm[4].y - lm[8].y);
168
+ const fistDist = Math.hypot(lm[0].x - lm[12].x, lm[0].y - lm[12].y); // Wrist to mid-tip
 
 
 
 
 
 
169
 
170
+ let g = "UNKNOWN";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
 
172
+ // Logic Tree
173
+ if (fistDist < 0.25 && !idxUp) g = "FIST (GRAVITY)"; // Relaxed threshold
174
+ else if (pinchDist < 0.08) g = "PINCH (ZOOM)";
175
+ else if (idxUp && pnkUp && !midUp) g = "ROCK (TRAILS)";
176
+ else if (idxUp && midUp && !rngUp) g = "PEACE (CHAOS)";
177
+ else if (idxUp && !midUp && !pnkUp) g = "INDEX (CURSOR)";
178
+ else if (!idxUp && !midUp && pnkUp) g = "PINKY (VORTEX)";
179
+ else if (idxUp && midUp && rngUp && pnkUp) g = "OPEN (SHIELD)";
180
+
181
  return g;
182
  }
183
 
184
+ // --- MEDIAPIPE SETUP ---
185
+ const videoElem = document.getElementById('debug-video');
186
+ const debugCanvas = document.getElementById('debug-canvas');
187
+ const debugCtx = debugCanvas.getContext('2d');
 
 
 
 
 
 
188
 
189
+ const uiTrack = document.getElementById('ui-track');
190
+ const uiGesture = document.getElementById('gesture-display');
191
+
192
+ function onResults(results) {
193
  document.getElementById('loading').style.display = 'none';
194
 
195
+ // Debug Draw
196
+ debugCtx.save();
197
+ debugCtx.clearRect(0, 0, debugCanvas.width, debugCanvas.height);
198
+
199
+ if (results.multiHandLandmarks && results.multiHandLandmarks.length > 0) {
200
  State.active = true;
201
+ uiTrack.innerText = "ONLINE";
202
+ uiTrack.style.color = "#00ff88";
203
+
204
  const lm = results.multiHandLandmarks[0];
205
 
206
+ // DRAW SKELETON (Crucial for Debug)
207
+ drawConnectors(debugCtx, lm, HAND_CONNECTIONS, {color: '#00ff00', lineWidth: 2});
208
+ drawLandmarks(debugCtx, lm, {color: '#ff0000', lineWidth: 1, radius: 2});
 
 
 
 
209
 
210
+ // Map Position
211
+ const x = (1 - lm[9].x) * 16 - 8;
212
+ const y = -(lm[9].y - 0.5) * 12;
 
 
 
 
213
 
214
+ State.hand.x += (x - State.hand.x) * 0.2;
215
+ State.hand.y += (y - State.hand.y) * 0.2;
216
+
217
+ // Detect
218
+ const g = detectGesture(lm);
219
+ if(g !== State.gesture) {
220
+ State.gesture = g;
221
+ uiGesture.innerHTML = `<span class="highlight">${g}</span>`;
222
+
223
+ // Swipe Logic
224
+ if(g === "INDEX (CURSOR)" && Math.abs(x - State.hand.x) > 0.5) {
225
+ // Simple logic for swipe
226
+ }
227
+ }
228
 
229
  } else {
230
  State.active = false;
231
+ uiTrack.innerText = "NO HAND";
232
+ uiTrack.style.color = "red";
233
+ uiGesture.innerText = "SHOW HAND";
234
  }
235
+ debugCtx.restore();
236
+ }
237
+
238
+ const hands = new Hands({locateFile: (file) => `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`});
239
+ hands.setOptions({maxNumHands: 1, minDetectionConfidence: 0.5, minTrackingConfidence: 0.5});
240
+ hands.onResults(onResults);
241
 
242
  const cam = new Camera(videoElem, {
243
+ onFrame: async () => { await hands.send({image: videoElem}); },
244
+ width: 320, height: 240
 
 
 
245
  });
246
  cam.start();
247
 
248
+ // --- ANIMATION LOOP ---
 
 
 
 
249
  function animate() {
250
  requestAnimationFrame(animate);
251
 
252
+ // Physics
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  for(let i=0; i<CONFIG.count; i++) {
254
  const i3 = i*3;
255
  let tx = tar[i3]; let ty = tar[i3+1]; let tz = tar[i3+2];
256
 
 
257
  if(State.active) {
258
  const dx = pos[i3] - State.hand.x;
259
  const dy = pos[i3+1] - State.hand.y;
260
+ const dist = Math.sqrt(dx*dx + dy*dy);
 
 
261
 
262
+ if(State.gesture.includes("FIST") && dist < 10) {
263
+ tx -= dx*0.5; ty -= dy*0.5; // Suck
264
+ } else if(State.gesture.includes("SHIELD") && dist < 6) {
265
+ tx += dx*0.5; ty += dy*0.5; // Push
 
 
 
 
 
 
 
 
 
 
 
 
266
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
  }
268
 
269
+ vel[i3] += (tx - pos[i3])*0.05;
270
+ vel[i3+1] += (ty - pos[i3+1])*0.05;
271
+ vel[i3+2] += (tz - pos[i3+2])*0.05;
272
 
273
+ vel[i3] *= 0.9; vel[i3+1] *= 0.9; vel[i3+2] *= 0.9;
274
+
275
+ pos[i3] += vel[i3]; pos[i3+1] += vel[i3+1]; pos[i3+2] += vel[i3+2];
 
 
 
 
 
 
 
 
 
 
 
276
  }
277
+
 
 
 
278
  geo.attributes.position.needsUpdate = true;
 
279
  renderer.render(scene, camera);
 
 
 
280
  }
281
  animate();
282
 
 
285
  camera.updateProjectionMatrix();
286
  renderer.setSize(window.innerWidth, window.innerHeight);
287
  };
 
288
  </script>
289
  </body>
290
  </html>