/* wanctl 门户
   视觉真源 docs/design/DESIGN.md。改这里之前先改那里。
   与官网 site/assets/app.css 同一套 token、同一套字体、同一套语法，两种密度：
   官网极疏（一屏一个主体、正文 17px），门户中密（一屏看完六台、正文 15px）。

   门户没有「产品渲染」，所以这里**一处阴影都没有**——官网那道唯一的投影
   只属于停在桌面上的那两块屏。 */

/* ── fonts ─────────────────────────────────────────────────────────── */
/* 自托管，不走 Google Fonts。ls 在大陆，访客拉不稳。
   与 site/assets/fonts/ 是同一批文件的副本：go:embed 不支持 `..`，
   跨不出包目录，所以只能各存一份。字体几乎不变，这份重复可接受。 */
@font-face{
  font-family:'Schibsted Grotesk';font-style:normal;font-weight:400 800;font-display:swap;
  src:url(fonts/schibsted-var-latin.woff2) format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face{
  font-family:'Schibsted Grotesk';font-style:normal;font-weight:400 800;font-display:swap;
  src:url(fonts/schibsted-var-latin-ext.woff2) format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face{
  font-family:'JetBrains Mono';font-style:normal;font-weight:400 700;font-display:swap;
  src:url(fonts/jbmono-var-latin.woff2) format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}

/* ── tokens ────────────────────────────────────────────────────────── */
:root{
  --canvas:#ffffff;
  --canvas-2:#f5f5f7;
  --ink:#1d1d1f;
  --ink-2:#6e6e73;      /* 白底 5.07:1，浅灰底 4.66:1 */
  --ink-3:#86868b;      /* 3.62:1 —— 仅装饰笔画与分隔，不承载文字 */
  --hairline:#d2d2d7;
  --blue:#0066cc;       /* Action Blue：一切「可点」，且仅此一色 */
  --blue-hi:#0071e3;
  --blue-wash:#eaf2fc;  /* 当前项底色。承载的是 --blue 文字，不承载正文 */

  /* 语义红。它**不是第二个强调色**：任何「点我」信号仍然只能是 --blue。
     红只陈述状态——裁决 denied、投递失败、危险命令的首词。
     白底 5.38:1、浅灰底 4.94:1，两处都过（tools/contrast.py 验）。
     在线 / 离线故意不引入绿：实心点 vs 空心圈，颜色不参与。 */
  --red:#d70015;

  --sans:'Schibsted Grotesk','PingFang SC','Hiragino Sans GB','Microsoft YaHei',sans-serif;
  --mono:'JetBrains Mono','PingFang SC',ui-monospace,SFMono-Regular,Menlo,monospace;

  /* 内容锁比官网的 1440 窄。官网是海报，要用掉整幅宽度；门户是工具，
     一行摊到 1440 宽会让设备卡稀释成一条，眼睛横扫的距离也过长。 */
  --maxw:1100px;
  --pad:max(clamp(20px,4vw,40px), calc((100vw - var(--maxw)) / 2));
  --ease:cubic-bezier(.22,.61,.36,1);
  --z-sticky:5;
  --z-overlay:20;
  --z-modal:30;
  --z-toast:40;
}

*{box-sizing:border-box}
/* hidden 是这套界面里唯一的「不存在」开关：JS 从头到尾只用它藏东西，没有一处
   指望用 display 把它盖回来。可浏览器默认表里的 [hidden]{display:none} 只有
   (0,1,0)，任何一句带类名的 display 都压得过它 —— .sgroup button{display:block}
   就这么把「邀请」那一栏给普通用户也显示了出来（管理员之外的人看得见一个自己
   点不动的入口）。同一个坑在 .ava img 那里已经被躲过一次（见下面那条注释），
   躲得过是因为那次有人记得；这条把它变成记不记得都躲得过。
   量过：全站带 hidden 的元素里，只有 #sInvites 的计算值不是 none，所以这一句
   除了修掉那一处，别处一个像素都不动（tools/portalpreview/sweep.mjs 的 hidden 断言）。 */
[hidden]{display:none!important}
html{-webkit-text-size-adjust:100%}
body{
  margin:0;background:var(--canvas);color:var(--ink);
  font-family:var(--sans);font-size:15px;font-weight:400;line-height:1.47;
  letter-spacing:-.2px;
  -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
}
a{color:var(--blue);text-decoration:none}
a:hover{text-decoration:underline}
:focus-visible{outline:2px solid var(--blue-hi);outline-offset:3px;border-radius:4px}
/* 命令、指纹、路径要能被逐字符读准：等宽处一律关连字，别让 -- 连成一道长划 */
.mono,code,pre,.cmd,.fp,.kv dd{
  font-family:var(--mono);letter-spacing:0;
  font-variant-ligatures:none;font-feature-settings:"liga" 0,"calt" 0;
}

/* ── 头部 ──────────────────────────────────────────────────────────────
   48px，与官网同高。backdrop-filter 是 DESIGN.md §8 里那个有理由的例外：
   吸顶条压在内容上必须半透，它承担功能而不是装饰。 */
.top{
  position:sticky;top:0;z-index:var(--z-sticky);
  display:flex;align-items:center;gap:14px;height:48px;padding:0 var(--pad);
  background:rgba(255,255,255,.86);backdrop-filter:saturate(180%) blur(20px);
}
/* 顶栏底下没有分隔线（2026-09-04 甲方拍板，官网同时也拿掉了）。
   之前那条是收在内容锁以内的一条发丝线，试过全幅也试过内缩，两种都不对：
   它在一个只放着字标和两三个 chip 的横条底下横过整页，把页面切成两截，
   而上半截并没有需要被隔开的东西。

   吸顶时的边界交给 backdrop-filter —— 内容从底下滑过去会被抹成一片柔光，
   那本来就是这层半透的职责（DESIGN.md §8 里唯一那个有理由的玻璃例外）。
   页面停在顶部时顶栏就应该化进页面里，那正是想要的。 */
/* 标记 + 字标。标记是那个末笔上扬的 w（site/assets/mark.svg 同一条路径），
   currentColor 描边 —— 它跟着字标的颜色走，不引入第二种墨色。 */
.top .brand{
  display:flex;align-items:center;gap:8px;
  font-size:17px;font-weight:600;letter-spacing:-.4px;color:var(--ink);
  flex:none;cursor:pointer;background:none;border:0;padding:0;font-family:inherit;
  /* 标记在自己那个 32 的 viewBox 里左右各留了 4.05 的边距（笔画 3.1、圆头），
     19px 下就是 2.4px —— 盒子压着内容锁，墨迹却比底下的卡片往里缩 2.4px。
     拉回 2px 而不是 2.4：圆头本来就该比直边多探出去一点点才看着齐。
     **改了 .mk 的尺寸要重算这个数。** */
  margin-left:-2px;
}
.mk{width:19px;height:19px;flex:none;display:block}
.top .brand:hover{text-decoration:none}
.top .who{
  margin-left:auto;display:flex;align-items:center;gap:8px;min-width:0;
  font-size:13px;color:var(--ink-2);
}
.top .who .nm{color:var(--ink);font-weight:600;white-space:nowrap}
/* 命名空间只在它跟登录名不一样时才出现 —— 一样的时候显示两遍是复述。 */
.top .who .ns{white-space:nowrap}
.top .who .ns::before{content:"· "}
/* 字母章。这里从来就只有字母：GitHub 头像被门户自己的 CSP 挡着，
   而 header(SSO) 模式压根没有头像。 */
.ava{
  width:22px;height:22px;border-radius:50%;flex:none;
  display:grid;place-items:center;background:var(--canvas-2);
  font-size:11px;font-weight:600;color:var(--ink-2);text-transform:uppercase;
  /* 头像叠在字母上，所以这里要成为定位上下文，并且自己把方角裁圆 —— 圆角
     只作用在盒子上，里面那张方图不裁就会露出四个角。 */
  position:relative;overflow:hidden;
}
.ava i{font-style:normal}
/* 不写 display：浏览器默认表里 [hidden]{display:none} 只有 (0,1,0)，
   这里随手写一句 display:block 就会把它盖掉，图没加载出来时留下一个碎图标。 */
.ava img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.top .tools{display:flex;align-items:center;gap:6px;flex:none}
/* 头部的三个次级控件（版本号 / 语言 / 齿轮）共用一个形状。
   圆角 999 在这套语法里本来只给主 CTA 和 pill —— 它们是 pill。 */
.chip{
  font:inherit;font-size:12.5px;line-height:1.2;color:var(--ink);
  background:none;border:1px solid var(--hairline);border-radius:999px;
  padding:4px 10px;cursor:pointer;white-space:nowrap;
  transition:background .16s var(--ease);
}
.chip:hover{background:var(--canvas-2)}
.chip:active{transform:scale(.95)}
/* 顶栏里的那几个不画边框。分隔线拿掉之后，一条只放着字标和三个描边胶囊的
   横带里，最重的东西成了那三个框 —— 它们是次级控件，不该比字标显眼。
   悬停时的浅灰底仍然说明它们能点。设备标题行里那个齿轮保留边框：
   那里是内容不是外壳，旁边还站着一个描边的 select。 */
.top .chip{border-color:transparent}
.top .chip:hover{border-color:transparent;background:var(--canvas-2)}
/* 最后那个控件要把自己的内边距吐回去。描边还在的时候，那圈内边距的外沿就是
   边框、边框压着内容锁，看着是齐的；边框一拿掉，内边距成了不可见的空白，
   右端的墨迹就比内容锁往里缩了 5~10px —— 而底下的标题和卡片是**压着锁**的
   （1728px 下实测：卡片右缘 1414，齿轮右缘 1408）。负外边距只挪盒子不挪墨迹，
   悬停时那块浅灰底会探进 --pad 的留白里，那正是它该在的地方。 */
.top .tools > .chip:last-child{margin-right:-10px}
.top .tools > .chip.icon:last-child{margin-right:-5px}
.chip.icon{padding:5px;display:grid;place-items:center}
.chip.icon svg{width:16px;height:16px;display:block}
@media (max-width:640px){
  .top .who .nm,.top .who .ns{display:none}
}

/* ── 页面骨架 ──────────────────────────────────────────────────────── */
main{padding:clamp(26px,4vw,44px) var(--pad) 88px}
.view{display:none}
.view.show{display:block}

h1{margin:0;font-size:clamp(26px,3.2vw,32px);font-weight:600;line-height:1.1;letter-spacing:-.4px}
h2{margin:0;font-size:20px;font-weight:600;line-height:1.2;letter-spacing:-.3px}
.lead{margin:8px 0 0;color:var(--ink-2);max-width:60ch}
.vhead{display:flex;align-items:flex-end;gap:14px;flex-wrap:wrap;margin-bottom:20px}
.vhead h1{flex:1 1 auto;min-width:0;overflow-wrap:anywhere}

/* ── 按钮 ────────────────────────────────────────────────────────────
   蓝色实心 pill 只给主 CTA。次级一律浅灰实心、等重。
   破坏性动作用红字而不是红底：红底会把「删除」做成页面上最亮的东西。 */
.btn{
  font:inherit;font-size:14px;font-weight:400;letter-spacing:-.2px;
  background:var(--blue);color:#fff;border:0;border-radius:999px;
  padding:8px 18px;cursor:pointer;white-space:nowrap;
  transition:background .16s var(--ease);
}
.btn:hover{background:var(--blue-hi);text-decoration:none}
.btn:active{transform:scale(.95)}
.btn[disabled]{opacity:.45;pointer-events:none}
.btn.soft{background:var(--canvas-2);color:var(--ink)}
.btn.soft:hover{background:#e8e8ed}
.btn.ghost{background:none;color:var(--blue);padding:8px 10px}
.btn.ghost:hover{background:var(--canvas-2)}
.btn.danger{background:none;color:var(--red);padding:8px 10px}
.btn.danger:hover{background:var(--canvas-2)}
/* 表格行尾那种最小动作 */
.act{
  font:inherit;font-size:13px;color:var(--blue);background:none;border:0;
  /* 负外边距把 padding 撑出来的那几像素收回去，让按钮的字跟同一行的
     单元格文字对齐 —— 红色本来就抢眼，差 3px 看着就是歪的。 */
  padding:3px 6px;margin:-3px -6px;border-radius:6px;cursor:pointer;white-space:nowrap;
}
.act:hover{background:var(--canvas-2)}
.act.danger{color:var(--red)}
/* 上面那个负外边距是为了让按钮的字跟同行文字对齐，但它也把相邻的两个动作
   吸到了一起（「接受拒绝」）。相邻时补回来。 */
.act + .act{margin-left:6px}

/* ── 待审批：门户的主体 ────────────────────────────────────────────────
   DESIGN.md §2 —— 留白中央必须有一个东西镇得住。有人在等你的时候，
   镇场的就是这张卡；没人等的时候，主体是设备清单。

   它在两处挂载、是同一个组件：主屏顶部（跨设备聚合）与设备下钻的待审批页
   （过滤到这一台）。 */
.asks{display:grid;gap:14px;margin-bottom:clamp(30px,4vw,46px)}
.asks:empty{display:none}
.ask{
  border:1px solid var(--hairline);border-radius:18px;overflow:hidden;
  background:var(--canvas);
}
.ask.gone{opacity:0;transform:translateY(-4px);transition:opacity .3s var(--ease),transform .3s var(--ease)}
.ask .head{
  display:flex;align-items:center;gap:10px;
  padding:13px 20px;border-bottom:1px solid var(--hairline);
  font-size:13px;color:var(--ink-2);
}
.ask .head .host{color:var(--ink);font-weight:600;font-size:14px;flex:none}
/* 别名后面那一小截真名。和设备卡同一条规则，只是这里排在行内。 */
.ask .head .real{margin-left:6px;font-weight:400;font-size:13px;color:var(--ink-2)}
/* 中间那格（种类 / 配对说明）吃掉剩余宽度并在必要时省略号，
   否则它一长就把秒表挤到第二行去。 */
.ask .head .what{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* 秒表。它是真的 —— `wanctl exec` 就阻塞在那儿等人回答，所以走字的是等待本身。 */
.ask .head .wait{margin-left:auto;flex:none;white-space:nowrap;font-variant-numeric:tabular-nums}
.ask .body{padding:20px}
.ask .cmd{
  font-size:14px;line-height:1.6;color:var(--ink);
  word-break:normal;overflow-wrap:break-word;margin:0 0 16px;
}
.ask .cmd .tok{white-space:nowrap}
.ask .cmd .tok.split{white-space:normal;word-break:break-all}
/* 危险命令的首词。红在这儿是陈述，不是按钮。 */
.ask .cmd .rm{color:var(--red);font-weight:700}
/* 键值表：左列固定窄，右列自己折行。数据是记录，所以左对齐、左缘对齐。 */
.kv{display:grid;grid-template-columns:auto 1fr;gap:6px 16px;margin:0;font-size:13px}
.kv dt{color:var(--ink-2);white-space:nowrap}
.kv dd{margin:0;min-width:0;overflow-wrap:anywhere;font-size:12.5px}
.ask .acts{
  display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  padding:14px 20px;border-top:1px solid var(--hairline);background:var(--canvas-2);
}
/* 层级按后果排，不按语气排（DESIGN.md §6.5）。
   「记住本目录」「全局都行」是持久授权 —— 这个产品里后果最重的动作 ——
   所以它们不能比「允许一次」轻。三个次级动作等重，作用域写在标签里，
   让后果看得见而不是藏在措辞里。
   「拒绝」不上红：拒绝是安全的那一侧，把它染成警告色是在劝人别拒绝。 */
.ask .acts .btn{font-size:13.5px;padding:7px 15px}
.ask .acts .spacer{margin-left:auto}
/* 窄屏上动作会换行，这时 margin-left:auto 把「拒绝」单独甩到第二行最右边，
   看着像它被隔离出去了。挤不下就让它跟着排。 */
@media (max-width:560px){
  .ask .acts .spacer{margin-left:0}
}
.ask .scope{font-size:11.5px;color:var(--ink-2);margin-left:2px}

/* 空态。99% 的时间没人等你 —— 这是常态，不是错误页，所以它一行就够，
   不加插画、不加第二行安慰。 */
.blank{
  padding:clamp(34px,6vw,60px) 0;text-align:center;color:var(--ink-2);font-size:14px;
}

/* ── 设备 ──────────────────────────────────────────────────────────── */
.grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}
.dev{
  /* 网格把同一行的卡片拉成等高；button 默认把内容垂直居中，于是有别名的
     那张（两行）和没有的（一行）名字不在同一条基线上。列排从顶开始。 */
  display:flex;flex-direction:column;justify-content:flex-start;
  width:100%;text-align:left;font:inherit;color:inherit;cursor:pointer;
  background:var(--canvas);border:1px solid var(--hairline);border-radius:18px;
  padding:14px 16px;transition:border-color .16s var(--ease),background .16s var(--ease);
}
.dev:hover{border-color:var(--ink-3);background:var(--canvas-2)}
.dev:active{transform:scale(.995)}
.dev .nm{display:flex;align-items:center;gap:8px;font-size:16px;font-weight:600;letter-spacing:-.3px}
/* 在线 = 实心墨点，离线 = 空心发丝圈。实心 vs 空心是无色的强区分，
   不必为「活着」发明一个绿色。 */
.dot{width:7px;height:7px;border-radius:50%;flex:none;background:var(--ink)}
.dot.off{background:none;border:1.5px solid var(--ink-3)}
.dev.offline{color:var(--ink-2)}
.dev.offline .nm{color:var(--ink-2);font-weight:400}
/* 「最近」只在离线时出现。在线设备这一行永远是「刚刚」，等于空转。 */
.dev .seen,.dev .real{display:block;margin-top:8px;font-size:12px;color:var(--ink-2)}
.dev .badge{
  margin-left:auto;font-size:11.5px;color:var(--ink-2);font-weight:400;
  border:1px solid var(--hairline);border-radius:999px;padding:1px 8px;white-space:nowrap;
}
/* 有人在等这台设备。数字比任何图标都直接。 */
.dev .badge.waiting{color:var(--blue);border-color:var(--blue)}
.dev .shared{display:block;margin-top:8px;font-size:12px;color:var(--ink-2)}

/* 首台设备的引导。只在一台都没有时出现——有设备之后它就是常驻噪声。 */
.onboard{
  border:1px solid var(--hairline);border-radius:18px;padding:22px 24px;
  display:flex;align-items:center;gap:16px;flex-wrap:wrap;
}
.onboard p{margin:0;flex:1 1 20rem;min-width:0;color:var(--ink-2)}

/* ── 设备下钻 ──────────────────────────────────────────────────────── */
.back{
  font:inherit;font-size:13px;color:var(--ink-2);background:none;border:0;
  padding:0 0 14px;cursor:pointer;display:inline-flex;align-items:center;gap:5px;
}
.back:hover{color:var(--ink)}
.dhead{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:6px}
/* 收缩得下去，也断得开。以前是 flex:0 0 auto —— 一个 32 字的机器名
   （DESKTOP-RQFV0SH-workstation-long）于是保持 max-content 宽，把整页顶宽。 */
.dhead h1{flex:0 1 auto;min-width:0;overflow-wrap:anywhere}
.dhead .fp{
  color:var(--ink-2);flex:1 1 100%;order:9;margin-top:2px;
  /* 指纹是拿来跟终端里那串逐字符核对的，所以整串可见、不截断、不藏在一次点击后面
     —— enroll 页早就是这么处理的（.auth .fp），同一个东西在两个表面上不该有两种脾气。
     51 个字符（SHA256: + 44）在 390px 的手机上按 12px 要 367px，而内容宽只有 350，
     于是它把整个页面撑宽，手机 Chrome 跟着把布局视口一起撑宽，浮层就按那个被撑坏的
     宽度居中 —— 甲方那张「确认框被推到右边、右缘切掉」的截图就是这么来的。
     字号跟着视口缩，让这一串在任何手机上都落成一行；break-all 是兜底，
     万一还是放不下，它宁可折行也不许顶破页面。 */
  font-size:min(12px,2.85vw);word-break:break-all;
}
.dhead .real{font-size:13px;color:var(--ink-2);min-width:0;overflow-wrap:anywhere}
/* 执行模式是这台设备的一个属性，不是一个页面 —— 所以它住在标题行，
   而不是像以前那样独占一个 tab。

   胶囊是原来那个 <select> 的形状（同样的字号、圆角、发丝线边框、红色变体），
   只是里面换成了一个按钮加一张自绘的菜单：原生下拉在 macOS Chrome 上是一张
   系统菜单，浮在胶囊左上方盖住设备名，看着不属于这一行。 */
.modemenu{position:relative;flex:none}
.dhead .modesel{
  width:auto;flex:none;font:inherit;font-size:13px;color:var(--ink);background:var(--canvas);
  border:1px solid var(--hairline);border-radius:999px;padding:5px 11px;cursor:pointer;
  display:inline-flex;align-items:center;gap:5px;white-space:nowrap;
  transition:background .16s var(--ease);
  /* 盒子要跟它替掉的那个 <select> 一样大，宽屏 30、手机 40（见下面的断点）。
     一个 <button> 里的文字按正文的 1.47 行高排，比 select 自己的内部行高高出
     一两个像素 —— 手机上那一两个像素正好把它从 40 掉到 39，而 40 是这份样式表
     给手指靶子定的下限（sweep.mjs 就是照这个数判的）。 */
  line-height:1.2;min-height:30px;
}
.dhead .modesel:hover{background:var(--canvas-2)}
.dhead .modesel.bypass{border-color:var(--red);color:var(--red)}
/* 共享给我的设备只能看：按钮 disabled，墨迹压下去，悬停也不再回应。 */
.dhead .modesel:disabled{cursor:default;color:var(--ink-2);border-color:var(--hairline)}
.dhead .modesel:disabled:hover{background:var(--canvas)}
/* 人字是装饰笔画，不跟标签抢分量；它同时是「开着没开」的唯一指示。 */
.dhead .modesel .cv{flex:none;opacity:.5;transition:transform .16s var(--ease)}
.dhead .modesel[aria-expanded="true"] .cv{transform:rotate(180deg)}

/* 菜单贴着胶囊的下沿开、左缘对齐 —— 它是这个控件长出来的，不是飘在页面上的
   另一块东西。整份样式表一处阴影都没有，这里也不破例：白底加一圈发丝线就够分层。
   left 由 app.js 在打开时校一次：胶囊靠得太右时把菜单往回收，
   横向溢出在手机上会把布局视口一起撑坏（见本文件末尾断点里的账）。 */
.modelist{
  position:absolute;top:calc(100% + 6px);left:0;z-index:var(--z-overlay);
  margin:0;padding:4px;list-style:none;min-width:100%;
  background:var(--canvas);border:1px solid var(--hairline);border-radius:12px;
}
.modeopt{
  display:flex;align-items:center;gap:8px;white-space:nowrap;
  font-size:13px;color:var(--ink);padding:7px 10px;border-radius:8px;cursor:pointer;
}
/* .on 是键盘走到的那一条 —— 程序性 focus 不一定触发 :focus-visible，
   光靠焦点环指不出「现在在第几条」。 */
.modeopt:hover,.modeopt.on{background:var(--canvas-2)}
/* 当前项只用一个勾说明，不再叠底色和第二种颜色：三个信号说同一件事。
   勾常驻但不可见，两条的文字才对得齐。 */
.modeopt .ck{flex:none;visibility:hidden}
.modeopt[aria-selected="true"] .ck{visibility:visible}

.tabs{display:flex;gap:2px;border-bottom:1px solid var(--hairline);margin:20px 0 22px}
.tab{
  font:inherit;font-size:14px;color:var(--ink-2);background:none;border:0;
  padding:9px 14px;cursor:pointer;position:relative;white-space:nowrap;
  display:inline-flex;align-items:center;gap:7px;
}
.tab:hover{color:var(--ink)}
.tab.on{color:var(--ink);font-weight:600}
/* 当前项的下划线压在容器那条发丝线上 —— 颜色变化就是分隔，不另加边框 */
.tab.on::after{content:"";position:absolute;left:14px;right:14px;bottom:-1px;height:2px;background:var(--ink)}
.tab .n{
  font-size:11.5px;font-weight:400;color:#fff;background:var(--blue);
  border-radius:999px;padding:0 6px;min-width:17px;text-align:center;
}
.pane{display:none}
.pane.show{display:block}

/* ── 表 ────────────────────────────────────────────────────────────────
   表是用来扫的，扫需要固定的左缘。只有横向发丝线，没有外框、没有斑马纹、
   没有卡片包裹 —— 行与行之间的那条线已经是全部需要的分隔。 */
table{width:100%;border-collapse:collapse;font-size:13.5px}
th{
  text-align:left;font-weight:400;color:var(--ink-2);font-size:12.5px;
  padding:0 12px 8px 0;border-bottom:1px solid var(--hairline);white-space:nowrap;
}
td{padding:11px 12px 11px 0;border-bottom:1px solid var(--hairline);vertical-align:top}
th:last-child,td:last-child{padding-right:0;text-align:right}
tr:last-child td{border-bottom:0}
td .sub{display:block;font-size:11.5px;color:var(--ink-2);margin-top:2px}
/* 等宽格子里装的是指纹、命令和路径 —— 断不开的长串，得允许它们在任意位置折。
   anywhere（不是 break-word）连最小内容宽度一起放开，这样一整串指纹不会把
   它那一列撑到把表顶出内容锁。 */
td.mono{overflow-wrap:anywhere}
td.num{font-variant-numeric:tabular-nums}
.tcap{margin:0 0 12px;color:var(--ink-2);font-size:13px}
.dim{color:var(--ink-2)}
.no{color:var(--red)}
/* 「机器自己报的那个名字」跟在显示名后面。.real 没有基础样式，每个用到它的地方
   自己决定形状：设备卡上另起一行（.dev .real），聚合待审批里留 6px（.ask .head .real）。
   共享授权表和配对单的 kv 是后来加的，两处都没写 —— 而 realHTML() 是直接拼进
   字符串的，中间没有空白，于是那里渲染成「workshopbench-02」，两个名字粘成一个词。
   用中点：新增授权那张表单里的设备选项本来就是「显示名 · 机器名」，表里跟它一致。 */
#acl .real,#pair .kv dd .real{color:var(--ink-2)}
#acl .real::before,#pair .kv dd .real::before{content:" · "}
/* 全表空态：一行字，与 .blank 同一个声音 —— .blank 是居中的，所以这里也居中。
   写成 td.tempty 而不是 .tempty：整行空态是一个 colspan 单元格，它同时也
   trivially 是「最后一列」，于是上面那条为操作列写的 td:last-child{text-align:right}
   把它一起右对齐了。四张表（令牌 / 邀请 / 好友 / 共享授权）的空态全都被顶到了
   右缘，读起来像一句掉在角落里的碎话，而不是这张表的状态。 */
td.tempty{padding:26px 0;color:var(--ink-2);font-size:13.5px;text-align:center}

/* ── 设置：左分组右内容 ────────────────────────────────────────────────
   与文档站是同一个布局家族（左导航 + 内容），两个表面因此共用一套肌肉记忆。

   它是一个**页面**，不是覆盖层。覆盖层会把顶栏一起盖掉，于是「我现在是哪个
   命名空间、指纹是多少」在进设置时消失 —— 一个安全工具不该这样。 */
.settings{
  display:grid;grid-template-columns:212px minmax(0,1fr);
  gap:clamp(24px,4vw,52px);
}
.settings > nav{position:sticky;top:64px;align-self:start}
.sview{display:none}
.sview.show{display:block}
/* 「回到清单」只属于窄屏的两级形态。宽屏上清单一直在左边，没有「回去」可回。 */
.spane > .back{display:none}
.sgroup{margin-bottom:22px}
.sgroup .t{font-size:12px;color:var(--ink-2);padding:0 10px 6px}
.sgroup button{
  display:block;width:100%;text-align:left;font:inherit;font-size:14px;
  color:var(--ink-2);background:none;border:0;border-radius:8px;
  padding:6px 10px;cursor:pointer;
}
/* 用 background-color 而不是 background 简写：窄屏那一级要在同一个盒子上
   再挂一枚人字形背景图，简写会把它一起清掉。 */
.sgroup button:hover{background-color:var(--canvas-2);color:var(--ink)}
.sgroup button.on{background-color:var(--blue-wash);color:var(--blue);font-weight:600}

/* ── 窄屏：设置是两级，不是一屏 ────────────────────────────────────────
   以前一列塞不下就直接把导航摞到内容上面，于是手机上要滚过整张菜单才够得着
   你点的那一节，而菜单自己读起来像是页面的一部分。iOS 设置那套：
   清单是一屏（`#settings`），点进去那一节是另一屏（`#settings/tokens`），
   顶上一枚人字形回清单。两级都在地址里，所以系统返回键是对的。

   宽屏一个字都不改 —— 下面全部条款都关在这个断点里面。 */
@media (max-width:760px){
  .settings{grid-template-columns:minmax(0,1fr)}
  .settings > nav{position:static}
  .sgroup{margin-bottom:14px}
  .settings[data-level="list"] > .spane{display:none}
  .settings[data-level="section"] > nav{display:none}
  .settings[data-level="section"] .spane > .back{display:inline-flex}
  /* 清单这一屏上没有「当前项」：高亮的是你上次去过的地方，不是你此刻在的地方。 */
  .settings[data-level="list"] .sgroup button.on{
    background-color:transparent;color:var(--ink-2);font-weight:400;
  }
  /* 手指的靶子。14px 行高 1.47 配 6px 内边距只有 34px 高。 */
  .sgroup button{padding:11px 10px}
  /* 每一行都会推开一屏，就得说自己会。同一枚人字形（.back 那条路径），
     指向它去的那个方向。文档那条是外链，它的 ↗ 已经在标签里了。 */
  .sgroup button[data-s]{
    padding-right:34px;
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2386868b' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m9 18 6-6-6-6'/%3E%3C/svg%3E");
    background-repeat:no-repeat;background-position:right 10px center;background-size:13px 13px;
  }
}

/* ── 表单 ──────────────────────────────────────────────────────────── */
.form{display:grid;gap:14px;max-width:34rem}
.field{display:grid;gap:5px}
.field label{font-size:13px;color:var(--ink-2)}
input,select,textarea{
  font:inherit;font-size:14px;color:var(--ink);background:var(--canvas);
  border:1px solid var(--hairline);border-radius:8px;padding:8px 11px;width:100%;
}
input:focus,select:focus,textarea:focus{outline:none;border-color:var(--blue-hi);box-shadow:0 0 0 3px var(--blue-wash)}
.check{display:flex;align-items:center;gap:8px;font-size:14px;cursor:pointer}
.check input{width:auto;flex:none}
.checks{display:grid;gap:9px;grid-template-columns:repeat(auto-fit,minmax(240px,1fr))}

/* 开关。整套东西里唯一的「状态即形状」控件。 */
.sw{
  width:40px;height:24px;border-radius:999px;background:var(--hairline);
  border:0;padding:0;cursor:pointer;position:relative;flex:none;
  transition:background .18s var(--ease);
}
.sw::after{
  content:"";position:absolute;top:2px;left:2px;width:20px;height:20px;border-radius:50%;
  background:#fff;transition:transform .18s var(--ease);
}
.sw.on{background:var(--blue)}
.sw.on::after{transform:translateX(16px)}
.sw.danger.on{background:var(--red)}
.row{display:flex;align-items:center;gap:12px}
.row .txt{flex:1;min-width:0}
.row .txt b{font-weight:600}
.row.fieldrow{margin-top:12px}
.row.fieldrow input{flex:1;min-width:0;max-width:20rem}

/* 命令与代码块。之前 pre 一条样式都没有，只继承了等宽字体 ——
   于是「电脑：一行装好」那页上，说明文字和要你复制的那条命令长得一样重，
   一眼看不出哪条是命令。 */
pre{
  margin:6px 0 14px;padding:12px 14px;
  background:var(--canvas-2);border-radius:8px;
  font-size:13px;line-height:1.6;color:var(--ink);
  overflow-x:auto;white-space:pre;
}
/* 能点就得说自己能点。老版有一句「点一下命令可以复制」，我把它当噪声砍了
   却没留替代物 —— 于是这块变成了一个没有任何提示的可点区域。
   标签平时不在，悬停才出现；没有悬停的设备（触屏）上一直显示。 */
pre[data-copy]{cursor:pointer;position:relative;padding-right:64px;transition:background .16s var(--ease)}
pre[data-copy]:hover{background:#ececf0}
pre[data-copy]::after{
  content:attr(data-copy);position:absolute;top:11px;right:13px;
  font-family:var(--sans);font-size:11.5px;letter-spacing:-.2px;color:var(--ink-2);
  opacity:0;transition:opacity .16s var(--ease);pointer-events:none;
}
pre[data-copy]:hover::after{opacity:1}
@media (hover:none){ pre[data-copy]::after{opacity:1} }
code{background:var(--canvas-2);border-radius:5px;padding:1px 5px;font-size:.92em}
pre code{background:none;padding:0;font-size:1em}

/* 卡片：设置页里那些成组的开关与表单 */
.card{border:1px solid var(--hairline);border-radius:18px;padding:20px}
.card + .card{margin-top:14px}
.card .note{margin:10px 0 0;font-size:12.5px;color:var(--ink-2)}
.card .note.warn{color:var(--red)}

/* 一次性凭据。三句话说同一件事已经砍掉，只剩码和一句。 */
.slip{
  display:none;margin-top:18px;border:1px solid var(--hairline);border-radius:18px;padding:18px 20px;
}
.slip.show{display:block}
.slip code{display:block;font-size:14px;word-break:break-all;margin-bottom:10px}
.slip .once{font-size:12.5px;color:var(--ink-2);margin-bottom:12px}

/* ── 模态 ──────────────────────────────────────────────────────────── */
.ov{
  position:fixed;inset:0;z-index:var(--z-modal);background:rgba(0,0,0,.32);
  display:none;align-items:center;justify-content:center;padding:20px;
}
.ov.show{display:flex}
.modal{
  background:var(--canvas);border-radius:18px;padding:24px;width:100%;max-width:26rem;
  /* svh 而不是 vh。手机浏览器的 vh 是地址栏收起之后的那个大视口，
     按它算出来的浮层比你眼前看得见的那块高，底下那排按钮正好落在屏幕外面。
     两条都留着：不认 svh 的浏览器读第一条。 */
  max-height:calc(100vh - 40px);
  max-height:calc(100svh - 40px);
  /* 四张浮层现在是同一个形状：标题钉住、正文自己滚、按钮钉住。
     以前只有更新日志那张是，另外三张是整块一起滚 —— 于是正文一长
     （一个 32 字的设备名、一段中文说明、五个字段的表单），
     「取消 / 确定」就被推到浮层外面，而人正是来按那两个键的。 */
  display:flex;flex-direction:column;overflow:hidden;
}
.modal > h2,.modal > .vhead,.modal > .btns{flex:none}
.modal .mbody,.modal .sbody{
  flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;
  /* 版本说明和错误里有 URL、路径、环境变量名，断不开的长串会顶破浮层的右缘。 */
  overflow-wrap:anywhere;
}
/* 更新日志那张只是更宽 —— 分三段的骨架现在是所有浮层共有的。 */
.modal.sheet{max-width:44rem}
.modal h2{margin:0 0 8px}
.modal p{margin:0 0 18px;color:var(--ink-2);font-size:14px}
.modal .mbody > :last-child{margin-bottom:0}
.modal .btns{display:flex;justify-content:flex-end;gap:8px;padding-top:18px}

/* ── toast ─────────────────────────────────────────────────────────── */
/* 反馈只有这一套。以前顶上还挂着一条 6 秒的错误条，两套机制做同一件事。 */
.toast{
  position:fixed;left:50%;bottom:26px;transform:translate(-50%,14px);z-index:var(--z-toast);
  background:var(--ink);color:var(--canvas);border-radius:999px;padding:9px 18px;font-size:13.5px;
  opacity:0;pointer-events:none;transition:opacity .2s var(--ease),transform .2s var(--ease);
  /* width:max-content 是必需的，不是修饰。left:50% 而没有 right，收缩到合适宽度时
     可用宽度只有视口的右半边 —— 360px 上就是 180px，于是底下那条 max-width 从来没
     生效过，一句英文提示在还剩一半空地的时候就折成了两行。 */
  width:max-content;max-width:calc(100vw - 40px);
}
.toast.show{opacity:1;transform:translate(-50%,0)}
/* 认不出来的服务端错误码。等宽是为了让它能被逐字符读准并抄进 issue 里；
   底色和字色都跟着 toast 走 —— 全局的 code 样式给的是浅灰底，压在深色或红色
   的 toast 上会变成一块补丁。 */
.toast code{
  background:none;padding:0;margin-left:6px;
  font-family:var(--mono);font-size:12.5px;color:inherit;opacity:.85;
}
.toast.no{background:var(--red)}

/* ── 认证页 ──────────────────────────────────────────────────────────
   登录 / 等待邀请 / 设备授权。三张不是 SPA 的一部分（由 Go 渲染 web/*.html），
   但共用这份样式表 —— handleAsset 故意不鉴权就是为了这个。

   一列锁到 25rem，段落一律左对齐（DESIGN.md §5：居中的可读上限是 1–3 行）。
   整页只有那块要复制的授权码是居中的：它是个物件，不是句子。
   没有卡片外框 —— 页面上再没有别的东西，一圈边框不分隔任何两样东西。 */
/* 竖向居中。这三张页面各自只有一件事，页面上再没有别的东西 —— 顶对齐会把
   那一件事挂在上三分之一、下面空掉半屏。底部内边距比顶部大，让内容落在
   正中略偏上（视觉中心比几何中心高）。min-height 不是 height：内容长过一屏
   时容器自己长，不会被裁掉。 */
.auth main{
  display:flex;flex-direction:column;justify-content:center;
  min-height:calc(100vh - 48px);min-height:calc(100dvh - 48px);
  max-width:25rem;margin:0 auto;padding:36px 20px 76px;
}
.auth .top .brand{cursor:default}       /* 三张页都无处可去，字标不是链接 */
.auth .top .tools{margin-left:auto}     /* SPA 里靠 .who 顶开，这里没有 .who */
.auth .host{margin:6px 0 0;font-size:13px;color:var(--ink-2)}
/* 这几行都是两三行以内的短句。balance 把它们摊成等长的几行，
   而不是留一个「you in.」那样的两字孤行。 */
.auth .lead{margin:18px 0 0;text-wrap:balance}
.auth b{font-weight:600;color:var(--ink)}
.auth .fine{margin:16px 0 0;font-size:12.5px;color:var(--ink-2);line-height:1.6;text-wrap:balance}
.auth .fine .act{font-size:inherit}

/* 主 CTA 通栏。这套语法里 999px 只给主 CTA 和 pill —— 这就是主 CTA，
   而且是整页唯一一个可点的东西。 */
.auth .go{
  display:flex;align-items:center;justify-content:center;gap:8px;
  width:100%;margin-top:26px;padding:11px 18px;font-size:15px;
}
.auth .go svg{width:17px;height:17px;flex:none}

/* 授权码。新设备接入时人看到的第一个 wanctl 界面，这块就是那一屏的主体。 */
.auth .code{
  margin-top:26px;padding:22px 14px;border-radius:18px;background:var(--canvas-2);
  font-family:var(--mono);font-size:clamp(26px,7vw,38px);font-weight:600;letter-spacing:2px;
  line-height:1.2;text-align:center;color:var(--ink);word-break:break-all;
  cursor:pointer;user-select:all;transition:background .16s var(--ease);
}
.auth .code:hover{background:#ececf0}
.auth .code:active{transform:scale(.99)}
.auth .meta{margin:10px 0 0;font-size:12.5px;color:var(--ink-2);text-align:center}
/* 指纹是拿来逐字符核对的，所以整串可见、可整选，不截断。
   11.5px 是为了让 SHA256: 加 44 个 base64 字符（恒定 51 字）在这个列宽里
   落成一行：断成两行时最后只剩一个 "="，看着像渲染坏了，而这一串正是
   人要拿去跟终端里的那串比对的。 */
.auth .fp{margin:8px 0 0;font-size:min(11.5px,2.85vw);color:var(--ink);word-break:break-all;user-select:all}

/* 等待邀请页的兑换表单 */
.auth form{display:grid;gap:10px;margin-top:24px}
.auth form input{font-family:var(--mono);text-align:center;padding:11px 12px}
.auth form .go{margin-top:0}
.auth .err{margin:10px 0 0;font-size:13px;color:var(--red);min-height:1.3em}

/* Temporary web-client grants reuse the auth page's single-column rhythm. */
.delegation main{max-width:38rem;justify-content:flex-start;padding-top:52px}
.delegate-details{margin:24px 0 0;display:grid;gap:5px;min-width:0}
.delegate-details dt{margin-top:9px;font-size:12px;color:var(--ink-2)}
.delegate-details dd{margin:0;overflow-wrap:anywhere}
.delegate-details dd.fp{margin-top:0}
.delegate-devices{min-width:0;margin:0;padding:0;border:0}
.delegate-devices legend{font-weight:600;margin-bottom:8px}
.delegate-device{display:flex;align-items:flex-start;gap:12px;padding:14px 0;border-bottom:1px solid var(--hairline)}
.delegate-device>span{min-width:0}
.delegate-device .fp,.delegate-id{display:block}
.delegate-id{margin-top:4px;font-size:11px;color:var(--ink-2);overflow-wrap:anywhere}
.delegate-device small{font-size:12px;color:var(--ink-2)}
.auth .delegate-device input,.auth .delegate-confirm input{width:17px;height:17px;flex:none;margin-top:3px;padding:0;accent-color:var(--blue)}
/* 有效期：四个常用值 + 一个自己填的分钟数。常用值是 pill，因为它们和顶栏那三个
   一样是次级控件；自己填的那个是输入框，但也收成 pill，好让这一行读成一组而不是
   「四个按钮外加一个表单控件」。选中的那颗用 --blue 的洗底，本页唯一的强调色。 */
.delegate-duration{display:grid;gap:9px;margin:16px 0}
.delegate-duration>label{font-size:13px;color:var(--ink-2)}
.duration-set{display:flex;flex-wrap:wrap;align-items:center;gap:8px}
.duration-set .chip[aria-pressed="true"]{border-color:var(--blue-hi);background:var(--blue-wash);color:var(--blue)}
.duration-custom{display:inline-flex;align-items:center;gap:7px;font-size:12.5px;color:var(--ink-2)}
.auth .duration-custom input{
  width:5.5rem;flex:none;font-size:12.5px;line-height:1.2;
  padding:4px 11px;border-radius:999px;text-align:right;
}
.delegate-confirm{display:flex;align-items:flex-start;gap:12px;font-size:13px;line-height:1.5}
.delegate-actions{display:flex;gap:10px;margin-top:8px}
.delegation form .fine{margin:2px 0}
.token-scope{display:block;font-size:11px;color:var(--ink-2);overflow-wrap:anywhere;max-width:26rem}

/* 「连接网页 AI」：这一屏的主体就是那一行要复制的话，中英各一条。
   两条都摆出来而不是跟着语言开关切换 —— 复制的人和读这句话的 AI 未必
   说同一种语言，而多出来的那一条只有一行高。 */
.auth h2{margin:30px 0 0;font-size:16px;font-weight:600;letter-spacing:-.2px}
.say{
  margin-top:14px;padding:14px 16px;border-radius:14px;background:var(--canvas-2);
  display:grid;grid-template-columns:1fr auto;align-items:end;gap:4px 12px;
}
.say-lang{grid-column:1/-1;margin:0;font-size:11.5px;color:var(--ink-2)}
.say-text{margin:0;font-size:14px;line-height:1.5;overflow-wrap:anywhere;min-width:0}
.say-copy{flex:none;align-self:end;font-size:13px;padding:7px 14px}
.say-steps{margin:12px 0 0;padding-left:1.15em;color:var(--ink-2);font-size:13.5px;line-height:1.6}
.say-steps li{margin:6px 0}
/* 复制反馈是一行状态文字。还没有话说的时候它不该先占一段空白。 */
.auth .fine#webfetchCopyHint:empty{margin:0}
.auth #webfetchPrompt{margin-top:12px;font-family:var(--mono);font-size:12px;line-height:1.55}

/* 申请访问的四种状态。页面把四段都发下来，选哪一段由 <body data-req>
   决定 —— 纯 CSS，脚本没跑起来的时候看到的也是对的那一段。
   状态是服务端算的：不能让人看见一张交上去会被拒的表单。 */
.auth .req{display:none}
.auth[data-req="none"] .req[data-req-state="none"],
.auth[data-req="pending"] .req[data-req-state="pending"],
.auth[data-req="approved"] .req[data-req-state="approved"],
.auth[data-req="declined"] .req[data-req-state="declined"]{display:block}
/* 这一栏是一句话，不是一串码：左对齐、正文字体，跟邀请码那格分得开。 */
.auth #note{font-family:inherit;text-align:left}
/* 已获准之后：没有东西可兑换了，那个框留着只是噪声；标题也换成另一句。
   于是这一屏只剩一个蓝按钮 —— 进门。 */
.auth[data-req="approved"] #redeem,
.auth[data-req="approved"] #err,
.auth[data-req="approved"] h1:not(.req){display:none}
/* 分隔两条路（兑换 / 申请）。发丝线，不是卡片：它分隔的是两件事，
   而这一页上再没有别的东西需要被框起来。 */
.auth .req[data-req-state="none"]{margin-top:26px;padding-top:22px;border-top:1px solid var(--hairline)}
.auth .req[data-req-state="none"] .fine{margin-top:0}

/* ── 手机 ──────────────────────────────────────────────────────────────
   一次扫遍全部状态之后加的这一块（tools/portalpreview/sweep.mjs）。
   在此之前整份样式表只有三条断点，而它们管的是设置的两级形态和一个按钮的
   对齐 —— 表、指纹、卡头、手指靶子四样在手机上从来没有被量过。

   宽屏一个像素都不改：下面所有条款都关在这个断点里面。 */
@media (max-width:640px){

  /* 外壳上的次级控件。48px 的顶栏里它们只有 21~28px 高，指头够不着
     —— 苹果和 Material 给的下限都是 44 / 48，这里取 40。 */
  .top .brand,.top .chip{min-height:40px}
  .top .chip{min-width:40px;display:inline-flex;align-items:center;justify-content:center}
  .chip.icon{min-width:40px;min-height:40px;padding:0}
  /* 内边距归零之后，图标到盒子右缘是 (40-16)/2 = 12px。
     负外边距要跟着改，否则齿轮的墨迹会比底下的卡片往里缩 12px。 */
  .top .tools > .chip.icon:last-child{margin-right:-12px}
  .back{padding:9px 8px 14px;margin-left:-8px}
  .tab{padding:11px 14px}
  .dhead .modesel{padding:9px 12px;min-height:40px}
  /* 菜单里的两条也是手指靶子：13px 的字加 11px 上下内边距正好 40。 */
  .modeopt{padding:11px 12px}
  /* 四个裁决键是这个产品里后果最重的点击，它们尤其不该只有 34px 高。 */
  .ask .acts .btn{padding:10px 15px}
  .check{min-height:40px}

  /* 待审批卡的头。设备名是 flex:none，一个 32 字的机器名（DESKTOP-… 那种）
     会把整行顶出卡片，而卡片是 overflow:hidden —— 名字于是被无声地切掉。
     挤不下就换行：名字占满第一行，种类和秒表退到第二行。 */
  .ask .head{flex-wrap:wrap}
  .ask .head .host{flex:1 1 100%;overflow-wrap:anywhere}

  /* ── 表在手机上不是表 ──────────────────────────────────────────────
     活动那张有五列，360px 上一列只剩 72px，而其中一列装的是整条命令；
     信任那张一列装的是指纹。任何列宽都救不了它们，量出来是整页被顶宽。

     所以一行变一块，格子竖排，列名跟着值走 —— 键值两列的形状跟待审批卡里
     那张 .kv 是同一套，不是新发明的排版。列名从表自己的 <th> 里取
     （app.js 的 relabel），没有第二份字典可漂。 */
  table.stack thead{display:none}
  table.stack,table.stack tbody,table.stack tr,table.stack td{display:block;width:auto}
  table.stack tr{padding:12px 0;border-bottom:1px solid var(--hairline)}
  table.stack tr:last-child{border-bottom:0}
  table.stack td{border:0;padding:0;text-align:left;overflow-wrap:anywhere}
  table.stack td + td{margin-top:5px}
  /* 行间距归 0：格子里第二行（比如命令底下那行工作目录）本来就带自己的
     margin，再吃一份 12px 的行距会把它推得像另一条记录。 */
  table.stack td[data-l]{display:grid;grid-template-columns:5.5rem minmax(0,1fr);gap:0 12px}
  table.stack td[data-l]::before{content:attr(data-l);color:var(--ink-2);font-size:12.5px}
  /* 值那一列。裸文本会被自动放进第 2 格，但格子里有两个元素时
     （控制端的名字 + 底下那行原名）第二个会掉回第 1 格，落在列名下面。 */
  table.stack td[data-l] > *{grid-column:2}
  /* 空表那一行是一句话，不是一条记录。 */
  table.stack td:empty{display:none}
  table.stack td[colspan]{padding:0}
  table.stack tr:has(td[colspan]){padding:0;border-bottom:0}
  /* 行尾的动作：自己一行、左对齐、够得着。 */
  table.stack td:last-child:not([data-l]){margin-top:8px}
  .act{padding:11px 10px;margin:-11px -10px}
  .act + .act{margin-left:10px}
  /* 下载表没有表头（一列是平台、一列是文件名），没有列名可挂，
     所以它不参与上面那套 —— 让格子自己折行就够。 */
  #dl td{overflow-wrap:anywhere}
  #dl td:last-child a{
    display:inline-flex;align-items:center;justify-content:center;
    min-width:40px;min-height:40px;margin:-12px 0;
  }

  /* 主 CTA 与次级按钮：8px 内边距配 14px 正文只有 34px 高。 */
  .btn{min-height:40px;display:inline-flex;align-items:center;justify-content:center}
  input,select,textarea{min-height:40px}
  /* 开关的轨道就是它的形状，不能为了指头把它撑胖。把盒子撑到 40 高、
     再用 background-clip 只在中间那 24px 上画轨道 —— 可点区域大了，
     画出来的东西一个像素都没动。滑块跟着往下挪同样的 8px。 */
  .sw{height:40px;padding:8px 0;background-clip:content-box}
  .sw::after{top:10px}
}

/* 命令与安装命令。横向滚的代码块等于把命令藏起来一半，而这块的全部意义
   就是让你在按下复制之前把它整条读完。断点比其余手机条款宽：768 的平板上
   那条 curl 仍然装不下（实测藏掉 327px），而其余条款在平板上并不适用。 */
@media (max-width:768px){
  pre{white-space:pre-wrap;overflow-wrap:anywhere;overflow-x:visible}
}

/* ── 动效降级 ──────────────────────────────────────────────────────── */
/* 内容可见性不绑在 transition 上：无 JS、无头渲染、reduced-motion
   三条路看到的都是终局。 */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.001ms!important;transition-duration:.001ms!important}
}

/* Device-owned wireless debugging setup. Keep both fields readable on phones. */
.adb-pair-form { display:flex; flex-wrap:wrap; align-items:end; gap:12px; margin-top:18px; }
.adb-pair-form label { display:flex; flex-direction:column; gap:8px; flex:1 1 140px; min-width:0; }
.adb-pair-form input { width:100%; min-width:0; }
.adb-pair-form .btn { min-height:44px; }
