Twan07 commited on
Commit
37352a3
·
verified ·
1 Parent(s): 1c154fc

Upload 7 files

Browse files
Files changed (7) hide show
  1. index.html +1134 -0
  2. index.tsx +2 -0
  3. metadata.json +8 -0
  4. package-lock.json +1092 -0
  5. package.json +20 -0
  6. tsconfig.json +29 -0
  7. vite.config.ts +21 -0
index.html ADDED
@@ -0,0 +1,1134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Hand Gesture Particle System</title>
7
+ <style>
8
+ body {
9
+ margin: 0;
10
+ overflow: hidden;
11
+ background-color: #050505;
12
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
13
+ user-select: none;
14
+ }
15
+
16
+ #canvas-container {
17
+ position: absolute;
18
+ top: 0;
19
+ left: 0;
20
+ width: 100vw;
21
+ height: 100vh;
22
+ z-index: 15; /* Higher than camera-wrapper */
23
+ pointer-events: none;
24
+ }
25
+
26
+ /* COMPACT UI */
27
+ #ui-layer {
28
+ position: absolute;
29
+ top: 15px;
30
+ left: 15px;
31
+ z-index: 20;
32
+ color: #ff69b4;
33
+ background: rgba(0, 0, 0, 0.6);
34
+ padding: 12px;
35
+ border-radius: 12px;
36
+ border: 1px solid rgba(255, 105, 180, 0.2);
37
+ font-size: 12px;
38
+ line-height: 1.4;
39
+ width: 210px;
40
+ backdrop-filter: blur(6px);
41
+ box-shadow: 0 4px 15px rgba(0,0,0,0.4);
42
+ pointer-events: auto;
43
+ transition: height 0.3s ease;
44
+ }
45
+
46
+ #ui-layer.collapsed {
47
+ width: auto;
48
+ min-width: 120px;
49
+ }
50
+
51
+ #ui-layer.collapsed #ui-content {
52
+ display: none;
53
+ }
54
+
55
+ .ui-header-row {
56
+ display: flex;
57
+ justify-content: space-between;
58
+ align-items: center;
59
+ border-bottom: 1px solid rgba(255,255,255,0.1);
60
+ padding-bottom: 4px;
61
+ margin-bottom: 8px;
62
+ }
63
+
64
+ #ui-layer h1 {
65
+ margin: 0;
66
+ font-size: 14px;
67
+ text-transform: uppercase;
68
+ letter-spacing: 1px;
69
+ color: #fff;
70
+ pointer-events: none;
71
+ }
72
+
73
+ #btn-collapse {
74
+ background: none;
75
+ border: none;
76
+ color: #ff69b4;
77
+ font-weight: bold;
78
+ font-size: 16px;
79
+ cursor: pointer;
80
+ padding: 0 5px;
81
+ line-height: 1;
82
+ }
83
+ #btn-collapse:hover {
84
+ color: #fff;
85
+ }
86
+
87
+ .stat-row {
88
+ display: flex;
89
+ justify-content: space-between;
90
+ align-items: center;
91
+ margin-bottom: 4px;
92
+ pointer-events: none;
93
+ }
94
+
95
+ .value {
96
+ font-weight: bold;
97
+ color: #fff;
98
+ }
99
+
100
+ /* Voice Toggle Button */
101
+ #btn-voice {
102
+ width: 100%;
103
+ margin-top: 5px;
104
+ margin-bottom: 8px;
105
+ padding: 8px;
106
+ background: rgba(255, 255, 255, 0.1);
107
+ border: 1px solid rgba(255, 105, 180, 0.3);
108
+ border-radius: 6px;
109
+ color: #ddd;
110
+ cursor: pointer;
111
+ font-size: 11px;
112
+ text-transform: uppercase;
113
+ transition: all 0.2s;
114
+ display: flex;
115
+ align-items: center;
116
+ justify-content: center;
117
+ gap: 8px;
118
+ }
119
+
120
+ #btn-voice:hover {
121
+ background: rgba(255, 105, 180, 0.2);
122
+ color: #fff;
123
+ }
124
+
125
+ #btn-voice.active {
126
+ background: rgba(0, 255, 255, 0.2);
127
+ color: #0ff;
128
+ border-color: #0ff;
129
+ box-shadow: 0 0 10px rgba(0, 255, 255, 0.3);
130
+ }
131
+
132
+ /* Draggable & Resizable Camera Wrapper */
133
+ #camera-wrapper {
134
+ position: absolute;
135
+ bottom: 15px;
136
+ right: 15px;
137
+ width: 200px;
138
+ height: 150px;
139
+ z-index: 18; /* Higher than canvas-container (15) */
140
+ background: rgba(0,0,0,0.5);
141
+ border: 1px solid #444;
142
+ border-radius: 8px;
143
+ display: flex;
144
+ flex-direction: column;
145
+ resize: both; /* Enable resizing */
146
+ overflow: hidden; /* Required for resize handle */
147
+ min-width: 100px;
148
+ min-height: 80px;
149
+ box-shadow: 0 0 10px rgba(0,0,0,0.5);
150
+ pointer-events: auto;
151
+ }
152
+
153
+ #camera-handle {
154
+ background: rgba(255, 105, 180, 0.2);
155
+ color: #ccc;
156
+ font-size: 10px;
157
+ text-align: center;
158
+ cursor: grab;
159
+ padding: 2px 0;
160
+ user-select: none;
161
+ flex-shrink: 0;
162
+ }
163
+ #camera-handle:active {
164
+ cursor: grabbing;
165
+ background: rgba(255, 105, 180, 0.4);
166
+ }
167
+
168
+ #webcam-preview {
169
+ width: 100%;
170
+ height: 100%;
171
+ object-fit: cover;
172
+ transform: scaleX(-1);
173
+ opacity: 0.8;
174
+ pointer-events: none; /* Let events pass to wrapper for resize */
175
+ }
176
+
177
+ /* Fullscreen override for AR mode */
178
+ #camera-wrapper.fullscreen {
179
+ width: 100vw !important;
180
+ height: 100vh !important;
181
+ top: 0 !important;
182
+ left: 0 !important;
183
+ right: auto !important;
184
+ bottom: auto !important;
185
+ border: none;
186
+ border-radius: 0;
187
+ resize: none;
188
+ background: black;
189
+ z-index: 1 !important; /* Move BEHIND canvas-container (15) in AR mode */
190
+ pointer-events: none; /* Let clicks pass through to canvas if needed */
191
+ }
192
+
193
+ #camera-wrapper.fullscreen #camera-handle {
194
+ display: none;
195
+ }
196
+
197
+ #camera-wrapper.fullscreen #webcam-preview {
198
+ opacity: 1.0;
199
+ }
200
+
201
+ #loading {
202
+ position: absolute;
203
+ top: 50%;
204
+ left: 50%;
205
+ transform: translate(-50%, -50%);
206
+ color: white;
207
+ z-index: 30;
208
+ font-size: 18px;
209
+ text-align: center;
210
+ background: rgba(0,0,0,0.8);
211
+ padding: 20px;
212
+ border-radius: 12px;
213
+ pointer-events: none;
214
+ }
215
+
216
+ #voice-indicator {
217
+ display: inline-block;
218
+ width: 8px;
219
+ height: 8px;
220
+ border-radius: 50%;
221
+ background-color: #555;
222
+ transition: all 0.2s;
223
+ }
224
+ #voice-indicator.listening {
225
+ background-color: #00ff00;
226
+ box-shadow: 0 0 6px #00ff00;
227
+ }
228
+ </style>
229
+
230
+ <!-- Three.js -->
231
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
232
+ <script src="https://unpkg.com/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
233
+
234
+ <!-- MediaPipe Hands -->
235
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js" crossorigin="anonymous"></script>
236
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/control_utils/control_utils.js" crossorigin="anonymous"></script>
237
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.js" crossorigin="anonymous"></script>
238
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js" crossorigin="anonymous"></script>
239
+ <style>
240
+ /* Fix for GitHub Pages 404s */
241
+ </style>
242
+ </head>
243
+ <body>
244
+
245
+ <div id="loading">Starting System...<br><span style="font-size:14px; color:#aaa;">Allow Camera & Mic</span></div>
246
+
247
+ <div id="ui-layer">
248
+ <div class="ui-header-row">
249
+ <h1>Controls</h1>
250
+ <button id="btn-collapse">−</button>
251
+ </div>
252
+
253
+ <div id="ui-content">
254
+ <button id="btn-voice">
255
+ <div id="voice-indicator"></div>
256
+ <span id="voice-btn-text">Enable Voice Mode</span>
257
+ </button>
258
+
259
+ <div class="stat-row">
260
+ <span>Gesture/Word:</span>
261
+ <span id="gesture-val" class="value">...</span>
262
+ </div>
263
+ <div class="stat-row">
264
+ <span>Fingers:</span>
265
+ <span id="finger-val" class="value">0</span>
266
+ </div>
267
+
268
+ <hr style="border: 0; border-top: 1px solid rgba(255,255,255,0.1); margin: 5px 0;">
269
+ <div style="font-size: 11px; color: #ddd; pointer-events: none;">
270
+ <b>Voice Mode ON:</b><br>
271
+ 🎤 Say ANY word<br>
272
+ 👋 Touch text to scatter<br>
273
+ <br>
274
+ <b>Voice Mode OFF:</b><br>
275
+ ☝️ 1: "I"<br>
276
+ ✌️ 2: "LOVE"<br>
277
+ 🤟 3: "YOU"<br>
278
+ ✋ 4: "I LOVE YOU"<br>
279
+ ✊ Fist: Shrink
280
+ </div>
281
+ </div>
282
+ </div>
283
+
284
+ <!-- Camera Wrapper for Drag & Resize -->
285
+ <div id="camera-wrapper">
286
+ <div id="camera-handle">:: Drag to Move ::</div>
287
+ <video id="webcam-preview" playsinline></video>
288
+ </div>
289
+
290
+ <div id="canvas-container"></div>
291
+
292
+ <script>
293
+ /**
294
+ * CONFIGURATION
295
+ */
296
+ const CONFIG = {
297
+ particleCount: 40000,
298
+ text1: "I",
299
+ text2: "LOVE",
300
+ text3: "YOU",
301
+ text4: "I LOVE YOU",
302
+ particleSize: 0.08,
303
+ scatterRadius: 35,
304
+ textScale: 0.055,
305
+ camZ: 40,
306
+ interactionRadius: 8.0,
307
+ repulsionStrength: 8.0
308
+ };
309
+
310
+ /**
311
+ * STATE MANAGEMENT
312
+ */
313
+ const state = {
314
+ targetGestureLabel: "Waiting...",
315
+ currentWeights: [0, 0, 0, 0, 0],
316
+ targetWeights: [0, 0, 0, 0, 0],
317
+
318
+ spreadTarget: 1.0,
319
+ currentSpread: 1.0,
320
+
321
+ scatterScaleTarget: 1.0,
322
+ currentScatterScale: 1.0,
323
+
324
+ fingerCount: 0,
325
+ galaxyEffectActive: false,
326
+
327
+ voiceModeActive: false,
328
+ wasVoiceModeActive: false,
329
+ voiceEnabledByUser: false,
330
+
331
+ // Rotation Logic
332
+ handPositionRaw: { x: 0.5, y: 0.5 },
333
+
334
+ // Interaction Physics Logic
335
+ handPositions: [],
336
+ isHandDetected: false
337
+ };
338
+
339
+ /**
340
+ * UI INTERACTION LOGIC (Collapse & Drag)
341
+ */
342
+
343
+ // 1. Collapse UI
344
+ const btnCollapse = document.getElementById('btn-collapse');
345
+ const uiLayer = document.getElementById('ui-layer');
346
+ btnCollapse.addEventListener('click', () => {
347
+ uiLayer.classList.toggle('collapsed');
348
+ btnCollapse.innerText = uiLayer.classList.contains('collapsed') ? '+' : '−';
349
+ });
350
+
351
+ // 2. Drag Camera
352
+ const camWrapper = document.getElementById('camera-wrapper');
353
+ const camHandle = document.getElementById('camera-handle');
354
+ let isDragging = false;
355
+ let dragOffset = { x: 0, y: 0 };
356
+
357
+ camHandle.addEventListener('mousedown', (e) => {
358
+ if (state.voiceModeActive) return; // Disable drag in full screen
359
+ isDragging = true;
360
+ const rect = camWrapper.getBoundingClientRect();
361
+ dragOffset.x = e.clientX - rect.left;
362
+ dragOffset.y = e.clientY - rect.top;
363
+
364
+ // Switch from bottom/right positioning to top/left for dragging logic
365
+ camWrapper.style.bottom = 'auto';
366
+ camWrapper.style.right = 'auto';
367
+ camWrapper.style.left = rect.left + 'px';
368
+ camWrapper.style.top = rect.top + 'px';
369
+ });
370
+
371
+ window.addEventListener('mousemove', (e) => {
372
+ if (isDragging) {
373
+ camWrapper.style.left = (e.clientX - dragOffset.x) + 'px';
374
+ camWrapper.style.top = (e.clientY - dragOffset.y) + 'px';
375
+ }
376
+ });
377
+
378
+ window.addEventListener('mouseup', () => {
379
+ isDragging = false;
380
+ });
381
+
382
+
383
+ /**
384
+ * THREE.JS SETUP
385
+ */
386
+ const container = document.getElementById('canvas-container');
387
+ const scene = new THREE.Scene();
388
+ scene.background = new THREE.Color(0x050505);
389
+ scene.fog = new THREE.FogExp2(0x050505, 0.012);
390
+
391
+ const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
392
+ camera.position.z = CONFIG.camZ;
393
+
394
+ const vFOV = THREE.MathUtils.degToRad(camera.fov);
395
+ const heightAtZero = 2 * Math.tan(vFOV / 2) * camera.position.z;
396
+ const widthAtZero = heightAtZero * camera.aspect;
397
+
398
+ const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
399
+ renderer.setSize(window.innerWidth, window.innerHeight);
400
+ renderer.setPixelRatio(window.devicePixelRatio);
401
+ container.appendChild(renderer.domElement);
402
+
403
+ const controls = new THREE.OrbitControls(camera, renderer.domElement);
404
+ controls.enableDamping = true;
405
+ controls.dampingFactor = 0.05;
406
+ controls.autoRotate = false;
407
+ controls.enableZoom = false;
408
+
409
+ window.addEventListener('resize', () => {
410
+ camera.aspect = window.innerWidth / window.innerHeight;
411
+ camera.updateProjectionMatrix();
412
+ renderer.setSize(window.innerWidth, window.innerHeight);
413
+ });
414
+
415
+ /**
416
+ * PARTICLE SYSTEM & TEXT GENERATION
417
+ */
418
+
419
+ // Dynamic Text Generator (Reuse for Voice)
420
+ function generateTextCoordinates(text, step = 2, scaleOverride = null) {
421
+ console.log(`Generating text coords for: "${text}"`);
422
+ const canvas = document.createElement('canvas');
423
+ const ctx = canvas.getContext('2d');
424
+ const width = 3000;
425
+ const height = 800;
426
+ canvas.width = width;
427
+ canvas.height = height;
428
+
429
+ ctx.fillStyle = 'black';
430
+ ctx.fillRect(0, 0, width, height);
431
+ ctx.fillStyle = 'white';
432
+ ctx.font = '900 250px Arial, sans-serif'; // Simplified font
433
+ ctx.textAlign = 'center';
434
+ ctx.textBaseline = 'middle';
435
+ ctx.fillText(text, width / 2, height / 2);
436
+
437
+ const imageData = ctx.getImageData(0, 0, width, height);
438
+ const data = imageData.data;
439
+ const coords = [];
440
+
441
+ const finalScale = scaleOverride || CONFIG.textScale;
442
+
443
+ for (let y = 0; y < height; y += step) {
444
+ for (let x = 0; x < width; x += step) {
445
+ const i = (y * width + x) * 4;
446
+ if (data[i] > 128) {
447
+ const pX = (x - width / 2) * finalScale;
448
+ const pY = -(y - height / 2) * finalScale;
449
+ coords.push(new THREE.Vector3(pX, pY, 0));
450
+ }
451
+ }
452
+ }
453
+ console.log(`Generated ${coords.length} points for "${text}"`);
454
+ return coords;
455
+ }
456
+
457
+ const coordsText1 = generateTextCoordinates(CONFIG.text1, 3);
458
+ const coordsText2 = generateTextCoordinates(CONFIG.text2, 3);
459
+ const coordsText3 = generateTextCoordinates(CONFIG.text3, 3);
460
+ const coordsText4 = generateTextCoordinates(CONFIG.text4, 3);
461
+ let coordsText5 = generateTextCoordinates("HELLO", 2);
462
+
463
+ const particleGeometry = new THREE.BufferGeometry();
464
+ const positions = new Float32Array(CONFIG.particleCount * 3);
465
+ const colors = new Float32Array(CONFIG.particleCount * 3);
466
+
467
+ const posScatter = [];
468
+ const posText1 = [];
469
+ const posText2 = [];
470
+ const posText3 = [];
471
+ const posText4 = [];
472
+ const posText5 = [];
473
+
474
+ function fillPosArray(targetArray, sourceCoords) {
475
+ targetArray.length = 0;
476
+ const depth = 2.0;
477
+ const noise = 0.2;
478
+ for (let i = 0; i < CONFIG.particleCount; i++) {
479
+ if (sourceCoords.length === 0) {
480
+ targetArray.push(new THREE.Vector3(0,0,0));
481
+ continue;
482
+ }
483
+ const index = Math.floor(Math.random() * sourceCoords.length);
484
+ const p = sourceCoords[index];
485
+ targetArray.push(new THREE.Vector3(
486
+ p.x + (Math.random() - 0.5) * noise,
487
+ p.y + (Math.random() - 0.5) * noise,
488
+ p.z + (Math.random() - 0.5) * depth
489
+ ));
490
+ }
491
+ }
492
+
493
+ for (let i = 0; i < CONFIG.particleCount; i++) {
494
+ const theta = Math.random() * Math.PI * 2;
495
+ const phi = Math.acos(Math.random() * 2 - 1);
496
+ const r = Math.cbrt(Math.random()) * CONFIG.scatterRadius;
497
+ const x = r * Math.sin(phi) * Math.cos(theta);
498
+ const y = r * Math.sin(phi) * Math.sin(theta);
499
+ const z = r * Math.cos(phi);
500
+ positions[i * 3] = x;
501
+ positions[i * 3 + 1] = y;
502
+ positions[i * 3 + 2] = z;
503
+ colors[i * 3] = 1; colors[i * 3 + 1] = 1; colors[i * 3 + 2] = 1;
504
+ posScatter.push(new THREE.Vector3(x, y, z));
505
+ }
506
+
507
+ fillPosArray(posText1, coordsText1);
508
+ fillPosArray(posText2, coordsText2);
509
+ fillPosArray(posText3, coordsText3);
510
+ fillPosArray(posText4, coordsText4);
511
+ fillPosArray(posText5, coordsText5);
512
+
513
+ particleGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
514
+ particleGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
515
+
516
+ const particleMaterial = new THREE.PointsMaterial({
517
+ vertexColors: true,
518
+ size: CONFIG.particleSize,
519
+ transparent: true,
520
+ opacity: 0.9,
521
+ blending: THREE.AdditiveBlending,
522
+ depthWrite: false,
523
+ sizeAttenuation: true
524
+ });
525
+
526
+ const particleSystem = new THREE.Points(particleGeometry, particleMaterial);
527
+ scene.add(particleSystem);
528
+
529
+ function updateDynamicText(text) {
530
+ const length = Math.max(text.length, 3);
531
+ let optimalScale = 0.09;
532
+ if (length > 4) {
533
+ optimalScale = 0.4 / length;
534
+ }
535
+ optimalScale = Math.max(0.035, optimalScale);
536
+
537
+ const newCoords = generateTextCoordinates(text, 2, optimalScale);
538
+ fillPosArray(posText5, newCoords);
539
+ }
540
+
541
+ /**
542
+ * SYSTEM 3: Galaxy Background
543
+ */
544
+ const galaxyCount = 8000;
545
+ const galaxyGeo = new THREE.BufferGeometry();
546
+ const galaxyPos = new Float32Array(galaxyCount * 3);
547
+ const galaxyColors = new Float32Array(galaxyCount * 3);
548
+
549
+ for(let i=0; i<galaxyCount; i++) {
550
+ const r = 20 + Math.random() * 60;
551
+ const theta = Math.random() * Math.PI * 2;
552
+ const phi = Math.acos(Math.random() * 2 - 1);
553
+
554
+ galaxyPos[i*3] = r * Math.sin(phi) * Math.cos(theta);
555
+ galaxyPos[i*3+1] = r * Math.sin(phi) * Math.sin(theta);
556
+ galaxyPos[i*3+2] = r * Math.cos(phi);
557
+
558
+ const c = new THREE.Color();
559
+ const rand = Math.random();
560
+ if(rand < 0.3) c.setHex(0x00ffff);
561
+ else if (rand < 0.6) c.setHex(0x0000ff);
562
+ else if (rand < 0.8) c.setHex(0x4b0082);
563
+ else c.setHex(0xffffff);
564
+
565
+ c.multiplyScalar(0.5 + Math.random() * 0.5);
566
+
567
+ galaxyColors[i*3] = c.r;
568
+ galaxyColors[i*3+1] = c.g;
569
+ galaxyColors[i*3+2] = c.b;
570
+ }
571
+
572
+ galaxyGeo.setAttribute('position', new THREE.BufferAttribute(galaxyPos, 3));
573
+ galaxyGeo.setAttribute('color', new THREE.BufferAttribute(galaxyColors, 3));
574
+
575
+ const galaxyMaterial = new THREE.PointsMaterial({
576
+ vertexColors: true,
577
+ size: 0.04,
578
+ transparent: true,
579
+ opacity: 0,
580
+ blending: THREE.AdditiveBlending,
581
+ depthWrite: false
582
+ });
583
+
584
+ const galaxySystem = new THREE.Points(galaxyGeo, galaxyMaterial);
585
+ scene.add(galaxySystem);
586
+
587
+
588
+ /**
589
+ * VOICE RECOGNITION
590
+ */
591
+ const voiceBtn = document.getElementById('btn-voice');
592
+ const voiceIndicator = document.getElementById('voice-indicator');
593
+ const voiceBtnText = document.getElementById('voice-btn-text');
594
+
595
+ let recognition = null;
596
+
597
+ if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) {
598
+ const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
599
+ recognition = new SpeechRecognition();
600
+ recognition.continuous = true;
601
+ recognition.interimResults = false;
602
+ recognition.lang = 'en-US';
603
+
604
+ recognition.onstart = () => {
605
+ console.log("Voice recognition started");
606
+ voiceIndicator.classList.add('listening');
607
+ voiceBtnText.innerText = "Voice Mode: ON";
608
+ voiceBtn.classList.add('active');
609
+ };
610
+
611
+ recognition.onerror = (event) => {
612
+ console.error("Voice recognition error", event.error);
613
+ if (event.error === 'not-allowed') {
614
+ alert("Microphone access denied. Please allow microphone access.");
615
+ state.voiceEnabledByUser = false;
616
+ }
617
+ voiceIndicator.classList.remove('listening');
618
+ };
619
+
620
+ recognition.onend = () => {
621
+ console.log("Voice recognition ended");
622
+ voiceIndicator.classList.remove('listening');
623
+ if (state.voiceEnabledByUser) {
624
+ // Add a small delay before restarting to avoid rapid loops
625
+ setTimeout(() => {
626
+ try { recognition.start(); } catch(e) { console.warn("Restart failed", e); }
627
+ }, 300);
628
+ } else {
629
+ voiceBtnText.innerText = "Enable Voice Mode";
630
+ voiceBtn.classList.remove('active');
631
+ }
632
+ };
633
+
634
+ recognition.onresult = (event) => {
635
+ if (!state.voiceEnabledByUser) return;
636
+
637
+ const last = event.results.length - 1;
638
+ const transcript = event.results[last][0].transcript.trim().toUpperCase();
639
+
640
+ const words = transcript.split(' ');
641
+ const displayPhrase = words.slice(-3).join(' ');
642
+
643
+ if (displayPhrase.length > 0) {
644
+ updateDynamicText(displayPhrase);
645
+ state.targetGestureLabel = "Voice: " + displayPhrase;
646
+ state.targetWeights = [0, 0, 0, 0, 1];
647
+ state.spreadTarget = 0.0;
648
+ }
649
+ };
650
+ } else {
651
+ voiceBtnText.innerText = "Voice Not Supported";
652
+ voiceBtn.disabled = true;
653
+ }
654
+
655
+ voiceBtn.addEventListener('click', () => {
656
+ if (!recognition) return;
657
+
658
+ state.voiceEnabledByUser = !state.voiceEnabledByUser;
659
+
660
+ if (state.voiceEnabledByUser) {
661
+ try {
662
+ recognition.start();
663
+ state.voiceModeActive = true;
664
+ state.targetGestureLabel = "Listening...";
665
+ state.spreadTarget = 0.0;
666
+ state.targetWeights = [0, 0, 0, 0, 1];
667
+
668
+ updateDynamicText("HELLO");
669
+ } catch(e) { console.warn(e); }
670
+ } else {
671
+ recognition.stop();
672
+ state.voiceModeActive = false;
673
+ state.targetGestureLabel = "Voice Mode OFF";
674
+ state.spreadTarget = 1.0;
675
+ }
676
+ });
677
+
678
+
679
+ /**
680
+ * HAND TRACKING & INTERACTION
681
+ */
682
+ const videoElement = document.getElementById('webcam-preview');
683
+ const uiGesture = document.getElementById('gesture-val');
684
+ const uiFingers = document.getElementById('finger-val');
685
+ const loading = document.getElementById('loading');
686
+
687
+ function onResults(results) {
688
+ loading.style.display = 'none';
689
+
690
+ let detectedGesture = 0;
691
+ let fCount = 0;
692
+ let openness = 1.0;
693
+
694
+ state.handPositions = [];
695
+
696
+ if (results.multiHandLandmarks && results.multiHandLandmarks.length > 0) {
697
+ state.isHandDetected = true;
698
+
699
+ for (const landmarks of results.multiHandLandmarks) {
700
+ const palmX = 1.0 - landmarks[9].x;
701
+ const palmY = landmarks[9].y;
702
+ const hPos = new THREE.Vector3(
703
+ (palmX - 0.5) * widthAtZero,
704
+ -(palmY - 0.5) * heightAtZero,
705
+ 0
706
+ );
707
+ state.handPositions.push(hPos);
708
+ }
709
+
710
+ const landmarks = results.multiHandLandmarks[0];
711
+ const handedness = results.multiHandedness[0].label;
712
+
713
+ const wrist = landmarks[0];
714
+ state.handPositionRaw.x = 1.0 - wrist.x;
715
+ state.handPositionRaw.y = wrist.y;
716
+
717
+ fCount = countFingers(landmarks, handedness);
718
+ openness = getHandOpenness(landmarks);
719
+
720
+ if (state.voiceEnabledByUser) {
721
+ state.targetWeights = [0, 0, 0, 0, 1];
722
+ state.spreadTarget = 0.0;
723
+ state.galaxyEffectActive = false;
724
+ } else {
725
+ if (fCount === 1) detectedGesture = 1;
726
+ else if (fCount === 2) detectedGesture = 2;
727
+ else if (fCount === 3) detectedGesture = 3;
728
+ else if (fCount === 4) detectedGesture = 4;
729
+
730
+ if (detectedGesture > 0) {
731
+ state.targetGestureLabel = getLabel(detectedGesture);
732
+ state.spreadTarget = 0.0;
733
+ state.targetWeights = getWeights(detectedGesture);
734
+ state.galaxyEffectActive = (detectedGesture === 4);
735
+ } else {
736
+ state.targetGestureLabel = fCount === 0 ? "Fist (Contract)" : "Scatter (Expand)";
737
+ state.spreadTarget = 1.0;
738
+ state.galaxyEffectActive = false;
739
+ const minScale = 0.1;
740
+ const maxScale = 1.5;
741
+ state.scatterScaleTarget = minScale + openness * (maxScale - minScale);
742
+ }
743
+ }
744
+
745
+ state.fingerCount = fCount;
746
+
747
+ } else {
748
+ state.isHandDetected = false;
749
+
750
+ if (state.voiceEnabledByUser) {
751
+ state.spreadTarget = 0.0;
752
+ state.targetWeights = [0, 0, 0, 0, 1];
753
+ state.handPositionRaw.x = 0.5;
754
+ state.handPositionRaw.y = 0.5;
755
+ } else {
756
+ state.spreadTarget = 1.0;
757
+ state.targetGestureLabel = "Waiting...";
758
+ state.scatterScaleTarget = 1.0;
759
+ state.handPositionRaw.x = 0.5;
760
+ state.handPositionRaw.y = 0.5;
761
+ }
762
+
763
+ state.fingerCount = 0;
764
+ state.galaxyEffectActive = false;
765
+ }
766
+
767
+ updateUI();
768
+ }
769
+
770
+ function getLabel(g) {
771
+ if(g===1) return CONFIG.text1;
772
+ if(g===2) return CONFIG.text2;
773
+ if(g===3) return CONFIG.text3;
774
+ if(g===4) return "I LOVE YOU";
775
+ return "";
776
+ }
777
+
778
+ function getWeights(g) {
779
+ if(g===1) return [1,0,0,0,0];
780
+ if(g===2) return [0,1,0,0,0];
781
+ if(g===3) return [0,0,1,0,0];
782
+ if(g===4) return [0,0,0,1,0];
783
+ return state.targetWeights;
784
+ }
785
+
786
+ function getHandOpenness(landmarks) {
787
+ const wrist = landmarks[0];
788
+ const tips = [8, 12, 16, 20];
789
+ const mcps = [5, 9, 13, 17];
790
+ let distTips = 0, distMcps = 0;
791
+ for(let i=0; i<4; i++){
792
+ distTips += Math.hypot(landmarks[tips[i]].x - wrist.x, landmarks[tips[i]].y - wrist.y);
793
+ distMcps += Math.hypot(landmarks[mcps[i]].x - wrist.x, landmarks[mcps[i]].y - wrist.y);
794
+ }
795
+ const ratio = distTips / distMcps;
796
+ return Math.max(0, Math.min(1, (ratio - 1.0) / 1.2));
797
+ }
798
+
799
+ function countFingers(landmarks, handedness) {
800
+ let count = 0;
801
+ const fingerTips = [8, 12, 16, 20];
802
+ const fingerPips = [6, 10, 14, 18];
803
+ for (let i = 0; i < 4; i++) {
804
+ if (landmarks[fingerTips[i]].y < landmarks[fingerPips[i]].y) count++;
805
+ }
806
+ const thumbTip = landmarks[4];
807
+ const thumbIp = landmarks[3];
808
+ const isRightHand = handedness === 'Right';
809
+ if (isRightHand) {
810
+ if (thumbTip.x < thumbIp.x) count++;
811
+ } else {
812
+ if (thumbTip.x > thumbIp.x) count++;
813
+ }
814
+ return count;
815
+ }
816
+
817
+ function updateUI() {
818
+ uiGesture.innerText = state.targetGestureLabel;
819
+ uiFingers.innerText = state.fingerCount;
820
+ }
821
+
822
+ const hands = new Hands({locateFile: (file) => {
823
+ return `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`;
824
+ }});
825
+
826
+ hands.setOptions({
827
+ maxNumHands: 2,
828
+ modelComplexity: 1,
829
+ minDetectionConfidence: 0.5,
830
+ minTrackingConfidence: 0.5
831
+ });
832
+
833
+ hands.onResults(onResults);
834
+
835
+ const cameraFeed = new Camera(videoElement, {
836
+ onFrame: async () => {
837
+ await hands.send({image: videoElement});
838
+ },
839
+ width: 1280,
840
+ height: 720
841
+ });
842
+
843
+ async function startCamera() {
844
+ try {
845
+ loading.innerHTML = "Requesting Camera Access...";
846
+ // Check if browser supports getUserMedia
847
+ if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
848
+ throw new Error("Browser API 'navigator.mediaDevices.getUserMedia' not available");
849
+ }
850
+
851
+ // Explicitly request permission first to debug
852
+ const stream = await navigator.mediaDevices.getUserMedia({ video: true });
853
+ // Stop the stream immediately, we just wanted to check permission/availability
854
+ stream.getTracks().forEach(track => track.stop());
855
+
856
+ loading.innerHTML = "Starting MediaPipe Camera...";
857
+ await cameraFeed.start();
858
+ } catch (err) {
859
+ console.error("Camera Error:", err);
860
+ loading.innerHTML = `Camera not found (${err.name}).<br>Switching to <b>Mouse Interaction Mode</b>.`;
861
+ setTimeout(() => {
862
+ loading.style.display = 'none';
863
+ activateMouseMode();
864
+ }, 2500);
865
+ }
866
+ }
867
+
868
+ let isMouseMode = false;
869
+ function activateMouseMode() {
870
+ isMouseMode = true;
871
+ state.isHandDetected = true; // Always "detect" hand in mouse mode
872
+
873
+ // Keep camera wrapper visible but indicate no signal
874
+ const camWrapper = document.getElementById('camera-wrapper');
875
+ camWrapper.style.display = 'flex'; // Ensure it's visible
876
+ camWrapper.style.justifyContent = 'center';
877
+ camWrapper.style.alignItems = 'center';
878
+ camWrapper.style.background = 'rgba(20, 20, 20, 0.8)';
879
+ camWrapper.style.border = '1px solid #444';
880
+
881
+ const video = document.getElementById('webcam-preview');
882
+ video.style.opacity = '0.1'; // Dim the broken video element
883
+
884
+ // Add a placeholder text if not exists
885
+ if (!document.getElementById('no-cam-msg')) {
886
+ const msg = document.createElement('div');
887
+ msg.id = 'no-cam-msg';
888
+ msg.innerHTML = "NO CAMERA<br>Mouse Mode";
889
+ msg.style.color = '#aaa';
890
+ msg.style.textAlign = 'center';
891
+ msg.style.fontSize = '12px';
892
+ msg.style.position = 'absolute';
893
+ camWrapper.appendChild(msg);
894
+ }
895
+
896
+ // Add mouse listener for interaction
897
+ window.addEventListener('mousemove', (event) => {
898
+ // Map mouse to 3D world coordinates on z=0 plane
899
+ const vec = new THREE.Vector3();
900
+ const pos = new THREE.Vector3();
901
+
902
+ vec.set(
903
+ (event.clientX / window.innerWidth) * 2 - 1,
904
+ -(event.clientY / window.innerHeight) * 2 + 1,
905
+ 0.5
906
+ );
907
+
908
+ vec.unproject(camera);
909
+ vec.sub(camera.position).normalize();
910
+
911
+ const distance = -camera.position.z / vec.z;
912
+ pos.copy(camera.position).add(vec.multiplyScalar(distance));
913
+
914
+ state.handPositions = [pos];
915
+
916
+ // Map mouse X to rotation
917
+ state.handPositionRaw.x = event.clientX / window.innerWidth;
918
+ state.handPositionRaw.y = event.clientY / window.innerHeight;
919
+ });
920
+
921
+ // Add click listener to cycle gestures
922
+ let gestureIndex = 0;
923
+ window.addEventListener('click', () => {
924
+ if (state.voiceModeActive) return;
925
+
926
+ gestureIndex = (gestureIndex + 1) % 5; // 0 to 4
927
+ const g = gestureIndex;
928
+
929
+ if (g === 0) {
930
+ state.targetGestureLabel = "Mouse Click: Scatter";
931
+ state.spreadTarget = 1.0;
932
+ state.galaxyEffectActive = false;
933
+ } else {
934
+ state.targetGestureLabel = "Mouse Click: " + getLabel(g);
935
+ state.spreadTarget = 0.0;
936
+ state.targetWeights = getWeights(g);
937
+ state.galaxyEffectActive = (g === 4);
938
+ }
939
+ updateUI();
940
+ });
941
+
942
+ // Update UI instructions
943
+ const uiContent = document.getElementById('ui-content');
944
+ const helpText = uiContent.querySelector('div[style*="font-size: 11px"]');
945
+ if(helpText) {
946
+ helpText.innerHTML = `
947
+ <b>Mouse Mode Active:</b><br>
948
+ 🖱️ Move mouse to interact<br>
949
+ 🖱️ Click to cycle gestures<br>
950
+ (I -> LOVE -> YOU -> ...)<br>
951
+ <br>
952
+ <b>Voice Mode:</b><br>
953
+ 🎤 Still works if Mic is available
954
+ `;
955
+ }
956
+ }
957
+
958
+ startCamera();
959
+
960
+ /**
961
+ * ANIMATION
962
+ */
963
+ function animate() {
964
+ requestAnimationFrame(animate);
965
+
966
+ // Handle AR Mode Transition
967
+ if (state.voiceModeActive !== state.wasVoiceModeActive) {
968
+ if (state.voiceModeActive) {
969
+ camWrapper.classList.add('fullscreen');
970
+ // Only make background transparent if camera is actually working/visible
971
+ if (!isMouseMode) {
972
+ scene.background = null;
973
+ } else {
974
+ scene.background = new THREE.Color(0x050505);
975
+ }
976
+
977
+ // Reset position when entering fullscreen to ensure it covers everything
978
+ camWrapper.style.top = '';
979
+ camWrapper.style.left = '';
980
+ camWrapper.style.bottom = '';
981
+ camWrapper.style.right = '';
982
+ } else {
983
+ camWrapper.classList.remove('fullscreen');
984
+ scene.background = new THREE.Color(0x050505);
985
+ // Reset to default corner position or keep last dragged position?
986
+ // Let's reset for safety or keep it if dragged.
987
+ // Since we modified inline styles during drag, they persist.
988
+ if (!camWrapper.style.top) {
989
+ camWrapper.style.bottom = '15px';
990
+ camWrapper.style.right = '15px';
991
+ }
992
+ }
993
+ state.wasVoiceModeActive = state.voiceModeActive;
994
+ }
995
+
996
+ // Interpolators
997
+ state.currentSpread += (state.spreadTarget - state.currentSpread) * 0.08;
998
+ state.currentScatterScale += (state.scatterScaleTarget - state.currentScatterScale) * 0.1;
999
+
1000
+ for(let i=0; i<5; i++) {
1001
+ state.currentWeights[i] += (state.targetWeights[i] - state.currentWeights[i]) * 0.1;
1002
+ }
1003
+
1004
+ // Hand Rotation
1005
+ const targetRotY = (state.handPositionRaw.x - 0.5) * 1.5;
1006
+ const targetRotX = (state.handPositionRaw.y - 0.5) * 1.5;
1007
+ particleSystem.rotation.y += (targetRotY - particleSystem.rotation.y) * 0.1;
1008
+ particleSystem.rotation.x += (targetRotX - particleSystem.rotation.x) * 0.1;
1009
+
1010
+ // Rotate Galaxy (independent)
1011
+ galaxySystem.rotation.y += 0.002;
1012
+ galaxySystem.rotation.x += 0.001;
1013
+
1014
+ const positionsArray = particleGeometry.attributes.position.array;
1015
+ const colorsArray = particleGeometry.attributes.color.array;
1016
+
1017
+ const time = Date.now() * 0.001;
1018
+
1019
+ const w1 = state.currentWeights[0];
1020
+ const w2 = state.currentWeights[1];
1021
+ const w3 = state.currentWeights[2];
1022
+ const w4 = state.currentWeights[3];
1023
+ const w5 = state.currentWeights[4];
1024
+
1025
+ const interactionRadSq = CONFIG.interactionRadius * CONFIG.interactionRadius;
1026
+
1027
+ for (let i = 0; i < CONFIG.particleCount; i++) {
1028
+ // 1. Calculate Target
1029
+ const p1 = posText1[i];
1030
+ const p2 = posText2[i];
1031
+ const p3 = posText3[i];
1032
+ const p4 = posText4[i];
1033
+ const p5 = posText5[i]; // Dynamic Text
1034
+
1035
+ const tx = p1.x*w1 + p2.x*w2 + p3.x*w3 + p4.x*w4 + p5.x*w5;
1036
+ const ty = p1.y*w1 + p2.y*w2 + p3.y*w3 + p4.y*w4 + p5.y*w5;
1037
+ const tz = p1.z*w1 + p2.z*w2 + p3.z*w3 + p4.z*w4 + p5.z*w5;
1038
+
1039
+ // 2. Scatter
1040
+ const s = posScatter[i];
1041
+ const sX = s.x * state.currentScatterScale;
1042
+ const sY = s.y * state.currentScatterScale;
1043
+ const sZ = s.z * state.currentScatterScale;
1044
+
1045
+ const noiseScale = 0.5 * state.currentScatterScale;
1046
+ const nX = Math.sin(time + i*0.1) * noiseScale;
1047
+ const nY = Math.cos(time + i*0.13) * noiseScale;
1048
+
1049
+ // 3. Blend
1050
+ let finalX = THREE.MathUtils.lerp(tx, sX + nX, state.currentSpread);
1051
+ let finalY = THREE.MathUtils.lerp(ty, sY + nY, state.currentSpread);
1052
+ let finalZ = THREE.MathUtils.lerp(tz, sZ, state.currentSpread);
1053
+
1054
+ // 4. Physics Interaction (Multi-Hand)
1055
+ if (state.voiceModeActive && state.handPositions.length > 0) {
1056
+ for (const hPos of state.handPositions) {
1057
+ const dx = finalX - hPos.x;
1058
+ const dy = finalY - hPos.y;
1059
+ const dz = finalZ - hPos.z;
1060
+ const distSq = dx*dx + dy*dy + dz*dz;
1061
+
1062
+ if (distSq < interactionRadSq) {
1063
+ const dist = Math.sqrt(distSq);
1064
+ const force = (CONFIG.interactionRadius - dist) / CONFIG.interactionRadius;
1065
+
1066
+ // Stronger repulsion logic
1067
+ const repulsion = Math.pow(force, 2) * CONFIG.repulsionStrength;
1068
+
1069
+ finalX += (dx / dist) * repulsion * 5;
1070
+ finalY += (dy / dist) * repulsion * 5;
1071
+ finalZ += (dz / dist) * repulsion * 5;
1072
+ }
1073
+ }
1074
+ }
1075
+
1076
+ // Update Position
1077
+ const cx = positionsArray[i*3];
1078
+ const cy = positionsArray[i*3+1];
1079
+ const cz = positionsArray[i*3+2];
1080
+ const speed = 0.1;
1081
+ positionsArray[i*3] += (finalX - cx) * speed;
1082
+ positionsArray[i*3+1] += (finalY - cy) * speed;
1083
+ positionsArray[i*3+2] += (finalZ - cz) * speed;
1084
+
1085
+ // 5. Colors (UNIFIED & NEW AR COLOR)
1086
+ const baseColor = new THREE.Color();
1087
+ const cPink1 = new THREE.Color(0xff69b4); // HotPink
1088
+ const cPurple = new THREE.Color(0xda70d6); // Orchid
1089
+ const cBlueNeon = new THREE.Color(0x00ffff); // Cyan for AR
1090
+ const cWhite = new THREE.Color(0xffffff);
1091
+
1092
+ if (state.currentSpread > 0.8) {
1093
+ // Scatter Color
1094
+ const dist = Math.sqrt(finalX*finalX + finalY*finalY + finalZ*finalZ);
1095
+ const normDist = Math.min(dist / 40, 1);
1096
+ baseColor.setHSL(0.9, 0.5, 0.8 + normDist*0.2);
1097
+ } else {
1098
+ if (w5 > 0.5) {
1099
+ // AR / Voice Mode -> BLUE NEON / High Contrast
1100
+ baseColor.copy(cBlueNeon);
1101
+ // Make it brighter/more contrasty
1102
+ baseColor.offsetHSL(0, 0, 0.1);
1103
+ // Sparkle
1104
+ if (Math.random() > 0.92) baseColor.setHex(0xffffff);
1105
+ } else if (w1>0.5 || w2>0.5 || w3>0.5 || w4>0.5) {
1106
+ // All gestures now use the Unified Pink/Purple scheme (Love Theme)
1107
+ baseColor.lerpColors(cPink1, cPurple, 0.5);
1108
+ const hueOffset = (finalX / 60) * 0.1;
1109
+ baseColor.offsetHSL(hueOffset, 0, 0);
1110
+ } else {
1111
+ baseColor.setHex(0xffffff);
1112
+ }
1113
+ }
1114
+
1115
+ colorsArray[i*3] = baseColor.r;
1116
+ colorsArray[i*3+1] = baseColor.g;
1117
+ colorsArray[i*3+2] = baseColor.b;
1118
+ }
1119
+
1120
+ particleGeometry.attributes.position.needsUpdate = true;
1121
+ particleGeometry.attributes.color.needsUpdate = true;
1122
+
1123
+ // Galaxy Animation (Opacity matches Gesture 4)
1124
+ galaxyMaterial.opacity = THREE.MathUtils.lerp(galaxyMaterial.opacity, state.galaxyEffectActive ? 0.8 : 0.0, 0.03);
1125
+
1126
+ controls.update();
1127
+ renderer.render(scene, camera);
1128
+ }
1129
+
1130
+ animate();
1131
+
1132
+ </script>
1133
+ </body>
1134
+ </html>
index.tsx ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ // Logic is implemented in index.html using Vanilla JS and Three.js as requested.
2
+ console.log("Particle System running in index.html");
metadata.json ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "requestFramePermissions": [
3
+ "camera",
4
+ "microphone"
5
+ ],
6
+ "name": "App",
7
+ "description": ""
8
+ }
package-lock.json ADDED
@@ -0,0 +1,1092 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "app",
3
+ "version": "0.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "app",
9
+ "version": "0.0.0",
10
+ "devDependencies": {
11
+ "@types/node": "^22.14.0",
12
+ "typescript": "~5.8.2",
13
+ "vite": "^6.2.0"
14
+ }
15
+ },
16
+ "node_modules/@esbuild/aix-ppc64": {
17
+ "version": "0.25.12",
18
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
19
+ "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
20
+ "cpu": [
21
+ "ppc64"
22
+ ],
23
+ "dev": true,
24
+ "license": "MIT",
25
+ "optional": true,
26
+ "os": [
27
+ "aix"
28
+ ],
29
+ "engines": {
30
+ "node": ">=18"
31
+ }
32
+ },
33
+ "node_modules/@esbuild/android-arm": {
34
+ "version": "0.25.12",
35
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
36
+ "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
37
+ "cpu": [
38
+ "arm"
39
+ ],
40
+ "dev": true,
41
+ "license": "MIT",
42
+ "optional": true,
43
+ "os": [
44
+ "android"
45
+ ],
46
+ "engines": {
47
+ "node": ">=18"
48
+ }
49
+ },
50
+ "node_modules/@esbuild/android-arm64": {
51
+ "version": "0.25.12",
52
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
53
+ "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
54
+ "cpu": [
55
+ "arm64"
56
+ ],
57
+ "dev": true,
58
+ "license": "MIT",
59
+ "optional": true,
60
+ "os": [
61
+ "android"
62
+ ],
63
+ "engines": {
64
+ "node": ">=18"
65
+ }
66
+ },
67
+ "node_modules/@esbuild/android-x64": {
68
+ "version": "0.25.12",
69
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
70
+ "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
71
+ "cpu": [
72
+ "x64"
73
+ ],
74
+ "dev": true,
75
+ "license": "MIT",
76
+ "optional": true,
77
+ "os": [
78
+ "android"
79
+ ],
80
+ "engines": {
81
+ "node": ">=18"
82
+ }
83
+ },
84
+ "node_modules/@esbuild/darwin-arm64": {
85
+ "version": "0.25.12",
86
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
87
+ "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
88
+ "cpu": [
89
+ "arm64"
90
+ ],
91
+ "dev": true,
92
+ "license": "MIT",
93
+ "optional": true,
94
+ "os": [
95
+ "darwin"
96
+ ],
97
+ "engines": {
98
+ "node": ">=18"
99
+ }
100
+ },
101
+ "node_modules/@esbuild/darwin-x64": {
102
+ "version": "0.25.12",
103
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
104
+ "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
105
+ "cpu": [
106
+ "x64"
107
+ ],
108
+ "dev": true,
109
+ "license": "MIT",
110
+ "optional": true,
111
+ "os": [
112
+ "darwin"
113
+ ],
114
+ "engines": {
115
+ "node": ">=18"
116
+ }
117
+ },
118
+ "node_modules/@esbuild/freebsd-arm64": {
119
+ "version": "0.25.12",
120
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
121
+ "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
122
+ "cpu": [
123
+ "arm64"
124
+ ],
125
+ "dev": true,
126
+ "license": "MIT",
127
+ "optional": true,
128
+ "os": [
129
+ "freebsd"
130
+ ],
131
+ "engines": {
132
+ "node": ">=18"
133
+ }
134
+ },
135
+ "node_modules/@esbuild/freebsd-x64": {
136
+ "version": "0.25.12",
137
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
138
+ "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
139
+ "cpu": [
140
+ "x64"
141
+ ],
142
+ "dev": true,
143
+ "license": "MIT",
144
+ "optional": true,
145
+ "os": [
146
+ "freebsd"
147
+ ],
148
+ "engines": {
149
+ "node": ">=18"
150
+ }
151
+ },
152
+ "node_modules/@esbuild/linux-arm": {
153
+ "version": "0.25.12",
154
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
155
+ "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
156
+ "cpu": [
157
+ "arm"
158
+ ],
159
+ "dev": true,
160
+ "license": "MIT",
161
+ "optional": true,
162
+ "os": [
163
+ "linux"
164
+ ],
165
+ "engines": {
166
+ "node": ">=18"
167
+ }
168
+ },
169
+ "node_modules/@esbuild/linux-arm64": {
170
+ "version": "0.25.12",
171
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
172
+ "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
173
+ "cpu": [
174
+ "arm64"
175
+ ],
176
+ "dev": true,
177
+ "license": "MIT",
178
+ "optional": true,
179
+ "os": [
180
+ "linux"
181
+ ],
182
+ "engines": {
183
+ "node": ">=18"
184
+ }
185
+ },
186
+ "node_modules/@esbuild/linux-ia32": {
187
+ "version": "0.25.12",
188
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
189
+ "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
190
+ "cpu": [
191
+ "ia32"
192
+ ],
193
+ "dev": true,
194
+ "license": "MIT",
195
+ "optional": true,
196
+ "os": [
197
+ "linux"
198
+ ],
199
+ "engines": {
200
+ "node": ">=18"
201
+ }
202
+ },
203
+ "node_modules/@esbuild/linux-loong64": {
204
+ "version": "0.25.12",
205
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
206
+ "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
207
+ "cpu": [
208
+ "loong64"
209
+ ],
210
+ "dev": true,
211
+ "license": "MIT",
212
+ "optional": true,
213
+ "os": [
214
+ "linux"
215
+ ],
216
+ "engines": {
217
+ "node": ">=18"
218
+ }
219
+ },
220
+ "node_modules/@esbuild/linux-mips64el": {
221
+ "version": "0.25.12",
222
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
223
+ "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
224
+ "cpu": [
225
+ "mips64el"
226
+ ],
227
+ "dev": true,
228
+ "license": "MIT",
229
+ "optional": true,
230
+ "os": [
231
+ "linux"
232
+ ],
233
+ "engines": {
234
+ "node": ">=18"
235
+ }
236
+ },
237
+ "node_modules/@esbuild/linux-ppc64": {
238
+ "version": "0.25.12",
239
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
240
+ "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
241
+ "cpu": [
242
+ "ppc64"
243
+ ],
244
+ "dev": true,
245
+ "license": "MIT",
246
+ "optional": true,
247
+ "os": [
248
+ "linux"
249
+ ],
250
+ "engines": {
251
+ "node": ">=18"
252
+ }
253
+ },
254
+ "node_modules/@esbuild/linux-riscv64": {
255
+ "version": "0.25.12",
256
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
257
+ "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
258
+ "cpu": [
259
+ "riscv64"
260
+ ],
261
+ "dev": true,
262
+ "license": "MIT",
263
+ "optional": true,
264
+ "os": [
265
+ "linux"
266
+ ],
267
+ "engines": {
268
+ "node": ">=18"
269
+ }
270
+ },
271
+ "node_modules/@esbuild/linux-s390x": {
272
+ "version": "0.25.12",
273
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
274
+ "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
275
+ "cpu": [
276
+ "s390x"
277
+ ],
278
+ "dev": true,
279
+ "license": "MIT",
280
+ "optional": true,
281
+ "os": [
282
+ "linux"
283
+ ],
284
+ "engines": {
285
+ "node": ">=18"
286
+ }
287
+ },
288
+ "node_modules/@esbuild/linux-x64": {
289
+ "version": "0.25.12",
290
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
291
+ "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
292
+ "cpu": [
293
+ "x64"
294
+ ],
295
+ "dev": true,
296
+ "license": "MIT",
297
+ "optional": true,
298
+ "os": [
299
+ "linux"
300
+ ],
301
+ "engines": {
302
+ "node": ">=18"
303
+ }
304
+ },
305
+ "node_modules/@esbuild/netbsd-arm64": {
306
+ "version": "0.25.12",
307
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
308
+ "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
309
+ "cpu": [
310
+ "arm64"
311
+ ],
312
+ "dev": true,
313
+ "license": "MIT",
314
+ "optional": true,
315
+ "os": [
316
+ "netbsd"
317
+ ],
318
+ "engines": {
319
+ "node": ">=18"
320
+ }
321
+ },
322
+ "node_modules/@esbuild/netbsd-x64": {
323
+ "version": "0.25.12",
324
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
325
+ "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
326
+ "cpu": [
327
+ "x64"
328
+ ],
329
+ "dev": true,
330
+ "license": "MIT",
331
+ "optional": true,
332
+ "os": [
333
+ "netbsd"
334
+ ],
335
+ "engines": {
336
+ "node": ">=18"
337
+ }
338
+ },
339
+ "node_modules/@esbuild/openbsd-arm64": {
340
+ "version": "0.25.12",
341
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
342
+ "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
343
+ "cpu": [
344
+ "arm64"
345
+ ],
346
+ "dev": true,
347
+ "license": "MIT",
348
+ "optional": true,
349
+ "os": [
350
+ "openbsd"
351
+ ],
352
+ "engines": {
353
+ "node": ">=18"
354
+ }
355
+ },
356
+ "node_modules/@esbuild/openbsd-x64": {
357
+ "version": "0.25.12",
358
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
359
+ "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
360
+ "cpu": [
361
+ "x64"
362
+ ],
363
+ "dev": true,
364
+ "license": "MIT",
365
+ "optional": true,
366
+ "os": [
367
+ "openbsd"
368
+ ],
369
+ "engines": {
370
+ "node": ">=18"
371
+ }
372
+ },
373
+ "node_modules/@esbuild/openharmony-arm64": {
374
+ "version": "0.25.12",
375
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
376
+ "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
377
+ "cpu": [
378
+ "arm64"
379
+ ],
380
+ "dev": true,
381
+ "license": "MIT",
382
+ "optional": true,
383
+ "os": [
384
+ "openharmony"
385
+ ],
386
+ "engines": {
387
+ "node": ">=18"
388
+ }
389
+ },
390
+ "node_modules/@esbuild/sunos-x64": {
391
+ "version": "0.25.12",
392
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
393
+ "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
394
+ "cpu": [
395
+ "x64"
396
+ ],
397
+ "dev": true,
398
+ "license": "MIT",
399
+ "optional": true,
400
+ "os": [
401
+ "sunos"
402
+ ],
403
+ "engines": {
404
+ "node": ">=18"
405
+ }
406
+ },
407
+ "node_modules/@esbuild/win32-arm64": {
408
+ "version": "0.25.12",
409
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
410
+ "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
411
+ "cpu": [
412
+ "arm64"
413
+ ],
414
+ "dev": true,
415
+ "license": "MIT",
416
+ "optional": true,
417
+ "os": [
418
+ "win32"
419
+ ],
420
+ "engines": {
421
+ "node": ">=18"
422
+ }
423
+ },
424
+ "node_modules/@esbuild/win32-ia32": {
425
+ "version": "0.25.12",
426
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
427
+ "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
428
+ "cpu": [
429
+ "ia32"
430
+ ],
431
+ "dev": true,
432
+ "license": "MIT",
433
+ "optional": true,
434
+ "os": [
435
+ "win32"
436
+ ],
437
+ "engines": {
438
+ "node": ">=18"
439
+ }
440
+ },
441
+ "node_modules/@esbuild/win32-x64": {
442
+ "version": "0.25.12",
443
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
444
+ "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
445
+ "cpu": [
446
+ "x64"
447
+ ],
448
+ "dev": true,
449
+ "license": "MIT",
450
+ "optional": true,
451
+ "os": [
452
+ "win32"
453
+ ],
454
+ "engines": {
455
+ "node": ">=18"
456
+ }
457
+ },
458
+ "node_modules/@rollup/rollup-android-arm-eabi": {
459
+ "version": "4.53.3",
460
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz",
461
+ "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==",
462
+ "cpu": [
463
+ "arm"
464
+ ],
465
+ "dev": true,
466
+ "license": "MIT",
467
+ "optional": true,
468
+ "os": [
469
+ "android"
470
+ ]
471
+ },
472
+ "node_modules/@rollup/rollup-android-arm64": {
473
+ "version": "4.53.3",
474
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz",
475
+ "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==",
476
+ "cpu": [
477
+ "arm64"
478
+ ],
479
+ "dev": true,
480
+ "license": "MIT",
481
+ "optional": true,
482
+ "os": [
483
+ "android"
484
+ ]
485
+ },
486
+ "node_modules/@rollup/rollup-darwin-arm64": {
487
+ "version": "4.53.3",
488
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz",
489
+ "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==",
490
+ "cpu": [
491
+ "arm64"
492
+ ],
493
+ "dev": true,
494
+ "license": "MIT",
495
+ "optional": true,
496
+ "os": [
497
+ "darwin"
498
+ ]
499
+ },
500
+ "node_modules/@rollup/rollup-darwin-x64": {
501
+ "version": "4.53.3",
502
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz",
503
+ "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==",
504
+ "cpu": [
505
+ "x64"
506
+ ],
507
+ "dev": true,
508
+ "license": "MIT",
509
+ "optional": true,
510
+ "os": [
511
+ "darwin"
512
+ ]
513
+ },
514
+ "node_modules/@rollup/rollup-freebsd-arm64": {
515
+ "version": "4.53.3",
516
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz",
517
+ "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==",
518
+ "cpu": [
519
+ "arm64"
520
+ ],
521
+ "dev": true,
522
+ "license": "MIT",
523
+ "optional": true,
524
+ "os": [
525
+ "freebsd"
526
+ ]
527
+ },
528
+ "node_modules/@rollup/rollup-freebsd-x64": {
529
+ "version": "4.53.3",
530
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz",
531
+ "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==",
532
+ "cpu": [
533
+ "x64"
534
+ ],
535
+ "dev": true,
536
+ "license": "MIT",
537
+ "optional": true,
538
+ "os": [
539
+ "freebsd"
540
+ ]
541
+ },
542
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
543
+ "version": "4.53.3",
544
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz",
545
+ "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==",
546
+ "cpu": [
547
+ "arm"
548
+ ],
549
+ "dev": true,
550
+ "license": "MIT",
551
+ "optional": true,
552
+ "os": [
553
+ "linux"
554
+ ]
555
+ },
556
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
557
+ "version": "4.53.3",
558
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz",
559
+ "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==",
560
+ "cpu": [
561
+ "arm"
562
+ ],
563
+ "dev": true,
564
+ "license": "MIT",
565
+ "optional": true,
566
+ "os": [
567
+ "linux"
568
+ ]
569
+ },
570
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
571
+ "version": "4.53.3",
572
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz",
573
+ "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==",
574
+ "cpu": [
575
+ "arm64"
576
+ ],
577
+ "dev": true,
578
+ "license": "MIT",
579
+ "optional": true,
580
+ "os": [
581
+ "linux"
582
+ ]
583
+ },
584
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
585
+ "version": "4.53.3",
586
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz",
587
+ "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==",
588
+ "cpu": [
589
+ "arm64"
590
+ ],
591
+ "dev": true,
592
+ "license": "MIT",
593
+ "optional": true,
594
+ "os": [
595
+ "linux"
596
+ ]
597
+ },
598
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
599
+ "version": "4.53.3",
600
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz",
601
+ "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==",
602
+ "cpu": [
603
+ "loong64"
604
+ ],
605
+ "dev": true,
606
+ "license": "MIT",
607
+ "optional": true,
608
+ "os": [
609
+ "linux"
610
+ ]
611
+ },
612
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
613
+ "version": "4.53.3",
614
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz",
615
+ "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==",
616
+ "cpu": [
617
+ "ppc64"
618
+ ],
619
+ "dev": true,
620
+ "license": "MIT",
621
+ "optional": true,
622
+ "os": [
623
+ "linux"
624
+ ]
625
+ },
626
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
627
+ "version": "4.53.3",
628
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz",
629
+ "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==",
630
+ "cpu": [
631
+ "riscv64"
632
+ ],
633
+ "dev": true,
634
+ "license": "MIT",
635
+ "optional": true,
636
+ "os": [
637
+ "linux"
638
+ ]
639
+ },
640
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
641
+ "version": "4.53.3",
642
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz",
643
+ "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==",
644
+ "cpu": [
645
+ "riscv64"
646
+ ],
647
+ "dev": true,
648
+ "license": "MIT",
649
+ "optional": true,
650
+ "os": [
651
+ "linux"
652
+ ]
653
+ },
654
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
655
+ "version": "4.53.3",
656
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz",
657
+ "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==",
658
+ "cpu": [
659
+ "s390x"
660
+ ],
661
+ "dev": true,
662
+ "license": "MIT",
663
+ "optional": true,
664
+ "os": [
665
+ "linux"
666
+ ]
667
+ },
668
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
669
+ "version": "4.53.3",
670
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz",
671
+ "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==",
672
+ "cpu": [
673
+ "x64"
674
+ ],
675
+ "dev": true,
676
+ "license": "MIT",
677
+ "optional": true,
678
+ "os": [
679
+ "linux"
680
+ ]
681
+ },
682
+ "node_modules/@rollup/rollup-linux-x64-musl": {
683
+ "version": "4.53.3",
684
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz",
685
+ "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==",
686
+ "cpu": [
687
+ "x64"
688
+ ],
689
+ "dev": true,
690
+ "license": "MIT",
691
+ "optional": true,
692
+ "os": [
693
+ "linux"
694
+ ]
695
+ },
696
+ "node_modules/@rollup/rollup-openharmony-arm64": {
697
+ "version": "4.53.3",
698
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz",
699
+ "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==",
700
+ "cpu": [
701
+ "arm64"
702
+ ],
703
+ "dev": true,
704
+ "license": "MIT",
705
+ "optional": true,
706
+ "os": [
707
+ "openharmony"
708
+ ]
709
+ },
710
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
711
+ "version": "4.53.3",
712
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz",
713
+ "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==",
714
+ "cpu": [
715
+ "arm64"
716
+ ],
717
+ "dev": true,
718
+ "license": "MIT",
719
+ "optional": true,
720
+ "os": [
721
+ "win32"
722
+ ]
723
+ },
724
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
725
+ "version": "4.53.3",
726
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz",
727
+ "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==",
728
+ "cpu": [
729
+ "ia32"
730
+ ],
731
+ "dev": true,
732
+ "license": "MIT",
733
+ "optional": true,
734
+ "os": [
735
+ "win32"
736
+ ]
737
+ },
738
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
739
+ "version": "4.53.3",
740
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz",
741
+ "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==",
742
+ "cpu": [
743
+ "x64"
744
+ ],
745
+ "dev": true,
746
+ "license": "MIT",
747
+ "optional": true,
748
+ "os": [
749
+ "win32"
750
+ ]
751
+ },
752
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
753
+ "version": "4.53.3",
754
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz",
755
+ "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==",
756
+ "cpu": [
757
+ "x64"
758
+ ],
759
+ "dev": true,
760
+ "license": "MIT",
761
+ "optional": true,
762
+ "os": [
763
+ "win32"
764
+ ]
765
+ },
766
+ "node_modules/@types/estree": {
767
+ "version": "1.0.8",
768
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
769
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
770
+ "dev": true,
771
+ "license": "MIT"
772
+ },
773
+ "node_modules/@types/node": {
774
+ "version": "22.19.2",
775
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.2.tgz",
776
+ "integrity": "sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw==",
777
+ "dev": true,
778
+ "license": "MIT",
779
+ "dependencies": {
780
+ "undici-types": "~6.21.0"
781
+ }
782
+ },
783
+ "node_modules/esbuild": {
784
+ "version": "0.25.12",
785
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
786
+ "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
787
+ "dev": true,
788
+ "hasInstallScript": true,
789
+ "license": "MIT",
790
+ "bin": {
791
+ "esbuild": "bin/esbuild"
792
+ },
793
+ "engines": {
794
+ "node": ">=18"
795
+ },
796
+ "optionalDependencies": {
797
+ "@esbuild/aix-ppc64": "0.25.12",
798
+ "@esbuild/android-arm": "0.25.12",
799
+ "@esbuild/android-arm64": "0.25.12",
800
+ "@esbuild/android-x64": "0.25.12",
801
+ "@esbuild/darwin-arm64": "0.25.12",
802
+ "@esbuild/darwin-x64": "0.25.12",
803
+ "@esbuild/freebsd-arm64": "0.25.12",
804
+ "@esbuild/freebsd-x64": "0.25.12",
805
+ "@esbuild/linux-arm": "0.25.12",
806
+ "@esbuild/linux-arm64": "0.25.12",
807
+ "@esbuild/linux-ia32": "0.25.12",
808
+ "@esbuild/linux-loong64": "0.25.12",
809
+ "@esbuild/linux-mips64el": "0.25.12",
810
+ "@esbuild/linux-ppc64": "0.25.12",
811
+ "@esbuild/linux-riscv64": "0.25.12",
812
+ "@esbuild/linux-s390x": "0.25.12",
813
+ "@esbuild/linux-x64": "0.25.12",
814
+ "@esbuild/netbsd-arm64": "0.25.12",
815
+ "@esbuild/netbsd-x64": "0.25.12",
816
+ "@esbuild/openbsd-arm64": "0.25.12",
817
+ "@esbuild/openbsd-x64": "0.25.12",
818
+ "@esbuild/openharmony-arm64": "0.25.12",
819
+ "@esbuild/sunos-x64": "0.25.12",
820
+ "@esbuild/win32-arm64": "0.25.12",
821
+ "@esbuild/win32-ia32": "0.25.12",
822
+ "@esbuild/win32-x64": "0.25.12"
823
+ }
824
+ },
825
+ "node_modules/fdir": {
826
+ "version": "6.5.0",
827
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
828
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
829
+ "dev": true,
830
+ "license": "MIT",
831
+ "engines": {
832
+ "node": ">=12.0.0"
833
+ },
834
+ "peerDependencies": {
835
+ "picomatch": "^3 || ^4"
836
+ },
837
+ "peerDependenciesMeta": {
838
+ "picomatch": {
839
+ "optional": true
840
+ }
841
+ }
842
+ },
843
+ "node_modules/fsevents": {
844
+ "version": "2.3.3",
845
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
846
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
847
+ "dev": true,
848
+ "hasInstallScript": true,
849
+ "license": "MIT",
850
+ "optional": true,
851
+ "os": [
852
+ "darwin"
853
+ ],
854
+ "engines": {
855
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
856
+ }
857
+ },
858
+ "node_modules/nanoid": {
859
+ "version": "3.3.11",
860
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
861
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
862
+ "dev": true,
863
+ "funding": [
864
+ {
865
+ "type": "github",
866
+ "url": "https://github.com/sponsors/ai"
867
+ }
868
+ ],
869
+ "license": "MIT",
870
+ "bin": {
871
+ "nanoid": "bin/nanoid.cjs"
872
+ },
873
+ "engines": {
874
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
875
+ }
876
+ },
877
+ "node_modules/picocolors": {
878
+ "version": "1.1.1",
879
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
880
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
881
+ "dev": true,
882
+ "license": "ISC"
883
+ },
884
+ "node_modules/picomatch": {
885
+ "version": "4.0.3",
886
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
887
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
888
+ "dev": true,
889
+ "license": "MIT",
890
+ "engines": {
891
+ "node": ">=12"
892
+ },
893
+ "funding": {
894
+ "url": "https://github.com/sponsors/jonschlinkert"
895
+ }
896
+ },
897
+ "node_modules/postcss": {
898
+ "version": "8.5.6",
899
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
900
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
901
+ "dev": true,
902
+ "funding": [
903
+ {
904
+ "type": "opencollective",
905
+ "url": "https://opencollective.com/postcss/"
906
+ },
907
+ {
908
+ "type": "tidelift",
909
+ "url": "https://tidelift.com/funding/github/npm/postcss"
910
+ },
911
+ {
912
+ "type": "github",
913
+ "url": "https://github.com/sponsors/ai"
914
+ }
915
+ ],
916
+ "license": "MIT",
917
+ "dependencies": {
918
+ "nanoid": "^3.3.11",
919
+ "picocolors": "^1.1.1",
920
+ "source-map-js": "^1.2.1"
921
+ },
922
+ "engines": {
923
+ "node": "^10 || ^12 || >=14"
924
+ }
925
+ },
926
+ "node_modules/rollup": {
927
+ "version": "4.53.3",
928
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz",
929
+ "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==",
930
+ "dev": true,
931
+ "license": "MIT",
932
+ "dependencies": {
933
+ "@types/estree": "1.0.8"
934
+ },
935
+ "bin": {
936
+ "rollup": "dist/bin/rollup"
937
+ },
938
+ "engines": {
939
+ "node": ">=18.0.0",
940
+ "npm": ">=8.0.0"
941
+ },
942
+ "optionalDependencies": {
943
+ "@rollup/rollup-android-arm-eabi": "4.53.3",
944
+ "@rollup/rollup-android-arm64": "4.53.3",
945
+ "@rollup/rollup-darwin-arm64": "4.53.3",
946
+ "@rollup/rollup-darwin-x64": "4.53.3",
947
+ "@rollup/rollup-freebsd-arm64": "4.53.3",
948
+ "@rollup/rollup-freebsd-x64": "4.53.3",
949
+ "@rollup/rollup-linux-arm-gnueabihf": "4.53.3",
950
+ "@rollup/rollup-linux-arm-musleabihf": "4.53.3",
951
+ "@rollup/rollup-linux-arm64-gnu": "4.53.3",
952
+ "@rollup/rollup-linux-arm64-musl": "4.53.3",
953
+ "@rollup/rollup-linux-loong64-gnu": "4.53.3",
954
+ "@rollup/rollup-linux-ppc64-gnu": "4.53.3",
955
+ "@rollup/rollup-linux-riscv64-gnu": "4.53.3",
956
+ "@rollup/rollup-linux-riscv64-musl": "4.53.3",
957
+ "@rollup/rollup-linux-s390x-gnu": "4.53.3",
958
+ "@rollup/rollup-linux-x64-gnu": "4.53.3",
959
+ "@rollup/rollup-linux-x64-musl": "4.53.3",
960
+ "@rollup/rollup-openharmony-arm64": "4.53.3",
961
+ "@rollup/rollup-win32-arm64-msvc": "4.53.3",
962
+ "@rollup/rollup-win32-ia32-msvc": "4.53.3",
963
+ "@rollup/rollup-win32-x64-gnu": "4.53.3",
964
+ "@rollup/rollup-win32-x64-msvc": "4.53.3",
965
+ "fsevents": "~2.3.2"
966
+ }
967
+ },
968
+ "node_modules/source-map-js": {
969
+ "version": "1.2.1",
970
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
971
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
972
+ "dev": true,
973
+ "license": "BSD-3-Clause",
974
+ "engines": {
975
+ "node": ">=0.10.0"
976
+ }
977
+ },
978
+ "node_modules/tinyglobby": {
979
+ "version": "0.2.15",
980
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
981
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
982
+ "dev": true,
983
+ "license": "MIT",
984
+ "dependencies": {
985
+ "fdir": "^6.5.0",
986
+ "picomatch": "^4.0.3"
987
+ },
988
+ "engines": {
989
+ "node": ">=12.0.0"
990
+ },
991
+ "funding": {
992
+ "url": "https://github.com/sponsors/SuperchupuDev"
993
+ }
994
+ },
995
+ "node_modules/typescript": {
996
+ "version": "5.8.3",
997
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
998
+ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
999
+ "dev": true,
1000
+ "license": "Apache-2.0",
1001
+ "bin": {
1002
+ "tsc": "bin/tsc",
1003
+ "tsserver": "bin/tsserver"
1004
+ },
1005
+ "engines": {
1006
+ "node": ">=14.17"
1007
+ }
1008
+ },
1009
+ "node_modules/undici-types": {
1010
+ "version": "6.21.0",
1011
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
1012
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
1013
+ "dev": true,
1014
+ "license": "MIT"
1015
+ },
1016
+ "node_modules/vite": {
1017
+ "version": "6.4.1",
1018
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
1019
+ "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
1020
+ "dev": true,
1021
+ "license": "MIT",
1022
+ "dependencies": {
1023
+ "esbuild": "^0.25.0",
1024
+ "fdir": "^6.4.4",
1025
+ "picomatch": "^4.0.2",
1026
+ "postcss": "^8.5.3",
1027
+ "rollup": "^4.34.9",
1028
+ "tinyglobby": "^0.2.13"
1029
+ },
1030
+ "bin": {
1031
+ "vite": "bin/vite.js"
1032
+ },
1033
+ "engines": {
1034
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
1035
+ },
1036
+ "funding": {
1037
+ "url": "https://github.com/vitejs/vite?sponsor=1"
1038
+ },
1039
+ "optionalDependencies": {
1040
+ "fsevents": "~2.3.3"
1041
+ },
1042
+ "peerDependencies": {
1043
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
1044
+ "jiti": ">=1.21.0",
1045
+ "less": "*",
1046
+ "lightningcss": "^1.21.0",
1047
+ "sass": "*",
1048
+ "sass-embedded": "*",
1049
+ "stylus": "*",
1050
+ "sugarss": "*",
1051
+ "terser": "^5.16.0",
1052
+ "tsx": "^4.8.1",
1053
+ "yaml": "^2.4.2"
1054
+ },
1055
+ "peerDependenciesMeta": {
1056
+ "@types/node": {
1057
+ "optional": true
1058
+ },
1059
+ "jiti": {
1060
+ "optional": true
1061
+ },
1062
+ "less": {
1063
+ "optional": true
1064
+ },
1065
+ "lightningcss": {
1066
+ "optional": true
1067
+ },
1068
+ "sass": {
1069
+ "optional": true
1070
+ },
1071
+ "sass-embedded": {
1072
+ "optional": true
1073
+ },
1074
+ "stylus": {
1075
+ "optional": true
1076
+ },
1077
+ "sugarss": {
1078
+ "optional": true
1079
+ },
1080
+ "terser": {
1081
+ "optional": true
1082
+ },
1083
+ "tsx": {
1084
+ "optional": true
1085
+ },
1086
+ "yaml": {
1087
+ "optional": true
1088
+ }
1089
+ }
1090
+ }
1091
+ }
1092
+ }
package.json ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "app",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+
13
+ },
14
+ "devDependencies": {
15
+ "@types/node": "^22.14.0",
16
+
17
+ "typescript": "~5.8.2",
18
+ "vite": "^6.2.0"
19
+ }
20
+ }
tsconfig.json ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "experimentalDecorators": true,
5
+ "useDefineForClassFields": false,
6
+ "module": "ESNext",
7
+ "lib": [
8
+ "ES2022",
9
+ "DOM",
10
+ "DOM.Iterable"
11
+ ],
12
+ "skipLibCheck": true,
13
+ "types": [
14
+ "node"
15
+ ],
16
+ "moduleResolution": "bundler",
17
+ "isolatedModules": true,
18
+ "moduleDetection": "force",
19
+ "allowJs": true,
20
+ "jsx": "react-jsx",
21
+ "paths": {
22
+ "@/*": [
23
+ "./*"
24
+ ]
25
+ },
26
+ "allowImportingTsExtensions": true,
27
+ "noEmit": true
28
+ }
29
+ }
vite.config.ts ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import path from 'path';
2
+ import { defineConfig, loadEnv } from 'vite';
3
+
4
+
5
+ export default defineConfig(({ mode }) => {
6
+ const env = loadEnv(mode, '.', '');
7
+ return {
8
+ server: {
9
+ port: 3000,
10
+ host: '0.0.0.0',
11
+ },
12
+ plugins: [],
13
+ define: {
14
+ },
15
+ resolve: {
16
+ alias: {
17
+ '@': path.resolve(__dirname, '.'),
18
+ }
19
+ }
20
+ };
21
+ });