/* ============================================================ 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; } }