/* 设计变量层。颜色不按"好看"分，按语义分：五种档案状态各占一对色，
   互不通用——observed 的绿永远不会跑到 inferred 上去，界面才骗不了人。 */
:root{
  --bg:#f6f6f3;--surface:#fff;--sunken:#efefea;--line:#e3e3dc;--line-strong:#c9c9bf;
  --ink:#15171a;--ink-2:#585f67;--ink-3:#8c929a;
  --accent:#1b3a63;--accent-soft:#e7edf5;--on-accent:#fff;
  --observed:#1e6b49;--observed-soft:#e2f0e8;
  --inferred:#8a6612;--inferred-soft:#f6eeda;
  --blocked:#963628;--blocked-soft:#f7e6e2;
  --human:#43539b;--human-soft:#e7eaf6;
  --unclicked:#8c929a;--unclicked-soft:#edede8;
  --s1:4px;--s2:8px;--s3:12px;--s4:16px;--s5:24px;--s6:32px;--s7:48px;
  --r-sm:4px;--r-md:8px;--r-pill:999px;
  --font:"Noto Sans SC","Microsoft YaHei","Segoe UI",sans-serif;
  --mono:"IBM Plex Mono","Cascadia Mono",Consolas,"Courier New",monospace;
  --shadow:0 1px 2px #15171a08;
}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--ink);font:15px/1.65 var(--font);display:grid;grid-template-columns:264px 1fr;min-height:100vh}
aside{background:var(--surface);border-right:1px solid var(--line);padding:var(--s5) var(--s4)}
.brand{font-size:22px;font-weight:650;letter-spacing:2px;margin-bottom:var(--s5)}
.brand span{display:block;font-size:12px;font-weight:400;letter-spacing:1px;color:var(--ink-3)}
aside h2{font-size:11px;font-weight:600;letter-spacing:1px;color:var(--ink-3);margin:var(--s5) 0 var(--s2)}
aside button{width:100%}
#history button{display:block;width:100%;text-align:left;font-size:13px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;margin:var(--s2) 0;border:0;background:0;padding:7px var(--s2)}
#history button:hover{background:var(--sunken)}
#history button.active{color:var(--accent);background:var(--accent-soft);font-weight:600}
/* 每行开头那个「研究」/「档案」。两份列表并成一份之后，这一小块是人分辨
   「点开的会是一屏分析，还是一条走过那个站的对话」的唯一线索。 */
.kind-tag{display:inline-block;min-width:26px;margin-right:var(--s2);font-size:11px;color:var(--ink-3)}
#history button.active .kind-tag{color:var(--accent)}
#history .muted{font-size:12px;margin:var(--s2) 0;line-height:1.7}
.history-controls{display:grid;gap:8px;margin:16px 0 8px}
.history-controls input,.history-controls select{width:100%;min-width:0;padding:8px 10px;font:inherit;font-size:12px;border:1px solid var(--line);border-radius:8px;background:var(--surface);color:var(--ink-2)}
.history-title{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.history-time{display:block;font-size:11px;font-weight:400;color:var(--ink-3);margin-top:3px}
.history-status{display:block;font-size:11px;font-weight:500;color:var(--ink-2);margin-top:4px}
.history-status[hidden]{display:none}
#history-more{font-size:12px}
#history-toggle{display:none}
.aside-note{color:var(--ink-3);font-size:12px;margin-top:var(--s7);line-height:1.7}
/* 上下 margin 必须是 0：grid item 的 auto margin 会把剩余空间吃进上下两头，
   而且优先级压过 align-self:start——短内容于是被竖直居中，页头掉到半空中，
   上下各空出小半屏。只留左右 auto 做水平居中，竖直方向交回给 align-self。 */
main{width:100%;max-width:1440px;margin:0 auto;padding:var(--s6) 40px 70px;align-self:start}
header{display:flex;justify-content:space-between;color:var(--ink-3);font-size:12px;padding-bottom:var(--s5)}
.badge{background:var(--sunken);color:var(--ink-2);border-radius:var(--r-pill);padding:2px 12px}
.intro h1{font-size:28px;line-height:1.35;font-weight:600;margin:var(--s2) 0 var(--s3)}
.intro p{color:var(--ink-2)}
.eyebrow{font-size:12px;color:var(--accent)!important;letter-spacing:2px}
.composer,#workspace{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-md);padding:var(--s5);margin-top:var(--s5);box-shadow:var(--shadow)}
label{display:block;font-weight:500;margin:var(--s3) 0 6px}
textarea,input:not([type=checkbox],[type=file]),select{font:inherit;width:100%;border:1px solid var(--line-strong);background:var(--surface);border-radius:var(--r-sm);padding:9px 11px;color:inherit}
textarea{resize:vertical}
textarea:focus,input:focus,select:focus{outline:2px solid var(--accent-soft);border-color:var(--accent)}
.input-row{display:flex;gap:var(--s4);align-items:center;flex-wrap:wrap;color:var(--ink-3);font-size:12px}
.file-label{border:1px dashed var(--line-strong);padding:7px 12px;border-radius:var(--r-sm);cursor:pointer;position:relative}
.file-label input{position:absolute;inset:0;width:100%;opacity:0;cursor:pointer}
.actions{display:flex;justify-content:flex-end;align-items:center;gap:var(--s3);flex-wrap:wrap;margin-top:var(--s4)}
.actions>span{margin-right:auto;font-size:12px;color:var(--ink-3)}
button{font:inherit;cursor:pointer;border:1px solid var(--line-strong);background:var(--surface);color:var(--ink-2);border-radius:var(--r-sm);padding:7px 13px}
button:hover{background:var(--sunken)}
button.primary{background:var(--accent);color:var(--on-accent);border-color:var(--accent);font-weight:600}
button.primary:hover{background:#16304f}
button:disabled{opacity:.5;cursor:wait}
.status{padding:var(--s4) var(--s1);color:var(--ink-3);font-size:13px}
.workspace-top{display:flex;justify-content:space-between;align-items:center}
h2{font-size:18px;margin:0}
h3{font-size:14px;margin:var(--s4) 0 var(--s1)}
#tabs,#doc-tabs{display:flex;gap:var(--s5);flex-wrap:wrap;padding:var(--s3) 0 0;border-bottom:1px solid var(--line)}
#tabs button,#doc-tabs button{border:0;border-bottom:2px solid transparent;background:0;border-radius:0;padding:0 2px 11px;color:var(--ink-3);font-size:13px}
#tabs button:hover,#doc-tabs button:hover{background:0;color:var(--ink)}
#tabs button[aria-selected=true],#doc-tabs button.active{color:var(--accent);border-bottom-color:var(--accent);font-weight:600}
.panel{padding:var(--s4) 0}
.panel p{overflow-wrap:anywhere}
.panel li{margin:var(--s2) 0;overflow-wrap:anywhere}
.muted,#processing{color:var(--ink-3);font-size:13px}
/* 出处标签跟在流程名后面，不换行、不抢视线：它回答的是「这条是谁说的」，不是内容本身。 */
.origin{margin-left:var(--s2);color:var(--ink-3);font-size:12px;white-space:nowrap}
.source{border-bottom:1px solid var(--line);padding:var(--s3) 0;overflow-wrap:anywhere}
.source small{display:block;color:var(--ink-3)}
pre{white-space:pre-wrap;overflow-wrap:anywhere;background:var(--sunken);border-radius:var(--r-sm);padding:var(--s4);font:13px/1.8 var(--mono)}
progress{width:100%;height:6px;accent-color:var(--accent)}
.check{font-size:14px}
.check input{margin-right:var(--s2)}
.proof{border-top:1px solid var(--line);margin-top:var(--s5);padding-top:var(--s4)}
.proof img{max-width:100%;border:1px solid var(--line);border-radius:var(--r-sm);margin-top:var(--s3)}
.proof a{display:inline-block;margin-right:var(--s4);color:var(--accent)}
#error,#login-error{padding:var(--s3);background:var(--blocked-soft);color:var(--blocked);margin-top:var(--s4);border-radius:var(--r-sm);border-left:3px solid var(--blocked)}
[hidden]{display:none!important}
/* 前门判完说一句。它长在 header 底下、两个工作台外面——判成哪边都看得见，
   否则「这条会话按『软件档案』在办」正好印在被藏起来的那一屏里。 */
#routed{display:flex;align-items:center;gap:var(--s3);flex-wrap:wrap;font-size:13px;color:var(--ink-2);
  background:var(--sunken);border-radius:var(--r-sm);padding:var(--s2) var(--s3);margin-bottom:var(--s4)}
#routed button{font-size:12px;padding:3px 10px}
/* 拿不准时那一问。摆在输入框和「发送」中间：问题是冲着刚打的那句话来的，
   答案也是那句话的一部分，中间不该隔着别的东西。 */
#front-ask{margin-top:var(--s3)}
#front-ask p{font-size:13px;color:var(--ink-2);margin-bottom:var(--s2)}
#front-ask button{font-size:13px;margin:0 var(--s2) var(--s2) 0}
.ex-head{display:flex;justify-content:space-between;align-items:flex-end;gap:var(--s4);flex-wrap:wrap}
/* 标题说的就是「现在打开的是哪份档案」（见 index.html 那段注释）。等宽字是因为它念的是
   一个站点名，不是一句话；不摆成 24 像素的大字——这一屏的主角是下面那条对话，
   一个每次进来都一样的标题不该先占掉一整行的视线。 */
#ex-session{font-size:15px;white-space:nowrap;color:var(--ink-2);font-family:var(--mono);letter-spacing:.3px}

/* 输入区钉在下面，不跟着对话滚。三样东西必须一直看得见：站点与登录态、
   有没有授权自动点按、以及那段口令与不可逆动作的承诺——它们是前置条件，
   不是聊过就翻篇的一条消息。
   那段承诺的内容跟着 docs/SECURITY_BOUNDARY.md 走，不许在这儿自己发挥：
   它写过一版「口令不经过服务端」，而口令确实经过服务端进程（一条不落库的接口
   → 进程内一次性柜子 → stdin 管道），于是界面替系统许了一个做不到的诺。
   现在的分界是三条：口令由人当场给且系统不存、只走管道不留痕、验证码答案只由人给。 */
.ex-say{position:sticky;bottom:0;background:var(--surface);border:1px solid var(--line);border-radius:var(--r-md);padding:var(--s3) var(--s4) var(--s4);margin-top:var(--s3);box-shadow:0 -2px 8px #15171a0a}
.ex-strip{display:flex;gap:var(--s3);align-items:center;flex-wrap:wrap;font-size:12px;padding-bottom:var(--s3)}
.ex-strip #ex-adv{margin-left:auto;font-size:12px;padding:4px 9px}
.ex-line{display:flex;gap:var(--s2);align-items:flex-end;flex-wrap:wrap}
.ex-line>#ex-text{flex:1;min-width:280px;font-size:14px}
.ex-line>#ex-send{padding:9px 22px}
.ex-quick{display:flex;gap:var(--s2);flex-wrap:wrap;padding-bottom:var(--s3)}
/* 还没有站的时候这一排是空的（见 exQuick：那五句话都要先有站）。padding 留着的话，
   输入区上头就空出一道没来由的白，看着像有东西没加载出来。空了就不占地方。 */
.ex-quick:empty{padding:0}
.ex-quick button{font-size:12px;padding:4px 12px;border-radius:var(--r-pill);color:var(--ink-2)}
button.ghost{border-color:transparent;background:0;color:var(--ink-3)}
button.ghost:hover{background:var(--sunken);color:var(--ink-2)}

/* 登录态和授权都是前置条件，不是流程里的一步，所以是状态徽章而不是章节。
   两枚长得一样是故意的：它们是同一类东西（开工之前必须成立的条件），
   之前一枚是徽章、另一枚是灰字，读起来像「一条要紧、一条是备注」。
   登录态这枚上永远不会出现「已登录」——profile 在，不代表站点那边的会话还活着。 */
.gate{display:inline-flex;align-items:center;gap:6px;border-radius:var(--r-pill);padding:4px 12px;font-size:12px;white-space:nowrap;border:1px solid transparent}
.gate::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor;flex:none}
.gate-unknown{background:var(--unclicked-soft);color:var(--ink-3);border-color:var(--line)}
.gate-none{background:var(--inferred-soft);color:var(--inferred)}
.gate-have{background:var(--observed-soft);color:var(--observed)}
#ex-advanced{margin-top:var(--s4);padding-top:var(--s3);border-top:1px solid var(--line)}
#ex-advanced label{font-size:12px;color:var(--ink-3);margin:var(--s2) 0 var(--s1)}
.ex-vow{margin:var(--s3) 0 0;padding-left:var(--s3);border-left:3px solid var(--line-strong);color:var(--ink-3);font-size:12px;line-height:1.8}
.ex-row{display:flex;gap:var(--s3);flex-wrap:wrap}
.ex-row>span{flex:1;min-width:155px}

/* 对话区：档案是一条一条被问出来的，所以覆盖率、经验卡、检索结果都长在回答里，
   跟那句问话待在一起。它们仍然是整块的表和卡片，只是不再各占一个标签页——
   人不必先学会系统的分类法，才问得出「覆盖率怎么样」。 */
/* 档案侧占满一屏：对话在上面长，输入区落在底下。不占满的话，空档案时所有东西挤在页顶，
   下面空出大半屏灰地，而 .ex-say 的 sticky bottom 也无从生效——一屏之内没有可滚的地方，
   钉不钉都一样。撑开的是 .ex-chat：多出来的高度给对话，不给输入区。 */
body[data-view=explore] main{display:flex;flex-direction:column;min-height:100vh}
/* 这两块归「产品研究」那一屏，档案侧一概不露：进度长在对话里（explore.js 的 exWorking），
   出错的话长在出错的那个格子旁边（exWarn）。
   进度行在这一屏里永远是空的（exView 把它清掉），却照样占着 32 像素——800 高的屏幕上
   一共才剩 367 像素给对话，这 32 像素是白丢的。红框更糟：研究侧晚回来的一次失败
   会把它点亮，于是档案工作台的页顶挂着一句说的是另一屏的红字。
   用 display:none 而不是 hidden 属性：那两处是 app.js 直接改 el.hidden 的，改回去就露了。 */
body[data-view=explore] #progress,body[data-view=explore] #error{display:none}
#explore-view{flex:1;display:flex;flex-direction:column}
.ex-chat{flex:1;margin-top:var(--s4);display:flex;flex-direction:column;gap:var(--s3)}
.ex-turn{border-radius:var(--r-md);overflow-wrap:anywhere}
.ex-turn-me{align-self:flex-end;max-width:min(560px,86%);background:var(--accent);color:var(--on-accent);padding:9px var(--s4)}
.ex-turn-me p{margin:0}
.ex-turn-sys{align-self:stretch;background:var(--surface);border:1px solid var(--line);box-shadow:var(--shadow);padding:var(--s3) var(--s4)}
.ex-turn-sys>p:first-child{margin-top:0}
.ex-turn-sys>p:last-child{margin-bottom:0}
.ex-turn h3{display:flex;align-items:center;gap:var(--s2);flex-wrap:wrap;margin:var(--s5) 0 var(--s2);font-size:13px;font-weight:600;letter-spacing:.5px;color:var(--ink-2)}
.ex-turn h3:first-child{margin-top:var(--s1)}
.ex-turn li{margin:6px 0;overflow-wrap:anywhere}
.ex-turn img{max-width:100%;border:1px solid var(--line);border-radius:var(--r-sm);margin-top:var(--s3)}
.ex-turn>a{display:inline-block;margin:var(--s3) var(--s4) 0 0;color:var(--accent);font-size:13px}
/* 证据就地展开，不甩到新标签页去看一份原始接口响应（见 explore.js 的 exEvidence）。
   入口样子照旧沿用原先那两个链接：人认得的还是同一个东西，只是这回在对话里打开。 */
.ex-evidence{margin-top:var(--s3)}
.ex-evidence>summary{cursor:pointer;color:var(--accent);font-size:13px}
.ex-evidence>pre{margin:var(--s2) 0 0;max-height:340px;overflow:auto}
.ex-turn li button,.ex-acts button{font-size:12px;padding:3px 9px}
.ex-turn li button{margin-left:6px}
/* 要人回答的那一句排在正文之后，得看得出它在等你，而不是又一条说明。 */
.ex-ask{color:var(--ink);font-weight:500;margin:var(--s3) 0 0}
.ex-choices{list-style:none;padding:0;margin:var(--s2) 0 0}
.ex-choices li{display:flex;align-items:center;gap:6px}
.ex-choices button{margin-left:0!important}
.ex-choices small{color:var(--ink-3);font-family:var(--mono)}
/* 每块档案顶上一句话，说明这张表回答什么、不回答什么。
   "未点击不等于不存在，只等于没验证"——这种话不写出来，用户就会自己补一个错的。 */
.ex-lede{background:var(--sunken);border-radius:var(--r-sm);padding:var(--s3) var(--s4);margin:0 0 var(--s4);color:var(--ink-2);font-size:13px;line-height:1.75}
.ex-empty{color:var(--ink-3);font-size:13px;text-align:center;padding:var(--s7) var(--s4);border:1px dashed var(--line-strong);border-radius:var(--r-md);margin:var(--s3) 0;line-height:1.9}
.ex-empty small{color:var(--ink-3);opacity:.8}

/* 来源徽章：observed / inferred / blocked / human / unclicked 各一色，
   界面上永远不把它们收拢成一个绿勾——五种状态的可信度根本不是一回事。 */
/* 底色先给中性：后端哪天多出一个没见过的状态，它会显示成一枚灰徽章，
   而不是悄悄套上旁边那条规则的绿色。认不出来就说认不出来。 */
.src{display:inline-flex;align-items:center;gap:6px;border-radius:var(--r-pill);padding:3px 10px;font-size:12px;font-weight:500;white-space:nowrap;vertical-align:middle;line-height:1.5;background:var(--unclicked-soft);color:var(--ink-3)}
.src::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor;flex:none}
.src-observed,.src-clicked{background:var(--observed-soft);color:var(--observed)}
.src-inferred{background:var(--inferred-soft);color:var(--inferred)}
.src-blocked,.src-failed{background:var(--blocked-soft);color:var(--blocked)}
.src-human{background:var(--human-soft);color:var(--human)}
.src-unclicked,.src-unknown{background:var(--unclicked-soft);color:var(--ink-3)}

/* 覆盖率按状态摊成五个格子：只报一个百分比，看不出「没点的」和「停在临界点的」各有多少。 */
.ex-bar{height:6px;background:var(--sunken);border-radius:3px;overflow:hidden;margin:var(--s2) 0}
/* 这条槽还兼着对话里那条活进度（exWorking）。过渡是为它加的：一轮跑几分钟，每秒改一次宽度，
   没有过渡就是一格一格地跳，跳完又静止，看着像卡住。这一屏别处不做动效，这里做是因为
   它要回答的正是「还活着吗」。排队那段百分比恒为 0，槽不动——那句话交给旁边的
   「排队等一台机器」去说，比拿个假动画糊过去诚实。 */
.ex-bar span{display:block;height:100%;background:var(--accent);transition:width .3s ease}
@media(prefers-reduced-motion:reduce){.ex-bar span{transition:none}}
.ex-tally{list-style:none;display:flex;flex-wrap:wrap;gap:var(--s3);padding:0;margin:var(--s3) 0}
.ex-tally li{flex:1 1 118px;margin:0;border:1px solid var(--line);border-radius:var(--r-sm);padding:var(--s3);background:var(--surface)}
.ex-tally i{display:block;width:28px;height:4px;border-radius:2px;margin-bottom:var(--s2);background:var(--unclicked)}
.ex-tally b{display:block;font:600 26px/1.1 var(--mono);color:var(--ink)}
.ex-tally span{display:block;font-size:12px;font-weight:500;color:var(--ink-2);margin-top:6px}
.ex-tally li[data-state=clicked] i{background:var(--observed)}
.ex-tally li[data-state=inferred] i{background:var(--inferred)}
.ex-tally li[data-state=blocked] i{background:var(--blocked)}
.ex-tally li[data-state=human] i{background:var(--human)}
/* failed 画成条纹而不是第六种实色：它压根不是一种结论，是「我们这一次没点成」。
   实色会让它跟旁边那四种结论平起平坐；不给规则又会落回灰色，读成「从没碰过」——
   而那正是这一格存在的理由（见 coverage_ledger 里 failed / blocked 分家那段）。 */
.ex-tally li[data-state=failed] i{background:repeating-linear-gradient(90deg,var(--blocked) 0 3px,var(--blocked-soft) 3px 6px)}

/* 临界点单列一块：它们是「走到了但故意没按」，跟失败是两回事，混在一起看就分不清了。 */
.ex-stop{background:var(--blocked-soft);border-left:3px solid var(--blocked);border-radius:0 var(--r-sm) var(--r-sm) 0;padding:var(--s4);margin:var(--s3) 0}
.ex-stop h3{margin:0 0 var(--s1);color:var(--blocked);font-size:14px}
.ex-stop li{color:var(--ink-2);font-size:13px}
.ex-stop .muted{color:var(--ink-2);opacity:.85}
/* 导出那一屏的主动作。摆在最前、长得像按钮：整包下载是这一屏唯一「拿得走」的东西，
   底下那行服务器路径只对登得上机器的人有意义。 */
.ex-download{display:inline-block;margin:0 0 10px;padding:8px 14px;border-radius:6px;
  background:var(--accent,#2f6feb);color:#fff;text-decoration:none;font-size:14px}
.ex-download:hover{filter:brightness(1.08)}

/* 一条链路、一张经验卡各自是一个对象，纠正和确认就长在这个对象上——
   不再是另开一张空表让人把形状重打一遍。 */
.ex-card{border:1px solid var(--line);border-radius:var(--r-md);padding:var(--s3) var(--s4);margin:var(--s3) 0;overflow-wrap:anywhere;background:var(--surface)}
.ex-card h4{margin:0 0 6px;font-size:14px;display:flex;align-items:center;gap:var(--s2);flex-wrap:wrap}
.ex-card p{margin:var(--s1) 0;font-family:var(--mono);font-size:13px;color:var(--ink-2)}
.ex-card small{display:block;color:var(--ink-3);font-size:12px;margin-top:var(--s1)}
.ex-acts{display:flex;flex-wrap:wrap;gap:var(--s2);margin-top:var(--s2)}
/* 经验的确认 / 撤下（explore.js 的 exLessonState）。状态色块沿用 .src；撤下的整张压暗，读得出它不再算数。 */
.ex-gone{opacity:.6}
.ex-done{font-size:12px;color:var(--observed);align-self:center}.ex-gone .ex-done{color:var(--blocked)}
.ex-retract{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s2);margin-top:var(--s2);padding:var(--s2) var(--s3);
  border-radius:var(--r-md);background:var(--blocked-soft);color:var(--blocked);font-size:12px}
.ex-retract span{flex:1 1 200px}.ex-retract button{font-size:12px;padding:3px 9px}
button.danger{background:var(--blocked);color:var(--on-accent);border-color:var(--blocked);font-weight:600}
/* 出错的那句话，长在出错的那个格子底下（explore.js 的 exWarn）。用跟 #error 一样的配色，
   是因为它就是 #error 那句话——只是挪到了人真正在看的地方。不撑成整块红底：
   对话里一条消息本来就有自己的底色，再糊一层红，读起来像整轮都废了，
   而实际上多半只是「口令没填」。左边一道竖线够了。 */
.ex-warn{margin:var(--s2) 0 0;padding-left:var(--s2);border-left:3px solid var(--blocked);
  color:var(--blocked);font-size:13px}
/* 对话里的口令输入格（explore.js 的 exCredential）。格子长在对话里，填的东西却不进对话：
   值直接发去本机那一轮登录，不落聊天流、不落库、不写盘。 */
.ex-cred{display:flex;gap:var(--s2);margin-top:var(--s3);flex-wrap:wrap}
.ex-cred input{flex:1 1 140px;width:auto}
.ex-cred button{flex:0 0 auto}
/* 二维码和图形验证码。二维码不能撑满对话宽度——人是举着手机对屏幕扫的，
   一张跟对话一样宽的码反而对不上焦；验证码本来就小，max-width 不会把它放大。 */
.ex-challenge{margin-top:var(--s3)}
.ex-challenge img{max-width:min(100%,240px)}
.ex-challenge .ex-acts input{flex:1 1 120px;width:auto;max-width:180px}
/* Agent 请人协助的那张卡（explore.js 的 exHumanChallenge，只有它带 data-challenge-id）。
   原因放第一行、字比旁注重；账号明文、口令和验证码遮挡，标签旁边写明。 */
.ex-challenge[data-challenge-id]{border:1px solid var(--line);border-radius:12px;padding:18px 20px;background:var(--surface)}
.ex-human-reason{font-size:15px;font-weight:500;color:var(--ink);margin:var(--s2) 0;line-height:1.7}
.ex-challenge label small{font-weight:400;font-size:12px;color:var(--ink-3);margin-left:var(--s2)}
/* 协议框里那段原文（explore.js 的 exConsent）。它是**远端站点的原话**，不是我们的说明，
   所以排得跟周围的句子不一样：左边一道线、底色压一档，一眼看得出这是引来的字。
   人要照着它决定同不同意，所以不压成 muted 那样的灰——那是给旁注用的。 */
.ex-pact{margin:var(--s2) 0 0;padding:var(--s2) var(--s3);border-left:3px solid var(--line);
  background:var(--sunken);font-size:13px;white-space:pre-wrap}
/* 行为验证那一小块（explore.js 的 exDrag）。这块不套上面那条 240px：人要在它上面
   按住往右拖到头，缩小一圈等于把拖动距离也缩了，手感和原页面对不上。
   touch-action:none 是手机上的关键一句——不写，浏览器会把「按住往右划」当成翻页手势
   截走，pointermove 一个都收不到，人以为自己拖了，这边什么都没录着。 */
/* 边框走 outline 不走 border：这块的坐标要一路换算回页面里的真实位置，
   border 会把外框撑大一圈，量出来的每个点都偏一格；outline 不占地方。 */
.ex-drag{position:relative;display:inline-block;max-width:100%;touch-action:none;
  cursor:grab;outline:1px solid var(--line-strong);background:var(--sunken);line-height:0}
.ex-drag:active{cursor:grabbing}
.ex-drag img{display:block;max-width:100%;height:auto;user-select:none}
.ex-drag canvas{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none}
.ex-drag .ex-drag-blind{display:flex;align-items:center;justify-content:center;padding:var(--s3);
  font-size:13px;line-height:1.5;color:var(--ink-3);text-align:center}

@media(max-width:850px){
  body{grid-template-columns:1fr}
  aside{border-right:0;border-bottom:1px solid var(--line);padding:var(--s3) var(--s4)}
  .brand{font-size:20px;margin-bottom:var(--s3)}
  .brand span{display:inline;margin-left:var(--s2)}
  #side-study,.aside-note{display:none}
  main{padding:var(--s4)}
  /* 窄屏上 aside 变成了顶部条，main 再占一整屏就把页面撑出一截空滚动（一屏的内容，
     却要多滚 aside 那么高）。这儿不需要它撑：输入区照样 sticky 钉在底边。 */
  body[data-view=explore] main{min-height:0}
  .intro h1{font-size:24px}
  .composer,#workspace{padding:var(--s4)}
  .ex-say{padding:var(--s3)}
  .ex-turn-sys{padding:var(--s3)}
  .ex-turn-me{max-width:92%}
  .ex-row>span,.ex-line>#ex-text,.ex-line>#ex-send{min-width:100%}
}

/* 登录页。body 那条两列栅格是给「侧栏 + 主区」排的，这一页没有侧栏——照用会把卡片
   挤进右边那个 1fr，左边白白空出 264px。这里换成单列居中。
   .login-card 本身是个 <main>，所以 main 那条 `align-self:start` 会落到它身上，
   place-items:center 压不过一条显式声明（那条是写在容器上的默认值，不是写在项上的），
   必须在这儿把 align-self 显式扳回 center，否则卡片贴着屏幕顶边。 */
body.login{grid-template-columns:1fr;place-items:center;padding:var(--s5)}
.login-card{width:100%;max-width:380px;margin:0;align-self:center;padding:var(--s6) var(--s5);
  background:var(--surface);border:1px solid var(--line);border-radius:var(--r-md);box-shadow:var(--shadow)}
.login-card .brand{margin-bottom:var(--s6)}
.login-card h1{font-size:22px;font-weight:600;margin:0 0 var(--s2)}
.login-card .actions{margin-top:var(--s5)}
.login-card #submit{width:100%;padding:10px}
.login-card code{font-family:var(--mono);font-size:12px;background:var(--sunken);padding:1px 5px;border-radius:var(--r-sm)}
.login-note{color:var(--ink-3);font-size:12px;margin:var(--s5) 0 0;line-height:1.7}
/* 页顶那一小块「谁登录着 / 退出」。放在面包屑那一行的右端，跟「本机工作区」并排。 */
#whoami{font-family:var(--mono);letter-spacing:.3px}
header #logout{font-size:12px;padding:2px 10px;margin-left:var(--s2)}
header .who{display:flex;align-items:center;gap:var(--s2)}

/* 对话工作区：会话侧栏、独立滚动的消息区、始终可用的输入框。 */
body:not(.login){--bg:#fff;--surface:#fff;--sunken:#f4f4f4;--line:#e9e9e9;
  --line-strong:#d9d9d9;--ink:#252525;--ink-2:#626262;--ink-3:#8b8b8b;
  --accent:#292929;--accent-soft:#ededed;grid-template-columns:232px minmax(0,1fr);
  height:100dvh;min-height:0;overflow:hidden;font-size:14px;}
body:not(.login)>aside{height:100dvh;background:#f7f7f7;padding:28px 16px 20px;
  display:flex;flex-direction:column;overflow:hidden;border-color:#ededed;}
body:not(.login) .brand{display:flex;align-items:center;gap:10px;font-size:17px;
  letter-spacing:0;line-height:1.5;margin-bottom:30px;padding:0 7px;}
body:not(.login) .brand span{letter-spacing:0;font-size:11px;}
body:not(.login) .brand .brand-mark,.ex-welcome-mark{display:grid;place-items:center;
  background:#282828;color:#fff;border-radius:12px;flex:none;font-size:17px;width:36px;height:36px;}
body:not(.login) #side-study{display:flex;flex:1;min-height:0;flex-direction:column;}
body:not(.login) #new{background:#fff;color:var(--ink);border:1px solid #dedede;
  border-radius:9px;text-align:left;font-size:13px;font-weight:500;padding:10px 12px;}
#research-mode{text-align:left;margin-top:7px;padding:8px 12px;font-size:13px;}
body:not(.login) aside h2{font-weight:400;letter-spacing:0;padding-left:11px;margin-top:27px;}
body:not(.login) #history{overflow-y:auto;min-height:0;}
body:not(.login) #history button{border-radius:7px;padding:9px 11px;margin:2px 0;}
body:not(.login) .aside-note{margin:22px 0 0;padding:15px 10px 0;border-top:1px solid var(--line);font-size:11px;}
.workspace-dot{display:inline-block;width:6px;height:6px;margin-right:7px;background:#5c896c;border-radius:50%;}
body:not(.login)>main{height:100dvh;min-height:0;max-width:none;padding:0 30px 20px;overflow-y:auto;}
body[data-view=explore] main{min-height:0;overflow:hidden;}
body:not(.login) header{height:68px;min-height:68px;align-items:center;padding:0;flex:none;}
body:not(.login) #crumb{font-size:13px;color:var(--ink-2);}
body:not(.login) .who .badge{background:none;padding:2px 7px;font-size:11px;}
body[data-view=explore] #routed{display:none;}
#explore-view{min-height:0;overflow:hidden;}
.ex-head{width:100%;max-width:880px;margin:0 auto;padding:0 12px 9px;flex:none;}
#ex-session{font:500 13px/1.5 var(--font);letter-spacing:0;color:var(--ink-3);}
.ex-chat{overflow-y:auto;overscroll-behavior:contain;min-height:0;margin:0;
  padding:24px max(12px,calc((100% - 856px)/2)) 32px;gap:24px;scrollbar-width:thin;}
.ex-turn{flex-shrink:0;}
.ex-turn-sys{border:0;box-shadow:none;background:none;padding:4px 0;line-height:1.85;}
.ex-turn-me{background:#f1f1f1;color:var(--ink);border-radius:16px;padding:10px 17px;max-width:78%;}
.ex-say{position:static;flex:none;width:100%;max-width:880px;margin:10px auto 0;
  border:1px solid #dedede;border-radius:18px;padding:12px 16px 10px;box-shadow:0 3px 16px #00000005;}
.ex-line{flex-wrap:nowrap;gap:12px;}
.ex-line>#ex-text{min-width:0;border:0;outline:none!important;background:none;box-shadow:none;
  resize:none;padding:8px 2px;font-size:14px;line-height:1.7;min-height:64px;max-height:150px;}
.ex-line>#ex-send{width:34px;height:34px;min-width:34px;padding:0;border-radius:50%;
  font:22px/1 var(--font);margin-bottom:8px;}
body:not(.login) button.primary:hover{background:#454545;}
.ex-strip{gap:8px;padding-bottom:4px;}
.ex-strip .gate{padding:3px 7px;border:0;font-size:11px;background:transparent;}
.ex-strip .gate::before{width:5px;height:5px;}
.ex-strip #ex-adv{font-size:11px;padding:3px 7px;}
.ex-quick{padding:4px 0;gap:6px;}
.ex-quick button{border:0;background:#f5f5f5;border-radius:6px;font-size:11px;}
.ex-vow{display:flex;justify-content:space-between;gap:10px;border:0;padding:8px 0 0;
  margin:0;font-size:10px;color:var(--ink-3);}
.ex-welcome{margin:auto 0;text-align:center;padding:38px 0 60px;flex-shrink:0;}
.ex-welcome-mark{margin:0 auto 23px;width:46px;height:46px;border-radius:15px;font-size:21px;}
.ex-welcome h1{font-size:28px;font-weight:550;letter-spacing:-.7px;margin:0 0 12px;line-height:1.4;}
.ex-welcome>p{color:var(--ink-3);font-size:13px;margin:0;line-height:1.8;}
.ex-suggestions{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin-top:34px;}
.ex-suggestions button{text-align:left;border:1px solid var(--line);border-radius:12px;padding:18px 15px;background:#fff;}
.ex-suggestions button:hover{background:#fafafa;border-color:#cfcfcf;}
.ex-suggestions strong{display:block;font-size:13px;font-weight:500;color:var(--ink);margin-bottom:6px;}
.ex-suggestions span{font-size:11px;color:var(--ink-3);line-height:1.7;display:block;}
.ex-work{flex:none;position:relative;width:100%;max-width:880px;margin:8px auto 0;
  padding:12px 16px 12px 34px;border:1px solid var(--line);border-radius:12px;background:var(--sunken);}
.ex-work::before{content:"";position:absolute;top:20px;left:16px;width:6px;height:6px;border-radius:50%;background:var(--observed);}
.ex-work-waiting::before{background:var(--inferred);}
.ex-work strong,.ex-work small{display:block;overflow-wrap:anywhere;}
.ex-work strong{font-size:12px;font-weight:500;}.ex-work small{font-size:12px;color:var(--ink-2);margin-top:3px;}
.ex-probe{border:1px solid var(--line);background:var(--surface);border-radius:12px;padding:18px 20px;}
.ex-probe .ex-lede{background:none;padding:0;white-space:pre-wrap;color:var(--ink);}
.ex-probe>.muted{font-size:12px;}.ex-probe .ex-acts button{min-height:34px;padding:5px 12px;border-radius:8px;}
@media(max-width:850px){
  body:not(.login){grid-template-columns:180px minmax(0,1fr);}
  body:not(.login)>aside{padding:22px 10px 14px;border-right:1px solid var(--line);border-bottom:0;}
  body:not(.login) .brand span{display:block;margin-left:0;}
  body:not(.login)>main{padding:0 18px 14px;}
  .ex-suggestions{gap:8px;}.ex-suggestions button{padding:13px 10px;}
  .ex-line>#ex-text{min-width:0;}.ex-line>#ex-send{min-width:34px;}
  .ex-vow span{display:none;}
}
@media(max-width:600px){
  body:not(.login){grid-template-columns:1fr;grid-template-rows:54px minmax(0,1fr);}
  body:not(.login)>aside{height:54px;flex-direction:row;align-items:center;padding:7px 14px;overflow:visible;border-bottom:1px solid var(--line);}
  body:not(.login) .brand{margin:0;padding:0;font-size:14px;gap:8px;}
  body:not(.login) .brand .brand-mark{width:29px;height:29px;font-size:14px;border-radius:9px;}
  body:not(.login) .brand div>span,body:not(.login) .aside-note{display:none;}
  body:not(.login) #side-study{display:block;flex:1;min-height:0;text-align:right;}
  body:not(.login) #side-study h2,body:not(.login) #history,#research-mode{display:none;}
  body:not(.login) .history-controls,body:not(.login) #history-more{display:none;}
  #history-toggle{display:block;width:auto;margin-left:auto;font-size:12px;}
  body.history-open #side-study{position:fixed;inset:54px 0 0;width:min(320px,100%);z-index:20;
    background:#f7f7f7;border-right:1px solid var(--line);padding:16px;display:flex;flex-direction:column;text-align:left;box-shadow:10px 0 30px #0001;}
  body.history-open #side-study h2,body.history-open #history{display:block;}
  body.history-open .history-controls{display:grid;}
  body.history-open #history-more:not([hidden]){display:block;}
  body:not(.login) #new{width:auto;padding:5px 10px;font-size:12px;}
  body:not(.login)>main{height:calc(100dvh - 54px);padding:0 12px 12px;}
  body:not(.login) header{height:45px;min-height:45px;}
  .ex-chat{padding:18px 6px 24px;}.ex-welcome{padding:20px 0 32px;}
  .ex-welcome h1{font-size:23px;}.ex-welcome>p{font-size:12px;}
  .ex-suggestions{grid-template-columns:1fr;margin-top:24px;gap:8px;}
  .ex-suggestions button{padding:11px 14px;}.ex-suggestions strong{margin-bottom:2px;}
  .ex-say{padding:9px 12px;border-radius:14px;}.ex-vow{font-size:9px;}
}
