@akshatbuilds/sonix 1.0.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.
Files changed (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +123 -0
  3. package/app/globals.css +110 -0
  4. package/app/layout.tsx +54 -0
  5. package/app/page.tsx +40 -0
  6. package/app/usage/page.tsx +9 -0
  7. package/cli/index.mjs +151 -0
  8. package/components/sound-card.tsx +595 -0
  9. package/components/sounds-gallery.tsx +137 -0
  10. package/components/theme-provider.tsx +11 -0
  11. package/components/theme-toggle.tsx +82 -0
  12. package/components/ui/button.tsx +57 -0
  13. package/components/ui/checkbox.tsx +30 -0
  14. package/components/ui/slider.tsx +28 -0
  15. package/components/ui/switch.tsx +29 -0
  16. package/components/ui/tooltip.tsx +30 -0
  17. package/components/usage-guide.tsx +155 -0
  18. package/components.json +21 -0
  19. package/lib/sounds.ts +329 -0
  20. package/lib/utils.ts +6 -0
  21. package/next-env.d.ts +6 -0
  22. package/next.config.mjs +5 -0
  23. package/package.json +96 -0
  24. package/postcss.config.mjs +8 -0
  25. package/public/click1.mp3 +0 -0
  26. package/public/click2.mp3 +0 -0
  27. package/public/registry/index.json +92 -0
  28. package/public/registry/sounds/button-click-secondary.json +13 -0
  29. package/public/registry/sounds/button-click.json +13 -0
  30. package/public/registry/sounds/error-beep.json +10 -0
  31. package/public/registry/sounds/error-buzz.json +10 -0
  32. package/public/registry/sounds/hover-blip.json +10 -0
  33. package/public/registry/sounds/hover-soft.json +10 -0
  34. package/public/registry/sounds/key-press.json +10 -0
  35. package/public/registry/sounds/notification-ping.json +10 -0
  36. package/public/registry/sounds/notification-subtle.json +10 -0
  37. package/public/registry/sounds/pop.json +10 -0
  38. package/public/registry/sounds/slider-tick.json +10 -0
  39. package/public/registry/sounds/success-bell.json +10 -0
  40. package/public/registry/sounds/success-chime.json +10 -0
  41. package/public/registry/sounds/swoosh.json +10 -0
  42. package/scripts/build-registry.mjs +293 -0
  43. package/tailwind.config.ts +100 -0
  44. package/tsconfig.json +33 -0
package/lib/sounds.ts ADDED
@@ -0,0 +1,329 @@
1
+ // UI Sound Effects - Procedurally generated audio for micro interactions
2
+ // Uses Web Audio API for real-time sound synthesis
3
+
4
+ export const SOUND_NAMES = [
5
+ 'button-click',
6
+ 'button-click-secondary',
7
+ 'hover-blip',
8
+ 'hover-soft',
9
+ 'success-chime',
10
+ 'success-bell',
11
+ 'error-buzz',
12
+ 'error-beep',
13
+ 'notification-ping',
14
+ 'notification-subtle',
15
+ 'swoosh',
16
+ 'pop',
17
+ 'slider-tick',
18
+ 'key-press',
19
+ ] as const;
20
+
21
+ export type SoundName = (typeof SOUND_NAMES)[number];
22
+
23
+ let audioCtx: AudioContext | null = null;
24
+ let soundEnabled = true;
25
+
26
+ // Respect prefers-reduced-motion (Web Interface Guidelines: Animation)
27
+ if (typeof window !== 'undefined') {
28
+ const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
29
+ soundEnabled = !mq.matches;
30
+ mq.addEventListener('change', (e) => {
31
+ soundEnabled = !e.matches;
32
+ });
33
+ }
34
+
35
+ /** Programmatically enable/disable all sounds */
36
+ export function setSoundEnabled(enabled: boolean) {
37
+ soundEnabled = enabled;
38
+ }
39
+
40
+ export function isSoundEnabled() {
41
+ return soundEnabled;
42
+ }
43
+
44
+ function getAudioContext(): AudioContext {
45
+ if (!audioCtx) {
46
+ audioCtx = new AudioContext();
47
+ }
48
+ if (audioCtx.state === 'suspended') {
49
+ audioCtx.resume();
50
+ }
51
+ return audioCtx;
52
+ }
53
+
54
+ // Sound synthesis definitions using Web Audio API
55
+ function synthesizeSound(ctx: AudioContext, name: SoundName) {
56
+ const now = ctx.currentTime;
57
+
58
+ switch (name) {
59
+ case 'button-click': {
60
+ // Play click1.mp3 from public folder
61
+ const audio = new Audio('/click1.mp3');
62
+ audio.volume = 0.5;
63
+ audio.play().catch(() => {});
64
+ break;
65
+ }
66
+ case 'button-click-secondary': {
67
+ // Play click2.mp3 from public folder
68
+ const audio = new Audio('/click2.mp3');
69
+ audio.volume = 0.5;
70
+ audio.play().catch(() => {});
71
+ break;
72
+ }
73
+ case 'hover-blip': {
74
+ const osc = ctx.createOscillator();
75
+ const gain = ctx.createGain();
76
+ osc.type = 'sine';
77
+ osc.frequency.setValueAtTime(1200, now);
78
+ osc.frequency.exponentialRampToValueAtTime(2400, now + 0.05);
79
+ gain.gain.setValueAtTime(0.15, now);
80
+ gain.gain.exponentialRampToValueAtTime(0.001, now + 0.05);
81
+ osc.connect(gain).connect(ctx.destination);
82
+ osc.start(now);
83
+ osc.stop(now + 0.05);
84
+ break;
85
+ }
86
+ case 'hover-soft': {
87
+ const osc = ctx.createOscillator();
88
+ const gain = ctx.createGain();
89
+ osc.type = 'sine';
90
+ osc.frequency.setValueAtTime(900, now);
91
+ gain.gain.setValueAtTime(0.08, now);
92
+ gain.gain.exponentialRampToValueAtTime(0.001, now + 0.06);
93
+ osc.connect(gain).connect(ctx.destination);
94
+ osc.start(now);
95
+ osc.stop(now + 0.06);
96
+ break;
97
+ }
98
+ case 'success-chime': {
99
+ // Two-note ascending chime
100
+ const osc1 = ctx.createOscillator();
101
+ const gain1 = ctx.createGain();
102
+ osc1.type = 'sine';
103
+ osc1.frequency.setValueAtTime(800, now);
104
+ gain1.gain.setValueAtTime(0.25, now);
105
+ gain1.gain.exponentialRampToValueAtTime(0.001, now + 0.15);
106
+ osc1.connect(gain1).connect(ctx.destination);
107
+ osc1.start(now);
108
+ osc1.stop(now + 0.15);
109
+
110
+ const osc2 = ctx.createOscillator();
111
+ const gain2 = ctx.createGain();
112
+ osc2.type = 'sine';
113
+ osc2.frequency.setValueAtTime(1200, now + 0.1);
114
+ gain2.gain.setValueAtTime(0.001, now);
115
+ gain2.gain.setValueAtTime(0.25, now + 0.1);
116
+ gain2.gain.exponentialRampToValueAtTime(0.001, now + 0.3);
117
+ osc2.connect(gain2).connect(ctx.destination);
118
+ osc2.start(now + 0.1);
119
+ osc2.stop(now + 0.3);
120
+ break;
121
+ }
122
+ case 'success-bell': {
123
+ // Three-note ascending bell
124
+ [1000, 1250, 1500].forEach((freq, i) => {
125
+ const osc = ctx.createOscillator();
126
+ const gain = ctx.createGain();
127
+ osc.type = 'sine';
128
+ osc.frequency.setValueAtTime(freq, now + i * 0.08);
129
+ gain.gain.setValueAtTime(0.001, now);
130
+ gain.gain.setValueAtTime(0.2, now + i * 0.08);
131
+ gain.gain.exponentialRampToValueAtTime(0.001, now + i * 0.08 + 0.15);
132
+ osc.connect(gain).connect(ctx.destination);
133
+ osc.start(now + i * 0.08);
134
+ osc.stop(now + i * 0.08 + 0.15);
135
+ });
136
+ break;
137
+ }
138
+ case 'error-buzz': {
139
+ const osc = ctx.createOscillator();
140
+ const gain = ctx.createGain();
141
+ osc.type = 'sawtooth';
142
+ osc.frequency.setValueAtTime(150, now);
143
+ gain.gain.setValueAtTime(0.2, now);
144
+ gain.gain.setValueAtTime(0.001, now + 0.08);
145
+ gain.gain.setValueAtTime(0.2, now + 0.1);
146
+ gain.gain.exponentialRampToValueAtTime(0.001, now + 0.2);
147
+ osc.connect(gain).connect(ctx.destination);
148
+ osc.start(now);
149
+ osc.stop(now + 0.2);
150
+ break;
151
+ }
152
+ case 'error-beep': {
153
+ // Two descending beeps
154
+ [500, 350].forEach((freq, i) => {
155
+ const osc = ctx.createOscillator();
156
+ const gain = ctx.createGain();
157
+ osc.type = 'square';
158
+ osc.frequency.setValueAtTime(freq, now + i * 0.12);
159
+ gain.gain.setValueAtTime(0.001, now);
160
+ gain.gain.setValueAtTime(0.15, now + i * 0.12);
161
+ gain.gain.exponentialRampToValueAtTime(0.001, now + i * 0.12 + 0.1);
162
+ osc.connect(gain).connect(ctx.destination);
163
+ osc.start(now + i * 0.12);
164
+ osc.stop(now + i * 0.12 + 0.1);
165
+ });
166
+ break;
167
+ }
168
+ case 'notification-ping': {
169
+ const osc = ctx.createOscillator();
170
+ const gain = ctx.createGain();
171
+ osc.type = 'sine';
172
+ osc.frequency.setValueAtTime(1100, now);
173
+ osc.frequency.exponentialRampToValueAtTime(1800, now + 0.05);
174
+ osc.frequency.exponentialRampToValueAtTime(1100, now + 0.1);
175
+ gain.gain.setValueAtTime(0.25, now);
176
+ gain.gain.exponentialRampToValueAtTime(0.001, now + 0.15);
177
+ osc.connect(gain).connect(ctx.destination);
178
+ osc.start(now);
179
+ osc.stop(now + 0.15);
180
+ break;
181
+ }
182
+ case 'notification-subtle': {
183
+ const osc = ctx.createOscillator();
184
+ const gain = ctx.createGain();
185
+ osc.type = 'sine';
186
+ osc.frequency.setValueAtTime(700, now);
187
+ gain.gain.setValueAtTime(0.1, now);
188
+ gain.gain.exponentialRampToValueAtTime(0.001, now + 0.12);
189
+ osc.connect(gain).connect(ctx.destination);
190
+ osc.start(now);
191
+ osc.stop(now + 0.12);
192
+ break;
193
+ }
194
+ case 'swoosh': {
195
+ // Slow fan blade pass — low filtered noise, smooth fade in/out
196
+ const bufferSize = Math.floor(ctx.sampleRate * 0.4);
197
+ const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
198
+ const data = buffer.getChannelData(0);
199
+ for (let i = 0; i < bufferSize; i++) {
200
+ data[i] = Math.random() * 2 - 1;
201
+ }
202
+ const noise = ctx.createBufferSource();
203
+ noise.buffer = buffer;
204
+
205
+ // Low-pass filter — keeps it rumbly and muffled like moving air
206
+ const lp = ctx.createBiquadFilter();
207
+ lp.type = 'lowpass';
208
+ lp.frequency.setValueAtTime(350, now);
209
+ lp.frequency.linearRampToValueAtTime(500, now + 0.15);
210
+ lp.frequency.linearRampToValueAtTime(250, now + 0.38);
211
+ lp.Q.setValueAtTime(0.5, now);
212
+
213
+ // Smooth bell-curve envelope — rises slowly, fades slowly
214
+ const noiseGain = ctx.createGain();
215
+ noiseGain.gain.setValueAtTime(0.001, now);
216
+ noiseGain.gain.linearRampToValueAtTime(0.06, now + 0.16);
217
+ noiseGain.gain.linearRampToValueAtTime(0.05, now + 0.24);
218
+ noiseGain.gain.exponentialRampToValueAtTime(0.001, now + 0.38);
219
+
220
+ noise.connect(lp).connect(noiseGain).connect(ctx.destination);
221
+ noise.start(now);
222
+ noise.stop(now + 0.4);
223
+ break;
224
+ }
225
+ case 'pop': {
226
+ const osc = ctx.createOscillator();
227
+ const gain = ctx.createGain();
228
+ osc.type = 'sine';
229
+ osc.frequency.setValueAtTime(950, now);
230
+ osc.frequency.exponentialRampToValueAtTime(150, now + 0.08);
231
+ gain.gain.setValueAtTime(0.35, now);
232
+ gain.gain.exponentialRampToValueAtTime(0.001, now + 0.08);
233
+ osc.connect(gain).connect(ctx.destination);
234
+ osc.start(now);
235
+ osc.stop(now + 0.08);
236
+ break;
237
+ }
238
+ case 'slider-tick': {
239
+ // Sharp, striking tick — short percussive click
240
+ const osc = ctx.createOscillator();
241
+ const gain = ctx.createGain();
242
+ osc.type = 'square';
243
+ osc.frequency.setValueAtTime(5000, now);
244
+ osc.frequency.exponentialRampToValueAtTime(1200, now + 0.005);
245
+ gain.gain.setValueAtTime(0.08, now);
246
+ gain.gain.exponentialRampToValueAtTime(0.001, now + 0.01);
247
+ osc.connect(gain).connect(ctx.destination);
248
+ osc.start(now);
249
+ osc.stop(now + 0.01);
250
+ break;
251
+ }
252
+ case 'key-press': {
253
+ // Lubed Cherry MX Brown — deep muted thock, no click
254
+ // Per-key variation for natural typing feel
255
+ const v = () => 0.94 + Math.random() * 0.12; // ±6% pitch drift
256
+ const drift = Math.random() * 0.0015; // 0–1.5ms jitter
257
+
258
+ // Layer 1: bottom-out thock (the main sound — stem hitting dampened housing)
259
+ // Low sine thud, fast attack, moderate decay — this IS the brown sound
260
+ const thud = ctx.createOscillator();
261
+ const thudGain = ctx.createGain();
262
+ const thudFilter = ctx.createBiquadFilter();
263
+ thud.type = 'sine';
264
+ thud.frequency.setValueAtTime(95 * v(), now + drift);
265
+ thud.frequency.exponentialRampToValueAtTime(38, now + drift + 0.04);
266
+ thudFilter.type = 'lowpass';
267
+ thudFilter.frequency.setValueAtTime(300, now + drift);
268
+ thudFilter.Q.setValueAtTime(0.5, now + drift);
269
+ thudGain.gain.setValueAtTime(0.22, now + drift);
270
+ thudGain.gain.exponentialRampToValueAtTime(0.001, now + drift + 0.055);
271
+ thud.connect(thudFilter).connect(thudGain).connect(ctx.destination);
272
+ thud.start(now + drift);
273
+ thud.stop(now + drift + 0.06);
274
+
275
+ // Layer 2: tactile bump (barely audible mid-range blip — the brown "bump")
276
+ // Very quiet, short, no sharp attack
277
+ const bump = ctx.createOscillator();
278
+ const bumpGain = ctx.createGain();
279
+ const bumpFilter = ctx.createBiquadFilter();
280
+ bump.type = 'triangle';
281
+ bump.frequency.setValueAtTime(420 * v(), now);
282
+ bump.frequency.exponentialRampToValueAtTime(180, now + 0.008);
283
+ bumpFilter.type = 'lowpass';
284
+ bumpFilter.frequency.setValueAtTime(600, now);
285
+ bumpGain.gain.setValueAtTime(0.03, now);
286
+ bumpGain.gain.exponentialRampToValueAtTime(0.001, now + 0.012);
287
+ bump.connect(bumpFilter).connect(bumpGain).connect(ctx.destination);
288
+ bump.start(now);
289
+ bump.stop(now + 0.012);
290
+
291
+ // Layer 3: dampened noise (lube kills scratchiness — just a soft low puff)
292
+ const bufLen = Math.floor(ctx.sampleRate * 0.03);
293
+ const buf = ctx.createBuffer(1, bufLen, ctx.sampleRate);
294
+ const d = buf.getChannelData(0);
295
+ for (let i = 0; i < bufLen; i++) d[i] = Math.random() * 2 - 1;
296
+ const noise = ctx.createBufferSource();
297
+ noise.buffer = buf;
298
+ const noiseLp = ctx.createBiquadFilter();
299
+ noiseLp.type = 'lowpass';
300
+ noiseLp.frequency.setValueAtTime(400 * v(), now + drift);
301
+ noiseLp.Q.setValueAtTime(0.3, now + drift);
302
+ const noiseGain = ctx.createGain();
303
+ noiseGain.gain.setValueAtTime(0.025, now + drift);
304
+ noiseGain.gain.exponentialRampToValueAtTime(0.001, now + drift + 0.025);
305
+ noise.connect(noiseLp).connect(noiseGain).connect(ctx.destination);
306
+ noise.start(now + drift);
307
+ noise.stop(now + drift + 0.03);
308
+ break;
309
+ }
310
+ }
311
+ }
312
+
313
+ // Helper function to play a sound
314
+ export function playSound(soundName: SoundName) {
315
+ if (!soundEnabled) return;
316
+ try {
317
+ const ctx = getAudioContext();
318
+ synthesizeSound(ctx, soundName);
319
+ } catch (err) {
320
+ console.error('Failed to play sound:', err);
321
+ }
322
+ }
323
+
324
+ // Hook for React components
325
+ export function useSoundEffects() {
326
+ return {
327
+ play: playSound,
328
+ };
329
+ }
package/lib/utils.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from 'clsx'
2
+ import { twMerge } from 'tailwind-merge'
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }
package/next-env.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
3
+ import "./.next/dev/types/routes.d.ts";
4
+
5
+ // NOTE: This file should not be edited
6
+ // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
@@ -0,0 +1,5 @@
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {}
3
+
4
+
5
+ export default nextConfig
package/package.json ADDED
@@ -0,0 +1,96 @@
1
+ {
2
+ "name": "@akshatbuilds/sonix",
3
+ "version": "1.0.0",
4
+ "description": "Micro UX sound effects for interactive web applications. Zero dependencies, copy-paste ready.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/abhi-yo/sound-library.git"
9
+ },
10
+ "keywords": [
11
+ "sound",
12
+ "audio",
13
+ "ui",
14
+ "ux",
15
+ "web-audio",
16
+ "sound-effects",
17
+ "react",
18
+ "nextjs"
19
+ ],
20
+ "bin": {
21
+ "sonix": "cli/index.mjs"
22
+ },
23
+ "scripts": {
24
+ "dev": "next dev --turbo",
25
+ "build": "next build",
26
+ "start": "next start",
27
+ "lint": "next lint"
28
+ },
29
+ "dependencies": {
30
+ "@hookform/resolvers": "^3.9.1",
31
+ "@radix-ui/react-accordion": "1.2.2",
32
+ "@radix-ui/react-alert-dialog": "1.1.4",
33
+ "@radix-ui/react-aspect-ratio": "1.1.1",
34
+ "@radix-ui/react-avatar": "1.1.2",
35
+ "@radix-ui/react-checkbox": "1.1.3",
36
+ "@radix-ui/react-collapsible": "1.1.2",
37
+ "@radix-ui/react-context-menu": "2.2.4",
38
+ "@radix-ui/react-dialog": "1.1.4",
39
+ "@radix-ui/react-dropdown-menu": "2.1.4",
40
+ "@radix-ui/react-hover-card": "1.1.4",
41
+ "@radix-ui/react-label": "2.1.1",
42
+ "@radix-ui/react-menubar": "1.1.4",
43
+ "@radix-ui/react-navigation-menu": "1.2.3",
44
+ "@radix-ui/react-popover": "1.1.4",
45
+ "@radix-ui/react-progress": "1.1.1",
46
+ "@radix-ui/react-radio-group": "1.2.2",
47
+ "@radix-ui/react-scroll-area": "1.2.2",
48
+ "@radix-ui/react-select": "2.1.4",
49
+ "@radix-ui/react-separator": "1.1.1",
50
+ "@radix-ui/react-slider": "1.2.2",
51
+ "@radix-ui/react-slot": "1.1.1",
52
+ "@radix-ui/react-switch": "1.1.2",
53
+ "@radix-ui/react-tabs": "1.1.2",
54
+ "@radix-ui/react-toast": "1.2.4",
55
+ "@radix-ui/react-toggle": "1.1.1",
56
+ "@radix-ui/react-toggle-group": "1.1.1",
57
+ "@radix-ui/react-tooltip": "1.1.6",
58
+ "@vercel/analytics": "^1.6.1",
59
+ "autoprefixer": "^10.4.20",
60
+ "class-variance-authority": "^0.7.1",
61
+ "clsx": "^2.1.1",
62
+ "cmdk": "1.1.1",
63
+ "date-fns": "4.1.0",
64
+ "embla-carousel-react": "8.5.1",
65
+ "input-otp": "1.4.1",
66
+ "lucide-react": "^0.544.0",
67
+ "next": "16.1.6",
68
+ "next-themes": "^0.4.6",
69
+ "react": "19.2.3",
70
+ "react-day-picker": "8.10.1",
71
+ "react-dom": "19.2.3",
72
+ "react-hook-form": "^7.54.1",
73
+ "react-resizable-panels": "^2.1.7",
74
+ "recharts": "2.15.0",
75
+ "sonner": "^1.7.1",
76
+ "tailwind-merge": "^2.5.5",
77
+ "tailwindcss-animate": "^1.0.7",
78
+ "vaul": "^1.1.2",
79
+ "zod": "^3.24.1"
80
+ },
81
+ "devDependencies": {
82
+ "@tailwindcss/postcss": "^4.1.13",
83
+ "@types/node": "^22",
84
+ "@types/react": "19.2.7",
85
+ "@types/react-dom": "19.2.3",
86
+ "postcss": "^8.5",
87
+ "tailwindcss": "^3.4.17",
88
+ "typescript": "5.7.3"
89
+ },
90
+ "pnpm": {
91
+ "overrides": {
92
+ "@types/react": "19.2.7",
93
+ "@types/react-dom": "19.2.3"
94
+ }
95
+ }
96
+ }
@@ -0,0 +1,8 @@
1
+ /** @type {import('postcss-load-config').Config} */
2
+ const config = {
3
+ plugins: {
4
+ tailwindcss: {},
5
+ },
6
+ }
7
+
8
+ export default config
Binary file
Binary file
@@ -0,0 +1,92 @@
1
+ {
2
+ "name": "sonix",
3
+ "version": "1.0.0",
4
+ "sounds": [
5
+ {
6
+ "name": "button-click",
7
+ "type": "mp3",
8
+ "file": "click1.mp3",
9
+ "category": "interaction",
10
+ "description": "Classic click sound for button interactions."
11
+ },
12
+ {
13
+ "name": "button-click-secondary",
14
+ "type": "mp3",
15
+ "file": "click2.mp3",
16
+ "category": "interaction",
17
+ "description": "Softer click for secondary actions."
18
+ },
19
+ {
20
+ "name": "hover-blip",
21
+ "type": "webaudio",
22
+ "category": "feedback",
23
+ "description": "Quick chirp when hovering over interactive elements."
24
+ },
25
+ {
26
+ "name": "hover-soft",
27
+ "type": "webaudio",
28
+ "category": "feedback",
29
+ "description": "Subtle and gentle hover feedback."
30
+ },
31
+ {
32
+ "name": "success-chime",
33
+ "type": "webaudio",
34
+ "category": "notification",
35
+ "description": "Pleasant chime for successful completion."
36
+ },
37
+ {
38
+ "name": "success-bell",
39
+ "type": "webaudio",
40
+ "category": "notification",
41
+ "description": "Uplifting bell for positive feedback."
42
+ },
43
+ {
44
+ "name": "error-buzz",
45
+ "type": "webaudio",
46
+ "category": "alert",
47
+ "description": "Attention-grabbing buzz for error states."
48
+ },
49
+ {
50
+ "name": "error-beep",
51
+ "type": "webaudio",
52
+ "category": "alert",
53
+ "description": "Clear beep to signal an error."
54
+ },
55
+ {
56
+ "name": "notification-ping",
57
+ "type": "webaudio",
58
+ "category": "alert",
59
+ "description": "Quick ping for notifications."
60
+ },
61
+ {
62
+ "name": "notification-subtle",
63
+ "type": "webaudio",
64
+ "category": "alert",
65
+ "description": "Quiet notification for background alerts."
66
+ },
67
+ {
68
+ "name": "swoosh",
69
+ "type": "webaudio",
70
+ "category": "transition",
71
+ "description": "Smooth whoosh for page transitions."
72
+ },
73
+ {
74
+ "name": "pop",
75
+ "type": "webaudio",
76
+ "category": "interaction",
77
+ "description": "Playful pop for tooltips and reveals."
78
+ },
79
+ {
80
+ "name": "slider-tick",
81
+ "type": "webaudio",
82
+ "category": "interaction",
83
+ "description": "Subtle tick for sliders and range inputs."
84
+ },
85
+ {
86
+ "name": "key-press",
87
+ "type": "webaudio",
88
+ "category": "interaction",
89
+ "description": "Mechanical switch click for text inputs."
90
+ }
91
+ ]
92
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "button-click-secondary",
3
+ "type": "mp3",
4
+ "files": [
5
+ {
6
+ "name": "button-click-secondary.ts",
7
+ "content": "export function play() {\n// Requires click2.mp3 in your public folder\nconst audio = new Audio('/click2.mp3');\naudio.volume = 0.5;\naudio.play();\n}"
8
+ }
9
+ ],
10
+ "assets": [
11
+ "click2.mp3"
12
+ ]
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "button-click",
3
+ "type": "mp3",
4
+ "files": [
5
+ {
6
+ "name": "button-click.ts",
7
+ "content": "export function play() {\n// Requires click1.mp3 in your public folder\nconst audio = new Audio('/click1.mp3');\naudio.volume = 0.5;\naudio.play();\n}"
8
+ }
9
+ ],
10
+ "assets": [
11
+ "click1.mp3"
12
+ ]
13
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "error-beep",
3
+ "type": "webaudio",
4
+ "files": [
5
+ {
6
+ "name": "error-beep.ts",
7
+ "content": "export function playErrorBeep() {\n const ctx = new AudioContext();\n const now = ctx.currentTime;\n [500, 350].forEach((freq, i) => {\n const osc = ctx.createOscillator();\n const gain = ctx.createGain();\n osc.type = 'square';\n osc.frequency.setValueAtTime(freq, now + i * 0.12);\n gain.gain.setValueAtTime(0.001, now);\n gain.gain.setValueAtTime(0.15, now + i * 0.12);\n gain.gain.exponentialRampToValueAtTime(0.001, now + i * 0.12 + 0.1);\n osc.connect(gain).connect(ctx.destination);\n osc.start(now + i * 0.12);\n osc.stop(now + i * 0.12 + 0.1);\n });\n}"
8
+ }
9
+ ]
10
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "error-buzz",
3
+ "type": "webaudio",
4
+ "files": [
5
+ {
6
+ "name": "error-buzz.ts",
7
+ "content": "export function playErrorBuzz() {\n const ctx = new AudioContext();\n const now = ctx.currentTime;\n const osc = ctx.createOscillator();\n const gain = ctx.createGain();\n osc.type = 'sawtooth';\n osc.frequency.setValueAtTime(150, now);\n gain.gain.setValueAtTime(0.2, now);\n gain.gain.setValueAtTime(0.001, now + 0.08);\n gain.gain.setValueAtTime(0.2, now + 0.1);\n gain.gain.exponentialRampToValueAtTime(0.001, now + 0.2);\n osc.connect(gain).connect(ctx.destination);\n osc.start(now);\n osc.stop(now + 0.2);\n}"
8
+ }
9
+ ]
10
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "hover-blip",
3
+ "type": "webaudio",
4
+ "files": [
5
+ {
6
+ "name": "hover-blip.ts",
7
+ "content": "export function playHoverBlip() {\n const ctx = new AudioContext();\n const now = ctx.currentTime;\n const osc = ctx.createOscillator();\n const gain = ctx.createGain();\n osc.type = 'sine';\n osc.frequency.setValueAtTime(1200, now);\n osc.frequency.exponentialRampToValueAtTime(2400, now + 0.05);\n gain.gain.setValueAtTime(0.15, now);\n gain.gain.exponentialRampToValueAtTime(0.001, now + 0.05);\n osc.connect(gain).connect(ctx.destination);\n osc.start(now);\n osc.stop(now + 0.05);\n}"
8
+ }
9
+ ]
10
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "hover-soft",
3
+ "type": "webaudio",
4
+ "files": [
5
+ {
6
+ "name": "hover-soft.ts",
7
+ "content": "export function playHoverSoft() {\n const ctx = new AudioContext();\n const now = ctx.currentTime;\n const osc = ctx.createOscillator();\n const gain = ctx.createGain();\n osc.type = 'sine';\n osc.frequency.setValueAtTime(900, now);\n gain.gain.setValueAtTime(0.08, now);\n gain.gain.exponentialRampToValueAtTime(0.001, now + 0.06);\n osc.connect(gain).connect(ctx.destination);\n osc.start(now);\n osc.stop(now + 0.06);\n}"
8
+ }
9
+ ]
10
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "key-press",
3
+ "type": "webaudio",
4
+ "files": [
5
+ {
6
+ "name": "key-press.ts",
7
+ "content": "export function playKeyPress() {\n const ctx = new AudioContext();\n const now = ctx.currentTime;\n // Lubed Cherry MX Brown — deep muted thock, no click\n const v = () => 0.94 + Math.random() * 0.12;\n const drift = Math.random() * 0.0015;\n // Layer 1: bottom-out thock (main sound)\n const thud = ctx.createOscillator();\n const thudGain = ctx.createGain();\n const thudFilter = ctx.createBiquadFilter();\n thud.type = 'sine';\n thud.frequency.setValueAtTime(95 * v(), now + drift);\n thud.frequency.exponentialRampToValueAtTime(38, now + drift + 0.04);\n thudFilter.type = 'lowpass';\n thudFilter.frequency.setValueAtTime(300, now + drift);\n thudFilter.Q.setValueAtTime(0.5, now + drift);\n thudGain.gain.setValueAtTime(0.22, now + drift);\n thudGain.gain.exponentialRampToValueAtTime(0.001, now + drift + 0.055);\n thud.connect(thudFilter).connect(thudGain).connect(ctx.destination);\n thud.start(now + drift); thud.stop(now + drift + 0.06);\n // Layer 2: tactile bump (barely audible)\n const bump = ctx.createOscillator();\n const bumpGain = ctx.createGain();\n const bumpFilter = ctx.createBiquadFilter();\n bump.type = 'triangle';\n bump.frequency.setValueAtTime(420 * v(), now);\n bump.frequency.exponentialRampToValueAtTime(180, now + 0.008);\n bumpFilter.type = 'lowpass';\n bumpFilter.frequency.setValueAtTime(600, now);\n bumpGain.gain.setValueAtTime(0.03, now);\n bumpGain.gain.exponentialRampToValueAtTime(0.001, now + 0.012);\n bump.connect(bumpFilter).connect(bumpGain).connect(ctx.destination);\n bump.start(now); bump.stop(now + 0.012);\n // Layer 3: dampened noise (soft low puff)\n const bufLen = Math.floor(ctx.sampleRate * 0.03);\n const buf = ctx.createBuffer(1, bufLen, ctx.sampleRate);\n const d = buf.getChannelData(0);\n for (let i = 0; i < bufLen; i++) d[i] = Math.random() * 2 - 1;\n const noise = ctx.createBufferSource();\n noise.buffer = buf;\n const noiseLp = ctx.createBiquadFilter();\n noiseLp.type = 'lowpass';\n noiseLp.frequency.setValueAtTime(400 * v(), now + drift);\n noiseLp.Q.setValueAtTime(0.3, now + drift);\n const noiseGain = ctx.createGain();\n noiseGain.gain.setValueAtTime(0.025, now + drift);\n noiseGain.gain.exponentialRampToValueAtTime(0.001, now + drift + 0.025);\n noise.connect(noiseLp).connect(noiseGain).connect(ctx.destination);\n noise.start(now + drift); noise.stop(now + drift + 0.03);\n}"
8
+ }
9
+ ]
10
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "notification-ping",
3
+ "type": "webaudio",
4
+ "files": [
5
+ {
6
+ "name": "notification-ping.ts",
7
+ "content": "export function playNotificationPing() {\n const ctx = new AudioContext();\n const now = ctx.currentTime;\n const osc = ctx.createOscillator();\n const gain = ctx.createGain();\n osc.type = 'sine';\n osc.frequency.setValueAtTime(1100, now);\n osc.frequency.exponentialRampToValueAtTime(1800, now + 0.05);\n osc.frequency.exponentialRampToValueAtTime(1100, now + 0.1);\n gain.gain.setValueAtTime(0.25, now);\n gain.gain.exponentialRampToValueAtTime(0.001, now + 0.15);\n osc.connect(gain).connect(ctx.destination);\n osc.start(now);\n osc.stop(now + 0.15);\n}"
8
+ }
9
+ ]
10
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "notification-subtle",
3
+ "type": "webaudio",
4
+ "files": [
5
+ {
6
+ "name": "notification-subtle.ts",
7
+ "content": "export function playNotificationSubtle() {\n const ctx = new AudioContext();\n const now = ctx.currentTime;\n const osc = ctx.createOscillator();\n const gain = ctx.createGain();\n osc.type = 'sine';\n osc.frequency.setValueAtTime(700, now);\n gain.gain.setValueAtTime(0.1, now);\n gain.gain.exponentialRampToValueAtTime(0.001, now + 0.12);\n osc.connect(gain).connect(ctx.destination);\n osc.start(now);\n osc.stop(now + 0.12);\n}"
8
+ }
9
+ ]
10
+ }