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,390 @@
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">
6
+ <title>EMG Signal Visualizer — Mudra Studio</title>
7
+ <style>
8
+ @media (prefers-reduced-motion: reduce) {
9
+ *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
10
+ }
11
+
12
+ :root {
13
+ --bg: #ffffff; --card: #f1f5f9; --primary: #0d9488; --accent: #14b8a6;
14
+ --text: #0f172a; --text-secondary: #475569; --success: #16a34a;
15
+ --warning: #ca8a04; --error: #dc2626;
16
+ --border: rgba(15,23,42,0.12); --surface: rgba(0,0,0,0.04);
17
+ --wave-bg: #f8fafc; --grid-line: rgba(15,23,42,0.06);
18
+ --ch0: #0d9488; --ch1: #14b8a6; --ch2: #ca8a04;
19
+ }
20
+
21
+ * { margin: 0; padding: 0; box-sizing: border-box; }
22
+ body { background: var(--bg); color: var(--text); font-family: 'Poppins', system-ui, sans-serif; overflow: hidden; height: 100vh; display: flex; flex-direction: column; }
23
+
24
+ .header { display: flex; align-items: center; justify-content: space-between; padding: 10px 16px; background: var(--card); border-bottom: 1px solid var(--border); z-index: 100; flex-shrink: 0; gap: 8px; flex-wrap: wrap; }
25
+ .header-left, .header-right { display: flex; align-items: center; gap: 10px; }
26
+ .brand { font-weight: 700; font-size: 14px; } .brand span { color: var(--primary); }
27
+ .dot { width: 9px; height: 9px; border-radius: 50%; background: var(--warning); box-shadow: 0 0 0 3px rgba(234,179,8,0.15); transition: all 150ms; }
28
+ .dot.live { background: var(--success); box-shadow: 0 0 0 3px rgba(34,197,94,0.18); }
29
+ .conn-label { font-size: 12px; color: var(--text-secondary); }
30
+
31
+ .btn { background: var(--surface); border: 1px solid var(--border); color: var(--text-secondary); padding: 5px 12px; border-radius: 8px; font-size: 12px; cursor: pointer; font-family: inherit; transition: border-color 150ms, color 150ms; white-space: nowrap; }
32
+ .btn:hover { border-color: var(--primary); color: var(--primary); }
33
+
34
+ /* Waveform panels */
35
+ .wave-area { flex: 1; display: flex; flex-direction: column; gap: 2px; padding: 0; position: relative; }
36
+ .wave-panel { flex: 1; position: relative; background: var(--wave-bg); border-bottom: 1px solid var(--border); min-height: 0; }
37
+ .wave-panel:last-child { border-bottom: none; }
38
+ .wave-label { position: absolute; top: 8px; left: 12px; font-size: 11px; font-weight: 600; z-index: 5; padding: 2px 10px; border-radius: 6px; background: var(--surface); border: 1px solid var(--border); }
39
+ .wave-label.ch0 { color: var(--ch0); }
40
+ .wave-label.ch1 { color: var(--ch1); }
41
+ .wave-label.ch2 { color: var(--ch2); }
42
+ .wave-canvas { display: block; width: 100%; height: 100%; }
43
+
44
+ .frozen-badge { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 18px; font-weight: 700; color: var(--warning); letter-spacing: 2px; opacity: 0; transition: opacity 200ms; pointer-events: none; z-index: 10; text-shadow: 0 0 20px rgba(234,179,8,0.3); }
45
+ .frozen-badge.show { opacity: 1; }
46
+
47
+ .view-badge { position: absolute; top: 8px; right: 12px; font-size: 11px; color: var(--primary); font-weight: 600; z-index: 5; padding: 2px 10px; border-radius: 6px; background: var(--surface); border: 1px solid var(--border); }
48
+
49
+ .toolbar { display: flex; align-items: center; justify-content: space-between; gap: 14px; padding: 10px 16px; background: var(--card); border-top: 1px solid var(--border); z-index: 100; flex-shrink: 0; flex-wrap: wrap; }
50
+ .toolbar-left, .toolbar-right { display: flex; align-items: center; gap: 12px; }
51
+ .channel-legend { display: flex; gap: 12px; }
52
+ .legend-item { display: flex; align-items: center; gap: 5px; font-size: 11px; color: var(--text-secondary); }
53
+ .legend-dot { width: 8px; height: 8px; border-radius: 3px; }
54
+ .legend-dot.ch0 { background: var(--ch0); }
55
+ .legend-dot.ch1 { background: var(--ch1); }
56
+ .legend-dot.ch2 { background: var(--ch2); }
57
+ .meter { display: flex; align-items: center; gap: 8px; }
58
+ .meter-bar { width: 60px; height: 8px; background: var(--surface); border-radius: 999px; overflow: hidden; border: 1px solid var(--border); }
59
+ .meter-fill { height: 100%; background: linear-gradient(90deg, var(--accent), var(--primary)); border-radius: 999px; transition: width 75ms linear; width: 50%; }
60
+ .meter-label { font-size: 11px; color: var(--text-secondary); }
61
+ .meter-val { font-size: 12px; color: var(--text-secondary); min-width: 36px; text-align: right; font-variant-numeric: tabular-nums; }
62
+ .hint { font-size: 11px; color: var(--text-secondary); opacity: 0.6; }
63
+
64
+ .mudra-badge { position: fixed; bottom: 12px; right: 12px; font-size: 11px; color: var(--text-secondary); opacity: 0.7; letter-spacing: 0.02em; pointer-events: none; z-index: 200; }
65
+
66
+ @media (max-width: 640px) {
67
+ .header { padding: 8px 10px; } .toolbar { gap: 8px; padding: 8px 10px; }
68
+ .hint { display: none; } .channel-legend { gap: 6px; }
69
+ .meter-bar { width: 40px; }
70
+ }
71
+ </style>
72
+ </head>
73
+ <body>
74
+
75
+ <div class="header">
76
+ <div class="header-left">
77
+ <div class="brand"><span>Mudra</span> EMG Signal Visualizer</div>
78
+ <span class="dot" id="dot"></span>
79
+ <span class="conn-label" id="connLabel">Connecting&hellip;</span>
80
+ </div>
81
+ <div class="header-right">
82
+ <a href="/gallery" class="btn" style="text-decoration:none;">&#8592; Gallery</a>
83
+ <button class="btn" id="simFreeze">Freeze</button>
84
+ <button class="btn" id="simClear">Clear</button>
85
+ <button class="btn" id="simView">Toggle View</button>
86
+ </div>
87
+ </div>
88
+
89
+ <div class="wave-area" id="waveArea">
90
+ <div class="wave-panel" id="panel0">
91
+ <div class="wave-label ch0">Ch 0 — Ulnar</div>
92
+ <canvas class="wave-canvas" id="canvas0"></canvas>
93
+ </div>
94
+ <div class="wave-panel" id="panel1">
95
+ <div class="wave-label ch1">Ch 1 — Median</div>
96
+ <canvas class="wave-canvas" id="canvas1"></canvas>
97
+ </div>
98
+ <div class="wave-panel" id="panel2">
99
+ <div class="wave-label ch2">Ch 2 — Radial</div>
100
+ <canvas class="wave-canvas" id="canvas2"></canvas>
101
+ </div>
102
+ <div class="frozen-badge" id="frozenBadge">FROZEN</div>
103
+ <div class="view-badge" id="viewBadge">LINE</div>
104
+ </div>
105
+
106
+ <div class="toolbar">
107
+ <div class="toolbar-left">
108
+ <div class="channel-legend">
109
+ <div class="legend-item"><div class="legend-dot ch0"></div>Ulnar</div>
110
+ <div class="legend-item"><div class="legend-dot ch1"></div>Median</div>
111
+ <div class="legend-item"><div class="legend-dot ch2"></div>Radial</div>
112
+ </div>
113
+ <div class="meter">
114
+ <span class="meter-label">Scale</span>
115
+ <div class="meter-bar"><div class="meter-fill" id="scaleFill"></div></div>
116
+ <span class="meter-val" id="scaleVal">1.0x</span>
117
+ </div>
118
+ </div>
119
+ <span class="hint">Space freeze &middot; C clear &middot; V toggle view &middot; [ ] scale</span>
120
+ </div>
121
+
122
+ <div class="mudra-badge">Created by Mudra</div>
123
+
124
+ <script>
125
+ /* ── Mudra Protocol Connection (v2) ────────────────────────── */
126
+ const WS_URL = 'ws://127.0.0.1:8766';
127
+ const SIGNALS = ['snc', 'gesture', 'pressure'];
128
+
129
+ let ws, reconnectTimer, simTimer;
130
+ let wsOpen = false, deviceReady = false, simulating = true;
131
+ const bus = {};
132
+
133
+ function on(ev, fn) { (bus[ev] ??= []).push(fn); }
134
+ function emit(ev, d) { (bus[ev] || []).forEach(fn => fn(d)); }
135
+ function send(p) { if (ws?.readyState === WebSocket.OPEN) ws.send(JSON.stringify(p)); }
136
+
137
+ function connect() {
138
+ try {
139
+ ws = new WebSocket(WS_URL);
140
+ ws.onopen = () => {
141
+ wsOpen = true; stopSim(); updateStatus();
142
+ SIGNALS.forEach(s => send({ command: 'subscribe', signal: s }));
143
+ };
144
+ ws.onmessage = (e) => {
145
+ try {
146
+ const msg = JSON.parse(e.data);
147
+ if (msg.type === 'connection_status') {
148
+ deviceReady = msg.data?.status === 'connected';
149
+ updateStatus(); return;
150
+ }
151
+ emit(msg.type, msg.data);
152
+ } catch {}
153
+ };
154
+ ws.onclose = () => {
155
+ wsOpen = false; deviceReady = false; updateStatus();
156
+ startSim();
157
+ clearTimeout(reconnectTimer);
158
+ reconnectTimer = setTimeout(connect, 1500);
159
+ };
160
+ ws.onerror = () => ws?.close();
161
+ } catch {
162
+ wsOpen = false; startSim();
163
+ clearTimeout(reconnectTimer);
164
+ reconnectTimer = setTimeout(connect, 1500);
165
+ }
166
+ }
167
+
168
+ function triggerGesture(type) {
169
+ if (wsOpen) send({ command: 'trigger_gesture', data: { type } });
170
+ else emit('gesture', { type, confidence: 0.95, timestamp: Date.now() });
171
+ }
172
+
173
+ /* ── Simulation (when Companion not reachable) ─────────────── */
174
+ function startSim() {
175
+ if (simTimer) return;
176
+ simulating = true;
177
+ let t = 0;
178
+ simTimer = setInterval(() => {
179
+ t += 0.05;
180
+ // Generate realistic SNC data with varying amplitude and frequency
181
+ const burstEnvelope = 0.5 + 0.5 * Math.sin(t * 0.15);
182
+ emit('snc', {
183
+ values: [
184
+ burstEnvelope * (0.4 * Math.sin(t * 3.7 + Math.sin(t * 0.8)) + 0.15 * Math.sin(t * 11.3) + (Math.random() - 0.5) * 0.2),
185
+ burstEnvelope * (0.35 * Math.cos(t * 4.1 + Math.cos(t * 0.6)) + 0.12 * Math.sin(t * 13.7) + (Math.random() - 0.5) * 0.18),
186
+ burstEnvelope * (0.3 * Math.sin(t * 5.3 + Math.sin(t * 1.1)) + 0.1 * Math.cos(t * 9.1) + (Math.random() - 0.5) * 0.15)
187
+ ],
188
+ frequency: 1000, timestamp: Date.now()
189
+ });
190
+ // Gentle pressure
191
+ const n = Math.max(0, Math.min(1, 0.5 + 0.2 * Math.sin(t * 0.3)));
192
+ emit('pressure', { value: Math.round(n * 100), normalized: n, timestamp: Date.now() });
193
+ // Occasional gestures
194
+ if (Math.random() < 0.002) {
195
+ const g = ['tap', 'double_tap', 'twist', 'double_twist'];
196
+ emit('gesture', { type: g[Math.floor(Math.random() * g.length)], confidence: 0.7 + Math.random() * 0.3, timestamp: Date.now() });
197
+ }
198
+ }, 16); // Higher frequency for SNC data
199
+ }
200
+
201
+ function stopSim() { clearInterval(simTimer); simTimer = null; simulating = false; }
202
+
203
+ /* ── Status UI ─────────────────────────────────────────────── */
204
+ const dotEl = document.getElementById('dot');
205
+ const connEl = document.getElementById('connLabel');
206
+
207
+ function updateStatus() {
208
+ if (wsOpen && deviceReady) { dotEl.className = 'dot live'; connEl.textContent = 'Connected'; }
209
+ else if (wsOpen) { dotEl.className = 'dot'; connEl.textContent = 'Waiting for Mudra Band\u2026'; }
210
+ else { dotEl.className = 'dot'; connEl.textContent = simulating ? 'Simulated' : 'Disconnected'; }
211
+ }
212
+
213
+ /* ── Waveform State ────────────────────────────────────────── */
214
+ const BUF_LEN = 500;
215
+ const buffers = [new Float32Array(BUF_LEN), new Float32Array(BUF_LEN), new Float32Array(BUF_LEN)];
216
+ let frozen = false;
217
+ let viewMode = 'line'; // 'line' or 'bar'
218
+ let vScale = 1.0; // vertical scale, 0.2 to 3.0
219
+
220
+ const canvases = [
221
+ document.getElementById('canvas0'),
222
+ document.getElementById('canvas1'),
223
+ document.getElementById('canvas2')
224
+ ];
225
+ const ctxs = canvases.map(c => c.getContext('2d'));
226
+
227
+ function resizeCanvases() {
228
+ canvases.forEach((c, i) => {
229
+ const panel = c.parentElement;
230
+ const r = panel.getBoundingClientRect();
231
+ c.width = r.width * devicePixelRatio;
232
+ c.height = r.height * devicePixelRatio;
233
+ ctxs[i].setTransform(devicePixelRatio, 0, 0, devicePixelRatio, 0, 0);
234
+ });
235
+ }
236
+ resizeCanvases();
237
+ window.addEventListener('resize', resizeCanvases);
238
+
239
+ /* ── Signal Handlers ───────────────────────────────────────── */
240
+ on('snc', (d) => {
241
+ if (frozen) return;
242
+ const vals = d.values || [0, 0, 0];
243
+ for (let ch = 0; ch < 3; ch++) {
244
+ buffers[ch].copyWithin(0, 1);
245
+ buffers[ch][BUF_LEN - 1] = vals[ch];
246
+ }
247
+ });
248
+
249
+ on('gesture', (d) => {
250
+ const type = d.type || 'unknown';
251
+ if (type === 'tap') {
252
+ frozen = !frozen;
253
+ document.getElementById('frozenBadge').classList.toggle('show', frozen);
254
+ }
255
+ if (type === 'double_tap') {
256
+ // Clear waveforms
257
+ for (let ch = 0; ch < 3; ch++) buffers[ch].fill(0);
258
+ }
259
+ if (type === 'twist') {
260
+ viewMode = viewMode === 'line' ? 'bar' : 'line';
261
+ document.getElementById('viewBadge').textContent = viewMode === 'line' ? 'LINE' : 'BAR';
262
+ }
263
+ });
264
+
265
+ on('pressure', (d) => {
266
+ const p = Math.max(0, Math.min(1, d.normalized ?? d.value / 100));
267
+ // Map pressure to vertical scale: 0.2x to 3.0x
268
+ vScale = 0.2 + p * 2.8;
269
+ document.getElementById('scaleVal').textContent = vScale.toFixed(1) + 'x';
270
+ document.getElementById('scaleFill').style.width = Math.round(p * 100) + '%';
271
+ });
272
+
273
+ /* ── Render Loop ───────────────────────────────────────────── */
274
+ const channelColors = ['--ch0', '--ch1', '--ch2'];
275
+
276
+ function getColor(varName) {
277
+ return getComputedStyle(document.documentElement).getPropertyValue(varName).trim();
278
+ }
279
+
280
+ function drawLine(ctx, buf, w, h, color) {
281
+ const gridColor = getColor('--grid-line') || 'rgba(148,163,184,0.06)';
282
+ // Grid
283
+ ctx.strokeStyle = gridColor;
284
+ ctx.lineWidth = 0.5;
285
+ for (let i = 0; i < 5; i++) {
286
+ const gy = h * i / 4;
287
+ ctx.beginPath(); ctx.moveTo(0, gy); ctx.lineTo(w, gy); ctx.stroke();
288
+ }
289
+ // Centerline
290
+ ctx.strokeStyle = gridColor;
291
+ ctx.lineWidth = 1;
292
+ ctx.beginPath(); ctx.moveTo(0, h / 2); ctx.lineTo(w, h / 2); ctx.stroke();
293
+
294
+ // Waveform
295
+ ctx.beginPath();
296
+ ctx.strokeStyle = color;
297
+ ctx.lineWidth = 1.5;
298
+ ctx.shadowColor = color;
299
+ ctx.shadowBlur = 4;
300
+ for (let i = 0; i < BUF_LEN; i++) {
301
+ const x = (i / (BUF_LEN - 1)) * w;
302
+ const v = buf[i] * vScale;
303
+ const y = h / 2 - v * (h * 0.4);
304
+ if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
305
+ }
306
+ ctx.stroke();
307
+ ctx.shadowBlur = 0;
308
+ }
309
+
310
+ function drawBar(ctx, buf, w, h, color) {
311
+ const gridColor = getColor('--grid-line') || 'rgba(148,163,184,0.06)';
312
+ // Grid
313
+ ctx.strokeStyle = gridColor;
314
+ ctx.lineWidth = 0.5;
315
+ for (let i = 0; i < 5; i++) {
316
+ const gy = h * i / 4;
317
+ ctx.beginPath(); ctx.moveTo(0, gy); ctx.lineTo(w, gy); ctx.stroke();
318
+ }
319
+ // Centerline
320
+ ctx.strokeStyle = gridColor;
321
+ ctx.lineWidth = 1;
322
+ ctx.beginPath(); ctx.moveTo(0, h / 2); ctx.lineTo(w, h / 2); ctx.stroke();
323
+
324
+ // Bars
325
+ const numBars = 80;
326
+ const barW = w / numBars - 1;
327
+ const step = Math.floor(BUF_LEN / numBars);
328
+
329
+ for (let i = 0; i < numBars; i++) {
330
+ const idx = i * step;
331
+ const v = buf[idx] * vScale;
332
+ const barH = Math.abs(v) * h * 0.4;
333
+ const x = i * (barW + 1);
334
+ const y = v >= 0 ? h / 2 - barH : h / 2;
335
+
336
+ ctx.fillStyle = color;
337
+ ctx.globalAlpha = 0.5 + Math.min(0.5, Math.abs(v) * 2);
338
+ ctx.fillRect(x, y, barW, barH);
339
+ }
340
+ ctx.globalAlpha = 1;
341
+ }
342
+
343
+ function render() {
344
+ const bgColor = getColor('--wave-bg') || '#0a0f12';
345
+ canvases.forEach((c, ch) => {
346
+ const r = c.parentElement.getBoundingClientRect();
347
+ const w = r.width;
348
+ const h = r.height;
349
+ const ctx = ctxs[ch];
350
+ ctx.fillStyle = bgColor;
351
+ ctx.fillRect(0, 0, w, h);
352
+
353
+ const color = getColor(channelColors[ch]);
354
+ if (viewMode === 'line') {
355
+ drawLine(ctx, buffers[ch], w, h, color);
356
+ } else {
357
+ drawBar(ctx, buffers[ch], w, h, color);
358
+ }
359
+ });
360
+ requestAnimationFrame(render);
361
+ }
362
+
363
+ /* ── Keyboard Fallback ─────────────────────────────────────── */
364
+ document.addEventListener('keydown', (e) => {
365
+ if (e.code === 'Space') { e.preventDefault(); triggerGesture('tap'); }
366
+ if (e.code === 'KeyC') triggerGesture('double_tap');
367
+ if (e.code === 'KeyV') triggerGesture('twist');
368
+ if (e.key === ']') {
369
+ vScale = Math.min(3, vScale + 0.2);
370
+ const p = (vScale - 0.2) / 2.8;
371
+ emit('pressure', { value: Math.round(p * 100), normalized: p, timestamp: Date.now() });
372
+ }
373
+ if (e.key === '[') {
374
+ vScale = Math.max(0.2, vScale - 0.2);
375
+ const p = (vScale - 0.2) / 2.8;
376
+ emit('pressure', { value: Math.round(p * 100), normalized: p, timestamp: Date.now() });
377
+ }
378
+ });
379
+
380
+ /* ── Buttons ───────────────────────────────────────────────── */
381
+ document.getElementById('simFreeze').addEventListener('click', () => triggerGesture('tap'));
382
+ document.getElementById('simClear').addEventListener('click', () => triggerGesture('double_tap'));
383
+ document.getElementById('simView').addEventListener('click', () => triggerGesture('twist'));
384
+
385
+ /* ── Init ──────────────────────────────────────────────────── */
386
+ connect();
387
+ render();
388
+ </script>
389
+ </body>
390
+ </html>