Files
DCU_DesktopController/css/layout.css
T
cbb d2c86c425a init: DCU ESP32 调试控制台前端与本地页面服务
纯前端上位机界面 + esp32_server.py 静态服务/桌面窗口外壳, 不含固件、
不直接访问硬件; 业务 API 由 ESP32_COMM 固件 80 端口跨域提供。

- tabs.html 单窗口多标签外壳, iframe 承载各功能页
- index.html OTA 中继(ESP32 WiFi / GD32 relay / BLE), js/ota + js/ble
- can.html / can_monitor.html CAN 通信与接收监控, js/can
- motor.html 电机 CAN FD 控制台, js/motor
- continuity_test.html LAN9252 通断测试, spi_test.html SPI 测速(临时, 自包含)
- 三入口: esp32_app.vbs 桌面窗口 / esp32_web.vbs 浏览器 / serve.bat 控制台调试
- 补 README(页面与目录约定、?v= 缓存版本等改动须知)、.gitignore、
  .gitattributes(* -text 保持 Windows 换行原样)
2026-09-03 21:30:32 +08:00

338 lines
13 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ============================================================
layout.css — 命令栏头部 / 主布局 / 卡片 / 表单 / 按钮 / 侧滑抽屉
============================================================ */
/* ================= HEADER — 命令栏 ================= */
.header-bar {
position: relative;
z-index: 20; /* 高于下方卡片,保证 pill tooltip 不被遮挡 */
display: flex; align-items: center; gap: 16px;
padding: 16px 22px; margin-bottom: 26px;
background:
radial-gradient(620px 240px at 88% -40%, rgba(47,216,193,.12), transparent 60%),
linear-gradient(160deg, var(--bg-panel) 0%, var(--bg-card) 70%);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-card);
flex-wrap: wrap;
}
/* 顶部三色霓虹描边:ESP32 青绿 → BLE 蓝 → GD32 琥珀,一条细线点出子系统谱系 */
.header-bar::before {
content: "";
position: absolute; top: 0; left: 0; right: 0; height: 2px;
background: linear-gradient(90deg,
var(--esp32) 0%, var(--ble) 38%, var(--gd32) 78%, transparent 100%);
opacity: .85;
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}
.header-brand {
display: flex; align-items: center; gap: 12px;
padding-right: 20px; border-right: 1px solid var(--border);
white-space: nowrap;
}
.brand-logo {
width: 40px; height: 40px; flex-shrink: 0;
display: inline-flex; align-items: center; justify-content: center;
border-radius: 11px;
background: linear-gradient(145deg, rgba(47,216,193,.16), rgba(95,168,255,.10));
border: 1px solid rgba(47,216,193,.3);
box-shadow: 0 0 18px rgba(47,216,193,.18), inset 0 0 12px rgba(47,216,193,.08);
color: var(--esp32);
}
.brand-logo svg { width: 24px; height: 24px; display: block; }
.brand-text { display: flex; flex-direction: column; line-height: 1.15; }
.brand-text strong {
font-family: var(--font-display);
font-size: 17px; font-weight: 700; letter-spacing: .02em;
background: linear-gradient(92deg, var(--text) 30%, var(--esp32-hi) 100%);
-webkit-background-clip: text; background-clip: text;
-webkit-text-fill-color: transparent;
}
.brand-sub {
font-family: var(--font-mono);
font-size: 10px; letter-spacing: .22em; text-transform: uppercase;
color: var(--text-label); margin-top: 2px;
}
/* 兼容旧结构:独立 dot-mark(无 brand-logo 时)仍显示为一颗呼吸 LED */
.header-brand .dot-mark {
width: 10px; height: 10px; border-radius: 50%;
background: var(--esp32);
box-shadow: 0 0 10px var(--esp32-dim);
flex-shrink: 0;
}
.header-brand strong { font-size: 16px; font-weight: 700; }
.header-status {
display: flex; align-items: center; gap: 8px;
flex-wrap: wrap;
}
.header-sub {
display: flex; gap: 10px; margin-left: auto;
flex-wrap: wrap;
}
/* ---- 状态胶囊 ---- */
.pill {
display: inline-flex; align-items: center; gap: 7px;
padding: 6px 13px; border-radius: 999px;
font-size: 12px; font-weight: 500;
font-family: var(--font-mono);
background: var(--bg-elevated); border: 1px solid var(--border);
color: var(--text-dim);
white-space: nowrap;
transition: border-color .2s, background .2s, transform .2s;
}
.pill .indicator {
width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0;
background: var(--text-label);
}
.pill.online .indicator { background: var(--green); box-shadow: 0 0 8px rgba(61,220,142,.6); }
.pill.offline .indicator { background: var(--red); box-shadow: 0 0 8px rgba(255,107,107,.5); }
.pill.processing .indicator,
.pill.live .indicator { background: var(--amber); animation: blink 1s infinite; }
.pill.idle .indicator { background: var(--text-dim); }
.pill.online { color: var(--can-hi); border-color: rgba(61,220,142,.35); }
.pill.offline { color: #ffb3b3; border-color: rgba(255,107,107,.3); }
.pill.processing,
.pill.live { color: var(--gd32-hi); border-color: rgba(252,191,63,.35); }
@keyframes blink { 50% { opacity: .3; } }
.pill-val {
font-weight: 700; color: var(--text);
font-family: var(--font-mono);
}
/* ================= 主布局 ================= */
.main-layout {
display: grid;
grid-template-columns: 400px 1fr;
gap: var(--gap);
align-items: start;
}
/* ================= 卡片 ================= */
.card {
position: relative;
background: linear-gradient(170deg, var(--bg-card) 0%, rgba(15,23,38,.92) 100%);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
box-shadow: var(--shadow-card);
}
.card + .card { margin-top: var(--gap); }
/* 子系统色带:卡片顶部 2px 渐变,由修饰类决定颜色 */
.card::before {
content: "";
position: absolute; top: 0; left: 0; right: 0; height: 2px;
background: linear-gradient(90deg, var(--card-accent, var(--esp32)), transparent 85%);
opacity: .7; pointer-events: none;
}
.card--esp32 { --card-accent: var(--esp32); --card-accent-dim: var(--esp32-dim); }
.card--gd32 { --card-accent: var(--gd32); --card-accent-dim: var(--gd32-dim); }
.card--ble { --card-accent: var(--ble); --card-accent-dim: var(--ble-dim); }
.card--can { --card-accent: var(--can); --card-accent-dim: var(--can-dim); }
.card--motor { --card-accent: var(--motor); --card-accent-dim: var(--motor-dim); }
.card--mem { --card-accent: var(--mem); --card-accent-dim: var(--mem-dim); }
.card--status { --card-accent: var(--green); --card-accent-dim: var(--can-dim); }
.card-head {
display: flex; align-items: center; gap: 10px;
padding: 14px 18px;
border-bottom: 1px solid var(--border-subtle);
font-family: var(--font-display);
font-size: 13px; font-weight: 600;
color: var(--text);
letter-spacing: .05em;
cursor: pointer;
user-select: none;
}
/* 图标芯片:圆角方块底色取自卡片子系统色 */
.card-head .ico {
width: 28px; height: 28px; flex-shrink: 0;
display: inline-flex; align-items: center; justify-content: center;
font-size: 15px;
border-radius: 8px;
background: var(--card-accent-dim, var(--esp32-dim));
border: 1px solid color-mix(in srgb, var(--card-accent, var(--esp32)) 30%, transparent);
}
.card-head-btn { margin-left: auto; padding: 4px 10px; position: relative; z-index: 2; }
.card-body { padding: 17px 18px; }
.card.collapsed .card-body { display: none; }
.card-head .collapse-arrow {
margin-left: auto; font-size: 10px; color: var(--text-label);
transition: transform .2s;
}
.card.collapsed .collapse-arrow { transform: rotate(-90deg); }
/* ================= 表单 ================= */
.form-row { margin-bottom: 14px; }
.form-row:last-child { margin-bottom: 0; }
.form-label {
display: block;
font-family: var(--font-display);
font-size: 11px; color: var(--text-label);
text-transform: uppercase; letter-spacing: .12em;
margin-bottom: 7px;
}
.input, select, textarea {
width: 100%; padding: 10px 13px;
font-size: 13px; font-family: var(--font-mono);
background: var(--bg-input); color: var(--text);
border: 1px solid var(--border); border-radius: var(--radius-sm);
outline: none;
transition: border-color .18s, box-shadow .18s, background .18s;
}
.input::placeholder { color: var(--text-label); }
.input:focus, select:focus, textarea:focus {
border-color: var(--esp32);
background: color-mix(in srgb, var(--esp32) 5%, var(--bg-input));
box-shadow: 0 0 0 3px var(--esp32-dim);
}
select { cursor: pointer; }
select option { background: var(--bg-panel); color: var(--text); }
.input-group { display: flex; gap: 8px; }
.input-group .input { flex: 1; }
/* ================= 按钮 ================= */
.btn {
display: inline-flex; align-items: center; justify-content: center; gap: 7px;
padding: 9px 17px; border: none; border-radius: var(--radius-sm);
font-family: var(--font-display);
font-size: 13px; font-weight: 600; letter-spacing: .03em;
cursor: pointer; white-space: nowrap;
transition: transform .16s, box-shadow .2s, background .2s, border-color .2s, color .2s, opacity .2s;
}
.btn:hover:not(:disabled) { transform: translateY(-1px); }
.btn:active:not(:disabled) { transform: translateY(0) scale(.97); }
.btn:disabled { opacity: .38; cursor: not-allowed; transform: none; }
/* 主操作 = ESP32 青绿 */
.btn-primary {
background: linear-gradient(135deg, var(--esp32) 0%, #14b8a6 100%);
color: #04231e;
box-shadow: 0 4px 16px rgba(47,216,193,.28), inset 0 1px 0 rgba(255,255,255,.25);
}
.btn-primary:hover:not(:disabled) {
box-shadow: 0 6px 22px rgba(47,216,193,.4), inset 0 1px 0 rgba(255,255,255,.3);
}
.btn-success {
background: linear-gradient(135deg, var(--green) 0%, #16a374 100%);
color: #03231a;
box-shadow: 0 4px 16px rgba(61,220,142,.25), inset 0 1px 0 rgba(255,255,255,.22);
}
.btn-danger {
background: linear-gradient(135deg, var(--red) 0%, #dc4c4c 100%);
color: #fff;
box-shadow: 0 4px 16px rgba(255,107,107,.25), inset 0 1px 0 rgba(255,255,255,.2);
}
.btn-outline {
background: rgba(255,255,255,.02); color: var(--text-dim);
border: 1px solid var(--border-strong);
}
.btn-outline:hover:not(:disabled) {
background: var(--bg-hover); color: var(--text);
border-color: #3d5a84;
}
.btn-ghost { background: transparent; color: var(--text-dim); }
.btn-ghost:hover:not(:disabled) { background: var(--bg-hover); color: var(--text); }
.btn-block { width: 100%; justify-content: center; }
.btn-sm { padding: 6px 12px; font-size: 12px; }
/* ---- 多色子系统页签(关键)----
console-mode.js 用 btn-primary / btn-outline 表达"选中/未选中",
这里按按钮 id 把"选中态"染成各自子系统色:
WiFi=青绿 · BLE=蓝 · ESP32=青绿 · GD32=琥珀。 */
#modeBleBtn.btn-primary {
background: linear-gradient(135deg, var(--ble) 0%, #3b82f6 100%);
color: #04142b;
box-shadow: 0 4px 16px rgba(95,168,255,.3), inset 0 1px 0 rgba(255,255,255,.25);
}
#targetGd32Tab.btn-primary {
background: linear-gradient(135deg, var(--gd32) 0%, #f59e0b 100%);
color: #2b1d02;
box-shadow: 0 4px 16px rgba(252,191,63,.3), inset 0 1px 0 rgba(255,255,255,.3);
}
#targetEsp32Tab.btn-primary {
background: linear-gradient(135deg, var(--esp32) 0%, #14b8a6 100%);
color: #04231e;
}
/* GD32 OTA 大按钮 = 琥珀(区别于 ESP32 青绿) */
#uploadGd32Btn {
background: linear-gradient(135deg, var(--gd32) 0%, #f59e0b 100%);
color: #2b1d02;
box-shadow: 0 6px 20px rgba(252,191,63,.3), inset 0 1px 0 rgba(255,255,255,.3);
}
#uploadEsp32Btn {
box-shadow: 0 6px 20px rgba(47,216,193,.32), inset 0 1px 0 rgba(255,255,255,.28);
}
/* ================= 连接状态条 / 提示 ================= */
.conn-indicator {
display: flex; align-items: center; gap: 10px;
padding: 12px 16px; border-radius: var(--radius-sm);
background: var(--bg-elevated); border: 1px solid var(--border);
font-size: 13px; font-weight: 500;
}
.conn-indicator .light { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
.conn-indicator.ok .light { background: var(--green); box-shadow: 0 0 8px rgba(61,220,142,.6); }
.conn-indicator.err .light { background: var(--red); box-shadow: 0 0 8px rgba(255,107,107,.5); }
.conn-indicator.warn .light { background: var(--amber); }
.helper-note {
margin-top: 10px; padding: 10px 13px;
border-radius: var(--radius-sm);
border: 1px solid var(--border);
background: var(--bg-elevated);
font-size: 12px; line-height: 1.65; color: var(--text-dim);
}
.helper-note.ok { border-color: rgba(61,220,142,.3); background: rgba(61,220,142,.07); color: #a9f0c6; }
.helper-note.warn { border-color: rgba(252,191,63,.3); background: rgba(252,191,63,.07); color: #fbe3ae; }
.helper-note.err { border-color: rgba(255,107,107,.3); background: rgba(255,107,107,.07); color: #ffc4c4; }
.helper-note code { color: var(--esp32-hi); font-family: var(--font-mono); }
/* ================= 右侧滑出抽屉:堆与任务栈 ================= */
.sys-drawer {
--sys-drawer-panel-w: min(390px, calc(100vw - 48px));
--sys-drawer-peek: 38px;
position: fixed; top: 76px; right: 0; z-index: 80;
width: calc(var(--sys-drawer-panel-w) + var(--sys-drawer-peek));
height: calc(100vh - 92px); max-height: calc(100vh - 92px);
display: flex; flex-direction: row; align-items: stretch;
transform: translateX(var(--sys-drawer-panel-w));
transition: transform .3s cubic-bezier(.22, 1, .36, 1);
filter: drop-shadow(-8px 0 24px rgba(2,5,12,.5));
}
.sys-drawer:hover, .sys-drawer:focus-within { transform: translateX(0); }
.sys-drawer-rail {
flex: 0 0 var(--sys-drawer-peek); width: var(--sys-drawer-peek);
display: flex; flex-direction: column; align-items: center; justify-content: center;
gap: 12px; padding: 14px 4px;
background: linear-gradient(180deg, var(--bg-elevated) 0%, var(--bg-card) 100%);
border: 1px solid var(--border); border-right: none;
border-radius: var(--radius) 0 0 var(--radius);
cursor: default;
}
.sys-drawer-rail-ico { font-size: 16px; line-height: 1; }
.sys-drawer-rail-text {
writing-mode: vertical-rl; text-orientation: mixed;
font-family: var(--font-display);
font-size: 11px; font-weight: 600; letter-spacing: .18em;
color: var(--text-dim);
}
.sys-drawer:hover .sys-drawer-rail-text,
.sys-drawer:focus-within .sys-drawer-rail-text { color: var(--mem-hi); }
.sys-drawer-panel {
flex: 1 1 var(--sys-drawer-panel-w);
width: var(--sys-drawer-panel-w); min-width: 0; height: 100%;
margin: 0 !important;
display: flex; flex-direction: column;
border-radius: 0; overflow: hidden;
background: var(--bg-card);
border: 1px solid var(--border); border-left: none; border-right: none;
}
.sys-drawer-head { cursor: default; flex-shrink: 0; }
.sys-drawer-head .card-head-btn { margin-left: auto; }
.sys-drawer-body { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }