mudra-skills 3.0.2 → 3.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/package.json +1 -1
  2. package/skills/mudra-master/SKILL.md +0 -196
  3. package/skills/mudra-master/mudra-preview/SKILL.md +0 -150
  4. package/skills/mudra-master/mudra-preview/assets/ar-menu.html +0 -382
  5. package/skills/mudra-master/mudra-preview/assets/document-scroller.html +0 -468
  6. package/skills/mudra-master/mudra-preview/assets/drum-machine.html +0 -788
  7. package/skills/mudra-master/mudra-preview/assets/emg-visualizer.html +0 -390
  8. package/skills/mudra-master/mudra-preview/assets/generative-art.html +0 -408
  9. package/skills/mudra-master/mudra-preview/assets/gesture-assistant.html +0 -342
  10. package/skills/mudra-master/mudra-preview/assets/gesture-speech.html +0 -444
  11. package/skills/mudra-master/mudra-preview/assets/hands-free-desktop.html +0 -376
  12. package/skills/mudra-master/mudra-preview/assets/model-rotator.html +0 -365
  13. package/skills/mudra-master/mudra-preview/assets/mudra-duel.html +0 -3200
  14. package/skills/mudra-master/mudra-preview/assets/mudra-monitor.html +0 -1451
  15. package/skills/mudra-master/mudra-preview/assets/mudra-ultimate-template.html +0 -891
  16. package/skills/mudra-master/mudra-preview/assets/music-sequencer.html +0 -455
  17. package/skills/mudra-master/mudra-preview/assets/neural-pong.html +0 -380
  18. package/skills/mudra-master/mudra-preview/assets/neural-snake.html +0 -364
  19. package/skills/mudra-master/mudra-preview/assets/presentation-controller.html +0 -345
  20. package/skills/mudra-master/mudra-preview/assets/pressure-painter.html +0 -285
  21. package/skills/mudra-master/mudra-preview/assets/runner.html +0 -1812
  22. package/skills/mudra-master/mudra-preview/assets/smart-home.html +0 -461
  23. package/skills/mudra-master/mudra-preview/assets/space-invaders.html +0 -1154
  24. package/skills/mudra-master/mudra-preview/assets/waterful-ring-toss.html +0 -1624
  25. package/skills/mudra-master/mudra-preview/references/agent_protocol.json +0 -634
  26. package/skills/mudra-master/mudra-preview/references/prompt.md +0 -16746
  27. package/skills/mudra-master/mudra-xr/SKILL.md +0 -245
  28. package/skills/mudra-master/mudra-xr/assets/demos/3dgs-walkthrough.html +0 -242
  29. package/skills/mudra-master/mudra-xr/assets/demos/aisimulator.html +0 -792
  30. package/skills/mudra-master/mudra-xr/assets/demos/balloonpop.html +0 -422
  31. package/skills/mudra-master/mudra-xr/assets/demos/ballpit.html +0 -284
  32. package/skills/mudra-master/mudra-xr/assets/demos/drone.html +0 -41
  33. package/skills/mudra-master/mudra-xr/assets/demos/gemini-icebreakers.html +0 -338
  34. package/skills/mudra-master/mudra-xr/assets/demos/gemini-xrobject.html +0 -302
  35. package/skills/mudra-master/mudra-xr/assets/demos/math3d.html +0 -188
  36. package/skills/mudra-master/mudra-xr/assets/demos/measure.html +0 -247
  37. package/skills/mudra-master/mudra-xr/assets/demos/occlusion.html +0 -251
  38. package/skills/mudra-master/mudra-xr/assets/demos/rain.html +0 -498
  39. package/skills/mudra-master/mudra-xr/assets/demos/screenwiper.html +0 -432
  40. package/skills/mudra-master/mudra-xr/assets/demos/splash.html +0 -519
  41. package/skills/mudra-master/mudra-xr/assets/demos/webcam_gestures.html +0 -253
  42. package/skills/mudra-master/mudra-xr/assets/demos/xremoji.html +0 -833
  43. package/skills/mudra-master/mudra-xr/assets/demos/xrpoet.html +0 -151
  44. package/skills/mudra-master/mudra-xr/assets/samples/depthmap.html +0 -266
  45. package/skills/mudra-master/mudra-xr/assets/samples/depthmesh.html +0 -100
  46. package/skills/mudra-master/mudra-xr/assets/samples/game_rps.html +0 -1099
  47. package/skills/mudra-master/mudra-xr/assets/samples/gestures_custom.html +0 -467
  48. package/skills/mudra-master/mudra-xr/assets/samples/gestures_heuristic.html +0 -256
  49. package/skills/mudra-master/mudra-xr/assets/samples/lighting.html +0 -163
  50. package/skills/mudra-master/mudra-xr/assets/samples/mesh_detection.html +0 -46
  51. package/skills/mudra-master/mudra-xr/assets/samples/modelviewer.html +0 -183
  52. package/skills/mudra-master/mudra-xr/assets/samples/paint.html +0 -133
  53. package/skills/mudra-master/mudra-xr/assets/samples/planar-vst.html +0 -45
  54. package/skills/mudra-master/mudra-xr/assets/samples/reticle.html +0 -95
  55. package/skills/mudra-master/mudra-xr/assets/samples/skybox_agent.html +0 -384
  56. package/skills/mudra-master/mudra-xr/assets/samples/sound.html +0 -390
  57. package/skills/mudra-master/mudra-xr/assets/samples/ui.html +0 -496
  58. package/skills/mudra-master/mudra-xr/assets/samples/virtual-screens.html +0 -808
  59. package/skills/mudra-master/mudra-xr/assets/templates/0_basic.html +0 -64
  60. package/skills/mudra-master/mudra-xr/assets/templates/1_ui.html +0 -107
  61. package/skills/mudra-master/mudra-xr/assets/templates/2_hands.html +0 -183
  62. package/skills/mudra-master/mudra-xr/assets/templates/3_depth.html +0 -89
  63. package/skills/mudra-master/mudra-xr/assets/templates/4_stereo.html +0 -84
  64. package/skills/mudra-master/mudra-xr/assets/templates/5_camera.html +0 -117
  65. package/skills/mudra-master/mudra-xr/assets/templates/6_ai.html +0 -154
  66. package/skills/mudra-master/mudra-xr/assets/templates/7_ai_live.html +0 -253
  67. package/skills/mudra-master/mudra-xr/assets/templates/8_objects.html +0 -95
  68. package/skills/mudra-master/mudra-xr/assets/templates/9_xr-toggle.html +0 -90
  69. package/skills/mudra-master/mudra-xr/assets/templates/heuristic_hand_gestures.html +0 -102
  70. package/skills/mudra-master/mudra-xr/assets/templates/meshes.html +0 -47
  71. package/skills/mudra-master/mudra-xr/assets/templates/planes.html +0 -47
  72. package/skills/mudra-master/mudra-xr/assets/templates/uikit.html +0 -187
  73. package/skills/mudra-master/mudra-xr/references/agent_protocol.json +0 -865
  74. package/skills/mudra-master/mudra-xr/references/prompt.md +0 -1605
  75. package/skills/mudra-preview/SKILL.md +0 -159
  76. package/skills/mudra-preview/assets/ar-menu.html +0 -382
  77. package/skills/mudra-preview/assets/document-scroller.html +0 -468
  78. package/skills/mudra-preview/assets/drum-machine.html +0 -788
  79. package/skills/mudra-preview/assets/emg-visualizer.html +0 -390
  80. package/skills/mudra-preview/assets/generative-art.html +0 -408
  81. package/skills/mudra-preview/assets/gesture-assistant.html +0 -342
  82. package/skills/mudra-preview/assets/gesture-speech.html +0 -444
  83. package/skills/mudra-preview/assets/hands-free-desktop.html +0 -376
  84. package/skills/mudra-preview/assets/model-rotator.html +0 -365
  85. package/skills/mudra-preview/assets/mudra-duel.html +0 -3200
  86. package/skills/mudra-preview/assets/mudra-monitor.html +0 -1451
  87. package/skills/mudra-preview/assets/mudra-ultimate-template.html +0 -997
  88. package/skills/mudra-preview/assets/music-sequencer.html +0 -455
  89. package/skills/mudra-preview/assets/neural-pong.html +0 -380
  90. package/skills/mudra-preview/assets/neural-snake.html +0 -364
  91. package/skills/mudra-preview/assets/presentation-controller.html +0 -345
  92. package/skills/mudra-preview/assets/pressure-painter.html +0 -285
  93. package/skills/mudra-preview/assets/runner.html +0 -1812
  94. package/skills/mudra-preview/assets/smart-home.html +0 -461
  95. package/skills/mudra-preview/assets/space-invaders.html +0 -1154
  96. package/skills/mudra-preview/assets/waterful-ring-toss.html +0 -1624
  97. package/skills/mudra-preview/references/agent_protocol.json +0 -701
  98. package/skills/mudra-preview/references/prompt.md +0 -16731
  99. package/skills/mudra-xr/SKILL.md +0 -245
  100. package/skills/mudra-xr/assets/demos/3dgs-walkthrough.html +0 -242
  101. package/skills/mudra-xr/assets/demos/aisimulator.html +0 -792
  102. package/skills/mudra-xr/assets/demos/balloonpop.html +0 -422
  103. package/skills/mudra-xr/assets/demos/ballpit.html +0 -284
  104. package/skills/mudra-xr/assets/demos/drone.html +0 -41
  105. package/skills/mudra-xr/assets/demos/gemini-icebreakers.html +0 -338
  106. package/skills/mudra-xr/assets/demos/gemini-xrobject.html +0 -302
  107. package/skills/mudra-xr/assets/demos/math3d.html +0 -188
  108. package/skills/mudra-xr/assets/demos/measure.html +0 -247
  109. package/skills/mudra-xr/assets/demos/occlusion.html +0 -251
  110. package/skills/mudra-xr/assets/demos/rain.html +0 -498
  111. package/skills/mudra-xr/assets/demos/screenwiper.html +0 -432
  112. package/skills/mudra-xr/assets/demos/splash.html +0 -519
  113. package/skills/mudra-xr/assets/demos/webcam_gestures.html +0 -253
  114. package/skills/mudra-xr/assets/demos/xremoji.html +0 -833
  115. package/skills/mudra-xr/assets/demos/xrpoet.html +0 -151
  116. package/skills/mudra-xr/assets/samples/depthmap.html +0 -266
  117. package/skills/mudra-xr/assets/samples/depthmesh.html +0 -100
  118. package/skills/mudra-xr/assets/samples/game_rps.html +0 -1099
  119. package/skills/mudra-xr/assets/samples/gestures_custom.html +0 -467
  120. package/skills/mudra-xr/assets/samples/gestures_heuristic.html +0 -256
  121. package/skills/mudra-xr/assets/samples/lighting.html +0 -163
  122. package/skills/mudra-xr/assets/samples/mesh_detection.html +0 -46
  123. package/skills/mudra-xr/assets/samples/modelviewer.html +0 -183
  124. package/skills/mudra-xr/assets/samples/paint.html +0 -133
  125. package/skills/mudra-xr/assets/samples/planar-vst.html +0 -45
  126. package/skills/mudra-xr/assets/samples/reticle.html +0 -95
  127. package/skills/mudra-xr/assets/samples/skybox_agent.html +0 -384
  128. package/skills/mudra-xr/assets/samples/sound.html +0 -390
  129. package/skills/mudra-xr/assets/samples/ui.html +0 -496
  130. package/skills/mudra-xr/assets/samples/virtual-screens.html +0 -808
  131. package/skills/mudra-xr/assets/templates/0_basic.html +0 -64
  132. package/skills/mudra-xr/assets/templates/1_ui.html +0 -107
  133. package/skills/mudra-xr/assets/templates/2_hands.html +0 -183
  134. package/skills/mudra-xr/assets/templates/3_depth.html +0 -89
  135. package/skills/mudra-xr/assets/templates/4_stereo.html +0 -84
  136. package/skills/mudra-xr/assets/templates/5_camera.html +0 -117
  137. package/skills/mudra-xr/assets/templates/6_ai.html +0 -154
  138. package/skills/mudra-xr/assets/templates/7_ai_live.html +0 -253
  139. package/skills/mudra-xr/assets/templates/8_objects.html +0 -95
  140. package/skills/mudra-xr/assets/templates/9_xr-toggle.html +0 -90
  141. package/skills/mudra-xr/assets/templates/heuristic_hand_gestures.html +0 -102
  142. package/skills/mudra-xr/assets/templates/meshes.html +0 -47
  143. package/skills/mudra-xr/assets/templates/planes.html +0 -47
  144. package/skills/mudra-xr/assets/templates/uikit.html +0 -187
  145. package/skills/mudra-xr/references/agent_protocol.json +0 -865
  146. package/skills/mudra-xr/references/prompt.md +0 -1605
@@ -1,159 +0,0 @@
1
- ---
2
- name: mudra-preview
3
- version: 3.0.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 :** Every generated app MUST
12
- include a Manual/Mudra **Mode toggle** as defined in `references/prompt.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
- **No disconnect overlay:** 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:** 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 :** Connection state MUST reflect
28
- the **band**, not the WebSocket. The Companion service accepts socket
29
- connections even when no band is paired. Every generated app MUST:
30
- 1. Send `{command:"get_status"}` immediately in `ws.onopen` (no waiting for any
31
- server-initiated frame — the new server sends none).
32
- 2. Poll `get_status` every 2 s while in Mudra mode.
33
- 3. Use the rule **`device.firmware && device.serial_number` both non-null** to
34
- determine band-connected (not `device.state` alone).
35
- 4. Display a **6-state connection label**: `Connecting…` / `Mudra connected`
36
- (with hand chip LEFT/RIGHT) / `WebSocket only` (orange `#eab308`, hand
37
- `None`) / `Reconnecting…` / `Already in use by another tab` (terminal).
38
- 5. Auto-reconnect with backoff [1, 2, 5, 10]s on non-conflict WebSocket drops.
39
- Do NOT retry after `client_already_connected` — show the terminal message.
40
-
41
- The canonical protocol is in `references/agent_protocol.json`.
42
-
43
- ## Steps
44
-
45
- 1. **Read the full instructions** from `references/prompt.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.
46
-
47
- 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).
48
-
49
- 3. **Select the best-matching template** from `assets/` inside the skill base directory. Use the selection rule from `references/prompt.md` (motion mode → interaction pattern → signal overlap).
50
-
51
- 4. **Generate the app** as a single self-contained HTML file:
52
- - Follow every rule in `references/prompt.md` (protocol contract, signal compatibility, mock WebSocket, theme, fallbacks)
53
- - Choose a color palette that matches the concept — never default to dark unless the concept calls for it
54
- - Include the Mudra badge, connection indicator, telemetry HUD, simulator panel (see below), and keyboard fallbacks
55
-
56
- ### Simulator Panel (Required)
57
-
58
- 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.
59
-
60
- **Layout rules**
61
- - One thin horizontal strip (usually in or just below the header, or pinned to the bottom edge).
62
- - **Always visible** — do NOT hide when the band is connected.
63
- - Group by signal. Use short labels so it stays compact (e.g., `Tap`, `2Tap`, `Twist`, `↑`, `↓`, `Roll L`).
64
- - Reuse the existing `.btn` style from the chosen template.
65
- - No drawers, no collapse, no accordion — one click to fire.
66
-
67
- **Buttons per signal** (only render the groups for signals the app actually subscribes to)
68
-
69
- | Signal | Buttons |
70
- |---|---|
71
- | `gesture` | `Tap`, `2Tap`, `Twist`, `2Twist` |
72
- | `nav_direction` | `↑`, `↓`, `←`, `→`, `Roll L`, `Roll R` |
73
- | `navigation` | `↑`, `↓`, `←`, `→` (each click emits one delta event of ±8) |
74
- | `pressure` | Slider `0–100%` (or `−` / `+` buttons if horizontal space is tight) |
75
- | `button` | `Press`, `Release` |
76
- | `imu_acc` | `Tilt X`, `Tilt Y`, `Tilt Z` (each fires a 5-frame burst at ±2 m/s²) |
77
- | `imu_gyro` | `Rot X`, `Rot Y`, `Rot Z` (each fires a 5-frame burst at ±10 deg/s) |
78
- | `snc` | `Spike` (injects a burst of elevated samples on all 3 channels) |
79
-
80
- **How each button must fire**
81
- - `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).
82
- - **All other signals** → emit locally by calling the same handler `ws.onmessage` would dispatch. Example:
83
- ```js
84
- function simDir(direction) {
85
- // same path the real signal would take
86
- handleNavDirection({ direction, timestamp: Date.now() });
87
- hudDir.textContent = direction;
88
- }
89
- ```
90
- Always update the telemetry HUD from the sim button, same as the real signal would.
91
-
92
- 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`).
93
-
94
- 6. **Report the file path** so the user can open it in a browser immediately.
95
-
96
- ## Default Signal Set
97
-
98
- Unless the user explicitly asks for a different signal, every generated
99
- app MUST restrict itself to **at most these signals** (subject to the
100
- exclusivity rules below):
101
-
102
- 1. **One** of `pressure` **or** `gesture` — never both. `gesture` and
103
- `pressure` are mutually exclusive.
104
- - **Tap exclusivity rule** (within `gesture`): use `tap` OR `double_tap`
105
- — **never both together** unless the user explicitly names both (e.g.,
106
- "use single tap for X and double tap for Y").
107
- - `tap` → frequent/lightweight: counter increment, navigation step,
108
- toggle, next/back, select, trigger. **Default choice.**
109
- - `double_tap` → only when the user explicitly requests it by name.
110
- Never pair it with `tap` as a default two-action scheme.
111
- - Generic synonyms ("tap", "click", "press") → `tap`.
112
- - When an app needs **two distinct gesture actions** (e.g., forward +
113
- backward), pair `tap` with `twist` — not with `double_tap`.
114
- `double_tap` is reserved for explicit user requests only.
115
- 2. **One** directional signal — either `nav_direction` **or**
116
- `navigation`, never both.
117
-
118
- Rules:
119
-
120
- - Drop any of the above if the concept does not need it (e.g., a pure
121
- tap-counter subscribes to `gesture` only and skips the directional
122
- signal).
123
- - **`gesture` and `pressure` are mutually exclusive** — pick one
124
- interaction model per app. Tap/twist concepts → `gesture`. Analog
125
- concepts (volume, brush, throttle) → `pressure`.
126
- - `nav_direction` and `navigation` are **mutually exclusive per app** —
127
- pick the one that fits the interaction (discrete swipes →
128
- `nav_direction`; continuous cursor/scroll → `navigation`). Never wire
129
- both into the same app.
130
- - **IMU+Biometric bundle (`imu_acc` + `imu_gyro` + `snc`) is
131
- inseparable.** If the concept needs any one of them, subscribe to all
132
- three. The bundle is **mutually exclusive** with `navigation` and
133
- `nav_direction` — pick directional motion OR the IMU+Biometric bundle,
134
- never both.
135
- - Other gesture subtypes (`twist`, `double_twist`, etc.) and other
136
- signals (`button`, `imu_acc`, `imu_gyro`, `snc`) are **off by
137
- default**. Only include them when the user's prompt names them,
138
- names a synonym from the Signal Inference table in
139
- `references/prompt.md` § "Signal Inference Reference", or describes
140
- an interaction that genuinely cannot be expressed with the defaults
141
- (e.g., "tilt to steer" → IMU+Biometric bundle; "hold to charge" →
142
- `button`).
143
- - Battery level and charging state are available via `device.battery` / `device.charging` in the
144
- `get_status` / `get_device_info` response.
145
- - The simulator panel must mirror whichever subset the app actually
146
- subscribes to — do not render buttons for signals that are not wired.
147
-
148
- ## Quick Reference
149
-
150
- - WebSocket endpoint: `ws://127.0.0.1:8766` (bare URL — NOT `/events`)
151
- - Mode toggle (Manual / Mudra) is **mandatory** in every generated app — see `references/prompt.md` § "Mode Toggle (Manual / Mudra) — Required"
152
- - Lazy WS lifecycle: open on Manual→Mudra, close on Mudra→Manual. Manual mode opens NO WebSocket.
153
- - Subscribe one signal per command: `{ "command": "subscribe", "signal": "<name>" }` — singular `signal`, never `signals`, never an array
154
- - Motion modes are mutually exclusive: Pointer (`navigation`+`button`) / Direction (`nav_direction`) / IMU+Biometric (`imu_acc`+`imu_gyro`+`snc`, always all three together)
155
- - IMU+Biometric bundle: `imu_acc`, `imu_gyro`, `snc` always subscribed together — never partially. The bundle is mutually exclusive with `navigation` and `nav_direction`.
156
- - `gesture` and `pressure` are mutually exclusive — never combine them
157
- - `button` combines freely with `gesture`, `pressure`, `snc`, `imu_acc`, `imu_gyro` (subject to the Pointer/Direction/IMU motion-mode XOR — `button` belongs to Pointer mode and never combines with `nav_direction`).
158
- - **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/prompt.md` § "Navigation sensitivity defaults".
159
- - Canonical protocol JSON: `references/agent_protocol.json`
@@ -1,382 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1">
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>