@@ -379,35 +379,68 @@ function ToolPanel({
379379
380380/* ---------- Master sliders (top bar on desktop, expandable on phone) ---------- */
381381
382+ /** Speed is logarithmic — 0.01x to 5x — so the slider carries a percentage of
383+ * that curve rather than the multiplier itself. */
384+ const SPEED_MIN = 0.01 ;
385+ const SPEED_MAX = 5.0 ;
386+ const speedToPct = ( v : number ) =>
387+ Math . round ( ( Math . log ( v / SPEED_MIN ) / Math . log ( SPEED_MAX / SPEED_MIN ) ) * 100 ) ;
388+ const pctToSpeed = ( pct : number ) => SPEED_MIN * Math . pow ( SPEED_MAX / SPEED_MIN , pct / 100 ) ;
389+ const formatSpeed = ( v : number ) => `${ v < 0.1 ? v . toFixed ( 3 ) : v < 1 ? v . toFixed ( 2 ) : v . toFixed ( 1 ) } x` ;
390+
391+ interface MasterSlider {
392+ label : string ;
393+ value : number ;
394+ display : string ;
395+ handler : ( pct : number ) => void ;
396+ flex : number ;
397+ }
398+
382399function MasterSliders ( {
383400 masterBright,
384401 smoothness,
385402 attack,
403+ animSpeed,
386404 onMasterBright,
387405 onSmooth,
388406 onAttack,
407+ onAnimSpeed,
389408 throttledSlider,
390409 vertical
391410} : {
392411 masterBright : number ;
393412 smoothness : number ;
394413 attack : number ;
414+ animSpeed : number ;
395415 onMasterBright : ( v : number ) => void ;
396416 onSmooth : ( v : number ) => void ;
397417 onAttack : ( v : number ) => void ;
418+ onAnimSpeed : ( v : number ) => void ;
398419 throttledSlider : ( handler : ( v : number ) => void ) => ( e : React . ChangeEvent < HTMLInputElement > ) => void ;
399420 vertical ?: boolean ;
400421} ) {
401- const sliders = [
402- { label : 'Bright' , value : masterBright , handler : onMasterBright , flex : 1 } ,
403- { label : 'Fade' , value : smoothness , handler : onSmooth , flex : 3 } ,
404- { label : 'Attack' , value : attack , handler : onAttack , flex : 1 }
422+ // The two an operator reaches for mid-show hold the fixed slots; brightness
423+ // and attack get set once, so they live behind the disclosure.
424+ const fixed : MasterSlider [ ] = [
425+ {
426+ label : 'Speed' ,
427+ value : speedToPct ( animSpeed ) ,
428+ display : formatSpeed ( animSpeed ) ,
429+ handler : ( pct ) => onAnimSpeed ( pctToSpeed ( pct ) ) ,
430+ flex : 2
431+ } ,
432+ { label : 'Fade' , value : smoothness , display : String ( smoothness ) , handler : onSmooth , flex : 3 }
433+ ] ;
434+ const extra : MasterSlider [ ] = [
435+ { label : 'Bright' , value : masterBright , display : String ( masterBright ) , handler : onMasterBright , flex : 1 } ,
436+ { label : 'Attack' , value : attack , display : String ( attack ) , handler : onAttack , flex : 1 }
405437 ] ;
438+ const [ showExtra , setShowExtra ] = useState ( false ) ;
406439
407440 if ( vertical ) {
408441 return (
409442 < div className = "space-y-3 p-4" >
410- { sliders . map ( ( s ) => (
443+ { [ ... fixed , ... extra ] . map ( ( s ) => (
411444 < div key = { s . label } className = "flex items-center gap-3" >
412445 < span className = "text-sm font-medium" style = { { color : '#888898' , minWidth : 56 } } >
413446 { s . label }
@@ -421,7 +454,7 @@ function MasterSliders({
421454 onChange = { throttledSlider ( s . handler ) }
422455 />
423456 < span className = "text-sm font-mono" style = { { color : '#888898' , minWidth : 32 , textAlign : 'right' } } >
424- { s . value }
457+ { s . display }
425458 </ span >
426459 </ div >
427460 ) ) }
@@ -431,7 +464,7 @@ function MasterSliders({
431464
432465 return (
433466 < >
434- { sliders . map ( ( s ) => (
467+ { [ ... fixed , ... ( showExtra ? extra : [ ] ) ] . map ( ( s ) => (
435468 < div key = { s . label } className = "flex items-center gap-2" style = { { minWidth : 0 , flex : s . flex } } >
436469 < span className = "text-xs font-medium shrink-0" style = { { color : '#888898' , textTransform : 'uppercase' , letterSpacing : '0.05em' , fontSize : 11 } } >
437470 { s . label }
@@ -446,10 +479,24 @@ function MasterSliders({
446479 onChange = { throttledSlider ( s . handler ) }
447480 />
448481 < span className = "text-xs font-mono shrink-0" style = { { color : '#888898' , minWidth : 28 , textAlign : 'right' } } >
449- { s . value }
482+ { s . display }
450483 </ span >
451484 </ div >
452485 ) ) }
486+ < button
487+ onClick = { ( ) => setShowExtra ( ( v ) => ! v ) }
488+ title = { showExtra ? 'Hide brightness and attack' : 'Brightness and attack' }
489+ className = "shrink-0 text-xs"
490+ style = { {
491+ padding : '2px 8px' ,
492+ borderRadius : 6 ,
493+ background : showExtra ? 'rgba(74,124,255,0.15)' : 'transparent' ,
494+ border : `1px solid ${ showExtra ? '#4a7cff' : '#1a1a25' } ` ,
495+ color : showExtra ? '#4a7cff' : '#888898'
496+ } }
497+ >
498+ ⋯
499+ </ button >
453500 </ >
454501 ) ;
455502}
@@ -896,9 +943,11 @@ export default function Home() {
896943 masterBright = { masterBright }
897944 smoothness = { smoothness }
898945 attack = { attack }
946+ animSpeed = { animSpeed }
899947 onMasterBright = { handleMasterBright }
900948 onSmooth = { handleSmooth }
901949 onAttack = { handleAttack }
950+ onAnimSpeed = { handleAnimSpeed }
902951 throttledSlider = { throttledSlider }
903952 vertical
904953 />
@@ -1156,9 +1205,11 @@ export default function Home() {
11561205 masterBright = { masterBright }
11571206 smoothness = { smoothness }
11581207 attack = { attack }
1208+ animSpeed = { animSpeed }
11591209 onMasterBright = { handleMasterBright }
11601210 onSmooth = { handleSmooth }
11611211 onAttack = { handleAttack }
1212+ onAnimSpeed = { handleAnimSpeed }
11621213 throttledSlider = { throttledSlider }
11631214 />
11641215 </ div >
0 commit comments