/* ===== 应用骨架：桌面左侧栏 / 手机底部标签栏 ===== */
.app {
  display: grid;
  grid-template-columns: var(--sidebar-w) 1fr;
  min-height: 100vh;
  min-height: 100dvh;
}

/**
 * 气泡视图要"铺满"，所以骨架高度必须**锁死**在视口高度上。
 *
 * 为什么非锁不可：`.app` 用 `min-height` 时，行高是 auto、由内容撑开，
 * `.main` 只有 `flex: 1` 而父级没有确定高度，于是子元素 `flex: 1` 拿不到
 * "剩余高度"，画布被压成一条（实测 1360×320 —— 宽度对了、高度没撑开）。
 * 锁成固定高度后：content(flex:1) → bubble-wrap(flex:1) → stage(flex:1)
 * 这条链才有"剩余空间"可分。内容超出时由 .content 自己滚动。
 */
.app.view-bubble {
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
}
.app.view-bubble .main { height: 100%; min-height: 0; overflow: hidden; }
/* 气泡视图里侧边栏自己滚（主区不动） */
.app.view-bubble .sidebar { position: static; height: 100%; overflow-y: auto; }

/* --- 侧边栏（桌面） --- */
.sidebar {
  display: flex; flex-direction: column; gap: 8px;
  padding: 16px 12px;
  border-right: 1px solid var(--line);
  background: var(--surface);
  position: sticky; top: 0; height: 100vh; height: 100dvh;
  /* 收起/展开是动画 grid 列宽（见文件末尾），这里不能写死宽度，
     否则侧栏不会跟着列变宽 —— 会露出空白或被裁掉。 */
  min-width: 0;
}
.brand { display: flex; align-items: center; gap: 10px; padding: 4px 6px 14px; }
.brand-mark {
  width: 32px; height: 32px; border-radius: 9px; flex: none;
  display: grid; place-items: center;
  background: var(--brand); color: #fff; font-weight: 700;
}
.brand-text { display: flex; flex-direction: column; line-height: 1.15; }
.brand-text b { font-size: calc(15px * var(--fs)); }
.brand-text small { font-size: calc(11px * var(--fs)); color: var(--muted); letter-spacing: .04em; }

.nav { display: flex; flex-direction: column; gap: 2px; flex: 1; }
.nav-item {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 11px; border-radius: var(--r-sm);
  border: 0; background: transparent; text-align: left; cursor: pointer;
  color: var(--text);
}
.nav-item:hover { background: var(--surface-2); }
.nav-item[aria-current="true"] { background: var(--brand-soft); color: var(--brand); font-weight: 600; }
.nav-item .nav-ico { width: 18px; text-align: center; font-size: calc(15px * var(--fs)); }

/*
 * 「换个看法」小列表（左栏第一项再点一次弹出来的那个）。
 * 三行 = 三种看法，当前那行打 ✓ 并高亮。
 */
.face-menu { display: flex; flex-direction: column; gap: 6px; }
.face-row {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 10px 12px; border-radius: var(--r-md); cursor: pointer; text-align: left;
  border: 1px solid var(--line); background: var(--surface); color: var(--text);
}
.face-row:hover { background: var(--surface-2); }
.face-row.is-on { border-color: var(--brand); background: var(--brand-soft); }
.face-row .face-ico { width: 20px; text-align: center; font-size: calc(16px * var(--fs)); flex: none; }
.face-row .face-text { display: flex; flex-direction: column; gap: 1px; flex: 1; min-width: 0; }
.face-row .face-name { font-size: calc(14px * var(--fs)); font-weight: 600; }
.face-row .face-desc { font-size: calc(11.5px * var(--fs)); color: var(--muted); }
.face-row .face-check { color: var(--brand); font-weight: 700; flex: none; }
.sidebar-foot { display: flex; flex-direction: column; gap: 8px; padding-top: 10px; border-top: 1px solid var(--line); }
.sidebar-foot .tiny { padding: 0 4px; }

/*
 * 侧栏展开/收起按钮。
 * 收起时它是一个 62px 宽的小条（只显示 »），点一下钉住展开（« 收起）。
 * 平板/触屏没有 hover，这是**唯一**能展开侧栏的入口。
 */
.sidebar-toggle {
  margin-top: 8px; padding: 6px 0; width: 100%;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--surface-2); color: var(--muted);
  font-size: calc(14px * var(--fs)); line-height: 1; cursor: pointer;
}
.sidebar-toggle:hover { color: var(--text); border-color: var(--brand); }

/* ===== 侧栏收起 / 悬停展开（用户要求：平时只留图标，鼠标移过去才展开文字）=====
   为什么要这样：气泡区要"铺满"，而侧栏平时白占 210px。

   ⚠️ 实现方式：**动画 grid 的列宽**，不要把侧栏改成 position:absolute。
      我第一版用了 absolute + 覆盖，结果 `.main` 被压成 62px 宽 ——
      因为侧栏脱离文档流后仍占着 grid 的第一列，主区被挤进那一列的宽度里，
      月历整个塌成一条（实测 month-grid 只有 0px 宽）。
      改成列宽过渡后，主区宽度跟着变，排版始终正确。

   · 收起 62px（放得下 18px 图标）；悬停/键盘聚焦时滑到 210px。
   · 文字用 opacity 过渡，不是 display:none —— 宽度动画时不会抖。 */
/*
 * ⚠️ 门槛从 900px 降到 **800px**（为了平板竖屏）。
 *
 * 实测常见平板的 WebView CSS 宽度：
 *   · 10.1" 1920x1200 @xhdpi(2.0) **横屏** → 960px
 *   · 11"   2560x1600 @xhdpi(2.0) **竖屏** → 800px   ← 正好卡在 900 门槛之下
 *   · 10.1" 1920x1200 @xhdpi(2.0) 竖屏     → 600px
 * 落在 800 的平板**本来该用侧栏**（双手持握时侧边比底部好够，而且侧栏收起只占 62px，
 * 比底部标签栏省下的横向空间更值），却因为 <900 被当成手机走了底部标签栏。
 *
 * 降到 800 之后：≥800 一律"侧栏（可收起）+ 无底部标签栏"，
 * <800 才是真正的手机档。600px 的平板竖屏会落到手机档 —— 这个可以接受
 * （600px 上侧栏确实太挤）。
 */
@media (min-width: 800px) {
  .app { transition: grid-template-columns 180ms ease; grid-template-columns: var(--sidebar-w-collapsed) 1fr; }
  .app:has(.sidebar:hover), .app:has(.sidebar:focus-within) { grid-template-columns: var(--sidebar-w-expanded) 1fr; }
  /*
   * 触屏（平板/触屏笔记本）没有 hover —— 靠 hover 展开等于**展不开**。
   * 所以加一个"点一下钉住"的状态：`.sidebar.is-open`。
   * 用 `:not(.is-dropzone)` 避免和拖拽投放态打架。
   */
  .app:has(.sidebar.is-open:not(.is-dropzone)) { grid-template-columns: var(--sidebar-w-expanded) 1fr; }
  /* 拖动气泡时，**强制展开**左侧栏 —— 投放区要盖在它上面，宽度必须一致。 */
  .app:has(.sidebar.is-dropzone) { grid-template-columns: var(--sidebar-w-expanded) 1fr; }
  .sidebar { width: auto; }
  /* 收起时文字既要"看不见"也要"不占宽度"。
     ⚠️ 只写 opacity:0 是不够的 —— 文字仍然占着 28px（标签）甚至把品牌名
        挤出左边（实测 brandMark 左边缘 = -16px，整个侧栏图标歪着）。
        所以收起时把宽度也收成 0；展开时再放开，配合宽度过渡一起滑出来。 */
  .sidebar .brand-text,
  .sidebar .nav-label,
  .sidebar .sidebar-foot .tiny {
    opacity: 0; width: 0; overflow: hidden;
    transition: opacity 120ms ease, width 180ms ease;
    white-space: nowrap;
  }
  .sidebar:hover .brand-text,
  .sidebar:hover .nav-label,
  .sidebar:hover .sidebar-foot .tiny,
  .sidebar:focus-within .brand-text,
  .sidebar:focus-within .nav-label,
  .sidebar:focus-within .sidebar-foot .tiny,
  .sidebar.is-open .brand-text,
  .sidebar.is-open .nav-label,
  .sidebar.is-open .sidebar-foot .tiny,
  .sidebar.is-dropzone .brand-text,
  .sidebar.is-dropzone .nav-label,
  .sidebar.is-dropzone .sidebar-foot .tiny {
    opacity: 1; width: auto; overflow: visible;
  }
  /* 拖动时投放区会盖住整条侧栏 —— 把侧栏自己的内容藏掉，
     这样视觉上就是"左侧栏变成了投放区"，而不是两层叠着。 */
  .sidebar.is-dropzone > * { visibility: hidden; }

  /* ---- 收起时的对齐：所有图标走同一个 32px 格子，整体居中 ---- */
  /* ⚠️ 这一串 `:not(...)` 必须**带上 `:not(.is-open)`**：
     点到"钉住展开"之后鼠标移开（触屏上更是必然），如果不排除 `.is-open`，
     就会出现"列宽 210px、内容却按收起态居中"的半吊子样子（图标居中、内边距 0）。
     平板档删掉"默认展开"之后（见下面那段），这个钉住态就是平板唯一的展开入口，
     所以必须看起来跟真展开一模一样。 */
  .sidebar:not(:hover):not(:focus-within):not(.is-dropzone):not(.is-open) { padding-left: 0; padding-right: 0; }
  .sidebar:not(:hover):not(:focus-within):not(.is-dropzone):not(.is-open) .brand { justify-content: center; padding: 4px 0 14px; }
  .sidebar:not(:hover):not(:focus-within):not(.is-dropzone):not(.is-open) .nav-item {
    justify-content: center; padding-left: 0; padding-right: 0; min-height: 38px;
  }
  .sidebar:not(:hover):not(:focus-within):not(.is-dropzone):not(.is-open) .sidebar-foot { align-items: center; }
  /* 图标格子：不管字形宽窄，都占同样的宽度，视觉中线才对得齐 */
  .nav-item .nav-ico { width: 20px; flex: none; line-height: 1; }
  .brand-mark { width: 32px; height: 32px; }
}

/* ===== 「平板档」（800–1080px）：默认展开，**但苹果平板不** =====
 *
 * 一段两分支，先说 10-13 那一刀（候选 #7「平板档侧栏默认展开」）：
 *   · 这一档（11" 竖屏 800、iPad 竖屏 820、10.1" 横屏 960、11" 横屏 1024）**基本没有 hover**。
 *     上面那套"悬停才滑出文字"在触屏上等于**永远收着** —— 实测侧栏 `62×1000`、导航项 `61×39`、
 *     收起按钮 `61×28`、导航文字 `width: 0`：用户看到一排图标，不知道哪个是哪个。
 *   · 所以这一档把默认值调过来：**文字首屏就在**，想省宽度的人点底部按钮收起 ——
 *     选择记在本机（`timetable.sidebar`）。
 *
 * 10-14 用户口径：「内测版把默认展开左侧栏删了，我有 pencil pro —— 内测版只改苹果平板端」：
 *   · **iPad**（`<html data-device="ipad">`，由 `web/ui/theme-boot.js` 在第一帧之前打好）
 *     不用这套 —— 他有 Pencil Pro，指点比手指准，不需要替他把 210px 的侧栏预先摊开
 *     （820/1024 的屏上那就是白吃掉四分之一宽）。iPad 上默认收起，跟桌面同一套语义：
 *     "点一下钉住展开"（`.sidebar.is-open`）。
 *   · 其余平板档设备（安卓平板、触屏笔记本）**一个字都不动**，还是默认展开 + 可收起。
 *   · 实现方式：下面每条"默认展开"的规则前面都挂 `html:not([data-device="ipad"])`；
 *     触控目标那两条不挂（手指/笔要点的东西 ≥44px，跟默认状态无关）。
 *
 * ⚠️ 不新增断点宽度：上限直接用内容围栏那条 1080（`web/css/base.css` 的 `--content-max`），
 *    于是"< 1080 = 围栏还没生效 = 平板档"说的是同一件事（断点清单见审计文档 §1）。
 * ⚠️ 收起态那条对齐规则用的是 `:not(:hover):not(:focus-within):not(.is-dropzone):not(.is-open)`，
 *    特异性比裸选择器高得多（0,6,0）—— 所以下面那条要改高度的规则必须把**同样长的链**写一遍，
 *    只写 `.sidebar .nav-item` 会被它盖掉（真踩过：44px 写在前面，实测仍是 38px）。
 */
@media (min-width: 800px) and (max-width: 1080px) {
  /* ---- 默认展开（iPad 除外）---- */
  html:not([data-device="ipad"]) .app { grid-template-columns: var(--sidebar-w-expanded) 1fr; }
  html:not([data-device="ipad"]) .app:has(.sidebar.is-collapsed:not(.is-dropzone)) {
    grid-template-columns: var(--sidebar-w-collapsed) 1fr;
  }

  html:not([data-device="ipad"]) .sidebar:not(.is-collapsed) .brand-text,
  html:not([data-device="ipad"]) .sidebar:not(.is-collapsed) .nav-label,
  html:not([data-device="ipad"]) .sidebar:not(.is-collapsed) .sidebar-foot .tiny {
    opacity: 1; width: auto; overflow: visible;
  }

  /* 展开态的对齐：左对齐 + 常规内边距（那条收起态规则把 padding 归零了） */
  html:not([data-device="ipad"]) .sidebar:not(.is-collapsed):not(:hover):not(:focus-within):not(.is-dropzone) {
    padding-left: 12px; padding-right: 12px;
  }
  html:not([data-device="ipad"]) .sidebar:not(.is-collapsed):not(:hover):not(:focus-within):not(.is-dropzone) .brand {
    justify-content: flex-start; padding: 4px 6px 14px;
  }
  html:not([data-device="ipad"]) .sidebar:not(.is-collapsed):not(:hover):not(:focus-within):not(.is-dropzone) .sidebar-foot {
    align-items: stretch;
  }
  html:not([data-device="ipad"]) .sidebar:not(.is-collapsed):not(:hover):not(:focus-within):not(.is-dropzone) .nav-item {
    justify-content: flex-start; padding-left: 11px; padding-right: 11px; min-height: 44px;
  }

  /* ---- 触控目标（两种设备都要）：手指/笔在点 ---- */
  .sidebar:not(:hover):not(:focus-within):not(.is-dropzone):not(.is-open) .nav-item {
    justify-content: center; padding-left: 0; padding-right: 0; min-height: 44px;
  }
  .sidebar .nav-item { min-height: 44px; }
  /* 展开/收起按钮：原来只有 28px 高（`padding: 6px 0`），手指够不着 */
  .sidebar-toggle { min-height: 44px; padding: 12px 0; }
}

/* ===== 投放区 = **盖住左侧栏**的一块区（用户要求）=====
   "左侧栏变成投放区"，而不是旁边多一块。
   定位用 position: fixed + 视口高度 —— 侧栏本身是 sticky 且高 100dvh，
   落在视口左侧整条；用 fixed 才能和它严丝合缝地重合。
   （之前用 absolute、left:-210px：left 对了，但 top/bottom 被所在的
     `.bubble-stage` 撑住，高度只有 695 而侧栏是 805，上下差一截。）
   底色 = **目的地那一层**的颜色；由 bubble.js 写入 --drop-color 并负责显隐。 */
.bubble-dropzone {
  position: fixed; z-index: 30;
  left: 0; top: 0; height: 100dvh;
  width: var(--sidebar-w-expanded);
  display: flex; flex-direction: column; justify-content: center; gap: 10px;
  padding: 18px 12px;
  border: 2px dashed color-mix(in srgb, var(--drop-color, var(--brand)) 60%, transparent);
  border-radius: var(--r-lg);
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--drop-color, var(--surface)) 34%, var(--surface)),
      color-mix(in srgb, var(--drop-color, var(--surface)) 16%, var(--surface)));
  backdrop-filter: blur(3px);
  pointer-events: none;              /* 命中判定用坐标算，不抢指针事件 */
  animation: dzIn 150ms ease-out both;
  text-align: center;
}
.bubble-dropzone[hidden] { display: none; }
@keyframes dzIn { from { opacity: 0; transform: translateX(-12px); } }
.bdz-head { font-size: calc(15px * var(--fs)); font-weight: 700; color: var(--text); }
.bdz-desc { font-size: calc(12.5px * var(--fs)); line-height: 1.5; color: var(--text); opacity: .85; }
.bdz-color {
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  margin-top: 4px; font-size: calc(11.5px * var(--fs)); color: var(--muted);
}
.bdz-color i {
  width: 26px; height: 26px; border-radius: 50%; display: block;
  border: 2px solid color-mix(in srgb, var(--line) 70%, transparent);
  box-shadow: 0 1px 6px -1px rgba(0, 0, 0, .25);
}

/* --- 主区 --- */
.main { display: flex; flex-direction: column; min-width: 0; }

/* 顶栏、横幅、内容区共用同一条"内容列"内边距（`--pad-inline`，见 base.css）。
   这样宽屏上正文不会一路拉到头，而顶栏的底边线 / 横幅底色仍然通铺整窗。 */
.topbar {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px var(--pad-inline);
  background: color-mix(in srgb, var(--bg) 86%, transparent);
  backdrop-filter: saturate(1.4) blur(10px);
  border-bottom: 1px solid var(--line);
}
.topbar-left, .topbar-right { display: flex; align-items: center; gap: 10px; min-width: 0; }
.title-block { min-width: 0; }
.topbar h1 { font-size: calc(18px * var(--fs)); font-weight: 650; white-space: nowrap; }
.subtitle { font-size: calc(12px * var(--fs)); color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.period-nav { display: flex; align-items: center; gap: 2px; }
.period-nav .today-btn { font-size: calc(13px * var(--fs)); }

/* 内容列（宽屏上限 = `--content-max`，窄屏退化成 20px 边距）。
   ⚠️ 气泡视图那条 `.app.view-bubble .content { padding: 0 }` 必须留在后面 —— 画布要铺满。 */
.content { flex: 1; min-width: 0; padding: 16px var(--pad-inline) 32px; outline: none; }

/* --- 提示条 --- */
.banner-host { display: flex; flex-direction: column; gap: 8px; padding: 0 var(--pad-inline); }
.banner-host:not(:empty) { padding-top: 12px; }

/* 气泡视图：顶栏和标题都撤掉，垂直空间全给气泡，并且**铺满右侧**。
   说明和设置都收进了 HUD 上的 ? 面板，所以这里不需要标题、副标题，
   也不需要右上角的「新建日程」（新建改成单击空白背景）。 */
.app.view-bubble .topbar {
  padding: 0;
  border-bottom: 0;
  background: transparent;
  backdrop-filter: none;
  min-height: 0;
}
.app.view-bubble .topbar .title-block { display: none; }
.app.view-bubble .topbar .new-event-btn { display: none; }
.app.view-bubble .period-nav { display: none; }
.app.view-bubble .banner-host:empty { display: none; }
/* 铺满：去掉内容区四周内边距，让气泡区贴到窗口边缘 */
.app.view-bubble .content {
  padding: 0;
  overflow: hidden;
  /* ⚠️ 必须是 flex 容器：.content 默认是 display:block，
     里面 .bubble-wrap 的 `flex: 1` 无从谈起，高度只会等于内容高度
     （实测 stage 卡在 240px = min-height，而 .content 有 697px 没用上）。 */
  display: flex;
  flex-direction: column;
}
/* 手机端顶栏只留左上角的菜单按钮（否则没法切视图） */
.app.view-bubble .topbar-left { min-height: 44px; align-items: center; }
.banner {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 10px 14px; border-radius: var(--r-md);
  border: 1px solid var(--line); background: var(--surface); box-shadow: var(--shadow);
  font-size: calc(13px * var(--fs));
}
.banner b { font-weight: 600; }
.banner .spacer { flex: 1; }
.banner.warn { border-color: color-mix(in srgb, var(--warn) 45%, var(--line)); background: color-mix(in srgb, var(--warn) 8%, var(--surface)); }
.banner.info { border-color: color-mix(in srgb, var(--brand) 40%, var(--line)); background: var(--brand-soft); }

/* --- 底部标签栏（手机） --- */
.tabbar { display: none; }

@media (max-width: 799px) {
  .app { grid-template-columns: 1fr; }
  .sidebar { display: none; }
  /* 顶栏的纵向内边距与 ≥800px 那套**统一成 12px**：原来是 10px，于是跨过 800px
     门槛时顶栏会从 66 长到 70（"端分离"线候选 #10 量到的 4px 跳变）。横向仍是
     写死的 14px —— 它和 `--pad-inline` 的下界是同一个值（见 base.css 那条注释）。 */
  .topbar { padding: 12px 14px; }
  .content { padding: 12px 14px calc(var(--tabbar-h) + 20px + env(safe-area-inset-bottom)); }
  .banner-host { padding: 0 14px; }

  .tabbar {
    /* ⚠️ 轨道**必须**是 `minmax(0, 1fr)`，不能写 `1fr`（2026-10-14 修的那个 bug）：
       `1fr` 的下限是 min-content —— 下面那条 44px 的 `min-width` 会被算进下限，
       于是 8 项 × 44px = 352px 变成一条**装不下的地板价**，320px 的手机上最后一格
       「更多 ⋯」被推出视口（实测：只剩 12px 空白窄条、没文字没图标、几何中心点不到）。
       而校内那一页的入口就在那一格里 ⇒ 用户看到的现象是"手机上找不到入口"。 */
    display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr);
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
    height: calc(var(--tabbar-h) + env(safe-area-inset-bottom));
    padding-bottom: env(safe-area-inset-bottom);
    background: color-mix(in srgb, var(--surface) 94%, transparent);
    backdrop-filter: blur(12px);
    border-top: 1px solid var(--line);
  }
  .tabbar .nav-item {
    flex-direction: column; gap: 2px; justify-content: center;
    border-radius: 0; font-size: calc(11px * var(--fs)); padding: 6px 0;
    /* 触控目标下限（用户 2026-10-13："手机版排版要更用心"）：
       414px 下 8 项 × 51.75px、高 57px，本来就够；给 360px 那种窄屏兜底。
       ⚠️ 这里**故意只写 min-height，不写 min-width**（2026-10-14 改）：宽度上的
       44px 地板价会变成"8 项 × 44px = 352px"这种**装不下就挤掉最后一项**的硬约束
       （320px 的屏幕实测：最后一格「更多 ⋯」只剩 12px 空白窄条、点不到，
       而校内那一页的入口就在那一格里）。宽度交给轨道均分：≥352px 时每项自然 ≥44px，
       320px 时压到 40px —— 窄 4px，换"每一项都在屏幕里、都点得到"。
       那条 44px 的线现在由 `tools/phone-tabbar.test.mjs` 在真浏览器里量着（360/414 必须 ≥44）。 */
    min-height: 44px;
  }
  .tabbar .nav-item .nav-ico { font-size: calc(17px * var(--fs)); width: auto; }
  /* 标签**只占一行**：宽屏下每个标签本来就装得下；到 320px 那种一格 40px 的宽度，
     两个以上的字会被挤成两行、第二行被标签栏切掉 1.8px（2026-10-14 实测）。
     宁可截断加省略号，也不要"露一半的字"。 */
  .tabbar .nav-item .nav-label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
  .tabbar .nav-item[aria-current="true"] { background: transparent; color: var(--brand); }
}

/* --- 触控目标：视觉尺寸不动，只把"可点区域"补到手指够得着 --- */
/* 为什么用伪元素而不是加 padding：加 padding 会把 34px 的方钮撑成 44px，
   顶栏会跟着长高、图标位置全变 —— 用户认的是现在这个视觉，改的应该只是命中区。
   为什么只补到 44 而不是更大：再大就会盖到旁边的可点元素上（顶栏按钮之间只有 6px）。 */
.icon-btn { position: relative; }
.icon-btn::after { content: ''; position: absolute; inset: -5px; border-radius: inherit; }
