
  /*
    ## 配色

    深色。不是为了「看起来高级」—— 这是一个**看片子**的工具，
    用户大部分时间在盯 9:16 的画面，浅色底会把眼睛从画面上拽走，
    而且视频周围那圈 letterbox 在白底上像个破洞。

    层次只有四级（底 → 栏 → 台 → 卡），再多一级就分不出来了。
  */
  :root {
    --bg:      #0d0d0f;   /* 最底，也是 letterbox 的颜色 */
    --rail:    #121214;   /* 左栏 */
    --stage:   #161618;   /* 主区 */
    --card:    #1c1c20;   /* 卡片、输入框 */
    --card-hi: #26262b;   /* hover / 选中 */
    --line:    #2b2b32;
    --line-soft: #212128;

    --ink:   #ececf0;
    --ink-2: #9a9aa6;
    --ink-3: #6b6b76;

    --accent: #5b8cff;
    --accent-ink: #fff;
    --accent-soft: rgba(91, 140, 255, .15);
    --ok: #3ecf8e;
    --warn: #e0a33a;
    --danger: #ff6b6b;

    --r-sm: 8px; --r-md: 12px; --r-lg: 18px;
    --rail-w: 236px;
    /*
      主区底部那段留白。

      **它是一个变量，因为吸底的东西要拿它来抵消。** `position: sticky` 贴的是
      滚动容器的**内容边**，不是视口边 —— 所以一条 `bottom: 0` 的工具条会悬在
      离底部这么高的地方。两处写死两个 60px 的话，改一处就会错位（见 `.bulk`）。
    */
    --page-pad-b: 60px;
    /* 放片子那块舞台的高度。竖屏为主所以给得高，`60vh` 是给小屏留的余地。 */
    --stage-h: min(460px, 60vh);
    /*
      会话流里那块小一档。
      这一页一屏要装下好几条记录 —— 用 `--stage-h` 的话一条就占满整屏，
      往下再无「还有别的」的暗示，用户不会想到去滚。
    */
    --shot-h: min(360px, 46vh);
  }
  * { box-sizing: border-box; }
  html, body { height: 100%; }
  body {
    margin: 0; background: var(--bg); color: var(--ink);
    font: 14px/1.65 system-ui, -apple-system, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
    -webkit-font-smoothing: antialiased;
  }
  a { color: var(--accent); text-decoration: none; }
  a:hover { text-decoration: underline; }
  ::selection { background: var(--accent-soft); }

  /* 滚动条：默认的亮色滚动条在深色界面上是一条白杠。 */
  ::-webkit-scrollbar { width: 10px; height: 10px; }
  ::-webkit-scrollbar-thumb { background: #34343c; border-radius: 6px; border: 3px solid transparent; background-clip: content-box; }
  ::-webkit-scrollbar-thumb:hover { background: #45454f; background-clip: content-box; }
  ::-webkit-scrollbar-track { background: transparent; }

  /* ---------------------------------------------------------------- 骨架 */

  body { display: flex; }
  /* 没登录时不要左栏 —— 那时候它一个能点的东西都没有。 */
  body.auth #rail { display: none; }
  /* 顶栏在这时候是一条 54px 的空横杠 —— 页面上唯一的内容是那张登录卡。 */
  body.auth #topbar { display: none; }
  body.auth .stage { background: #0b0b0d; }
  body.auth main {
    display: grid; place-items: center; min-height: 100vh; padding: 40px;
    background:
      linear-gradient(rgba(255,255,255,.018) 1px, transparent 1px),
      linear-gradient(90deg, rgba(255,255,255,.018) 1px, transparent 1px),
      #0b0b0d;
    background-size: 48px 48px;
  }
  #rail {
    width: var(--rail-w); flex: 0 0 var(--rail-w); height: 100vh;
    background: var(--rail); border-right: 1px solid var(--line-soft);
    display: flex; flex-direction: column; overflow: hidden;
  }
  .rail-brand {
    display: flex; align-items: center; gap: 6px;
    padding: 18px 16px 14px; font-weight: 700; letter-spacing: .01em; font-size: 22px;
  }
  /*
    真 logo。自带的蓝紫渐变正好和 `--accent` 是同一路，所以不加底色。

    **框要比看上去的需要大一圈。** 这张图是 144×144 的满画布导出，
    眼睛图形只占中间六成，四周是透明留白 —— 按「和文字一样高」去设尺寸，
    出来的图形只有文字的三分之二，在一行里明显矮一截。
  */
  /* 对照即梦：logo 图形约 26px 高、名字约 22px 粗体（原来 36px 框 / 16px 字，各小了一圈）。 */
  .rail-brand .mark { width: 46px; height: 46px; flex: 0 0 46px; display: block; margin: -6px -2px -6px 0; object-fit: contain; }
  /*
    贴牌的 logo 加载失败时顶上的文字位（见 app.mjs 的 brandImageFallback）。
    代理商起的名字没有长度上限（清洗只截 120 字符），长名字在这 36px 高的
    一行里必须截断 —— 撑开左栏比少看几个字更伤。
  */
  .brand-fallback-name {
    max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    font-size: 18px;
  }
  .rail-brand span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /*
    登录 / 注册页的品牌块。这一屏没有左栏（上面 body.auth 那几句），
    贴牌用户在这里认出「这是谁家」全靠它 —— 只在有注入品牌时由 app.mjs 渲染，
    主站登录页一个节点都不加。
  */
  .auth-brand { display: flex; align-items: center; gap: 10px; margin-bottom: 22px; }
  .auth-brand-logo { width: 56px; height: 56px; object-fit: contain; }
  .auth-brand-name { font-weight: 650; font-size: 17px; letter-spacing: .01em; }
  .auth-brand > .auth-brand-name:only-child { font-size: 22px; }
  .rail-nav { padding: 4px 10px; display: flex; flex-direction: column; gap: 2px; }
  /* 游客左栏的入口是 div[role=button]：测试和脚本拿 `#tabs button` 当「已进工作区」的信号，游客的不能算。 */
  .rail-nav button, .rail-nav [role="button"] {
    display: flex; align-items: center; gap: 11px; width: 100%;
    padding: 9px 10px; border: 0; border-radius: var(--r-sm);
    background: transparent; color: var(--ink-2); font: inherit; text-align: left; cursor: pointer;
  }
  .rail-nav button:hover, .rail-nav [role="button"]:hover { background: var(--card); color: var(--ink); }
  .rail-nav button[aria-current="true"], .rail-nav [role="button"][aria-current="true"] { background: var(--card-hi); color: var(--ink); }
  /*
    **内联 SVG 必须有默认尺寸。**

    只在 `.rail-nav .ico` 和 `.composer-bar .pill .ico` 上给过宽高 ——
    于是同一个 `icon()` 放进别的地方（会话流那排动作按钮）时没有尺寸，
    在 flex 行里它按内容撑开、把旁边的文字挤成一列竖排的字。
    症状看起来像「文案太长」，实际上和文案无关。
  */
  .ico { width: 15px; height: 15px; flex: 0 0 15px; }
  .rail-nav .ico { width: 18px; height: 18px; flex: 0 0 18px; opacity: .9; }

  .rail-group {
    padding: 18px 20px 6px; font-size: 12px; color: var(--ink-3);
    display: flex; align-items: center; gap: 8px;
  }
  .rail-group .spacer { flex: 1; }
  /* 这个 ＋ 原先是 `font-size:12px` 加 `padding:2px 6px` —— 算下来大约
     18×16 像素的可点区域，比一个指尖还小。用户的反馈是「点了经常跳不出来」：
     有一半是真没点着（旁边 8px 的 gap 和 padding 都不吃点击）。
     现在给足 26×26 并把整块做成命中区，字号也放大到能看清。 */
  .rail-group button {
    border: 0; background: transparent; color: var(--ink-3); cursor: pointer;
    font: inherit; font-size: 15px; line-height: 1; padding: 0;
    width: 26px; height: 26px; flex: 0 0 26px; margin: -6px -6px -6px 0;
    display: grid; place-items: center; border-radius: 7px;
  }
  .rail-group button:hover { background: var(--card); color: var(--ink); }
  .rail-group button:active { background: var(--card-hi); }
  .rail-list { flex: 1; overflow-y: auto; padding: 0 10px 14px; display: flex; flex-direction: column; gap: 1px; }
  .rail-item {
    display: flex; align-items: center; gap: 10px; padding: 8px 10px;
    border: 0; border-radius: var(--r-sm); background: transparent; color: var(--ink-2);
    font: inherit; text-align: left; cursor: pointer; width: 100%;
  }
  .rail-item:hover { background: var(--card); color: var(--ink); }
  .rail-item[aria-current="true"] { background: var(--card-hi); color: var(--ink); }
  .rail-item .thumb {
    width: 26px; height: 26px; flex: 0 0 26px; border-radius: 7px; object-fit: cover;
    background: var(--card-hi); display: grid; place-items: center;
    font-size: 11px; color: rgba(255,255,255,.88); overflow: hidden; font-weight: 600;
  }
  .rail-item .name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .rail-empty { padding: 6px 12px; color: var(--ink-3); font-size: 12.5px; }

  /* 置顶的那个图钉。不占位 —— 没置顶的条目上它不该留一块空白。 */
  .rail-item .pin { display: none; font-size: 10px; opacity: .7; }
  .rail-item[data-pinned="1"] .pin { display: inline; }
  /* 菜单开着的时候图钉让位给 `⋯`，不然两个挤在一起。 */
  .rail-item[data-pinned="1"]:hover .pin { display: none; }

  /* `⋯` 平时不出现 —— 一列十条会话，十个常驻的点会把名字挤没。 */
  .rail-acts { position: relative; flex: 0 0 auto; display: none; }
  .rail-item:hover .rail-acts,
  .rail-item:focus-within .rail-acts { display: block; }
  .rail-more {
    border: 0; background: transparent; color: var(--ink-3); cursor: pointer;
    font: inherit; line-height: 1; padding: 0; width: 22px; height: 22px;
    display: grid; place-items: center; border-radius: 6px;
  }
  .rail-more:hover { background: var(--card-hi); color: var(--ink); }

  /**
   * 菜单**挂在 `<body>` 上，不在左栏里**。
   *
   * `.rail-list` 是 `overflow-y: auto` —— 按规范，一个轴设了 auto，
   * 另一个轴也会变成裁切。菜单留在里面的话会被切掉一半，
   * 而且是「越靠近列表底部切得越狠」，最下面那几条完全看不见。
   *
   * 所以位置由 JS 按按钮的 `getBoundingClientRect` 算出来，写在 style 里。
   */
  .rail-menu {
    position: fixed; z-index: 40; min-width: 148px;
    padding: 5px; border-radius: 10px;
    background: #202026; border: 1px solid var(--line);
    box-shadow: 0 12px 34px rgba(0,0,0,.45);
    display: flex; flex-direction: column; gap: 1px;
  }
  .rail-menu button {
    display: flex; align-items: center; gap: 9px; width: 100%; padding: 7px 9px;
    border: 0; border-radius: 6px; background: transparent; color: var(--ink-2);
    font: inherit; font-size: 12.5px; text-align: left; cursor: pointer; white-space: nowrap;
  }
  .rail-menu button:hover { background: var(--card-hi); color: var(--ink); }
  .rail-menu button .glyph { width: 14px; flex: 0 0 14px; text-align: center; font-size: 11px; }
  .rail-menu button.danger:hover { background: rgba(214,84,84,.16); color: #ff9c9c; }
  .rail-menu button.danger[data-armed="1"] { background: rgba(214,84,84,.16); color: #ff9c9c; }

  /* 就地改名。看起来还是那一行，只是能打字了。 */
  .rail-rename {
    flex: 1; min-width: 0; margin: 0; padding: 2px 6px;
    border: 1px solid var(--accent); border-radius: 5px;
    background: var(--stage); color: var(--ink); font: inherit; font-size: 13px;
  }


  .stage { flex: 1; min-width: 0; height: 100vh; display: flex; flex-direction: column; background: var(--stage); }

  #topbar {
    display: flex; align-items: center; gap: 12px; flex: 0 0 auto;
    padding: 0 20px; height: 54px; border-bottom: 1px solid var(--line-soft);
  }
  #topbar .title { font-weight: 600; font-size: 14.5px; }
  #topbar .sub { color: var(--ink-3); font-size: 12.5px; }
  #topbar .spacer { flex: 1; }
  .chip {
    display: inline-flex; align-items: center; gap: 6px; height: 30px; padding: 0 11px;
    border-radius: 999px; background: var(--card); color: var(--ink-2);
    font-size: 13px; border: 1px solid var(--line-soft); cursor: pointer;
  }
  .chip:hover { background: var(--card-hi); color: var(--ink); }
  .chip .spark { color: var(--accent); }
  .chip .num { color: var(--ink); font-variant-numeric: tabular-nums; font-weight: 600; }

  /*
    ## VIP 徽标

    用**低饱和的旧金**（`#d9b45b`），不是 `#ffd700`。纯金在深色底上会发荧光，
    和这套界面其余部分（低饱和、细边框）完全不搭 —— 而且那种亮度正是
    用户说过的「AI 感」：颜色越猛，越像一个默认模板。

    尊贵靠的是**克制加一致**：同一支金色同时出现在边框、文字和徽记上，
    底色只给 7% 的透明度；整条顶栏上再没有第二处用金。旁边的余额 chip
    是中性灰底，所以这一小块金是那一行里唯一有颜色的东西 —— 那才是重量。

    高度和 `.chip` 一样 30px：它和余额、头像在同一条基线上，
    差一两个像素在一行里看得出来。
  */
  .vip {
    display: inline-flex; align-items: center; gap: 5px; height: 30px; padding: 0 10px;
    border-radius: 999px; font-size: 12px; font-weight: 650; letter-spacing: .06em;
    color: #d9b45b; border: 1px solid rgba(217,180,91,.42);
    background: rgba(217,180,91,.07);
    /* 只有很轻的一层外发光。再重就成了「会员卡」。 */
    box-shadow: 0 0 0 1px rgba(217,180,91,.06), 0 2px 10px rgba(217,180,91,.10);
    cursor: default; user-select: none;
  }
  .vip .crown { width: 13px; height: 13px; flex: 0 0 13px; fill: currentColor; opacity: .95; }
  /*
    头像不做实心圆。

    高饱和的圆底配白色大写首字母是所有 AI 产品的默认头像，而且在这条顶栏上
    它会是唯一一块纯色 —— 旁边的余额 chip 和「退出」都是透明底加一条细边，
    一个填满的圆会把视线从面包屑和下面的画面上拽走。这套界面里 `--accent`
    那一档亮度只留给真要人点的主按钮，头像不在那一档。

    所以退回顶栏原本的语言：透明底 + 细边 + 低对比字母，边和 hover 都跟
    chip 用同一套值，两个元素并排时看着才像一家的。圆角收成 `--r-sm` 的
    方块而不是整圆，是为了跟 chip 的胶囊拉开形状差别 —— 不然一大一小两个
    圆并排会让人以为是同一组控件的两个状态。

    比 chip 矮 2px：它在这条栏上优先级最低，但字母不能再淡了 ——
    那是用户认「我现在登在哪个账号」的唯一线索，所以静止态就给到 --ink-2，
    hover 再升到 --ink。
  */
  .avatar {
    width: 28px; height: 28px; border-radius: var(--r-sm); flex: 0 0 28px; cursor: pointer;
    background: transparent; border: 1px solid var(--line-soft); color: var(--ink-2);
    display: grid; place-items: center; font-size: 12px; font-weight: 600; letter-spacing: .02em;
  }
  .avatar:hover { background: var(--card-hi); border-color: var(--line); color: var(--ink); }

  main { flex: 1; overflow-y: auto; padding: 24px 20px var(--page-pad-b); }
  .wrap { max-width: 900px; margin: 0 auto; }

  /* ---------------------------------------------------------------- 组件 */

  section {
    background: var(--card); border: 1px solid var(--line-soft); border-radius: var(--r-md);
    padding: 20px; margin: 0 auto 16px; max-width: 900px;
  }
  h2 { font-size: 15px; margin: 0 0 4px; font-weight: 650; }
  h2 + .hint { margin-top: 0; }
  .hint { color: var(--ink-2); font-size: 13px; margin: 0 0 14px; }
  label { display: block; font-size: 12.5px; color: var(--ink-2); margin: 14px 0 6px; }

  input, textarea, select {
    width: 100%; padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--r-sm);
    font: inherit; background: #17171b; color: var(--ink); outline: none;
  }
  input::placeholder, textarea::placeholder { color: var(--ink-3); }
  input:focus, textarea:focus, select:focus { border-color: #3d3d47; background: #191920; }
  textarea { min-height: 88px; resize: vertical; line-height: 1.7; }
  /*
    下拉箭头自己画。`appearance:none` 之后原生箭头就没了，
    而**不加 appearance:none 的话 Windows 上的下拉是一块白色** ——
    深色界面里那是最扎眼的一处。
  */
  select {
    appearance: none; padding-right: 30px;
    background-image:
      linear-gradient(45deg, transparent 50%, var(--ink-3) 50%),
      linear-gradient(135deg, var(--ink-3) 50%, transparent 50%);
    background-position: right 16px center, right 11px center;
    background-size: 5px 5px, 5px 5px; background-repeat: no-repeat;
  }
  /* 下拉展开的那一层由系统画，只能靠这两个属性染色。 */
  select option { background: #1c1c20; color: var(--ink); }

  /*
    文件选择按钮。原生的那个在深色底上是一块浅灰方块，配着一句
    「未选择任何文件」的系统文案 —— 整个界面里最像「没做完」的地方。
  */
  input[type="file"] {
    padding: 8px 10px; color: var(--ink-3); font-size: 13px;
    background: #17171b; cursor: pointer;
  }
  input[type="file"]::file-selector-button {
    margin-right: 12px; padding: 7px 14px; border: 0; border-radius: 7px;
    background: var(--card-hi); color: var(--ink); font: inherit; cursor: pointer;
  }
  input[type="file"]::file-selector-button:hover { background: #32323a; }
  input[type="checkbox"], input[type="radio"] { width: auto; accent-color: var(--accent); }

  button {
    padding: 9px 16px; border: 0; border-radius: var(--r-sm);
    background: var(--accent); color: var(--accent-ink);
    font: inherit; font-weight: 500; cursor: pointer;
  }
  button:hover { filter: brightness(1.08); }
  button.secondary { background: var(--card-hi); color: var(--ink); }
  button.secondary:hover { background: #303038; filter: none; }
  button.ghost { background: transparent; color: var(--ink-2); }
  button.ghost:hover { background: var(--card-hi); color: var(--ink); filter: none; }
  button:disabled, button:disabled:hover { opacity: .38; cursor: not-allowed; filter: none; }

  .row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
  .row > * { flex: 0 0 auto; }
  .grow { flex: 1 1 200px; }

  .msg {
    padding: 11px 13px; border-radius: var(--r-sm); font-size: 13.5px; margin: 14px 0 0;
    white-space: pre-wrap; border: 1px solid transparent;
  }
  .msg.err  { background: rgba(255,107,107,.10); color: #ff9b9b; border-color: rgba(255,107,107,.22); }
  .msg.ok   { background: rgba(62,207,142,.10);  color: #6fe0ae; border-color: rgba(62,207,142,.22); }
  .msg.warn { background: rgba(224,163,58,.10);  color: #e8bd6b; border-color: rgba(224,163,58,.22); }

  table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
  th, td { text-align: left; padding: 9px 8px; border-bottom: 1px solid var(--line-soft); }
  tr:last-child td { border-bottom: 0; }
  th { color: var(--ink-3); font-weight: 500; font-size: 12px; }

  pre {
    background: #131317; border: 1px solid var(--line-soft); padding: 13px;
    border-radius: var(--r-sm); overflow: auto; font-size: 12.5px; max-height: 360px;
    color: var(--ink-2); line-height: 1.6;
  }
  code { font-size: 12.5px; color: var(--ink-2); }

  .money { font-variant-numeric: tabular-nums; font-weight: 600; }
  .big { font-size: 26px; }
  .hidden { display: none !important; }
  hr { border: 0; border-top: 1px solid var(--line-soft); margin: 18px 0; }

  /* 旧的顶部标签样式：已经搬到左栏，留一份兜底不让它散架。 */
  .tabs { display: flex; gap: 4px; }
  .tabs button { background: transparent; color: var(--ink-2); padding: 7px 13px; }

  /* ------------------------------------------------- 产出卡（构建记录用） */

  .feed { max-width: 900px; margin: 0 auto; display: flex; flex-direction: column; gap: 26px; }
  .out-head { display: flex; align-items: center; gap: 9px; font-size: 13px; color: var(--ink-2); margin-bottom: 9px; }
  .out-head .name { color: var(--ink); font-weight: 550; }
  .out-head .meta { color: var(--ink-3); }
  .out-head .meta i { font-style: normal; padding: 0 7px; opacity: .45; }
  .out-stage {
    background: var(--bg); border-radius: var(--r-md); overflow: hidden;
    display: grid; place-items: center; min-height: 180px;
  }
  /*
    放片子的舞台**固定高度**。
    `<video>` 在元数据到手之前用的是 300×150 的默认尺寸，不定高的话
    卡片会先窄后宽地跳一下 —— 一屏几条构建就是几次跳动，像页面在抽搐。

    竖屏为主，所以给得高一点；`60vh` 是给笔记本小屏留的余地，
    不然一条片子就占满整屏，看不到它下面的按钮。
  */
  .out-stage.film { height: var(--stage-h); }
  /*
    **两个方向都限死，宽高都交给浏览器按固有比例算。**

    `height: 100%` 会钉死一个方向：元数据还没到的时候 `<video>` 的固有尺寸
    是 300×150，于是盒子成了 300 宽 × 满高，而真实画面是 9:16 ——
    配上舞台的 `overflow: hidden`，上下就被切掉，用户看到的是画面中段一条。

    ⚠️ `max-height: 100%` 在这里**不管用**：舞台是 grid 容器，
    实测 video 的 computed height 仍然是固有的 1280px，照样被裁。
    所以两边都用同一个绝对值（`--stage-h`），不依赖百分比怎么解析。

    `max-*` 加 `auto` 是「完整装下」的标准写法：任何比例的片子都缩到
    刚好放进舞台，四周留黑边，一个像素都不裁。
  */
  .out-stage video {
    display: block; max-width: 100%; max-height: var(--stage-h);
    width: auto; height: auto; object-fit: contain;
    /*
      元数据到手之前 `<video>` 的固有尺寸是 300×150 —— 一屏几条构建
      全是居中的小黑框，看起来像全都没加载出来。

      `auto 9 / 16` 是「有固有比例就用固有的，没有才用这个」：竖屏短视频
      占绝大多数，先按它占好位，加载完由真实比例接管，横屏的片子也不会被压扁。
    */
    aspect-ratio: auto 9 / 16;
  }
  .out-actions { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
  .out-actions button, .out-actions a.pill {
    display: inline-flex; align-items: center; gap: 7px; height: 32px; padding: 0 13px;
    border-radius: var(--r-sm); background: var(--card); color: var(--ink-2);
    border: 1px solid var(--line-soft); font-size: 13px; font-weight: 400; cursor: pointer;
  }
  .out-actions button:hover, .out-actions a.pill:hover {
    background: var(--card-hi); color: var(--ink); filter: none; text-decoration: none;
  }
  /*
    成片切换键：**左半边是「切到这一版播」，右半边是下载。**

    一次构建出带字幕和不带字幕两条片子。分成两排（一排看一排下）的话
    就是四个按钮，而它们两两同名；合成一个按钮又要用户猜点了会发生什么。
    贴成一个 pill 的两半，两个动作各有各的位置，点错的代价只是开始一个下载。
  */
  .film-pill {
    display: inline-flex; align-items: stretch; height: 32px;
    background: var(--card); border: 1px solid var(--line-soft);
    border-radius: var(--r-sm); overflow: hidden;
  }
  .film-pill > button, .film-pill > a {
    border: 0; background: transparent; color: var(--ink-2);
    font-size: 13px; font-weight: 400; display: inline-flex; align-items: center;
    padding: 0 13px; cursor: pointer;
  }
  .film-pill > a { padding: 0 10px; border-left: 1px solid var(--line-soft); font-size: 14px; }
  .film-pill > button:hover, .film-pill > a:hover {
    background: var(--card-hi); color: var(--ink); filter: none; text-decoration: none;
  }
  /* 正在播的那一版。边框而不是填充 —— 填充会和主按钮抢。 */
  .film-pill[aria-current="true"] { border-color: var(--accent); }
  .film-pill[aria-current="true"] > button { color: var(--ink); }
  .film-pill[aria-current="true"] > button::before {
    content: "▶"; font-size: 9px; margin-right: 7px; color: var(--accent);
  }
  /* 只有一个版本时没什么可切的，左半边就只是个标签。 */
  .film-pill[data-single] > button { cursor: default; }
  .film-pill[data-single] > button:hover { background: transparent; color: var(--ink-2); }

  .state-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 7px; display: inline-block; }
  .state-dot.run  { background: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
  .state-dot.ok   { background: var(--ok); }
  .state-dot.bad  { background: var(--danger); }
  .state-dot.idle { background: var(--ink-3); }

  /* ------------------------------------------------------------ 底部输入区 */

  /*
    **输入区固定，内容区自己滚。**

    第一版用的是 `position: sticky; bottom: 0`。sticky 元素在文档流里占的是
    它自己那个位置（最后），滚到最底时不挡人，但**中间的滚动状态会盖住后面的内容** ——
    实测「出片」那张卡有一半压在输入框底下，看起来像页面裂了。

    所以这一页不让 `main` 滚：它变成上下两格，上面那格滚，下面那格钉住。
  */
  main.has-composer { padding: 0; display: flex; flex-direction: column; overflow: hidden; }
  main.has-composer > .feed-scroll { flex: 1; overflow-y: auto; padding: 24px 20px 8px; }
  .composer-dock { flex: 0 0 auto; padding: 8px 20px 18px; background: var(--stage); }
  .composer {
    max-width: 900px; margin: 0 auto; background: var(--card);
    border: 1px solid var(--line-soft); border-radius: 20px; padding: 16px 18px 12px;
  }
  .composer:focus-within { border-color: #33333c; }

  /*
    **正在干什么，就写在输入框上沿。**

    写稿要几十秒、出片要几分钟，而进度原先只画在「出片」那张卡里 ——
    用户刚按完的是底部那个键，视线在这儿，那张卡多半还在视口外。
    他看到的是按钮变灰，然后什么都没有。
  */
  .composer-status {
    display: flex; align-items: center; gap: 9px; margin-bottom: 12px;
    padding: 9px 12px; border-radius: 10px; font-size: 13px;
    background: var(--accent-soft); color: #a8c3ff; border: 1px solid rgba(91,140,255,.25);
  }
  .composer-status .spin {
    width: 13px; height: 13px; flex: 0 0 13px; border-radius: 50%;
    border: 2px solid rgba(168,195,255,.3); border-top-color: #a8c3ff;
    animation: spin 0.8s linear infinite;
  }
  @keyframes spin { to { transform: rotate(360deg); } }

  /* 状态条只显示实际耗时、最近阶段和活动提示，不把计时器伪装成完成比例。 */
  @keyframes agent-breath { 0%,100% { opacity: .25 } 50% { opacity: 1 } }
  .agent-work { padding: 8px 0 2px; }
  .agent-work[hidden] { display: none; }
  .agent-work .work-head { display: flex; gap: 8px; align-items: baseline; }
  .agent-work .work-dot {
    color: var(--accent, #4c7ef3); animation: agent-breath 1.6s ease-in-out infinite;
  }
  .agent-work .work-action { color: var(--ink-2); font-size: 13px; font-weight: 550; }
  .agent-work .work-meta {
    margin-left: auto; color: var(--ink-3, #888); font-size: 12.5px;
    font-variant-numeric: tabular-nums; white-space: nowrap;
  }
  .agent-work .work-eta::before { content: " · "; }
  .agent-work .work-bar {
    height: 3px; margin-top: 6px; border-radius: 2px; overflow: hidden;
    background: color-mix(in srgb, var(--ink-3, #888) 22%, transparent);
  }
  .agent-work .work-bar-fill {
    height: 100%; width: 0; border-radius: 2px;
    background: var(--accent, #4c7ef3);
    transition: width 1s ease-out;
  }
  /* 写稿没有可测的完成百分比；显示活动提示，避免五分钟后长期停在 95%。 */
  @keyframes agent-working { from { transform: translateX(-100%); } to { transform: translateX(340%); } }
  .agent-work.working .work-bar-fill { width: 30%; animation: agent-working 1.8s linear infinite; }
  @media (prefers-reduced-motion: reduce) {
    .agent-work.working .work-bar-fill { animation: none; }
  }
  /* 标题不许被压成一字一行；说明里带长文件名（微信图片的名字有一百多个字符、没有空格）时
     在条内省略，不撑破输入框。 */
  .composer-status .what { color: #cfdcff; font-weight: 500; flex: 0 0 auto; white-space: nowrap; }
  .composer-status .why { color: #8fa7d8; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* 跑完了、或者只是陈述一个事实时，不要转圈 —— 转圈意味着「还在动」。 */
  .composer-status.idle { background: var(--card-hi); color: var(--ink-2); border-color: var(--line); }
  .composer-status.idle .what { color: var(--ink); }
  .composer-status.idle .why { color: var(--ink-3); }

  /* 上半部分：左边一个上传方块，右边文字区。 */
  .composer-top { display: flex; gap: 14px; align-items: flex-start; }
  /*
    **上传是一个方块，不是一个小加号。**
    即梦那边把它做成一张虚线占位卡，和文字并排 —— 一眼就知道「这里能放东西」，
    而一个 30px 的 `＋` 藏在一排 pill 里，要找。
  */
  .drop-card {
    flex: 0 0 66px; width: 66px; height: 82px; border-radius: 12px;
    border: 1px dashed #3a3a44; background: #17171b; color: var(--ink-3);
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
    font-size: 11px; cursor: pointer; padding: 0; line-height: 1.3;
  }
  .drop-card:hover { border-color: var(--accent); color: var(--ink-2); background: #1b1b21; filter: none; }
  .drop-card .big { font-size: 24px; line-height: 1; font-weight: 200; }
  .composer-text { flex: 1; min-width: 0; }
  .composer textarea {
    border: 0; background: transparent; padding: 2px 0; min-height: 78px; font-size: 14px;
    line-height: 1.75; color: var(--ink);
  }
  .composer textarea:focus { background: transparent; }
  .composer textarea:disabled { color: var(--ink-3); }

  /* 下半部分：一排参数，右下角发送。 */
  .composer-bar { display: flex; align-items: center; gap: 4px; margin-top: 4px; flex-wrap: wrap; }
  .composer-bar .spacer { flex: 1; min-width: 12px; }
  .composer-bar .pill {
    display: inline-flex; align-items: center; gap: 7px; height: 32px; padding: 0 10px;
    border-radius: var(--r-sm); background: transparent; color: var(--ink-2);
    border: 0; font-size: 13px; cursor: pointer; font-weight: 400; white-space: nowrap;
  }
  .composer-bar .pill:hover { background: var(--card-hi); color: var(--ink); filter: none; }
  .composer-bar .pill .ico { width: 15px; height: 15px; flex: 0 0 15px; opacity: .85; }
  /* 当前模式 —— 即梦把「视频生成」那个做成蓝的，因为它说的是「这一下会干什么」。 */
  .composer-bar .pill.mode { color: var(--accent); cursor: default; }
  .composer-bar .pill.mode:hover { background: transparent; color: var(--accent); }
  /* 已经有了的素材：亮一点，并且带一个数。没有的保持灰，点了就去选。 */
  .composer-bar .pill[data-has="1"] { color: var(--ink); }
  .composer-bar .pill[data-has="1"] .ico { opacity: 1; color: var(--accent); }
  .composer-bar .sep { width: 1px; height: 16px; background: var(--line); margin: 0 5px; }
  /*
    「贴视频链接」。**和旁边那两个事实 pill 长得不一样，因为它能点。**

    「跟参考片」是标签，点了什么也不会发生。混在一起的话
    这个真能点的按钮会被当成装饰跳过去 —— 所以给它一条边，
    和一排没有边的灰字分开。
  */
  .composer-bar .pill.link-open {
    color: var(--ink-2); border: 1px solid var(--line); background: transparent;
  }
  .composer-bar .pill.link-open:hover { border-color: var(--accent); color: var(--ink); }

  /*
    贴链接那块面板。

    摆在输入框**上方**（不是弹出层）：它一次只开一个，而且开着的时候
    用户手上正好有一段刚复制的东西要粘。做成浮层的话，点一下别处就收了，
    而「点一下别处」在粘贴这个动作里太容易发生（去切个输入法都算）。
  */
  .link-panel {
    margin-bottom: 10px; padding: 10px; border-radius: var(--r-md);
    background: #17171b; border: 1px solid var(--line);
  }
  .link-row { display: flex; gap: 8px; align-items: center; }
  .link-row input {
    flex: 1; min-width: 0; height: 34px; padding: 0 10px; border-radius: var(--r-sm);
    border: 1px solid var(--line); background: var(--card); color: var(--ink); font-size: 13px;
  }
  .link-row input:focus { border-color: var(--accent); outline: none; }
  .link-row .send { flex: 0 0 auto; }
  .link-panel .hint { margin: 8px 0 0; font-size: 11.5px; display: flex; flex-wrap: wrap; gap: 4px 12px; }
  .link-panel .link-note { color: var(--ink-3); }

  /* 打 `@` 弹出来的素材清单。 */
  .composer-text { position: relative; }
  .mentions {
    position: absolute; bottom: 100%; left: 0; z-index: 30; margin-bottom: 6px;
    min-width: 280px; max-width: 460px; max-height: 240px; overflow-y: auto;
    background: #202026; border: 1px solid var(--line); border-radius: var(--r-md);
    padding: 5px; box-shadow: 0 12px 34px rgba(0,0,0,.5);
  }
  .mentions-head { padding: 6px 10px 8px; font-size: 12px; color: var(--ink-3); }
  .mentions button {
    display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
    background: transparent; color: var(--ink-2); font-size: 13px; font-weight: 400;
    padding: 6px 8px; border-radius: 8px;
  }
  .mentions button:hover { background: var(--card-hi); color: var(--ink); filter: none; }
  /* 缩略图比 id 好认 —— 用户记得住「那张手持的图」，记不住 `img-2`。 */
  .mentions button .face {
    width: 34px; height: 34px; flex: 0 0 34px; border-radius: 7px; object-fit: cover;
    background: var(--card-hi); display: grid; place-items: center; font-size: 15px;
  }
  /* 参考片的脸是第一帧 <video>，铺满 34px 的小格 —— 和图片同一套规矩。 */
  .mentions button .face > video, .mentions button .face > img {
    width: 100%; height: 100%; object-fit: cover; display: block; background: #000;
  }
  .mentions button .who { flex: 1; min-width: 0; }
  .mentions button .who b { display: block; color: var(--accent); font-weight: 500; font-size: 12.5px; }
  .mentions button .who > span {
    display: block; color: var(--ink-3); font-size: 11.5px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .mentions button .tip { color: var(--ink-3); font-size: 11.5px; flex: 0 0 auto; }
  /*
    发送键有两副面孔（`paintSend` 的 bare 参数决定）：

    · 底部主发送键（.send.round）：即梦的样子 —— 白圆、黑色向上箭头、
      没有字（用户拿即梦的截图点名要的）。暗色界面上白色就是最高视觉
      权重，品牌蓝只留给别处的小强调。当前动作挂在 title 上。
    · 「贴链接」那行的键（.send）：保留「开始下载」四个字 —— 那就是它的
      动作，光杆箭头没人知道按下去是去下载。
  */
  .send {
    height: 34px; padding: 0 6px 0 15px; border-radius: 999px;
    display: inline-flex; align-items: center; gap: 9px; background: var(--accent);
    font-size: 13px; font-weight: 500;
  }
  .send .arrow {
    width: 24px; height: 24px; border-radius: 50%; flex: 0 0 24px;
    background: rgba(255,255,255,.22); display: grid; place-items: center; font-size: 13px;
  }
  .send:disabled { background: var(--card-hi); color: var(--ink-3); }
  .send:disabled .arrow { background: rgba(255,255,255,.07); }

  /* 主发送键：即梦那颗的两态复刻（对着它的两张特写逐项量的），27px 正圆
     （原 40px 的 2/3，用户定的）、无边框无阴影、线条箭头高约直径 45%。
     两态的差别只在**底色和箭头色**，形状尺寸一动不动：
       · 可提交 —— 纯白 #fff 底 + 近黑 #1a1a1a 箭头，整行最亮的元素；
       · 不可提交 —— 深灰 #3a3a3c 底 + 中灰 #8e8e93 箭头，降两档但看得见。 */
  .send.round {
    width: 27px; height: 27px; padding: 0; border-radius: 50%; flex: 0 0 27px;
    display: grid; place-items: center;
    background: #fff; color: #1a1a1a;
  }
  .send.round .arrow {
    width: 12px; height: 12px; flex: none; display: block;
    background: none; border-radius: 0;
  }
  .send.round:disabled { background: #3c3e40; color: #e2e4e4; }
  .send.round:disabled .arrow { background: none; }

  /* ------------------------------------------------- 素材托盘（那一叠扑克牌）

     即梦把这条生成用到的全部参考收在**左上角一小叠**里：几张缩略图错开着
     叠在一起，右下角挂一个 ＋。占地约 70×80，不管你挂了一个还是八个。

     我们原先是一排横向铺开的卡片（一张 260px 宽，带文件名和大小）——
     传三个文件就把整行占满，把提示词挤到下一行去；传八个就是两行。
     那一排显示的信息（完整文件名、字节数）也没人需要：用户知道自己刚选了什么。 */

  .tray { position: relative; flex: 0 0 76px; width: 76px; height: 86px; }
  /* 空的时候就是原来那张虚线方块 —— 这时候「＋」要大、要明显。 */
  .tray.empty .deck { display: none; }
  .tray.empty .tray-add {
    position: static; width: 100%; height: 100%; border-radius: 12px;
    border: 1px dashed #3a3a44; background: #17171b; color: var(--ink-3);
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
    font-size: 11px; line-height: 1.3; box-shadow: none;
  }
  .tray.empty .tray-add:hover { border-color: var(--accent); color: var(--ink-2); background: #1b1b21; }
  .tray.empty .tray-add .plus { font-size: 24px; line-height: 1; font-weight: 200; }
  .tray:not(.empty) .tray-add .label { display: none; }

  .deck { position: absolute; left: 4px; top: 4px; width: 56px; height: 74px; cursor: pointer; }
  /*
    每张卡**绝对定位在同一处**，靠 `--r` / `--x` 错开。
    完全重合看起来是一张；排开就又回到那一排横条 —— 错开一点点，
    「这里不止一个」这个信息就传达到了，而占地没有变。
  */
  .deck .card {
    position: absolute; inset: 0; border-radius: 9px; overflow: hidden;
    background: var(--card-hi); border: 1.5px solid #2f2f38;
    display: grid; place-items: center; font-size: 20px;
    transform: rotate(var(--r)) translateX(var(--x));
    box-shadow: 0 2px 8px rgba(0,0,0,.45);
    transition: transform .14s;
  }
  .deck .card > img { width: 100%; height: 100%; object-fit: cover; display: block; }
  /* 鼠标放上去时整叠轻轻散开 —— 告诉用户它是可以点开的。 */
  .deck:hover .card { transform: rotate(calc(var(--r) * 1.9)) translateX(calc(var(--x) * 1.9)); }
  /* 还在传的那张压一层暗色 + 转圈。 */
  .deck .card.busy::after {
    content: ""; position: absolute; inset: 0; background: rgba(0,0,0,.5);
  }
  .deck .card .spin {
    position: absolute; z-index: 1; width: 16px; height: 16px; border-radius: 50%;
    border: 2px solid rgba(255,255,255,.25); border-top-color: #fff;
    animation: spin .8s linear infinite;
  }
  .deck .count {
    position: absolute; right: -6px; top: -6px; z-index: 5; min-width: 18px; height: 18px;
    padding: 0 4px; border-radius: 9px; background: var(--accent); color: #fff;
    font-size: 11px; font-weight: 600; display: grid; place-items: center;
    box-shadow: 0 0 0 2px var(--card);
  }
  /* 视频那张左下角标个时长位，和资产页一致。 */
  .deck .card .clock {
    position: absolute; left: 3px; bottom: 3px; z-index: 1; padding: 0 3px; border-radius: 3px;
    background: rgba(0,0,0,.6); color: #fff; font-size: 9px; line-height: 13px;
  }

  /* ＋：挂在那一叠的右下角，压在上面。 */
  .tray-add {
    position: absolute; right: 0; bottom: 0; z-index: 6;
    width: 26px; height: 26px; padding: 0; border-radius: 50%; border: 2px solid var(--card);
    background: #3a3a44; color: var(--ink); font-size: 15px; line-height: 1;
    display: grid; place-items: center; cursor: pointer;
  }
  .tray-add:hover { background: var(--accent); color: #fff; filter: none; }

  /* 点开那一叠：一行一个，能看清、能删。 */
  .tray-open {
    /*
      ⚠️ `position: fixed`，落点和高度由 JS 在打开那一刻按视口算。

      原先是 `absolute` 挂在托盘上往上顶、高度写死 —— 往上顶会伸进
      `main.has-composer` 的 `overflow: hidden` 裁剪线：窗口矮的时候弹层
      顶端那几行画在裁剪线以外，**看得见弹层、看不见头几行**，而弹层自己
      的滚动条滚到顶也够不着（实测原话：滚动条滚不上去了，看不到参考片
      —— 参考片恰好排在第一行）。fixed 不受任何祖先裁剪，算好再放。
    */
    position: fixed; z-index: 30;
    min-width: 260px; max-width: 380px; overflow-y: auto;
    background: #202026; border: 1px solid var(--line); border-radius: var(--r-md);
    padding: 5px; box-shadow: 0 12px 34px rgba(0,0,0,.5);
  }
  .tray-open .row {
    display: flex; align-items: center; gap: 9px; padding: 6px 8px; border-radius: 8px;
  }
  .tray-open .row:hover { background: var(--card-hi); }
  .tray-open .face {
    width: 32px; height: 32px; flex: 0 0 32px; border-radius: 6px;
    background: var(--card-hi); display: grid; place-items: center; font-size: 14px;
    /*
      ⚠️ `overflow` 和里面那张 `img` 的尺寸都不能省。

      这一格里放的可能是一个字符（🎬 / 🎵），也可能是一张真图。
      `<img>` 自己没有宽高时按**固有尺寸**铺开 —— 一张 2160×3840 的人物照
      会把整个弹出层糊满，文字压在图底下完全读不了，
      而 `.face` 上那句 `object-fit: cover` 管的是它自己，不是子元素。
    */
    overflow: hidden;
  }
  .tray-open .face > img { width: 100%; height: 100%; object-fit: cover; display: block; }
  .tray-open .who { flex: 1; min-width: 0; }
  .tray-open .who b {
    display: block; color: var(--ink); font-weight: 400; font-size: 12.5px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .tray-open .who span { color: var(--ink-3); font-size: 11.5px; }
  /* 序号要显眼 —— 它是用户要在提示词里打出来的那串字。 */
  .tray-open .alias {
    color: #9db8ff; background: var(--accent-soft);
    padding: 0 4px; border-radius: 4px; font-size: 11px;
  }
  .tray-open .drop {
    border: 0; background: transparent; color: var(--ink-3); cursor: pointer;
    padding: 3px 6px; line-height: 1; font-size: 15px; border-radius: 6px; flex: 0 0 auto;
  }
  .tray-open .drop:hover { background: rgba(255,107,107,.15); color: #ff9b9b; filter: none; }

  /*
    参考片的脸是一个 `<video preload="metadata">`（第一帧），规则照抄
    `.face > img` 那一套：父级给多大它铺多大。时长角标（即梦同款 00:15）
    等元数据到了由 JS 填进 `.dur`。
  */
  .face-video { position: relative; display: block; width: 100%; height: 100%; overflow: hidden; }
  .face-video > video, .face-video > img { width: 100%; height: 100%; object-fit: cover; display: block; background: #000; }
  .face-video .dur {
    position: absolute; right: 3px; bottom: 3px; font-size: 10px; line-height: 14px;
    padding: 0 4px; border-radius: 4px; color: #fff; background: rgba(0,0,0,.65);
  }

  /* 悬停放大（即梦同款）：素材行上悬停，旁边浮一张大图 / 能播的视频。 */
  .tray-peek {
    position: fixed; z-index: 60; width: 280px;
    background: #202026; border: 1px solid var(--line); border-radius: 12px;
    padding: 6px; box-shadow: 0 14px 40px rgba(0,0,0,.55);
  }
  .tray-peek .cap {
    font-size: 11.5px; color: var(--ink-2); padding: 2px 4px 6px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .tray-peek .frame {
    width: 266px; height: min(440px, calc(100vh - 120px));
    background: #000; border-radius: 8px; overflow: hidden; display: grid; place-items: center;
  }
  .tray-peek .frame > img, .tray-peek .frame > video {
    width: 100%; height: 100%; object-fit: contain; display: block;
  }

  /* 已经选好、还没发出去的文件。 */
  .attachments { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
  .attach {
    display: flex; align-items: center; gap: 8px; padding: 6px 8px 6px 6px;
    background: #17171b; border: 1px solid var(--line); border-radius: 10px;
    font-size: 12.5px; color: var(--ink-2); max-width: 260px;
  }
  .attach .face {
    width: 34px; height: 34px; flex: 0 0 34px; border-radius: 7px; object-fit: cover;
    background: var(--card-hi); display: grid; place-items: center; font-size: 15px;
  }
  .attach .who { overflow: hidden; }
  .attach .who b { display: block; color: var(--ink); font-weight: 500; font-size: 12.5px; }
  .attach .who span { color: var(--ink-3); font-size: 11.5px; }
  .attach .drop {
    border: 0; background: transparent; color: var(--ink-3); cursor: pointer;
    padding: 2px 4px; line-height: 1; font-size: 15px; border-radius: 5px;
  }
  .attach .drop:hover { background: var(--card-hi); color: var(--ink); filter: none; }

  /* `+` 那个菜单。 */
  .menu-wrap { position: relative; }
  .menu {
    position: absolute; bottom: 38px; left: 0; z-index: 20; min-width: 170px;
    background: #202026; border: 1px solid var(--line); border-radius: var(--r-md);
    padding: 5px; box-shadow: 0 12px 34px rgba(0,0,0,.5);
  }
  .menu button {
    display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
    background: transparent; color: var(--ink-2); font-size: 13px; font-weight: 400;
    padding: 8px 10px; border-radius: 7px;
  }
  .menu button:hover { background: var(--card-hi); color: var(--ink); filter: none; }
  .menu button .tip { margin-left: auto; color: var(--ink-3); font-size: 11.5px; }

  /* 素材缩略图墙 —— 比一行一行的表格省一半竖向空间。 */
  .tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(156px, 1fr)); gap: 10px; }
  .tile { background: #17171b; border: 1px solid var(--line-soft); border-radius: 10px; overflow: hidden; }
  .tile > img { width: 100%; height: 104px; object-fit: cover; display: block; background: var(--bg); }
  .tile .body { padding: 8px; display: flex; flex-direction: column; gap: 6px; }
  .tile select, .tile input { padding: 5px 8px; font-size: 12.5px; }
  /* 当前主播那一格。全片的长相由它固定 —— 一眼要能认出是哪张。 */
  .tile[data-host="1"] { border-color: var(--accent); }
  .tile .body > button { padding: 5px 9px; font-size: 12px; }
  .tile .foot { display: flex; align-items: center; gap: 6px; }
  .tile .foot .hint { margin: 0; font-size: 11.5px; flex: 1; }
  .tile .foot button { padding: 4px 9px; font-size: 12px; }
  .voice-row {
    display: flex; align-items: center; gap: 12px; padding: 10px 12px;
    background: #17171b; border: 1px solid var(--line-soft); border-radius: 10px;
  }
  .voice-row audio { height: 32px; flex: 1; min-width: 0; }

  /* ============================================================== 会话流
     一个项目 = 一条会话。这一页从上到下是**时间**：拆参考片、出片、再出片。
     它替掉了原先那三张「第 1/2/3 步」的卡 —— 那三张说的是「系统现在什么状态」，
     而用户回来时想知道的是「上一条出成什么样了」。 */

  /* 日期分隔。做得比正文大一圈 —— 它是这条流里唯一的锚点，
     用户滚动时靠它判断「翻到多久以前了」。 */
  .day {
    font-size: 19px; font-weight: 650; color: var(--ink);
    margin: 30px 0 14px; letter-spacing: .01em;
  }
  .day:first-child { margin-top: 4px; }

  /*
    「这条会话用的是你自己调过的字幕样式」。**中性状态说明，不是警告。**

    没有底色、没有边框、没有图标 —— 和 `.msg warn` 那种黄底条刻意区分开：
    这不是出错了，是用户自己选的，画成警告会让人以为片子哪里坏了。

    `align-items: baseline` 而不是 `center`：右边那颗按钮的字要和左边那行灰字
    坐在**同一条基线**上。居中对齐时按钮的内边距会把它顶下去半行，
    看起来像两个不相干的东西凑在一行。

    只有这条会话脱离了平台默认时才会出现（见 app.mjs 的 `recipeNote`），
    所以它不占常态版面。
  */
  .recipe-note {
    display: flex; align-items: baseline; gap: 12px;
    margin: 4px 0 20px; flex-wrap: wrap;
  }
  /* `.hint` 自带 `margin: 0 0 14px`，在这一行里会把按钮拽歪。 */
  .recipe-note .hint { margin: 0; flex: 1 1 260px; }
  .recipe-note button { flex: 0 0 auto; font-size: 12.5px; padding: 6px 11px; }
  /* 恢复成功之后原地换成一条 `.msg ok`，它不该再顶着上面那 4px。 */
  .recipe-note > .msg { margin-top: 0; width: 100%; }

  .shot { margin-bottom: 26px; }
  /* 提示词那一行：左边叠着参考素材，右边是话。 */
  .shot-say { display: flex; align-items: flex-start; gap: 11px; margin-bottom: 9px; }
  /*
    参考素材缩略图**叠着放**，不是排开。
    一条记录可能带六七张图，排开就是一整行缩略图把提示词挤到下一行；
    叠起来占两个格子的宽度，而「这次带了参考」这个信息一样传达到了。
  */
  .shot-refs { display: flex; flex: 0 0 auto; padding-right: 6px; }
  .shot-refs > * {
    width: 34px; height: 34px; border-radius: 8px; object-fit: cover;
    background: var(--card-hi); border: 2px solid var(--stage);
    display: grid; place-items: center; font-size: 15px; flex: 0 0 34px;
  }
  .shot-refs > * + * { margin-left: -14px; }
  .shot-refs .more { font-size: 11px; color: var(--ink-2); font-weight: 600; }
  .shot-said { flex: 1; min-width: 0; font-size: 14px; line-height: 1.7; }
  /* @素材 在正文里画成 chip —— 和即梦一样，一眼看出这句话指了哪几样东西。 */
  .mention {
    display: inline-flex; align-items: center; gap: 4px; vertical-align: -3px;
    padding: 1px 7px 1px 4px; margin: 0 1px; border-radius: 6px;
    background: var(--accent-soft); color: #9db8ff; font-size: 12.5px;
  }
  .mention img, .mention .glyph {
    width: 16px; height: 16px; border-radius: 4px; object-fit: cover;
    display: grid; place-items: center; font-size: 10px; background: rgba(255,255,255,.08);
  }
  /* 没提示词的那些（老记录、或者从素材直接出的片）用一句灰字占位，
     不要留空 —— 空着看起来像渲染坏了。 */
  .shot-said.none { color: var(--ink-3); }

  /* 参数行。即梦是紧跟在提示词后面的同一段灰字，我们照做。 */
  .shot-meta { color: var(--ink-3); font-size: 12.5px; margin: -4px 0 10px 45px; }
  .shot-meta i { font-style: normal; padding: 0 7px; opacity: .45; }
  .shot-meta .more {
    border: 0; background: transparent; color: var(--ink-3); cursor: pointer;
    font: inherit; padding: 0; text-decoration: underline; text-underline-offset: 3px;
  }
  .shot-meta .more:hover { color: var(--ink-2); filter: none; }

  .shot-body { margin-left: 45px; }
  /*
    产出还没拉回来时，**先把片子那块地方占住**。

    不占的话这一格只有一行小字高，`fillShot` 一填就撑到 360px ——
    一屏几条记录同时撑开，页面整个往下窜，而用户可能正看着中间某一段。
  */
  .shot-body .shot-loading {
    height: var(--shot-h); max-width: 560px; margin: 0;
    display: grid; place-items: center;
    background: var(--bg); border-radius: var(--r-md);
  }
  /* 竖屏片子在会话流里比「出片」那一页小一档 —— 这里一屏要装下好几条。 */
  .shot-body .out-stage.film { height: var(--shot-h); max-width: 560px; }
  .shot-body .out-stage video { max-height: var(--shot-h); }
  .shot-acts { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
  .shot-acts button, .shot-acts a.pill, .shot-acts .film-pill {
    height: 30px; font-size: 12.5px;
  }
  .shot-acts button, .shot-acts a.pill {
    display: inline-flex; align-items: center; gap: 6px; padding: 0 12px;
    border-radius: var(--r-sm); background: var(--card); color: var(--ink-2);
    border: 1px solid var(--line-soft); font-weight: 400; cursor: pointer;
    /* 这一排会换行（`flex-wrap`），而 flex 项默认可以被压缩 ——
       不钉住的话「重新编辑」会被挤成四行竖排的字。 */
    flex: 0 0 auto; white-space: nowrap;
  }
  .shot-acts button:hover, .shot-acts a.pill:hover {
    background: var(--card-hi); color: var(--ink); filter: none; text-decoration: none;
  }
  /*
    删除键。**静止时比旁边两个更暗** —— 它是这一排里唯一不可逆的动作，
    不该和「重新编辑」争同样的注意力。

    点第一下进入 `armed`，整个键变红并把后果写在自己身上
    （「真删？3 个成片一起没」）—— 和「确认花 4.49 元」是同一条规矩：
    把代价写进按钮的文字里，用户不可能「没注意到」。
  */
  .shot-acts button.danger { color: var(--ink-3); }
  .shot-acts button.danger:hover {
    color: #ff9b9b; background: rgba(255,107,107,.08); border-color: rgba(255,107,107,.28);
  }
  .shot-acts button.danger[data-armed="1"],
  .shot-acts button.danger[data-armed="1"]:hover {
    color: #ff9b9b; background: rgba(255,107,107,.14); border-color: rgba(255,107,107,.45);
  }

  .shot-detail {
    margin: 10px 0 0 45px; padding: 12px 14px; border-radius: var(--r-md);
    background: #17171b; border: 1px solid var(--line-soft); font-size: 12.5px;
  }
  .shot-detail dl { display: grid; grid-template-columns: auto 1fr; gap: 4px 16px; margin: 0; }
  .shot-detail dt { color: var(--ink-3); }
  .shot-detail dd { margin: 0; color: var(--ink-2); word-break: break-all; }

  /* 还在跑 / 跑挂了的那两种，片子的位置上放状态。 */
  .shot-wait {
    margin-left: 45px; display: flex; align-items: center; gap: 10px;
    padding: 16px 18px; border-radius: var(--r-md);
    background: var(--card); border: 1px solid var(--line-soft); color: var(--ink-2);
    max-width: 560px; font-size: 13px;
  }

  .help h1 { font-size: 24px; margin: 0 0 16px; }
  .help-toc { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 24px; }
  .help-toc a { padding: 5px 12px; border-radius: 999px; background: var(--card); color: var(--ink-2); font-size: 13px; text-decoration: none; }
  .help-toc a:hover { background: var(--card-hi); color: var(--ink); }
  .help-section { margin: 0 0 32px; scroll-margin-top: 16px; }
  .help-section h2 { font-size: 18px; margin: 0 0 10px; }
  .help-section p, .help-section li { line-height: 1.75; color: var(--ink-2); }
  .help-section ol, .help-section ul { padding-left: 22px; margin: 8px 0; }
  .help-example { margin: 12px 0; padding: 12px 14px; border-radius: 12px; background: var(--card); border-left: 3px solid var(--accent); }
  .help-example p { margin: 4px 0; }
  .help-label { font-size: 12px; color: var(--ink-3); margin-bottom: 4px; }
  .help-case { margin: 10px 0; padding: 12px 14px; border-radius: 12px; background: var(--card); }
  .help-case h4 { margin: 0 0 6px; font-size: 14.5px; }
  .help-case p { margin: 4px 0; }
  .help-say { color: var(--ink) !important; }
  .help-tip { padding: 8px 12px; border-radius: 10px; background: var(--card-hi); font-size: 13px; }
  .tpl h1 { font-size: 24px; margin: 0 0 6px; }
  .tpl-tabs { display: inline-flex; gap: 4px; margin: 16px 0 0; padding: 4px; border-radius: 999px; background: var(--card); }
  .tpl-tab { all: unset; cursor: pointer; padding: 6px 18px; border-radius: 999px; font-size: 14px; color: var(--ink-2); }
  .tpl-tab[aria-pressed="true"] { background: var(--card-hi); color: var(--ink); font-weight: 600; }
  .tpl-form { max-width: 420px; }
  .tpl-form input, .tpl-form select { width: 100%; box-sizing: border-box; margin-bottom: 8px; }
  .tpl-chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 14px 0 16px; }
  .tpl-chips .skill[aria-pressed="true"] { background: var(--accent); color: #fff; border-color: var(--accent); }
  .tpl-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 16px; }
  .tpl-card { all: unset; box-sizing: border-box; cursor: pointer; display: flex; flex-direction: column; gap: 6px;
    padding: 10px; border-radius: 14px; background: var(--card); border: 1px solid var(--line-soft); }
  .tpl-card:hover { background: var(--card-hi); }
  .tpl-cover { position: relative; aspect-ratio: 9 / 14; border-radius: 10px; overflow: hidden; background: #000; }
  .tpl-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
  .tpl-nocover { display: grid; place-items: center; width: 100%; height: 100%; font-size: 28px; }
  .tpl-dur { position: absolute; right: 6px; bottom: 6px; padding: 1px 7px; border-radius: 999px; background: rgba(0,0,0,.6); color: #fff; font-size: 12px; }
  .tpl-title { font-weight: 600; font-size: 14px; }
  .tpl-hl { font-size: 12.5px; color: var(--ink-2); min-height: 2.6em; line-height: 1.3; }
  .tpl-meta { display: flex; flex-wrap: wrap; gap: 4px 10px; font-size: 12px; color: var(--ink-3); }
  .tpl-overlay { position: fixed; inset: 0; background: rgba(0,0,0,.65); display: grid; place-items: center; z-index: 100; padding: 20px; }
  .tpl-modal { display: flex; gap: 20px; max-width: 760px; width: 100%; max-height: 90vh; overflow: auto;
    background: var(--card-hi); border-radius: 16px; padding: 18px; }
  .tpl-modal-video { flex: 0 0 240px; }
  .tpl-modal-video video { width: 100%; border-radius: 10px; background: #000; max-height: 70vh; }
  .tpl-modal-side { flex: 1; min-width: 0; }
  .tpl-modal-side h3 { margin: 0 0 4px; }
  .tpl-use { margin-right: 8px; }
  .tpl-item { display: flex; flex-direction: column; gap: 6px; }
  .tpl-manage { display: flex; gap: 8px; }
  .tpl-manage button { flex: 1; font-size: 12.5px; padding: 4px 0; }
  @media (max-width: 640px) { .tpl-modal { flex-direction: column; } .tpl-modal-video { flex-basis: auto; } }
  .more-wrap { position: relative; display: inline-block; }
  .more-menu {
    position: absolute; left: 0; top: calc(100% + 6px); z-index: 20; min-width: 140px;
    display: flex; flex-direction: column; gap: 2px; padding: 6px;
    background: var(--card-hi); border: 1px solid var(--line); border-radius: 12px;
    box-shadow: 0 8px 24px rgba(0,0,0,.35);
  }
  .more-menu[hidden] { display: none; }
  .more-menu button { justify-content: flex-start; width: 100%; background: transparent; border: 0; }
  .more-menu button:hover { background: var(--card); }

  /* ---------------------------------------------------------- 空会话的落地屏 */
  /*
    即梦打开是一句「你好，今天想要创作什么?」加一个居中的输入框。
    我们这儿第一次进来的人面对的是一个空项目 —— 同样的问题，同样的答案：
    **先说话，别先解释**。
  */
  .hello { display: flex; flex-direction: column; align-items: center; padding: 7vh 0 0; }
  .hello h1 { font-size: 32px; font-weight: 700; margin: 0 0 14px; letter-spacing: .01em; }
  /* ---- 首页（空会话的问候屏）：顶部流动渐变 + 下方模板墙 ---- */
  .home-wrap { max-width: 1180px; }
  .hello { position: relative; isolation: isolate; }
  /*
    输入框的宽度对照即梦：同一张 2936px 宽的截图里，它的框占内容区约 83%、折合约 1020 CSS px，
    我们原来的 760px 只有它的四分之三，看上去像「一个小表单」而不是「这一屏的主角」。
  */
  .hello > .composer, .hello > .skills, .hello > .hello-hint, .hello > .msg { max-width: 1020px; }
  .home-glow {
    position: absolute; z-index: -1; pointer-events: none; left: 50%; top: -8vh;
    width: min(1200px, 100%); height: 440px; transform: translateX(-50%);
    filter: blur(70px); opacity: .28;
    background:
      radial-gradient(38% 60% at 24% 42%, rgba(70,120,255,.65), transparent 72%),
      radial-gradient(34% 56% at 72% 30%, rgba(165,90,255,.5), transparent 72%),
      radial-gradient(30% 50% at 52% 78%, rgba(40,200,225,.4), transparent 72%);
    animation: glowDrift 16s ease-in-out infinite alternate;
  }
  @keyframes glowDrift {
    from { transform: translateX(-50%) translateY(0) scale(1); }
    to   { transform: translateX(-46%) translateY(16px) scale(1.07); }
  }
  .hello h1 { color: #fff; }
  /* 和输入框同宽、同一条中轴线：模板墙不再贴着页面左边铺开。 */
  .home-wall { align-self: center; width: 100%; max-width: 1020px; margin-top: 56px; }
  .home-wall.guest-wall .tpl-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .home-wall-title { font-size: 18px; font-weight: 600; margin: 0 0 4px; color: var(--ink); }
  .home-wall .tpl-tabs { margin-top: 10px; }
  .tpl-card, .tpl-item { animation: tplRise .5s cubic-bezier(.2,.7,.2,1) both; animation-delay: calc(var(--i, 0) * 45ms); }
  .tpl-item > .tpl-card { animation: none; }
  @keyframes tplRise { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
  .tpl-card { transition: transform .2s ease, background .2s ease; }
  .tpl-card:hover { transform: translateY(-3px); }
  .tpl-cover img { transition: transform .35s ease; }
  .tpl-card:hover .tpl-cover img { transform: scale(1.05); }
  .tpl-cover video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0; transition: opacity .25s; }
  .tpl-cover video.on { opacity: 1; }
  .tpl-cover .tpl-dur { z-index: 1; }
  .tpl-info { display: flex; flex-direction: column; gap: 6px; }
  /*
    首页模板墙：对照即梦「最近上新」—— **图是主角，字压在图上**，没有卡片底色和内边距。
    「做同款」页和「我的模板」仍然是带底色的竖卡（那里要给「编辑 / 删除」留位置），所以只作用在 `.home-wall`。
    文字叠在封面上靠同一格网格（cover 和 info 都占 1/1），不用绝对定位 ——
    标题、亮点、元信息长短不一，绝对定位得猜高度。
  */
  .home-wall .tpl-grid { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 14px; }
  .home-wall .tpl-card {
    display: grid; padding: 0; gap: 0; border: 0; border-radius: 16px; overflow: hidden; background: #000;
  }
  .home-wall .tpl-card:hover { background: #000; }
  .home-wall .tpl-cover { grid-area: 1 / 1; aspect-ratio: 3 / 4; border-radius: 0; }
  .home-wall .tpl-cover .tpl-dur { top: 10px; bottom: auto; right: 10px; }
  .home-wall .tpl-info {
    grid-area: 1 / 1; align-self: end; z-index: 1; pointer-events: none; gap: 4px;
    padding: 56px 14px 14px; background: linear-gradient(to top, rgba(0,0,0,.82), rgba(0,0,0,0));
  }
  .home-wall .tpl-title { font-size: 16px; color: #fff; }
  .home-wall .tpl-hl { min-height: 0; color: rgba(255,255,255,.72);
    display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden; }
  .home-wall .tpl-meta { color: rgba(255,255,255,.55); }
  @media (prefers-reduced-motion: reduce) {
    .home-glow, .tpl-card, .tpl-item { animation: none; }
    .tpl-card, .tpl-cover img { transition: none; }
  }
  /*
    落地屏上的输入框：更大、更圆、底色带一点冷调的半透明 —— 对照即梦那只框
    （高约 156px、圆角约 22px、深青灰底配一圈发亮的细边）。只作用在 `.hello` 里；
    有历史之后贴底的那只框（`.composer-dock`）不变，那时它是工具、不是主角。
  */
  .hello .composer {
    width: 100%; padding: 20px 24px 14px; border-radius: 28px;
    background: linear-gradient(180deg, rgba(36,44,53,.92), rgba(25,30,37,.92));
    border: 1px solid rgba(255,255,255,.1);
    box-shadow: 0 18px 50px rgba(0,0,0,.35), inset 0 1px 0 rgba(255,255,255,.04);
  }
  .hello .composer:focus-within { border-color: rgba(120,190,255,.45); }
  .hello .composer textarea { min-height: 96px; font-size: 15px; }
  .hello .composer .drop-card {
    flex-basis: 76px; width: 76px; height: 94px; border-radius: 14px;
    transform: rotate(-6deg); transition: transform .2s ease, border-color .2s ease;
  }
  .hello .composer .drop-card:hover { transform: rotate(0deg); }
  .hello .composer-bar { margin-top: 8px; }
  .hello-hint { color: var(--ink-3); font-size: 13px; margin: 0 0 22px; text-align: center; }
  @media (max-width: 760px) {
    .hello h1 { font-size: 24px; }
    .hello .composer { padding: 14px 14px 10px; border-radius: 20px; }
    .hello .composer .drop-card { flex-basis: 60px; width: 60px; height: 76px; }
    .home-wall .tpl-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; }
    .home-wall.guest-wall .tpl-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  }

  #org-switch {
    margin-left: 12px; max-width: 200px; padding: 4px 8px; font-size: 13px;
    background: var(--card); color: var(--ink-2); border: 1px solid var(--line); border-radius: 8px;
  }

  /* 没有参考视频、走「图片 + 要求」时，会话流开头那张「照哪个爆款结构写」的卡。 */
  .viral-card {
    max-width: 900px; margin: 0 auto 14px; padding: 14px 18px; border-radius: 14px;
    border: 1px solid rgba(255,150,80,.28);
    background: linear-gradient(135deg, rgba(255,130,60,.10), rgba(255,255,255,.02));
  }
  .viral-card .viral-title { font-size: 12px; font-weight: 650; letter-spacing: .06em; color: #ffb27a; margin-bottom: 6px; }
  .viral-card p { margin: 4px 0; font-size: 14px; color: var(--ink); line-height: 1.6; }
  .viral-card .viral-ref { color: var(--ink-2); }
  .viral-card .viral-hint { font-size: 12px; color: var(--ink-3); }
  .viral-card .viral-actions { display: flex; gap: 8px; margin-top: 10px; }
  /* 会话流是时间线、不是卡片堆（见下面 `.feed-scroll section`）：这里只留一道橙色的竖线当记号。 */
  .feed-scroll .viral-card { border-left: 2px solid rgba(255,150,80,.7); border-radius: 0; padding: 2px 0 2px 14px; max-width: 900px; }

  .skills { display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; margin-top: 18px; }
  .skills .skill.extra[hidden] { display: none; }
  .skills .skill.more-skills { background: transparent; color: var(--ink); }
  .skills .skill .caret { font-size: 11px; opacity: .7; }
  .skill {
    display: inline-flex; align-items: center; gap: 6px; height: 32px; padding: 0 13px;
    border-radius: 999px; background: var(--card); color: var(--ink-2);
    border: 1px solid var(--line-soft); font-size: 13px; font-weight: 400; cursor: pointer;
  }
  .skill:hover { background: var(--card-hi); color: var(--ink); filter: none; }
  .skill .slash { color: var(--ink-3); }

  /*
    「出片」那张卡是**下一步**，不是主角 —— 主角是底部那个输入框。

    它顶着完整的卡片底色和边框时，看起来像和上面那些生成记录平级的一条，
    而且是最下面、最大的一条 —— 用户的视线会停在它身上，
    然后在它和输入框之间的两个「生成」按钮里犹豫。
    收成一条低对比的横条之后，它退回它该在的位置：一个给想先看价的人的出口。
  */
  section.go {
    background: transparent; border: 0; border-top: 1px dashed var(--line);
    border-radius: 0; padding: 16px 0 0; margin-bottom: 8px;
  }
  section.go h2 { font-size: 13px; color: var(--ink-2); font-weight: 500; }
  section.go .hint { font-size: 12.5px; color: var(--ink-3); }

  /* 输入框上沿的提醒。和 `.composer-status` 分开：那个说「正在跑」，
     这个说「有件事你可能没注意到」（比如三张人物照只会用第一张）。 */
  .composer-note {
    display: flex; align-items: center; gap: 9px; margin-bottom: 10px;
    padding: 8px 12px; border-radius: 10px; font-size: 12.5px;
    background: rgba(224,163,58,.09); color: #e8bd6b; border: 1px solid rgba(224,163,58,.2);
  }
  .composer-note .spacer { flex: 1; }
  .composer-note button {
    border: 0; background: transparent; color: #f0cf8e; cursor: pointer;
    font: inherit; padding: 2px 8px; border-radius: 6px; text-decoration: underline;
    text-underline-offset: 3px;
  }
  .composer-note button:hover { background: rgba(224,163,58,.14); filter: none; }

  /* 参数 pill 里那种「这是事实，不是选项」的。即梦那排是能改的，
     我们这几项（模型、画幅、时长）由参考片和源决定 —— 做成能点开的下拉
     再告诉用户「改不了」是更糟的欺骗，所以它们长得像标签，也确实是标签。 */
  .composer-bar .pill.fact { color: var(--ink-3); cursor: help; }
  .composer-bar .pill.fact:hover { background: transparent; color: var(--ink-2); }
  /*
    模板 pill 和旁边那几个「事实」长得像，但它**能点** —— 所以要有
    手型光标、边框和一个 ▾。没有这三样的话它混在一排只读标签里，
    没人会想到去点它，而模板是这一屏上最要紧的那个选择。
  */
  .composer-bar .pill.fact.pick {
    color: var(--ink-2); cursor: pointer;
    border: 1px solid var(--line); border-radius: 999px; padding: 3px 10px;
  }
  .composer-bar .pill.fact.pick:hover { background: var(--card-hi); color: var(--ink); }
  .composer-bar .pill.fact.pick .caret { margin-left: 5px; font-size: 9px; opacity: .65; }
  /* 模板菜单比会话菜单宽：每条都带一句说明，窄了就折成四行。 */
  .rail-menu.template-menu { width: 340px; padding: 6px; }
  .rail-menu.template-menu button { align-items: flex-start; padding: 9px 10px; }
  .rail-menu.template-menu button .hint { white-space: normal; line-height: 1.45; }
  .rail-menu.template-menu button.picked { background: var(--card-hi); color: var(--ink); }

  /* ============================================================== 资产页 */

  /* 顶部居中那组分段控件。 */
  .seg {
    display: inline-flex; padding: 3px; gap: 2px; border-radius: 999px;
    background: var(--card); border: 1px solid var(--line-soft);
  }
  .seg button {
    height: 28px; padding: 0 16px; border-radius: 999px; background: transparent;
    color: var(--ink-2); font-size: 13px; font-weight: 400;
  }
  .seg button[aria-current="true"] { background: var(--card-hi); color: var(--ink); }
  .seg button:hover { filter: none; color: var(--ink); }

  /* 过滤那一行是**文字按钮**，不是 pill —— 即梦那样，选中的变白，其余灰。
     一排胶囊会和上面那组分段控件打架。 */
  .filters { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; margin: 4px 0 6px; }
  .filters button {
    background: transparent; color: var(--ink-3); padding: 4px 0; font-size: 13.5px; font-weight: 400;
  }
  .filters button[aria-current="true"] { color: var(--ink); font-weight: 550; }
  .filters button:hover { color: var(--ink); filter: none; }
  .filters .spacer { flex: 1; }
  .filters .count { color: var(--ink-3); font-size: 12.5px; }

  .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(196px, 1fr)); gap: 14px; }
  /*
    每格**固定高度**、内容 `contain` 居中。
    9:16 的成片和 1:1 的商品图摆在同一个网格里，不定高的话每一行的高度
    由那一行里最高的那个决定 —— 网格会参差不齐，滚起来像在抖。
  */
  .cell {
    position: relative; height: 176px; border-radius: 10px; overflow: hidden;
    background: var(--bg); border: 1px solid var(--line-soft); cursor: pointer;
    display: grid; place-items: center;
  }
  .cell:hover { border-color: #3a3a44; }
  .cell > img, .cell > video {
    max-width: 100%; max-height: 100%; width: auto; height: auto; display: block;
  }
  .cell .glyph { font-size: 30px; opacity: .5; }
  .cell .clock {
    position: absolute; left: 8px; bottom: 8px; padding: 1px 6px; border-radius: 5px;
    background: rgba(0,0,0,.62); color: #fff; font-size: 11.5px;
    font-variant-numeric: tabular-nums; letter-spacing: .02em;
  }
  .cell .tag {
    position: absolute; left: 8px; top: 8px; padding: 1px 7px; border-radius: 5px;
    background: rgba(0,0,0,.62); color: rgba(255,255,255,.82); font-size: 11px;
  }
  /* hover 才出的那一条。常驻的话，一屏三十个格子就是三十排按钮。 */
  .cell .acts {
    position: absolute; right: 6px; top: 6px; display: flex; gap: 4px;
    opacity: 0; transition: opacity .12s;
  }
  .cell:hover .acts, .cell[data-picked="1"] .acts { opacity: 1; }
  .cell .acts a, .cell .acts button {
    width: 26px; height: 26px; padding: 0; border-radius: 7px; border: 0;
    background: rgba(0,0,0,.62); color: #fff; font-size: 13px;
    display: grid; place-items: center; cursor: pointer;
  }
  .cell .acts a:hover, .cell .acts button:hover { background: rgba(0,0,0,.85); filter: none; }
  /* 复选框在左上，和右上的操作分开 —— 两者常被连点，挨着放会点错。 */
  .cell .pickbox {
    position: absolute; left: 7px; top: 7px; width: 18px; height: 18px; border-radius: 5px;
    border: 1.5px solid rgba(255,255,255,.55); background: rgba(0,0,0,.4);
    display: grid; place-items: center; cursor: pointer; opacity: 0; transition: opacity .12s;
    color: transparent; font-size: 12px; line-height: 1;
  }
  .cell:hover .pickbox, .cell[data-picked="1"] .pickbox { opacity: 1; }
  .cell[data-picked="1"] .pickbox { background: var(--accent); border-color: var(--accent); color: #fff; }
  .cell[data-picked="1"] { border-color: var(--accent); }
  .cell[data-picked="1"] .tag { display: none; }

  .cell-foot { margin: 6px 2px 0; font-size: 12px; color: var(--ink-3); }
  .cell-foot b {
    display: block; color: var(--ink-2); font-weight: 400; font-size: 12.5px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }

  /**
   * 选了东西之后**贴在最底下**那条。
   *
   * ## 为什么它原先没贴到底
   *
   * `position: sticky; bottom: 0` 贴的是**滚动容器的内容边**，不是视口边 ——
   * 而 `main` 有 `padding-bottom: 60px`。于是这条 bar 悬在离底部 60px 的地方：
   * 下面还露着大半排格子，看起来既不像吸底的工具条，也不像文档的一部分，
   * 而且它**盖住了那一排的名字**。
   *
   * ## 为什么不干脆放到文档末尾
   *
   * 用户在第三屏选中两张图，动作栏跑到第十屏的末尾 —— 他得先滚下去才能按。
   * 选择是在哪儿发生的，动作就得在哪儿够得着。所以还是吸底，
   * 只是要**真的吸到底**，而且**不许盖住内容**。
   */
  /**
   * ## 为什么是 `fixed` 而不是 `sticky`
   *
   * 试过 sticky，**贴不到底**：滚到最底下时元素已经在它的自然流位置上，
   * `bottom` 那个偏移只决定「什么时候开始吸」，不会再把它往下推。
   * 而自然流位置下面还压着 `main` 的 `padding-bottom` —— 于是这条栏
   * 永远悬在离底边 60px 的地方，下面露着大半排格子，它自己还盖着那排的名字。
   *
   * 补一个负的 `bottom` / `margin-bottom` 去抵消那段 padding 也行不通：
   * 负 margin 确实把后面的内容拉上来了，可 padding 还在，最后只是把两个数
   * 耦合在一起，改一处就错位（实测浮起的高度正好等于另一处留的空白）。
   *
   * `fixed` 没有这些关系 —— 它就是钉在视口底边。代价是要自己算出
   * 「和正文那一栏一样宽、一样居中」：正文是 `.wrap`（`max-width: 900px`
   * 居中在左栏右边那一整块里），下面三行就是把它复述一遍。
   */
  .bulk {
    position: fixed; bottom: 0; z-index: 10;
    left: 50%; margin-left: calc(var(--rail-w) / 2); transform: translateX(-50%);
    width: min(900px, calc(100vw - var(--rail-w) - 40px));
    display: flex; align-items: center; gap: 10px; padding: 12px 16px;
    border-radius: var(--r-md) var(--r-md) 0 0;
    background: #202026; border: 1px solid var(--line); border-bottom: 0;
    box-shadow: 0 -10px 30px rgba(0,0,0,.45);
  }
  .bulk .spacer { flex: 1; }
  /**
   * **给它腾出位置，别盖住最后一排。**
   *
   * 钉死在视口底边的东西一定会压住内容，除非滚动区自己留出这么高的空白。
   * 只在真的选了东西时留，否则平时页面底下会凭空多出一块。
   *
   * 加在 `.wrap` 上而不是 `main` 的 padding 上：现在这条栏是 `fixed`，
   * 两者已经不耦合了，但留在内容上更准 —— `main` 的 padding 是所有页面
   * 共用的，为资产页一个场景去改它，别的页面会跟着多出一段空白。
   */
  main:has(.bulk:not(.hidden)) > .wrap { padding-bottom: 76px; }

  /* ------------------------------------------------------------ 积分消耗明细 */

  /*
    **一行四格，列是对齐的。**

    这张表回答的是「钱去哪了」，而那个问题要靠**竖着扫**来答：
    金额一列对齐才能一眼看出哪一笔特别大。用 flex 让每行自己排的话，
    会话名一长，右边的金额就往外跑，那一列就没法扫了。

    金额和余额用等宽数字（`tabular-nums`）—— 比例数字里的 1 比 8 窄，
    一列数字会参差不齐，而这一列是给人对账用的。
  */
  .ledger { display: flex; flex-direction: column; gap: 2px; }
  .led-day { margin-bottom: 14px; }
  .led-day-head {
    display: flex; align-items: baseline; gap: 10px; padding: 8px 2px 6px;
    font-size: 12.5px; color: var(--ink-3); border-bottom: 1px solid var(--line-soft);
  }
  .led-day-head .spacer { flex: 1; }
  .led-day-sum { font-variant-numeric: tabular-nums; color: var(--ink-2); }

  .led-row {
    display: grid; grid-template-columns: 52px minmax(0, 1fr) auto 150px;
    align-items: baseline; gap: 12px; padding: 9px 2px;
    border-bottom: 1px solid var(--line-soft); font-size: 13px;
  }
  .led-row:hover { background: var(--card); }
  .led-time { color: var(--ink-3); font-variant-numeric: tabular-nums; font-size: 12.5px; }
  .led-what { min-width: 0; }
  .led-what b { font-weight: 500; color: var(--ink); }
  /*
    小字那一行**允许换行**，不截断。

    这里写的是「哪条会话 · 哪次构建 · 预扣多少偏差多少」—— 截成一行
    正好会把偏差那一截切掉，而那恰恰是用户来这一页要找的东西。
  */
  .led-sub { display: block; color: var(--ink-3); font-size: 12px; line-height: 1.5; }

  .led-amt {
    font-variant-numeric: tabular-nums; white-space: nowrap; text-align: right;
    color: var(--ink-2);
  }
  .led-amt.spend  { color: var(--ink); }
  .led-amt.topup  { color: var(--ok); }
  .led-amt.refund { color: var(--warn); }
  .led-amt.hold   { color: var(--warn); }
  /* 不动余额的那几行（预扣退回）压暗 —— 它们是解释，不是账。 */
  .led-amt.flat   { color: var(--ink-3); }
  .led-row.release .led-what b { color: var(--ink-2); font-weight: 400; }

  .led-bal {
    text-align: right; color: var(--ink-3); font-size: 12.5px; white-space: nowrap;
    font-variant-numeric: tabular-nums;
  }

  /* 窄屏：余额那一列先走。它是可以自己加出来的，会话名不能。 */
  @media (max-width: 860px) {
    .led-row { grid-template-columns: 46px minmax(0, 1fr) auto; }
    .led-bal { display: none; }
  }

  /* ============================================================== 主体
     整个组织公共的素材。格子复用 `.tile`（和项目素材那一页同一套），
     这里只加「它是靠名字被 @ 到的」这件事需要的几条。 */

  /* `@名字` 是这一页唯一要让人记住的东西，所以做得比别的都醒目。 */
  .subj-at {
    font-weight: 600; font-size: 13px; color: var(--accent);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  /* 主体图可能横可能竖：不能用 `.tile > img` 那套固定高度 + cover ——
     竖版人像会被裁成只剩半张脸（实测头像被切到下巴以上）。
     改成固定比例的展示框 + `contain` 完整显示，空出来的两侧用同一张图的
     模糊放大版铺满，横竖图的格子一样高、网格依然整齐。 */
  .subj-media {
    position: relative; display: block; aspect-ratio: 4 / 5; overflow: hidden;
    background: var(--bg); cursor: zoom-in;
  }
  .subj-media::before {
    content: ""; position: absolute; inset: -24px;
    background: var(--src) center / cover no-repeat;
    filter: blur(22px) brightness(.55); transform: translateZ(0);
  }
  .subj-media > img {
    position: relative; display: block; width: 100%; height: 100%;
    object-fit: contain;
  }
  /* 音色主体没有图可贴。给一块和图片展示框等高的色块，否则那一格比别的矮一截。 */
  .subj-audio {
    aspect-ratio: 4 / 5; display: flex; align-items: center; justify-content: center;
    font-size: 30px; background: var(--bg);
  }

  /* `@` 面板里的分组标题：会话素材 / 组织主体。
     两组的引用符不是一套东西（序号 vs 名字），不分开的话用户会以为
     主体也是这条会话里的一张图，然后在下一条会话里找不到它。 */
  .mentions-group {
    padding: 6px 10px 2px; font-size: 11.5px; color: var(--ink-3);
    letter-spacing: .02em;
  }
  .mentions-group:not(:first-of-type) { border-top: 1px solid var(--line-soft); margin-top: 4px; }
  /* 主体那几条压一条左边框 —— 收起分组标题看不见时（滚动）也分得出来。 */
  .mentions button.is-subject { box-shadow: inset 2px 0 0 var(--accent); }

  /* ---------------------------------------------------------- Agent 对话

     **会话流是时间线，不是卡片堆**（即梦同款）：`section` 那三件套
     （底色/边框/内衬）是给设置类页面用的，流里的东西靠间距和明度分层，
     一条线读到底。同屏三四张描边的框，是这页原先「不清爽」的最大来源。
     卡片感只留给输入框（它浮在滚动内容上）和报价卡（钱要个容器）。 */
  .feed-scroll section {
    background: none; border: 0; padding: 0; margin: 0 auto 22px; max-width: none;
  }

  /* Agent 消息流。不设 max-height、不内部滚动 —— 整条时间线跟着页面滚
     （即梦同理）。一个自带滚动条的盒子嵌在页面滚动条里，是最经典的
     「应用感」来源。过程不进消息流之后它也长不到哪去（见 agent.mjs）。 */
  .agent-feed {
    margin: 10px 0;
    display: flex; flex-direction: column; gap: 6px;
  }
  .agent-line { font-size: 13.5px; line-height: 1.6; white-space: pre-wrap; word-break: break-word; }
  /* say 气泡按段落/列表渲染（view.mjs 的 sayBlocks）—— 模型开口就是 markdown，
     纯文本渲染会把 **星号** 原样摆上屏幕。 */
  .agent-line.say { white-space: normal; }
  .agent-line.say p { margin: 0 0 6px; }
  .agent-line.say p:last-child, .agent-line.say ul:last-child { margin-bottom: 0; }
  .agent-line.say ul { margin: 0 0 6px; padding-left: 18px; }
  .agent-line.say li { margin: 2px 0; }
  .agent-line.say {
    align-self: flex-start;
    max-width: 90%;
  }
  .agent-line.mine {
    align-self: flex-end; max-width: 90%;
  }
  .agent-line { position: relative; min-width: 0; }
  .msg-body { border-radius: 10px; padding: 8px 11px; }
  .agent-line.say .msg-body { background: var(--bg); }
  .agent-line.mine .msg-body { background: var(--accent); color: #fff; }
  .msg-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; margin-bottom: 8px; white-space: normal; }
  .agent-line.mine .msg-actions { justify-content: flex-end; }
  .msg-act {
    appearance: none; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
    min-height: 32px; padding: 4px 10px; border: 1px solid var(--line); border-radius: 7px;
    background: var(--card); color: var(--ink); font: inherit; font-size: 12px; line-height: 1.4; cursor: pointer;
  }
  .msg-copy::before { content: "⧉"; font-size: 15px; }
  .msg-edit::before { content: "✎"; font-size: 15px; }
  .msg-act:hover { background: var(--card-hi); border-color: var(--ink-3); }
  .msg-act:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
  /* 它在干什么：一行灰字，不占视觉重量。 */
  .agent-line.note { color: var(--ink-3); font-size: 12.5px; }
  .agent-line.warn { color: #9a6700; font-size: 12.5px; }

  /* 报价：**整条链上唯一花钱的地方**，所以长得和别的都不一样。 */
  .agent-quote {
    border: 1px solid var(--line); border-radius: 12px; padding: 12px 14px;
    background: var(--bg); margin: 4px 0;
  }
  .agent-quote-head { font-size: 19px; font-weight: 650; margin-bottom: 6px; }
  .agent-quote p { margin: 4px 0; }
  .agent-replica { margin: 6px 0; font-size: 12.5px; color: var(--ink-2); }
  .agent-replica summary { cursor: pointer; }
  .agent-replica ul { margin: 4px 0 0; padding-left: 18px; }

  /* 出片自动验收：过了是绿边，没过是琥珀边 —— 一眼分得出。 */
  .agent-acceptance {
    border: 1px solid var(--line); border-radius: 10px; padding: 8px 12px; margin: 4px 0;
    font-size: 12.5px;
  }
  .agent-acceptance.ok { border-color: #8cc79a; }
  .agent-acceptance.bad { border-color: #e0b35c; }
  .agent-acceptance-head { font-weight: 600; margin-bottom: 4px; }
  .agent-acceptance ul { margin: 0; padding-left: 18px; }

  .agent-spend { font-size: 12px; margin-bottom: 6px; }

  /* ---------------------------------------------------------------- 手机 / 窄屏
     左栏收成抽屉（顶栏左上角的 ☰ 开合），正文铺满整屏；
     桌面端这几条一概不生效，☰ 和遮罩在桌面上是隐藏的。 */
  html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
  #rail-toggle, #rail-backdrop { display: none; }
  @media (max-width: 900px) {
    body.auth main { padding: 22px; }
  }
  @media (max-width: 760px) {
    body.auth #rail-toggle { display: none; }
    body.auth main { min-height: 100dvh; padding: 12px; }
    #rail-toggle {
      display: inline-grid; place-items: center; flex: 0 0 34px; width: 34px; height: 34px;
      padding: 0; font-size: 18px; line-height: 1; background: transparent; color: var(--ink-2);
      border: 1px solid var(--line-soft); border-radius: var(--r-sm);
    }
    #rail {
      position: fixed; left: 0; top: 0; z-index: 60;
      width: min(82vw, 300px); height: 100vh; height: 100dvh;
      transform: translateX(-100%); transition: transform .2s ease;
      box-shadow: none;
    }
    body.rail-open #rail { transform: translateX(0); box-shadow: 8px 0 30px rgba(0,0,0,.5); }
    #rail-backdrop {
      position: fixed; inset: 0; z-index: 55; background: rgba(0,0,0,.5);
      opacity: 0; pointer-events: none; transition: opacity .2s ease;
    }
    body.rail-open #rail-backdrop { display: block; opacity: 1; pointer-events: auto; }

    .stage { width: 100%; height: 100vh; height: 100dvh; }
    #topbar { padding: 0 12px; gap: 8px; }
    #topbar .title { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    #topbar .spacer { display: none; }
    #topbar .who { display: none; }
    #topbar .chip { padding: 0 9px; }
    #topbar .ghost { padding: 0 8px; white-space: nowrap; }

    main { padding: 14px 12px var(--page-pad-b); }
    section { padding: 14px; }
    main.has-composer > .feed-scroll { padding: 14px 12px 8px; }
    .composer-dock { padding: 6px 10px 12px; }
    .composer { padding: 12px 12px 8px; border-radius: 16px; }
    .composer-top { gap: 10px; }
    .drop-card { flex-basis: 54px; width: 54px; height: 68px; }
    .composer-bar { gap: 2px; }
    .feed { gap: 20px; }
    .grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 10px; }
    .tiles { grid-template-columns: repeat(auto-fill, minmax(128px, 1fr)); }
    .shot-body .out-stage, .shot-body .out-stage.film { max-width: 100%; }
    .shot-detail dl { grid-template-columns: 1fr; }
    .agent-line.say, .agent-line.mine { max-width: 96%; }
    .agent-line .msg-actions { opacity: 1; }

    /* 悬浮菜单别比屏幕还宽 */
    .rail-menu.template-menu { width: min(340px, calc(100vw - 24px)); }
    .rail-menu { max-width: calc(100vw - 24px); }

    /* 底部批量栏：整宽贴底 */
    .bulk {
      left: 0; margin-left: 0; transform: none; width: 100%;
      flex-wrap: wrap; padding: 10px 12px;
    }
  }
