diff --git a/app/globals.css b/app/globals.css index a3dfc05..6f60215 100644 --- a/app/globals.css +++ b/app/globals.css @@ -354,4 +354,31 @@ .custom-scrollbar::-webkit-scrollbar-thumb:active { background: color-mix(in oklch, var(--muted-foreground) 70%, transparent); } + + /* Clip/Region styling for Ableton-style appearance */ + .track-clip-container { + @apply absolute inset-2 rounded-sm shadow-sm overflow-hidden transition-all duration-150; + background: oklch(0.2 0.01 var(--hue) / 0.3); + border: 1px solid oklch(0.4 0.02 var(--hue) / 0.5); + } + + .track-clip-container:hover { + border-color: oklch(0.5 0.03 var(--hue) / 0.7); + } + + .track-clip-header { + @apply absolute top-0 left-0 right-0 h-4 pointer-events-none z-10 px-2 flex items-center; + background: linear-gradient(to bottom, rgb(0 0 0 / 0.1), transparent); + } +} + +@layer utilities { + [data-theme='light'] .track-clip-container { + background: oklch(0.95 0.01 var(--hue) / 0.3); + border: 1px solid oklch(0.7 0.02 var(--hue) / 0.5); + } + + [data-theme='light'] .track-clip-header { + background: linear-gradient(to bottom, rgb(255 255 255 / 0.15), transparent); + } } diff --git a/components/tracks/Track.tsx b/components/tracks/Track.tsx index a18b8a3..ffde1cd 100644 --- a/components/tracks/Track.tsx +++ b/components/tracks/Track.tsx @@ -3,6 +3,7 @@ import * as React from 'react'; import { Volume2, VolumeX, Headphones, Trash2, ChevronDown, ChevronRight, UnfoldHorizontal, Upload, Plus, Mic, Gauge } from 'lucide-react'; import type { Track as TrackType } from '@/types/track'; +import { COLLAPSED_TRACK_HEIGHT, MIN_TRACK_HEIGHT, MAX_TRACK_HEIGHT } from '@/types/track'; import { Button } from '@/components/ui/Button'; import { Slider } from '@/components/ui/Slider'; import { cn } from '@/lib/utils/cn'; @@ -77,6 +78,8 @@ export function Track({ const [showEffects, setShowEffects] = React.useState(false); const [themeKey, setThemeKey] = React.useState(0); const inputRef = React.useRef(null); + const [isResizing, setIsResizing] = React.useState(false); + const resizeStartRef = React.useRef({ y: 0, height: 0 }); // Selection state const [isSelecting, setIsSelecting] = React.useState(false); @@ -393,7 +396,44 @@ export function Track({ } }; - const trackHeight = track.collapsed ? 48 : track.height; + const trackHeight = track.collapsed ? COLLAPSED_TRACK_HEIGHT : track.height; + + // Track height resize handlers + const handleResizeStart = React.useCallback( + (e: React.MouseEvent) => { + if (track.collapsed) return; + e.preventDefault(); + e.stopPropagation(); + setIsResizing(true); + resizeStartRef.current = { y: e.clientY, height: track.height }; + }, + [track.collapsed, track.height] + ); + + React.useEffect(() => { + if (!isResizing) return; + + const handleMouseMove = (e: MouseEvent) => { + const delta = e.clientY - resizeStartRef.current.y; + const newHeight = Math.max( + MIN_TRACK_HEIGHT, + Math.min(MAX_TRACK_HEIGHT, resizeStartRef.current.height + delta) + ); + onUpdateTrack(track.id, { height: newHeight }); + }; + + const handleMouseUp = () => { + setIsResizing(false); + }; + + window.addEventListener('mousemove', handleMouseMove); + window.addEventListener('mouseup', handleMouseUp); + + return () => { + window.removeEventListener('mousemove', handleMouseMove); + window.removeEventListener('mouseup', handleMouseUp); + }; + }, [isResizing, onUpdateTrack, track.id]); return (
{/* Left: Track Control Panel (Fixed Width) - Ableton Style */}
e.stopPropagation()} > {/* Track Name (Full Width) */} @@ -520,45 +560,59 @@ export function Track({ {/* Track Controls - Only show when not collapsed */} {!track.collapsed && ( -
+
{/* Pan Knob */} - +
+ +
{/* Vertical Volume Fader with integrated meter */} - +
+ +
)}
{/* Right: Waveform Area (Flexible Width) */}
{track.audioBuffer ? ( - +
+ {/* Clip Header */} +
+ + {track.name} + +
+ + {/* Waveform Canvas */} + +
) : ( !track.collapsed && ( <> @@ -594,7 +648,7 @@ export function Track({ {/* Bottom: Effects Section (Collapsible, Full Width) - Ableton Style */} {!track.collapsed && ( -
+
{/* Effects Header - clickable to toggle */}
+
{track.effectChain.effects.length === 0 ? (
@@ -731,6 +785,20 @@ export function Track({
)} + {/* Track Height Resize Handle */} + {!track.collapsed && ( +
+
+
+ )} + {/* Effect Browser Dialog */} {/* Level Meter Background (green/yellow/red gradient) */}
= { gray: 'rgb(156, 163, 175)', }; -export const DEFAULT_TRACK_HEIGHT = 180; -export const MIN_TRACK_HEIGHT = 60; -export const MAX_TRACK_HEIGHT = 300; +export const DEFAULT_TRACK_HEIGHT = 240; // Increased from 180 to accommodate vertical controls +export const MIN_TRACK_HEIGHT = 120; // Increased from 60 for vertical fader/knob layout +export const MAX_TRACK_HEIGHT = 400; // Increased from 300 for better waveform viewing +export const COLLAPSED_TRACK_HEIGHT = 48; // Extracted constant for collapsed state