@adea-ai/audio 0.22.2 → 0.23.1

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.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
1
  # Audio package
2
2
 
3
- Owns the optional music controller and React bindings used by Adea. Audio is
3
+ Owns the optional music controller and Solid bindings used by Adea. Audio is
4
4
  lazy and non-blocking so scene loading does not depend on sound assets.
package/dist/config.js CHANGED
@@ -1,6 +1,7 @@
1
+ //#region src/config.ts
1
2
  /** Optional soundtrack hooks for Adea. */
2
- export const AUDIO_BASE_URL = '/assets/audio';
3
- export const MUSIC_DIR = `${AUDIO_BASE_URL}/sounds/music`;
4
- export const MUSIC_FILES = {
5
- silent: '',
6
- };
3
+ var AUDIO_BASE_URL = "/assets/audio";
4
+ var MUSIC_DIR = `${AUDIO_BASE_URL}/sounds/music`;
5
+ var MUSIC_FILES = { silent: "" };
6
+ //#endregion
7
+ export { AUDIO_BASE_URL, MUSIC_DIR, MUSIC_FILES };
@@ -1,152 +1,125 @@
1
- import { MUSIC_DIR, MUSIC_FILES } from './config';
2
- const MUSIC_MUTE_KEY = 'adea:audio:music-muted';
3
- const LEGACY_MUTE_KEY = 'adea:audio:muted';
4
- const DEFAULT_MUSIC_VOLUME = 0.8;
5
- const FADE_STEP_MS = 30;
1
+ import { MUSIC_DIR, MUSIC_FILES } from "./config.js";
2
+ //#region src/controller.ts
3
+ var MUSIC_MUTE_KEY = "adea:audio:music-muted";
4
+ var LEGACY_MUTE_KEY = "adea:audio:muted";
5
+ var DEFAULT_MUSIC_VOLUME = .8;
6
+ var FADE_STEP_MS = 30;
6
7
  function clampVolume(value) {
7
- return value <= 0 ? 0 : value > 1 ? 1 : value;
8
+ return value <= 0 ? 0 : value > 1 ? 1 : value;
8
9
  }
9
10
  function readStorage(key) {
10
- if (typeof window === 'undefined')
11
- return null;
12
- try {
13
- return window.localStorage.getItem(key);
14
- }
15
- catch {
16
- return null;
17
- }
11
+ if (typeof window === "undefined") return null;
12
+ try {
13
+ return window.localStorage.getItem(key);
14
+ } catch {
15
+ return null;
16
+ }
18
17
  }
19
18
  function writeStorage(key, muted) {
20
- if (typeof window === 'undefined')
21
- return;
22
- try {
23
- if (muted)
24
- window.localStorage.setItem(key, '1');
25
- else
26
- window.localStorage.removeItem(key);
27
- }
28
- catch {
29
- // Storage can be unavailable; the in-memory mute still applies.
30
- }
19
+ if (typeof window === "undefined") return;
20
+ try {
21
+ if (muted) window.localStorage.setItem(key, "1");
22
+ else window.localStorage.removeItem(key);
23
+ } catch {}
31
24
  }
32
25
  function loadMusicMuted() {
33
- return readStorage(MUSIC_MUTE_KEY) === '1' || readStorage(LEGACY_MUTE_KEY) === '1';
26
+ return readStorage(MUSIC_MUTE_KEY) === "1" || readStorage(LEGACY_MUTE_KEY) === "1";
34
27
  }
35
- export class SoundController {
36
- musicElements = new Map();
37
- desiredMusic = null;
38
- currentMusic = null;
39
- activeFades = new Set();
40
- _musicMuted;
41
- constructor() {
42
- this._musicMuted = loadMusicMuted();
43
- }
44
- get musicMuted() {
45
- return this._musicMuted;
46
- }
47
- /** Retry queued playback after a user gesture. */
48
- async unlock() {
49
- this.startDesiredMusic();
50
- }
51
- /** Start or switch to a looping soundtrack. */
52
- playMusic(id, options = {}) {
53
- if (!MUSIC_FILES[id])
54
- return;
55
- this.desiredMusic = id;
56
- this.prepareMusicElement(id);
57
- if (!this.musicMuted)
58
- this.startMusic(id, options);
59
- }
60
- setMusicMuted(muted) {
61
- this._musicMuted = muted;
62
- writeStorage(MUSIC_MUTE_KEY, muted);
63
- for (const element of this.musicElements.values()) {
64
- if (muted)
65
- element.pause();
66
- }
67
- if (!muted)
68
- this.startDesiredMusic();
69
- }
70
- toggleMusicMute() {
71
- this.setMusicMuted(!this._musicMuted);
72
- return this._musicMuted;
73
- }
74
- prepareMusicElement(id) {
75
- if (this.musicElements.has(id))
76
- return;
77
- const element = new Audio(`${MUSIC_DIR}/${MUSIC_FILES[id]}`);
78
- element.loop = true;
79
- // Defer the request until playback is allowed so audio never competes with
80
- // the scene's first visual and physics assets.
81
- element.preload = 'none';
82
- this.musicElements.set(id, element);
83
- }
84
- startMusic(id, options = {}) {
85
- if (this.currentMusic === id) {
86
- const active = this.musicElements.get(id);
87
- if (active?.paused)
88
- void active.play().catch(() => undefined);
89
- return;
90
- }
91
- const fadeMs = options.fadeMs ?? 500;
92
- const target = clampVolume(options.volume ?? DEFAULT_MUSIC_VOLUME);
93
- const previousId = this.currentMusic;
94
- const previous = previousId ? (this.musicElements.get(previousId) ?? null) : null;
95
- this.currentMusic = id;
96
- const element = this.musicElements.get(id);
97
- if (!element)
98
- return;
99
- element.volume = 0;
100
- element.currentTime = 0;
101
- void element
102
- .play()
103
- .then(() => {
104
- if (this.currentMusic !== id) {
105
- element.pause();
106
- return;
107
- }
108
- if (previous) {
109
- this.fadeMusic(previous, 0, fadeMs, () => {
110
- previous.pause();
111
- previous.currentTime = 0;
112
- });
113
- }
114
- this.fadeMusic(element, target, fadeMs);
115
- })
116
- .catch(() => {
117
- // Autoplay is blocked before the first user gesture. Keep the desired
118
- // track so unlock() can retry it later.
119
- if (previous)
120
- this.currentMusic = previousId;
121
- else
122
- this.currentMusic = null;
123
- });
124
- }
125
- startDesiredMusic() {
126
- if (this.musicMuted || !this.desiredMusic)
127
- return;
128
- const desired = this.desiredMusic;
129
- if (this.currentMusic !== desired) {
130
- this.startMusic(desired);
131
- return;
132
- }
133
- const element = this.musicElements.get(desired);
134
- if (element?.paused)
135
- void element.play().catch(() => undefined);
136
- }
137
- fadeMusic(element, target, durationMs, onDone) {
138
- const start = element.volume;
139
- const startTime = performance.now();
140
- const interval = window.setInterval(() => {
141
- const progress = Math.min(1, (performance.now() - startTime) / Math.max(durationMs, 1));
142
- element.volume = start + (target - start) * progress;
143
- if (progress >= 1) {
144
- window.clearInterval(interval);
145
- this.activeFades.delete(interval);
146
- onDone?.();
147
- }
148
- }, FADE_STEP_MS);
149
- this.activeFades.add(interval);
150
- }
151
- }
152
- export const soundController = new SoundController();
28
+ var SoundController = class {
29
+ musicElements = /* @__PURE__ */ new Map();
30
+ desiredMusic = null;
31
+ currentMusic = null;
32
+ activeFades = /* @__PURE__ */ new Set();
33
+ _musicMuted;
34
+ constructor() {
35
+ this._musicMuted = loadMusicMuted();
36
+ }
37
+ get musicMuted() {
38
+ return this._musicMuted;
39
+ }
40
+ /** Retry queued playback after a user gesture. */
41
+ async unlock() {
42
+ this.startDesiredMusic();
43
+ }
44
+ /** Start or switch to a looping soundtrack. */
45
+ playMusic(id, options = {}) {
46
+ if (!MUSIC_FILES[id]) return;
47
+ this.desiredMusic = id;
48
+ this.prepareMusicElement(id);
49
+ if (!this.musicMuted) this.startMusic(id, options);
50
+ }
51
+ setMusicMuted(muted) {
52
+ this._musicMuted = muted;
53
+ writeStorage(MUSIC_MUTE_KEY, muted);
54
+ for (const element of this.musicElements.values()) if (muted) element.pause();
55
+ if (!muted) this.startDesiredMusic();
56
+ }
57
+ toggleMusicMute() {
58
+ this.setMusicMuted(!this._musicMuted);
59
+ return this._musicMuted;
60
+ }
61
+ prepareMusicElement(id) {
62
+ if (this.musicElements.has(id)) return;
63
+ const element = new Audio(`${MUSIC_DIR}/${MUSIC_FILES[id]}`);
64
+ element.loop = true;
65
+ element.preload = "none";
66
+ this.musicElements.set(id, element);
67
+ }
68
+ startMusic(id, options = {}) {
69
+ if (this.currentMusic === id) {
70
+ const active = this.musicElements.get(id);
71
+ if (active?.paused) active.play().catch(() => void 0);
72
+ return;
73
+ }
74
+ const fadeMs = options.fadeMs ?? 500;
75
+ const target = clampVolume(options.volume ?? DEFAULT_MUSIC_VOLUME);
76
+ const previousId = this.currentMusic;
77
+ const previous = previousId ? this.musicElements.get(previousId) ?? null : null;
78
+ this.currentMusic = id;
79
+ const element = this.musicElements.get(id);
80
+ if (!element) return;
81
+ element.volume = 0;
82
+ element.currentTime = 0;
83
+ element.play().then(() => {
84
+ if (this.currentMusic !== id) {
85
+ element.pause();
86
+ return;
87
+ }
88
+ if (previous) this.fadeMusic(previous, 0, fadeMs, () => {
89
+ previous.pause();
90
+ previous.currentTime = 0;
91
+ });
92
+ this.fadeMusic(element, target, fadeMs);
93
+ }).catch(() => {
94
+ if (previous) this.currentMusic = previousId;
95
+ else this.currentMusic = null;
96
+ });
97
+ }
98
+ startDesiredMusic() {
99
+ if (this.musicMuted || !this.desiredMusic) return;
100
+ const desired = this.desiredMusic;
101
+ if (this.currentMusic !== desired) {
102
+ this.startMusic(desired);
103
+ return;
104
+ }
105
+ const element = this.musicElements.get(desired);
106
+ if (element?.paused) element.play().catch(() => void 0);
107
+ }
108
+ fadeMusic(element, target, durationMs, onDone) {
109
+ const start = element.volume;
110
+ const startTime = performance.now();
111
+ const interval = window.setInterval(() => {
112
+ const progress = Math.min(1, (performance.now() - startTime) / Math.max(durationMs, 1));
113
+ element.volume = start + (target - start) * progress;
114
+ if (progress >= 1) {
115
+ window.clearInterval(interval);
116
+ this.activeFades.delete(interval);
117
+ onDone?.();
118
+ }
119
+ }, FADE_STEP_MS);
120
+ this.activeFades.add(interval);
121
+ }
122
+ };
123
+ var soundController = new SoundController();
124
+ //#endregion
125
+ export { SoundController, soundController };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { AUDIO_BASE_URL, MUSIC_FILES, type MusicId } from './config';
2
2
  export { SoundController, soundController, type MusicOptions } from './controller';
3
3
  export { musicForScene, sceneMusicTracks } from './scene-music';
4
- export { MusicToggle, SoundProvider, useSceneMusic, useSound, type SoundContextValue, } from './react';
4
+ export { MusicToggle, SoundProvider, useSceneMusic, useSound, type SoundContextValue, } from './sound';
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- // Optional soundtrack hooks for Adea.
2
- export { AUDIO_BASE_URL, MUSIC_FILES } from './config';
3
- export { SoundController, soundController } from './controller';
4
- export { musicForScene, sceneMusicTracks } from './scene-music';
5
- export { MusicToggle, SoundProvider, useSceneMusic, useSound, } from './react';
1
+ import { AUDIO_BASE_URL, MUSIC_FILES } from "./config.js";
2
+ import { SoundController, soundController } from "./controller.js";
3
+ import { musicForScene, sceneMusicTracks } from "./scene-music.js";
4
+ import { MusicToggle, SoundProvider, useSceneMusic, useSound } from "./sound.js";
5
+ export { AUDIO_BASE_URL, MUSIC_FILES, MusicToggle, SoundController, SoundProvider, musicForScene, sceneMusicTracks, soundController, useSceneMusic, useSound };
@@ -1,8 +1,11 @@
1
+ //#region src/scene-music.ts
1
2
  /** Scene route id -> optional track. Deployments can add tracks without changing scene code. */
2
- export const sceneMusicTracks = {
3
- 'hq-home': 'silent',
4
- 'hq-work': 'silent',
3
+ var sceneMusicTracks = {
4
+ "hq-home": "silent",
5
+ "hq-work": "silent"
5
6
  };
6
- export function musicForScene(sceneId) {
7
- return sceneMusicTracks[sceneId ?? ''] ?? 'silent';
7
+ function musicForScene(sceneId) {
8
+ return sceneMusicTracks[sceneId ?? ""] ?? "silent";
8
9
  }
10
+ //#endregion
11
+ export { musicForScene, sceneMusicTracks };
@@ -1,4 +1,4 @@
1
- import { type ReactNode } from 'react';
1
+ import { type ParentProps } from 'solid-js';
2
2
  import { soundController, type MusicOptions } from './controller';
3
3
  import type { MusicId } from './config';
4
4
  export type SoundContextValue = {
@@ -8,9 +8,7 @@ export type SoundContextValue = {
8
8
  toggleMusicMute: () => void;
9
9
  playMusic: (id: MusicId, options?: MusicOptions) => void;
10
10
  };
11
- export declare function SoundProvider({ children }: {
12
- children: ReactNode;
13
- }): import("react").JSX.Element;
11
+ export declare function SoundProvider(props: ParentProps): import("solid-js").JSX.Element;
14
12
  export declare function useSound(): SoundContextValue;
15
13
  export declare function useSceneMusic(sceneId: string | null | undefined): void;
16
- export declare function MusicToggle(): import("react").JSX.Element;
14
+ export declare function MusicToggle(): import("solid-js").JSX.Element;
package/dist/sound.js ADDED
@@ -0,0 +1,99 @@
1
+ import { soundController } from "./controller.js";
2
+ import { musicForScene } from "./scene-music.js";
3
+ import { className, createComponent, delegateEvents, effect, insert, memo, setAttribute, template } from "solid-js/web";
4
+ import { Music, Music2 } from "lucide-solid";
5
+ import { createContext, createEffect, createMemo, createSignal, on, onCleanup, onMount, useContext } from "solid-js";
6
+ //#region src/sound.tsx
7
+ var _tmpl$ = /*#__PURE__*/ template(`<button type=button>`);
8
+ var SoundContext = createContext(null);
9
+ function SoundProvider(props) {
10
+ const [musicMuted, setMusicMuted] = createSignal(false);
11
+ const [ready, setReady] = createSignal(false);
12
+ onMount(() => {
13
+ let mounted = true;
14
+ queueMicrotask(() => {
15
+ if (mounted) setMusicMuted(soundController.musicMuted);
16
+ });
17
+ const onGesture = () => {
18
+ soundController.unlock().then(() => {
19
+ if (mounted) setReady(true);
20
+ });
21
+ };
22
+ window.addEventListener("pointerdown", onGesture, { capture: true });
23
+ window.addEventListener("keydown", onGesture, { capture: true });
24
+ window.addEventListener("touchstart", onGesture, { capture: true });
25
+ if (new URLSearchParams(window.location.search).has("debug")) window.__agentHqSound = soundController;
26
+ onCleanup(() => {
27
+ mounted = false;
28
+ window.removeEventListener("pointerdown", onGesture, { capture: true });
29
+ window.removeEventListener("keydown", onGesture, { capture: true });
30
+ window.removeEventListener("touchstart", onGesture, { capture: true });
31
+ });
32
+ });
33
+ const value = createMemo(() => ({
34
+ controller: soundController,
35
+ ready: ready(),
36
+ musicMuted: musicMuted(),
37
+ toggleMusicMute: () => setMusicMuted(soundController.toggleMusicMute()),
38
+ playMusic: (id, options) => soundController.playMusic(id, options)
39
+ }));
40
+ return createComponent(SoundContext.Provider, {
41
+ get value() {
42
+ return value();
43
+ },
44
+ get children() {
45
+ return props.children;
46
+ }
47
+ });
48
+ }
49
+ function useSound() {
50
+ const value = useContext(SoundContext);
51
+ if (!value) throw new Error("useSound must be used within <SoundProvider>");
52
+ return value;
53
+ }
54
+ function useSceneMusic(sceneId) {
55
+ createEffect(on(() => sceneId, (scene) => {
56
+ const timeout = window.setTimeout(() => {
57
+ soundController.playMusic(musicForScene(scene));
58
+ }, 1500);
59
+ onCleanup(() => window.clearTimeout(timeout));
60
+ }));
61
+ }
62
+ function MusicButton(props) {
63
+ return (() => {
64
+ var _el$ = _tmpl$();
65
+ _el$.$$click = () => props.onToggle();
66
+ insert(_el$, (() => {
67
+ var _c$ = memo(() => !!props.muted);
68
+ return () => _c$() ? createComponent(Music, {
69
+ "class": "size-5",
70
+ "aria-hidden": "true"
71
+ }) : createComponent(Music2, {
72
+ "class": "size-5",
73
+ "aria-hidden": "true"
74
+ });
75
+ })());
76
+ effect((_p$) => {
77
+ var _v$ = props.muted ? "Unmute music" : "Mute music", _v$2 = props.muted, _v$3 = `inline-flex size-9 items-center justify-center rounded-lg border border-input bg-background transition-colors ${props.muted ? "text-muted-foreground hover:text-foreground" : "bg-primary text-primary-foreground"}`;
78
+ _v$ !== _p$.e && setAttribute(_el$, "aria-label", _p$.e = _v$);
79
+ _v$2 !== _p$.t && setAttribute(_el$, "aria-pressed", _p$.t = _v$2);
80
+ _v$3 !== _p$.a && className(_el$, _p$.a = _v$3);
81
+ return _p$;
82
+ }, {
83
+ e: void 0,
84
+ t: void 0,
85
+ a: void 0
86
+ });
87
+ return _el$;
88
+ })();
89
+ }
90
+ function MusicToggle() {
91
+ const { musicMuted, toggleMusicMute } = useSound();
92
+ return createComponent(MusicButton, {
93
+ muted: musicMuted,
94
+ onToggle: toggleMusicMute
95
+ });
96
+ }
97
+ delegateEvents(["click"]);
98
+ //#endregion
99
+ export { MusicToggle, SoundProvider, useSceneMusic, useSound };
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@adea-ai/audio",
3
- "version": "0.22.2",
4
- "description": "Optional, lazy music controller and React bindings for Adea. Audio is non-blocking so scene loading never waits on sound assets.",
3
+ "version": "0.23.1",
4
+ "description": "Optional, lazy music controller and Solid bindings for Adea. Audio is non-blocking so scene loading never waits on sound assets.",
5
5
  "keywords": [
6
6
  "audio",
7
7
  "music",
8
- "react",
8
+ "solid",
9
9
  "threejs",
10
10
  "workspace"
11
11
  ],
@@ -27,21 +27,23 @@
27
27
  "exports": {
28
28
  ".": {
29
29
  "types": "./dist/index.d.ts",
30
+ "solid": "./src/index.ts",
30
31
  "development": "./src/index.ts",
31
32
  "import": "./dist/index.js"
32
33
  }
33
34
  },
34
35
  "scripts": {
35
36
  "lint": "oxlint",
36
- "build": "tsc -p tsconfig.json",
37
- "dev": "tsc -p tsconfig.json --watch --preserveWatchOutput",
37
+ "build": "vite build && tsc -p tsconfig.json --emitDeclarationOnly",
38
+ "dev": "vite build --watch",
38
39
  "typecheck": "tsc -p tsconfig.json --noEmit"
39
40
  },
40
41
  "dependencies": {
41
- "lucide-react": "^1.33.0",
42
- "react": "19.3.0"
42
+ "lucide-solid": "^1.45.0",
43
+ "solid-js": "^1.9.15"
43
44
  },
44
45
  "devDependencies": {
45
- "@types/react": "^19.2.18"
46
+ "vite": "^8.0.14",
47
+ "vite-plugin-solid": "^2.11.14"
46
48
  }
47
49
  }
package/src/index.ts CHANGED
@@ -9,4 +9,4 @@ export {
9
9
  useSceneMusic,
10
10
  useSound,
11
11
  type SoundContextValue,
12
- } from './react'
12
+ } from './sound'
@@ -1,7 +1,15 @@
1
- 'use client'
2
-
3
- import { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from 'react'
4
- import { Music, Music2 } from 'lucide-react'
1
+ import { Music, Music2 } from 'lucide-solid'
2
+ import {
3
+ createContext,
4
+ createMemo,
5
+ createSignal,
6
+ on,
7
+ onCleanup,
8
+ onMount,
9
+ createEffect,
10
+ useContext,
11
+ type ParentProps,
12
+ } from 'solid-js'
5
13
  import { soundController, type MusicOptions } from './controller'
6
14
  import { musicForScene } from './scene-music'
7
15
  import type { MusicId } from './config'
@@ -16,11 +24,11 @@ export type SoundContextValue = {
16
24
 
17
25
  const SoundContext = createContext<SoundContextValue | null>(null)
18
26
 
19
- export function SoundProvider({ children }: { children: ReactNode }) {
20
- const [musicMuted, setMusicMuted] = useState(false)
21
- const [ready, setReady] = useState(false)
27
+ export function SoundProvider(props: ParentProps) {
28
+ const [musicMuted, setMusicMuted] = createSignal(false)
29
+ const [ready, setReady] = createSignal(false)
22
30
 
23
- useEffect(() => {
31
+ onMount(() => {
24
32
  let mounted = true
25
33
  queueMicrotask(() => {
26
34
  if (mounted) setMusicMuted(soundController.musicMuted)
@@ -38,26 +46,23 @@ export function SoundProvider({ children }: { children: ReactNode }) {
38
46
  ;(window as unknown as { __agentHqSound?: typeof soundController }).__agentHqSound =
39
47
  soundController
40
48
  }
41
- return () => {
49
+ onCleanup(() => {
42
50
  mounted = false
43
51
  window.removeEventListener('pointerdown', onGesture, { capture: true })
44
52
  window.removeEventListener('keydown', onGesture, { capture: true })
45
53
  window.removeEventListener('touchstart', onGesture, { capture: true })
46
- }
47
- }, [])
54
+ })
55
+ })
48
56
 
49
- const value = useMemo<SoundContextValue>(
50
- () => ({
51
- controller: soundController,
52
- ready,
53
- musicMuted,
54
- toggleMusicMute: () => setMusicMuted(soundController.toggleMusicMute()),
55
- playMusic: (id, options) => soundController.playMusic(id, options),
56
- }),
57
- [musicMuted, ready]
58
- )
57
+ const value = createMemo<SoundContextValue>(() => ({
58
+ controller: soundController,
59
+ ready: ready(),
60
+ musicMuted: musicMuted(),
61
+ toggleMusicMute: () => setMusicMuted(soundController.toggleMusicMute()),
62
+ playMusic: (id, options) => soundController.playMusic(id, options),
63
+ }))
59
64
 
60
- return <SoundContext.Provider value={value}>{children}</SoundContext.Provider>
65
+ return <SoundContext.Provider value={value()}>{props.children}</SoundContext.Provider>
61
66
  }
62
67
 
63
68
  export function useSound(): SoundContextValue {
@@ -67,27 +72,32 @@ export function useSound(): SoundContextValue {
67
72
  }
68
73
 
69
74
  export function useSceneMusic(sceneId: string | null | undefined): void {
70
- useEffect(() => {
71
- const timeout = window.setTimeout(() => {
72
- soundController.playMusic(musicForScene(sceneId))
73
- }, 1500)
74
- return () => window.clearTimeout(timeout)
75
- }, [sceneId])
75
+ createEffect(
76
+ on(
77
+ () => sceneId,
78
+ (scene) => {
79
+ const timeout = window.setTimeout(() => {
80
+ soundController.playMusic(musicForScene(scene))
81
+ }, 1500)
82
+ onCleanup(() => window.clearTimeout(timeout))
83
+ }
84
+ )
85
+ )
76
86
  }
77
87
 
78
- function MusicButton({ muted, onToggle }: { muted: boolean; onToggle: () => void }) {
88
+ function MusicButton(props: { muted: boolean; onToggle: () => void }) {
79
89
  return (
80
90
  <button
81
91
  type="button"
82
- aria-label={muted ? 'Unmute music' : 'Mute music'}
83
- aria-pressed={muted}
84
- onClick={onToggle}
85
- className={`inline-flex size-9 items-center justify-center rounded-lg border border-input bg-background transition-colors ${muted ? 'text-muted-foreground hover:text-foreground' : 'bg-primary text-primary-foreground'}`}
92
+ aria-label={props.muted ? 'Unmute music' : 'Mute music'}
93
+ aria-pressed={props.muted}
94
+ onClick={() => props.onToggle()}
95
+ class={`inline-flex size-9 items-center justify-center rounded-lg border border-input bg-background transition-colors ${props.muted ? 'text-muted-foreground hover:text-foreground' : 'bg-primary text-primary-foreground'}`}
86
96
  >
87
- {muted ? (
88
- <Music className="size-5" aria-hidden="true" />
97
+ {props.muted ? (
98
+ <Music class="size-5" aria-hidden="true" />
89
99
  ) : (
90
- <Music2 className="size-5" aria-hidden="true" />
100
+ <Music2 class="size-5" aria-hidden="true" />
91
101
  )}
92
102
  </button>
93
103
  )
package/tsconfig.json CHANGED
@@ -4,7 +4,8 @@
4
4
  "rootDir": "src",
5
5
  "outDir": "dist",
6
6
  "declaration": true,
7
- "jsx": "react-jsx"
7
+ "jsx": "preserve",
8
+ "jsxImportSource": "solid-js"
8
9
  },
9
10
  "include": ["src"]
10
11
  }
package/dist/react.js DELETED
@@ -1,67 +0,0 @@
1
- 'use client';
2
- import { jsx as _jsx } from "react/jsx-runtime";
3
- import { createContext, useContext, useEffect, useMemo, useState } from 'react';
4
- import { Music, Music2 } from 'lucide-react';
5
- import { soundController } from './controller';
6
- import { musicForScene } from './scene-music';
7
- const SoundContext = createContext(null);
8
- export function SoundProvider({ children }) {
9
- const [musicMuted, setMusicMuted] = useState(false);
10
- const [ready, setReady] = useState(false);
11
- useEffect(() => {
12
- let mounted = true;
13
- queueMicrotask(() => {
14
- if (mounted)
15
- setMusicMuted(soundController.musicMuted);
16
- });
17
- const onGesture = () => {
18
- void soundController.unlock().then(() => {
19
- if (mounted)
20
- setReady(true);
21
- });
22
- };
23
- window.addEventListener('pointerdown', onGesture, { capture: true });
24
- window.addEventListener('keydown', onGesture, { capture: true });
25
- window.addEventListener('touchstart', onGesture, { capture: true });
26
- if (new URLSearchParams(window.location.search).has('debug')) {
27
- ;
28
- window.__agentHqSound =
29
- soundController;
30
- }
31
- return () => {
32
- mounted = false;
33
- window.removeEventListener('pointerdown', onGesture, { capture: true });
34
- window.removeEventListener('keydown', onGesture, { capture: true });
35
- window.removeEventListener('touchstart', onGesture, { capture: true });
36
- };
37
- }, []);
38
- const value = useMemo(() => ({
39
- controller: soundController,
40
- ready,
41
- musicMuted,
42
- toggleMusicMute: () => setMusicMuted(soundController.toggleMusicMute()),
43
- playMusic: (id, options) => soundController.playMusic(id, options),
44
- }), [musicMuted, ready]);
45
- return _jsx(SoundContext.Provider, { value: value, children: children });
46
- }
47
- export function useSound() {
48
- const value = useContext(SoundContext);
49
- if (!value)
50
- throw new Error('useSound must be used within <SoundProvider>');
51
- return value;
52
- }
53
- export function useSceneMusic(sceneId) {
54
- useEffect(() => {
55
- const timeout = window.setTimeout(() => {
56
- soundController.playMusic(musicForScene(sceneId));
57
- }, 1500);
58
- return () => window.clearTimeout(timeout);
59
- }, [sceneId]);
60
- }
61
- function MusicButton({ muted, onToggle }) {
62
- return (_jsx("button", { type: "button", "aria-label": muted ? 'Unmute music' : 'Mute music', "aria-pressed": muted, onClick: onToggle, className: `inline-flex size-9 items-center justify-center rounded-lg border border-input bg-background transition-colors ${muted ? 'text-muted-foreground hover:text-foreground' : 'bg-primary text-primary-foreground'}`, children: muted ? (_jsx(Music, { className: "size-5", "aria-hidden": "true" })) : (_jsx(Music2, { className: "size-5", "aria-hidden": "true" })) }));
63
- }
64
- export function MusicToggle() {
65
- const { musicMuted, toggleMusicMute } = useSound();
66
- return _jsx(MusicButton, { muted: musicMuted, onToggle: toggleMusicMute });
67
- }