@@ -17,6 +17,11 @@ const MODE_COLORS = {
1717 exec : "hsl(268, 94%, 65%)" , // Slightly lighter than --color-exec-mode for visibility
1818} as const ;
1919
20+ // FFT size determines number of frequency bins (fftSize / 2)
21+ // Higher = more bars but less responsive, lower = fewer bars but more responsive
22+ const FFT_SIZE = 128 ; // 64 bars
23+ const NUM_BARS = FFT_SIZE / 2 ;
24+
2025interface RecordingOverlayProps {
2126 state : VoiceInputState ;
2227 mode : UIMode ;
@@ -28,22 +33,37 @@ export const RecordingOverlay: React.FC<RecordingOverlayProps> = (props) => {
2833 const isRecording = props . state === "recording" ;
2934 const isTranscribing = props . state === "transcribing" ;
3035 const containerRef = useRef < HTMLDivElement > ( null ) ;
31- const [ containerWidth , setContainerWidth ] = useState ( 400 ) ;
36+ const [ containerWidth , setContainerWidth ] = useState ( 600 ) ;
3237
33- // Measure container width for the canvas
38+ // Measure container width for the canvas using ResizeObserver
3439 useLayoutEffect ( ( ) => {
35- const measure = ( ) => {
36- if ( containerRef . current ) {
37- setContainerWidth ( containerRef . current . offsetWidth ) ;
40+ const container = containerRef . current ;
41+ if ( ! container ) return ;
42+
43+ const observer = new ResizeObserver ( ( entries ) => {
44+ for ( const entry of entries ) {
45+ setContainerWidth ( entry . contentRect . width ) ;
3846 }
39- } ;
40- measure ( ) ;
41- window . addEventListener ( "resize" , measure ) ;
42- return ( ) => window . removeEventListener ( "resize" , measure ) ;
47+ } ) ;
48+
49+ observer . observe ( container ) ;
50+ // Initial measurement
51+ setContainerWidth ( container . offsetWidth ) ;
52+
53+ return ( ) => observer . disconnect ( ) ;
4354 } , [ ] ) ;
4455
4556 const modeColor = MODE_COLORS [ props . mode ] ;
4657
58+ // Calculate bar dimensions to fill the container width
59+ // Total width = numBars * barWidth + (numBars - 1) * gap
60+ // We want gap = barWidth / 2 for nice spacing
61+ // So: width = numBars * barWidth + (numBars - 1) * barWidth/2
62+ // = barWidth * (numBars + (numBars - 1) / 2)
63+ // = barWidth * (1.5 * numBars - 0.5)
64+ const barWidth = Math . max ( 2 , Math . floor ( containerWidth / ( 1.5 * NUM_BARS - 0.5 ) ) ) ;
65+ const gap = Math . max ( 1 , Math . floor ( barWidth / 2 ) ) ;
66+
4767 // Border and background classes based on state
4868 const containerClasses = cn (
4969 "mb-1 flex w-full flex-col items-center justify-center gap-1 rounded-md border px-3 py-2 transition-all focus:outline-none" ,
@@ -69,13 +89,13 @@ export const RecordingOverlay: React.FC<RecordingOverlayProps> = (props) => {
6989 mediaRecorder = { props . mediaRecorder }
7090 width = { containerWidth }
7191 height = { 32 }
72- barWidth = { 2 }
73- gap = { 1 }
92+ barWidth = { barWidth }
93+ gap = { gap }
7494 barColor = { modeColor }
75- smoothingTimeConstant = { 0.5 }
76- fftSize = { 256 }
77- minDecibels = { - 80 }
78- maxDecibels = { - 20 }
95+ smoothingTimeConstant = { 0.8 }
96+ fftSize = { FFT_SIZE }
97+ minDecibels = { - 70 }
98+ maxDecibels = { - 30 }
7999 />
80100 ) : (
81101 < TranscribingAnimation />
0 commit comments