/* ═══════════════════════════════════════════
 *  布局（栅格、头部、侧栏、主区域）
 * ═══════════════════════════════════════════ */

/* ── Layout ──────────────────────────────── */
/* iOS Safari 修复：
   - height:100vh 取的是 URL 栏最小时的高度，URL 栏展开时 #app 比可见区域更高，底部输入框被遮
   - 100dvh（dynamic viewport height）会动态匹配可见高度，iOS 15.4+ / Android Chrome 108+ 支持
   - 老浏览器读不懂 100dvh 这一行会被忽略，继续用上面的 100vh 兜底
   - input.js 里另有 VisualViewport 监听，弹出键盘时进一步动态设置 #app.style.height
   - "announcement" 行使用 auto 高度：announcement-bar[hidden] 时该行自动收为 0,
     有公告时按内容（约 32px）自适应展开（详见 §8.19） */
#app { display:grid; grid-template-rows:var(--header-h) auto 1fr; grid-template-columns:var(--sidebar-w) 1fr; grid-template-areas:"header header" "announcement announcement" "sidebar main"; height:100vh; height:100dvh }

/* ── Header ──────────────────────────────── */
#header { grid-area:header; display:flex; align-items:center; padding:0 20px; background:var(--bg2); border-bottom:1px solid var(--border); z-index:100; gap:16px }
.header-logo { display:flex; align-items:center; gap:10px; text-decoration:none; color:var(--text) }
.logo-icon { width:32px; height:32px; flex-shrink:0; display:block; }
.logo-text { font-size:14px; font-weight:700; background:linear-gradient(135deg,var(--accent),var(--accent2)); -webkit-background-clip:text; -webkit-text-fill-color:transparent; white-space:nowrap; }
.header-right { display:flex; align-items:center; gap:10px; margin-left:auto }
.btn-ghost { padding:6px 14px; background:transparent; border:1px solid var(--border); border-radius:20px; color:var(--text-muted); font-size:13px; cursor:pointer; font-family:var(--sans); transition:all var(--trans); white-space:nowrap }
.btn-ghost:hover { border-color:var(--accent); color:var(--accent) }
#menu-toggle { display:none; background:none; border:none; cursor:pointer; padding:4px; color:var(--text); font-size:22px; line-height:1 }

/* ── Announcement Bar(顶部公告条:固定 + 滚动)─────────
   - grid-area:announcement -> 独占栅格的第二行
   - 内部 DOM 由 announcements.js 动态填充,典型结构:
       <div id="announcement-bar" class="announcement-bar has-pinned has-scroll">
         <div class="ann-pinned-slot ann-style-promo">📌 ...固定公告... </div>
         <div class="ann-viewport"><span class="ann-dot"></span><div class="ann-track"><span class="ann-item">...</span></div></div>
       </div>
   - 没有 pinned: 只有 ann-viewport 全宽
   - 没有 scroll: 只有 ann-pinned-slot 全宽
   - 两者都没: bar.hidden=true, grid auto 行自动收为 0 高
   - 详见 §8.19 */
.announcement-bar { grid-area:announcement; display:flex; align-items:stretch; gap:0; padding:0; min-height:30px; /* fallback for old browsers */ background:linear-gradient(90deg, rgba(110,231,183,0.07), rgba(129,140,248,0.07)); /* 2026-05-24 改用 color-mix,跟着 palette 走 */ background:linear-gradient(90deg, color-mix(in srgb, var(--accent) 9%, transparent), color-mix(in srgb, var(--accent2) 9%, transparent)); border-bottom:1px solid var(--border); font-size:12.5px; overflow:hidden; }
.announcement-bar[hidden] { display:none; }

/* ── 左侧固定公告 pill ──────────────────────────────
   总是可见 / 不滚动,作为"明显广告位"(滚动看不到的用户也能看到)
   max-width 防止它吃掉整个公告条 */
.ann-pinned-slot {
  display:flex;
  align-items:center;
  gap:6px;
  padding:5px 14px 5px 16px;
  max-width:42%;
  flex-shrink:0;
  font-weight:600;
  color:var(--accent);
  border-right:1px solid var(--border);
  cursor:default;
  transition:background var(--trans), opacity var(--trans);
  white-space:nowrap;
  overflow:hidden;
}
/* 仅滚动区存在时才需要分隔线; 单独固定公告时去掉 */
.announcement-bar:not(.has-scroll) .ann-pinned-slot { border-right:none; max-width:100%; flex:1; }
.ann-pinned-slot[data-clickable="1"] { cursor:pointer; }
.ann-pinned-slot[data-clickable="1"]:hover { background:rgba(255,255,255,0.04); }
[data-theme="light"] .ann-pinned-slot[data-clickable="1"]:hover { background:rgba(0,0,0,0.03); }
.ann-pinned-icon { flex-shrink:0; font-size:14px; line-height:1; }
.ann-pinned-text { overflow:hidden; text-overflow:ellipsis; line-height:1.4; }

/* ── 右侧滚动 viewport ──────────────────────────────
   消息从 left:100% 起点开始, JS 算位移注入 --ann-dist, CSS animation 平移 */
.ann-viewport { flex:1; position:relative; overflow:hidden; height:30px; display:flex; align-items:center; padding:0 14px; gap:10px; }
.ann-dot { width:6px; height:6px; background:var(--accent); border-radius:50%; animation:pulse 2s infinite; flex-shrink:0 }
.dot { width:6px; height:6px; background:var(--accent); border-radius:50%; animation:pulse 2s infinite; flex-shrink:0 }
@keyframes pulse { 0%,100%{opacity:1;transform:scale(1)} 50%{opacity:.5;transform:scale(1.4)} }
.ann-track { position:absolute; left:32px; right:0; top:0; bottom:0; height:100%; pointer-events:none; }
.ann-item {
  position:absolute;
  left:100%;                      /* 起点:紧贴 viewport 右边缘外 */
  /* 2026-05-24 重写居中方式 v2: 之前用 top:50% + transform:translateY(-50%) 在
   * 部分移动端浏览器算不准 (inline -> inline-block 的高度估计有偏差),导致文字垂直
   * 偏离中线、上半 / 下半被 overflow:hidden 裁掉。改成:
   *   - top:0; bottom:0 + height:100%  -> 三重保险确保 item 高度 = track 高度
   *     (单独的 top:0/bottom:0 在 iOS Safari 嵌套 flex 容器内偶发算不准)
   *   - display:flex; align-items:center -> 让 flex 帮文字垂直居中
   * 既不依赖元素自身高度的隐式计算,也不依赖 transform 的百分比。
   */
  top:0;
  bottom:0;
  height:100%;
  display:flex;
  align-items:center;
  white-space:nowrap;
  pointer-events:auto;
  color:var(--accent);
  font-weight:500;
  cursor:default;
  line-height:1.4;
  transition:opacity var(--trans);
}
.ann-item[data-clickable="1"] { cursor:pointer; }
.ann-item[data-clickable="1"]:hover { opacity:0.78; text-decoration:underline; text-underline-offset:3px; }

/* 风格变体(与 announcements.style 字段对应)
   注意 .ann-pinned-slot 和 .ann-item 共用,所以选择器写宽一点 */
.ann-style-info,   .ann-style-info   .ann-pinned-icon { color:var(--accent); }
.ann-style-promo,  .ann-style-promo  .ann-pinned-icon { color:var(--accent2); }
.ann-style-warn,   .ann-style-warn   .ann-pinned-icon { color:#fbbf24; }
.ann-style-danger, .ann-style-danger .ann-pinned-icon { color:var(--danger); }

/* 滚动动画:从初始位置(紧贴 viewport 右边外)一路平移到完全滑出左侧
   位移 = container 宽 + item 宽 + 留白, 由 JS 通过 --ann-dist 注入
   2026-05-24: 因为居中改成 flex(无 Y 偏移),keyframes 只动 X 不动 Y */
@keyframes ann-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(calc(-1 * var(--ann-dist, 400px))); }
}

/* ── Sidebar ─────────────────────────────── */
#sidebar { grid-area:sidebar; background:var(--bg2); border-right:1px solid var(--border); overflow-y:auto; display:flex; flex-direction:column }
.sidebar-section { padding:8px 0 }
.sidebar-label { padding:14px 16px 6px; font-size:11px; text-transform:uppercase; letter-spacing:1.5px; color:#9ca3af; font-weight:600; display:flex; align-items:center; gap:6px; }
.sidebar-label::before { content:''; flex-shrink:0; width:3px; height:11px; background:linear-gradient(180deg, var(--accent), var(--accent2)); border-radius:2px; }

/* 可折叠分组 */
.nav-group-header { padding:14px 16px 6px; font-size:11px; text-transform:uppercase; letter-spacing:1.5px; color:#9ca3af; font-weight:600; display:flex; align-items:center; gap:6px; cursor:pointer; user-select:none; transition:color var(--trans); }
.nav-group-header:hover { color: var(--text); }
.nav-group-header::before { content:''; flex-shrink:0; width:3px; height:11px; background:linear-gradient(180deg, var(--accent), var(--accent2)); border-radius:2px; }
.nav-group-header .arrow-down { margin-left:auto; font-size:9px; transition:transform 0.2s; opacity:0.5; }
.nav-group.collapsed .nav-group-header .arrow-down { transform:rotate(-90deg); }
.nav-group-children { overflow:hidden; transition: max-height 0.3s ease; }
.nav-group.collapsed .nav-group-children { max-height: 0 !important; }
.nav-divider { height:1px; background:var(--border); margin:4px 16px }
.nav-item { display:flex; align-items:center; gap:10px; padding:9px 16px; cursor:pointer; transition:all var(--trans); color:#9ca3af; font-size:13.5px; user-select:none; border-left:2px solid transparent }
.nav-item:hover { background:rgba(255,255,255,0.04); color:var(--text) }
.nav-item.active { background:rgba(110,231,183,0.07); color:var(--accent); border-left-color:var(--accent) }
.nav-icon { width:22px; height:22px; display:flex; align-items:center; justify-content:center; font-size:15px; flex-shrink:0 }

/* 加载骨架 */
.nav-skeleton { padding:9px 16px; display:flex; align-items:center; gap:10px }
.skel-icon { width:22px; height:14px; background:var(--border); border-radius:4px }
.skel-text { height:12px; background:var(--border); border-radius:4px; width:70px; animation:shimmer 1.2s infinite }
@keyframes shimmer { 0%,100%{opacity:.4} 50%{opacity:.9} }

/* ── Main ────────────────────────────────── */
#main { grid-area:main; overflow:hidden; position:relative; background:var(--bg); display:flex; flex-direction:column }

/* 页面加载中蒙层 */
#page-loading { display:none; position:absolute; inset:0; align-items:center; justify-content:center; flex-direction:column; gap:14px; background:var(--bg); z-index:10 }
.page-spinner { width:36px; height:36px; border:3px solid var(--border); border-top-color:var(--accent); border-radius:50%; animation:spin .9s linear infinite }
@keyframes spin { to{transform:rotate(360deg)} }

/* iframe 渲染动态页面 */
#page-frame { display:none; width:100%; flex:1; border:none }

/* ── Sidebar overlay (mobile) ── */
/* ── Sidebar overlay (mobile) ── */
#sidebar-overlay { display:none; position:fixed; inset:0; background:rgba(0,0,0,.6); z-index:99 }