变更点: 1. esp32_server.py - /api/motor/ui-state、/api/lab/ui-state:配置原子落盘 data/*.json(WebView2 清存储仍在) - _GuiApi.ecat_api/bridge_info:桌面壳经 GUI 进程裸 socket 直连 ecat_native,不经浏览器 HTTP 2. js/common/desktop-bridge.js(新) - 探测 pywebview.api(含 iframe top/parent),transport-ecat 优先走原生桥 3. connection.js + responsive.css + components.css - setQuietMode:离线且无传输时 html[data-dcu-calm=1] 掐常驻动画 - 进度条斜纹仅 .live 挂载,空闲不再按刷新率空转重绘 4. device-link.js - 先查 native/health,已健康跳过 motor/start,避免 loop_stop 重进 OP 假超时 - 周期强制 0.25ms + DC SYNC0 对齐;轮询自愈被配错的 DC/周期 5. motor/main.js + transport-ecat.js - 配置双写 localStorage + 磁盘 ui-state;pagehide/sendBeacon 刷盘;导入导出 - 探测代际 probeGen;ECAT 失败阈值 12;跟踪 bootActive/slaveAl/slaveOp 6. lab/spi_test.html、ota/boot_ecat.js、can_monitor 等跟随桥/静默态 7. .gitignore - 排除 scripts/_* 与 anal/collect/measure/trace 等调试脚本、lab 产物、data/ 运行态
271 lines
12 KiB
CSS
271 lines
12 KiB
CSS
/* ============================================================
|
|
responsive.css — 响应式断点 + 动效(对齐新设计令牌)
|
|
============================================================ */
|
|
|
|
/* ================= 断点 ================= */
|
|
@media (max-width: 1100px) {
|
|
.main-layout { grid-template-columns: 1fr; }
|
|
.sys-drawer {
|
|
--sys-drawer-panel-w: min(340px, calc(100vw - 40px));
|
|
--sys-drawer-peek: 32px;
|
|
top: 64px; height: calc(100vh - 72px); max-height: calc(100vh - 72px);
|
|
}
|
|
.can-monitor-drawer { --sys-drawer-panel-w: min(400px, calc(100vw - 40px)); }
|
|
.badge-grid { grid-template-columns: repeat(2, 1fr); }
|
|
.can-toolbar-grid, .can-toolbar-foot, .can-editor-layout { grid-template-columns: 1fr; }
|
|
.message-list { max-height: 420px; }
|
|
.can-meta-grid { grid-template-columns: 1fr 1fr; }
|
|
}
|
|
@media (max-width: 640px) {
|
|
.container { padding: 16px; }
|
|
.sys-drawer {
|
|
--sys-drawer-panel-w: calc(100vw - 36px);
|
|
--sys-drawer-peek: 28px;
|
|
top: 56px; height: calc(100vh - 60px); max-height: calc(100vh - 60px);
|
|
}
|
|
.sys-drawer-rail-text { font-size: 11px; }
|
|
.header-bar { flex-direction: column; align-items: flex-start; }
|
|
.header-sub { margin-left: 0; }
|
|
.badge-grid, .metric-grid { grid-template-columns: 1fr; }
|
|
.status-kv-grid, .status-task-meta { grid-template-columns: 1fr; }
|
|
.can-grid { grid-template-columns: 1fr 1fr; }
|
|
.btn-row { flex-direction: column; }
|
|
.can-meta-grid { grid-template-columns: 1fr; }
|
|
.can-launch-top, .can-launch-actions, .can-launch-foot {
|
|
grid-template-columns: 1fr; flex-direction: column; align-items: flex-start;
|
|
}
|
|
.can-selection-bar, .can-selection-actions { flex-direction: column; align-items: flex-start; }
|
|
.can-msg-toolbar { flex-wrap: wrap; }
|
|
.history-item-row {
|
|
grid-template-columns: auto 56px minmax(0, 1fr);
|
|
grid-template-rows: auto auto;
|
|
}
|
|
.history-item-hex, .history-item-foot { grid-column: 1 / -1; text-align: left; }
|
|
.can-title { font-size: 22px; }
|
|
.can-frame-hex { font-size: 15px; }
|
|
.message-table, .signal-table { min-width: 0; max-width: 100%; }
|
|
}
|
|
|
|
/* ================= 动画关键帧 =================
|
|
* 性能约定:常驻(infinite)动画只动 transform/opacity(合成器直通,
|
|
* 不走主线程重绘);box-shadow/background-position 关键帧仅限短暂、
|
|
* 临时态(如 dragover / OTA 阶段活跃)。 */
|
|
@keyframes fadeInUp {
|
|
from { opacity: 0; transform: translateY(10px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
@keyframes popIn {
|
|
0% { opacity: 0; transform: scale(.85); }
|
|
60% { opacity: 1; transform: scale(1.04); }
|
|
100% { opacity: 1; transform: scale(1); }
|
|
}
|
|
/* 光泽扫过:由 background-position 改为伪元素自身平移(合成器友好),
|
|
* 扫动节奏与原版一致:扫过 + 停预周期。 */
|
|
@keyframes shimmer {
|
|
0% { transform: translateX(-100%); }
|
|
100% { transform: translateX(100%); }
|
|
}
|
|
@keyframes ringPulse {
|
|
0% { box-shadow: 0 0 0 0 rgba(47,216,193,.5); }
|
|
70% { box-shadow: 0 0 0 10px rgba(47,216,193,0); }
|
|
100% { box-shadow: 0 0 0 0 rgba(47,216,193,0); }
|
|
}
|
|
/* 常驻波纹/呼吸辉光的合成器友好版:叠加层只动 transform/opacity。 */
|
|
@keyframes ringRipple {
|
|
0% { transform: scale(1); opacity: .5; }
|
|
70%,100% { transform: scale(3); opacity: 0; }
|
|
}
|
|
@keyframes glowBreathFade {
|
|
0%,100% { opacity: 0; }
|
|
50% { opacity: 1; }
|
|
}
|
|
@keyframes shake {
|
|
0%,100% { transform: translateX(0); }
|
|
20% { transform: translateX(-4px); } 40% { transform: translateX(4px); }
|
|
60% { transform: translateX(-3px); } 80% { transform: translateX(3px); }
|
|
}
|
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
@keyframes floatY { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-4px); } }
|
|
@keyframes flowDash { to { background-position: 200% 0; } }
|
|
@keyframes logoPulse {
|
|
0%,100% { opacity: 0; }
|
|
50% { opacity: 1; }
|
|
}
|
|
|
|
/* ================= 无设备静默态:掐掉常驻动画 =================
|
|
* 常驻动画按显示器刷新率逐帧跑:本机 160Hz 实测「没接设备」时
|
|
* 主线程 140ms/s 全在重算+重绘(160 次/s recalc),renderer+GPU 各占约
|
|
* 一核的七成;全关后回到 ~0。设备离线且无传输在跑时这些动效没有任何
|
|
* 信息量,故由连接态统一挂 html[data-dcu-calm="1"] 关掉。
|
|
* 只掐 infinite 装饰动效;一次性入场/吐司动画与故障灯保留。 */
|
|
html[data-dcu-calm="1"] .progress-fill,
|
|
html[data-dcu-calm="1"] .progress-fill::after,
|
|
html[data-dcu-calm="1"] .heap-bar-track::after,
|
|
html[data-dcu-calm="1"] .status-task-bar::after,
|
|
html[data-dcu-calm="1"] .stage-status-bar .ssb-dot,
|
|
html[data-dcu-calm="1"] .pill.live .indicator,
|
|
html[data-dcu-calm="1"] .conn-indicator.warn .light,
|
|
html[data-dcu-calm="1"] .brand-logo::before,
|
|
html[data-dcu-calm="1"] .header-brand .dot-mark::before,
|
|
html[data-dcu-calm="1"] .header-brand .dot-mark::after,
|
|
html[data-dcu-calm="1"] .led-pulse {
|
|
animation: none !important;
|
|
}
|
|
|
|
/* ================= 入场:头部 + 卡片错落浮现 ================= */
|
|
.header-bar { animation: fadeInUp .5s ease both; }
|
|
.main-layout > div > .card { animation: fadeInUp .55s ease both; }
|
|
.main-layout > div:nth-child(1) > .card:nth-child(1) { animation-delay: .06s; }
|
|
.main-layout > div:nth-child(1) > .card:nth-child(2) { animation-delay: .13s; }
|
|
.main-layout > div:nth-child(1) > .card:nth-child(3) { animation-delay: .20s; }
|
|
.main-layout > div:nth-child(1) > .card:nth-child(4) { animation-delay: .27s; }
|
|
.main-layout > div:nth-child(2) > .card:nth-child(1) { animation-delay: .09s; }
|
|
.main-layout > div:nth-child(2) > .card:nth-child(2) { animation-delay: .16s; }
|
|
.main-layout > div:nth-child(2) > .card:nth-child(3) { animation-delay: .23s; }
|
|
.main-layout > div:nth-child(2) > .card:nth-child(4) { animation-delay: .30s; }
|
|
.main-layout > div:nth-child(2) > .card:nth-child(5) { animation-delay: .37s; }
|
|
|
|
/* ================= 卡片悬停:抬升 + 子系统色描边 ================= */
|
|
.card { transition: transform .25s ease, border-color .25s ease, box-shadow .25s ease; }
|
|
.card:hover {
|
|
transform: translateY(-2px);
|
|
border-color: color-mix(in srgb, var(--card-accent, var(--esp32)) 32%, var(--border));
|
|
box-shadow: 0 14px 34px rgba(2,5,12,.5),
|
|
0 0 0 1px color-mix(in srgb, var(--card-accent, var(--esp32)) 12%, transparent);
|
|
}
|
|
|
|
/* ================= 品牌 Logo:呼吸辉光 =================
|
|
* box-shadow 关键帧每帧走主线程重绘;改为叠加静态辉光的伪元素
|
|
* 只动 opacity(合成器直通),基础辉光由 layout.css 静态 box-shadow 提供,
|
|
* 呼吸节奏与原 logoPulse/ringPulse/glowBreath 一致。 */
|
|
.brand-logo { position: relative; }
|
|
.brand-logo::before {
|
|
content: "";
|
|
position: absolute; inset: -1px; border-radius: inherit;
|
|
box-shadow: 0 0 26px rgba(47,216,193,.4), inset 0 0 16px rgba(47,216,193,.14);
|
|
opacity: 0; pointer-events: none;
|
|
animation: logoPulse 3.2s ease-in-out infinite;
|
|
will-change: opacity;
|
|
}
|
|
.header-brand .dot-mark { position: relative; }
|
|
.header-brand .dot-mark::after {
|
|
content: "";
|
|
position: absolute; inset: 0; border-radius: 50%;
|
|
background: rgba(47,216,193,.5); pointer-events: none;
|
|
animation: ringRipple 2.4s ease-out infinite;
|
|
will-change: transform, opacity;
|
|
}
|
|
.header-brand .dot-mark::before {
|
|
content: "";
|
|
position: absolute; inset: 0; border-radius: 50%;
|
|
box-shadow: 0 0 20px rgba(47,216,193,.45);
|
|
opacity: 0; pointer-events: none;
|
|
animation: glowBreathFade 3.6s ease-in-out infinite;
|
|
will-change: opacity;
|
|
}
|
|
|
|
/* ================= 胶囊 / 按钮微交互 ================= */
|
|
.pill:hover { transform: translateY(-1px); border-color: var(--border-strong); }
|
|
|
|
.btn { position: relative; overflow: hidden; }
|
|
.btn-primary::before, .btn-success::before, .btn-danger::before {
|
|
content: "";
|
|
position: absolute; top: 0; left: -120%;
|
|
width: 60%; height: 100%;
|
|
background: linear-gradient(90deg, transparent, rgba(255,255,255,.4), transparent);
|
|
transform: skewX(-20deg);
|
|
transition: left .55s ease;
|
|
pointer-events: none;
|
|
}
|
|
.btn-primary:hover:not(:disabled)::before,
|
|
.btn-success:hover:not(:disabled)::before,
|
|
.btn-danger:hover:not(:disabled)::before { left: 130%; }
|
|
|
|
/* ================= 上传区:拖拽脉冲 + 图标浮动 ================= */
|
|
.upload-zone:hover .upload-ico { animation: floatY 1.6s ease-in-out infinite; }
|
|
.upload-zone.dragover { animation: ringPulse 1.2s ease-out infinite; }
|
|
|
|
/* ================= 文件信息浮现 ================= */
|
|
.file-info.show { animation: fadeInUp .3s ease both; }
|
|
|
|
/* ================= 进度条:流动斜纹 =================
|
|
* 斜纹只在真的有进度时挂(.live 由 setProgress/setEcatPct 开关):
|
|
* 空闲时进度条 width:0%,看不见却在按刷新率重绘,纯白烧。 */
|
|
.progress-fill {
|
|
background-image:
|
|
linear-gradient(90deg, var(--esp32), #14b8a6),
|
|
repeating-linear-gradient(45deg, rgba(255,255,255,.2) 0 10px, rgba(255,255,255,0) 10px 20px);
|
|
background-size: 100% 100%, 200% 100%;
|
|
background-blend-mode: normal, overlay;
|
|
}
|
|
.progress-fill.live { animation: flowDash 1.4s linear infinite; }
|
|
.progress-fill.done { animation: none; }
|
|
|
|
/* ================= 阶段胶囊:激活弹入 ================= */
|
|
.pp-step.active, .pp-step.done { animation: popIn .35s cubic-bezier(.4,1.4,.5,1) both; }
|
|
|
|
/* ================= 阶段流:连接线生长 + 圆点动效 ================= */
|
|
.stage-node:not(:last-child)::after {
|
|
background: linear-gradient(90deg, var(--esp32) 0%, var(--esp32) 50%, var(--border) 50%, var(--border) 100%);
|
|
background-size: 200% 100%; background-position: 100% 0;
|
|
transition: background-position .5s ease;
|
|
}
|
|
.stage-node.done:not(:last-child)::after,
|
|
.stage-node.live:not(:last-child)::after { background-position: 0% 0; }
|
|
.stage-dot { transition: transform .3s ease, background .3s, border-color .3s, color .3s, box-shadow .3s; }
|
|
.stage-node.live .stage-dot { animation: ringPulse 1.6s ease-out infinite; }
|
|
.stage-node.done .stage-dot { animation: popIn .35s cubic-bezier(.4,1.4,.5,1) both; }
|
|
.stage-node.err .stage-dot { animation: shake .4s ease both; }
|
|
|
|
/* ================= 状态条切换浮现 ================= */
|
|
.stage-status-bar.active, .stage-status-bar.ok, .stage-status-bar.err { animation: fadeInUp .3s ease both; }
|
|
.stage-result { animation: popIn .4s cubic-bezier(.4,1.4,.5,1) both; }
|
|
|
|
/* ================= 指标数值过渡 ================= */
|
|
.metric .v, .badge-stat .v { transition: color .3s ease, transform .3s ease; }
|
|
|
|
/* ================= 连接指示 ================= */
|
|
.conn-indicator { transition: background .25s ease, border-color .25s ease; }
|
|
.conn-indicator.warn .light { animation: ringPulse 1.4s ease-out infinite; }
|
|
/* 在线呼吸辉光:常驻动画改伪元素 opacity(合成器直通),节奏同原 glowBreath */
|
|
.conn-indicator.ok .light { position: relative; }
|
|
.conn-indicator.ok .light::after {
|
|
content: "";
|
|
position: absolute; inset: 0; border-radius: 50%;
|
|
box-shadow: 0 0 20px rgba(47,216,193,.45);
|
|
opacity: 0; pointer-events: none;
|
|
animation: glowBreathFade 3s ease-in-out infinite;
|
|
will-change: opacity;
|
|
}
|
|
|
|
/* ================= 堆 / 任务条:光泽扫过 =================
|
|
* shimmer 改动 transform(见上方关键帧注释):十几根条常驻扫光
|
|
* 不再每帧触发主线程重绘,视觉效果不变。 */
|
|
.heap-bar-track, .status-task-bar { position: relative; overflow: hidden; }
|
|
.heap-bar-track::after, .status-task-bar::after {
|
|
content: "";
|
|
position: absolute; inset: 0;
|
|
background: linear-gradient(90deg, transparent 0%, rgba(255,255,255,.12) 50%, transparent 100%);
|
|
transform: translateX(-100%);
|
|
animation: shimmer 2.8s linear infinite;
|
|
will-change: transform;
|
|
pointer-events: none;
|
|
}
|
|
.heap-bar-fill, .status-task-bar-fill { transition: width .6s cubic-bezier(.4,0,.2,1); }
|
|
|
|
/* ================= 日志新行淡入 ================= */
|
|
.log-line { animation: fadeIn .25s ease both; }
|
|
|
|
/* ================= Toast 弹出 ================= */
|
|
.toast.show { animation: popIn .35s cubic-bezier(.4,1.4,.5,1) both; }
|
|
|
|
/* ================= 降低动态偏好 ================= */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
*, *::before, *::after {
|
|
animation-duration: .001ms !important;
|
|
animation-iteration-count: 1 !important;
|
|
transition-duration: .001ms !important;
|
|
}
|
|
}
|