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,151 @@
1
+ ---
2
+ name: mudra-preview
3
+ version: 2.2.0
4
+ description: Generate a working Mudra Band interactive app preview as a single-file HTML. Use when the user describes a Mudra-controlled experience (gesture, pressure, navigation, IMU, SNC), wants to prototype a Mudra Companion app, or asks to build/preview a Mudra app.
5
+ ---
6
+
7
+ # Mudra Preview
8
+
9
+ Generate a complete, working single-file HTML app controlled by Mudra Band signals.
10
+
11
+ **Mandatory feature (v1.1.0, updated v1.4.0):** Every generated app MUST
12
+ include a Manual/Mudra **Mode toggle** as defined in `references/promt.md`
13
+ § "Mode Toggle (Manual / Mudra) — Required". Manual is the default; Mudra
14
+ opens a single WebSocket lazily and disables the simulator panel so signals
15
+ come only from the band.
16
+
17
+ **Removed in v1.4.0:** Do **not** render a "Band disconnected" overlay,
18
+ toast, or any other separate disconnect alert. Connection state is
19
+ communicated **only** through the existing connection-status pill (which
20
+ shows "Connecting…" / "Connected" / "Disconnected"). No extra notice,
21
+ no banner, no modal.
22
+
23
+ **Branding (v1.4.0):** The footer/badge text MUST be exactly
24
+ **"Created by Mudra"** — never "Created with Mudra Studio" or any
25
+ other variant.
26
+
27
+ **Mandatory feature (v1.2.0):** Connection state MUST reflect the **band**, not
28
+ the WebSocket. The Companion service accepts socket connections even when no
29
+ band is paired, so flipping to "Connected" on `ws.onopen` is a lie. Every
30
+ generated app MUST send `{command:"get_status"}` on open and poll it every
31
+ 2 s while in Mudra mode, and only show "Connected" when the response has
32
+ `data.device.state === "connected"`. See `references/promt.md` §
33
+ "Disconnect detection — band state via `get_status` polling (mandatory)".
34
+
35
+ The canonical protocol is in `references/agent_protocol.json` (v2.0).
36
+
37
+ ## Steps
38
+
39
+ 1. **Read the full instructions** from `references/promt.md` inside the skill base directory. That file contains the complete protocol contract, signal compatibility rules, the Mode Toggle architecture (mandatory), build defaults, and sample catalog — follow all of it.
40
+
41
+ 2. **Infer intent** from the user's description (or the args passed to this skill). Fill gaps with smart defaults. Ask only if there is genuine ambiguity (e.g., `gesture` vs `pressure`, `navigation` vs `nav_direction`, or directional motion vs IMU+Biometric bundle).
42
+
43
+ 3. **Select the best-matching template** from `assets/` inside the skill base directory. Use the selection rule from `references/promt.md` (motion mode → interaction pattern → signal overlap).
44
+
45
+ 4. **Generate the app** as a single self-contained HTML file:
46
+ - Follow every rule in `references/promt.md` (protocol contract, signal compatibility, mock WebSocket, theme, fallbacks)
47
+ - Choose a color palette that matches the concept — never default to dark unless the concept calls for it
48
+ - Include the Mudra badge, connection indicator, telemetry HUD, simulator panel (see below), and keyboard fallbacks
49
+
50
+ ### Simulator Panel (Required)
51
+
52
+ Every generated app MUST include a **compact, always-visible simulator panel** with one button set per subscribed signal. The user must be able to trigger every signal with a click — so they can test without the band, or alongside it.
53
+
54
+ **Layout rules**
55
+ - One thin horizontal strip (usually in or just below the header, or pinned to the bottom edge).
56
+ - **Always visible** — do NOT hide when the band is connected.
57
+ - Group by signal. Use short labels so it stays compact (e.g., `Tap`, `2Tap`, `Twist`, `↑`, `↓`, `Roll L`).
58
+ - Reuse the existing `.btn` style from the chosen template.
59
+ - No drawers, no collapse, no accordion — one click to fire.
60
+
61
+ **Buttons per signal** (only render the groups for signals the app actually subscribes to)
62
+
63
+ | Signal | Buttons |
64
+ |---|---|
65
+ | `gesture` | `Tap`, `2Tap`, `Twist`, `2Twist` |
66
+ | `nav_direction` | `↑`, `↓`, `←`, `→`, `Roll L`, `Roll R` |
67
+ | `navigation` | `↑`, `↓`, `←`, `→` (each click emits one delta event of ±8) |
68
+ | `pressure` | Slider `0–100%` (or `−` / `+` buttons if horizontal space is tight) |
69
+ | `button` | `Press`, `Release` |
70
+ | `imu_acc` | `Tilt X`, `Tilt Y`, `Tilt Z` (each fires a 5-frame burst at ±2 m/s²) |
71
+ | `imu_gyro` | `Rot X`, `Rot Y`, `Rot Z` (each fires a 5-frame burst at ±10 deg/s) |
72
+ | `snc` | `Spike` (injects a burst of elevated samples on all 3 channels) |
73
+
74
+ **How each button must fire**
75
+ - `gesture` buttons → `ws.send(JSON.stringify({ command: 'trigger_gesture', data: { type: '<name>' } }))`. This works both when connected to the real band (round-trips through the service) and when the mock is active (mock echoes it back).
76
+ - **All other signals** → emit locally by calling the same handler `ws.onmessage` would dispatch. Example:
77
+ ```js
78
+ function simDir(direction) {
79
+ // same path the real signal would take
80
+ handleNavDirection({ direction, timestamp: Date.now() });
81
+ hudDir.textContent = direction;
82
+ }
83
+ ```
84
+ Always update the telemetry HUD from the sim button, same as the real signal would.
85
+
86
+ 5. **Save the output** to `preview/<concept-name>.html` in the project root (create the `preview/` directory if it doesn't exist). Use a short kebab-case name derived from the concept (e.g., `preview/drum-machine.html`).
87
+
88
+ 6. **Report the file path** so the user can open it in a browser immediately.
89
+
90
+ ## Default Signal Set (v1.3.0 — Required)
91
+
92
+ Unless the user explicitly asks for a different signal, every generated
93
+ app MUST restrict itself to **at most these signals** (subject to the
94
+ exclusivity rules below):
95
+
96
+ 1. **One** of `pressure` **or** `gesture` — never both. `gesture` and
97
+ `pressure` are mutually exclusive.
98
+ - **Tap exclusivity rule** (within `gesture`): use `tap` OR `double_tap`
99
+ — **never both together** unless the user explicitly names both (e.g.,
100
+ "use single tap for X and double tap for Y").
101
+ - `tap` → frequent/lightweight: counter increment, navigation step,
102
+ toggle, next/back, select, trigger. **Default choice.**
103
+ - `double_tap` → only when the user explicitly requests it by name.
104
+ Never pair it with `tap` as a default two-action scheme.
105
+ - Generic synonyms ("tap", "click", "press") → `tap`.
106
+ - When an app needs **two distinct gesture actions** (e.g., forward +
107
+ backward), pair `tap` with `twist` — not with `double_tap`.
108
+ `double_tap` is reserved for explicit user requests only.
109
+ 2. **One** directional signal — either `nav_direction` **or**
110
+ `navigation`, never both.
111
+
112
+ Rules:
113
+
114
+ - Drop any of the above if the concept does not need it (e.g., a pure
115
+ tap-counter subscribes to `gesture` only and skips the directional
116
+ signal).
117
+ - **`gesture` and `pressure` are mutually exclusive** — pick one
118
+ interaction model per app. Tap/twist concepts → `gesture`. Analog
119
+ concepts (volume, brush, throttle) → `pressure`.
120
+ - `nav_direction` and `navigation` are **mutually exclusive per app** —
121
+ pick the one that fits the interaction (discrete swipes →
122
+ `nav_direction`; continuous cursor/scroll → `navigation`). Never wire
123
+ both into the same app.
124
+ - **IMU+Biometric bundle (`imu_acc` + `imu_gyro` + `snc`) is
125
+ inseparable.** If the concept needs any one of them, subscribe to all
126
+ three. The bundle is **mutually exclusive** with `navigation` and
127
+ `nav_direction` — pick directional motion OR the IMU+Biometric bundle,
128
+ never both.
129
+ - Other gesture subtypes (`twist`, `double_twist`, etc.) and other
130
+ signals (`button`, `imu_acc`, `imu_gyro`, `snc`, `battery`) are
131
+ **off by default**. Only include them when the user's prompt names
132
+ them, names a synonym from the Signal Inference table in
133
+ `references/promt.md` § "Signal Inference Reference", or describes
134
+ an interaction that genuinely cannot be expressed with the defaults
135
+ (e.g., "tilt to steer" → IMU+Biometric bundle; "hold to charge" →
136
+ `button`).
137
+ - The simulator panel must mirror whichever subset the app actually
138
+ subscribes to — do not render buttons for signals that are not wired.
139
+
140
+ ## Quick Reference
141
+
142
+ - WebSocket endpoint: `ws://127.0.0.1:8766` (bare URL — NOT `/events`)
143
+ - Mode toggle (Manual / Mudra) is **mandatory** in every generated app — see `references/promt.md` § "Mode Toggle (Manual / Mudra) — Required"
144
+ - Lazy WS lifecycle: open on Manual→Mudra, close on Mudra→Manual. Manual mode opens NO WebSocket.
145
+ - Subscribe one signal per command: `{ "command": "subscribe", "signal": "<name>" }` — singular `signal`, never `signals`, never an array
146
+ - Motion modes are mutually exclusive: Pointer (`navigation`+`button`) / Direction (`nav_direction`) / IMU+Biometric (`imu_acc`+`imu_gyro`+`snc`, always all three together)
147
+ - IMU+Biometric bundle: `imu_acc`, `imu_gyro`, `snc` always subscribed together — never partially. The bundle is mutually exclusive with `navigation` and `nav_direction`.
148
+ - `gesture` and `pressure` are mutually exclusive — never combine them
149
+ - `button` and `battery` combine freely (subject to the Pointer XOR — `button` belongs to Pointer mode and never combines with `nav_direction`)
150
+ - **Navigation sensitivity is gentle by default**: keyboard `step = 3`, sim button `±3`, cursor multiplier `0.002`. Raise only when the prompt explicitly asks for fast/snappy movement. See `references/promt.md` § "Navigation sensitivity defaults".
151
+ - Canonical protocol JSON: `references/agent_protocol.json` (v2.0)
@@ -0,0 +1,382 @@
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>AR Menu Navigator — 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
+ --glass: rgba(241,245,249,0.75); --glass-border: rgba(13,148,136,0.2);
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: 200; 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
+ .ar-viewport { flex: 1; position: relative; overflow: hidden; display: flex; align-items: center; justify-content: center; background: var(--bg);
34
+ background-image:
35
+ linear-gradient(rgba(119,234,233,0.03) 1px, transparent 1px),
36
+ linear-gradient(90deg, rgba(119,234,233,0.03) 1px, transparent 1px);
37
+ background-size: 40px 40px;
38
+ }
39
+
40
+ .breadcrumb { position: absolute; top: 16px; left: 16px; display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-secondary); z-index: 10; }
41
+ .bc-item { cursor: pointer; } .bc-item:hover { color: var(--primary); }
42
+ .bc-sep { opacity: 0.4; }
43
+ .bc-current { color: var(--primary); font-weight: 600; }
44
+
45
+ .pressure-meter { position: absolute; top: 16px; right: 16px; z-index: 10; display: flex; align-items: center; gap: 8px; }
46
+ .pm-label { font-size: 10px; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.06em; }
47
+ .pm-bar { width: 70px; height: 5px; background: var(--surface); border-radius: 999px; overflow: hidden; border: 1px solid var(--border); }
48
+ .pm-fill { height: 100%; background: linear-gradient(90deg, var(--accent), var(--primary)); border-radius: 999px; transition: width 80ms; width: 0%; }
49
+ .pm-val { font-size: 11px; color: var(--text-secondary); font-variant-numeric: tabular-nums; min-width: 28px; text-align: right; }
50
+
51
+ .menu-container { transition: transform 300ms cubic-bezier(0.25,0.46,0.45,0.94); }
52
+ .menu-panel { display: flex; flex-direction: column; gap: 6px; min-width: 220px; }
53
+ .menu-item { padding: 14px 18px; border-radius: 12px; background: var(--glass); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); border: 1px solid var(--glass-border); font-size: 14px; font-weight: 500; color: var(--text); cursor: default; transition: all 200ms; display: flex; align-items: center; gap: 10px; }
54
+ .mi-emoji { font-size: 18px; }
55
+ .mi-label { flex: 1; }
56
+ .mi-arrow { font-size: 12px; color: var(--text-secondary); opacity: 0.5; }
57
+ .menu-item.highlighted { border-color: var(--primary); background: rgba(119,234,233,0.08); box-shadow: 0 0 20px rgba(119,234,233,0.1); }
58
+ .menu-item.highlighted .mi-label { color: var(--primary); }
59
+
60
+ .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: 200; flex-shrink: 0; flex-wrap: wrap; }
61
+ .hint { font-size: 11px; color: var(--text-secondary); opacity: 0.6; }
62
+
63
+ .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: 300; }
64
+
65
+ @media (max-width: 640px) {
66
+ .header { padding: 8px 10px; } .toolbar { gap: 8px; padding: 8px 10px; }
67
+ .menu-panel { min-width: 170px; }
68
+ .menu-item { padding: 10px 14px; font-size: 13px; }
69
+ .hint { display: none; }
70
+ .pressure-meter { display: none; }
71
+ }
72
+ </style>
73
+ </head>
74
+ <body>
75
+
76
+ <div class="header">
77
+ <div class="header-left">
78
+ <div class="brand"><span>Mudra</span> AR Menu Navigator</div>
79
+ <span class="dot" id="dot"></span>
80
+ <span class="conn-label" id="connLabel">Connecting&hellip;</span>
81
+ </div>
82
+ <div class="header-right">
83
+ <a href="/gallery" class="btn" style="text-decoration:none;">&#8592; Gallery</a>
84
+ <button class="btn" id="simTap">Simulate Tap</button>
85
+ <button class="btn" id="simTwist">Simulate Twist</button>
86
+ </div>
87
+ </div>
88
+
89
+ <div class="ar-viewport" id="viewport">
90
+ <div class="breadcrumb" id="breadcrumb"></div>
91
+ <div class="pressure-meter">
92
+ <span class="pm-label">Scale</span>
93
+ <div class="pm-bar"><div class="pm-fill" id="pressFill"></div></div>
94
+ <span class="pm-val" id="pressVal">0%</span>
95
+ </div>
96
+ <div class="menu-container" id="menuContainer"></div>
97
+ </div>
98
+
99
+ <div class="toolbar">
100
+ <button class="btn" id="selectBtn">Select</button>
101
+ <button class="btn" id="backBtn">Back</button>
102
+ <button class="btn" id="homeBtn">Home</button>
103
+ <span class="hint">Arrows navigate &middot; Space select &middot; Backspace back &middot; [ ] pressure</span>
104
+ </div>
105
+
106
+ <div class="mudra-badge">Created by Mudra</div>
107
+
108
+ <script>
109
+ /* ── Mudra Protocol Connection (v2) ────────────────────────── */
110
+ const WS_URL = 'ws://127.0.0.1:8766';
111
+ const SIGNALS = ['navigation', 'gesture', 'pressure'];
112
+
113
+ let ws, reconnectTimer, simTimer;
114
+ let wsOpen = false, deviceReady = false, simulating = true;
115
+ const bus = {};
116
+
117
+ function on(ev, fn) { (bus[ev] ??= []).push(fn); }
118
+ function emit(ev, d) { (bus[ev] || []).forEach(fn => fn(d)); }
119
+ function send(p) { if (ws?.readyState === WebSocket.OPEN) ws.send(JSON.stringify(p)); }
120
+
121
+ function connect() {
122
+ try {
123
+ ws = new WebSocket(WS_URL);
124
+ ws.onopen = () => {
125
+ wsOpen = true; stopSim(); updateStatus();
126
+ SIGNALS.forEach(s => send({ command: 'subscribe', signal: s }));
127
+ };
128
+ ws.onmessage = (e) => {
129
+ try {
130
+ const msg = JSON.parse(e.data);
131
+ if (msg.type === 'connection_status') {
132
+ deviceReady = msg.data?.status === 'connected';
133
+ updateStatus(); return;
134
+ }
135
+ emit(msg.type, msg.data);
136
+ } catch {}
137
+ };
138
+ ws.onclose = () => {
139
+ wsOpen = false; deviceReady = false; updateStatus();
140
+ startSim();
141
+ clearTimeout(reconnectTimer);
142
+ reconnectTimer = setTimeout(connect, 1500);
143
+ };
144
+ ws.onerror = () => ws?.close();
145
+ } catch {
146
+ wsOpen = false; startSim();
147
+ clearTimeout(reconnectTimer);
148
+ reconnectTimer = setTimeout(connect, 1500);
149
+ }
150
+ }
151
+
152
+ function triggerGesture(type) {
153
+ if (wsOpen) send({ command: 'trigger_gesture', data: { type } });
154
+ else emit('gesture', { type, confidence: 0.95, timestamp: Date.now() });
155
+ }
156
+
157
+ /* ── Simulation (when Companion not reachable) ─────────────── */
158
+ function startSim() {
159
+ if (simTimer) return;
160
+ simulating = true;
161
+ let t = 0, navAcc = 0;
162
+ simTimer = setInterval(() => {
163
+ t += 0.05;
164
+ navAcc += 0.02;
165
+ if (navAcc > 1) {
166
+ navAcc = 0;
167
+ emit('navigation', { delta_x: 0, delta_y: 6, timestamp: Date.now() });
168
+ }
169
+ const n = 0.3 + 0.25 * Math.sin(t * 0.4) + (Math.random() - 0.5) * 0.05;
170
+ emit('pressure', { value: Math.round(n * 100), normalized: Math.max(0, Math.min(1, n)), timestamp: Date.now() });
171
+ if (Math.random() < 0.004) {
172
+ const g = ['tap', 'double_tap', 'twist', 'double_twist'];
173
+ emit('gesture', { type: g[Math.floor(Math.random() * g.length)], confidence: 0.7 + Math.random() * 0.3, timestamp: Date.now() });
174
+ }
175
+ }, 60);
176
+ }
177
+
178
+ function stopSim() { clearInterval(simTimer); simTimer = null; simulating = false; }
179
+
180
+ /* ── Status UI ─────────────────────────────────────────────── */
181
+ const dotEl = document.getElementById('dot');
182
+ const connEl = document.getElementById('connLabel');
183
+
184
+ function updateStatus() {
185
+ if (wsOpen && deviceReady) { dotEl.className = 'dot live'; connEl.textContent = 'Connected'; }
186
+ else if (wsOpen) { dotEl.className = 'dot'; connEl.textContent = 'Waiting for Mudra Band\u2026'; }
187
+ else { dotEl.className = 'dot'; connEl.textContent = simulating ? 'Simulated' : 'Disconnected'; }
188
+ }
189
+
190
+ /* ── Menu Data ─────────────────────────────────────────────── */
191
+ const menuTree = {
192
+ label: 'Home',
193
+ items: [
194
+ { label: 'Maps', emoji: '\u{1F5FA}\uFE0F', children: [
195
+ { label: 'Navigate' }, { label: 'Search Places' }, { label: 'Saved Locations' }, { label: 'Traffic' }
196
+ ]},
197
+ { label: 'Music', emoji: '\u{1F3B5}', children: [
198
+ { label: 'Now Playing' }, { label: 'Playlists' }, { label: 'Search' }
199
+ ]},
200
+ { label: 'Messages', emoji: '\u{1F4AC}', children: [
201
+ { label: 'Inbox' }, { label: 'Compose' }, { label: 'Starred' }, { label: 'Archive' }
202
+ ]},
203
+ { label: 'Camera', emoji: '\u{1F4F7}', children: [
204
+ { label: 'Photo' }, { label: 'Video' }, { label: 'Gallery' }
205
+ ]},
206
+ { label: 'Settings', emoji: '\u2699\uFE0F', children: [
207
+ { label: 'Display' }, { label: 'Gestures' }, { label: 'Connectivity' }, { label: 'About' }
208
+ ]},
209
+ ]
210
+ };
211
+
212
+ /* ── Menu State ────────────────────────────────────────────── */
213
+ let menuPath = [];
214
+ let currentItems = menuTree.items;
215
+ let highlightIdx = 0;
216
+ let pressure = 0.3;
217
+ let navAccY = 0;
218
+
219
+ const containerEl = document.getElementById('menuContainer');
220
+ const breadcrumbEl = document.getElementById('breadcrumb');
221
+ const pressFillEl = document.getElementById('pressFill');
222
+ const pressValEl = document.getElementById('pressVal');
223
+
224
+ function renderMenu() {
225
+ /* Clear old panel */
226
+ while (containerEl.firstChild) containerEl.removeChild(containerEl.firstChild);
227
+
228
+ const panel = document.createElement('div');
229
+ panel.className = 'menu-panel';
230
+
231
+ currentItems.forEach((item, i) => {
232
+ const el = document.createElement('div');
233
+ el.className = 'menu-item' + (i === highlightIdx ? ' highlighted' : '');
234
+
235
+ const emo = document.createElement('span');
236
+ emo.className = 'mi-emoji';
237
+ emo.textContent = item.emoji || '\u2022';
238
+
239
+ const lab = document.createElement('span');
240
+ lab.className = 'mi-label';
241
+ lab.textContent = item.label;
242
+
243
+ el.appendChild(emo);
244
+ el.appendChild(lab);
245
+
246
+ if (item.children) {
247
+ const arr = document.createElement('span');
248
+ arr.className = 'mi-arrow';
249
+ arr.textContent = '\u25B6';
250
+ el.appendChild(arr);
251
+ }
252
+
253
+ el.addEventListener('click', () => { highlightIdx = i; selectItem(); });
254
+ panel.appendChild(el);
255
+ });
256
+
257
+ containerEl.appendChild(panel);
258
+ applyScale();
259
+ renderBreadcrumb();
260
+ }
261
+
262
+ function renderBreadcrumb() {
263
+ while (breadcrumbEl.firstChild) breadcrumbEl.removeChild(breadcrumbEl.firstChild);
264
+
265
+ const home = document.createElement('span');
266
+ home.className = 'bc-item';
267
+ home.textContent = 'Home';
268
+ home.addEventListener('click', goHome);
269
+ breadcrumbEl.appendChild(home);
270
+
271
+ menuPath.forEach((entry, i) => {
272
+ const sep = document.createElement('span');
273
+ sep.className = 'bc-sep';
274
+ sep.textContent = ' \u203A ';
275
+ breadcrumbEl.appendChild(sep);
276
+
277
+ const crumb = document.createElement('span');
278
+ crumb.textContent = entry.node.label;
279
+ if (i === menuPath.length - 1) {
280
+ crumb.className = 'bc-current';
281
+ } else {
282
+ crumb.className = 'bc-item';
283
+ crumb.addEventListener('click', () => {
284
+ menuPath = menuPath.slice(0, i + 1);
285
+ currentItems = menuPath[menuPath.length - 1].node.children;
286
+ highlightIdx = 0;
287
+ renderMenu();
288
+ });
289
+ }
290
+ breadcrumbEl.appendChild(crumb);
291
+ });
292
+ }
293
+
294
+ function applyScale() {
295
+ const scale = 0.75 + pressure * 0.5;
296
+ containerEl.style.transform = 'scale(' + scale + ')';
297
+ }
298
+
299
+ function moveHighlight(dir) {
300
+ highlightIdx = (highlightIdx + dir + currentItems.length) % currentItems.length;
301
+ const items = containerEl.querySelectorAll('.menu-item');
302
+ items.forEach((el, i) => el.classList.toggle('highlighted', i === highlightIdx));
303
+ }
304
+
305
+ function selectItem() {
306
+ const item = currentItems[highlightIdx];
307
+ if (item && item.children) {
308
+ menuPath.push({ node: item, selectedIdx: highlightIdx });
309
+ currentItems = item.children;
310
+ highlightIdx = 0;
311
+ renderMenu();
312
+ }
313
+ }
314
+
315
+ function goBack() {
316
+ if (menuPath.length === 0) return;
317
+ const prev = menuPath.pop();
318
+ currentItems = menuPath.length === 0 ? menuTree.items : menuPath[menuPath.length - 1].node.children;
319
+ highlightIdx = prev.selectedIdx;
320
+ renderMenu();
321
+ }
322
+
323
+ function goHome() {
324
+ menuPath = [];
325
+ currentItems = menuTree.items;
326
+ highlightIdx = 0;
327
+ renderMenu();
328
+ }
329
+
330
+ /* ── Signal Handlers ───────────────────────────────────────── */
331
+ on('navigation', (d) => {
332
+ navAccY += (d.delta_y || 0);
333
+ if (Math.abs(navAccY) > 12) {
334
+ moveHighlight(navAccY > 0 ? 1 : -1);
335
+ navAccY = 0;
336
+ }
337
+ });
338
+
339
+ on('gesture', (d) => {
340
+ const t = d.type || '';
341
+ if (t === 'tap') selectItem();
342
+ if (t === 'twist') goBack();
343
+ if (t === 'double_tap') goHome();
344
+ });
345
+
346
+ on('pressure', (d) => {
347
+ pressure = Math.max(0, Math.min(1, d.normalized ?? d.value / 100));
348
+ pressFillEl.style.width = Math.round(pressure * 100) + '%';
349
+ pressValEl.textContent = Math.round(pressure * 100) + '%';
350
+ applyScale();
351
+ });
352
+
353
+ /* ── Keyboard Fallback ─────────────────────────────────────── */
354
+ let kbPressure = 0.3;
355
+ document.addEventListener('keydown', (e) => {
356
+ if (e.code === 'ArrowDown') moveHighlight(1);
357
+ if (e.code === 'ArrowUp') moveHighlight(-1);
358
+ if (e.code === 'ArrowRight' || e.code === 'Space') { e.preventDefault(); selectItem(); }
359
+ if (e.code === 'ArrowLeft' || e.code === 'Backspace') { e.preventDefault(); goBack(); }
360
+ if (e.key === ']') {
361
+ kbPressure = Math.min(1, kbPressure + 0.08);
362
+ emit('pressure', { value: Math.round(kbPressure * 100), normalized: kbPressure, timestamp: Date.now() });
363
+ }
364
+ if (e.key === '[') {
365
+ kbPressure = Math.max(0, kbPressure - 0.08);
366
+ emit('pressure', { value: Math.round(kbPressure * 100), normalized: kbPressure, timestamp: Date.now() });
367
+ }
368
+ });
369
+
370
+ /* ── Buttons ───────────────────────────────────────────────── */
371
+ document.getElementById('simTap').addEventListener('click', () => triggerGesture('tap'));
372
+ document.getElementById('simTwist').addEventListener('click', () => triggerGesture('twist'));
373
+ document.getElementById('selectBtn').addEventListener('click', selectItem);
374
+ document.getElementById('backBtn').addEventListener('click', goBack);
375
+ document.getElementById('homeBtn').addEventListener('click', goHome);
376
+
377
+ /* ── Init ──────────────────────────────────────────────────── */
378
+ renderMenu();
379
+ connect();
380
+ </script>
381
+ </body>
382
+ </html>