/* ============================================================
   Nona.X 工作台 · 登录页（含「访客模式」同盘双态切换）
   
   构图：纯黑底 + 霓虹线框装饰 + 巨型渐变光环圆盘 + 玻璃面板。
   所有尺寸都从 --disc（圆盘直径）换算，因此整体比例在任何屏幕上
   都与参考图一致，而不是各元素各自写死像素。

   一块盘，两种身份。开关就是标题本身：
     用户（大 · 白 · 带身份色下划线）  |  访客（小 · 暗）
   点小的那个 → 字号互换、整盘扫掠换色、内容交叉淡化。

   四条设计纪律：
     · 不加控件 —— 标题行即开关，占的就是原 --fs-title 那一格，零增高
     · 谁大谁是当前身份 —— 状态靠字号差表达，不靠色块/边框/背景
     · 换色是"扫掠"不是"淡化" —— 青蓝沿圆环顺时针刷一圈，亮弧贴前沿跑
     · 动与静也是身份语法 —— 访客环带刻度并极缓自转，账号环静止
   ============================================================ */

/* 注册成可插值的自定义属性，才能被 transition 平滑驱动。
   不支持 @property 的旧浏览器（Safari < 16.4）拿不到插值：
   换色会直接跳变而不是扫过去 —— 身份切换本身仍完全可用，属可接受的降级。 */
@property --reveal{syntax:"<angle>";initial-value:0deg;inherits:true}

:root{
  /* 声明深色配色方案：让浏览器把原生控件与自动填充底色都按深色渲染，
   * 与页面的暗色玻璃质感一致（原先没声明，自动填充会刷浅蓝底）。 */
  color-scheme:dark;
  /* 圆盘比参考图小 1/3：参考图是"占满屏"的观感，实际用起来偏挤。
   * 其余尺寸全部由 --disc 等比换算，所以整体缩放只需改这一处。
   * 下限取 340px：再小的话各尺寸的 clamp 下限会"顶住"，
   * 内容不再等比缩小，矮视口（如横屏手机）会被撑破。 */
  --disc:max(340px, min(57.5vmin, 508px));
  /* 外框留白：被两个窄/矮屏断点改小（12px / 10px），所以取成变量。
   * 真正的内边距在文件末尾由 max(--stage-pad, env(safe-area-inset-*)) 算出，
   * 好让刘海的位移只在"确实比留白更大"时生效。 */
  --stage-pad:24px;
  --R:calc(var(--disc) / 2);
  --ring:calc(var(--disc) * .0055);          /* 光环厚度 */
  --tick:calc(var(--disc) * .018);           /* 长刻度：向环外伸出的长度 */
  --tick-s:calc(var(--disc) * .010);         /* 细刻度的长度 */
  /* 尺寸全部按圆盘直径等比换算；clamp 的下限刻意压得较低 ——
   * 下限一旦在小屏幕上"顶住"，内容就不再随圆盘等比缩小，
   * 整块内容会比圆盘还高而被裁掉（手机端实测过 1.15~1.27 倍）。 */
  --h-field:clamp(36px, calc(var(--disc) * .087), 68px);
  --h-btn:clamp(38px, calc(var(--disc) * .092), 72px);
  --fs-title:clamp(22px, calc(var(--disc) * .071), 54px);
  --fs-sw:calc(var(--fs-title) * .52);       /* 次要身份的字号 —— 字号差即状态 */
  --fs-sw-hover:calc(var(--fs-title) * .60);
  --fs-field:clamp(13px, calc(var(--disc) * .026), 20px);
  --fs-btn:clamp(13px, calc(var(--disc) * .029), 22px);
  --fs-brand:clamp(14px, calc(var(--disc) * .039), 30px);
  --fs-note:clamp(11.5px, calc(var(--disc) * .020), 15px);
  --col:64%;                                 /* 表单列宽 = 圆盘直径的比例 */
  /* 纵向间距同样抽成变量，窄屏时统一收紧 */
  --gap-form:calc(var(--disc) * .028);
  --gap-brand:calc(var(--disc) * .008);
  /* 标题要落在「圆盘上沿 ↔ 账号输入栏」的正中：
   * 设上方留白 g1、标题下方留白 g2，居中即要求 g1 = g2。
   * 内容块在圆内垂直居中 ⇒ g1 = (圆盘高 − 内容高) / 2，
   * 实测固定部分占圆盘 0.849 ⇒ g2 ≈ (1 − 0.849) / 3 ≈ .050×圆盘
   * （原来是 .085，偏大导致标题贴顶）。改动其它间距后需重新校准这个值。
   * 注意：开关行与原 .title 同为 line-height:1.5，所以这个校准值继续有效。 */
  --m-title:calc(var(--disc) * .050);
  --m-rule-top:calc(var(--disc) * .085);
  --m-rule-bottom:calc(var(--disc) * .030);

  /* ---------- 文本与语义色 ---------- */
  --txt:#EDEDF7;
  --txt-2:#9A9AB8;
  --txt-3:#6E6E8C;
  --bad:#FF6B81;
  --ok:#34E5B0;
  --note:#67E8F9;

  /* ---------- 身份色 A · 用户（粉紫）---------- */
  --o-c1:#E23A5E; --o-c2:#8B5CF6; --o-c3:#3B82F6; --o-c4:#C026D3;
  --o-glow:rgba(168,85,247,.34);
  --o-glow-2:rgba(225,29,72,.20);
  --o-tint-1:rgba(225,29,72,.20);
  --o-tint-2:rgba(192,38,211,.34);
  --o-tint-3:rgba(124,58,237,.18);
  --o-accent:#C084FC;

  /* ---------- 身份色 B · 访客（青蓝）---------- */
  --g-c1:#22D3EE; --g-c2:#38BDF8; --g-c3:#6366F1; --g-c4:#2DD4BF;
  --g-glow:rgba(34,211,238,.34);
  --g-glow-2:rgba(45,212,191,.20);
  --g-tint-1:rgba(45,212,191,.20);
  --g-tint-2:rgba(34,211,238,.30);
  --g-tint-3:rgba(99,102,241,.18);
  --g-accent:#67E8F9;

  /* 登录按钮渐变：粉 → 紫 → 蓝（与参考图一致）
   * 第一档从参考图的 #E13A80 压深到 #CE3A82：原色白字只有 4.10:1，
   * 低于 AA 的 4.5:1；压深后 4.61:1 达标，色相几乎看不出变化。
   * 其余各档 6.01 / 7.00，中间过渡也都 ≥4.6。 */
  --btn-g1:#CE3A82; --btn-g2:#8B2FD8; --btn-g3:#4A46C8;
  /* 访客按钮：同一套胶囊、同一高度与位置，只换身份色 ——
     形状不变只换色，是"访客是平行身份而非次级功能"的关键。 */
  --btn-x1:#0E7490; --btn-x2:#0891B2; --btn-x3:#4F46E5;

  /* 扫掠换色的时长与曲线（唯一时间源，环与亮弧共用） */
  --wipe:.92s cubic-bezier(.5,.05,.2,1);
}

*{box-sizing:border-box}
html,body{height:100%}
body{
  margin:0;
  background:#000;
  color:var(--txt);
  font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif;
  -webkit-font-smoothing:antialiased;
  /* 排版总开关（与 styles.css 同源，那边有四条纪律的完整说明）：
   * 光学尺寸自动切换、字距调整、数字等宽。登录页的密码框、账号框都常驻数字，
   * 等宽数字能让输入时的字距不跳。 */
  font-optical-sizing:auto;
  font-kerning:normal;
  font-variant-numeric:tabular-nums;
  overflow:hidden;
}

/* 只在视觉上隐藏，读屏仍可读到 */
.sr-only{
  position:absolute;width:1px;height:1px;
  padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0;
}

.stage{
  position:relative;
  min-height:100%;
  display:flex;
  align-items:center;
  justify-content:center;
  padding:var(--stage-pad);
}

/* ---------- 背景霓虹线框 ---------- */
.art{
  position:absolute;inset:0;
  background:url("assets/login-art.svg") center/cover no-repeat;
  /* 中心留空：线条避开圆盘，只在四周铺开，形成"包围感" */
  -webkit-mask-image:radial-gradient(closest-side at 50% 50%,
    transparent 0%, transparent 26%, rgba(0,0,0,.55) 46%, #000 72%);
  mask-image:radial-gradient(closest-side at 50% 50%,
    transparent 0%, transparent 26%, rgba(0,0,0,.55) 46%, #000 72%);
  opacity:.85;
  pointer-events:none;
}

/* ---------- 盘与刻度带的公共定位层 ----------
   `flex:none` 不能省：.stage 是 flex 容器，而 .stack 里**只有绝对定位的子元素**
   （.gate / .ticks-wrap / .disc 全部 inset:0），它的 min-content 宽度因此是 0 ——
   默认的 flex-shrink:1 会在窄视口下把它压扁，连带把圆盘压成椭圆。
   原来的 .disc 是 flex 项、里面有真实内容撑着 min-content，所以没暴露这个问题；
   包了一层之后这层保护就没了。写死 flex:none，尺寸只由 --disc 决定。 */
.stack{position:relative;flex:none;width:var(--disc);height:var(--disc)}
/* --reveal 的过渡挂在这里，不再挂在 body 上：
   被**继承**的自定义属性一旦挂在 body 上过渡，每帧都会让整棵文档树失效重算
   （典型的 style recalc storm，登录页最重的一段动效）。
   下移到 .gate —— 环与亮弧的共同父层，失效范围收敛到圆盘内部。
   两者仍共用同一个插值属性，因此亮弧天然贴着扫掠的前沿，不需要各自对时。
   更深一层的做法是在遮罩元素上直接动 transform:rotate()，彻底去掉变量插值；
   当前保留变量，是因为它与 920ms 的扫掠共用同一条曲线、改起来风险更低。 */
.gate{
  position:absolute;inset:0;
  transition:--reveal var(--wipe);
}
body[data-id="owner"] .gate{--reveal:0deg}
body[data-id="guest"] .gate{--reveal:360deg}

/* ---------- 刻度环：只在访客态浮现，极缓自转 ----------
   分两档：每 30° 一道长刻度（12 道），每 10° 一道细刻度（36 道）。
   单一密度的等长刻度看着像"掉了牙的齿轮"，分档之后才有仪表盘的节奏。
   总深度 9px 左右 —— 齿太长的根因是它相对于 --ring（2.8px）有 5 倍高，
   收到 3 倍出头才和环本身成比例。 */
/* 裁切壳：刻度元素比圆盘大一圈（inset:-tick），旋转时它那个**正方形**
   包围盒会甩到视口外，把页面撑出一块横向可滚区域（手机实测 scrollWidth +7~59）。
   壳子正好是刻度的外接正方形，用 overflow:clip 兜住旋转溢出 ——
   而刻度的墨迹本身是一个 R + --tick 的圆，正好内切于这个正方形，
   所以一点都裁不到，观感与之前完全一致。 */
.ticks-wrap{
  position:absolute;inset:calc(-1 * var(--tick));
  overflow:clip;
  pointer-events:none;
}
.ticks{
  position:absolute;inset:0;
  border-radius:50%;
  opacity:0;
  transition:opacity .55s ease .3s;
}
/* will-change 不再常驻：它是"即将变化"的提示，常驻等于长期占着一层合成层与显存。
   这里只在真正会连续变形的状态下声明（访客态的 60s 自转）。 */
body[data-id="guest"] .ticks{opacity:1;animation:tickspin 60s linear infinite;will-change:transform}
.ticks::before,
.ticks::after{content:"";position:absolute;inset:0;border-radius:50%}

/* 环带正好从圆盘外沿起：元素半径是 R + --tick，
   所以 100% - --tick 就是圆盘外沿，刻度因此贴在盘上而不是浮在空中。 */
.ticks::before{
  background:repeating-conic-gradient(from 0deg,
    rgba(103,232,249,.30) 0deg .40deg, transparent .40deg 10deg);
  -webkit-mask:radial-gradient(circle calc(var(--R) + var(--tick)) at 50% 50%,
    transparent calc(100% - var(--tick)),
    #000 calc(100% - var(--tick) + var(--tick-s)));
  mask:radial-gradient(circle calc(var(--R) + var(--tick)) at 50% 50%,
    transparent calc(100% - var(--tick)),
    #000 calc(100% - var(--tick) + var(--tick-s)));
}
.ticks::after{
  background:repeating-conic-gradient(from 0deg,
    rgba(103,232,249,.55) 0deg .85deg, transparent .85deg 30deg);
  -webkit-mask:radial-gradient(circle calc(var(--R) + var(--tick)) at 50% 50%,
    transparent calc(100% - var(--tick)), #000 100%);
  mask:radial-gradient(circle calc(var(--R) + var(--tick)) at 50% 50%,
    transparent calc(100% - var(--tick)), #000 100%);
}
body[data-id="guest"] .ticks{opacity:1;animation:tickspin 60s linear infinite}
@keyframes tickspin{to{transform:rotate(360deg)}}

/* ============================================================
   圆盘
   ============================================================ */
.disc{
  position:absolute;inset:0;
  border-radius:50%;
  padding:var(--ring);
  /* 盘外那圈大光晕已交给两层伪元素（.disc::before / .gate::before）做交叉淡化，
     这里只留不随身份变化的"盘内压暗"。 */
  box-shadow:inset 0 0 40px rgba(0,0,0,.6);
  opacity:0;
  /* 首屏用渐显，不带缩放：整页统一成"淡化"这一种动效语言。
     只过渡 opacity —— 原先还过渡 box-shadow（.7s），
     而 box-shadow 过渡每帧都要重绘，700ms 也远超 UI 动效 300ms 的硬线。 */
  transition:opacity .62s cubic-bezier(.4,0,.2,1);
}
/* 账号态（紫）外光晕：切换访客时淡出的那一层 */
.disc::before{
  content:"";position:absolute;inset:0;border-radius:50%;pointer-events:none;
  box-shadow:0 0 90px var(--o-glow),0 0 190px var(--o-glow-2);
  transition:opacity .3s cubic-bezier(.16,1,.3,1);
}
body.ready .disc{opacity:1}
body[data-id="guest"] .disc::before{opacity:0}
/* 访客态（青）外光晕：挂在 .gate 上（与 .disc 同框），负责淡入。
   两层各管一个色、只动 opacity，比"过渡 box-shadow"便宜一大截；
   300ms 也与身份切换这种低频动作相称。 */
.gate::before{
  content:"";position:absolute;inset:0;border-radius:50%;pointer-events:none;
  box-shadow:0 0 90px var(--g-glow),0 0 190px var(--g-glow-2);
  opacity:0;transition:opacity .3s cubic-bezier(.16,1,.3,1);
}
body[data-id="guest"] .gate::before{opacity:1}

/* 内圈：一层极淡的描边，做出参考图里"双环"的层次 */
.disc::after{
  content:"";position:absolute;inset:calc(var(--ring) + 7px);
  border-radius:50%;
  border:1px solid rgba(255,255,255,.045);
  pointer-events:none;
  transition:border-color .3s cubic-bezier(.16,1,.3,1), box-shadow .3s cubic-bezier(.16,1,.3,1);
}
/* 访客态把内圈细线也染青并给一点内辉 —— 让盘内多一层冷色纵深，
   否则整盘只有外环变色，中间读起来还是"账号那口锅"。
   上面那条过渡原先写 .7s：对象只是 1px 细线，收到 300ms 后观感不变，
   而 700ms 已明确超出 UI 动效的 300ms 上限。 */
body[data-id="guest"] .disc::after{
  border-color:rgba(103,232,249,.15);
  box-shadow:inset 0 0 26px rgba(34,211,238,.14);
}

/* ---------- 两个环 + 扫掠前沿 ---------- */
.ring{position:absolute;inset:0;border-radius:50%}
.ring--o{
  background:conic-gradient(from 270deg,
    var(--o-c1) 0%, var(--o-c2) 28%, var(--o-c3) 50%, var(--o-c4) 76%, var(--o-c1) 100%);
}
/* 青蓝环常驻在 DOM 里，用一圈 conic 遮罩按 --reveal 揭开 ——
   于是换色是一道顺时针刷过去的"扫掠"，而不是两色交叉淡化。
   这里不再写 will-change:mask-image：mask-image 不在可合成属性之列，
   提示基本无收益，常驻反倒白占一层合成层与显存。 */
.ring--g{
  background:conic-gradient(from 270deg,
    var(--g-c1) 0%, var(--g-c2) 28%, var(--g-c3) 50%, var(--g-c4) 76%, var(--g-c1) 100%);
  -webkit-mask:conic-gradient(from 0deg, #000 0 var(--reveal), transparent var(--reveal) 360deg);
  mask:conic-gradient(from 0deg, #000 0 var(--reveal), transparent var(--reveal) 360deg);
}

/* 亮弧：贴着扫掠的前沿跑。位置由同一个 --reveal 决定，
   所以两者永远同步，不需要各自配时。 */
.sweep{
  position:absolute;inset:0;border-radius:50%;pointer-events:none;
  opacity:0;
  background:conic-gradient(from 0deg,
    transparent 0deg,
    transparent calc(var(--reveal) - 30deg),
    rgba(255,255,255,.10) calc(var(--reveal) - 16deg),
    rgba(255,255,255,.92) var(--reveal),
    transparent calc(var(--reveal) + 7deg));
  -webkit-mask:radial-gradient(circle var(--R) at 50% 50%,
    transparent calc(100% - var(--ring) - 3px), #000 calc(100% - var(--ring)));
  mask:radial-gradient(circle var(--R) at 50% 50%,
    transparent calc(100% - var(--ring) - 3px), #000 calc(100% - var(--ring)));
}
.gate.switching .sweep{animation:arc .92s cubic-bezier(.5,.05,.2,1)}
@keyframes arc{
  0%{opacity:0}
  7%{opacity:1}
  86%{opacity:1}
  100%{opacity:0}
}

/* 光晕外扩：新身份色从环上炸开一圈 */
.pulse{
  position:absolute;inset:0;border-radius:50%;pointer-events:none;
  border:2px solid transparent;opacity:0;
}
body[data-id="owner"] .pulse{border-color:rgba(192,132,252,.9)}
body[data-id="guest"] .pulse{border-color:rgba(103,232,249,.9)}
.gate.switching .pulse{animation:pulse .9s cubic-bezier(.2,.7,.3,1)}
@keyframes pulse{
  0%{transform:scale(1);opacity:.6}
  100%{transform:scale(1.16);opacity:0}
}

/* ---------- 盘内玻璃体 ---------- */
.disc-inner{
  position:relative;
  width:100%;height:100%;
  border-radius:50%;
  display:flex;align-items:center;justify-content:center;
  overflow:hidden;
  background:
    radial-gradient(72% 58% at 50% 84%, var(--o-tint-2), transparent 72%),
    radial-gradient(62% 52% at 20% 26%, var(--o-tint-1), transparent 72%),
    radial-gradient(70% 60% at 82% 40%, var(--o-tint-3), transparent 72%),
    linear-gradient(180deg, rgba(18,13,24,.94), rgba(8,6,12,.97));
  -webkit-backdrop-filter:blur(20px) saturate(1.25);
  backdrop-filter:blur(20px) saturate(1.25);
  border:1px solid rgba(255,255,255,.06);
}
.tint{
  position:absolute;inset:0;border-radius:50%;pointer-events:none;
  opacity:0;transition:opacity .75s cubic-bezier(.4,0,.2,1);
  background:
    radial-gradient(72% 58% at 50% 84%, var(--g-tint-2), transparent 72%),
    radial-gradient(62% 52% at 20% 26%, var(--g-tint-1), transparent 72%),
    radial-gradient(70% 60% at 82% 40%, var(--g-tint-3), transparent 72%);
}
body[data-id="guest"] .tint{opacity:1}

/* 访客态的扫描线：给冷色盘面加一层"仪器正在读取"的质感 */
.scanlines{
  position:absolute;inset:0;border-radius:50%;pointer-events:none;
  opacity:0;transition:opacity .9s cubic-bezier(.4,0,.2,1);
  background:repeating-linear-gradient(0deg, rgba(34,211,238,.055) 0 1px, transparent 1px 5px);
}
body[data-id="guest"] .scanlines{opacity:1}

.col{
  width:var(--col);
  display:flex;
  flex-direction:column;
  align-items:center;
  text-align:center;
}

/* ============================================================
   开关 = 标题行
   line-height 与原 .title 一致（1.5），所以整行高度和原来的标题行
   完全相等 —— 盘内那套"内容在圆内垂直居中"的校准一格都不用重算。
   ============================================================ */
.switch{
  display:flex;align-items:center;justify-content:center;
  gap:calc(var(--disc) * .030);
  margin:0 0 var(--m-title);
  line-height:1.5;
}
.sw{
  position:relative;
  margin:0;padding:0;
  border:none;background:none;
  font-family:inherit;font-weight:300;
  cursor:pointer;
  /* 用 --txt-2 而不是 --txt-3：压盘内近黑玻璃实测 6.8:1，
     --txt-3 只有 3.75:1，低于 AA 4.5:1 —— 而它又是唯一的入口，不能做得这么暗。 */
  color:var(--txt-2);
  font-size:var(--fs-sw);
  letter-spacing:.08em;
  line-height:inherit;
  transition:font-size .44s cubic-bezier(.16,1,.3,1),
             letter-spacing .44s cubic-bezier(.16,1,.3,1),
             color .3s ease,
             text-shadow .34s ease;
}
/* 字号差就是状态指示：谁大谁是当前身份。
   不用选中背景/边框/色块 —— 那会让这一行变成"控件"，而不是"标题"。 */
.sw.on{
  font-size:var(--fs-title);
  /* 字距必须跟着字号走：不活跃那个只有 --fs-sw（≈ 标题的 52%），
     活跃这个已经涨到 --fs-title（桌面 36px）。原先两者共用 +.08em ——
     小字上刚好，36px 的「用户」却被撑开近 3px，正是"大字号字距过宽"的典型。
     收到 0 之后，字变大反而更紧，整行读起来是一个整体而不是两个字。 */
  letter-spacing:0;
  color:#fff;
  cursor:default;
  text-shadow:0 2px 18px rgba(255,255,255,.12);
}
/* 小字的字重不能跟着大字号一起"轻"：非活跃态在手机上只有 12.5px，
   300 的细体压在近黑盘上会发虚（微软雅黑还没有 Light 档，Windows 上直接落到 400，
   等于两边不一样）。这里给非活跃态定 400、活跃态保留 300 的细体气质 ——
   状态差从"字号 + 颜色"扩展到"字号 + 颜色 + 字重"，层次更清楚。 */
.sw:not(.on){font-weight:400}
.sw:focus-visible{outline:2px solid currentColor;outline-offset:6px;border-radius:4px}

.sw-txt{display:inline-block;position:relative}
/* text-indent 抵消 letter-spacing 在末字后留出的空隙，保持视觉居中。
   活跃态的字距已经收到 0，没有需要抵消的空隙 —— 留着 .14em 反而把字推偏，
   所以跟着一起归零（下划线也就能正对字形）。 */
.sw .sw-txt{text-indent:.08em}
.sw.on .sw-txt{text-indent:0}
/* 下划线挂在文字上（不是按钮盒），所以它始终贴着字，不会被行高拉开 */
.sw-txt::after{
  content:"";position:absolute;left:0;right:0;bottom:-.16em;height:2px;border-radius:2px;
  opacity:0;transform:scaleX(.55);
  transition:opacity .3s ease, transform .44s cubic-bezier(.16,1,.3,1);
}
.sw.on .sw-txt::after{opacity:1;transform:scaleX(1)}
.sw--owner.on .sw-txt::after{background:linear-gradient(90deg,transparent,var(--o-accent),transparent)}
.sw--guest.on .sw-txt::after{background:linear-gradient(90deg,transparent,var(--g-accent),transparent)}
/* 只在访客态给下划线加辉光：账号是日常态，克制；访客是特殊态，值得被点亮 */
body[data-id="guest"] .sw--guest.on .sw-txt::after{filter:drop-shadow(0 0 7px rgba(103,232,249,.85))}

/* 分隔竖线 */
.sw-sep{
  width:1px;height:calc(var(--fs-title) * .78);
  background:linear-gradient(180deg, transparent, rgba(255,255,255,.22), transparent);
  transition:opacity .3s ease;
}

/* 访客入口未开放时由 JS 摘掉（线上默认关闭访客模式）。
   用 display:none 而不是只靠 [hidden]：作者样式里的 display 会盖过 UA 的 [hidden]。
   分隔线一并去掉，否则这一行会剩一根孤零零的竖线。 */
body.no-guest .sw--guest{display:none}
body.no-guest .sw-sep{display:none}

/* 可点提示：非当前身份右上角一枚同色小点，慢呼吸 —— 只负责被看见。
   加一层同色辉光，否则在深色盘上会被读成一个脏点。
   偏移取 .62em 而不是更大的值：它是伸在按钮盒**外面**的，
   探得越远越容易在窄屏上撞到圆环的可用边界（实测 -.9em 时手机上只剩 2px 余量）。 */
.sw-dot{
  position:absolute;top:-.12em;right:-.62em;
  width:.20em;height:.20em;border-radius:50%;
  background:currentColor;opacity:0;
  box-shadow:0 0 8px currentColor;
}
.sw:not(.on) .sw-dot{opacity:1;animation:breathe 3.4s ease-in-out infinite}
@keyframes breathe{
  0%,100%{opacity:.28;transform:scale(.8)}
  50%{opacity:1;transform:scale(1.15)}
}

/* ============================================================
   盘内两个面板：同构、等高、交叉淡化
   grid 让两者占同一格，格高取两者最大值 → 切换时盘内一格都不动。
   ============================================================ */
.panes{display:grid;width:100%}
.pane{
  grid-area:1 / 1;
  width:100%;
  display:flex;flex-direction:column;align-items:center;
  justify-content:center;
  opacity:0;pointer-events:none;
  transform:translateY(7px);
  transition:opacity .32s ease, transform .42s cubic-bezier(.16,1,.3,1);
}
.pane.on{opacity:1;pointer-events:auto;transform:none;transition-delay:.10s}

/* ---------- 表单 ---------- */
.form{width:100%;display:flex;flex-direction:column;gap:var(--gap-form)}
/* 访客面板与用户面板**逐格对齐**：两个只读胶囊走的正是「账号 / 密码」的位置，
   再用 height:100% + 按钮 margin-top:auto 把少掉的那一行空隙全部塞到最后，
   于是主按钮占的正是「登录」的位置 —— 切过去时按钮一动不动，只有内容换了。
   这是对"访客面板少一行"最干净的收尾：空档就留在那一格，不摊到别处。 */
.pane--guest .form{height:100%}
.pane--guest .form .submit{margin-top:auto}

/* 反自动填充诱饵：必须"渲染出来但看不见、也碰不到"。
 * 用 display:none / visibility:hidden 会被浏览器直接跳过，诱饵等于白放；
 * 所以走"移出视口 + 缩到 1px + 全透明"这条路。绝对定位，不占 flex 间距。 */
.af-decoy{
  position:absolute;left:-9999px;top:-9999px;
  width:1px;height:1px;overflow:hidden;
  opacity:0;pointer-events:none;
}
.af-decoy input{width:1px;height:1px;border:0;padding:0;background:transparent}

.fld{
  position:relative;
  height:var(--h-field);
  display:flex;align-items:center;
  border:1px solid rgba(168,85,247,.34);
  border-radius:999px;
  background:rgba(255,255,255,.028);
  transition:border-color .18s ease, box-shadow .18s ease, background .18s ease;
}
.fld:not(.fld--info):focus-within{
  border-color:var(--o-accent);
  background:rgba(255,255,255,.05);
  box-shadow:0 0 0 3px rgba(168,85,247,.22), 0 0 22px rgba(168,85,247,.18);
}

.fld .ico{
  flex:none;
  width:calc(var(--h-field) * .96);
  display:flex;align-items:center;justify-content:center;
  color:#B9A7E6;
}
.fld .ico svg{width:calc(var(--h-field) * .42);height:calc(var(--h-field) * .42)}

.fld input{
  flex:1;min-width:0;
  height:100%;
  border:none;outline:none;background:transparent;
  color:var(--txt);
  font:inherit;
  font-size:var(--fs-field);
  padding:0 4px 0 0;
}
.fld input::placeholder{color:var(--txt-3)}
/* 交出 outline 后仍保留可见焦点环（自定义画在 .fld 上） */
.fld input:focus-visible{outline:none}
/* 浏览器自动填充（Chrome 会把已保存的账号填进来）会给输入框刷一层底色。
 * 这里**不能**用"不透明内阴影去盖"的老办法 —— 我们的输入框是透明的玻璃质感，
 * 那层阴影就是一块实心黑底，账号一被自动填充整格就变黑。
 * 改用超长过渡把浏览器注入的底色"拖住"，让它永远来不及显示出来，
 * 输入框保持透明；再配合 color-scheme:dark，即使该技巧失效，
 * 浏览器给的也是深色底而不是浅蓝，不会与深色界面撞色。 */
.fld input:-webkit-autofill,
.fld input:-webkit-autofill:hover,
.fld input:-webkit-autofill:focus,
.fld input:-webkit-autofill:active{
  -webkit-text-fill-color:var(--txt);
  caret-color:var(--txt);
  transition:background-color 9999999s ease-in-out 0s;
}

.eye{
  flex:none;
  width:calc(var(--h-field) * .82);
  height:100%;
  display:flex;align-items:center;justify-content:center;
  border:none;background:none;cursor:pointer;
  color:var(--txt-3);
  transition:color .16s ease;
}
.eye svg{width:calc(var(--h-field) * .40);height:calc(var(--h-field) * .40)}
.eye .i-off{display:none}
.eye.on .i-on{display:none}
.eye.on .i-off{display:block}
.eye:focus-visible{outline:2px solid var(--o-accent);outline-offset:-4px;border-radius:999px}
/* Edge / IE 会往密码框里塞自带的"显隐"与"清除"按钮。
 * 我们自己已经有一个眼睛图标了，不藏掉就会出现"两只眼睛"。 */
.fld input::-ms-reveal,
.fld input::-ms-clear{display:none;width:0;height:0}

/* ---------- 访客面板的只读信息胶囊 ----------
   与输入框同形同高，所以两个面板的纵向节奏完全一致。
   用同一种形状说"这里本来是能输入的地方，现在只读"。 */
.fld--info{
  border-color:rgba(34,211,238,.34);
  background:rgba(34,211,238,.045);
  cursor:default;
}
.fld--info .ico{color:var(--g-accent)}
.fld--info .txt{
  flex:1;min-width:0;
  font-size:var(--fs-field);
  color:#CFE9F5;
  text-align:left;
  letter-spacing:.01em;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.fld--info .tag{
  flex:none;
  margin-right:calc(var(--h-field) * .34);
  font-size:var(--fs-note);
  letter-spacing:.06em;
  color:#0B2B36;
  background:var(--g-accent);
  border-radius:999px;
  padding:.18em .6em;
}

/* ---------- 记住我 ---------- */
.remember{
  display:flex;align-items:center;gap:10px;
  align-self:flex-start;
  margin-top:calc(var(--disc) * .006);
  padding:4px 2px;
  cursor:pointer;
  color:var(--txt-2);
  font-size:var(--fs-note);
  user-select:none;
}
.remember input{position:absolute;opacity:0;width:0;height:0}
.remember .tick{
  width:1.15em;height:1.15em;flex:none;
  border:1.5px solid rgba(168,85,247,.55);
  border-radius:50%;
  position:relative;
  transition:border-color .16s ease, background .16s ease;
}
/* 起始态用 scale(.4)+opacity:0，不用 scale(0)：
   现实里没有东西从"零"里冒出来；scale(0) 会显得凭空出现，
   也会把这条过冲曲线（1.3）的突兀感放大。 */
.remember .tick::after{
  content:"";position:absolute;inset:2.5px;
  border-radius:50%;
  background:var(--o-accent);
  transform:scale(.4);opacity:0;
  transition:transform .18s cubic-bezier(.2,.9,.3,1.3),opacity .18s ease;
}
.remember input:checked + .tick{border-color:var(--o-accent);background:rgba(168,85,247,.16)}
.remember input:checked + .tick::after{transform:scale(1);opacity:1}
.remember input:focus-visible + .tick{outline:2px solid var(--o-accent);outline-offset:2px}

/* ---------- 提示 ---------- */
.msg{
  margin:0;
  min-height:1.3em;
  align-self:flex-start;
  font-size:clamp(12px, calc(var(--disc) * .020), 15px);
  color:var(--bad);
  opacity:0;
  transition:opacity .18s ease;
}
.msg.bad,.msg.ok,.msg.note{opacity:1}
.msg.ok{color:var(--ok)}
/* 中性提示（用在"访客后端未接"这类非报错的信息上） */
.msg.note{color:var(--note)}

/* ---------- 主按钮 ---------- */
.submit{
  position:relative;
  width:100%;
  height:var(--h-btn);
  border:none;cursor:pointer;
  border-radius:999px;
  background:linear-gradient(100deg, var(--btn-g1) 0%, var(--btn-g2) 52%, var(--btn-g3) 100%);
  color:#fff;
  font:inherit;
  font-size:var(--fs-btn);
  font-weight:500;
  letter-spacing:.06em;
  display:flex;align-items:center;justify-content:center;
  transition:transform .16s cubic-bezier(.2,.9,.3,1.4), box-shadow .25s ease, filter .25s ease;
  box-shadow:0 10px 30px rgba(168,85,247,.34), 0 4px 14px rgba(225,29,72,.24);
}
.submit:active:not(:disabled){transform:translateY(0) scale(.99)}
.submit:focus-visible{outline:2px solid #fff;outline-offset:3px}
.submit:disabled{cursor:default}
/* 访客主按钮：同一套胶囊、同一高度与位置，只换身份色 */
.submit--guest{
  background:linear-gradient(100deg, var(--btn-x1) 0%, var(--btn-x2) 52%, var(--btn-x3) 100%);
  box-shadow:0 10px 30px rgba(34,211,238,.30), 0 4px 14px rgba(79,70,229,.26);
}

.submit-arrow{
  position:absolute;right:calc(var(--h-btn) * .30);
  display:flex;align-items:center;
  transition:transform .2s cubic-bezier(.2,.9,.3,1.4);
}
.submit-arrow svg{width:calc(var(--fs-btn) * 1.15);height:calc(var(--fs-btn) * 1.15)}

.spinner{
  display:none;
  width:calc(var(--fs-btn) * 1.05);height:calc(var(--fs-btn) * 1.05);
  border:2px solid rgba(255,255,255,.35);
  border-top-color:#fff;
  border-radius:50%;
  animation:spin .7s linear infinite;
}
@keyframes spin{to{transform:rotate(360deg)}}
.submit.busy .submit-txt,.submit.busy .submit-arrow{visibility:hidden}
.submit.busy .spinner{display:block}
.submit.done{filter:saturate(1.2) brightness(1.08)}

/* ---------- 分隔线 + 品牌 ---------- */
.rule{
  width:100%;
  height:1px;
  margin:var(--m-rule-top) 0 var(--m-rule-bottom);
  background:linear-gradient(90deg, transparent, rgba(168,85,247,.34) 22%, rgba(255,255,255,.16) 50%, rgba(168,85,247,.34) 78%, transparent);
  transition:background .55s ease;
}
body[data-id="guest"] .rule{
  background:linear-gradient(90deg, transparent, rgba(34,211,238,.34) 22%, rgba(255,255,255,.16) 50%, rgba(34,211,238,.34) 78%, transparent);
}

.brand{display:flex;flex-direction:column;align-items:center;gap:var(--gap-brand)}
.brand-logo{
  width:clamp(36px, calc(var(--disc) * .092), 72px);
  height:clamp(36px, calc(var(--disc) * .092), 72px);
  border-radius:22%;
  overflow:hidden;
  display:block;
  border:1px solid rgba(255,255,255,.14);
  box-shadow:0 6px 22px rgba(168,85,247,.36), 0 2px 10px rgba(225,29,72,.26);
  transition:box-shadow .55s ease;
}
body[data-id="guest"] .brand-logo{
  box-shadow:0 6px 22px rgba(34,211,238,.36), 0 2px 10px rgba(79,70,229,.26);
}
.brand-logo img{width:100%;height:100%;object-fit:cover;display:block}
.brand-name{
  font-size:var(--fs-brand);
  font-weight:700;
  letter-spacing:.3px;
  background:linear-gradient(92deg,#67E8F9 0%,#A5B4FC 42%,#C084FC 66%,#F0ABFC 100%);
  -webkit-background-clip:text;background-clip:text;
  -webkit-text-fill-color:transparent;
  filter:drop-shadow(0 0 10px rgba(168,85,247,.4));
}

/* ---------- 错误抖动 ---------- */
@keyframes shake{
  10%,90%{transform:translateX(-4px)}
  20%,80%{transform:translateX(5px)}
  30%,50%,70%{transform:translateX(-7px)}
  40%,60%{transform:translateX(7px)}
}
.disc.shake{animation:shake .5s cubic-bezier(.36,.07,.19,.97)}

/* ---------- 登入过渡：整页渐隐 ----------
 * 整个舞台（含背景线框）一起淡到黑场，再交给工作台从黑场淡入。
 * 两端共用同一片黑，读起来是一次连续的渐隐，而不是两个独立动作。
 * 时长为「快」档（.13s）：整段过渡由两半拼成（登录页淡出 + 工作台淡入），
 * 所以这里给短一点，两半加起来才落在半秒级别。
 * 改这里要同步看 styles.css 的 .enter-veil。 */
.stage{transition:opacity .13s cubic-bezier(.4,0,.2,1)}
body.entering .stage{opacity:0}

/* 注：工作台侧的「接续入场」遮罩 .enter-veil 属于 index.html，
 * 其样式放在 styles.css，不要写在这里。 */

/* ============================================================
   悬停反馈：只在"有精确指针"的设备上启用
   触摸屏上 :hover 会粘住 —— 点完访客后"用户"仍保持放大的样子，
   读起来像两个身份同时生效。用 media query 直接把它们关掉。
   ============================================================ */
@media (hover:hover) and (pointer:fine){
  .fld:not(.fld--info):hover{border-color:rgba(168,85,247,.5)}
  .eye:hover{color:#C9B8F5}
  .remember:hover{color:var(--txt)}
  .submit:hover:not(:disabled){
    transform:translateY(-1px);
    box-shadow:0 14px 38px rgba(168,85,247,.46), 0 6px 18px rgba(225,29,72,.3);
    filter:saturate(1.06);
  }
  .submit--guest:hover:not(:disabled){
    box-shadow:0 14px 38px rgba(34,211,238,.44), 0 6px 18px rgba(79,70,229,.32);
  }
  .submit:hover:not(:disabled) .submit-arrow{transform:translateX(3px)}
  /* 悬停时先"长大"一点并染上自己的身份色 ——
     这本身就是"点下去它会变成主角"的预告。 */
  .sw:not(.on):hover{color:var(--txt);font-size:var(--fs-sw-hover)}
  .sw--owner:not(.on):hover{color:var(--o-accent);text-shadow:0 0 20px rgba(168,85,247,.55)}
  .sw--guest:not(.on):hover{color:var(--g-accent);text-shadow:0 0 20px rgba(103,232,249,.55)}
  .switch:hover .sw-sep{opacity:.45}
}

/* ============================================================
   触摸设备：把开关行的命中区扩到"能一次点中"
   未激活身份只有两个字（手机上约 25×19px），远小于建议的 44px。
   用伪元素向外扩，不动任何布局尺寸：
     · 垂直扩 --fs-title × .6（上下各一）→ 命中高 ≈ 48px
     · 水平扩 --disc × .014 → 两个按钮的扩展区正好在 gap 中线附近相接，不重叠
   依据 WCAG 2.5.8（AA 24×24）与 2.5.5（AAA 44×44 的间隔例外条款）。
   ============================================================ */
@media (pointer:coarse){
  .sw{-webkit-tap-highlight-color:transparent}
  .sw::before{
    content:"";position:absolute;
    inset:calc(var(--fs-title) * -.6) calc(var(--disc) * -.014);
    border-radius:10px;
  }
  .submit{-webkit-tap-highlight-color:transparent}
}

/* ============================================================
   响应式
   ============================================================ */
/* 平板（768~1366 宽、高度够）：屏幕上去了，圆盘也该跟着上去 ——
   508px 的上限原本是按笔记本定的，放在 12.9" 上只占屏宽 1/3，显得空。
   同一段区间也覆盖 1366×768 这类小笔记本，圆盘略增约 4%，观感更满。 */
@media (min-width:768px) and (max-width:1366px) and (min-height:561px){
  :root{--disc:max(340px, min(60vmin, 620px))}
}

@media (max-width:520px){
  :root{
    --col:74%;
    /* 圆盘尺寸与「加刻度环之前」严格一致：94vmin，下限 300px。
     * 曾为给刻度带让位压到 88vmin —— 结果是手机端圆盘整体缩了 6.4%
     * （88/94 = .9362，21 档视口实测差值就是这个数），纯属过度保守：
     * 刻度只向外伸 --tick（本档为 2.4% 直径），320px 视口实测
     * 「圆盘 + 刻度」仍有约 9px 水平余量，根本不必缩圆盘。
     * 再套一层 min(300px, 95vw)：300px 的硬下限在 <319px 视口会反超 94vmin，
     * 让「圆盘 + 刻度」（×1.048）顶出视口（300px 视口实测溢出 5px）。
     * 95vw 只在 <316px 生效，真机最小 320px 的取值完全不受影响。 */
    --disc:max(min(300px, 95vw), min(94vmin, 760px));
    --tick:calc(var(--disc) * .024);
    --tick-s:calc(var(--disc) * .013);
    --fs-brand:clamp(12.5px, calc(var(--disc) * .040), 30px);
    /* 窄屏圆盘本身小，纵向间距统一收紧，
     * 让整块内容仍能完整落在圆内。 */
    --gap-form:calc(var(--disc) * .020);
    --gap-brand:calc(var(--disc) * .010);
    /* 这一档的间距必须比原来更紧：圆是"上窄下窄"的，
       而开关行是顶部最宽的一件。内容一高，开关行就顶到圆的最窄处，
       两侧的"用""客"会被圆环切角（390px 实测越界 2.2px）。
       把纵向留白压下来，让内容占比从 .96 回到 .90，
       开关行处可用弦宽从 96px 涨到 145px（开关行本身 110px）。 */
    --m-title:calc(var(--disc) * .050);
    --m-rule-top:calc(var(--disc) * .038);
    --m-rule-bottom:calc(var(--disc) * .020);
  }
  /* 外框留白收窄，给圆盘腾出垂直空间 */
  .stage{--stage-pad:12px}
  .switch{gap:calc(var(--disc) * .036)}
  /* 品牌是全页最可让的一块，省下来的高度全给它 —— 输入框与主按钮
     的 clamp 下限（36 / 38px）刻意不动，手机上那是命中区的底线。 */
  .brand-logo{
    width:clamp(30px, calc(var(--disc) * .088), 72px);
    height:clamp(30px, calc(var(--disc) * .088), 72px);
  }
}
/* 注：矮屏的收紧统一由下面的 max-height 变量块负责，
 * 不要再单独写 .title/.rule 的 margin —— 直接写死会盖过变量，反而更宽松。 */
/* 矮屏（横屏手机 / 小尺寸笔记本）：vmin 由高度决定，圆盘随之变小，
 * 在窄屏那套收紧值的基础上再压一档，否则内容会贴住甚至越过圆环。 */
@media (max-height:700px){
  :root{
    --tick:calc(var(--disc) * .016);
    --tick-s:calc(var(--disc) * .009);
    --gap-form:calc(var(--disc) * .018);
    --gap-brand:calc(var(--disc) * .010);
    --m-title:calc(var(--disc) * .046);
    --m-rule-top:calc(var(--disc) * .042);
    --m-rule-bottom:calc(var(--disc) * .024);
  }
}
/* 横屏手机（高 ≤ 560）：圆盘由高度决定，加一圈刻度后会顶破视口。
   这里连各尺寸的 clamp 下限一起下调 —— 只缩圆盘不缩内容的话，
   内容占比反而上升，标题行会被圆环切掉两个角（破相）。 */
@media (max-height:560px){
  :root{
    --disc:max(268px, min(76vmin, 460px));
    --tick:calc(var(--disc) * .016);
    --tick-s:calc(var(--disc) * .009);
    --h-field:clamp(30px, calc(var(--disc) * .092), 68px);
    --h-btn:clamp(32px, calc(var(--disc) * .098), 72px);
    --fs-title:clamp(18px, calc(var(--disc) * .075), 54px);
    --fs-brand:clamp(11.5px, calc(var(--disc) * .040), 30px);
    --gap-form:calc(var(--disc) * .018);
    --gap-brand:calc(var(--disc) * .010);
    --m-title:calc(var(--disc) * .040);
    --m-rule-top:calc(var(--disc) * .032);
    --m-rule-bottom:calc(var(--disc) * .018);
  }
  .stage{--stage-pad:10px}
  .switch{gap:calc(var(--disc) * .020)}
  .brand-logo{
    width:clamp(26px, calc(var(--disc) * .085), 72px);
    height:clamp(26px, calc(var(--disc) * .085), 72px);
  }
}
/* 极小屏（≤380px）：圆盘只有 ~300px，上面那套下限还是会"顶住"，
 * 这里再降一档。代价是输入框偏小，但至少内容完整落在圆内、
 * 不会出现标题被圆环切掉的破相。 */
@media (max-width:380px){
  :root{
    --h-field:clamp(32px, calc(var(--disc) * .100), 68px);
    --h-btn:clamp(34px, calc(var(--disc) * .105), 72px);
    --fs-title:clamp(20px, calc(var(--disc) * .075), 54px);
    --fs-brand:clamp(12px, calc(var(--disc) * .040), 30px);
    --gap-form:calc(var(--disc) * .016);
    --gap-brand:calc(var(--disc) * .010);
    --m-title:calc(var(--disc) * .046);
    --m-rule-top:calc(var(--disc) * .030);
    --m-rule-bottom:calc(var(--disc) * .018);
  }
  .brand-logo{
    width:clamp(28px, calc(var(--disc) * .092), 72px);
    height:clamp(28px, calc(var(--disc) * .092), 72px);
  }
  .switch{gap:calc(var(--disc) * .026)}
  .remember{padding:1px 2px}
  .msg{min-height:1.05em}
}

/* ---------- 无障碍：削弱动效 ---------- */
@media (prefers-reduced-motion:reduce){
  /* 扫掠的时间源在 .gate（见上文），这里一并停掉 */
  .gate{transition:none}
  .disc{transition:none;opacity:1}
  /* 外光晕的两层伪元素：直接给到位，不淡入淡出 */
  .disc::before,.gate::before{transition:none}
  .disc.shake{animation:none}
  .tint,.scanlines,.ticks,.rule,.brand-logo,.sw,.sw-txt::after,.pane,
  .remember .tick,.remember .tick::after{transition:none}
  body[data-id="guest"] .ticks{animation:none}
  .sw:not(.on) .sw-dot{animation:none;opacity:.7}
  /* 扫掠与脉冲是纯装饰，直接不给 —— 一下子换完色更省事，也更不刺激。
     位移与缩放被停掉后，"闪一下"反而是多余的刺激。
     同时显式写 animation:none（不再只靠 display:none）：两者都关，
     以后哪怕有人改了这一行的 display，动画也不会漏网重跑。 */
  .gate.switching .sweep,.gate.switching .pulse{display:none;animation:none}
  .pane{transform:none}
  .spinner{animation-duration:2s}
  /* 淡出属于低刺激动效，保留但缩短；不做位移或缩放 */
  .stage{transition:opacity .2s linear}
  .submit,.submit-arrow{transition:none}
  .eye,.fld{transition:none}
}

/* ================= 移动端平台层（2026-09-18）=================
 * 与 styles.css 里那一节同源，只是登录页的版式与控件不同，取值要单独写。
 * 逐条理由见 styles.css 顶部同名注释，这里只记登录页特有的部分。 */

/* 横屏时系统会给正文自动放大字号（font inflation），版面会突然变形 */
html{-webkit-text-size-adjust:100%;text-size-adjust:100%}
/* body 本来就是 overflow:hidden，不存在下拉刷新；写上是为了与工作台口径一致 */
body{overscroll-behavior:none}

/* ---------- 输入框字号下限 16px ----------
 * 登录页输入框字号走 --fs-field = clamp(13px, 圆盘 * .026, 20px)，
 * 手机上圆盘小，一律落到 13px —— 正好踩中 iOS「聚焦就整页放大、且不缩回」的阈值。
 * 这里只给字号加 16px 下界，**不动 --h-field**：胶囊在手机上本来就有 40px 上下高，
 * 16px 的字放得进去；把高度也一起改大才是把版面撑坏。
 * 只对触屏生效，桌面仍走原公式（大屏本来就会算到 16~20px，等于没变）。 */
@media (pointer:coarse){
  :root{--fs-field:max(16px, clamp(13px, calc(var(--disc) * .026), 20px))}
}

/* ---------- 控件语义与点按反馈 ----------
 * 登录页能点的只有这四处。user-select 只加在按钮类控件上：
 * .remember 已经自带 user-select:none（见上文），账号密码框是内容，保持可选中。 */
.sw,.submit,.eye,.remember,.submit-arrow{
  touch-action:manipulation;
  -webkit-touch-callout:none;
}
.sw,.submit,.eye{-webkit-user-select:none;user-select:none}

/* ---------- 刘海 / 灵动岛 ----------
 * 登录页早就带了 viewport-fit=cover（内容要铺到刘海下面才够"满"），
 * 所以圆盘与表单必须自己退开安全区。
 * 用 max() 而不是 calc(留白 + env())：这一版式是"等高等宽居中 + overflow:hidden"，
 * 硬加上一整条状态栏高度，会在纵向本来就吃紧的机型上把表单压出屏幕；
 * max() 只在刘海确实大于原留白时才生效，增量最小。
 * 四边都写：横屏时刘海在左右两侧。 */
.stage{
  padding-top:max(var(--stage-pad), env(safe-area-inset-top));
  padding-right:max(var(--stage-pad), env(safe-area-inset-right));
  padding-bottom:max(var(--stage-pad), env(safe-area-inset-bottom));
  padding-left:max(var(--stage-pad), env(safe-area-inset-left));
}

/* ══════════════════════════════════════════════════════════════
   亮色登录页（2026-10-12）
   ──────────────────────────────────────────────────────────────
   与工作台同源：白玻璃盘 + 深墨文字。霓虹环保留（是品牌识别），
   只把"底"与"文字"翻过来；外光晕压淡，免得白底上糊成一团。
   开关与工作台共用（localStorage 键 wb_theme），首帧前在 login.html 打上。
   ══════════════════════════════════════════════════════════════ */
:root[data-theme="light"]{
  color-scheme:light;
  --txt:#0F172A;
  --txt-2:#4A5568;
  --txt-3:#6B7280;
  --bad:#DC2626;
  --ok:#15803D;
  --note:#0369A1;
  /* 身份色保持（品牌），只把外光晕压淡，白底上才不糊 */
  --o-glow:rgba(168,85,247,.22);
  --o-glow-2:rgba(225,29,72,.10);
  --g-glow:rgba(34,211,238,.22);
  --g-glow-2:rgba(45,212,191,.10);
}
/* 底色：黑场 → 浅色。登录页"渐隐"是让 .stage 淡出、露出 body 底，
   所以这里一改，"渐隐到黑"自动变成"渐隐到浅"，与工作台的白场遮罩接得上。 */
:root[data-theme="light"] body{
  background:
    radial-gradient(880px 600px at 12% -8%,rgba(168,85,247,.16),transparent 60%),
    radial-gradient(820px 640px at 92% 6%,rgba(34,211,238,.13),transparent 60%),
    radial-gradient(900px 700px at 60% 112%,rgba(236,72,153,.10),transparent 62%),
    linear-gradient(180deg,#F8FAFE 0%,#EDF1F8 100%);
}
/* 背景霓虹线框：白底上把亮线压暗，只留很淡的一层装饰 */
:root[data-theme="light"] .art{opacity:.30;filter:brightness(.42) saturate(1.7)}
/* 盘内玻璃：暗玻璃 → 白玻璃（三个身份色光斑保留，白底上更柔和） */
:root[data-theme="light"] .disc-inner{
  background:
    radial-gradient(72% 58% at 50% 84%, var(--o-tint-2), transparent 72%),
    radial-gradient(62% 52% at 20% 26%, var(--o-tint-1), transparent 72%),
    radial-gradient(70% 60% at 82% 40%, var(--o-tint-3), transparent 72%),
    linear-gradient(180deg, rgba(255,255,255,.95), rgba(247,249,254,.97));
  border-color:rgba(15,23,42,.06);
}
/* 盘内压暗 / 内圈细线：白底改成淡墨 */
:root[data-theme="light"] .disc{box-shadow:inset 0 0 40px rgba(15,23,42,.05)}
:root[data-theme="light"] .disc::after{border-color:rgba(15,23,42,.06)}
:root[data-theme="light"] body[data-id="guest"] .disc::after{
  border-color:rgba(14,116,144,.20);box-shadow:inset 0 0 26px rgba(34,211,238,.10);
}
/* 亮弧：白底上要改用深色前沿（否则白弧在白底上等于隐形） */
:root[data-theme="light"] .sweep{
  background:conic-gradient(from 0deg,
    transparent 0deg,
    transparent calc(var(--reveal) - 30deg),
    rgba(15,23,42,.05) calc(var(--reveal) - 16deg),
    rgba(15,23,42,.55) var(--reveal),
    transparent calc(var(--reveal) + 7deg));
}
/* 标题开关：当前身份是白字，白底上要改成深墨 */
:root[data-theme="light"] .sw.on{color:#0F172A;text-shadow:none}
/* 访客只读胶囊：青底文字在白底上改用深青 */
:root[data-theme="light"] .fld--info{border-color:rgba(14,116,144,.28);background:rgba(34,211,238,.07)}
:root[data-theme="light"] .fld--info .txt{color:#0B4A5E}
/* 身份下划线的发光：白底不发光 */
:root[data-theme="light"] body[data-id="guest"] .sw--guest.on .sw-txt::after{filter:none}
/* 品牌字：原渐变是给近黑底调的（白底上几乎看不见），换成深一档并去掉发光 */
:root[data-theme="light"] .brand-name{
  background:linear-gradient(92deg,#4F46E5 0%,#7C3AED 50%,#C026D3 100%);
  filter:none;
}
