Files
DCU_DesktopController/css/layout.css
T
cbb eac3717f9a feat: 添加局域网设备发现功能和链路胶囊优化
- 实现局域网设备发现面板,支持探测ESP32中继板并自动切换端点
- 添加POST /api/lan/probe接口用于设备探测,返回三态判决结果
- 实现快扫和全段扫描两种模式,支持种子地址优先探测
- 添加端点唯一写手机制,防止多处同时修改设备IP
- 优化链路胶囊UI显示WiFi/EtherCAT/BLE连接状态和设备名称
- 修复EtherCAT链路OTA刷写期间PDO循环暂停和AL状态处理问题
- 调整PDO周期设置范围为0.125-4ms,支持亚毫秒显示为微秒
- 添加ESP32 WiFi使能开关控件样式和数据来源角标显示
2026-10-13 00:48:34 +08:00

606 lines
22 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);
}
/* ---- 顶栏三链路胶囊(WiFi / ECAT / BLE) ---- */
.link-pill { gap: 6px; padding: 5px 10px 5px 8px; }
.link-pill-tag {
font-size: 10px; font-weight: 700; letter-spacing: .04em;
padding: 1px 5px; border-radius: 4px;
color: var(--esp32); background: var(--esp32-dim);
}
.link-pill-tag.tag-ecat { color: var(--mem-hi); background: var(--mem-dim); }
.link-pill-tag.tag-ble { color: var(--ble); background: rgba(95,168,255,.12); }
.link-pill #pillWifiTxt,
.link-pill #pillEcatTxt,
.link-pill #pillBleTxt,
.link-pill #canNetTxt {
font-size: 11px; overflow: hidden; text-overflow: ellipsis;
}
/* WiFi 要带 IP+信号;ECAT/BLE 连接后显示设备名 */
.link-pill #pillWifiTxt,
.link-pill #canNetTxt { max-width: 22em; }
.link-pill #pillEcatTxt { max-width: 14em; }
.link-pill #pillBleTxt { max-width: 18em; }
/* ================= 主布局 ================= */
.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 chrome(OTA/CAN 等页)。电机页主题按钮在 motor.css,勿互相复制。 */
.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);
}
#modeEcatBtn.btn-primary {
background: linear-gradient(135deg, #8b5cf6 0%, #6366f1 100%);
color: #0f0a1f;
box-shadow: 0 4px 16px rgba(139,92,246,.3), inset 0 1px 0 rgba(255,255,255,.25);
}
#modeWifiBtn.btn-outline {
border-color: rgba(47,216,193,.45);
color: var(--esp32);
}
#modeBleBtn.btn-outline {
border-color: rgba(95,168,255,.45);
color: var(--ble);
}
#modeEcatBtn.btn-outline {
border-color: rgba(139,92,246,.45);
color: #a78bfa;
}
#modeWifiBtn.btn-outline:hover:not(:disabled) {
border-color: var(--esp32);
background: rgba(47,216,193,.08);
color: var(--esp32);
}
#modeBleBtn.btn-outline:hover:not(:disabled) {
border-color: var(--ble);
background: rgba(95,168,255,.08);
color: var(--ble);
}
#modeEcatBtn.btn-outline:hover:not(:disabled) {
border-color: #8b5cf6;
background: rgba(139,92,246,.08);
color: #a78bfa;
}
/* 升级目标:左右两个等宽扁按钮(GD32 琥珀 · ESP32 青绿) */
.target-tab-row {
display: flex;
gap: 8px;
margin-bottom: 14px;
}
.target-tab-btn {
flex: 1 1 0;
min-width: 0;
min-height: 40px;
padding: 10px 14px;
font-size: 14px;
font-weight: 700;
letter-spacing: .04em;
flex-direction: row;
gap: 8px;
border-radius: var(--radius-sm);
}
.target-tab-btn .target-tab-ico {
font-size: 15px;
line-height: 1;
opacity: .9;
}
.target-tab-btn .target-tab-txt {
line-height: 1.1;
}
.target-tab-btn.btn-outline {
border-width: 1.5px;
background: rgba(255,255,255,.03);
}
#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;
box-shadow: 0 4px 16px rgba(47,216,193,.28), inset 0 1px 0 rgba(255,255,255,.25);
}
/* 未选中时用各自子系统的淡描边,避免两个灰按钮看不出身份 */
#targetGd32Tab.btn-outline {
border-color: rgba(252,191,63,.45);
color: var(--gd32);
}
#targetEsp32Tab.btn-outline {
border-color: rgba(47,216,193,.45);
color: var(--esp32);
}
#targetGd32Tab.btn-outline:hover:not(:disabled) {
border-color: var(--gd32);
background: rgba(252,191,63,.08);
color: var(--gd32);
}
#targetEsp32Tab.btn-outline:hover:not(:disabled) {
border-color: var(--esp32);
background: rgba(47,216,193,.08);
color: var(--esp32);
}
#ppEcatOta.active {
color: #2b1d02;
background: linear-gradient(135deg, var(--gd32), #f59e0b);
box-shadow: 0 2px 12px rgba(252,191,63,.35);
}
/* EtherCAT 连接说明 + 探测:与卡片内其他控件同一套字重/字距 */
.ecat-mode-note {
margin: 0 0 12px;
padding: 10px 12px;
border-radius: var(--radius-sm);
border: 1px solid var(--border-subtle);
background: rgba(252,191,63,.06);
font-size: 12px;
line-height: 1.7;
color: var(--text-dim);
}
.ecat-mode-note b {
color: var(--gd32);
font-weight: 600;
}
#bootEcatProbeBtn {
margin-bottom: 10px;
border-color: rgba(252,191,63,.4);
color: var(--gd32);
background: rgba(252,191,63,.05);
}
#bootEcatProbeBtn:hover:not(:disabled) {
border-color: var(--gd32);
background: rgba(252,191,63,.12);
color: var(--gd32-hi);
}
/* ---- APP ↔ BOOT 跳转控制台 ---- */
.jump-role-panel {
display: grid;
grid-template-columns: 1fr;
gap: 10px;
margin-bottom: 12px;
}
@media (min-width: 420px) {
.jump-role-panel { grid-template-columns: 140px 1fr; align-items: stretch; }
}
.jump-role-badge {
display: flex; flex-direction: column; justify-content: center; align-items: center;
gap: 2px; min-height: 92px; padding: 12px 10px;
border-radius: 12px; border: 1px solid var(--border);
background: var(--bg-elevated); text-align: center;
transition: border-color .2s, background .2s, box-shadow .2s;
cursor: pointer; user-select: none;
}
.jump-role-badge:hover {
border-color: var(--gd32);
box-shadow: 0 0 0 3px rgba(252,191,63,.12);
}
.jump-role-badge:active { transform: scale(0.98); }
.jump-role-badge .jump-role-label {
font-family: var(--font-display); font-size: 10px; letter-spacing: .12em;
text-transform: uppercase; color: var(--text-label);
}
.jump-role-badge strong {
font-family: var(--font-display); font-size: 18px; font-weight: 700;
letter-spacing: .04em; color: var(--text);
}
.jump-role-badge .jump-role-sw {
font-family: var(--font-mono); font-size: 11px; color: var(--text-dim);
}
.jump-role-badge[data-role="app"] {
border-color: rgba(47,216,193,.55);
background: rgba(47,216,193,.1);
box-shadow: 0 0 0 3px rgba(47,216,193,.08);
}
.jump-role-badge[data-role="app"] strong { color: var(--esp32); }
.jump-role-badge[data-role="boot"] {
border-color: rgba(252,191,63,.6);
background: rgba(252,191,63,.12);
box-shadow: 0 0 0 3px rgba(252,191,63,.1);
}
.jump-role-badge[data-role="boot"] strong { color: var(--gd32); }
.jump-role-badge[data-role="unknown"] strong { color: var(--text-dim); font-size: 15px; }
.jump-role-meta {
display: grid; grid-template-columns: 1fr 1fr; gap: 6px 10px;
font-size: 12px;
}
.jump-role-meta > div {
display: flex; flex-direction: column; gap: 2px; min-width: 0;
}
.jump-role-meta span {
color: var(--text-label); font-family: var(--font-display);
font-size: 10px; letter-spacing: .08em; text-transform: uppercase;
}
.jump-role-meta b {
font-family: var(--font-mono); font-weight: 600; color: var(--text);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.jump-action-row {
display: grid; grid-template-columns: 88px 1fr; gap: 8px; margin-top: 4px;
}
.jump-btn-primary {
min-height: 44px;
background: linear-gradient(135deg, var(--gd32) 0%, #f59e0b 100%);
color: #2b1d02 !important; font-weight: 700;
box-shadow: 0 4px 14px rgba(252,191,63,.28);
}
.jump-btn-primary:hover:not(:disabled) {
filter: brightness(1.05);
color: #2b1d02 !important;
}
.jump-btn-secondary { min-height: 44px; }
.jump-btn-test {
margin-top: 8px; min-height: 40px;
border-color: rgba(252,191,63,.35); color: var(--gd32);
background: transparent;
}
.jump-btn-test:hover:not(:disabled) {
background: rgba(252,191,63,.1); border-color: var(--gd32);
}
.jump-btn-test:disabled { opacity: .45; cursor: not-allowed; }
.jump-history {
margin-top: 12px; border: 1px solid var(--border-subtle);
border-radius: var(--radius-sm); background: var(--bg-card);
overflow: hidden;
}
.jump-history-head {
padding: 8px 12px; border-bottom: 1px solid var(--border-subtle);
font-family: var(--font-display); font-size: 11px; letter-spacing: .1em;
text-transform: uppercase; color: var(--text-label);
}
.jump-history-list {
max-height: 148px; overflow-y: auto; padding: 6px 0;
}
.jump-history-empty {
padding: 14px 12px; font-size: 12px; color: var(--text-dim); text-align: center;
}
.jump-history-item {
display: flex; align-items: flex-start; gap: 8px;
padding: 6px 12px; font-size: 12px; line-height: 1.45;
font-family: var(--font-mono);
}
.jump-history-item + .jump-history-item { border-top: 1px dashed var(--border-subtle); }
.jump-history-item .jh-tag {
flex-shrink: 0; min-width: 42px; text-align: center;
padding: 1px 6px; border-radius: 999px; font-size: 10px; font-weight: 700;
background: var(--bg-input); color: var(--text-dim);
}
.jump-history-item.ok .jh-tag { background: rgba(61,220,142,.18); color: var(--can-hi); }
.jump-history-item.err .jh-tag { background: rgba(255,107,107,.16); color: var(--red); }
.jump-history-item.info .jh-tag { background: rgba(47,216,193,.14); color: var(--esp32); }
.jump-history-item .jh-msg { flex: 1; color: var(--text); word-break: break-all; }
.jump-history-item .jh-time {
flex-shrink: 0; color: var(--text-dim); font-size: 10px;
}
/* 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);
}
#bootEcatRunBtn {
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);
}
#bootEcatRunBtn:disabled {
opacity: .38;
}
#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; background: var(--text-label); }
.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); box-shadow: 0 0 8px rgba(252,191,63,.45); }
.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; }