/* ==========================================================================
   车镜 CarMirror · website-ui.css
   桌面表现层 —— index.html 里最后一个加载的样式表。

   这一层只做「表现」：配色、栅格、间距、光效。
   它不引入任何新组件语义，也不改任何行为；所有选择器都挂在
   已存在的类名或视图层新加的纯展示类名上。

   层叠位置：本文件在 base / components / views / prism / scanner /
   card-nav / specular-button 之后加载。因此同特异度时本文件胜出，
   只有对方特异度更高时才需要显式加权 —— 下面凡是特异度高于 (0,1,0)
   的选择器都在注释里写明了它在压谁，避免后人误删。
   ========================================================================== */

:root {
  --app-width: 1480px;
  --radius: 14px;
  --radius-lg: 18px;

  /* 强调色：参考图的亮蓝。
     base.css:43-55 记录了一条硬约束 —— 强调色要直接当文字色用，
     必须自己满足 WCAG AA。实测 #38bdf8 对 --bg (#0b1216) 为 8.8:1，
     高于 base.css 记录的 7.98:1，该约束继续成立。
     不要换成参考图里那些更深的蓝，它们过不了 4.5:1。 */
  --accent: #38bdf8;

  /* --accent-soft / --accent-line 这里刻意不再重定义。
     base.css:65-66 已经用 color-mix 从 --accent 派生，
     原先写死的 rgb(67 199 232 / …) 是另一个色相，跟 --accent 对不上，
     换色时也不会跟随。删掉后自动继承派生值。 */

  /* 功能格用的第四个色调。安全三色（ok/warn/danger）已被语义占用，
     这个紫只用于「同车型问答」这类中性模块，不承担状态含义。 */
  --alt: #a78bfa;

  /* 参考图的 #F0454B 比 base.css 的 --danger 暗：它在 --card 上只有
     4.18:1，当正文色不达标。所以只拿来描边和分隔线，不碰文字。 */
  --danger-stroke: #f0454b;

  /* 环境光。刻意与 --danger 分开：参考图里尾灯红和危险红是同一个色值，
     那会让「装饰」和「告警」在视觉上同义。这里降饱和并限制透明度上限
     .26，确保它读起来是光，不是状态。 */
  --ambient-red:  rgb(190 62 58 / .26);
  --ambient-blue: rgb(38 132 170 / .24);

  /* 外壳尺寸。原先 76px 在三处硬编码（appbar 高、tabbar 高、view 高度
     扣减），改一处忘一处就会错位，收敛成一个令牌。 */
  --appbar-h: 76px;
}

/* ==========================================================================
   页面底衬与环境光
   ========================================================================== */

body {
  background: #080f16;
  background-image: linear-gradient(135deg, #050b16 0%, #0b1628 48%, #050914 100%);
}

/* scanner.css:1-5 以 (0,1,1) 把 body 背景重设为纯 #08090d 且 background-image:
   none。而 router.js:164 在除 landing / login / register / about 之外的每一个
   路由上都挂 scanner —— 也就是说主页、车库、报告全都走这一支，上面那条
   (0,0,1) 的渐变其实一直没生效。这里用同为 (0,1,1) 的选择器靠加载顺序
   接回来（website-ui.css 是 index.html 里最后一个样式表）。
   不去动 scanner.css 本身：那三条是画布自己的挂载契约。 */
body.has-scanner {
  background-color: #080f16;
  background-image: linear-gradient(135deg, #050b16 0%, #0b1628 48%, #050914 100%);
}

/* 红蓝双色环境光挂在 .stage 的伪元素上，而不是 body { background-image }：
   prism.css:3 与 scanner.css:3 都以 (0,2,0) 重设了 body 背景，本文件的
   (0,0,1) 压不过特异度，那些路由上整条光会消失。.stage 在它们之外，
   且已经是全屏容器。

   这一份画的是「框外」的光。框内还需要另画一份到 .app-shell::before
   上 —— 外壳不透明，框外的光透不进来。见下面 :149 那段。 */
.stage { position: relative; padding: 28px 34px 42px; gap: 20px; }

/* 账号路由有自己的极光背景（base.css:238），不叠加，避免两套光打架 */
/* 两块光都用 inset: 0 的满幅盒子，靠「渐变圆心落在盒子外面」来表达
   光源在画面之外。不用带负 inset 的小盒子，也不用 border-radius：

   · 负 inset 会把文档撑出去 —— right: -10vw 让 scrollWidth 变成 110vw，
     整页多一条横向滚动条；bottom: -18vh 同理在外壳下面多出一段空滚动区。
     （向左 / 向上溢出不可滚动所以看不出来，向右 / 向下的会。）
   · 把那个小盒子改回贴边、再用 border-radius: 50% 修形状也不行：
     渐变在触到盒子边界时还远没有透明，椭圆裁切就会留下一道肉眼可见的
     硬弧线横在右上角。

   圆心可以取在绘制区域之外，渐变按数学位置照算、只绘制盒内那部分，
   于是任何边界上都不存在突变。半径用绝对长度而非百分比：百分比会跟着
   盒高走，截图把外壳放平到内容高度时光晕会被拉成另一个样子。 */
body:not([data-theme="auth"]) .stage::before,
body:not([data-theme="auth"]) .stage::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

/* 右上尾灯红 */
body:not([data-theme="auth"]) .stage::before {
  background: radial-gradient(circle 680px at 104% -60px,
                              var(--ambient-red), transparent 72%);
}

/* 左下冷蓝 */
body:not([data-theme="auth"]) .stage::after {
  background: radial-gradient(circle 640px at -4% calc(100% + 60px),
                              var(--ambient-blue), transparent 74%);
}

/* 伪元素与外壳都是 position 定位、z-index auto 时按文档顺序绘制，
   ::after 会盖到外壳上面。显式排序，别依赖顺序。 */
.app-shell { z-index: 1; }
.stage-foot { position: relative; z-index: 1; }

/* 扫描线画布：原本 opacity .4 且满饱和，是一条彩虹色的波纹，横穿顶栏。
   参考图的底衬是干净的深海军蓝加红 / 蓝两团环境光，这条彩虹和它正面冲突，
   也是整页里唯一一处跑出色系的元素。
   这里只调表现：压低不透明度并去掉大部分饱和度，让它退回成一层会动的
   微弱纹理。画布的挂载 / 卸载 / 动画全由 prism-background.js 与 router.js
   管，一行没碰，行为不变。 */
.scanner-background {
  opacity: .16;
  filter: saturate(.3);
}

/* scanner.css:22-28 把外壳背景清成 transparent 并删掉 ::before 打底，
   于是卡片直接浮在画布上：文字压在会动的波纹上，且外壳失去了边框之外的
   实体感。这里补一层半透明海军蓝作衬底 —— 画布仍然透上来，只是退到
   玻璃后面。(0,2,0) 与 scanner.css 同级，靠加载顺序胜出。 */
body.has-scanner .app-shell {
  background-color: rgb(8 15 24 / .82);
}

/* 上面那层衬底同时也挡住了 .stage 上的环境光 —— 光在外壳外面，外壳一旦
   不透明，框内就看不到红蓝了，只剩四周一圈。参考图恰恰是「光在框内、
   在卡片后面」。
   所以把同一组红蓝再画一遍到外壳自己的 ::before 上：那是 base.css:225
   定义的 inset: 0 / z-index: -1 打底层，位置正好在玻璃卡片之下、衬底之上。
   scanner.css:26 用 content: none 删掉了它，这里以同为 (0,2,1) 的选择器
   靠加载顺序接回来，并换成环境光渐变。
   圆心同样落在盒外，理由见上面 .stage 那段注释。 */
body.has-scanner .app-shell::before {
  content: "";
  background:
    radial-gradient(circle 620px at 103% -40px,
                    var(--ambient-red), transparent 70%),
    radial-gradient(circle 560px at -3% calc(100% + 40px),
                    var(--ambient-blue), transparent 72%);
}

/* --------------------------------------------------------------------------
   主页背景照片

   照片挂在 .app-shell 自己的 background-image 上，压暗遮罩与红蓝光留在
   .app-shell::before。这两层的先后不是碰巧，是规范定死的：同一个元素内
   background-color 先画、background-image 后画；而 base.css:225 那个
   ::before 是 z-index: -1，在 .app-shell 用 isolation: isolate
   (base.css:210) 建起的层叠上下文里，负 z-index 子元素画在父盒背景「之上」、
   所有内容「之下」。于是自下而上恰好是：
     壳底色 → 照片 → 遮罩 + 红蓝光 → 卡片与文字。
   不额外插 DOM，也不用 filter —— filter 会给祖先建立包含块，八宫格那面
   preserve-3d 的墙会直接塌回平面。

   命中用 :has(.grid8)，不用 .view-dashboard。主页有两套分支：home.js:431
   有车时渲染 .view-body.view-dashboard，home.js:416 没车时只渲染
   .view-body —— 后者同样是主页，刚注册还没加车的用户看到的就是它。两套
   分支都会走 renderGrid()，而八宫格全站只有主页渲染，所以 .grid8 是唯一
   同时覆盖这两种状态的结构标记。(/wallet 与 /report 也有 .dashboard，
   但没有 .grid8，不会被误伤。)

   特异度：(0,3,1)，压过上面 :135 那条 body.has-scanner .app-shell (0,2,1)。

   图片来源：用户提供的照片，收进 assets/home-bg.jpg，只动了一处 ——
   原图左侧烤进了一块仪表读数：「VEHICLE STATUS / POWER·TORQUE·ENERGY
   三条进度条 / > ALL SYSTEMS NORMAL」。车况类产品把这种「全系统正常」的
   分系统读数常驻在真实 UI 背后，用户无从分辨哪些像素是照片、哪些是程序
   输出，正是 README:98-108 表述规范要挡的过度声称，所以那块做了局部高斯
   模糊（sigma 16 + 羽化，见 cm-shot/mkbg.js，可复现）。照片其余部分
   —— 灯框、车身、尾灯、地面倒影 —— 一个像素没动。
   -------------------------------------------------------------------------- */
body.has-scanner:has(.grid8) .app-shell {
  background-image: url("../assets/home-bg.jpg");
  background-repeat: no-repeat;
  background-size: cover;
  /* 纵向取 58%：源图上缘是空天花板、下缘是地面倒影，58% 把灯框与车身
     提到视线高度。横向 50% 时不裁切 —— 1480px 的壳宽正好是 1024 源宽的
     1.445 倍，cover 的缩放系数由宽度决定，左右刚好铺满。手机档窄得多，
     裁切方向反过来由宽度主导，另给一套，见文件末尾 ≤800px 那段。 */
  background-position: 50% 58%;
}

body.has-scanner:has(.grid8) .app-shell::before {
  content: "";
  /* 四层，自上而下：右上尾灯红、左下冷蓝、横向压暗、纵向压暗。
     红蓝两层与 :147 那份同源，照片本身就有红色尾灯与冷白灯框，两者同色系。

     压暗必须留在这里、不能烤进图片：.section 在 views.css:26 只有 flex 与
     gap，没有任何背景，所以「待办 / 全部功能 / 服务」这些标题是直接压在
     背景上的，而照片的灯框高光接近纯白。遮罩写在 CSS 里，数值可调、可评审，
     换图也不用重新出图。
     横向那层左重右轻：左列是仪表卡与标题密集区，右列的八宫格格子自身有
     80~90% 不透明的底，能自己兜住文字。
     但右端不能真的轻到底 —— 见下面 .54 那行的说明。 */
  background:
    radial-gradient(circle 620px at 103% -40px,
                    var(--ambient-red), transparent 70%),
    radial-gradient(circle 560px at -3% calc(100% + 40px),
                    var(--ambient-blue), transparent 72%),
    /* 右端 .36 → .54：右上角是照片的灯框，也是 .section-head 里那行英文小字
       （All Modules，12px / #a3b9c2）所在的位置，而 .section-head 没有任何
       自己的底。实测这行字笔画背后的像素是 rgb(65,65,78)，对比度 4.07:1，
       低于 WCAG AA 对小字的 4.5:1。

       改右端而不是整体压暗，是量出来的：关掉照片这行字回到 7.33:1，关掉
       红辉光只回到 4.24:1 —— 亏空 3.26 里照片占了 3.26，辉光占 0.17，所以
       要动的是照片这一侧。三种改法都测过（cm-shot/bgfix.js）：
         右端 .36→.54   4.88:1  整屏平均亮度 −0.5
         右上角椭圆遮罩  4.21:1  −0.3，仍不达标
         顶部整条 .58→.74  4.39:1  −0.4，仍不达标
       只有第一种过线，代价也最小，灯框与车身该亮的地方照样亮。

       另外这行字所在的 .section-head 会随内容滚动、而背景画在不滚动的
       .app-shell 上，所以照片是从文字底下滑过去的 —— 五个滚动位置都量过，
       最差的一格就是滚到顶时的这里。 */
    linear-gradient(90deg, rgb(5 11 18 / .62) 0%,
                           rgb(5 11 18 / .22) 46%,
                           rgb(5 11 18 / .54) 100%),
    linear-gradient(180deg, rgb(5 11 18 / .58) 0%,
                            rgb(5 11 18 / .32) 38%,
                            rgb(5 11 18 / .72) 100%);

  /* base.css:234-236 在不支持 backdrop-filter 的浏览器上把这一层压到 .45。
     那条是为原来的纯渐变打底写的；叠到照片上就等于连遮罩一起削掉 45%，
     标题会直接压在灯框高光上。这里固定回 1。
     本条 (0,3,2) 已经压过它的 (0,1,1)，@supports 不加特异度。 */
  opacity: 1;
}

/* --------------------------------------------------------------------------
   故障灯速查背景

   .light-tools 只在 /lights 出现，因此这里不会改动主页、车库或报告页。
   图片保留为独立资源，遮罩留在 CSS 中，便于在不同屏幕比例下维持文字和
   红 / 黄状态色的辨识度。
   -------------------------------------------------------------------------- */
body.has-scanner:has(.light-tools) .app-shell {
  background-image: url("../assets/warning-lights-bg.png");
  background-repeat: no-repeat;
  background-size: cover;
  background-position: 50% 54%;
}

body.has-scanner:has(.light-tools) .app-shell::before {
  content: "";
  background:
    linear-gradient(90deg,
      rgb(2 8 15 / .48) 0%,
      rgb(2 8 15 / .30) 22%,
      rgb(2 8 15 / .42) 78%,
      rgb(2 8 15 / .52) 100%),
    linear-gradient(180deg,
      rgb(2 8 15 / .56) 0%,
      rgb(2 8 15 / .42) 38%,
      rgb(2 8 15 / .72) 100%);
  opacity: 1;
}

/* --------------------------------------------------------------------------
   我的车库背景

   激活车辆卡是车库概览页的独有结构标记。添加 / 编辑车辆表单不命中，
   避免较长表单叠在图片细节上；主页、速查和其他业务页也不受影响。
   -------------------------------------------------------------------------- */
body.has-scanner:has(.vehicle-card[data-active="true"]) .app-shell {
  background-image: url("../assets/garage-bg.png");
  background-repeat: no-repeat;
  background-size: cover;
  background-position: 50% 50%;
}

body.has-scanner:has(.vehicle-card[data-active="true"]) .app-shell::before {
  content: "";
  background:
    linear-gradient(90deg,
      rgb(2 8 16 / .42) 0%,
      rgb(2 8 16 / .16) 18%,
      rgb(2 8 16 / .28) 82%,
      rgb(2 8 16 / .46) 100%),
    linear-gradient(180deg,
      rgb(2 8 16 / .50) 0%,
      rgb(2 8 16 / .24) 34%,
      rgb(2 8 16 / .64) 100%);
  opacity: 1;
}

/* prism.css 与 scanner.css 是同一套结构、同一个问题：(0,1,1) 重设 body
   背景、(0,2,0) 清空外壳背景、(0,2,1) 用 content: none 删掉外壳打底层。

   区别在于 prism.css:22 那条「把画布压到 .3」的规则要求 :has(.auth-landing)，
   而 router.js:163-164 只在 /register 与 /about 挂 prism，landing 走的是
   两者皆 false 的分支、根本不带 has-login-prism —— 那条选择器永不命中。
   于是这两页的画布一直是满不透明度，呈暖橄榄色，和其余路由的深海军蓝
   不是一个色系。/about 是 theme=app，本该和主页同一套底衬。

   这里只接管 /about：/register 是 theme=auth，有自己成套的账号视觉，
   :not([data-theme="auth"]) 把它整个排除在外，一个字节不动。
   prism.css 本身也不碰 —— 那几条是画布自己的挂载契约。 */
body.has-login-prism:not([data-theme="auth"]) {
  background-color: #080f16;
  background-image: linear-gradient(135deg, #050b16 0%, #0b1628 48%, #050914 100%);
}

/* 与 .scanner-background 同样的处理：退成一层会动的微弱纹理。
   画布的挂载 / 卸载 / 动画全在 prism-background.js 与 js/effects/prism.js，
   一行没碰。 */
body:not([data-theme="auth"]) .login-prism-background {
  opacity: .16;
  filter: saturate(.3);
}

body.has-login-prism:not([data-theme="auth"]) .app-shell {
  background-color: rgb(8 15 24 / .82);
}

/* 框内的红蓝，与 has-scanner 那份同源，理由见上面 :149 */
body.has-login-prism:not([data-theme="auth"]) .app-shell::before {
  content: "";
  background:
    radial-gradient(circle 620px at 103% -40px,
                    var(--ambient-red), transparent 70%),
    radial-gradient(circle 560px at -3% calc(100% + 40px),
                    var(--ambient-blue), transparent 72%);
}

/* /register 的免责声明在外壳之外，直接压在 prism 画布上。画布是动的，
   白色亮斑扫过页脚时这段文字几乎读不出来 —— 对比度随动画帧摆动。
   prism.css:21-24 本来就是为这件事写的（注释原文是 "Keep the landing
   page's unframed text readable over the bright prism"），但它的选择器
   要求 :has(.auth-landing)，永不命中，见上面那段。

   这里不去压画布：那是账号页的视觉主体，压了就把 /register 的设计改了。
   只在文字自己底下垫一层暗色，对比度就不再依赖画布此刻画到哪一帧。
   /about 的画布已经在上面被调暗，这层对它是无害的冗余。 */
body.has-login-prism .stage-foot {
  padding: 14px 20px;
  border-radius: var(--radius);
  background: rgb(6 12 18 / .74);
}

/* ==========================================================================
   外壳
   ========================================================================== */

.app-shell {
  max-width: var(--app-width);
  height: min(820px, calc(100vh - 110px));
  min-height: 650px;
  border: 1px solid var(--accent-line);
  border-radius: 20px;
  background: rgb(8 16 23 / .72);
  box-shadow: 0 30px 90px rgb(0 0 0 / .5), inset 0 1px 0 rgb(255 255 255 / .12);
}

/* 原先这条渐变的第三个色标是红棕色，它落在 z-index:-1 的打底层上，
   等于在所有卡片背后压了一块暖色，把冷色调整体拉脏。改为纯冷色，
   暖色一律由环境光单独提供，不混进打底层。

   这条是 (0,1,1)，上面 :149 那条 body.has-scanner 前缀的是 (0,2,1)，
   所以挂了 scanner 的路由（除 landing / login / register / about 之外
   全部）走那一份带辉光的；这一份只剩 prism 的 register / about 在用。 */
.app-shell::before {
  background: linear-gradient(145deg, rgb(22 44 56 / .92), rgb(8 16 23 / .9) 52%, rgb(11 26 38 / .94));
}

.appbar {
  height: var(--appbar-h);
  padding: 0 42px;
  background: rgb(6 14 21 / .56);
  border-bottom-color: rgb(255 255 255 / .14);
}

.appbar-title {
  color: #fff;
  font-size: 1.05rem;
  letter-spacing: .08em;
}
.appbar-title::first-letter { color: inherit; }
.appbar-actions { gap: 14px; }

/* React Bits FoldText 的原生适配：顶栏每次切换路由时，标题按字符从顶部
   铰链展开。保留白色品牌规范，并用语义副本避免屏幕阅读器逐字朗读。 */
.appbar-title .fold-text {
  display: inline-block;
  color: #fff;
  line-height: 1;
  white-space: pre;
  user-select: text;
}

.appbar-title .fold-text-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.appbar-title .fold-text-visual { display: inline; }

.appbar-title .fold-text-segment {
  display: inline-block;
  line-height: inherit;
  perspective: 700px;
  transform-style: preserve-3d;
  vertical-align: baseline;
}

.appbar-title .fold-text-piece {
  position: relative;
  display: inline-block;
  color: inherit;
  line-height: inherit;
  transform-origin: 50% 0%;
  transform-style: preserve-3d;
  backface-visibility: hidden;
  will-change: transform, opacity;
  animation: appbar-fold-piece .65s cubic-bezier(.215, .61, .355, 1) var(--fold-delay) both;
}

.appbar-title .fold-text-piece::after {
  content: "";
  position: absolute;
  inset: -.08em -.02em;
  pointer-events: none;
  border-radius: .08em;
  background: linear-gradient(180deg,
    rgb(0 0 0 / .58) 0%,
    rgb(0 0 0 / .22) 42%,
    rgb(255 255 255 / .26) 100%);
  mix-blend-mode: multiply;
  animation: appbar-fold-crease .65s ease-out var(--fold-delay) both;
}

@keyframes appbar-fold-piece {
  from { opacity: 0; transform: rotateX(-92deg); }
  to { opacity: 1; transform: rotateX(0); }
}

@keyframes appbar-fold-crease {
  from { opacity: .55; }
  to { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .appbar-title .fold-text-piece,
  .appbar-title .fold-text-piece::after {
    animation: none;
  }

  .appbar-title .fold-text-piece {
    opacity: 1;
    transform: none;
  }

  .appbar-title .fold-text-piece::after { opacity: 0; }
}

.tabbar {
  height: 68px;
  padding: 0 180px;
  background: rgb(6 14 21 / .72);
  border-top-color: rgb(255 255 255 / .14);
}

.tab { font-size: .78rem; letter-spacing: .04em; }
.tab[aria-current="page"] { background: transparent; position: relative; }
.tab[aria-current="page"]::after {
  content: "";
  position: absolute;
  bottom: 10px;
  width: 28px;
  height: 2px;
  background: var(--accent);
}

.view { padding: 42px 52px 54px; }
.view-body { max-width: 1360px; margin: 0 auto; }

/* ==========================================================================
   桌面外壳：导航并入顶栏
   ========================================================================== */

@media (min-width: 801px) {
  .app-shell { display: block; }
  .appbar { position: relative; }

  .tabbar {
    position: absolute;
    top: 0;
    right: 120px;
    width: 430px;
    height: var(--appbar-h);
    padding: 0;
    border: 0;
    background: transparent;
    /* components.css:1002-1007 给 .tabbar 上了玻璃：模糊 + 投影 + 高光。
       这里背景已经透明，那层模糊会在顶栏上留下一块可见的灰影矩形。
       背景清掉时这三项必须一起清掉。 */
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    box-shadow: none;
    background-image: none;
  }

  .tab { position: relative; }
  .tab[aria-current="page"]::after { bottom: 17px; }
  .view { height: calc(100% - var(--appbar-h)); }
}

/* ==========================================================================
   卡片与网格的通用表现
   ========================================================================== */

.card, .dashboard, .metric, .list, .grid8-cell, .promo, .vehicle-card,
.chart-block, .question, .tech-card, .post, .statusbar {
  border-color: rgb(56 189 248 / .24);
}

/* 报告页三张大型 SVG 只缩放图表本体；标题、解释文字与费用统计卡保持原尺寸。
   雷达图由原 1/6 放大三倍至 1/2，费用分布与纵向趋势由原 1/4 放大三倍至 3/4。 */
@media (min-width: 801px) {
  .report-radar-chart > svg {
    width: 50%;
    margin-inline: auto;
  }

  :is(.repair-cost-chart, .report-trend-chart) > svg {
    width: 75%;
    margin-inline: auto;
  }
}

.report-radar-chart {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background:
    radial-gradient(circle at 50% 45%, rgb(22 112 182 / .22), transparent 42%),
    radial-gradient(circle at 18% 8%, rgb(75 165 224 / .09), transparent 32%),
    linear-gradient(155deg, rgb(14 37 52 / .97), rgb(5 20 31 / .99));
  border-color: rgb(96 176 226 / .36);
  box-shadow:
    inset 0 1px 0 rgb(211 239 255 / .11),
    inset 0 0 55px rgb(22 100 164 / .08),
    0 18px 34px rgb(0 7 13 / .24);
}

.report-radar-chart::after {
  content: "";
  position: absolute;
  inset: 9px;
  z-index: -1;
  border: 1px solid rgb(76 155 207 / .16);
  border-radius: calc(var(--radius-lg) - 6px);
  pointer-events: none;
}

.report-radar-chart > .card-title {
  position: relative;
  z-index: 2;
  color: #eef8ff;
  font-weight: 850;
  letter-spacing: .04em;
  text-shadow:
    0 1px 0 #9cbfd3,
    0 3px 0 #34586b,
    0 7px 13px rgb(0 0 0 / .5);
}

.report-radar-svg {
  display: block;
  height: auto;
  overflow: visible;
  filter: drop-shadow(0 13px 15px rgb(0 5 10 / .38));
}

.report-radar-fill-start { stop-color: #168cff; stop-opacity: .78; }
.report-radar-fill-end { stop-color: #0b5fcb; stop-opacity: .42; }
.report-radar-depth-start { stop-color: #0879e7; stop-opacity: .32; }
.report-radar-depth-end { stop-color: #031b32; stop-opacity: .08; }
.report-radar-node-shine { stop-color: #e5fbff; }
.report-radar-node-mid { stop-color: #47c6ff; }
.report-radar-node-edge { stop-color: #0874df; }

.report-radar-ground-shadow {
  fill: rgb(0 3 8 / .56);
  filter: url(#reportRadarShadow);
}

.report-radar-grid-depth polygon {
  fill: none;
  stroke: rgb(38 119 180 / .24);
  stroke-width: .8;
  stroke-dasharray: 3 4;
}

.report-radar-grid polygon,
.report-radar-grid line {
  fill: none;
  stroke: rgb(105 173 225 / .56);
  stroke-width: 1;
}

.report-radar-grid polygon:last-of-type {
  stroke: rgb(137 198 240 / .72);
  stroke-width: 1.35;
}

.report-radar-area-shadow {
  fill: rgb(0 5 15 / .62);
  filter: url(#reportRadarShadow);
}

.report-radar-side {
  fill: url(#reportRadarDepth);
  stroke: rgb(30 137 230 / .22);
  stroke-width: .55;
}

.report-radar-area {
  fill: url(#reportRadarFill);
  stroke: #4bc5ff;
  stroke-width: 4;
  stroke-linejoin: round;
}

.report-radar-node-shadow {
  fill: rgb(0 4 12 / .58);
  filter: url(#reportRadarShadow);
}

.report-radar-node {
  fill: url(#reportRadarNode);
  stroke: #56cfff;
  stroke-width: 1.3;
}

.report-radar-node-glint { fill: rgb(242 254 255 / .96); }

.report-radar-ticks {
  fill: #b8d8ec;
  font-size: 13px;
  font-weight: 650;
  paint-order: stroke;
  stroke: rgb(4 18 29 / .86);
  stroke-width: 3px;
}

.report-radar-leaders polyline {
  fill: none;
  stroke: #58adff;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.report-radar-leaders > circle {
  fill: #58adff;
  filter: url(#reportRadarGlow);
}

.report-radar-axis-name {
  fill: #edf8ff;
  font-size: 20px;
  font-weight: 760;
  letter-spacing: .035em;
  paint-order: stroke;
  stroke: rgb(2 13 23 / .82);
  stroke-width: 4px;
  text-shadow: 0 4px 8px rgb(0 0 0 / .42);
}

.report-radar-axis-missing {
  fill: #89aabe;
  font-size: 11px;
  font-weight: 650;
}

.histogram-3d-frame { color: rgb(86 140 181 / .76); }
.histogram-3d-floor { color: rgb(49 136 199 / .72); }
.histogram-3d-front {
  fill: currentColor;
  fill-opacity: .62;
  stroke: currentColor;
  stroke-opacity: .88;
  stroke-width: .65;
}
.histogram-3d-side { fill: currentColor; fill-opacity: .34; }
.histogram-3d-top {
  fill: currentColor;
  fill-opacity: .92;
  stroke: rgb(205 241 255 / .72);
  stroke-width: .55;
}
.histogram-3d-reflection { fill: currentColor; fill-opacity: .09; }

/* 纵向趋势：参考车载诊断仪表的分层结构，数据仍由原 CUSUM 结果驱动。 */
.report-trend-chart {
  position: relative;
  isolation: isolate;
  background:
    radial-gradient(circle at 76% 18%, rgb(33 91 127 / .2), transparent 38%),
    linear-gradient(155deg, rgb(18 39 51 / .96), rgb(7 23 33 / .98));
  border-color: rgb(116 190 236 / .38);
  box-shadow:
    inset 0 1px 0 rgb(209 239 255 / .12),
    inset 0 -18px 38px rgb(0 8 15 / .32),
    0 20px 34px rgb(0 7 13 / .22);
}

.trend-3d-heading { position: relative; z-index: 2; }

.trend-3d-heading .card-title {
  color: #d8efff;
  font-weight: 850;
  letter-spacing: .035em;
  text-shadow:
    0 1px 0 #8bbbd4,
    0 3px 0 #31576d,
    0 7px 13px rgb(0 0 0 / .58),
    0 0 16px rgb(112 199 255 / .22);
}

.trend-3d-status {
  position: relative;
  padding: .5rem .9rem;
  border: 1px solid currentColor;
  border-radius: 11px;
  font-weight: 800;
  letter-spacing: .035em;
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / .18),
    inset 0 -3px 0 rgb(0 0 0 / .28),
    0 7px 13px rgb(0 0 0 / .38),
    0 0 14px color-mix(in srgb, currentColor 22%, transparent);
}

.trend-3d-svg {
  overflow: visible;
  filter: drop-shadow(0 12px 11px rgb(0 5 10 / .44));
}

.trend-3d-panel-start { stop-color: #173b51; }
.trend-3d-panel-end { stop-color: #071b2a; }
.trend-3d-floor-start { stop-color: #12354d; stop-opacity: .82; }
.trend-3d-floor-end { stop-color: #061725; stop-opacity: .96; }
.trend-3d-line-start { stop-color: #ffe477; }
.trend-3d-line-end { stop-color: #ff9d00; }
.trend-3d-point-shine { stop-color: #fff8bd; }
.trend-3d-point-mid { stop-color: #ffc21c; }
.trend-3d-point-edge { stop-color: #dd7900; }

.trend-3d-wall-shadow { fill: rgb(0 4 9 / .68); }
.trend-3d-wall {
  fill: url(#trend3dPanel);
  stroke: rgb(125 203 249 / .48);
  stroke-width: 1.2;
}
.trend-3d-wall-highlight {
  fill: none;
  stroke: rgb(197 232 255 / .68);
  stroke-width: .9;
}
.trend-3d-grid line {
  stroke: rgb(96 163 205 / .32);
  stroke-width: .85;
}
.trend-3d-floor polygon {
  fill: url(#trend3dFloor);
  stroke: rgb(93 177 230 / .42);
  stroke-width: .8;
}
.trend-3d-floor line {
  stroke: rgb(79 158 209 / .3);
  stroke-width: .65;
}
.trend-3d-floor-edge {
  fill: none;
  stroke: rgb(163 219 252 / .7);
  stroke-width: 1;
}
.trend-3d-y-plate {
  fill: #0d2a3d;
  stroke: rgb(109 190 240 / .52);
  stroke-width: 1;
}
.trend-3d-x-plate {
  fill: #0a2233;
  stroke: rgb(122 202 250 / .58);
  stroke-width: 1;
}
.trend-3d-axis-highlight {
  fill: none;
  stroke: rgb(210 239 255 / .68);
  stroke-width: .85;
}
.trend-3d-baseline line {
  stroke: #b7d5eb;
  stroke-width: 1.6;
  stroke-dasharray: 6 5;
  opacity: .84;
}
.trend-3d-series path { fill: none; stroke-linecap: round; stroke-linejoin: round; }
.trend-3d-series-shadow {
  stroke: rgb(0 0 0 / .82);
  stroke-width: 10;
  filter: url(#trend3dLineShadow);
}
.trend-3d-series-edge { stroke: #b85e00; stroke-width: 8; }
.trend-3d-series-line { stroke: url(#trend3dLine); stroke-width: 5.5; }
.trend-3d-series-shine { stroke: rgb(255 241 156 / .9); stroke-width: 1.35; }
.trend-3d-point-shadow { fill: rgb(0 0 0 / .55); filter: url(#trend3dLineShadow); }
.trend-3d-point {
  fill: url(#trend3dPoint);
  stroke: #ffc328;
  stroke-width: 1.4;
  filter: url(#trend3dPointGlow);
}
.trend-3d-point-glint { fill: rgb(255 255 222 / .94); }
.trend-3d-alarm-shadow,
.trend-3d-alarm-line {
  fill: none;
  stroke-width: 2;
  stroke-dasharray: 6 5;
}
.trend-3d-alarm-shadow { stroke: rgb(0 0 0 / .75); stroke-width: 5; }
.trend-3d-alarm-line { stroke: #ff626a; }
.trend-3d-alarm-glow { fill: rgb(255 66 78 / .22); filter: url(#trend3dPointGlow); }
.trend-3d-alarm-ring {
  fill: none;
  stroke: #ff626a;
  stroke-width: 4;
}
.trend-3d-alarm text {
  fill: #ff7780;
  font-size: 13px;
  font-weight: 800;
  paint-order: stroke;
  stroke: rgb(10 22 30 / .8);
  stroke-width: 3px;
}
.trend-3d-axis-labels {
  fill: #c9e5f7;
  font-size: 12px;
  font-weight: 750;
  paint-order: stroke;
  stroke: rgb(2 12 19 / .72);
  stroke-width: 2px;
}
.trend-3d-note {
  position: relative;
  z-index: 2;
  margin: .35rem .2rem .1rem;
  padding: 1rem 1.15rem;
  border: 1px solid rgb(108 190 240 / .36);
  border-radius: 14px;
  color: #bad5e6;
  line-height: 1.72;
  background: linear-gradient(155deg, rgb(17 46 65 / .94), rgb(7 25 37 / .97));
  box-shadow:
    inset 0 1px 0 rgb(216 240 255 / .09),
    inset 0 -4px 0 rgb(0 8 14 / .34),
    0 11px 20px rgb(0 5 10 / .32);
}

@media (max-width: 520px) {
  .trend-3d-heading .card-title { font-size: 1.1rem; }
  .trend-3d-status { padding: .42rem .68rem; }
  .trend-3d-note { padding: .85rem .95rem; }
}

.section { gap: 16px; }
.section-head h2 { font-size: 1.1rem; }
.stage-foot { max-width: 900px; }
.metric-value { text-shadow: 0 0 14px rgb(56 189 248 / .16); }

/* .section-head 右侧那行英文小字（All Modules / 合作内容…）在首页要比
   base.css:340 的 --fg-subtle #a3b9c2 亮一档。

   原因是这行字的处境比别处都差：12px、没有自己的底（views.css:32 的
   .section-head 只有 flex），而且它跟着内容滚、背景却画在不滚的
   .app-shell 上 —— 照片是从它笔画底下滑过去的，灯框那条高光迟早会经过。

   只在有照片的首页抬（body.has-scanner:has(.grid8)），别的路由仍用原
   token，不动全局。

   为什么不是继续压暗照片：390px 档实测过两条路（cm-shot/bgfix-head.js）
     这行字提到 #b8ccd4   5.79:1   整屏平均亮度 −0.1
     手机档遮罩再压到 .68  4.35:1   −0.4，而且仍然不达标
   压暗那条既更伤照片、又没解决问题 —— 因为每个宽度裁切不同，灯框落点和
   最差滚动位置都不一样（1600 档在 0px、420 档在 741px、390 档在 1052px），
   逐档加遮罩是在用整张照片的通透度去换一行 12px 的小字。

   遮罩那两处该做的仍然要做：390px 档单靠提亮到不了线（笔画背后
   rgb(101,97,105)，3.64:1），提亮 + 遮罩一起才过。两层是互补的。
   七个宽度 × 16 个滚动位置全部通过：node bgscroll.js <WxH> 16 */
body.has-scanner:has(.grid8) .section-head .text-xs { color: #b8ccd4; }

/* ==========================================================================
   主仪表卡
   ========================================================================== */

.dashboard {
  position: relative;
  overflow: hidden;
  background: linear-gradient(120deg, rgb(21 42 52 / .92), rgb(8 19 28 / .82));
  box-shadow: inset 0 0 0 1px rgb(56 189 248 / .05), 0 18px 42px rgb(0 0 0 / .28);
}

.dash-head {
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: 100%;
  text-align: center;
}

.dash-head-line {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

.dash-head-cn { font-size: .95rem; font-weight: 600; color: var(--fg); }

.dash-head-en {
  font-size: .68rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--fg-faint);
}

.dash-head-note { font-size: .7rem; color: var(--fg-faint); }

/* 呼吸点：纯装饰，不代表任何实时状态，所以用中性的强调色而非语义色 */
.dash-pulse {
  width: 7px;
  height: 7px;
  flex: none;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 0 var(--accent-line);
  animation: dash-pulse 2.6s var(--ease) infinite;
}

@keyframes dash-pulse {
  0%   { box-shadow: 0 0 0 0 var(--accent-line); }
  70%  { box-shadow: 0 0 0 7px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}

.grade-pill { display: inline-flex; align-items: center; gap: 7px; }
.grade-en {
  font-size: .62rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  opacity: .72;
}

/* 空态 CTA：背景与描边由 specular-button.css 用 !important 定义，
   这里只排版，不去抢那层材质。 */
.dash-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 12px 20px;
}

.cta-en {
  font-size: .64rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  opacity: .66;
}

/* ==========================================================================
   四格指标
   ========================================================================== */

.metric-grid { grid-template-columns: repeat(2, 1fr); gap: 14px; }

.metric {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 18px 18px 16px;
  background: rgb(11 27 37 / .78);
}

.metric-label { font-size: .74rem; color: var(--fg-subtle); }

.metric-label-en {
  font-size: .58rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--fg-faint);
}

.metric-value { margin-top: 4px; }

/* 「平稳 / 趋势恶化 / 数据不足」是词不是数，用数字字号会撑破格子 */
.metric-value-word { font-size: 1.05rem; }

.metric-unit { margin-left: 3px; font-size: .68rem; color: var(--fg-faint); }
.metric-note { margin-top: 2px; font-size: .64rem; line-height: 1.5; color: var(--fg-faint); }

/* ==========================================================================
   功能网格
   ========================================================================== */

/* 每格一个色调键。--tile 只驱动图标、描边与辉光 —— 不驱动任何文字颜色，
   这样新增色调不需要重做对比度核算。 */
.grid8-cell { --tile: var(--accent); }
.grid8-cell[data-accent="warn"]   { --tile: var(--warn); }
.grid8-cell[data-accent="ok"]     { --tile: var(--ok); }
.grid8-cell[data-accent="cool"]   { --tile: var(--accent); }
.grid8-cell[data-accent="danger"] { --tile: var(--danger-stroke); }
.grid8-cell[data-accent="alt"]    { --tile: var(--alt); }
.grid8-cell[data-accent="muted"]  { --tile: var(--fg-faint); }

.grid8-cell {
  position: relative;
  background: linear-gradient(180deg, rgb(18 39 66 / .8), rgb(8 22 41 / .9));
}

.grid8-cell svg { color: var(--tile); }

/* 只有可点的格子才有 hover 反馈。components.css:1078 起的 hover 规则
   已经排除了 aria-disabled，这里保持同样的约定。 */
.grid8-cell:not([aria-disabled="true"]):hover {
  border-color: color-mix(in srgb, var(--tile) 62%, transparent);
  box-shadow: 0 12px 28px rgb(0 0 0 / .34), 0 0 22px color-mix(in srgb, var(--tile) 16%, transparent);
}

.cell-cn { font-weight: 600; color: var(--fg); }

.cell-en {
  font-size: .6rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--fg-faint);
}

.cell-desc { font-size: .7rem; line-height: 1.6; color: var(--fg-subtle); }
.cell-desc-en { font-size: .6rem; letter-spacing: .04em; color: var(--fg-faint); }

/* 小格子放不下副标题与说明，默认只留中文名；下面按格子尺寸逐级放开。 */
.grid8-cell .cell-en,
.grid8-cell .cell-desc,
.grid8-cell .cell-desc-en { display: none; }

/* 移动端的大格（2×2）与宽格（1×2）有空间，放开一部分 */
.grid8-cell:first-child .cell-en,
.grid8-cell:first-child .cell-desc,
.grid8-cell:nth-child(7) .cell-en { display: block; }

.cell-go { display: none; }

.cell-go-off {
  display: block;
  font-size: .58rem;
  letter-spacing: .06em;
  color: var(--fg-faint);
}

/* components.css:595 的 .grid8-cell:first-child svg 是 (0,2,1)，会把
   尾部箭头也放大到 2.75rem。本条同为 (0,2,1)，靠加载顺序压过它。 */
.grid8-cell .cell-go svg { width: 1rem; height: 1rem; color: var(--fg-faint); }

/* ==========================================================================
   底部状态条
   ========================================================================== */

/* .statusbar 不在 components.css 的玻璃清单里，材质在这里自备，
   并同样提供无 backdrop-filter 的实色回退。 */
.statusbar {
  display: grid;
  grid-template-columns: minmax(200px, .85fr) minmax(320px, 2.3fr) minmax(170px, .8fr);
  gap: 22px;
  align-items: center;
  padding: 18px 22px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background-color: var(--card);
  background-image: var(--glass-highlight);
  box-shadow: var(--glass-shadow);
}

@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .statusbar {
    background-color: var(--glass-bg);
    -webkit-backdrop-filter: blur(var(--glass-blur));
    backdrop-filter: blur(var(--glass-blur));
  }
}

.statusbar-veh { display: flex; align-items: center; gap: 14px; }

/* 首页问候与车型名称是主视觉锚点，放大后让左上区域更饱满。 */
.home-greeting {
  display: block;
  font-size: 1.05rem;
  line-height: 1.35;
  letter-spacing: .025em;
}

.home-vehicle-title {
  margin: .2rem 0 0;
  font-size: 1.65rem;
  line-height: 1.18;
  font-weight: 800;
  letter-spacing: .02em;
  text-shadow: 0 3px 14px rgb(0 0 0 / .28);
}

/* GradientText 的原生实现：沿用组件的 300% 渐变、首尾同色回环与悬停暂停。 */
.gradient-text {
  --gradient-text-start: #dff6ff;
  --gradient-text-mid: #52c7ff;
  --gradient-text-end: #f5fbff;
  background-image: linear-gradient(
    90deg,
    var(--gradient-text-start),
    var(--gradient-text-mid),
    var(--gradient-text-end),
    var(--gradient-text-start)
  );
  background-size: 300% 100%;
  background-position: 0% 50%;
  background-repeat: repeat;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  animation: home-gradient-text 6s ease-in-out infinite alternate;
  cursor: default;
}

.gradient-text:hover { animation-play-state: paused; }

@keyframes home-gradient-text {
  from { background-position: 0% 50%; }
  to { background-position: 100% 50%; }
}

@media (prefers-reduced-motion: reduce) {
  .gradient-text { animation: none; background-position: 50% 50%; }
}

@media (max-width: 520px) {
  .home-greeting { font-size: 1rem; }
  .home-vehicle-title { font-size: 1.4rem; }
}

.statusbar-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  flex: none;
  border-radius: 12px;
  border: 1px solid var(--accent-line);
  background: var(--accent-soft);
  color: var(--accent);
}

.statusbar-cn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: .78rem;
  font-weight: 600;
  color: var(--fg);
}

.statusbar-cn svg { width: 1rem; height: 1rem; color: var(--fg-faint); }

.statusbar-en {
  font-size: .58rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--fg-faint);
}

.statusbar-model { margin-top: 3px; font-size: .8rem; color: var(--fg-muted); }

.statusbar-live {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-top: 3px;
  font-size: .62rem;
  color: var(--fg-faint);
}

.statusbar-live .dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--ok);
}

.statusbar-vin { font-size: .62rem; letter-spacing: .04em; color: var(--fg-faint); }

.statusbar-sys { display: flex; flex-direction: column; gap: 14px; min-width: 0; }

.statusbar-sys-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  text-align: center;
}

.statusbar-sys-head .statusbar-cn {
  font-size: 1.12rem;
  letter-spacing: .08em;
  text-shadow: 0 0 18px rgb(56 189 248 / .48);
}

.statusbar-sys-head .statusbar-en {
  font-size: .62rem;
  letter-spacing: .14em;
  text-transform: none;
  color: var(--accent);
}

.capability-radar-shell {
  position: relative;
  isolation: isolate;
  width: 100%;
  max-width: 760px;
  margin-inline: auto;
  padding: 22px 24px 68px;
  overflow: hidden;
  border: 1px solid rgb(56 189 248 / .42);
  border-radius: 18px;
  background:
    radial-gradient(circle at 50% 46%, rgb(16 98 210 / .2), transparent 45%),
    linear-gradient(180deg, rgb(3 14 29 / .86), rgb(2 10 22 / .72));
  box-shadow:
    inset 0 0 48px rgb(20 103 214 / .09),
    0 18px 38px rgb(0 0 0 / .28);
}

/* 桌面端将整张能力雷达卡等比收至原尺寸的 2/3；移动端仍保持满宽可读。 */
@media (min-width: 801px) {
  .capability-radar-shell {
    width: 66.6667%;
    max-width: 506.667px;
    padding: 14.667px 16px 45.333px;
    border-radius: 12px;
    box-shadow:
      inset 0 0 32px rgb(20 103 214 / .09),
      0 12px 25px rgb(0 0 0 / .28);
  }

  .statusbar-sys .capability-radar-shell::before {
    inset: 6.667px;
    background:
      linear-gradient(90deg, var(--accent) 0 25px, transparent 25px) 0 0 / 60px 1px no-repeat,
      linear-gradient(180deg, var(--accent) 0 25px, transparent 25px) 0 0 / 1px 60px no-repeat,
      linear-gradient(270deg, var(--accent) 0 25px, transparent 25px) 100% 100% / 60px 1px no-repeat,
      linear-gradient(0deg, var(--accent) 0 25px, transparent 25px) 100% 100% / 1px 60px no-repeat;
  }

  .statusbar-sys .capability-radar-tag {
    top: 12px;
    right: 13.333px;
    padding: 3.333px 6.667px;
    border-radius: 3.333px;
    font-size: .367rem;
    letter-spacing: .08em;
  }

  .statusbar-sys .capability-radar-score {
    right: 16px;
    bottom: 12px;
    gap: 3.333px;
    padding: 5.333px 8px;
    border-radius: 4.667px;
  }

  .statusbar-sys .capability-radar-score > span {
    margin-right: 3.333px;
    font-size: .4rem;
  }

  .statusbar-sys .capability-radar-score strong { font-size: 1.2rem; }
  .statusbar-sys .capability-radar-score small { font-size: .453rem; }
}

.capability-radar-shell::before,
.capability-radar-shell::after {
  content: "";
  position: absolute;
  pointer-events: none;
}

.capability-radar-shell::before {
  inset: 10px;
  z-index: -1;
  opacity: .45;
  background:
    linear-gradient(90deg, var(--accent) 0 38px, transparent 38px) 0 0 / 90px 1px no-repeat,
    linear-gradient(180deg, var(--accent) 0 38px, transparent 38px) 0 0 / 1px 90px no-repeat,
    linear-gradient(270deg, var(--accent) 0 38px, transparent 38px) 100% 100% / 90px 1px no-repeat,
    linear-gradient(0deg, var(--accent) 0 38px, transparent 38px) 100% 100% / 1px 90px no-repeat;
}

.capability-radar-shell::after {
  inset: 0;
  z-index: -2;
  background: linear-gradient(90deg, transparent, rgb(56 189 248 / .04), transparent);
}

.capability-radar-tag {
  position: absolute;
  top: 18px;
  right: 20px;
  padding: 5px 10px;
  border: 1px solid rgb(56 189 248 / .42);
  border-radius: 5px;
  font-size: .55rem;
  letter-spacing: .12em;
  color: var(--accent);
}

.capability-radar-svg {
  display: block;
  width: 100%;
  height: auto;
  color: #35b9ff;
  overflow: visible;
}

.capability-radar-grid { color: rgb(73 158 255 / .76); }
.capability-radar-orbit { color: var(--accent); }
.capability-radar-data { color: #35b9ff; }

.capability-radar-axis-name {
  fill: #f7fbff;
  font-size: 16px;
  font-weight: 650;
  letter-spacing: .04em;
  paint-order: stroke;
  stroke: rgb(2 10 22 / .92);
  stroke-width: 4px;
}

.capability-radar-axis-value {
  fill: #35a9ff;
  font-size: 22px;
  font-weight: 750;
  paint-order: stroke;
  stroke: rgb(2 10 22 / .92);
  stroke-width: 4px;
}

.capability-radar-area {
  transform-box: fill-box;
  transform-origin: center;
  animation: capability-radar-reveal .9s var(--ease) both;
}

.capability-radar-score {
  position: absolute;
  right: 24px;
  bottom: 18px;
  display: flex;
  align-items: baseline;
  gap: 5px;
  padding: 8px 12px;
  border: 1px solid rgb(56 189 248 / .48);
  border-radius: 7px;
  background: rgb(3 16 33 / .78);
  color: var(--accent);
  box-shadow: inset 0 0 18px rgb(56 189 248 / .08);
}

.capability-radar-score > span {
  margin-right: 5px;
  font-size: .6rem;
  color: var(--fg-subtle);
}

.capability-radar-score strong { font-size: 1.8rem; line-height: 1; }
.capability-radar-score small { font-size: .68rem; color: var(--fg-muted); }

@keyframes capability-radar-reveal {
  from { opacity: 0; transform: scale(.12); }
  to { opacity: 1; transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .capability-radar-area { animation: none; }
}

.sys-strip { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 8px; }

.sys-item {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
  padding: 8px 9px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: rgb(9 21 30 / .6);
}

/* 安全三维（制动 / 转向悬挂 / 轮胎）左侧留一条标识边。
   这是「这一项关乎安全」的提示，不是「这一项有问题」的告警，
   所以用中性描边色，不用 --danger。 */
.sys-item[data-safety="true"] { border-left: 2px solid var(--accent-line); }

.sys-cn {
  font-size: .68rem;
  color: var(--fg-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.sys-en {
  font-size: .5rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--fg-faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.sys-value { margin-top: 2px; font-size: .95rem; font-weight: 600; color: var(--fg); }
.sys-state { font-size: .56rem; color: var(--fg-faint); }

.statusbar-svc { display: flex; flex-direction: column; gap: 2px; }
.svc-value { margin-top: 4px; display: inline-flex; align-items: baseline; gap: 5px; }
.svc-value .num { font-size: 1.5rem; font-weight: 700; color: var(--fg); }
.svc-unit { font-size: .66rem; color: var(--fg-subtle); }
.statusbar-sub { margin-top: 2px; font-size: .62rem; line-height: 1.5; color: var(--fg-faint); }

/* ==========================================================================
   HUD 仪表取色
   ========================================================================== */

/* charts.js 把 tone class 只加在圆环那个 <g> 上，环心的 <text> 不在里面，
   所以数字一直是 views.css 里写死的青色 —— 会出现「49 分 + 红徽章 +
   青色数字」。charts.js 不在本次改动范围内，改用 home.js 抬到 .dashboard
   上的 data-tone 取色。(0,2,0) 压过 views.css 的 (0,1,0)。
   档位仍来自 last.level（规则命中），没有按分数重新分档。 */
.dashboard[data-tone="ok"]     .gauge-hud-number { color: var(--ok); }
.dashboard[data-tone="warn"]   .gauge-hud-number { color: var(--warn); }
.dashboard[data-tone="danger"] .gauge-hud-number { color: var(--danger); }

.dashboard[data-tone="ok"]     .gauge-hud-number,
.dashboard[data-tone="warn"]   .gauge-hud-number,
.dashboard[data-tone="danger"] .gauge-hud-number {
  filter: drop-shadow(0 0 6px color-mix(in srgb, currentColor 45%, transparent));
}

/* 关于页五项编号算法：标题醒目，方法标签在窄屏自然换行。 */
.algo-entry { --algo-heading-color: #80dfff; }
.algo-entry:nth-child(2) { --algo-heading-color: #d5b0ff; }
.algo-entry:nth-child(3) { --algo-heading-color: #6ee7c3; }
.algo-entry:nth-child(4) { --algo-heading-color: #ffd078; }
.algo-entry:nth-child(5) { --algo-heading-color: #ffad99; }
.algo-entry .algo-entry-head {
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 20px;
}
.algo-entry-title {
  display: flex;
  align-items: baseline;
  gap: 10px;
  min-width: 0;
  color: var(--algo-heading-color);
  font-size: 1.5rem;
  font-weight: 750;
  line-height: 1.45;
}
.algo-entry-number { flex: none; font-size: 1.15em; }
.algo-entry-title > span:last-child { min-width: 0; overflow-wrap: anywhere; }
.algo-entry-head > .badge { white-space: normal; }
@media (max-width: 600px) {
  .algo-entry-title { gap: 8px; font-size: 1.25rem; }
}

/* 首页服务标题：放大字号，用亮色强化深色卡片上的标题层级。 */
.view-dashboard .promo-title {
  color: #ffd078;
  font-size: clamp(1.25rem, 1.5vw, 1.5rem);
  font-weight: 750;
  line-height: 1.45;
  overflow-wrap: anywhere;
}
.view-dashboard .promo:nth-child(2) .promo-title { color: #7de8ff; }
.view-dashboard .promo:nth-child(3) .promo-title { color: #d5b0ff; }

/* 待办倒计时：按事项区分颜色，临期 / 逾期优先使用警示色。 */
.reminder-countdown {
  --reminder-color: #80caff;
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  padding: 7px 10px;
  border-radius: 8px;
  background: color-mix(in srgb, var(--reminder-color) 10%, transparent);
  color: var(--reminder-color);
  font-size: .75rem;
  font-weight: 500;
  line-height: 1.2;
  white-space: nowrap;
}
.reminder-countdown[data-kind="insurance"] { --reminder-color: #d5b0ff; }
.reminder-countdown[data-kind="inspection"] { --reminder-color: #6ee7c3; }
.reminder-countdown[data-state="warn"] { --reminder-color: #ffd078; }
.reminder-countdown[data-state="overdue"] { --reminder-color: #ff8d99; }
.reminder-countdown-value {
  font-size: 1.75rem;
  font-weight: 750;
  font-variant-numeric: tabular-nums;
}
@media (max-width: 520px) {
  .reminder-countdown { gap: 3px; padding: 6px 8px; }
  .reminder-countdown-value { font-size: 1.5rem; }
}

/* ==========================================================================
   桌面主页栅格
   ========================================================================== */

/* 挂在 .view-body.view-dashboard 上，不用 .view-body:has(> .dashboard)。
   后者会一并命中 /wallet（garage.js）与 /report（diagnose.js）—— 那两个
   视图也有 .dashboard 子元素，但子元素顺序完全不同，套上主页栅格后
   免责声明会被放进第一行。这是改动前就存在的错位。 */
@media (min-width: 1240px) {
  .view-body.view-dashboard {
    display: grid;
    grid-template-columns: minmax(340px, .9fr) minmax(560px, 1.6fr);
    gap: 22px;
  }

  /* 首屏一行：左仪表 / 右功能矩阵。两者等高拉伸，仪表在卡内纵向居中，
     这样右列 4 行格子决定行高时左边不会留出一块空白。 */
  .view-body.view-dashboard > header       { grid-column: 1 / -1; grid-row: 1; }
  .view-body.view-dashboard > .dashboard   { grid-column: 1; grid-row: 2; }
  .view-body.view-dashboard > .section:has(.grid8) { grid-column: 2; grid-row: 2; }

  /* 指标条改为整行四格：留在左列时会被压成 2×2，和右侧矩阵撞在一起，
     且左列会比右列高出一大截。 */
  .view-body.view-dashboard > .metric-grid { grid-column: 1 / -1; grid-row: 3; }

  /* 待办区：有记录时是 .list，空态时是 .card > .empty */
  .view-body.view-dashboard > .section:has(.list),
  .view-body.view-dashboard > .section:has(.empty) { grid-column: 1 / -1; grid-row: 4; }

  .view-body.view-dashboard > .statusbar { grid-column: 1 / -1; grid-row: 5; }
  .view-body.view-dashboard > .section:has(.promo-row) { grid-column: 1 / -1; grid-row: 6; }
  .view-body.view-dashboard > .disclaimer { grid-column: 1 / -1; grid-row: 7; }

  /* 仪表卡在拉伸后的行里居中 */
  .view-body.view-dashboard > .dashboard { justify-content: center; }

  /* 右列八格：等分 2×4，不再保留移动端的大格 / 宽格异形。
     components.css:581/596 的 span 是 (0,2,0)，这里 (0,3,0) 压过。 */
  .view-body.view-dashboard .grid8 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-auto-rows: 132px;
    gap: 14px;
  }

  .view-body.view-dashboard .grid8-cell:first-child,
  .view-body.view-dashboard .grid8-cell:nth-child(7) {
    grid-column: auto;
    grid-row: auto;
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-start;
    gap: 4px;
    padding: 18px 18px 16px;
    text-align: left;
    font-size: .8125rem;
    font-weight: 400;
  }

  .view-body.view-dashboard .grid8-cell {
    align-items: flex-start;
    justify-content: flex-start;
    gap: 4px;
    padding: 18px 18px 16px;
    text-align: left;
  }

  /* 桌面双列按任务属性排布：右列是诊断主流程，左列是查询与资料入口。
     DOM 仍保持主流程优先，手机退回单列时阅读顺序无需另做反转。 */
  .view-body.view-dashboard .grid8-cell:nth-child(1) { grid-column: 2; grid-row: 1; }
  .view-body.view-dashboard .grid8-cell:nth-child(2) { grid-column: 2; grid-row: 2; }
  .view-body.view-dashboard .grid8-cell:nth-child(3) { grid-column: 2; grid-row: 3; }
  .view-body.view-dashboard .grid8-cell:nth-child(4) { grid-column: 2; grid-row: 4; }
  .view-body.view-dashboard .grid8-cell:nth-child(5) { grid-column: 1; grid-row: 1; }
  .view-body.view-dashboard .grid8-cell:nth-child(6) { grid-column: 1; grid-row: 2; }
  .view-body.view-dashboard .grid8-cell:nth-child(7) { grid-column: 1; grid-row: 3; }
  .view-body.view-dashboard .grid8-cell:nth-child(8) { grid-column: 1; grid-row: 4; }

  /* 等分后每格图标同样大小；(0,3,1) 压过 components.css:595 的 (0,2,1) */
  .view-body.view-dashboard .grid8-cell svg,
  .view-body.view-dashboard .grid8-cell:first-child svg { width: 1.5rem; height: 1.5rem; }

  .view-body.view-dashboard .grid8-cell .cell-en,
  .view-body.view-dashboard .grid8-cell .cell-desc,
  .view-body.view-dashboard .grid8-cell .cell-desc-en { display: block; }

  .view-body.view-dashboard .grid8-cell .cell-desc { margin-top: 4px; }

  /* 箭头绝对定位到右下角，不参与列高计算 */
  .view-body.view-dashboard .grid8-cell .cell-go {
    display: block;
    position: absolute;
    right: 14px;
    bottom: 14px;
    opacity: .55;
  }

  .view-body.view-dashboard .grid8-cell:not([aria-disabled="true"]):hover .cell-go { opacity: 1; }
  .view-body.view-dashboard .grid8-cell .cell-go-off { position: static; }

  .view-body.view-dashboard .metric-grid { grid-template-columns: repeat(4, 1fr); }

  /* 三重提醒（保养 / 年检 / 保险）是三条并列的到期项，不是一串流水记录。
     整行铺开时标题在最左、天数在最右，中间一千多像素全是空的；改成并列
     三栏后横向空间用满，也更贴近「三重提醒」本来的含义。
     只作用于首页 —— 其他视图的 .list 是真正的列表，保持原样。 */
  .view-body.view-dashboard > .section:has(.list) .list {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  }

  /* 横排后行间分隔线要换成列间分隔线，圆角也不再按首尾行计算。 */
  .view-body.view-dashboard > .section:has(.list) .list-item {
    border-radius: 0;
  }

  .view-body.view-dashboard > .section:has(.list) .list-item + .list-item {
    border-top: 0;
    border-left: 1px solid var(--border);
  }

  /* 仪表卡被右列 4 行格子拉高后，260px 的表盘在卡里显得偏小。
     上限用 min(…, 100%)：断点正好落在 1240px 时左列算出来是 376.6px，
     扣掉 38px 的左右内边距只剩 298.6px 的内容宽，写死 300px 会被
     .dashboard 的 overflow: hidden 削掉一条边。 */
  .view-body.view-dashboard > .dashboard > svg { width: min(300px, 100%); }
}

/* ==========================================================================
   中等宽度（801–1239）：单列，但仍是桌面密度
   ========================================================================== */

@media (min-width: 801px) {
  .grid8 { grid-template-columns: repeat(4, minmax(0, 1fr)); grid-auto-rows: 118px; gap: 14px; }
  .metric-grid { grid-template-columns: repeat(4, 1fr); }

  /* 这四条原先写在媒体查询外面，会一路泄漏到手机端：
     其中 .dashboard .btn { width: auto } 会把 home.js 空态里的
     .btn-block 从整行宽压回内容宽。现在只在桌面生效。 */
  .dashboard { min-height: 330px; padding: 34px 38px; }
  .dashboard > svg { width: 260px; z-index: 1; }
  .dashboard-caption { z-index: 1; }
  .dashboard .btn { z-index: 1; }
  .promo-row { margin-inline: 0; padding-inline: 0; }

  /* 服务位在桌面端不需要横向滚动：三张卡直接铺满整行。
     .promo-row 仍是 flex（views.css:199），这里换成等分栅格并关掉滚动。
     从 801px 起就生效 —— 只在 ≥1240 放开的话，801–1239 这一段
     三张 15rem 的卡合计 752px 装不进 728px 的内容宽，第三张会被切掉
     一条，而滚动条是隐藏的（views.css:203），看起来就像坏了。
     auto-fit 会把空轨道折叠掉，所以宽屏仍然是三等分铺满整行。 */
  .view-body.view-dashboard .promo-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 14px;
    overflow: visible;
  }

  /* views.css:221-222 把卡片钉成 flex:none / width:15rem（横向滚动需要）。
     栅格里这两条会让卡片不铺满列宽，必须一起解开。 */
  .view-body.view-dashboard .promo {
    width: auto;
    flex: initial;
  }
}

/* ==========================================================================
   窄屏
   ========================================================================== */

@media (max-width: 1239px) {
  .statusbar { grid-template-columns: 1fr; gap: 16px; }
  .statusbar-svc { padding-top: 14px; border-top: 1px solid var(--border); }
}

@media (max-width: 800px) {
  .stage { padding: 16px; }
  .app-shell { height: calc(100vh - 32px); min-height: 0; }
  .appbar { padding: 0 22px; }
  .tabbar { padding: 0 20px; }
  .view { padding: 28px 24px 38px; }
  .dashboard { padding: 26px; }
  .sys-strip { grid-template-columns: repeat(3, minmax(0, 1fr)); }

  /* 手机档裁切方向反过来了，所以横向必须另给一个值。
     桌面上壳是宽扁的（1480×820），cover 的缩放由宽度决定，左右铺满、上下
     裁掉；手机上壳是窄高的（约 420×870），缩放改由高度决定，于是变成上下
     铺满、左右各裁掉一半 —— 横向只剩源图约 48% 的宽度。
     50% 会把窗口停在灯框上，正好从车头切一刀，主体（车尾与尾灯灯带，
     源图 x≈700~1010）整个被裁掉；取 70% 把窗口推到右侧，灯框留一条边缘、
     整台车与尾灯完整保留。纵向同时略降到 62%，让地面倒影多进来一点，
     补偿窄画面里丢掉的横向纵深。 */
  body.has-scanner:has(.grid8) .app-shell {
    background-position: 70% 62%;
  }

  /* 同样因为裁切方向变了，遮罩也得另配一套。

     桌面那条是把横向右端压到 .54 —— 因为桌面上灯框稳定落在右上角。手机
     档没有这个便利：窗口右移到 70% 之后，灯框那条高光横跨整个画面宽度，
     左右打薄厚度毫无意义（420px 的壳里横向渐变几乎没有铺开的余地）。
     真正有效的是纵向那层的中段：把 .32 提到 .56。

     数值是扫出来的（cm-shot/bgfix-ph2.js，16 个滚动位置）：
       .50  4.54:1  整屏平均亮度 −0.6   过线但余量太薄
       .56  4.85:1  −0.8                 与桌面档 4.88:1 的余量齐平
       .62  5.19:1  −1.0                 白压暗，照片开始发灰
     另外试过在 .56 之上再把顶端 .58 提到 .66，对比度一点没动（4.85:1），
     纯粹白亏 —— 说明这行字压根不在顶端那一档的作用域里，已弃。

     这里要强调的是「滚动位置」：背景画在不滚动的 .app-shell 上，内容在
     .view 里滚，所以照片是从文字底下滑过去的。最差的一帧出现在滚到
     741px（笔画背后 rgb(81,73,49)，那条暖色灯框），而不是滚到顶时。
     早前用 5 个采样点扫，落点是 0/432/864/1296/1728，正好从 741 两边跨
     过去，基线报了「全部通过」—— 这一档必须密采样才测得准。 */
  body.has-scanner:has(.grid8) .app-shell::before {
    background:
      radial-gradient(circle 620px at 103% -40px,
                      var(--ambient-red), transparent 70%),
      radial-gradient(circle 560px at -3% calc(100% + 40px),
                      var(--ambient-blue), transparent 72%),
      linear-gradient(90deg, rgb(5 11 18 / .62) 0%,
                             rgb(5 11 18 / .22) 46%,
                             rgb(5 11 18 / .54) 100%),
      linear-gradient(180deg, rgb(5 11 18 / .58) 0%,
                              rgb(5 11 18 / .56) 38%,
                              rgb(5 11 18 / .72) 100%);
  }
}

@media (max-width: 520px) {
  .stage { padding: 0; }
  .app-shell {
    /* base.css:308-310 用 100vh 打底再用 100dvh 覆盖，这样不支持 dvh 的
       浏览器仍有可用高度。原先这里只写 100vh，等于把 dvh 那一层删掉，
       移动端地址栏收起时底部会空出一条。两行都要保留。 */
    height: 100vh;
    height: 100dvh;
    border-radius: 0;
    border: 0;
  }
  .appbar { height: 60px; padding: 0 16px; }
  .view { padding: 22px 16px 30px; }
  .tabbar { padding: 0; }
  .dashboard { min-height: 300px; padding: 22px; }
  .metric-grid { grid-template-columns: repeat(2, 1fr); gap: 8px; }
  .metric { padding: 12px; }
  .grid8 { grid-template-columns: repeat(3, minmax(0, 1fr)); grid-auto-rows: 80px; gap: 9px; }
  .statusbar { padding: 14px 16px; }
  .sys-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .capability-radar-shell { padding: 16px 6px 64px; border-radius: 14px; }
  .capability-radar-tag { top: 12px; right: 12px; font-size: .48rem; }
  .capability-radar-score { right: 12px; bottom: 14px; }
}

/* ========================================================================== 
   账号入口
   ========================================================================== */

/* 未登录首页使用车辆健康透视图。原图的视觉重心在右侧，按高度完整展示，
   左侧留给品牌、入口与说明；暗幕只负责稳定文字对比度，不改变图片色相。 */
body[data-theme="auth"]:has(.auth-landing) .app-shell {
  background-color: #020812;
  background-image: url("../assets/landing-vehicle-bg.png");
  background-repeat: no-repeat;
  background-size: auto 100%;
  background-position: right center;
}

body[data-theme="auth"]:has(.auth-landing) .app-shell::before {
  content: "";
  inset: 0;
  transform: none;
  animation: none;
  background:
    linear-gradient(90deg,
      rgb(2 8 16 / .98) 0%,
      rgb(2 8 16 / .88) 38%,
      rgb(2 8 16 / .5) 62%,
      rgb(2 8 16 / .12) 100%),
    linear-gradient(180deg,
      rgb(2 8 16 / .28) 0%,
      transparent 45%,
      rgb(2 8 16 / .36) 100%);
}

body[data-theme="auth"]:has(.auth-landing) .appbar {
  background: rgb(2 8 16 / .88);
}

.auth-landing {
  min-height: 560px;
  justify-content: center;
  position: relative;
  gap: 22px;
}

.auth-landing::before {
  content: none;
}

.auth-landing > * { position: relative; z-index: 1; }
.auth-landing .auth-brand { gap: 7px; }
.auth-landing .auth-brand .brand-en { font-size: 1rem; letter-spacing: .045em; }
.auth-landing .auth-brand .brand-mark {
  font-size: clamp(3.25rem, 5.2vw, 5.5rem);
  line-height: 1.08;
  letter-spacing: .12em;
}
.auth-landing .auth-brand .brand-tagline { font-size: 1.2rem; }

.auth-landing .landing-card-nav,
.auth-landing .auth-actions { width: min(760px, 100%); max-width: none; }

.auth-landing .landing-card-nav-toggle {
  height: 76px;
  padding-inline: 24px;
  border-radius: 18px;
}
.auth-landing .landing-card-nav-toggle h2 { font-size: 1.1rem; }
.auth-landing .landing-card-nav-icon { width: 28px; height: 28px; }
.auth-landing .landing-card-nav-icon svg { width: 22px; height: 22px; }

.auth-landing .auth-actions { gap: 10px; }
.auth-landing .auth-actions .btn {
  height: 48px;
  border-radius: 14px;
  font-size: .95rem;
}

.auth-landing .disclaimer {
  width: min(900px, 100%);
  max-width: none;
  font-size: .8rem;
  line-height: 1.75;
  overflow-wrap: anywhere;
}

@media (max-width: 800px) {
  body[data-theme="auth"]:has(.auth-landing) .app-shell {
    background-size: auto 100%;
    background-position: 66% center;
  }

  body[data-theme="auth"]:has(.auth-landing) .app-shell::before {
    background:
      linear-gradient(90deg, rgb(2 8 16 / .9), rgb(2 8 16 / .56)),
      linear-gradient(180deg, rgb(2 8 16 / .42), transparent 42%, rgb(2 8 16 / .62));
  }

  .auth-landing { gap: 17px; }
  .auth-landing .auth-brand .brand-en { font-size: .82rem; }
  .auth-landing .auth-brand .brand-mark { font-size: 2.8rem; }
  .auth-landing .auth-brand .brand-tagline { font-size: 1rem; }
  .auth-landing .landing-card-nav-toggle { height: 64px; padding-inline: 18px; border-radius: 14px; }
  .auth-landing .landing-card-nav-toggle h2 { font-size: 1rem; }
  .auth-landing .auth-actions .btn { height: 43px; font-size: .875rem; }
  .auth-landing .disclaimer { font-size: .72rem; line-height: 1.65; }
}


/* ==========================================================================
   全部功能 · 漂浮墙（静止 3D 墙 + 悬停抬起）

   参照 React Bits <DriftWall /> 的视觉语言，但那是个 React 组件，带
   import './DriftWall.css'，本项目是 file:// 下直接双击打开的无构建纯静态
   页面，组件本身没法用，能搬的只有观感：透视平面 + 列深度递退 + 悬停
   translateZ 抬起。

   原版有三件事在这里是不能照搬的，逐条说明为什么没做：

   1) copies / 列内复制若干份图块。DriftWall 是靠重复图片把列填满再无限
      滚动的。这八格是八条真实路由链接，复制一份就等于把同一个无障碍名
      播两遍，还会让 home.js:243 那句 CM.views[c.view] 的未上线判定出现
      两个副本。一格没复制。
   2) 无限滚动。原版靠 copies 把列填满再无限滚，这里八格是八条唯一路由，
      不能复制，滚出去就是空洞。改成有界往复漂移，并且指针一进入整面墙就
      就地定格 —— 见下面「漂移」一节。
   3) dim:.55 + #060010 蒙版 + grayscale 的静息压暗。原版铺的是照片，压暗
      无所谓；这八格里有 .cell-desc / .cell-desc-en 两级说明文字，用的是
      --fg-subtle 与 --fg-faint，本来就贴着可读下限，再压一道就掉到线下。
      所以这里反过来做：静息态一个像素都不改，只在 hover 时提亮。
      —— 也因此没有给格子本身套 mask：原版的边缘溶解是拿 mask-composite
      把四周化开，套在文字上就是把导航名淡掉。溶解只留给下面那层光晕。

   透视挂在 .section 上而不是 .grid8 上：perspective 作用于「子元素」，
   若和 rotate 一起写在 .grid8 上，.grid8 自身的旋转就没有透视可投，只会
   得到一个没有近大远小的仿射斜切，看着是歪的而不是有纵深的。
   ========================================================================== */

/* 只认首页。.view-body:has(> .dashboard) 会连 /wallet 与 /report 一起命中
   （home.js:427-428 已经踩过这个坑并留了注释），而 landing 页在
   .landing-card-nav-content 里另有一张 .grid8（auth.js:101），
   两者都必须排除在外，所以一律用 .view-dashboard 这个明确挂点。 */
.view-body.view-dashboard .section:has(.grid8) {
  position: relative;
  perspective: var(--dw-p, 900px);

  /* 灭点钉在右缘（100%），不是居中。

     实测 1600px 下 .view 的裁切区是 x=61..1539（base.css:263-268
     overflow-x: hidden），而八宫格自身的布局盒右缘就在 1540 —— 右边一个
     像素的余量都没有，左边（到症状筛查分那张卡）却有七十来个。

     缩放是「绕灭点」发生的，落在灭点上的那个点不动。所以最优解不是把灭点
     放中间再想办法躲，而是让「近侧」和「灭点」重合：近侧被放大但钉住不外
     扩，远侧则朝画面内部缩回去，两个方向都在往有余量的地方让。
     这也是下面 rotateY 取负值的原因 —— 负值＝右缘近、左缘远，把「近大」
     锁在没有余量的那一侧，把「远小」让给有余量的那一侧。

     实测：灭点 96% + 正 rotateY（左缘近）时，近列左缘冲到 x=459，压住症状
     筛查分卡片 143px；换成 100% + 负 rotateY 后同样的纵深下左缘退到 626，
     水平方向 0 裁切、0 遮挡。 */
  perspective-origin: 100% 45%;
}

/* --dw-set：整面墙沿自身法线前推的距离。

   不推的话，墙是骑在 z=0 两侧的 —— 远的一半 z<0，投影缩放 p/(p-z) < 1，
   那半边的字会被真的画小。实测上一版（p=860、rotateX9/rotateY-14、不前推）
   智能问诊 0.83、召回查询 0.85、故障灯速查 0.81：最大、文字最多的主模块
   按 81% 渲染，.cell-desc-en 的 9.6px 实际只有 7.8px。这跟本节开头第 3 条
   「静息态一个像素都不改」自相矛盾。

   前推之后最远的一角回到 z≈0 附近，纵深主要由「近的一侧更大」提供，而不是
   「远的一侧更小」—— 近端放大比远端缩小好看，也不牺牲远端的可读性。

   这里有一条跑不掉的取舍，sweep.js 扫了 48 组参数确认过：
   平均缩放≈1 才能让投影正好填满布局盒，而平均≈1 就意味着墙骑在 z=0 两侧、
   远的一半必被画小。想让最小缩放回到 1，就只能整面前推，投影面积随之超出
   布局盒，于是必须另外留出空间。两者不可兼得。
   取的中间点是 --dw-set: 110px —— 最小缩放 0.967（缩 3%，肉眼看不出），
   最大 / 最小比 1.295（上一版实际只有 1.20，因为列递退那条规则当时被注释
   语法吃掉了，见下面 :1005 那段）。再往上推到 140px 只换来 1.307，却要多占
   40px 纵向空间，不划算。

   ⚠ 改 --dw-turn / --dw-tilt / --dw-set 都必须重测，否则上面这些数会无声
      失效：sweep.js 扫参数，dwv2.js 打每格 scale，vspill.js 查遮挡。 */
.view-body.view-dashboard .grid8 {
  --dw-turn: -15deg;   /* 绕 Y，负值 = 右缘近、左缘远（右侧没有余量，见上） */
  --dw-tilt: -5deg;    /* 绕 X，负值 = 顶边近 */
  --dw-set: 110px;

  position: relative;
  transform-style: preserve-3d;
  transform: rotateX(var(--dw-tilt)) rotateY(var(--dw-turn)) translateZ(var(--dw-set));

  /* ⚠ 这两行是功能性的，不是装饰，别当成多余的清理掉。

     preserve-3d 之下，.grid8 自己的盒子画在 z=0，而列递退那半数格子在
     z=-64px —— 它们在父盒「后面」。命中测试按真实 3D 前后关系走，于是点在
     递退格上的那一下会被 .grid8 自己接走，链接根本收不到。表现不是「点错
     模块」，而是「点了没反应」：实测 1600px 下 8 格里有 4 格（故障灯速查 /
     智能问诊 / 召回查询 / 我的车库）完全点不动，hash 停在 #/home 不变。

     这条违反「不改变其功能」，必须修。把父盒移出命中测试、格子自己再开回来
     即可 —— 只改谁接收指针，不动任何事件绑定，DOM 与路由一字未动。
     .grid8:hover（用来暂停漂移）不受影响：悬停子元素同样会给祖先加 :hover，
     与祖先的 pointer-events 无关。

     验证用 click.js —— 它派发真实的 CDP 鼠标事件再读 location.hash，是唯一
     可信的判据。elementFromPoint / elementsFromPoint 在 3D 后代上给出的答案
     互相矛盾（前者报父盒、后者报格子），两个都不能用来下结论。 */
  pointer-events: none;
}

.view-body.view-dashboard .grid8 .grid8-cell {
  pointer-events: auto;
}

/* 基准 Z 走 --dw-z，而不是把度数直接写进 transform：列递退与 hover 抬起
   要落在同一条 transform 上，谁写死谁就把对方覆盖掉。改成一个变量以后，
   「这一格现在在第几层」永远只有一个真值。
   漂移则挪到独立的 translate 属性上 —— translate 与 transform 是两个属性，
   同时生效且互不覆盖，所以「持续漂」和「悬停抬」可以并存。
   选择器要写到 (0,5,0)，不能只写 .view-body.view-dashboard .grid8-cell。
   后者是 (0,3,0)，而 components.css:1030-1031 那条微交互 transition 是
   (0,3,1)：:is(...) 里最重的一支是 .checkbox-row:has(input:not(:disabled))
   = (0,2,1)，末尾 :not(:disabled, [disabled], …) 再加 (0,1,0)。
   (0,3,1) > (0,3,0)，所以上一版那句 420ms 从来没有生效过 —— 实测计算值是
   「scale, box-shadow, background-color, border-color, color | 0.18s」，
   里面根本没有 transform，抬起是硬跳的。这里补一个 .section 一个 .grid8
   凑到 (0,5,0) 压过去。

   transition 不可累加，覆写就必须把 components.css:1032-1034 原有的五条
   全部搬回来（scale / box-shadow / background-color / border-color / color），
   否则整套微交互的缓动会一起掉光。

   --micro-scale 归 1：components.css:1050 会在 hover 时上 scale: 1.01，
   走的是 180ms 的另一只时钟，和这里 420ms 的抬起不同步，肉眼就是「先弹
   一下再推出去」，而且是对文字的第二次重采样。抬起本身的放大远不止 1%，
   这 1% 没有信息量，关掉只留一个位移通道。components.css:1099 的 :active
   scale:.99 不在此列，按下反馈保留。 */
.view-body.view-dashboard .section .grid8 .grid8-cell {
  --dw-z: 0px;
  --micro-scale: 1;
  transform: translateZ(var(--dw-z));
  transition: transform 420ms cubic-bezier(.22, .61, .36, 1),
              scale 180ms var(--ease),
              box-shadow 260ms ease,
              color .15s ease,
              border-color .15s ease,
              background-color .15s ease;
}

/* --------------------------------------------------------------------------
   漂移

   原版 DriftWall 是靠 copies 把列填满再无限滚动的。这里八格是八条唯一路由，
   不能复制，所以无限滚动做不出来 —— 滚出去就是个空洞。能做的是有界往复：
   两组格子以不同周期反向缓动，13s 与 17s 不整除，相位会一直错开，看上去
   就不是整齐的呼吸而是各自在漂。

   两件事保证它不违反「不改变其功能」：

   1) 指针一进入 .grid8，整面墙立即 animation-play-state: paused —— 暂停是
      就地定格，不回弹。也就是说用户开始瞄准的那一刻，所有坐标就冻住了，
      不存在「按下去时格子已经挪开」。:focus-within 让键盘用户同享。
   2) 只在 [data-reveal-state="seen"] 之后接管 animation。util.js:350 的 ITEM
      里含 .grid8-cell，每一格都是滚动揭示的目标，揭示期间 base.css:419 要用
      animation 跑 reveal-in；这里若无条件抢占同一个属性，入场动画就被吃掉，
      而 util.js:405 又只认 animationName === 'reveal-in'。虽然 util.js:409 有
      500ms 兜底不会卡死，但入场效果会没。等 util.js:388 打上 seen 再开漂。
   -------------------------------------------------------------------------- */

@keyframes dw-drift-a {
  0%, 100% { translate: 0 -11px; }
  50%      { translate: 0 11px; }
}

@keyframes dw-drift-b {
  0%, 100% { translate: 0 12px; }
  50%      { translate: 0 -12px; }
}

.view-body.view-dashboard .grid8-cell[data-reveal-state="seen"]:nth-child(odd) {
  animation: dw-drift-a 13s ease-in-out infinite;
}

.view-body.view-dashboard .grid8-cell[data-reveal-state="seen"]:nth-child(even) {
  animation: dw-drift-b 17s ease-in-out infinite;
}

.view-body.view-dashboard .grid8:hover .grid8-cell,
.view-body.view-dashboard .grid8:focus-within .grid8-cell {
  animation-play-state: paused;
}

/* 列深度递退。只有 ≥1240px 才有明确的双列坐标；主流程 1–4 在右列，
   查询与资料入口 5–8 在左列。
   801–1239 是 4 列且异形 span 仍在，≤800 是 3 列，两档的 nth-child 都不等于
   列号，硬套只会得到一堆随机深浅，所以那两档不做递退，只靠偏航出纵深。

   递退给 5–8 号格（左列）。--dw-turn 取负值时正 x 转向 +z，也就是右缘近、
   左缘远，所以左列才是该往里推的那一侧 —— 给近侧加递退等于把墙拧回去。
   改 --dw-turn 的正负号时这里的 odd/even 必须跟着换。

   选择器必须写到 (0,6,0)：上面那条基准 --dw-z 已经是 (0,5,0)，若这里只写
   .view-body.view-dashboard .grid8-cell:nth-child(odd) = (0,4,0)，会被基准
   整条压掉，递退无声失效。 */
@media (min-width: 1240px) {
  .view-body.view-dashboard .section .grid8 .grid8-cell:nth-child(-n+4) {
    --dw-z: 0px;
  }

  .view-body.view-dashboard .section .grid8 .grid8-cell:nth-child(n+5) {
    --dw-z: -64px;
  }

  /* 投影比布局盒大，得给它留出地方。

     transform 不参与布局：栅格只占 570px 高，而投影（含 ±12px 漂移）占
     735px，多出来的部分直接盖在别人身上 —— 实测压住 .section-head 47px、
     压住底下那条 .metric-grid 110px，「关于与算法」整块浮在 SELF-CHECKS
     上面。这不是可以调参数躲开的：sweep.js 扫的 48 组里，连最弱的一组
     （turn -11 / tilt -3 / 不前推）都还要越界 11px 和 29px。

     所以只能显式留白。上下不对称是因为灭点在 45%（偏上），放大量朝下释放
     得多。留白撑开的是布局盒，投影正好落在这段留白里，静息态看不到空档。

     ⚠ 这两个数是跟 --dw-turn / --dw-tilt / --dw-set 绑死的，改了要用
        vspill.js 重测（它直接问页面「这面墙盖住了谁」，不靠猜兄弟节点 ——
        .metric-grid 并不是 .section 的兄弟，DOM 顺序和视觉顺序对不上）。 */
  .view-body.view-dashboard .section .grid8 {
    margin-block: 70px 157px;
  }
}

/* 801–1239：这一档是 4 列、异形 span 还在、不做列递退，墙的形状和 ≥1240
   完全不同 —— 纵深只来自偏航，前推那 110px 买不到什么立体感，却照样把投影
   撑大、压住上面的 .section-head 和下面的 .statusbar。

   这里不靠加留白解决：实测这一档给 .grid8 加 26px 上边距，头部与墙的净距
   只改善 10px —— 版心在这一档是居中的，section 长高之后整体又往上跑，
   加多少吃掉一大半。直接把前推收到 30px，投影自己缩回布局盒附近，剩下的
   零头再用少量下边距兜住。 */
@media (min-width: 801px) and (max-width: 1239px) {
  .view-body.view-dashboard .grid8 { --dw-set: 30px; }
  .view-body.view-dashboard .section .grid8 { margin-block: 38px 70px; }
}

/* 近侧那层光晕。压暗远侧会连远侧的说明文字一起压，所以改成提亮近侧；
   推到 -70px（比最远的一列还远）让它只在格子缝隙与四周透出来，
   不覆到任何文字上。原版的边缘溶解只用在这一层。 */
.view-body.view-dashboard .grid8::after {
  content: "";
  position: absolute;
  inset: -8% -6%;
  pointer-events: none;
  transform: translateZ(-70px);
  background: radial-gradient(ellipse 60% 70% at 18% 34%,
                              rgb(120 180 255 / .14), transparent 70%);
  -webkit-mask-image: radial-gradient(ellipse 82% 86% at 50% 46%, #000 52%, transparent 100%);
          mask-image: radial-gradient(ellipse 82% 86% at 50% 46%, #000 52%, transparent 100%);
}

/* :focus-visible 与 :hover 完全同款 —— 键盘走到哪一格，哪一格就抬起来，
   否则整面墙对键盘用户就只是一张歪着的静止图。

   本条必须写到 (0,7,0)，不能只压 :445。它真正要压过的是下面那条列递退
   .view-body.view-dashboard .section .grid8 .grid8-cell:nth-child(odd)，
   那条是 (0,6,0)。原先本条只有 (0,5,0)，于是 ≥1240 时递退的四格（奇数格，
   即左列）hover 根本不抬 —— 实测 --dw-z 从头到尾钉在 -64px，一帧都没动，
   而偶数格正常抬到 72px。半数格子没有悬停反馈，且因为「有一半是好的」，
   随手抽一格验证很容易抽到好的那一半（之前正是测了第 3 格才漏掉）。
   验证要挑奇数格：node dwv2.js 1600 0。

   border-color 故意不写，留给
   :445 继续管；box-shadow 是有意覆写的：:445 的 0 12px 28px 是贴在平面上
   的投影，格子既然浮起来了，投影就得跟着拉长、拉散。
   aria-disabled 的未上线格子被 :not() 挡在外面，不会抬起 ——
   它们在 home.js:252 上带着行内 style="opacity:.4"，行内样式优先级最高，
   本文件任何规则都改不动它，也不该去改。

   抬起给的是绝对近平面 72px，不是「在原深度上再加多少」：远列本来在 -52px，
   若按相对量抬，同一个动作在两列会停在两个不同的高度，hover 反馈就不一致了。
   统一抬到 72px，无论从哪一列过来，浮起后都在同一层。 */
.view-body.view-dashboard .section .grid8 .grid8-cell:not([aria-disabled="true"]):hover,
.view-body.view-dashboard .section .grid8 .grid8-cell:not([aria-disabled="true"]):focus-visible {
  --dw-z: 72px;
  box-shadow: 0 30px 70px -18px rgb(0 0 0 / .76),
              0 0 30px color-mix(in srgb, var(--tile) 24%, transparent);
}

/* 窄屏收力。实测 420px 下整张墙只有 388px 宽、单格 114x72，透视没有足够的
   尺度差可以读成「纵深」，-14deg 在这个尺寸上只会显得整块歪掉；同时近大远小
   会让远侧的格子真的变小，手机上的点击目标本来就紧张。姿态压到几乎察觉不到，
   保留一点立体感即可。漂移幅度同步收一半：小格子上 ±11px 的位移占比太大。 */
@media (max-width: 800px) {
  /* 改变量而不是重写 transform：原先这里直接写死 rotateX(4deg) rotateY(-3.5deg)，
     等于把 translateZ(var(--dw-set)) 一并丢掉，姿态和上面那套变量各说各话。
     走变量之后三个角度只有一个出处，改一处即可，数值与之前一致。 */
  .view-body.view-dashboard .grid8 {
    --dw-turn: -3.5deg;
    --dw-tilt: 4deg;
    --dw-set: 0px;
  }

  /* 这一档几乎不放大，只需要很少的余量兜住漂移 */
  .view-body.view-dashboard .section .grid8 { margin-block: 0 10px; }

  /* 跟着上面那条一起升到 (0,7,0)：两条必须同级，否则这一档的 34px 会被
     上面通用的 72px 压掉，手机上就抬得跟桌面一样高了。 */
  .view-body.view-dashboard .section .grid8 .grid8-cell:not([aria-disabled="true"]):hover,
  .view-body.view-dashboard .section .grid8 .grid8-cell:not([aria-disabled="true"]):focus-visible {
    --dw-z: 34px;
  }

  @keyframes dw-drift-a {
    0%, 100% { translate: 0 -5px; }
    50%      { translate: 0 5px; }
  }
  @keyframes dw-drift-b {
    0%, 100% { translate: 0 6px; }
    50%      { translate: 0 -6px; }
  }
}

/* ==========================================================================
   降低动态效果
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .dash-pulse { animation: none; }

  /* 静止的倾斜姿态与列递退本身不是动效，留着 —— 它们是布局的一部分，
     去掉整面墙就塌回平面了。会动的两样全部关掉：持续漂移直接取消，
     hover 抬起收掉过渡、幅度减半。位置在任何时刻都是静止且确定的。 */
  .view-body.view-dashboard .grid8-cell {
    transition: color .15s ease, border-color .15s ease, background-color .15s ease;
  }
  .view-body.view-dashboard .grid8-cell[data-reveal-state="seen"]:nth-child(odd),
  .view-body.view-dashboard .grid8-cell[data-reveal-state="seen"]:nth-child(even) {
    animation: none;
    translate: none;
  }
  /* 同样升到 (0,7,0)，并且靠在文件更后面赢过上面两条同级规则。
     只要有一条留在 (0,5,0)，≥1240 的递退 (0,6,0) 就会把它整条吃掉。 */
  .view-body.view-dashboard .section .grid8 .grid8-cell:not([aria-disabled="true"]):hover,
  .view-body.view-dashboard .section .grid8 .grid8-cell:not([aria-disabled="true"]):focus-visible {
    --dw-z: 24px;
  }
}

/* ========================================================================== 
   首页主仪表模块：机甲面板置换

   原图包含烤死的标题、73 分和状态文字。外层使用用户提供的机甲面板，
   中间遮掉固定红环与数字，上层仍由现有 HTML / SVG 输出实时数据。
   选择器限定到首页明确挂点，避免影响报告页与钱包中的 .dashboard。
   ========================================================================== */

.view-body.view-dashboard > .dashboard {
  width: 100%;
  aspect-ratio: 1;
  min-height: 0;
  align-self: center;
  justify-self: stretch;
  display: block;
  padding: 0;
  border: 0;
  border-radius: 0;
  isolation: isolate;
  overflow: hidden;
  clip-path: polygon(4% 0, 96% 0, 100% 4%, 100% 96%, 96% 100%, 4% 100%, 0 96%, 0 4%);
  background: #06111c url("../assets/dashboard-mechanical-bg.png") center / cover no-repeat;
  box-shadow: none;
  filter: drop-shadow(0 18px 34px rgb(0 0 0 / .42));
}

/* 抹掉素材内固定的红色进度环和 73，给实时 SVG 留出干净表底。 */
.view-body.view-dashboard > .dashboard::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(circle closest-side at 50% 48%,
    rgb(4 13 22 / .99) 0 44%,
    rgb(5 16 27 / .98) 45%,
    transparent 46%);
}

.view-body.view-dashboard > .dashboard .dash-head {
  position: absolute;
  top: 5%;
  left: 20%;
  z-index: 2;
  width: 60%;
  height: 15%;
  justify-content: center;
  padding: 1.5% 5% 1%;
  clip-path: polygon(12% 0, 88% 0, 100% 100%, 0 100%);
  background: linear-gradient(180deg, rgb(10 27 44 / .99), rgb(4 15 26 / .99));
}

.view-body.view-dashboard > .dashboard > svg {
  position: absolute;
  top: 19%;
  left: 20%;
  z-index: 2;
  width: 60%;
  height: auto;
  aspect-ratio: 1;
}

.view-body.view-dashboard > .dashboard .dashboard-caption {
  position: absolute;
  left: 18%;
  bottom: 5%;
  z-index: 2;
  width: 64%;
  min-height: 20%;
  justify-content: center;
  padding: 2% 5%;
  clip-path: polygon(8% 0, 92% 0, 100% 24%, 92% 100%, 8% 100%, 0 24%);
  background: linear-gradient(180deg, rgb(8 23 37 / .99), rgb(4 14 24 / .99));
}

.view-body.view-dashboard > .dashboard[data-tone="none"] .dashboard-caption {
  bottom: 13%;
  min-height: 18%;
}

.view-body.view-dashboard > .dashboard .dash-cta {
  position: absolute;
  left: 24%;
  bottom: 3%;
  z-index: 3;
  width: 52%;
}

@media (max-width: 520px) {
  .view-body.view-dashboard > .dashboard .dash-head-cn { font-size: .82rem; }
  .view-body.view-dashboard > .dashboard .dash-head-en { font-size: .56rem; }
  .view-body.view-dashboard > .dashboard .dash-head-note { font-size: .58rem; }
  .view-body.view-dashboard > .dashboard .dashboard-caption { font-size: .7rem; }
}

/* ========================================================================== 
   我的车库 · 模块底色 35%

   只降低底色的不透明度，不使用 opacity，避免连带削弱文字、边框与按钮。
   激活车辆卡是车库概览页的独有标记，因此添加 / 编辑页与其他路由不命中。
   ========================================================================== */

body.has-scanner:has(.vehicle-card[data-active="true"])
  :is(.vehicle-card, .list, .card) {
  background-color: rgb(22 38 42 / .35);
}

body.has-scanner:has(.vehicle-card[data-active="true"]) .metric {
  background-color: rgb(11 27 37 / .35);
}

/* 油耗趋势在上一版 1/6 宽度的基础上放大三倍，桌面端占内容区一半并居中。
   小屏仍占满可用宽度，保证坐标可读。 */
body.has-scanner:has(.vehicle-card[data-active="true"]) .garage-fuel-chart {
  box-sizing: border-box;
  width: 50%;
  min-width: 0;
  margin-inline: auto;
  padding: 10px 12px;
  gap: 6px;
}

@media (max-width: 700px) {
  body.has-scanner:has(.vehicle-card[data-active="true"]) .garage-fuel-chart {
    width: 100%;
  }
}
