@ -1284,6 +1284,7 @@ HTML = """<!doctype html>
. config - toolbar button : hover , . config - toolbar button : focus - visible , . config - restore - panel button : hover , . config - restore - panel button : focus - visible { outline : 0 ; border - color : var ( - - ui - accent ) ; color : var ( - - ui - accent ) ; transform : translateY ( - 1 px ) ; }
. config - scale - control { display : inline - flex ! important ; align - items : center ; gap : 6 px ! important ; min - width : 148 px ; color : var ( - - ui - muted ) ! important ; font - size : 10 px ! important ; white - space : nowrap ; }
. config - scale - control input { width : 74 px ; height : 16 px ; margin : 0 ; accent - color : var ( - - ui - accent ) ; cursor : pointer ; }
. config - scale - control input : disabled { opacity : .45 ; cursor : default ; }
. config - scale - control output { min - width : 34 px ; color : var ( - - ui - text ) ; font - variant - numeric : tabular - nums ; text - align : right ; }
. config - layout - state { min - width : 0 ; color : var ( - - ui - muted ) ; font - size : 10 px ; }
. config - workspace { display : grid ; grid - template - columns : minmax ( 0 , 1 fr ) ; min - width : 0 ; min - height : 0 ; }
@ -1314,6 +1315,7 @@ HTML = """<!doctype html>
. config - canvas > . config - block { position : absolute ; box - sizing : border - box ; container : config - block / inline - size ; margin : 0 ; min - width : 240 px ; min - height : 110 px ; border : 1 px solid var ( - - ui - border ) ! important ; border - radius : 10 px ; background : var ( - - ui - panel ) ! important ; box - shadow : 0 6 px 18 px rgba ( 0 , 0 , 0 , .1 ) ; overflow : auto ; transition : border - color .2 s ease , box - shadow .2 s ease , opacity .2 s ease ; }
. config - canvas > . config - block { cursor : grab ; touch - action : none ; }
. config - canvas > . config - block : hover { border - color : color - mix ( in srgb , var ( - - ui - accent ) 45 % , var ( - - ui - border ) ) ! important ; }
. config - canvas > . config - block . is - selected { border - color : color - mix ( in srgb , var ( - - ui - accent ) 70 % , var ( - - ui - border ) ) ! important ; box - shadow : 0 0 0 2 px color - mix ( in srgb , var ( - - ui - accent ) 12 % , transparent ) , 0 6 px 18 px rgba ( 0 , 0 , 0 , .1 ) ; }
. config - canvas > . config - block . is - dragging , . config - canvas > . config - block . is - resizing { z - index : 5 ; border - color : var ( - - ui - accent ) ! important ; box - shadow : 0 10 px 28 px color - mix ( in srgb , var ( - - ui - accent ) 16 % , transparent ) ; transition : none ; }
. config - canvas > . config - custom - block { border - style : dashed ! important ; }
. config - block > summary { position : sticky ; top : 0 ; z - index : 2 ; background : var ( - - ui - panel ) ; }
@ -1359,6 +1361,7 @@ HTML = """<!doctype html>
. config - canvas > . config - block { min - width : 220 px ; max - width : calc ( 100 % - 8 px ) ; }
. config - structure - create { align - items : stretch ; }
. config - structure - create label , . config - structure - create label : first - child { min - width : min ( 100 % , 220 px ) ; flex : 1 1 180 px ; }
. config - scale - control { flex : 1 1 100 % ; justify - content : space - between ; min - width : 0 ; }
. config - trash { bottom : 10 px ; }
}
@keyframes form - field - in { from { opacity : .35 ; transform : translateY ( 3 px ) ; } to { opacity : 1 ; transform : none ; } }
@ -1539,7 +1542,7 @@ HTML = """<!doctype html>
< span class = " config-toolbar-title " > Блоки конфигурации < / span >
< button id = " configResetLayout " type = " button " > сбросить раскладку < / button >
< button id = " configRestoreToggle " type = " button " aria - expanded = " false " > структура < / button >
< label class = " config-scale-control " title = " Масштаб элементов и текста в нутри блоков " > < span > масштаб < / span > < input id = " configContentScale " type = " range " min = " 80 " max = " 125 " step = " 5 " value = " 100 " aria - label = " Масштаб содержимого блоков" > < output id = " configContentScaleValue " > 100 % < / output > < / label >
< label class = " config-scale-control " title = " Масштаб элементов и текста в ыбранного блока " > < span id = " configScaleLabel " > масштаб блока < / span > < input id = " configContentScale " type = " range " min = " 80 " max = " 125 " step = " 5 " value = " 100 " aria - label = " Масштаб содержимого выбранного блока" disabled > < output id = " configContentScaleValue " > 100 % < / output > < / label >
< span id = " configLayoutState " class = " config-layout-state " > перетаскивайте за заголовок или свободное место < / span >
< / div >
< div id = " configWorkspace " class = " config-workspace " >
@ -2008,6 +2011,7 @@ HTML = """<!doctype html>
let configZIndex = 1 ;
let configContentScale = 1 ;
let configBlockObserver = null ;
let selectedConfigBlock = null ;
function readConfigLayout ( ) {
try {
const saved = JSON . parse ( localStorage . getItem ( ' fpv-config-layout-v1 ' ) | | ' {} ' ) ;
@ -2031,15 +2035,43 @@ HTML = """<!doctype html>
const width = Math . max ( configMinWidth ( ) , block . clientWidth | | block . offsetWidth | | configMinWidth ( ) ) ;
const height = Math . max ( 110 , block . clientHeight | | block . offsetHeight | | 110 ) ;
const area = Math . max ( .56 , Math . min ( 1.85 , ( width / 720 ) * ( height / 320 ) ) ) ;
const scale = Math . max ( .74 , Math . min ( 1.35 , Math . sqrt ( area ) * configContentScale ) ) ;
const entry = configLayout [ block . dataset . section ] | | { } ;
const userScale = Number . isFinite ( Number ( block . _configUserScale ) ) ? block . _configUserScale : ( Number ( entry . scale ) | | 100 ) / 100 ;
block . _configUserScale = Math . max ( .8 , Math . min ( 1.25 , userScale ) ) ;
const scale = Math . max ( .74 , Math . min ( 1.35 , Math . sqrt ( area ) * block . _configUserScale ) ) ;
block . style . setProperty ( ' --block-scale ' , scale . toFixed ( 3 ) ) ;
}
function applyConfigContentScale ( value ) {
configContentScale = Math . max ( .8 , Math . min ( 1.25 , ( Number ( value ) | | 100 ) / 100 ) ) ;
const block = selectedConfigBlock ;
if ( ! block | | configBlockState ( block ) ) return ;
block . _configUserScale = configContentScale ;
const entry = configLayout [ block . dataset . section ] | | { } ;
entry . scale = Math . round ( configContentScale * 100 ) ;
configLayout [ block . dataset . section ] = entry ;
setValue ( ' configContentScale ' , Math . round ( configContentScale * 100 ) ) ;
setText ( ' configContentScaleValue ' , ` $ { Math . round ( configContentScale * 100 ) } % ` ) ;
configBlocks . forEach ( updateConfigBlockScale ) ;
try { localStorage . setItem ( ' fpv-config-content-scale ' , String ( Math . round ( configContentScale * 100 ) ) ) ; } catch ( _error ) { }
updateConfigBlockScale ( block ) ;
saveConfigLayout ( ) ;
}
function selectConfigBlock ( block ) {
if ( ! block | | configBlockState ( block ) ) {
selectedConfigBlock = null ;
configBlocks . forEach ( other = > other . classList . remove ( ' is-selected ' ) ) ;
const control = $ ( ' configContentScale ' ) ;
if ( control ) control . disabled = true ;
return ;
}
selectedConfigBlock = block ;
configBlocks . forEach ( other = > other . classList . toggle ( ' is-selected ' , other == = block ) ) ;
const entry = configLayout [ block . dataset . section ] | | { } ;
const value = Math . round ( ( Number ( entry . scale ) | | 100 ) ) ;
configContentScale = Math . max ( .8 , Math . min ( 1.25 , value / 100 ) ) ;
setValue ( ' configContentScale ' , value ) ;
setText ( ' configContentScaleValue ' , ` $ { value } % ` ) ;
const control = $ ( ' configContentScale ' ) ;
if ( control ) control . disabled = false ;
updateConfigBlockScale ( block ) ;
}
function configTitle ( block ) {
return block . querySelector ( ' :scope > summary .block-title ' ) ? . textContent | | block . dataset . section | | ' Блок ' ;
@ -2085,6 +2117,8 @@ HTML = """<!doctype html>
entry . width = Math . round ( block . offsetWidth ) ;
if ( block . style . height & & block . style . height != = ' auto ' ) entry . height = Math . round ( block . offsetHeight ) ;
else delete entry . height ;
if ( Number . isFinite ( Number ( block . _configUserScale ) ) & & Math . abs ( block . _configUserScale - 1 ) > .001 ) entry . scale = Math . round ( block . _configUserScale * 100 ) ;
else delete entry . scale ;
if ( block . style . zIndex ) entry . z = Number ( block . style . zIndex ) ;
configLayout [ key ] = entry ;
}
@ -2186,12 +2220,13 @@ HTML = """<!doctype html>
saveConfigLayout ( ) ;
applyStoredConfigLayout ( ) ;
renderConfigRestorePanel ( ) ;
selectConfigBlock ( configBlocks . find ( candidate = > ! configBlockState ( candidate ) ) | | null ) ;
}
function focusConfigBlock ( block , item ) {
if ( ! block | | configBlockState ( block ) ) return ;
configBlocks . forEach ( other = > other . classList . remove ( ' is-selected ' ) ) ;
document . querySelectorAll ( ' .config-restore-item.is-selected ' ) . forEach ( node = > node . classList . remove ( ' is-selected ' ) ) ;
block. classList . add ( ' is-selected ' ) ;
selectConfigBlock( block ) ;
item ? . classList . add ( ' is-selected ' ) ;
bringConfigBlockFront ( block ) ;
block . scrollIntoView ? . ( { behavior : ' smooth ' , block : ' nearest ' , inline : ' nearest ' } ) ;
@ -2281,6 +2316,7 @@ HTML = """<!doctype html>
if ( key ) delete configLayout [ key ] ;
saveConfigLayout ( ) ;
renderConfigRestorePanel ( ) ;
selectConfigBlock ( configBlocks . find ( candidate = > ! configBlockState ( candidate ) ) | | null ) ;
setText ( ' configLayoutState ' , ' блок удалён навсегда ' ) ;
}
function nextCustomConfigKey ( ) {
@ -2362,6 +2398,12 @@ HTML = """<!doctype html>
const trash = $ ( ' configTrash ' ) ;
if ( trash ) trash . classList . toggle ( ' is-over ' , trashDrop ( event ) ) ;
}
function showConfigTrash ( ) {
const trash = $ ( ' configTrash ' ) ;
if ( ! trash ) return ;
trash . hidden = false ;
trash . classList . remove ( ' is-over ' ) ;
}
function finishConfigDrag ( block , event , dragged ) {
const trash = $ ( ' configTrash ' ) ;
const droppedOnTrash = trashDrop ( event ) ;
@ -2400,6 +2442,7 @@ HTML = """<!doctype html>
block . open = true ;
} ) ;
block . addEventListener ( ' pointerdown ' , event = > {
selectConfigBlock ( block ) ;
bringConfigBlockFront ( block ) ;
const edge = configResizeEdge ( event , block ) ;
if ( ! edge | | configDragBlocked ( block , event ) ) return ;
@ -2483,6 +2526,7 @@ HTML = """<!doctype html>
const startLeft = parseFloat ( block . style . left ) | | block . offsetLeft | | 0 ;
const startTop = parseFloat ( block . style . top ) | | block . offsetTop | | 0 ;
let dragged = false ;
showConfigTrash ( ) ;
block . setPointerCapture ? . ( event . pointerId ) ;
const move = moveEvent = > {
if ( ! dragged & & Math . hypot ( moveEvent . clientX - startX , moveEvent . clientY - startY ) < 4 ) return ;
@ -2490,8 +2534,6 @@ HTML = """<!doctype html>
dragged = true ;
event . preventDefault ( ) ;
block . classList . add ( ' is-dragging ' ) ;
const trash = $ ( ' configTrash ' ) ;
if ( trash ) { trash . hidden = false ; trash . classList . remove ( ' is-over ' ) ; }
}
event . preventDefault ( ) ;
const maxLeft = Math . max ( 0 , configCanvasWidth ( ) - block . offsetWidth ) ;
@ -2521,6 +2563,7 @@ HTML = """<!doctype html>
block . style . width = ' ' ;
block . style . height = ' ' ;
block . style . zIndex = ' ' ;
block . _configUserScale = 1 ;
block . open = true ;
block . dataset . blockState = ' ' ;
block . classList . remove ( ' config-block-hidden ' , ' config-block-deleted ' ) ;
@ -2532,6 +2575,7 @@ HTML = """<!doctype html>
saveConfigLayout ( ) ;
applyStoredConfigLayout ( ) ;
renderConfigRestorePanel ( ) ;
selectConfigBlock ( configBlocks . find ( block = > ! configBlockState ( block ) ) | | null ) ;
setText ( ' configLayoutState ' , ' раскладка сброшена ' ) ;
}
function initConfigCanvas ( ) {
@ -2555,6 +2599,7 @@ HTML = """<!doctype html>
} ) ;
applyStoredConfigLayout ( ) ;
renderConfigRestorePanel ( ) ;
selectConfigBlock ( configBlocks . find ( block = > ! configBlockState ( block ) ) | | null ) ;
$ ( ' configResetLayout ' ) ? . addEventListener ( ' click ' , resetConfigLayout ) ;
$ ( ' configRestoreToggle ' ) ? . addEventListener ( ' click ' , ( ) = > {
restorePanelOpen = ! restorePanelOpen ;
@ -2586,9 +2631,7 @@ HTML = """<!doctype html>
} ) ;
configBlocks . forEach ( block = > configBlockObserver . observe ( block ) ) ;
}
let savedScale = 100 ;
try { savedScale = Number ( localStorage . getItem ( ' fpv-config-content-scale ' ) | | 100 ) ; } catch ( _error ) { }
applyConfigContentScale ( savedScale ) ;
selectConfigBlock ( selectedConfigBlock ) ;
$ ( ' configContentScale ' ) ? . addEventListener ( ' input ' , event = > applyConfigContentScale ( event . target . value ) ) ;
}
function parseFrameSize ( value ) {