@mulanjs/mulanjs 1.0.1-dev.20260212143840

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 (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -0
  3. package/dist/compiler/compiler.js +90 -0
  4. package/dist/compiler/script-compiler.js +314 -0
  5. package/dist/compiler/sfc-parser.js +93 -0
  6. package/dist/compiler/style-compiler.js +56 -0
  7. package/dist/compiler/template-compiler.js +442 -0
  8. package/dist/components/bloch-sphere.js +252 -0
  9. package/dist/core/component.js +145 -0
  10. package/dist/core/hooks.js +229 -0
  11. package/dist/core/quantum.js +284 -0
  12. package/dist/core/query.js +63 -0
  13. package/dist/core/reactive.js +105 -0
  14. package/dist/core/renderer.js +70 -0
  15. package/dist/core/vault.js +81 -0
  16. package/dist/index.js +52 -0
  17. package/dist/mulan.esm.js +1948 -0
  18. package/dist/mulan.js +215 -0
  19. package/dist/router/index.js +210 -0
  20. package/dist/security/sanitizer.js +47 -0
  21. package/dist/store/index.js +42 -0
  22. package/dist/types/compiler/compiler.d.ts +7 -0
  23. package/dist/types/compiler/script-compiler.d.ts +8 -0
  24. package/dist/types/compiler/sfc-parser.d.ts +21 -0
  25. package/dist/types/compiler/style-compiler.d.ts +7 -0
  26. package/dist/types/compiler/template-compiler.d.ts +7 -0
  27. package/dist/types/compiler.d.ts +7 -0
  28. package/dist/types/components/bloch-sphere.d.ts +16 -0
  29. package/dist/types/core/component.d.ts +54 -0
  30. package/dist/types/core/hooks.d.ts +49 -0
  31. package/dist/types/core/quantum.d.ts +50 -0
  32. package/dist/types/core/query.d.ts +14 -0
  33. package/dist/types/core/reactive.d.ts +21 -0
  34. package/dist/types/core/renderer.d.ts +4 -0
  35. package/dist/types/core/vault.d.ts +12 -0
  36. package/dist/types/index.d.ts +70 -0
  37. package/dist/types/router/index.d.ts +24 -0
  38. package/dist/types/script-compiler.d.ts +8 -0
  39. package/dist/types/security/sanitizer.d.ts +17 -0
  40. package/dist/types/sfc-parser.d.ts +21 -0
  41. package/dist/types/store/index.d.ts +10 -0
  42. package/dist/types/style-compiler.d.ts +7 -0
  43. package/dist/types/template-compiler.d.ts +7 -0
  44. package/package.json +64 -0
  45. package/src/cli/extensions/mulanjs-vscode-1.0.0.vsix +0 -0
  46. package/src/cli/index.js +600 -0
  47. package/src/compiler/compiler.ts +102 -0
  48. package/src/compiler/script-compiler.ts +336 -0
  49. package/src/compiler/sfc-parser.ts +118 -0
  50. package/src/compiler/style-compiler.ts +66 -0
  51. package/src/compiler/template-compiler.ts +519 -0
  52. package/src/compiler/tsconfig.json +13 -0
  53. package/src/loader/index.js +81 -0
@@ -0,0 +1,252 @@
1
+ import { effect } from '../core/reactive';
2
+ export class MuBlochSphereElement extends HTMLElement {
3
+ static get observedAttributes() {
4
+ return ['size'];
5
+ }
6
+ constructor() {
7
+ super();
8
+ this._arrow = null;
9
+ this._container = null;
10
+ this._disposeEffect = null;
11
+ this.attachShadow({ mode: 'open' });
12
+ }
13
+ connectedCallback() {
14
+ this.render();
15
+ }
16
+ disconnectedCallback() {
17
+ if (this._disposeEffect) {
18
+ this._disposeEffect();
19
+ this._disposeEffect = null;
20
+ }
21
+ }
22
+ // Property setter for 'qubit' (passed as .qubit="${q}" in Mulan)
23
+ set qubit(val) {
24
+ this._qubit = val;
25
+ this.initReactivity();
26
+ }
27
+ get qubit() {
28
+ return this._qubit;
29
+ }
30
+ attributeChangedCallback(name, oldValue, newValue) {
31
+ if (name === 'size' && this._container) {
32
+ this._container.style.width = newValue + 'px';
33
+ this._container.style.height = newValue + 'px';
34
+ }
35
+ }
36
+ initReactivity() {
37
+ if (this._disposeEffect)
38
+ this._disposeEffect();
39
+ if (!this._qubit)
40
+ return;
41
+ // MulanJS Effect: Run whenever the qubit state changes
42
+ this._disposeEffect = effect(() => {
43
+ const state = this._qubit.value; // Access reactive proxy
44
+ if (!state || !state.amplitudes)
45
+ return;
46
+ // Assume Single Qubit for Visualizer (Index 0 if passed a register)
47
+ // If passed a register, we visualize the first qubit's logical projection (partial trace simplified)
48
+ // Or assume input IS a specific qubit projection?
49
+ // For simplified demo, we assume the register is size 1 OR we visualize Q0 of the register.
50
+ const amps = state.amplitudes;
51
+ // Support 1-qubit visualization from N-qubit register requires partial trace.
52
+ // For now, let's assume the user passes a 1-qubit register OR we visualize index 0.
53
+ // |psi> = a|0> + b|1>
54
+ // a = amps[0] (re, im)
55
+ // b = amps[1] (re, im)
56
+ // Dealing with multi-qubit registers (naive projection for visualization):
57
+ // We sum up probabilities for 0xxxx vs 1xxxx to get Z-axis.
58
+ // This is "marginal probability".
59
+ let p0 = 0;
60
+ let p1 = 0;
61
+ // Calculate Probabilities
62
+ for (let i = 0; i < amps.length; i++) {
63
+ const mag = amps[i].re * amps[i].re + amps[i].im * amps[i].im;
64
+ if ((i & 1) === 0)
65
+ p0 += mag;
66
+ else
67
+ p1 += mag;
68
+ }
69
+ // Theta from Z-projection
70
+ // P0 = cos^2(theta/2) -> theta = 2 * acos(sqrt(P0))
71
+ const theta = 2 * Math.acos(Math.min(1, Math.sqrt(p0)));
72
+ // Phi?
73
+ // Phase is relative phase between |0> and |1>.
74
+ // We can look at the phase of the '1' component relative to '0'.
75
+ // Simple approach: Look at amps[1] phase vs amps[0] phase.
76
+ // But with entanglement, pure state phase is tricky.
77
+ // Let's implement full density matrix if needed, but for now:
78
+ // naive: atan2(amps[1].im, amps[1].re) - atan2(amps[0].im, amps[0].re)
79
+ // We use the first pair (0 and 1) as proxy if multiple.
80
+ const a0 = amps[0];
81
+ const a1 = amps[1]; // Valid for N>=1
82
+ const phase0 = Math.atan2(a0.im, a0.re);
83
+ const phase1 = Math.atan2(a1.im, a1.re);
84
+ let phi = phase1 - phase0;
85
+ // Update Arrow
86
+ this.updateArrow(theta, phi);
87
+ });
88
+ }
89
+ updateArrow(theta, phi) {
90
+ if (!this._arrow)
91
+ return;
92
+ // Convert Quantum Coords (Theta, Phi) to CSS Transforms
93
+ // Theta 0 = Top (|0>)
94
+ // Theta PI = Bottom (|1>)
95
+ // Theta PI/2 = Equator
96
+ // CSS Rotate sequence:
97
+ // 1. Start pointing UP (Y or Z axis in CSS?)
98
+ // Let's say Arrow starts pointing UP (Y-).
99
+ // Rotate Z by Phi (Azimuth)
100
+ // Rotate X by Theta (Polar) - No, that's not standard Euler.
101
+ // Standard Physics:
102
+ // Z is Up (in Bloch), but CSS 3D Y is usually 'Down' or 'Up'.
103
+ // Let's Map:
104
+ // Bloch Z+ (|0>) -> CSS Y- (Top)
105
+ // Bloch Z- (|1>) -> CSS Y+ (Bottom)
106
+ // Bloch X+ (|+>) -> CSS Z+ (Front)
107
+ // Transform:
108
+ // rotateY(phi) ? No, phi rotates around Z-axis (Vertical).
109
+ // theta rotates from Z-axis down.
110
+ // CSS:
111
+ // rotateY(phi) -> Rotates around Vertical axis.
112
+ // rotateZ(theta) -> Rotates "down" from up?
113
+ // Let's simplify:
114
+ // transform: rotateY(${phi}rad) rotateZ(${theta}rad)
115
+ // Note: CSS rotations order matters.
116
+ const degTheta = theta * (180 / Math.PI);
117
+ const degPhi = phi * (180 / Math.PI);
118
+ // Adjustment for visual alignment
119
+ this._arrow.style.transform = `rotateY(${degPhi}deg) rotateZ(${degTheta}deg)`;
120
+ // Color based on state
121
+ const isSuperposition = Math.abs(theta - Math.PI / 2) < 0.1;
122
+ this._arrow.style.backgroundColor = isSuperposition ? '#00ffff' : '#ff00ff';
123
+ }
124
+ render() {
125
+ if (!this.shadowRoot)
126
+ return;
127
+ const size = this.getAttribute('size') || '200';
128
+ this.shadowRoot.innerHTML = `
129
+ <style>
130
+ :host {
131
+ display: inline-block;
132
+ perspective: 1000px;
133
+ }
134
+ .sphere-container {
135
+ width: ${size}px;
136
+ height: ${size}px;
137
+ position: relative;
138
+ transform-style: preserve-3d;
139
+ margin: 0 auto;
140
+ }
141
+ .sphere {
142
+ width: 100%;
143
+ height: 100%;
144
+ border-radius: 50%;
145
+ border: 1px solid rgba(255, 255, 255, 0.2);
146
+ position: absolute;
147
+ background: radial-gradient(circle at 30% 30%, rgba(255, 255, 255, 0.1), rgba(0, 0, 0, 0.8));
148
+ box-shadow: inset 0 0 20px rgba(255, 255, 255, 0.05);
149
+ transform: rotateX(15deg) rotateY(15deg); /* Initial tilt for view */
150
+ }
151
+ /* Equator Ring */
152
+ .equator {
153
+ position: absolute;
154
+ top: 50%;
155
+ left: 0;
156
+ width: 100%;
157
+ height: 100%;
158
+ border: 1px dashed rgba(255, 255, 255, 0.3);
159
+ border-radius: 50%;
160
+ transform: rotateX(90deg);
161
+ pointer-events: none;
162
+ }
163
+ /* Axis Lines */
164
+ .axis {
165
+ position: absolute;
166
+ background: rgba(255, 255, 255, 0.1);
167
+ }
168
+ .z-axis { width: 2px; height: 100%; left: 50%; top: 0; }
169
+
170
+ /* The Quantum Arrow */
171
+ .arrow-container {
172
+ position: absolute;
173
+ top: 50%;
174
+ left: 50%;
175
+ width: 0;
176
+ height: 0;
177
+ transform-style: preserve-3d;
178
+ transform: rotateX(15deg) rotateY(15deg); /* Match sphere tilt */
179
+ }
180
+
181
+ .arrow-pivot {
182
+ position: absolute;
183
+ top: 0;
184
+ left: 0;
185
+ width: 4px;
186
+ height: 50%; /* Length of radius */
187
+ /* Pivot logic: we want to rotate around the center point */
188
+ /* CSS Default transform-origin is 50% 50% */
189
+ /* We construct the arrow such that it points UP from center */
190
+ }
191
+
192
+ .arrow-rod {
193
+ width: 4px;
194
+ height: ${parseInt(size) / 2 - 10}px;
195
+ background: #ff00ff;
196
+ position: absolute;
197
+ bottom: 0;
198
+ left: -2px;
199
+ transform-origin: bottom center;
200
+ transition: transform 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275), background-color 0.3s;
201
+ border-radius: 2px;
202
+ box-shadow: 0 0 10px currentColor;
203
+ }
204
+
205
+ .arrow-head {
206
+ width: 0;
207
+ height: 0;
208
+ border-left: 6px solid transparent;
209
+ border-right: 6px solid transparent;
210
+ border-bottom: 12px solid #ff00ff;
211
+ position: absolute;
212
+ top: -10px;
213
+ left: -6px;
214
+ }
215
+
216
+ .label {
217
+ position: absolute;
218
+ color: #aaa;
219
+ font-family: monospace;
220
+ font-size: 10px;
221
+ }
222
+ .label-0 { top: 5px; left: 50%; transform: translateX(-50%); }
223
+ .label-1 { bottom: 5px; left: 50%; transform: translateX(-50%); }
224
+ </style>
225
+
226
+ <div class="sphere-container">
227
+ <div class="sphere">
228
+ <div class="equator"></div>
229
+ <div class="axis z-axis"></div>
230
+ <div class="label label-0">|0></div>
231
+ <div class="label label-1">|1></div>
232
+ </div>
233
+
234
+ <div class="arrow-container">
235
+ <!-- The rod is the actual rotating element -->
236
+ <div class="arrow-rod">
237
+ <div class="arrow-head"></div>
238
+ </div>
239
+ </div>
240
+ </div>
241
+ `;
242
+ this._container = this.shadowRoot.querySelector('.sphere-container');
243
+ this._arrow = this.shadowRoot.querySelector('.arrow-rod');
244
+ // Re-init reactivity if qubit was set before render
245
+ if (this._qubit)
246
+ this.initReactivity();
247
+ }
248
+ }
249
+ // Register the custom element
250
+ if (typeof customElements !== 'undefined') {
251
+ customElements.define('mu-bloch-sphere', MuBlochSphereElement);
252
+ }
@@ -0,0 +1,145 @@
1
+ import { render } from './renderer';
2
+ import { setCurrentInstance } from './hooks';
3
+ import { effect } from './reactive';
4
+ // Or a simple component that returns an object of state/methods used by template?
5
+ // For Mulan 2.0, let's say functional component returns a template string render function explicitly.
6
+ export class MuComponent {
7
+ constructor(container) {
8
+ this._hooks = {};
9
+ this._effects = [];
10
+ this._isDestroyed = false;
11
+ this._propsQueue = [];
12
+ this._eventQueue = [];
13
+ this.container = container;
14
+ this.state = {};
15
+ this.$uid = 'mu_' + Math.random().toString(36).substr(2, 9);
16
+ // MULAN INSIGHT: Global Registry for Debugging
17
+ if (typeof window !== 'undefined') {
18
+ const global = window;
19
+ global.__MULAN_INSIGHT__ = global.__MULAN_INSIGHT__ || { components: new Map() };
20
+ global.__MULAN_INSIGHT__.components.set(this.$uid, this);
21
+ }
22
+ // Setup context for hooks
23
+ setCurrentInstance(this);
24
+ this.setup();
25
+ setCurrentInstance(null);
26
+ }
27
+ // Optional setup method for class components wanting to use hooks
28
+ setup() { }
29
+ onMount() {
30
+ var _a, _c;
31
+ // Mulan Cycle: Init
32
+ (_a = this._hooks.onMuInit) === null || _a === void 0 ? void 0 : _a.forEach(fn => fn());
33
+ // Mulan Cycle: Mount (Simulated immediately after init for now in this version)
34
+ (_c = this._hooks.onMuMount) === null || _c === void 0 ? void 0 : _c.forEach(fn => fn());
35
+ }
36
+ onUpdate() { }
37
+ onDestroy() {
38
+ var _a, _c;
39
+ if (this._isDestroyed)
40
+ return;
41
+ this._isDestroyed = true;
42
+ console.log(`[Mulan Cycle] Destroying component ${this.$uid} (${this.constructor.name})`);
43
+ // MULAN INSIGHT: Cleanup
44
+ if (typeof window !== 'undefined') {
45
+ const global = window;
46
+ (_a = global.__MULAN_INSIGHT__) === null || _a === void 0 ? void 0 : _a.components.delete(this.$uid);
47
+ }
48
+ // Mulan Cycle: Stop all effects to prevent leaks
49
+ console.log(`[Mulan Cycle] Stopping ${this._effects.length} effects for ${this.$uid}`);
50
+ this._effects.forEach(stop => stop());
51
+ this._effects = [];
52
+ // Mulan Cycle: Destroy hooks
53
+ (_c = this._hooks.onMuDestroy) === null || _c === void 0 ? void 0 : _c.forEach(fn => fn());
54
+ }
55
+ // Helper for compiler to register property bindings
56
+ _b(id, prop, value) {
57
+ this._propsQueue.push([id, prop, value]);
58
+ return "";
59
+ }
60
+ // Helper for compiler to register event bindings
61
+ _e(id, type, handler) {
62
+ this._eventQueue.push([id, type, handler]);
63
+ return "";
64
+ }
65
+ mount() {
66
+ console.log(`[Mulan Cycle] Mounting component ${this.$uid}`);
67
+ const stop = effect(() => {
68
+ console.log(`[Mulan Reactivity] Triggering update for ${this.$uid}`);
69
+ this.update();
70
+ });
71
+ this._effects.push(stop);
72
+ this.onMount();
73
+ }
74
+ update() {
75
+ if (this._isDestroyed) {
76
+ console.warn(`[Mulan Warning] Update called on destroyed component ${this.$uid}. Blocking render.`);
77
+ return;
78
+ }
79
+ // Clear queues before render
80
+ this._propsQueue = [];
81
+ this._eventQueue = [];
82
+ const template = this.template();
83
+ // Render HTML
84
+ render(template, this.container);
85
+ // Flush Side Effects
86
+ this.flushProps();
87
+ this.flushEvents();
88
+ this.onUpdate();
89
+ }
90
+ flushProps() {
91
+ for (const [id, prop, value] of this._propsQueue) {
92
+ const el = this.container.querySelector(`[data-mu-id="${id}"]`);
93
+ if (el) {
94
+ // @ts-ignore
95
+ el[prop] = value;
96
+ }
97
+ }
98
+ this._propsQueue = [];
99
+ }
100
+ flushEvents() {
101
+ for (const [id, type, handler] of this._eventQueue) {
102
+ const el = this.container.querySelector(`[data-mu-id="${id}"]`);
103
+ if (el) {
104
+ // Bind handler to this component instance to ensure 'this' works in methods
105
+ el.addEventListener(type, handler.bind(this));
106
+ }
107
+ }
108
+ this._eventQueue = [];
109
+ }
110
+ }
111
+ // Helper to create functional components wrapped in the class system
112
+ // Helper to create functional components wrapped in the class system
113
+ // Supports both () => render and { setup() } signatures
114
+ export function defineComponent(optionsOrSetup) {
115
+ return class UniversalComponent extends MuComponent {
116
+ constructor(container) {
117
+ super(container);
118
+ // setup() is called by super constructor
119
+ }
120
+ setup() {
121
+ let setupResult;
122
+ if (typeof optionsOrSetup === 'function') {
123
+ // It's a setup function returning render fn
124
+ setupResult = optionsOrSetup.call(this);
125
+ }
126
+ else if (typeof optionsOrSetup === 'object' && optionsOrSetup.setup) {
127
+ // It's an options object with setup()
128
+ setupResult = optionsOrSetup.setup.call(this);
129
+ }
130
+ // Handle result
131
+ if (typeof setupResult === 'function') {
132
+ // It's a render function
133
+ this.template = setupResult;
134
+ }
135
+ else if (setupResult && typeof setupResult === 'object') {
136
+ // It's bindings
137
+ Object.assign(this, setupResult);
138
+ // We assume template is assigned to prototype by compiler
139
+ }
140
+ }
141
+ template() {
142
+ return '';
143
+ }
144
+ };
145
+ }
@@ -0,0 +1,229 @@
1
+ import { reactive, effect } from './reactive';
2
+ import { persistent } from './vault';
3
+ // Global context to track the current component instance
4
+ let currentInstance = null;
5
+ export function setCurrentInstance(instance) {
6
+ currentInstance = instance;
7
+ }
8
+ export function getCurrentInstance() {
9
+ return currentInstance;
10
+ }
11
+ // --- Mulan Unique Reactivity Hooks ---
12
+ export function muState(initialValue) {
13
+ if (typeof initialValue === 'object' && initialValue !== null) {
14
+ return reactive(initialValue);
15
+ }
16
+ // Core reactive state container for primitives
17
+ return reactive({ value: initialValue });
18
+ }
19
+ export function muMemo(computeFn) {
20
+ const signal = reactive({ value: undefined });
21
+ const stop = effect(() => {
22
+ signal.value = computeFn();
23
+ });
24
+ const instance = getCurrentInstance();
25
+ if (instance) {
26
+ if (!instance._effects)
27
+ instance._effects = [];
28
+ instance._effects.push(stop);
29
+ }
30
+ return signal;
31
+ }
32
+ export function muEffect(fn) {
33
+ const stop = effect(fn);
34
+ const instance = getCurrentInstance();
35
+ if (instance) {
36
+ if (!instance._effects)
37
+ instance._effects = [];
38
+ instance._effects.push(stop);
39
+ }
40
+ }
41
+ // --- The "Mulan Cycle" (Lifecycle) ---
42
+ export function onMuInit(fn) {
43
+ const instance = getCurrentInstance();
44
+ if (instance) {
45
+ if (!instance._hooks)
46
+ instance._hooks = {};
47
+ if (!instance._hooks.onMuInit)
48
+ instance._hooks.onMuInit = [];
49
+ instance._hooks.onMuInit.push(fn);
50
+ }
51
+ else {
52
+ console.warn('onMuInit called outside of component setup context.');
53
+ }
54
+ }
55
+ export function onMuMount(fn) {
56
+ // New hook for when component is actually in DOM (simulated for now via update)
57
+ const instance = getCurrentInstance();
58
+ if (instance) {
59
+ if (!instance._hooks)
60
+ instance._hooks = {};
61
+ if (!instance._hooks.onMuMount)
62
+ instance._hooks.onMuMount = [];
63
+ instance._hooks.onMuMount.push(fn);
64
+ }
65
+ }
66
+ export function onMuDestroy(fn) {
67
+ const instance = getCurrentInstance();
68
+ if (instance) {
69
+ if (!instance._hooks)
70
+ instance._hooks = {};
71
+ if (!instance._hooks.onMuDestroy)
72
+ instance._hooks.onMuDestroy = [];
73
+ instance._hooks.onMuDestroy.push(fn);
74
+ }
75
+ }
76
+ /**
77
+ * onMuIdle - The "Environment Life" Hook.
78
+ * Executes heavy logic ONLY when the browser is taking a nap (idle).
79
+ */
80
+ export function onMuIdle(fn) {
81
+ const instance = getCurrentInstance();
82
+ // Polyfill for Safari/Old Browsers
83
+ const requestIdleCallback = window.requestIdleCallback || function (cb) {
84
+ return setTimeout(() => {
85
+ cb({
86
+ didTimeout: false,
87
+ timeRemaining: function () { return 50; }
88
+ });
89
+ }, 1);
90
+ };
91
+ const cancelIdleCallback = window.cancelIdleCallback || function (id) {
92
+ clearTimeout(id);
93
+ };
94
+ const idleId = requestIdleCallback(() => {
95
+ fn();
96
+ });
97
+ // Auto-cleanup if component dies before idle time
98
+ if (instance) {
99
+ onMuDestroy(() => {
100
+ cancelIdleCallback(idleId);
101
+ });
102
+ }
103
+ }
104
+ /**
105
+ * onMuResume - The "Tab Life" Hook.
106
+ * Executes when the user switches BACK to this tab.
107
+ * Perfect for refreshing data or resuming animations to save battery.
108
+ */
109
+ export function onMuResume(fn) {
110
+ const handler = () => {
111
+ if (document.visibilityState === 'visible') {
112
+ fn();
113
+ }
114
+ };
115
+ document.addEventListener('visibilitychange', handler);
116
+ onMuDestroy(() => document.removeEventListener('visibilitychange', handler));
117
+ }
118
+ /**
119
+ * onMuShake - The "Physical Life" Hook.
120
+ * Executes when the device is shaken.
121
+ * Usage: Undo, Refresh, or "Rage Quit" easter eggs.
122
+ */
123
+ export function onMuShake(fn) {
124
+ // Threshold for shake detection
125
+ const threshold = 15;
126
+ let lastX = 0, lastY = 0, lastZ = 0;
127
+ let lastTime = 0;
128
+ const handler = (e) => {
129
+ const current = e.accelerationIncludingGravity;
130
+ if (!current)
131
+ return;
132
+ const time = Date.now();
133
+ if ((time - lastTime) > 100) {
134
+ const diffTime = time - lastTime;
135
+ lastTime = time;
136
+ const x = current.x || 0;
137
+ const y = current.y || 0;
138
+ const z = current.z || 0;
139
+ const speed = Math.abs(x + y + z - lastX - lastY - lastZ) / diffTime * 10000;
140
+ if (speed > threshold) {
141
+ fn();
142
+ }
143
+ lastX = x;
144
+ lastY = y;
145
+ lastZ = z;
146
+ }
147
+ };
148
+ if (window.DeviceMotionEvent) {
149
+ window.addEventListener('devicemotion', handler);
150
+ onMuDestroy(() => window.removeEventListener('devicemotion', handler));
151
+ }
152
+ else {
153
+ console.warn("[MulanJS] Device Motion not supported on this device.");
154
+ }
155
+ }
156
+ /**
157
+ * onMuVoice - The "Sound Life" Hook.
158
+ * Executes when a specific word is spoken.
159
+ * @param command The word to listen for (e.g., "save", "next")
160
+ * @param fn The action to take
161
+ */
162
+ export function onMuVoice(command, fn) {
163
+ const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
164
+ if (!SpeechRecognition) {
165
+ console.warn("[MulanJS] Voice Control (Web Speech API) not supported in this browser.");
166
+ return;
167
+ }
168
+ const recognition = new SpeechRecognition();
169
+ recognition.continuous = true;
170
+ recognition.lang = 'en-US';
171
+ recognition.interimResults = false;
172
+ recognition.onresult = (event) => {
173
+ const last = event.results.length - 1;
174
+ const spoken = event.results[last][0].transcript.trim().toLowerCase();
175
+ console.log(`[Mulan Voice] Heard: "${spoken}"`);
176
+ if (spoken.includes(command.toLowerCase())) {
177
+ fn();
178
+ }
179
+ };
180
+ recognition.start();
181
+ // Auto-restart if it stops (Continuous listening)
182
+ recognition.onend = () => {
183
+ // Simple check to see if we should still be listening
184
+ // In a real app we might want more control
185
+ // recognition.start();
186
+ };
187
+ onMuDestroy(() => {
188
+ recognition.stop();
189
+ });
190
+ }
191
+ // --- "Outside The Box" Hooks (Mulan Exclusives) ---
192
+ /**
193
+ * muGeom - Tracks window or element dimensions reactively.
194
+ */
195
+ export function muGeom() {
196
+ const dims = muState({ width: window.innerWidth, height: window.innerHeight });
197
+ const handler = () => {
198
+ dims.width = window.innerWidth;
199
+ dims.height = window.innerHeight;
200
+ };
201
+ window.addEventListener('resize', handler);
202
+ // Auto-cleanup
203
+ onMuDestroy(() => {
204
+ window.removeEventListener('resize', handler);
205
+ });
206
+ return dims;
207
+ }
208
+ /**
209
+ * muPulse - Reactive network status.
210
+ */
211
+ export function muPulse() {
212
+ const status = muState({ online: navigator.onLine });
213
+ const setOnline = () => status.online = true;
214
+ const setOffline = () => status.online = false;
215
+ window.addEventListener('online', setOnline);
216
+ window.addEventListener('offline', setOffline);
217
+ onMuDestroy(() => {
218
+ window.removeEventListener('online', setOnline);
219
+ window.removeEventListener('offline', setOffline);
220
+ });
221
+ return status;
222
+ }
223
+ /**
224
+ * muVault - Secure reactive LocalStorage wrapper.
225
+ * Powered by the Iron Fortress persistent primitive.
226
+ */
227
+ export function muVault(key, initial, options = {}) {
228
+ return persistent(key, initial, options);
229
+ }