@@ -154,22 +154,22 @@ function FormatInfoTooltip({ format }: { format: "mp4" | "webm" | "mov" }) {
154154 strokeWidth = "2"
155155 strokeLinecap = "round"
156156 strokeLinejoin = "round"
157- className = "text-neutral-600 hover:text-neutral-400 transition-colors cursor-help"
157+ className = "text-panel-text-5 hover:text-panel-text-3 transition-colors cursor-help"
158158 >
159159 < circle cx = "12" cy = "12" r = "10" />
160160 < path d = "M9.09 9a3 3 0 015.83 1c0 2-3 3-3 3" />
161161 < line x1 = "12" y1 = "17" x2 = "12.01" y2 = "17" />
162162 </ svg >
163163 { open && (
164- < div className = "absolute top-full right-0 mt-1.5 w-52 p-2 rounded bg-neutral-900 border border-neutral-700 shadow-lg z-50" >
165- < p className = "text-[10px] font-semibold text-neutral-200 mb-0.5" > { info . label } </ p >
166- < p className = "text-[9px] text-neutral-400 leading-tight" > { info . desc } </ p >
164+ < div className = "absolute top-full right-0 mt-1.5 w-52 p-2 rounded bg-panel-input border border-neutral-700 shadow-lg z-50" >
165+ < p className = "text-[10px] font-semibold text-panel-text-1 mb-0.5" > { info . label } </ p >
166+ < p className = "text-[9px] text-panel-text-3 leading-tight" > { info . desc } </ p >
167167 < div className = "mt-1.5 pt-1.5 border-t border-neutral-800" >
168168 { ( [ "mp4" , "mov" , "webm" ] as const )
169169 . filter ( ( f ) => f !== format )
170170 . map ( ( f ) => (
171- < p key = { f } className = "text-[9px] text-neutral-500 leading-relaxed" >
172- < span className = "text-neutral-400 font-medium" > { FORMAT_INFO [ f ] . label } </ span >
171+ < p key = { f } className = "text-[9px] text-panel-text-4 leading-relaxed" >
172+ < span className = "text-panel-text-3 font-medium" > { FORMAT_INFO [ f ] . label } </ span >
173173 { " — " }
174174 { FORMAT_INFO [ f ] . desc }
175175 </ p >
@@ -209,80 +209,97 @@ function FormatExportButton({
209209 // MOV (ProRes) is a fixed-quality codec — quality selector has no effect.
210210 const showQuality = format !== "mov" ;
211211
212+ const selectCls =
213+ "h-7 w-full px-2 text-[11px] bg-panel-input rounded-md text-panel-text-1 outline-none cursor-pointer disabled:opacity-50 hover:bg-panel-hover transition-colors" ;
214+
212215 return (
213- < div className = "flex items-center gap-1 flex-wrap justify-end" >
214- < FormatInfoTooltip format = { format } />
215- { /* Resolution must remain the leftmost <select> in this row — it
216- carries `rounded-l` for the joined-button look. If you ever hide it
217- (feature-flag, etc.), move `rounded-l` to whichever element ends up
218- leftmost. */ }
219- < select
220- value = { resolution }
221- onChange = { ( e ) => setResolution ( e . target . value as ResolutionPreset | "auto" ) }
222- disabled = { isRendering }
223- className = "h-5 px-1 text-[10px] rounded-l bg-neutral-800 border border-neutral-700 text-neutral-300 outline-none disabled:opacity-50"
224- >
225- { SCALE_OPTION_ORDER . map ( ( value ) => (
226- < option key = { value } value = { value } disabled = { ! scaleApplies ( value , compositionDimensions ) } >
227- { scaleOptionLabel ( value , compositionDimensions ) }
228- </ option >
229- ) ) }
230- </ select >
231- { showQuality && (
232- < select
233- value = { quality }
234- onChange = { ( e ) => {
235- const v = e . target . value as "draft" | "standard" | "high" ;
236- setQuality ( v ) ;
237- persistRenderSettings ( format , v , fps ) ;
238- } }
239- disabled = { isRendering }
240- title = { QUALITY_OPTIONS . find ( ( q ) => q . value === quality ) ?. title }
241- className = "h-5 px-1 text-[10px] bg-neutral-800 border border-neutral-700 text-neutral-300 outline-none disabled:opacity-50"
242- >
243- { QUALITY_OPTIONS . map ( ( q ) => (
244- < option key = { q . value } value = { q . value } title = { q . title } >
245- { q . label }
246- </ option >
247- ) ) }
248- </ select >
249- ) }
250- < select
251- value = { fps }
252- onChange = { ( e ) => {
253- const v = Number ( e . target . value ) as 24 | 30 | 60 ;
254- setFps ( v ) ;
255- persistRenderSettings ( format , quality , v ) ;
256- } }
257- disabled = { isRendering }
258- title = "Frames per second"
259- className = "h-5 px-1 text-[10px] bg-neutral-800 border border-neutral-700 text-neutral-300 outline-none disabled:opacity-50"
260- >
261- < option value = { 24 } > 24fps</ option >
262- < option value = { 30 } > 30fps</ option >
263- < option value = { 60 } > 60fps</ option >
264- </ select >
265- < select
266- value = { format }
267- onChange = { ( e ) => {
268- const v = e . target . value as "mp4" | "webm" | "mov" ;
269- setFormat ( v ) ;
270- persistRenderSettings ( v , quality , fps ) ;
271- } }
272- disabled = { isRendering }
273- className = "h-5 px-1 text-[10px] bg-neutral-800 border border-neutral-700 text-neutral-300 outline-none disabled:opacity-50"
274- >
275- < option value = "mp4" > MP4</ option >
276- < option value = "mov" > MOV</ option >
277- < option value = "webm" > WebM</ option >
278- </ select >
216+ < div className = "flex flex-col gap-3" >
217+ < div className = "grid grid-cols-2 gap-2" >
218+ < div className = "flex flex-col gap-1" >
219+ < div className = "flex items-center gap-1" >
220+ < span className = "text-[10px] text-panel-text-4" > Format</ span >
221+ < FormatInfoTooltip format = { format } />
222+ </ div >
223+ < select
224+ value = { format }
225+ onChange = { ( e ) => {
226+ const v = e . target . value as "mp4" | "webm" | "mov" ;
227+ setFormat ( v ) ;
228+ persistRenderSettings ( v , quality , fps ) ;
229+ } }
230+ disabled = { isRendering }
231+ className = { selectCls }
232+ >
233+ < option value = "mp4" > MP4</ option >
234+ < option value = "mov" > MOV (ProRes)</ option >
235+ < option value = "webm" > WebM</ option >
236+ </ select >
237+ </ div >
238+ < div className = "flex flex-col gap-1" >
239+ < span className = "text-[10px] text-panel-text-4" > Resolution</ span >
240+ < select
241+ value = { resolution }
242+ onChange = { ( e ) => setResolution ( e . target . value as ResolutionPreset | "auto" ) }
243+ disabled = { isRendering }
244+ className = { selectCls }
245+ >
246+ { SCALE_OPTION_ORDER . map ( ( value ) => (
247+ < option
248+ key = { value }
249+ value = { value }
250+ disabled = { ! scaleApplies ( value , compositionDimensions ) }
251+ >
252+ { scaleOptionLabel ( value , compositionDimensions ) }
253+ </ option >
254+ ) ) }
255+ </ select >
256+ </ div >
257+ < div className = "flex flex-col gap-1" >
258+ < span className = "text-[10px] text-panel-text-4" > Frame rate</ span >
259+ < select
260+ value = { fps }
261+ onChange = { ( e ) => {
262+ const v = Number ( e . target . value ) as 24 | 30 | 60 ;
263+ setFps ( v ) ;
264+ persistRenderSettings ( format , quality , v ) ;
265+ } }
266+ disabled = { isRendering }
267+ className = { selectCls }
268+ >
269+ < option value = { 24 } > 24 fps</ option >
270+ < option value = { 30 } > 30 fps</ option >
271+ < option value = { 60 } > 60 fps</ option >
272+ </ select >
273+ </ div >
274+ { showQuality && (
275+ < div className = "flex flex-col gap-1" >
276+ < span className = "text-[10px] text-panel-text-4" > Quality</ span >
277+ < select
278+ value = { quality }
279+ onChange = { ( e ) => {
280+ const v = e . target . value as "draft" | "standard" | "high" ;
281+ setQuality ( v ) ;
282+ persistRenderSettings ( format , v , fps ) ;
283+ } }
284+ disabled = { isRendering }
285+ className = { selectCls }
286+ >
287+ { QUALITY_OPTIONS . map ( ( q ) => (
288+ < option key = { q . value } value = { q . value } >
289+ { q . label }
290+ </ option >
291+ ) ) }
292+ </ select >
293+ </ div >
294+ ) }
295+ </ div >
279296 < button
280297 onClick = { ( ) => {
281298 trackStudioEvent ( "render_start" , { format, quality, resolution, fps } ) ;
282299 void onStartRender ( format , quality , resolution , fps ) ;
283300 } }
284301 disabled = { isRendering }
285- className = "flex items-center gap-1 px-2 py-0.5 text-[10px ] font-semibold rounded-r bg-studio -accent text-[#09090B] hover:brightness-110 transition-colors disabled:opacity-50"
302+ className = "w-full flex items-center justify-center h-8 text-[11px ] font-semibold rounded-md bg-panel -accent text-[#09090B] hover:brightness-110 transition-colors disabled:opacity-50"
286303 >
287304 { isRendering ? "Rendering..." : "Export" }
288305 </ button >
@@ -313,23 +330,12 @@ export const RenderQueue = memo(function RenderQueue({
313330
314331 return (
315332 < div className = "flex flex-col h-full" >
316- { /* Header — no title, already shown in header button */ }
317- < div className = "flex items-center justify-end flex-wrap gap-y-1.5 px-3 py-2 border-b border-neutral-800/50 flex-shrink-0" >
318- < div className = "flex items-center gap-1.5" >
319- { completedCount > 0 && (
320- < button
321- onClick = { onClearCompleted }
322- className = "text-[10px] text-neutral-600 hover:text-neutral-400 transition-colors"
323- >
324- Clear
325- </ button >
326- ) }
327- < FormatExportButton
328- onStartRender = { onStartRender }
329- isRendering = { isRendering }
330- compositionDimensions = { compositionDimensions }
331- />
332- </ div >
333+ < div className = "px-3 py-3 border-b border-panel-border flex-shrink-0" >
334+ < FormatExportButton
335+ onStartRender = { onStartRender }
336+ isRendering = { isRendering }
337+ compositionDimensions = { compositionDimensions }
338+ />
333339 </ div >
334340
335341 { /* Job list */ }
@@ -343,7 +349,7 @@ export const RenderQueue = memo(function RenderQueue({
343349 fill = "none"
344350 stroke = "currentColor"
345351 strokeWidth = "1.5"
346- className = "text-neutral-700 "
352+ className = "text-panel-text-5 "
347353 >
348354 < rect
349355 x = "2"
@@ -361,17 +367,32 @@ export const RenderQueue = memo(function RenderQueue({
361367 strokeLinejoin = "round"
362368 />
363369 </ svg >
364- < p className = "text-[10px] text-neutral-600 text-center" > No renders yet</ p >
370+ < p className = "text-[10px] text-panel-text-5 text-center" > No renders yet</ p >
365371 </ div >
366372 ) : (
367- jobs . map ( ( job ) => (
368- < RenderQueueItem
369- key = { job . id }
370- job = { job }
371- projectId = { projectId }
372- onDelete = { ( ) => onDelete ( job . id ) }
373- />
374- ) )
373+ < div >
374+ { completedCount > 0 && (
375+ < div className = "flex items-center justify-between px-3 py-1.5 border-b border-panel-border" >
376+ < span className = "text-[10px] text-panel-text-4" >
377+ { jobs . length } render{ jobs . length === 1 ? "" : "s" }
378+ </ span >
379+ < button
380+ onClick = { onClearCompleted }
381+ className = "text-[10px] text-panel-text-4 hover:text-panel-text-2 transition-colors"
382+ >
383+ Clear
384+ </ button >
385+ </ div >
386+ ) }
387+ { jobs . map ( ( job ) => (
388+ < RenderQueueItem
389+ key = { job . id }
390+ job = { job }
391+ projectId = { projectId }
392+ onDelete = { ( ) => onDelete ( job . id ) }
393+ />
394+ ) ) }
395+ </ div >
375396 ) }
376397 </ div >
377398 </ div >
0 commit comments