/* ==========================================================================
/* finesse · register=product · A=inherited（一个色值都没动） · B=固定刻度
 * ── v58 手感层：八个微交互各指定唯一的家，一个界面最多两拍 ──
 * 3D倾斜光影(网格) · 弹簧交错流(网格) · 流体胶囊(芯片排) · 滚动码表(统计读数)
 * 磁吸游标(主按钮) · 阻尼抽屉(灯箱) · 弥散光晕(聚焦态) · 弹性微缩(全部可点元素)
 * 全部 gate 在"有精细指针 + 没开减少动效"之后：不合格的用户拿到的是**不做**，不是"做得轻" */
/* finesse · register=product · A=inherited（一个色值都没动） · B=固定刻度
 * C=无骨架变更（只动状态之间的过渡） · D=手写 FLIP + CSS 过渡（零依赖、零动画库）
 * E=连续性（东西从哪来、到哪去） · SOUL=6 SPECTACLE=3 DENSITY=8
 *
 * 交互质感层（全站）：四拍 —— 开图共享元素转场 / 网格重排归位 / 按下回应 / 图片淡入。
 * 与 v39 的图标动效层同一条线（反馈与连续性，不是装饰），补的是它没覆盖的状态过渡。
 */
   组件层 · 应用骨架 / 按钮 / 表单 / 卡片 / 弹层 / 提示
   骨架：桌面端左侧固定导航栏，移动端顶部栏 + 底部标签栏。
   ========================================================================== */

.app-root { position: relative; min-height: 100dvh; }

/* ---------------------------------------------------------------- 左侧导航 */
.app-rail {
  display: none;
  position: fixed; inset-block: 0; inset-inline-start: 0; z-index: var(--z-nav);
  width: var(--sidebar-w);
  flex-direction: column;
  background: var(--sidebar);
  border-right: 1px solid var(--sidebar-border);
  color: var(--sidebar-foreground);
}
@media (min-width: 1024px) { .app-rail { display: flex; } }

.app-rail__brand {
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-5) var(--space-4) var(--space-4);
  font-family: var(--font-display);
  font-size: var(--text-base); font-weight: 600; letter-spacing: 0.01em;
}
@media (hover: hover) {
  .app-rail__brand:hover { color: var(--foreground); }
}
.app-rail__mark {
  width: 1.5rem; height: 1.5rem; flex-shrink: 0;
  display: grid; place-items: center;
  border-radius: var(--radius-sm);
  background: var(--primary); color: var(--primary-foreground);
}

.app-rail__nav {
  flex: 1 1 auto; overflow-y: auto;
  padding: var(--space-2) var(--space-3) var(--space-4);
  border-top: 1px solid var(--sidebar-border);
}

.app-nav__item {
  display: flex; align-items: center; gap: var(--space-3);
  padding: 0.4rem var(--space-2);
  border-radius: var(--radius-md);
  font-size: var(--text-sm); font-weight: 500;
  color: var(--muted-foreground);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.app-nav__item svg { flex-shrink: 0; opacity: 0.85; }
@media (hover: hover) {
  .app-nav__item:hover { background: var(--sidebar-accent); color: var(--foreground); }
}
.app-nav__item[aria-current="page"] {
  background: var(--sidebar-accent); color: var(--foreground); font-weight: 600;
}
.app-nav__item[aria-current="page"] svg { color: var(--primary); opacity: 1; }
.app-nav__item .badge { margin-left: auto; }

.app-rail__foot {
  /*
   * 内边距：上下对称，左右也要留够。
   *
   * 上下：原来是 space-2 / space-3（上 8 下 12），四个按钮整体比视觉中心
   * 高 1.5px —— 被指出来了。那条 border-top 算在盒子里，写成对称之后
   * 内容中心离盒子中心只差 0.5px。
   *
   * 左右：这里原来也是 space-3（12px），但**装不下**四个 44px 的按钮 ——
   * 4×44 + 3×2 = 182，而侧栏 199 减去左右各 12 只剩 175。
   * 结果是右边被挤掉，左 12、右 5，整排看着往右偏，
   * 最右边那个（重载）离边缘最近，最像"位置不对"。
   * 改成 space-1：可用 191，装得下，左右各余 4.5，对称。
   *
   * 视觉上一点不挤：每个按钮 44px 宽、图标 22px 居中，
   * 图标离侧栏边缘还有 4.5 + 11 = 15.5px。
   */
  padding: var(--space-2) var(--space-1);
  border-top: 1px solid var(--sidebar-border);
  /* 设置 · 主题 · 跳转 · 重载，四个图标挨在一起 */
  display: flex; align-items: center; justify-content: center; gap: 2px;
}

/*
 * 工具按钮：设置 / 主题 / 跳转。
 *
 * 电脑在侧栏底部，手机在顶栏 —— **同一套类名，同一套尺寸**。
 *
 * 这是这一块返工的原因：两边各写一遍，尺寸就飘了
 * （电脑 32×32、手机 30×30、再后来又各是各的间隔），
 * 用户一眼看出来："主题的标识大小间隔都不一样，位置也不对。"
 * 现在只有一个定义，两边不可能不一致。
 *
 * 44×44 而不是 32×32：这个尺寸在手指上也够用，于是电脑和手机可以
 * 真的用同一个值，而不是"看着差不多、量起来不一样"。
 */
.app-tool-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.75rem; height: 2.75rem; flex: 0 0 auto;
  border-radius: var(--radius-md);
  color: var(--muted-foreground);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
@media (hover: hover) {
  .app-tool-btn:hover { background: var(--sidebar-accent); color: var(--foreground); }
}
.app-tool-btn[aria-current='page'] { background: var(--sidebar-accent); color: var(--foreground); }
.app-tool-btn svg { width: 18px; height: 18px; }

/* ---------------------------------------------------------------- 移动端顶栏 */
.app-header {
  position: sticky; top: 0; z-index: var(--z-nav);
  /*
   * 顶部安全区。定义在 :root 上而不是直接写在这里 ——
   * boot 时会用 JS 复查一次（见 main.js 的 applySafeAreaFallback）：
   * iOS 上从 Chrome 加到主屏幕的独立窗口，env() 经常恒为 0，
   * 那时 20px 的下限根本不够（iPhone 的状态栏是 44~59），
   * JS 量出来之后会用行内样式覆盖这个变量。
   */
  padding-top: var(--safe-top);
  /*
   * 高度要**加上顶部安全区**，不能只写死 --header-h。
   * --safe-top 定义在 tokens.css 的 :root 上（boot 时 JS 会复查）。
   *
   * index.html 里有 viewport-fit=cover（为了让底部安全区也能自己处理），
   * 代价是页面会延伸到状态栏底下 —— 而 position: sticky; top: 0 的顶栏
   * 正好贴在最上面，于是"影视档案馆"那几个字被手机的时间/信号栏压住。
   *
   * 顶部安全区：**取 env() 和一个下限里更大的那个**。
   *
   * 为什么不能只写 env()：
   * env(safe-area-inset-top) 在不少情况下恒为 0 ——
   * 旧一点的 WebView、部分安卓内核、或者 viewport-fit 没吃到的场合。
   * 那时顶栏会直接顶到状态栏底下，字被时间/信号/电量盖住，用户的原话
   * 是"上面那个地方还是被盖住"。
   *
   * max() 保证了两件事：
   *   · 支持 env() 且有刘海 → 用真实值（47 / 59）
   *   · 不支持或报 0        → 用 20px 下限，绝不会贴到 0
   * 多出来的那 20px 只在没有安全区的窄屏上出现，视觉上就是顶栏略高一点，
   * 比"被系统状态栏盖住"好得多。
   */
  min-height: calc(var(--header-h) + var(--safe-top));
  /*
   * 左右也要让。横屏时刘海在**左边**（iPhone 转过来之后），
   * 竖屏时它是顶部 —— 只处理 top/bottom 的话，横屏下
   * 品牌名正好压在刘海里，而且系统不让你点它盖住的地方。
   */
  padding-left: max(var(--space-4), env(safe-area-inset-left, 0px));
  padding-right: max(var(--space-4), env(safe-area-inset-right, 0px));
  display: flex; align-items: center; gap: var(--space-2);
  padding-inline: var(--space-4);
  /*
   * 顶栏必须是**不透明**的。
   *
   * 原来这里是 86% 半透明 + backdrop-filter: blur(16px) 的磨砂玻璃。
   * 桌面端看着还行（顶栏只在窄屏出现），但在手机上很糟：
   * 这一条盖住的是**从屏幕最顶端开始**的整块区域（含状态栏那一段），
   * 于是底下的内容被糊成一片，用户的原话是
   * "上面有一部分被透明的挡板盖住变模糊了"。
   *
   * 顶栏是"我去哪儿"的固定框架，不是浮在内容上的装饰 ——
   * 它不该让下面透上来。模糊留给真正浮着的东西（图的工具条、弹层）。
   */
  background: var(--background);
  border-bottom: 1px solid var(--border);
}
@media (min-width: 1024px) { .app-header { display: none; } }

.app-header__brand {
  display: flex; align-items: center; gap: var(--space-2);
  font-family: var(--font-display); font-weight: 600; min-width: 0;
  /*
   * 左右不留内边距、不吃负外边距 —— 让 logo 的左边**正好落在 16px 上**，
   * 和页面其它内容（.container 的左右内边距也是 16）对齐。
   *
   * 原来这里 padding 4px + margin -4px 想给悬停背景留点余地，
   * 结果 logo 落在了 12px —— 比全页少 4px。单看没感觉，
   * 和下面的标题、卡片摆在一起就是"歪的"。
   */
  padding-inline: 0;
  min-height: 2.75rem;
  border-radius: var(--radius-md);
}
@media (hover: hover) {
  .app-header__brand:hover { background: var(--sidebar-accent); }
}
@media (hover: hover) {
  .app-header__brand:hover { color: var(--foreground); }
}
.app-header__logo {
  width: 1.5rem; height: 1.5rem; flex-shrink: 0;
  border-radius: var(--radius-sm);
  background: var(--primary); color: var(--primary-foreground);
  display: grid; place-items: center;
}
.app-header__name { font-size: var(--text-base); white-space: nowrap; }
@media (max-width: 380px) { .app-header__name { display: none; } }

/* ---------------------------------------------------------------- 主区域 */
.app-main {
  min-height: 100dvh;
  padding-block: var(--space-5) var(--space-12);
  /* 移动端底部标签栏占位 */
  padding-bottom: calc(var(--space-12) + var(--tabbar-h) + env(safe-area-inset-bottom, 0px));
  /* 横屏时内容不能伸到刘海底下 */
  padding-left: max(var(--space-4), env(safe-area-inset-left, 0px));
  padding-right: max(var(--space-4), env(safe-area-inset-right, 0px));
}
@media (min-width: 1024px) {
  .app-main {
    margin-inline-start: var(--sidebar-w);
    padding-block: var(--space-8) var(--space-16);
    /* 桌面端不需要横向让位（没有刘海），改回普通内边距 */
    padding-left: var(--space-6);
    padding-right: var(--space-6);
  }
}

/* ---------------------------------------------------------------- 底部标签栏 */
.app-tabbar {
  position: fixed; inset-inline: 0; bottom: 0; z-index: var(--z-nav);
  height: calc(var(--tabbar-h) + env(safe-area-inset-bottom, 0px));
  padding-bottom: env(safe-area-inset-bottom, 0px);
  /* 横屏时底栏两端也会撞上圆角/刘海 */
  padding-left: env(safe-area-inset-left, 0px);
  padding-right: env(safe-area-inset-right, 0px);
  /* 三格：档案馆 · 搜索 · 设置（都是"去处"；主题是"工具"，在顶栏） */
  display: grid; grid-template-columns: repeat(3, 1fr);
  /*
   * 和顶栏同一个道理：不透明。
   *
   * 磨砂玻璃只适合"浮在内容上的一小块"（图的工具条、弹层），
   * 不适合这种横跨整屏、固定不动的框架 —— 它把滚过去的内容糊成一片，
   * 看着像蒙了层塑料膜。
   */
  background: var(--background);
  border-top: 1px solid var(--border);
}
@media (min-width: 1024px) { .app-tabbar { display: none; } }

.app-tabbar__item {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 3px; font-size: var(--text-2xs); color: var(--muted-foreground);
  transition: color var(--dur-fast) var(--ease-out);
}
.app-tabbar__item[aria-current="page"] { color: var(--primary); font-weight: 600; }
.app-tabbar__item:active { opacity: 0.65; }



/* ---------------------------------------------------------------- 页头 */
.page-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: var(--space-4); flex-wrap: wrap;
  padding-bottom: var(--space-4);
  margin-bottom: var(--space-6);
  border-bottom: 1px solid var(--border);
}
.page-head__title {
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  font-weight: 600;
  letter-spacing: -0.01em;
}
@media (min-width: 768px) { .page-head__title { font-size: var(--text-3xl); } }
.page-head__sub { color: var(--muted-foreground); font-size: var(--text-sm); margin-top: var(--space-1); }
.page-head__actions { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }

/* 返回链接：出现在页头之上的细线导航 */
.page-back {
  display: inline-flex; align-items: center; gap: var(--space-2);
  font-size: var(--text-sm); color: var(--muted-foreground);
  margin-bottom: var(--space-4);
}
@media (hover: hover) {
  .page-back:hover { color: var(--foreground); }
}
@media (hover: hover) {
  .page-back:hover svg { transform: translateX(-2px); }
}
.page-back svg { transition: transform var(--dur-fast) var(--ease-out); }

/* ---------------------------------------------------------------- 按下回应 */
/*
 * 全应用一句话约定：**按下去，它立刻缩一下**。
 *
 * 现状是散着的 —— 整个应用只有 10 处写了 :active，其余按下去毫无变化。
 * 那不只是"少了点手感"：没有回应时，用户分不清"我没点上"和"点上了但没反应"，
 * 于是会重复点。这是可用性问题，不是装饰问题。
 *
 * 用 scale 不用位移：位移会让相邻的东西看起来也在动，缩放只动它自己。
 * 幅度按元素大小分两档 —— 小控件 0.94 看得出来，大卡片 0.985 就够
 * （同一个比例在大面积上会显得夸张）。
 *
 * 用 :where() 把特异度压到 0：这是"兜底约定"，任何组件自己写的
 * :active / transform 都该压过它，而不是被它改写。
 * 已经自己写过的（.btn / .deck__nav …）不受影响。
 */
:where(.poster, .work-card, .entity-card, .result-item, .trash-item, .photo-tile,
       .chip, .tabs__tab, .segmented__item, .pagination__btn, .storage-row,
       .settings-nav__item, .accent-swatch, .lightbox__close) {
  transition: transform var(--dur-fast) var(--ease-out);
}
:where(.poster, .work-card, .entity-card, .result-item, .trash-item, .photo-tile):active {
  transform: scale(0.985);
}
:where(.chip, .tabs__tab, .segmented__item, .pagination__btn, .storage-row,
       .settings-nav__item, .accent-swatch, .lightbox__close):active {
  transform: scale(0.94);
}

/* ---------------------------------------------------------------- 手感层 */
/*
 * ---- 动态弥散光晕边框 ----
 *
 * 聚焦时不只是描边，外面还有一圈很淡的散光。
 *
 * 为什么"动态"指的是跟着状态走、而不是自己呼吸：
 * 一个原地脉动的光晕是装饰，读两次就烦；而"焦点移到哪光圈跟到哪"是真的在帮忙
 * —— 键盘用户一眼就能找到焦点在哪。这也是这个应用一贯的取舍
 * （用户自己的话：形式，不是样子）。
 *
 * 光晕**不做淡入**：它是聚焦提示的一部分，键盘用户需要它立刻出现。
 * 用 box-shadow 而不是 outline 是因为它能散开；只给表单控件和按钮，
 * 因为这几类身上本来没有别的 box-shadow，不会盖掉卡片自己的投影。
 */
:where(.btn, .chip, .input, select, textarea, .tabs__tab, .segmented__item,
       .storage-row, .settings-nav__item, .photo-tile, .pagination__btn):focus-visible {
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--ring) 20%, transparent),
              0 0 20px 3px color-mix(in srgb, var(--ring) 16%, transparent);
}

/*
 * 三处指针/数值的物理感。全部只在"有精细指针 + 没开减少动效"时才有东西可看：
 * 触屏上没有指针位置，减少动效的用户要的是别动（见 js/ui/motion.js 的 ACTIVE）。
 * 静态时这些规则一条都不生效 —— 不会因为"动效层"改变任何布局尺寸。
 */

/* ---- 3D 倾斜光影 ---- */
/*
 * perspective 给容器，不给卡片：卡片各自给透视的话，每张卡的灭点都不一样，
 * 一屏卡片会像被拧过。容器一个灭点，整片看起来才在同一空间里。
 */
.grid-auto { perspective: 1100px; }
/*
 * 只有正在被指的那张加 preserve-3d 和 will-change：
 * 全量挂着会为几十张卡各建一个合成层，滚动反而变卡。
 */
.poster.is-tilting {
  transform-style: preserve-3d;
  will-change: transform;
  z-index: 2;
}
/*
 * 高光跟着指针跑。
 *
 * 这一层比"转角"更值：转角让人看出是 3D，高光让人看出**有厚度**。
 * 用 radial-gradient 而不是叠一张图 —— 一个渐变，零请求。
 * 颜色用白色高光而不是主色：主色高光在浅色主题下会变成一块脏斑。
 */
.poster::after {
  content: ''; position: absolute; inset: 0; z-index: 1;
  border-radius: inherit; pointer-events: none;
  background: radial-gradient(circle at var(--gx, 50%) var(--gy, 50%),
              color-mix(in srgb, #fff 26%, transparent), transparent 46%);
  opacity: 0;
  transition: opacity var(--dur-normal) var(--ease-out);
}
.poster.is-tilting::after { opacity: 1; }

/* ---- 滚动码表 ---- */
/*
 * 旧值向上滚出、新值从下滚入。两个 span，不做"每位一个轮子"——
 * 按位拆 DOM 的话数字一长就碎成一堆节点，而这里要的只是"看得出它变了"。
 */
/* 不限定 .stat__value —— 配图页的计数器也要用同一套 */
.roll { position: relative; display: inline-block; overflow: hidden; }
.roll__item { display: block; transition: transform var(--dur-slow) var(--ease-spring); }
/* 默认（往前）：新值从**下面**进来 */
/*
 * 新数字必须**撑满容器并继承容器的对齐**，不能只贴在左边缘。
 *
 * 原来写的是 inset-inline-start: 0 —— 绝对定位元素会收缩到内容宽，
 * 于是它落在容器左边；而旧数字是靠 text-align: right 贴右边的。
 * 结果滚动过程中是"旧数字在右、新数字在左"，滚完新数字才跳回右边 ——
 * 用户看到的就是"数字先往左跳一下，再变成下一个"。
 *
 * 用 inset-inline: 0（两边都贴）让它撑满，text-align 又是继承的：
 * 页码那种右对齐的、统计栏那种左对齐的，两边都对。
 */
.roll .roll__next { position: absolute; inset-inline: 0; top: 0; transform: translateY(100%); }
.roll--go .roll__item { transform: translateY(-100%); }
.roll--go .roll__next { transform: translateY(0); }
/*
 * 往回翻：方向整个反过来 —— 旧值向下滚出，新值从**上面**落进来。
 * 两个方向一样的话，往回翻看起来也像在前进（用户直接指出过这一点）。
 */
.roll--back .roll__next { transform: translateY(-100%); }
.roll--go.roll--back .roll__item { transform: translateY(100%); }
.roll--go.roll--back .roll__next { transform: translateY(0); }

/* ---- 磁吸 ---- */
/*
 * 主要动作在指针靠近时朝它偏一点点（位移由 js/ui/motion.js 写进 --mx/--my）。
 * 用弹簧缓动：松手回位要有一点过冲，才像被吸住又弹开。
 */
.btn--primary {
  transform: translate(var(--mx, 0px), var(--my, 0px));
  transition: transform var(--dur-slow) var(--ease-spring),
              background-color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              opacity var(--dur-fast) var(--ease-out);
}
/* 按下时在磁吸位移之上再缩一点，两个位移要写在同一条 transform 里 */
.btn--primary:active:not(:disabled) { transform: translate(var(--mx, 0px), var(--my, 0px)) scale(0.97); }

/* ---------------------------------------------------------------- 按钮 */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  border-radius: var(--radius-md);
  font-size: var(--text-sm); font-weight: 500; white-space: nowrap;
  border: 1px solid transparent;
  transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out), opacity var(--dur-fast) var(--ease-out);
  user-select: none;
}
.btn:active:not(:disabled) { opacity: 0.78; }
.btn:disabled { opacity: 0.42; }
.btn--sm { height: 1.875rem; padding-inline: var(--space-3); font-size: var(--text-xs); }
.btn--md { height: 2.125rem; padding-inline: var(--space-4); }
.btn--lg { height: 2.5rem; padding-inline: var(--space-5); font-size: var(--text-base); }
.btn--icon { width: 2.125rem; height: 2.125rem; padding: 0; }
.btn--icon.btn--sm { width: 1.875rem; height: 1.875rem; }
.btn--block { width: 100%; }

.btn--primary { background: var(--primary); color: var(--primary-foreground); }
@media (hover: hover) {
  .btn--primary:hover:not(:disabled) { background: color-mix(in srgb, var(--primary) 86%, var(--foreground)); }
}

.btn--secondary { background: var(--secondary); color: var(--secondary-foreground); }
@media (hover: hover) {
  .btn--secondary:hover:not(:disabled) { background: color-mix(in srgb, var(--secondary) 84%, var(--foreground)); }
}

.btn--outline { background: var(--background-elevated); border-color: var(--border-strong); color: var(--foreground); }
@media (hover: hover) {
  .btn--outline:hover:not(:disabled) { border-color: var(--foreground); background: var(--background-elevated); }
}

.btn--ghost { background: transparent; color: var(--muted-foreground); }
@media (hover: hover) {
  .btn--ghost:hover:not(:disabled) { background: var(--muted); color: var(--foreground); }
}

.btn--destructive { background: var(--destructive); color: var(--destructive-foreground); }
@media (hover: hover) {
  .btn--destructive:hover:not(:disabled) { background: color-mix(in srgb, var(--destructive) 86%, black); }
}

.btn--danger-ghost { background: transparent; color: var(--destructive); }
@media (hover: hover) {
  .btn--danger-ghost:hover:not(:disabled) { background: color-mix(in srgb, var(--destructive) 10%, transparent); }
}

.btn.is-active { background: var(--primary-soft); color: var(--primary); }

.btn__spinner { width: 0.9em; height: 0.9em; }

/* ---------------------------------------------------------------- 表单 */
.field { display: flex; flex-direction: column; gap: var(--space-2); }
.field__label { font-size: var(--text-sm); font-weight: 500; display: flex; align-items: center; gap: var(--space-2); }
.field__label .optional { font-size: var(--text-xs); color: var(--faint-foreground); font-weight: 400; }
.field__hint { font-size: var(--text-xs); color: var(--muted-foreground); }
.field__error { font-size: var(--text-xs); color: var(--destructive); }

.input, .textarea, .select {
  width: 100%;
  min-height: 2.125rem;
  padding: var(--space-2) var(--space-3);
  background: var(--background-elevated);
  border: 1px solid var(--input);
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
  color: var(--foreground);
  transition: border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
.input::placeholder, .textarea::placeholder { color: var(--faint-foreground); }
@media (hover: hover) {
  .input:hover, .textarea:hover, .select:hover { border-color: var(--border-strong); }
}
.input:focus, .textarea:focus, .select:focus {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px var(--primary-soft);
  outline: none;
}
.input[aria-invalid="true"], .textarea[aria-invalid="true"] { border-color: var(--destructive); }
.textarea { min-height: 5.5rem; padding-block: var(--space-3); resize: vertical; line-height: var(--leading-normal); }
.input--sm { min-height: 1.875rem; font-size: var(--text-xs); }
.input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; }

.select {
  appearance: none;
  padding-right: var(--space-8);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%238a847c' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--space-3) center;
}

.input-icon { position: relative; display: flex; align-items: center; width: 100%; }
.input-icon > svg { position: absolute; left: var(--space-3); color: var(--faint-foreground); pointer-events: none; }
.input-icon > .input { padding-left: 2.25rem; }
.input-icon__clear { position: absolute; right: var(--space-2); color: var(--muted-foreground); border-radius: var(--radius-full); padding: var(--space-1); }
@media (hover: hover) {
  .input-icon__clear:hover { background: var(--muted); color: var(--foreground); }
}
.input-icon__clear.is-hidden { display: none; }

/* 小号文字按钮：跟正文排在同一行，不抢注意力 */
.link-btn {
  color: var(--muted-foreground);
  font-size: var(--text-xs);
  text-decoration: underline;
  text-underline-offset: 3px;
  padding: 0 var(--space-1);
  border-radius: var(--radius-sm);
}
@media (hover: hover) {
  .link-btn:hover { color: var(--foreground); }
}

.checkbox { display: inline-flex; align-items: center; gap: var(--space-2); cursor: pointer; font-size: var(--text-sm); user-select: none; }
.checkbox input { appearance: none; width: 1rem; height: 1rem; flex-shrink: 0;
  border: 1.5px solid var(--border-strong); border-radius: var(--radius-xs); background: var(--background-elevated);
  display: grid; place-content: center; transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out); }
.checkbox input::after { content: ""; width: 0.55rem; height: 0.55rem; transform: scale(0);
  background: var(--primary-foreground); clip-path: polygon(14% 44%, 0 65%, 40% 100%, 100% 16%, 84% 0%, 38% 66%); transition: transform var(--dur-fast) var(--ease-spring); }
.checkbox input:checked { background: var(--primary); border-color: var(--primary); }
.checkbox input:checked::after { transform: scale(1); }

.switch { display: inline-flex; align-items: center; gap: var(--space-3); cursor: pointer; font-size: var(--text-sm); }
.switch input { appearance: none; width: 2.25rem; height: 1.25rem; border-radius: var(--radius-full);
  background: var(--border-strong); position: relative; transition: background var(--dur-fast) var(--ease-out); flex-shrink: 0; }
.switch input::after { content: ""; position: absolute; top: 2px; left: 2px; width: calc(1.25rem - 4px); height: calc(1.25rem - 4px);
  border-radius: 50%; background: #fff; box-shadow: var(--shadow-xs); transition: transform var(--dur-normal) var(--ease-spring); }
.switch input:checked { background: var(--primary); }
.switch input:checked::after { transform: translateX(1rem); }

.segmented { display: inline-flex; padding: 2px; gap: 2px; background: var(--muted); border-radius: var(--radius-lg); }
.segmented__item {
  padding: var(--space-2) var(--space-4); border-radius: var(--radius-md);
  font-size: var(--text-sm); font-weight: 500; color: var(--muted-foreground);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out); white-space: nowrap;
}
@media (hover: hover) {
  .segmented__item:hover { color: var(--foreground); }
}
.segmented__item[aria-selected="true"], .segmented__item[data-active="true"] {
  background: var(--background-elevated); color: var(--foreground); box-shadow: var(--shadow-xs); font-weight: 600;
}

/* ---------------------------------------------------------------- 容器 */
/* .card 只用于真正需要「一块独立表面」的地方（modal、设置分组）。
   普通内容优先用留白与细分隔线，而不是层层套盒子。 */
.card {
  background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-xl);
  color: var(--card-foreground); overflow: hidden;
}
.card--pad { padding: var(--space-5); }
.card__head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); padding: var(--space-4) var(--space-5); border-bottom: 1px solid var(--border); }
.card__title { font-size: var(--text-base); font-weight: 600; display: flex; align-items: center; gap: var(--space-2); }
.card__body { padding: var(--space-5); }

.panel {
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  background: var(--card);
  padding: var(--space-4);
}
.panel--flat { background: transparent; border-color: var(--border); }

/* ---------------------------------------------------------------- 徽标 / 状态 */
.badge {
  display: inline-flex; align-items: center; gap: var(--space-1);
  padding: 0.05rem var(--space-2); border-radius: var(--radius-sm);
  font-size: var(--text-2xs); font-weight: 600; line-height: 1.7; white-space: nowrap;
  border: 1px solid transparent;
}
.badge--default { background: var(--primary); color: var(--primary-foreground); }
.badge--secondary { background: var(--secondary); color: var(--muted-foreground); }
.badge--outline { border-color: var(--border); color: var(--muted-foreground); }
.badge--success { background: color-mix(in srgb, var(--success) 14%, transparent); color: var(--success); }
.badge--info { background: color-mix(in srgb, var(--info) 14%, transparent); color: var(--info); }
.badge--warning { background: color-mix(in srgb, var(--warning) 16%, transparent); color: var(--warning); }
.badge--destructive { background: color-mix(in srgb, var(--destructive) 12%, transparent); color: var(--destructive); }
.badge--prophecy { background: color-mix(in srgb, var(--chart-4) 16%, transparent); color: var(--chart-4); }

/* 胶囊开关：标签、快速筛选等 */
.chip {
  display: inline-flex; align-items: center; gap: var(--space-2);
  padding: 0.2rem var(--space-3); border-radius: var(--radius-full);
  border: 1px solid var(--border); background: var(--background-elevated);
  font-size: var(--text-xs); font-weight: 500; color: var(--muted-foreground);
  transition: border-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
  white-space: nowrap;
}
@media (hover: hover) {
  .chip:hover { border-color: var(--border-strong); color: var(--foreground); }
}
.chip[aria-pressed="true"] { background: var(--primary-soft); border-color: var(--primary-line); color: var(--primary); font-weight: 600; }
/*
 * ---- 流体胶囊（这一排是下划线页签，所以胶囊就是那条下划线）----
 *
 * 走过一次弯路，记在这里：
 * 第一版在芯片**背后**放了一块圆的滑动底色。但这一排在 pages.css 里有明确的决定 ——
 * 「状态筛选做成下划线页签：一排文字比一排胶囊更容易扫读，也不会和卡片抢注意力」，
 * 芯片是 padding 0、无边框、无底色，选中态只是一条 1.5px 下划线。
 * 在文字后面塞一块胶囊，等于**推翻那个决定**，于是怎么调都难看：
 * 42x37 的块配全圆角是鸡蛋形、文字贴边、还蹭到下面的线。
 *
 * 修法不是把胶囊修好看，是**让本来就是主角的那条下划线滑起来**：
 * 原设计一个字没改（还是文字 + 一条线），而"流体形变"落在**线的拉伸**上 ——
 * 一条线被拉长再收回，比一块底色被拉长再收回自然得多。
 */
.filters--tabs { position: relative; }
/* 选中芯片自己的那条线交给胶囊去画，否则会有两条线同时存在 */
.filters--tabs > .chip[aria-pressed="true"]::after { background: transparent; }
.capsule {
  position: absolute; left: 0; bottom: -1px; height: 2px;
  border-radius: 2px;
  background: var(--primary);
  pointer-events: none; z-index: 0;
  /* 常态过渡：js 只在有"上一个位置"时才另起一段带动画的滑动 */
  transition: transform var(--dur-slow) var(--ease-spring),
              width var(--dur-slow) var(--ease-spring),
              opacity var(--dur-fast) var(--ease-out);
}

/* ---------------------------------------------------------------- 评分星 */
.stars { display: inline-flex; align-items: center; gap: 1px; }
.stars__star { color: var(--border-strong); transition: color var(--dur-fast) var(--ease-out); }
.stars__star--on { color: var(--primary); }
.stars--interactive .stars__star { cursor: pointer; }
@media (hover: hover) {
  .stars--interactive .stars__star:hover { color: var(--primary); }
}
.stars--sm .stars__star { width: 0.8125rem; height: 0.8125rem; }
.stars--md .stars__star { width: 1rem; height: 1rem; }
.stars--lg .stars__star { width: 1.375rem; height: 1.375rem; }

/* ---------------------------------------------------------------- 海报 */
.poster {
  position: relative; aspect-ratio: 2 / 3; border-radius: var(--radius-md); overflow: hidden;
  background: var(--muted); display: block;
  box-shadow: inset 0 0 0 1px rgb(0 0 0 / 0.08);
}
/*
 * 图到位时淡入，而不是"啪"地换上去。
 *
 * 全应用共用这一条约定（js/ui/image.js 的 revealOnLoad 挂这两个类）：
 * 海报墙、记录卡缩略图、配图那一摞都走它，免得三处各写一套时长和缓动。
 * 加载中保持透明，所以底下垫着的骨架/海报底色是看得见的 —— 那才是"在加载"的样子。
 */
img.is-loading { opacity: 0; }
img.is-loaded { animation: fa-img-in var(--dur-slow) var(--ease-out) both; }
@keyframes fa-img-in { from { opacity: 0; } to { opacity: 1; } }

.poster__img { width: 100%; height: 100%; object-fit: cover; transition: transform var(--dur-slow) var(--ease-out); }
.poster__fallback {
  width: 100%; height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--space-1); padding: var(--space-3) var(--space-2); text-align: center;
  /*
   * 标题长了要能收住。
   *
   * 这里是 flex 居中（justify-content: center），内容是"标题 + 片种·年份"。
   * 标题一长，整块就比卡片高，而 flex 居中是**上下各溢出一半** ——
   * 外层 .poster 的 overflow: hidden 再把两头裁掉，
   * 结果就是"王者荣耀KPL年度总决赛2025"这种长标题上下都被切一半。
   *
   * 所以标题自己限行数（下面那条 -webkit-line-clamp），
   * 并且整块允许收缩，不靠外层裁。
   */
  overflow: hidden;
  min-height: 0;
  background: var(--muted);
  color: var(--faint-foreground); font-size: var(--text-xs);
}
.poster__fallback strong {
  font-family: var(--font-display); font-size: var(--text-sm); color: var(--foreground); font-weight: 600;
  /* 最多三行，多的省略号。中文没有空格，一定要允许任意位置断行 */
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  word-break: break-word;
  overflow-wrap: anywhere;
  line-height: 1.3;
  max-width: 100%;
}
/* 片种·年份那行永远留着，不能被标题挤没 */
.poster__fallback > span { flex: 0 0 auto; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.poster__overlay {
  position: absolute; inset-inline: 0; bottom: 0; padding: var(--space-5) var(--space-2) var(--space-2);
  background: linear-gradient(180deg, transparent, var(--scrim-from));
  color: #fff; display: flex; flex-direction: column; gap: var(--space-1);
}
.poster__badges { position: absolute; top: var(--space-2); left: var(--space-2); display: flex; flex-wrap: wrap; gap: var(--space-1); max-width: calc(100% - var(--space-4)); }
.poster__select {
  position: absolute; top: var(--space-2); right: var(--space-2); z-index: 2;
  width: 1.375rem; height: 1.375rem; border-radius: var(--radius-sm);
  border: 1.5px solid rgb(255 255 255 / 0.85); background: rgb(0 0 0 / 0.35); display: grid; place-items: center;
  transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.poster__select[aria-checked="true"] { background: var(--primary); border-color: var(--primary); }
.poster__select svg { color: #fff; opacity: 0; transition: opacity var(--dur-fast) var(--ease-out); }
.poster__select[aria-checked="true"] svg { opacity: 1; }

.work-card { display: flex; flex-direction: column; gap: var(--space-2); }
@media (hover: hover) {
  .work-card:hover .poster__img { transform: scale(1.04); }
}
@media (hover: hover) {
  .work-card:hover .work-card__title { color: var(--primary); }
}
.work-card__title {
  font-size: var(--text-sm); font-weight: 600; line-height: var(--leading-snug);
  transition: color var(--dur-fast) var(--ease-out);
  /*
   * 最多两行，多了省略。
   * 手机上三列并排，长片名（"在地下城寻求邂逅是否搞错了什么"）会占掉三行，
   * 同一行的卡片高度就参差不齐，底下那排跟着往下错。
   */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.work-card__meta {
  font-size: var(--text-xs); color: var(--faint-foreground);
  display: flex; align-items: center; gap: var(--space-1);
  white-space: nowrap; overflow: hidden;
}
.work-card__meta > span { flex: 0 0 auto; }

/* ---------------------------------------------------------------- 空状态 */
.empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--space-3); padding: var(--space-12) var(--space-6); text-align: center;
}
.empty__icon {
  width: 2.75rem; height: 2.75rem; border-radius: var(--radius-lg); display: grid; place-items: center;
  background: var(--muted); color: var(--faint-foreground);
}
.empty__title { font-family: var(--font-display); font-size: var(--text-lg); font-weight: 600; }
.empty__desc { color: var(--muted-foreground); font-size: var(--text-sm); max-width: 30rem; }

/* ---------------------------------------------------------------- 弹层 */
.overlay {
  position: fixed; inset: 0; z-index: var(--z-overlay);
  background: var(--overlay);
  /*
   * 毛玻璃加在**遮罩**上，不是加在面板上。
   *
   * 面板最高能长到 min(88dvh, 48rem)（见下面的 .modal），那么大的面积做半透明，
   * 底下的内容会一整片糊上来 —— 正是本文件那条教义说的"磨砂玻璃只适合浮在
   * 内容上的一小块"。所以面板保持不透明的实体，模糊交给遮罩：遮罩本来就是
   * 一层蒙版，模糊加在它身上，效果是"背后的整页失焦"，弹层浮在失焦之上。
   *
   * 10px 对齐项目里已有的浮层写法（pages.css 的 .graph-toolbar / .graph-legend）。
   * 不用更重的值：遮罩背后是整页内容，模糊半径一大，手机 GPU 的合成开销立刻上去。
   *
   * 模糊是**锦上添花**，不是可读性的依靠：上面那层 --overlay 深色蒙版单独就够
   * （浅色 0.42 / 深色 0.66 / 午夜 0.76），所以老浏览器不支持 backdrop-filter 时
   * 什么都不用做，退化成现在这个样子即可。
   * 也正因如此，这里**不加 transition、不加额外动画** —— 遮罩已经有 fa-fade-in 了。
   */
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  animation: fa-fade-in var(--dur-normal) var(--ease-out) both;
}
.modal-wrap { position: fixed; inset: 0; z-index: var(--z-modal); display: grid; place-items: center; padding: var(--space-4); overflow-y: auto; }
.modal {
  width: 100%; max-width: var(--modal-max, 30rem); max-height: min(88dvh, 48rem);
  display: flex; flex-direction: column;
  background: var(--popover); color: var(--popover-foreground);
  border: 1px solid var(--border); border-radius: var(--radius-2xl);
  box-shadow: var(--shadow-xl); animation: fa-pop var(--dur-normal) var(--ease-out) both;
  /*
   * clip 而不是 hidden。
   *
   * overflow: hidden 的元素**在程序上仍然可以滚**（聚焦、scrollIntoView 都会滚它），
   * 只有 clip 是真正的"不能滚"。弹层本身不该滚：要滚的是里面的 .modal__body，
   * 否则聚焦到内容深处的元素时，标题栏会被一起顶出去、关闭按钮就找不到了。
   */
  overflow: hidden;
  overflow: clip;
}
.modal--wide { --modal-max: 54rem; }
.modal--full { --modal-max: 68rem; max-height: min(92dvh, 60rem); }
.modal__head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-4); padding: var(--space-5) var(--space-5) var(--space-3); }
.modal__title { font-size: var(--text-lg); font-weight: 600; }
.modal__desc { font-size: var(--text-sm); color: var(--muted-foreground); margin-top: var(--space-1); }
.modal__body { padding: var(--space-2) var(--space-5) var(--space-5); overflow-y: auto; flex: 1 1 auto; }
.modal__foot { display: flex; align-items: center; justify-content: flex-end; gap: var(--space-2); padding: var(--space-4) var(--space-5); border-top: 1px solid var(--border); background: var(--background); flex-wrap: wrap; }
.modal__foot--split { justify-content: space-between; }

/*
 * 底部面板的把手（弹层抽屉 + 配图灯箱的抽屉共用，元素由 dialog.js 的
 * createGrabber() 统一造）。只在窄屏出现 —— 宽屏的弹层是居中卡片、
 * 灯箱也不是"从底部拉出来的"，两边都没有可抓的地方。
 * 窄屏那一段在下面同一个断点的 @media 里（和抽屉规则放在一起，改断点不会漏）。
 */
.grabber { display: none; }

/*
 * 拖拽中的两个状态类（attachDragToClose 加/摘）。
 *
 * 拖动中直接写 transform、不加过渡（加了就跟手滞后一拍）；弹回时才交给
 * .is-settling 的 transition —— 同一个已存在节点上有明确的"旧值 → 新值"，
 * 过渡在这里是合法的（入场动画那种新建节点只能用 @keyframes）。
 */
.is-dragging { transition: none; }
.is-dragging .grabber { cursor: grabbing; }
.is-settling { transition: transform var(--dur-normal) var(--ease-out); }

@media (max-width: 640px) {
  .modal-wrap {
    padding: 0; place-items: end stretch;
    /*
     * 拖动时那 2° 摆动会让全宽的弹层左右各探出十几像素，而 overflow-y: auto
     * 会把 overflow-x 一起算成 auto —— 探出去的部分就变成一段横向可滚的区域。
     * 这里写 clip 也会被规范计算成 hidden（另一轴是 auto 时 clip 不成立），
     * 索性直接写 hidden：横轴没有滚动条、用户也划不动，摆动只是视觉反馈。
     */
    overflow-x: hidden;
  }
  .modal { max-width: none; max-height: 92dvh; border-radius: var(--radius-2xl) var(--radius-2xl) 0 0; animation: fa-slide-up var(--dur-slow) var(--ease-out) both; }
  /*
   * 把手：一小截横条 + 一条虚线（对齐既有的克制风格，不做成大圆角气泡）。
   *
   * touch-action: none 是必须的 —— 不写的话浏览器会把这段手势当成滚动，
   * pointermove 根本不会派下来，表现是"代码明明写了却完全没反应"。
   */
  .grabber {
    display: flex; align-items: center; justify-content: center;
    flex: 0 0 auto; height: 1.75rem;
    touch-action: none;
    border-bottom: 1px dashed var(--border);
    cursor: grab;
    -webkit-user-select: none; user-select: none;
    -webkit-tap-highlight-color: transparent;
  }
  .grabber::before {
    content: ''; display: block;
    width: 2.5rem; height: 0.25rem;
    border-radius: var(--radius-full);
    background: var(--border-strong);
  }
}

/* ---------------------------------------------------------------- Toast */
.toast-region {
  position: fixed; inset-inline: 0; bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom, 0px) + var(--space-3));
  z-index: var(--z-toast); display: flex; flex-direction: column-reverse; align-items: center;
  gap: var(--space-2); padding-inline: var(--space-4); pointer-events: none;
}
@media (min-width: 1024px) { .toast-region { bottom: var(--space-6); } }

.toast {
  pointer-events: auto; display: flex; align-items: center; gap: var(--space-3);
  max-width: min(28rem, 100%); padding: var(--space-3) var(--space-4);
  background: var(--popover); color: var(--popover-foreground);
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg); font-size: var(--text-sm);
  animation: fa-rise var(--dur-normal) var(--ease-out) both;
}
.toast--leaving { animation: fa-fade-in var(--dur-normal) var(--ease-out) reverse both; }
.toast__icon { flex-shrink: 0; }
.toast--success .toast__icon { color: var(--success); }
.toast--error .toast__icon { color: var(--destructive); }
.toast--info .toast__icon { color: var(--info); }
.toast__msg { flex: 1 1 auto; min-width: 0; }
.toast__action { flex-shrink: 0; font-weight: 600; color: var(--primary); font-size: var(--text-xs); padding: var(--space-1) var(--space-2); border-radius: var(--radius-sm); }
@media (hover: hover) {
  .toast__action:hover { background: var(--primary-soft); }
}

/* ---------------------------------------------------------------- 标签页 */
.tabs { display: flex; flex-direction: column; gap: var(--space-5); }
.tabs__list {
  display: flex; gap: var(--space-5); border-bottom: 1px solid var(--border);
  overflow-x: auto; scrollbar-width: none;
}
.tabs__list::-webkit-scrollbar { display: none; }
.tabs__tab {
  position: relative; flex: 0 0 auto;
  padding: var(--space-2) 0 var(--space-3);
  font-size: var(--text-sm); font-weight: 500; color: var(--muted-foreground); white-space: nowrap;
  transition: color var(--dur-fast) var(--ease-out);
}
.tabs__tab::after {
  content: ""; position: absolute; inset-inline: 0; bottom: -1px; height: 1.5px;
  background: transparent; transition: background var(--dur-fast) var(--ease-out);
}
@media (hover: hover) {
  .tabs__tab:hover { color: var(--foreground); }
}
.tabs__tab[aria-selected="true"] { color: var(--foreground); font-weight: 600; }
.tabs__tab[aria-selected="true"]::after { background: var(--primary); }
.tabs__panel { min-height: 1px; }

/* ---------------------------------------------------------------- 分页 */
.pagination { display: flex; align-items: center; justify-content: center; gap: var(--space-1); flex-wrap: wrap; margin-top: var(--space-8); }
.pagination__btn {
  min-width: 2rem; height: 2rem; padding-inline: var(--space-2);
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius-md); font-size: var(--text-sm); color: var(--muted-foreground);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
@media (hover: hover) {
  .pagination__btn:hover:not(:disabled) { background: var(--muted); color: var(--foreground); }
}
.pagination__btn:disabled { opacity: 0.32; }
.pagination__btn[aria-current="page"] { background: var(--foreground); color: var(--background); font-weight: 600; }
.pagination__ellipsis { color: var(--faint-foreground); padding-inline: var(--space-1); }

/* ---------------------------------------------------------------- 批量操作栏 */
.bulkbar {
  position: sticky; top: calc(var(--header-h) + var(--space-2)); z-index: var(--z-sticky);
  display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap;
  padding: var(--space-2) var(--space-3); margin-bottom: var(--space-4);
  background: var(--popover); border: 1px solid var(--primary-line);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-md);
  animation: fa-rise var(--dur-normal) var(--ease-out) both;
}
@media (min-width: 1024px) { .bulkbar { top: var(--space-2); } }

/* ---------------------------------------------------------------- 统计 */
.stat { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.stat__value { font-size: var(--text-xl); font-weight: 600; line-height: 1.1; font-variant-numeric: tabular-nums; letter-spacing: -0.02em; }
.stat__label { font-size: var(--text-2xs); color: var(--muted-foreground); letter-spacing: 0.03em; }
.stat--accent .stat__value { color: var(--primary); }

/* 上下细线 + 左对齐 + 按内容宽度排列：像一本刊物的数据栏，而不是仪表盘卡片 */
.stat-strip {
  display: flex; flex-wrap: wrap;
  gap: var(--space-4) var(--space-10);
  padding-block: var(--space-4);
  border-block: 1px solid var(--border);
  margin-bottom: var(--space-6);
}
.stat-strip .stat { align-items: flex-start; min-width: 4rem; }

/* ---------------------------------------------------------------- 其他 */
.divider { height: 1px; background: var(--border); margin-block: var(--space-4); }
.divider--label { display: flex; align-items: center; gap: var(--space-3); color: var(--faint-foreground); font-size: var(--text-xs); }
.divider--label::before, .divider--label::after { content: ""; flex: 1 1 auto; height: 1px; background: var(--border); }

.stack-list { display: flex; flex-direction: column; gap: var(--space-3); }

.loading-row { display: flex; align-items: center; justify-content: center; gap: var(--space-3); padding: var(--space-10); color: var(--muted-foreground); font-size: var(--text-sm); }

.error-box {
  display: flex; flex-direction: column; align-items: center; gap: var(--space-3);
  padding: var(--space-8); text-align: center; border: 1px dashed var(--border-strong);
  border-radius: var(--radius-lg); color: var(--muted-foreground);
}
.error-box__title { color: var(--destructive); font-weight: 600; }

.tag-list { display: flex; flex-wrap: wrap; gap: var(--space-2); }

/* 键盘可达的跳转链接 */
.skip-link:focus { z-index: var(--z-cmdk); }

/*
 * 这一段必须留在**文件最末尾**。
 *
 * 它里面的选择器和上面很多规则同特异性（比如 .input、.chip），
 * 谁赢只看谁在后面。放在中间的话，后面的 .input { min-height: 2.125rem }
 * 会把它整个盖掉 —— 实测踩过两次：筛选芯片和搜索框都停在 39px，
 * 而规则看着明明写对了。
 */
/* ---------------------------------------------------------------- 触屏尺寸 */
/*
 * 手机上所有能点的东西至少 44×44 —— 这是 Apple 和 Google 都写进规范的最小值，
 * 比它小就会出现"明明点到了却没反应"。
 *
 * 加这一段是因为实测发现的：390px 的手机上，顶栏两个按钮只有 30×30、
 * 普通按钮 34 高、筛选芯片 37 高，整页几乎没有一个达标 ——
 * 用户的原话是"设置和切换主题就点不到"。
 *
 * 判据是"手指还是鼠标"（pointer: coarse）**或**"屏幕窄不窄"。
 *
 * 为什么要带上宽度这一半：pointer 才是语义正确的那个（触屏笔记本也是手指），
 * 但它没法在无头浏览器里模拟 —— CDP 能把视口调成 390px，
 * 却改不了 pointer 的取值，于是"手机上到底长什么样"根本测不了。
 * 加上宽度之后两边都覆盖到：真机走 pointer，测试和窄窗口走宽度。
 */
@media (pointer: coarse), (max-width: 768px) {
  /*
   * 顶栏的工具按钮不用在这里改尺寸 —— .app-tool-btn 本身就是 44×44，
   * 电脑和手机同一个值（见上面那个类的说明）。
   * 这里只需要保证顶栏本身够高、把按钮放得下。
   */
  .app-header { min-height: 3.25rem; }

  /*
   * 所有按钮。
   *
   * 写成 .btn.btn--md 这一串、而且把 height 和 min-height 一起改：
   * 只写 .btn { min-height } 实测没生效（.btn--md 的 height: 2.125rem
   * 照样把高度按在 34px）—— 与其琢磨是谁赢了层叠，不如把两条都写死，
   * 顺便让"这个尺寸是给手指的"这件事在代码里一眼可见。
   */
  .btn.btn--sm, .btn.btn--md, .btn.btn--lg,
  .btn.btn--icon, .btn { height: auto; min-height: 2.75rem; }
  .btn.btn--icon { min-width: 2.75rem; }

  /* 筛选芯片、标签页：一律 2.75rem（44），别用 2.5 —— 那是 40，还是差一点 */
  .chip { min-height: 2.75rem; padding-inline: var(--space-3); }
  .tabs__tab, .segmented__item { min-height: 2.75rem; }

  /*
   * 输入框：太矮的话手指点不准，也容易误触下一行。
   *
   * 必须把 .input / .select / .textarea 这几个**类**也写上 ——
   * 只写元素名的话特异性是 (0,0,1)，打不过 .input 的 (0,1,0)，
   * 那条 min-height 就形同虚设（实测搜索框和三个下拉都停在 39px）。
   * 类名放在元素名后面，同特异性时后者胜。
   */
  input, select, textarea, .field__control,
  .input, .select, .textarea { min-height: 2.75rem; }

  /* 列表项里的"详情/记一笔"这种成对按钮，别挤成一条 */
  .row > .btn { flex: 0 0 auto; }

  /* 页头的品牌链接也是可点的（回首页），别只有 24 高 */
  .app-header__brand { min-height: 2.75rem; }

  /* 建档页的"已经注册过了？去登录"是个纯文字链接，也会被点到 */
  .gate__link { min-height: 2.75rem; }
}

/* ---------------------------------------------------------------- 存储账本 */
/*
 * 「数据管理」那一块：像手机设置里的储存空间，一块一块列出来，点进去看明细。
 *
 * 只用颜色和间距的 token，不写死数值 —— 六个主题（浅色/深色/午夜黑 +
 * 以后加的换皮主题）都要能看。尺寸和圆角沿用设置页现有的行样式，
 * 不引入新的视觉语言。
 */
.storage-meter {
  height: 6px;
  border-radius: 999px;
  background: var(--muted);
  overflow: hidden;
  margin-top: var(--space-2);
}
.storage-meter__fill {
  height: 100%;
  border-radius: 999px;
  background: var(--primary);
  transition: width 0.3s ease;
}

.storage-group {
  display: flex;
  flex-direction: column;
}
.storage-group + .storage-group {
  margin-top: var(--space-5);
}
.storage-group__title {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--muted-foreground);
  margin-bottom: var(--space-2);
}

.storage-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--border);
  background: none;
  text-align: left;
  color: inherit;
  font: inherit;
}
.storage-row:last-child {
  border-bottom: 0;
}
.storage-row--tap {
  cursor: pointer;
}
.storage-row--tap:hover {
  background: var(--accent);
}
.storage-row__main {
  flex: 1 1 auto;
  min-width: 0;
}
.storage-row__label {
  font-size: var(--text-sm);
  font-weight: 500;
}
.storage-row__note {
  font-size: var(--text-xs);
  color: var(--muted-foreground);
  margin-top: 2px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.storage-row__value {
  flex: 0 0 auto;
  font-size: var(--text-sm);
  color: var(--muted-foreground);
  font-variant-numeric: tabular-nums;
}

/* 配图明细：一行一张，名字 + 大小 + 删除 */
.storage-photo-list {
  display: flex;
  flex-direction: column;
  max-height: 45vh;
  overflow-y: auto;
}
.storage-photo {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--border);
}
.storage-photo:last-child {
  border-bottom: 0;
}
.storage-photo__info {
  flex: 1 1 auto;
  min-width: 0;
}
.storage-photo__name {
  font-size: var(--text-sm);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.storage-photo__meta {
  font-size: var(--text-xs);
  color: var(--muted-foreground);
  font-variant-numeric: tabular-nums;
}

/* 手机上把行高撑到能点 */
@media (pointer: coarse), (max-width: 768px) {
  .storage-row {
    padding: var(--space-3) 0;
    min-height: 44px;
  }
}
