@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.
- package/LICENSE +21 -0
- package/README.md +123 -0
- package/app/globals.css +110 -0
- package/app/layout.tsx +54 -0
- package/app/page.tsx +40 -0
- package/app/usage/page.tsx +9 -0
- package/cli/index.mjs +151 -0
- package/components/sound-card.tsx +595 -0
- package/components/sounds-gallery.tsx +137 -0
- package/components/theme-provider.tsx +11 -0
- package/components/theme-toggle.tsx +82 -0
- package/components/ui/button.tsx +57 -0
- package/components/ui/checkbox.tsx +30 -0
- package/components/ui/slider.tsx +28 -0
- package/components/ui/switch.tsx +29 -0
- package/components/ui/tooltip.tsx +30 -0
- package/components/usage-guide.tsx +155 -0
- package/components.json +21 -0
- package/lib/sounds.ts +329 -0
- package/lib/utils.ts +6 -0
- package/next-env.d.ts +6 -0
- package/next.config.mjs +5 -0
- package/package.json +96 -0
- package/postcss.config.mjs +8 -0
- package/public/click1.mp3 +0 -0
- package/public/click2.mp3 +0 -0
- package/public/registry/index.json +92 -0
- package/public/registry/sounds/button-click-secondary.json +13 -0
- package/public/registry/sounds/button-click.json +13 -0
- package/public/registry/sounds/error-beep.json +10 -0
- package/public/registry/sounds/error-buzz.json +10 -0
- package/public/registry/sounds/hover-blip.json +10 -0
- package/public/registry/sounds/hover-soft.json +10 -0
- package/public/registry/sounds/key-press.json +10 -0
- package/public/registry/sounds/notification-ping.json +10 -0
- package/public/registry/sounds/notification-subtle.json +10 -0
- package/public/registry/sounds/pop.json +10 -0
- package/public/registry/sounds/slider-tick.json +10 -0
- package/public/registry/sounds/success-bell.json +10 -0
- package/public/registry/sounds/success-chime.json +10 -0
- package/public/registry/sounds/swoosh.json +10 -0
- package/scripts/build-registry.mjs +293 -0
- package/tailwind.config.ts +100 -0
- 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
package/next-env.d.ts
ADDED
package/next.config.mjs
ADDED
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
|
+
}
|
|
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
|
+
}
|