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,345 @@
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>Presentation Controller — 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
+ --slide-bg: #f8fafc;
18
+ }
19
+
20
+ * { margin: 0; padding: 0; box-sizing: border-box; }
21
+ body { background: var(--bg); color: var(--text); font-family: 'Poppins', system-ui, sans-serif; overflow: hidden; height: 100vh; display: flex; flex-direction: column; }
22
+
23
+ .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; }
24
+ .header-left, .header-right { display: flex; align-items: center; gap: 10px; }
25
+ .brand { font-weight: 700; font-size: 14px; } .brand span { color: var(--primary); }
26
+ .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; }
27
+ .dot.live { background: var(--success); box-shadow: 0 0 0 3px rgba(34,197,94,0.18); }
28
+ .conn-label { font-size: 12px; color: var(--text-secondary); }
29
+
30
+ .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; }
31
+ .btn:hover { border-color: var(--primary); color: var(--primary); }
32
+
33
+ .slide-container { flex: 1; position: relative; overflow: hidden; background: var(--slide-bg); }
34
+ .slide { position: absolute; inset: 0; display: flex; flex-direction: column; justify-content: center; align-items: center; padding: 40px 60px; transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.5s; text-align: center; }
35
+ .slide.active { transform: translateX(0); opacity: 1; }
36
+ .slide.left { transform: translateX(-100%); opacity: 0; }
37
+ .slide.right { transform: translateX(100%); opacity: 0; }
38
+ .slide h1 { font-size: clamp(28px, 5vw, 52px); font-weight: 800; margin-bottom: 16px; line-height: 1.15; }
39
+ .slide h1 em { font-style: normal; color: var(--primary); }
40
+ .slide p { font-size: clamp(14px, 2vw, 18px); color: var(--text-secondary); max-width: 600px; line-height: 1.7; }
41
+ .slide .tag { display: inline-block; background: rgba(119,234,233,0.1); color: var(--primary); padding: 4px 14px; border-radius: 20px; font-size: 13px; margin-bottom: 14px; font-weight: 600; border: 1px solid rgba(119,234,233,0.15); }
42
+ .slide ul { list-style: none; display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; margin-top: 20px; }
43
+ .slide ul li { background: var(--surface); border: 1px solid var(--border); padding: 10px 18px; border-radius: 10px; font-size: 14px; color: var(--text-secondary); }
44
+
45
+ .laser { position: fixed; border-radius: 50%; background: var(--error); box-shadow: 0 0 16px var(--error), 0 0 40px rgba(239,68,68,0.35); pointer-events: none; z-index: 90; opacity: 0; transition: opacity 0.15s; transform: translate(-50%, -50%); }
46
+
47
+ .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; }
48
+ .toolbar-left, .toolbar-right { display: flex; align-items: center; gap: 12px; }
49
+ .nav-dots { display: flex; gap: 6px; }
50
+ .nav-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--surface); border: 1px solid var(--border); cursor: pointer; transition: background 150ms, border-color 150ms; }
51
+ .nav-dot.active { background: var(--primary); border-color: var(--primary); }
52
+ .slide-counter { font-size: 13px; color: var(--text-secondary); font-variant-numeric: tabular-nums; min-width: 48px; text-align: center; }
53
+ .meter { display: flex; align-items: center; gap: 8px; }
54
+ .meter-bar { width: 80px; height: 8px; background: var(--surface); border-radius: 999px; overflow: hidden; border: 1px solid var(--border); }
55
+ .meter-fill { height: 100%; background: linear-gradient(90deg, var(--accent), var(--primary)); border-radius: 999px; transition: width 75ms linear; width: 0%; }
56
+ .meter-val { font-size: 12px; color: var(--text-secondary); min-width: 32px; text-align: right; font-variant-numeric: tabular-nums; }
57
+ .laser-badge { font-size: 11px; color: var(--error); font-weight: 600; opacity: 0; transition: opacity 150ms; }
58
+ .laser-badge.active { opacity: 1; }
59
+ .hint { font-size: 11px; color: var(--text-secondary); opacity: 0.6; }
60
+
61
+ .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; }
62
+
63
+ @media (max-width: 640px) {
64
+ .header { padding: 8px 10px; } .toolbar { gap: 8px; padding: 8px 10px; }
65
+ .slide { padding: 24px 20px; } .meter-bar { width: 50px; } .hint { display: none; }
66
+ }
67
+ </style>
68
+ </head>
69
+ <body>
70
+
71
+ <div class="header">
72
+ <div class="header-left">
73
+ <div class="brand"><span>Mudra</span> Presentation Controller</div>
74
+ <span class="dot" id="dot"></span>
75
+ <span class="conn-label" id="connLabel">Connecting&hellip;</span>
76
+ </div>
77
+ <div class="header-right">
78
+ <a href="/gallery" class="btn" style="text-decoration:none;">&#8592; Gallery</a>
79
+ <button class="btn" id="simTap">Simulate Tap</button>
80
+ <button class="btn" id="simTwist">Simulate Twist</button>
81
+ </div>
82
+ </div>
83
+
84
+ <div class="slide-container" id="slideContainer">
85
+ <div class="slide active" id="slide0">
86
+ <div class="tag">Welcome</div>
87
+ <h1>Present with <em>Neural Input</em></h1>
88
+ <p>Control your presentations using the Mudra wristband. No clicker, no remote — just natural gestures from your hand.</p>
89
+ </div>
90
+ <div class="slide right" id="slide1">
91
+ <div class="tag">The Problem</div>
92
+ <h1>Clickers Are <em>Outdated</em></h1>
93
+ <p>Physical remotes get lost, run out of batteries, and limit your movement. Neural input frees you from hardware constraints entirely.</p>
94
+ </div>
95
+ <div class="slide right" id="slide2">
96
+ <div class="tag">Technology</div>
97
+ <h1>How <em>Mudra</em> Works</h1>
98
+ <p>The Mudra wristband reads surface nerve signals (sEMG) from your wrist, detecting micro-gestures with millisecond latency and zero physical movement required.</p>
99
+ </div>
100
+ <div class="slide right" id="slide3">
101
+ <div class="tag">Gestures</div>
102
+ <h1>Gesture <em>Controls</em></h1>
103
+ <p>Intuitive mapping of neural gestures to presentation actions. All customizable via the API.</p>
104
+ <ul>
105
+ <li>Tap: Next Slide</li>
106
+ <li>Twist: Previous</li>
107
+ <li>Double Tap: Laser Pointer</li>
108
+ <li>Double Twist: Reset</li>
109
+ </ul>
110
+ </div>
111
+ <div class="slide right" id="slide4">
112
+ <div class="tag">Get Started</div>
113
+ <h1>Join the <em>Waitlist</em></h1>
114
+ <p>Be among the first developers to build with neural input APIs. Visit mudrastudio.dev to register for early access and start building the future of hands-free interaction.</p>
115
+ </div>
116
+ </div>
117
+
118
+ <div class="laser" id="laser"></div>
119
+
120
+ <div class="toolbar">
121
+ <div class="toolbar-left">
122
+ <div class="nav-dots" id="navDots"></div>
123
+ <span class="slide-counter" id="slideCounter">1 / 5</span>
124
+ <span class="laser-badge" id="laserBadge">LASER</span>
125
+ </div>
126
+ <div class="toolbar-right">
127
+ <div class="meter">
128
+ <span class="meter-val" id="pressVal">0%</span>
129
+ <div class="meter-bar"><div class="meter-fill" id="pressFill"></div></div>
130
+ </div>
131
+ <span class="hint">Arrow keys &middot; Space tap &middot; L laser &middot; [ ] size</span>
132
+ </div>
133
+ </div>
134
+
135
+ <div class="mudra-badge">Created by Mudra</div>
136
+
137
+ <script>
138
+ /* ── Mudra Protocol Connection (v2) ────────────────────────── */
139
+ const WS_URL = 'ws://127.0.0.1:8766';
140
+ const SIGNALS = ['gesture', 'navigation', 'pressure'];
141
+
142
+ let ws, reconnectTimer, simTimer;
143
+ let wsOpen = false, deviceReady = false, simulating = true;
144
+ const bus = {};
145
+
146
+ function on(ev, fn) { (bus[ev] ??= []).push(fn); }
147
+ function emit(ev, d) { (bus[ev] || []).forEach(fn => fn(d)); }
148
+ function send(p) { if (ws?.readyState === WebSocket.OPEN) ws.send(JSON.stringify(p)); }
149
+
150
+ function connect() {
151
+ try {
152
+ ws = new WebSocket(WS_URL);
153
+ ws.onopen = () => {
154
+ wsOpen = true; stopSim(); updateStatus();
155
+ SIGNALS.forEach(s => send({ command: 'subscribe', signal: s }));
156
+ };
157
+ ws.onmessage = (e) => {
158
+ try {
159
+ const msg = JSON.parse(e.data);
160
+ if (msg.type === 'connection_status') {
161
+ deviceReady = msg.data?.status === 'connected';
162
+ updateStatus(); return;
163
+ }
164
+ emit(msg.type, msg.data);
165
+ } catch {}
166
+ };
167
+ ws.onclose = () => {
168
+ wsOpen = false; deviceReady = false; updateStatus();
169
+ startSim();
170
+ clearTimeout(reconnectTimer);
171
+ reconnectTimer = setTimeout(connect, 1500);
172
+ };
173
+ ws.onerror = () => ws?.close();
174
+ } catch {
175
+ wsOpen = false; startSim();
176
+ clearTimeout(reconnectTimer);
177
+ reconnectTimer = setTimeout(connect, 1500);
178
+ }
179
+ }
180
+
181
+ function triggerGesture(type) {
182
+ if (wsOpen) send({ command: 'trigger_gesture', data: { type } });
183
+ else emit('gesture', { type, confidence: 0.95, timestamp: Date.now() });
184
+ }
185
+
186
+ /* ── Simulation (when Companion not reachable) ─────────────── */
187
+ function startSim() {
188
+ if (simTimer) return;
189
+ simulating = true;
190
+ let t = 0, autoSlideTimer = 0;
191
+ simTimer = setInterval(() => {
192
+ t += 0.05;
193
+ // Simulate pressure
194
+ const n = Math.max(0, Math.min(1, 0.3 + 0.25 * Math.sin(t * 0.5) + (Math.random() - 0.5) * 0.08));
195
+ emit('pressure', { value: Math.round(n * 100), normalized: n, timestamp: Date.now() });
196
+ // Simulate navigation (move laser in circles when laser mode active)
197
+ if (laserMode) {
198
+ emit('navigation', { delta_x: Math.cos(t * 1.2) * 3, delta_y: Math.sin(t * 1.2) * 3, timestamp: Date.now() });
199
+ }
200
+ // Auto-advance slides every ~5s
201
+ autoSlideTimer += 60;
202
+ if (autoSlideTimer >= 5000) {
203
+ autoSlideTimer = 0;
204
+ emit('gesture', { type: 'tap', confidence: 0.85, timestamp: Date.now() });
205
+ }
206
+ // Occasional random gestures
207
+ if (Math.random() < 0.002) {
208
+ const g = ['tap', 'double_tap', 'twist', 'double_twist'];
209
+ emit('gesture', { type: g[Math.floor(Math.random() * g.length)], confidence: 0.7 + Math.random() * 0.3, timestamp: Date.now() });
210
+ }
211
+ }, 60);
212
+ }
213
+
214
+ function stopSim() { clearInterval(simTimer); simTimer = null; simulating = false; }
215
+
216
+ /* ── Status UI ─────────────────────────────────────────────── */
217
+ const dotEl = document.getElementById('dot');
218
+ const connEl = document.getElementById('connLabel');
219
+
220
+ function updateStatus() {
221
+ if (wsOpen && deviceReady) { dotEl.className = 'dot live'; connEl.textContent = 'Connected'; }
222
+ else if (wsOpen) { dotEl.className = 'dot'; connEl.textContent = 'Waiting for Mudra Band\u2026'; }
223
+ else { dotEl.className = 'dot'; connEl.textContent = simulating ? 'Simulated' : 'Disconnected'; }
224
+ }
225
+
226
+ /* ── Slide Logic ───────────────────────────────────────────── */
227
+ const TOTAL = 5;
228
+ let current = 0;
229
+ let laserMode = false;
230
+ let laserX = 50, laserY = 50;
231
+ let pressure = 0;
232
+
233
+ const laserEl = document.getElementById('laser');
234
+ const laserBadge = document.getElementById('laserBadge');
235
+ const pressVal = document.getElementById('pressVal');
236
+ const pressFill = document.getElementById('pressFill');
237
+ const slideCounter = document.getElementById('slideCounter');
238
+ const navDotsEl = document.getElementById('navDots');
239
+
240
+ // Build nav dots
241
+ for (let i = 0; i < TOTAL; i++) {
242
+ const d = document.createElement('div');
243
+ d.className = 'nav-dot' + (i === 0 ? ' active' : '');
244
+ d.id = 'ndot' + i;
245
+ d.addEventListener('click', () => goTo(i));
246
+ navDotsEl.appendChild(d);
247
+ }
248
+
249
+ function goTo(idx) {
250
+ if (idx < 0 || idx >= TOTAL || idx === current) return;
251
+ const dir = idx > current ? 1 : -1;
252
+ document.getElementById('slide' + current).className = 'slide ' + (dir > 0 ? 'left' : 'right');
253
+ document.getElementById('slide' + idx).className = 'slide active';
254
+ document.getElementById('ndot' + current).classList.remove('active');
255
+ document.getElementById('ndot' + idx).classList.add('active');
256
+ current = idx;
257
+ slideCounter.textContent = (current + 1) + ' / ' + TOTAL;
258
+ }
259
+
260
+ function nextSlide() { goTo(current + 1 < TOTAL ? current + 1 : current); }
261
+ function prevSlide() { goTo(current - 1 >= 0 ? current - 1 : current); }
262
+
263
+ /* ── Signal Handlers ───────────────────────────────────────── */
264
+ on('gesture', (d) => {
265
+ const type = d.type || 'unknown';
266
+ if (type === 'tap') nextSlide();
267
+ if (type === 'twist') prevSlide();
268
+ if (type === 'double_tap') {
269
+ laserMode = !laserMode;
270
+ laserEl.style.opacity = laserMode ? '1' : '0';
271
+ laserBadge.classList.toggle('active', laserMode);
272
+ }
273
+ if (type === 'double_twist') goTo(0);
274
+ });
275
+
276
+ on('navigation', (d) => {
277
+ if (!laserMode) return;
278
+ laserX = Math.max(2, Math.min(98, laserX + (d.delta_x || 0) * 0.5));
279
+ laserY = Math.max(2, Math.min(98, laserY + (d.delta_y || 0) * 0.5));
280
+ laserEl.style.left = laserX + '%';
281
+ laserEl.style.top = laserY + '%';
282
+ });
283
+
284
+ on('pressure', (d) => {
285
+ pressure = Math.max(0, Math.min(1, d.normalized ?? d.value / 100));
286
+ pressVal.textContent = Math.round(pressure * 100) + '%';
287
+ pressFill.style.width = Math.round(pressure * 100) + '%';
288
+ // Laser pointer size scales with pressure
289
+ const size = 8 + pressure * 24;
290
+ laserEl.style.width = size + 'px';
291
+ laserEl.style.height = size + 'px';
292
+ });
293
+
294
+ /* ── Keyboard Fallback ─────────────────────────────────────── */
295
+ let kbPressure = 0.3;
296
+ document.addEventListener('keydown', (e) => {
297
+ if (e.code === 'ArrowRight') { e.preventDefault(); triggerGesture('tap'); }
298
+ if (e.code === 'ArrowLeft') { e.preventDefault(); triggerGesture('twist'); }
299
+ if (e.code === 'Space') { e.preventDefault(); triggerGesture('tap'); }
300
+ if (e.code === 'KeyL') {
301
+ triggerGesture('double_tap');
302
+ }
303
+ if (e.key === ']') {
304
+ kbPressure = Math.min(1, kbPressure + 0.08);
305
+ emit('pressure', { value: Math.round(kbPressure * 100), normalized: kbPressure, timestamp: Date.now() });
306
+ }
307
+ if (e.key === '[') {
308
+ kbPressure = Math.max(0, kbPressure - 0.08);
309
+ emit('pressure', { value: Math.round(kbPressure * 100), normalized: kbPressure, timestamp: Date.now() });
310
+ }
311
+ });
312
+
313
+ /* ── Buttons ───────────────────────────────────────────────── */
314
+ document.getElementById('simTap').addEventListener('click', () => triggerGesture('tap'));
315
+ document.getElementById('simTwist').addEventListener('click', () => triggerGesture('twist'));
316
+
317
+ /* ── Mouse/Touch for Laser ─────────────────────────────────── */
318
+ const slideContainer = document.getElementById('slideContainer');
319
+ slideContainer.addEventListener('mousemove', (e) => {
320
+ if (!laserMode) return;
321
+ const r = slideContainer.getBoundingClientRect();
322
+ laserX = ((e.clientX - r.left) / r.width) * 100;
323
+ laserY = ((e.clientY - r.top) / r.height) * 100;
324
+ laserEl.style.left = laserX + '%';
325
+ laserEl.style.top = laserY + '%';
326
+ });
327
+ slideContainer.addEventListener('touchmove', (e) => {
328
+ if (!laserMode) return;
329
+ e.preventDefault();
330
+ const r = slideContainer.getBoundingClientRect();
331
+ laserX = ((e.touches[0].clientX - r.left) / r.width) * 100;
332
+ laserY = ((e.touches[0].clientY - r.top) / r.height) * 100;
333
+ laserEl.style.left = laserX + '%';
334
+ laserEl.style.top = laserY + '%';
335
+ }, { passive: false });
336
+
337
+ /* ── Init ──────────────────────────────────────────────────── */
338
+ laserEl.style.left = '50%';
339
+ laserEl.style.top = '50%';
340
+ laserEl.style.width = '12px';
341
+ laserEl.style.height = '12px';
342
+ connect();
343
+ </script>
344
+ </body>
345
+ </html>
@@ -0,0 +1,285 @@
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>Pressure Painter — 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
+ --canvas-bg: #f8fafc;
18
+ }
19
+
20
+ * { margin: 0; padding: 0; box-sizing: border-box; }
21
+ body { background: var(--bg); color: var(--text); font-family: 'Poppins', system-ui, sans-serif; overflow: hidden; height: 100vh; display: flex; flex-direction: column; }
22
+
23
+ .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; }
24
+ .header-left, .header-right { display: flex; align-items: center; gap: 10px; }
25
+ .brand { font-weight: 700; font-size: 14px; } .brand span { color: var(--primary); }
26
+ .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; }
27
+ .dot.live { background: var(--success); box-shadow: 0 0 0 3px rgba(34,197,94,0.18); }
28
+ .conn-label { font-size: 12px; color: var(--text-secondary); }
29
+
30
+ .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; }
31
+ .btn:hover { border-color: var(--primary); color: var(--primary); }
32
+ .btn.primary-btn { background: var(--primary); color: #072f33; border-color: transparent; font-weight: 600; }
33
+ .btn.primary-btn:hover { opacity: 0.9; }
34
+
35
+ canvas { flex: 1; cursor: crosshair; display: block; background: var(--canvas-bg); }
36
+
37
+ .toolbar { display: flex; align-items: center; justify-content: center; gap: 14px; padding: 10px 16px; background: var(--card); border-top: 1px solid var(--border); z-index: 100; flex-shrink: 0; flex-wrap: wrap; }
38
+ .color-swatch { width: 28px; height: 28px; border-radius: 8px; border: 2px solid var(--border); flex-shrink: 0; }
39
+ .meter { display: flex; align-items: center; gap: 8px; }
40
+ .meter-bar { width: 80px; height: 8px; background: var(--surface); border-radius: 999px; overflow: hidden; border: 1px solid var(--border); }
41
+ .meter-fill { height: 100%; background: linear-gradient(90deg, var(--accent), var(--primary)); border-radius: 999px; transition: width 75ms linear; width: 0%; }
42
+ .meter-val { font-size: 12px; color: var(--text-secondary); min-width: 32px; text-align: right; font-variant-numeric: tabular-nums; }
43
+ .hint { font-size: 11px; color: var(--text-secondary); opacity: 0.6; }
44
+
45
+ .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; }
46
+
47
+ @media (max-width: 640px) {
48
+ .header { padding: 8px 10px; } .toolbar { gap: 8px; padding: 8px 10px; }
49
+ .meter-bar { width: 50px; } .hint { display: none; }
50
+ }
51
+ </style>
52
+ </head>
53
+ <body>
54
+
55
+ <div class="header">
56
+ <div class="header-left">
57
+ <div class="brand"><span>Mudra</span> Pressure Painter</div>
58
+ <span class="dot" id="dot"></span>
59
+ <span class="conn-label" id="connLabel">Connecting&hellip;</span>
60
+ </div>
61
+ <div class="header-right">
62
+ <a href="/gallery" class="btn" style="text-decoration:none;">&#8592; Gallery</a>
63
+ <button class="btn" id="simTap">Simulate Tap</button>
64
+ </div>
65
+ </div>
66
+
67
+ <canvas id="canvas"></canvas>
68
+
69
+ <div class="toolbar">
70
+ <button class="btn" id="clearBtn">Clear</button>
71
+ <div class="color-swatch" id="swatch"></div>
72
+ <div class="meter">
73
+ <span class="meter-val" id="pressVal">0%</span>
74
+ <div class="meter-bar"><div class="meter-fill" id="pressFill"></div></div>
75
+ </div>
76
+ <span class="hint">[ ] pressure &middot; Space tap &middot; D clear</span>
77
+ </div>
78
+
79
+ <div class="mudra-badge">Created by Mudra</div>
80
+
81
+ <script>
82
+ /* ── Mudra Protocol Connection (v2) ────────────────────────── */
83
+ const WS_URL = 'ws://127.0.0.1:8766';
84
+ const SIGNALS = ['pressure', 'gesture', 'snc'];
85
+
86
+ let ws, reconnectTimer, simTimer;
87
+ let wsOpen = false, deviceReady = false, simulating = true;
88
+ const bus = {};
89
+
90
+ function on(ev, fn) { (bus[ev] ??= []).push(fn); }
91
+ function emit(ev, d) { (bus[ev] || []).forEach(fn => fn(d)); }
92
+ function send(p) { if (ws?.readyState === WebSocket.OPEN) ws.send(JSON.stringify(p)); }
93
+
94
+ function connect() {
95
+ try {
96
+ ws = new WebSocket(WS_URL);
97
+ ws.onopen = () => {
98
+ wsOpen = true; stopSim(); updateStatus();
99
+ SIGNALS.forEach(s => send({ command: 'subscribe', signal: s }));
100
+ };
101
+ ws.onmessage = (e) => {
102
+ try {
103
+ const msg = JSON.parse(e.data);
104
+ if (msg.type === 'connection_status') {
105
+ deviceReady = msg.data?.status === 'connected';
106
+ updateStatus(); return;
107
+ }
108
+ emit(msg.type, msg.data);
109
+ } catch {}
110
+ };
111
+ ws.onclose = () => {
112
+ wsOpen = false; deviceReady = false; updateStatus();
113
+ startSim();
114
+ clearTimeout(reconnectTimer);
115
+ reconnectTimer = setTimeout(connect, 1500);
116
+ };
117
+ ws.onerror = () => ws?.close();
118
+ } catch {
119
+ wsOpen = false; startSim();
120
+ clearTimeout(reconnectTimer);
121
+ reconnectTimer = setTimeout(connect, 1500);
122
+ }
123
+ }
124
+
125
+ function triggerGesture(type) {
126
+ if (wsOpen) send({ command: 'trigger_gesture', data: { type } });
127
+ else emit('gesture', { type, confidence: 0.95, timestamp: Date.now() });
128
+ }
129
+
130
+ /* ── Simulation (when Companion not reachable) ─────────────── */
131
+ function startSim() {
132
+ if (simTimer) return;
133
+ simulating = true;
134
+ let t = 0;
135
+ simTimer = setInterval(() => {
136
+ t += 0.05;
137
+ const n = Math.max(0, Math.min(1, 0.3 + 0.25 * Math.sin(t * 0.7) + (Math.random() - 0.5) * 0.1));
138
+ emit('pressure', { value: Math.round(n * 100), normalized: n, timestamp: Date.now() });
139
+ emit('snc', {
140
+ values: [
141
+ 0.3 * Math.sin(t * 1.3) + (Math.random() - 0.5) * 0.1,
142
+ 0.2 * Math.cos(t * 1.7) + (Math.random() - 0.5) * 0.1,
143
+ 0.15 * Math.sin(t * 2.1) + (Math.random() - 0.5) * 0.08
144
+ ],
145
+ frequency: 1000, timestamp: Date.now()
146
+ });
147
+ if (Math.random() < 0.004) {
148
+ const g = ['tap', 'double_tap', 'twist', 'double_twist'];
149
+ emit('gesture', { type: g[Math.floor(Math.random() * g.length)], confidence: 0.7 + Math.random() * 0.3, timestamp: Date.now() });
150
+ }
151
+ }, 60);
152
+ }
153
+
154
+ function stopSim() { clearInterval(simTimer); simTimer = null; simulating = false; }
155
+
156
+ /* ── Status UI ─────────────────────────────────────────────── */
157
+ const dotEl = document.getElementById('dot');
158
+ const connEl = document.getElementById('connLabel');
159
+
160
+ function updateStatus() {
161
+ if (wsOpen && deviceReady) { dotEl.className = 'dot live'; connEl.textContent = 'Connected'; }
162
+ else if (wsOpen) { dotEl.className = 'dot'; connEl.textContent = 'Waiting for Mudra Band\u2026'; }
163
+ else { dotEl.className = 'dot'; connEl.textContent = simulating ? 'Simulated' : 'Disconnected'; }
164
+ }
165
+
166
+ /* ── Canvas Setup ──────────────────────────────────────────── */
167
+ const canvas = document.getElementById('canvas');
168
+ const ctx = canvas.getContext('2d');
169
+ const swatch = document.getElementById('swatch');
170
+ const pressVal = document.getElementById('pressVal');
171
+ const pressFill = document.getElementById('pressFill');
172
+
173
+ let hue = 179, pressure = 0;
174
+ let cursorX = 0, cursorY = 0, mouseActive = false;
175
+ let simTime = 0;
176
+ const points = [];
177
+
178
+ function resize() {
179
+ const saved = ctx.getImageData(0, 0, canvas.width, canvas.height);
180
+ const r = canvas.getBoundingClientRect();
181
+ canvas.width = r.width * devicePixelRatio;
182
+ canvas.height = r.height * devicePixelRatio;
183
+ ctx.setTransform(devicePixelRatio, 0, 0, devicePixelRatio, 0, 0);
184
+ ctx.putImageData(saved, 0, 0);
185
+ }
186
+ resize();
187
+ window.addEventListener('resize', resize);
188
+
189
+ canvas.addEventListener('mousemove', (e) => {
190
+ const r = canvas.getBoundingClientRect();
191
+ cursorX = e.clientX - r.left; cursorY = e.clientY - r.top;
192
+ mouseActive = true;
193
+ });
194
+ canvas.addEventListener('mouseleave', () => { mouseActive = false; });
195
+ canvas.addEventListener('touchmove', (e) => {
196
+ e.preventDefault();
197
+ const r = canvas.getBoundingClientRect();
198
+ cursorX = e.touches[0].clientX - r.left; cursorY = e.touches[0].clientY - r.top;
199
+ mouseActive = true;
200
+ }, { passive: false });
201
+
202
+ /* ── Signal Handlers ───────────────────────────────────────── */
203
+ on('pressure', (d) => {
204
+ pressure = Math.max(0, Math.min(1, d.normalized ?? d.value / 100));
205
+ pressVal.textContent = Math.round(pressure * 100) + '%';
206
+ pressFill.style.width = Math.round(pressure * 100) + '%';
207
+ });
208
+
209
+ on('snc', (d) => {
210
+ const vals = d.values || [0, 0, 0];
211
+ const energy = (Math.abs(vals[0]) + Math.abs(vals[1]) + Math.abs(vals[2])) / 3;
212
+ hue = Math.round((energy * 720) % 360);
213
+ swatch.style.background = `hsl(${hue}, 80%, 55%)`;
214
+ });
215
+
216
+ on('gesture', (d) => {
217
+ const type = d.type || 'unknown';
218
+ if (type === 'tap') hue = Math.floor(Math.random() * 360);
219
+ if (type === 'double_tap') {
220
+ points.length = 0;
221
+ ctx.clearRect(0, 0, canvas.width / devicePixelRatio, canvas.height / devicePixelRatio);
222
+ }
223
+ swatch.style.background = `hsl(${hue}, 80%, 55%)`;
224
+ });
225
+
226
+ /* ── Keyboard Fallback ─────────────────────────────────────── */
227
+ let kbPressure = 0.3;
228
+ document.addEventListener('keydown', (e) => {
229
+ if (e.code === 'Space') { e.preventDefault(); triggerGesture('tap'); }
230
+ if (e.code === 'KeyD') triggerGesture('double_tap');
231
+ if (e.key === ']') {
232
+ kbPressure = Math.min(1, kbPressure + 0.08);
233
+ emit('pressure', { value: Math.round(kbPressure * 100), normalized: kbPressure, timestamp: Date.now() });
234
+ }
235
+ if (e.key === '[') {
236
+ kbPressure = Math.max(0, kbPressure - 0.08);
237
+ emit('pressure', { value: Math.round(kbPressure * 100), normalized: kbPressure, timestamp: Date.now() });
238
+ }
239
+ });
240
+
241
+ /* ── Buttons ───────────────────────────────────────────────── */
242
+ document.getElementById('simTap').addEventListener('click', () => triggerGesture('tap'));
243
+ document.getElementById('clearBtn').addEventListener('click', () => {
244
+ points.length = 0;
245
+ ctx.clearRect(0, 0, canvas.width / devicePixelRatio, canvas.height / devicePixelRatio);
246
+ });
247
+
248
+ /* ── Paint Loop ────────────────────────────────────────────── */
249
+ function paintLoop() {
250
+ simTime += 0.02;
251
+ const r = canvas.getBoundingClientRect();
252
+ const w = r.width, h = r.height;
253
+
254
+ let x = cursorX, y = cursorY;
255
+ if (!mouseActive) {
256
+ x = w / 2 + Math.sin(simTime * 0.5) * w * 0.3 + Math.cos(simTime * 0.8) * w * 0.1;
257
+ y = h / 2 + Math.cos(simTime * 0.4) * h * 0.25 + Math.sin(simTime * 0.7) * h * 0.1;
258
+ }
259
+
260
+ const size = 2 + pressure * 38;
261
+ const alpha = 0.2 + pressure * 0.8;
262
+ points.push({ x, y, size, hue, alpha });
263
+ if (points.length > 8000) points.splice(0, 100);
264
+
265
+ if (points.length >= 2) {
266
+ const p0 = points[points.length - 2], p1 = points[points.length - 1];
267
+ ctx.beginPath();
268
+ ctx.moveTo(p0.x, p0.y);
269
+ ctx.lineTo(p1.x, p1.y);
270
+ ctx.strokeStyle = `hsla(${p1.hue}, 80%, 55%, ${p1.alpha})`;
271
+ ctx.lineWidth = p1.size;
272
+ ctx.lineCap = 'round';
273
+ ctx.lineJoin = 'round';
274
+ ctx.stroke();
275
+ }
276
+
277
+ requestAnimationFrame(paintLoop);
278
+ }
279
+
280
+ swatch.style.background = `hsl(${hue}, 80%, 55%)`;
281
+ connect();
282
+ paintLoop();
283
+ </script>
284
+ </body>
285
+ </html>