Avery Felts 0f17775f3f Add multi-genre support and expanded instrument options
- Add 4 genres: Hip Hop, Classical, Trap, Pop with unique patterns
- Add new instrument layers: Bass, Brass, Piano
- Each layer now has 4 instrument variations to choose from
- Add genre-specific drum patterns, chord progressions, and melodies
- Add duration control (1-10 minutes)
- Rename app to "Beat Generator" with modern gradient header
- Redesign UI with 2-column instrument grid layout
- Add color-coded accent for each instrument section

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 17:57:12 -07:00

144 lines
4.2 KiB
TypeScript

'use client';
import { Toggle } from '@/components/ui/toggle';
import { Slider } from '@/components/ui/slider';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Volume2, VolumeX } from 'lucide-react';
import { ReactNode } from 'react';
interface InstrumentOption {
value: string;
label: string;
}
interface LayerBoxProps {
title: string;
icon: ReactNode;
volume: number;
muted: boolean;
instrument: string;
instrumentOptions: InstrumentOption[];
onVolumeChange: (volume: number) => void;
onToggleMute: () => void;
onInstrumentChange: (instrument: string) => void;
accentColor: 'orange' | 'pink' | 'purple' | 'blue' | 'green' | 'yellow';
}
const accentStyles = {
orange: {
border: 'border-lofi-orange/30 hover:border-lofi-orange/50',
bg: 'bg-lofi-orange/5',
icon: 'text-lofi-orange',
slider: '[&_[data-slot=slider-range]]:bg-lofi-orange [&_[data-slot=slider-thumb]]:border-lofi-orange',
},
pink: {
border: 'border-lofi-pink/30 hover:border-lofi-pink/50',
bg: 'bg-lofi-pink/5',
icon: 'text-lofi-pink',
slider: '[&_[data-slot=slider-range]]:bg-lofi-pink [&_[data-slot=slider-thumb]]:border-lofi-pink',
},
purple: {
border: 'border-lofi-purple/30 hover:border-lofi-purple/50',
bg: 'bg-lofi-purple/5',
icon: 'text-lofi-purple',
slider: '[&_[data-slot=slider-range]]:bg-lofi-purple [&_[data-slot=slider-thumb]]:border-lofi-purple',
},
blue: {
border: 'border-blue-400/30 hover:border-blue-400/50',
bg: 'bg-blue-400/5',
icon: 'text-blue-400',
slider: '[&_[data-slot=slider-range]]:bg-blue-400 [&_[data-slot=slider-thumb]]:border-blue-400',
},
green: {
border: 'border-emerald-400/30 hover:border-emerald-400/50',
bg: 'bg-emerald-400/5',
icon: 'text-emerald-400',
slider: '[&_[data-slot=slider-range]]:bg-emerald-400 [&_[data-slot=slider-thumb]]:border-emerald-400',
},
yellow: {
border: 'border-yellow-400/30 hover:border-yellow-400/50',
bg: 'bg-yellow-400/5',
icon: 'text-yellow-400',
slider: '[&_[data-slot=slider-range]]:bg-yellow-400 [&_[data-slot=slider-thumb]]:border-yellow-400',
},
};
export function LayerBox({
title,
icon,
volume,
muted,
instrument,
instrumentOptions,
onVolumeChange,
onToggleMute,
onInstrumentChange,
accentColor,
}: LayerBoxProps) {
const styles = accentStyles[accentColor];
return (
<div
className={`
rounded-xl border-2 p-4 transition-all duration-200
${styles.border} ${styles.bg}
`}
>
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<span className={styles.icon}>{icon}</span>
<span className="font-medium text-sm">{title}</span>
</div>
<div className="flex items-center gap-2">
<Select value={instrument} onValueChange={onInstrumentChange}>
<SelectTrigger className="h-7 w-28 text-xs bg-secondary/50 border-border/50">
<SelectValue />
</SelectTrigger>
<SelectContent>
{instrumentOptions.map((opt) => (
<SelectItem key={opt.value} value={opt.value} className="text-xs">
{opt.label}
</SelectItem>
))}
</SelectContent>
</Select>
<Toggle
pressed={!muted}
onPressedChange={onToggleMute}
size="sm"
className="h-7 w-7 p-0"
aria-label={`Toggle ${title}`}
>
{muted ? (
<VolumeX className="h-3.5 w-3.5 text-muted-foreground" />
) : (
<Volume2 className={`h-3.5 w-3.5 ${styles.icon}`} />
)}
</Toggle>
</div>
</div>
<div className="flex items-center gap-3">
<Slider
value={[volume]}
onValueChange={([v]) => onVolumeChange(v)}
min={0}
max={1}
step={0.01}
className={`flex-1 ${styles.slider}`}
disabled={muted}
/>
<span className="text-xs text-muted-foreground font-mono w-10 text-right">
{Math.round(volume * 100)}%
</span>
</div>
</div>
);
}