diff --git a/firmware/esp32/overland-controller/overland-controller.ino b/firmware/esp32/overland-controller/overland-controller.ino index da2e425..180dfd5 100644 --- a/firmware/esp32/overland-controller/overland-controller.ino +++ b/firmware/esp32/overland-controller/overland-controller.ino @@ -139,6 +139,163 @@ button.secondary,button[onclick^="toggle"],button[onclick^="connect"],button[onc #config-wifi .list{gap:10px} } + +/* UI theme v3 */ +:root{ + --panel:#0b121b; + --panel2:#111b28; + --panel3:#162235; + --accent:#5f8fcf; + --save:#1f7a3d; + --save2:#14582b; + --danger:#843543; + --danger2:#5b202b; + --softline:rgba(255,255,255,.08); +} +.top{ + background:linear-gradient(180deg,rgba(15,24,36,.96),rgba(9,14,21,.90)); + border-bottom:1px solid var(--softline); + padding:16px 18px; + border-radius:0 0 22px 22px; + box-shadow:0 14px 36px rgba(0,0,0,.25); +} +.brand{ + font-size:20px; + font-weight:950; + letter-spacing:-.03em; +} +.pill{ + padding:8px 12px; + border-radius:999px; + border:1px solid var(--softline); + background:#0b131d; +} +.grid{gap:18px} +.card{ + border-radius:22px; + padding:20px; + background: + linear-gradient(180deg,rgba(23,34,50,.96),rgba(10,17,26,.98)), + radial-gradient(circle at top left,rgba(95,143,207,.18),transparent 38%); +} +.card .label{ + display:flex; + align-items:center; + gap:8px; + margin-bottom:6px; +} +.card .label::before, +.configGroupTitle::before{ + content:""; + width:8px; + height:22px; + border-radius:999px; + background:linear-gradient(180deg,var(--accent),#2b4c75); + display:inline-block; + vertical-align:middle; + margin-right:8px; +} +.configGroupTitle{ + display:flex; + align-items:center; +} +.subtabs{ + position:sticky; + top:0; + z-index:5; + backdrop-filter:blur(10px); + box-shadow:0 10px 28px rgba(0,0,0,.25); +} +.subtabbtn{ + flex:0 0 auto; + min-width:92px; + justify-content:center; +} +.configSubpage .grid{ + animation:fadeIn .14s ease-out; +} +@keyframes fadeIn{ + from{opacity:.65;transform:translateY(3px)} + to{opacity:1;transform:none} +} +input,select,textarea{ + border-radius:14px; + padding:12px 13px; +} +button{ + display:inline-flex; + align-items:center; + justify-content:center; + gap:8px; +} +button.save, +button[onclick^="save"]{ + background:linear-gradient(180deg,var(--save),var(--save2)); +} +button.danger, +button[onclick^="reset"], +button[onclick^="factory"]{ + background:linear-gradient(180deg,var(--danger),var(--danger2)); +} +button.secondary, +button[onclick^="toggle"], +button[onclick^="connect"], +button[onclick^="scan"]{ + background:linear-gradient(180deg,#315983,#1f3d61); +} +.btnrow{ + gap:10px; +} +.list{ + gap:10px; +} +.configWarning{ + margin-top:8px; +} +.big{ + letter-spacing:-.05em; +} +.kpi div{ + background:linear-gradient(180deg,#111b28,#0c141f); + border-color:var(--softline); +} +@media(min-width:980px){ + #configPage{ + display:grid; + grid-template-columns:190px 1fr; + gap:18px; + align-items:start; + } + #configPage .subtabs{ + display:flex; + flex-direction:column; + align-self:start; + top:14px; + padding:10px; + max-height:calc(100vh - 120px); + } + #configPage .subtabbtn{ + width:100%; + justify-content:flex-start; + border-radius:14px; + } + #config-general, + #config-wifi, + #config-relays, + #config-temps, + #config-bms, + #config-maintenance{ + grid-column:2; + } +} +@media(max-width:640px){ + .card{padding:16px;border-radius:18px} + .top{padding:14px} + .brand{font-size:18px} + button{width:100%} + .inlineActions{grid-template-columns:1fr} +} + diff --git a/tests/test_http_api_contract.py b/tests/test_http_api_contract.py index 67aeb2d..f753601 100644 --- a/tests/test_http_api_contract.py +++ b/tests/test_http_api_contract.py @@ -473,7 +473,7 @@ def test_embedded_webui_preserves_config_input_while_polling(): assert 'setConfigValue("apSsidInput"' in source assert "deviceNameInput" in source assert "bmsNameInput" in source - assert 'setConfigValue("tempEnabledCount"' in source + assert "tempEnabledCount" in source def test_ap_config_can_be_reset_to_defaults(): @@ -500,3 +500,16 @@ def test_embedded_webui_has_consistent_button_intent_styles(): assert 'class="save" onclick="saveApConfig()"' in source assert 'class="danger" onclick="resetApConfig()"' in source assert 'class="save" onclick="saveWifi()"' in source + + +def test_embedded_webui_theme_v3(): + source = firmware_source() + + assert "/* UI theme v3 */" in source + assert "--accent:#5f8fcf" in source + assert "@media(min-width:980px)" in source + assert "#configPage" in source + assert "grid-template-columns:190px 1fr" in source + assert "button.save" in source + assert "button.danger" in source + assert "button.secondary" in source