@adea-ai/audio 0.12.0 → 0.12.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/dist/config.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /** Optional soundtrack hooks for Adea. */
2
- export const AUDIO_BASE_URL = "/assets/audio";
2
+ export const AUDIO_BASE_URL = '/assets/audio';
3
3
  export const MUSIC_DIR = `${AUDIO_BASE_URL}/sounds/music`;
4
4
  export const MUSIC_FILES = {
5
- silent: "",
5
+ silent: '',
6
6
  };
@@ -1,4 +1,4 @@
1
- import { type MusicId } from "./config";
1
+ import { type MusicId } from './config';
2
2
  export type MusicOptions = {
3
3
  /** 0..1 track gain (default 0.8). */
4
4
  volume?: number;
@@ -1,13 +1,13 @@
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";
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
4
  const DEFAULT_MUSIC_VOLUME = 0.8;
5
5
  const FADE_STEP_MS = 30;
6
6
  function clampVolume(value) {
7
7
  return value <= 0 ? 0 : value > 1 ? 1 : value;
8
8
  }
9
9
  function readStorage(key) {
10
- if (typeof window === "undefined")
10
+ if (typeof window === 'undefined')
11
11
  return null;
12
12
  try {
13
13
  return window.localStorage.getItem(key);
@@ -17,11 +17,11 @@ function readStorage(key) {
17
17
  }
18
18
  }
19
19
  function writeStorage(key, muted) {
20
- if (typeof window === "undefined")
20
+ if (typeof window === 'undefined')
21
21
  return;
22
22
  try {
23
23
  if (muted)
24
- window.localStorage.setItem(key, "1");
24
+ window.localStorage.setItem(key, '1');
25
25
  else
26
26
  window.localStorage.removeItem(key);
27
27
  }
@@ -30,7 +30,7 @@ function writeStorage(key, muted) {
30
30
  }
31
31
  }
32
32
  function loadMusicMuted() {
33
- return readStorage(MUSIC_MUTE_KEY) === "1" || readStorage(LEGACY_MUTE_KEY) === "1";
33
+ return readStorage(MUSIC_MUTE_KEY) === '1' || readStorage(LEGACY_MUTE_KEY) === '1';
34
34
  }
35
35
  export class SoundController {
36
36
  musicElements = new Map();
@@ -78,7 +78,7 @@ export class SoundController {
78
78
  element.loop = true;
79
79
  // Defer the request until playback is allowed so audio never competes with
80
80
  // the scene's first visual and physics assets.
81
- element.preload = "none";
81
+ element.preload = 'none';
82
82
  this.musicElements.set(id, element);
83
83
  }
84
84
  startMusic(id, options = {}) {
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { AUDIO_BASE_URL, MUSIC_FILES, type MusicId } from "./config";
2
- export { SoundController, soundController, type MusicOptions } from "./controller";
3
- export { musicForScene, sceneMusicTracks } from "./scene-music";
4
- export { MusicToggle, SoundProvider, useSceneMusic, useSound, type SoundContextValue, } from "./react";
1
+ export { AUDIO_BASE_URL, MUSIC_FILES, type MusicId } from './config';
2
+ export { SoundController, soundController, type MusicOptions } from './controller';
3
+ export { musicForScene, sceneMusicTracks } from './scene-music';
4
+ export { MusicToggle, SoundProvider, useSceneMusic, useSound, type SoundContextValue, } from './react';
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
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";
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';
package/dist/react.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { type ReactNode } from "react";
2
- import { soundController, type MusicOptions } from "./controller";
3
- import type { MusicId } from "./config";
1
+ import { type ReactNode } from 'react';
2
+ import { soundController, type MusicOptions } from './controller';
3
+ import type { MusicId } from './config';
4
4
  export type SoundContextValue = {
5
5
  controller: typeof soundController;
6
6
  ready: boolean;
package/dist/react.js CHANGED
@@ -1,9 +1,9 @@
1
- "use client";
1
+ 'use client';
2
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";
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
7
  const SoundContext = createContext(null);
8
8
  export function SoundProvider({ children }) {
9
9
  const [musicMuted, setMusicMuted] = useState(false);
@@ -20,18 +20,19 @@ export function SoundProvider({ children }) {
20
20
  setReady(true);
21
21
  });
22
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")) {
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
+ ;
27
28
  window.__agentHqSound =
28
29
  soundController;
29
30
  }
30
31
  return () => {
31
32
  mounted = false;
32
- window.removeEventListener("pointerdown", onGesture, { capture: true });
33
- window.removeEventListener("keydown", onGesture, { capture: true });
34
- window.removeEventListener("touchstart", onGesture, { capture: true });
33
+ window.removeEventListener('pointerdown', onGesture, { capture: true });
34
+ window.removeEventListener('keydown', onGesture, { capture: true });
35
+ window.removeEventListener('touchstart', onGesture, { capture: true });
35
36
  };
36
37
  }, []);
37
38
  const value = useMemo(() => ({
@@ -46,7 +47,7 @@ export function SoundProvider({ children }) {
46
47
  export function useSound() {
47
48
  const value = useContext(SoundContext);
48
49
  if (!value)
49
- throw new Error("useSound must be used within <SoundProvider>");
50
+ throw new Error('useSound must be used within <SoundProvider>');
50
51
  return value;
51
52
  }
52
53
  export function useSceneMusic(sceneId) {
@@ -58,7 +59,7 @@ export function useSceneMusic(sceneId) {
58
59
  }, [sceneId]);
59
60
  }
60
61
  function MusicButton({ muted, onToggle }) {
61
- 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" })) }));
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" })) }));
62
63
  }
63
64
  export function MusicToggle() {
64
65
  const { musicMuted, toggleMusicMute } = useSound();
@@ -1,4 +1,4 @@
1
- import type { MusicId } from "./config";
1
+ import type { MusicId } from './config';
2
2
  /** Scene route id -> optional track. Deployments can add tracks without changing scene code. */
3
3
  export declare const sceneMusicTracks: Record<string, MusicId>;
4
4
  export declare function musicForScene(sceneId: string | null | undefined): MusicId;
@@ -1,8 +1,8 @@
1
1
  /** Scene route id -> optional track. Deployments can add tracks without changing scene code. */
2
2
  export const sceneMusicTracks = {
3
- "hq-home": "silent",
4
- "hq-work": "silent",
3
+ 'hq-home': 'silent',
4
+ 'hq-work': 'silent',
5
5
  };
6
6
  export function musicForScene(sceneId) {
7
- return sceneMusicTracks[sceneId ?? ""] ?? "silent";
7
+ return sceneMusicTracks[sceneId ?? ''] ?? 'silent';
8
8
  }
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@adea-ai/audio",
3
- "version": "0.12.0",
4
- "type": "module",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
3
+ "version": "0.12.1",
7
4
  "files": [
8
5
  "dist",
9
6
  "src",
10
7
  "tsconfig.json",
11
8
  "README.md"
12
9
  ],
10
+ "type": "module",
11
+ "main": "dist/index.js",
12
+ "types": "dist/index.d.ts",
13
13
  "exports": {
14
14
  ".": {
15
15
  "types": "./dist/index.d.ts",
@@ -18,16 +18,16 @@
18
18
  }
19
19
  },
20
20
  "scripts": {
21
- "lint": "eslint .",
21
+ "lint": "oxlint",
22
22
  "build": "tsc -p tsconfig.json",
23
23
  "dev": "tsc -p tsconfig.json --watch --preserveWatchOutput",
24
24
  "typecheck": "tsc -p tsconfig.json --noEmit"
25
25
  },
26
- "devDependencies": {
27
- "@types/react": "^19.2.18"
28
- },
29
26
  "dependencies": {
30
27
  "lucide-react": "^1.33.0",
31
28
  "react": "19.2.8"
29
+ },
30
+ "devDependencies": {
31
+ "@types/react": "^19.2.18"
32
32
  }
33
33
  }
package/src/config.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  /** Optional soundtrack hooks for Adea. */
2
- export const AUDIO_BASE_URL = "/assets/audio";
3
- export const MUSIC_DIR = `${AUDIO_BASE_URL}/sounds/music`;
2
+ export const AUDIO_BASE_URL = '/assets/audio'
3
+ export const MUSIC_DIR = `${AUDIO_BASE_URL}/sounds/music`
4
4
 
5
5
  export const MUSIC_FILES = {
6
- silent: "",
7
- } as const;
6
+ silent: '',
7
+ } as const
8
8
 
9
- export type MusicId = keyof typeof MUSIC_FILES;
9
+ export type MusicId = keyof typeof MUSIC_FILES
package/src/controller.ts CHANGED
@@ -1,144 +1,144 @@
1
- import { MUSIC_DIR, MUSIC_FILES, type MusicId } from "./config";
1
+ import { MUSIC_DIR, MUSIC_FILES, type MusicId } from './config'
2
2
 
3
3
  export type MusicOptions = {
4
4
  /** 0..1 track gain (default 0.8). */
5
- volume?: number;
5
+ volume?: number
6
6
  /** Crossfade/fade duration in ms (default 500). */
7
- fadeMs?: number;
8
- };
7
+ fadeMs?: number
8
+ }
9
9
 
10
- const MUSIC_MUTE_KEY = "adea:audio:music-muted";
11
- const LEGACY_MUTE_KEY = "adea:audio:muted";
12
- const DEFAULT_MUSIC_VOLUME = 0.8;
13
- const FADE_STEP_MS = 30;
10
+ const MUSIC_MUTE_KEY = 'adea:audio:music-muted'
11
+ const LEGACY_MUTE_KEY = 'adea:audio:muted'
12
+ const DEFAULT_MUSIC_VOLUME = 0.8
13
+ const FADE_STEP_MS = 30
14
14
 
15
15
  function clampVolume(value: number): number {
16
- return value <= 0 ? 0 : value > 1 ? 1 : value;
16
+ return value <= 0 ? 0 : value > 1 ? 1 : value
17
17
  }
18
18
 
19
19
  function readStorage(key: string): string | null {
20
- if (typeof window === "undefined") return null;
20
+ if (typeof window === 'undefined') return null
21
21
  try {
22
- return window.localStorage.getItem(key);
22
+ return window.localStorage.getItem(key)
23
23
  } catch {
24
- return null;
24
+ return null
25
25
  }
26
26
  }
27
27
 
28
28
  function writeStorage(key: string, muted: boolean): void {
29
- if (typeof window === "undefined") return;
29
+ if (typeof window === 'undefined') return
30
30
  try {
31
- if (muted) window.localStorage.setItem(key, "1");
32
- else window.localStorage.removeItem(key);
31
+ if (muted) window.localStorage.setItem(key, '1')
32
+ else window.localStorage.removeItem(key)
33
33
  } catch {
34
34
  // Storage can be unavailable; the in-memory mute still applies.
35
35
  }
36
36
  }
37
37
 
38
38
  function loadMusicMuted(): boolean {
39
- return readStorage(MUSIC_MUTE_KEY) === "1" || readStorage(LEGACY_MUTE_KEY) === "1";
39
+ return readStorage(MUSIC_MUTE_KEY) === '1' || readStorage(LEGACY_MUTE_KEY) === '1'
40
40
  }
41
41
 
42
42
  export class SoundController {
43
- private musicElements = new Map<MusicId, HTMLAudioElement>();
44
- private desiredMusic: MusicId | null = null;
45
- private currentMusic: MusicId | null = null;
46
- private activeFades = new Set<number>();
47
- private _musicMuted: boolean;
43
+ private musicElements = new Map<MusicId, HTMLAudioElement>()
44
+ private desiredMusic: MusicId | null = null
45
+ private currentMusic: MusicId | null = null
46
+ private activeFades = new Set<number>()
47
+ private _musicMuted: boolean
48
48
 
49
49
  constructor() {
50
- this._musicMuted = loadMusicMuted();
50
+ this._musicMuted = loadMusicMuted()
51
51
  }
52
52
 
53
53
  get musicMuted(): boolean {
54
- return this._musicMuted;
54
+ return this._musicMuted
55
55
  }
56
56
 
57
57
  /** Retry queued playback after a user gesture. */
58
58
  async unlock(): Promise<void> {
59
- this.startDesiredMusic();
59
+ this.startDesiredMusic()
60
60
  }
61
61
 
62
62
  /** Start or switch to a looping soundtrack. */
63
63
  playMusic(id: MusicId, options: MusicOptions = {}): void {
64
- if (!MUSIC_FILES[id]) return;
65
- this.desiredMusic = id;
66
- this.prepareMusicElement(id);
67
- if (!this.musicMuted) this.startMusic(id, options);
64
+ if (!MUSIC_FILES[id]) return
65
+ this.desiredMusic = id
66
+ this.prepareMusicElement(id)
67
+ if (!this.musicMuted) this.startMusic(id, options)
68
68
  }
69
69
 
70
70
  setMusicMuted(muted: boolean): void {
71
- this._musicMuted = muted;
72
- writeStorage(MUSIC_MUTE_KEY, muted);
71
+ this._musicMuted = muted
72
+ writeStorage(MUSIC_MUTE_KEY, muted)
73
73
  for (const element of this.musicElements.values()) {
74
- if (muted) element.pause();
74
+ if (muted) element.pause()
75
75
  }
76
- if (!muted) this.startDesiredMusic();
76
+ if (!muted) this.startDesiredMusic()
77
77
  }
78
78
 
79
79
  toggleMusicMute(): boolean {
80
- this.setMusicMuted(!this._musicMuted);
81
- return this._musicMuted;
80
+ this.setMusicMuted(!this._musicMuted)
81
+ return this._musicMuted
82
82
  }
83
83
 
84
84
  private prepareMusicElement(id: MusicId): void {
85
- if (this.musicElements.has(id)) return;
86
- const element = new Audio(`${MUSIC_DIR}/${MUSIC_FILES[id]}`);
87
- element.loop = true;
85
+ if (this.musicElements.has(id)) return
86
+ const element = new Audio(`${MUSIC_DIR}/${MUSIC_FILES[id]}`)
87
+ element.loop = true
88
88
  // Defer the request until playback is allowed so audio never competes with
89
89
  // the scene's first visual and physics assets.
90
- element.preload = "none";
91
- this.musicElements.set(id, element);
90
+ element.preload = 'none'
91
+ this.musicElements.set(id, element)
92
92
  }
93
93
 
94
94
  private startMusic(id: MusicId, options: MusicOptions = {}): void {
95
95
  if (this.currentMusic === id) {
96
- const active = this.musicElements.get(id);
97
- if (active?.paused) void active.play().catch(() => undefined);
98
- return;
96
+ const active = this.musicElements.get(id)
97
+ if (active?.paused) void active.play().catch(() => undefined)
98
+ return
99
99
  }
100
100
 
101
- const fadeMs = options.fadeMs ?? 500;
102
- const target = clampVolume(options.volume ?? DEFAULT_MUSIC_VOLUME);
103
- const previousId = this.currentMusic;
104
- const previous = previousId ? (this.musicElements.get(previousId) ?? null) : null;
105
- this.currentMusic = id;
106
- const element = this.musicElements.get(id);
107
- if (!element) return;
108
- element.volume = 0;
109
- element.currentTime = 0;
101
+ const fadeMs = options.fadeMs ?? 500
102
+ const target = clampVolume(options.volume ?? DEFAULT_MUSIC_VOLUME)
103
+ const previousId = this.currentMusic
104
+ const previous = previousId ? (this.musicElements.get(previousId) ?? null) : null
105
+ this.currentMusic = id
106
+ const element = this.musicElements.get(id)
107
+ if (!element) return
108
+ element.volume = 0
109
+ element.currentTime = 0
110
110
  void element
111
111
  .play()
112
112
  .then(() => {
113
113
  if (this.currentMusic !== id) {
114
- element.pause();
115
- return;
114
+ element.pause()
115
+ return
116
116
  }
117
117
  if (previous) {
118
118
  this.fadeMusic(previous, 0, fadeMs, () => {
119
- previous.pause();
120
- previous.currentTime = 0;
121
- });
119
+ previous.pause()
120
+ previous.currentTime = 0
121
+ })
122
122
  }
123
- this.fadeMusic(element, target, fadeMs);
123
+ this.fadeMusic(element, target, fadeMs)
124
124
  })
125
125
  .catch(() => {
126
126
  // Autoplay is blocked before the first user gesture. Keep the desired
127
127
  // track so unlock() can retry it later.
128
- if (previous) this.currentMusic = previousId;
129
- else this.currentMusic = null;
130
- });
128
+ if (previous) this.currentMusic = previousId
129
+ else this.currentMusic = null
130
+ })
131
131
  }
132
132
 
133
133
  private startDesiredMusic(): void {
134
- if (this.musicMuted || !this.desiredMusic) return;
135
- const desired = this.desiredMusic;
134
+ if (this.musicMuted || !this.desiredMusic) return
135
+ const desired = this.desiredMusic
136
136
  if (this.currentMusic !== desired) {
137
- this.startMusic(desired);
138
- return;
137
+ this.startMusic(desired)
138
+ return
139
139
  }
140
- const element = this.musicElements.get(desired);
141
- if (element?.paused) void element.play().catch(() => undefined);
140
+ const element = this.musicElements.get(desired)
141
+ if (element?.paused) void element.play().catch(() => undefined)
142
142
  }
143
143
 
144
144
  private fadeMusic(
@@ -147,19 +147,19 @@ export class SoundController {
147
147
  durationMs: number,
148
148
  onDone?: () => void
149
149
  ): void {
150
- const start = element.volume;
151
- const startTime = performance.now();
150
+ const start = element.volume
151
+ const startTime = performance.now()
152
152
  const interval = window.setInterval(() => {
153
- const progress = Math.min(1, (performance.now() - startTime) / Math.max(durationMs, 1));
154
- element.volume = start + (target - start) * progress;
153
+ const progress = Math.min(1, (performance.now() - startTime) / Math.max(durationMs, 1))
154
+ element.volume = start + (target - start) * progress
155
155
  if (progress >= 1) {
156
- window.clearInterval(interval);
157
- this.activeFades.delete(interval);
158
- onDone?.();
156
+ window.clearInterval(interval)
157
+ this.activeFades.delete(interval)
158
+ onDone?.()
159
159
  }
160
- }, FADE_STEP_MS);
161
- this.activeFades.add(interval);
160
+ }, FADE_STEP_MS)
161
+ this.activeFades.add(interval)
162
162
  }
163
163
  }
164
164
 
165
- export const soundController = new SoundController();
165
+ export const soundController = new SoundController()
package/src/index.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  // Optional soundtrack hooks for Adea.
2
2
 
3
- export { AUDIO_BASE_URL, MUSIC_FILES, type MusicId } from "./config";
4
- export { SoundController, soundController, type MusicOptions } from "./controller";
5
- export { musicForScene, sceneMusicTracks } from "./scene-music";
3
+ export { AUDIO_BASE_URL, MUSIC_FILES, type MusicId } from './config'
4
+ export { SoundController, soundController, type MusicOptions } from './controller'
5
+ export { musicForScene, sceneMusicTracks } from './scene-music'
6
6
  export {
7
7
  MusicToggle,
8
8
  SoundProvider,
9
9
  useSceneMusic,
10
10
  useSound,
11
11
  type SoundContextValue,
12
- } from "./react";
12
+ } from './react'
package/src/react.tsx CHANGED
@@ -1,50 +1,50 @@
1
- "use client";
1
+ 'use client'
2
2
 
3
- import { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from "react";
4
- import { Music, Music2 } from "lucide-react";
5
- import { soundController, type MusicOptions } from "./controller";
6
- import { musicForScene } from "./scene-music";
7
- import type { MusicId } from "./config";
3
+ import { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from 'react'
4
+ import { Music, Music2 } from 'lucide-react'
5
+ import { soundController, type MusicOptions } from './controller'
6
+ import { musicForScene } from './scene-music'
7
+ import type { MusicId } from './config'
8
8
 
9
9
  export type SoundContextValue = {
10
- controller: typeof soundController;
11
- ready: boolean;
12
- musicMuted: boolean;
13
- toggleMusicMute: () => void;
14
- playMusic: (id: MusicId, options?: MusicOptions) => void;
15
- };
10
+ controller: typeof soundController
11
+ ready: boolean
12
+ musicMuted: boolean
13
+ toggleMusicMute: () => void
14
+ playMusic: (id: MusicId, options?: MusicOptions) => void
15
+ }
16
16
 
17
- const SoundContext = createContext<SoundContextValue | null>(null);
17
+ const SoundContext = createContext<SoundContextValue | null>(null)
18
18
 
19
19
  export function SoundProvider({ children }: { children: ReactNode }) {
20
- const [musicMuted, setMusicMuted] = useState(false);
21
- const [ready, setReady] = useState(false);
20
+ const [musicMuted, setMusicMuted] = useState(false)
21
+ const [ready, setReady] = useState(false)
22
22
 
23
23
  useEffect(() => {
24
- let mounted = true;
24
+ let mounted = true
25
25
  queueMicrotask(() => {
26
- if (mounted) setMusicMuted(soundController.musicMuted);
27
- });
26
+ if (mounted) setMusicMuted(soundController.musicMuted)
27
+ })
28
28
 
29
29
  const onGesture = () => {
30
30
  void soundController.unlock().then(() => {
31
- if (mounted) setReady(true);
32
- });
33
- };
34
- window.addEventListener("pointerdown", onGesture, { capture: true });
35
- window.addEventListener("keydown", onGesture, { capture: true });
36
- window.addEventListener("touchstart", onGesture, { capture: true });
37
- if (new URLSearchParams(window.location.search).has("debug")) {
38
- (window as unknown as { __agentHqSound?: typeof soundController }).__agentHqSound =
39
- soundController;
31
+ if (mounted) setReady(true)
32
+ })
33
+ }
34
+ window.addEventListener('pointerdown', onGesture, { capture: true })
35
+ window.addEventListener('keydown', onGesture, { capture: true })
36
+ window.addEventListener('touchstart', onGesture, { capture: true })
37
+ if (new URLSearchParams(window.location.search).has('debug')) {
38
+ ;(window as unknown as { __agentHqSound?: typeof soundController }).__agentHqSound =
39
+ soundController
40
40
  }
41
41
  return () => {
42
- mounted = false;
43
- window.removeEventListener("pointerdown", onGesture, { capture: true });
44
- window.removeEventListener("keydown", onGesture, { capture: true });
45
- window.removeEventListener("touchstart", onGesture, { capture: true });
46
- };
47
- }, []);
42
+ mounted = false
43
+ window.removeEventListener('pointerdown', onGesture, { capture: true })
44
+ window.removeEventListener('keydown', onGesture, { capture: true })
45
+ window.removeEventListener('touchstart', onGesture, { capture: true })
46
+ }
47
+ }, [])
48
48
 
49
49
  const value = useMemo<SoundContextValue>(
50
50
  () => ({
@@ -55,34 +55,34 @@ export function SoundProvider({ children }: { children: ReactNode }) {
55
55
  playMusic: (id, options) => soundController.playMusic(id, options),
56
56
  }),
57
57
  [musicMuted, ready]
58
- );
58
+ )
59
59
 
60
- return <SoundContext.Provider value={value}>{children}</SoundContext.Provider>;
60
+ return <SoundContext.Provider value={value}>{children}</SoundContext.Provider>
61
61
  }
62
62
 
63
63
  export function useSound(): SoundContextValue {
64
- const value = useContext(SoundContext);
65
- if (!value) throw new Error("useSound must be used within <SoundProvider>");
66
- return value;
64
+ const value = useContext(SoundContext)
65
+ if (!value) throw new Error('useSound must be used within <SoundProvider>')
66
+ return value
67
67
  }
68
68
 
69
69
  export function useSceneMusic(sceneId: string | null | undefined): void {
70
70
  useEffect(() => {
71
71
  const timeout = window.setTimeout(() => {
72
- soundController.playMusic(musicForScene(sceneId));
73
- }, 1500);
74
- return () => window.clearTimeout(timeout);
75
- }, [sceneId]);
72
+ soundController.playMusic(musicForScene(sceneId))
73
+ }, 1500)
74
+ return () => window.clearTimeout(timeout)
75
+ }, [sceneId])
76
76
  }
77
77
 
78
78
  function MusicButton({ muted, onToggle }: { muted: boolean; onToggle: () => void }) {
79
79
  return (
80
80
  <button
81
81
  type="button"
82
- aria-label={muted ? "Unmute music" : "Mute music"}
82
+ aria-label={muted ? 'Unmute music' : 'Mute music'}
83
83
  aria-pressed={muted}
84
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"}`}
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'}`}
86
86
  >
87
87
  {muted ? (
88
88
  <Music className="size-5" aria-hidden="true" />
@@ -90,10 +90,10 @@ function MusicButton({ muted, onToggle }: { muted: boolean; onToggle: () => void
90
90
  <Music2 className="size-5" aria-hidden="true" />
91
91
  )}
92
92
  </button>
93
- );
93
+ )
94
94
  }
95
95
 
96
96
  export function MusicToggle() {
97
- const { musicMuted, toggleMusicMute } = useSound();
98
- return <MusicButton muted={musicMuted} onToggle={toggleMusicMute} />;
97
+ const { musicMuted, toggleMusicMute } = useSound()
98
+ return <MusicButton muted={musicMuted} onToggle={toggleMusicMute} />
99
99
  }
@@ -1,11 +1,11 @@
1
- import type { MusicId } from "./config";
1
+ import type { MusicId } from './config'
2
2
 
3
3
  /** Scene route id -> optional track. Deployments can add tracks without changing scene code. */
4
4
  export const sceneMusicTracks: Record<string, MusicId> = {
5
- "hq-home": "silent",
6
- "hq-work": "silent",
7
- };
5
+ 'hq-home': 'silent',
6
+ 'hq-work': 'silent',
7
+ }
8
8
 
9
9
  export function musicForScene(sceneId: string | null | undefined): MusicId {
10
- return sceneMusicTracks[sceneId ?? ""] ?? "silent";
10
+ return sceneMusicTracks[sceneId ?? ''] ?? 'silent'
11
11
  }