@joewinke/jatui 0.28.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CLAUDE.md CHANGED
@@ -229,7 +229,7 @@ const rec = createAudioRecorder({
229
229
 
230
230
  ### Sound (first-class sibling of motion)
231
231
 
232
- One preferences store every app sound goes through. UI + notification sounds are **off by default** (opt-in); the `incoming-call` ringer is **opt-out** (plays out of the box, muted via `categories.calls`). Semantic names (`confirm`, `error`, `notify`, `message`, `send`, `toggle`, `tap`, `incoming-call`) map to bundled CC0 clips or a host's own file. Policy per category: background tab, quiet hours and the quiet/reduced-motion mode silence `ui`/`notifications`; calls always ring. Discipline doc: `docs/sound.md`.
232
+ One preferences store every app sound goes through. UI + notification sounds are **off by default** (opt-in); the `incoming-call` ringer is **opt-out** (plays out of the box, muted via `categories.calls`). Semantic names (`confirm`, `error`, `notify`, `message`, `send`, `toggle`, `tap`, `incoming-call`, plus the booking-delight set `tick` 0.03 / `select` 0.08 / `chime` 0.12 / `attention` 0.10) map to bundled CC0 clips or a host's own file (`SOUND_CLIP_VARIANTS['error-soft']` is a gentler `error`). Policy per category: background tab, quiet hours and the quiet/reduced-motion mode silence `ui`/`notifications`; calls always ring. **Throttle:** per-name `minIntervalMs` (tick 80 ms, select 120 ms) drops — never queues — a play inside the window (`blocked: 'throttled'`); per-call `minIntervalMs` overrides. **Pitch:** `playSound('tick', { pitch: pentatonicPitch(i) })` (semitones → `playbackRate`; `playbackRate` also accepted). `tick` is the one hover sound — call it only on pointer-fine devices. **Site level:** host stamps `<html data-sound="off|subtle">` (like `data-motion`; `setSoundLevel` / `resolveSoundLevel` / `soundLevelDefaults`); `subtle` only starts UI sound on for a visitor with no saved preference — a saved preference (the panel mute) always wins, and `decideSound` is unchanged. Discipline doc: `docs/sound.md`.
233
233
 
234
234
  | Component | Purpose | Key Props |
235
235
  |-----------|---------|-----------|
@@ -310,6 +310,7 @@ const callbacks: AnnotationCallbacks = {
310
310
  | Component | Purpose | Key Props |
311
311
  |-----------|---------|-----------|
312
312
  | `ThemeSelector` | DaisyUI theme switcher with preview dots | `themes`, `defaultTheme`, `dropUp`, `storageKey`, `onThemeChange` |
313
+ | `ThemeToggle` | Icon-only sun<->moon light/dark toggle with an animated morph (transform+opacity on motion tokens; instant under reduced-motion; spring flattens on `data-motion="calm"`). A pure control — flips the bindable `theme` and fires `onchange`; storage stays with the consumer. | `theme` (bindable), `lightTheme`, `darkTheme`, `onchange`, `size` |
313
314
 
314
315
  ### Keyboard Navigation
315
316
 
@@ -12,6 +12,11 @@ CC0-licensed UI sound clips sourced from Kenney's Interface Sounds pack, pre-enc
12
12
  | `toggle.ts` | toggle-001 | ~0.14s | Toggle switch or checkbox state change |
13
13
  | `send.ts` | switch-on | ~0.36s | Message sent or form submitted |
14
14
  | `pop.ts` | click-soft | ~0.007s | Subtle click or button tap |
15
+ | `tick.ts` | tick | ~0.05s | Pointer/focus crossed onto a choice (jatui-synthesized) |
16
+ | `select.ts` | select | ~0.10s | A choice was made (jatui-synthesized) |
17
+ | `chime.ts` | chime | ~0.72s | A big thing went right (jatui-synthesized) |
18
+ | `attention.ts` | attention | ~0.39s | Read this before you continue (jatui-synthesized) |
19
+ | `error-soft.ts` | error-soft | ~0.32s | Soft failure tone, variant for `error` (jatui-synthesized) |
15
20
 
16
21
  ## Provenance
17
22
 
@@ -24,6 +29,10 @@ CC0-licensed UI sound clips sourced from Kenney's Interface Sounds pack, pre-enc
24
29
  | switch-on | https://github.com/kapishdima/soundcn/blob/main/registry/soundcn/sounds/switch-on/switch-on.ts | CC0 |
25
30
  | click-soft | https://github.com/kapishdima/soundcn/blob/main/registry/soundcn/sounds/click-soft/click-soft.ts | CC0 |
26
31
 
32
+ The five jatui-synthesized clips are CC0 by construction: rendered offline from
33
+ the booking-delight prototype's WebAudio recipes by `scripts/render-sound-clips.mjs`
34
+ (WAV → MP3 @ 64kbps mono 44.1kHz → base64). Re-run it to change one.
35
+
27
36
  Original pack: **Kenney Interface Sounds** — https://kenney.nl/assets/interface-sounds (CC0 / Public Domain)
28
37
 
29
38
  ## Usage
@@ -40,6 +49,8 @@ sound player, which loads them lazily and applies the user's preferences
40
49
  | `send` | send |
41
50
  | `toggle` | toggle |
42
51
  | `tap` | pop |
52
+ | `tick`, `select`, `chime`, `attention` | the clip of the same name |
53
+ | (variant) `SOUND_CLIP_VARIANTS['error-soft']` | error-soft |
43
54
  | `incoming-call` | notify, replayed every 1.2 s (register a real ringer file instead) |
44
55
 
45
56
  ```typescript
@@ -0,0 +1 @@
1
+ export declare const attentionSound = "data:audio/mpeg;base64,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";
@@ -0,0 +1,4 @@
1
+ // Source: rendered by jatui from the booking-delight prototype's WebAudio recipe (triangle 660 -> 494 Hz, low-passed at 1.8 kHz)
2
+ // License: CC0 — synthesized, no third-party audio (jatui-98a2r)
3
+ // Read this before you continue
4
+ export const attentionSound = "data:audio/mpeg;base64,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";
@@ -0,0 +1 @@
1
+ export declare const chimeSound = "data:audio/mpeg;base64,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";
@@ -0,0 +1,4 @@
1
+ // Source: rendered by jatui from the booking-delight prototype's WebAudio recipe (C6 E6 G6 bell arpeggio, 60 ms apart, 600 ms tail)
2
+ // License: CC0 — synthesized, no third-party audio (jatui-98a2r)
3
+ // A big thing just went right (visit booked)
4
+ export const chimeSound = "data:audio/mpeg;base64,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";
@@ -0,0 +1 @@
1
+ export declare const errorSoftSound = "data:audio/mpeg;base64,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qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqr/+1LEwIOJzBMEAOTASRYXIEWBiTiqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqv/7UsS6A8YgEPpkpSAAAAA0gAAABKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq";
@@ -0,0 +1,4 @@
1
+ // Source: rendered by jatui from the booking-delight prototype's WebAudio recipe (sine 392 -> 330 Hz, 120 ms each)
2
+ // License: CC0 — synthesized, no third-party audio (jatui-98a2r)
3
+ // Soft failure tone (alternative to the error buzz)
4
+ export const errorSoftSound = "data:audio/mpeg;base64,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qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqr/+1LEwIOJzBMEAOTASRYXIEWBiTiqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqv/7UsS6A8YgEPpkpSAAAAA0gAAABKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq";
@@ -0,0 +1 @@
1
+ export declare const selectSound = "data:audio/mpeg;base64,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";
@@ -0,0 +1,4 @@
1
+ // Source: rendered by jatui from the booking-delight prototype's WebAudio recipe (90 ms marimba pluck, 880 Hz + 2.76f partial)
2
+ // License: CC0 — synthesized, no third-party audio (jatui-98a2r)
3
+ // A choice was made
4
+ export const selectSound = "data:audio/mpeg;base64,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";
@@ -0,0 +1 @@
1
+ export declare const tickSound = "data:audio/mpeg;base64,SUQzBAAAAAAAIlRTU0UAAAAOAAADTGF2ZjYzLjEuMTAxAAAAAAAAAAAAAAD/+1DAAAAAAAAAAAAAAAAAAAAAAABJbmZvAAAADwAAAAMAAANCAH9/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v////////////////////////////////////////////wAAAABMYXZjNjMuMS4AAAAAAAAAAAAAAAAkAl8AAAAAAAADQpf8lC4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//tQxAAACRRO2hWMAAGQFaoDN1AAh+/EH8hy7DbK0JYKA1ELhMpwcNhRgMAmMBMyBICIZlOCjteBgDEBNSCncfx/Icqw2/8/Xt9qWGAfPggGSgIFw//5SJ/8p//8nlVQoBIC+0vl8TCwAYWLS2CICYIZIPnIt6cRpy8GCCBga4BgGJwKBhoJhzlFdADAwEC+QeiOE4Tyewo4t4uQh5GmJ5JvIKSRBCgWVLUp/z58uGZqfRX/80FTYJsX+GwgTAn/oNLKmtdxdEuCqQIAGAoChv/7UsQHAQqUPQ4d7oABYYahpD5o2ACgNmDoGOYfAd5g+BWGQidsa2MFx7E/3HSi1EeZReYnL8b0v0bfq8Z6kMYLhSYnCeYaAWDQQEgFR6WsBg6VHhr0f//3s/99AEqytefFDSl/XeZamEAgCYREhjSEn4seZaThurx/RRqCJd5lL7TNYmZGlnot4s2LIuKEFbdTXsWeytBpCosZmUrpRdctsDLpuWKSjvFzYxT10GbCwslNSwSPPtM+7diZo+A4WQpTsAkES25sgadIAggIRD2Y//tSxA6BC9mfACNgSoEkhd+kN6SgjEIR3TRERVBfIra7VarGMzKnstDOrysUznrt6p93aqqQzys6l5dfUrYIK7c9B/M9dKqvXIH3Vxylm/+pTJ8SLvgfQAC/Zh6qFevWF8+fbyOUDCchCgRZVQ4WBoFQVdEIKlXSp3grBWSWAQ0s6s7sBUFeJQVzpUFiuCv/4iEwNB2Jfg11B2s7/9R4RUxBTUU0LjBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU=";
@@ -0,0 +1,4 @@
1
+ // Source: rendered by jatui from the booking-delight prototype's WebAudio recipe (35 ms sine blip at 1318.5 Hz, 8% pitch drop)
2
+ // License: CC0 — synthesized, no third-party audio (jatui-98a2r)
3
+ // Pointer or focus crossed onto a choice
4
+ export const tickSound = "data:audio/mpeg;base64,SUQzBAAAAAAAIlRTU0UAAAAOAAADTGF2ZjYzLjEuMTAxAAAAAAAAAAAAAAD/+1DAAAAAAAAAAAAAAAAAAAAAAABJbmZvAAAADwAAAAMAAANCAH9/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v////////////////////////////////////////////wAAAABMYXZjNjMuMS4AAAAAAAAAAAAAAAAkAl8AAAAAAAADQpf8lC4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//tQxAAACRRO2hWMAAGQFaoDN1AAh+/EH8hy7DbK0JYKA1ELhMpwcNhRgMAmMBMyBICIZlOCjteBgDEBNSCncfx/Icqw2/8/Xt9qWGAfPggGSgIFw//5SJ/8p//8nlVQoBIC+0vl8TCwAYWLS2CICYIZIPnIt6cRpy8GCCBga4BgGJwKBhoJhzlFdADAwEC+QeiOE4Tyewo4t4uQh5GmJ5JvIKSRBCgWVLUp/z58uGZqfRX/80FTYJsX+GwgTAn/oNLKmtdxdEuCqQIAGAoChv/7UsQHAQqUPQ4d7oABYYahpD5o2ACgNmDoGOYfAd5g+BWGQidsa2MFx7E/3HSi1EeZReYnL8b0v0bfq8Z6kMYLhSYnCeYaAWDQQEgFR6WsBg6VHhr0f//3s/99AEqytefFDSl/XeZamEAgCYREhjSEn4seZaThurx/RRqCJd5lL7TNYmZGlnot4s2LIuKEFbdTXsWeytBpCosZmUrpRdctsDLpuWKSjvFzYxT10GbCwslNSwSPPtM+7diZo+A4WQpTsAkES25sgadIAggIRD2Y//tSxA6BC9mfACNgSoEkhd+kN6SgjEIR3TRERVBfIra7VarGMzKnstDOrysUznrt6p93aqqQzys6l5dfUrYIK7c9B/M9dKqvXIH3Vxylm/+pTJ8SLvgfQAC/Zh6qFevWF8+fbyOUDCchCgRZVQ4WBoFQVdEIKlXSp3grBWSWAQ0s6s7sBUFeJQVzpUFiuCv/4iEwNB2Jfg11B2s7/9R4RUxBTUU0LjBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU=";
@@ -0,0 +1,158 @@
1
+ <script lang="ts">
2
+ /**
3
+ * ThemeToggle — icon-only light/dark switch with an animated sun<->moon morph
4
+ * (mos-p6o2d). The public-site counterpart to ThemeSelector: where a site
5
+ * offers exactly two themes, a dropdown with visible "Dark mode" text is
6
+ * chrome noise — this is a single icon button instead.
7
+ *
8
+ * The component is a CONTROL, not a theme manager: it flips the bindable
9
+ * `theme` between `lightTheme` and `darkTheme` and fires `onchange`. It does
10
+ * not touch localStorage, `document.documentElement` or any storage key —
11
+ * persistence, pre-paint system-preference behaviour and DB theme overrides
12
+ * stay with the consumer (see JST's PublicThemeToggle wrapper, which owns
13
+ * the exact storage contract ThemeSelector established).
14
+ *
15
+ * Motion: the morph uses transform + opacity only, driven by the jatui
16
+ * motion tokens (`--motion-fast` duration, spring for the settle, glide for
17
+ * the cross-fade). Honours `data-motion="calm"` (tokens re-declared there —
18
+ * springs flatten) and `prefers-reduced-motion` (instant swap, both via the
19
+ * motion.css global floor and a component-local guard, since not every
20
+ * consumer loads motion.css). Colours are token-only — no literals.
21
+ */
22
+ let {
23
+ theme = $bindable('light'),
24
+ lightTheme = 'light',
25
+ darkTheme = 'dark',
26
+ onchange,
27
+ size = 'md'
28
+ }: {
29
+ /** Current theme name (bindable). Anything that is not `darkTheme` reads as light. */
30
+ theme?: string;
31
+ /** Theme name considered "light" (sun shown; the target when dark). */
32
+ lightTheme?: string;
33
+ /** Theme name considered "dark" (moon shown; the target when light). */
34
+ darkTheme?: string;
35
+ /** Fired after the toggle flips the bound theme. */
36
+ onchange?: (theme: string) => void;
37
+ /** Hit-target size — sm 40px, md 44px, lg 48px. Never below the 40px touch floor. */
38
+ size?: 'sm' | 'md' | 'lg';
39
+ } = $props();
40
+
41
+ let isDark = $derived(theme === darkTheme);
42
+
43
+ const BUTTON_SIZES = {
44
+ sm: 'size-10',
45
+ md: 'size-11',
46
+ lg: 'size-12'
47
+ } as const;
48
+
49
+ const ICON_SIZES = { sm: 18, md: 20, lg: 24 } as const;
50
+
51
+ const label = $derived(isDark ? 'Switch to light mode' : 'Switch to dark mode');
52
+
53
+ function toggle() {
54
+ const next = isDark ? lightTheme : darkTheme;
55
+ theme = next;
56
+ onchange?.(next);
57
+ }
58
+ </script>
59
+
60
+ <button
61
+ type="button"
62
+ class="jatui-theme-toggle inline-flex shrink-0 items-center justify-center rounded-full
63
+ text-base-content transition-colors duration-[var(--motion-instant,80ms)]
64
+ focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary
65
+ hover:bg-base-content/10 focus-visible:bg-base-content/10
66
+ active:bg-base-content/15 disabled:pointer-events-none disabled:opacity-50
67
+ {BUTTON_SIZES[size]}"
68
+ class:is-dark={isDark}
69
+ aria-label={label}
70
+ aria-pressed={isDark}
71
+ title={label}
72
+ onclick={toggle}
73
+ >
74
+ <span
75
+ class="glyphs relative block"
76
+ style="width: {ICON_SIZES[size]}px; height: {ICON_SIZES[size]}px"
77
+ aria-hidden="true"
78
+ >
79
+ <!-- Sun: ring + eight rays, lucide geometry. -->
80
+ <svg
81
+ xmlns="http://www.w3.org/2000/svg"
82
+ viewBox="0 0 24 24"
83
+ fill="none"
84
+ stroke="currentColor"
85
+ stroke-width="2"
86
+ stroke-linecap="round"
87
+ stroke-linejoin="round"
88
+ class="glyph glyph-sun absolute inset-0"
89
+ width={ICON_SIZES[size]}
90
+ height={ICON_SIZES[size]}
91
+ >
92
+ <circle cx="12" cy="12" r="4" />
93
+ <path d="M12 2v2" />
94
+ <path d="M12 20v2" />
95
+ <path d="m4.93 4.93 1.41 1.41" />
96
+ <path d="m17.66 17.66 1.41 1.41" />
97
+ <path d="M2 12h2" />
98
+ <path d="M20 12h2" />
99
+ <path d="m6.34 17.66-1.41 1.41" />
100
+ <path d="m19.07 4.93-1.41 1.41" />
101
+ </svg>
102
+ <!-- Moon: crescent, lucide geometry. -->
103
+ <svg
104
+ xmlns="http://www.w3.org/2000/svg"
105
+ viewBox="0 0 24 24"
106
+ fill="none"
107
+ stroke="currentColor"
108
+ stroke-width="2"
109
+ stroke-linecap="round"
110
+ stroke-linejoin="round"
111
+ class="glyph glyph-moon absolute inset-0"
112
+ width={ICON_SIZES[size]}
113
+ height={ICON_SIZES[size]}
114
+ >
115
+ <path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" />
116
+ </svg>
117
+ </span>
118
+ </button>
119
+
120
+ <style>
121
+ /*
122
+ * Morph: transform + opacity ONLY (compositor-friendly, no layout thrash).
123
+ * The outgoing glyph rotates away and shrinks while fading; the incoming
124
+ * one rotates in. Spring carries the small-element settle; glide the fade.
125
+ */
126
+ .glyph {
127
+ transition:
128
+ transform var(--motion-fast, 150ms) var(--motion-spring, cubic-bezier(0.34, 1.56, 0.64, 1)),
129
+ opacity var(--motion-fast, 150ms) var(--motion-glide, cubic-bezier(0.16, 1, 0.3, 1));
130
+ }
131
+
132
+ .glyph-sun {
133
+ opacity: 1;
134
+ transform: rotate(0deg) scale(1);
135
+ }
136
+
137
+ .glyph-moon {
138
+ opacity: 0;
139
+ transform: rotate(-90deg) scale(0.5);
140
+ }
141
+
142
+ .is-dark .glyph-sun {
143
+ opacity: 0;
144
+ transform: rotate(90deg) scale(0.5);
145
+ }
146
+
147
+ .is-dark .glyph-moon {
148
+ opacity: 1;
149
+ transform: rotate(0deg) scale(1);
150
+ }
151
+
152
+ /* Component-local reduced-motion guard — not every consumer loads motion.css. */
153
+ @media (prefers-reduced-motion: reduce) {
154
+ .glyph {
155
+ transition: none;
156
+ }
157
+ }
158
+ </style>
@@ -0,0 +1,15 @@
1
+ type $$ComponentProps = {
2
+ /** Current theme name (bindable). Anything that is not `darkTheme` reads as light. */
3
+ theme?: string;
4
+ /** Theme name considered "light" (sun shown; the target when dark). */
5
+ lightTheme?: string;
6
+ /** Theme name considered "dark" (moon shown; the target when light). */
7
+ darkTheme?: string;
8
+ /** Fired after the toggle flips the bound theme. */
9
+ onchange?: (theme: string) => void;
10
+ /** Hit-target size — sm 40px, md 44px, lg 48px. Never below the 40px touch floor. */
11
+ size?: 'sm' | 'md' | 'lg';
12
+ };
13
+ declare const ThemeToggle: import("svelte").Component<$$ComponentProps, {}, "theme">;
14
+ type ThemeToggle = ReturnType<typeof ThemeToggle>;
15
+ export default ThemeToggle;
package/dist/index.d.ts CHANGED
@@ -93,6 +93,7 @@ export type { AudioRecordingResult } from './utils/audioRecorderCore';
93
93
  export { createAudioRecorder } from './utils/audioRecorder';
94
94
  export type { AudioRecorder, AudioRecorderCore, AudioRecorderError, AudioRecorderErrorKind, AudioRecorderFrame, AudioRecorderMode, AudioRecorderOptions, AudioRecorderPhase, AudioRecorderSnapshot } from './utils/audioRecorderCore';
95
95
  export { default as ThemeSelector } from './components/ThemeSelector.svelte';
96
+ export { default as ThemeToggle } from './components/ThemeToggle.svelte';
96
97
  export { default as Sparkline } from './components/Sparkline.svelte';
97
98
  export { default as BurndownChart } from './components/BurndownChart.svelte';
98
99
  export { default as AvatarUpload } from './components/AvatarUpload.svelte';
package/dist/index.js CHANGED
@@ -127,6 +127,7 @@ export { default as AudioRecorder } from './components/AudioRecorder.svelte';
127
127
  export { createAudioRecorder } from './utils/audioRecorder';
128
128
  // Components — from Headcount
129
129
  export { default as ThemeSelector } from './components/ThemeSelector.svelte';
130
+ export { default as ThemeToggle } from './components/ThemeToggle.svelte';
130
131
  export { default as Sparkline } from './components/Sparkline.svelte';
131
132
  export { default as BurndownChart } from './components/BurndownChart.svelte';
132
133
  // Components — from JST
@@ -11,6 +11,8 @@ export interface StartOptions {
11
11
  loop: boolean;
12
12
  /** Gap between repeats when looping; 0 = seamless. */
13
13
  loopGapMs: number;
14
+ /** Playback rate (1 = as recorded; 2 = an octave up and twice as short). */
15
+ playbackRate?: number;
14
16
  /** Called once when a non-looping voice finishes on its own. */
15
17
  onEnded?: () => void;
16
18
  }
@@ -40,7 +40,7 @@ export function createWebAudioBackend() {
40
40
  return null; // codec unsupported / network error — stay silent
41
41
  }
42
42
  },
43
- start(clip, { gain, loop, loopGapMs, onEnded }) {
43
+ start(clip, { gain, loop, loopGapMs, playbackRate = 1, onEnded }) {
44
44
  const audio = context();
45
45
  if (!audio || !clip)
46
46
  return null;
@@ -58,6 +58,7 @@ export function createWebAudioBackend() {
58
58
  return;
59
59
  source = audio.createBufferSource();
60
60
  source.buffer = clip;
61
+ source.playbackRate.value = playbackRate;
61
62
  source.loop = loop && loopGapMs <= 0;
62
63
  source.connect(out);
63
64
  source.onended = () => {
@@ -5,13 +5,15 @@
5
5
  * mapped to bundled CC0 clips (or a host's own files), a pure policy function,
6
6
  * and a drop-in SoundSettings section. Discipline: docs/sound.md.
7
7
  */
8
- export { SOUND_CATEGORIES, SOUND_NAMES, SOUND_SPECS, CUSTOM_SOUND_SPEC, BUILTIN_CLIPS, isSoundName, soundSpecFor } from './names';
8
+ export { SOUND_CATEGORIES, SOUND_NAMES, SOUND_SPECS, CUSTOM_SOUND_SPEC, BUILTIN_CLIPS, SOUND_CLIP_VARIANTS, PENTATONIC_STEPS, isSoundName, soundSpecFor, pentatonicPitch } from './names';
9
9
  export type { SoundCategory, SoundName, SoundSpec } from './names';
10
10
  export { DEFAULT_SOUND_PREFERENCES, SOUND_PREFS_STORAGE_KEY, LEGACY_SOUND_ENABLED_KEY, createSoundPreferences, getSoundPreferences, setSoundPreferences, normalizeSoundPreferences } from './preferences';
11
11
  export type { SoundPreferences, SoundPreferencesPatch, SoundPreferencesStore, SoundPreferencesStorage, SoundQuietMode, QuietHours, CreateSoundPreferencesOptions } from './preferences';
12
+ export { SOUND_LEVELS, DEFAULT_SOUND_LEVEL, SOUND_LEVEL_ATTR, isSoundLevel, parseSoundLevel, resolveSoundLevel, setSoundLevel, soundLevelDefaults } from './level';
13
+ export type { SoundLevel } from './level';
12
14
  export { decideSound, inQuietHours, readSoundEnvironment } from './policy';
13
15
  export type { SoundEnvironment, SoundRequest, SoundDecision, SoundBlockReason } from './policy';
14
- export { createSoundPlayer, getSoundPlayer, setSoundPlayer, playSound } from './player';
16
+ export { createSoundPlayer, getSoundPlayer, setSoundPlayer, playSound, rateFor as soundPlaybackRate } from './player';
15
17
  export type { SoundId, SoundPlayer, SoundHandle, PlaySoundOptions, CreateSoundPlayerOptions } from './player';
16
18
  export { createWebAudioBackend } from './backend';
17
19
  export type { SoundBackend, SoundVoice, StartOptions as SoundStartOptions } from './backend';
@@ -5,9 +5,10 @@
5
5
  * mapped to bundled CC0 clips (or a host's own files), a pure policy function,
6
6
  * and a drop-in SoundSettings section. Discipline: docs/sound.md.
7
7
  */
8
- export { SOUND_CATEGORIES, SOUND_NAMES, SOUND_SPECS, CUSTOM_SOUND_SPEC, BUILTIN_CLIPS, isSoundName, soundSpecFor } from './names';
8
+ export { SOUND_CATEGORIES, SOUND_NAMES, SOUND_SPECS, CUSTOM_SOUND_SPEC, BUILTIN_CLIPS, SOUND_CLIP_VARIANTS, PENTATONIC_STEPS, isSoundName, soundSpecFor, pentatonicPitch } from './names';
9
9
  export { DEFAULT_SOUND_PREFERENCES, SOUND_PREFS_STORAGE_KEY, LEGACY_SOUND_ENABLED_KEY, createSoundPreferences, getSoundPreferences, setSoundPreferences, normalizeSoundPreferences } from './preferences';
10
+ export { SOUND_LEVELS, DEFAULT_SOUND_LEVEL, SOUND_LEVEL_ATTR, isSoundLevel, parseSoundLevel, resolveSoundLevel, setSoundLevel, soundLevelDefaults } from './level';
10
11
  export { decideSound, inQuietHours, readSoundEnvironment } from './policy';
11
- export { createSoundPlayer, getSoundPlayer, setSoundPlayer, playSound } from './player';
12
+ export { createSoundPlayer, getSoundPlayer, setSoundPlayer, playSound, rateFor as soundPlaybackRate } from './player';
12
13
  export { createWebAudioBackend } from './backend';
13
14
  export { default as SoundSettings } from './SoundSettings.svelte';
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Site sound level — the host's say in whether UI sound starts on.
3
+ *
4
+ * A host stamps `data-sound="off|subtle"` on `<html>` (or any subtree) the
5
+ * same way it stamps `data-motion`, usually from per-site config. The level
6
+ * only changes the DEFAULT for a visitor with no saved preference:
7
+ *
8
+ * off the library default — UI + notification sounds start off.
9
+ * subtle they start on (`enabled: true`) for a visitor who never chose.
10
+ *
11
+ * A saved visitor preference (the panel mute, SoundSettings) always wins,
12
+ * because `createSoundPreferences` applies `defaults` only when nothing is
13
+ * saved. The level never touches volume, categories, quiet hours or the
14
+ * `decideSound` policy — hidden tabs, quiet hours and `quiet: auto` (reduced
15
+ * motion) still silence what they silence.
16
+ */
17
+ import type { SoundPreferencesPatch } from './preferences';
18
+ export type SoundLevel = 'off' | 'subtle';
19
+ export declare const SOUND_LEVELS: readonly SoundLevel[];
20
+ export declare const DEFAULT_SOUND_LEVEL: SoundLevel;
21
+ /** The attribute a host sets on `<html>` or any subtree. */
22
+ export declare const SOUND_LEVEL_ATTR = "data-sound";
23
+ export declare function isSoundLevel(value: unknown): value is SoundLevel;
24
+ /** Coerce any attribute/config value to a level; unknown values become `off`. */
25
+ export declare function parseSoundLevel(value: unknown, fallback?: SoundLevel): SoundLevel;
26
+ /**
27
+ * The level in force for `node`: its nearest `[data-sound]` ancestor
28
+ * (inclusive), else `<html>`'s, else `off`. An unknown value counts as `off`
29
+ * — a typo fails silent, never loud. SSR-safe (`off`).
30
+ */
31
+ export declare function resolveSoundLevel(node?: Element | null): SoundLevel;
32
+ /**
33
+ * Set the level on a surface (default `<html>`). `null` removes the attribute.
34
+ * No-op without a DOM. Set it before the first sound plays: the shared
35
+ * preferences store reads the level once, when it is created.
36
+ */
37
+ export declare function setSoundLevel(level: SoundLevel | null, target?: Element | null): void;
38
+ /**
39
+ * The preference defaults a level implies, for `createSoundPreferences({ defaults })`.
40
+ * A panel scoping its own store to a subtree passes `soundLevelDefaults(resolveSoundLevel(panelEl))`.
41
+ */
42
+ export declare function soundLevelDefaults(level: SoundLevel): SoundPreferencesPatch;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Site sound level — the host's say in whether UI sound starts on.
3
+ *
4
+ * A host stamps `data-sound="off|subtle"` on `<html>` (or any subtree) the
5
+ * same way it stamps `data-motion`, usually from per-site config. The level
6
+ * only changes the DEFAULT for a visitor with no saved preference:
7
+ *
8
+ * off the library default — UI + notification sounds start off.
9
+ * subtle they start on (`enabled: true`) for a visitor who never chose.
10
+ *
11
+ * A saved visitor preference (the panel mute, SoundSettings) always wins,
12
+ * because `createSoundPreferences` applies `defaults` only when nothing is
13
+ * saved. The level never touches volume, categories, quiet hours or the
14
+ * `decideSound` policy — hidden tabs, quiet hours and `quiet: auto` (reduced
15
+ * motion) still silence what they silence.
16
+ */
17
+ export const SOUND_LEVELS = ['off', 'subtle'];
18
+ export const DEFAULT_SOUND_LEVEL = 'off';
19
+ /** The attribute a host sets on `<html>` or any subtree. */
20
+ export const SOUND_LEVEL_ATTR = 'data-sound';
21
+ export function isSoundLevel(value) {
22
+ return typeof value === 'string' && SOUND_LEVELS.includes(value);
23
+ }
24
+ /** Coerce any attribute/config value to a level; unknown values become `off`. */
25
+ export function parseSoundLevel(value, fallback = DEFAULT_SOUND_LEVEL) {
26
+ if (typeof value !== 'string')
27
+ return fallback;
28
+ const v = value.trim().toLowerCase();
29
+ return isSoundLevel(v) ? v : fallback;
30
+ }
31
+ /**
32
+ * The level in force for `node`: its nearest `[data-sound]` ancestor
33
+ * (inclusive), else `<html>`'s, else `off`. An unknown value counts as `off`
34
+ * — a typo fails silent, never loud. SSR-safe (`off`).
35
+ */
36
+ export function resolveSoundLevel(node) {
37
+ const el = (node ?? (typeof document !== 'undefined' ? document.documentElement : null));
38
+ if (!el || typeof el.closest !== 'function')
39
+ return DEFAULT_SOUND_LEVEL;
40
+ const host = el.closest(`[${SOUND_LEVEL_ATTR}]`);
41
+ return host ? parseSoundLevel(host.getAttribute(SOUND_LEVEL_ATTR)) : DEFAULT_SOUND_LEVEL;
42
+ }
43
+ /**
44
+ * Set the level on a surface (default `<html>`). `null` removes the attribute.
45
+ * No-op without a DOM. Set it before the first sound plays: the shared
46
+ * preferences store reads the level once, when it is created.
47
+ */
48
+ export function setSoundLevel(level, target) {
49
+ const el = target ?? (typeof document !== 'undefined' ? document.documentElement : null);
50
+ if (!el)
51
+ return;
52
+ if (level === null)
53
+ el.removeAttribute(SOUND_LEVEL_ATTR);
54
+ else
55
+ el.setAttribute(SOUND_LEVEL_ATTR, level);
56
+ }
57
+ /**
58
+ * The preference defaults a level implies, for `createSoundPreferences({ defaults })`.
59
+ * A panel scoping its own store to a subtree passes `soundLevelDefaults(resolveSoundLevel(panelEl))`.
60
+ */
61
+ export function soundLevelDefaults(level) {
62
+ return level === 'subtle' ? { enabled: true } : {};
63
+ }
@@ -10,7 +10,7 @@
10
10
  */
11
11
  export type SoundCategory = 'ui' | 'notifications' | 'calls';
12
12
  export declare const SOUND_CATEGORIES: readonly SoundCategory[];
13
- export type SoundName = 'confirm' | 'error' | 'notify' | 'message' | 'send' | 'toggle' | 'tap' | 'incoming-call';
13
+ export type SoundName = 'confirm' | 'error' | 'notify' | 'message' | 'send' | 'toggle' | 'tap' | 'tick' | 'select' | 'chime' | 'attention' | 'incoming-call';
14
14
  export declare const SOUND_NAMES: readonly SoundName[];
15
15
  export interface SoundSpec {
16
16
  category: SoundCategory;
@@ -25,6 +25,11 @@ export interface SoundSpec {
25
25
  * ringer file); >0 replays a short clip on an interval (the built-in ring).
26
26
  */
27
27
  loopGapMs: number;
28
+ /**
29
+ * Throttle floor in ms: a play within this long of the last one that
30
+ * actually sounded is DROPPED (never queued). 0 = no throttle.
31
+ */
32
+ minIntervalMs: number;
28
33
  description: string;
29
34
  }
30
35
  export declare const SOUND_SPECS: Record<SoundName, SoundSpec>;
@@ -37,3 +42,21 @@ export declare function soundSpecFor(name: string): SoundSpec;
37
42
  * that never play a sound never ship the base64 payloads in their entry chunk.
38
43
  */
39
44
  export declare const BUILTIN_CLIPS: Record<SoundName, () => Promise<string>>;
45
+ /**
46
+ * Alternative bundled clips a host can point a name at:
47
+ *
48
+ * await getSoundPlayer().register('error', await SOUND_CLIP_VARIANTS['error-soft'](), { volume: 0.08 })
49
+ *
50
+ * `error-soft` is two low sine tones (392 → 330 Hz) with no buzz — for
51
+ * surfaces where the stock error buzz is too harsh (booking flows).
52
+ */
53
+ export declare const SOUND_CLIP_VARIANTS: {
54
+ 'error-soft': () => Promise<string>;
55
+ };
56
+ /** Semitone offsets of a rising major pentatonic — one per step of a 7-item strip. */
57
+ export declare const PENTATONIC_STEPS: readonly number[];
58
+ /**
59
+ * Semitone offset for item `index` on a rising pentatonic (wraps up an octave
60
+ * past the table), for `playSound('tick', { pitch: pentatonicPitch(i) })`.
61
+ */
62
+ export declare function pentatonicPitch(index: number): number;
@@ -17,6 +17,10 @@ export const SOUND_NAMES = [
17
17
  'send',
18
18
  'toggle',
19
19
  'tap',
20
+ 'tick',
21
+ 'select',
22
+ 'chime',
23
+ 'attention',
20
24
  'incoming-call'
21
25
  ];
22
26
  export const SOUND_SPECS = {
@@ -26,6 +30,7 @@ export const SOUND_SPECS = {
26
30
  essential: false,
27
31
  loop: false,
28
32
  loopGapMs: 0,
33
+ minIntervalMs: 0,
29
34
  description: 'An important action succeeded (saved, paid, published)'
30
35
  },
31
36
  error: {
@@ -34,6 +39,7 @@ export const SOUND_SPECS = {
34
39
  essential: true,
35
40
  loop: false,
36
41
  loopGapMs: 0,
42
+ minIntervalMs: 0,
37
43
  description: 'An action failed'
38
44
  },
39
45
  notify: {
@@ -42,6 +48,7 @@ export const SOUND_SPECS = {
42
48
  essential: false,
43
49
  loop: false,
44
50
  loopGapMs: 0,
51
+ minIntervalMs: 0,
45
52
  description: 'Something happened the user did not cause'
46
53
  },
47
54
  message: {
@@ -50,6 +57,7 @@ export const SOUND_SPECS = {
50
57
  essential: false,
51
58
  loop: false,
52
59
  loopGapMs: 0,
60
+ minIntervalMs: 0,
53
61
  description: 'A message arrived from another person'
54
62
  },
55
63
  send: {
@@ -58,6 +66,7 @@ export const SOUND_SPECS = {
58
66
  essential: false,
59
67
  loop: false,
60
68
  loopGapMs: 0,
69
+ minIntervalMs: 0,
61
70
  description: 'The user sent a message or submitted a form'
62
71
  },
63
72
  toggle: {
@@ -66,6 +75,7 @@ export const SOUND_SPECS = {
66
75
  essential: false,
67
76
  loop: false,
68
77
  loopGapMs: 0,
78
+ minIntervalMs: 0,
69
79
  description: 'A switch changed state'
70
80
  },
71
81
  tap: {
@@ -74,14 +84,52 @@ export const SOUND_SPECS = {
74
84
  essential: false,
75
85
  loop: false,
76
86
  loopGapMs: 0,
87
+ minIntervalMs: 0,
77
88
  description: 'A deliberate, low-frequency press'
78
89
  },
90
+ tick: {
91
+ category: 'ui',
92
+ volume: 0.03,
93
+ essential: false,
94
+ loop: false,
95
+ loopGapMs: 0,
96
+ minIntervalMs: 80,
97
+ description: 'The pointer or keyboard focus crossed onto a choice (pointer-fine only, pitch by position)'
98
+ },
99
+ select: {
100
+ category: 'ui',
101
+ volume: 0.08,
102
+ essential: false,
103
+ loop: false,
104
+ loopGapMs: 0,
105
+ minIntervalMs: 120,
106
+ description: 'The user chose something (a card, a day, a time, an answer)'
107
+ },
108
+ chime: {
109
+ category: 'ui',
110
+ volume: 0.12,
111
+ essential: false,
112
+ loop: false,
113
+ loopGapMs: 0,
114
+ minIntervalMs: 0,
115
+ description: 'A big thing just went right (the visit is booked) — rarer and warmer than confirm'
116
+ },
117
+ attention: {
118
+ category: 'ui',
119
+ volume: 0.1,
120
+ essential: false,
121
+ loop: false,
122
+ loopGapMs: 0,
123
+ minIntervalMs: 0,
124
+ description: 'Read this before you continue — calm, never an alarm'
125
+ },
79
126
  'incoming-call': {
80
127
  category: 'calls',
81
128
  volume: 0.5,
82
129
  essential: true,
83
130
  loop: true,
84
131
  loopGapMs: 1200,
132
+ minIntervalMs: 0,
85
133
  description: 'Someone is calling — rings until answered or declined'
86
134
  }
87
135
  };
@@ -92,6 +140,7 @@ export const CUSTOM_SOUND_SPEC = {
92
140
  essential: false,
93
141
  loop: false,
94
142
  loopGapMs: 0,
143
+ minIntervalMs: 0,
95
144
  description: 'Host-registered sound'
96
145
  };
97
146
  /** Pre-semantic clip names (utils/sound.ts era) mapped to their semantic spec. */
@@ -117,5 +166,31 @@ export const BUILTIN_CLIPS = {
117
166
  send: () => import('../assets/sounds/send').then((m) => m.sendSound),
118
167
  toggle: () => import('../assets/sounds/toggle').then((m) => m.toggleSound),
119
168
  tap: () => import('../assets/sounds/pop').then((m) => m.popSound),
169
+ tick: () => import('../assets/sounds/tick').then((m) => m.tickSound),
170
+ select: () => import('../assets/sounds/select').then((m) => m.selectSound),
171
+ chime: () => import('../assets/sounds/chime').then((m) => m.chimeSound),
172
+ attention: () => import('../assets/sounds/attention').then((m) => m.attentionSound),
120
173
  'incoming-call': () => import('../assets/sounds/notify').then((m) => m.notifySound)
121
174
  };
175
+ /**
176
+ * Alternative bundled clips a host can point a name at:
177
+ *
178
+ * await getSoundPlayer().register('error', await SOUND_CLIP_VARIANTS['error-soft'](), { volume: 0.08 })
179
+ *
180
+ * `error-soft` is two low sine tones (392 → 330 Hz) with no buzz — for
181
+ * surfaces where the stock error buzz is too harsh (booking flows).
182
+ */
183
+ export const SOUND_CLIP_VARIANTS = {
184
+ 'error-soft': () => import('../assets/sounds/error-soft').then((m) => m.errorSoftSound)
185
+ };
186
+ /** Semitone offsets of a rising major pentatonic — one per step of a 7-item strip. */
187
+ export const PENTATONIC_STEPS = [0, 2, 4, 7, 9, 12, 14];
188
+ /**
189
+ * Semitone offset for item `index` on a rising pentatonic (wraps up an octave
190
+ * past the table), for `playSound('tick', { pitch: pentatonicPitch(i) })`.
191
+ */
192
+ export function pentatonicPitch(index) {
193
+ const i = Math.max(0, Math.floor(index));
194
+ const n = 5; // notes per octave — the table's last two entries are the next octave
195
+ return PENTATONIC_STEPS[i % n] + 12 * Math.floor(i / n);
196
+ }
@@ -24,9 +24,18 @@ export interface PlaySoundOptions {
24
24
  category?: SoundCategory;
25
25
  essential?: boolean;
26
26
  loop?: boolean;
27
+ /**
28
+ * Pitch offset in semitones, applied through `playbackRate` (so it also
29
+ * shortens/lengthens the clip). `pentatonicPitch(i)` gives a rising scale.
30
+ */
31
+ pitch?: number;
32
+ /** Explicit playback rate; multiplied with `pitch` when both are given. */
33
+ playbackRate?: number;
34
+ /** Throttle floor in ms for this play, replacing the spec's `minIntervalMs`. */
35
+ minIntervalMs?: number;
27
36
  /**
28
37
  * Explicit user test (a "Preview" button in settings): bypasses every gate
29
- * except master volume. Never use it for app-triggered sounds.
38
+ * except master volume (and the throttle). Never use it for app-triggered sounds.
30
39
  */
31
40
  preview?: boolean;
32
41
  }
@@ -50,8 +59,12 @@ export interface CreateSoundPlayerOptions {
50
59
  preferences?: SoundPreferencesStore;
51
60
  environment?: () => SoundEnvironment;
52
61
  backend?: SoundBackend;
62
+ /** Monotonic ms clock for the throttle (test seam). Defaults to `performance.now`. */
63
+ now?: () => number;
53
64
  }
54
65
  export declare function createSoundPlayer(options?: CreateSoundPlayerOptions): SoundPlayer;
66
+ /** Effective playback rate for a play: `playbackRate × 2^(pitch/12)`, clamped to a sane range. */
67
+ export declare function rateFor(opts: Pick<PlaySoundOptions, 'pitch' | 'playbackRate'>): number;
55
68
  /** The app-wide player, bound to the shared preferences store. */
56
69
  export declare function getSoundPlayer(): SoundPlayer;
57
70
  /** Test seam: replace (or with no argument, drop) the shared player. */
@@ -22,6 +22,9 @@ export function createSoundPlayer(options = {}) {
22
22
  let backend = options.backend ?? null;
23
23
  const entries = new Map();
24
24
  const active = new Set();
25
+ const now = options.now ?? (() => globalThis.performance?.now() ?? Date.now());
26
+ // Last time each name actually sounded — the throttle drops, never queues.
27
+ const lastPlayed = new Map();
25
28
  const audio = () => (backend ??= createWebAudioBackend());
26
29
  function clipFor(name) {
27
30
  let entry = entries.get(name);
@@ -61,7 +64,8 @@ export function createSoundPlayer(options = {}) {
61
64
  volume: opts.volume ?? base.volume,
62
65
  category: opts.category ?? base.category,
63
66
  essential: opts.essential ?? base.essential,
64
- loop: opts.loop ?? base.loop
67
+ loop: opts.loop ?? base.loop,
68
+ minIntervalMs: opts.minIntervalMs ?? base.minIntervalMs ?? 0
65
69
  };
66
70
  const p = prefs.get();
67
71
  let gain;
@@ -75,7 +79,14 @@ export function createSoundPlayer(options = {}) {
75
79
  if (!d.play)
76
80
  return silent(d.reason);
77
81
  gain = d.gain;
82
+ const t = now();
83
+ const last = lastPlayed.get(name);
84
+ if (spec.minIntervalMs > 0 && last !== undefined && t - last < spec.minIntervalMs) {
85
+ return silent('throttled');
86
+ }
87
+ lastPlayed.set(name, t);
78
88
  }
89
+ const playbackRate = rateFor(opts);
79
90
  let voice = null;
80
91
  let playing = true;
81
92
  const record = {
@@ -103,6 +114,7 @@ export function createSoundPlayer(options = {}) {
103
114
  gain,
104
115
  loop: spec.loop,
105
116
  loopGapMs: spec.loopGapMs,
117
+ playbackRate,
106
118
  onEnded: () => record.handle.stop()
107
119
  })
108
120
  : null;
@@ -125,6 +137,11 @@ export function createSoundPlayer(options = {}) {
125
137
  }
126
138
  };
127
139
  }
140
+ /** Effective playback rate for a play: `playbackRate × 2^(pitch/12)`, clamped to a sane range. */
141
+ export function rateFor(opts) {
142
+ const rate = (opts.playbackRate ?? 1) * Math.pow(2, (opts.pitch ?? 0) / 12);
143
+ return Number.isFinite(rate) && rate > 0 ? Math.min(8, Math.max(0.125, rate)) : 1;
144
+ }
128
145
  let shared = null;
129
146
  /** The app-wide player, bound to the shared preferences store. */
130
147
  export function getSoundPlayer() {
@@ -18,7 +18,9 @@ export interface SoundRequest {
18
18
  /** Base gain 0–1 (the spec volume or the caller's override). */
19
19
  volume: number;
20
20
  }
21
- export type SoundBlockReason = 'disabled' | 'category-muted' | 'hidden' | 'quiet-hours' | 'quiet' | 'silent';
21
+ export type SoundBlockReason = 'disabled' | 'category-muted' | 'hidden' | 'quiet-hours' | 'quiet' | 'silent'
22
+ /** Dropped by the per-name throttle (set by the player, never by decideSound). */
23
+ | 'throttled';
22
24
  export type SoundDecision = {
23
25
  play: true;
24
26
  gain: number;
@@ -7,7 +7,9 @@
7
7
  * across tabs via the `storage` event, and tolerant of corrupt/partial JSON.
8
8
  *
9
9
  * UI and notification sounds are OFF by default (opt-in); calls are ON by
10
- * default (opt-out) so an incoming call rings out of the box. A device that set the pre-0.28 boolean
10
+ * default (opt-out) so an incoming call rings out of the box. A host can flip
11
+ * the UI default per site with `<html data-sound="subtle">` (level.ts); a
12
+ * saved visitor preference always wins over it. A device that set the pre-0.28 boolean
11
13
  * (`jatui-sound-enabled`) is migrated on first read.
12
14
  */
13
15
  import { type SoundCategory } from './names';
@@ -64,7 +66,11 @@ export interface CreateSoundPreferencesOptions {
64
66
  defaults?: SoundPreferencesPatch;
65
67
  }
66
68
  export declare function createSoundPreferences(options?: CreateSoundPreferencesOptions): SoundPreferencesStore;
67
- /** The app-wide store (lazy, so SSR imports never touch storage). */
69
+ /**
70
+ * The app-wide store (lazy, so SSR imports never touch storage). Its defaults
71
+ * come from the site sound level on `<html data-sound>` at creation time, so
72
+ * `subtle` starts UI sound on for a visitor with nothing saved (see level.ts).
73
+ */
68
74
  export declare function getSoundPreferences(): SoundPreferencesStore;
69
75
  /** Test seam: replace (or with no argument, drop) the shared store. */
70
76
  export declare function setSoundPreferences(store: SoundPreferencesStore | null): void;
@@ -7,10 +7,13 @@
7
7
  * across tabs via the `storage` event, and tolerant of corrupt/partial JSON.
8
8
  *
9
9
  * UI and notification sounds are OFF by default (opt-in); calls are ON by
10
- * default (opt-out) so an incoming call rings out of the box. A device that set the pre-0.28 boolean
10
+ * default (opt-out) so an incoming call rings out of the box. A host can flip
11
+ * the UI default per site with `<html data-sound="subtle">` (level.ts); a
12
+ * saved visitor preference always wins over it. A device that set the pre-0.28 boolean
11
13
  * (`jatui-sound-enabled`) is migrated on first read.
12
14
  */
13
15
  import { SOUND_CATEGORIES } from './names';
16
+ import { resolveSoundLevel, soundLevelDefaults } from './level';
14
17
  export const SOUND_PREFS_STORAGE_KEY = 'jatui-sound-prefs';
15
18
  /** The pre-semantic boolean key from utils/sound.ts — read once to migrate. */
16
19
  export const LEGACY_SOUND_ENABLED_KEY = 'jatui-sound-enabled';
@@ -134,9 +137,13 @@ export function createSoundPreferences(options = {}) {
134
137
  };
135
138
  }
136
139
  let shared = null;
137
- /** The app-wide store (lazy, so SSR imports never touch storage). */
140
+ /**
141
+ * The app-wide store (lazy, so SSR imports never touch storage). Its defaults
142
+ * come from the site sound level on `<html data-sound>` at creation time, so
143
+ * `subtle` starts UI sound on for a visitor with nothing saved (see level.ts).
144
+ */
138
145
  export function getSoundPreferences() {
139
- return (shared ??= createSoundPreferences());
146
+ return (shared ??= createSoundPreferences({ defaults: soundLevelDefaults(resolveSoundLevel()) }));
140
147
  }
141
148
  /** Test seam: replace (or with no argument, drop) the shared store. */
142
149
  export function setSoundPreferences(store) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joewinke/jatui",
3
- "version": "0.28.0",
3
+ "version": "0.30.0",
4
4
  "private": false,
5
5
  "description": "Shared Svelte 5 component library for JAT projects",
6
6
  "type": "module",