mudra-skills 3.0.2 → 3.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/package.json +1 -1
  2. package/skills/mudra-master/SKILL.md +0 -196
  3. package/skills/mudra-master/mudra-preview/SKILL.md +0 -150
  4. package/skills/mudra-master/mudra-preview/assets/ar-menu.html +0 -382
  5. package/skills/mudra-master/mudra-preview/assets/document-scroller.html +0 -468
  6. package/skills/mudra-master/mudra-preview/assets/drum-machine.html +0 -788
  7. package/skills/mudra-master/mudra-preview/assets/emg-visualizer.html +0 -390
  8. package/skills/mudra-master/mudra-preview/assets/generative-art.html +0 -408
  9. package/skills/mudra-master/mudra-preview/assets/gesture-assistant.html +0 -342
  10. package/skills/mudra-master/mudra-preview/assets/gesture-speech.html +0 -444
  11. package/skills/mudra-master/mudra-preview/assets/hands-free-desktop.html +0 -376
  12. package/skills/mudra-master/mudra-preview/assets/model-rotator.html +0 -365
  13. package/skills/mudra-master/mudra-preview/assets/mudra-duel.html +0 -3200
  14. package/skills/mudra-master/mudra-preview/assets/mudra-monitor.html +0 -1451
  15. package/skills/mudra-master/mudra-preview/assets/mudra-ultimate-template.html +0 -891
  16. package/skills/mudra-master/mudra-preview/assets/music-sequencer.html +0 -455
  17. package/skills/mudra-master/mudra-preview/assets/neural-pong.html +0 -380
  18. package/skills/mudra-master/mudra-preview/assets/neural-snake.html +0 -364
  19. package/skills/mudra-master/mudra-preview/assets/presentation-controller.html +0 -345
  20. package/skills/mudra-master/mudra-preview/assets/pressure-painter.html +0 -285
  21. package/skills/mudra-master/mudra-preview/assets/runner.html +0 -1812
  22. package/skills/mudra-master/mudra-preview/assets/smart-home.html +0 -461
  23. package/skills/mudra-master/mudra-preview/assets/space-invaders.html +0 -1154
  24. package/skills/mudra-master/mudra-preview/assets/waterful-ring-toss.html +0 -1624
  25. package/skills/mudra-master/mudra-preview/references/agent_protocol.json +0 -634
  26. package/skills/mudra-master/mudra-preview/references/prompt.md +0 -16746
  27. package/skills/mudra-master/mudra-xr/SKILL.md +0 -245
  28. package/skills/mudra-master/mudra-xr/assets/demos/3dgs-walkthrough.html +0 -242
  29. package/skills/mudra-master/mudra-xr/assets/demos/aisimulator.html +0 -792
  30. package/skills/mudra-master/mudra-xr/assets/demos/balloonpop.html +0 -422
  31. package/skills/mudra-master/mudra-xr/assets/demos/ballpit.html +0 -284
  32. package/skills/mudra-master/mudra-xr/assets/demos/drone.html +0 -41
  33. package/skills/mudra-master/mudra-xr/assets/demos/gemini-icebreakers.html +0 -338
  34. package/skills/mudra-master/mudra-xr/assets/demos/gemini-xrobject.html +0 -302
  35. package/skills/mudra-master/mudra-xr/assets/demos/math3d.html +0 -188
  36. package/skills/mudra-master/mudra-xr/assets/demos/measure.html +0 -247
  37. package/skills/mudra-master/mudra-xr/assets/demos/occlusion.html +0 -251
  38. package/skills/mudra-master/mudra-xr/assets/demos/rain.html +0 -498
  39. package/skills/mudra-master/mudra-xr/assets/demos/screenwiper.html +0 -432
  40. package/skills/mudra-master/mudra-xr/assets/demos/splash.html +0 -519
  41. package/skills/mudra-master/mudra-xr/assets/demos/webcam_gestures.html +0 -253
  42. package/skills/mudra-master/mudra-xr/assets/demos/xremoji.html +0 -833
  43. package/skills/mudra-master/mudra-xr/assets/demos/xrpoet.html +0 -151
  44. package/skills/mudra-master/mudra-xr/assets/samples/depthmap.html +0 -266
  45. package/skills/mudra-master/mudra-xr/assets/samples/depthmesh.html +0 -100
  46. package/skills/mudra-master/mudra-xr/assets/samples/game_rps.html +0 -1099
  47. package/skills/mudra-master/mudra-xr/assets/samples/gestures_custom.html +0 -467
  48. package/skills/mudra-master/mudra-xr/assets/samples/gestures_heuristic.html +0 -256
  49. package/skills/mudra-master/mudra-xr/assets/samples/lighting.html +0 -163
  50. package/skills/mudra-master/mudra-xr/assets/samples/mesh_detection.html +0 -46
  51. package/skills/mudra-master/mudra-xr/assets/samples/modelviewer.html +0 -183
  52. package/skills/mudra-master/mudra-xr/assets/samples/paint.html +0 -133
  53. package/skills/mudra-master/mudra-xr/assets/samples/planar-vst.html +0 -45
  54. package/skills/mudra-master/mudra-xr/assets/samples/reticle.html +0 -95
  55. package/skills/mudra-master/mudra-xr/assets/samples/skybox_agent.html +0 -384
  56. package/skills/mudra-master/mudra-xr/assets/samples/sound.html +0 -390
  57. package/skills/mudra-master/mudra-xr/assets/samples/ui.html +0 -496
  58. package/skills/mudra-master/mudra-xr/assets/samples/virtual-screens.html +0 -808
  59. package/skills/mudra-master/mudra-xr/assets/templates/0_basic.html +0 -64
  60. package/skills/mudra-master/mudra-xr/assets/templates/1_ui.html +0 -107
  61. package/skills/mudra-master/mudra-xr/assets/templates/2_hands.html +0 -183
  62. package/skills/mudra-master/mudra-xr/assets/templates/3_depth.html +0 -89
  63. package/skills/mudra-master/mudra-xr/assets/templates/4_stereo.html +0 -84
  64. package/skills/mudra-master/mudra-xr/assets/templates/5_camera.html +0 -117
  65. package/skills/mudra-master/mudra-xr/assets/templates/6_ai.html +0 -154
  66. package/skills/mudra-master/mudra-xr/assets/templates/7_ai_live.html +0 -253
  67. package/skills/mudra-master/mudra-xr/assets/templates/8_objects.html +0 -95
  68. package/skills/mudra-master/mudra-xr/assets/templates/9_xr-toggle.html +0 -90
  69. package/skills/mudra-master/mudra-xr/assets/templates/heuristic_hand_gestures.html +0 -102
  70. package/skills/mudra-master/mudra-xr/assets/templates/meshes.html +0 -47
  71. package/skills/mudra-master/mudra-xr/assets/templates/planes.html +0 -47
  72. package/skills/mudra-master/mudra-xr/assets/templates/uikit.html +0 -187
  73. package/skills/mudra-master/mudra-xr/references/agent_protocol.json +0 -865
  74. package/skills/mudra-master/mudra-xr/references/prompt.md +0 -1605
  75. package/skills/mudra-preview/SKILL.md +0 -159
  76. package/skills/mudra-preview/assets/ar-menu.html +0 -382
  77. package/skills/mudra-preview/assets/document-scroller.html +0 -468
  78. package/skills/mudra-preview/assets/drum-machine.html +0 -788
  79. package/skills/mudra-preview/assets/emg-visualizer.html +0 -390
  80. package/skills/mudra-preview/assets/generative-art.html +0 -408
  81. package/skills/mudra-preview/assets/gesture-assistant.html +0 -342
  82. package/skills/mudra-preview/assets/gesture-speech.html +0 -444
  83. package/skills/mudra-preview/assets/hands-free-desktop.html +0 -376
  84. package/skills/mudra-preview/assets/model-rotator.html +0 -365
  85. package/skills/mudra-preview/assets/mudra-duel.html +0 -3200
  86. package/skills/mudra-preview/assets/mudra-monitor.html +0 -1451
  87. package/skills/mudra-preview/assets/mudra-ultimate-template.html +0 -997
  88. package/skills/mudra-preview/assets/music-sequencer.html +0 -455
  89. package/skills/mudra-preview/assets/neural-pong.html +0 -380
  90. package/skills/mudra-preview/assets/neural-snake.html +0 -364
  91. package/skills/mudra-preview/assets/presentation-controller.html +0 -345
  92. package/skills/mudra-preview/assets/pressure-painter.html +0 -285
  93. package/skills/mudra-preview/assets/runner.html +0 -1812
  94. package/skills/mudra-preview/assets/smart-home.html +0 -461
  95. package/skills/mudra-preview/assets/space-invaders.html +0 -1154
  96. package/skills/mudra-preview/assets/waterful-ring-toss.html +0 -1624
  97. package/skills/mudra-preview/references/agent_protocol.json +0 -701
  98. package/skills/mudra-preview/references/prompt.md +0 -16731
  99. package/skills/mudra-xr/SKILL.md +0 -245
  100. package/skills/mudra-xr/assets/demos/3dgs-walkthrough.html +0 -242
  101. package/skills/mudra-xr/assets/demos/aisimulator.html +0 -792
  102. package/skills/mudra-xr/assets/demos/balloonpop.html +0 -422
  103. package/skills/mudra-xr/assets/demos/ballpit.html +0 -284
  104. package/skills/mudra-xr/assets/demos/drone.html +0 -41
  105. package/skills/mudra-xr/assets/demos/gemini-icebreakers.html +0 -338
  106. package/skills/mudra-xr/assets/demos/gemini-xrobject.html +0 -302
  107. package/skills/mudra-xr/assets/demos/math3d.html +0 -188
  108. package/skills/mudra-xr/assets/demos/measure.html +0 -247
  109. package/skills/mudra-xr/assets/demos/occlusion.html +0 -251
  110. package/skills/mudra-xr/assets/demos/rain.html +0 -498
  111. package/skills/mudra-xr/assets/demos/screenwiper.html +0 -432
  112. package/skills/mudra-xr/assets/demos/splash.html +0 -519
  113. package/skills/mudra-xr/assets/demos/webcam_gestures.html +0 -253
  114. package/skills/mudra-xr/assets/demos/xremoji.html +0 -833
  115. package/skills/mudra-xr/assets/demos/xrpoet.html +0 -151
  116. package/skills/mudra-xr/assets/samples/depthmap.html +0 -266
  117. package/skills/mudra-xr/assets/samples/depthmesh.html +0 -100
  118. package/skills/mudra-xr/assets/samples/game_rps.html +0 -1099
  119. package/skills/mudra-xr/assets/samples/gestures_custom.html +0 -467
  120. package/skills/mudra-xr/assets/samples/gestures_heuristic.html +0 -256
  121. package/skills/mudra-xr/assets/samples/lighting.html +0 -163
  122. package/skills/mudra-xr/assets/samples/mesh_detection.html +0 -46
  123. package/skills/mudra-xr/assets/samples/modelviewer.html +0 -183
  124. package/skills/mudra-xr/assets/samples/paint.html +0 -133
  125. package/skills/mudra-xr/assets/samples/planar-vst.html +0 -45
  126. package/skills/mudra-xr/assets/samples/reticle.html +0 -95
  127. package/skills/mudra-xr/assets/samples/skybox_agent.html +0 -384
  128. package/skills/mudra-xr/assets/samples/sound.html +0 -390
  129. package/skills/mudra-xr/assets/samples/ui.html +0 -496
  130. package/skills/mudra-xr/assets/samples/virtual-screens.html +0 -808
  131. package/skills/mudra-xr/assets/templates/0_basic.html +0 -64
  132. package/skills/mudra-xr/assets/templates/1_ui.html +0 -107
  133. package/skills/mudra-xr/assets/templates/2_hands.html +0 -183
  134. package/skills/mudra-xr/assets/templates/3_depth.html +0 -89
  135. package/skills/mudra-xr/assets/templates/4_stereo.html +0 -84
  136. package/skills/mudra-xr/assets/templates/5_camera.html +0 -117
  137. package/skills/mudra-xr/assets/templates/6_ai.html +0 -154
  138. package/skills/mudra-xr/assets/templates/7_ai_live.html +0 -253
  139. package/skills/mudra-xr/assets/templates/8_objects.html +0 -95
  140. package/skills/mudra-xr/assets/templates/9_xr-toggle.html +0 -90
  141. package/skills/mudra-xr/assets/templates/heuristic_hand_gestures.html +0 -102
  142. package/skills/mudra-xr/assets/templates/meshes.html +0 -47
  143. package/skills/mudra-xr/assets/templates/planes.html +0 -47
  144. package/skills/mudra-xr/assets/templates/uikit.html +0 -187
  145. package/skills/mudra-xr/references/agent_protocol.json +0 -865
  146. package/skills/mudra-xr/references/prompt.md +0 -1605
@@ -1,1154 +0,0 @@
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>Space Invaders - Mudra Edition v2</title>
7
- <style>
8
- * {
9
- margin: 0;
10
- padding: 0;
11
- box-sizing: border-box;
12
- }
13
-
14
- body {
15
- background: linear-gradient(to bottom, #0a0a0f, #1a1a2e);
16
- display: flex;
17
- flex-direction: column;
18
- align-items: center;
19
- justify-content: center;
20
- min-height: 100vh;
21
- font-family: 'Courier New', monospace;
22
- color: #f8fafc;
23
- overflow: hidden;
24
- }
25
-
26
- h1 {
27
- color: #77eae9;
28
- margin: 20px 0;
29
- text-shadow: 0 0 10px #77eae9;
30
- font-size: 32px;
31
- }
32
-
33
- #gameCanvas {
34
- border: 2px solid #77eae9;
35
- box-shadow: 0 0 20px rgba(119, 234, 233, 0.3);
36
- background: #0a0a0f;
37
- image-rendering: pixelated;
38
- image-rendering: crisp-edges;
39
- }
40
-
41
- #mudraHud {
42
- position: fixed;
43
- top: 10px;
44
- right: 10px;
45
- display: flex;
46
- flex-direction: column;
47
- gap: 6px;
48
- font-size: 12px;
49
- font-family: 'Courier New', monospace;
50
- z-index: 10;
51
- }
52
-
53
- .hud-card {
54
- background: rgba(0, 0, 0, 0.82);
55
- border: 1px solid rgba(119, 234, 233, 0.25);
56
- border-radius: 8px;
57
- padding: 8px 12px;
58
- min-width: 180px;
59
- }
60
-
61
- .hud-status {
62
- display: flex;
63
- align-items: center;
64
- gap: 8px;
65
- }
66
-
67
- .hud-dot {
68
- width: 8px;
69
- height: 8px;
70
- border-radius: 50%;
71
- background: #eab308;
72
- box-shadow: 0 0 0 3px rgba(234, 179, 8, 0.15);
73
- transition: background 150ms ease, box-shadow 150ms ease;
74
- }
75
-
76
- .hud-dot.connected {
77
- background: #22c55e;
78
- box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.18);
79
- }
80
-
81
- .hud-label {
82
- color: #94a3b8;
83
- font-size: 10px;
84
- text-transform: uppercase;
85
- letter-spacing: 0.05em;
86
- }
87
-
88
- .hud-value {
89
- color: #77eae9;
90
- font-weight: 700;
91
- }
92
-
93
- .hud-row {
94
- display: flex;
95
- justify-content: space-between;
96
- align-items: center;
97
- margin-top: 4px;
98
- }
99
-
100
- .hud-bar {
101
- height: 4px;
102
- border-radius: 2px;
103
- background: rgba(148, 163, 184, 0.22);
104
- margin-top: 4px;
105
- overflow: hidden;
106
- }
107
-
108
- .hud-bar > span {
109
- display: block;
110
- height: 100%;
111
- width: 0%;
112
- background: linear-gradient(90deg, #2dd4bf, #77eae9);
113
- transition: width 75ms linear;
114
- }
115
-
116
- .hud-actions {
117
- display: flex;
118
- gap: 4px;
119
- margin-top: 6px;
120
- }
121
-
122
- .hud-btn {
123
- border: none;
124
- border-radius: 5px;
125
- padding: 4px 8px;
126
- font-size: 10px;
127
- font-weight: 700;
128
- cursor: pointer;
129
- background: #77eae9;
130
- color: #072f33;
131
- font-family: inherit;
132
- }
133
-
134
- .hud-btn.secondary {
135
- background: transparent;
136
- color: #94a3b8;
137
- border: 1px solid rgba(148, 163, 184, 0.3);
138
- font-weight: 600;
139
- }
140
-
141
- #instructions {
142
- margin-top: 15px;
143
- text-align: center;
144
- color: #64748b;
145
- font-size: 12px;
146
- max-width: 800px;
147
- }
148
-
149
- .mudra-badge {
150
- position: fixed;
151
- bottom: 12px;
152
- right: 12px;
153
- font-size: 11px;
154
- color: #94a3b8;
155
- opacity: 0.7;
156
- font-family: inherit;
157
- letter-spacing: 0.02em;
158
- pointer-events: none;
159
- }
160
- </style>
161
- </head>
162
- <body>
163
- <a href="/gallery" style="position:fixed;top:12px;left:12px;z-index:9999;padding:6px 14px;background:rgba(0,0,0,0.6);color:#77eae9;text-decoration:none;font-size:13px;font-family:system-ui,sans-serif;border-radius:8px;backdrop-filter:blur(8px);border:1px solid rgba(119,234,233,0.2);transition:background 0.2s;"onmouseenter="this.style.background='rgba(0,0,0,0.8)'"onmouseleave="this.style.background='rgba(0,0,0,0.6)'">&larr; Gallery</a>
164
- <h1>SPACE INVADERS - MUDRA EDITION</h1>
165
- <canvas id="gameCanvas" width="800" height="600"></canvas>
166
- <div id="instructions">
167
- <p><strong>KEYBOARD:</strong> Arrow keys / A D - Move | SPACE - Shoot | S - Shield | P - Pause | 1-4 - Power level</p>
168
- <p><strong>MUDRA:</strong> Hand tilt - Move | Tap gesture - Shoot | Pressure - Power control</p>
169
- </div>
170
-
171
- <!-- Mudra Telemetry HUD -->
172
- <div id="mudraHud">
173
- <div class="hud-card">
174
- <div class="hud-status">
175
- <span class="hud-dot" id="hudDot"></span>
176
- <span id="hudStatusText">Connecting...</span>
177
- </div>
178
- <div class="hud-actions">
179
- <button class="hud-btn" id="btnSimTap">Sim Tap</button>
180
- <button class="hud-btn secondary" id="btnResub">Resub</button>
181
- <button class="hud-btn secondary" id="btnGetStatus">Status</button>
182
- </div>
183
- </div>
184
- <div class="hud-card">
185
- <div class="hud-row">
186
- <span class="hud-label">Gesture</span>
187
- <span class="hud-value" id="hudGesture">none</span>
188
- </div>
189
- <div class="hud-row">
190
- <span class="hud-label">Navigation</span>
191
- <span class="hud-value" id="hudDirection">dx:0</span>
192
- </div>
193
- <div class="hud-row">
194
- <span class="hud-label">Pressure</span>
195
- <span class="hud-value" id="hudPressure">0%</span>
196
- </div>
197
- <div class="hud-bar"><span id="hudPressureBar"></span></div>
198
- <div class="hud-row">
199
- <span class="hud-label">Battery</span>
200
- <span class="hud-value" id="hudBattery">--</span>
201
- </div>
202
- </div>
203
- </div>
204
-
205
- <div class="mudra-badge">Created by Mudra</div>
206
-
207
- <script>
208
- // ─── Canvas ───
209
- const canvas = document.getElementById('gameCanvas');
210
- const ctx = canvas.getContext('2d');
211
-
212
- const COLORS = {
213
- black: '#0a0a0f',
214
- cardBg: '#1e293b',
215
- primary: '#77eae9',
216
- accent: '#2dd4bf',
217
- success: '#22c55e',
218
- warning: '#eab308',
219
- error: '#ef4444',
220
- text: '#f8fafc',
221
- textSecondary: '#cbd5e1',
222
- textMuted: '#64748b'
223
- };
224
-
225
- // ─── Sprites ───
226
- const SPRITES = {
227
- squid: [
228
- ` ####
229
- ## ##
230
- ########
231
- ## ## ##
232
- # # `,
233
- ` ####
234
- ## ##
235
- ########
236
- ## ## ##
237
- # ## #`
238
- ],
239
- crab: [
240
- ` # #
241
- # #
242
- ######
243
- ## ## ##
244
- ########
245
- # # `,
246
- ` # #
247
- # #
248
- ######
249
- ## ## ##
250
- ########
251
- # # # #`
252
- ],
253
- octopus: [
254
- ` ##
255
- ####
256
- ######
257
- ## ## ##
258
- ########
259
- # ## # `,
260
- ` ##
261
- ####
262
- ######
263
- ## ## ##
264
- ########
265
- # ## #`
266
- ],
267
- player: ` ##
268
- ##
269
- ######
270
- ########
271
- ## ## ##`,
272
- ufo: ` ####
273
- ## ##
274
- ########
275
- # # #`,
276
- shield: `##########
277
- #### ####
278
- ### ###
279
- ## ##`
280
- };
281
-
282
- function parseSprite(pattern) {
283
- const lines = pattern.trim().split('\n');
284
- const pixels = [];
285
- for (let y = 0; y < lines.length; y++) {
286
- for (let x = 0; x < lines[y].length; x++) {
287
- if (lines[y][x] === '#') pixels.push({x, y});
288
- }
289
- }
290
- return {pixels, width: Math.max(...lines.map(l => l.length)), height: lines.length};
291
- }
292
-
293
- function drawSprite(sprite, x, y, color, pixelSize = 3) {
294
- ctx.fillStyle = color;
295
- for (let pixel of sprite.pixels) {
296
- ctx.fillRect(x + pixel.x * pixelSize, y + pixel.y * pixelSize, pixelSize, pixelSize);
297
- }
298
- }
299
-
300
- const parsedSprites = {
301
- squid: SPRITES.squid.map(s => parseSprite(s)),
302
- crab: SPRITES.crab.map(s => parseSprite(s)),
303
- octopus: SPRITES.octopus.map(s => parseSprite(s)),
304
- player: parseSprite(SPRITES.player),
305
- ufo: parseSprite(SPRITES.ufo),
306
- shield: parseSprite(SPRITES.shield)
307
- };
308
-
309
- // ─── Game State ───
310
- let gameState = {
311
- state: 'menu',
312
- score: 0,
313
- level: 1,
314
- lives: 3,
315
- paused: false,
316
- introTimer: 10 * 60,
317
- isBossLevel: false
318
- };
319
-
320
- // ─── Mudra Connection (Protocol-safe) ───
321
- const WS_URL = "ws://127.0.0.1:8766";
322
- const PRESSURE_WINDOW = 5;
323
- const pressureSamples = [];
324
- let reconnectTimer;
325
-
326
- const mudra = {
327
- connected: false,
328
- deviceReady: false,
329
- ws: null,
330
- gesture: "none",
331
- gestureTimestamp: 0,
332
- navDeltaDisplay: "0",
333
- pressure: 0,
334
- smoothPressure: 0,
335
- battery: null,
336
- charging: false,
337
- // Game-facing signals
338
- deltaX: 0, // accumulated navigation delta, consumed each frame
339
- shootTriggered: false,
340
- shieldTriggered: false
341
- };
342
-
343
- // HUD elements
344
- const hud = {
345
- dot: document.getElementById('hudDot'),
346
- statusText: document.getElementById('hudStatusText'),
347
- gesture: document.getElementById('hudGesture'),
348
- direction: document.getElementById('hudDirection'),
349
- pressure: document.getElementById('hudPressure'),
350
- pressureBar: document.getElementById('hudPressureBar'),
351
- battery: document.getElementById('hudBattery')
352
- };
353
-
354
- function connectMudra() {
355
- try {
356
- mudra.ws = new WebSocket(WS_URL);
357
-
358
- mudra.ws.onopen = () => {
359
- mudra.connected = true;
360
- setMudraStatus(true, "Connected");
361
- subscribeSignals();
362
- };
363
-
364
- mudra.ws.onmessage = (event) => {
365
- let msg;
366
- try { msg = JSON.parse(event.data); } catch { return; }
367
-
368
- // Connection status from Companion
369
- if (msg.type === "connection_status") {
370
- mudra.deviceReady = msg.data?.status === "connected";
371
- setMudraStatus(mudra.deviceReady, mudra.deviceReady ? "Mudra Band ready" : "Connect your Mudra Band");
372
- return;
373
- }
374
-
375
- if (msg.type === "gesture") handleMudraGesture(msg.data);
376
- if (msg.type === "navigation") handleMudraNavigation(msg.data);
377
- if (msg.type === "pressure") handleMudraPressure(msg.data);
378
- if (msg.type === "battery") handleMudraBattery(msg.data);
379
- };
380
-
381
- mudra.ws.onerror = () => {
382
- mudra.connected = false;
383
- mudra.deviceReady = false;
384
- setMudraStatus(false, "Connection error");
385
- };
386
-
387
- mudra.ws.onclose = () => {
388
- mudra.connected = false;
389
- mudra.deviceReady = false;
390
- setMudraStatus(false, "Disconnected. Reconnecting...");
391
- scheduleReconnect();
392
- };
393
- } catch (e) {
394
- console.log('Mudra not available');
395
- }
396
- }
397
-
398
- function subscribeSignals() {
399
- // Pointer mode: navigation for continuous movement + gesture/pressure freely combine
400
- // (no nav_direction, no imu — signal-safe)
401
- const signals = ["gesture", "navigation", "pressure", "battery"];
402
- signals.forEach(signal => sendMudra({ command: "subscribe", signal }));
403
- }
404
-
405
- function sendMudra(payload) {
406
- if (!mudra.ws || mudra.ws.readyState !== WebSocket.OPEN) return;
407
- mudra.ws.send(JSON.stringify(payload));
408
- }
409
-
410
- function scheduleReconnect() {
411
- clearTimeout(reconnectTimer);
412
- reconnectTimer = setTimeout(connectMudra, 1200);
413
- }
414
-
415
- function setMudraStatus(connected, text) {
416
- hud.dot.classList.toggle('connected', connected);
417
- hud.statusText.textContent = text;
418
- }
419
-
420
- // ─── Signal Handlers ───
421
- function handleMudraGesture(data = {}) {
422
- const type = data.type || "unknown";
423
- mudra.gesture = type;
424
- mudra.gestureTimestamp = Date.now();
425
- hud.gesture.textContent = type;
426
- pulseHud(hud.gesture);
427
-
428
- // Map gestures to game actions
429
- if (type === "tap") {
430
- mudra.shootTriggered = true;
431
- }
432
- if (type === "double_tap") {
433
- mudra.shieldTriggered = true;
434
- }
435
- }
436
-
437
- function handleMudraNavigation(data = {}) {
438
- const dx = Number(data.delta_x || 0);
439
- const dy = Number(data.delta_y || 0);
440
- // Accumulate deltas — consumed each frame in player.update()
441
- mudra.deltaX += dx * 3;
442
- hud.direction.textContent = `dx:${dx.toFixed(1)}`;
443
- }
444
-
445
- function handleMudraPressure(data = {}) {
446
- const normalized = clamp01(Number(data.normalized ?? 0));
447
- pressureSamples.push(normalized);
448
- if (pressureSamples.length > PRESSURE_WINDOW) pressureSamples.shift();
449
- mudra.smoothPressure = pressureSamples.reduce((s, v) => s + v, 0) / pressureSamples.length;
450
- mudra.pressure = normalized;
451
-
452
- hud.pressure.textContent = `${Math.round(mudra.smoothPressure * 100)}%`;
453
- hud.pressureBar.style.width = `${Math.round(mudra.smoothPressure * 100)}%`;
454
- }
455
-
456
- function handleMudraBattery(data = {}) {
457
- if (typeof data.level !== "number") return;
458
- mudra.battery = data.level;
459
- mudra.charging = Boolean(data.charging);
460
- hud.battery.textContent = `${mudra.battery}%${mudra.charging ? " ⚡" : ""}`;
461
- }
462
-
463
- function pulseHud(el) {
464
- el.style.transition = 'none';
465
- el.style.transform = 'scale(1.15)';
466
- void el.offsetWidth;
467
- el.style.transition = 'transform 150ms ease-out';
468
- el.style.transform = 'scale(1)';
469
- }
470
-
471
- // ─── HUD Buttons ───
472
- document.getElementById('btnSimTap').addEventListener('click', () => {
473
- sendMudra({ command: "trigger_gesture", data: { type: "tap" } });
474
- handleMudraGesture({ type: "tap" });
475
- });
476
-
477
- document.getElementById('btnResub').addEventListener('click', subscribeSignals);
478
-
479
- document.getElementById('btnGetStatus').addEventListener('click', () => {
480
- sendMudra({ command: "get_status" });
481
- });
482
-
483
- // ─── Audio ───
484
- const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
485
- function playSound(freq, type = 'square', duration = 0.1, vol = 0.1) {
486
- try {
487
- const osc = audioCtx.createOscillator();
488
- const gain = audioCtx.createGain();
489
- osc.type = type;
490
- osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
491
- gain.gain.setValueAtTime(vol, audioCtx.currentTime);
492
- gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + duration);
493
- osc.connect(gain);
494
- gain.connect(audioCtx.destination);
495
- osc.start();
496
- osc.stop(audioCtx.currentTime + duration);
497
- } catch (e) {}
498
- }
499
-
500
- // ─── Player ───
501
- class Player {
502
- constructor() {
503
- this.x = canvas.width / 2 - 15;
504
- this.y = canvas.height - 80;
505
- this.width = 30;
506
- this.height = 21;
507
- this.speed = 3;
508
- this.heat = 0;
509
- this.shieldActive = false;
510
- this.shieldTime = 0;
511
- this.keyboardPower = 0.5;
512
- }
513
-
514
- update(keys) {
515
- // Keyboard movement
516
- if (keys['arrowleft'] || keys['a']) this.x -= this.speed;
517
- if (keys['arrowright'] || keys['d']) this.x += this.speed;
518
-
519
- // Mudra navigation movement (continuous delta)
520
- if (mudra.connected && mudra.deltaX !== 0) {
521
- this.x += mudra.deltaX;
522
- mudra.deltaX = 0; // consume
523
- }
524
-
525
- this.x = Math.max(0, Math.min(canvas.width - this.width, this.x));
526
-
527
- if (this.heat > 0) this.heat = Math.max(0, this.heat - 0.5);
528
-
529
- if (this.shieldActive) {
530
- this.shieldTime--;
531
- if (this.shieldTime <= 0) this.shieldActive = false;
532
- }
533
- }
534
-
535
- draw() {
536
- const overheating = this.heat > 75;
537
- if (overheating && Math.floor(Date.now() / 100) % 2 === 0) {
538
- drawSprite(parsedSprites.player, this.x, this.y, COLORS.error, 3);
539
- } else {
540
- drawSprite(parsedSprites.player, this.x, this.y, COLORS.primary, 3);
541
- }
542
-
543
- if (this.shieldActive) {
544
- ctx.strokeStyle = COLORS.primary;
545
- ctx.lineWidth = 2;
546
- ctx.beginPath();
547
- ctx.arc(this.x + this.width / 2, this.y + this.height / 2, 25, 0, Math.PI * 2);
548
- ctx.stroke();
549
- }
550
- }
551
-
552
- activateShield() {
553
- this.shieldActive = true;
554
- this.shieldTime = 180;
555
- playSound(800, 'sine', 0.2);
556
- }
557
- }
558
-
559
- // ─── Power Bar ───
560
- class PowerBar {
561
- constructor() {
562
- this.x = 10;
563
- this.y = canvas.height - 30;
564
- this.width = 200;
565
- this.height = 20;
566
- }
567
-
568
- draw(pressure) {
569
- ctx.fillStyle = COLORS.cardBg;
570
- ctx.fillRect(this.x - 2, this.y - 2, this.width + 4, this.height + 4);
571
-
572
- const fillWidth = this.width * pressure;
573
- let color;
574
- if (pressure < 0.25) color = COLORS.textMuted;
575
- else if (pressure < 0.5) color = COLORS.success;
576
- else if (pressure < 0.75) color = COLORS.warning;
577
- else color = COLORS.error;
578
-
579
- ctx.fillStyle = color;
580
- ctx.fillRect(this.x, this.y, fillWidth, this.height);
581
-
582
- ctx.fillStyle = COLORS.text;
583
- ctx.font = '12px Courier New';
584
- ctx.fillText('POWER', this.x, this.y - 5);
585
- }
586
- }
587
-
588
- // ─── Bullet ───
589
- class Bullet {
590
- constructor(x, y, power) {
591
- this.x = x;
592
- this.y = y;
593
- this.speed = 10;
594
- this.power = power;
595
- this.active = true;
596
- this.damage = power < 0.25 ? 0.5 : power < 0.5 ? 1 : power < 0.75 ? 2 : 4;
597
-
598
- if (power < 0.25) this.color = COLORS.textMuted;
599
- else if (power < 0.5) this.color = COLORS.success;
600
- else if (power < 0.75) this.color = COLORS.warning;
601
- else this.color = COLORS.error;
602
- }
603
-
604
- update() {
605
- this.y -= this.speed;
606
- if (this.y < 0) this.active = false;
607
- }
608
-
609
- draw() {
610
- ctx.fillStyle = this.color;
611
- ctx.fillRect(this.x - 2, this.y, 4, 15);
612
- }
613
- }
614
-
615
- // ─── Enemy ───
616
- class Enemy {
617
- constructor(x, y, type) {
618
- this.x = x;
619
- this.y = y;
620
- this.type = type;
621
- this.active = true;
622
- this.frame = 0;
623
- this.animTimer = 0;
624
- this.flashTimer = 0;
625
-
626
- if (type === 0) {
627
- this.sprite = parsedSprites.squid;
628
- this.color = COLORS.error;
629
- this.points = 30;
630
- this.maxHealth = 4;
631
- } else if (type === 1) {
632
- this.sprite = parsedSprites.crab;
633
- this.color = COLORS.warning;
634
- this.points = 20;
635
- this.maxHealth = 2;
636
- } else {
637
- this.sprite = parsedSprites.octopus;
638
- this.color = COLORS.success;
639
- this.points = 10;
640
- this.maxHealth = 1;
641
- }
642
-
643
- this.health = this.maxHealth;
644
- this.width = this.sprite[0].width * 3;
645
- this.height = this.sprite[0].height * 3;
646
- }
647
-
648
- takeDamage(damage) {
649
- this.health -= damage;
650
- this.flashTimer = 5;
651
- if (this.health <= 0) { this.active = false; return true; }
652
- return false;
653
- }
654
-
655
- update() {
656
- this.animTimer++;
657
- if (this.animTimer >= 30) { this.animTimer = 0; this.frame = 1 - this.frame; }
658
- if (this.flashTimer > 0) this.flashTimer--;
659
- }
660
-
661
- draw() {
662
- if (!this.active) return;
663
- const color = this.flashTimer > 0 ? COLORS.text : this.color;
664
- drawSprite(this.sprite[this.frame], this.x, this.y, color, 3);
665
- }
666
- }
667
-
668
- // ─── Boss ───
669
- class Boss {
670
- constructor(level) {
671
- this.x = canvas.width / 2 - 60;
672
- this.y = 60;
673
- this.width = 120;
674
- this.height = 90;
675
- this.maxHealth = 20 + Math.floor(level / 3) * 10;
676
- this.health = this.maxHealth;
677
- this.level = level;
678
- this.active = true;
679
- this.direction = 1;
680
- this.speed = 2;
681
- this.shootTimer = 0;
682
- this.frame = 0;
683
- this.animTimer = 0;
684
- this.flashTimer = 0;
685
- }
686
-
687
- get phase() {
688
- const pct = this.health / this.maxHealth;
689
- if (pct < 0.33) return 3;
690
- if (pct < 0.66) return 2;
691
- return 1;
692
- }
693
-
694
- update(playerX) {
695
- this.animTimer++;
696
- if (this.animTimer >= 20) { this.animTimer = 0; this.frame = 1 - this.frame; }
697
-
698
- this.x += this.speed * this.direction;
699
- if (this.x <= 50 || this.x >= canvas.width - this.width - 50) this.direction *= -1;
700
-
701
- if (this.phase === 3) {
702
- const diff = (playerX - this.width / 2) - this.x;
703
- this.x += Math.max(-4, Math.min(4, diff * 0.1));
704
- }
705
- this.x = Math.max(10, Math.min(canvas.width - this.width - 10, this.x));
706
-
707
- if (this.flashTimer > 0) this.flashTimer--;
708
- }
709
-
710
- takeDamage(damage) {
711
- this.health -= damage;
712
- this.flashTimer = 10;
713
- playSound(200, 'triangle', 0.1);
714
- return this.health <= 0;
715
- }
716
-
717
- draw() {
718
- const flash = this.flashTimer > 0 && this.flashTimer % 4 < 2;
719
- let color;
720
- if (flash) color = COLORS.text;
721
- else if (this.phase === 3) color = COLORS.error;
722
- else if (this.phase === 2) color = COLORS.warning;
723
- else color = '#c850ff';
724
-
725
- ctx.fillStyle = color;
726
- ctx.fillRect(this.x, this.y, this.width, this.height);
727
-
728
- ctx.fillStyle = COLORS.black;
729
- ctx.fillRect(this.x + 20, this.y + 25, 15, 15);
730
- ctx.fillRect(this.x + 85, this.y + 25, 15, 15);
731
-
732
- // Health bar
733
- const barWidth = 200;
734
- const barX = canvas.width / 2 - barWidth / 2;
735
- ctx.fillStyle = COLORS.cardBg;
736
- ctx.fillRect(barX - 2, 18, barWidth + 4, 16);
737
-
738
- const pct = this.health / this.maxHealth;
739
- ctx.fillStyle = this.phase === 3 ? COLORS.error : this.phase === 2 ? COLORS.warning : COLORS.success;
740
- ctx.fillRect(barX, 20, barWidth * pct, 12);
741
-
742
- ctx.fillStyle = '#c850ff';
743
- ctx.font = 'bold 12px Courier New';
744
- ctx.textAlign = 'center';
745
- ctx.fillText(`BOSS LV.${Math.floor(this.level / 3)}`, canvas.width / 2, 15);
746
- ctx.textAlign = 'left';
747
- }
748
- }
749
-
750
- // ─── Shield ───
751
- class Shield {
752
- constructor(x, y) {
753
- this.x = x;
754
- this.y = y;
755
- this.pixelSize = 4;
756
- this.width = parsedSprites.shield.width * this.pixelSize;
757
- this.height = parsedSprites.shield.height * this.pixelSize;
758
- this.health = 10;
759
- this.active = true;
760
- }
761
-
762
- takeDamage() {
763
- this.health--;
764
- if (this.health <= 0) this.active = false;
765
- }
766
-
767
- draw() {
768
- if (!this.active) return;
769
- drawSprite(parsedSprites.shield, this.x, this.y, COLORS.primary, 4);
770
- if (this.health < 10) {
771
- ctx.fillStyle = COLORS.error;
772
- ctx.fillRect(this.x, this.y - 5, (this.health / 10) * this.width, 3);
773
- }
774
- }
775
- }
776
-
777
- // ─── Game Manager ───
778
- let player = new Player();
779
- let powerBar = new PowerBar();
780
- let bullets = [];
781
- let enemies = [];
782
- let shields = [];
783
- let boss = null;
784
- let shootCooldown = 0;
785
- let keys = {};
786
- let menuCursorX = canvas.width / 2;
787
-
788
- function createEnemies() {
789
- enemies = [];
790
- for (let row = 0; row < 5; row++) {
791
- for (let col = 0; col < 10; col++) {
792
- const type = row === 0 ? 0 : row < 3 ? 1 : 2;
793
- enemies.push(new Enemy(50 + col * 70, 80 + row * 40, type));
794
- }
795
- }
796
- }
797
-
798
- function createShields() {
799
- shields = [];
800
- for (let i = 0; i < 4; i++) {
801
- shields.push(new Shield(100 + i * 180, canvas.height - 150));
802
- }
803
- }
804
-
805
- function nextLevel() {
806
- gameState.level++;
807
- gameState.isBossLevel = gameState.level % 3 === 0;
808
-
809
- if (gameState.isBossLevel) {
810
- boss = new Boss(gameState.level);
811
- enemies = [];
812
- playSound(100, 'sawtooth', 0.5);
813
- } else {
814
- boss = null;
815
- createEnemies();
816
- playSound(800, 'sine', 0.3);
817
- }
818
- }
819
-
820
- function checkCollisions() {
821
- for (let bullet of bullets) {
822
- if (!bullet.active) continue;
823
-
824
- if (boss && boss.active) {
825
- if (bullet.x > boss.x && bullet.x < boss.x + boss.width &&
826
- bullet.y > boss.y && bullet.y < boss.y + boss.height) {
827
- bullet.active = false;
828
- if (boss.takeDamage(bullet.damage)) {
829
- gameState.score += 500;
830
- boss.active = false;
831
- boss = null;
832
- playSound(50, 'sawtooth', 1);
833
- }
834
- }
835
- }
836
-
837
- for (let enemy of enemies) {
838
- if (!enemy.active) continue;
839
- if (bullet.x > enemy.x && bullet.x < enemy.x + enemy.width &&
840
- bullet.y > enemy.y && bullet.y < enemy.y + enemy.height) {
841
- bullet.active = false;
842
- const destroyed = enemy.takeDamage(bullet.damage);
843
- if (destroyed) {
844
- gameState.score += enemy.points;
845
- playSound(150, 'sawtooth', 0.1);
846
- } else {
847
- playSound(300, 'square', 0.05);
848
- }
849
- }
850
- }
851
-
852
- for (let shield of shields) {
853
- if (!shield.active) continue;
854
- if (bullet.x > shield.x && bullet.x < shield.x + shield.width &&
855
- bullet.y > shield.y && bullet.y < shield.y + shield.height) {
856
- bullet.active = false;
857
- shield.takeDamage();
858
- }
859
- }
860
- }
861
- }
862
-
863
- function drawHUD() {
864
- ctx.fillStyle = COLORS.text;
865
- ctx.font = '18px Courier New';
866
- ctx.fillText(`Score: ${gameState.score}`, 10, 30);
867
- ctx.fillText(`Level: ${gameState.level}`, 10, 55);
868
-
869
- const hearts = '\u2764\uFE0F'.repeat(gameState.lives);
870
- ctx.fillText(`Lives: ${hearts}`, canvas.width - 150, 30);
871
-
872
- if (!mudra.connected) {
873
- ctx.fillStyle = COLORS.textMuted;
874
- ctx.font = '12px Courier New';
875
- ctx.fillText('Power: 1=Low 2=Med 3=High 4=Max', canvas.width - 250, 55);
876
- }
877
-
878
- if (gameState.isBossLevel && boss && boss.active && Date.now() % 1000 < 500) {
879
- ctx.fillStyle = COLORS.error;
880
- ctx.font = 'bold 24px Courier New';
881
- ctx.textAlign = 'center';
882
- ctx.fillText('BOSS FIGHT!', canvas.width / 2, 55);
883
- ctx.textAlign = 'left';
884
- }
885
- }
886
-
887
- function drawMenu() {
888
- if (keys['arrowleft'] || keys['a']) menuCursorX -= 3;
889
- if (keys['arrowright'] || keys['d']) menuCursorX += 3;
890
- // Mudra navigation on menu
891
- if (mudra.deltaX !== 0) {
892
- menuCursorX += mudra.deltaX;
893
- mudra.deltaX = 0;
894
- }
895
- menuCursorX = Math.max(50, Math.min(canvas.width - 50, menuCursorX));
896
-
897
- ctx.fillStyle = COLORS.primary;
898
- ctx.font = 'bold 48px Courier New';
899
- ctx.textAlign = 'center';
900
- ctx.fillText('SPACE INVADERS', canvas.width / 2, 150);
901
-
902
- ctx.font = 'bold 32px Courier New';
903
- ctx.fillStyle = '#c850ff';
904
- ctx.fillText('MUDRA EDITION', canvas.width / 2, 195);
905
-
906
- const time = Date.now() / 1000;
907
- const frame = Math.floor(time * 2) % 2;
908
- drawSprite(parsedSprites.squid[frame], 250, 250, COLORS.error, 4);
909
- drawSprite(parsedSprites.crab[frame], 360, 250, COLORS.warning, 4);
910
- drawSprite(parsedSprites.octopus[frame], 470, 250, COLORS.success, 4);
911
-
912
- drawSprite(parsedSprites.player, menuCursorX - 15, 340, COLORS.primary, 3);
913
-
914
- ctx.font = '24px Courier New';
915
- ctx.fillStyle = COLORS.text;
916
- ctx.fillText('Press SPACE or Tap to Start', canvas.width / 2, 400);
917
-
918
- ctx.font = '14px Courier New';
919
- ctx.fillStyle = COLORS.textMuted;
920
- ctx.fillText('Boss fights every 3 levels!', canvas.width / 2, 450);
921
- ctx.fillText('Heat management \u2022 Power tiers \u2022 Shield system', canvas.width / 2, 470);
922
-
923
- ctx.textAlign = 'left';
924
- }
925
-
926
- function drawIntro() {
927
- ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';
928
- ctx.fillRect(0, 0, canvas.width, canvas.height);
929
-
930
- ctx.fillStyle = COLORS.primary;
931
- ctx.font = 'bold 48px Courier New';
932
- ctx.textAlign = 'center';
933
- ctx.fillText('GET READY!', canvas.width / 2, 150);
934
-
935
- ctx.font = '18px Courier New';
936
- ctx.fillStyle = COLORS.textSecondary;
937
- const tips = [
938
- 'Move: Arrow Keys / A D / Mudra Hand Tilt',
939
- 'Shoot: SPACE / Mudra Tap',
940
- 'Shield: S / Mudra Double-Tap',
941
- 'Pressure controls shot power!',
942
- `Starting in ${Math.ceil(gameState.introTimer / 60)}...`
943
- ];
944
-
945
- let y = 220;
946
- for (let tip of tips) {
947
- ctx.fillText(tip, canvas.width / 2, y);
948
- y += 35;
949
- }
950
- ctx.textAlign = 'left';
951
- }
952
-
953
- function drawPaused() {
954
- ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
955
- ctx.fillRect(0, 0, canvas.width, canvas.height);
956
- ctx.fillStyle = COLORS.warning;
957
- ctx.font = 'bold 48px Courier New';
958
- ctx.textAlign = 'center';
959
- ctx.fillText('PAUSED', canvas.width / 2, canvas.height / 2);
960
- ctx.textAlign = 'left';
961
- }
962
-
963
- function drawGameOver() {
964
- ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
965
- ctx.fillRect(0, 0, canvas.width, canvas.height);
966
-
967
- ctx.fillStyle = COLORS.error;
968
- ctx.font = 'bold 48px Courier New';
969
- ctx.textAlign = 'center';
970
- ctx.fillText('GAME OVER', canvas.width / 2, 250);
971
-
972
- ctx.font = '24px Courier New';
973
- ctx.fillStyle = COLORS.text;
974
- ctx.fillText(`Final Score: ${gameState.score}`, canvas.width / 2, 320);
975
- ctx.fillText(`Level: ${gameState.level}`, canvas.width / 2, 355);
976
- ctx.fillText('Press R or Tap to Restart', canvas.width / 2, 420);
977
-
978
- ctx.textAlign = 'left';
979
- }
980
-
981
- function drawStars() {
982
- ctx.fillStyle = COLORS.textMuted;
983
- for (let i = 0; i < 50; i++) {
984
- const x = (i * 137) % canvas.width;
985
- const y = (i * 211) % canvas.height;
986
- ctx.fillRect(x, y, 1, 1);
987
- }
988
- }
989
-
990
- // ─── Game Loop ───
991
- function gameLoop() {
992
- ctx.fillStyle = COLORS.black;
993
- ctx.fillRect(0, 0, canvas.width, canvas.height);
994
- drawStars();
995
-
996
- // Consume Mudra gesture triggers
997
- const mudraShoot = mudra.shootTriggered;
998
- const mudraShield = mudra.shieldTriggered;
999
- mudra.shootTriggered = false;
1000
- mudra.shieldTriggered = false;
1001
-
1002
- if (gameState.state === 'menu') {
1003
- drawMenu();
1004
- // Mudra tap or keyboard to start
1005
- if (mudraShoot) {
1006
- gameState.state = 'intro';
1007
- gameState.introTimer = 10 * 60;
1008
- createEnemies();
1009
- createShields();
1010
- playSound(400, 'square', 0.2);
1011
- }
1012
- } else if (gameState.state === 'intro') {
1013
- if (mudraShoot) {
1014
- gameState.state = 'playing';
1015
- playSound(600, 'square', 0.2);
1016
- } else {
1017
- gameState.introTimer--;
1018
- if (gameState.introTimer <= 0) gameState.state = 'playing';
1019
- }
1020
- drawIntro();
1021
- } else if (gameState.state === 'playing') {
1022
- if (!gameState.paused) {
1023
- player.update(keys);
1024
-
1025
- if (shootCooldown > 0) shootCooldown--;
1026
-
1027
- // Determine power: Mudra pressure or keyboard power
1028
- const pressure = mudra.connected ? mudra.smoothPressure : player.keyboardPower;
1029
-
1030
- // Shooting: keyboard space, mudra tap gesture, or high pressure
1031
- if ((keys[' '] || mudraShoot || (mudra.connected && mudra.pressure > 0.3)) && shootCooldown === 0) {
1032
- player.heat = Math.min(100, player.heat + pressure * 20);
1033
- bullets.push(new Bullet(player.x + player.width / 2, player.y, pressure));
1034
- const freq = pressure > 0.75 ? 550 : pressure > 0.5 ? 440 : pressure > 0.25 ? 330 : 220;
1035
- playSound(freq, 'square', 0.1);
1036
- shootCooldown = 20;
1037
- }
1038
-
1039
- // Shield: keyboard S, mudra up-direction, or double-tap
1040
- if (mudraShield && !player.shieldActive) {
1041
- player.activateShield();
1042
- }
1043
-
1044
- // Keyboard power control
1045
- if (keys['1']) player.keyboardPower = 0.2;
1046
- if (keys['2']) player.keyboardPower = 0.5;
1047
- if (keys['3']) player.keyboardPower = 0.7;
1048
- if (keys['4']) player.keyboardPower = 0.9;
1049
-
1050
- bullets = bullets.filter(b => { b.update(); return b.active; });
1051
- for (let enemy of enemies) enemy.update();
1052
- if (boss && boss.active) boss.update(player.x + player.width / 2);
1053
- checkCollisions();
1054
- enemies = enemies.filter(e => e.active);
1055
-
1056
- if (gameState.isBossLevel) {
1057
- if (!boss || !boss.active) nextLevel();
1058
- } else {
1059
- if (enemies.length === 0) nextLevel();
1060
- }
1061
-
1062
- if (player.heat >= 100) {
1063
- gameState.lives--;
1064
- if (gameState.lives <= 0) {
1065
- gameState.state = 'gameover';
1066
- } else {
1067
- player = new Player();
1068
- bullets = [];
1069
- }
1070
- playSound(100, 'sawtooth', 0.5);
1071
- }
1072
- }
1073
-
1074
- for (let shield of shields) shield.draw();
1075
- player.draw();
1076
- for (let bullet of bullets) bullet.draw();
1077
-
1078
- if (boss && boss.active) {
1079
- boss.draw();
1080
- } else {
1081
- for (let enemy of enemies) enemy.draw();
1082
- }
1083
-
1084
- powerBar.draw(mudra.connected ? mudra.smoothPressure : player.keyboardPower);
1085
- drawHUD();
1086
-
1087
- if (gameState.paused) drawPaused();
1088
- } else if (gameState.state === 'gameover') {
1089
- drawGameOver();
1090
- if (mudraShoot) {
1091
- gameState = {
1092
- state: 'menu', score: 0, level: 1, lives: 3,
1093
- paused: false, introTimer: 10 * 60, isBossLevel: false
1094
- };
1095
- player = new Player();
1096
- bullets = [];
1097
- enemies = [];
1098
- boss = null;
1099
- }
1100
- }
1101
-
1102
- requestAnimationFrame(gameLoop);
1103
- }
1104
-
1105
- // ─── Keyboard Input ───
1106
- window.addEventListener('keydown', (e) => {
1107
- keys[e.key.toLowerCase()] = true;
1108
-
1109
- if (gameState.state === 'menu' && e.key === ' ') {
1110
- gameState.state = 'intro';
1111
- gameState.introTimer = 10 * 60;
1112
- createEnemies();
1113
- createShields();
1114
- }
1115
-
1116
- if (gameState.state === 'intro' && e.key === ' ') {
1117
- gameState.state = 'playing';
1118
- playSound(600, 'square', 0.2);
1119
- }
1120
-
1121
- if (gameState.state === 'gameover' && e.key.toLowerCase() === 'r') {
1122
- gameState = {
1123
- state: 'menu', score: 0, level: 1, lives: 3,
1124
- paused: false, introTimer: 10 * 60, isBossLevel: false
1125
- };
1126
- player = new Player();
1127
- bullets = [];
1128
- enemies = [];
1129
- boss = null;
1130
- }
1131
-
1132
- if (e.key.toLowerCase() === 'p' && gameState.state === 'playing') {
1133
- gameState.paused = !gameState.paused;
1134
- }
1135
-
1136
- if (e.key.toLowerCase() === 's' && gameState.state === 'playing' && !player.shieldActive) {
1137
- player.activateShield();
1138
- }
1139
- });
1140
-
1141
- window.addEventListener('keyup', (e) => {
1142
- keys[e.key.toLowerCase()] = false;
1143
- });
1144
-
1145
- // ─── Utilities ───
1146
- function clamp(v, min, max) { return Math.max(min, Math.min(max, v)); }
1147
- function clamp01(v) { return Number.isFinite(v) ? clamp(v, 0, 1) : 0; }
1148
-
1149
- // ─── Start ───
1150
- connectMudra();
1151
- gameLoop();
1152
- </script>
1153
- </body>
1154
- </html>