/* =========================
   ✅ Image loading optimization
   - Fade-in transition prevents "refresh" flicker
   - Placeholder background while loading
   ========================= */

/* 图片加载时的占位背景 + 渐入 + 模糊过渡 */
.chat-messages .bubble img.chat-image,
.chat-messages .agent-img-stage img.chat-image,
.msg .bubble .bubble-attachment img {
  background: var(--color-bg-surface);
  opacity: 0;
  transition: opacity 0.3s ease-out, filter 0.5s ease-out, transform 0.5s ease-out;
}

/* 图片加载完成后显示 */
.chat-messages .bubble img.chat-image.loaded,
.chat-messages .agent-img-stage img.chat-image.loaded,
.msg .bubble .bubble-attachment img.loaded {
  opacity: 1;
}

/* 如果图片已经有有效 src（缓存命中），立即显示 */
.chat-messages .bubble img.chat-image[data-cached="true"],
.chat-messages .agent-img-stage img.chat-image[data-cached="true"],
.msg .bubble .bubble-attachment img[data-cached="true"] {
  opacity: 1;
  transition: none;
}

/* 上传中的画法【只有一份】,在 ai-chat-base.css 的 [data-uploading](压暗 + 进度环)。
   这里原先另写了一条 opacity:0.85 —— 同一状态两套画法,而且它先跑,看着就是"图有点淡"、没有环。 */


/* =========================
   ✅ Image bubble fix (iOS)
   ========================= */

/* ★ Room bubbles are excluded from every media rule below (:where(:not(.studio-meeting-msg)) — zero added
   specificity, so the main chat cascade is untouched). Reason: in the main chat a bubble holding media IS the
   media card — panel background, hairline, its own radius, and for the user side no bubble at all. A meeting-room
   message is a chat bubble that happens to carry an attachment, and it owns its look in ai-chat-studio.css.
   Without the exclusion the agent's bubble grew an outer hairline and the owner's lost its bubble entirely.
   Any new `.msg.user/.msg.assistant .bubble:has(...)` rule must carry the same exclusion — gated by
   scripts/meeting-feed.test.mjs. */
/* 只针对"包含图片"的 assistant bubble — restore card styling
   .agent-img-stage = 生成中图片的临时舞台(占位 shimmer→图,文字在下方流)，与气泡图容器共用同一基准，done 后图并进真气泡 */
.chat-messages .msg.assistant .agent-img-stage,
.chat-messages .msg.assistant .jgallery-stage, /* 判断参考图独立卡(文字=卡外纯气泡,与出图同构;用户 2026-07-23 定) */
.chat-messages .msg.assistant:where(:not(.studio-meeting-msg)) .bubble:has(img){
  padding: 12px;                 /* 给图片留边距（可调） */
  border: none;                  /* 不用 border，避免 iOS subpixel 歪框感 */
  border-radius: var(--img-radius);   /* 全站图片圆角单一源 */
  overflow: hidden;              /* 关键：圆角由外层裁切统一控制 */
  background: var(--color-bg-panel);
  width: fit-content;
  max-width: 85%;

  /* 用 inset-shadow 当边框：更稳、更不"歪" */
  box-shadow: 0 0 0 1px var(--color-border) inset;   /* ★ 标准边线（图片轮廓，与横条一致）*/

  /* iOS 合成层稳定（减少边框抖动） */
  transform: translateZ(0);
  -webkit-transform: translateZ(0);
}

/* 图片本体：铺满容器，不要自己圆角/边框 */
.msg.assistant .agent-img-stage img,
.msg.assistant:where(:not(.studio-meeting-msg)) .bubble img{
  display: block;
  width: 100%;
  height: auto;
  border-radius: 0 !important;   /* 防止你 JS 里给 img 写了圆角 */
  box-shadow: none !important;
}

/* agent 整体回复：图后面还跟着文字时，图与文字留 12px 间距（否则文字贴太近）。
   用结构选择器 :not(:last-child) 而非 class —— HTML 缓存快照恢复的图没有 agent-reply-image class，
   靠 class 会漏掉所有旧图；靠"图不是气泡最后一个子元素"则任何恢复路径都生效。
   纯图片回复（图是最后一个子元素）不加间距。 */
.msg.assistant .bubble img.chat-image:not(:last-child){
  margin-bottom: 12px;
}

/* ✅ thinking shimmer（更明显版） — restore bubble appearance */
.chat-messages .msg.assistant .bubble.thinking{
  position: relative;
  overflow: hidden;
  background: #f4f4f4;
  border: 1px solid var(--color-border);
  border-radius: calc(18px * var(--corner-k));
  padding: 10px 14px;
  width: fit-content;
  max-width: 85%;
  color: rgba(0,0,0,.65);
}

.chat-messages .bubble.thinking::after{
  content: "";
  position: absolute;
  top: 0;
  left: -140%;
  width: 140%;
  height: 100%;
  pointer-events: none;
  background: linear-gradient(
    90deg,
    rgba(255,255,255,0) 0%,
    rgba(255,255,255,0.85) 45%,
    rgba(255,255,255,0) 80%
  );
  animation: bubbleShimmer 1.1s ease-in-out infinite;
}

@keyframes bubbleShimmer{
  0%   { transform: translateX(0); }
  100% { transform: translateX(200%); }
}

/* ✅ 图片操作 thinking：图片占位块（同一 .msg 行内，bubble 后面） */
.thinking-image-block {
  width: 280px;
  min-height: 280px;
  margin-top: 6px;
  border-radius: calc(12px * var(--corner-k));
  overflow: hidden;
  position: relative;
  /* 对比度加大：旧 #efefef↔#f3f3f3 差距太小,肉眼看是静止(用户"死板") */
  background: linear-gradient(135deg, #e9e9e9 0%, #f8f8f8 50%, #e9e9e9 100%);
  /* 300% 画布+对角全程走位 → 光带覆盖整图(旧 200%+只动 X 轴只扫中间)；3s=用户点名降速到 60% */
  background-size: 300% 300%;
  animation: thinkingImageShimmer 5s ease-in-out infinite;
}

/* ★ chat-loop 出图舞台里的占位：与最终图同宽(舞台 85%=气泡 max-width)同形(1:1=生成图默认) → 换图零尺寸跳变 */
.chat-messages .msg.assistant .agent-img-stage { width: 85%; }
.agent-img-stage .thinking-image-block {
  width: 100%;
  height: auto;
  min-height: 0;
  aspect-ratio: 1 / 1;
  margin-top: 0;
  border-radius: 0;   /* 与最终图一致：图本体方角(基准 border-radius:0!important)、圆角由外层卡裁 */
}
/* ★2026-07-22 文字出卡终态：图卡与下方文字气泡的间距(live 与刷新渲染同构 [横条][图卡][文字气泡]) */
.chat-messages .msg.assistant .agent-img-stage { margin: 2px 0 8px; }
@keyframes thinkingImageShimmer {
  0%   { background-position: 0% 0%; }
  50%  { background-position: 100% 100%; }
  100% { background-position: 0% 0%; }
}

/* 编辑预览：原图轻度模糊 */
.thinking-image-block .thinking-image-preview {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
  corner-shape: inherit;
  filter: blur(6px) saturate(0.7);
  transform: scale(1.04);
  opacity: 0.75;
}

/* 占位预览在出图舞台内:压过 .msg.assistant .agent-img-stage img 的 height:auto(特异性(0,2,2)),
   否则绝对定位图高度塌回固有比例,横图铺不满方形占位(2026-07-26 真机)。 */
.msg.assistant .agent-img-stage img.thinking-image-preview {
  height: 100%;
  object-fit: cover;
}

/* ★带预览时(2026-07-26 用户定):不换效果——同一条光带(同 135° 几何/同 keyframes/同 5s)
   改为透明叠层从模糊原图上扫过(纯灰底换成图,光影原样保留)。亮暗同一叠层(白光带两种底都成立)。 */
.thinking-image-block.has-preview::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  corner-shape: inherit;
  background: linear-gradient(135deg, rgba(248,248,248,0) 0%, rgba(248,248,248,0.45) 50%, rgba(248,248,248,0) 100%);
  background-size: 300% 300%;
  animation: thinkingImageShimmer 5s ease-in-out infinite;
  pointer-events: none;
}

@media (max-width: 768px), (max-height: 500px) {
  .thinking-image-block {
    width: 220px;
    min-height: 220px;
  }
}

/* 夜间模式(对比度加大：旧 #2e2e2e↔#333 肉眼不可见=看着静止；300%+3s 与亮色同步) */
.dark-mode .thinking-image-block {
  background: linear-gradient(135deg, #2a2a2a 0%, #404040 50%, #2a2a2a 100%);
  background-size: 300% 300%;
  animation: thinkingImageShimmer 5s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce){
  .chat-messages .bubble.thinking::after{ animation: none; opacity: .25; }
  .thinking-image-block { animation: none; }
}

/* ✅ Streaming 尾部渐隐(第三版 2026-07-24)：逐字内联 opacity(thinking-ui _applyTrailingFade),
   mask/clip 两版真机不可见已弃;本 class 仅作标记,不再有视觉规则。 */
.streaming-trail { }


/* ✅ AI 回复左对齐 */
.chat-container .msg.assistant,
.chat-container .msg.assistant .bubble{
  text-align: left !important;
}

/* ✅ 所有气泡统一 16px */
.chat-messages .bubble {
  font-size: 16px;
}

/* ===== Text output style（保留） ===== */
.ai-text {
  white-space: pre-wrap;
  font-size: 16px;
  line-height: 1.6;
  color: #000;
}

/* =========================
   ✅ Markdown 渲染样式（assistant 消息）
   用 .chat-messages 提高 specificity，覆盖 custom-styles.css 全局 h1-h6 !important 规则
   ========================= */

/* --- 容器 --- */
.chat-messages .bubble.md-rendered {
  font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif !important;
  font-size: 16px !important;
  line-height: 1.6 !important;
  color: var(--color-text);
  word-break: break-word;
  white-space: normal !important;
}

/* --- 段落 --- */
.chat-messages .bubble.md-rendered p {
  margin: 0 0 var(--md-para-gap) !important;
  font-family: inherit !important;
  font-size: inherit !important;
  line-height: inherit !important;
}
.chat-messages .bubble.md-rendered p:last-child {
  margin-bottom: 0 !important;
}

/* --- 标题（覆盖 custom-styles.css h1-h6 !important） --- */
.chat-messages .bubble.md-rendered h1,
.chat-messages .bubble.md-rendered h2,
.chat-messages .bubble.md-rendered h3,
.chat-messages .bubble.md-rendered h4,
.chat-messages .bubble.md-rendered h5,
.chat-messages .bubble.md-rendered h6 {
  font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif !important;
  font-style: normal !important;
  letter-spacing: normal !important;
  text-transform: none !important;
  margin: 1.2em 0 0.35em !important;
  line-height: 1.35 !important;
}
.chat-messages .bubble.md-rendered h1 { font-size: 22px !important; font-weight: 500 !important; }
.chat-messages .bubble.md-rendered h2 { font-size: 20px !important; font-weight: 500 !important; }
.chat-messages .bubble.md-rendered h3 { font-size: 18px !important; font-weight: 500 !important; }
.chat-messages .bubble.md-rendered h4,
.chat-messages .bubble.md-rendered h5,
.chat-messages .bubble.md-rendered h6 { font-size: 16px !important; font-weight: 500 !important; }
.chat-messages .bubble.md-rendered > :first-child {
  margin-top: 0 !important;
}

/* --- 粗体 / 斜体 --- */
/* md-rendered 有两种挂法(气泡本体 / 内层 .text,训练台走后者)—— 选择器只认 .md-rendered,两种都吃到;
   500 = 全站唯一加粗档(400+100),浏览器默认 700 违反字重铁律 */
.chat-messages .md-rendered strong { font-weight: 500; }
.chat-messages .bubble.md-rendered em { font-style: italic; }

/* --- 行内代码 --- */
.chat-messages .bubble.md-rendered code {
  background: var(--color-bg-surface);
  border-radius: calc(4px * var(--corner-k));
  padding: 0.15em 0.4em;
  font-size: 0.9em;
  font-family: "SF Mono", "Menlo", "Monaco", "Consolas", "PingFang SC", "Microsoft YaHei", monospace;
}

/* --- 代码块 --- */
.chat-messages .bubble.md-rendered pre {
  background: #f3f3f3;
  color: #374151;
  border-radius: calc(8px * var(--corner-k));
  padding: 12px 16px;
  margin: 0.75em 0;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  font-size: 14px;
  line-height: 1.5;
}
.chat-messages .bubble.md-rendered pre code {
  background: none;
  padding: 0;
  border-radius: 0;
  color: inherit;
  font-size: inherit;
}

/* --- 代码块容器 + 复制按钮 --- */
.code-block-wrap { position: relative; }
/* 同一基准 = 气泡图标行的 .msg-iconbtn(盒 --footer-btn-box、18px 图标、--radius-icon-btn);别再自定一套尺寸 */
.code-copy-btn {
  position: absolute;
  top: 6px;
  right: 6px;
  width: var(--footer-btn-box);
  height: var(--footer-btn-box);
  padding: 7px;
  background: transparent;
  border: none;
  border-radius: var(--radius-icon-btn);
  cursor: pointer;
  color: #999;
  opacity: 0;
  transition: opacity 0.2s, color 0.2s, background 0.15s;
  z-index: 1;
}
html:not([data-input="touch"]) .code-block-wrap:hover .code-copy-btn{ opacity: 1; }
/* 触屏没有 hover:常驻,否则手机上这颗钮永远是 opacity 0 */
html[data-input="touch"] .code-copy-btn{ opacity: .9; }
html:not([data-input="touch"]) .code-copy-btn:hover{ background: var(--color-bg-card); color: #666; }
.code-copy-btn:active { opacity: 0.7; }
.code-copy-btn svg { width: 100%; height: 100%; display: block; }
.code-copy-btn.copied { opacity: 1; color: #4caf50; }

/* --- 列表（紧凑：项间距 = 行高，无额外 margin） --- */
.chat-messages .bubble.md-rendered ul,
.chat-messages .bubble.md-rendered ol {
  margin: 0.3em 0 var(--md-para-gap) !important;
  padding: 0 0 0 1.4em !important;
  line-height: 1.6 !important;
}
.chat-messages .bubble.md-rendered li {
  margin: 0 !important;
  padding: 0 !important;
  padding-inline-start: 0 !important;
  line-height: 1.6 !important;
}
.chat-messages .bubble.md-rendered li + li {
  margin-top: 0.15em !important;
}
.chat-messages .bubble.md-rendered li p {
  margin: 0 !important;
  padding: 0 !important;
  line-height: 1.6 !important;
}
/* 嵌套列表 */
.chat-messages .bubble.md-rendered li ul,
.chat-messages .bubble.md-rendered li ol {
  margin: 0.1em 0 !important;
}

/* --- 引用 --- */
.chat-messages .bubble.md-rendered blockquote {
  border-left: 3px solid rgba(128,128,128,.3);
  margin: 0.5em 0 !important;
  padding: 0.25em 0 0.25em 12px;
  color: inherit;
  opacity: 0.75;
}

/* --- 分割线 --- */
.chat-messages .bubble.md-rendered hr {
  border: none !important;
  border-top: 1px solid rgba(128,128,128,.2) !important;
  margin: 1.2em 0 !important;
}

/* --- 表格 --- */
/* 宽表格【自己滚】,不许把消息列表顶成横向滚动(全站法:宽内容在自己的 overflow-x 容器里滚)——
   同一文件里 pre 早就是这么做的。width:100% 挡不住:表格是 auto 布局,最小内容宽超过容器就照样撑出去
   (手机上实测 476 > 398,整条 feed 跟着横晃,owner 2026-09-03)。display:block 让 table 元素自己成为滚动盒。 */
.chat-messages .bubble.md-rendered table {
  border-collapse: collapse;
  margin: 0.75em 0;
  display: block;
  width: 100%;
  max-width: 100%;
  overflow-x: auto;
  font-size: 14px;
}
.chat-messages .bubble.md-rendered th,
.chat-messages .bubble.md-rendered td {
  border: 1px solid rgba(128,128,128,.25) !important;
  padding: 6px 10px;
  text-align: left;
  color: inherit !important;
}
.chat-messages .bubble.md-rendered th {
  background: rgba(128,128,128,.12) !important;
  font-weight: 500 !important;
  font-family: inherit !important;
  font-style: normal !important;
  text-transform: none !important;
  letter-spacing: normal !important;
  opacity: 1 !important;
}

/* --- 链接 --- */
.chat-messages .bubble.md-rendered a {
  color: #0066cc;
  text-decoration: none;
  background: none !important;
}
html:not([data-input="touch"]) .chat-messages .bubble.md-rendered a:hover{
  text-decoration: underline;
}

/* --- Dark mode --- */
.dark-mode .chat-messages .bubble.md-rendered code { background: rgba(128,128,128,.25); }
.dark-mode .chat-messages .bubble.md-rendered pre { background: #1e1e1e; color: #d4d4d4; }
.dark-mode .chat-messages .bubble.md-rendered a { color: #4da6ff; }
.dark-mode .code-copy-btn { color: #777; }
html:not([data-input="touch"]) .dark-mode .code-copy-btn:hover{ color: #aaa; }
.dark-mode .code-copy-btn.copied { color: #4caf50; }


/* =========================
   ✅ User uploaded image in bubble (ChatGPT-like)
   目标：用户发送的"输入图片"在 user bubble 里更大、更像 ChatGPT
   ========================= */

/* 用户消息：如果 bubble 只含图片，移除外层装饰（避免双层边框） */
.msg.user:where(:not(.studio-meeting-msg)) .bubble:has(img),
.msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-attachments){
  padding: 0;              /* 移除外层 padding */
  border: none;            /* 移除外层边框 */
  border-radius: 0;        /* 移除外层圆角 */
  background: transparent; /* 透明背景 */
  box-shadow: none;        /* 移除外层阴影 */
}

/* ✅ 用户 bubble 里的图片：固定卡片尺寸（更大） */
.msg.user .bubble .bubble-attachments{
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 0;        /* 移除底部间距（因为已经分离了文字气泡） */
}

/* 单张/多张都统一成"方形大缩略图" - 边框在这一层 */
.msg.user .bubble .bubble-attachment{
  width: 160px;             /* 参考 ChatGPT/Claude 等主流 AI 助手尺寸 */
  height: 160px;
  border-radius: calc(14px * var(--corner-k));      /* 圆角也相应增大 */
  overflow: hidden;
  background: #f3f3f3;
  box-shadow: 0 0 0 1px var(--color-border); /* ★ 标准边线（图片轮廓）*/
}

/* ✅ 手机端：用户附件图片自适应宽度，避免图片在小屏幕上靠右挤压 */
@media (max-width: 768px), (max-height: 500px) {
  .msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-attachments) {
    max-width: 100%;           /* 放宽 bubble 限制，允许图片占满 */
  }
  .msg.user .bubble .bubble-attachments {
    justify-content: flex-end; /* 换行时图片靠右对齐，匹配用户消息方向 */
  }
  .msg.user .bubble .bubble-attachment {
    width: calc(50vw - 24px);  /* 两张图并排自适应屏幕宽度 */
    height: calc(50vw - 24px);
  }
}

/* 图片填充方式 */
.msg.user .bubble .bubble-attachment img{
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  border-radius: 0 !important;
  box-shadow: none !important;
}

/* ✅ 如果你 JS 没包 .bubble-attachment，而是直接 bubble 里放 img，也给它兜底 */
.msg.user .bubble > img{
  width: 160px;             /* 直接 img 的兜底尺寸 */
  max-width: 60vw;
  height: auto;
  display: block;
}

/* ✅ 用户 bubble 文本的间距（图下文字） */
.msg.user .bubble .bubble-text{
  white-space: pre-wrap;
  font-size: 16px;
  line-height: 1.6;
  color: #000;
}

/* ✅ 用户 bubble：图片和文字之间的间距（确保分支一致性） */
.msg.user .bubble .bubble-attachments + .bubble-text,
.msg.user .bubble .bubble-attachments + .bubble-content,
.msg.user .bubble .bubble-attachments + p,
.msg.user .bubble .bubble-attachment + .bubble-text,
.msg.user .bubble .bubble-attachment + .bubble-content,
.msg.user .bubble .bubble-attachment + p,
.msg.user .bubble img + .bubble-text,
.msg.user .bubble img + .bubble-content,
.msg.user .bubble img + p {
  margin-top: var(--img-gap);  /* 图片下方的文字间距（与附件族间距同源） */
}

/* ✅ 修复：当 bubble 同时有图片和文字时，恢复内边距 */
.msg.user:where(:not(.studio-meeting-msg)) .bubble:has(img):has(.bubble-text),
.msg.user:where(:not(.studio-meeting-msg)) .bubble:has(img):has(.bubble-content),
.msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-attachments):has(.bubble-text),
.msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-attachments):has(.bubble-content) {
  padding: 12px;
  background: var(--color-bg-surface);
  border-radius: calc(16px * var(--corner-k));
}

.bubble-text{
  white-space: pre-wrap;
}

/* =========================
   ✅ 分组消息：视觉上属于同一次发送
   当用户发送"图片 + 文字"时，文字气泡紧贴图片气泡
   ========================= */
.msg.msg-grouped{
  /* 同一次发送的相邻行:.msg margin-bottom 1rem(16px) − 6px = 10px,与 --img-gap 对齐(改任一处需同步) */
  margin-top: -6px;
}

/* ✅ 修复：通过 JS 智能判断添加 msg-grouped-reset 类来重置间距 */
/* 只有真正需要重置时才应用（跟在共享图片后的分支文字不会被重置） */
.msg.msg-grouped.msg-grouped-reset {
  margin-top: 0;  /* 重置负边距 */
}

/* =========================
   ✅ 长消息折叠（仅用户消息）
   方案：bubble 内部用 .collapse-inner 包裹内容，
   对 inner 设 overflow:hidden + max-height，
   按钮在 bubble 内但 inner 外面 → 既可见又可点击
   ========================= */

/* --- 折叠内容包装器 --- */
.collapse-inner.collapsed {
  max-height: 1280px;
  overflow: hidden;
  position: relative;
}

/* --- 无渐变：干净截断，按钮与文字间留 2 行空间 --- */

/* --- "显示完整消息" 按钮（bubble 内部，collapse-inner 外面） --- */
.msg-show-full {
  padding: 6px 0;
  margin: 50px 0 0;
  color: #999;
  font-size: 16px;
  font-weight: 500;
  cursor: pointer;
  text-align: right;
}
.dark-mode .msg-show-full {
  color: #777;
}
.msg-show-full:active {
  color: #333;
}
.dark-mode .msg-show-full:active {
  color: #ccc;
}


/* =========================
   ✅ Memory save notification
   - Collapsed: icon + faded text
   - Hover: expand card with memory content + manage button
   ========================= */

/* 紧贴上面的思考条:间距用时间线家族的 6px 节奏(.agent-steps/.agent-seg gap),不另起一档 */
.memory-notice {
  padding: 0 0 2px;
}
.agent-steps:has(+ .memory-notice) { margin-bottom: 6px; }

/* 折叠态头部：icon + 文字，淡色 */
.memory-notice-header {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
  color: var(--meta-text);
  cursor: default;
}
.memory-notice-header svg {
  width: var(--icon-size-sm);
  height: var(--icon-size-sm);
  flex-shrink: 0;
  opacity: 0.6;
}

/* 详情面板：默认隐藏 */
.memory-notice-detail {
  display: none;
  margin-top: 6px;
  padding: 10px 12px;
  background: #f8f8f8;
  border-radius: calc(10px * var(--corner-k));
  font-size: 13px;
  color: #333;
  line-height: 1.5;
}
/* hover 展开 */
html:not([data-input="touch"]) .memory-notice:hover .memory-notice-detail{
  display: block;
}

.memory-notice-item {
  margin: 0 0 6px;
}
.memory-notice-item:last-child {
  margin-bottom: 0;
}

/* "管理 >" 按钮 */
.memory-notice-manage {
  display: flex;
  align-items: center;
  gap: 2px;
  margin-top: 8px;
  padding: 0;
  border: none;
  background: none;
  font-size: 13px;
  color: #888;
  cursor: pointer;
}
html:not([data-input="touch"]) .memory-notice-manage:hover{
  color: #333;
}
.memory-notice-manage svg {
  width: var(--icon-size-sm);
  height: var(--icon-size-sm);
}

/* --- 夜间模式 --- */
.dark-mode .memory-notice-detail { background: #2a2a2a; color: #ccc; }
.dark-mode .memory-notice-manage { color: #666; }
html:not([data-input="touch"]) .dark-mode .memory-notice-manage:hover{ color: #ccc; }

/* =========================
   ✅ File cards in chat bubble — shared base (PDF/DOC/Sheet/Code/Audio/Video/Archive/PPT)
   ========================= */

/* bubble 含文件卡片时恢复基本样式 */
.msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-pdf-card),
.msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-doc-card),
.msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-sheet-card),
.msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-code-card),
.msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-audio-card),
.msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-video-card),
.msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-archive-card),
.msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-ppt-card),
.msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-psd-card) {
  padding: 0;
  border: none;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}

/* chat-loop export tool-card wrap: the card hangs off the reply row (below the text, above the footer icons). */
/* Gap to the text above = the reply's own paragraph rhythm (--md-para-gap), one beat below the last line.
   The old --img-gap (10px) glued the card to the text tighter than the paragraphs above it (owner 2026-08-26). */
.msg .duo-export-toolcard { margin: var(--md-para-gap) 0 0; }
/* The piece card (trace_shape / build_piece, ui-chat-piece-cards.js) sits DIRECTLY on the reply row - no
   toolcard wrap - so it must declare the same beat itself. Both paths land at the same total: the row's own
   6px flex gap (bubble-footer spacing, ai-chat-interactions.css) + this margin. */
.chat-messages .msg > .bubble-doc-card { margin-top: var(--md-para-gap); }

/* 卡片基础布局 */
.bubble-pdf-card,
.bubble-doc-card,
.bubble-sheet-card,
.bubble-code-card,
.bubble-audio-card,
.bubble-video-card,
.bubble-archive-card,
.bubble-ppt-card,
.bubble-psd-card,
.bubble-psd-card {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px;
  border-radius: calc(12px * var(--corner-k));
  background: var(--color-bg-card);
  min-width: 180px;
  max-width: 280px;
  cursor: pointer;
}

/* 同一次发送内的一切附件间距 = var(--img-gap)（图↔图 / 图↔卡 / 卡↔卡 / 跨行 msg-grouped 同值） */
:is(.bubble-pdf-card, .bubble-doc-card, .bubble-sheet-card, .bubble-code-card,
    .bubble-audio-card, .bubble-video-card, .bubble-archive-card, .bubble-ppt-card, .bubble-psd-card)
+ :is(.bubble-pdf-card, .bubble-doc-card, .bubble-sheet-card, .bubble-code-card,
      .bubble-audio-card, .bubble-video-card, .bubble-archive-card, .bubble-ppt-card, .bubble-psd-card) {
  margin-top: var(--img-gap);
}

/* 图片网格与文件卡相邻（两个方向都会出现：组序按上传首次出现排） */
.msg.user .bubble .bubble-attachments
+ :is(.bubble-pdf-card, .bubble-doc-card, .bubble-sheet-card, .bubble-code-card,
      .bubble-audio-card, .bubble-video-card, .bubble-archive-card, .bubble-ppt-card, .bubble-psd-card),
.msg.user .bubble :is(.bubble-pdf-card, .bubble-doc-card, .bubble-sheet-card, .bubble-code-card,
      .bubble-audio-card, .bubble-video-card, .bubble-archive-card, .bubble-ppt-card, .bubble-psd-card)
+ .bubble-attachments {
  margin-top: var(--img-gap);
}

/* 图标基础布局 */
.bubble-pdf-icon,
.bubble-doc-icon,
.bubble-sheet-icon,
.bubble-code-icon,
.bubble-audio-icon,
.bubble-video-icon,
.bubble-archive-icon,
.bubble-ppt-icon,
.bubble-psd-icon {
  width: 36px;
  height: 36px;
  border-radius: calc(10px * var(--corner-k));   /* owner 2026-08-29: the 36px file-type tile read too tight at 8; 10 base = 15 with the squircle */
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  color: #fff;
}
.bubble-doc-icon svg, .bubble-sheet-icon svg,
.bubble-code-icon svg, .bubble-audio-icon svg, .bubble-video-icon svg,
.bubble-archive-icon svg, .bubble-ppt-icon svg { width: var(--icon-size); height: var(--icon-size); }
/* PSD/PDF 用官方品牌标志：svg 铺满方块，标志按官方 viewBox 比例自然落位 */
.bubble-psd-icon svg, .bubble-pdf-icon svg { width: 100%; height: 100%; display: block; }

/* 图标颜色（唯一差异） */
.bubble-pdf-icon { background: #fa0f00; }
.bubble-doc-icon { background: #2B579A; }
.bubble-sheet-icon { background: #217346; }
.bubble-code-icon { background: #334155; }
.bubble-audio-icon { background: #F59E0B; }
.bubble-video-icon { background: #7C3AED; }
.bubble-archive-icon { background: #C2410C; }
.bubble-ppt-icon { background: #D24726; }
.bubble-psd-icon { background: #001E36; }

/* 信息区 */
.bubble-pdf-info, .bubble-doc-info, .bubble-sheet-info,
.bubble-code-info, .bubble-audio-info, .bubble-video-info,
.bubble-archive-info, .bubble-ppt-info, .bubble-psd-info {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

/* 文件名 */
.bubble-pdf-name, .bubble-doc-name, .bubble-sheet-name,
.bubble-code-name, .bubble-audio-name, .bubble-video-name,
.bubble-archive-name, .bubble-ppt-name, .bubble-psd-name {
  font-size: 13px;
  font-weight: 500;
  line-height: 1.3;
  color: var(--color-text);
  max-width: 200px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 类型标签 */
.bubble-pdf-sub, .bubble-doc-sub, .bubble-sheet-sub,
.bubble-code-sub, .bubble-audio-sub, .bubble-video-sub,
.bubble-archive-sub, .bubble-ppt-sub, .bubble-psd-sub {
  font-size: 11px;
  font-weight: 400;
  color: var(--color-text-secondary);
}

/* 上传中半透明 */

/* A reply the server never got (saveAgentReply gave up): the words + a real Retry button under the bubble.
   Red = you have to act (the site's one danger token), same family as the failed-upload dot below. */
.reply-unsaved { display: flex; align-items: center; gap: 10px; margin: 6px 0 0 2px; font-size: 12px; color: var(--color-danger); }

/* 上传失败状态 */
/* A file that never arrived and a file that was deleted read the same: the card stays, dimmed,
   with a red edge, so the history still shows what was sent. One block - a copy would drift. */
.file-upload-failed,
.file-gone {
  opacity: 0.5;
  border: 1px solid rgba(220, 38, 38, 0.4);
  position: relative;
}
.file-upload-failed::after,
.file-gone::after {
  content: '';
  position: absolute;
  top: 6px;
  right: 6px;
  width: 8px;
  height: 8px;
  background: #dc2626;
  border-radius: 50%;
  corner-shape: round;
}
.dark-mode .file-upload-failed,
.dark-mode .file-gone {
  border-color: rgba(248, 113, 113, 0.4);
}
.dark-mode .file-upload-failed::after,
.dark-mode .file-gone::after {
  background: #f87171;
}

/* 卡片与文本共存时 */
.msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-pdf-card):has(.bubble-text),
.msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-doc-card):has(.bubble-text),
.msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-sheet-card):has(.bubble-text),
.msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-code-card):has(.bubble-text),
.msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-audio-card):has(.bubble-text),
.msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-video-card):has(.bubble-text),
.msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-archive-card):has(.bubble-text),
.msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-ppt-card):has(.bubble-text),
.msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-psd-card):has(.bubble-text) {
  padding: 12px;
  background: var(--color-bg-surface);
  border-radius: calc(16px * var(--corner-k));
}

.bubble-pdf-card + .bubble-text,
.bubble-doc-card + .bubble-text,
.bubble-sheet-card + .bubble-text,
.bubble-code-card + .bubble-text,
.bubble-audio-card + .bubble-text,
.bubble-video-card + .bubble-text,
.bubble-archive-card + .bubble-text,
.bubble-ppt-card + .bubble-text,
.bubble-psd-card + .bubble-text {
  margin-top: var(--img-gap);
}

/* =========================
   ✅ 会话草稿卡（文档形态：整卡=文档本身，设计宪法第七条唯一例外）
   ========================= */
.duo-draft-card {
  position: relative;   /* anchor for the selection toolbar */
  width: 100%;
  max-width: 680px;
  /* Reply-flow card family: same paragraph beat as the export/piece cards below the text (one source) */
  margin-top: var(--md-para-gap);
  border-radius: calc(16px * var(--corner-k));
  /* 实色面板底:半透明大面板在暗色下是一块灰浮板(按钮半透明禁令同理),文档卡吃同一课 */
  background: var(--color-bg-panel);
  /* NO overflow:hidden — the send menu is absolute inside the card and must
     overhang it; rounding needs no clip (bg on the card itself, body scrolls
     in its own box) and the menu must track the anchor while scrolling */
}
.duo-draft-head {
  display: flex;
  align-items: center;
  gap: 8px;
  /* Right pad MUST equal top pad: the corner buttons' hover shape sits
     equidistant from the card's top and right edges (owner rule) */
  padding: 10px 10px 10px 14px;
  border-bottom: 1px solid var(--color-border);
}
.duo-draft-kind {
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--color-text-secondary);
  flex-shrink: 0;
}
/* Head order: [kind][title][ver] = left info group; icons pushed right by
   margin-left:auto so the version never reads as part of the icon cluster */
.duo-draft-title {
  font-size: 13px;
  font-weight: 500;
  color: var(--color-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  flex: 0 1 auto;
}
.duo-draft-ver {
  font-size: 12px;
  line-height: 1;
  color: var(--color-text-secondary);
  flex-shrink: 0;
}
.duo-draft-actions {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  flex-shrink: 0;
  margin-left: auto;
  position: relative;   /* anchor: the send menu is absolute so it scrolls with the card */
}
/* Icon buttons in the card head are one size down from message-footer ones,
   so they don't inflate the 34px head row */
.duo-draft-actions .msg-iconbtn {
  min-width: 26px;
  min-height: 26px;
  padding: 4px;
}
.duo-draft-actions .msg-iconbtn svg {
  width: var(--icon-size);
  height: var(--icon-size);
}
/* Mobile follows the message-footer mobile icon size (15px glyphs) */
@media (max-width: 768px), (max-height: 500px) {
  .duo-draft-actions .msg-iconbtn {
    min-width: 24px;
    min-height: 24px;
  }
  .duo-draft-actions .msg-iconbtn svg {
    width: var(--icon-size-sm);
    height: var(--icon-size-sm);
  }
}
.duo-draft-body[contenteditable="true"] {
  outline: none;   /* caret colour comes from the app-wide caret rule (ai-chat-base.css) */
}
/* Borderless card: editing state and version bump ride on box-shadow rings.
   Ring colour = the card's own divider line (--color-border), owner 2026-08-06 */
.duo-draft-editing {
  box-shadow: 0 0 0 1px var(--color-border);
}
/* Editing must not change layout: same body typography, buttons appended below */
/* Shared bar-box recipe: selection toolbar + diff review bar */
.duo-draft-barbox {
  align-items: center;
  gap: 2px;
  padding: 4px;
  background: var(--color-bg-panel);
  border: 1px solid var(--color-border);
  border-radius: calc(10px * var(--corner-k));
  box-shadow: 0 6px 20px rgba(0, 0, 0, .12);
}
.duo-draft-seltools {
  position: absolute;
  display: flex;
  z-index: 31;
}
/* Touch dock: in flow inside the card (below body, above Cancel/Save) so it
   never fights the OS selection callout floating at the selection */
.duo-draft-seltools.is-docked {
  position: static;
  margin: 8px 14px 12px;   /* bottom gap: the bar must not sit on the card edge */
  width: fit-content;
  max-width: calc(100% - 28px);
  border-radius: 999px; corner-shape: round; /* pill form when docked */
  padding: 4px 4px 4px 8px;
}
/* Docked (touch): formatting buttons hidden — only Ask for changes + a short
   capsule; the OS callout and Cancel/Save leave no room for a full toolbar */
.duo-draft-seltools.is-docked .duo-draft-fmtbtn {
  display: none;
}
.duo-draft-seltools.is-docked .duo-draft-askinput {
  flex: 0 1 auto;
  width: 170px;
}
.duo-draft-fmtbtn {
  min-width: 28px;
  height: 28px;
  padding: 0 6px;
  border: 0;
  border-radius: calc(6px * var(--corner-k));
  background: transparent;
  color: var(--color-text);
  font-size: 14px;
  cursor: pointer;
}
html:not([data-input="touch"]) .duo-draft-fmtbtn:hover,
html:not([data-input="touch"]) .duo-draft-fmtbtn:active{
  background: var(--color-bg-hover);
}
/* Icon size = the site-wide icon standard (.msg-iconbtn svg): 18px desktop, 15px mobile */
.duo-draft-fmtbtn svg {
  width: var(--icon-size);
  height: var(--icon-size);
  display: block;
  margin: 0 auto;
}
@media (max-width: 768px), (max-height: 500px) {
  .duo-draft-fmtbtn svg {
    width: var(--icon-size-sm);
    height: var(--icon-size-sm);
  }
}
.duo-draft-askinput {
  flex: 0 1 auto;
  width: 190px;
  min-width: 0;
  height: 28px;
  padding: 0 4px 0 10px;
  border: none;
  background: transparent;
  color: var(--color-text);
  font-size: 13px;
  outline: none;
}
/* chat-button is absolutely positioned for the composer pill — in the capsule it
   flows inline; colours/state rules stay untouched from the composer stylesheet */
.duo-draft-seltools .duo-draft-asksend {
  position: static;
  width: 26px;
  height: 26px;
  flex-shrink: 0;
}
.duo-draft-seltools .duo-draft-asksend svg {
  width: 26px;
  height: 26px;
}
.duo-draft-askinput::placeholder {
  color: var(--color-text-tertiary);
}
/* Saved-selection marker while the ask input holds focus (native selection
   collapses on focus change; the ask target is kept in JS) */
::highlight(duo-draft-sel) {
  background: rgba(37, 99, 235, 0.32);
}
.duo-draft-asking .duo-draft-askinput {
  opacity: .5;
}
/* Ask pending: the same light wave the image-edit placeholder runs
   (thinkingImageShimmer), swept across the whole card as an overlay */
.duo-draft-asking::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  corner-shape: inherit;
  pointer-events: none;
  z-index: 5;
  background: linear-gradient(135deg, transparent 38%, var(--color-bg-card) 50%, transparent 62%);
  background-size: 300% 300%;
  animation: thinkingImageShimmer 5s ease-in-out infinite;
}
/* Send button ticks like a watch hand while generating: +95deg fast, settle
   back 5deg (net 90 per tick), four ticks per revolution */
.duo-draft-asking .duo-draft-asksend svg {
  transform-origin: 50% 50%;
  animation: duoDraftClockTick 2.8s infinite;
}
@keyframes duoDraftClockTick {
  0%, 15% { transform: rotate(0deg); }
  17% { transform: rotate(95deg); }
  19%, 40% { transform: rotate(90deg); }
  42% { transform: rotate(185deg); }
  44%, 65% { transform: rotate(180deg); }
  67% { transform: rotate(275deg); }
  69%, 90% { transform: rotate(270deg); }
  92% { transform: rotate(365deg); }
  94%, 100% { transform: rotate(360deg); }
}
.duo-draft-reviewbar {
  display: inline-flex;
  margin: 8px 0;
}
.duo-draft-accept {
  font-weight: 500;
}
.duo-draft-diff-old {
  text-decoration: line-through;
  color: var(--color-text-secondary);
  opacity: .75;
}
.duo-draft-diff-old * {
  text-decoration: line-through;
}
.duo-draft-diff-new {
  border-radius: calc(8px * var(--corner-k));
  padding: 2px 8px;
  margin: 4px -8px;
  background: var(--color-bg-hover);
  /* Same blue family as the unread dot / selection marker (#2563eb) */
  color: #2563eb;
}
.dark-mode .duo-draft-diff-new,
.dark-mode-active .duo-draft-diff-new {
  color: #7fb0ff;   /* lightened on dark, same reasoning as --color-live */
}
/* Flat text button of the toolbar family (reviewbar Undo/Accept) */
.duo-draft-askbtn {
  height: 28px;
  padding: 0 10px;
  border: 0;
  border-radius: calc(10px * var(--corner-k) - 4px);
  background: transparent;
  color: var(--color-text);
  font-size: 13px;
  white-space: nowrap;
  cursor: pointer;
}
html:not([data-input="touch"]) .duo-draft-askbtn:hover,
html:not([data-input="touch"]) .duo-draft-askbtn:active{
  background: var(--color-bg-hover);
}
/* Menu surface = the site dropdown baseline (.sidebar-action-menu): panel bg,
   --menu-shadow/--menu-radius tokens, NO outer border. Absolute in the actions
   anchor so it tracks the card on scroll; left set by JS (anchor-left, down-right). */
.duo-draft-menu {
  position: absolute;
  top: calc(100% + 6px);
  min-width: 180px;
  background: var(--color-bg-panel);
  border-radius: var(--menu-radius);
  box-shadow: var(--menu-shadow);
  padding: 4px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  z-index: 30;
}
/* Solid colour brand marks read optically heavier than line icons — one size
   down (the !important beats the 24px !important in the .action-icon baseline) */
.duo-draft-menu .action-icon svg {
  width: var(--icon-size) !important;
  height: var(--icon-size) !important;
}
.duo-draft-menu__item {
  display: flex;
  align-items: center;
  gap: 10px;   /* icon-to-label rhythm = .action-item baseline */
  height: 38px;
  padding: 0 12px;
  border: 0;
  border-radius: var(--radius-menu-row);
  background: transparent;
  color: var(--color-text);
  font-size: 14px;
  text-align: left;
  cursor: pointer;
}
html:not([data-input="touch"]) .duo-draft-menu__item:hover,
html:not([data-input="touch"]) .duo-draft-menu__item:active{
  background: var(--color-bg-hover);
}
.duo-draft-body {
  padding: 12px 14px;
  font-size: 14px;
  line-height: 1.6;
  color: var(--color-text);
  max-height: 360px;
  overflow-y: auto;
}
/* Same paragraph rhythm as the bubble markdown baseline (.bubble.md-rendered p) —
   the card body carries md-rendered without .bubble so it misses those rules */
.duo-draft-body p {
  margin: 0 0 var(--md-para-gap) !important;
}
.duo-draft-body p:last-child {
  margin-bottom: 0 !important;
}
/* 版本更新提示:描边短暂点亮一下(原地效果,不位移;卡无常驻边框,走 box-shadow 环) */
.duo-draft-bump { animation: duoDraftBump 0.9s ease-out; }
@keyframes duoDraftBump {
  0% { box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.45); }
  100% { box-shadow: 0 0 0 1px rgba(0, 0, 0, 0); }
}
.dark-mode .duo-draft-bump,
.dark-mode-active .duo-draft-bump { animation-name: duoDraftBumpDark; }
@keyframes duoDraftBumpDark {
  0% { box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.45); }
  100% { box-shadow: 0 0 0 1px rgba(255, 255, 255, 0); }
}

/* =========================
   ✅ PDF Viewer Overlay
   ========================= */

/* PDF viewer: 弹窗模式（居中圆角 + 半透明遮罩） */
.pdf-viewer-overlay {
  position: fixed;
  inset: 0;
  /* 查看器那一档(同 .ai-image-viewer):全屏接管的查看器必须压过点开它的面板
     (从维度面板里点开一个文件,预览曾出现在面板【下面】,owner 2026-09-05);
     从它里面叫出来的改名/删除确认、它网格上的选中操作条,各自在更上面的档(见 --z-* 唯一源)。 */
  z-index: var(--z-viewer);
  background: rgba(0,0,0,0.5);
  display: flex;
  align-items: center;
  justify-content: center;
}

.pdf-viewer-dialog {
  display: flex;
  flex-direction: column;
  width: calc(100% - 60px);
  max-width: 960px;
  height: 90vh;
  background: var(--color-bg-panel);
  border-radius: var(--modal-radius);   /* 全站弹窗窗口倒角单一源 */
  overflow: hidden;
  box-shadow: 0 8px 32px rgba(0,0,0,0.2);
}

.pdf-viewer-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 16px;
  background: var(--color-bg-panel);
  flex-shrink: 0;
}

.pdf-viewer-title {
  font-size: 16px;               /* 标题族基准(字号阶梯,原 15px 孤值) */
  /* 400 与面包屑当前项一致:本类如今【只剩查看器外壳一个使用方】(旧 PDF overlay 已物删),
     不再是共享基准。同一个文件名不该因为"有没有面包屑"而渲染成两种字重。 */
  font-weight: 400;
  font-family: "Flair", "Jost Fallback", "PingFang SC", "Microsoft YaHei", sans-serif;
  color: var(--color-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1;
  padding: 0 12px;
}

.pdf-viewer-close {
  flex-shrink: 0;   /* 尺寸/图标走并列的 .project-modal-close(28方/14) */
}

.pdf-viewer-frame {
  flex: 1;
  border: none;
  width: calc(100% - 30px);
  margin: 0 15px 15px;
  border-radius: 0;
}

/* PDF viewer 暗色模式 */
.pdf-viewer-overlay.dark-mode {
  background: rgba(0,0,0,0.7);
}
.pdf-viewer-overlay.dark-mode .pdf-viewer-dialog {
  background: #212121;
  box-shadow: 0 8px 32px rgba(0,0,0,0.5);
}
.pdf-viewer-overlay.dark-mode .pdf-viewer-header {
  background: #212121;
}
.pdf-viewer-overlay.dark-mode .pdf-viewer-title {
  color: #e5e5e5;
}

/* PDF viewer 移动端：全屏 */
@media (max-width: 768px), (max-height: 500px) {
  /* Full screen here, so the scrim is opaque: nothing of the page shows through, and blurring what cannot be seen
     only costs a GPU pass on the device least able to afford one. */
  .pdf-viewer-overlay { background: #000; -webkit-backdrop-filter: none; backdrop-filter: none; }
  .pdf-viewer-dialog {
    width: 100%;
    height: 100%;
    border-radius: 0;
    box-shadow: none;
  }
  .pdf-viewer-frame {
    width: 100%;
    margin: 0;
    border-radius: 0;
  }
}

/* --- 其他夜间模式补充 --- */
.dark-mode .chat-messages .bubble img.chat-image,
.dark-mode .msg .bubble .bubble-attachment img { background: transparent; }
/* 用户 bubble 只含图片/文件卡片时，移除背景（覆盖 base.css 的 #303030） */
.dark-mode .chat-messages .msg.user:where(:not(.studio-meeting-msg)) .bubble:has(img),
.dark-mode .chat-messages .msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-attachments),
.dark-mode .chat-messages .msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-pdf-card),
.dark-mode .chat-messages .msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-doc-card),
.dark-mode .chat-messages .msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-sheet-card),
.dark-mode .chat-messages .msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-code-card),
.dark-mode .chat-messages .msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-audio-card),
.dark-mode .chat-messages .msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-video-card),
.dark-mode .chat-messages .msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-archive-card),
.dark-mode .chat-messages .msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-ppt-card),
.dark-mode .chat-messages .msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-psd-card) {
  background: transparent;
  box-shadow: none;
}
/* 以下 dark-mode 覆盖需要保留：base.css 的 .dark-mode .msg.assistant/.user .bubble 优先级 (0,5,0)
   会覆盖 bubbles.css 中 :has() 规则 (0,4,1)，必须用更高优先级重新声明 */
.dark-mode .chat-messages .msg.assistant:where(:not(.studio-meeting-msg)) .bubble:has(img) { background: var(--color-bg-panel); box-shadow: 0 0 0 1px var(--color-border) inset; }
.dark-mode .chat-messages .msg.user:where(:not(.studio-meeting-msg)) .bubble:has(img):has(.bubble-text),
.dark-mode .chat-messages .msg.user:where(:not(.studio-meeting-msg)) .bubble:has(img):has(.bubble-content),
.dark-mode .chat-messages .msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-attachments):has(.bubble-text),
.dark-mode .chat-messages .msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-attachments):has(.bubble-content) { background: var(--color-bg-surface); }
.dark-mode .chat-messages .msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-pdf-card):has(.bubble-text),
.dark-mode .chat-messages .msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-doc-card):has(.bubble-text),
.dark-mode .chat-messages .msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-sheet-card):has(.bubble-text),
.dark-mode .chat-messages .msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-code-card):has(.bubble-text),
.dark-mode .chat-messages .msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-audio-card):has(.bubble-text),
.dark-mode .chat-messages .msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-video-card):has(.bubble-text),
.dark-mode .chat-messages .msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-archive-card):has(.bubble-text),
.dark-mode .chat-messages .msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-ppt-card):has(.bubble-text),
.dark-mode .chat-messages .msg.user:where(:not(.studio-meeting-msg)) .bubble:has(.bubble-psd-card):has(.bubble-text) { background: var(--color-bg-surface); }
.dark-mode .chat-messages .msg.assistant .bubble.thinking { background: #2a2a2a; border-color: rgba(255,255,255,0.1); color: rgba(255,255,255,0.6); }
.dark-mode .chat-messages .bubble.thinking::after { background: linear-gradient(90deg, rgba(42,42,42,0) 0%, rgba(60,60,60,0.8) 45%, rgba(42,42,42,0) 80%); }
.dark-mode .ai-text { color: #e5e5e5; }
.dark-mode .msg.user .bubble .bubble-attachment { background: transparent; box-shadow: none; }
.dark-mode .msg.user .bubble .bubble-text { color: #e5e5e5; }

/* 判断文档「参考图」画廊（ui-chat-ref-gallery.js 后处理生成）——【视觉基准 = 图片页
   .images-grid】(用户 2026-07-17 定稿)：无圆角 + 4px 密缝间距 + 1:1 cover，"档案方阵"
   观感与 Images 页一致；不再走聊天气泡的 --img-radius/--img-gap 圆角语言（有意例外）。
   ★有意例外（用户点名）：本画廊【不带】标准图片边线 var(--color-border)——参考图多为
   白底优化图，成片方阵下边线显噪；非漏写，勿"顺手补齐"。
   caption 用 opacity 不硬编码颜色 → 暗色自动安全。看细节交给灯箱（点缩略图开原图）。 */
.duo-ref-gallery {
  /* 3 列等分撑满气泡宽（固定方块锁不齐右缘,等分网格每行严丝合缝）。 */
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 4px;                            /* 基准=.images-grid（ai-chat-images-page.css）的 4px 密缝 */
  margin: 12px 0 4px;
}
.duo-ref-item { margin: 0; min-width: 0; position: relative; }   /* 配文浮层定位基准 */
/* ★ 独立卡模式(.jgallery-stage,主聊天判断参考图;用户 2026-07-23 定"文字卡外"):
   气泡内的 3 列等分网格是"撑满容器"设计,搬进 fit-content 卡会留空列+margin 叠加=四边不等。
   卡模式改收缩包裹:flex wrap+固定瓦片(220px=占位家族 .thinking-image-block 既有档位)+画廊 margin 归零,
   卡的 12px 内距即四边唯一间距。镜像文档画廊(bubble 内)不受影响。 */
.jgallery-stage .duo-ref-gallery {
  display: flex;
  flex-wrap: wrap;
  margin: 0;
}
.jgallery-stage .duo-ref-item {
  width: 220px;
  max-width: 100%;
}
/* 手机:220px 瓦片在窄卡里塞不下两张→竖排(2026-07-23 真机)。回到 2 列等分(镜像画廊移动端同基准),
   卡撑满可用宽(85% 同 agent-img-stage),网格填满=四边仍等距。 */
@media (max-width: 768px), (max-height: 500px) {
  .chat-messages .msg.assistant .jgallery-stage { width: 85%; }
  .jgallery-stage .duo-ref-gallery { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .jgallery-stage .duo-ref-item { width: auto; }
}
.duo-ref-thumb {
  display: block; aspect-ratio: 1 / 1;
  border-radius: 0; overflow: hidden;   /* 基准=.images-grid-item 的无圆角(方阵语言) */
  background: var(--color-bg-tertiary, #f3f3f3);        /* 同上传附件底色 */
  cursor: zoom-in;
}
/* 提高特异性压过 .msg.assistant .bubble img 的 height:auto（同 agent 做法，靠特异性+源码顺序，不用 !important） */
.duo-ref-gallery .duo-ref-thumb img.duo-ref-img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform 0.15s;         /* hover 放大基准=.images-grid-item img（整套照搬） */
}
/* :not([data-input="touch"]) 守卫同 Images 页：hover-capable 触屏不触发放大；
   scale 被 .duo-ref-thumb 的 overflow:hidden 裁在格内(非 5.3 禁的"抬升离位") */
html:not([data-input="touch"]) .duo-ref-item:hover img.duo-ref-img{
  transform: scale(1.05);
}

.duo-ref-item figcaption {
  /* 配文=图内底部浮层(用户定稿方向):渐变暗底+白字保证任意图上可读——这组颜色是
     压在照片上的胶片式 scrim,主题无关,非硬编码主题色。hover 淡入、只动 opacity
     不位移;pointer-events:none 不挡点击开灯箱;圆角随瓦片底部裁齐。 */
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 18px 8px 8px;
  text-align: center; font-size: 11px; line-height: 1.35;   /* 站内次要小字主力档(同头像缩写/横幅) */
  color: #fff;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.55), rgba(0, 0, 0, 0));
  border-radius: 0;                    /* 随瓦片改无圆角 */
  pointer-events: none;
  word-break: break-word;
  opacity: 0;
  transition: opacity .12s ease;
}
html:not([data-input="touch"]) .duo-ref-item:hover figcaption{ opacity: 1; }

.duo-ref-item:active figcaption { opacity: 1; }   /* 触摸按下反馈(5.3 配对) */
@media (hover: none) and (pointer: coarse) {
  .duo-ref-item figcaption { opacity: 1; }        /* 触屏常驻 */
}
/* 手机/窄屏（≤768px，全站惯例断点）：两列等分（同上传附件的手机两张并排思路） */
@media (max-width: 768px), (max-height: 500px) {
  .duo-ref-gallery { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ── 等人定一件事的那张卡 ────────────────────────────────────────────────────
   ★形与字全部走站里的真源,一个数都不新造:
     窗口倒角 = --modal-radius(全站弹窗唯一源) · 底 = --color-bg-panel
     阴影 = 同 .settings-modal · 关闭钮 = .chat-header-btn(DOM 上并列那个 class)
     字号 16/14/13、字重 500 = 设置弹窗那套 · 圆角 8px = 站里按钮/标签的圆角
   ★头部那条线要【贯穿】:所以内边距给内容,不给盒子 —— 给了盒子,线就在两边缩进去一截。 */
.ask-choice {
  /* 它是从输入框胶囊拉起来的,所以倒角/阴影/边线【同 pill 一个源】—— 两个读成一件东西 */
  border-radius: var(--composer-pill-radius);
  corner-shape: round;
  border: 1px solid var(--color-border);
  background: var(--color-bg-panel);
  color: var(--color-text);
  box-shadow: var(--composer-pill-shadow);
  overflow: hidden;
}
.ask-choice-head {
  display: flex; align-items: center; gap: 18px;
  padding: 0 18px;
  border-bottom: 1px solid var(--color-border);
}
.ask-choice-body { padding: 18px; }
.ask-choice-foot { padding: 0 18px 18px; }

/* 不许滚:它一滚,滚动条就出现在关闭钮旁边(真机上那根"乱七八糟的"就是它) */
.ask-choice-tabs { display: flex; gap: 18px; align-items: center; flex: 1; min-width: 0; overflow: hidden; }
.ask-choice-tab {
  padding: 14px 0; margin-bottom: -1px;
  border: 0; border-bottom: 1px solid transparent;
  background: transparent; color: var(--color-text-secondary);
  font: inherit; font-size: 14px; white-space: nowrap; cursor: pointer;
}
.ask-choice-tab.is-on { color: var(--color-text); border-bottom-color: currentColor; }
.ask-choice-tab.is-answered::after { content: " ✓"; }
.ask-choice-nav {
  display: flex; align-items: center; justify-content: center;
  width: var(--header-btn-box); height: var(--header-btn-box); padding: 0;
  border: 0; border-radius: calc(6px * var(--corner-k)); background: transparent;
  color: var(--color-text-secondary); font: inherit; font-size: 15px; cursor: pointer;
}
.ask-choice-nav:disabled { color: var(--color-text-tertiary); cursor: default; }
html:not([data-input="touch"]) .ask-choice-nav:not(:disabled):hover { background: var(--color-bg-card); }
/* tabular-nums:每个数字等宽,1→2 翻页时 "of 3" 不跳(同查看器页码那处的治法) */
.ask-choice-count { font-size: 13px; color: var(--color-text-secondary); flex: 1; font-variant-numeric: tabular-nums; }
.ask-choice-close { margin-left: auto; }

.ask-choice-question { font-size: 14px; font-weight: 500; line-height: 1.5; margin-bottom: 10px; }
/* 选项 = 站里的单选行真源(.memory-off-option + .memory-off-radio,夜间模式现成),
   这里只补文字那一列的排布,不定义任何观感值 */
/* ★行是【恒定网格】:圈永远占第一列(定宽 20 = .memory-off-radio 的盒),内容列随便变 ——
   选中 Other 展开胶囊、答案换行,都推不动圈。真机撞过:flex 行里内容一变,圈就挪窝。 */
.ask-choice-option {
  color: var(--color-text);
  display: grid; grid-template-columns: 20px 1fr; column-gap: 12px; align-items: start;
}
.ask-choice-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
/* 圈对齐【第一行文字的中心】:真源的 margin-top:1px 是按一行文字设计的,标题行高 21、圈 20,
   1px 恰好居中 —— 但 Other 行的内容是 33px 的胶囊行、文字在其中居中,圈就得跟整行居中 */
.ask-choice-otherrow .memory-off-radio { align-self: center; margin-top: 0; }
/* Other 的输入:同一行贯穿的胶囊,形随主输入(边线/倒角同 --composer-pill 那套),
   长高逻辑也照主输入 —— 一行起步、字多了长、到上限滚(算法在 JS,同 initTextarea) */
.ask-choice-otherrow .ask-choice-text { flex-direction: row; align-items: center; gap: 12px; flex: 1; }
.ask-choice-other-input {
  flex: 1; min-width: 0; padding: 6px 14px; resize: none;
  /* ★主题 theme.css 给一切 textarea 压了 min-height:4lh(四行)+ field-sizing:content ——
     初始那个大方块就是它。主输入(.chat-input)也是这样各自覆盖的;高度归 JS 管(同主输入算法) */
  min-height: 0; field-sizing: fixed;
  border: 1px solid var(--color-border); border-radius: var(--composer-pill-radius);
  corner-shape: round;
  background: transparent; color: var(--color-text); font: inherit; font-size: 14px; line-height: 1.5;
}
.ask-choice-other-input:focus { outline: none; border-color: var(--color-text-tertiary); }
.ask-choice-label { font-size: 14px; }
.ask-choice-hint { font-size: 13px; line-height: 1.5; color: var(--color-text-secondary); }
.ask-choice-detail { white-space: pre-wrap; overflow-wrap: anywhere; max-height: 180px; overflow-y: auto; margin-bottom: 10px; }
.ask-choice-other { font-size: 14px; color: var(--color-text-secondary); }
/* 提交钮:形与位全取 .chat-button 基准(主输入框那颗圆钮:34 高 / 999px / 距右下各 7px)——
   胶囊右端的圆弧与那颗圆钮同位同高,两个才读成同一家的东西 */
.ask-choice-go {
  height: 34px; padding: 0 16px; border: 0; border-radius: 999px;
  corner-shape: round;
  background: var(--color-bg-card); color: var(--color-text);
  font: inherit; font-size: 14px; font-weight: 500; cursor: pointer;
}
.ask-choice-go:disabled { color: var(--color-text-tertiary); cursor: default; }
html:not([data-input="touch"]) .ask-choice-go:not(:disabled):hover { background: var(--color-bg-surface); }
/* 选项行 hover:同面板里其它可点件一样给一层 --color-bg-card,左右各让 10px 出血才不贴字 */
html:not([data-input="touch"]) .ask-choice-option:hover,
html:not([data-input="touch"]) .ask-choice-otherrow:hover {
  background: var(--color-bg-card); border-radius: calc(8px * var(--corner-k));
}
.ask-choice-option { padding-left: 10px; padding-right: 10px; margin-left: -10px; margin-right: -10px; width: calc(100% + 20px); }
.ask-choice[data-answered], .ask-choice[data-dismissed] { pointer-events: none; }
.ask-choice[data-dismissed] { display: none; }

/* 弹在【输入框上方】,不进聊天记录。底部偏移由 JS 量 #aiComposerBar 的真实位置写进来。 */
.ask-choice-sheet {
  /* 左边/宽度/底边全部由 JS 量输入框那颗 pill 写进来 —— 它是从 pill 上长出来的,不是居中飘着的一块板 */
  position: fixed; bottom: 0; z-index: 60;
  pointer-events: none;
  transform: translateY(16px); opacity: 0; visibility: hidden;
  transition: transform 220ms cubic-bezier(.32,.72,0,1), opacity 160ms ease, visibility 0s linear 220ms;
}
.ask-choice-sheet[data-open] {
  pointer-events: auto; transform: none; opacity: 1; visibility: visible; transition-delay: 0s;
}
/* 面板本身不滚:它一滚,滚动条压在头部和标签上(实测冒出一根系统原生带箭头的)。
   要滚的只有正文,而且走站里唯一那套滚动条模板 .duo-scroll(JS 已给 body 挂了这个 class)。 */
.ask-choice-sheet .ask-choice { width: 100%; }
.ask-choice-body { max-height: 46vh; overflow-y: auto; }
/* 单选圈不在这里画:它整个来自 .memory-off-radio 那套真源(含选中点和夜间模式) */
/* ★拉起 = pill 淡出、面板从它的位置升起;收回反着来 —— 两段接在一起才读成"从输入框拉起来"。
   visibility 藏(几何还在,锚点才量得到),opacity 负责过渡。 */
.composer-bar .composer-pill { transition: box-shadow 0.3s ease-in-out, opacity 160ms ease, visibility 0s linear; }
.composer-bar.ask-choice-open .composer-pill { opacity: 0; visibility: hidden; transition-delay: 0s, 0s, 160ms; }
/* 站里没有整条深色大按钮这种东西 —— 右下角一颗普通按钮,Esc 提示占左边 */
.ask-choice-sheet .ask-choice-foot { display: flex; align-items: center; gap: 12px; }
/* 右下角:胶囊右端圆弧与主输入框那颗圆钮的右顶点重合(它距 pill 右 7px、底 7px) */
.ask-choice-sheet .ask-choice-foot { padding: 0 7px 7px; }
.ask-choice-sheet .ask-choice-go { margin-left: auto; }
/* 手机:照 Claude Code 手机版那张(owner 图 2)。卡片占满 pill 宽(它本就近全宽),
   高度由 JS 顶到"对话最后一行之下"为止;放不下的正文自己滚。 */
@media (max-width: 768px), (max-height: 500px) {
  .ask-choice-sheet .ask-choice { max-width: none; }
  /* 手机不要 header 那条线(owner 定)。‹ 1 of 3 › 收成紧凑一组靠左:
     count 的 flex:1 是桌面布局的(那边它把关闭钮推到右端),在手机上会把数字和右箭头
     拉开一整行,箭头组就散了 —— 关闭钮靠 .ask-choice-close 自己的 margin-left:auto 去右边 */
  /* 高度照桌面 tab 版的节奏(tab 自身上下 14px)—— 手机版没有 tab 撑高,得由 head 自己给 */
  .ask-choice-sheet .ask-choice-head { border-bottom: 0; gap: 4px; padding-top: 14px; padding-bottom: 6px; }
  .ask-choice-sheet .ask-choice-count { flex: none; }
  /* 图 2 无单选圈:选项 = 加粗标题 + 说明,选项之间一条贯穿的发丝线,选中打 ✓(同 tab 的记号) */
  .ask-choice-sheet .memory-off-radio,
  .ask-choice-sheet .ask-choice-option input[type="radio"] { display: none; }
  /* 圈藏了,它那列也得收掉,否则每行左边空出 20px 的坑 */
  .ask-choice-sheet .ask-choice-option { grid-template-columns: 1fr; }
  .ask-choice-sheet .ask-choice-option + .ask-choice-option,
  .ask-choice-sheet .ask-choice-otherrow { border-top: 1px solid var(--color-border); }
  .ask-choice-sheet .ask-choice-option .ask-choice-label { font-weight: 500; }
  /* 选中不打对号(owner 定):选中的那行铺一层卡片底色,同面板里其它选中/hover 的记号 */
  .ask-choice-sheet .ask-choice-option.is-picked { background: var(--color-bg-card); border-radius: calc(8px * var(--corner-k)); }
  /* 提交钮:实心反色胶囊带箭头(token 互换,夜间自动翻),同图 2 右下那颗 */
  .ask-choice-sheet .ask-choice-go { background: var(--color-text); color: var(--color-bg-panel); padding: 0 20px; }
  .ask-choice-sheet .ask-choice-go:disabled { background: var(--color-bg-card); color: var(--color-text-tertiary); }
  .ask-choice-sheet .ask-choice-go::after { content: " \2192"; }
  .ask-choice-sheet .ask-choice-foot { padding: 0 16px 16px; }
}
@media (prefers-reduced-motion: reduce) { .ask-choice-sheet { transition: none; } }
