@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
@@ -0,0 +1,137 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { SoundCard } from './sound-card';
5
+ import type { SoundName } from '@/lib/sounds';
6
+
7
+ const SOUNDS_DATA: Array<{
8
+ name: SoundName;
9
+ label: string;
10
+ category: string;
11
+ description: string;
12
+ }> = [
13
+ {
14
+ name: 'button-click',
15
+ label: 'Button Click',
16
+ category: 'Interaction',
17
+ description: 'Classic click sound for button interactions. Crisp and satisfying.',
18
+ },
19
+ {
20
+ name: 'button-click-secondary',
21
+ label: 'Button Click (Secondary)',
22
+ category: 'Interaction',
23
+ description: 'Softer click for secondary actions and alternatives.',
24
+ },
25
+ {
26
+ name: 'hover-blip',
27
+ label: 'Hover Blip',
28
+ category: 'Feedback',
29
+ description: 'Quick chirp when hovering over interactive elements.',
30
+ },
31
+ {
32
+ name: 'hover-soft',
33
+ label: 'Hover Soft',
34
+ category: 'Feedback',
35
+ description: 'Subtle and gentle hover feedback sound.',
36
+ },
37
+ {
38
+ name: 'success-chime',
39
+ label: 'Success Chime',
40
+ category: 'Notification',
41
+ description: 'Pleasant chime to indicate successful completion.',
42
+ },
43
+ {
44
+ name: 'success-bell',
45
+ label: 'Success Bell',
46
+ category: 'Notification',
47
+ description: 'Uplifting bell sound for positive feedback.',
48
+ },
49
+ {
50
+ name: 'error-buzz',
51
+ label: 'Error Buzz',
52
+ category: 'Alert',
53
+ description: 'Attention-grabbing buzz for error states.',
54
+ },
55
+ {
56
+ name: 'error-beep',
57
+ label: 'Error Beep',
58
+ category: 'Alert',
59
+ description: 'Clear beep to signal an error occurred.',
60
+ },
61
+ {
62
+ name: 'notification-ping',
63
+ label: 'Notification Ping',
64
+ category: 'Alert',
65
+ description: 'Quick ping for notifications and messages.',
66
+ },
67
+ {
68
+ name: 'notification-subtle',
69
+ label: 'Notification Subtle',
70
+ category: 'Alert',
71
+ description: 'Quiet notification sound for background alerts.',
72
+ },
73
+ {
74
+ name: 'swoosh',
75
+ label: 'Swoosh',
76
+ category: 'Transition',
77
+ description: 'Smooth whoosh for page transitions and animations.',
78
+ },
79
+ {
80
+ name: 'pop',
81
+ label: 'Pop',
82
+ category: 'Interaction',
83
+ description: 'Playful pop sound for confirmation or reveal.',
84
+ },
85
+ {
86
+ name: 'slider-tick',
87
+ label: 'Slider Tick',
88
+ category: 'Interaction',
89
+ description: 'Subtle tick sound for sliders and range inputs.',
90
+ },
91
+ {
92
+ name: 'key-press',
93
+ label: 'Key Press',
94
+ category: 'Interaction',
95
+ description: 'Mechanical switch click for text input and keyboards.',
96
+ },
97
+ ];
98
+
99
+ export function SoundsGallery() {
100
+ const [selectedCategory, setSelectedCategory] = useState<string>('All');
101
+
102
+ const categories = [
103
+ 'All',
104
+ ...new Set(SOUNDS_DATA.map(s => s.category)),
105
+ ];
106
+
107
+ const filteredSounds =
108
+ selectedCategory === 'All'
109
+ ? SOUNDS_DATA
110
+ : SOUNDS_DATA.filter(s => s.category === selectedCategory);
111
+
112
+ return (
113
+ <div className="flex flex-col gap-8">
114
+ <div className="flex flex-wrap gap-2">
115
+ {categories.map(category => (
116
+ <button
117
+ key={category}
118
+ onClick={() => setSelectedCategory(category)}
119
+ className={`rounded-full px-4 py-2 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ${
120
+ selectedCategory === category
121
+ ? 'bg-primary text-primary-foreground'
122
+ : 'border border-border bg-card text-card-foreground hover:bg-accent'
123
+ }`}
124
+ >
125
+ {category}
126
+ </button>
127
+ ))}
128
+ </div>
129
+
130
+ <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
131
+ {filteredSounds.map(sound => (
132
+ <SoundCard key={sound.name} {...sound} />
133
+ ))}
134
+ </div>
135
+ </div>
136
+ );
137
+ }
@@ -0,0 +1,11 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import {
5
+ ThemeProvider as NextThemesProvider,
6
+ type ThemeProviderProps,
7
+ } from 'next-themes'
8
+
9
+ export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
10
+ return <NextThemesProvider {...props}>{children}</NextThemesProvider>
11
+ }
@@ -0,0 +1,82 @@
1
+ 'use client';
2
+
3
+ import { useTheme } from 'next-themes';
4
+ import { useEffect, useState, useRef, useCallback } from 'react';
5
+ import { Button } from '@/components/ui/button';
6
+ import { playSound } from '@/lib/sounds';
7
+
8
+ export function ThemeToggle() {
9
+ const { theme, setTheme } = useTheme();
10
+ const [mounted, setMounted] = useState(false);
11
+ const buttonRef = useRef<HTMLButtonElement>(null);
12
+
13
+ useEffect(() => setMounted(true), []);
14
+
15
+ const handleToggle = useCallback(() => {
16
+ const nextTheme = theme === 'dark' ? 'light' : 'dark';
17
+
18
+ const btn = buttonRef.current;
19
+ if (btn && document.startViewTransition) {
20
+ const rect = btn.getBoundingClientRect();
21
+ const x = rect.left + rect.width / 2;
22
+ const y = rect.top + rect.height / 2;
23
+
24
+ // Set CSS custom properties for the circle origin
25
+ document.documentElement.style.setProperty('--reveal-x', `${x}px`);
26
+ document.documentElement.style.setProperty('--reveal-y', `${y}px`);
27
+
28
+ // Use View Transition API — captures old state as screenshot,
29
+ // switches theme instantly, then animates the old screenshot away
30
+ const transition = document.startViewTransition(() => {
31
+ setTheme(nextTheme);
32
+ });
33
+
34
+ transition.ready.then(() => {
35
+ document.documentElement.animate(
36
+ {
37
+ clipPath: [
38
+ `circle(0% at ${x}px ${y}px)`,
39
+ `circle(150% at ${x}px ${y}px)`,
40
+ ],
41
+ },
42
+ {
43
+ duration: 700,
44
+ easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
45
+ pseudoElement: '::view-transition-new(root)',
46
+ }
47
+ );
48
+ });
49
+ } else {
50
+ setTheme(nextTheme);
51
+ }
52
+
53
+ playSound('swoosh');
54
+ }, [theme, setTheme]);
55
+
56
+ if (!mounted) return <div className="h-9 w-9" />;
57
+
58
+ return (
59
+ <Button
60
+ ref={buttonRef}
61
+ variant="outline"
62
+ size="sm"
63
+ onClick={handleToggle}
64
+ className="h-9 w-9 rounded-full p-0"
65
+ aria-label="Toggle theme"
66
+ >
67
+ {theme === 'dark' ? (
68
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
69
+ <circle cx="12" cy="12" r="4" />
70
+ <path d="M12 2v2" /><path d="M12 20v2" />
71
+ <path d="m4.93 4.93 1.41 1.41" /><path d="m17.66 17.66 1.41 1.41" />
72
+ <path d="M2 12h2" /><path d="M20 12h2" />
73
+ <path d="m6.34 17.66-1.41 1.41" /><path d="m19.07 4.93-1.41 1.41" />
74
+ </svg>
75
+ ) : (
76
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
77
+ <path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" />
78
+ </svg>
79
+ )}
80
+ </Button>
81
+ );
82
+ }
@@ -0,0 +1,57 @@
1
+ import * as React from 'react'
2
+ import { Slot } from '@radix-ui/react-slot'
3
+ import { cva, type VariantProps } from 'class-variance-authority'
4
+
5
+ import { cn } from '@/lib/utils'
6
+
7
+ const buttonVariants = cva(
8
+ 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: 'bg-primary text-primary-foreground hover:bg-primary/90',
13
+ destructive:
14
+ 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
15
+ outline:
16
+ 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
17
+ secondary:
18
+ 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
19
+ ghost: 'hover:bg-accent hover:text-accent-foreground',
20
+ link: 'text-primary underline-offset-4 hover:underline',
21
+ },
22
+ size: {
23
+ default: 'h-10 px-4 py-2',
24
+ sm: 'h-9 rounded-md px-3',
25
+ lg: 'h-11 rounded-md px-8',
26
+ icon: 'h-10 w-10',
27
+ },
28
+ },
29
+ defaultVariants: {
30
+ variant: 'default',
31
+ size: 'default',
32
+ },
33
+ },
34
+ )
35
+
36
+ export interface ButtonProps
37
+ extends
38
+ React.ButtonHTMLAttributes<HTMLButtonElement>,
39
+ VariantProps<typeof buttonVariants> {
40
+ asChild?: boolean
41
+ }
42
+
43
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
44
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
45
+ const Comp = asChild ? Slot : 'button'
46
+ return (
47
+ <Comp
48
+ className={cn(buttonVariants({ variant, size, className }))}
49
+ ref={ref}
50
+ {...props}
51
+ />
52
+ )
53
+ },
54
+ )
55
+ Button.displayName = 'Button'
56
+
57
+ export { Button, buttonVariants }
@@ -0,0 +1,30 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import * as CheckboxPrimitive from '@radix-ui/react-checkbox'
5
+ import { Check } from 'lucide-react'
6
+
7
+ import { cn } from '@/lib/utils'
8
+
9
+ const Checkbox = React.forwardRef<
10
+ React.ElementRef<typeof CheckboxPrimitive.Root>,
11
+ React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
12
+ >(({ className, ...props }, ref) => (
13
+ <CheckboxPrimitive.Root
14
+ ref={ref}
15
+ className={cn(
16
+ 'peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
17
+ className,
18
+ )}
19
+ {...props}
20
+ >
21
+ <CheckboxPrimitive.Indicator
22
+ className={cn('flex items-center justify-center text-current')}
23
+ >
24
+ <Check className="h-4 w-4" />
25
+ </CheckboxPrimitive.Indicator>
26
+ </CheckboxPrimitive.Root>
27
+ ))
28
+ Checkbox.displayName = CheckboxPrimitive.Root.displayName
29
+
30
+ export { Checkbox }
@@ -0,0 +1,28 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import * as SliderPrimitive from '@radix-ui/react-slider'
5
+
6
+ import { cn } from '@/lib/utils'
7
+
8
+ const Slider = React.forwardRef<
9
+ React.ElementRef<typeof SliderPrimitive.Root>,
10
+ React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
11
+ >(({ className, ...props }, ref) => (
12
+ <SliderPrimitive.Root
13
+ ref={ref}
14
+ className={cn(
15
+ 'relative flex w-full touch-none select-none items-center',
16
+ className,
17
+ )}
18
+ {...props}
19
+ >
20
+ <SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary">
21
+ <SliderPrimitive.Range className="absolute h-full bg-primary" />
22
+ </SliderPrimitive.Track>
23
+ <SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
24
+ </SliderPrimitive.Root>
25
+ ))
26
+ Slider.displayName = SliderPrimitive.Root.displayName
27
+
28
+ export { Slider }
@@ -0,0 +1,29 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import * as SwitchPrimitives from '@radix-ui/react-switch'
5
+
6
+ import { cn } from '@/lib/utils'
7
+
8
+ const Switch = React.forwardRef<
9
+ React.ElementRef<typeof SwitchPrimitives.Root>,
10
+ React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
11
+ >(({ className, ...props }, ref) => (
12
+ <SwitchPrimitives.Root
13
+ className={cn(
14
+ 'peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
15
+ className,
16
+ )}
17
+ {...props}
18
+ ref={ref}
19
+ >
20
+ <SwitchPrimitives.Thumb
21
+ className={cn(
22
+ 'pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0',
23
+ )}
24
+ />
25
+ </SwitchPrimitives.Root>
26
+ ))
27
+ Switch.displayName = SwitchPrimitives.Root.displayName
28
+
29
+ export { Switch }
@@ -0,0 +1,30 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import * as TooltipPrimitive from '@radix-ui/react-tooltip'
5
+
6
+ import { cn } from '@/lib/utils'
7
+
8
+ const TooltipProvider = TooltipPrimitive.Provider
9
+
10
+ const Tooltip = TooltipPrimitive.Root
11
+
12
+ const TooltipTrigger = TooltipPrimitive.Trigger
13
+
14
+ const TooltipContent = React.forwardRef<
15
+ React.ElementRef<typeof TooltipPrimitive.Content>,
16
+ React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
17
+ >(({ className, sideOffset = 4, ...props }, ref) => (
18
+ <TooltipPrimitive.Content
19
+ ref={ref}
20
+ sideOffset={sideOffset}
21
+ className={cn(
22
+ 'z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
23
+ className,
24
+ )}
25
+ {...props}
26
+ />
27
+ ))
28
+ TooltipContent.displayName = TooltipPrimitive.Content.displayName
29
+
30
+ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
@@ -0,0 +1,155 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+
5
+ function CopyIcon({ className }: { className?: string }) {
6
+ return (
7
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}>
8
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
9
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
10
+ </svg>
11
+ );
12
+ }
13
+
14
+ function CheckIcon({ className }: { className?: string }) {
15
+ return (
16
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}>
17
+ <polyline points="20 6 9 17 4 12" />
18
+ </svg>
19
+ );
20
+ }
21
+
22
+ const availableSounds = [
23
+ { name: 'button-click', use: 'Primary button presses', source: 'click1.mp3' },
24
+ { name: 'button-click-secondary', use: 'Secondary / ghost buttons', source: 'click2.mp3' },
25
+ { name: 'hover-blip', use: 'Hovering over interactive elements', source: 'Web Audio API' },
26
+ { name: 'hover-soft', use: 'Subtle hover feedback', source: 'Web Audio API' },
27
+ { name: 'success-chime', use: 'Form submissions, saves', source: 'Web Audio API' },
28
+ { name: 'success-bell', use: 'Achievements, milestones', source: 'Web Audio API' },
29
+ { name: 'error-buzz', use: 'Validation errors, failed actions', source: 'Web Audio API' },
30
+ { name: 'error-beep', use: 'Warnings, blocked actions', source: 'Web Audio API' },
31
+ { name: 'notification-ping', use: 'New messages, alerts', source: 'Web Audio API' },
32
+ { name: 'notification-subtle', use: 'Background updates', source: 'Web Audio API' },
33
+ { name: 'swoosh', use: 'Page transitions, panel slides', source: 'Web Audio API' },
34
+ { name: 'pop', use: 'Tooltips, popovers, reveals', source: 'Web Audio API' },
35
+ { name: 'slider-tick', use: 'Sliders, range inputs', source: 'Web Audio API' },
36
+ { name: 'key-press', use: 'Text inputs, keyboards', source: 'Web Audio API' },
37
+ ];
38
+
39
+ export function UsageGuide() {
40
+ const [copiedQuick, setCopiedQuick] = useState(false);
41
+
42
+ const quickStartCode = `const ctx = new AudioContext();
43
+ const osc = ctx.createOscillator();
44
+ const gain = ctx.createGain();
45
+ osc.type = 'sine';
46
+ osc.frequency.setValueAtTime(800, ctx.currentTime);
47
+ gain.gain.setValueAtTime(0.25, ctx.currentTime);
48
+ gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.15);
49
+ osc.connect(gain).connect(ctx.destination);
50
+ osc.start();
51
+ osc.stop(ctx.currentTime + 0.15);`;
52
+
53
+ return (
54
+ <div className="flex flex-col gap-6">
55
+ <div className="flex flex-col gap-2 border-b border-border pb-6">
56
+ <h2 className="text-2xl font-bold tracking-tight sm:text-3xl">
57
+ How to Use
58
+ </h2>
59
+ <p className="text-sm text-muted-foreground">
60
+ Every Web Audio sound has a <strong>copy</strong> button — it copies standalone code you can paste anywhere, no imports needed. MP3 sounds have a <strong>download</strong> button instead — drop the file in your project and play it with <code className="rounded bg-muted px-1.5 py-0.5 text-xs font-mono">new Audio()</code>.
61
+ </p>
62
+ </div>
63
+
64
+ <div className="grid gap-4 sm:grid-cols-2">
65
+ {/* Web Audio example */}
66
+ <div className="rounded-lg border border-border bg-card p-5">
67
+ <div className="flex items-center justify-between mb-3">
68
+ <h3 className="text-sm font-semibold text-card-foreground">Web Audio sounds</h3>
69
+ <button
70
+ onClick={() => {
71
+ navigator.clipboard.writeText(quickStartCode).then(() => {
72
+ setCopiedQuick(true);
73
+ setTimeout(() => setCopiedQuick(false), 2000);
74
+ });
75
+ }}
76
+ aria-label={copiedQuick ? 'Copied' : 'Copy code'}
77
+ className="rounded-md border border-border bg-muted p-1.5 text-muted-foreground transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
78
+ >
79
+ {copiedQuick ? <CheckIcon /> : <CopyIcon />}
80
+ </button>
81
+ </div>
82
+ <pre className="overflow-x-auto rounded-md bg-muted p-4 text-xs leading-relaxed font-mono text-muted-foreground">
83
+ <code>{quickStartCode}</code>
84
+ </pre>
85
+ <p className="mt-3 text-[11px] text-muted-foreground">
86
+ Zero dependencies. Works in any framework. Hit the copy button on any card above to get its snippet.
87
+ </p>
88
+ </div>
89
+
90
+ {/* MP3 example */}
91
+ <div className="rounded-lg border border-border bg-card p-5">
92
+ <div className="flex items-center justify-between mb-3">
93
+ <h3 className="text-sm font-semibold text-card-foreground">MP3 sounds</h3>
94
+ </div>
95
+ <pre className="overflow-x-auto rounded-md bg-muted p-4 text-xs leading-relaxed font-mono text-muted-foreground">
96
+ <code>{`// 1. Download the mp3 from the card above
97
+ // 2. Put it in your public/ folder
98
+ // 3. Play it:
99
+
100
+ const audio = new Audio('/click1.mp3');
101
+ audio.volume = 0.5;
102
+ audio.play();`}</code>
103
+ </pre>
104
+ <p className="mt-3 text-[11px] text-muted-foreground">
105
+ Hit the download button on the first two cards to save the mp3 files.
106
+ </p>
107
+ </div>
108
+ </div>
109
+
110
+ {/* Sound reference table */}
111
+ <div className="flex flex-col gap-3">
112
+ <h3 className="text-sm font-semibold">All Sounds</h3>
113
+ <div className="overflow-x-auto rounded-lg border border-border">
114
+ <table className="w-full text-sm" style={{ fontVariantNumeric: 'tabular-nums' }}>
115
+ <thead>
116
+ <tr className="border-b border-border bg-muted">
117
+ <th className="px-4 py-2.5 text-left text-xs font-medium text-muted-foreground">
118
+ Name
119
+ </th>
120
+ <th className="px-4 py-2.5 text-left text-xs font-medium text-muted-foreground">
121
+ Use
122
+ </th>
123
+ <th className="px-4 py-2.5 text-left text-xs font-medium text-muted-foreground">
124
+ Source
125
+ </th>
126
+ </tr>
127
+ </thead>
128
+ <tbody>
129
+ {availableSounds.map((sound) => (
130
+ <tr
131
+ key={sound.name}
132
+ className="border-b border-border last:border-0 transition-colors hover:bg-muted/50"
133
+ >
134
+ <td className="px-4 py-2.5">
135
+ <code className="rounded bg-muted px-1.5 py-0.5 text-[11px] font-mono text-card-foreground">
136
+ {sound.name}
137
+ </code>
138
+ </td>
139
+ <td className="px-4 py-2.5 text-xs text-muted-foreground">
140
+ {sound.use}
141
+ </td>
142
+ <td className="px-4 py-2.5">
143
+ <span className={`text-[10px] font-mono ${sound.source.endsWith('.mp3') ? 'text-amber-500' : 'text-emerald-500'}`}>
144
+ {sound.source}
145
+ </span>
146
+ </td>
147
+ </tr>
148
+ ))}
149
+ </tbody>
150
+ </table>
151
+ </div>
152
+ </div>
153
+ </div>
154
+ );
155
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "default",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "tailwind.config.ts",
8
+ "css": "app/globals.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "aliases": {
14
+ "components": "@/components",
15
+ "utils": "@/lib/utils",
16
+ "ui": "@/components/ui",
17
+ "lib": "@/lib",
18
+ "hooks": "@/hooks"
19
+ },
20
+ "iconLibrary": "lucide"
21
+ }