mudra-skills 1.0.0

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 (149) hide show
  1. package/.claude-plugin/plugin.json +19 -0
  2. package/CLAUDE.md +35 -0
  3. package/bin/install.js +25 -0
  4. package/package.json +28 -0
  5. package/skills/mudra-master/SKILL.md +202 -0
  6. package/skills/mudra-master/mudra-preview/SKILL.md +151 -0
  7. package/skills/mudra-master/mudra-preview/assets/ar-menu.html +382 -0
  8. package/skills/mudra-master/mudra-preview/assets/document-scroller.html +468 -0
  9. package/skills/mudra-master/mudra-preview/assets/drum-machine.html +788 -0
  10. package/skills/mudra-master/mudra-preview/assets/emg-visualizer.html +390 -0
  11. package/skills/mudra-master/mudra-preview/assets/generative-art.html +408 -0
  12. package/skills/mudra-master/mudra-preview/assets/gesture-assistant.html +342 -0
  13. package/skills/mudra-master/mudra-preview/assets/gesture-speech.html +444 -0
  14. package/skills/mudra-master/mudra-preview/assets/hands-free-desktop.html +376 -0
  15. package/skills/mudra-master/mudra-preview/assets/model-rotator.html +365 -0
  16. package/skills/mudra-master/mudra-preview/assets/mudra-duel.html +3200 -0
  17. package/skills/mudra-master/mudra-preview/assets/mudra-monitor.html +1451 -0
  18. package/skills/mudra-master/mudra-preview/assets/mudra-ultimate-template.html +891 -0
  19. package/skills/mudra-master/mudra-preview/assets/music-sequencer.html +455 -0
  20. package/skills/mudra-master/mudra-preview/assets/neural-pong.html +380 -0
  21. package/skills/mudra-master/mudra-preview/assets/neural-snake.html +364 -0
  22. package/skills/mudra-master/mudra-preview/assets/presentation-controller.html +345 -0
  23. package/skills/mudra-master/mudra-preview/assets/pressure-painter.html +285 -0
  24. package/skills/mudra-master/mudra-preview/assets/runner.html +1812 -0
  25. package/skills/mudra-master/mudra-preview/assets/smart-home.html +461 -0
  26. package/skills/mudra-master/mudra-preview/assets/space-invaders.html +1154 -0
  27. package/skills/mudra-master/mudra-preview/assets/waterful-ring-toss.html +1624 -0
  28. package/skills/mudra-master/mudra-preview/references/agent_protocol.json +646 -0
  29. package/skills/mudra-master/mudra-preview/references/promt.md +16829 -0
  30. package/skills/mudra-master/mudra-xr/SKILL.md +245 -0
  31. package/skills/mudra-master/mudra-xr/assets/demos/3dgs-walkthrough.html +242 -0
  32. package/skills/mudra-master/mudra-xr/assets/demos/aisimulator.html +792 -0
  33. package/skills/mudra-master/mudra-xr/assets/demos/balloonpop.html +422 -0
  34. package/skills/mudra-master/mudra-xr/assets/demos/ballpit.html +284 -0
  35. package/skills/mudra-master/mudra-xr/assets/demos/drone.html +41 -0
  36. package/skills/mudra-master/mudra-xr/assets/demos/gemini-icebreakers.html +338 -0
  37. package/skills/mudra-master/mudra-xr/assets/demos/gemini-xrobject.html +302 -0
  38. package/skills/mudra-master/mudra-xr/assets/demos/math3d.html +188 -0
  39. package/skills/mudra-master/mudra-xr/assets/demos/measure.html +247 -0
  40. package/skills/mudra-master/mudra-xr/assets/demos/occlusion.html +251 -0
  41. package/skills/mudra-master/mudra-xr/assets/demos/rain.html +498 -0
  42. package/skills/mudra-master/mudra-xr/assets/demos/screenwiper.html +432 -0
  43. package/skills/mudra-master/mudra-xr/assets/demos/splash.html +519 -0
  44. package/skills/mudra-master/mudra-xr/assets/demos/webcam_gestures.html +253 -0
  45. package/skills/mudra-master/mudra-xr/assets/demos/xremoji.html +833 -0
  46. package/skills/mudra-master/mudra-xr/assets/demos/xrpoet.html +151 -0
  47. package/skills/mudra-master/mudra-xr/assets/samples/depthmap.html +266 -0
  48. package/skills/mudra-master/mudra-xr/assets/samples/depthmesh.html +100 -0
  49. package/skills/mudra-master/mudra-xr/assets/samples/game_rps.html +1099 -0
  50. package/skills/mudra-master/mudra-xr/assets/samples/gestures_custom.html +467 -0
  51. package/skills/mudra-master/mudra-xr/assets/samples/gestures_heuristic.html +256 -0
  52. package/skills/mudra-master/mudra-xr/assets/samples/lighting.html +163 -0
  53. package/skills/mudra-master/mudra-xr/assets/samples/mesh_detection.html +46 -0
  54. package/skills/mudra-master/mudra-xr/assets/samples/modelviewer.html +183 -0
  55. package/skills/mudra-master/mudra-xr/assets/samples/paint.html +133 -0
  56. package/skills/mudra-master/mudra-xr/assets/samples/planar-vst.html +45 -0
  57. package/skills/mudra-master/mudra-xr/assets/samples/reticle.html +95 -0
  58. package/skills/mudra-master/mudra-xr/assets/samples/skybox_agent.html +384 -0
  59. package/skills/mudra-master/mudra-xr/assets/samples/sound.html +390 -0
  60. package/skills/mudra-master/mudra-xr/assets/samples/ui.html +496 -0
  61. package/skills/mudra-master/mudra-xr/assets/samples/virtual-screens.html +808 -0
  62. package/skills/mudra-master/mudra-xr/assets/templates/0_basic.html +64 -0
  63. package/skills/mudra-master/mudra-xr/assets/templates/1_ui.html +107 -0
  64. package/skills/mudra-master/mudra-xr/assets/templates/2_hands.html +183 -0
  65. package/skills/mudra-master/mudra-xr/assets/templates/3_depth.html +89 -0
  66. package/skills/mudra-master/mudra-xr/assets/templates/4_stereo.html +84 -0
  67. package/skills/mudra-master/mudra-xr/assets/templates/5_camera.html +117 -0
  68. package/skills/mudra-master/mudra-xr/assets/templates/6_ai.html +154 -0
  69. package/skills/mudra-master/mudra-xr/assets/templates/7_ai_live.html +253 -0
  70. package/skills/mudra-master/mudra-xr/assets/templates/8_objects.html +95 -0
  71. package/skills/mudra-master/mudra-xr/assets/templates/9_xr-toggle.html +90 -0
  72. package/skills/mudra-master/mudra-xr/assets/templates/heuristic_hand_gestures.html +102 -0
  73. package/skills/mudra-master/mudra-xr/assets/templates/meshes.html +47 -0
  74. package/skills/mudra-master/mudra-xr/assets/templates/planes.html +47 -0
  75. package/skills/mudra-master/mudra-xr/assets/templates/uikit.html +187 -0
  76. package/skills/mudra-master/mudra-xr/references/agent_protocol.json +878 -0
  77. package/skills/mudra-master/mudra-xr/references/promt.md +1516 -0
  78. package/skills/mudra-preview/SKILL.md +151 -0
  79. package/skills/mudra-preview/assets/ar-menu.html +382 -0
  80. package/skills/mudra-preview/assets/document-scroller.html +468 -0
  81. package/skills/mudra-preview/assets/drum-machine.html +788 -0
  82. package/skills/mudra-preview/assets/emg-visualizer.html +390 -0
  83. package/skills/mudra-preview/assets/generative-art.html +408 -0
  84. package/skills/mudra-preview/assets/gesture-assistant.html +342 -0
  85. package/skills/mudra-preview/assets/gesture-speech.html +444 -0
  86. package/skills/mudra-preview/assets/hands-free-desktop.html +376 -0
  87. package/skills/mudra-preview/assets/model-rotator.html +365 -0
  88. package/skills/mudra-preview/assets/mudra-duel.html +3200 -0
  89. package/skills/mudra-preview/assets/mudra-monitor.html +1451 -0
  90. package/skills/mudra-preview/assets/mudra-ultimate-template.html +891 -0
  91. package/skills/mudra-preview/assets/music-sequencer.html +455 -0
  92. package/skills/mudra-preview/assets/neural-pong.html +380 -0
  93. package/skills/mudra-preview/assets/neural-snake.html +364 -0
  94. package/skills/mudra-preview/assets/presentation-controller.html +345 -0
  95. package/skills/mudra-preview/assets/pressure-painter.html +285 -0
  96. package/skills/mudra-preview/assets/runner.html +1812 -0
  97. package/skills/mudra-preview/assets/smart-home.html +461 -0
  98. package/skills/mudra-preview/assets/space-invaders.html +1154 -0
  99. package/skills/mudra-preview/assets/waterful-ring-toss.html +1624 -0
  100. package/skills/mudra-preview/references/agent_protocol.json +646 -0
  101. package/skills/mudra-preview/references/promt.md +16829 -0
  102. package/skills/mudra-xr/SKILL.md +245 -0
  103. package/skills/mudra-xr/assets/demos/3dgs-walkthrough.html +242 -0
  104. package/skills/mudra-xr/assets/demos/aisimulator.html +792 -0
  105. package/skills/mudra-xr/assets/demos/balloonpop.html +422 -0
  106. package/skills/mudra-xr/assets/demos/ballpit.html +284 -0
  107. package/skills/mudra-xr/assets/demos/drone.html +41 -0
  108. package/skills/mudra-xr/assets/demos/gemini-icebreakers.html +338 -0
  109. package/skills/mudra-xr/assets/demos/gemini-xrobject.html +302 -0
  110. package/skills/mudra-xr/assets/demos/math3d.html +188 -0
  111. package/skills/mudra-xr/assets/demos/measure.html +247 -0
  112. package/skills/mudra-xr/assets/demos/occlusion.html +251 -0
  113. package/skills/mudra-xr/assets/demos/rain.html +498 -0
  114. package/skills/mudra-xr/assets/demos/screenwiper.html +432 -0
  115. package/skills/mudra-xr/assets/demos/splash.html +519 -0
  116. package/skills/mudra-xr/assets/demos/webcam_gestures.html +253 -0
  117. package/skills/mudra-xr/assets/demos/xremoji.html +833 -0
  118. package/skills/mudra-xr/assets/demos/xrpoet.html +151 -0
  119. package/skills/mudra-xr/assets/samples/depthmap.html +266 -0
  120. package/skills/mudra-xr/assets/samples/depthmesh.html +100 -0
  121. package/skills/mudra-xr/assets/samples/game_rps.html +1099 -0
  122. package/skills/mudra-xr/assets/samples/gestures_custom.html +467 -0
  123. package/skills/mudra-xr/assets/samples/gestures_heuristic.html +256 -0
  124. package/skills/mudra-xr/assets/samples/lighting.html +163 -0
  125. package/skills/mudra-xr/assets/samples/mesh_detection.html +46 -0
  126. package/skills/mudra-xr/assets/samples/modelviewer.html +183 -0
  127. package/skills/mudra-xr/assets/samples/paint.html +133 -0
  128. package/skills/mudra-xr/assets/samples/planar-vst.html +45 -0
  129. package/skills/mudra-xr/assets/samples/reticle.html +95 -0
  130. package/skills/mudra-xr/assets/samples/skybox_agent.html +384 -0
  131. package/skills/mudra-xr/assets/samples/sound.html +390 -0
  132. package/skills/mudra-xr/assets/samples/ui.html +496 -0
  133. package/skills/mudra-xr/assets/samples/virtual-screens.html +808 -0
  134. package/skills/mudra-xr/assets/templates/0_basic.html +64 -0
  135. package/skills/mudra-xr/assets/templates/1_ui.html +107 -0
  136. package/skills/mudra-xr/assets/templates/2_hands.html +183 -0
  137. package/skills/mudra-xr/assets/templates/3_depth.html +89 -0
  138. package/skills/mudra-xr/assets/templates/4_stereo.html +84 -0
  139. package/skills/mudra-xr/assets/templates/5_camera.html +117 -0
  140. package/skills/mudra-xr/assets/templates/6_ai.html +154 -0
  141. package/skills/mudra-xr/assets/templates/7_ai_live.html +253 -0
  142. package/skills/mudra-xr/assets/templates/8_objects.html +95 -0
  143. package/skills/mudra-xr/assets/templates/9_xr-toggle.html +90 -0
  144. package/skills/mudra-xr/assets/templates/heuristic_hand_gestures.html +102 -0
  145. package/skills/mudra-xr/assets/templates/meshes.html +47 -0
  146. package/skills/mudra-xr/assets/templates/planes.html +47 -0
  147. package/skills/mudra-xr/assets/templates/uikit.html +187 -0
  148. package/skills/mudra-xr/references/agent_protocol.json +878 -0
  149. package/skills/mudra-xr/references/promt.md +1516 -0
@@ -0,0 +1,1154 @@
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>