*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

/* 自托管 Source Serif 4 (英文衬线): 绕开 Google Fonts (国内慢/被墙), 走同源 /static/fonts。
   变体字体, 一个文件覆盖 400–700 字重; 中文不在此字体内, 自动回退到 --font-serif 栈里的宋体。 */
@font-face {
  font-family: "Source Serif 4";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/static/fonts/SourceSerif4-var.woff2") format("woff2");
}
@font-face {
  font-family: "Source Serif 4";
  font-style: italic;
  font-weight: 400 700;
  font-display: swap;
  src: url("/static/fonts/SourceSerif4-italic.woff2") format("woff2");
}

:root {
  /* --ui-base：顶栏/底栏；--page-bg：主内容区（均可在 ui-config.js 覆盖） */
  --ui-base:   #000000;
  --page-bg:   #000000;
  --bg:        var(--page-bg);
  --chrome-bg: var(--ui-base);
  --chrome-control-bg: color-mix(in srgb, var(--ui-base) 82%, #ffffff);
  --surface:   #012e37;
  /* news 列表 + detail 的衬线字体 (用户要求): Source Serif 4 webfont, 系统衬线 / 宋体兜底 */
  --font-serif: "Source Serif 4", Georgia, "Times New Roman", "Songti SC", serif;
  --border:    #3a3f48;
  --accent:    #4a9eff;
  --accent-h:  #2d7fd4;
  --text:      #e8eef3;
  --text2:     #a8b8c6;
  --muted:     #7d8f9f;
  --tag-bg:    color-mix(in srgb, var(--ui-base) 55%, #1e3a50);
  --tag-text:  #8ec5ff;
  --radius:    12px;
  /* 宽屏下主列与 #tab-bar 对齐（与 min-width:1024px 底栏一致） */
  --chrome-column-max: 860px;
  --chrome-gutter: 0.75rem;
  --header-h:  42px;
  /* 与顶栏实际高度一致（旧版 +2px 曾配合已移除的 ::after 分隔线） */
  --header-stack-h: var(--header-h);
  /* 底栏可视高度 = 图标外框，避免上下留白. 84→64 收窄, 给内容多留空间 */
  --tab-icon-outer: 64px;
  --tab-h:          var(--tab-icon-outer);
  --card-hover:  #2e343c;
  --card-active: #343b45;
  --detail-body: #c4ced8;
  --badge-lang-bg:   #2a2048;
  --badge-lang-text: #c9b8fc;
  --header-shadow:   rgba(0, 0, 0, 0.35);
  --tab-bar-shadow:  rgba(0, 0, 0, 0.4);
  --filter-shadow:   rgba(0, 0, 0, 0.3);
  --chat-bg:           var(--page-bg);
  --chat-msg-asst-bg:  var(--surface);
  --chat-msg-asst-text:#e8eaed;
  --chat-msg-user-bg:  #2d7a42;
  --chat-msg-user-text:#e8eaed;
  --chat-msg-name:     #8a9aa8;
  --chat-input-bar-bg: color-mix(in srgb, var(--page-bg) 88%, #000000);
  --chat-input-bar-border: #3d454e;
  --chat-input-bg:     #2a3038;
  --chat-input-text:   #e8eaed;
  --chat-input-focus:  #22c55e;
  --chat-send-bg:      #3CB371;
  --chat-send-bg-h:    #329963;
  --chat-send-disabled:#4a5568;
  --chat-keyboard-dock-overlap: 8px;
  /* 与发送按钮尺寸一致；输入条整体调矮时同步略减 */
  --chat-composer-ctrl-h: calc(12px + 1.375rem);
  --chat-avatar-bg:    #3a4d5c;
  --chat-typing-dot:   #6b7888;
  --cal-weekend:       #ff8a80;
  color-scheme: dark;
}

[data-theme="dark"] {
  --ui-base:   #000000;
  --page-bg:   #000000;
  --bg:        var(--page-bg);
  --chrome-bg: var(--ui-base);
  --chrome-control-bg: color-mix(in srgb, var(--ui-base) 78%, #ffffff);
  --surface:   #012e37;
  --border:    #32363d;
  --accent:    #6eb0ff;
  --accent-h:  #4a90ea;
  --text:      #eef2f6;
  --text2:     #b4c0cc;
  --muted:     #8b96a3;
  --tag-bg:    color-mix(in srgb, var(--ui-base) 50%, #1a3a50);
  --tag-text:  #a8d4ff;
  --card-hover:  #252a32;
  --card-active: #2a3038;
  --detail-body: #d0d8e0;
  --badge-lang-bg:   #352858;
  --badge-lang-text: #d4c4fc;
  --header-shadow:   rgba(0, 0, 0, 0.5);
  --tab-bar-shadow:  rgba(0, 0, 0, 0.55);
  --filter-shadow:   rgba(0, 0, 0, 0.4);
  --chat-bg:           var(--page-bg);
  --chat-msg-asst-bg:  #252b34;
  --chat-msg-asst-text:#eef2f6;
  --chat-msg-user-bg:  #2f6b42;
  --chat-msg-user-text:#e8eaed;
  --chat-msg-name:     #9aa6b0;
  --chat-input-bar-bg: color-mix(in srgb, var(--page-bg) 90%, #000000);
  --chat-input-bar-border: #3d4d5c;
  --chat-input-bg:     #222830;
  --chat-input-text:   #eef2f6;
  --chat-input-focus:  #4ade80;
  --chat-send-bg:      #3CB371;
  --chat-send-bg-h:    #329963;
  --chat-send-disabled:#4a5568;
  --chat-avatar-bg:    #3d4d5e;
  --chat-typing-dot:   #6b7580;
  --cal-weekend:       #ffab91;
  color-scheme: dark;
}

/* 关掉文档级橡皮筋回弹: iOS standalone 的滚动容器是 **html (documentElement)**, 之前只写在 body 上
   不生效 (用户实测"到顶/底停不下来一直弹")。overscroll-behavior 必须挂到真正的滚动元素 html 上。
   iOS 16.4+ 支持; 列表下拉刷新是自写 touch 逻辑 (preventDefault + transform), 不依赖原生 overscroll, 不受影响。 */
html {
  overscroll-behavior: none;
}
body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", sans-serif;
  background: var(--bg);
  color: var(--text);
  min-height: 100vh;
  min-height: 100dvh;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  /* 给全宽贴底 flat bar 让位 (高度 ≈ 顶 padding + 图标 + 安全区) */
  padding-bottom: calc(var(--tab-h) + var(--safe-bottom) + 12px);
  transition: padding-bottom 0.28s cubic-bezier(0.4, 0, 0.2, 1);
  overscroll-behavior: none;
}

/* ══════════════════════════════════════════
   HEADER
══════════════════════════════════════════ */
.site-header-wrap {
  position: sticky;
  top: 0;
  z-index: 200;
  /* 顶栏与底栏同色: 抬升面 (--chrome-surface, 定义在 index.html 内联 :root)。⚠️ 与内联同名规则同步。 */
  background: var(--chrome-surface);
}

header.site-header-minimal {
  background: var(--chrome-surface);
  border-bottom: var(--chrome-hairline);
  height: var(--header-h);
  padding: 0 0.45rem;
  display: grid;
  grid-template-columns: minmax(40px, 1fr) auto minmax(40px, 1fr);
  align-items: stretch;
  position: relative;
}
/* 所有 header 子元素锁第 1 行: cipher 下 col3 同时有「+」和返回, 不锁行会被自动排布挤到第 2 行,
   把 logo 一起带下去 (那个"logo 掉下来"的根因)。 */
header.site-header-minimal > * { grid-row: 1; }

.header-menu {
  grid-column: 1;
  justify-self: start;
  align-self: center;
  display: inline-flex;
  flex-direction: column;
  justify-content: center;
  align-items: flex-start;           /* 两道横线左对齐, 配合上长下短 */
  gap: 7px;
  width: 36px;
  height: 32px;
  margin-left: 0.35rem;
  padding: 0 6px;
  background: none;
  border: none;
  cursor: pointer;
}
/* 两道横线: 上长下短, 线条加粗加宽 */
.header-menu span {
  display: block;
  height: 4px;                        /* 用户要求横线更粗 (原 3px) */
  border-radius: 999px;
  background: var(--text);
  box-shadow: 0 0 6px rgba(255, 255, 255, 0.18);
}
.header-menu span:first-child { width: 24px; }
.header-menu span:last-child  { width: 15px; }
/* 菜单(侧栏)在所有页面常驻, 含 news detail —— 详情页返回键移到右上角, 左上角留给侧栏。 */
/* 侧栏打开锁滚动: position:fixed + JS 存档 scrollY (见 setSideDrawerOpen)。
   不再用 touch-action:none —— iOS Safari 解锁后触摸滚动经常不恢复 (换字号 reflow 后尤甚)。
   top 由 JS 内联设成 -scrollY, 这里给 left/right/width 保持整页宽度不抖。 */
body.side-drawer-open {
  position: fixed;
  left: 0;
  right: 0;
  width: 100%;
  overflow: hidden;
}

.header-back {
  grid-column: 1;
  justify-self: start;
  align-self: center;
  display: none;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  min-width: 34px;
  margin-left: 0.35rem;
  padding: 0 2px 1px 0;              /* ‹ 字形视觉居中微调 */
  background: rgba(255, 255, 255, 0.08);   /* 透明圆形背景 (用户要求) */
  border: none;
  border-radius: 50%;
  color: var(--text);
  font-size: 1.65rem;
  font-weight: 300;
  line-height: 1;
  cursor: pointer;
  transition: color 0.15s, background 0.15s;
}
.header-back:hover { color: var(--accent); background: rgba(255, 255, 255, 0.15); }
.header-back.visible { display: inline-flex; }
/* cipher (chat) + news detail: 返回放到右上角, 左上角让位给常驻菜单(侧栏) */
body.in-chat .header-back,
body.article-detail-open .header-back {
  grid-column: 3;
  justify-self: end;
  margin-left: 0;
  margin-right: 0.35rem;
}

/* 发起新对话按钮已移到 cipher 输入框最左侧 (#chat-new), 见 #chat-input-bar 区。 */

.side-drawer {
  position: fixed;
  inset: 0;
  z-index: 900;
  pointer-events: none;
  visibility: hidden;
}
.side-drawer.open {
  pointer-events: auto;
  visibility: visible;
}
.side-drawer-backdrop {
  position: absolute;
  inset: 0;
  border: none;
  background: rgba(0, 0, 0, 0);
  opacity: 0;
  transition: opacity 0.22s ease, background 0.22s ease;
}
.side-drawer.open .side-drawer-backdrop {
  background: rgba(0, 0, 0, 0.46);
  opacity: 1;
}
.side-drawer-panel {
  position: absolute;
  inset: 0;                          /* 全屏侧栏 (用户要求): 宽屏更宽松, 设置项一行不挤 */
  width: 100%;
  padding: calc(env(safe-area-inset-top) + 22px) 28px calc(var(--safe-bottom) + 28px);
  /* 主体色统一到 chrome 抬升面 (与顶栏/底栏/输入框同一 #1a1a1a, 用户要求); 控件用更亮的
     --chrome-control-bg, 仍有对比不糊。 */
  background: var(--chrome-surface);
  color: #f4f4f4;
  box-shadow: 18px 0 38px rgba(0, 0, 0, 0.38);
  transform: translateX(-102%);
  transition: transform 0.26s cubic-bezier(0.22, 1, 0.36, 1);
  overflow: hidden;                  /* 不可滑动: 内容固定排布, 超出裁掉 */
  display: flex;
  flex-direction: column;
}
.side-drawer.open .side-drawer-panel {
  transform: translateX(0);
}
/* drawer 返回按钮: 在 profile 那一行 (header) 内靠右 (margin-left:auto), 不再绝对定位浮角上 */
.drawer-close {
  flex: 0 0 auto;
  margin-left: auto;                /* 推到 header 行最右 */
  width: 40px;
  height: 40px;
  border: none;
  background: rgba(255, 255, 255, 0.08);   /* 透明圆形背景 (用户要求) */
  border-radius: 50%;
  color: var(--text);
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: color 0.15s, background 0.15s;
}
.drawer-close:hover { color: var(--accent); background: rgba(255, 255, 255, 0.15); }
/* 设置项 label 与控件同一行 (用户要求每项一行不换行): label 固定窄列, 控件 flex:1 填满右侧,
   不留中缝 (避免早期"左空右挤")。 */
.drawer-row {
  display: flex;
  align-items: center;
  gap: 0.7rem;
}
.drawer-row .drawer-section-label {
  margin-bottom: 0;
  flex: 0 0 5.5rem;           /* 加宽以容下放大后的 Timezone/Language 单行 */
}
.drawer-row .font-size-picker { flex: 1; }
.drawer-row .font-size-btn { flex: 1; }
.drawer-row .drawer-select { flex: 1; width: auto; }
/* 头像 + 昵称 + 账号名 同一行 (用户要求): 头像左, 昵称与 @handle 横排居中 */
.drawer-profile {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  margin-bottom: 1.1rem;             /* 收窄: 设置项整体上移 (用户要求) */
}
.drawer-avatar {
  display: block;
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  object-fit: cover;
  margin-bottom: 0;
}
.drawer-name {
  font-size: 1.15rem;
  font-weight: 800;
  line-height: 1.2;
}
.drawer-handle {
  color: #f4f4f4;            /* 侧栏文字一律白色, 不用暗灰 (用户要求) */
  font-size: 0.9rem;
  line-height: 1.2;
}

/* 字体大小 picker: 4 个 "A" 按钮, size 递增字号让用户直接看出差距 */
.drawer-section {
  margin-top: 0.55rem;                /* 收紧: 设置项整体上移 (用户要求) */
  padding: 0.6rem 0;                  /* 横向不加内边距: 面板已有 padding, 双层会在窄侧栏挤掉控件 */
  border-top: 1px solid color-mix(in srgb, #fff 8%, transparent);
}
.drawer-section-label {
  font-size: 0.95rem;        /* 调大 (原 0.78) */
  white-space: nowrap;       /* 不换行 (用户要求): 配下方加宽的 flex-basis, Timezone/Language 单行 */
  color: #f4f4f4;            /* 侧栏文字一律白色 (用户要求) */
  margin-bottom: 0.55rem;
  font-weight: 600;
}
.font-size-picker {
  display: flex;
  gap: 0.4rem;
  align-items: flex-end;
}
.font-size-btn {
  flex: 1;
  height: 40px;
  border-radius: 10px;
  border: 1px solid color-mix(in srgb, #fff 14%, transparent);
  background: color-mix(in srgb, #fff 6%, transparent);
  color: #fff;
  font-weight: 700;
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s, transform 0.12s;
  line-height: 1;
}
.font-size-btn[data-size="1"] { font-size: 0.85rem; }
.font-size-btn[data-size="2"] { font-size: 1.05rem; }
.font-size-btn[data-size="3"] { font-size: 1.45rem; }
.font-size-btn[data-size="4"] { font-size: 1.85rem; }
.font-size-btn:hover { background: color-mix(in srgb, #fff 12%, transparent); }
.font-size-btn:active { transform: scale(0.94); }
.font-size-btn.is-active {
  background: #3CB371;
  border-color: #3CB371;
  color: #fff;
}

/* drawer 内通用 <select> (语言 / 时区). 跟 font-size-btn 同款圆角灰底, 但是原生
   select 控件本身样式无法跨平台一致, 用 appearance:none + 自己画右侧三角箭头.
   字体/字号统一靠 inherit, 不在控件层硬编码. */
.drawer-select {
  width: 100%;
  height: 40px;
  border-radius: 10px;
  border: 1px solid color-mix(in srgb, #fff 14%, transparent);
  background: color-mix(in srgb, #fff 6%, transparent);
  color: #fff;
  font-weight: 600;
  font-size: 0.95rem;
  padding: 0 2rem 0 0.85rem;
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'><path d='M1 1l5 5 5-5' stroke='rgba(255,255,255,0.6)' stroke-width='1.8' fill='none' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-repeat: no-repeat;
  background-position: right 0.85rem center;
  transition: background-color 0.12s, border-color 0.12s;
}
.drawer-select:hover {
  background-color: color-mix(in srgb, #fff 12%, transparent);
}
.drawer-select:focus {
  outline: none;
  border-color: #22c55e;
  box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.18);
}
/* 原生 option 在 macOS / Win 下父背景跟原页面冲突, 强制深底 */
.drawer-select option {
  background: #1a1f24;
  color: #fff;
}

/* 会话历史列表: 单行截断, hover 高亮; 占满侧栏剩余空间, 不单独内滚 (超出裁掉) */
.drawer-section-history {
  padding-top: 0.5rem;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.chat-history-list {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;               /* 不可滑动: 固定大小, 超出裁掉 */
  margin: 0 -0.35rem;             /* 让 hover 高亮略微出血到 section padding 边 */
}
.chat-history-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 0.55rem 0.55rem;
  border: none;
  background: none;
  color: #f4f4f4;                  /* 侧栏文字一律白色 (用户要求) */
  font-size: 0.95rem;
  line-height: 1.3;
  border-radius: 8px;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: background 0.12s, color 0.12s;
}
.chat-history-item:hover { background: color-mix(in srgb, #fff 9%, transparent); color: #fff; }
.chat-history-item.is-current {
  background: color-mix(in srgb, #3CB371 16%, transparent);
  color: #fff;
}
.chat-history-empty {
  padding: 0.45rem 0.55rem;
  color: #f4f4f4;                  /* 侧栏文字一律白色 (用户要求) */
  font-size: 0.9rem;
}


/* 字号档位: body class + CSS 变量, 让卡片/详情/cipher 一起跟着改;
   单位选 rem 是因为 :root 没改, 父级布局/控件不受影响, 只是字号文字本身放大。
   档位 1=标准(默认) / 2=偏大(在标准与大之间插值) / 3=大 / 4=特大。
   原最小档 0.9 已删 (没意义), 1.25 是 1.0↔1.5 的中间插值档。默认 .font-size-1。 */
body.font-size-1 { --reading-scale: 1.0; }
body.font-size-2 { --reading-scale: 1.25; }   /* 插值档 */
body.font-size-3 { --reading-scale: 1.5; }
body.font-size-4 { --reading-scale: 2.0; }    /* 特大档 */

/* 新闻卡 + 详情正文按 scale 放大; 标题字号本身就比正文大, scale 乘上去自然按比例 */
/* news 列表字号固定, 不跟 --reading-scale 走 (字号设置只作用于 detail/cipher 阅读区) */
.card-title { font-size: 1.02rem; }
.card-snippet { font-size: 0.86rem; }
#d-title { font-size: calc(1.3rem * var(--reading-scale, 1)); }
#d-body, .detail-body { font-size: calc(1rem * var(--reading-scale, 1)); line-height: 1.7; }
.logo.logo-center {
  grid-column: 2;
  justify-self: center;
  align-self: stretch;
  position: relative;
  width: min(140px, 48vw);
  height: var(--header-h);
  max-height: var(--header-h);
  line-height: 0;
  text-decoration: none;
  /* 勿 hidden：drop-shadow 光晕会被切顶；顶栏条本身不依赖此处裁切 */
  overflow: visible;
}
/* 用绝对定位 + 明确 max-height（不用 100%，避免 iOS flex 子项百分比算错） */
.logo.logo-center img {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  display: block;
  max-width: 100%;
  max-height: calc(var(--header-h) - 6px);
  width: auto;
  height: auto;
  object-fit: contain;
  object-position: center center;
  filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.18));
}

/* ══════════════════════════════════════════
   BOTTOM TAB BAR
   设计 (用户改版): 微信式**全宽贴底 flat bar** —— 去掉悬浮胶囊, 整条贴屏幕底边, 实色深底 +
   顶部发丝分隔线; 选中态绿色由图标 _chosen 图自带。安全区走 padding, 背景实色铺到屏底。
══════════════════════════════════════════ */
#tab-bar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  padding: 6px 0 var(--safe-bottom);   /* 上留白 + 底部安全区(home indicator) */
  background: var(--chrome-surface);    /* 与顶栏/输入框同一抬升面 flat 深色 */
  border-top: var(--chrome-hairline);
  display: flex;
  align-items: center;
  justify-content: space-around;
  z-index: 300;
  /* iOS standalone 键盘后布局视口被砍, 贴底元素会浮起; 用 translateY 把整条往下顶进死区, flat 深底
     照样盖住, 不像旧悬浮胶囊那样露缝。补偿量 = min(亏空, 安全区)。--ih-deficit 由 JS syncIhDeficit 实时算。 */
  transform: translateY(min(var(--ih-deficit, 0px), var(--safe-bottom)));
  transition: transform 0.22s ease, opacity 0.2s ease;
  box-sizing: border-box;
}
.tab-btn {
  flex: 0 0 var(--tab-icon-outer);
  width: var(--tab-icon-outer);
  height: var(--tab-icon-outer);
  display: flex;
  align-items: center;
  justify-content: center;
  align-self: center;
  /* 透明圆：只看到 tab logo 本身，不要黑底/边框/阴影 */
  background: transparent;
  border: none;
  border-radius: 999px;
  box-shadow: none;
  color: var(--muted);
  cursor: pointer;
  transition: color 0.15s, transform 0.1s;
  padding: 0;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  pointer-events: auto;
}
.tab-btn:active { transform: scale(0.96); }
.tab-btn.active {
  background: transparent;
  border: none;
  color: var(--accent);
}
/* 全宽 flat bar 用 space-around 均分, 不再把首尾图标往里掖 (那是窄胶囊时代的事)。 */

/* 三个 tab 的图标容器同尺寸：横图 horizon 的图片靠 object-fit:contain 自然居中 */
.tab-btn .tab-icon.tab-tabimg {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  align-self: center;
  width: 100%;
  height: 100%;
  font-size: 0;
  line-height: 1;
  overflow: hidden;
  position: relative;
}
.tab-btn .tab-icon.tab-tabimg img {
  position: static;
  display: block;
  box-sizing: border-box;
  width: auto;
  height: auto;
  max-width: 68%;
  max-height: 68%;
  object-fit: contain;
  object-position: center center;
  flex-shrink: 0;
}
.tab-btn[data-tab="calendar"] .tab-icon.tab-tabimg {
  width: 100%;
}
.tab-btn[data-tab="calendar"] .tab-icon.tab-tabimg img {
  max-width: 108%;
  max-height: 94%;
}
.tab-btn .tab-avatar {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  overflow: hidden;
  font-size: 0;
}
.tab-btn .tab-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ══════════════════════════════════════════
   AUDIO PLAYER TAB（固定在 #tab-bar 上方的迷你播放器）
   - 宽度/位置与 #tab-bar 严格对齐：left/right/max-width/margin 一致
   - 高度 = #tab-bar 的 50%（var(--tab-h) * 0.5）
   - 圆角矩形 + 半透明 + backdrop-filter（与 #tab-bar 同审美）
   - 默认 hidden；body.has-audio 且 news 页（无 .tab-bar-overlay）才出现
══════════════════════════════════════════ */
#audio-player-tab {
  display: none;
  position: fixed;
  bottom: calc(var(--tab-h) + max(10px, var(--safe-bottom)) + 8px);
  left: 1rem;
  right: 1rem;
  margin: 0 auto;
  max-width: 420px;
  height: calc(var(--tab-h) * 0.5);
  padding: 0 12px 0 calc(var(--tab-h) * 0.12);
  align-items: center;
  gap: 0.6rem;
  background: color-mix(in srgb, var(--chrome-bg) 75%, transparent);
  border: none;
  border-radius: calc(var(--tab-h) * 0.2);
  box-shadow: 0 8px 22px rgba(0, 0, 0, 0.28);
  backdrop-filter: blur(18px) saturate(135%);
  -webkit-backdrop-filter: blur(18px) saturate(135%);
  z-index: 305;
  box-sizing: border-box;
  pointer-events: none; /* 与 #tab-bar 同：内部交互元素再 auto，避免空白处误触穿透 */
  transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s ease;
}
body.has-audio:not(.tab-bar-overlay) #audio-player-tab { display: flex; }

.audio-player-toggle {
  position: relative; /* 给内部 .audio-player-toggle-bars 提供绝对定位锚 */
  flex: 0 0 auto;
  width: calc(var(--tab-h) * 0.52);
  height: calc(var(--tab-h) * 0.52);
  border: none;
  border-radius: 50%;
  background: #3CB371;
  color: #ffffff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  cursor: pointer;
  pointer-events: auto;
  -webkit-appearance: none;
  font-size: calc(var(--tab-h) * 0.23);
  line-height: 1;
  transition: background-color 0.15s ease, transform 0.1s;
}
.audio-player-toggle:hover  { background: #329963; }
.audio-player-toggle:active { transform: scale(0.94); }
.audio-player-toggle-icon   { line-height: 1; }
/* 播放态: ▶/⏸ 字符让位给动画声波柱, 与卡片 .card-play 同步 */
.audio-player-toggle.is-playing .audio-player-toggle-icon { display: none; }
.audio-player-toggle-bars {
  position: absolute;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: center;
  gap: 2px;
  pointer-events: none;
}
.audio-player-toggle.is-playing .audio-player-toggle-bars { display: inline-flex; }
.audio-player-toggle-bars > span {
  width: 2px;
  height: 36%;
  background: currentColor;
  border-radius: 1px;
  transform-origin: 50% 50%;
  animation: cardPlayBars 0.9s ease-in-out infinite;
}
.audio-player-toggle-bars > span:nth-child(2) { animation-delay: 0.15s; }
.audio-player-toggle-bars > span:nth-child(3) { animation-delay: 0.3s; }
@media (prefers-reduced-motion: reduce) {
  .audio-player-toggle-bars > span { animation: none; transform: scaleY(1); }
}

.audio-player-name {
  flex: 1 1 auto;
  min-width: 0;
  color: var(--text);
  font-size: 0.82rem;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  pointer-events: auto;
}

/* 迷你播放器最右侧的播放/暂停按钮 (用户要求): 圆形, 与左侧 toggle 同色系 */
.audio-player-pause {
  flex: 0 0 auto;
  width: calc(var(--tab-h) * 0.46);
  height: calc(var(--tab-h) * 0.46);
  margin-left: 0.4rem;
  border: none;
  border-radius: 50%;
  background: color-mix(in srgb, var(--text) 12%, transparent);
  color: var(--text);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: calc(var(--tab-h) * 0.2);
  line-height: 1;
  cursor: pointer;
  pointer-events: auto;
  -webkit-appearance: none;
  transition: background-color 0.15s ease, transform 0.1s;
}
.audio-player-pause:hover { background: color-mix(in srgb, var(--text) 18%, transparent); }
.audio-player-pause:active { transform: scale(0.94); }

/* ══════════════════════════════════════════
   MAIN LAYOUT
══════════════════════════════════════════ */
main {
  padding: 0;
}

.page { display: none; }
.page.active { display: block; }

#status {
  font-size: 0.78rem;
  color: var(--muted);
  margin-bottom: 0.35rem;
  padding: 0 1rem;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  min-height: 1.4em;
}

/* ══════════════════════════════════════════
   手机：固定列表行高、下滑藏顶栏/底栏、减弱双击缩放
══════════════════════════════════════════ */
@media (max-width: 699px) {
  /*
   * 顶栏 fixed + body 上内边距：下滑收起时与底栏同理收回内边距，避免只 transform 留出空白条。
   */
  body {
    padding-top: calc(env(safe-area-inset-top, 0px) + var(--header-stack-h));
    transition:
      padding-top 0.28s cubic-bezier(0.4, 0, 0.2, 1),
      padding-bottom 0.28s cubic-bezier(0.4, 0, 0.2, 1);
  }

  /*
   * 顶栏须从物理顶 (top:0) 铺满背景 + padding-top: safe-area。
   * 若仅用 top: env(safe-area-inset-top)，屏幕最上缘到顶栏之间无遮挡，iOS 下拉/回弹时列表会穿到灵动岛/状态栏后。
   */
  .site-header-wrap {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 200;
    padding-top: env(safe-area-inset-top, 0px);
    box-sizing: border-box;
    transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1);
  }

  /*
   * 勿在 html 上写 touch-action: manipulation：部分 iOS/WebKit 下会干扰整页纵向滚动。
   * 双击缩放抑制放在可点击控件上即可。
   */
  #news-list .card,
  .tab-btn,
  .header-back,
  .cal-nav-btn {
    touch-action: manipulation;
  }

  body.chrome-scroll-hide-tabs {
    padding-top: env(safe-area-inset-top, 0px);
    padding-bottom: var(--safe-bottom);
  }

  body.chrome-scroll-hide-tabs .site-header-wrap {
    transform: translateY(calc(-100% - 8px));
    pointer-events: none;
  }

  #tab-bar {
    transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1);
  }

  body.chrome-scroll-hide-tabs #tab-bar {
    transform: translateY(calc(var(--tab-h) + var(--safe-bottom) + 28px));
    /* 避免底栏已移出视口时仍抢占触摸，导致页面像「划不动」 */
    pointer-events: none;
  }

  /* 迷你播放器与底栏一起下滑藏；起点比底栏高一档，translateY 量也要相应更大 */
  body.chrome-scroll-hide-tabs #audio-player-tab {
    transform: translateY(calc(var(--tab-h) * 1.5 + var(--safe-bottom) + 36px));
    pointer-events: none;
  }

  #news-list .card {
    box-sizing: border-box;
    min-height: 0;                 /* 缩略图(82px)+padding 自然定高, 不再强制留白 */
    max-height: none;
    overflow: hidden;
    align-items: stretch;
    flex-shrink: 0;
  }
  /* 头条卡是整块大图, 不受普通卡的等高/overflow 约束 */
  #news-list .card.card-hero {
    min-height: 0;
    overflow: visible;
  }

  #news-list .card .card-body {
    min-height: 0;
    overflow: hidden;
  }
}

/* ══════════════════════════════════════════
   NEWS CARDS
══════════════════════════════════════════ */
#news-list { display: flex; flex-direction: column; }

/* ============================================================
   工程模式诊断小字 (.eng-info): 仅在 <body class="eng-mode"> 时显形 (见 core.js initEngMode)。
   任何地方想加诊断小字, 塞个 <span class="eng-info">…</span> 即可, 生产模式自动整体隐藏。
   ============================================================ */
.eng-info {
  font-size: 11px;
  line-height: 1.5;
  color: #4ade80;   /* 工程绿: 一眼区分诊断字 / 跟 app 绿主题一致 */
  letter-spacing: .02em;
  font-variant-numeric: tabular-nums;
}
body:not(.eng-mode) .eng-info { display: none !important; }
/* 加载耗时叠层: position:fixed 浮在内容上 (顶栏下方左上角), 不占布局、不挡点击 (pointer-events:none)。
   半透明深底 + 模糊, 保证在任意内容上都可读。 */
#eng-overlay {
  position: fixed;
  top: calc(var(--header-h) + 5px);
  left: 8px;
  z-index: 500;
  pointer-events: none;
  max-width: calc(100vw - 16px);
  padding: 3px 8px;
  border-radius: 6px;
  background: rgba(0, 0, 0, 0.55);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  white-space: nowrap;
}

.card {
  /* 主体色统一到 chrome 抬升面 (与顶栏/底栏/horizon 一致, 用户要求)。分割线改成发丝白线,
     在 #1a1a1a 上仍有轻微分隔。⚠️ 与 index.html 内联 .card 同步。 */
  background: var(--chrome-surface);
  border-bottom: var(--chrome-hairline);
  padding: 1rem;
  display: flex;
  gap: 0.85rem;
  cursor: pointer;
  align-items: stretch;          /* body 撑到与缩略图等高, 让「听」药丸能顶到底部对齐 */
  -webkit-tap-highlight-color: transparent;
  /* 流畅度极限: 离屏卡片不参与 layout/paint, 长列表滚动 60fps 不掉.
     content-visibility: auto 让浏览器跳过 (但保留位置), contain-intrinsic-size 给个占位高度
     避免滚动时回弹. 88px = thumb (56) + padding (16*2). */
  content-visibility: auto;
  contain-intrinsic-size: auto 88px;
}
.card-thumb {
  width: 128px; height: 90px;    /* 矩形 (≈4:3 横图), 不裁成方块; 比上版再放大一点 */
  border-radius: 12px;
  overflow: hidden;
  flex-shrink: 0;
  align-self: flex-start;        /* 标题更高时缩略图顶对齐, 不被拉伸 */
  background: var(--border);
}
.card-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* 列表卡片: 左 缩略图; 右 [标题 + 「听」药丸] 竖排 (药丸 margin-top:auto 顶到底)。 */
.card-body { flex: 1; min-width: 0; display: flex; flex-direction: column; }

.card-title {
  font-family: var(--font-serif);   /* 衬线 (用户要求 news 列表换衬线) */
  font-size: 1.02rem;
  font-weight: 700;
  color: var(--text);
  line-height: 1.32;
  overflow-wrap: anywhere;       /* 完整显示, 需要时换行, 不截断 */
}
.card-snippet {
  font-size: 0.82rem;
  font-weight: 400;
  color: var(--muted);
  line-height: 1.35;
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.card-play {
  align-self: flex-start;      /* 在缩略图下方左对齐 */
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 0.34rem;
  min-height: 28px;
  margin-top: 0;
  padding: 0.22rem 0.62rem 0.22rem 0.48rem;
  border: none;
  border-radius: 999px;
  background: color-mix(in srgb, var(--chrome-control-bg) 82%, #ffffff 8%);
  color: #3CB371;
  font: inherit;
  font-size: 0.8rem;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  opacity: 1;
  -webkit-appearance: none;
  transition: background-color 0.15s ease, color 0.15s ease, opacity 0.15s ease;
}
.card-play:hover {
  background: color-mix(in srgb, var(--chrome-control-bg) 70%, #3CB371 14%);
}
.card-play.is-playing {
  background: color-mix(in srgb, #3CB371 18%, var(--chrome-control-bg) 82%);
}
/*
  加载态: 进度环本身就是视觉指示, 不再降透明度 (避免环看起来灰扑扑)。
  只把 cursor 切到 progress 暗示"正在做事"。
*/
.card-play.is-loading {
  cursor: progress;
}
.card-play:disabled { opacity: 1; cursor: default; }
/*
  播放按钮左侧图标 + 进度环容器。
    - .card-play-icon-wrap：相对定位的"圆形空间"，宽高略大于图标本身留给环
    - .card-play-ring：绝对铺满，默认隐藏；加载态淡入
    - .card-play-ring-progress：dashoffset 由 --p (0..1) 驱动，与 JS 解耦
    - is-loading-indeterminate：拿不到 content-length 时旋转兜底
  环周长 = 2π·10 ≈ 62.83；dasharray 与 dashoffset 用同一常量
*/
.card-play-icon-wrap {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.4em;
  height: 1.4em;
  flex-shrink: 0;
}
.card-play-ring {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  transform: rotate(-90deg);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease;
}
.card-play.is-loading .card-play-ring { opacity: 1; }
/*
  ▶ 图标隐藏时机:
    - is-loading 期间: 让进度环单独成为焦点
    - is-playing 期间: 让动画声波柱代替 ▶
  按钮位置不变, 避免布局跳变
*/
.card-play.is-loading .card-play-icon,
.card-play.is-playing .card-play-icon { opacity: 0; }
/*
  播放态的"动画声波柱": 默认隐藏, .is-playing 显示 + 三柱错相位上下跳。
  柱子绝对定位在 icon-wrap 内, 占满 wrap 的圆形空间。
*/
.card-play-bars {
  position: absolute;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: center;
  gap: 2px;
  pointer-events: none;
}
.card-play.is-playing .card-play-bars { display: inline-flex; }
.card-play-bars > span {
  width: 2px;
  height: 30%;
  background: currentColor;
  border-radius: 1px;
  transform-origin: 50% 50%;
  animation: cardPlayBars 0.9s ease-in-out infinite;
}
.card-play-bars > span:nth-child(2) { animation-delay: 0.15s; }
.card-play-bars > span:nth-child(3) { animation-delay: 0.3s; }
@keyframes cardPlayBars {
  0%, 100% { transform: scaleY(0.55); }
  50%      { transform: scaleY(1.6); }
}
@media (prefers-reduced-motion: reduce) {
  .card-play-bars > span { animation: none; transform: scaleY(1); }
}
/*
  水平进度条: 仅在 .is-playing 时显示, fill 宽度由 --play-p (0..1) 驱动。
  细短一根, 不抢字数视觉, 风格与 ▶/⏸ 同样的绿色 currentColor。
*/
.card-play-progress {
  display: none;
  width: 60px;
  height: 2px;
  background: rgba(60, 179, 113, 0.22);
  border-radius: 1px;
  overflow: hidden;
}
.card-play.is-playing .card-play-progress { display: inline-block; }
/* 播放态: 进度条已经表达"还有多少没听完", 总分钟数同时显示会重复, 隐藏 */
.card-play.is-playing .card-play-time { display: none; }

/* 双 lang 按钮一行排开 (有几个显几个; 都没有就这整行 row 也不渲染) */
.card-play-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

/* 播放药丸 + 引擎标签 同一行 (引擎标签在播放键右侧) */
.card-foot {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-wrap: wrap;
  margin-top: 0.34rem;
}
/* 内容生产引擎标签 (临时 debug 用, 让用户区分哪个子引擎产的)。每引擎一个色, 见 data-engine。 */
.card-engine-tag {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  min-height: 30px;
  padding: 0.3rem 0.66rem;
  border-radius: 999px;
  font-size: 0.84rem;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0.02em;
  background: color-mix(in srgb, var(--chrome-control-bg) 70%, #888 8%);
  color: var(--muted);
  white-space: nowrap;
}
.card-engine-tag[data-engine="cluster"]   { background: color-mix(in srgb, #5B8DEF 18%, transparent); color: #5B8DEF; }
.card-engine-tag[data-engine="transfer"]  { background: color-mix(in srgb, #E0782B 20%, transparent); color: #E0782B; }
.card-engine-tag[data-engine="interview"] { background: color-mix(in srgb, #9B5BEF 18%, transparent); color: #9B5BEF; }
.card-engine-tag[data-engine="preview"]   { background: color-mix(in srgb, #2BB8A3 20%, transparent); color: #2BB8A3; }

/* 中文播放按钮: 与英文 .card-play 同结构同色 (用户要求中英都用绿色, 不再用蒂芙尼蓝)。
   保留 .card-play-tiffany 类名只为不动 feed.js 逻辑, 颜色已全部回退成基础绿。 */
.card-play-tiffany {
  color: #3CB371;                      /* 主色: 绿 (与英文一致) */
}
.card-play-tiffany:hover { background: color-mix(in srgb, #3CB371 14%, transparent); }
.card-play-tiffany .card-play-icon { color: #3CB371; }
.card-play-tiffany .card-play-progress {
  background: color-mix(in srgb, #3CB371 22%, transparent);
}
/* 加载环 stroke 跟随 currentColor; 进度 fill 默认 currentColor 也走蒂芙尼蓝 */
.card-play-tiffany .card-play-ring-progress { stroke: #3CB371; }
.card-play-tiffany .card-play-bars > span { background: #3CB371; }

/* ── lang 主色联动 ─────────────────────────────────────────────────────────
   body.audio-lang-zh / audio-lang-en 由 JS 在播放时切换。
   en 走基础绿色 (兜底, 不需要 override; 与英文卡片绿色按钮一致);
   zh 把底栏迷你播放器 + 全屏进度条 + 字幕活动行 + 速度按钮一并染蒂芙尼蓝
   (与中文卡片蒂芙尼蓝按钮一致)。 */
body.audio-lang-zh #audio-player-toggle {
  background: #3CB371;
}
body.audio-lang-zh #audio-player-toggle:hover { background: #2E9E63; }
body.audio-lang-zh .audio-player-toggle-bars > span { background: #fff; }

body.audio-lang-zh .player-progress-fill { background: #8FD9A8; }
body.audio-lang-zh .player-cap-line.is-active { color: #fff; text-shadow: 0 1px 6px rgba(8, 90, 88, 0.4); }
body.audio-lang-zh .player-speed-opt.is-active {
  background: #3CB371;
  color: #fff;
}
/* 全屏播放器的封面 + 大圆按钮保持白色, 模糊背景仍走 hero 色 — 不强行染紫保留照片质感 */
.card-play-progress-fill {
  display: block;
  width: calc(var(--play-p, 0) * 100%);
  height: 100%;
  background: currentColor;
  transition: width 0.12s linear;
}
@media (prefers-reduced-motion: reduce) {
  .card-play-progress-fill { transition: none; }
}
.card-play-ring-track {
  fill: none;
  stroke: rgba(60, 179, 113, 0.22);
  stroke-width: 2.4;
}
.card-play-ring-progress {
  fill: none;
  stroke: #3CB371;
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-dasharray: 62.83;
  stroke-dashoffset: calc(62.83 * (1 - var(--p, 0)));
  transition: stroke-dashoffset 0.12s linear;
}
/* 无 content-length 兜底: 环固定显示 25% 弧 + 整体旋转 */
.card-play.is-loading-indeterminate .card-play-ring {
  animation: cardPlayRingSpin 0.9s linear infinite;
}
.card-play.is-loading-indeterminate .card-play-ring-progress {
  stroke-dashoffset: 47.12; /* 62.83 * 0.75 → 露 1/4 弧 */
  transition: none;
}
@keyframes cardPlayRingSpin {
  from { transform: rotate(-90deg); }
  to   { transform: rotate(270deg); }
}
@media (prefers-reduced-motion: reduce) {
  .card-play.is-loading-indeterminate .card-play-ring { animation: none; }
  .card-play-ring-progress { transition: none; }
}
.card-play-icon {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1em;
  height: 1em;
  font-size: 0.78rem;
  line-height: 1;
}

/* ════════════ D1 听感优先: 「听」实心药丸 + 头条大图卡 ════════════
   普通卡: 缩略图放大圆角(上面已改); 「听」按钮做成实心绿药丸, 顶到右列底部, 不遮封面。
   播放态(声波柱/进度/加载环)仍由 audio.js 驱动 —— 这里只重新着色 + 定位。
   实心药丸底色已编码语言(绿/蒂芙尼蓝), 故内部图标/声波/进度环统一染深墨, 不再走 currentColor 绿
   (绿底上看不见)。全部用 #news-list 作用域, 不影响别处复用。 */
#news-list .card-play {
  margin-top: auto;                       /* 顶到右列底部, 与缩略图底对齐 */
  align-self: flex-start;
  background: #4ade80;
  color: #06250f;                         /* 深墨: 图标/声波/进度 fill 吃这个 currentColor */
  padding: 0.4rem 0.78rem 0.4rem 0.5rem;
  font-size: 0.82rem;
  box-shadow: 0 2px 10px rgba(74, 222, 128, 0.26);
}
#news-list .card-play:hover { background: #5ee68f; }
#news-list .card-play.is-playing { background: #38d176; }
#news-list .card-play .card-play-icon { color: #06250f; }
#news-list .card-play-bars > span { background: #06250f; }
#news-list .card-play-progress { background: rgba(6, 37, 15, 0.28); }
#news-list .card-play-progress-fill { background: #06250f; }
#news-list .card-play-ring-track { stroke: rgba(6, 37, 15, 0.30); }
#news-list .card-play-ring-progress { stroke: #06250f; }
/* zh 播放键和 en 完全一样的绿色 (用户要求): 不再覆盖背景, 直接继承上面 #news-list .card-play 的
   绿色实心药丸 (含 hover/is-playing)。原蒂芙尼蓝 #1ccfca/#09a8a3 就是"点播放还是蓝色"的来源。 */
/* 头条「听全文」前缀文案; 播放时让位给声波/进度 */
.card-play-lead { font-weight: 700; }
.card-play.is-playing .card-play-lead { display: none; }

/* ── 头条大图卡 (列表第一条) ─────────────────────────────────────── */
.card-hero {
  display: block;
  padding: 0;
  border-bottom: none;
  cursor: pointer;
  margin: 0.35rem 1rem 0.7rem;            /* 四周收一点, 不再整屏满铺, 侧边与列表内容对齐 */
  content-visibility: visible;            /* 首条常驻顶部, 不做离屏跳过 (88px 占位高度会错) */
}

/* 头条轮播: 3 条 hero 横向 scroll-snap, 手指左右滑切换 */
.hero-carousel { position: relative; }
.hero-track {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}
.hero-track::-webkit-scrollbar { display: none; }
.hero-track .hero-slide {
  flex: 0 0 100%;
  min-width: 0;
  /* start 比 center 在 iOS 上落点更稳; snap-stop:always 限一次滑一屏, 不会冲过头/落在两屏中间 */
  scroll-snap-align: start;
  scroll-snap-stop: always;
}
.hero-track .hero-slide .card-hero { margin: 0.35rem 1rem 0.5rem; }
.hero-dots {
  display: flex;
  justify-content: center;
  gap: 6px;
  margin: 0.1rem 0 0.5rem;
}
.hero-dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--text) 26%, transparent);
  transition: background 0.2s ease, width 0.2s ease;
  cursor: pointer;
}
.hero-dot.active { background: #4ade80; width: 16px; border-radius: 3px; }
.card-hero-img {
  width: 100%;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: 16px 16px 0 0;
  background: var(--border);
}
.card-hero-img img { width: 100%; height: 100%; object-fit: cover; display: block; }
.card-hero.no-img .card-hero-img { display: none; }
.card-hero.no-img .card-hero-pad { border-radius: 16px; }
.card-hero-pad {
  background: var(--chrome-surface);   /* 头条卡背景统一到 chrome 抬升面 (不再深黑 #11161c, 用户要求) */
  border-radius: 0 0 16px 16px;
  padding: 0.85rem 0.95rem 0.95rem;
}
.card-hero-title {
  font-family: var(--font-serif);
  color: #fff;
  font-size: 1.22rem;               /* 固定: 字号设置只影响 detail/cipher 阅读区, 不动 news 列表 */
  font-weight: 700;
  line-height: 1.3;
  margin-bottom: 0.8rem;
  overflow-wrap: anywhere;
}
.card-hero-foot { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; }
.card-hero-src { color: var(--muted); font-size: 0.78rem; font-weight: 500; }
/* 头条里的「听全文」药丸比列表略大 */
#news-list .card-hero .card-play {
  margin-top: 0;
  font-size: 0.86rem;
  padding: 0.5rem 0.95rem 0.5rem 0.62rem;
}

.card-skeleton {
  cursor: default;
  pointer-events: none;
}
.skeleton-box,
.skeleton-line {
  background: linear-gradient(
    90deg,
    color-mix(in srgb, var(--border) 60%, transparent),
    color-mix(in srgb, var(--border) 88%, #ffffff 6%),
    color-mix(in srgb, var(--border) 60%, transparent)
  );
  background-size: 220% 100%;
  animation: skeletonPulse 1.15s ease-in-out infinite;
}
.skeleton-line {
  height: 0.82rem;
  border-radius: 999px;
}
.skeleton-line-lg { width: min(78%, 28rem); }
.skeleton-line-md { width: min(58%, 20rem); }
.skeleton-line-sm { width: min(42%, 15rem); }

@keyframes skeletonPulse {
  from { background-position: 120% 0; }
  to { background-position: -120% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .skeleton-box,
  .skeleton-line {
    animation: none;
  }
}

.badge {
  font-size: 0.68rem;
  padding: 0.1rem 0.4rem;
  border-radius: 4px;
  font-weight: 500;
  white-space: nowrap;
}
.badge-source { background: var(--tag-bg); color: var(--tag-text); }
.badge-lang   { background: var(--badge-lang-bg); color: var(--badge-lang-text); }
.badge-date   { color: var(--muted); font-size: 0.68rem; }

/* ── Scroll sentinel (infinite scroll loading indicator) ── */
#scroll-sentinel {
  display: flex;
  justify-content: center;
  padding: 0.15rem 0;
  min-height: 1px;
}

/* ══════════════════════════════════════════
   DETAIL VIEW
══════════════════════════════════════════ */
#detail-view { display: none; }
/* 详情页主体色统一到 chrome 抬升面 (与顶栏/底栏一致, 用户要求); min-height 撑满, 内容不足时也不露黑底 */
#detail-view.visible {
  display: block;
  background: var(--chrome-surface);
  min-height: 100dvh;
}
/* 兜底: 详情打开时整页画布也压成同色, 任何容器透出处都不再是纯黑 */
body.article-detail-open { background: var(--chrome-surface); }

.detail-wrap {
  max-width: 720px;
  margin: 0 auto;
  padding: 1.1rem 1rem 0;   /* 顶部留白: 标题别贴着 header (用户要求) */
}

.detail-kicker {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.85rem;
}
.detail-kicker .badge { font-size: 0.72rem; }

.detail-title {
  font-family: var(--font-serif);   /* 衬线 (用户要求 detail 标题换衬线) */
  font-size: 1.6rem;
  font-weight: 700;
  line-height: 1.28;
  color: var(--text);
  margin-bottom: 1rem;
  letter-spacing: -0.02em;
}

/* 详情 hero: 外层 <picture> 撑布局 (aspect-ratio + display 控制), 内层 <img> 充满
   webp 由 <source> 优先选, 不支持就回退到 <img> 的 jpg src */
.detail-hero-wrap {
  display: block;
  width: 100%;
  max-height: 340px;
  aspect-ratio: 800 / 420;       /* 跟 medium JPG 比例对齐, 防止 layout shift */
  border-radius: var(--radius);
  overflow: hidden;
  margin-bottom: 1.1rem;
  background: var(--border);
}
.detail-hero {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* 加载态: skeleton 脉冲挂在 wrap 上, img 透明让背景透过 */
.detail-hero.is-loading + *,
.detail-hero-wrap:has(.detail-hero.is-loading) {
  background: linear-gradient(
    90deg,
    color-mix(in srgb, var(--border) 60%, transparent),
    color-mix(in srgb, var(--border) 88%, #fff 6%),
    color-mix(in srgb, var(--border) 60%, transparent)
  );
  background-size: 220% 100%;
  animation: skeletonPulse 1.15s ease-in-out infinite;
}

.detail-byline {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  font-size: 0.8rem;
  color: var(--muted);
  margin-bottom: 1.25rem;
}
/* 无来源/作者时不留空行 + 不画分隔线 (用户要求图文之间不要横线) */
.detail-kicker:empty,
.detail-byline:empty { display: none; }
.detail-byline .sep { color: var(--border); }
.detail-byline a { color: var(--accent); text-decoration: none; font-weight: 500; }
.detail-byline a:hover { text-decoration: underline; }

.detail-body {
  /* news detail 正文衬线体 */
  font-family: var(--font-serif);
  font-size: 1rem;
  line-height: 1.95;
  color: var(--detail-body);
  word-break: break-word;
  white-space: pre-wrap;
}

/* 生产稿尾部: 实体标签(队/球员/教练) + 引擎标签 + 综合自的来源稿。全空时 JS 不渲染。 */
.detail-sources:empty { display: none; }
.detail-entity-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: 0.9rem;
}
.detail-entity-tag {
  display: inline-flex;
  align-items: center;
  padding: 0.3rem 0.66rem;
  border-radius: 999px;
  font-size: 0.82rem;
  font-weight: 600;
  background: color-mix(in srgb, var(--text) 9%, transparent);
  color: var(--text);
  white-space: nowrap;
}
.detail-sources {
  margin-top: 1.6rem;
  padding-top: 1rem;
  border-top: 1px solid color-mix(in srgb, var(--text) 12%, transparent);
}
.detail-sources-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.6rem;
}
.detail-sources-label {
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--muted);
  letter-spacing: 0.04em;
}
.detail-sources-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.detail-sources-list li {
  font-size: 0.86rem;
  line-height: 1.45;
  color: var(--text);
}
.detail-sources-list a {
  color: var(--text);
  text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--accent) 50%, transparent);
}
.detail-sources-list a:hover { color: var(--accent); }
.detail-sources-list .src-name {
  display: block;
  font-size: 0.76rem;
  color: var(--muted);
  margin-top: 0.1rem;
}

.detail-body-loading {
  color: var(--muted);
  font-size: 0.9rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 2rem 0;
}

.detail-gallery {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.75rem;
  margin-top: 2rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
}
.detail-gallery img {
  width: 100%;
  max-height: 280px;
  border-radius: 10px;
  object-fit: cover;
  display: block;
}

.detail-source-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: 2rem;
  padding: 0.7rem 1.4rem;
  background: var(--accent);
  color: #fff;
  border-radius: 8px;
  font-size: 0.9rem;
  font-weight: 600;
  text-decoration: none;
  transition: background 0.15s;
}
.detail-source-btn:hover { background: var(--accent-h); }

.detail-actions {
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-top: 2rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
}
.detail-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.65rem 1.25rem;
  border-radius: 8px;
  font-size: 0.88rem;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  border: none;
  transition: background 0.15s, color 0.15s;
}
.detail-btn-primary { background: var(--accent); color: #fff; }
.detail-btn-primary:hover { background: var(--accent-h); }
.detail-btn-outline { background: none; border: 1.5px solid var(--border); color: var(--text2); }
.detail-btn-outline:hover { border-color: var(--accent); color: var(--accent); }

.divider { border: none; border-top: 1px solid var(--border); margin: 1.25rem 0; }

@media (min-width: 700px) {
  .detail-title { font-size: 1.95rem; }
  .detail-hero  { max-height: 420px; }
  .detail-body  { font-size: 1.05rem; }
  .detail-gallery { grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
  .detail-gallery img { max-height: 220px; }
}

/* ══════════════════════════════════════════
   CHAT PAGE — Cipher：单列文档流 + Markdown（非微信双头像气泡）
══════════════════════════════════════════ */
#page-chat {
  display: none;
  position: fixed;
  top: var(--header-stack-h);
  left: 0; right: 0;
  /* 背景层铺到安全区底 (渐变延伸到 tab 栏后方, 不留缝)。 */
  bottom: var(--safe-bottom);
  height: auto;
  flex-direction: column;
  background: var(--chat-bg);
  z-index: 50;
  overflow: hidden;
  /* 输入框现在是 #page-chat-wrap 里的最后一个 flex 子项; 这段底 padding 把内层 (消息+输入框)
     整体上抬, 让输入框落在底部 tab 栏之上 (静息) 或键盘之上 (输入)。背景不受影响仍铺到底。
     —— 取代旧的"输入框 fixed 浮层 + #chat-messages 估算 padding-bottom 躲它"那套, 内容压不到
     输入框下面从此是布局硬保证, 不再依赖估算对齐 (iOS standalone 视口飘移时也不会露馅)。 */
  padding-bottom: calc(
    max(
      calc(var(--tab-h) + max(10px, var(--safe-bottom)) + 8px - min(var(--ih-deficit, 0px), var(--safe-bottom))),
      calc(var(--keyboard-inset-bottom, 0px) - var(--chat-keyboard-dock-overlap))
    ) - var(--safe-bottom)
  );
  transition: padding-bottom 0.18s ease;
}
#page-chat.active { display: flex; }

/* cipher 页背景: 上 60% 纯黑 + 下 40% 春绿渐变 (5/25 用户调). 整体黑底感更稳重,
   只在屏幕下沿过渡到 spring green, 跟输入框衔接处不抢戏. */
body.in-chat #page-chat {
  /* C 均匀线性 (用户选定): 一道干净的黑→中绿斜坡, 去掉霓虹尾, 线性自然。 */
  background: linear-gradient(180deg,
    #000000 0%,
    #000000 56%,
    #0c5f36 100%);
}

/* cipher 页保留底部 3-tab (用户要求): 强制 fixed 贴底, 跟 news 完全一致 —— 必须覆盖宽屏
   (≥1024) 的 `body.tab-bar-overlay #tab-bar { position:static }`, 否则因 #page-chat 是 fixed
   脱离文档流, 静态 tab 栏会浮到 #app-shell 顶部卡在 header 下面 (iPad 上那排错位淡图标)。 */
/* cipher 也用全宽贴底 flat bar (与 news/horizon 一致); !important 仅为压过宽屏 ≥1024 的
   `body.tab-bar-overlay #tab-bar{position:static}` —— #page-chat 是 fixed 脱流, 静态底栏会浮到顶。 */
body.in-chat #tab-bar {
  display: flex !important;
  position: fixed !important;
  top: auto !important;
  bottom: 0 !important;
  left: 0 !important;
  right: 0 !important;
  margin: 0 !important;
  width: auto !important;
  max-width: none !important;
  height: auto !important;
  padding: 6px 0 var(--safe-bottom) !important;
  z-index: 300 !important;
  transform: translateY(min(var(--ih-deficit, 0px), var(--safe-bottom)));
}
/* 注意: cipher 绝不能给 body 设 position:fixed。standalone PWA 下, position:fixed 的 body 会成为
   其 fixed 子元素 (#tab-bar) 的包含块, 把 tab 顶起浮在底部留一大片空; 且离开 cipher 时 iOS 不重排,
   会把这个浮起高度带进 horizon。cipher 里所有可见元素 (顶栏 / #page-chat / 输入框 / tab) 各自都是
   position:fixed 自定位, 消息在 #chat-messages 内部滚动, #main-news 又 display:none —— body 无需
   fixed/锁滚, overflow:hidden 足矣。 */
body.in-chat {
  width: 100%;
  padding-top: calc(env(safe-area-inset-top, 0px) + var(--header-stack-h));
  overflow: hidden;
}

/* padding-top 已吃掉顶栏高度后，shell 应填满 body 内容盒，勿再按整视口减顶栏否则会溢出/裁切 */
body.in-chat.tab-bar-overlay #app-shell {
  height: 100%;
  max-height: none;
  min-height: 0;
}
body.in-chat .site-header-wrap {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  /* iOS 键盘：visualViewport 下移时 offsetTop>0，fixed 会整段留在「屏幕上方」外；用变量拉回可见区 */
  transform: translateY(var(--vv-offset-top, 0px)) translateZ(0);
  will-change: transform;
  pointer-events: auto !important;
}
/* 消息区与顶栏同步补偿；输入条在网格外，不受此 transform 影响 */
body.in-chat #page-chat {
  transform: translateY(var(--vv-offset-top, 0px)) translateZ(0);
}
/* ============================================================
   Cipher "thinking" ambient gradient — 跟 Google Gemini 同款
   ──────────────────────────────────────────────────────────
   - 3 个半透明色块 (蓝/绿/琥珀), 80vw 圆形, 强 blur, mix-blend-mode: screen 混色
   - 各自漂浮 (transform translate+scale 关键帧), 周期 14/16/18s 错开, 永不重合
   - 默认 opacity:0 + visibility:hidden, 不占任何渲染开销
   - body.cipher-thinking 时淡入; body.in-chat 守门, 切到 news/calendar tab 直接消失
   - 底部 vignette 让屏幕下半部分变黑, 跟参考截图一致
   ============================================================ */
#cipher-aura {
  position: fixed;
  inset: 0;
  z-index: 1;            /* body bg (0) 之上, header (200) / chat 内容之下 */
  pointer-events: none;
  overflow: hidden;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.6s ease, visibility 0s linear 0.6s;
  /* 用 contain 限制 reflow / paint 范围, 动画期间不影响外部 */
  contain: strict;
}
body.in-chat.cipher-thinking #cipher-aura {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.5s ease, visibility 0s linear 0s;
}
/* 底部 vignette: 上 70% 透明 → 下方 100% 黑, 光斑覆盖屏幕上半到三分之二. 原 35% 太靠上,
   gradient 只剩 header 那一窄条, 不够 "Gemini 满屏氛围" 的感觉 */
#cipher-aura::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg,
    transparent 0%,
    transparent 55%,
    rgba(0,0,0,0.55) 80%,
    rgba(0,0,0,0.95) 100%);
  pointer-events: none;
}
.cipher-aura-blob {
  position: absolute;
  /* 放大: 120vw (原 95vw), 让单个 blob 覆盖更大范围, 与其他 blob 重叠混色更柔 */
  width: 120vw;
  height: 120vw;
  max-width: 900px;
  max-height: 900px;
  border-radius: 50%;
  filter: blur(80px);
  mix-blend-mode: screen;
  will-change: transform;
}
/* 三色 blob 全部换成绿调 (原蓝/绿/琥珀 → 春绿/翠绿/草绿), 配合 Gemini 仿绿调.
   class 名 -blue/-amber 保留是为了少改 index.html, 颜色与名字脱钩. */
.cipher-aura-blob-blue {
  background: radial-gradient(circle, rgba(34, 197, 94, 0.85) 0%, rgba(34, 197, 94, 0) 60%);
  top: -20vh;
  left: -30vw;
  animation: cipher-drift-blue 14s ease-in-out infinite,
             cipher-aura-hue 15s ease-in-out infinite;
}
.cipher-aura-blob-green {
  background: radial-gradient(circle, rgba(21, 128, 61, 0.82) 0%, rgba(21, 128, 61, 0) 60%);
  top: 5vh;
  left: 12vw;
  animation: cipher-drift-green 16s ease-in-out infinite -2s,
             cipher-aura-hue 15s ease-in-out infinite;
}
.cipher-aura-blob-amber {
  background: radial-gradient(circle, rgba(74, 222, 128, 0.78) 0%, rgba(74, 222, 128, 0) 60%);
  top: 18vh;
  left: 30vw;
  animation: cipher-drift-amber 18s ease-in-out infinite -5s,
             cipher-aura-hue 15s ease-in-out infinite;
}
@keyframes cipher-drift-blue {
  0%, 100% { transform: translate(0, 0) scale(1); }
  25%      { transform: translate(20vw, 18vh) scale(1.12); }
  50%      { transform: translate(38vw, 8vh)  scale(0.95); }
  75%      { transform: translate(8vw, 28vh)  scale(1.08); }
}
@keyframes cipher-drift-green {
  0%, 100% { transform: translate(0, 0) scale(1); }
  33%      { transform: translate(-14vw, 22vh) scale(1.18); }
  66%      { transform: translate(28vw, -4vh)  scale(0.9); }
}
@keyframes cipher-drift-amber {
  0%, 100% { transform: translate(0, 0) scale(1); }
  40%      { transform: translate(30vw, 14vh)   scale(1.22); }
  80%      { transform: translate(-18vw, -6vh)  scale(0.85); }
}
/* 整团柔光缓慢在 绿↔蓝 之间循环 (仿 Gemini 思考时的变色呼吸)。三块同步同相, 整体一起变色。
   绿基色 hue≈142: -25→黄绿, +95→青蓝。filter 里保留 blur(80px) 否则动画期会丢掉模糊。 */
@keyframes cipher-aura-hue {
  0%, 100% { filter: blur(80px) hue-rotate(-25deg); }
  50%      { filter: blur(80px) hue-rotate(95deg); }
}
/* thinking 时, header 自身的实色背景遮住 aura, 视觉就成了"光斑只在 header 下面". 让 header
   在 thinking 期间变透明, 跟参考截图一致. 文字 / icon 还在 (z:200 > aura 的 1). */
body.in-chat.cipher-thinking .site-header-wrap,
body.in-chat.cipher-thinking header.site-header-minimal {
  background: transparent;
}
/* 关键!: #page-chat 自身是 position:fixed z-index:50 + 实色 chat-bg, 覆盖了 aura 整个屏幕
   下方 90%. 上一版只看到 header 透出的那 ~10%, 下面整片黑. thinking 期间让它透明,
   chat 内容 (文字 / 气泡) 还在前面 (它们自己的 z-index 或文档顺序压在 #page-chat 之上),
   光斑从 #page-chat 透明区透出来. */
body.in-chat.cipher-thinking #page-chat {
  background: transparent;
}

/* ============================================================
   Cipher 入场「半调点阵波」(仿 Gemini) —— 切到 cipher 欢迎态/新开 session 放一次
   ──────────────────────────────────────────────────────────
   - 极光 blob (蓝/紫/绿) 透过圆点 mask → 流动点阵; 入场扫色 蓝→紫→绿 (绿=cipher 静息色)
   - 一次性 ~1.3s, 播完淡出, 不常驻 (省 iOS 合成开销); prefers-reduced-motion 跳过
   - 在 #page-chat 内、#page-chat-wrap 之下 (z:0 < wrap z:1), 故 hero 文字/输入框压在上面
   - 仅 body.cipher-entering 时显形 (JS 在欢迎态触发, 有会话历史则不放)
   ============================================================ */
#cipher-halftone {
  position: absolute;
  top: 0; left: 0; right: 0;
  bottom: 3.4rem;            /* 止于输入框上方: 不再铺到输入框/底栏后面 (用户要求) */
  z-index: -1;               /* 在 #page-chat-wrap 内, hero 文字/输入框之下 */
  overflow: hidden;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  contain: strict;
  /* 圆点网格 mask: 粗颗粒 (~4px 点 / 14px 间距), 绿色极光从点里透出成半调点阵 */
  -webkit-mask-image: radial-gradient(circle, #000 0 2px, transparent 2.7px);
  -webkit-mask-size: 14px 14px;
  mask-image: radial-gradient(circle, #000 0 2px, transparent 2.7px);
  mask-size: 14px 14px;
}
/* wrap 抬到点阵之上, hero 文字/输入框不被盖 */
#page-chat-wrap { position: relative; z-index: 1; }

/* 全宽竖向绿渐变带: 横向完全均匀 (左右无热点), 一条亮带从底部往上推 → 点阵均匀地从下到上升。
   比 60% 高, 留出位移行程; 亮带集中在自身下部, 顶部透明。 */
.ht-fill {
  position: absolute;
  left: 0; right: 0;
  bottom: 0;
  height: 150%;
  background: linear-gradient(to top,
    rgba(34, 197, 94, 0) 0%,
    rgba(34, 197, 94, 0.95) 16%,
    rgba(34, 197, 94, 0.6) 30%,
    rgba(34, 197, 94, 0) 52%);
  will-change: transform;
}

/* 不再入场期压黑 #page-chat: cipher 绿渐变上半本就是纯黑, 绿点阵在那照样清楚; 保留绿渐变常驻 →
   点阵淡出后直接是绿底, 不会有"先黑一段再出绿"的空窗 (用户实测的黑屏间隔)。 */
body.cipher-entering #cipher-halftone {
  visibility: visible;
  animation: ht-fade 1s ease forwards;        /* 整层淡入淡出 (时长再拉长) */
}
body.cipher-entering .ht-fill {
  animation: ht-rise 1s linear forwards;  /* 匀速下落: 末尾不减速、不悬停, 全程速度一致 (用户要求) */
}
@keyframes ht-fade {
  0%   { opacity: 0; }
  10%  { opacity: 1; }
  80%  { opacity: 1; }   /* 淡出提前 (86→80): 落到位后悬停更短 */
  100% { opacity: 0; }
}
@keyframes ht-rise {
  0%   { transform: translateY(-42%); }  /* 顶部进入 */
  50%  { transform: translateY(2%); }    /* 前 50% 时间快冲, 走完 ~2/3 路程 */
  67%  { transform: translateY(14%); }   /* 落到接近输入框 */
  100% { transform: translateY(22%); }   /* 最后 1/3 时间只走 14→22, 明显放慢收尾 */
}

/* 欢迎语跟着淡入 (用户要求)。纯 opacity、不带 translateY 位移 —— 之前 translateY(10px→0) 的"上飘"在
   iOS 上结束后会因 flex 居中的 hero 在衬线字体加载完重排而轻微下弹 (用户实测)。去掉位移就没有"飘+回弹"。 */
body.cipher-entering .cipher-hero {
  animation: cipher-hero-fade 0.8s ease 0.1s both;
}
@keyframes cipher-hero-fade {
  0%   { opacity: 0; }
  100% { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  body.cipher-entering #cipher-halftone,
  body.cipher-entering .ht-fill,
  body.cipher-entering .cipher-hero { animation: none; }
  #cipher-halftone { visibility: hidden; opacity: 0; }
}
/* 系统偏好减少动效: 关掉漂浮, 但保留淡入颜色 (静态光斑也比纯黑好看, 不至于体验断崖) */
@media (prefers-reduced-motion: reduce) {
  .cipher-aura-blob { animation: none; }
}

#page-chat-wrap {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  max-width: var(--chrome-column-max);
  margin: 0 auto;
  width: 100%;
  overflow: hidden;
}

#chat-messages {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  /* 内部 flex column, 让 .cipher-hero (flex:1) 能撑满空间居中; .cipher-msg 是 block 级
     div, 在 flex column 父级下表现为按内容高度依次堆叠, 与原 block 流视觉等价. */
  display: flex;
  flex-direction: column;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  /* 输入框已是同列 flex 子项占实位 (见 #page-chat padding-bottom), 这里只留一点点底间距, 不再
     用一坨估算 padding 去躲一个 fixed 浮层 —— 那套在 iOS standalone 视口飘移时对不齐, 是"尾部被遮 /
     滚不到底"的根。--chat-safe-extra 仍兜浏览器底部工具栏的小遮挡。 */
  padding: 16px 12px calc(8px + var(--chat-safe-extra, 0px));
  display: flex;
  flex-direction: column;
  gap: 1.15rem;
  touch-action: pan-y;
  /* Firefox: 细滚动条 + 半透明拇指 / 透明轨道 */
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, .18) transparent;
}

/* PC 端默认那条又粗又亮的滚动条在纯黑上太突兀, 收成细的半透明条 (仿 ChatGPT)。
   桌面才有常驻滚动条; 移动是 overlay 滚动条, 不受 width 影响、不显形。 */
#chat-messages::-webkit-scrollbar { width: 8px; }
#chat-messages::-webkit-scrollbar-track { background: transparent; }
#chat-messages::-webkit-scrollbar-thumb {
  /* 透明边 + background-clip 让拇指更细、与右缘留缝, 不贴边 */
  background: rgba(255, 255, 255, .16);
  background-clip: padding-box;
  border: 2px solid transparent;
  border-radius: 999px;
}
#chat-messages::-webkit-scrollbar-thumb:hover {
  background: rgba(255, 255, 255, .3);
  background-clip: padding-box;
}

.cipher-msg {
  width: 100%;
  max-width: 100%;
}

.cipher-msg-asst .cipher-md {
  font-family: var(--font-serif);   /* cipher 输出也走衬线 (用户要求); code/pre 仍是自己的等宽栈 */
  color: var(--chat-msg-asst-text);
  font-size: calc(1rem * var(--reading-scale, 1));
  line-height: 1.7;
  word-break: break-word;
}
.cipher-welcome-brand {
  color: #4ade80;
}

/* ============================================================
   Cipher hero — 仿 Gemini "G&R, 你好, 我们开始吧" 居中大字 + logo
   ──────────────────────────────────────────────────────────
   - 只在 body.in-chat 且 NOT body.chat-has-messages 时生效
   - 一旦用户首次发问 (appendMsg user/asst 时 body 加 chat-has-messages),
     .cipher-hero 整段隐藏, 消息流接管
   - flex 居中 + 占满 #chat-messages 剩余空间, 不依赖固定 vh 防键盘弹起错位
   ============================================================ */
.cipher-hero {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.2rem;
  padding: 1rem 1.5rem;
  text-align: center;
  /* relative + z-index 让 hero 文字浮在 aura blob 之上 (aura z:1, page-chat 自身实色) */
  position: relative;
  z-index: 2;
}
.cipher-hero-logo {
  width: 96px;
  height: auto;
  filter: drop-shadow(0 4px 18px rgba(34, 197, 94, 0.35));
  user-select: none;
}
.cipher-hero-text {
  /* hero 文案不在 .cipher-msg-asst 作用域里, 拿不到那条 serif 规则 → 这里显式声明,
     和 news 衬线对齐 (用户要求)。 */
  font-family: var(--font-serif);
  /* 显著放大 (用户多次要求再大): clamp 下限/视口系数/上限再上调一档 */
  font-size: clamp(3rem, 11vw, 4.8rem) !important;
  font-weight: 600;
  line-height: 1.25 !important;
  color: #e8eaed;
  max-width: 18ch;
}
.cipher-hero-text .cipher-welcome-brand {
  font-weight: 700;
  /* spring green 强调 brand 名, 跟 hero logo drop-shadow 同色系 */
  color: #4ade80;
}
/* 用户首次发消息后, 退回普通消息流: hero 直接 display:none, #chat-messages 内
   .cipher-msg 元素自然成为列表项 (因为 hero 是兄弟节点, 不影响 flex 布局) */
body.chat-has-messages .cipher-hero {
  display: none;
}

.cipher-msg-user {
  display: flex;
  justify-content: flex-end;
}

.cipher-msg-user .cipher-md.cipher-user-text {
  max-width: min(92%, 34rem);
  padding: 0.55rem 0.85rem;
  border-radius: 1rem;
  background: var(--chat-msg-user-bg);
  color: var(--chat-msg-user-text);
  font-size: calc(0.95rem * var(--reading-scale, 1));
  line-height: 1.55;
  white-space: pre-wrap;
  word-break: break-word;
}

/* Markdown 排版（助手） */
.cipher-md p { margin: 0 0 0.65em; }
.cipher-md p:last-child { margin-bottom: 0; }
/* 增量流式: 每个块独立 div 包裹 (.cipher-block 已提交 / .cipher-live 活动)。块内最后一个 <p>
   会被 p:last-child 清掉下边距, 故在块容器上补块间距, 让相邻块不贴死。 */
.cipher-block { margin-bottom: 0.65em; }
.cipher-block:last-child { margin-bottom: 0; }
.cipher-md h1, .cipher-md h2, .cipher-md h3, .cipher-md h4 {
  margin: 1em 0 0.45em;
  font-weight: 700;
  line-height: 1.25;
  color: var(--text);
}
.cipher-msg-asst .cipher-md h1, .cipher-msg-asst .cipher-md h2,
.cipher-msg-asst .cipher-md h3, .cipher-msg-asst .cipher-md h4 {
  color: var(--text);
}
/* 标题也跟 --reading-scale 一起放大, 且始终比正文略大 —— 否则大/特大字号档下正文变大、
   标题仍是固定 rem, 看着像"某些行没渲染、还是小字"(用户反馈)。 */
.cipher-md h1 { font-size: calc(1.3rem * var(--reading-scale, 1)); }
.cipher-md h2 { font-size: calc(1.15rem * var(--reading-scale, 1)); }
.cipher-md h3, .cipher-md h4 { font-size: calc(1.05rem * var(--reading-scale, 1)); }
.cipher-md ul, .cipher-md ol {
  margin: 0.35em 0 0.65em;
  padding-left: 1.35rem;
}
.cipher-md li { margin: 0.2em 0; }
.cipher-md blockquote {
  margin: 0.5em 0;
  padding: 0.35em 0 0.35em 0.85em;
  border-left: 3px solid var(--accent);
  color: var(--text2);
}
.cipher-md a {
  color: var(--accent);
  text-decoration: none;
}
.cipher-md a:hover { text-decoration: underline; }
.cipher-md code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 0.88em;
  padding: 0.12em 0.35em;
  border-radius: 4px;
  background: color-mix(in srgb, var(--chat-msg-asst-bg) 65%, var(--border));
}
.cipher-md pre {
  margin: 0.65em 0;
  padding: 0.75rem 0.85rem;
  border-radius: 8px;
  overflow-x: auto;
  background: color-mix(in srgb, var(--page-bg) 92%, #000);
  border: 1px solid var(--chat-input-bar-border);
}
.cipher-md pre code {
  padding: 0;
  background: none;
  font-size: 0.82rem;
  line-height: 1.45;
}
.cipher-md hr {
  border: none;
  border-top: 1px solid var(--border);
  margin: 1rem 0;
}
.cipher-md table {
  border-collapse: collapse;
  width: 100%;
  margin: 0.65em 0;
  font-size: 0.9rem;
}
.cipher-md th, .cipher-md td {
  border: 1px solid var(--border);
  padding: 0.35em 0.5em;
  text-align: left;
}

.cipher-md.cipher-plain {
  white-space: pre-wrap;
  color: var(--muted);
  font-size: 0.92rem;
}

.cipher-md.thinking {
  min-height: 2rem;
  display: flex;
  align-items: center;
  justify-content: flex-start;
}
.cipher-md.thinking::after {
  content: "";
  display: block;
  width: 36px; height: 8px;
  background: radial-gradient(circle, var(--chat-typing-dot) 2px, transparent 2px) 0/12px 8px,
              radial-gradient(circle, var(--chat-typing-dot) 2px, transparent 2px) 14px/12px 8px,
              radial-gradient(circle, var(--chat-typing-dot) 2px, transparent 2px) 28px/12px 8px;
  animation: typing 1s infinite;
}
@keyframes typing {
  0%,100% { opacity: 0.3; }
  33%  { opacity: 1; }
}

/* 流式"正在输入"动态指示: 接在已显示内容末尾的三点跳动波 (逐块揭示的间隙里表示还在输出)。 */
.cipher-typing {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-left: 6px;
  vertical-align: middle;
}
.cipher-typing > span {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--chat-typing-dot);
  animation: cipherTypingBounce 1.2s ease-in-out infinite;
}
.cipher-typing > span:nth-child(2) { animation-delay: 0.15s; }
.cipher-typing > span:nth-child(3) { animation-delay: 0.3s; }
@keyframes cipherTypingBounce {
  0%, 80%, 100% { transform: translateY(0); opacity: 0.4; }
  40%           { transform: translateY(-4px); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .cipher-typing > span { animation: none; opacity: 0.6; }
}

/* 流式：纯文本 + pre-wrap（app.js 流式阶段不跑 marked）；结束时整段转 Markdown */
.cipher-md.is-streaming {
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--chat-msg-asst-text);
  font-size: 0.97rem;
  line-height: 1.65;
}
.cipher-md .stream-token {
  display: inline;
  animation: streamTokenIn 0.4s cubic-bezier(0.22, 1, 0.36, 1) forwards;
  opacity: 0;
}
@keyframes streamTokenIn {
  from {
    opacity: 0;
    transform: translateY(0.35em);
    filter: blur(1px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
    filter: none;
  }
}
.cipher-md .stream-caret {
  display: inline-block;
  width: 2px;
  height: 1.05em;
  margin-left: 2px;
  vertical-align: -0.12em;
  border-radius: 1px;
  background: color-mix(in srgb, var(--chat-msg-asst-text) 88%, transparent);
  animation: streamCaretBlink 0.88s ease-in-out infinite;
}
@keyframes streamCaretBlink {
  0%, 40% { opacity: 0.85; }
  55%, 100% { opacity: 0.15; }
}
@media (prefers-reduced-motion: reduce) {
  .cipher-md .stream-token {
    animation: none;
    opacity: 1;
    filter: none;
  }
  .cipher-md .stream-caret { animation: none; opacity: 0.55; }
}

#chat-input-bar {
  display: none;
  /* 同列 flex 子项, 钉在 #page-chat-wrap 底 (消息区 flex:1 在上方滚动)。不再 position:fixed —— 故也
     不需要当年那个"避开父级 transform 包含块"的兄弟节点 hack, 直接放回 wrap 里。位置 (避开 tab 栏 /
     键盘) 由 #page-chat 的 padding-bottom 抬整列负责。 */
  flex: 0 0 auto;
  width: 100%;
  box-sizing: border-box;
  /* 与顶栏/底栏同一抬升面风格 (用户要求): --chrome-surface + 中性发丝描边, 单行高度不变 */
  background: var(--chrome-surface);
  border: var(--chrome-hairline);
  border-radius: 16px;                   /* 偏圆角矩形 (原 26px 接近胶囊) */
  margin: 0;
  padding: 5px 6px;                       /* 左右对称: 左侧 #chat-new 按钮自带内缩 */
  flex-direction: row;
  align-items: center;
  gap: 0.3rem;
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.3);
}
body.in-chat #chat-input-bar {
  display: flex;
}

#chat-input {
  flex: 1;
  min-width: 0;
  width: auto;
  background: transparent;
  border: none;
  border-radius: 0;
  padding: 0 4px;
  font-size: 1rem;
  color: var(--chat-input-text);
  outline: none;
  resize: none;
  max-height: 96px;
  /* 单行高度即可 (用户要求): lh 随 font-size+line-height; 旧浏览器用 1.5rem ≈ 1×(1rem×1.5).
     输入超过 1 行时 JS (chat-input input 事件) 会按 scrollHeight 自动撑高到 max-height 96px. */
  min-height: calc(1rem * 1.5);
  min-height: 1lh;
  line-height: 1.5;
  font-family: inherit;
  -webkit-appearance: none;
}
#chat-input:focus { outline: none; }
/* 发起新对话: 输入框最左侧 ghost 圆按钮 (compose 图标) */
#chat-new {
  width: var(--chat-composer-ctrl-h);
  height: var(--chat-composer-ctrl-h);
  padding: 0;
  border-radius: 50%;
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text);
  cursor: pointer;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s, border-color 0.15s, transform 0.12s;
}
#chat-new:hover { border-color: color-mix(in srgb, var(--chat-send-bg) 60%, var(--border)); }
#chat-new:active { transform: scale(0.92); }
.chat-input-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.4rem;
  min-height: var(--chat-composer-ctrl-h);
}

/* 麦克风按钮: 静态时透明圆, 录音时变红 + 呼吸; 与 #chat-send 同尺寸 */
#chat-mic {
  width: var(--chat-composer-ctrl-h);
  height: var(--chat-composer-ctrl-h);
  padding: 0;
  border-radius: 50%;
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text);
  font-size: 1.05rem;
  line-height: 1;
  cursor: pointer;
  flex-shrink: 0;
  transition: background 0.15s, border-color 0.15s, color 0.15s, transform 0.12s;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
#chat-mic:hover { border-color: color-mix(in srgb, var(--chat-send-bg) 60%, var(--border)); }
#chat-mic:active { transform: scale(0.92); }
#chat-mic.is-recording {
  background: #e64646;
  border-color: #e64646;
  color: #fff;
  animation: micPulse 1.1s ease-in-out infinite;
}
#chat-mic.is-connecting {
  background: color-mix(in srgb, var(--border) 70%, transparent);
  border-color: color-mix(in srgb, var(--border) 70%, transparent);
  color: var(--muted);
  cursor: default;
}
#chat-mic:disabled { opacity: 0.5; cursor: default; }
@keyframes micPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(230, 70, 70, 0.55); }
  50%      { box-shadow: 0 0 0 8px rgba(230, 70, 70, 0); }
}

#chat-send {
  width: var(--chat-composer-ctrl-h);
  height: var(--chat-composer-ctrl-h);
  padding: 0;
  border-radius: 50%;
  background: var(--chat-send-bg);
  border: none;
  color: #fff;
  font-size: 1.15rem;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  flex-shrink: 0;
  transition: background 0.15s;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
#chat-send:active  { background: var(--chat-send-bg-h); }
#chat-send:disabled { background: var(--chat-send-disabled); cursor: default; }

/* 滚到最新消息 FAB (Gemini 同款向下箭头):
   - 默认: opacity:0 + pointer-events:none, 不参与点击, 不挡视线
   - body.in-chat.chat-not-at-bottom: 浮现, 用户上滚 / 键盘起遮挡时触发
   - bottom 用跟输入栏同源公式 (safe-area / keyboard-inset), 再 +88px 抬到输入栏顶上方
   - 显示永远只在 chat tab + 没看到底部时 */
#chat-scroll-fab {
  display: flex;
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(
    max(
      var(--safe-bottom),
      var(--keyboard-inset-bottom, 0px) - var(--chat-keyboard-dock-overlap)
    ) + 88px
  );
  z-index: 130;                          /* > #chat-input-bar (120) */
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.12);
  background: rgba(28, 31, 36, 0.92);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  color: rgba(232, 238, 243, 0.92);
  align-items: center;
  justify-content: center;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.45);
  /* 默认隐藏 (opacity + pointer-events 双保险, transform 配合做"从下方稍微抬起来"动画) */
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
  transition:
    opacity 0.2s ease,
    transform 0.22s cubic-bezier(0.22, 1, 0.36, 1),
    visibility 0s linear 0.2s;
}
body.in-chat.chat-not-at-bottom #chat-scroll-fab {
  opacity: 1;
  pointer-events: auto;
  visibility: visible;
  transition:
    opacity 0.2s ease,
    transform 0.22s cubic-bezier(0.22, 1, 0.36, 1),
    visibility 0s linear 0s;
}
body:not(.in-chat) #chat-scroll-fab { display: none; }
#chat-scroll-fab:hover { background: rgba(40, 44, 52, 0.96); color: #fff; }
#chat-scroll-fab:active { transform: translateX(-50%) scale(0.92); }

/* ══════════════════════════════════════════
   CALENDAR PAGE
══════════════════════════════════════════ */
#page-calendar {
  display: none;
  position: fixed;
  top: var(--header-stack-h);
  left: 0; right: 0;
  bottom: calc(var(--tab-h) + var(--safe-bottom));
  background: var(--chrome-surface);   /* 主体色统一到 chrome 抬升面 (与顶栏/底栏一致, 用户要求) */
  z-index: 50;
  /* page 本身不滚: 联赛/二级 tab 钉死在上, 只有内层 #horizon-scroll 滚动
     (iOS standalone 下 position:fixed 容器里的 sticky 不可靠, 故拆成 flex 列 + 内层独立滚) */
  overflow: hidden;
}
#page-calendar.active { display: flex; flex-direction: column; }

/* ============================================================
   Full-screen audio player (hash route #player)
   - 全屏覆盖在新闻列表 / 详情之上 (article-detail-open 时也能浮)
   - 共享全局 _audioElement, 不重新加载, 关页面播放不停
   - 速度按钮直接改 audio.playbackRate, 无需 reload
   ============================================================ */
#page-player {
  position: fixed;
  inset: 0;
  background: var(--bg);
  z-index: 400;     /* 高于 tab-bar (300) + audio-player-tab (300), 低于 side-drawer (500) */
  overflow: hidden; /* 内部 .player-captions 自己滚, page 不滚 */
  box-sizing: border-box;
}
body.player-open #page-player { display: block; }
body.player-open #audio-player-tab,
body.player-open #tab-bar { display: none; }

/* 模糊背景: JS 用 hero 图取色后会注入 --player-bg-grad 渐变, 没图就是兜底纯色 */
.player-bg-blur {
  position: absolute;
  inset: -10%;          /* 多铺一圈, 让 blur 边缘不露底 */
  background: var(--player-bg-grad, linear-gradient(135deg, #1b5e3f 0%, #0d2e21 80%));
  filter: blur(40px) saturate(1.2);
  z-index: 0;
  pointer-events: none;
  transition: background 0.6s ease;
}
.player-bg-blur::after {
  /* 暗化叠加, 保证字幕白字可读 */
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(0,0,0,0.30) 0%, rgba(0,0,0,0.55) 100%);
}

.player-content {
  position: relative;
  z-index: 1;
  height: 100%;
  width: 100%;
  max-width: 720px;
  margin: 0 auto;
  /* 顶部留白 = 状态栏 safe-area + 返回键 + grabber 呼吸; dynamic island 设备 safe-area-inset-top ≈ 47px */
  padding:
    calc(env(safe-area-inset-top, 0px) + 0.9rem)
    1.5rem
    calc(var(--safe-bottom) + 1rem);
  box-sizing: border-box;
  display: grid;
  /* 默认: grabber(auto) / stage(1fr 大封面) / captions(0, 隐) / controls(auto)
     captions-on: grabber(auto) / stage(auto 小封面横排) / captions(1fr 滚) / controls(auto)
     行高交换避免"stage 还占 1fr 时 art 缩小留出 350px 空白, captions 在下面挤一团"的视觉错位 */
  grid-template-rows: auto 1fr auto auto;
  gap: 0.6rem;
}
body.captions-on .player-content {
  grid-template-rows: auto auto 1fr auto;
}

/* 拖动条 (sheet 视觉提示, 装饰性, 不响应交互) */
.player-grabber {
  width: 38px;
  height: 5px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.45);
  margin: 0 auto 0.2rem;
}

/* 主区: 封面 + 文案; flex 列布局, 居中, captions-on 时整块压扁让位 */
.player-stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.4rem;
  min-height: 0;
  padding-top: calc(44px + 0.6rem);  /* 给左上角返回键留视觉缓冲, 防止 art 顶在它旁边 */
  transition: gap 0.28s ease;
}
body.captions-on .player-stage {
  /* 横排小封面 + 文案左对齐, 类似 Apple Music now-playing 顶部条; align-items: flex-start
     而不是 center, 让小图顶部对齐到 meta 第一行, 不会再"图比文字栏高, 底边压到字幕区" */
  flex-direction: row;
  align-items: flex-start;
  justify-content: flex-start;
  text-align: left;
  gap: 0.85rem;
  padding-top: calc(44px + 0.2rem);
}
body.captions-on .player-meta {
  flex: 1;
  min-width: 0;
  /* 让 meta 与 art 同高 (justify-content:center 让单行居中, 多行 stretch). 视觉上图跟文齐 */
  align-self: stretch;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* 全屏播放器顶部左上角的"<"返回, 跟 header #back-btn 解耦 — header 在播放器之下被盖, 全局返回看不见。
   贴 safe-area, 白色半透明大按钮 + 黑色"<", 在任何 hero 取色背景上都极其显眼 (上一版灰底白字偏暗
   贴着 dynamic island, 用户根本扫不到)。 */
.player-back {
  position: absolute;
  top: calc(env(safe-area-inset-top, 0px) + 1.2rem);
  right: 0.9rem;                   /* 返回放右上角 (用户要求) */
  left: auto;
  z-index: 2;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: none;
  background: rgba(255, 255, 255, 0.92);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  color: #111;
  font-size: 1.9rem;
  font-weight: 600;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.45);
  padding: 0 0.32rem 0.18rem 0;
}
.player-back:active { transform: scale(0.94); }

/* ── 统一返回键 (仿参考图: 半透明磨砂灰圆 + chevron) ───────────────────────
   detail 顶栏返回 / 侧栏返回 / 全屏播放器返回 三处共用此视觉; display 与定位仍由各自规则管,
   故这里不设 display/position。放在三者各自规则之后, 靠源码顺序覆盖其视觉属性。 */
.header-back,
.drawer-close,
.player-back {
  width: 44px;
  height: 44px;
  min-width: 44px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.12);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  color: #fff;
  box-shadow: none;
  font-size: 0;                     /* 去掉旧 ‹ 字形残留影响 */
  transition: background 0.15s;
}
.header-back:hover,
.drawer-close:hover,
.player-back:hover { background: rgba(255, 255, 255, 0.22); color: #fff; }
/* pointer-events:none —— iOS Safari 上点 fill:none 的 SVG 不一定把 click 冒泡到 button,
   导致"返回点不动"。让 chevron 对指针透明, 整圈都落到 button 上。 */
.back-chevron { width: 27px; height: 27px; display: block; pointer-events: none; }

.player-art-wrap {
  display: flex;
  justify-content: center;
  align-items: center;
}
.player-art {
  /* compact 默认: 大封面 (Apple Podcasts 视觉重心) */
  width: min(70vw, 320px);
  height: min(70vw, 320px);
  object-fit: cover;
  border-radius: 14px;
  box-shadow: 0 18px 48px rgba(0, 0, 0, 0.5);
  background: rgba(255,255,255,0.06);
  transition: width 0.28s ease, height 0.28s ease;
}
/* captions-on: art 缩小到角落感的小封面, 主屏让字幕用 */
body.captions-on .player-art {
  width: min(28vw, 110px);
  height: min(28vw, 110px);
}

.player-meta {
  text-align: left;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  color: #fff;
  width: 100%;
}
.player-kicker {
  font-size: 0.78rem;
  font-weight: 600;
  color: rgba(255,255,255,0.7);
  letter-spacing: 0.02em;
}
.player-kicker:empty { display: none; }
.player-title {
  font-size: 1.3rem;
  font-weight: 700;
  line-height: 1.3;
  text-shadow: 0 1px 3px rgba(0,0,0,0.35);
  word-break: break-word;
  /* 控制超长标题, 最多 3 行避免顶出 stage 区 */
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.player-subtitle {
  font-size: 0.85rem;
  color: rgba(255,255,255,0.65);
  word-break: break-word;
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
body.captions-on .player-title { font-size: 1rem; -webkit-line-clamp: 2; }
body.captions-on .player-subtitle { font-size: 0.74rem; }

/* 字幕列表: 默认完全隐藏, captions-on 时显示; 活动行大字, 自动 smooth scroll 让活动行居中 */
.player-captions {
  display: none;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  scroll-behavior: smooth;
  /* 原 12vh/14vh 留白太大: 标题与字幕之间、字幕下方各空出一大块 (用户反馈"上面顶太高/下面空").
     收到 5vh/8vh: 仍够 active 行滚到中部, 但不再有大段空白。 */
  padding: 5vh 0 8vh;
  scroll-snap-type: y proximity;
  scrollbar-width: none;
  /* 让 captions 占满 stage 上方 grid row + 自己上限, 不挤压 controls */
  max-height: 100%;
}
body.captions-on .player-captions { display: block; }
/* captions-on 的 stage / meta 横排规则已在上面统一定义 (含 align-items: flex-start), 这里不再重复
   以前重复的 align-items: center 会让小封面跟文字栏 baseline 错位, 看起来图压到字幕 */
.player-captions::-webkit-scrollbar { display: none; }

.player-cap-line {
  font-size: 1.1rem;
  line-height: 1.5;
  color: rgba(255, 255, 255, 0.4);
  padding: 0.55rem 0.5rem;
  margin: 0.25rem 0;
  /* 滚动字幕用左对齐: 多行不再两端对齐, 读起来不会跳眼 (用户反馈) */
  text-align: left;
  cursor: pointer;
  transition: color 0.3s ease, font-size 0.3s ease, opacity 0.3s ease;
  scroll-snap-align: center;
  word-break: break-word;
}
.player-cap-line:hover { color: rgba(255,255,255,0.6); }
.player-cap-line.is-active {
  color: #fff;
  font-size: 1.45rem;
  font-weight: 600;
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.45);
  opacity: 1;
}
.player-cap-line.is-past {
  color: rgba(255, 255, 255, 0.3);
}

/* 双语行: primary 跟当前播放 lang (主字号高亮), secondary 是另一种 lang (次字号半透) */
.player-cap-line.is-bilingual {
  display: flex;
  flex-direction: column;
  gap: 0.18rem;
}
.player-cap-line-primary {
  font-size: inherit;
  font-weight: inherit;
  color: inherit;
  line-height: 1.4;
}
.player-cap-line-secondary {
  font-size: 0.82em;
  color: rgba(255, 255, 255, 0.55);
  line-height: 1.35;
  font-weight: 400;
  text-shadow: none;
}
/* active 时 primary 拉大, secondary 也微亮一点但仍小 */
.player-cap-line.is-active .player-cap-line-primary {
  font-size: 1em;  /* 继承 .is-active 的 1.45rem */
}
.player-cap-line.is-active .player-cap-line-secondary {
  color: rgba(255, 255, 255, 0.78);
  font-size: 0.65em;     /* 1.45 * 0.65 ≈ 0.94rem, 比未 active 的 secondary 略大 */
}
/* past 行的双语两段都更淡, 不抢戏 */
.player-cap-line.is-past .player-cap-line-secondary {
  color: rgba(255, 255, 255, 0.22);
}

.player-captions-empty {
  text-align: center;
  font-size: 0.9rem;
  color: rgba(255,255,255,0.5);
  padding: 4rem 0;
}

/* 底部控制区: progress → transport(-15/▶/+30) → speed → toolbar(caption) */
.player-controls {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.7rem;
  padding: 0.3rem 0 0;
}

/* 主控行: [倍速下拉] [播放/暂停] [字幕], 居中一行 */
.player-transport {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.4rem;
  margin-top: 0.1rem;
}

/* 跳转按钮: 圆环 + 中间数字 + 弧形箭头 (跟 Apple Podcasts 同感) */
.player-skip {
  width: 52px;
  height: 52px;
  border: none;
  background: transparent;
  color: #fff;
  cursor: pointer;
  position: relative;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  -webkit-tap-highlight-color: transparent;
}
.player-skip:active { transform: scale(0.92); }
.player-skip-icon {
  width: 100%;
  height: 100%;
}
.player-skip-num {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.72rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  padding-top: 4px;       /* 略下移让数字落在圆环正中 */
  pointer-events: none;
}

/* 底部工具栏: 仅 caption 切换. 占整行宽度, 按钮靠左 (跟参考设计一致). */
.player-toolbar {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  padding: 0.6rem 0.2rem 0;
}
.player-tool-btn {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: none;
  background: transparent;
  color: rgba(255, 255, 255, 0.75);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.12s, color 0.12s;
}
.player-tool-btn:hover { background: rgba(255,255,255,0.1); color: #fff; }
.player-tool-btn[aria-pressed="true"] {
  background: rgba(255,255,255,0.92);
  color: #111;
}
/* 中英双语切换: 文字按钮 (复用 .player-tool-btn 圆 + 按下高亮) */
.player-bi-btn {
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: -0.02em;
}

.player-progress {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
.player-progress-bar {
  position: relative;
  height: 18px;
  cursor: pointer;
  touch-action: none;
  display: flex;
  align-items: center;
}
/* .player-progress-track 等同原来 ::before, 但放真实元素方便 z-index 调整 (Apple Podcasts 上有
   半透明轨道 + 高对比 fill, 跟现在效果一致) */
.player-progress-track {
  position: absolute;
  left: 0; right: 0;
  height: 4px;
  background: rgba(255, 255, 255, 0.22);
  border-radius: 2px;
}
.player-progress-bar::before {
  /* 兼容: 旧 ::before 仍存在但隐藏, 防止与 .player-progress-track 双轨 */
  content: none;
}
.player-progress-fill { height: 4px; }
.player-progress-fill {
  position: absolute;
  left: 0;
  height: 3px;
  background: rgba(255, 255, 255, 0.95);
  border-radius: 2px;
  width: 0;
  pointer-events: none;
}
.player-progress-thumb {
  position: absolute;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
  transform: translateX(-50%);
  left: 0;
  pointer-events: none;
}
.player-times {
  display: flex;
  justify-content: space-between;
  font-size: 0.72rem;
  color: rgba(255,255,255,0.7);
  font-variant-numeric: tabular-nums;
}

/* 播放键: 原大白圆 + 黑色 ▶ 字形 看着"廉价". 升级为:
   - 双层 radial-gradient 制造玻璃质感 (上亮下暗的微微立体)
   - inner shadow (inset) 给底缘一圈高光暗化, 像物理按钮
   - outer drop shadow 多层 (近层近黑色定位 + 远层柔光扩散)
   - 字形保持 ▶ / ⏸ (Unicode), 但用 font-size + letter-spacing 让位置看着对中
   - active 加深的同时往内陷 (scale + shadow 收缩), 真实按压反馈 */
.player-play-toggle {
  width: 76px;
  height: 76px;
  border-radius: 50%;
  border: none;
  color: #0a0a0a;
  background:
    radial-gradient(circle at 30% 22%, #ffffff 0%, #f6f6f6 45%, #d8d8d8 100%);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.75rem;
  cursor: pointer;
  position: relative;
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.9) inset,
    0 -3px 8px rgba(0, 0, 0, 0.08) inset,
    0 1px 1px rgba(255, 255, 255, 0.6),
    0 8px 16px rgba(0, 0, 0, 0.35),
    0 20px 44px rgba(0, 0, 0, 0.45);
  transition: transform 0.12s ease, box-shadow 0.12s ease;
  -webkit-tap-highlight-color: transparent;
}
.player-play-toggle:hover {
  background: radial-gradient(circle at 30% 22%, #ffffff 0%, #fafafa 45%, #e2e2e2 100%);
}
.player-play-toggle:active {
  transform: scale(0.95);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.7) inset,
    0 -2px 4px rgba(0, 0, 0, 0.18) inset,
    0 3px 8px rgba(0, 0, 0, 0.32),
    0 8px 18px rgba(0, 0, 0, 0.35);
}
.player-play-icon {
  line-height: 1;
  /* ▶ 字形几何上重心偏左, 微微 translate 让视觉居中 */
  margin-left: 0.18rem;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.4);
}
/* 暂停: 简约双竖线 (替换原 ⏸ 字形) */
.player-pause-icon {
  display: none;
  align-items: center;
  gap: 7px;
}
.player-pause-icon > span {
  width: 5px;
  height: 26px;
  border-radius: 2.5px;
  background: currentColor;
}
/* 播放中 → 藏 ▶, 显双竖线; 暂停态 (默认) 反之 */
.player-play-toggle.is-playing .player-play-icon { display: none; }
.player-play-toggle.is-playing .player-pause-icon { display: inline-flex; }

/* 倍速: 触发按钮 + 上弹下拉菜单 */
.player-speed {
  position: relative;
  display: flex;
}
.player-speed-btn {
  height: 36px;
  min-width: 48px;
  padding: 0 0.85rem;
  border-radius: 18px;
  border: 1px solid rgba(255, 255, 255, 0.25);
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
  backdrop-filter: blur(6px);
  transition: background 0.12s, border-color 0.12s;
}
.player-speed-btn:hover { background: rgba(255, 255, 255, 0.18); }
.player-speed-menu {
  position: absolute;
  bottom: calc(100% + 10px);
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px;
  background: rgba(28, 28, 30, 0.97);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 12px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
  z-index: 10;
}
.player-speed-menu[hidden] { display: none; }
.player-speed-opt {
  min-width: 70px;
  padding: 8px 14px;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: #fff;
  font-size: 0.85rem;
  font-weight: 500;
  text-align: center;
  cursor: pointer;
  transition: background 0.12s;
}
.player-speed-opt:hover { background: rgba(255, 255, 255, 0.12); }
.player-speed-opt.is-active { background: #fff; color: #111; font-weight: 700; }

@media (prefers-reduced-motion: reduce) {
  .player-captions { scroll-behavior: auto; }
  .player-cap-line { transition: none; }
}

/* 须在 #page-chat / #page-calendar 基础规则之后，才能覆盖 top；与上方「手机」fixed 顶栏一致 */
@media (max-width: 699px) {
  #page-chat,
  #page-calendar {
    top: calc(env(safe-area-inset-top, 0px) + var(--header-stack-h));
    transition:
      top 0.28s cubic-bezier(0.4, 0, 0.2, 1),
      bottom 0.28s cubic-bezier(0.4, 0, 0.2, 1);
  }

  body.chrome-scroll-hide-tabs #page-chat,
  body.chrome-scroll-hide-tabs #page-calendar {
    top: env(safe-area-inset-top, 0px);
    bottom: max(var(--safe-bottom), var(--keyboard-inset-bottom, 0px));
  }
}

/* ============ Horizon tab (球赛日历改造: 赛程 + 球员 list) ============ */
#horizon-wrap {
  padding: 0.25rem 1rem 0;          /* 底部 6rem 留白下移到 #horizon-scroll (它才是滚动层) */
  max-width: 720px;
  /* width:100% 必须显式给: #page-calendar 改 flex 列后, 带 margin:auto 的 flex 子项会**停止横向拉伸、
     改成按内容定宽** —— 有数据(列表宽)→撑满, 404(空)→缩成 tab 行宽再被 margin 居中, 于是切换联赛时
     整行名称左右抖 18px (用户实测的根因)。width:100% 钉死全宽, 尺寸不再随内容变。 */
  width: 100%;
  margin: 0 auto;
  box-sizing: border-box;
  font-family: var(--font-serif);   /* 整个 horizon 页统一衬线, 与 news 对齐 (用户要求) */
  /* 撑满 #page-calendar 高度, 内部分「固定 tab」+「滚动区」两段 */
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.horizon-top-tabs {
  display: flex;
  /* 不用 space-between: 它按内容宽度分配空隙, 一旦可用宽度变 (切联赛后列表有/无数据 → 滚动条/内容
     宽度变化) 整行名称就会左右抖 (用户实测"切换后名称距离改变")。改成每个 tab 各占固定 1/3 列
     (.horizon-top-tab flex:1 1 0), 位置与内容宽度/滚动条/高亮全解耦, 永不抖。 */
  gap: 0.4rem;
  border-bottom: 1px solid var(--border);
  margin-bottom: 0.5rem;
  background: var(--chrome-surface);   /* 与 #page-calendar 主体同色, 不留黑条 */
  z-index: 2;
  overflow: hidden;                 /* 不再横向滚动 (用户要求固定住) */
  flex: 0 0 auto;                   /* 钉在顶部不滚 (sticky 在 iOS fixed 容器里失效, 改 flex 固定) */
}
#horizon-views {
  flex: 0 0 auto;                   /* 二级 tab 同样钉顶不滚 */
}
#horizon-scroll {
  flex: 1 1 auto;                   /* 唯一滚动区: 吃掉剩余高度 */
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  /* 顶/底边界不向外链 rubber-band: 顶部下拉回弹会让超长滚动层整层重栅格化 → iOS 内存冲顶冻死 */
  overscroll-behavior: contain;
  padding-bottom: 6rem;             /* 底部留白 (原在 #horizon-wrap), 让最后一条不被底栏盖住 */
}
.horizon-top-tab {
  flex: 1 1 0;                      /* 各占固定 1/3 列: 位置不随内容宽度/滚动条/高亮变, 切换不抖 */
  min-width: 0;
  /* logo 在上、名字在下竖排居中 (原横排): 横排里 logo 一大名字就没地方, 两个都调大塞不进 1/3 列;
     竖排后 logo 大小与名字宽度解耦, 两个都能放大不裁。 */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.3rem;
  background: transparent;
  border: none;
  color: rgba(255, 255, 255, 0.62);  /* 未选中: 偏亮的灰, 黑底也读得清 */
  font-size: 0.82rem;               /* 调大 (原 0.72) */
  font-weight: 600;
  white-space: nowrap;
  padding: 0.6rem 0;
  cursor: pointer;
  position: relative;
  font-family: inherit;
  transition: color 0.15s ease;
}
.horizon-tab-logo {
  width: 46px;                /* 再调大 (原 38 → 22) */
  height: 46px;
  object-fit: contain;
  flex: 0 0 auto;
  background: #fff;            /* 白底圆角片: 联赛 logo 多是深色, 黑背景上看不清, 垫白底就清楚了 */
  border-radius: 11px;
  padding: 2px;
  box-sizing: border-box;
}
/* 竖排下名字居中, 在空格处断到第二行 (Premier/League、Champions/League); 中文 2 字单行。 */
.horizon-top-tab span {
  white-space: normal;
  line-height: 1.12;
  text-align: center;
  max-width: 100%;       /* 竖排, 名字吃满 1/3 列宽即可, 不再限 6em */
}
/* 选中只换颜色 + 下方绿条, **不改字重** —— 700/600 宽度不同, 配 space-between 会让整行名称位置
   随切换抖动 (用户实测"联赛名称距离改变了"); 字重恒定就不 reflow。 */
.horizon-top-tab.active { color: var(--text); }
.horizon-top-tab.active::after {
  content: "";
  position: absolute;
  bottom: -1px; left: 0; right: 0;
  height: 2px;
  background: #4ade80;
  border-radius: 1px;
}
.horizon-view { display: none; }
.horizon-view.active { display: block; }

.horizon-subtabs {
  display: flex;
  gap: 0.5rem;
  overflow-x: auto;
  padding: 0.5rem 0;
  margin-bottom: 0.4rem;
  -webkit-overflow-scrolling: touch;
}
.horizon-subtabs::-webkit-scrollbar { display: none; }
.horizon-subtab {
  background: var(--chrome-control-bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.4rem 0.9rem;
  font-size: 0.88rem;
  cursor: pointer;
  white-space: nowrap;
  font-family: inherit;
}
.horizon-subtab.active {
  background: #4ade80;
  color: #0a0e14;
  border-color: #4ade80;
  font-weight: 600;
}

.horizon-list, .horizon-cards {
  display: flex;
  flex-direction: column;
  gap: 0;
}
.horizon-loading, .horizon-error, .horizon-empty {
  text-align: center;
  color: var(--muted);
  padding: 2rem 0;
}
.horizon-error { color: #f87171; }

/* 轮次标题: 每轮所有比赛上方统一一条 (Round X / 第 X 轮) */
.hx-round-header {
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--text);
  letter-spacing: 0.02em;
  padding: 0.35rem 0.2rem 0.1rem;
  margin-top: 0.35rem;
}
.hx-round-header:first-child { margin-top: 0; }

/* 比赛行 (仿参考图): 最左日期, 中间主客两行竖排 [logo] 队名 …… 比分, 右侧分隔线 + 状态。
   无卡片框, 行间用细分隔线 (与参考一致)。 */
.horizon-fixture {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 0.6rem;
  padding: 0.75rem 0.4rem;
  border-bottom: 1px solid color-mix(in srgb, #fff 8%, transparent);
  /* 离屏行跳过布局/绘制/图片解码, 让 Safari 回收离屏队徽内存 (整季 380 行/760 队徽时是关键).
     ~78px = 两行队伍(26×2)+ 间距 + 上下 padding 的占位高度, 防滚动条回弹。 */
  content-visibility: auto;
  contain-intrinsic-size: auto 78px;
}
.hx-date {
  flex: 0 0 auto;
  min-width: 2.8rem;
  font-size: 0.72rem;
  color: var(--text);    /* 时间用白色, 不用暗灰 (用户要求) */
  text-align: center;
  line-height: 1.3;
}
.hx-teams {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  min-width: 0;
}
.hx-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  min-width: 0;
}
.hx-logo {
  width: 26px; height: 26px;
  object-fit: contain;
  flex: 0 0 auto;
}
.hx-name {
  flex: 1;
  font-size: 0.95rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.hx-goals {
  flex: 0 0 auto;
  min-width: 1.1em;
  text-align: right;
  font-size: 1.05rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.horizon-fixture.upcoming .hx-goals { color: var(--muted); font-weight: 400; }
/* 右侧状态列: 左边一条竖分隔线 + 居中状态文字 (Full-Time / 时间) */
.hx-status {
  align-self: stretch;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-width: 5.5rem;
  padding-left: 0.9rem;
  border-left: 1px solid color-mix(in srgb, #fff 12%, transparent);
  font-size: 0.82rem;
  color: var(--muted);
  line-height: 1.3;
  text-align: center;
}
.hx-time { color: var(--muted); font-size: 0.78rem; }

.horizon-cards { gap: 0.5rem; }
@media (min-width: 600px) {
  .horizon-cards {
    display: grid;
    grid-template-columns: 1fr 1fr;
  }
}
.horizon-player-card {
  display: flex;
  gap: 0.8rem;
  padding: 0.7rem;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: color-mix(in srgb, var(--page-bg) 50%, transparent);
  align-items: center;
}
.hp-photo {
  width: 64px; height: 64px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--chrome-control-bg);
  flex-shrink: 0;
}
.hp-info { min-width: 0; flex: 1; }
.hp-name {
  font-size: 1rem;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.hp-meta, .hp-team {
  font-size: 0.78rem;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-top: 1px;
}
.hp-stats {
  display: flex;
  gap: 0.6rem;
  margin-top: 0.35rem;
  font-size: 0.78rem;
  flex-wrap: wrap;
}
.hp-stats span { color: var(--text); }

/* ============ 旧 .calendar-* 样式留作 dead-code,不再使用 ============ */
.calendar-wrap {
  max-width: 420px;
  margin: 0 auto;
  padding: 1rem 1rem 1.5rem;
}

.calendar-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 1rem;
}

.cal-title {
  font-size: 1.12rem;
  font-weight: 700;
  color: var(--text);
  flex: 1;
  text-align: center;
}

.cal-nav-btn {
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: 1.65rem;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  padding-bottom: 3px;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.cal-nav-btn:hover { border-color: var(--accent); color: var(--accent); background: var(--tag-bg); }
.cal-nav-btn:active { opacity: 0.92; }

.calendar-weekdays {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  text-align: center;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--muted);
  margin-bottom: 0.5rem;
}

.calendar-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 5px;
}

.cal-cell {
  aspect-ratio: 1;
  max-height: 52px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 10px;
  font-size: 0.88rem;
  font-weight: 500;
  color: var(--text);
}

.cal-day {
  background: var(--surface);
  border: 1px solid var(--border);
}

.cal-day.cal-today {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
  font-weight: 700;
}

.calendar-weekdays .cal-weekend-label {
  color: var(--cal-weekend);
}

.cal-day.cal-weekend:not(.cal-today) {
  color: var(--cal-weekend);
}

.cal-pad {
  visibility: hidden;
  pointer-events: none;
}

/* ══════════════════════════════════════════
   EMPTY / LOADING
══════════════════════════════════════════ */
.empty {
  text-align: center;
  color: var(--muted);
  padding: 4rem 1rem;
  font-size: 0.9rem;
}
.spinner {
  display: inline-block;
  width: 1.1rem; height: 1.1rem;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.75s linear infinite;
  vertical-align: middle;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ── 下拉刷新 (pull-to-refresh) ─────────────────────────
   新闻列表滚到顶后继续下拉触发. JS 把 #list-view 跟手下移 + 驱动指示器
   transform/opacity, 松手过阈值加 .ptr-refreshing → 绿环 CSS 动画转圈. */
#ptr-indicator {
  position: fixed;
  left: 50%;
  top: calc(var(--header-stack-h) + 6px);
  z-index: 60;
  width: 34px;
  height: 34px;
  margin-left: -17px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--surface);
  box-shadow: 0 3px 12px rgba(0, 0, 0, 0.5);
  opacity: 0;
  transform: translateY(-40px);
  pointer-events: none;
}
#ptr-spinner {
  display: block;
  width: 20px;
  height: 20px;
  border: 2.5px solid rgba(61, 220, 132, 0.22);
  border-top-color: #3ddc84;
  border-radius: 50%;
}
#ptr-indicator.ptr-refreshing #ptr-spinner {
  animation: ptr-spin 0.7s linear infinite;
}
@keyframes ptr-spin { to { transform: rotate(360deg); } }

/* ══════════════════════════════════════════
   DESKTOP
══════════════════════════════════════════ */
@media (min-width: 700px) {
  :root {
    --header-h: 46px;
    --tab-icon-outer: 84px;
    --tab-h: var(--tab-icon-outer);
  }

  #app-shell {
    display: flex;
    flex-direction: column;
    min-height: calc(100vh - var(--header-stack-h));
    width: 100%;
    overflow: hidden;
  }

  header.site-header-minimal { padding: 0 0.75rem; }

  main { padding: 0; max-width: none; }

  .card { padding: 1.1rem 1.25rem; gap: 1rem; }
  #news-list { border-radius: 0; }
  .card-thumb { width: 96px; height: 70px; }

  .detail-title { font-size: 1.5rem; }
  .detail-body  { padding: 1.75rem 2rem; }
  .detail-images { grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
  .detail-images img { max-height: 300px; }

  .calendar-wrap { max-width: 480px; padding: 1.5rem 2rem 2rem; }
  .cal-title { font-size: 1.2rem; }
  .cal-cell { max-height: 58px; font-size: 0.92rem; }

  .tab-btn { padding: 0; }

  /* 与底栏同列：左右仅 --chrome-gutter；消息/输入区不再重复加水平 padding */
  #page-chat-wrap { padding: 0 var(--chrome-gutter); }
  #chat-messages {
    /* 输入框同列占实位, 桌面也只留小底间距 (避开 tab 栏由 #page-chat padding-bottom 统一负责) */
    padding: 16px 0 calc(8px + var(--chat-safe-extra, 0px));
  }
}

/* 仅真正宽屏：新闻列表底栏须 fixed（若在 main 文档流会排在无限列表之后，永远滚不到） */
@media (min-width: 1024px) {
  body:not(.article-detail-open):not(.tab-bar-overlay) {
    padding-bottom: calc(var(--tab-h) + var(--safe-bottom));
  }

  body.tab-bar-overlay:not(.article-detail-open) {
    padding-bottom: var(--safe-bottom);
  }

  /*
   * 宽屏切到 cipher / horizon 时，#app-shell 内为「主区 flex:1 + 底栏」；高度必须卡在视口减顶栏，
   * 勿再用 main.offsetHeight（含整页新闻列表高度）当 shell 高，否则中间会被拉成几千像素空白。
   */
  body.tab-bar-overlay #app-shell {
    height: calc(100dvh - var(--header-stack-h));
    max-height: calc(100dvh - var(--header-stack-h));
    min-height: 0;
  }
  /* cipher 专门覆盖: 基础规则 `body.in-chat.tab-bar-overlay #app-shell{height:100%}` (line ~1563,
     给手机用的) 特异性比上面那条高, 桌面会把 #app-shell 塌成内容高 → #chat-messages 撑不满、
     无法内部滚动、body 又 overflow:hidden → 整页"卡死"。这里用同特异性 + 更靠后压回视口高。 */
  body.in-chat.tab-bar-overlay #app-shell {
    height: calc(100dvh - var(--header-stack-h));
    max-height: calc(100dvh - var(--header-stack-h));
    min-height: 0;
  }

  /* 新闻列表与底栏同一列（max-width、左右内边距与 #tab-bar 一致）；<1024px 底栏全宽，列表仍贴边 */
  #list-view {
    max-width: var(--chrome-column-max);
    margin-left: auto;
    margin-right: auto;
    width: 100%;
    box-sizing: border-box;
    padding: 0 var(--chrome-gutter);
  }

  /* PC 适配: 桌面新闻列表直接沿用基础的单列 flex (#news-list, 见上方), 列宽 = #list-view 的
     --chrome-column-max (860), 与 cipher (#page-chat-wrap) 同宽 —— 两页同一条中轴, 底部 420
     tab 胶囊天然对齐; 不再双列/加宽, 桌面与移动端、cipher 阅读流完全统一, 无需任何覆盖。 */
  /* 详情页在桌面仍是单列居中阅读 (列表才双列), detail-view 自己已 max-width:720 居中 */

  body:not(.article-detail-open):not(.tab-bar-overlay) #tab-bar {
    position: fixed;
    bottom: max(10px, var(--safe-bottom));
    left: 1rem;
    right: 1rem;
    margin-top: 0;
    margin-left: auto;
    margin-right: auto;
    transform: none;
    width: auto;
    max-width: 420px;
    height: var(--tab-h);
    min-height: 0;
    padding: 0 10px;
    box-sizing: border-box;
    /* 实色面 + 发丝线: 桌面浮动胶囊不能半透明, 否则新闻卡从背后透出来、图标压在卡片文字上成鬼影。
       用 --chrome-surface 与移动端 flat bar 同色, 读起来是一条干净的浮动导航。 */
    background: var(--chrome-surface);
    border: var(--chrome-hairline);
    border-radius: 14px;
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.34);
  }

  body.article-detail-open #tab-bar {
    position: fixed;
    bottom: max(10px, var(--safe-bottom));
    left: 1rem;
    right: 1rem;
    margin-top: 0;
    margin-left: auto;
    margin-right: auto;
    max-width: 420px;
    transform: none;
    /* 实色面 + 发丝线: 桌面浮动胶囊不能半透明, 否则新闻卡从背后透出来、图标压在卡片文字上成鬼影。
       用 --chrome-surface 与移动端 flat bar 同色, 读起来是一条干净的浮动导航。 */
    background: var(--chrome-surface);
    border: var(--chrome-hairline);
    border-radius: 14px;
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.34);
    height: var(--tab-h);
    padding: 0 10px;
  }

  body.tab-bar-overlay #tab-bar {
    position: static;
    margin-top: 0.1rem;
    margin-left: auto;
    margin-right: auto;
    left: auto;
    right: auto;
    transform: none;
    width: min(100%, 420px);
    max-width: 420px;
    height: var(--tab-h);
    min-height: var(--tab-h);
    padding: 0 10px;
    /* 实色面 + 发丝线: 桌面浮动胶囊不能半透明, 否则新闻卡从背后透出来、图标压在卡片文字上成鬼影。
       用 --chrome-surface 与移动端 flat bar 同色, 读起来是一条干净的浮动导航。 */
    background: var(--chrome-surface);
    border: var(--chrome-hairline);
    border-radius: 14px;
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.34);
  }

  body.tab-bar-overlay #page-chat {
    position: relative;
    flex: 1 1 auto;
    min-height: 0;
    top: auto;
    left: auto;
    right: auto;
    bottom: auto;
    height: auto;
    max-height: none;
  }

  /* 桌面 cipher: #page-chat 在 flex 列里塌成内容高度 (~290px, 因 #app-shell height:100% 落回
     body 内容盒), 挂在它底部的春绿渐变于是浮到屏幕中段成一条诡异横亮带。手机端 #page-chat 是
     position:fixed 占满全高, 绿光正好落在底部输入框处, 故只桌面犯病。修法: 桌面让 #page-chat
     自身透明, 把同款绿光固定钉到视口底部 (z:50 层, 落在 z:120 输入框 / z:300 底栏之下),
     观感与手机端一致, 且不动任何高度/布局 (避免裁切静态底栏)。 */
  body.in-chat.tab-bar-overlay #page-chat {
    background: transparent;
    /* 桌面无 iOS 键盘, 去掉 translateZ — 否则 transform 会让下面 position:fixed 的绿光
       以 #page-chat (塌短的 290px) 为包含块, bottom:0 仍落在屏幕中段。 */
    transform: none;
  }
  body.in-chat.tab-bar-overlay #page-chat::before {
    content: "";
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    height: min(40vh, 380px);
    /* 与移动端 C 均匀线性同款: 透明 → 中绿 #0c5f36 的均匀斜坡, 去霓虹尾。 */
    background: linear-gradient(180deg,
      rgba(0, 0, 0, 0) 0%,
      #0c5f36 100%);
    pointer-events: none;
  }

  body.tab-bar-overlay #page-calendar {
    position: relative;
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    top: auto;
    left: auto;
    right: auto;
    bottom: auto;
    height: auto;
    max-height: none;
  }

  body.tab-bar-overlay #page-calendar.active {
    display: flex;
    flex-direction: column;
  }

  body.tab-bar-overlay #page-calendar .calendar-wrap {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
  }
}
