@@ -140,7 +140,7 @@ function updateArrowButtonsState() {
140140
141141function markLoaded ( frame ) {
142142 const oldFrameId = loadedFrameId ; // Store the old ID
143-
143+
144144 // Remove marker from the old frame
145145 if ( oldFrameId !== null ) {
146146 const prev = document . querySelector ( `#frames [data-id='${ oldFrameId } ']` ) ;
@@ -149,11 +149,11 @@ function markLoaded(frame){
149149 prev . classList . remove ( 'selected' ) ;
150150 }
151151 }
152-
152+
153153 // Update the global state
154154 loadedFrameId = frame ? frame . id : null ;
155155 loadedFrame = frame ;
156-
156+
157157 // Add marker to the new frame
158158 if ( frame && frame . id ) {
159159 try {
@@ -207,20 +207,20 @@ async function persistFrame(){
207207 // Backend is responsible for naming - send empty if no value
208208 const frameName = ( loadedFrame && loadedFrame . name ) || '' ;
209209 const duration_ms = ( loadedFrame && loadedFrame . duration_ms ) || 1000 ;
210-
210+
211211 // Build payload with ID if we're updating an existing frame
212212 const payload = {
213213 rows : grid ,
214214 name : frameName ,
215215 duration_ms : duration_ms ,
216216 brightness_levels : BRIGHTNESS_LEVELS
217217 } ;
218-
218+
219219 if ( loadedFrame && loadedFrame . id ) {
220220 payload . id = loadedFrame . id ;
221221 payload . position = loadedFrame . position ;
222222 }
223-
223+
224224 console . debug ( '[ui] persistFrame (save to DB + update board)' , payload ) ;
225225
226226 try {
@@ -278,24 +278,24 @@ async function initEditor(){
278278 headers : { 'Content-Type' :'application/json' } ,
279279 body : JSON . stringify ( { } ) // no id = load last or create empty
280280 } , 'json' , 'load initial frame' ) ;
281-
281+
282282 if ( data && data . ok && data . frame ) {
283283 const frame = data . frame ;
284-
284+
285285 // Populate grid
286286 setGridFromRows ( frame . rows || [ ] ) ;
287-
287+
288288 // Populate name input
289289 if ( frameTitle ) frameTitle . textContent = frame . name || `Frame ${ frame . id } ` ;
290-
290+
291291 // Show C vector representation
292292 if ( data . vector ) {
293293 showVectorText ( data . vector ) ;
294294 }
295-
295+
296296 // Mark as loaded in sidebar
297297 markLoaded ( frame ) ;
298-
298+
299299 console . debug ( '[ui] initEditor loaded frame:' , frame . id ) ;
300300 }
301301 } catch ( err ) {
@@ -343,7 +343,7 @@ function displayFrame(frame) {
343343
344344 // Populate grid
345345 setGridFromRows ( frame . rows || [ ] ) ;
346-
346+
347347 // Populate name input
348348 if ( frameTitle ) frameTitle . textContent = frame . name || `Frame ${ frame . id } ` ;
349349
@@ -353,7 +353,7 @@ function displayFrame(frame) {
353353
354354async function playAnimation ( ) {
355355 if ( ! playAnimationBtn ) return ;
356-
356+
357357 // Stop any previous animation loop
358358 if ( animationTimeout ) {
359359 clearTimeout ( animationTimeout ) ;
@@ -368,20 +368,20 @@ async function playAnimation() {
368368 playAnimationBtn . disabled = false ; // re-enable button
369369 return ;
370370 }
371-
371+
372372 console . debug ( `[ui] playAnimation, frameIds=` , frameIds ) ;
373-
373+
374374 const payload = {
375375 frames : frameIds ,
376376 loop : false
377377 } ;
378-
378+
379379 const data = await fetchWithHandling ( '/play_animation' , {
380380 method : 'POST' ,
381381 headers : { 'Content-Type' : 'application/json' } ,
382382 body : JSON . stringify ( payload )
383383 } , 'json' , 'play animation' ) ;
384-
384+
385385 if ( data . error ) {
386386 showError ( 'Error: ' + data . error ) ;
387387 playAnimationBtn . disabled = false ;
@@ -401,10 +401,10 @@ async function playAnimation() {
401401
402402 const frame = sessionFrames [ currentFrameIndex ] ;
403403 displayFrame ( frame ) ;
404-
404+
405405 const duration = frame . duration_ms || 1000 ;
406406 currentFrameIndex ++ ;
407-
407+
408408 animationTimeout = setTimeout ( animateNextFrame , duration ) ;
409409 } ;
410410 animateNextFrame ( ) ;
@@ -487,7 +487,7 @@ async function refreshFrames(){
487487 const data = await fetchWithHandling ( '/list_frames' , { } , 'json' , 'refresh frames' ) ;
488488 sessionFrames = data . frames || [ ] ;
489489 renderFrames ( ) ;
490-
490+
491491 // Re-apply loaded state after rendering
492492 if ( loadedFrameId !== null && loadedFrame !== null ) {
493493 const el = document . querySelector ( `#frames [data-id='${ loadedFrameId } ']` ) ;
@@ -507,7 +507,7 @@ function createEditableField(element, onSave) {
507507 const input = document . createElement ( 'input' ) ;
508508 input . type = 'text' ;
509509 input . value = originalValue . replace ( / m s $ / , '' ) ; // Remove ' ms' for duration
510-
510+
511511 // Replace element with input
512512 element . style . display = 'none' ;
513513 element . parentNode . insertBefore ( input , element ) ;
@@ -559,7 +559,7 @@ function renderFrames(){
559559 }
560560 const name = document . createElement ( 'div' ) ; name . className = 'frame-name' ; name . textContent = f . name || ( 'Frame ' + f . id ) ;
561561 const duration = document . createElement ( 'div' ) ; duration . className = 'frame-duration' ; duration . textContent = `${ f . duration_ms || 1000 } ms` ;
562-
562+
563563 // Make name and duration editable
564564 createEditableField ( name , ( newName ) => {
565565 const rows = ( f . id === loadedFrameId ) ? collectGridBrightness ( ) : f . rows ;
@@ -569,7 +569,7 @@ function renderFrames(){
569569 body : JSON . stringify ( { id : f . id , name : newName , duration_ms : f . duration_ms , rows : rows } )
570570 } ) . then ( ( ) => refreshFrames ( ) ) ;
571571 } ) ;
572-
572+
573573 createEditableField ( duration , ( newDuration ) => {
574574 const durationMs = parseInt ( newDuration , 10 ) ;
575575 if ( ! isNaN ( durationMs ) ) {
@@ -586,7 +586,7 @@ function renderFrames(){
586586 item . addEventListener ( 'click' , ( e ) => {
587587 // Don't do anything if clicking inside an input field during editing
588588 if ( e . target . tagName === 'INPUT' ) return ;
589-
589+
590590 // If it's already selected, do nothing
591591 if ( loadedFrameId === f . id ) return ;
592592
@@ -616,9 +616,9 @@ function renderFrames(){
616616 await refreshFrames ( ) ;
617617 }
618618 } ) ;
619-
619+
620620 item . appendChild ( thumb ) ; item . appendChild ( name ) ; item . appendChild ( duration ) ;
621-
621+
622622 container . appendChild ( item ) ;
623623 } ) ;
624624
@@ -684,24 +684,24 @@ async function loadFrameIntoEditor(id){
684684 headers :{ 'Content-Type' :'application/json' } ,
685685 body : JSON . stringify ( { id} )
686686 } , 'json' , `load frame ${ id } ` ) ;
687-
687+
688688 if ( data && data . ok && data . frame ) {
689689 const f = data . frame ;
690-
690+
691691 // Populate grid
692692 setGridFromRows ( f . rows || [ ] ) ;
693-
693+
694694 // Populate name input
695695 if ( frameTitle ) frameTitle . textContent = f . name || `Frame ${ f . id } ` ;
696-
696+
697697 // Mark as loaded in sidebar
698698 markLoaded ( f ) ;
699-
699+
700700 // Show C vector representation (backend already sends it via load_frame)
701701 if ( data . vector ) {
702702 showVectorText ( data . vector ) ;
703703 }
704-
704+
705705 console . debug ( '[ui] loaded frame into editor:' , id ) ;
706706 }
707707 } catch ( err ) {
@@ -734,14 +734,14 @@ async function deleteFrame(id){
734734
735735async function handleNewFrameClick ( ) {
736736 console . debug ( '[ui] new frame button clicked' ) ;
737-
737+
738738 // Clear editor
739739 cells . forEach ( c => { c . classList . remove ( 'on' ) ; delete c . dataset . b ; } ) ;
740740 showVectorText ( '' ) ;
741-
741+
742742 // Clear loaded frame reference (we're creating new)
743743 clearLoaded ( ) ;
744-
744+
745745 // Create empty frame in DB (no name = backend assigns progressive name)
746746 const grid = collectGridBrightness ( ) ; // all zeros
747747 try {
@@ -755,22 +755,22 @@ async function handleNewFrameClick() {
755755 brightness_levels : BRIGHTNESS_LEVELS
756756 } )
757757 } , 'json' , 'create new frame' ) ;
758-
758+
759759 if ( data && data . ok && data . frame ) {
760760 // Set name to the backend-assigned name (Frame {id})
761761 if ( frameTitle ) frameTitle . textContent = data . frame . name || `Frame ${ data . frame . id } ` ;
762-
762+
763763 // Show C vector representation
764764 if ( data . vector ) {
765765 showVectorText ( data . vector ) ;
766766 }
767-
767+
768768 // Refresh frames list
769769 await refreshFrames ( ) ;
770-
770+
771771 // Mark as loaded
772772 markLoaded ( data . frame ) ;
773-
773+
774774 console . debug ( '[ui] new frame created:' , data . frame . id ) ;
775775 }
776776 } catch ( err ) {
@@ -803,18 +803,20 @@ document.addEventListener('DOMContentLoaded', () => {
803803 if ( customSelect ) {
804804 const trigger = customSelect . querySelector ( '.custom-select__trigger' ) ;
805805 const options = customSelect . querySelectorAll ( '.custom-option' ) ;
806- const triggerSvg = trigger . querySelector ( 'svg .tool-icon' ) ;
807-
806+ const triggerImg = trigger . querySelector ( 'img .tool-icon' ) ;
807+
808808 trigger . addEventListener ( 'click' , ( ) => {
809809 customSelect . classList . toggle ( 'open' ) ;
810810 } ) ;
811811
812812 options . forEach ( option => {
813813 option . addEventListener ( 'click' , ( ) => {
814814 const value = option . getAttribute ( 'data-value' ) ;
815- const svg = option . querySelector ( 'svg.tool-icon' ) ;
816-
817- triggerSvg . innerHTML = svg . innerHTML ;
815+ const img = option . querySelector ( 'img.tool-icon' ) ;
816+
817+ if ( triggerImg && img ) {
818+ triggerImg . src = img . src ;
819+ }
818820 customSelect . classList . remove ( 'open' ) ;
819821
820822 selectedTool = value ;
@@ -835,9 +837,18 @@ document.addEventListener('DOMContentLoaded', () => {
835837 const brightnessAlphaValue = document . getElementById ( 'brightness-alpha-value' ) ;
836838
837839 if ( brightnessAlphaSlider && brightnessAlphaValue ) {
838- brightnessAlphaSlider . addEventListener ( 'input' , ( ) => {
839- brightnessAlphaValue . textContent = brightnessAlphaSlider . value ;
840- } ) ;
840+ // Function to update the slider's background gradient
841+ const updateSliderBackground = ( ) => {
842+ const value = parseInt ( brightnessAlphaSlider . value ) ;
843+ const max = parseInt ( brightnessAlphaSlider . max ) ;
844+ const percent = ( value / max ) * 100 ;
845+ brightnessAlphaSlider . style . setProperty ( '--slider-value-percent' , `${ percent } %` ) ;
846+ brightnessAlphaValue . textContent = value ;
847+ } ;
848+
849+ brightnessAlphaSlider . addEventListener ( 'input' , updateSliderBackground ) ;
850+ // Call once to set initial state
851+ updateSliderBackground ( ) ;
841852 }
842853
843854 loadConfig ( brightnessAlphaSlider , brightnessAlphaValue ) ;
@@ -897,14 +908,15 @@ if (copyAnimBtn) {
897908 setTimeout ( hideError , 3000 ) ;
898909 return ;
899910 }
900-
911+
901912 try {
902913 const frameToCopy = loadedFrame ;
903914 const newFramePayload = {
904915 name : `${ frameToCopy . name } (copy)` ,
905916 rows : frameToCopy . rows ,
906917 duration_ms : frameToCopy . duration_ms ,
907- brightness_levels : frameToCopy . brightness_levels
918+ brightness_levels : frameToCopy . brightness_levels ,
919+ position : frameToCopy . position
908920 } ;
909921 await fetchWithHandling ( '/persist_frame' , {
910922 method : 'POST' ,
@@ -914,7 +926,7 @@ if (copyAnimBtn) {
914926 } catch ( err ) {
915927 console . error ( `[ui] Failed to copy frame ${ loadedFrameId } ` , err ) ;
916928 }
917-
929+
918930 await refreshFrames ( ) ;
919931 } ) ;
920932}
@@ -926,13 +938,13 @@ if (deleteAnimBtn) {
926938 setTimeout ( hideError , 3000 ) ;
927939 return ;
928940 }
929-
941+
930942 const idToDelete = loadedFrameId ;
931943 await deleteFrame ( idToDelete ) ;
932-
944+
933945 clearLoaded ( ) ;
934946 await refreshFrames ( ) ;
935-
947+
936948 const frameToLoad = sessionFrames . find ( f => f . id !== idToDelete ) || ( sessionFrames . length > 0 ? sessionFrames [ 0 ] : null ) ;
937949
938950 if ( frameToLoad ) {
@@ -984,7 +996,7 @@ if (applyDurationBtn) {
984996 body : JSON . stringify ( payload )
985997 } , 'json' , `update duration for frame ${ frame . id } ` ) . catch ( err => {
986998 console . error ( `[ui] Failed to update duration for frame ${ frame . id } ` , err ) ;
987- return Promise . resolve ( ) ;
999+ return Promise . resolve ( ) ;
9881000 } ) ;
9891001 }
9901002 return Promise . resolve ( ) ;
0 commit comments