/* Remote workspace portal. Theme colors are centralized below; panels manage their own
   overflow so desktop and mobile layouts remain usable within the viewport. */
/* 修改记录（1.4.33 / 1.3.21）：登录页上下布局断点由 1100px 收紧到 900px，避免桌面窗口轻微缩放时整页跳变。 */
/* 修改记录（1.4.32 / 1.3.20）：文件夹大小的状态样式（.fm-size[data-state]），
   拖动移动的落点反馈（.fm-drop-ok 可放下、.fm-drop-no 不可放下）与多选拖动标签（.fm-drag-ghost）。

   修改记录（1.4.29 / 1.3.17 基础上的增量修改，版本号未改动）：
   1. 文件管理拖放上传的“黑洞”落点（.fm-drop / .fm-hole / .fm-drop-chip），覆盖在文件管理区域上、不占布局；
      动态黑洞由 app.js 的 BlackHole 画在 .fm-hole-canvas 上（.fm-hole.live），静态分层落点（透镜光环、吸积盘、视界）
      用于减少动态效果、无法绘制与“不支持”状态；减少动态效果时吸入与过渡全部关闭。
   2. “指令”面板说明中的“会先清空当前输入”加粗显示（.term-cmds-note strong）。

   修改记录（1.4.28 / 1.3.16 基础上的增量修改，版本号未改动）：
   1. 双 IP 常驻信号时序修复（见“常驻信号时序”一节）：光点改为一个元素走完整条连线、匀速无跳变，
      服务器端光晕与光点同一周期、在光点外缘接触节点的时刻才开始；复制反馈与地址更新提示不再打乱同步。
   2. “指令”面板分组标题不再强制全大写，显示为 Claude Code / Codex。

   修改记录（1.4.27 / 1.3.15 基础上的增量修改，版本号未改动）：
   1. 双 IP 动效状态（见“双 IP 路由的动效状态”一节）：ok 时连线上低频常驻信号（3.2 秒一周期，光点由用户端
      移向服务器端、到达时节点轻微光晕）；pending 柔和等待脉冲；none 弱化、无信号；is-settling / ip-updated
      短反馈。全部为伪元素 transform / opacity 动画，不改变布局；减少动态效果时全部关闭。
   2. 新增服务器系统时间样式（.server-clock / .head-clock）：桌面在双 IP 卡片左侧；手机及“较窄桌面 + 长 IP”
      时改到标题上方眉标位置，不增加标题栏高度。
   3. 网页终端快捷栏分组（按键 | Claude、Codex、指令 | 粘贴）与“指令”面板（.term-cmds，桌面两列、手机单列可滚动）。
   4. 扫描修复：400px 以下手机的时钟行去掉秒数、收紧字距，半小时时区（如 UTC+5:30）也不会挤到双 IP 卡片。

   修改记录（增量修改，部署步骤见 remote-agent-cli.py 顶部）：
   1. 双 IP 路由样式 .ip-route：节点 + 连线、删除线用背景渐变绘制（换行后每行都有）、入场动画、复制反馈；
      手机上标题栏改为网格，长地址或屏幕 ≤360px 时双 IP 移到第二行靠右。
   2. 传输列表收起样式（.collapsed / .peeking / .t-toggle / .t-peek），传输框底部增加间距。
   3. 浅色主题整体调暗，不再有大面积纯白（底色 #E1DFD7、面板 #EEECE6），文字对比度 ≥ 4.5:1。
   4. 深浅两套主题加强边界：--line / --line2 / --line3 分为分隔线、控件边框、悬停强调三级；
      画面方向卡片、流畅 / 高清切换、登录页主题按钮改用控件级边框。 */
@font-face{font-family:"Geist";src:url(/gateway/geist.woff2) format("woff2");font-weight:100 900;font-display:swap}
@font-face{font-family:"Geist Mono";src:url(/gateway/geist-mono.woff2) format("woff2");font-weight:100 900;font-display:swap}
:root{color-scheme:dark;
  /* 边界分三级（对底色的对比度）：--line 分隔线与卡片外框 ≈1.6:1，--line2 按钮 / 输入框等可交互控件 ≈2.8:1，
     --line3 悬停与强调 ≈3.9:1；面板与底色也拉开一档。以后调色时保持这三级的相对关系，不要再调回接近底色。 */
  --bg:#0E0F11;--panel:#181A1E;--raise:#22252B;--raise2:#2E3239;--line:#33383F;--line2:#565C66;--line3:#6E7580;
  --text:#ECEDEE;--text2:#A6ACB4;--text3:#868D95;
  --signal:#FFB224;--signal-hover:#FFC24D;--on-signal:#141109;--signal-ink:#FFB224;--signal-display:#FFB224;
  --signal-soft:rgba(255,178,36,.14);--signal-line:rgba(255,178,36,.35);--signal-tint:rgba(255,178,36,.06);--signal-tint2:rgba(255,178,36,.05);--signal-ring:rgba(255,178,36,.15);--signal-track:rgba(255,178,36,.2);
  --danger:#FF6B6B;--danger-hover:#FF8585;--on-danger:#170707;--danger-ink:#FF8A8A;--danger-warn:#FF9B9B;--danger-line:rgba(255,107,107,.45);--danger-tint:rgba(255,107,107,.1);--danger-tint2:rgba(255,107,107,.08);
  --ok:#34D399;--ok-ink:#34D399;--ok-halo:rgba(52,211,153,.16);--busy:#F2B33D;
  --track:#343940;--folder:#FFB224;--check:#FFB224;--row-hover:#16181B;--drawer-bg:#121417;--surface-float:#1B1E22;--dialog-bg:#16181B;--input-bg:#0E0F11;
  --scrim:rgba(8,9,10,.55);--overlay:rgba(5,6,7,.7);--shadow-float:0 18px 50px rgba(0,0,0,.5);--shadow-dialog:0 30px 80px rgba(0,0,0,.6);
  --term-bg:#08090A;--m-space:#5B9DFF;--m-files:#C4A3FF;--m-traffic:#34D399;
  --mono:"Geist Mono",ui-monospace,"SFMono-Regular","Cascadia Mono",Consolas,"DejaVu Sans Mono",monospace;
  --sans:"Geist",system-ui,-apple-system,"Segoe UI","PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC","Noto Sans SC",sans-serif}
:root[data-theme="light"]{color-scheme:light;
  /* 浅色主题是“暗调纸面”而不是纯白：最亮的面（输入框、浮层）亮度约 0.86，页面底色约 0.76，
     比原先的纯白面板（1.0）低约 15%～25%，长时间使用不刺眼。正文、次要文字、强调文字、成功 / 危险文字
     在底色和面板上的对比度都保持 ≥ 4.5:1（以后调整色值时需重新核对）。不要再引入 #FFFFFF 大面积底色。
     边界同样分三级：--line ≈1.35～1.5:1，--line2（控件）≈2.4～2.7:1，--line3 ≈3:1；面板比底色亮一档以区分区块。 */
  --bg:#E1DFD7;--panel:#EEECE6;--raise:#D6D4CC;--raise2:#CBC9C0;--line:#C4C2B9;--line2:#96948B;--line3:#7E7C73;
  --text:#1B1C1F;--text2:#41454C;--text3:#545960;
  --signal:#FFB224;--signal-hover:#F5A300;--on-signal:#1A1203;--signal-ink:#8A5100;--signal-display:#A86500;
  --signal-soft:rgba(198,126,0,.2);--signal-line:rgba(138,81,0,.4);--signal-tint:rgba(255,178,36,.14);--signal-tint2:rgba(255,178,36,.11);--signal-ring:rgba(198,126,0,.24);--signal-track:rgba(138,81,0,.18);
  --danger:#C92828;--danger-hover:#B01F1F;--on-danger:#FFFFFF;--danger-ink:#A81C1C;--danger-warn:#961818;--danger-line:rgba(201,40,40,.42);--danger-tint:rgba(201,40,40,.08);--danger-tint2:rgba(201,40,40,.08);
  --ok:#12965F;--ok-ink:#08653F;--ok-halo:rgba(18,150,95,.18);--busy:#B87D00;
  --track:#CBC9C0;--folder:#9A6300;--check:#8A5100;--row-hover:#D8D6CE;--drawer-bg:#ECEAE4;--surface-float:#EFEDE8;--dialog-bg:#EFEDE8;--input-bg:#F1EFEA;
  --scrim:rgba(22,23,26,.3);--overlay:rgba(22,23,26,.45);--shadow-float:0 14px 40px rgba(40,36,28,.16);--shadow-dialog:0 24px 70px rgba(40,36,28,.22);
  --term-bg:#ECEAE4;--m-space:#2A64D6;--m-files:#7546DE;--m-traffic:#0A7650}
html.theme-anim *,html.theme-anim *::before,html.theme-anim *::after{transition:background-color .3s ease,border-color .3s ease,color .3s ease,fill .3s ease,stroke .3s ease,box-shadow .3s ease!important}

/* ---------- base ---------- */
*{box-sizing:border-box}
html{height:100%;background:var(--bg)}
body{min-height:100%;margin:0;background:var(--bg);color:var(--text);font-family:var(--sans);font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
button,input,select,textarea{font:inherit;color:inherit}
button{cursor:pointer}
a{color:var(--signal-ink)}
:focus-visible{outline:2px solid var(--signal-ink);outline-offset:2px}
[hidden]{display:none!important}
.visually-hidden{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0 0 0 0)!important;white-space:nowrap!important;border:0!important}
code{font-family:var(--mono);font-feature-settings:"tnum" 1}
.muted{color:var(--text3)}
/* 分区小标题；不要叫 .label，文件行里的文件名元素也叫 label（曾因此把文件名渲染成大写小字）。 */
.kicker{margin:0 0 12px;font-family:var(--mono);font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--text3)}
/* .mark 是内联 SVG 图标（见网关 LOGO_MARK），自带圆角外框；线条颜色跟随文字颜色。 */
.mark{display:block;flex:none;width:40px;height:40px;color:var(--text)}
.mark svg{display:block;width:100%;height:100%}
.mark .mk-accent{stroke:var(--signal-ink)}.mark .mk-dot{fill:var(--signal-ink)}
@keyframes fade{from{opacity:0}}
@keyframes pop{from{opacity:0;transform:translateY(8px) scale(.985)}}
@keyframes rise{from{opacity:0;transform:translateY(8px)}}
@keyframes spin{to{transform:rotate(360deg)}}

/* ---------- buttons ---------- */
.btn,.dlg-actions button,.dlg-menu button,.picker-list button,.fm-transfer button,.fm-transfers-head button{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:40px;margin:0;padding:0 16px;border:1px solid var(--line2);border-radius:8px;background:transparent;color:var(--text);font-size:13px;font-weight:500;line-height:1.2;text-decoration:none;white-space:nowrap;transition:background .15s,border-color .15s,transform .1s}
.btn:hover,.dlg-actions button:hover,.dlg-menu button:hover,.fm-transfer button:hover,.fm-transfers-head button:hover{background:var(--raise);border-color:var(--line3)}
.btn:active{transform:translateY(1px)}
.btn svg{flex:none;width:16px;height:16px}
.btn.primary,.dlg-actions button:not([class]),.dlg-actions button[class=""]{border-color:var(--signal);background:var(--signal);color:var(--on-signal);font-weight:600}
.btn.primary:hover,.dlg-actions button:not([class]):hover,.dlg-actions button[class=""]:hover{border-color:var(--signal-hover);background:var(--signal-hover)}
.btn.danger,.dlg-menu button.danger{border-color:var(--danger-line);color:var(--danger-ink)}
.btn.danger:hover,.dlg-menu button.danger:hover{border-color:var(--danger);background:var(--danger-tint)}
.dlg-actions button.danger-strong{border-color:var(--danger);background:var(--danger);color:var(--on-danger);font-weight:600}
.dlg-actions button.danger-strong:hover{border-color:var(--danger-hover);background:var(--danger-hover)}
.btn.ghost,.dlg-actions button.secondary{border-color:transparent;color:var(--text2)}
.btn.ghost:hover,.dlg-actions button.secondary:hover{color:var(--text)}
.btn.big{min-height:48px;padding:0 22px;font-size:14px}
.btn[disabled],button[disabled]{opacity:.45;cursor:not-allowed}
.icon-btn{display:grid;flex:none;place-items:center;width:44px;height:44px;margin:0;padding:0;border:0;border-radius:8px;background:transparent;color:var(--text3)}
.icon-btn:hover{background:var(--raise);color:var(--text)}
.icon-btn svg{width:18px;height:18px}
.icon-btn[disabled]{opacity:.35}

/* ---------- forms & messages ---------- */
.field{display:grid;gap:6px;margin-bottom:16px}
.field label,.dlg-body label{font-size:12px;color:var(--text2)}
.field input,.dlg-body input[type=text],.dlg-body input[type=password],.dlg-body textarea{width:100%;height:44px;padding:0 12px;border:1px solid var(--line2);border-radius:8px;background:var(--input-bg);color:var(--text);font-family:var(--mono)}
.dlg-body textarea{height:auto;min-height:140px;padding:10px 12px;resize:vertical}
.field input:focus,.dlg-body input:focus,.dlg-body textarea:focus{outline:none;border-color:var(--signal-ink);box-shadow:0 0 0 3px var(--signal-ring)}
.flash{margin:0 0 16px;padding:10px 12px;border-radius:8px;background:var(--raise);font-size:13px}
.flash.error{background:var(--danger-tint2);color:var(--danger-warn)}
.flash.ok{color:var(--ok-ink)}

/* ---------- login & notice pages ---------- */
.login{display:grid;grid-template-columns:minmax(0,1.35fr) minmax(380px,1fr);min-height:100vh;min-height:100dvh}
.login-left{position:relative;display:flex;flex-direction:column;justify-content:space-between;gap:40px;padding:48px 56px 44px;border-right:1px solid var(--line);overflow:hidden}
.login-left::before{content:"";position:absolute;inset:0;background-image:linear-gradient(var(--line) 1px,transparent 1px),linear-gradient(90deg,var(--line) 1px,transparent 1px);background-size:72px 72px;opacity:.35;-webkit-mask-image:linear-gradient(180deg,#000 0%,transparent 75%);mask-image:linear-gradient(180deg,#000 0%,transparent 75%);pointer-events:none}
.login-left>*{position:relative}
.brand{display:flex;align-items:center;gap:12px;font-family:var(--mono);font-size:12px;letter-spacing:.14em;color:var(--text2)}
/* 品牌文字 ciaude.xyz（小写域名，收紧字距）。 */
.brand-name{font-size:16px;font-weight:500;letter-spacing:.02em;color:var(--text)}.brand-name span{color:var(--text3)}
.headline{margin:0;font-size:clamp(40px,5.4vw,76px);font-weight:700;line-height:1.08;letter-spacing:-.02em}
.headline em{font-style:normal;color:var(--signal-display)}
.lede{max-width:520px;margin:22px 0 0;font-size:16px;line-height:1.7;color:var(--text2)}
.features{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));border-top:1px solid var(--line)}
.feature{padding:18px 18px 0 0}
.feature+.feature{padding-left:18px;border-left:1px solid var(--line)}
.feature span{display:block;margin-bottom:8px;font-family:var(--mono);font-size:11px;color:var(--signal-ink)}
.feature b{display:block;margin-bottom:4px;font-size:14px;font-weight:600}
.feature p{margin:0;font-size:12.5px;line-height:1.6;color:var(--text3)}
.login-right{display:flex;align-items:center;justify-content:center;padding:48px 40px;background:var(--panel)}
.form{width:min(100%,360px)}
.form h2{margin:0 0 6px;font-size:24px;font-weight:600}
.form .sub{margin:0 0 28px;font-size:13px;color:var(--text3)}
.pw-wrap{position:relative}
.pw-wrap input{padding-right:64px}
.pw-toggle{position:absolute;right:6px;top:6px;height:32px;padding:0 10px;border:0;border-radius:6px;background:transparent;color:var(--text2);font-size:12px}
.pw-toggle:hover{background:var(--raise);color:var(--text)}
.form .btn.primary{width:100%;min-height:48px;margin-top:8px;font-size:15px}
.form-note{margin:22px 0 0;padding-top:18px;border-top:1px solid var(--line);font-size:12px;line-height:1.7;color:var(--text3)}
.login-theme{margin-left:auto;width:40px;height:40px;border:1px solid var(--line2);border-radius:999px}
.notice{display:grid;place-content:center;justify-items:start;gap:14px;min-height:100vh;min-height:100dvh;padding:40px 24px;max-width:520px;margin:0 auto}
.notice h1{margin:8px 0 0;font-size:28px;font-weight:600}
.notice p{margin:0;color:var(--text2)}

/* ---------- workspace shell ---------- */
body.workspace{height:var(--app-height,100vh);overflow:hidden}
/* 行高固定为容器高度（minmax(0,1fr)），左栏放不下时自身纵向滚动；不要改回由内容决定，
   否则窗口变矮时左栏底部按钮和右侧面板底部会被 .ws 的 overflow:hidden 截掉。 */
.ws{position:relative;display:grid;grid-template-columns:84px minmax(0,1fr);grid-template-rows:minmax(0,1fr);height:100%;min-height:520px;overflow:hidden}
.rail{display:flex;flex-direction:column;align-items:center;gap:6px;min-height:0;padding:18px 0 14px;overflow-x:hidden;overflow-y:auto;scrollbar-width:none;border-right:1px solid var(--line);background:var(--bg);z-index:4}
.rail>*{flex-shrink:0}
.rail>.mark{margin-bottom:18px}
.nav-item{position:relative;display:flex;flex-direction:column;align-items:center;gap:5px;width:68px;min-height:60px;margin:0;padding:9px 0 7px;border:0;border-radius:10px;background:transparent;color:var(--text3);font-size:12px;transition:color .18s,background .18s}
.nav-item:hover{background:var(--raise);color:var(--text)}
.nav-item[aria-selected=true]{color:var(--text)}
.nav-item[aria-selected=true]::before{content:"";position:absolute;left:-8px;top:14px;bottom:14px;width:3px;border-radius:0 3px 3px 0;background:var(--signal-ink)}
.nav-item svg{width:22px;height:22px}
.rail-gap{flex:1}
.meters{display:flex;flex-direction:column;align-items:center;gap:10px;width:68px;margin:0;padding:12px 0 10px;border:1px solid transparent;border-radius:10px;background:transparent;color:inherit;transition:background .18s,border-color .18s}
.meters:hover,.meters[aria-expanded=true]{background:var(--raise);border-color:var(--line)}
.meter-col{display:flex;flex-direction:column;align-items:center;gap:5px}
.bar{position:relative;display:block;width:6px;height:clamp(44px,8.5vh,84px);border-radius:3px;background:var(--track);overflow:hidden}
.bar span{position:absolute;left:0;right:0;bottom:0;height:var(--p,0%);border-radius:3px;background:var(--c);transition:height .6s cubic-bezier(.2,.8,.2,1),width .6s cubic-bezier(.2,.8,.2,1)}
.meter-label{font-size:11px;color:var(--text3)}
/* 主题、退出按钮宽度必须与格子一致（36px），否则会超出窗口且点击区域重叠。 */
.account{display:grid;grid-template-columns:repeat(2,36px);place-items:center;gap:7px 4px;width:76px;margin-top:8px;padding-top:10px;border-top:1px solid var(--line)}
.account .icon-btn{width:36px;height:36px}
.account .avatar{grid-column:1/-1;grid-row:2}
.avatar{display:grid;place-items:center;width:34px;height:34px;border-radius:50%;background:var(--raise2);font-family:var(--mono);font-size:13px;font-weight:600;text-transform:uppercase}
.main{display:flex;flex-direction:column;min-width:0;min-height:0}
.head{display:flex;align-items:flex-end;justify-content:space-between;gap:24px;padding:26px 36px 18px;border-bottom:1px solid var(--line)}
.eyebrow{margin:0 0 6px;font-family:var(--mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--text3)}
.title{margin:0;font-size:28px;font-weight:600;letter-spacing:-.01em;line-height:1.15}
.head-meta{display:flex;align-items:center;gap:10px}
.head-mobile{display:none;align-items:center;gap:2px}
/* ---------- 服务器系统时间（网关 server_clock_html，app.js setupServerClock） ----------
   桌面：标题栏右侧、双 IP 卡片左边的两行文字（时间 / 时区 · UTC 偏移），不加边框，与 IP 卡片区分层级；
   秒数用次一级颜色，让“时:分”成为视觉重点。等宽数字，走字时宽度不变、不会推动旁边的卡片。
   手机：标题栏一行放不下第三个组件，时钟改为占用标题上方英文眉标的位置（时间 + UTC 偏移），
   标题栏高度不变；完整时区名在悬停提示里。两种只显示其一，读屏只读一次。 */
.server-clock{display:flex;flex-direction:column;align-items:flex-end;gap:3px;margin-right:6px;font-family:var(--mono);font-feature-settings:"tnum" 1;line-height:1.2;white-space:nowrap;cursor:default}
.server-clock .clock-time{font-size:15px;font-weight:500;letter-spacing:.02em;color:var(--text)}
.clock-sec{color:var(--text3)}
.server-clock .clock-zone{font-size:11px;letter-spacing:.02em;color:var(--text3)}
.head-clock{display:none}
.head-clock .clock-time{color:var(--text2)}
/* 较窄的桌面窗口里双 IP 是长地址（IPv6）时，时钟让位：改到眉标位置，保证两个地址不换行。 */
@media (min-width:761px) and (max-width:1100px){
  .head:has(.ip-route--long) .head-meta>.server-clock{display:none}
  .head:has(.ip-route--long) [data-page-eyebrow]{display:none}
  .head:has(.ip-route--long) .head-clock{display:block;white-space:nowrap}
}
/* ---------- 双 IP 路由（网关 ip_pair_html） ----------
   一条两站的“路由”：上行访问者出口 IP（灰、删除线、空心节点），细线连到下行服务器出口 IP
   （强调色、实心节点）。没有可见标题，语义由节点与连线表达，悬停提示在 title 上。
   约束：地址必须完整显示——不用 ellipsis、不 nowrap 截断，放不下时在任意字符处换行；
   删除线用背景渐变画在行内文本上（box-decoration-break:clone），换行后每一行都有，也能做绘制动画。 */
.head-meta{min-width:0}
.ip-route{--ip-node:7px;--ip-gap:5px;--ip-cycle:3.2s;--ip-dot-r:2.4px;position:relative;isolation:isolate;display:flex;flex-direction:column;align-items:flex-end;gap:var(--ip-gap);min-width:0;max-width:100%;
  padding:7px 8px 7px 10px;border:1px solid var(--line);border-radius:12px;
  background:linear-gradient(180deg,var(--ip-surface,rgba(255,255,255,.025)),transparent);
  font-family:var(--mono);font-feature-settings:"tnum" 1,"zero" 0;text-align:right;transition:border-color .2s}
:root[data-theme="light"] .ip-route{--ip-surface:rgba(255,255,255,.28)}
.ip-route:hover,.ip-route:focus-within{border-color:var(--line2)}
.ip-row{position:relative;display:flex;align-items:center;justify-content:flex-end;gap:11px;max-width:100%;min-width:0;margin:0;padding:1px 5px;border:0;border-radius:6px;background:none;color:inherit;font:inherit;text-align:right;cursor:copy;transition:color .2s}
.ip-row:disabled{cursor:default}
/* break-word 而不是 anywhere：后者会把最小内容宽度缩到一个字符，导致有空间时也被挤成两行。 */
.ip-val{min-width:0;max-width:100%;overflow-wrap:break-word;word-break:normal}
.ip-user{color:var(--text3);font-size:11.5px;line-height:1.35;letter-spacing:.02em}
.ip-user .ip-text{background:linear-gradient(var(--ip-strike,var(--text3)),var(--ip-strike,var(--text3))) 0 56%/100% 1px no-repeat;
  -webkit-box-decoration-break:clone;box-decoration-break:clone}
.ip-user[data-state="none"] .ip-text{background:none}
.ip-server{color:var(--signal-ink);font-size:13.5px;font-weight:500;line-height:1.3;letter-spacing:.01em}
.ip-server[data-state="pending"]{color:var(--text3)}
.ip-server[data-state="none"]{color:var(--text3);font-weight:400}
.ip-row:not(:disabled):hover{color:var(--text2)}
.ip-server:not(:disabled):hover{color:var(--signal-hover)}
:root[data-theme="light"] .ip-server:not(:disabled):hover{color:var(--signal-display)}
/* 节点：空心（访问者）与实心带光晕（服务器）。连线由两行各画一半：上行从节点中心向下，下行从上方到节点中心。 */
.ip-node{position:relative;z-index:1;flex:none;width:var(--ip-node);height:var(--ip-node);border-radius:50%;box-sizing:border-box;transition:background .2s,box-shadow .2s,border-color .2s}
.ip-user .ip-node{border:1.5px solid var(--line3)}
.ip-server .ip-node{background:var(--signal);box-shadow:0 0 0 3px var(--signal-soft)}
.ip-server[data-state="pending"] .ip-node{background:none;box-shadow:none;border:1.5px solid var(--signal-line);border-top-color:var(--signal);animation:spin .9s linear infinite}
.ip-server[data-state="none"] .ip-node{background:none;box-shadow:none;border:1.5px dashed var(--line3)}
.ip-user::after,.ip-server::before{content:"";position:absolute;right:calc(5px + var(--ip-node) / 2 - .5px);width:1px;pointer-events:none}
.ip-user::after{top:calc(50% + var(--ip-node) / 2 + 1px);bottom:calc(var(--ip-gap) / -2);background:var(--line2);transform-origin:top}
.ip-server::before{top:calc(var(--ip-gap) / -2);bottom:calc(50% + var(--ip-node) / 2 + 4px);background:linear-gradient(var(--line2),var(--signal-line));transform-origin:top}
.ip-server[data-state="none"]::before{background:var(--line2)}
/* 复制反馈：文字与节点短暂变为完成色，读屏通过 data-ip-live 播报。 */
.ip-row.copied,.ip-row.copied:hover{color:var(--ok-ink)}
.ip-row.copied .ip-node{border-color:var(--ok);background:var(--ok);box-shadow:0 0 0 3px var(--ok-halo)}
.ip-row:focus-visible{outline:2px solid var(--signal-ink);outline-offset:1px}
/* ---- 双 IP 路由的动效状态（data-route 由网关生成、app.js 随服务器地址更新）----
   约束：只动 transform / opacity / background-position（节点光晕、信号光点都是伪元素），不改任何尺寸和位置，
   所以文字、节点、连线和页面布局都不会跳动；全部是 CSS 动画，不用 JS 定时器逐帧改 DOM，
   页面在后台时浏览器自动暂停。减少动态效果时由文件末尾的全局规则关闭全部动画（完全静止）。
   1. pending（查询中）：两段连线柔和呼吸，服务器节点转圈。
   2. ok（两端地址都可用）：常驻信号，见下方“常驻信号时序”。
   3. none（任一端暂不可用）：连线和节点弱化，不显示信号。
   4. is-entering：每次进入工作台播放一次的完整进入动画，期间暂停常驻信号；
      is-settling：载入时仍在查询、随后查询成功，只播放节点点亮 + 地址淡入；
      ip-updated：后台刷新得到新的服务器地址，地址与节点短暂提示。
   5. 复制成功（.copied）时隐藏该行节点的光晕，避免橙色光晕盖住绿色的复制反馈。 */
/* ---- 常驻信号时序（修复：末段跳动、服务器端点先于光点亮起）----
   以前光点分成上下两段（.ip-user::before / .ip-server::after）各自动画，两段长度不同、交接处有回退和停顿，
   下段终点停在节点上方约 1.5px 处原地淡出，光点从未进入节点；节点光晕又是另一组独立动画，复制反馈
   （animation:none）或 ip-updated（换成 ip-ping）结束后会从头重新开始，与光点永久错位。现在：
   - 光点只有一个元素 .ip-route::after，从用户节点中心直到服务器节点中心一整段，几何位置由 app.js
     measureIpRoute 实测写入 --ip-cx / --ip-y0 / --ip-d（各种宽度、缩放、IPv4 / IPv6 换行都按实际排版）。
   - 光点、用户端发射（ip-signal-emit）、服务器端光晕（ip-signal-arrive）同用 --ip-cycle 与同一组选择器
     同时开始；复制反馈和 ip-updated 都不再替换这组动画（见 .copied 与 .ip-node::before），所以不会错位或漂移。
   - 接触规则：光点外缘（半径 --ip-dot-r）首次接触服务器节点圆盘边缘的时刻 = 70%。光点在 6%~100% 匀速：
     6% 位于用户节点中心，70% 前进 d - c（c = 节点半径 + 光点半径，即刚好接触），之后保持同一速度
     （100% 处为 (d - c) × 94 / 64）。光晕关键帧在 70% 之前透明度为 0，所以首个可见光晕帧里光点已接触节点。
   - 节点 z-index:1 盖在光点上方（.ip-route 用 isolation 限制层叠范围），光点越过接触点后被实心节点逐渐遮住，
     等于“进入”节点；光点画在背景里、超出元素底边（节点中心）的部分被裁掉，不会从节点下方穿出。
     94% 之后才把透明度降为 0（此时已完全在节点内），100% → 0% 回到起点时不可见。
   - 只动 background-position / opacity，不改元素尺寸和位置；测量值变化时浏览器沿用当前动画进度，不会重启。 */
.ip-route::after{content:"";position:absolute;left:calc(var(--ip-cx,0px) - 2.5px);top:var(--ip-y0,0px);width:5px;height:var(--ip-d,0px);
  --ip-contact:calc(var(--ip-node) / 2 + var(--ip-dot-r));opacity:0;pointer-events:none;
  background:radial-gradient(circle at 50% 50%,var(--signal) 0 1.4px,transparent var(--ip-dot-r)) 0 -2.5px/5px 5px no-repeat}
.ip-node::after,.ip-node::before{content:"";position:absolute;inset:-4px;border-radius:50%;opacity:0;pointer-events:none}
.ip-user .ip-node::after{inset:-1.5px;background:var(--text3)}
.ip-server .ip-node::after,.ip-server .ip-node::before{background:var(--signal)}
.ip-route[data-route="ok"][data-ip-geo]:not(.is-entering):not(.is-settling)::after{animation:ip-signal-dot var(--ip-cycle) linear infinite}
.ip-route[data-route="ok"][data-ip-geo]:not(.is-entering):not(.is-settling) .ip-user .ip-node::after{animation:ip-signal-emit var(--ip-cycle) ease-out infinite}
.ip-route[data-route="ok"][data-ip-geo]:not(.is-entering):not(.is-settling) .ip-server .ip-node::after{animation:ip-signal-arrive var(--ip-cycle) ease-out infinite}
/* 只盖住透明度，不停止动画：!important 声明优先于动画值，复制反馈结束后光晕仍与光点同一进度。 */
.ip-row.copied .ip-node::after{opacity:0!important}
.ip-route[data-route="pending"]:not(.is-entering) .ip-user::after,.ip-route[data-route="pending"]:not(.is-entering) .ip-server::before{animation:ip-wait 1.6s ease-in-out infinite alternate}
.ip-route[data-route="none"] .ip-user::after,.ip-route[data-route="none"] .ip-server::before{opacity:.4}
.ip-route[data-route="none"] .ip-node{opacity:.7}
@keyframes ip-signal-emit{0%{opacity:0}6%{opacity:.55}16%,100%{opacity:0}}
@keyframes ip-signal-dot{0%,6%{background-position:0 -2.5px;opacity:0}12%,94%{opacity:.7}
  70%{background-position:0 calc(var(--ip-d,0px) - var(--ip-contact) - 2.5px)}
  100%{background-position:0 calc((var(--ip-d,0px) - var(--ip-contact)) * 1.46875 - 2.5px);opacity:0}}
@keyframes ip-signal-arrive{0%,70%{opacity:0;transform:scale(.55)}78%{opacity:.24;transform:scale(1.15)}94%,100%{opacity:0;transform:scale(1.55)}}
@keyframes ip-wait{from{opacity:.3}to{opacity:1}}
/* 完整进入动画：删除线自左向右划过 → 连线向下生长 → 服务器节点点亮并扩散一圈（地址可用时）。 */
.ip-route.is-entering .ip-user .ip-text{animation:ip-strike .55s .25s cubic-bezier(.65,0,.35,1) both}
.ip-route.is-entering .ip-user::after,.ip-route.is-entering .ip-server::before{animation:ip-grow .26s cubic-bezier(.2,.8,.2,1) both}
.ip-route.is-entering .ip-user::after{animation-delay:.72s}.ip-route.is-entering .ip-server::before{animation-delay:.9s}
.ip-route.is-entering .ip-server[data-state="ok"] .ip-node,.ip-route.is-settling .ip-server .ip-node{animation:ip-light .5s 1.08s cubic-bezier(.2,.8,.2,1) both}
.ip-route.is-entering .ip-server[data-state="ok"] .ip-text,.ip-route.is-settling .ip-server .ip-text{animation:ip-reveal .5s 1.08s ease both}
.ip-route.is-settling .ip-server .ip-node,.ip-route.is-settling .ip-server .ip-text{animation-delay:0s}
.ip-server.ip-updated .ip-text{animation:ip-update .9s ease}
/* 更新提示画在 ::before 上，不替换 ::after 的常驻光晕，结束后光晕与光点仍同步。 */
.ip-server.ip-updated .ip-node::before{animation:ip-ping .9s ease-out}
@keyframes ip-strike{from{background-size:0 1px}}
@keyframes ip-grow{from{transform:scaleY(0)}}
@keyframes ip-light{0%{transform:scale(.3);box-shadow:0 0 0 0 var(--signal-soft)}60%{transform:scale(1.15);box-shadow:0 0 0 7px transparent}100%{transform:scale(1);box-shadow:0 0 0 3px var(--signal-soft)}}
@keyframes ip-reveal{from{opacity:.35;filter:saturate(0)}}
@keyframes ip-update{0%{opacity:.2}35%{opacity:1}100%{opacity:1}}
@keyframes ip-ping{0%{opacity:.45;transform:scale(.6)}100%{opacity:0;transform:scale(2)}}
.grant{display:flex;align-items:center;gap:8px;padding:5px 12px 5px 7px;border:1px solid var(--signal-line);border-radius:999px;color:var(--signal-ink);font-family:var(--mono);font-size:12px;animation:fade .25s ease}
.grant svg{width:22px;height:22px;transform:rotate(-90deg)}
.grant .ring-track{stroke:var(--signal-track)}.grant .ring-run{stroke:var(--signal-ink);transition:stroke-dashoffset 1s linear}
.ws-panel{position:relative;display:flex;flex:1;flex-direction:column;min-height:0;animation:rise .28s cubic-bezier(.2,.8,.2,1)}
.flash-toast{position:fixed;right:28px;bottom:28px;z-index:30;display:flex;align-items:center;gap:10px;max-width:min(440px,calc(100vw - 24px));padding:10px 8px 10px 16px;border:1px solid var(--line2);border-radius:10px;background:var(--surface-float);box-shadow:var(--shadow-float);font-size:13px;animation:rise .25s cubic-bezier(.2,.8,.2,1)}
.flash-toast::before{content:"";flex:none;width:7px;height:7px;border-radius:50%;background:var(--ok)}
.flash-toast.error::before{background:var(--danger)}
.flash-toast .icon-btn{width:32px;height:32px}

/* ---------- remote browser ---------- */
.launch{flex:1;min-height:0;overflow:auto;display:grid;grid-template-columns:minmax(0,1.1fr) minmax(0,1fr);margin:0}
.launch-left{padding:36px;border-right:1px solid var(--line)}
.launch-right{display:flex;flex-direction:column;justify-content:flex-start;gap:22px;padding:36px 48px}
.launch.running{grid-template-columns:minmax(0,1fr)}
.launch.running .launch-right{max-width:720px}
.devices{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}
.device{position:relative;display:flex;flex-direction:column;align-items:center;gap:14px;min-height:190px;padding:22px 12px 16px;border:1px solid var(--line2);border-radius:10px;color:var(--text2);text-align:center;cursor:pointer;transition:border-color .15s,background .15s}
.device:hover{border-color:var(--line3);background:var(--row-hover)}
.device:has(input:checked){border-color:var(--signal-ink);background:var(--signal-tint2);color:var(--text)}
.device:has(input:focus-visible){outline:2px solid var(--signal-ink);outline-offset:2px}
.device-shape{display:flex;flex:1;align-items:center;justify-content:center}
.device-shape i{display:block;border:1.5px solid currentColor;border-radius:5px;opacity:.85}
.device:has(input:checked) .device-shape i{border-color:var(--signal-ink)}
.device b{display:block;font-size:14px;font-weight:600;color:var(--text)}
.device small{display:block;margin-top:2px;font-family:var(--mono);font-size:11px;color:var(--text3);white-space:nowrap}
.launch .res-hd{display:none}
.launch.running .launch-right{padding-top:48px}
.launch:has(input[name=quality][value=hd]:checked) .res-hd{display:inline}
.launch:has(input[name=quality][value=hd]:checked) .res-smooth{display:none}
.seg{display:inline-flex;padding:3px;border:1px solid var(--line2);border-radius:9px}
.seg label{position:relative;display:grid;place-items:center;min-height:34px;padding:0 16px;border-radius:6px;color:var(--text2);font-size:13px;cursor:pointer}
.seg label:has(input:checked){background:var(--raise);color:var(--text);box-shadow:inset 0 0 0 1px var(--line3)}
.seg label:has(input:focus-visible){outline:2px solid var(--signal-ink)}
.launch-title{margin:0;font-size:26px;font-weight:600;line-height:1.35}
.big-time{margin:0;font-family:var(--mono);font-size:64px;font-weight:500;letter-spacing:-.02em;line-height:1}
.big-time.late{color:var(--danger-ink)}
.facts{display:grid;grid-template-columns:auto 1fr;align-items:center;gap:10px 24px;margin:0;font-size:13px}
.facts dt{color:var(--text3)}
.facts dd{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin:0;font-family:var(--mono);color:var(--text)}
.facts code{font-size:15px;letter-spacing:.06em}
.facts .btn{min-height:30px;padding:0 10px;font-family:var(--sans);font-size:12px}
.actions{display:flex;flex-wrap:wrap;gap:10px}
.actions form{margin:0}

/* ---------- web terminal ---------- */
.term{display:flex;flex:1;flex-direction:column;min-height:0}
.term-bar{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;min-height:52px;padding:8px 36px 0;border-bottom:1px solid var(--line)}
.term-tabs{display:flex;align-items:flex-end;gap:2px;min-width:0;overflow-x:auto;scrollbar-width:none}
.term-tabs::-webkit-scrollbar{display:none}
.term-tab{display:flex;align-items:center;height:42px;padding:0 4px 0 12px;border:1px solid transparent;border-bottom:0;border-radius:8px 8px 0 0;color:var(--text3);font-size:13px;white-space:nowrap;animation:fade .2s ease}
.term-tab.active{position:relative;top:1px;border-color:var(--line);background:var(--term-bg);color:var(--text)}
.term-tab-open{display:flex;align-items:center;gap:8px;height:100%;margin:0;padding:0 4px 0 0;border:0;background:transparent;color:inherit}
.tab-no{font-family:var(--mono);font-size:11px;color:var(--text3)}
.term-tab.active .tab-no{color:var(--signal-ink)}
.term-tab-close{display:grid;place-items:center;width:28px;height:28px;margin:0;padding:0;border:0;border-radius:6px;background:transparent;color:var(--text3);font-size:16px;line-height:1}
.term-tab-close:hover{background:var(--raise);color:var(--text)}
.term-tab-new{display:flex;align-items:center;gap:6px;height:34px;margin:0 0 4px 8px;padding:0 12px;border:1px dashed var(--line2);border-radius:8px;background:transparent;color:var(--text2);font-size:12px;white-space:nowrap}
.term-tab-new:hover{border-color:var(--text3);color:var(--text)}
.term-tab-new[disabled]{border-style:solid;color:var(--text3);opacity:1}
.term-tab-new .short{display:none}
.term-tools{display:flex;flex:none;flex-wrap:wrap;align-items:center;justify-content:flex-end;gap:8px;min-width:0;padding-bottom:8px}.term-modes{display:inline-flex;padding:3px;border:1px solid var(--line);border-radius:10px;background:var(--raise)}.term-modes .btn{min-height:32px;margin:0;padding:0 10px;border:0}.term-modes .btn.active{background:var(--signal);color:var(--on-signal);box-shadow:none}
.term-frame{position:relative;flex:1;min-height:0;overflow:hidden;background:var(--term-bg)}
.term-frame .term-view{position:absolute;inset:0;display:block;width:100%;height:100%;border:0;background:var(--term-bg);visibility:hidden}
.term-frame .term-view.active{visibility:visible}
.term-frame .term-view.term-preload{visibility:hidden!important;pointer-events:none}
.term-placeholder{display:grid;place-content:center;justify-items:center;gap:10px;height:100%;padding:24px;text-align:center}
.term-placeholder .ph-title{margin:0;font-size:20px;font-weight:600}
.term-placeholder p{margin:0;color:var(--text3)}
.term-placeholder .btn{margin-top:8px}
.term-loading{position:absolute;inset:0;z-index:2;display:grid;place-content:center;justify-items:center;gap:10px;padding:24px;background:var(--term-bg);color:var(--text2);text-align:center}
.term-loading[hidden]{display:none}
.term-loading .muted{font-size:12px}
.term-spinner{width:26px;height:26px;border:3px solid var(--line2);border-top-color:var(--signal-ink);border-radius:50%;animation:spin .8s linear infinite}
.term-keys{position:relative;display:flex;flex:none;flex-wrap:wrap;align-items:center;gap:6px;padding:8px 36px;border-top:1px solid var(--line)}
.term-keys::-webkit-scrollbar{display:none}
.term-keys button{flex:none;min-width:52px;min-height:38px;margin:0;padding:0 12px;border:1px solid var(--line2);border-radius:8px;background:transparent;font-family:var(--mono);font-size:12px}
.term-keys button:hover{background:var(--raise)}
/* 快捷栏分组：按键 | Claude、Codex、指令 | 粘贴（靠右）。“指令”面板在快捷栏上方展开（app.js toggleCommands），
   宽度跟随快捷栏而不是按钮，手机上也不会超出屏幕。 */
.term-keys-sep{flex:none;width:1px;height:22px;margin:0 4px;background:var(--line2)}
.term-keys .term-agent{font-family:var(--sans);font-weight:500}
.term-keys .term-paste{margin-left:auto;font-family:var(--sans)}
.term-keys .term-cmd-toggle{display:inline-flex;align-items:center;gap:6px;font-family:var(--sans);font-weight:500}
.term-cmd-toggle svg{width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;transition:transform .2s}
.term-cmd-toggle[aria-expanded="false"] svg{transform:rotate(180deg)}
.term-keys .term-cmd-toggle[aria-expanded="true"]{border-color:var(--signal-line);background:var(--signal-tint);color:var(--text)}
.term-cmds{position:absolute;left:36px;right:36px;bottom:calc(100% + 8px);z-index:6;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:4px 18px;max-width:680px;max-height:min(60vh,460px);overflow:auto;padding:12px;border:1px solid var(--line2);border-radius:12px;background:var(--surface-float);box-shadow:var(--shadow-float);animation:rise .18s cubic-bezier(.2,.8,.2,1)}
.term-cmd-group{display:flex;flex-direction:column;gap:2px;min-width:0}
/* 分组标题按品牌名原样显示（Claude Code / Codex），不再 text-transform:uppercase 成全大写；字体、字号、间距不变。 */
.term-cmd-group>p{margin:2px 8px 6px;font-family:var(--mono);font-size:11px;letter-spacing:.12em;color:var(--text3)}
.term-keys .term-cmd{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:10px;min-width:0;min-height:36px;padding:0 8px;border:0;border-radius:7px;font-family:var(--sans);font-size:12.5px;text-align:left}
.term-cmd code{font-family:var(--mono);font-size:12.5px;color:var(--signal-ink)}
.term-cmd span{overflow:hidden;color:var(--text2);text-overflow:ellipsis;white-space:nowrap}
.term-cmd em{font-style:normal;font-size:11px;color:var(--text3);white-space:nowrap}
.term-keys .term-cmd:hover,.term-keys .term-cmd:focus-visible{background:var(--raise)}
.term-cmds-note{grid-column:1/-1;margin:6px 8px 0;padding-top:8px;border-top:1px solid var(--line);font-size:11.5px;color:var(--text3)}
.term-cmds-note strong{font-weight:600;color:var(--text2)}
.statusline{display:flex;flex:none;flex-wrap:wrap;align-items:center;gap:6px 14px;min-height:40px;padding:8px 36px;border-top:1px solid var(--line);font-family:var(--mono);font-size:11.5px;color:var(--text3)}
.statusline strong{font-weight:500;color:var(--text2)}
.term-dot{flex:none;width:7px;height:7px;border-radius:50%;background:var(--line3)}
.term-dot.running{background:var(--ok);box-shadow:0 0 0 3px var(--ok-halo)}
.term-dot.busy{background:var(--busy)}
.term-dot.error{background:var(--danger)}
.term-note{margin-left:auto}
.term-note.flash-note{color:var(--ok-ink)}

/* ---------- file manager ---------- */
.fm{position:relative;display:flex;flex:1;flex-direction:column;min-height:0;overflow:hidden}
.fbar{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:10px 12px;padding:14px 36px;border-bottom:1px solid var(--line)}
.fpath{display:flex;align-items:center;gap:4px;min-width:0}
.fpath .icon-btn{width:36px;height:36px}
.crumbs{display:flex;flex-wrap:wrap;align-items:center;gap:2px;min-width:0;font-family:var(--mono);font-size:13px;color:var(--text2)}
.crumbs button{max-width:24ch;min-height:32px;margin:0;padding:0 8px;overflow:hidden;border:0;border-radius:6px;background:transparent;color:var(--text2);font-family:var(--mono);text-overflow:ellipsis;white-space:nowrap}
.crumbs button:hover{background:var(--raise);color:var(--text)}
.crumbs button[aria-current=page]{color:var(--text)}
.crumbs .sep{color:var(--line3)}
.ftools{display:flex;flex-wrap:wrap;align-items:center;gap:8px}
.fm-upload{position:relative;cursor:pointer}
.fm-upload:focus-within{outline:2px solid var(--signal-ink);outline-offset:2px}
.toggle{display:inline-flex;align-items:center;gap:8px;min-height:40px;padding:0 10px;border-radius:8px;color:var(--text2);font-size:13px;cursor:pointer}
.toggle:hover{background:var(--raise);color:var(--text)}
.toggle input,.check input{width:16px;height:16px;margin:0;accent-color:var(--check);cursor:pointer}
.table{flex:1;min-height:0;overflow:auto;padding:0 20px 92px;overscroll-behavior:contain}
.row,.fm-row{display:grid;grid-template-columns:44px minmax(0,1fr) 110px 160px 44px;align-items:center;min-height:52px;padding:0 16px;border-bottom:1px solid var(--line);transition:background .12s}
.fm-row:hover{background:var(--row-hover)}
.fm-row.selected{background:var(--signal-tint)}
.headrow{position:sticky;top:0;z-index:1;min-height:40px;background:var(--bg);font-family:var(--mono);font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--text3)}
.check{display:grid;place-items:center;width:44px;height:44px;margin-left:-12px;cursor:pointer}
.fm-name{display:flex;align-items:center;gap:12px;min-width:0;min-height:44px;margin:0;padding:0 4px;border:0;border-radius:8px;background:transparent;color:var(--text);font-weight:500;text-align:left}
.fm-name:hover .label{color:var(--signal-ink)}
.fm-name .text{display:flex;flex-direction:column;min-width:0}
.fm-name .label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.fm-meta{display:none;font-family:var(--mono);font-size:11px;font-weight:400;color:var(--text3)}
.fm-icon{flex:none;width:20px;height:20px;color:var(--text3)}
.fm-icon.dir{color:var(--folder)}
.fm-size,.fm-time{font-family:var(--mono);font-size:12px;color:var(--text2);white-space:nowrap}
.fm-size{padding-right:24px;text-align:right}
.headrow .fm-size,.headrow .fm-time{font-size:11px;color:var(--text3)}
.fm-size[data-state=pending],.fm-size[data-state=skipped]{color:var(--text3)}
.fm-size[data-state=partial]{color:var(--text)}
.fm-size[data-state=denied],.fm-size[data-state=error]{color:var(--danger-ink)}
/* 拖动移动（app.js FileManager.moveTarget）：可放下的文件夹行、路径栏按钮和“上一级”按钮显示强调色边框，
   不可放下（目录自身或其子目录）显示红色；拖动期间路径栏中的上级目录显示细边框，提示可作为落点。 */
.fm-row[draggable=true]{-webkit-user-drag:element}
.fm-row.fm-drop-ok,.crumbs button.fm-drop-ok,.icon-btn.fm-drop-ok{background:var(--signal-tint);box-shadow:inset 0 0 0 1.5px var(--signal-line)}
.fm-row.fm-drop-no,.crumbs button.fm-drop-no,.icon-btn.fm-drop-no{background:var(--danger-tint);box-shadow:inset 0 0 0 1.5px var(--danger-line);cursor:no-drop}
.fm.fm-moving .crumbs button:not([aria-current]){box-shadow:inset 0 0 0 1px var(--line3)}
.fm-drag-ghost{position:fixed;top:-100px;left:-100px;padding:6px 12px;border-radius:8px;background:var(--signal);color:var(--on-signal);font-size:13px;font-weight:600;white-space:nowrap;pointer-events:none}
.fm-more{display:grid;place-items:center;width:36px;height:36px;margin:0;padding:0;border:0;border-radius:6px;background:transparent;color:var(--text3)}
.fm-more:hover{background:var(--raise);color:var(--text)}
.fm-more svg{width:18px;height:18px}
.fm-empty{display:grid;place-items:center;min-height:180px;padding:40px 20px;color:var(--text3);text-align:center}
/* 拖放上传的“黑洞”落点（app.js FileManager.setupDrop / absorb）：覆盖文件管理区域，不改变布局、不接收指针事件。
   .open 有效内容进入、入口张开；.invalid 不支持的内容；.absorbing 松开后约 0.5 秒的吸入（卡片由 JS 动画）。
   减少动态效果时上面的全局规则关闭旋转与过渡，只保留静态落点；JS 同时跳过吸入动画。 */
.fm-drop{position:absolute;inset:8px;z-index:8;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:16px;border:1.5px dashed var(--line3);border-radius:14px;background:var(--bg);background:color-mix(in srgb,var(--bg) 92%,transparent);text-align:center;pointer-events:none;overflow:hidden;container-type:size;opacity:0;transition:opacity .16s ease,border-color .16s ease}
.fm-drop[hidden]{display:none}
.fm-drop.open,.fm-drop.invalid{opacity:1}
.fm-drop.open{border-color:var(--signal-line)}
.fm-drop.invalid{border-color:var(--danger-line)}
.fm-drop.collapsing{opacity:0;transition:opacity .26s ease}
/* 黑洞落点：宽 16:10，按落点区域的宽高取最大可用尺寸，文字始终留在区域内，不产生滚动。
   .live 时由画布（WebGL 或 Canvas 2D）绘制，静态层隐藏；静态层用于减少动态效果、无法绘制和“不支持”状态。 */
.fm-hole{position:relative;flex:none;width:min(420px,88%);width:min(420px,88cqw,(100cqh - 100px) * 1.6);aspect-ratio:16/10;transform:scale(.55);opacity:.5;transition:transform .45s cubic-bezier(.2,.8,.2,1),opacity .3s ease}
.fm-drop.open .fm-hole{transform:scale(1);opacity:1}
.fm-drop.invalid .fm-hole{transform:scale(.62);opacity:.4;filter:grayscale(1)}
.fm-hole::before{content:"";position:absolute;inset:0 12%;border-radius:50%;background:radial-gradient(closest-side,rgba(4,3,8,.94) 40%,rgba(4,3,8,.7) 70%,transparent)}
.fm-hole>span{position:absolute;left:50%;top:50%;border-radius:50%;transform:translate(-50%,-50%)}
.fm-hole-lens{width:27%;aspect-ratio:1;background:radial-gradient(closest-side,transparent 60%,#FFE2AA 66%,rgba(255,150,40,.75) 78%,transparent);-webkit-mask:linear-gradient(#000 50%,rgba(0,0,0,.4) 50%);mask:linear-gradient(#000 50%,rgba(0,0,0,.4) 50%)}
.fm-hole-disk{width:75%;height:20%;background:radial-gradient(closest-side,transparent 32%,#FFF1D2 37%,#FFB347 47%,#E0561B 66%,rgba(140,30,8,.55) 84%,transparent);-webkit-mask:linear-gradient(90deg,#000,rgba(0,0,0,.5));mask:linear-gradient(90deg,#000,rgba(0,0,0,.5))}
.fm-hole-disk.front{clip-path:inset(50% 0 0 0)}
.fm-hole-core{width:16.25%;aspect-ratio:1;background:#000;box-shadow:0 0 0 1.5px rgba(255,220,160,.9),0 0 16px 3px rgba(255,140,30,.5)}
.fm-hole.live::before,.fm-hole.live>span{visibility:hidden}
.fm-hole-canvas{position:absolute;inset:0;display:block;width:100%;height:100%;pointer-events:none}
.fm-drop.absorbing .fm-hole:not(.live) .fm-hole-core{animation:fm-hole-swallow .6s cubic-bezier(.3,0,.2,1)}
.fm-drop-title,.fm-drop-hint{position:relative;z-index:2}
.fm-drop-title{margin:0;font-size:15px;font-weight:600;color:var(--text)}
.fm-drop-hint{max-width:min(460px,100%);margin:0;overflow-wrap:anywhere;font-size:12px;color:var(--text3)}
.fm-drop.invalid .fm-drop-title{color:var(--danger-ink)}
.fm-drop-chip{position:absolute;left:0;top:0;z-index:1;display:inline-flex;align-items:center;gap:6px;max-width:150px;padding:4px 8px;border:1px solid var(--signal-line);border-radius:7px;background:var(--surface-float);box-shadow:var(--shadow-float),0 0 14px rgba(255,160,50,.35);font-size:11.5px;line-height:1.3;white-space:nowrap;transform-origin:0 0;will-change:transform,opacity}
.fm-drop-chip>span{overflow:hidden;text-overflow:ellipsis}
.fm-drop-chip svg{flex:none;width:14px;height:14px;color:var(--text3)}.fm-drop-chip svg.dir{color:var(--folder)}
@keyframes fm-hole-swallow{0%{transform:translate(-50%,-50%) scale(1)}40%{transform:translate(-50%,-50%) scale(1.16)}100%{transform:translate(-50%,-50%) scale(.9)}}
.fm-status{position:absolute;left:36px;bottom:12px;z-index:2;max-width:calc(100% - 72px);margin:0;padding:6px 12px;border-radius:8px;background:var(--surface-float);box-shadow:var(--shadow-float);font-size:12.5px;color:var(--text2);pointer-events:none}
.fm-status:empty{display:none}
.fm-status.error{color:var(--danger-ink)}.fm-status.ok{color:var(--ok-ink)}
.fm-transfers{flex:none;max-height:30%;overflow:auto;margin:0 20px 14px;padding:4px 12px;border:1px solid var(--line);border-radius:10px;background:var(--panel)}
.fm-transfer{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:4px 10px;padding:8px 0;font-size:12px}
.fm-transfer+.fm-transfer{border-top:1px solid var(--line)}
.fm-transfer .t-name{overflow:hidden;font-weight:600;text-overflow:ellipsis;white-space:nowrap}
.fm-transfer .t-state{color:var(--text3)}
.fm-transfer.failed .t-state{color:var(--danger-ink)}.fm-transfer.done .t-state{color:var(--ok-ink)}
.fm-transfer button{min-height:30px;padding:0 10px;font-size:12px}
/* 传输区标题栏（统计 + 暂停队列 / 全部取消 / 清除已结束），滚动时固定在顶部。 */
.fm-transfers-head{position:sticky;top:-4px;z-index:1;display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin:0 -12px;padding:8px 12px;border-bottom:1px solid var(--line);background:var(--panel)}
.fm-transfers-head .t-summary{font-size:12px;color:var(--text2);white-space:nowrap}
/* 收起 / 展开：统计文字连同箭头是一个按钮；收起后只保留标题栏，显示当前任务与底边细进度线。 */
.fm-transfers-head .t-toggle{display:inline-flex;align-items:center;gap:7px;min-height:30px;margin:0 auto 0 -6px;padding:0 8px 0 6px;border:0;border-radius:7px;background:transparent;color:var(--text2);font-size:12px;transition:background .15s,color .15s}
.fm-transfers-head .t-toggle:hover{background:var(--raise);border-color:transparent;color:var(--text)}
.t-chevron{width:16px;height:16px;flex:none;color:var(--text3);transition:transform .28s cubic-bezier(.2,.8,.2,1),color .15s}
.t-toggle:hover .t-chevron{color:var(--text)}
.fm-transfers.collapsed .t-chevron{transform:rotate(-90deg)}
.t-now{display:none;flex:1 1 auto;min-width:0;overflow:hidden;font-family:var(--mono);font-size:11.5px;color:var(--text3);text-overflow:ellipsis;white-space:nowrap;font-feature-settings:"tnum" 1}
.t-peek{position:absolute;left:0;bottom:-1px;width:0;height:2px;border-radius:0 2px 2px 0;background:var(--signal-ink);opacity:0;transition:width .25s linear,opacity .2s}
.fm-transfers.collapsed{overflow:hidden;padding-top:0;padding-bottom:0}
.fm-transfers.collapsed:not(.animating) .fm-transfer-list{display:none}
.fm-transfers.collapsed .fm-transfers-head{border-bottom-color:transparent}
.fm-transfers.collapsed .transfer-follow{display:none}
.fm-transfers.collapsed.peeking .t-now{display:block}
.fm-transfers.collapsed.peeking .t-toggle{margin-right:6px}
.fm-transfers.collapsed.peeking .t-peek{opacity:1}
.fm-transfers.animating{overflow:hidden;transition:height .28s cubic-bezier(.2,.8,.2,1)}
.fm-transfers-head button{min-height:30px;padding:0 10px;font-size:12px}
.fm-transfers-head button.danger{border-color:var(--danger-line);color:var(--danger-ink)}
.fm-transfers-head button.danger:hover{border-color:var(--danger);background:var(--danger-tint)}
.transfer-follow{display:inline-flex;align-items:center;gap:6px;min-height:30px;padding:0 6px;color:var(--text3);font-size:12px;white-space:nowrap;cursor:pointer}
.transfer-follow input{width:15px;height:15px;margin:0;accent-color:var(--signal)}
.fm-transfer.cancelled .t-state{color:var(--text3)}.fm-transfer.cancelled .meter span{background:var(--line3)}
/* 有传输任务时状态提示排在传输区下方，避免盖住传输进度。 */
.fm.has-transfers .fm-status{position:static;flex:none;align-self:flex-start;max-width:calc(100% - 40px);margin:0 20px 10px;box-shadow:none}
.meter{grid-column:1/-1;height:4px;border-radius:2px;background:var(--track);overflow:hidden}
.meter span{display:block;height:100%;border-radius:inherit;background:var(--signal-ink);transition:width .2s}
.selbar{position:absolute;left:50%;bottom:16px;z-index:3;display:flex;align-items:center;gap:4px;max-width:calc(100% - 40px);overflow:hidden;padding:6px 6px 6px 18px;border:1px solid var(--line2);border-radius:12px;background:var(--surface-float);box-shadow:var(--shadow-float);transform:translateX(-50%);animation:fade .2s ease}
.selbar-count{margin-right:10px;font-size:13px;color:var(--text2);white-space:nowrap}
.selbar .sep{width:1px;height:24px;margin:0 4px;background:var(--line2)}
.selbar .icon-btn{width:40px;height:40px}

/* ---------- usage drawer ---------- */
.scrim{position:absolute;inset:0 0 0 84px;z-index:5;background:var(--scrim);opacity:0;pointer-events:none;transition:opacity .25s}
.scrim.open{opacity:1;pointer-events:auto}
.drawer{position:absolute;top:0;bottom:0;left:84px;z-index:6;width:360px;padding:28px 28px 24px;overflow:auto;border-right:1px solid var(--line2);background:var(--drawer-bg);transform:translateX(-102%);visibility:hidden;transition:transform .32s cubic-bezier(.2,.8,.2,1),visibility .32s}
.drawer.open{transform:none;visibility:visible}
.drawer-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:26px}
.drawer-head h2{margin:0;font-size:18px;font-weight:600}
.gauge{padding:18px 0;border-top:1px solid var(--line)}
.gauge.na{color:var(--text3);font-size:13px}
.gauge-top{display:flex;align-items:baseline;justify-content:space-between;margin-bottom:12px}
.gauge-name{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--text2)}
.gauge-name i{width:8px;height:8px;border-radius:2px}
.gauge-pct{font-family:var(--mono);font-size:13px;color:var(--text2)}
.gauge-val{margin:0 0 12px;font-family:var(--mono);font-size:26px;font-weight:500;letter-spacing:-.02em}
.gauge-val small{font-size:14px;font-weight:400;color:var(--text3)}
.ticks{display:grid;grid-template-columns:repeat(25,minmax(0,1fr));gap:3px}
.ticks span{height:14px;border-radius:1.5px;background:var(--track)}
.drawer-foot{display:grid;grid-template-columns:auto 1fr;gap:8px 18px;margin:8px 0 0;padding-top:18px;border-top:1px solid var(--line);font-size:12px}
.drawer-foot dt{color:var(--text3)}
.drawer-foot dd{margin:0;font-family:var(--mono);color:var(--text2)}
.space-breakdown{margin-top:18px;padding-top:18px;border-top:1px solid var(--line)}
.space-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px}.space-head h3{margin:0;font-size:14px}.space-head button{padding:4px 0;border:0;background:transparent;color:var(--text3);font-size:12px}.space-head button:hover{color:var(--text)}
.space-loading,.space-total{margin:0 0 10px;font-size:12px;color:var(--text3)}
.space-stack{display:flex;height:14px;margin-bottom:12px;overflow:hidden;border-radius:4px;background:var(--track)}.space-stack span{min-width:0}
.space-row{display:grid;grid-template-columns:10px minmax(0,1fr) auto 48px;align-items:center;gap:8px;min-height:28px;font-size:12px}.space-dot{width:8px;height:8px;border-radius:2px;background:var(--space-color)}.space-label{color:var(--text2)}.space-value,.space-percent{font-family:var(--mono);text-align:right;color:var(--text3)}
.space-breakdown>.btn{width:100%;margin-top:14px}
.cleanup-options{display:grid;gap:8px;margin-top:8px}.cleanup-options label{display:flex;align-items:flex-start;gap:10px;padding:10px;border:1px solid var(--line);border-radius:8px;cursor:pointer}.cleanup-options label:hover{border-color:var(--line3);background:var(--raise)}.cleanup-options label.disabled{opacity:.45;cursor:not-allowed}.cleanup-options input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--signal)}.cleanup-options span{display:grid;gap:3px}.cleanup-options b{font-size:13px}.cleanup-options small{color:var(--text3);font-size:11px;line-height:1.4}

/* ---------- dialogs & menus ---------- */
.dlg{width:min(calc(100vw - 32px),460px);max-height:min(86dvh,640px);padding:0;border:1px solid var(--line2);border-radius:14px;background:var(--dialog-bg);box-shadow:var(--shadow-dialog);color:var(--text)}
.dlg[open]{display:flex;animation:pop .22s cubic-bezier(.2,.8,.2,1)}
.dlg::backdrop{background:var(--overlay);backdrop-filter:blur(2px)}
.dlg-form{display:flex;flex:1;flex-direction:column;min-height:0;max-height:inherit;padding:24px}
.dlg-title{flex:none;margin:0 0 14px;font-size:18px;font-weight:600}
.dlg-body{flex:1;min-height:0;overflow:auto;color:var(--text2);font-size:14px;line-height:1.6}
.dlg-body p{margin:0 0 10px}
.dlg-body ul{margin:0 0 12px;padding:0;list-style:none;border:1px solid var(--line);border-radius:8px;word-break:break-all}
.dlg-body li{padding:9px 12px;font-size:13px;color:var(--text)}
.dlg-body li+li{border-top:1px solid var(--line)}
.dlg-body label{display:block;margin:6px 0}
.dlg-warning{margin:10px 0;padding:10px 12px;border-radius:8px;background:var(--danger-tint2);color:var(--danger-warn);font-weight:600}
.dlg-actions{display:flex;flex:none;flex-wrap:wrap;justify-content:flex-end;gap:8px;margin-top:18px}
.dlg-menu{display:grid;gap:6px}
.dlg-menu button{justify-content:flex-start;width:100%}
.picker{display:flex;flex-direction:column;gap:8px}
.picker-path{padding:8px 10px;border-radius:8px;background:var(--raise);font-family:var(--mono);font-size:12.5px;word-break:break-all}
.picker-list{max-height:min(40dvh,320px);overflow:auto;border:1px solid var(--line);border-radius:10px}
.picker-list button{justify-content:flex-start;width:100%;min-height:44px;border:0;border-radius:0}
.picker-list button+button{border-top:1px solid var(--line)}
.picker-list button:hover{background:var(--row-hover)}
.theme-btn .ico{display:none}
:root[data-theme-mode="system"] .theme-btn .ico-system,:root[data-theme-mode="light"] .theme-btn .ico-light,:root[data-theme-mode="dark"] .theme-btn .ico-dark{display:block}
.theme-menu{position:fixed;z-index:60;width:236px;padding:6px;border:1px solid var(--line2);border-radius:12px;background:var(--surface-float);box-shadow:var(--shadow-float);animation:pop .16s cubic-bezier(.2,.8,.2,1)}
.theme-menu p{margin:6px 10px 8px;font-family:var(--mono);font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--text3)}
.theme-opt{display:grid;grid-template-columns:30px 1fr 16px;align-items:center;gap:8px;width:100%;min-height:48px;margin:0;padding:6px 10px;border:0;border-radius:8px;background:transparent;color:var(--text);text-align:left}
.theme-opt:hover{background:var(--raise)}
.theme-opt svg{width:18px;height:18px;color:var(--text2)}
.theme-opt b{display:block;font-size:13px;font-weight:500}
.theme-opt small{display:block;font-size:11.5px;color:var(--text3)}
.theme-opt .tick{visibility:hidden;color:var(--signal-ink)}
.theme-opt[aria-checked=true] .tick{visibility:visible}
.theme-opt[aria-checked=true] svg:first-child{color:var(--signal-ink)}

/* ---------- welcome animation ---------- */
/* 欢迎动画（登录后约 2.8 秒，同一网页登录会话只播放一次），不随主题变化（全屏黑底）。 */
.intro-splash{position:fixed;inset:0;z-index:10000;visibility:hidden;overflow:hidden;background:#000;isolation:isolate;cursor:default;opacity:1;transition:opacity .8s ease,filter .8s cubic-bezier(.33,0,.2,1),transform .8s ease}.intro-splash.intro-active{visibility:visible}.intro-splash.intro-exit{opacity:0;filter:blur(28px) saturate(1.8) contrast(1.2);transform:scale(1.055);pointer-events:none}
.intro-stage{position:absolute;inset:0;overflow:hidden;background:#000;animation:intro-stage-in 2s cubic-bezier(.2,.72,.2,1) both}.intro-art{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:center;filter:saturate(1.08) contrast(1.045) brightness(.88);transform:scale(1.035);animation:intro-cinematic-push 2.8s cubic-bezier(.18,.72,.28,1) both}.intro-stage:after{content:"";position:absolute;inset:-18%;background:radial-gradient(ellipse at 50% 52%,#b658ff2c 0,#0a73ff16 24%,transparent 53%);mix-blend-mode:screen;animation:intro-pulse 1.25s ease-in-out infinite alternate}.intro-light-sweep{position:absolute;inset:-35%;background:linear-gradient(110deg,transparent 39%,#bce8ff12 47%,#fff5 50%,#a564ff17 53%,transparent 61%);mix-blend-mode:screen;transform:translateX(-55%);animation:intro-sweep 2.4s .18s ease-in-out both}.intro-vignette{position:absolute;inset:0;box-shadow:inset 0 0 13vw 5vw #000,inset 0 0 4vw #020518;background:linear-gradient(180deg,#0007 0,transparent 18%,transparent 78%,#0009 100%)}.intro-grain{position:absolute;inset:-50%;opacity:.09;background-image:repeating-radial-gradient(circle at 37% 43%,#fff 0 .5px,transparent .7px 3px);background-size:7px 9px;animation:intro-grain .23s steps(2) infinite}.intro-particles{position:absolute;inset:0;width:100%;height:100%;mix-blend-mode:screen;pointer-events:none}.intro-loading-mark{position:absolute;left:50%;bottom:max(3.3vh,env(safe-area-inset-bottom));transform:translateX(-50%);margin:0;font:500 9px/1 ui-monospace,SFMono-Regular,Consolas,monospace;letter-spacing:.42em;color:#b8c7ff88;animation:intro-mark 2.1s ease both}
@keyframes intro-stage-in{0%{opacity:0;filter:blur(20px) brightness(.4)}28%{opacity:1;filter:blur(0) brightness(1)}100%{opacity:1}}@keyframes intro-cinematic-push{0%{transform:scale(1.08)}100%{transform:scale(1.025)}}@keyframes intro-pulse{to{opacity:.54;transform:scale(1.06)}}@keyframes intro-sweep{0%{transform:translateX(-60%);opacity:0}24%{opacity:1}100%{transform:translateX(58%);opacity:0}}@keyframes intro-grain{0%{transform:translate(1%,-1%)}50%{transform:translate(-1%,1%)}100%{transform:translate(.5%,.5%)}}@keyframes intro-mark{0%{opacity:0}25%,78%{opacity:.8}100%{opacity:0}}
@media(max-aspect-ratio:4/5){.intro-vignette{box-shadow:inset 0 0 22vw 8vw #000}}@media(prefers-reduced-motion:reduce){.intro-stage,.intro-art,.intro-light-sweep,.intro-grain,.intro-loading-mark{animation:none!important}.intro-splash{transition:opacity .25s ease!important}}

/* ---------- responsive ---------- */
@media (max-width:1100px){
  .launch{grid-template-columns:1fr;align-content:start}.launch-left{border-right:0;border-bottom:1px solid var(--line)}
  .row,.fm-row{grid-template-columns:44px minmax(0,1fr) 96px 44px}.fm-time{display:none}
}
/* 登录页只在真正接近平板/手机宽度时改为上下布局。原来的 1100px 断点会让普通桌面窗口
   仅缩窄几十像素就整页跳变；900px 以下再切换，宽屏窗口的结构保持稳定。 */
@media (max-width:900px){
  .login{grid-template-columns:1fr}.login-left{padding:40px 32px 32px;border-right:0;border-bottom:1px solid var(--line)}.login-right{padding:40px 24px 56px}
}
@media (max-width:760px){
  .ws{grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,1fr) auto;min-height:0}
  .rail{grid-row:2;flex-direction:row;flex-wrap:wrap;justify-content:space-around;gap:0;padding:0 8px max(6px,env(safe-area-inset-bottom));overflow:visible;border-top:1px solid var(--line);border-right:0}.rail>*{flex-shrink:1}
  .rail>.mark,.rail-gap,.account{display:none}
  .meters{order:-1;flex:1 0 100%;flex-direction:row;justify-content:space-between;gap:14px;width:auto;padding:10px 6px 8px;border:0;border-radius:0}
  .meters:hover,.meters[aria-expanded=true]{background:transparent}
  .meter-col{flex:1;flex-direction:row;gap:8px}
  .bar{flex:1;width:auto;height:4px}
  .bar span{top:0;right:auto;width:var(--p,0%);height:auto}
  .nav-item{flex:1;width:auto;min-height:54px}
  .nav-item[aria-selected=true]::before{left:30%;right:30%;top:-1px;bottom:auto;width:auto;height:2px;border-radius:0 0 2px 2px}
  .head{align-items:center;padding:14px 12px 12px 16px}.title{font-size:22px}.eyebrow{margin-bottom:2px}
  .head-mobile{display:flex}
  /* 手机标题栏改为网格：标题 | 删除授权 | 双 IP | 主题、退出。head-meta 用 display:contents 让其子元素直接参与网格。
     IPv4 与标题同一行；地址较长（IPv6，ip-route--long）或屏幕很窄时，双 IP 移到第二行右侧，
     保持自身宽度、每个地址尽量一行放下；标题栏变高只压缩面板内部，不产生整页滚动。 */
  .head{display:grid;grid-template-columns:minmax(0,1fr) auto auto auto;column-gap:8px;row-gap:10px}
  .head>div:first-child{grid-column:1;grid-row:1;min-width:0}
  .head-meta{display:contents}
  .head-meta>.grant{grid-column:2;grid-row:1}
  .head-meta>.ip-route{grid-column:3;grid-row:1;justify-self:end}
  .head-meta>.head-mobile{grid-column:4;grid-row:1}
  .head-meta>.server-clock{display:none}
  .head [data-page-eyebrow]{display:none}
  .head-clock{display:block;white-space:nowrap;letter-spacing:.06em;text-transform:none}
  .ip-route{padding:5px 5px 5px 7px;border-radius:10px}
  .ip-route--long{grid-column:1/-1!important;grid-row:2!important;justify-self:end!important}
  .ip-user{font-size:clamp(10px,2.75vw,11.5px)}
  .ip-server{font-size:clamp(10.5px,3.05vw,13px)}
  .term-bar{flex-wrap:wrap;padding:6px 12px 0}.term-tools>[data-term-action=copy],.term-tools>[data-term-action=reconnect]{display:none}.term-tools .btn{min-height:34px;padding:0 10px}.term-modes .btn{min-height:30px;padding:0 7px;font-size:11px}
  .term-tab{padding-left:10px}.term-tab-new .long{display:none}.term-tab-new .short{display:inline}
  .term-keys{padding:8px 12px}.term-cmds{left:12px;right:12px;grid-template-columns:minmax(0,1fr);max-height:55vh}.term-keys-sep{display:none}.statusline{padding:8px 12px}.term-note{display:none}.term-note.flash-note{display:block;margin-left:0}
  .fbar{padding:10px 12px}.ftools{gap:2px}.ftools .btn:not(.primary),.ftools .toggle{padding:0 10px}.ftools .btn-label{display:none}
  .table{padding:0 0 92px}.row,.fm-row{grid-template-columns:44px minmax(0,1fr) 44px;padding:0 12px}.fm-size{display:none}.fm-meta{display:block}.headrow{display:none}
  .fm-status{left:12px;max-width:calc(100% - 24px)}.fm-transfers{margin:0 12px 10px}
  /* 手机上收起时只留统计与当前任务两行；操作按钮在展开后使用。 */
  .fm-transfers.collapsed .fm-transfers-head>button:not(.t-toggle){display:none}
  .fm-transfers.collapsed.peeking .t-now{flex-basis:100%;padding:0 0 2px 23px}.fm.has-transfers .fm-status{max-width:calc(100% - 24px);margin:0 12px 8px}
  /* 选中一个文件夹时按钮最多（含“重命名”）：窄屏上换行显示，不再被裁掉。 */
  .selbar{left:10px;right:10px;bottom:10px;flex-wrap:wrap;justify-content:flex-end;gap:2px;padding:4px 4px 4px 12px;transform:none}
  .selbar-count{margin-right:auto}.selbar .btn{min-height:40px;padding:0 10px}.selbar .sep{display:none}
  .launch-left,.launch-right{padding:20px 16px}.big-time{font-size:44px}.devices{gap:8px}.device{min-height:150px;padding:16px 6px 12px}
  .scrim{inset:0}
  .drawer{top:auto;left:0;right:0;bottom:0;width:auto;max-height:80%;border-top:1px solid var(--line2);border-right:0;border-radius:16px 16px 0 0;transform:translateY(102%)}
  /* 手机上浮动提示放在顶部：放在底部会盖住底部导航栏（测试中发现提示显示期间无法切换页面）。 */
  .flash-toast{top:max(10px,env(safe-area-inset-top));left:12px;right:12px;bottom:auto;max-width:none}
  .login-left{gap:22px;padding:28px 20px 22px}.headline{font-size:34px}.lede,.features{display:none}
  .login-right{align-items:flex-start;padding:28px 20px 40px}.form .sub{margin-bottom:22px}
}
/* 桌面布局在窗口较矮时压缩左栏，尽量不出现滚动；低于 .ws 最小高度 520px 时允许整页纵向滚动，
   原先 body 禁止滚动，超出部分直接看不到。 */
@media (max-width:420px){.head{padding-left:14px}.head .title{font-size:20px}}
/* 400px 以下手机：标题列只剩约 100px，时钟行去掉秒数并收紧字距（如“00:44 UTC+5:30”），完整时间在悬停提示里。 */
@media (max-width:400px){.head-clock .clock-sec{display:none}.head-clock{letter-spacing:.02em}}
@media (max-width:360px){.head-meta>.ip-route{grid-column:1/-1;grid-row:2}}
@media (min-width:761px){body.workspace{overflow-x:hidden;overflow-y:auto}}
@media (max-height:700px) and (min-width:761px){.rail{gap:2px;padding:12px 0 10px}.rail>.mark{margin-bottom:8px}.nav-item{min-height:52px;padding:6px 0 5px}.meters{gap:6px;padding:8px 0 6px}.bar{height:clamp(28px,6vh,44px)}.account{margin-top:4px;padding-top:6px}}
@media (max-height:560px) and (min-width:761px){.ip-route{--ip-gap:2px;padding:4px 6px 4px 8px}.head{padding:12px 28px 10px}.title{font-size:22px}.eyebrow{display:none}.bar{height:32px}.nav-item{min-height:50px}}
@media (prefers-reduced-motion:reduce){*,*::before,*::after{animation:none!important;transition:none!important;scroll-behavior:auto!important}}
