2026-08-18 16:55:42 +03:00
<!doctype html>
< html lang = "en" >
< head >
< meta charset = "utf-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1" >
2026-08-28 13:30:12 +03:00
< title > win64_mp</ title >
2026-08-18 16:55:42 +03:00
< style >
2026-08-29 18:37:36 +03:00
/* —— Design tokens —— */
2026-08-18 16:55:42 +03:00
: root {
2026-08-29 18:37:36 +03:00
--bg : #07130f ;
--bg-deep : #040b09 ;
--surface : #04241e ;
--surface-raised : #073028 ;
--surface-soft : rgba ( 3 , 48 , 39 , 0.72 );
--surface-glass : rgba ( 2 , 44 , 34 , 0.82 );
--text : #ebeeef ;
--text-secondary : #bfc3c4 ;
--text-muted : #868d8f ;
--accent : #2dd4bf ;
--accent-bright : #5eead4 ;
--accent-deep : #059669 ;
--accent-ink : #022c22 ;
--border : rgba ( 94 , 234 , 212 , 0.14 );
--border-strong : rgba ( 94 , 234 , 212 , 0.24 );
--border-active : rgba ( 94 , 234 , 212 , 0.42 );
--danger : #f87171 ;
--danger-deep : #b91c1c ;
--warning : #fbbf24 ;
--success : #34d399 ;
--focus : rgba ( 94 , 234 , 212 , 0.45 );
--radius-sm : 9 px ;
--radius-md : 14 px ;
--radius-lg : 20 px ;
--shadow-panel :
0 18 px 42 px rgba ( 0 , 0 , 0 , 0.28 ),
inset 0 1 px 0 rgba ( 255 , 255 , 255 , 0.05 );
--shadow-glass :
0 16 px 36 px rgba ( 0 , 0 , 0 , 0.25 ),
inset 0 1 px 0 rgba ( 255 , 255 , 255 , 0.05 );
--control-h : 40 px ;
--font :
Inter , ui-sans-serif , system-ui , - apple-system , BlinkMacSystemFont ,
"Segoe UI" , sans-serif ;
--mono : ui-monospace , "Cascadia Code" , Consolas , monospace ;
--ease : 180 ms ease ;
--max : 1180 px ;
2026-08-18 16:55:42 +03:00
}
2026-08-29 18:37:36 +03:00
/* —— Reset / base —— */
*, * :: before , * :: after { box-sizing : border-box ; }
html { color-scheme : dark ; }
2026-08-18 16:55:42 +03:00
body {
margin : 0 ;
2026-08-29 18:37:36 +03:00
min-height : 100 vh ;
font : 15 px / 1.5 var ( -- font );
2026-08-18 16:55:42 +03:00
color : var ( -- text );
2026-08-29 18:37:36 +03:00
background-color : var ( -- bg - deep );
background-image :
radial-gradient ( ellipse 90 % 55 % at 50 % -10 % , rgba ( 45 , 212 , 191 , 0.09 ), transparent 55 % ),
linear-gradient ( rgba ( 94 , 234 , 212 , 0.035 ) 1 px , transparent 1 px ),
linear-gradient ( 90 deg , rgba ( 94 , 234 , 212 , 0.035 ) 1 px , transparent 1 px );
background-size : auto , 48 px 48 px , 48 px 48 px ;
background-attachment : fixed ;
-webkit- font-smoothing : antialiased ;
2026-08-18 16:55:42 +03:00
}
2026-08-29 18:37:36 +03:00
button , input , select , textarea { font : inherit ; color : inherit ; }
button { cursor : pointer ; }
button : disabled { opacity : 0.42 ; cursor : not-allowed ; }
img , canvas { max-width : 100 % ; height : auto ; }
a { color : var ( -- accent ); }
h1 , h2 , h3 { margin : 0 ; font-weight : 600 ; letter-spacing : -0.02 em ; }
p { margin : 0 ; }
/* —— Layout —— */
. app {
width : min ( 100 % - 2 rem , var ( -- max ));
margin : 0 auto ;
padding : 1.25 rem 0 2.5 rem ;
animation : rise 0.45 s ease both ;
}
@ keyframes rise {
from { opacity : 0 ; transform : translateY ( 8 px ); }
to { opacity : 1 ; transform : none ; }
}
/* —— Top bar (glass) —— */
. topbar {
2026-08-18 16:55:42 +03:00
display : flex ;
2026-08-29 18:37:36 +03:00
align-items : center ;
2026-08-18 16:55:42 +03:00
justify-content : space-between ;
gap : 1 rem ;
2026-08-29 18:37:36 +03:00
padding : 0.85 rem 1.1 rem ;
margin-bottom : 0.85 rem ;
border-radius : var ( -- radius - md );
background : var ( -- surface - glass );
border : 1 px solid var ( -- border );
box-shadow : var ( -- shadow - glass );
backdrop-filter : blur ( 16 px );
-webkit- backdrop-filter : blur ( 16 px );
2026-08-18 16:55:42 +03:00
}
2026-08-29 18:37:36 +03:00
. brand {
2026-08-18 16:55:42 +03:00
display : flex ;
2026-08-29 18:37:36 +03:00
align-items : center ;
gap : 0.75 rem ;
min-width : 0 ;
}
. brand-mark {
flex : none ;
width : 34 px ;
height : 34 px ;
border-radius : 10 px ;
display : grid ;
place-items : center ;
color : var ( -- accent - ink );
background : linear-gradient ( 180 deg , var ( -- accent ), var ( -- accent - deep ));
border : 1 px solid rgba ( 45 , 212 , 191 , 0.5 );
box-shadow : 0 8 px 18 px rgba ( 5 , 150 , 105 , 0.25 );
}
. brand-mark svg { width : 18 px ; height : 18 px ; }
. brand-text { min-width : 0 ; }
. brand-text h1 {
font-size : 1.05 rem ;
line-height : 1.2 ;
}
. brand-text p {
margin-top : 0.1 rem ;
font-size : 0.78 rem ;
color : var ( -- text - muted );
}
/* —— Navigation —— */
nav . tabs {
display : flex ;
flex-wrap : wrap ;
gap : 0.35 rem ;
padding : 0.4 rem ;
margin-bottom : 1 rem ;
border-radius : var ( -- radius - md );
background : var ( -- surface - glass );
border : 1 px solid var ( -- border );
box-shadow : var ( -- shadow - glass );
backdrop-filter : blur ( 16 px );
-webkit- backdrop-filter : blur ( 16 px );
}
nav . tabs button {
appearance : none ;
border : 1 px solid transparent ;
background : transparent ;
color : var ( -- text - secondary );
border-radius : var ( -- radius - sm );
padding : 0.55 rem 0.85 rem ;
font-size : 0.9 rem ;
font-weight : 500 ;
transition : background var ( -- ease ), color var ( -- ease ), border-color var ( -- ease ), box-shadow var ( -- ease );
}
nav . tabs button : hover {
color : var ( -- text );
background : rgba ( 94 , 234 , 212 , 0.06 );
border-color : var ( -- border );
}
nav . tabs button . active {
color : var ( -- accent - bright );
background : rgba ( 45 , 212 , 191 , 0.12 );
border-color : var ( -- border - active );
box-shadow : inset 0 1 px 0 rgba ( 255 , 255 , 255 , 0.04 );
}
nav . tabs button : focus-visible {
outline : 2 px solid var ( -- focus );
outline-offset : 2 px ;
}
/* —— Alerts —— */
. error {
display : none ;
margin : 0 0 1 rem ;
padding : 0.75 rem 1 rem ;
border-radius : var ( -- radius - sm );
border : 1 px solid rgba ( 248 , 113 , 113 , 0.35 );
background : rgba ( 127 , 29 , 29 , 0.35 );
color : #fecaca ;
font-size : 0.92 rem ;
}
. error . show { display : block ; }
/* —— Badges —— */
. badge {
display : inline-flex ;
align-items : center ;
2026-08-18 16:55:42 +03:00
gap : 0.4 rem ;
2026-08-29 18:37:36 +03:00
font-size : 0.75 rem ;
font-weight : 600 ;
letter-spacing : 0.04 em ;
text-transform : uppercase ;
padding : 0.35 rem 0.7 rem ;
border-radius : 999 px ;
border : 1 px solid var ( -- border );
background : rgba ( 3 , 48 , 39 , 0.85 );
color : var ( -- text - muted );
2026-08-18 16:55:42 +03:00
}
2026-08-29 18:37:36 +03:00
. badge :: before {
content : "" ;
width : 0.45 rem ;
height : 0.45 rem ;
border-radius : 50 % ;
background : currentColor ;
opacity : 0.85 ;
2026-08-18 16:55:42 +03:00
}
2026-08-29 18:37:36 +03:00
. badge . ok {
color : var ( -- success );
border-color : rgba ( 52 , 211 , 153 , 0.35 );
background : rgba ( 6 , 78 , 59 , 0.45 );
2026-08-18 16:55:42 +03:00
}
2026-08-29 18:37:36 +03:00
. badge . bad {
color : var ( -- danger );
border-color : rgba ( 248 , 113 , 113 , 0.35 );
background : rgba ( 127 , 29 , 29 , 0.4 );
}
/* —— Panels / surfaces —— */
2026-08-18 16:55:42 +03:00
. panel {
display : none ;
2026-08-29 18:37:36 +03:00
padding : 1.15 rem 1.25 rem 1.35 rem ;
border-radius : var ( -- radius - lg );
background-color : var ( -- surface );
background-image : linear-gradient ( 145 deg , rgba ( 45 , 212 , 191 , 0.06 ), transparent 42 % );
border : 1 px solid var ( -- border );
box-shadow : var ( -- shadow - panel );
2026-08-18 16:55:42 +03:00
}
. panel . active { display : block ; }
2026-08-29 18:37:36 +03:00
. panel-head {
display : flex ;
flex-wrap : wrap ;
align-items : flex-start ;
justify-content : space-between ;
gap : 0.75 rem 1 rem ;
margin-bottom : 1 rem ;
padding-bottom : 0.9 rem ;
border-bottom : 1 px solid var ( -- border );
}
. panel-head h2 {
font-size : 1.05 rem ;
color : var ( -- text );
}
. panel-head . lede {
margin-top : 0.2 rem ;
font-size : 0.85 rem ;
color : var ( -- text - muted );
max-width : 42 rem ;
}
. panel-actions {
display : flex ;
flex-wrap : wrap ;
gap : 0.45 rem ;
align-items : center ;
}
. toolbar {
display : flex ;
flex-wrap : wrap ;
gap : 0.5 rem ;
align-items : center ;
margin-bottom : 0.85 rem ;
padding : 0.65 rem 0.75 rem ;
border-radius : var ( -- radius - md );
background : rgba ( 0 , 0 , 0 , 0.22 );
border : 1 px solid var ( -- border );
}
. toolbar . compact { padding : 0.55 rem 0.65 rem ; }
. meta {
color : var ( -- text - muted );
font-size : 0.85 rem ;
}
. hint {
flex : 1 ;
min-width : 12 rem ;
padding : 0.65 rem 0.8 rem ;
border-radius : var ( -- radius - sm );
border : 1 px solid var ( -- border );
background : rgba ( 0 , 0 , 0 , 0.2 );
color : var ( -- text - muted );
font-size : 0.88 rem ;
}
. sep {
width : 1 px ;
align-self : stretch ;
min-height : 1.5 rem ;
background : var ( -- border );
margin : 0 0.15 rem ;
}
/* —— Forms —— */
2026-08-18 16:55:42 +03:00
. row {
display : flex ;
flex-wrap : wrap ;
gap : 0.5 rem ;
align-items : center ;
margin-bottom : 0.75 rem ;
}
2026-08-29 18:37:36 +03:00
label {
display : inline-flex ;
flex-direction : column ;
gap : 0.28 rem ;
font-size : 0.72 rem ;
font-weight : 600 ;
letter-spacing : 0.04 em ;
text-transform : uppercase ;
color : var ( -- text - muted );
}
label . inline {
flex-direction : row ;
align-items : center ;
text-transform : none ;
letter-spacing : 0 ;
font-size : 0.9 rem ;
font-weight : 500 ;
color : var ( -- text - secondary );
}
label . check {
display : inline-flex ;
flex-direction : row ;
align-items : center ;
gap : 0.5 rem ;
user-select : none ;
text-transform : none ;
letter-spacing : 0 ;
font-size : 0.9 rem ;
font-weight : 500 ;
color : var ( -- text - secondary );
padding : 0.45 rem 0.7 rem ;
border-radius : var ( -- radius - sm );
border : 1 px solid var ( -- border );
background : rgba ( 0 , 0 , 0 , 0.18 );
cursor : pointer ;
transition : border-color var ( -- ease ), background var ( -- ease );
}
label . check : hover { border-color : var ( -- border - strong ); }
label . check input {
width : 1 rem ;
height : 1 rem ;
margin : 0 ;
accent-color : var ( -- accent );
}
input [ type = "text" ],
input [ type = "number" ],
input [ type = "search" ],
textarea ,
select {
appearance : none ;
height : var ( -- control - h );
padding : 0 0.75 rem ;
border-radius : var ( -- radius - sm );
border : 1 px solid var ( -- border );
background : rgba ( 0 , 0 , 0 , 0.28 );
color : var ( -- text );
transition : border-color var ( -- ease ), box-shadow var ( -- ease ), background var ( -- ease );
}
textarea {
height : auto ;
min-height : 5.5 rem ;
padding : 0.7 rem 0.8 rem ;
width : 100 % ;
resize : vertical ;
font-family : var ( -- mono );
font-size : 0.9 rem ;
line-height : 1.45 ;
}
select {
padding-right : 2 rem ;
background-image :
linear-gradient ( 45 deg , transparent 50 % , var ( -- text - muted ) 50 % ),
linear-gradient ( 135 deg , var ( -- text - muted ) 50 % , transparent 50 % );
background-position :
calc ( 100 % - 14 px ) calc ( 50 % - 2 px ),
calc ( 100 % - 9 px ) calc ( 50 % - 2 px );
background-size : 5 px 5 px , 5 px 5 px ;
background-repeat : no-repeat ;
2026-08-18 16:55:42 +03:00
}
input [ type = "text" ], textarea { flex : 1 ; min-width : 12 rem ; }
2026-08-29 18:37:36 +03:00
input :: placeholder , textarea :: placeholder { color : var ( -- text - muted ); opacity : 0.85 ; }
input : hover , textarea : hover , select : hover { border-color : var ( -- border - strong ); }
input : focus-visible , textarea : focus-visible , select : focus-visible , button : focus-visible {
outline : none ;
border-color : var ( -- border - active );
box-shadow : 0 0 0 3 px var ( -- focus );
2026-08-18 16:55:42 +03:00
}
2026-08-29 18:37:36 +03:00
input . narrow { width : 5 rem ; flex : none ; min-width : 0 ; }
input . narrow-sm { width : 4.5 rem ; flex : none ; min-width : 0 ; }
select . grow { min-width : 12 rem ; }
/* —— Buttons —— */
. btn ,
. row button ,
. panel-actions button ,
. toolbar button ,
. startup-card button ,
2026-08-29 18:26:27 +03:00
td . actions button {
2026-08-29 18:37:36 +03:00
appearance : none ;
display : inline-flex ;
align-items : center ;
justify-content : center ;
gap : 0.4 rem ;
min-height : var ( -- control - h );
padding : 0 0.9 rem ;
border-radius : var ( -- radius - sm );
border : 1 px solid var ( -- border - strong );
background : rgba ( 7 , 48 , 40 , 0.9 );
color : var ( -- text - secondary );
font-weight : 500 ;
font-size : 0.9 rem ;
transition : background var ( -- ease ), border-color var ( -- ease ), color var ( -- ease ), box-shadow var ( -- ease ), transform 120 ms ease ;
}
. btn : hover : not ( : disabled ),
. row button : hover : not ( : disabled ),
. panel-actions button : hover : not ( : disabled ),
. toolbar button : hover : not ( : disabled ),
. startup-card button : hover : not ( : disabled ),
td . actions button : hover : not ( : disabled ) {
color : var ( -- text );
border-color : var ( -- border - active );
background : rgba ( 10 , 64 , 54 , 0.95 );
}
. btn : active : not ( : disabled ),
. row button : active : not ( : disabled ),
. panel-actions button : active : not ( : disabled ),
. toolbar button : active : not ( : disabled ),
. startup-card button : active : not ( : disabled ) {
transform : translateY ( 1 px );
}
. btn . primary ,
. row button . primary ,
. panel-actions button . primary ,
. toolbar button . primary {
color : var ( -- accent - ink );
background : linear-gradient ( 180 deg , var ( -- accent ), var ( -- accent - deep ));
border-color : rgba ( 45 , 212 , 191 , 0.5 );
box-shadow : 0 8 px 18 px rgba ( 5 , 150 , 105 , 0.22 );
}
. btn . primary : hover : not ( : disabled ),
. row button . primary : hover : not ( : disabled ),
. panel-actions button . primary : hover : not ( : disabled ),
. toolbar button . primary : hover : not ( : disabled ) {
color : var ( -- accent - ink );
background : linear-gradient ( 180 deg , var ( -- accent - bright ), #10b981 );
}
. btn . danger ,
. row button . danger ,
. panel-actions button . danger ,
. startup-card button . danger ,
td . actions button . danger {
color : #fecaca ;
border-color : rgba ( 248 , 113 , 113 , 0.35 );
background : rgba ( 127 , 29 , 29 , 0.35 );
}
. btn . danger : hover : not ( : disabled ),
. row button . danger : hover : not ( : disabled ),
. panel-actions button . danger : hover : not ( : disabled ),
. startup-card button . danger : hover : not ( : disabled ),
td . actions button . danger : hover : not ( : disabled ) {
color : #fff ;
border-color : rgba ( 248 , 113 , 113 , 0.55 );
background : rgba ( 153 , 27 , 27 , 0.55 );
}
td . actions button {
min-height : 32 px ;
padding : 0 0.65 rem ;
font-size : 0.8 rem ;
2026-08-29 18:26:27 +03:00
}
td . actions button + button { margin-left : 0.35 rem ; }
2026-08-29 18:37:36 +03:00
/* —— Status —— */
# status-fields {
display : grid ;
grid-template-columns : repeat ( auto - fill , minmax ( 220 px , 1 fr ));
gap : 0.65 rem ;
margin : 0 0 1 rem ;
}
# status-fields . status-item {
display : flex ;
flex-direction : column ;
gap : 0.2 rem ;
min-width : 0 ;
}
# status-fields dt {
margin : 0 ;
padding-left : 0.15 rem ;
font-size : 0.7 rem ;
font-weight : 600 ;
letter-spacing : 0.05 em ;
text-transform : uppercase ;
color : var ( -- text - muted );
}
# status-fields dd {
margin : 0 ;
padding : 0.7 rem 0.8 rem ;
border-radius : var ( -- radius - sm );
border : 1 px solid var ( -- border );
background : rgba ( 0 , 0 , 0 , 0.22 );
color : var ( -- text );
word-break : break-all ;
font-size : 0.92 rem ;
}
. startup-card {
display : flex ;
flex-wrap : wrap ;
align-items : center ;
gap : 0.65 rem ;
padding : 0.85 rem 1 rem ;
border-radius : var ( -- radius - md );
border : 1 px solid var ( -- border );
background : rgba ( 0 , 0 , 0 , 0.2 );
}
. startup-card . meta { flex : 1 ; min-width : 8 rem ; font-size : 0.92 rem ; color : var ( -- text - secondary ); }
/* —— Tables —— */
. table-wrap {
2026-08-18 16:55:42 +03:00
overflow : auto ;
2026-08-29 18:37:36 +03:00
border-radius : var ( -- radius - md );
border : 1 px solid var ( -- border );
background : rgba ( 0 , 0 , 0 , 0.18 );
2026-08-18 16:55:42 +03:00
}
2026-08-29 18:37:36 +03:00
table {
2026-08-18 19:12:25 +03:00
width : 100 % ;
2026-08-29 18:37:36 +03:00
border-collapse : collapse ;
min-width : 520 px ;
2026-08-18 19:12:25 +03:00
}
2026-08-29 18:37:36 +03:00
th , td {
text-align : left ;
padding : 0.65 rem 0.85 rem ;
border-bottom : 1 px solid var ( -- border );
vertical-align : middle ;
}
th {
position : sticky ;
top : 0 ;
z-index : 1 ;
color : var ( -- text - muted );
font-weight : 600 ;
font-size : 0.72 rem ;
letter-spacing : 0.05 em ;
text-transform : uppercase ;
background : rgba ( 4 , 36 , 30 , 0.96 );
}
tbody tr { transition : background var ( -- ease ); }
tbody tr : hover td { background : rgba ( 94 , 234 , 212 , 0.04 ); }
tbody tr : last-child td { border-bottom : 0 ; }
td . name { cursor : pointer ; color : var ( -- accent ); font-weight : 500 ; }
2026-09-01 20:20:15 +03:00
td . name . dir { display : flex ; align-items : center ; gap : 0.5 rem ; }
td . name . dir-label { flex : 1 ; min-width : 0 ; }
td . name . folder-zip {
flex-shrink : 0 ;
padding : 0.15 rem 0.45 rem ;
font-size : 0.72 rem ;
font-weight : 500 ;
color : var ( -- text - muted );
background : var ( -- surface -2 );
border : 1 px solid var ( -- border );
border-radius : 4 px ;
cursor : pointer ;
}
td . name . folder-zip : hover { color : var ( -- accent - bright ); border-color : var ( -- accent - dim ); }
2026-08-29 18:37:36 +03:00
td . name . file { color : var ( -- text ); font-weight : 400 ; }
td . name . file : hover { color : var ( -- accent - bright ); }
td . actions { white-space : nowrap ; text-align : right ; }
td . log-name {
cursor : pointer ;
color : var ( -- accent );
font-weight : 500 ;
}
tr . log-row . selected td {
background : rgba ( 45 , 212 , 191 , 0.1 );
}
/* —— Media / capture —— */
. stage {
position : relative ;
overflow : hidden ;
border-radius : var ( -- radius - md );
border : 1 px solid var ( -- border );
background : #020807 ;
box-shadow : inset 0 1 px 0 rgba ( 255 , 255 , 255 , 0.03 );
}
# video-canvas ,
2026-08-29 18:34:05 +03:00
# webcam-canvas {
width : 100 % ;
height : auto ;
display : block ;
2026-08-29 18:37:36 +03:00
vertical-align : middle ;
background : #020807 ;
cursor : crosshair ;
2026-08-29 18:34:05 +03:00
}
2026-08-29 18:37:36 +03:00
# video-canvas . view-only { cursor : default ; }
. shots {
display : grid ;
grid-template-columns : repeat ( auto - fill , minmax ( 240 px , 1 fr ));
gap : 0.85 rem ;
2026-08-29 18:27:11 +03:00
}
2026-08-29 18:37:36 +03:00
. shots figure {
margin : 0 ;
padding : 0.5 rem ;
border-radius : var ( -- radius - md );
border : 1 px solid var ( -- border );
background : rgba ( 0 , 0 , 0 , 0.22 );
2026-08-18 16:55:42 +03:00
}
2026-08-29 18:37:36 +03:00
. shots img {
display : block ;
width : 100 % ;
border-radius : var ( -- radius - sm );
border : 1 px solid var ( -- border );
}
. shots figcaption { margin-top : 0.45 rem ; padding : 0 0.15 rem ; }
/* —— Code / output —— */
pre {
margin : 0.5 rem 0 0 ;
padding : 0.9 rem 1 rem ;
border-radius : var ( -- radius - md );
border : 1 px solid var ( -- border );
background : #020807 ;
color : #d1fae5 ;
overflow : auto ;
max-height : 22 rem ;
white-space : pre-wrap ;
font-family : var ( -- mono );
font-size : 0.85 rem ;
line-height : 1.5 ;
}
# log-view { margin-top : 0.75 rem ; max-height : 28 rem ; }
# log-view-title {
margin : 0.85 rem 0 0.35 rem ;
font-size : 0.85 rem ;
color : var ( -- text - muted );
}
# video-key-hint { /* kept as id; styled via .hint when both applied */ }
/* —— Modifier keys —— */
2026-08-28 13:22:59 +03:00
. mod-btn {
position : relative ;
2026-08-29 18:37:36 +03:00
min-width : 3.9 rem ;
2026-08-28 13:22:59 +03:00
font-weight : 600 ;
letter-spacing : 0.02 em ;
2026-08-29 18:37:36 +03:00
color : var ( -- text - muted );
background : rgba ( 0 , 0 , 0 , 0.25 );
border-color : var ( -- border );
2026-08-28 13:11:56 +03:00
}
2026-08-28 13:22:59 +03:00
. mod-btn :: before {
content : "" ;
display : inline-block ;
width : 0.45 rem ;
height : 0.45 rem ;
margin-right : 0.35 rem ;
border-radius : 50 % ;
2026-08-29 18:37:36 +03:00
background : var ( -- text - muted );
2026-08-28 13:22:59 +03:00
vertical-align : 0.05 em ;
2026-08-29 18:37:36 +03:00
transition : background var ( -- ease ), box-shadow var ( -- ease );
2026-08-28 13:22:59 +03:00
}
. mod-btn . active {
2026-08-29 18:37:36 +03:00
color : var ( -- accent - bright );
background : rgba ( 45 , 212 , 191 , 0.14 );
border-color : var ( -- border - active );
box-shadow : inset 0 1 px 0 rgba ( 255 , 255 , 255 , 0.04 );
2026-08-28 13:22:59 +03:00
}
. mod-btn . active :: before {
background : var ( -- accent );
2026-08-29 18:37:36 +03:00
box-shadow : 0 0 0 2 px rgba ( 45 , 212 , 191 , 0.25 );
2026-08-28 13:22:59 +03:00
}
2026-08-29 18:37:36 +03:00
. stack-gap { margin-top : 0.85 rem ; }
. empty {
padding : 1.25 rem ;
text-align : center ;
color : var ( -- text - muted );
2026-08-28 13:22:59 +03:00
font-size : 0.9 rem ;
2026-08-28 13:11:56 +03:00
}
2026-08-29 18:37:36 +03:00
/* —— Responsive —— */
@ media ( max-width : 720px ) {
. app { width : min ( 100 % - 1.25 rem , var ( -- max )); padding-top : 0.85 rem ; }
. topbar { padding : 0.75 rem 0.85 rem ; }
. panel { padding : 1 rem ; border-radius : 16 px ; }
nav . tabs { gap : 0.25 rem ; }
nav . tabs button { flex : 1 1 auto ; text-align : center ; padding : 0.55 rem 0.6 rem ; font-size : 0.82 rem ; }
# status-fields { grid-template-columns : 1 fr ; }
. toolbar { align-items : stretch ; }
. toolbar label { flex : 1 1 40 % ; }
input [ type = "text" ] { min-width : 100 % ; }
table { min-width : 460 px ; }
2026-08-28 13:22:59 +03:00
}
2026-08-29 18:37:36 +03:00
@ media ( max-width : 420px ) {
. brand-text p { display : none ; }
. sep { display : none ; }
2026-08-28 13:22:59 +03:00
}
2026-08-29 18:37:36 +03:00
/* —— Reduced motion —— */
@ media ( prefers-reduced-motion : reduce ) {
*, * :: before , * :: after {
animation-duration : 0.01 ms !important ;
animation-iteration-count : 1 !important ;
transition-duration : 0.01 ms !important ;
}
. app { animation : none ; }
2026-08-28 13:11:56 +03:00
}
2026-08-18 16:55:42 +03:00
</ style >
</ head >
< body >
2026-08-29 18:37:36 +03:00
< div class = "app" >
< header class = "topbar" >
< div class = "brand" >
< div class = "brand-mark" aria-hidden = "true" >
< svg viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" >
< path d = "M12 2 4 6v6c0 5 3.5 8.5 8 10 4.5-1.5 8-5 8-10V6l-8-4z" />
< path d = "M9 12h6" />
</ svg >
</ div >
< div class = "brand-text" >
< h1 > win64_mp</ h1 >
< p > Local agent console</ p >
</ div >
2026-08-18 16:55:42 +03:00
</ div >
2026-08-29 18:37:36 +03:00
< span id = "health" class = "badge" > checking</ span >
</ header >
< p id = "error" class = "error" role = "alert" ></ p >
< nav class = "tabs" aria-label = "Sections" >
< button data-tab = "status" class = "active" type = "button" > Status</ button >
< button data-tab = "files" type = "button" > Files</ button >
< button data-tab = "screenshot" type = "button" > Screenshot</ button >
< button data-tab = "video" type = "button" > Video</ button >
< button data-tab = "webcam" type = "button" > Webcam</ button >
< button data-tab = "exec" type = "button" > Command</ button >
2026-09-01 20:17:06 +03:00
< button data-tab = "update" type = "button" > Update</ button >
2026-08-29 18:37:36 +03:00
< button data-tab = "logs" type = "button" > Logs</ button >
</ nav >
< main >
< section id = "status" class = "panel active" >
< div class = "panel-head" >
< div >
< h2 > Host status</ h2 >
< p class = "lede" > Runtime identity, network, and persistence on this machine.</ p >
</ div >
< div class = "panel-actions" >
< button id = "refresh-status" class = "primary" type = "button" > Refresh</ button >
</ div >
</ div >
< dl id = "status-fields" ></ dl >
< div class = "startup-card" >
< span id = "startup-state" class = "meta" > Startup: —</ span >
< button id = "startup-add" type = "button" > Add to startup</ button >
< button id = "startup-remove" class = "danger" type = "button" > Remove from startup</ button >
</ div >
</ section >
< section id = "files" class = "panel" >
< div class = "panel-head" >
< div >
< h2 > File browser</ h2 >
< p class = "lede" > Browse, preview, upload, and delete files on the host.</ p >
</ div >
</ div >
< div class = "toolbar" >
< input id = "file-path" type = "text" placeholder = "Directory path, e.g. C:\Users" aria-label = "Directory path" >
< button id = "file-up" type = "button" > Up</ button >
< button id = "file-list" class = "primary" type = "button" > List</ button >
< button id = "file-upload" type = "button" > Upload</ button >
< input id = "file-picker" type = "file" hidden >
</ div >
< p id = "file-meta" class = "meta" ></ p >
< div class = "table-wrap stack-gap" >
< table >
< thead >
< tr >< th > Name</ th >< th > Type</ th >< th > Size</ th >< th > Modified</ th >< th ></ th ></ tr >
</ thead >
< tbody id = "file-rows" ></ tbody >
</ table >
</ div >
</ section >
< section id = "screenshot" class = "panel" >
< div class = "panel-head" >
< div >
< h2 > Screenshot</ h2 >
< p class = "lede" > Capture the primary display as a still image.</ p >
</ div >
< div class = "panel-actions" >
< button id = "shot-capture" class = "primary" type = "button" > Capture</ button >
</ div >
</ div >
< div class = "toolbar" >
< label > Format
< select id = "shot-format" >
< option value = "png" > PNG</ option >
< option value = "jpeg" > JPEG</ option >
</ select >
</ label >
< label > Quality
< input id = "shot-quality" class = "narrow" type = "number" min = "1" max = "100" value = "80" >
</ label >
</ div >
< div id = "shot-gallery" class = "shots" ></ div >
</ section >
< section id = "video" class = "panel" >
< div class = "panel-head" >
< div >
< h2 > Remote desktop</ h2 >
< p class = "lede" > Live monitor stream. Interaction is off until you enable it.</ p >
</ div >
< div class = "panel-actions" >
< button id = "video-start" class = "primary" type = "button" > Start</ button >
< button id = "video-stop" type = "button" disabled > Stop</ button >
</ div >
</ div >
< div class = "toolbar" >
< label > FPS
< input id = "video-fps" class = "narrow-sm" type = "number" min = "1" max = "15" value = "5" >
</ label >
< label > Quality
< input id = "video-quality" class = "narrow" type = "number" min = "1" max = "100" value = "40" >
</ label >
< label > Monitor
< input id = "video-monitor" class = "narrow-sm" type = "number" min = "0" value = "0" >
</ label >
< span class = "sep" aria-hidden = "true" ></ span >
< label class = "check" >
< input id = "video-interact" type = "checkbox" >
Interactable
</ label >
</ div >
< div class = "stage" >
< canvas id = "video-canvas" class = "view-only" width = "1280" height = "720" ></ canvas >
</ div >
< p id = "video-meta" class = "meta stack-gap" ></ p >
< div class = "row stack-gap" >
< div id = "video-key-hint" class = "hint" > View only — enable Interactable to send mouse and keyboard</ div >
</ div >
< div class = "row" >
< button type = "button" class = "mod-btn" data-mod = "ctrl" aria-pressed = "false" > Ctrl</ button >
< button type = "button" class = "mod-btn" data-mod = "alt" aria-pressed = "false" > Alt</ button >
< button type = "button" class = "mod-btn" data-mod = "shift" aria-pressed = "false" > Shift</ button >
< button type = "button" class = "mod-btn" data-mod = "win" aria-pressed = "false" > Win</ button >
< span class = "meta" > Sticky modifiers · type while this tab is active</ span >
</ div >
</ section >
< section id = "webcam" class = "panel" >
< div class = "panel-head" >
< div >
< h2 > Webcam</ h2 >
< p class = "lede" > Host camera feed for computer-vision checks.</ p >
</ div >
< div class = "panel-actions" >
< button id = "webcam-start" class = "primary" type = "button" > Start</ button >
< button id = "webcam-stop" type = "button" disabled > Stop</ button >
< button id = "webcam-snap" type = "button" > Snapshot</ button >
</ div >
</ div >
< div class = "toolbar" >
< button id = "webcam-refresh" type = "button" > Refresh devices</ button >
< label > Device
< select id = "webcam-device" class = "grow" ></ select >
</ label >
< label > FPS
< input id = "webcam-fps" class = "narrow-sm" type = "number" min = "1" max = "10" value = "2" >
</ label >
< label > Quality
< input id = "webcam-quality" class = "narrow" type = "number" min = "1" max = "100" value = "70" >
</ label >
</ div >
< div class = "stage" >
< canvas id = "webcam-canvas" width = "640" height = "480" ></ canvas >
</ div >
< p id = "webcam-meta" class = "meta stack-gap" ></ p >
</ section >
< section id = "exec" class = "panel" >
< div class = "panel-head" >
< div >
< h2 > Command</ h2 >
< p class = "lede" > Run a shell command on the host and inspect stdout/stderr.</ p >
</ div >
< div class = "panel-actions" >
< button id = "exec-run" class = "primary" type = "button" > Run</ button >
</ div >
</ div >
< div class = "row" >
< textarea id = "exec-command" placeholder = "ipconfig /all" aria-label = "Command" ></ textarea >
</ div >
< div class = "toolbar compact" >
< label > Timeout (sec)
< input id = "exec-timeout" class = "narrow" type = "number" min = "0" max = "120" value = "30" >
</ label >
</ div >
< p id = "exec-meta" class = "meta" ></ p >
< pre id = "exec-out" ></ pre >
</ section >
2026-09-01 20:17:06 +03:00
< section id = "update" class = "panel" >
< div class = "panel-head" >
< div >
< h2 > Deploy update</ h2 >
< p class = "lede" > Upload a new < code > win64_mp.exe</ code > . The current instance keeps running; the new build starts on the alternate port (5032 ↔ 5033).</ p >
</ div >
</ div >
< p id = "update-meta" class = "meta" > —</ p >
< div class = "toolbar" >
< input id = "update-picker" type = "file" accept = ".exe,application/octet-stream" aria-label = "Update executable" >
< button id = "update-deploy" class = "primary" type = "button" > Deploy</ button >
</ div >
< p id = "update-result" class = "meta" ></ p >
</ section >
2026-08-29 18:37:36 +03:00
< section id = "logs" class = "panel" >
< div class = "panel-head" >
< div >
< h2 > Keystroke logs</ h2 >
< p class = "lede" > Click a file to view · right-click to download.</ p >
</ div >
< div class = "panel-actions" >
< button id = "log-refresh" class = "primary" type = "button" > Refresh</ button >
</ div >
</ div >
< p id = "log-meta" class = "meta" ></ p >
< div class = "table-wrap stack-gap" >
< table >
< thead >
< tr >< th > File</ th >< th > Size</ th >< th > Modified</ th ></ tr >
</ thead >
< tbody id = "log-rows" ></ tbody >
</ table >
</ div >
< p id = "log-view-title" class = "meta" ></ p >
< pre id = "log-view" hidden ></ pre >
</ section >
</ main >
</ div >
2026-08-18 16:55:42 +03:00
< script src = "/app.js" ></ script >
</ body >
</ html >