html, body { height: 100%; }
body { margin: 0; background: var(--color-bg); color: var(--color-text); font-family: var(--font-body); }
#app { min-height: 100%; }

.satu-authside { position: relative; padding: var(--space-8); display: flex; flex-direction: column; align-items: center; justify-content: space-between; background: var(--color-surface); overflow: hidden; }
.gw-login { min-height: 100vh; display: grid; grid-template-columns: 1fr 1fr; background: var(--color-bg); font-family: var(--font-body); color: var(--color-text); }
.gw-flash { margin: 0; padding: 10px var(--space-3); border-radius: var(--radius-sm); font-size: 13px; line-height: 1.6; }
.gw-flash-err { color: var(--color-accent-800); background: var(--color-accent-100); }
.gw-flash-ok { color: var(--color-accent-2-800); background: var(--color-accent-2-100); }
/* 带 × 的那种（flashRow 画的）才排成一行。选择器问的是「有没有那颗按钮」，因为
   同一个 .gw-flash 还被几处当纯说明块用（下面那条 note、机器失联那条），它们没有
   按钮，也就不该被改成 flex——那会把里面并排的字和小按钮重新排一遍。 */
.gw-flash:has(.gw-flash-x) { display: flex; align-items: flex-start; gap: var(--space-2); }
.gw-flash-text { flex: 1; min-width: 0; }
/* × 只借提示条自己的颜色：这条本来就是一块有色的面，再给按钮描边填底就是两层。
   平时压半透明，指上去才实——它是「读完了可以撤掉」，不是这条提示的主角。 */
.gw-flash-x {
  flex: none;
  margin: 1px -4px 0 0;
  padding: 2px;
  display: inline-flex;
  align-items: center;
  background: none;
  border: 0;
  border-radius: var(--radius-sm);
  color: inherit;
  opacity: 0.6;
  cursor: pointer;
}
.gw-flash-x:hover { opacity: 1; }
/* 既不是错也不是成功，是一句说明。搜索模式那条用它——用红的会被读成「出事了」。 */
.gw-flash-note { color: var(--foreground); background: var(--color-neutral-200); }

.satu-provrow { cursor: pointer; }
.satu-provrow[aria-selected="true"] { background: var(--color-accent-100); }
.satu-provrow[aria-selected="true"]:hover { background: var(--color-accent-100); }

.gw-roles { position: sticky; top: 0; z-index: 3; display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4); padding: var(--space-2) 0 var(--space-4); background: var(--color-neutral-100); }
.satu-modelhead, .satu-modelrow { grid-template-columns: 2fr 1fr 1.1fr 0.9fr 1.1fr 1.1fr 168px; }
.gw-caps { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; }
.satu-modelrow .satu-rowactions { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; }

.gw-credlist .satu-provhead,
.gw-credlist .satu-provrow { grid-template-columns: minmax(160px, 2fr) 72px 96px minmax(140px, 1.4fr) minmax(300px, auto); }
.gw-credlist .satu-provrow { cursor: default; }
.gw-secret { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
.gw-secret .input { border-radius: var(--radius-md); padding: 8px 12px; }
.gw-provactions { display: flex; align-items: center; justify-content: flex-end; gap: var(--space-2); flex-wrap: wrap; }
/* 测试结论独占一行。跟按钮抢横向空间的话，一出结果整排按钮就会换行错位。 */
.gw-provactions .gw-testline { flex-basis: 100%; text-align: right; }

.gw-page { flex: 1; min-height: 0; overflow-y: auto; padding: var(--space-6); }
.gw-page-inner { max-width: 980px; margin: 0 auto; display: flex; flex-direction: column; gap: var(--space-6); }
.gw-page-inner.narrow { max-width: 860px; }
.gw-page-inner.gw-profile { max-width: 760px; }

.satu-userrow { width: 100%; }
.gw-logout { flex: none; padding: 0; border: 0; background: transparent; font-family: var(--font-body); font-size: 12px; font-weight: 600; color: var(--color-accent-700); cursor: pointer; }
.gw-logout:hover { color: var(--color-accent-600); }

@media (max-width: 900px) {
  .gw-login { grid-template-columns: 1fr; }
  .satu-authside { display: none; }
  .gw-roles { grid-template-columns: 1fr; }
}

.gw-modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 25;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-6);
  background: color-mix(in srgb, var(--color-neutral-900) 42%, transparent);
}
.gw-modal {
  width: 100%;
  max-width: 440px;
  box-sizing: border-box;
  background: var(--popover);
  color: var(--color-text);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.gw-modal .btn-primary {
  background: var(--color-accent-500);
  border-color: var(--color-accent-500);
}

.satu-menuitem:disabled { opacity: 0.45; cursor: not-allowed; }
@media (max-width: 800px) {
  .gw-page-inner > div[style*="repeat(4, 1fr)"] { grid-template-columns: 1fr 1fr !important; }
}

/* main：两行（header / 正文）两列（内容 / 右栏）。右栏跨两行，所以能顶到 header
   那一行去——做成 header 下面的兄弟节点就顶不上去。列宽由 appView 内联给出。 */
.gw-main { min-width: 0; height: 100%; min-height: 0; box-sizing: border-box; overflow: hidden;
  background: var(--color-neutral-100); border: 1px solid var(--color-divider); border-radius: var(--radius-md);
  display: grid; grid-template-rows: auto minmax(0, 1fr); }
.gw-head { grid-row: 1; grid-column: 1; display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-3) var(--space-6); border-bottom: 1px solid var(--color-divider); }
.gw-body { grid-row: 2; grid-column: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
.gw-aside { grid-row: 1 / span 2; grid-column: 2; position: relative; min-width: 0; overflow: hidden;
  border-left: 1px solid var(--color-divider); display: flex; flex-direction: column; }
/* 右栏没有自己的 header：开关在对话 header 上，所以这里不会有一条和左边对不齐的
   横线。整栏从顶上直接开始，顶部内边距和 .gw-head 一致，标题看起来就在同一行。 */
.gw-aside-body { flex: 1; min-height: 0; overflow-y: auto; padding: var(--space-3) var(--space-4) var(--space-4);
  display: flex; flex-direction: column; gap: var(--space-3); }
/* 「运行环境」那一叠：右栏的常态。点开一条日常任务时它整个 hidden，但**不从 DOM 里
   拿掉**——里面那块屏是一条 VNC 连接，卸一次就断一次（见 render.js 的 pageAside）。 */
.gw-aside-stack { display: flex; flex-direction: column; gap: var(--space-3); }
/* `display: flex` 会盖掉 [hidden] 自带的 display:none（作者样式赢 UA 样式）。少了
   这一行，点开一条日常任务时那一叠还在原地，两屏叠着画。 */
.gw-aside-stack[hidden] { display: none; }
.gw-aside-stack > h3 { margin: 0; font-size: 12px; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted-foreground); line-height: 30px; }
/* 抓手压在左边框上：可视只有 1px 的边框，但有 7px 的命中区，不然很难抓准。 */
.gw-aside-grip { position: absolute; left: -3px; top: 0; bottom: 0; width: 7px; cursor: col-resize; z-index: 2; }
.gw-aside-grip:hover { background: color-mix(in srgb, var(--color-accent-500) 35%, transparent); }
.gw-aside .satu-kv { display: block; padding: 0; border: 0; }
.gw-aside .satu-kv > span:first-child { display: block; font-size: 11px; color: var(--muted-foreground); }
/* 这些值是机器生成的长串（uid、路径、版本号），必须能断行，否则会把整栏撑开。 */
.gw-aside .satu-kv > span:last-child { display: block; font-size: 12px; word-break: break-all; line-height: 1.5; }

.gw-chat { flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
/* 窄屏收掉右边那栏：环境信息是辅助的，对话本身不能被挤扁。 */
/* 这些值是机器生成的长串（uid、路径、版本号），必须能断行，否则会把整栏撑开。 */
/* flex: 1 不能少。.gw-chat 从 grid 改成 flex 列之后，没有它主区就只有内容高度，
   输入框会被顶到对话正文底下而不是贴着页面底部。 */
.gw-chat-main { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
/* 消息流、气泡、输入区在 chat.css。这里只留对话页的外框。 */
.gw-chat-empty, .gw-chat-empty-main { margin: 0; padding: var(--space-4); font-size: 14px; color: var(--muted-foreground); line-height: 1.6; }
.gw-chat-empty-main { flex: 1; display: flex; align-items: center; justify-content: center; }
/* ── 侧栏顶层的 Bot 名单 ──────────────────────────────────────────────
   做成聊天列表的样子：头像、名字、最近回复的时间、一行摘要。它们是这个产品的主体，
   不该藏在一个还要先点开的导航项底下。 */
/* 名单占满剩余高度、自己滚。Bot 多的时候滚的是名单，而不是把下面的入口顶出屏幕。 */
.satu-botlist { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 2px; }
/* 「新建 Bot」。贴在名单下沿、跟名单一样宽——它是名单的动作，不是又一个导航项，
   所以用虚线边框跟上面那些实心的行区分开。收成窄栏（.satu-rail）时只剩那个加号。 */
.satu-newbot {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  box-sizing: border-box;
  margin-top: 4px;
  padding: 8px var(--space-3);
  border: 1px dashed var(--color-divider);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--muted-foreground);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}
.satu-newbot:hover { color: var(--color-text); border-color: var(--color-text); }
.satu-rail .satu-newbot { justify-content: center; }
.satu-rail .satu-newbot span { display: none; }
/* 页面入口沉在底部，不跟着名单滚。 */
.satu-navfoot { flex: none; display: flex; flex-direction: column; gap: 2px; padding-top: 2px; }
/* 没有 Bot 名单的那一侧（平台管理员）——菜单就是这一列的全部内容。十几条再加上分组
   标题，矮一点的屏就装不下了，而 aside 是 overflow:hidden，装不下等于**直接裁掉**，
   最底下那几条谁都点不到。所以这一侧让它自己滚。
   有名单时不能这么写：那时 navfoot 必须 flex:none 沉在底部，一旦可压缩就会被名单
   挤扁。两种情形靠「有没有名单」区分，正是 :has() 问的那件事。 */
aside:not(:has(.satu-botlist)) .satu-navfoot { flex: 0 1 auto; min-height: 0; overflow-y: auto; }
.satu-botrow {
  display: flex;
  /* 头像跨两行，所以竖直居中——只有一行（还没说过话的 Bot）时也一样居中，
     行高从 34 塌到 34，看着不会跳。 */
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  box-sizing: border-box;
  padding: 8px var(--space-3);
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  cursor: pointer;
  font-family: var(--font-body);
  text-align: left;
  color: color-mix(in srgb, var(--color-text) 78%, transparent);
}
.satu-botrow:hover { background: color-mix(in srgb, var(--color-text) 6%, transparent); color: var(--color-text); }
.satu-botrow[aria-current="true"] { background: var(--color-accent); color: var(--color-bg); }
.satu-botrow[aria-current="true"]:hover { background: var(--color-accent-600); }
.satu-botrow:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
.satu-botavatar { flex: none; display: flex; align-items: center; }
/* min-width: 0 是省略号能生效的前提——不写的话 flex 子项按内容宽度算，长名字会把
   时间挤出侧栏。 */
.satu-botmain { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.satu-botline { display: flex; align-items: baseline; gap: 7px; min-width: 0; }

/* 本地 Bot 是运行边界，不是一句次要说明。徽标在侧栏和会话顶栏复用，使用有底色、有
   描边的形状，避免像原来那行 10px 灰字一样和摘要混在一起。 */
.satu-localtag {
  display: inline-flex;
  align-items: center;
  flex: none;
  padding: 2px 7px;
  border: 1px solid var(--color-accent-300);
  border-radius: 999px;
  background: var(--color-accent-100);
  color: var(--color-accent-800);
  font-size: 10.5px;
  font-weight: 700;
  line-height: 1.2;
  vertical-align: middle;
  white-space: nowrap;
}
.satu-localtag[data-compact] { padding: 1px 5px; font-size: 9.5px; }
.satu-botrow[aria-current="true"] .satu-localtag {
  border-color: color-mix(in srgb, var(--color-bg) 58%, transparent);
  background: color-mix(in srgb, var(--color-bg) 18%, transparent);
  color: var(--color-bg);
}

/* ── 状态点 ────────────────────────────────────────────────────────
   三态靠**形状**分，不只靠颜色：空闲是空心圈、执行中是实心并闪、待人工是实心琥珀。
   只用颜色的话，色觉障碍的人看到的是三个一模一样的点；而且侧栏选中行会整条染成
   品牌色，压在上面的纯色差别更难分。 */
.satu-botdot {
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 999px;
  border: 1.5px solid currentColor;
  opacity: 0.4;
  align-self: center;
  transition: opacity 120ms ease, background 120ms ease;
}
.satu-botdot[data-state="busy"] {
  background: currentColor;
  opacity: 1;
  animation: satu-blink 1.15s ease-in-out infinite;
}
.satu-botdot[data-state="review"] {
  background: var(--color-warn-500);
  border-color: var(--color-warn-500);
  opacity: 1;
  animation: none;
}
@keyframes satu-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.22; } }
@media (prefers-reduced-motion: reduce) {
  .satu-botdot[data-state="busy"] { animation: none; }
}
/* 收起侧栏时整行只剩头像，点跟着收掉——它挤在图标旁边只会让图标看着歪。 */
.satu-rail .satu-botdot { display: none; }

/* ── 机器通联指示灯 ──────────────────────────────────────────────────
   和上面的 botdot 同一条规矩：**四态靠形状分，不只靠颜色**。在线是实心、迟了是实心
   带闪、失联是空心粗环、没配对是空心虚线。色觉障碍的人只看颜色会看到四个一样的点，
   而这盏灯的全部意义就是「一眼扫过去哪台不对」。

   灯旁边永远跟着一句状态文字，那才是权威——灯只负责让人扫得快。 */
.satu-linkdot {
  flex: none;
  width: 9px;
  height: 9px;
  border-radius: 999px;
  border: 2px solid var(--muted-foreground);
  align-self: center;
}
.satu-linkdot[data-link="online"] {
  background: var(--color-ok-500);
  border-color: var(--color-ok-500);
}
.satu-linkdot[data-link="stale"] {
  background: var(--color-warn-500);
  border-color: var(--color-warn-500);
  animation: satu-blink 1.6s ease-in-out infinite;
}
/* 失联是**空心**：实心配深色在浅背景上反而不显眼，而空心粗环和上面两个实心点在
   形状上一眼分得开。 */
.satu-linkdot[data-link="offline"] {
  background: transparent;
  border-color: var(--destructive);
}
.satu-linkdot[data-link="unpaired"] {
  background: transparent;
  border-style: dashed;
  border-color: var(--muted-foreground);
}
/* 说不上来的时候就画得说不上来：实心灰，既不像在线的绿，也不像没配对的虚线。 */
.satu-linkdot[data-link="unknown"] {
  background: var(--muted-foreground);
  border-color: var(--muted-foreground);
  opacity: 0.45;
}
@media (prefers-reduced-motion: reduce) {
  .satu-linkdot[data-link="stale"] { animation: none; }
}

/* ── 占用条（CPU / 内存 / 磁盘 / journal）────────────────────────────
   **叫 gauge 不叫 meter**：shell.css 里早就有一套 `.satu-meter` / `.satu-meterfill`
   （Bot 统计页那几条按比例撑满宽度的进度条）。同名而不同形状，晚来的这套会顺着
   层叠盖上去——把那边的进度条从「撑满容器」压成 72px 的一小截，而那一页谁也没动过。
   三档颜色，门槛 75% 和 90%。**颜色不是唯一信息**：条子旁边永远跟着一个百分数，
   跟通联灯那条规矩一样——只靠颜色的话，色觉障碍的人看到的是三根一模一样的条。

   门槛定在 75/90 而不是 90/95：这一页的用处是在机器出事**之前**看见它，而一块盘
   从 90% 到写满往往只要几个小时，那时 bot 起不来、连日志都写不进去。 */
.satu-gauge {
  flex: none;
  display: inline-block;
  width: 72px;
  height: 6px;
  border-radius: 999px;
  background: var(--secondary);
  overflow: hidden;
  vertical-align: middle;
}
.satu-gauge > span {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: var(--color-ok-500);
}
.satu-gauge[data-level="warn"] > span { background: var(--color-warn-500); }
.satu-gauge[data-level="hot"] > span { background: var(--destructive); }
/* 算不出百分比（还没采到、或者压根没有上限可比）：空心虚线槽。**不能画成实心的
   0%**——那是「量到了，就是 0」，和「没量到」是完全相反的两个结论，跟通联灯里
   「失联是空心、没配对是虚线」同一条规矩。 */
.satu-gauge[data-level="none"] {
  background: transparent;
  border: 1px dashed var(--border);
  opacity: 0.6;
}

/* 机器卡片的头一行：灯、编号、短码、状态文字。编号和短码用等宽体——它们是用来
   照着念和比对的，比例字体下 6a9434e2 里的 0/o、1/l 分不清。 */
.satu-machinehead {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-bottom: var(--space-2);
}
.satu-machineno {
  font-weight: 600;
}
.satu-machineid {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--muted-foreground);
}
/* 刷新推到最右。头一行是 flex-wrap 的，窄屏上它会自己换行到下一行的右端——
   比固定定位好：绝对定位到卡片右上角，窄屏时会压在状态文字上。 */
.satu-machinerefresh {
  margin-left: auto;
}
.satu-botname {
  flex: 1;
  min-width: 0;
  font-size: 13.5px;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.satu-botrow[aria-current="true"] .satu-botname { font-weight: 600; }
/* 「有事等你」的图标。**点之外还要有它**：8px 的状态点在一列名字里太容易滑过去，
   而这一格说的是「你不去点，那一轮就一直停在席位上等着」。

   **画成一颗实心徽章，不是一个描边图形。** 13px 的细描边压在奶油底上，和旁边那颗
   同样琥珀的状态点糊成一团——远看就是两个小点，人扫过去根本不会停。填满之后它是这
   一列里唯一的色块。
   
   底色用 500、图形用 100：这套色阶在深色主题里是**翻过来的**（100 是深的、800 是浅
   的），所以 500 底 + 100 图形是唯一一组两个主题下都够对比的搭配——写死白色的话，
   深色主题里就是浅琥珀底压白图形。 */
.satu-botneed {
  /* 底色和图形色走两个变量：选中的那一行要整组换掉（见下面那条），而光晕的颜色
     跟着底色走——写死 warn-500 的话，翻过来的那一行会甩出一圈琥珀光晕。 */
  --need-ink: var(--color-warn-500);
  --need-glyph: var(--color-warn-100);
  flex: none;
  align-self: center;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 999px;
  background: var(--need-ink);
  color: var(--need-glyph);
  animation: satu-need-pulse 1.5s ease-in-out infinite;
}
/**
 * **选中的那一行要翻过来。**
 *
 * 那一行整条染成品牌赭红（`[aria-current]`），琥珀压在上面几乎看不见——而人正看着的
 * 那颗 Bot 恰恰最可能是在等他的那一颗。翻成浅底 + 琥珀图形，两种底色下都立得住。
 * 这就是那颗状态点的注释里说的「压在品牌色上的纯色差别更难分」，同一个坑。
 */
.satu-botrow[aria-current="true"] .satu-botneed {
  --need-ink: var(--color-bg);
  --need-glyph: var(--color-warn-500);
}
.satu-botneed[hidden] { display: none; }
.satu-botneed svg { display: block; width: 10px; height: 10px; }
/**
 * 一圈往外推的光晕 + 一档很浅的呼吸。
 *
 * **不做「闪没」那种闪烁**：透明度掉到看不见的那半拍里，这颗徽章等于不存在，而人的
 * 视线正好可能在那半拍扫过去。亮度只压到 0.72，一直看得见，动的是外面那圈光晕——
 * 会动的东西在余光里比会消失的东西显眼得多。
 */
@keyframes satu-need-pulse {
  0%, 100% { opacity: 1; box-shadow: 0 0 0 0 color-mix(in srgb, var(--need-ink) 55%, transparent); }
  70% { opacity: 0.72; box-shadow: 0 0 0 5px color-mix(in srgb, var(--need-ink) 0%, transparent); }
}
/* 关了动效的人**不能因此少看见一件事**：动画去掉，徽章本身照旧是实心亮色。 */
@media (prefers-reduced-motion: reduce) {
  .satu-botneed { animation: none; }
}
/* 侧栏收起来只剩头像，这个图标跟着收——挤在图标旁边只会让头像看着歪（同 botdot）。 */
.satu-rail .satu-botneed { display: none; }
.satu-bottime { flex: none; font-size: 11px; opacity: 0.6; font-variant-numeric: tabular-nums; }
/* 摘要只给一行。多行会让每一行的高度随内容跳，整份名单看着就散了。 */
.satu-botsnip {
  font-size: 12px;
  opacity: 0.62;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.satu-botsnip[hidden] { display: none; }
/* 收起侧栏时只剩头像，名字、时间、摘要一起收掉。 */
.satu-rail .satu-botrow { justify-content: center; padding: 8px 0; }
.satu-rail .satu-botmain { display: none; }
.gw-chat-main > .gw-flash { margin: var(--space-3) var(--space-6) 0; }

/* 金额输入框：$ 压在输入框左内边距里，输入内容相应右移让位。 */
.satu-money { position: relative; display: block; }
.satu-money > span {
  position: absolute; left: 16px; top: 50%; transform: translateY(-50%);
  font-size: 14px; color: var(--muted-foreground); pointer-events: none;
}
.satu-money .input { padding-left: 30px; }

/* 统计表。列比供应商表多，单独一套。 */
.satu-stathead, .satu-statrow { display: grid; grid-template-columns: 2fr 1fr 1fr 1fr 1fr 1fr 1fr; gap: var(--space-3); align-items: center; padding: var(--space-3) var(--space-4); }
.satu-stathead { padding: 10px var(--space-4); background: var(--card); border-bottom: 1px solid var(--border); font-size: 11.5px; font-weight: 600; letter-spacing: 0.04em; color: var(--muted-foreground); }
/* 除第一列外都是数字列：右对齐，并且要**等宽数字**——比例数字下 `1` 比 `7` 窄，
   右对齐只保证末位齐，上下两行的千分位仍然错开，一列数扫下来是歪的。 */
.satu-stathead > *:not(:first-child), .satu-statrow > *:not(:first-child) { text-align: right; font-variant-numeric: tabular-nums; }
/* 一行里的格子按**首行基线**对齐，不按盒子居中：带副行的格子（公司名下面的时间、
   输入下面的缓存、金额下面的角标）比别的格子高，居中会把它的首行顶上去，于是同一
   行的几个数字落在三个高度上。基线对齐让数字都停在同一条线上，副行统一往下挂。 */
.satu-statrow { align-items: baseline; border-bottom: 1px solid var(--border); min-width: 0; }
.satu-statrow:last-child { border-bottom: 0; }
.satu-statrow:hover { background: var(--card); }
.satu-stathead:first-child { border-top-left-radius: var(--radius-lg); border-top-right-radius: var(--radius-lg); }

/* ── 首页（没登录的人看到的 `/`，见 ui/pages-landing.js）──────────────────
   全部走 theme.css 的 token，所以暗色主题不用再写一遍。这一屏没有外壳（没有侧栏
   顶栏），滚动容器就是 .satu-lp 自己。 */
.satu-lp {
  /* 让**文档本身**滚，不套一层 overflow 容器：顶栏那条 sticky 才是黏在视口上的，
     而且移动端的地址栏收放也只在文档滚动时才对。工作台那边（appView）是反过来的
     ——它锁死 100vh 由内部各栏自己滚，那是应用；这一屏是网页。 */
  min-height: 100vh;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-body);
}
.satu-lp-wrap { width: 100%; max-width: 1120px; margin: 0 auto; padding: 0 var(--space-6); box-sizing: border-box; }

/* 顶栏黏住。半透明 + 模糊，底下那片暖奶油滚过去时还看得见一点，页面不会被切成两段。 */
.satu-lp-top {
  position: sticky;
  top: 0;
  z-index: 5;
  background: color-mix(in srgb, var(--color-bg) 88%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--color-divider);
}
.satu-lp-topin { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); height: 62px; }
.satu-lp-brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  font-family: var(--font-heading);
  font-size: 19px;
  cursor: pointer;
}
.satu-lp-brand[data-static] { cursor: default; font-size: 16px; }
.satu-lp-brand img { border-radius: var(--radius-sm); object-fit: contain; }
.satu-lp-topact { display: flex; align-items: center; gap: var(--space-3); }
/* 源码那颗。不描边不填底——它和右边那组语言开关、登录按钮是三级不同的东西，
   给它也画个框的话，顶栏右边就成了三个抢同样注意力的方块。 */
.satu-lp-ghlink {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  flex: none;
  border-radius: 999px;
  color: var(--color-text);
  opacity: 0.62;
  transition: opacity 120ms ease, background 120ms ease;
}
.satu-lp-ghlink:hover { opacity: 1; background: var(--secondary); color: var(--color-text); }
.satu-lp-ghlink:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
/* 「联系销售」。btn-secondary 的描边款：它和右边那颗实心的「登录」是两件事——
   一个是「先问问」，一个是「我有账号」，实心只该有一颗。 */
.satu-lp-sales { flex: none; gap: 6px; }
.satu-lp-sales svg { flex: none; }

/* 弹窗：二维码那张卡。 */
.satu-lp-salesbox { display: flex; flex-direction: column; gap: var(--space-4); max-width: 380px; }
/* 二维码**底色写死白的**，暗色主题下也不反——反色的码有相当一部分扫码器读不出来。
   所以给它一层白底圆角框，在深色页面上看着是一张卡片，而不是一块没适配的白斑。 */
.satu-lp-qr {
  align-self: center;
  padding: var(--space-3);
  border-radius: var(--radius-md);
  background: #ffffff;
  box-shadow: var(--shadow-sm);
  line-height: 0;
}
.satu-lp-qr img { display: block; width: 180px; height: 180px; }
.satu-lp-salesnum { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-3); }
.satu-lp-salesnum > span { font-family: var(--font-heading); font-size: 16px; font-variant-numeric: tabular-nums; }
.satu-lp-salesnum .btn { gap: 6px; text-decoration: none; }
/* 「示例二维码」那句。琥珀不是灰：它不是一句补充说明，是一件还没做完的事。 */
.satu-lp-salesnote { margin: 0; padding: 9px 12px; border-radius: var(--radius-sm); background: var(--color-warn-100); color: var(--color-warn-800); font-size: 12px; line-height: 1.6; }
/* 语言开关：两颗贴在一起的小按钮，选中的那颗压一层底色。做成一段而不是下拉，
   因为只有两个值——点一下就到，不该先展开再选。 */
.satu-lp-lang { display: inline-flex; padding: 2px; border: 1px solid var(--border); border-radius: 999px; background: var(--popover); }
.satu-lp-lang button {
  padding: 4px 11px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--muted-foreground);
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}
.satu-lp-lang button[aria-pressed="true"] { background: var(--secondary); color: var(--secondary-foreground); }

/* 题图和文案并排。左边略宽——那一列是要读的，图是给它作证的。 */
.satu-lp-hero {
  display: grid;
  /* 右列比左列**宽**：那块小样是这一屏唯一说得清「它长什么样」的东西，而左边是
     一段本来就该窄着读的正文（45ch 上下最舒服，再宽只会让眼睛来回甩）。原来是左
     1.05 / 右 1，小样于是被挤成一张缩略图。 */
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  gap: var(--space-6);
  align-items: center;
  padding-top: 72px;
  padding-bottom: 72px;
}
.satu-lp-heroleft { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-4); }
.satu-lp-eyebrow {
  padding: 5px 12px;
  border-radius: 999px;
  background: var(--color-accent-100);
  color: var(--color-accent-800);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.01em;
}
/* clamp 而不是媒体查询：标题是这一屏唯一真正大的东西，它得跟着窗口连续变，
   在断点上「啪」地跳一档比一直偏小还显眼。 */
/* keep-all：中文默认「哪儿都能断」，于是标题会在「交给一 / 个同事」中间折，把一个词
   劈成两半。加上它之后只在标点和空格处折，这句就断在那个逗号后面——英文那版靠空格
   断行，不受影响。 */
.satu-lp-hero h1 { margin: 0; font-size: clamp(30px, 4.2vw, 50px); line-height: 1.12; letter-spacing: -0.02em; word-break: keep-all; }
.satu-lp-lead { margin: 0; max-width: 46ch; font-size: 16px; line-height: 1.7; color: color-mix(in srgb, var(--color-text) 72%, transparent); }
.satu-lp-ctas { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-2); }
.satu-lp-ctas .btn { padding: 11px 18px; font-size: 15px; }
.satu-lp-fine { margin: 0; font-size: 13px; line-height: 1.6; color: var(--muted-foreground); }

/* 题图：一块产品形状的面。名册在左，对话在右，和登录之后是同一个布局。 */
.satu-lp-shot {
  display: grid;
  grid-template-columns: minmax(0, 208px) minmax(0, 1fr);
  gap: 1px;
  background: var(--color-divider);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
.satu-lp-shotside { display: flex; flex-direction: column; gap: 3px; padding: var(--space-3); background: var(--color-surface); }
/* 名册那几行是**真的按钮**（见 pages-landing.js 的 lpShot）：点一行换一条对话。
   所以这里要把 <button> 自带的那身边框、底色、居中和系统字全卸掉，再补上手感。 */
.satu-lp-shotrow {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  padding: 9px 10px;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: inherit;
  font-family: var(--font-body);
  text-align: left;
  cursor: pointer;
  min-width: 0;
}
.satu-lp-shotrow:hover { background: color-mix(in srgb, var(--color-text) 6%, transparent); }
.satu-lp-shotrow[data-on="true"], .satu-lp-shotrow[data-on="true"]:hover { background: var(--color-accent-100); }
/* 键盘走到这儿要看得见。用 outline 而不是换底色——选中态已经占了底色那一格。 */
.satu-lp-shotrow:focus-visible { outline: 2px solid var(--ring); outline-offset: -2px; }
/* 「点一个试试」。没有它的话，这三行长得跟一张截图上的三行一模一样，没人知道能点。 */
/* 紧跟在那三行下面，不沉底：它说的是「上面这三行能点」，离开那三行就成了一句
   悬在角落里、不知道在说谁的话。 */
.satu-lp-shothint { padding: var(--space-2) 10px 0; font-size: 11px; color: var(--muted-foreground); }
.satu-lp-shotface {
  width: 32px;
  height: 32px;
  flex: none;
  border-radius: 999px;
  background: var(--color-accent-2-200);
  color: var(--color-accent-2-800);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-heading);
  font-size: 13px;
}
.satu-lp-shottext { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.satu-lp-shotname { display: flex; align-items: center; gap: 6px; min-width: 0; font-size: 13px; }
/* 名字单独包一层，只为窄屏上能把**名字**收掉而把那颗状态点留下（见下面 560 那段）。 */
.satu-lp-shotname b { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.satu-lp-shotsnip { font-size: 12px; color: var(--muted-foreground); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* 状态点的三档和侧栏名册同义：忙=赭红，待人工=琥珀，空闲=灰。 */
.satu-lp-shotdot { width: 8px; height: 8px; flex: none; border-radius: 999px; background: var(--color-neutral-500); }
.satu-lp-shotdot[data-state="busy"] { background: var(--color-accent-500); }
.satu-lp-shotdot[data-state="review"] { background: var(--color-warn-500); }
/* 对话那半边**自己撑开**：名册只有三行，光靠它整块小样才 150px 高，摆在一段
   五六行的正文旁边就是一张贴纸。给一个下限，让它跟左边那一列够得上。 */
/* 对话**沉底**，像一条已经聊了一会儿的会话；上面留白就是滚上去的那些。居中的话
   三条气泡浮在一块面的正中间，看着像一张卡片而不是一条对话。 */
/* 右半边：会话抬头 + 对话。**高度是钉的**——这块演示是首屏唯一说得清「它长什么样」
   的东西，跟着两条消息的高度走的话，它就只有一张卡片那么高，摆在标题旁边像个附注。 */
/* 输入框长在这块屏的下沿（lpComposer），所以它也要有一圈内边距兜着——不然那颗
   框会贴死在面的边上，看着像被切了一刀。 */
.satu-lp-shotmain { display: flex; flex-direction: column; height: 500px; padding-bottom: var(--space-4); background: var(--popover); }

/* 会话抬头。真界面里这一行长在顶栏里（chat.js 的 chatHeadInline）：头像、名字和
   身份、右边一颗在线灯。少了它，右半边就是一段没有出处的对话。 */
.satu-lp-convohead { display: flex; align-items: center; gap: var(--space-3); flex: none; padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--color-divider); }
.satu-lp-convoid { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
.satu-lp-convoid b { font-size: 13.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.satu-lp-convoid span { font-size: 11.5px; color: var(--muted-foreground); }
/* 在线灯的三档和真界面同义：空闲绿、正在执行赭红、待人工琥珀（chat.css 的 .sw-convo-live）。 */
.satu-lp-live { display: inline-flex; align-items: center; gap: 6px; flex: none; padding: 3px 10px; border-radius: 999px; font-size: 11.5px; font-weight: 600; background: var(--color-ok-100); color: var(--color-ok-800); }
.satu-lp-live i { width: 7px; height: 7px; border-radius: 999px; background: var(--color-ok-500); }
.satu-lp-live[data-state="busy"] { background: var(--color-accent-100); color: var(--color-accent-800); }
.satu-lp-live[data-state="busy"] i { background: var(--color-accent-500); }
.satu-lp-live[data-state="review"] { background: var(--color-warn-100); color: var(--color-warn-800); }
.satu-lp-live[data-state="review"] i { background: var(--color-warn-500); }

/* 对话**沉底**，像一条已经聊了一会儿的会话；上面留白就是滚上去的那些。 */
/* 高度是**定死的**（上面那个 height），而且取的是三条演示里最长那条的高度——跟着
   内容撑的话，点一下名册整页会往下抖一截；截短了则会把最上面那条消息拦腰切开，看着
   像画坏了。短的那两条上面多出来的留白正好是「滚上去的那些」。 */
.satu-lp-thread { flex: 1; min-height: 0; overflow: hidden; display: flex; flex-direction: column; justify-content: flex-end; gap: var(--space-4); padding: var(--space-4) var(--space-4) var(--space-2); }

/* 一条消息：头像在外，气泡和时间在一列里。人说的那一行整行反过来（头像到右边），
   和真界面的 .sw-msg[data-role='user'] 是同一招。 */
.satu-lp-msg { display: flex; align-items: flex-start; gap: var(--space-2); }
.satu-lp-msg[data-role="user"] { flex-direction: row-reverse; }
.satu-lp-face {
  width: 28px;
  height: 28px;
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm);
  background: var(--secondary);
  color: var(--secondary-foreground);
  font-family: var(--font-heading);
  font-size: 12px;
}
.satu-lp-face[data-big] { width: 32px; height: 32px; font-size: 13px; background: var(--color-accent-2-200); color: var(--color-accent-2-800); }
.satu-lp-msg[data-role="assistant"] .satu-lp-face { background: var(--color-accent-2-200); color: var(--color-accent-2-800); }
/* 气泡列最宽 72%，和真界面一个数：再宽长句一行装太多字，眼睛回行会丢。 */
.satu-lp-col { display: flex; flex-direction: column; gap: 4px; min-width: 0; max-width: 72%; }
.satu-lp-msg[data-role="user"] .satu-lp-col { align-items: flex-end; }
.satu-lp-bubble {
  box-sizing: border-box;
  max-width: 100%;
  padding: 9px 13px;
  border-radius: 14px;
  font-size: 13px;
  line-height: 1.7;
}
/* 靠说话人那一侧的下角收成 6px。这一下不是装饰：一屏上左右两串气泡，尖角朝谁
   就是谁说的，不用去数缩进。 */
.satu-lp-bubble[data-role="user"] { background: var(--primary); color: var(--primary-foreground); border-bottom-right-radius: 5px; }
.satu-lp-bubble[data-role="assistant"] { background: var(--card); color: var(--card-foreground); border: 1px solid var(--border); border-bottom-left-radius: 5px; }
/* 时间在气泡**外面**：压进去的话同一条消息里就有两种字号两种颜色，而时间只是旁注。 */
.satu-lp-time { font-size: 10.5px; color: var(--muted-foreground); font-variant-numeric: tabular-nums; }
/* 工具痕迹长在气泡**里面**（真界面的 .sw-chips 就在气泡里）——浮在气泡外面的话，
   它看着像系统提示，而它其实是这句回答的一部分：他是怎么得出这个结论的。 */
.satu-lp-chips { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 9px; }
/* 药丸一颗一行字，装不下就省略号——**不许换行**。窄屏上「读了 refunds-2026-09.csv」
   会折成两行，一颗药丸顶两行高，两颗就把气泡撑高一倍。 */
.satu-lp-chip { display: inline-flex; align-items: center; gap: 4px; max-width: 100%; padding: 2px 9px; border-radius: 999px; background: var(--secondary); color: var(--secondary-foreground); font-size: 10.5px; line-height: 1.7; white-space: nowrap; overflow: hidden; }
.satu-lp-chip span { overflow: hidden; text-overflow: ellipsis; }
.satu-lp-chip svg { flex: none; opacity: 0.7; }

/* 输入框。整块是一颗按钮（见 pages-landing.js 的 lpComposer），所以按钮那身默认样式
   要卸干净。**一行**：附件、占位字、那句说明、发送，全排在同一条 48px 高的线上。
   （两行那版是照真界面的 .sw-composer-box 来的——那边要容纳多行正文、@ 药丸、命令
   选单，所以才分两行；这儿只有一句占位字，两行就是一块空着的面。） */
.satu-lp-composer {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: auto;
  height: 48px;
  box-sizing: border-box;
  /* 左右都留出边距：外面这一层是**它离面的边有多远**（和上面消息那一栏的
     padding 同一个数，两者左右才对得齐），里面那一层是**内容离框的边有多远**。 */
  margin: 0 var(--space-4);
  padding: 0 7px;
  border: 1px solid var(--input-border);
  border-radius: 999px;
  background: var(--popover);
  font-family: var(--font-body);
  text-align: left;
  cursor: pointer;
  transition: border-color 120ms ease, box-shadow 120ms ease;
}
/* 指上去/键盘走到时按真输入框的聚焦环亮——它现在是一条通往登录的路，得有手感。 */
.satu-lp-composer:hover,
.satu-lp-composer:focus-visible {
  outline: 0;
  border-color: var(--ring);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--ring) 22%, transparent);
}
/* 占位字吃掉中间全部宽度，装不下就省略号——它和右边那句说明抢地方时，该让的是它。
   用 muted，和真的 placeholder 同一档灰；写成正文色就像里面已经打了字。 */
.satu-lp-composer-ph { flex: 1; min-width: 0; font-size: 14px; color: var(--muted-foreground); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.satu-lp-composer-hint { flex: none; font-size: 11px; color: var(--muted-foreground); }
.satu-lp-iconbtn { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; flex: none; border-radius: 999px; color: var(--secondary-foreground); opacity: 0.7; }
.satu-lp-send { display: inline-flex; align-items: center; justify-content: center; width: 34px; height: 34px; flex: none; border-radius: 999px; background: var(--primary); color: var(--primary-foreground); box-shadow: var(--shadow-2xs); }
.satu-lp-composer:hover .satu-lp-send { background: var(--color-accent-600); }

/* 转人工那条：只有「小雨 · 研究」那一路有。琥珀，和真界面里那张待办同一个颜色。 */
.satu-lp-handoff { display: flex; align-items: center; gap: 7px; align-self: flex-start; margin-left: 36px; padding: 7px 11px; border-radius: var(--radius-sm); background: var(--color-warn-100); color: var(--color-warn-800); font-size: 12px; font-weight: 600; }
.satu-lp-handoff svg { flex: none; }

.satu-lp-sec { padding-bottom: 72px; }
.satu-lp-sechead { max-width: 56ch; margin-bottom: var(--space-6); }
.satu-lp-sechead h2 { margin: 0 0 var(--space-2); font-size: clamp(24px, 2.6vw, 32px); letter-spacing: -0.015em; word-break: keep-all; }
.satu-lp-sechead p { margin: 0; font-size: 15px; line-height: 1.7; color: color-mix(in srgb, var(--color-text) 68%, transparent); }

/* 卡片：**小样在上，字在下**，四张排两列。原来是八张纯文字卡排三四列，整页看下来
   就是一片字。小样各占一块固定高度（170px），四张的上沿因此对得齐——高度随内容浮动
   的话，两列之间的标题会错开一行，一眼就散。 */
/* 钉死两列，不用 auto-fit：四张小样排成 2×2 是一个完整的方块；auto-fit 在中间那段
   宽度上会排成 3 + 1，最后那张孤零零占一整行，看着像漏画了两张。 */
.satu-lp-cards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-4); }
.satu-lp-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--popover);
  box-shadow: var(--shadow-2xs);
}
.satu-lp-cardshot {
  height: 170px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  box-sizing: border-box;
  border-radius: var(--radius-sm);
  /* 小样底下垫一层比卡片略深的暖面：小样自己是白的，直接贴在白卡上会糊成一片。 */
  background: linear-gradient(160deg, var(--color-accent-100), var(--color-neutral-200));
  overflow: hidden;
}
.satu-lp-cardshot > * { width: 100%; max-width: 380px; }
.satu-lp-card h3 { margin: 0; font-size: 16px; }
.satu-lp-card p { margin: 0; font-size: 13.5px; line-height: 1.65; color: var(--muted-foreground); }

/* 小样里的窗口壳：三颗灯 + 标题。工作区、日常任务、账本三块共用。 */
.satu-lp-win { border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--popover); box-shadow: var(--shadow-sm); overflow: hidden; }
.satu-lp-winbar { display: flex; align-items: center; gap: 5px; padding: 7px 9px; border-bottom: 1px solid var(--color-divider); background: var(--color-surface); }
.satu-lp-winbar i { width: 7px; height: 7px; border-radius: 999px; background: var(--color-neutral-400); }
.satu-lp-winbar i:first-child { background: var(--color-accent-400); }
.satu-lp-winbar span { margin-left: 5px; font-size: 11px; color: var(--muted-foreground); }
.satu-lp-winbody { display: flex; flex-direction: column; gap: 5px; padding: 9px; }

.satu-lp-file { display: flex; align-items: center; gap: 7px; font-size: 11.5px; color: var(--color-text); }
.satu-lp-file svg { flex: none; color: var(--color-accent-600); }
.satu-lp-file span { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.satu-lp-file em { flex: none; font-style: normal; font-size: 10.5px; color: var(--muted-foreground); }
/* 终端那一行：整块小样里唯一的等宽字，它一出现「这是一台机器」就不用再解释了。 */
.satu-lp-term { margin-top: 3px; padding: 7px 8px; border-radius: var(--radius-sm); background: var(--color-neutral-900); color: var(--color-neutral-200); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; white-space: nowrap; overflow: hidden; }
.satu-lp-term b { color: var(--color-accent-400); font-weight: 400; }
.satu-lp-caret { display: inline-block; width: 6px; height: 11px; margin-left: 3px; vertical-align: -1px; background: var(--color-accent-400); }

.satu-lp-appr { padding: var(--space-3); border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--popover); box-shadow: var(--shadow-sm); }
.satu-lp-apprtag { display: inline-flex; align-items: center; gap: 5px; padding: 3px 9px; border-radius: 999px; background: var(--color-warn-100); color: var(--color-warn-800); font-size: 11px; font-weight: 600; }
.satu-lp-appr p { margin: var(--space-2) 0 var(--space-3); font-size: 13px; line-height: 1.5; }
.satu-lp-apprbtns { display: flex; gap: 7px; }
.satu-lp-apprbtns span { padding: 5px 14px; border: 1px solid var(--border); border-radius: 999px; background: var(--popover); font-size: 11.5px; font-weight: 600; color: var(--secondary-foreground); }
.satu-lp-apprbtns span[data-yes] { border-color: transparent; background: var(--color-accent-500); color: var(--primary-foreground); }

.satu-lp-cron { display: flex; align-items: center; gap: 8px; font-size: 11.5px; }
.satu-lp-cronwhen { flex: none; padding: 2px 7px; border-radius: var(--radius-sm); background: var(--color-accent-2-100); color: var(--color-accent-2-800); font-weight: 600; }
.satu-lp-cronwhat { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--muted-foreground); }

.satu-lp-ledger { gap: 9px; }
/* 柱子的高度是排版，不是数据（见 lpMockLedger）——所以不标刻度，也不写坐标轴。 */
.satu-lp-bars { display: flex; align-items: flex-end; gap: 6px; height: 62px; }
.satu-lp-bars i { flex: 1; border-radius: 3px 3px 0 0; background: var(--color-accent-300); }
.satu-lp-bars i:nth-child(6) { background: var(--color-accent-500); }
.satu-lp-ledgerfoot { display: flex; align-items: baseline; gap: 8px; }
.satu-lp-ledgerfoot b { font-family: var(--font-heading); font-size: 19px; }
.satu-lp-ledgerfoot span { font-size: 10.5px; color: var(--muted-foreground); }

/* 剩下那几样：一个图标一个词，排成一条带。 */
.satu-lp-chips { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-4); }
.satu-lp-chip { display: inline-flex; align-items: center; gap: 7px; padding: 8px 14px; border: 1px solid var(--border); border-radius: 999px; background: var(--popover); font-size: 13px; }
.satu-lp-chipicon { display: inline-flex; color: var(--color-accent-600); }

.satu-lp-steps { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--space-4); margin: 0; padding: 0; list-style: none; }
.satu-lp-step { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3) var(--space-4); border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--popover); }
.satu-lp-stepnum {
  width: 30px;
  height: 30px;
  flex: none;
  border-radius: 999px;
  background: var(--color-accent-500);
  color: var(--primary-foreground);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-heading);
  font-size: 14px;
}
.satu-lp-step h3 { margin: 0; font-size: 14px; line-height: 1.45; }

.satu-lp-tail { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-4); margin-top: var(--space-8); padding-top: var(--space-6); border-top: 1px solid var(--color-divider); }
.satu-lp-tail span { font-size: 13.5px; color: var(--muted-foreground); }

.satu-lp-foot { border-top: 1px solid var(--color-divider); background: var(--color-surface); }
.satu-lp-footin { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-3); padding-top: var(--space-6); padding-bottom: var(--space-6); font-size: 12px; color: var(--muted-foreground); }

@media (max-width: 760px) {
  .satu-lp-cards { grid-template-columns: 1fr; }
}

@media (max-width: 900px) {
  .satu-lp-hero { grid-template-columns: 1fr; padding-top: 48px; padding-bottom: 48px; }
  /* 窄屏上题图收在文案下面就够了，别再让它挤成两栏——那时名册那一栏只剩一条边。 */
  .satu-lp-shot { grid-template-columns: 1fr; }
  .satu-lp-shotside { flex-direction: row; overflow: hidden; }
  .satu-lp-shotside .satu-lp-shotrow { flex: 1; min-width: 0; }
  .satu-lp-shotsnip { display: none; }
  /* 顶栏那颗「登录」和首屏的主 CTA 说的是同一件事，窄屏上一前一后隔不到一屏，
     留一颗就够——留下面那颗大的。 */
  .satu-lp-topsign { display: none; }
}

@media (max-width: 560px) {
  /* 「联系销售」收成一颗只有图标的圆钮：顶栏那一行在这个宽度上已经排着 logo、
     GitHub、语言开关三样了，再加四个字就换行。**不能跟着「登录」一起藏**（那条在
     900 那段里）——藏了的话页面上再没有第二处能联系到人。
     900 到 560 之间不收：那一档「登录」已经让出了位置，四个字放得下。 */
  .satu-lp-sales { padding: 8px; border-radius: 999px; }
  .satu-lp-sales span { display: none; }
  /* 手机上名册收成一条横着的选择器：头像 + 状态点，名字和摘要都撤掉（一行三个名字
     只会各剩两个字）。**不能整个藏掉**——那三行现在是这块演示唯一的开关，藏了它
     右边就再也换不了人，等于把可交互的东西又变回一张图。 */
  /* 名字和摘要收掉（一行三个名字只会各剩两个字），**那颗状态点留着**——它是这条
     窄带上唯一还在说「谁在干活、谁等着你」的东西。 */
  .satu-lp-shotname b { display: none; }
  .satu-lp-shotside { justify-content: center; gap: var(--space-2); }
  /* width 要跟着收。按钮那身重置里有 `width: 100%`（竖着排时它要撑满名册栏），
     横过来之后 `flex: none` 不会去动这个宽度——三颗头像于是各占一整行的宽，两颗
     被挤出可视区，只剩中间那颗看得见。 */
  .satu-lp-shotside .satu-lp-shotrow { flex: none; width: auto; padding: 6px; }
  .satu-lp-shothint { margin: 0; padding: 0 0 0 var(--space-2); align-self: center; }
  /* 手机上矮一档。**别再往下压**：输入框和会话抬头是定高的，这块高度一减全从中间
     那格对话里扣——扣过头就把最上面那句问话截掉，剩一句没头没脑的回答。 */
  .satu-lp-shotmain { height: 440px; }
  /* 只留「问 + 答」这一轮。三轮那条（小满）在 375 宽上排下来有 470px 高，塞进 380
     就会把最上面那句**问话**截掉——剩一句没头没脑的回答，比少一轮糟得多。 */
  .satu-lp-thread .satu-lp-msg:nth-child(3) { display: none; }
}

/* ── 隐私政策与服务条款（/privacy、/terms，见 ui/pages-legal.js）─────────
   外壳（顶栏、页脚、那条 wrap）借首页那一套，这里只管正文：一列目录 + 一列文本。
   两页共用，所以别往这儿写任何一页专有的东西。 */
.satu-lg-head { padding-top: 56px; padding-bottom: var(--space-6); border-bottom: 1px solid var(--color-divider); }
.satu-lg-head h1 { margin: 0 0 var(--space-3); font-size: clamp(28px, 3.4vw, 40px); letter-spacing: -0.02em; }
.satu-lg-lead { margin: 0; max-width: 60ch; font-size: 15.5px; line-height: 1.7; color: color-mix(in srgb, var(--color-text) 72%, transparent); }
.satu-lg-meta { margin: var(--space-4) 0 0; font-size: 13px; color: var(--muted-foreground); font-variant-numeric: tabular-nums; }
/* 「还没过法务」那条横条。和首页那句「示例二维码」同一身打扮（警告色、小字），
   因为它们是同一种东西：一件没做完的事在页面上留下的唯一痕迹。 */
.satu-lg-note { margin: var(--space-4) 0 0; max-width: 72ch; padding: 10px 13px; border-radius: var(--radius-sm); background: var(--color-warn-100); color: var(--color-warn-800); font-size: 12.5px; line-height: 1.65; }

.satu-lg-grid { display: grid; grid-template-columns: 232px minmax(0, 1fr); gap: var(--space-8); align-items: start; padding-top: var(--space-8); padding-bottom: 64px; }

/* 目录黏在顶栏底下。top 要躲开那条 62px 高的 sticky 顶栏，否则跳到某一节之后，
   目录自己被压在顶栏底下半截。 */
.satu-lg-toc { position: sticky; top: 78px; display: flex; flex-direction: column; gap: 1px; }
.satu-lg-toc a {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  color: var(--muted-foreground);
  text-decoration: none;
  font-size: 13px;
  line-height: 1.5;
}
.satu-lg-toc a:hover { background: var(--secondary); color: var(--color-text); }
.satu-lg-toc a:focus-visible { outline: 2px solid var(--ring); outline-offset: -2px; }
.satu-lg-toc a span { flex: none; width: 16px; font-variant-numeric: tabular-nums; font-size: 11.5px; opacity: 0.7; }

.satu-lg-body { max-width: 72ch; }
/* 锚点跳过来时，标题不要顶在视口最上沿被 sticky 顶栏盖住。 */
.satu-lg-sec { scroll-margin-top: 80px; }
.satu-lg-sec + .satu-lg-sec { margin-top: var(--space-8); }
.satu-lg-sec h2 { display: flex; align-items: baseline; gap: var(--space-3); margin: 0 0 var(--space-4); font-size: 19px; }
.satu-lg-secnum { flex: none; font-size: 13px; color: var(--muted-foreground); font-variant-numeric: tabular-nums; }
.satu-lg-sec p { margin: 0 0 var(--space-3); font-size: 14.5px; line-height: 1.85; color: color-mix(in srgb, var(--color-text) 80%, transparent); }
.satu-lg-sec ul { margin: 0 0 var(--space-3); padding-left: 1.15em; }
.satu-lg-sec li { margin-bottom: var(--space-2); font-size: 14.5px; line-height: 1.8; color: color-mix(in srgb, var(--color-text) 80%, transparent); }
.satu-lg-sec > *:last-child { margin-bottom: 0; }

/* 页脚那几条（首页、登录页、这两页共用）。首页那边是按钮（站内跳转走 data-act），
   登录那三屏是真链接（开新标签页，见 shell.js 的 authAside）——两种都要把默认打扮
   撤掉，否则页脚上会冒出两个灰框、或者两条蓝字。 */
.satu-lg-links { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--space-4); }
.satu-lg-links button,
.satu-lg-links a {
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
  cursor: pointer;
}
.satu-lg-links button:hover,
.satu-lg-links a:hover { color: var(--color-text); text-decoration-color: currentColor; }
.satu-lg-links button:focus-visible,
.satu-lg-links a:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; border-radius: 2px; }

/* 「点击『…』即表示你同意…」那句（shell.js 的 authConsent）：注册和接受邀请两屏的
   提交按钮底下各一份。压在按钮下面、比正文小一号，但两条链接要**看得出是链接**——
   这句话的全部意义就在于那两条点得开。 */
.satu-authconsent { margin: calc(var(--space-2) * -1) 0 0; font-size: 12px; line-height: 1.65; color: var(--muted-foreground); }
.satu-authconsent a { color: var(--color-accent-700); text-decoration: underline; text-underline-offset: 3px; }
.satu-authconsent a:hover { color: var(--color-accent-600); }

@media (max-width: 900px) {
  /* 窄屏上目录横过来摆在正文前面：竖着那一列到这个宽度已经没地方放，而整个撤掉的话
     这两页就只剩一根长长的滚动条——法律文本恰恰是要跳着看的。 */
  .satu-lg-grid { grid-template-columns: 1fr; gap: var(--space-6); }
  .satu-lg-toc {
    position: static;
    flex-direction: row;
    flex-wrap: wrap;
    gap: var(--space-2);
    padding-bottom: var(--space-4);
    border-bottom: 1px solid var(--color-divider);
  }
  .satu-lg-toc a { padding: 4px 10px; border: 1px solid var(--border); border-radius: 999px; font-size: 12.5px; }
  .satu-lg-head { padding-top: 36px; }
  /* 顶栏那颗「登录」在窄屏上是藏起来的（见上面 900 那段），因为首页底下还有一颗更大的
     主 CTA。**这两页没有那颗**——跟着藏的话，手机上从这儿再也走不进产品。 */
  .satu-lg .satu-lp-topsign { display: inline-flex; }
}
