/* ==========================================================================
   基础层 · Reset / 排版 / 通用工具类
   ========================================================================== */

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

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-behavior: smooth;
  scrollbar-gutter: stable;
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  margin: 0;
  min-height: 100dvh;
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  color: var(--foreground);
  background-color: var(--background);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  overflow-wrap: break-word;
}

h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }

h1, h2, h3 { font-weight: 600; letter-spacing: -0.018em; line-height: var(--leading-tight); }

a { color: inherit; text-decoration: none; }
@media (hover: hover) {
  a:hover { color: var(--primary); }
}

img, svg, video, canvas { display: block; max-width: 100%; }
img { height: auto; }

button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; background: none; border: none; padding: 0; }
button:disabled { cursor: not-allowed; }

/*
 * 手机上**连点同一个按钮**（切排列、翻页、连点标签页）会被浏览器当成"双击缩放"：
 * 页面被放大之后，一切看着都像"卡住了"，得刷新才回来 —— 用户报的正是这个
 * （「我切排列切得比较频繁，它很容易卡住，得重新刷新一下」）。
 *
 * touch-action: manipulation = 允许拖动与双指缩放，**只关掉双击缩放**。
 * 不用 maximum-scale / user-scalable=no 锁视口：那是把双指缩放一起禁掉，伤无障碍。
 */
button, a, label, summary, input, select, textarea, [role="button"], .chip {
  touch-action: manipulation;
}

hr { border: none; border-top: 1px solid var(--border); margin: var(--space-6) 0; }

code, kbd, samp, pre { font-family: var(--font-mono); font-size: 0.92em; }
kbd {
  padding: 0.1em 0.36em;
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  background: var(--muted);
  font-size: var(--text-2xs);
  color: var(--muted-foreground);
}

::selection { background: var(--primary-soft); color: var(--foreground); }

/* ---- 焦点可见性（键盘导航） ---- */
:focus { outline: none; }
:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* ---- 滚动条 ---- */
* { scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--border-strong);
  border-radius: var(--radius-full);
  border: 3px solid transparent;
  background-clip: content-box;
}
@media (hover: hover) {
  ::-webkit-scrollbar-thumb:hover { background: var(--muted-foreground); background-clip: content-box; }
}

/* ==========================================================================
   布局工具类
   ========================================================================== */

.container { width: 100%; max-width: var(--layout-max); margin-inline: auto; padding-inline: var(--space-5); }
@media (min-width: 768px) { .container { padding-inline: var(--space-8); } }

.row { display: flex; align-items: center; }
.row-between { display: flex; align-items: center; justify-content: space-between; }
.col { display: flex; flex-direction: column; }
.center { display: flex; align-items: center; justify-content: center; }
.wrap { flex-wrap: wrap; }
.grow { flex: 1 1 auto; min-width: 0; }
.shrink-0 { flex-shrink: 0; }

.gap-1 { gap: var(--space-1); }
.gap-2 { gap: var(--space-2); }
.gap-3 { gap: var(--space-3); }
.gap-4 { gap: var(--space-4); }
.gap-5 { gap: var(--space-5); }
.gap-6 { gap: var(--space-6); }
.gap-8 { gap: var(--space-8); }

.grid { display: grid; gap: var(--space-4); }
.grid-auto { grid-template-columns: repeat(auto-fill, minmax(var(--grid-min, 9.5rem), 1fr)); }
/* 窄屏海报墙：一屏能扫到 3 部，比 2 部大图更好翻 */
@media (max-width: 640px) {
  .grid-auto { grid-template-columns: repeat(auto-fill, minmax(6.6rem, 1fr)); gap: var(--space-4) var(--space-3); }
}

.mt-1 { margin-top: var(--space-1); } .mt-2 { margin-top: var(--space-2); }
.mt-3 { margin-top: var(--space-3); } .mt-4 { margin-top: var(--space-4); }
.mt-6 { margin-top: var(--space-6); } .mt-8 { margin-top: var(--space-8); }
.mb-2 { margin-bottom: var(--space-2); } .mb-4 { margin-bottom: var(--space-4); }
.mb-6 { margin-bottom: var(--space-6); }

.text-2xs { font-size: var(--text-2xs); }
.text-xs { font-size: var(--text-xs); }
.text-sm { font-size: var(--text-sm); }
.text-lg { font-size: var(--text-lg); }
.text-xl { font-size: var(--text-xl); }
.text-2xl { font-size: var(--text-2xl); }
.text-3xl { font-size: var(--text-3xl); }
.text-muted { color: var(--muted-foreground); }
.text-faint { color: var(--faint-foreground); }
.text-primary { color: var(--primary); }
.text-destructive { color: var(--destructive); }
.text-success { color: var(--success); }
.font-medium { font-weight: 500; }
.font-semibold { font-weight: 600; }
.font-bold { font-weight: 700; }
.tabular { font-variant-numeric: tabular-nums; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.clamp-1 { display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden; }
.clamp-2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.clamp-3 { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.pre-wrap { white-space: pre-wrap; }
.text-center { text-align: center; }
.uppercase { text-transform: uppercase; letter-spacing: 0.08em; }

/* 小标题：用于把长页面切成可扫读的段落，取代「再套一层卡片」 */
.eyebrow {
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint-foreground);
}

.section-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-4); flex-wrap: wrap;
  padding-bottom: var(--space-2);
  border-bottom: 1px solid var(--border);
  margin-bottom: var(--space-4);
}
.section-head__title { font-size: var(--text-base); font-weight: 600; }
.section-head__note { font-size: var(--text-xs); color: var(--muted-foreground); }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.skip-link {
  position: absolute; top: -100px; left: var(--space-4); z-index: var(--z-cmdk);
  padding: var(--space-3) var(--space-4);
  background: var(--primary); color: var(--primary-foreground);
  border-radius: var(--radius-md); font-size: var(--text-sm); font-weight: 600;
  transition: top var(--dur-fast) var(--ease-out);
}
.skip-link:focus { top: var(--space-4); }

.hidden { display: none !important; }

/* ==========================================================================
   动画
   ========================================================================== */
@keyframes fa-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes fa-rise { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@keyframes fa-pop { from { opacity: 0; transform: scale(0.97); } to { opacity: 1; transform: scale(1); } }
@keyframes fa-slide-up { from { transform: translateY(100%); } to { transform: none; } }
@keyframes fa-spin { to { transform: rotate(360deg); } }
@keyframes fa-shimmer { 0% { background-position: 100% 0; } 100% { background-position: -100% 0; } }

.anim-fade { animation: fa-fade-in var(--dur-normal) var(--ease-out) both; }
.anim-rise { animation: fa-rise var(--dur-slow) var(--ease-out) both; }
.anim-pop { animation: fa-pop var(--dur-normal) var(--ease-spring) both; }

.spinner {
  width: 1em; height: 1em; border-radius: 50%;
  border: 2px solid currentColor; border-top-color: transparent;
  animation: fa-spin 620ms linear infinite;
  display: inline-block; vertical-align: -0.14em;
}

.skeleton {
  background: var(--skeleton);
  background-size: 400% 100%;
  animation: fa-shimmer 1.4s ease-in-out infinite;
  border-radius: var(--radius-md);
}

.boot-splash {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--space-4); min-height: 100dvh; color: var(--faint-foreground);
  font-size: var(--text-sm); letter-spacing: 0.1em;
}
.boot-splash__reel {
  width: 34px; height: 34px; border-radius: 50%;
  border: 2px solid var(--border); border-top-color: var(--primary);
  animation: fa-spin 900ms linear infinite;
}

.noscript {
  padding: var(--space-8); text-align: center; font-size: var(--text-lg);
}

/* ==========================================================================
   主题切换：两个方向刚好相反
   ========================================================================== */
/*
 * finesse · component: theme-wipe · register=product
 * states: default（圆心 = 真实按下点）· 换深色/午夜＝**向外扩** · 换浅色＝**往里收**
 *         · 键盘激活（没有坐标，圆心退到那颗控件的中心）· 连点（第二下直接换，不叠第二层）
 *         · reduced-motion + 不支持 View Transitions（直接换，不做补间）
 * 机制在 js/ui/theme-wipe.js（圆心、半径、往哪边、什么时候不起转场）；这里只管两层快照怎么裁。
 * tokens: --dur-wipe / --ease-wipe（tokens.css，已在「减少动态效果」那块逐条压平）；
 *         data-wipe 由 JS 写在 <html> 上（属性选择器是"匹配"，不是继承）；
 *         圆心与半径则是 JS 算好后**以字面量注入 @keyframes**，见 ui/theme-wipe.js 的 applyGeometry。
 *
 * 组件级构建：不参与页面轮换，所以 .finesse/log.json 里没有这一条 ——
 * 那是整页构建的记录，把"换主题这一下"写进去会让下一轮拿它去轮换。
 * 三个入口（侧栏/顶栏那枚按钮、设置页三颗按钮、命令面板三条命令）共用同一套机制。
 *
 * 换主题不是把整页重画一遍，是浏览器拍的两张**整屏快照**叠在一起。
 * 两层的尺寸、位置、滚动位置完全一致，页面内容**一个像素都不缩放** —— 只改裁切。
 *
 * 为什么用快照，不克隆一份 DOM 再给它套新主题：克隆体上的 canvas 是空的、
 * 滚动位置会归零、表单里打到一半的半句话会丢，整页还要多背一份节点。
 */

/* ---- 换深色 / 午夜黑：新主题在上层，圆由小到大 ---- */
::view-transition-old(root) {
  /*
   * 下面那层**原地不动**。它默认带一条淡出：圆还没盖到的地方会先变透明，
   * 露出来的是页面底色 —— 看着是"整页闪了一下"，而不是"被新主题盖过去"。
   */
  animation: none;
}
::view-transition-new(root) {
  /*
   * 时长 / 曲线 / 填充在这里（走令牌）；**关键帧的名字**由 JS 注入的那条规则给。
   *
   * 为什么拆成两半：圆心和半径是每一算一次的字面量（那颗按钮的中心、到最远角的距离），
   * 而它们是**写进 @keyframes 里的数字**，不是变量 —— 原因见 ui/theme-wipe.js 的 applyGeometry：
   * 靠 CSS 变量从 <html> 继承进这棵伪元素树，不是所有引擎都通，不通就退回兜底值，
   * 圆会永远从屏幕正中长出来。
   *
   * 万一注入没发生（没有 animation-name）：这一层退回浏览器默认的淡入淡出 ——
   * 那是"没有这个动效"的样子，总好过从一个不对的地方长出一个圆。
   */
  animation-duration: var(--dur-wipe);
  animation-timing-function: var(--ease-wipe);
  animation-fill-mode: both;
}

/* ---- 换浅色：把深色**收进去** —— 旧的那层翻到上层，圆由大到小 ---- */
:root[data-wipe='in']::view-transition-old(root) {
  /*
   * 这里是这套做法唯一"绕"的地方，但绕得值：
   * 同一份两层快照，只把**谁在上面**换一下，同一个圆就变成了反方向的动作 ——
   * 深色变成一个圆从整屏缩回按钮、缩没，露出底下的浅色。
   * 不用改 JS 的几何，也不用第二套快照。
   */
  z-index: 1;
  animation-duration: var(--dur-wipe);
  animation-timing-function: var(--ease-wipe);
  animation-fill-mode: both;
}
:root[data-wipe='in']::view-transition-new(root) {
  z-index: 0;
  /* 这层在下面待着就行，它不动 */
  animation: none;
}
}
