:root{
  --bg:#04070f;
  --panel-1:rgba(16,34,64,.82);
  --panel-2:rgba(7,16,32,.88);
  --line:rgba(72,148,255,.22);
  --line-strong:rgba(86,178,255,.45);
  --cyan:#33d6ff;
  --blue:#2f7bff;
  --amber:#ffb020;
  --red:#ff4d5e;
  --green:#2fe0a8;
  --txt:#e6f0ff;
  --dim:#7f97ba;
  --num:'Bahnschrift','DIN Alternate','Segoe UI',sans-serif;
}

*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%;background:var(--bg);overflow:hidden}
body{font-family:'Microsoft YaHei','PingFang SC','Segoe UI',sans-serif;color:var(--txt);-webkit-font-smoothing:antialiased}

/* v3.43 底图边缘不再掉成近黑：原来是 circle 径向，62% 就落到 #04070f，16:9 屏上
   圆形先横向到底，左右两侧比上下黑得更早。这里换成横向铺满的椭圆、底色抬一档。
   想回旧观感加 ?bg=orig（旧值见文件末尾）。 */
#viewport{position:fixed;inset:0;display:grid;place-items:center;
  background:radial-gradient(ellipse 78% 62% at 50% 45%,#0a1c38 0%,#081528 70%,#071026 100%)}
#stage{position:absolute;left:50%;top:50%;width:1920px;height:1080px;transform-origin:center center;
  transform:translate(-50%,-50%) scale(1);
  background:
    radial-gradient(ellipse at 50% 38%,rgba(24,72,140,.38),transparent 58%),
    linear-gradient(180deg,#050c1b 0%,#04070f 100%);
  overflow:hidden}
/* 网格（降透明度）+ 暗角 vignette */
#stage::after{content:'';position:absolute;inset:0;pointer-events:none;z-index:5;
  background-image:
    linear-gradient(rgba(60,130,220,.025) 1px,transparent 1px),
    linear-gradient(90deg,rgba(60,130,220,.025) 1px,transparent 1px),
    radial-gradient(ellipse at 50% 50%,transparent 40%,rgba(2,5,12,.55) 100%);
  background-size:48px 48px,48px 48px,100% 100%;
  mask-image:radial-gradient(ellipse at 50% 45%,#000 30%,transparent 82%)}
/* 极淡扫描线 */
#stage::before{content:'';position:absolute;inset:0;pointer-events:none;z-index:6;
  background:repeating-linear-gradient(0deg,rgba(120,180,255,.022) 0px,rgba(120,180,255,.022) 1px,transparent 1px,transparent 3px)}

/* ---------- header ---------- */
#hd{position:absolute;left:0;top:0;width:1920px;height:104px;z-index:70;display:flex;align-items:center;
  justify-content:space-between;padding:0 28px;gap:20px;overflow:hidden}
/* 顶栏底色 + 斜向扫光全部在 .hd-deco(SVG) 里，这里不再画额外装饰线 */
#hd .hd-left{position:relative;z-index:3}
.hd-left{display:flex;align-items:center;gap:10px;min-width:0}
.hd-logo{height:60px;width:auto;display:block;margin-left:20px}
/* 标题居中：参考图的标题就坐在中央隆起的平台上，左右装饰以它为轴镜像 */
.hd-title{position:absolute;left:0;right:0;top:0;height:104px;display:flex;align-items:center;justify-content:center;
  z-index:2;
  font-size:40px;font-weight:700;letter-spacing:5px;line-height:1;white-space:nowrap;
  padding-bottom:34px;pointer-events:none;
  background:linear-gradient(180deg,#ffffff 30%,#eaf8ff 72%,#a6d9f7 100%);
  -webkit-background-clip:text;background-clip:text;color:transparent;
  filter:drop-shadow(0 3px 8px rgba(3,18,40,.95)) drop-shadow(0 0 22px rgba(120,205,255,.45)) drop-shadow(0 0 46px rgba(60,150,235,.22))}
/* 标题牌与两侧刻度的几何全在 index.html 的 .hd-deco(SVG) 里，此处只管定位与层级 */
.hd-deco{position:absolute;left:0;top:0;width:1920px;height:104px;pointer-events:none;z-index:1}
.hd-right{position:relative;z-index:3;display:flex;align-items:center;gap:12px;min-width:0;justify-content:flex-end}
/* 全屏按钮：钉在顶栏右上角（顶栏 padding 28，所以右边距由 #hd 的 padding 给出） */
.fs-btn{width:38px;height:38px;border-radius:10px;padding:0;color:#b9d4f5;
  background:rgba(10,24,46,.72);border:1px solid rgba(72,148,255,.42);
  box-shadow:0 10px 24px rgba(0,0,0,.42),inset 0 0 18px rgba(47,123,255,.14);
  backdrop-filter:blur(6px)}
.fs-btn:hover{color:#e2f6ff;border-color:rgba(51,214,255,.82);background:rgba(18,44,80,.85);
  box-shadow:0 12px 28px rgba(0,0,0,.5),inset 0 0 18px rgba(51,214,255,.26),0 0 16px rgba(51,214,255,.26)}

/* 全屏地图底下是纯黑，标题栏的斜光带会显得像块悬空的亮片，压一档让它退回背景 */
.map-full-active #hd .hd-deco{opacity:.55;transition:opacity .3s}
#hd .hd-deco{transition:opacity .3s}

/* ---------- 悬浮工具条（可拖动） ---------- */
/* 定位用 transform 而非 left/top：拖动时只走合成器，不触发重排；
   坐标是舞台坐标系（1920×1080），缩放后按当前 scale 折算鼠标位移 */
.dock{position:absolute;left:0;top:0;z-index:85;display:flex;flex-direction:column;align-items:stretch;gap:8px;
  padding:8px;border-radius:12px;
  background:rgba(10,24,46,.82);
  border:1px solid rgba(72,148,255,.42);
  box-shadow:0 14px 34px rgba(0,0,0,.5),inset 0 0 22px rgba(47,123,255,.12);
  cursor:grab;touch-action:none;user-select:none;
  transition:border-color .2s,box-shadow .2s}
.dock:hover{border-color:rgba(72,148,255,.7)}
.dock.is-drag{cursor:grabbing;border-color:rgba(51,214,255,.85);
  box-shadow:0 18px 44px rgba(0,0,0,.6),inset 0 0 22px rgba(51,214,255,.2)}
/* 顶部拖动提示：横排点阵，暗示这一竖条可以按住拖 */
.dock-grip{width:28px;height:7px;flex:none;align-self:center;margin:0 0 2px;opacity:.5;
  background-image:radial-gradient(rgba(160,205,255,.95) 1px,transparent 1.4px);
  background-size:6px 5px;background-position:0 0;background-repeat:repeat}
.dock .btn{height:38px;font-size:15px;padding:0 16px;letter-spacing:1px;width:100%}
.dock .btn-dock{white-space:nowrap;min-width:104px}
/* 巡航按钮文字 7 个字，按 15px 会把整条工具条撑宽 30 多像素、压到地图上；压到 13px 无字距 */
.dock .btn-cruise{font-size:13px;letter-spacing:0;padding:0 10px;font-variant-numeric:tabular-nums}
/* 上线开关：DASH_HIDE_IMPORT = 1 时 app.js 给「导入数据」加 hidden（说明见 index.html 顶部）。
   显式写 display:none，不靠 UA 的 [hidden] 规则——.dock .btn 有 height/padding，
   哪天补上 display 就会盖掉 UA 样式，按钮又冒出来。 */
.dock .btn[hidden]{display:none!important}

.btn{border:1px solid var(--line);background:rgba(20,44,84,.7);color:var(--txt);cursor:pointer;
  font-family:inherit;font-size:15px;border-radius:6px;padding:0 20px;height:40px;
  transition:.2s;backdrop-filter:blur(6px)}
.btn:hover{border-color:var(--line-strong);background:rgba(36,78,148,.8)}
.btn:active{transform:translateY(1px)}
.btn-primary{border-color:rgba(51,214,255,.55);color:#d8f4ff;box-shadow:0 0 18px rgba(51,214,255,.18) inset}
.btn-ghost{color:var(--dim)}
.btn-icon{display:inline-flex;align-items:center;justify-content:center;width:40px;height:40px;padding:0;border-radius:8px}
.btn-icon svg{display:block}
.btn-icon:hover{box-shadow:0 0 14px rgba(51,214,255,.35)}
/* 浏览器全屏时隐藏整条悬浮工具条（全屏按钮已经钉在顶栏右上角，不受影响）。
   v3.21 起自动巡航按钮也在 dock 内，跟着整条一起隐藏，不用再单列一条规则 */
.is-screen-full #dock{display:none}

/* ---------- panels ---------- */
.col{position:absolute;inset:0;z-index:10;pointer-events:none;transition:opacity .45s ease,filter .45s ease}
/* 列容器铺满舞台但不拦截事件，只有面板等实际内容接收鼠标 */
.col .panel{pointer-events:auto}
.panel{position:absolute;border-radius:0;
  /* 四条边框用多层渐变绘制：左右竖边垂直方向中间亮上下暗，上下横边水平方向中间亮左右暗；
     clip-path 会裁掉 border，故不用 border/box-shadow 画框 */
  background:
    /* 左竖边：垂直渐变，中间亮上下暗 */
    linear-gradient(180deg,rgba(100,170,255,.12) 0%,rgba(100,170,255,.7) 50%,rgba(100,170,255,.12) 100%) left 0 top 0 / 1px 100% no-repeat,
    /* 右竖边 */
    linear-gradient(180deg,rgba(100,170,255,.12) 0%,rgba(100,170,255,.7) 50%,rgba(100,170,255,.12) 100%) right 0 top 0 / 1px 100% no-repeat,
    /* 上横边：水平渐变，中间亮左右暗 */
    linear-gradient(90deg,rgba(100,170,255,.12),rgba(100,170,255,.55) 50%,rgba(100,170,255,.12)) left 0 top 0 / 100% 1px no-repeat,
    /* 下横边 */
    linear-gradient(90deg,rgba(100,170,255,.12),rgba(100,170,255,.55) 50%,rgba(100,170,255,.12)) left 0 bottom 0 / 100% 1px no-repeat,
    /* 面板底色 */
    linear-gradient(180deg,var(--panel-1),var(--panel-2));
  box-shadow:inset 0 0 50px rgba(47,123,255,.08);
  filter:drop-shadow(0 12px 40px rgba(0,0,0,.4));
  display:flex;flex-direction:column;overflow:hidden;
  clip-path:polygon(0 12px,12px 0,calc(100% - 12px) 0,100% 12px,100% calc(100% - 12px),calc(100% - 12px) 100%,12px 100%,0 calc(100% - 12px))}
/* 左上 L 形角标：内缩 2px 避开切角裁切；增强发光 */
.panel::before{content:'';position:absolute;left:2px;top:2px;width:14px;height:14px;
  border-left:2px solid var(--cyan);border-top:2px solid var(--cyan);
  z-index:3;pointer-events:none;filter:drop-shadow(0 0 6px rgba(51,214,255,.85))}
/* 右下 L 形角标 */
.panel::after{content:'';position:absolute;right:2px;bottom:2px;width:14px;height:14px;
  border-right:2px solid var(--cyan);border-bottom:2px solid var(--cyan);
  z-index:3;pointer-events:none;filter:drop-shadow(0 0 6px rgba(51,214,255,.85))}
/* 标题条：六屏改版后列卡只有 292~296 宽（一屏 320 内留边距），
   原 21px + 右侧 134px 装饰区会把副标题挤到折线上，这里整体收窄 */
.p-title{height:44px;font-size:16px;letter-spacing:.5px;padding:0 10px 0 14px;gap:4px;
  flex:none;display:flex;align-items:center;font-weight:600;position:relative;isolation:isolate;
  /* 阶梯形折线：大菱形右边框→右→斜上(与菱形右边框平行)→右延伸淡化 */
  background:
    /* 第三段：从拐角顶部(right:122.68)向右延伸淡化，位于副标题下方(bottom:9.75<14.75) */
    linear-gradient(90deg,rgba(80,150,255,.72),transparent) right 32.68px bottom 9.75px / 90px 1px no-repeat,
    /* 第二段：斜线向上(/形)，长度再减半2.75px，底部right:123.574顶部right:122.68 */
    linear-gradient(108deg,transparent calc(50% - .5px),rgba(80,150,255,.72) calc(50% - .5px),rgba(80,150,255,.72) calc(50% + .5px),transparent calc(50% + .5px)) right 122.68px bottom 7px / .894px 2.75px no-repeat,
    /* 第一段：从大菱形右边框出发向右到拐角底部，在最下面小菱形下边框下4px */
    linear-gradient(rgba(80,150,255,.72),rgba(80,150,255,.72)) right 123.574px bottom 7px / 13px 1px no-repeat}
/* 标题左侧色条 */
.p-title .p-title-bar{display:none}
/* 底层平行四边形：偏左下，仅边框轮廓，左边淡化，宽度缩短1/3 */
.p-title::before{content:'';position:absolute;left:0;right:134px;top:6px;bottom:-2px;z-index:-1;pointer-events:none;
  background:transparent;border:2px solid rgba(100,170,255,.35);
  transform:skewX(-18deg);border-radius:2px;
  -webkit-mask:linear-gradient(90deg,transparent 0%,#000 30%);
  mask:linear-gradient(90deg,transparent 0%,#000 30%)}
/* 顶层平行四边形：透蓝渐变填充 + 边框轮廓，左边淡化，宽度缩短1/3 */
.p-title::after{content:'';position:absolute;left:0;right:130px;top:2px;bottom:2px;z-index:-1;pointer-events:none;
  background:linear-gradient(90deg,rgba(50,120,210,.5),rgba(50,120,210,.12));
  border:2px solid rgba(100,170,255,.5);
  transform:skewX(-18deg);border-radius:2px;
  -webkit-mask:linear-gradient(90deg,transparent 0%,#000 30%);
  mask:linear-gradient(90deg,transparent 0%,#000 30%)}
.p-title em{font-style:normal;font-size:9px;font-weight:400;color:var(--dim);
  letter-spacing:0;margin-left:3px;position:relative;z-index:2;white-space:nowrap}
/* 标题右侧6个小菱形：倾斜排列，左边框与大菱形右边框平行 */
.p-diamonds{position:absolute;right:119px;top:44%;transform:translateY(-50%) skewX(-18deg);display:flex;flex-direction:column;gap:3px;z-index:1;pointer-events:none}
.p-diamonds i{display:block;width:7px;height:4px;background:rgba(80,150,255,.92);border-radius:1px}
.p-diamonds i:nth-child(1){opacity:.35}
.p-diamonds i:nth-child(2){opacity:.5}
.p-diamonds i:nth-child(3){opacity:.68}
.p-diamonds i:nth-child(4){opacity:.85}
.p-diamonds i:nth-child(5){opacity:1}
.p-body{flex:1;min-height:0;position:relative}

/* ---------- total row ---------- */
/* 1280 宽 = 中间 4 屏，四格各 320：格宽与屏宽整数对齐，
   分隔线正好落在 640 / 960 / 1280 三条拼缝上 —— 缝只切线、不切字 */
.panel-total{display:grid;grid-template-columns:repeat(4,1fr);align-items:center;padding:0;
  clip-path:none;border-radius:8px;position:relative;overflow:hidden;
  /* 边框同 KPI：左右竖边渐变（中间亮、上下淡出），由 ::after 绘制，border 只做 1px 占位 */
  border:1px solid transparent;background:transparent;
  box-shadow:inset 0 0 26px rgba(30,90,190,.14),0 4px 20px rgba(0,0,0,.42)}
/* 整体蓝底：独立圆角层，深蓝玻璃质感（比 KPI 更沉，避免大面积发白）
   注意：必须清掉 .panel::before 继承来的 left/top 2px 青色 L 形角标（border-left/border-top），
   否则 inset 撑满后这两条 2px 青边会被拉成整卡顶部横线 + 左侧竖线，看着像一道刺眼白线 */
.panel-total::before{content:'';position:absolute;inset:3px 5px;width:auto;height:auto;border-radius:6px;z-index:0;pointer-events:none;
  border:none;filter:none;
  background:linear-gradient(180deg,rgba(32,78,146,.66) 0%,rgba(16,42,88,.5) 46%,rgba(8,20,44,.34) 100%)}
/* 边框：竖直渐变，中线最亮、向上下两端淡出成透明（只看得见左右两条竖边）
   顺带覆盖 .panel::after 的右下 L 形角标 → 必须重置 border / filter / width / height，
   否则继承来的 14x14 尺寸会把整圈边框缩成一个小圆环 */
.panel-total::after{content:'';position:absolute;inset:0;width:auto;height:auto;border-radius:8px;padding:1px;z-index:3;pointer-events:none;
  border:none;
  background:linear-gradient(180deg,rgba(120,197,255,.02) 0%,rgba(155,218,255,.95) 40%,rgba(155,218,255,.95) 60%,rgba(120,197,255,.02) 100%);
  -webkit-mask:linear-gradient(#000,#000) content-box,linear-gradient(#000,#000);
  -webkit-mask-composite:xor;
  mask:linear-gradient(#000,#000) content-box,linear-gradient(#000,#000);
  mask-composite:exclude;
  filter:drop-shadow(0 0 3px rgba(80,180,255,.45))}
/* 单元格：去掉边框和蓝底，纯内容容器 */
.total-cell{display:flex;flex-direction:column;gap:3px;align-items:center;justify-content:center;position:relative;z-index:1;
  border:none;border-radius:0;padding:4px 20px 10px;min-width:176px;height:100%;
  background:transparent;box-shadow:none}
.total-cell::before{display:none}
.t-label{font-size:14px;color:#e8f2ff;letter-spacing:2px;position:relative;
  padding:2px 16px;text-shadow:0 1px 4px rgba(0,0,0,.5);z-index:2}
.t-label::before{content:'';position:absolute;left:50%;top:32%;transform:translate(-50%,-50%);
  width:150%;height:150%;z-index:-1;
  clip-path:polygon(12% 100%,88% 100%,68% 0,32% 0);
  background:linear-gradient(0deg,rgba(52,130,220,.92) 0%,rgba(52,130,220,0) 28%);
  filter:drop-shadow(0 0 6px rgba(52,130,220,.4))}
.t-val{display:flex;align-items:baseline;gap:6px;position:relative;z-index:2}
.t-val b{font-family:var(--num);font-size:30px;font-weight:600;font-variant-numeric:tabular-nums;
  background:linear-gradient(180deg,#eaf7ff 22%,#79c2ff 100%);
  -webkit-background-clip:text;background-clip:text;color:transparent;
  filter:drop-shadow(0 0 20px rgba(70,150,255,.45))}
.t-val i{font-style:normal;font-size:13px;color:var(--dim)}
.t-val.accent b{background:linear-gradient(180deg,#ffefc4 25%,#ffab12 100%);
  -webkit-background-clip:text;background-clip:text;color:transparent;
  filter:drop-shadow(0 0 22px rgba(255,176,32,.5))}
/* 汇总数字下方呼吸光带 */
.total-cell::after{content:'';position:absolute;bottom:6px;left:50%;transform:translateX(-50%);
  width:60px;height:2px;border-radius:2px;z-index:2;
  background:linear-gradient(90deg,transparent,rgba(51,214,255,.9),transparent);
  animation:breathe 2.6s ease-in-out infinite}
.total-cell:nth-child(3)::after{background:linear-gradient(90deg,transparent,rgba(255,176,32,.9),transparent)}
@keyframes breathe{0%,100%{opacity:.35;width:40px}50%{opacity:1;width:72px}}
/* 分隔线脱离 grid 流，按 25/50/75% 钉在格与格的交界上（即 640/960/1280，正对拼缝） */
.total-split{position:absolute;top:50%;margin-top:-22px;width:1px;height:44px;z-index:1;pointer-events:none;
  background:linear-gradient(180deg,transparent,rgba(120,175,255,.34),transparent)}
.total-split:nth-child(2){left:25%}
.total-split:nth-child(4){left:50%}
.total-split:nth-child(6){left:75%}

/* 六屏拼缝参考线（?seams=1 时才插入）：每 320px 一条，落在屏与屏的交界上 */
.seam-guide{position:absolute;inset:0;z-index:96;pointer-events:none;
  background-image:repeating-linear-gradient(90deg,
    rgba(255,72,92,.55) 0 2px, transparent 2px 320px)}

/* ---------- 重点仓实景墙（右列第 6 屏，竖向无缝上滚） ---------- */
/* 做法：track 里放两份相同列表，靠 translateY(-50%) 循环 —— 动画结束那帧
   第二份正好落回第一份起点，接缝不可见。纯 CSS 动画走合成器，不占主线程。 */
.wh-wall{display:flex;flex-direction:column}
.ww-view{position:relative;flex:1;min-height:0;overflow:hidden;
  /* 上下边缘渐隐，滚到边界不像被刀切 */
  -webkit-mask-image:linear-gradient(180deg,transparent 0,#000 46px,#000 calc(100% - 46px),transparent 100%);
  mask-image:linear-gradient(180deg,transparent 0,#000 46px,#000 calc(100% - 46px),transparent 100%)}
.ww-track{position:absolute;left:0;top:0;width:100%;will-change:transform;
  animation:wwScroll linear infinite}
/* 悬停暂停：现场演示要点某张图时不至于一直跑掉 */
.ww-view:hover .ww-track{animation-play-state:paused}
@keyframes wwScroll{from{transform:translate3d(0,0,0)}to{transform:translate3d(0,-50%,0)}}

.ww-item{position:relative;padding:0 8px;margin-bottom:12px}
.ww-pic{position:relative;width:100%;aspect-ratio:16/9;overflow:hidden;border-radius:3px;
  background:rgba(8,20,40,.9);
  /* 图片四周细描边，深色照片也能看清边界 */
  box-shadow:inset 0 0 0 1px rgba(96,164,255,.28)}
.ww-pic img{width:100%;height:100%;object-fit:cover;display:block}
/* 底部渐变托底：保证仓名压在任何亮色照片上都读得清 */
.ww-meta{position:absolute;left:0;right:0;bottom:0;padding:28px 9px 7px;
  background:linear-gradient(180deg,rgba(4,10,22,0),rgba(4,10,22,.9) 66%);
  display:flex;align-items:baseline;gap:7px}
.ww-name{font-size:15px;font-weight:600;color:#eaf6ff;letter-spacing:1px;
  text-shadow:0 1px 6px rgba(0,0,0,.95)}
.ww-city{font-size:10px;color:#9dc0e6;letter-spacing:.4px}
/* v3.22：照片左上角的金色「重点仓」标已去掉 —— 整列上墙的都是重点仓，
   每张再挂同一个标是废话，还压住画面。仓名/城市仍留在图片下沿。 */

.ww-empty{padding:60px 12px;text-align:center;font-size:13px;color:var(--dim);letter-spacing:2px}

/* ---------- map panel ---------- */
/* 不再对 left/top/width/height 做过渡：尺寸过渡期间 canvas 只能被 CSS 拉伸，
   而位图要等下一次 resize 才重排，两者不同步 → 就是"噔噔"顿挫的根因。
   现在几何瞬间到位 + 只做 transform 动画，全程合成器完成，不触发任何重绘 */
/* 世界地图背景层：始终全屏铺满，不缩放，z-index 在面板之下 */
/* v3.29：底图不再铺世界地图，换成「蜂窝网格 + 中心冷光 + 定时扫描」。
   网格和扫描都放在伪元素里：::before 是蜂窝，::after 是从左到右扫过的光带。
   网纹用 mask 抠掉中间一块 —— 地图底下不需要纹理，只留四周的"科技感"底噪。 */
/* 全屏底图。v3.43：最外圈原来是 rgba(4,9,20,.92)（几乎纯黑且铺满全屏，是「两侧太黑」
   的主因），现在改成 rgba(9,24,50,.72)、中间那档也抬了一档 —— 边角从黑变深蓝，
   中心亮度不变，聚焦关系还在。回退：?bg=orig */
#mapBg{position:absolute;left:0;top:0;width:1920px;height:1080px;z-index:1;pointer-events:none;
  overflow:hidden;
  background:
    radial-gradient(ellipse at 50% 42%,rgba(28,78,160,.55) 0%,rgba(12,36,78,.46) 48%,rgba(9,24,50,.72) 100%),
    radial-gradient(circle at 50% 50%,rgba(40,110,210,.18),transparent 60%)}
/* 网格：不是常驻底纹，而是一圈从画面中心往外扩散的波。
   做法：网格整片铺好，再用一个「环形」mask 把它裁剩一圈 —— mask 是固定的环形图案
   （中心透明 → 环处实心 → 外面透明），动画只放大 mask 的尺寸，环就自己往外跑。
   因为环的百分比写在渐变里，环宽会随扩散一起变粗，正好是水波/声波扩散该有的样子。
   上下淡出放在父级 .bg-grid-mask：两层 mask 嵌套天然相交，不依赖 mask-composite。

   v3.33 底纹几何换了：原来的蜂窝图是「六边形描两遍 + 外圈再补一段折线」拼出来的
   （heropatterns 的 hexagons），放大看每条边是 3 根平行线；客户要"1 条线"。
   现在的 tile 是 26×45 的蜂窝格，六边形按三角格点算出来、边去重后只画一次，
   相邻格共用一条边 —— 真正的单线蜂窝，无断口。（detail: tile 里连相邻列越界的
   那几段也一起画，靠 52×90 的 background-size 平铺才对得上缝，删不得。） */
.bg-grid-mask{position:absolute;inset:0;pointer-events:none;
  -webkit-mask-image:linear-gradient(180deg,transparent 0%,#000 9%,#000 88%,transparent 100%);
          mask-image:linear-gradient(180deg,transparent 0%,#000 9%,#000 88%,transparent 100%)}
/* 挖洞层：第 2 圈以内不出现网格 —— 客户要的是「网格扫描从第 2 圈开始」。
   椭圆圆心/半径用 --grid-*（由 map3d.js 按第 2 圈的真实投影写进来），
   2D 皮肤没有 map3d.js，用这里的兜底值（按看板态的实测比例）。
   0→97% 全透明、97→100% 渐变到实心，边缘才不是刀切的。 */
.bg-grid-hole{position:absolute;inset:0;pointer-events:none;
  -webkit-mask-image:radial-gradient(ellipse var(--grid-rx,25.9%) var(--grid-ry,31.1%) at var(--grid-cx,50%) var(--grid-cy,59.8%),transparent 0 97%,#000 100%);
          mask-image:radial-gradient(ellipse var(--grid-rx,25.9%) var(--grid-ry,31.1%) at var(--grid-cx,50%) var(--grid-cy,59.8%),transparent 0 97%,#000 100%)}
.bg-grid{
  /* 遮罩的中心必须落在「第 2 圈」的中心，不是容器的中心 —— 看板态下第 2 圈
     压在画面偏下 10%（--grid-cy 59.8%），不补这一下，环带只在地图上方露出一条，
     下面全被洞裁掉（红块实测过）。做法不是挪 mask-position（百分比语义不同源，
     算不准），而是把元素本身摆成"中心对齐第 2 圈中心"：
     left/top 定中心点，width/height 撑满，再 translate(-50%,-50%) 回正。 */
  position:absolute;left:var(--grid-cx,50%);top:var(--grid-cy,59.8%);
  width:100%;height:100%;transform:translate(-50%,-50%);
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='25.98' height='45' viewBox='0 0 25.98 45'%3E%3Cg fill='none' stroke='%2363a8ff' stroke-opacity='0.42' stroke-width='0.7'%3E%3Cpath d='M-12.99 7.5L0 15M-12.99 37.5L0 30M0 15L0 30M0 15L12.99 7.5M0 30L12.99 37.5M12.99 -7.5L12.99 7.5M12.99 7.5L25.98 15M12.99 37.5L12.99 52.5M12.99 37.5L25.98 30M25.98 15L25.98 30M25.98 15L38.97 7.5M25.98 30L38.97 37.5'/%3E%3C/g%3E%3C/svg%3E") 0 0/52px 90px repeat;
  /* 椭圆（ellipse 50% 50% = 内切）而不是正圆：第 2 圈本身是椭圆（498×336），
     用正圆扫过去，左右永远被洞挡住、只有上下冒头，看着像"只扫了南北"。
     mask-size 用两个值，宽高比锁死 1.478（= 498/336），环就一直是椭圆的比例。 */
  -webkit-mask-image:radial-gradient(ellipse 50% 50% at 50% 50%,transparent 0 40%,rgba(0,0,0,.45) 46%,#000 52%,rgba(0,0,0,.45) 60%,transparent 68% 100%);
          mask-image:radial-gradient(ellipse 50% 50% at 50% 50%,transparent 0 40%,rgba(0,0,0,.45) 46%,#000 52%,rgba(0,0,0,.45) 60%,transparent 68% 100%);
  -webkit-mask-size:1900px 1285px;mask-size:1900px 1285px;
  -webkit-mask-repeat:no-repeat;  mask-repeat:no-repeat;
  -webkit-mask-position:center;   mask-position:center;
  animation:gridRipple 10s linear infinite}
/* v3.33 节奏：1.6 秒匀速扩散到底（原来 7 秒走完全程，慢得像在漂）→ 0.4 秒淡出余光
   → 剩下 8 秒整片不显示，等下一轮，正好是客户要的"扩完停 8 秒"。
   扩散段用 linear 而不是 ease-out：ease-out 在前 15% 就把大部分距离走完了，
   实测 0.45s 时波已经出画，等于"闪一下"就没了；匀速才看得出波在往外推。
   终点尺寸从 4355 提到 5000：4355 时椭圆环带的内沿还没出画，屏幕左右两侧会一直
   挂着一条残环，淡出也淡不掉；5000×3378（宽高比锁 1.478）时环带整个跑到画外，
   只剩四角一点点余光，交给 opacity 收干净。 */
@keyframes gridRipple{
  0%   {-webkit-mask-size:1900px 1285px;mask-size:1900px 1285px;opacity:1}
  16%  {-webkit-mask-size:5000px 3378px;mask-size:5000px 3378px;opacity:1}
  20%  {-webkit-mask-size:5000px 3378px;mask-size:5000px 3378px;opacity:0}
  100% {-webkit-mask-size:5000px 3378px;mask-size:5000px 3378px;opacity:0}}

#mapPanel{position:absolute;z-index:20;border:1px solid rgba(72,148,255,.32);border-radius:8px;overflow:hidden;
  background:rgba(8,20,44,.35);
  transform-origin:0 0;will-change:transform,opacity;
  transition:border-radius .5s ease,background .5s ease}
/* v3.21：仪表盘态不画地图方框 —— 边框和底色都透明，让中国地图直接融进世界地图底图。
   全屏态（map-full）与背景态（map-bg）各自仍带遮罩，不在这里改。 */
#mapPanel.map-mini{left:320px;top:216px;width:1280px;height:850px;
  border-color:transparent;background:transparent}
/* 收起地图时的过渡快照：resize 期间盖在画布上，重绘完再淡出（见 app.js takeMapSnap） */
.map-snap{position:absolute;z-index:25;pointer-events:none;border-radius:8px}
/* 背景模式：全屏铺满，置于面板之后，作为看板背景图，不缩放 */
#mapPanel.map-bg{left:0;top:0;width:1920px;height:1080px;border-radius:0;border-color:transparent;
  box-shadow:none;z-index:5;opacity:.62;pointer-events:none;
  background:rgba(8,20,44,.35)}
#mapPanel.map-full{left:0;top:0;width:1920px;height:1080px;border-radius:0;border-color:transparent;
  box-shadow:none;z-index:60;
  background:rgba(8,20,44,.6)}
/* v3.47 从 1 抬到 2：给南海插图深底（.map-south-bg，z-index 0）让出中间层，
   让插图本身画在深底之上、边框之下 */
#mapChart{position:absolute;inset:0;z-index:2}

/* ---------- 地图外圈装饰（v3.29）----------
   双圆环 + 地面罗盘网格 + 脉冲环 + 扫描线，全部纯 CSS/SVG，不占主线程。
   这一层只在 2D 皮肤下出现；t3d 皮肤里由 three.js 在地板平面上重画一套
   （2D 的椭圆投影跟正交斜视角对不上，所以不能共用）。 */
#mapDeco{position:absolute;inset:0;z-index:0;pointer-events:none;overflow:hidden;
  transition:opacity .45s ease}
/* t3d 下整块关掉：display:none 才能让那几条 CSS 动画彻底停下，不白烧合成器 */
#mapPanel.deco-off #mapDeco{display:none}

/* 地面罗盘：同心圆 + 每 15° 一根放射线，正片叠底式地垫在版图下 */
.deco-wheel{position:absolute;left:50%;top:50%;width:76%;height:54%;
  transform:translate(-50%,-50%);border-radius:50%;
  background:
    repeating-conic-gradient(from 0deg,rgba(96,168,255,.16) 0deg .2deg,transparent .2deg 15deg),
    radial-gradient(circle,
      transparent 0 31.6%,rgba(96,168,255,.13) 31.8% 32.1%,transparent 32.3% 57.6%,
      rgba(96,168,255,.11) 57.8% 58.1%,transparent 58.3% 81.6%,
      rgba(96,168,255,.09) 81.8% 82.1%,transparent 82.3%);
  -webkit-mask-image:radial-gradient(ellipse 50% 50% at 50% 50%,#000 0 62%,transparent 74%);
          mask-image:radial-gradient(ellipse 50% 50% at 50% 50%,#000 0 62%,transparent 74%);
  animation:wheelBreath 9s ease-in-out infinite}
@keyframes wheelBreath{0%,100%{opacity:.72}50%{opacity:1}}

/* 双圆环：两条椭圆描边，另配两道沿环游走的彗尾光弧（一冷一暖，速度不同） */
.deco-rings{position:absolute;inset:0;width:100%;height:100%}
.dr{fill:none;vector-effect:non-scaling-stroke;stroke-linecap:round}
.dr-outer{stroke:rgba(110,192,255,.34);stroke-width:1.2}
.dr-inner{stroke:rgba(110,192,255,.20);stroke-width:1}
.dr-comet{stroke:rgba(140,228,255,.9);stroke-width:2;
  stroke-dasharray:46 3300;animation:cometRun 13s linear infinite;
  filter:drop-shadow(0 0 5px rgba(95,214,255,.7))}
.dr-comet2{stroke:rgba(255,206,120,.72);stroke-width:1.6;
  stroke-dasharray:30 2880;animation:cometRun2 19s linear infinite reverse;
  filter:drop-shadow(0 0 4px rgba(255,190,90,.5))}
@keyframes cometRun{from{stroke-dashoffset:0}to{stroke-dashoffset:-3346}}
@keyframes cometRun2{from{stroke-dashoffset:0}to{stroke-dashoffset:-2910}}

.map-tip{position:absolute;left:50%;top:auto;bottom:26px;transform:translateX(-50%);
  font-size:15px;color:rgba(190,220,255,.6);letter-spacing:2px;pointer-events:none;
  opacity:0;transition:opacity .4s;z-index:3}
.map-full .map-tip{opacity:1}
.map-tip.hide{opacity:0}

/* 南海诸岛插图框：矩形边框，尺寸由 JS 按插图 geo 的框同步（见 relayoutGeo）。
   注意：v3.21 隐藏的是外层「地图卡片」的框，不是这个小框 —— 保留。
   v3.47 关键修正：深底从本元素拆出去，单独做成 .map-south-bg 压在 #mapChart **下面**。
   原来深底和边框挤在同一个 div 上，而它 z-index 高于 canvas —— 那层 .88 不透明的
   深色直接盖在插图上，岛礁的真实亮度 243 被压到 55，怎么调 itemStyle 都提不亮
   （实测把背景设为 transparent，同区域最亮像素从 55.6 跳到 243.4）。
   现在分层：.map-south-bg(0) < #mapChart(2) < .map-south-frame(3)，
   深底照样挡掉背后穿过的地环，插图却画在深底之上。 */
.map-south-frame{position:absolute;left:0;top:0;font-size:12px;color:rgba(160,200,245,.6);
  letter-spacing:1px;pointer-events:none;z-index:3;white-space:nowrap;
  border:1px solid rgba(110,190,255,.58);
  box-shadow:inset 0 0 10px rgba(47,123,255,.10);
  transition:left .25s,top .25s,width .25s,height .25s}
/* 文字标在框正上方，不占框内空间、不压岛屿 */
.map-south-frame span{position:absolute;left:50%;bottom:100%;transform:translateX(-50%);margin-bottom:3px}
/* 插图的深底（原 v3.34 写在 .map-south-frame 上的那层）。
   全屏时外圈那条亮环正好从这个框里穿过去，九段线插图本来是"另开小窗"的制图惯例，
   透出背后的地环就很怪 —— 所以还是要有底，只是必须压在 canvas 之下。 */
.map-south-bg{position:absolute;left:0;top:0;z-index:0;pointer-events:none;
  /* 纯色不透明：插图从这层底下挪到上面之后，透出的 12% 不再是「和插图一起被压暗的
     背景」，而是 3D 地板环那道亮青线 —— 12% 的亮线在深底上照样看得见，会斜穿插图框。
     半透明压不住（试过 .96/.94 仍有残留），所以直接用实色。 */
  background:linear-gradient(180deg,#071226,#040a16);
  transition:left .25s,top .25s,width .25s,height .25s}
/* t3d：地图是 #mapChart(2) 里的 WebGL canvas，深底得抬到 canvas 之上才挡得住地板 */
html.t3d .map-south-bg{z-index:3}
/* ?skin=gl3d 时南海诸岛插图用独立 2D ECharts 实例渲染（3D 主图放不下九段线） */
.south-3d{position:absolute;inset:0;pointer-events:none}

/* 自动巡航：文字开关（图标按钮在展厅里认不出来，改成明写"开/关"）。
   v3.21 从 mapPanel 里的 map-tools 挪进 dock，常态可见；尺寸由 .dock .btn-cruise 控制 */
.btn-cruise.is-on{border-color:rgba(47,224,168,.6);color:#b9ffe8;
  box-shadow:inset 0 0 18px rgba(47,224,168,.12)}

.map-legend{position:absolute;left:32px;bottom:26px;display:flex;gap:22px;font-size:14px;
  color:var(--dim);z-index:4;opacity:0;transition:opacity .4s}
.map-full .map-legend{opacity:1}
.dot{display:inline-block;width:9px;height:9px;border-radius:50%;background:#3fc6ff;margin-right:7px;
  vertical-align:1px;box-shadow:0 0 10px rgba(63,198,255,.9)}
.dot.star{background:var(--amber);border-radius:0;width:11px;height:11px;clip-path:polygon(50% 0%,61% 35%,98% 35%,68% 57%,79% 91%,50% 70%,21% 91%,32% 57%,2% 35%,39% 35%);box-shadow:none}

/* ---------- dashboard hidden in full mode ---------- */
/* 不用 blur：1920 宽的两列做 filter:blur() 每帧都要重算，是切换卡顿的主因之一 */
.map-full-active .col{opacity:0;pointer-events:none;transition:opacity .32s ease}
/* 过渡结束后彻底移出渲染树，避免隐藏元素继续占合成资源 */
.map-settled .col{display:none}
.map-full-active #hd::before{opacity:1}

/* ---------- warehouse card ---------- */
/* v3.42：指标只剩「库房面积 / 在库吨位」两项，右侧说明栏收窄并改为上下竖排
   （两项横排时要各占一半宽度，右边会空出一大片；竖排后每项整行铺满） */
.wh-card{position:absolute;left:50%;top:50%;width:800px;transform:translate(-50%,-50%) scale(.94);
  z-index:80;opacity:0;pointer-events:none;transition:opacity .3s ease,transform .3s cubic-bezier(.22,.61,.36,1);
  border-radius:0;overflow:hidden;
  /* 左右竖边渐变（中间亮、向上下两端淡出）：2px 实线 + 6px 柔光 + 底色 */
  background:
    linear-gradient(180deg,rgba(96,180,255,0) 0%,rgba(120,200,255,.38) 50%,rgba(96,180,255,0) 100%) left 0 top 0 / 6px 100% no-repeat,
    linear-gradient(180deg,rgba(96,180,255,.02) 0%,rgba(165,225,255,.95) 40%,rgba(165,225,255,.95) 60%,rgba(96,180,255,.02) 100%) left 0 top 0 / 2px 100% no-repeat,
    linear-gradient(180deg,rgba(96,180,255,0) 0%,rgba(120,200,255,.38) 50%,rgba(96,180,255,0) 100%) right 0 top 0 / 6px 100% no-repeat,
    linear-gradient(180deg,rgba(96,180,255,.02) 0%,rgba(165,225,255,.95) 40%,rgba(165,225,255,.95) 60%,rgba(96,180,255,.02) 100%) right 0 top 0 / 2px 100% no-repeat,
    linear-gradient(180deg,rgba(15,34,64,.98),rgba(6,14,28,.99));
  /* 内发光 + drop-shadow 外投影（随切角） */
  box-shadow:inset 0 0 80px rgba(47,123,255,.08);
  filter:drop-shadow(0 30px 90px rgba(0,0,0,.75));
  clip-path:polygon(0 16px,16px 0,calc(100% - 16px) 0,100% 16px,100% calc(100% - 16px),calc(100% - 16px) 100%,16px 100%,0 calc(100% - 16px))}
/* 卡片四角科技角标：内缩 2px 避开切角；增强发光 */
.wh-card::before{content:'';position:absolute;left:2px;top:2px;width:18px;height:18px;
  border-left:2px solid var(--cyan);border-top:2px solid var(--cyan);z-index:5;pointer-events:none;
  filter:drop-shadow(0 0 6px rgba(51,214,255,.85))}
.wh-card::after{content:'';position:absolute;right:2px;bottom:2px;width:18px;height:18px;
  border-right:2px solid var(--cyan);border-bottom:2px solid var(--cyan);z-index:5;pointer-events:none;
  filter:drop-shadow(0 0 6px rgba(51,214,255,.85))}
.wh-card.show{opacity:1;pointer-events:auto;transform:translate(-50%,-50%) scale(1)}
.card-close{position:absolute;right:14px;top:12px;width:40px;height:40px;border-radius:50%;
  border:1px solid var(--line);background:rgba(20,44,84,.7);color:#cfe4ff;font-size:22px;line-height:1;
  cursor:pointer;z-index:2}
.card-close:hover{background:rgba(40,86,160,.9)}
.card-head{display:flex;align-items:center;gap:14px;padding:22px 28px 16px;border-bottom:1px solid rgba(72,148,255,.14)}
.card-name h3{display:inline-block;font-size:26px;font-weight:700;letter-spacing:2px;vertical-align:middle}
.card-name span{font-size:14px;color:var(--dim);margin-left:12px;letter-spacing:1px}
.card-badge{font-size:12px;color:#ffd98a;border:1px solid rgba(255,176,32,.5);border-radius:4px;
  padding:3px 10px;letter-spacing:1px;background:rgba(255,176,32,.1)}
.card-badge.hide{display:none}
.card-body{display:grid;grid-template-columns:400px 1fr;gap:22px;padding:20px 28px 26px}
.card-media{display:flex;flex-direction:column;gap:10px}
.media-main{width:100%;height:230px;border-radius:8px;overflow:hidden;position:relative;background:#0a1628;
  border:1px solid rgba(72,148,255,.2);display:grid;place-items:center}
.media-main img{width:100%;height:100%;object-fit:cover;display:block}
.media-main .ph{font-size:13px;color:var(--dim);letter-spacing:1px;text-align:center;padding:20px}
.media-main video{width:100%;height:100%;object-fit:cover}
/* 视频态：画面完整不裁切，背后垫一层模糊同源画面填满空白（竖屏素材两侧不留死黑） */
.media-main.video-mode{background:#050e1c}
.media-main.video-mode > video:not(.vbg){position:relative;z-index:2;object-fit:contain}
.media-main .vbg{position:absolute;left:-10%;top:-10%;width:120%;height:120%;object-fit:cover;
  filter:blur(16px) brightness(.42) saturate(.9);z-index:1}
.media-thumbs{display:flex;gap:10px}
.thumb{position:relative;flex:1;height:64px;border-radius:6px;overflow:hidden;cursor:pointer;
  border:1px solid rgba(72,148,255,.2);opacity:.6;transition:.2s}
.thumb img{width:100%;height:100%;object-fit:cover;display:block}
.thumb.active{opacity:1;border-color:var(--cyan);box-shadow:0 0 16px rgba(51,214,255,.35)}
.thumb.video{display:grid;place-items:center;background:rgba(10,28,54,.9);font-size:12px;color:#9dc4f0;
  letter-spacing:1px}
.thumb.video::before{content:'';width:0;height:0;border-left:9px solid rgba(51,214,255,.9);
  border-top:6px solid transparent;border-bottom:6px solid transparent;margin-bottom:4px}
.thumb.video.has-media{display:block;background:#0a1628}
.thumb.video.has-media video,.thumb.video.has-media img{width:100%;height:100%;object-fit:cover;display:block;opacity:.9}
.thumb.video.has-media::before{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  margin:0;border-left-color:#fff;filter:drop-shadow(0 0 5px rgba(0,0,0,.85))}
.c-intro{font-size:15px;line-height:1.9;color:#c2d6ef;min-height:86px}
.c-stats{display:grid;grid-template-columns:1fr;gap:10px;margin-top:14px}
.c-stats > div{border:1px solid rgba(72,148,255,.2);border-radius:0;padding:10px 14px;position:relative;
  background:rgba(12,30,56,.55);display:flex;flex-direction:column;gap:4px;overflow:hidden}
.c-stats > div::before{content:'';position:absolute;left:0;top:0;width:2px;height:100%;
  background:linear-gradient(180deg,var(--cyan),transparent)}
.c-stats .s-row{display:flex;align-items:center}
.c-stats span{font-size:14px;color:var(--dim);letter-spacing:1px}
.c-stats b{font-family:var(--num);font-size:26px;font-weight:600;color:#eaf4ff;font-variant-numeric:tabular-nums}
.c-stats .pct{font-style:normal;font-size:11px;color:var(--cyan);margin-left:auto}
.c-stats .bar{height:4px;background:rgba(72,148,255,.15);border-radius:2px;overflow:hidden;margin-top:2px}
.c-stats .bar i{display:block;height:100%;border-radius:2px;width:0;
  background:linear-gradient(90deg,var(--blue),var(--cyan));transition:width .8s cubic-bezier(.22,.61,.36,1)}
.c-meta{display:flex;gap:26px;margin-top:14px;font-size:12px;color:var(--dim);letter-spacing:1px}

/* ---------- import dialog ---------- */
.imp-dlg{position:absolute;inset:0;z-index:95;display:grid;place-items:center;
  background:rgba(3,8,18,.74);opacity:0;pointer-events:none;transition:opacity .25s}
.imp-dlg.show{opacity:1;pointer-events:auto}
.imp-inner{position:relative;width:760px;padding:32px 36px 28px;border-radius:12px;
  border:1px solid rgba(86,178,255,.45);
  background:linear-gradient(180deg,rgba(15,34,64,.99),rgba(6,14,28,.99));
  box-shadow:0 30px 90px rgba(0,0,0,.78),inset 0 0 80px rgba(47,123,255,.08);
  transform:translateY(14px);transition:transform .28s cubic-bezier(.22,.61,.36,1)}
.imp-dlg.show .imp-inner{transform:translateY(0)}
.imp-title{font-size:22px;font-weight:700;letter-spacing:2px;margin-bottom:10px}
.imp-status{font-size:13px;color:var(--cyan);letter-spacing:.5px;margin-bottom:18px}
.imp-ol{margin:0 0 24px 20px;font-size:14px;line-height:2;color:#c2d6ef}
.imp-actions{display:flex;gap:14px}
.imp-errors{display:none;margin-top:18px;padding:14px 16px;border-radius:8px;max-height:230px;overflow:auto;
  border:1px solid rgba(255,77,94,.45);background:rgba(255,77,94,.08)}
.imp-errors.show{display:block}
.imp-err-title{font-size:14px;color:#ff8d99;margin-bottom:8px}
.imp-err{font-size:13px;line-height:1.9;color:#ffc9cf}

/* ---------- toast ---------- */
.toast{position:absolute;left:50%;bottom:120px;transform:translate(-50%,16px);z-index:90;
  padding:14px 26px;border-radius:8px;font-size:15px;letter-spacing:1px;
  background:rgba(12,30,56,.96);border:1px solid var(--line-strong);color:#dbeaff;
  opacity:0;pointer-events:none;transition:.28s}
.toast.show{opacity:1;transform:translate(-50%,0)}

/* ---------- 视频全屏 ---------- */
.vf{position:absolute;inset:0;z-index:130;background:rgba(2,6,14,.985);
  opacity:0;pointer-events:none;transition:opacity .26s ease}
.vf.show{opacity:1;pointer-events:auto}
.vf-stage{position:absolute;inset:0;display:grid;place-items:center;padding:14px 14px 52px;cursor:zoom-out}
.vf-stage video{max-width:100%;max-height:100%;width:auto;height:auto;object-fit:contain;
  display:block;border-radius:6px;box-shadow:0 24px 80px rgba(0,0,0,.7);background:#000}
.vf-stage .ph{color:var(--dim);font-size:16px;letter-spacing:2px}
.vf-close{position:absolute;right:34px;top:30px;z-index:2;width:52px;height:52px;border-radius:50%;
  border:1px solid rgba(120,180,255,.35);background:rgba(10,26,50,.85);color:#cfe2ff;
  font-size:30px;line-height:1;cursor:pointer;display:grid;place-items:center;transition:.2s}
.vf-close:hover{border-color:var(--cyan);color:#fff;transform:rotate(90deg)}
.vf-bar{position:absolute;left:0;right:0;bottom:0;height:56px;display:flex;align-items:center;
  justify-content:space-between;padding:0 34px;font-size:14px;letter-spacing:1px;
  color:rgba(170,205,245,.7);background:linear-gradient(transparent,rgba(3,8,18,.85));pointer-events:none}
.vf-name{color:#dbeaff;font-size:16px}
/* 视频主区右下角的进入全屏按钮 */
.media-main.video-mode{cursor:zoom-in}
.vf-btn{position:absolute;right:10px;bottom:10px;z-index:5;width:34px;height:34px;border-radius:6px;
  display:grid;place-items:center;background:rgba(6,18,36,.72);border:1px solid rgba(120,180,255,.35);
  color:#cfe2ff;font-size:15px;opacity:.75;transition:.2s;pointer-events:none}
.media-main.video-mode:hover .vf-btn{opacity:1;border-color:var(--cyan)}

/* ---------------- 无数据提示（v3.13） ----------------
   没有内置演示数据兜底后，服务器和本机都没数据时给一条明确提示，
   而不是让人对着空屏猜是不是坏了。 */
.no-data{position:absolute;left:50%;top:118px;transform:translateX(-50%);z-index:38;
  display:none;align-items:center;gap:16px;padding:10px 18px;border-radius:8px;
  background:linear-gradient(180deg,rgba(12,36,74,.95),rgba(8,22,48,.95));
  border:1px solid rgba(86,178,255,.45);box-shadow:0 8px 28px rgba(0,0,0,.45)}
.no-data.show{display:flex}
.no-data-txt{font-size:15px;color:#dbeaff;letter-spacing:1px}
.no-data .btn{padding:6px 16px;font-size:13px}

/* ---------------- 背景底图回退（v3.43）：?bg=orig ----------------
   默认已改成「椭圆化 + 拉长衰减」（见文件顶部 #viewport / #mapBg）。
   这里是旧观感：底图走到两侧会掉成近黑，跟中间那块亮蓝断开一截。
   当时实测三层叠加在压黑：
     · #mapBg 第一层椭圆径向，100% 处是 rgba(4,9,20,.92) —— 铺满全屏、几乎不透明，
       是最主要的压黑来源；
     · #viewport 用 circle 径向，62% 就落到 #04070f；16:9 屏上圆形先横向到底，
       左右 x<280 / x>1640 已经是纯底色，所以「两侧」比上下更明显；
     · #stage 的中心光晕只到 58%，边缘一点忙都帮不上。
   另外两套候选（左右侧补光 / 线性打底）实测后没采纳，结论：
   侧补光只治左右、底边照样黑，还会在中间留下一条横向暗带；
   线性打底最均匀但整屏发亮，投投影仪时暗部本来就发灰，会糊成一片。 */
html.bg-orig #mapBg{background:
  radial-gradient(ellipse at 50% 42%,rgba(28,78,160,.55) 0%,rgba(10,30,66,.4) 45%,rgba(4,9,20,.92) 100%),
  radial-gradient(circle at 50% 50%,rgba(40,110,210,.18),transparent 60%)}
html.bg-orig #viewport{background:
  radial-gradient(circle at 50% 42%,#0a1c38 0%,#04070f 62%)}
