Files
xiaobai-review/xiaobai-datahub/admin/styles.css
T
475ed4a872 feat(HEL-540): 数据中枢后台升级为「第八版·星港中枢」全动效单页
- 用 Canvas 2D 自写透视投影重写 Stage 引擎:中央自转星体 + 4 颗来源卫星
  (tushare/东方财富/腾讯/iFinD)沿 2 层完整椭圆轨道环绕,4 条入向线 +
  2 条去向线(发布/审计),卫星名称与 LINK/ACT 双灯始终最上层、常显不隐
- 落地 HEL-538 安全盒硬公式:x0=cabinW+24,x1=W-24,y0=64,y1=H-118,
  世界坐标统一钳制在 ±1.28(横)×±0.6656(纵),去向端半径 1.25
- 全屏无卡片舞台:#stageWrap 去边框/圆角/阴影,改为通栏
- LINK=稳态常亮,ACT=事件驱动短闪(tushare recent_calls / 健康探测 /
  批次与发布状态变化),全局令牌桶 ≤3 簇/秒,四源独立队列
- 页面隐藏暂停 rAF + 清空未播闪簇、恢复不补播;prefers-reduced-motion
  运行时 change 监听(非只读一次);静态版同步换成星体+双轨道示意图
- 识别色只复用既有设计 Token --chan1..4,未新增配色系统
- 修正审计尾迹角度/半径范围使其构造性落在安全盒硬包络内
- 修正 drawAuditTrail 误将完整 hex 色值当 rgba 前缀拼接导致
  addColorStop 抛错的运行时 bug(day 主题下必现)

自测:node --check 通过;三档(1440x900/1280x800/1024x768)×日夜安全盒
数学自测脚本全通过(S 命中 427/380/291);Playwright 真浏览器起本地
测试服务端验证:六幕滚动、详情开合、隐藏/恢复暂停与续播、
prefers-reduced-motion 运行时切换、真实 probe 事件驱动 ACT、
60 秒连续运行无 DOM 增长/无 console 报错;xiaobai-datahub/tests/
133 个后端测试全绿;git diff --check 无空白问题。

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-13 11:56:54 +08:00

288 lines
15 KiB
CSS

/* xiaobai-datahub 数据中枢后台 —— 第八版「星港中枢」
整页星空舞台 + 中央自转星体 + 4 颗来源卫星,六幕连续滚动换场(详见 app.js Stage 引擎)。
统一动效 Token:进入/退出用强 ease-out,屏内移动用强 ease-in-out,持续流动用 linear。
禁止 ease-in、禁止 transition:all —— 全文件遵守。 */
:root {
color-scheme: light;
--bar-h: 52px;
--radius: 14px;
--pad: 16px;
--font-cn: "Segoe UI", "PingFang SC", "Noto Sans SC", "Microsoft YaHei", sans-serif;
--font-mono: "JetBrains Mono", "DejaVu Sans Mono", Consolas, monospace;
--ease-out: cubic-bezier(.22, 1, .36, 1);
--ease-in-out: cubic-bezier(.65, 0, .35, 1);
--ease-linear: linear;
/* day (default) */
--bg0: #EDF0F7;
--bg1: #F8FAFF;
--surface: #ffffff;
--stage-edge: rgba(47, 99, 214, .16);
--text: #1B2547;
--muted: #5B6785;
--faint: #93A0BD;
--line: rgba(47, 99, 214, .18);
--action: #2F63D6;
--cyan: #0A9C93;
--amber: #B97A0E;
--danger: #D64F4F;
--ok: #1E9E6A;
--warn: #B97A0E;
--glass: rgba(255, 255, 255, .82);
--chip: rgba(47, 99, 214, .08);
--face: rgba(120, 160, 235,);
--edge: rgba(47, 99, 214,);
--slab: rgba(10, 156, 147,);
--ring: rgba(47, 99, 214,);
--chan1: rgba(47, 99, 214,);
--chan2: rgba(10, 124, 146,);
--chan3: rgba(109, 93, 214,);
--chan4: rgba(190, 120, 10,);
}
:root[data-theme="night"] {
color-scheme: dark;
--bg0: #05080F;
--bg1: #0A1020;
--surface: #12172A;
--stage-edge: rgba(90, 140, 255, .14);
--text: #E8EEFC;
--muted: #8B97B8;
--faint: #59637F;
--line: rgba(120, 160, 255, .16);
--action: #4A86FF;
--cyan: #2FD8CE;
--amber: #FFB84D;
--danger: #FF6B6B;
--ok: #3ED598;
--warn: #FFB84D;
--glass: rgba(13, 20, 38, .72);
--chip: rgba(74, 134, 255, .10);
--face: rgba(58, 96, 180,);
--edge: rgba(140, 190, 255,);
--slab: rgba(47, 216, 206,);
--ring: rgba(120, 170, 255,);
--chan1: rgba(150, 205, 255,);
--chan2: rgba(120, 180, 255,);
--chan3: rgba(185, 175, 255,);
--chan4: rgba(255, 214, 160,);
}
* { box-sizing: border-box; }
html, body { margin: 0; background: var(--bg0); color: var(--text); font-family: var(--font-cn); }
body { overflow-x: hidden; }
.mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.muted { color: var(--muted); }
.error { color: var(--danger); }
.ok { color: var(--ok); }
.warn { color: var(--warn); }
.fail { color: var(--danger); }
/* ---------- 登录 / 改密(未进入舞台的前置流程) ---------- */
.auth-panel {
max-width: 420px; margin: 12vh auto; padding: 30px 32px; border-radius: var(--radius);
background: var(--glass); border: 1px solid var(--line); backdrop-filter: blur(14px);
box-shadow: 0 30px 70px -32px rgba(20, 30, 60, .35);
}
.auth-panel h1 { margin: 0 0 6px; font-size: 21px; }
.auth-panel label { display: block; margin: 12px 0; font-size: 13px; color: var(--muted); }
.auth-panel input {
width: 100%; margin-top: 6px; padding: 9px 11px; border: 1px solid var(--line); border-radius: 8px;
background: var(--chip); color: var(--text); font-family: var(--font-cn);
}
.auth-panel input:focus { outline: 2px solid var(--action); outline-offset: 1px; }
.auth-panel .badge-sim { display: inline-block; margin-bottom: 10px; }
button { font-family: var(--font-cn); cursor: pointer; }
.btn, button.primary, .auth-panel button[type="submit"] {
background: linear-gradient(135deg, var(--action), var(--cyan)); color: #fff; border: 0; border-radius: 8px;
padding: 8px 16px; font-size: 13px; transition: transform .12s var(--ease-out), opacity .12s var(--ease-out);
}
.btn:active, button:active { transform: translateY(1px); }
.btn.ghost, button.ghost {
background: var(--chip); color: var(--text); border: 1px solid var(--line);
}
.btn.warn { background: transparent; color: var(--warn); border: 1px solid var(--warn); }
.btn.danger, button.danger { background: var(--danger); color: #fff; border: 0; }
.btn.pri { background: linear-gradient(135deg, var(--action), var(--cyan)); color: #fff; border: 0; }
.btn { padding: 6px 13px; border-radius: 8px; font-size: 12px; }
.badge-sim {
font-size: 11px; padding: 3px 10px; border-radius: 99px; letter-spacing: .1em;
color: var(--amber); border: 1px solid var(--amber); opacity: .85;
}
/* ---------- 通用表格 / 卡片(cabin、detail、reduced 三处共用同一来源) ---------- */
table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
th, td { text-align: left; padding: 7px 6px; border-bottom: 1px dashed var(--line); vertical-align: top; }
th { color: var(--faint); font-weight: 500; font-size: 11px; letter-spacing: .04em; }
.pill { font-size: 12px; padding: 2px 10px; border-radius: 999px; border: 1px solid var(--line); color: var(--muted); }
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 10px; margin-bottom: 14px; }
.card { border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px; background: var(--chip); }
.card strong { font-size: 18px; }
.toolbar { display: flex; gap: 8px; flex-wrap: wrap; margin: 10px 0; align-items: center; }
.toolbar label { font-size: 12px; color: var(--muted); display: flex; align-items: center; gap: 6px; }
.toolbar input { padding: 6px 9px; border: 1px solid var(--line); border-radius: 6px; background: var(--chip); color: var(--text); }
h2 { font-size: 16px; margin: 4px 0 10px; }
h3 { font-size: 13px; margin: 14px 0 6px; color: var(--muted); }
/* ---------- 顶部总控条 ---------- */
#topbar {
height: var(--bar-h); display: flex; align-items: center; gap: 14px; padding: 0 22px;
border-bottom: 1px solid var(--line); background: linear-gradient(180deg, var(--bg1), transparent);
backdrop-filter: blur(10px);
position: sticky; top: 0; z-index: 4;
}
#topbar .logo { font-weight: 700; font-size: 15px; letter-spacing: .04em; }
#topbar .logo em { font-style: normal; color: var(--cyan); }
#topbar .crumb { font-size: 12px; color: var(--faint); }
#topbar .spacer { flex: 1; }
#topbar .who { font-size: 12px; }
/* ---------- 全幅星空舞台(第八版「星港中枢」,calc(100vh - 52px) 高,六幕连续场景) ----------
#track 的六段高幕(每段 130vh)在它身后正常参与文档流把页面撑高,
#stageWrap 用 sticky 钉在视口里,滚动时只有 #track 的高度被“划过”,
舞台本体在这段距离内始终可见、位置不变,直到 #track 撑出的空间耗尽才随之离场。
整个舞台是同构的日/夜全幅星空:无卡片边框、无圆角大框、无 overflow 裁切——
星系(轨道/卫星/线路/去向端)改由 app.js 的响应式安全盒公式钳制,构造性不出屏。 */
#stageWrap {
flex: none; height: calc(100vh - var(--bar-h)); min-height: 480px; margin: 0; border-radius: 0;
border: 0; box-shadow: none;
position: sticky; top: var(--bar-h); z-index: 2;
}
#scene { position: absolute; inset: 0; width: 100%; height: 100%; display: block; cursor: crosshair; }
/* ---------- 控制舱:当前场景的关键数据与操作 ---------- */
#cabin {
position: absolute; left: 22px; top: 22px; width: 300px; z-index: 5; max-height: calc(100% - 44px);
overflow: auto; background: var(--glass); border: 1px solid var(--line); border-radius: 14px;
backdrop-filter: blur(14px); padding: 16px 18px;
transition: opacity .45s var(--ease-out), transform .45s var(--ease-out);
}
#cabin.hide { opacity: 0; transform: translateX(-14px); pointer-events: none; }
#cabin .eyebrow { font-size: 10px; letter-spacing: .22em; color: var(--cyan); margin-bottom: 6px; }
#cabin h2 { font-size: 18px; margin: 0 0 4px; }
#cabin .sub { font-size: 11.5px; color: var(--muted); line-height: 1.6; margin-bottom: 10px; }
#cabin .rows { display: flex; flex-direction: column; gap: 7px; margin-bottom: 4px; }
#cabin .row { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--muted); }
#cabin .row b { color: var(--text); font-weight: 600; }
#cabin .row .fill { flex: 1; }
#cabin .dotlamp { width: 7px; height: 7px; border-radius: 99px; background: var(--ok); box-shadow: 0 0 8px var(--ok); flex: none; }
#cabin .dotlamp.warn { background: var(--warn); box-shadow: 0 0 8px var(--warn); }
#cabin .dotlamp.fail { background: var(--danger); box-shadow: 0 0 8px var(--danger); }
#cabin .actions { display: flex; gap: 8px; margin-top: 12px; flex-wrap: wrap; }
/* ---------- 底部章节轨(导航等同滚动跳转) ---------- */
#rail {
position: absolute; left: 50%; bottom: 16px; transform: translateX(-50%); z-index: 5;
display: flex; gap: 6px; align-items: center; padding: 8px 12px; border-radius: 99px;
background: var(--glass); border: 1px solid var(--line); backdrop-filter: blur(14px);
}
#rail .stop {
display: flex; align-items: center; gap: 7px; padding: 5px 11px; border-radius: 99px; cursor: pointer;
font-size: 11.5px; color: var(--faint); background: transparent; border: 0;
transition: color .25s var(--ease-in-out), background .25s var(--ease-in-out);
font-family: var(--font-cn); white-space: nowrap;
}
#rail .stop i { width: 6px; height: 6px; border-radius: 99px; background: var(--faint); transition: background .25s var(--ease-in-out), box-shadow .25s var(--ease-in-out); display: inline-block; }
#rail .stop.on { color: var(--text); background: var(--chip); }
#rail .stop.on i { background: var(--cyan); box-shadow: 0 0 10px var(--cyan); }
#rail .sep { width: 12px; height: 1px; background: var(--line); }
/* ---------- 图例 ---------- */
#legend {
position: absolute; right: 22px; top: 22px; z-index: 5; font-size: 10.5px; color: var(--muted);
background: var(--glass); border: 1px solid var(--line); border-radius: 12px; padding: 10px 13px;
backdrop-filter: blur(14px); line-height: 1.9;
}
#legend .lampico { display: inline-block; width: 16px; height: 6px; border-radius: 99px; vertical-align: middle; margin-right: 6px; }
#legend .li-link { background: rgba(47, 216, 206, .5); box-shadow: 0 0 6px rgba(47, 216, 206, .6); }
#legend .li-act { background: var(--amber); box-shadow: 0 0 8px var(--amber); }
/* ---------- 详情面板:从端口/来源空间位置展开的功能抽屉 ---------- */
#detail {
position: absolute; z-index: 8; width: 360px; max-width: calc(100% - 40px); max-height: calc(100% - 40px);
overflow: auto; pointer-events: auto; background: var(--glass); border: 1px solid var(--line);
border-radius: 14px; backdrop-filter: blur(16px); padding: 16px 18px;
transition: transform .4s var(--ease-out), opacity .3s var(--ease-out);
box-shadow: 0 24px 60px -20px rgba(0, 0, 0, .45);
}
#detail.closed { transform: scale(.14); opacity: 0; pointer-events: none; }
#detail h3 { font-size: 14px; margin: 0 0 2px; color: var(--text); }
#detail .dsub { font-size: 10.5px; color: var(--faint); margin-bottom: 10px; letter-spacing: .04em; }
#detailClose {
position: absolute; right: 10px; top: 10px; width: 24px; height: 24px; border-radius: 8px;
border: 1px solid var(--line); background: transparent; color: var(--muted); cursor: pointer; font-size: 13px; line-height: 1;
}
#detail .dtag {
position: absolute; left: -7px; top: 26px; width: 14px; height: 14px; transform: rotate(45deg);
background: var(--glass); border-left: 1px solid var(--line); border-bottom: 1px solid var(--line);
}
/* ---------- 滚动提示 ---------- */
#hint {
position: absolute; left: 50%; bottom: 100px; transform: translateX(-50%); z-index: 5;
font-size: 11px; letter-spacing: .3em; color: var(--faint); transition: opacity .5s var(--ease-out);
}
#hint.off { opacity: 0; }
/* 滚动章节占位:仅提供滚动行程,无可见内容 */
#track { position: relative; z-index: 1; pointer-events: none; }
#track section { height: 130vh; }
/* ---------- 危险操作 / 确认弹层,复用 dialog ---------- */
dialog { border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); color: var(--text); padding: 20px; }
/* ================================================================
减少动态效果(prefers-reduced-motion):舞台切换为六幕静态空间图 + 固定文字状态
信息与功能与动效版完全一致,只是去掉滚动镜头、旋转与闪烁。
================================================================ */
#staticShell { display: none; }
html.reduced #stageWrap, html.reduced #track, html.reduced #hint { display: none !important; }
html.reduced #staticShell { display: block; }
#staticNav {
display: flex; gap: 4px; padding: 8px var(--pad); border-bottom: 1px solid var(--line);
background: var(--surface); flex-wrap: wrap; position: sticky; top: 0; z-index: 3;
}
#staticNav button {
background: transparent; color: var(--muted); border: 0; padding: 7px 12px; border-radius: 8px; font-size: 13px;
}
#staticNav button.active { color: var(--action); font-weight: 600; background: var(--chip); }
.scene-static { padding: var(--pad); border-bottom: 1px solid var(--line); }
.scene-static .scene-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 10px; }
.scene-static .scene-head .eyebrow { font-size: 11px; letter-spacing: .18em; color: var(--cyan); }
.scene-static .diagram {
width: 100%; max-width: 620px; height: 190px; border-radius: 12px; border: 1px solid var(--line);
background: var(--chip); display: block; margin-bottom: 12px;
}
.scene-static .lamprow { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 10px; }
.lampchip {
display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--muted);
border: 1px solid var(--line); border-radius: 999px; padding: 4px 10px; background: var(--chip);
}
.lampchip .lk, .lampchip .ac { width: 7px; height: 7px; border-radius: 99px; background: var(--faint); display: inline-block; }
.lampchip .lk.on { background: var(--cyan); box-shadow: 0 0 5px var(--cyan); }
.lampchip .ac.on { background: var(--amber); box-shadow: 0 0 6px var(--amber); }
.lampchip .ac.recent { outline: 1px solid var(--amber); transition: outline-color .8s var(--ease-out); }
/* ---------- 主内容区(main/reduced 内容容器统一样式,供动效版 detail 与静态版共用) ---------- */
main#page { padding: var(--pad); min-height: calc(100vh - 96px); background: var(--surface); }
/* ---------- 响应式:1440 / 1280 / 1024 常见桌面宽 ---------- */
@media (max-width: 1280px) {
#cabin { width: 260px; }
#detail { width: 320px; }
}
@media (max-width: 1100px) {
#cabin { width: 230px; padding: 13px 14px; }
#legend { display: none; }
}
/* ---------- 系统级兜底:即便 JS 未及时接管,也不留半成品动效 ---------- */
@media (prefers-reduced-motion: reduce) {
#stageWrap, #track, #hint { display: none !important; }
#staticShell { display: block !important; }
* { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important; }
}