/* ============================================================
   客服快速回复 · 样式表（iOS 27 / Liquid Glass 版）
   浅色为默认，支持深色（跟随系统 + 手动切换）

   ★ 玻璃三件套（缺一件就不像 27 版）：
     ① 半透明填充  --glass-fill
     ② 加深的边缘  --glass-edge（外描边 0.5px）
     ③ 镜面高光    --glass-spec（inset 顶部 1px 纯白）
     27 版对 Liquid Glass 的修订就是「更会漫射背景 + 更明显的暗边与高光」，
     只写 backdrop-filter 会糊成一片，撑不起层次。
   ============================================================ */

:root {
  --bg: #eef4f8;
  --bg-soft: #e6eef4;
  --surface: #ffffff;
  --surface-2: #f6fafb;
  --surface-3: #e9f1f5;
  --line: rgba(14, 42, 39, 0.09);
  --line-strong: rgba(14, 42, 39, 0.16);
  --text: #0b1418;
  --text-2: #4f6068;
  --text-3: #8b9ba4;
  --primary: #0f9d8f;
  --primary-strong: #0b7d73;
  --primary-soft: rgba(15, 157, 143, 0.10);
  --primary-line: rgba(15, 157, 143, 0.26);
  --danger: #d9534f;
  --danger-soft: rgba(217, 83, 79, 0.10);
  --warn: #c9821a;
  --shadow-xs: 0 1px 2px rgba(11, 40, 37, 0.05);
  --shadow-sm: 0 1px 3px rgba(11, 40, 37, 0.06), 0 6px 16px -12px rgba(11, 40, 37, 0.22);
  --shadow-md: 0 2px 6px rgba(11, 40, 37, 0.05), 0 18px 38px -20px rgba(11, 40, 37, 0.28);
  --shadow-lg: 0 30px 70px -30px rgba(11, 40, 37, 0.34);
  --radius-lg: 24px;
  --radius: 24px;
  --radius-sm: 12px;
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --header-h: 0px;

  /* ---------- iOS 27 玻璃配方 ---------- */
  --glass-fill: linear-gradient(180deg, rgba(255, 255, 255, 0.82), rgba(255, 255, 255, 0.60));
  --glass-fill-soft: linear-gradient(180deg, rgba(255, 255, 255, 0.74), rgba(255, 255, 255, 0.52));
  --glass-edge: rgba(12, 32, 45, 0.09);
  --glass-spec: rgba(255, 255, 255, 0.95);
  --glass-blur: blur(24px) saturate(180%);
  /* 表单输入框：不用「更白的玻璃」——那在白色弹层上根本看不出来。
     改成微微下凹的浅色填充 + 内阴影，像 iOS 表单里的 filled field。 */
  --field-fill: rgba(11, 32, 40, 0.045);
  --field-inset: inset 0 0 0 0.5px rgba(12, 32, 45, 0.07), inset 0 1px 2px rgba(12, 32, 45, 0.05);
  /* 弹层（sheet）不能用玻璃填充：上面还压着一层调暗的背景，
     半透明白叠在灰底上出来就是「灰玻璃压灰底」，字还在、但整块糊成一坨。
     iOS 的 sheet 本来就是接近不透明的面板，玻璃语言体现在它的边缘和高光上。 */
  --sheet-fill: linear-gradient(180deg, #ffffff 0%, #f7fafc 100%);
  --glass-shadow:
    0 0 0 0.5px var(--glass-edge),
    inset 0 1px 0 var(--glass-spec),
    inset 0 -1px 0 rgba(255, 255, 255, 0.34),
    0 10px 26px -20px rgba(12, 32, 45, 0.55);

  /* 分类色板：分类名是用户自己填的，没法硬编码对应关系，
     app.js 里按名字哈希出 t-0 ~ t-7 之一（同一个名字永远同一个颜色）。 */
  --t0: #c07a00; --t0-bg: rgba(255, 186, 60, 0.20);
  --t1: #2b6fd6; --t1-bg: rgba(70, 140, 240, 0.17);
  --t2: #15885f; --t2-bg: rgba(40, 180, 120, 0.17);
  --t3: #7455d0; --t3-bg: rgba(140, 110, 235, 0.18);
  --t4: #4b5fd6; --t4-bg: rgba(100, 120, 230, 0.17);
  --t5: #0d8ea8; --t5-bg: rgba(40, 175, 205, 0.17);
  --t6: #c8437a; --t6-bg: rgba(220, 90, 140, 0.16);
  --t7: #0b7d73; --t7-bg: rgba(20, 175, 160, 0.17);

  /* 整页字号缩放系数。
     本文件所有 font-size 都写成 calc(Npx * var(--fs-scale))，改这一处就能整体调大小。
     手机端 1.08、PC 端 1.1，分别在下面各自的媒体查询里设置。 */
  --fs-scale: 1;
}

[data-theme='dark'] {
  --bg: #0a1014;
  --bg-soft: #101a20;
  --surface: #162023;
  --surface-2: #1b2629;
  --surface-3: #212e31;
  --line: rgba(255, 255, 255, 0.08);
  --line-strong: rgba(255, 255, 255, 0.16);
  --text: #eaf2f4;
  --text-2: #9fb2b8;
  --text-3: #6d8087;
  --primary: #2ec4b0;
  --primary-strong: #1ea795;
  --primary-soft: rgba(46, 196, 176, 0.14);
  --primary-line: rgba(46, 196, 176, 0.32);
  --danger: #e8736e;
  --danger-soft: rgba(232, 115, 110, 0.14);
  --warn: #e0a53f;
  --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.4), 0 8px 20px -14px rgba(0, 0, 0, 0.7);
  --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.45), 0 22px 44px -24px rgba(0, 0, 0, 0.8);
  --shadow-lg: 0 36px 80px -34px rgba(0, 0, 0, 0.85);

  /* 深色不是把白玻璃调暗，是换成「黑色玻璃」：填充降到 14%，
     镜面高光压到 22%，暗边变成真正的黑 —— 层次全靠背景的彩色漫射撑起来。 */
  --glass-fill: linear-gradient(180deg, rgba(255, 255, 255, 0.145), rgba(255, 255, 255, 0.065));
  --glass-fill-soft: linear-gradient(180deg, rgba(255, 255, 255, 0.115), rgba(255, 255, 255, 0.045));
  --glass-edge: rgba(0, 0, 0, 0.55);
  --glass-spec: rgba(255, 255, 255, 0.22);
  --glass-blur: blur(26px) saturate(160%);
  --field-fill: rgba(255, 255, 255, 0.06);
  --field-inset: inset 0 0 0 0.5px rgba(255, 255, 255, 0.07), inset 0 1px 2px rgba(0, 0, 0, 0.35);
  --sheet-fill: linear-gradient(180deg, #202b30 0%, #182226 100%);
  --glass-shadow:
    0 0 0 0.5px var(--glass-edge),
    inset 0 1px 0 var(--glass-spec),
    inset 0 -1px 0 rgba(255, 255, 255, 0.04),
    0 10px 26px -20px rgba(0, 0, 0, 0.75);

  --t0: #f0b063; --t0-bg: rgba(255, 180, 60, 0.20);
  --t1: #82b4f5; --t1-bg: rgba(70, 140, 240, 0.22);
  --t2: #6ddba6; --t2-bg: rgba(40, 180, 120, 0.22);
  --t3: #b39cf5; --t3-bg: rgba(140, 110, 235, 0.24);
  --t4: #9aa8f7; --t4-bg: rgba(100, 120, 230, 0.24);
  --t5: #6fd0e8; --t5-bg: rgba(40, 175, 205, 0.22);
  --t6: #f090b4; --t6-bg: rgba(220, 90, 140, 0.22);
  --t7: #5fdccb; --t7-bg: rgba(20, 175, 160, 0.22);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
}

html {
  /* 双击不再放大。manipulation = 允许滚动 + 双指缩放，但屏蔽双击缩放。
     这是目前覆盖最广的做法（iOS 13+ / Android Chrome 都认），
     viewport 里的 user-scalable=no 只作为老设备兜底，两个一起上才稳。
     注意：touch-action 只管手势，不影响滚动模型，可以放心写在 html 上。 */
  touch-action: manipulation;
  /* 部分安卓浏览器横屏时会自作主张放大字号，钉死它 */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  /* 横向滑到边界时不要再带着整页回弹 */
  overscroll-behavior-x: none;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC',
    'Hiragino Sans GB', 'Microsoft YaHei', 'Noto Sans SC', sans-serif;
  background: var(--bg);
  color: var(--text);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -webkit-tap-highlight-color: transparent;
  /* 锁死整页横向平移。
     这一行必须写在 body 上、而且 html 上不能有 overflow ——
     html 的 overflow 是 visible 时，body 的 overflow 会被浏览器应用到视口上，
     于是视口横向滚不动；一旦在 html 上也写 overflow-x，
     滚动会变成「html 滚 + body 内部再滚」两层：window.scrollY 永远为 0、
     window.scrollBy 失效（拖动排序的边缘自动滚动会静默失灵），
     iOS 上还会丢掉地址栏收起和惯性滚动的原生手感。 */
  overflow-x: hidden;
  overscroll-behavior-x: none;
}

/* 环境光：四角各一团彩色雾。
   玻璃要「有东西可折射」才像玻璃 —— 背景是纯色的话，再好的配方也只是一张白卡片。
   控制在 .45~.55 的浓度：能看出来是彩色的，但盯一天不会累。 */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(880px 620px at 4% -8%, rgba(133, 199, 255, 0.55), transparent 62%),
    radial-gradient(780px 560px at 98% 0%, rgba(196, 170, 255, 0.46), transparent 62%),
    radial-gradient(760px 580px at 92% 104%, rgba(255, 199, 160, 0.42), transparent 60%),
    radial-gradient(700px 540px at 0% 96%, rgba(150, 235, 208, 0.46), transparent 60%);
}
[data-theme='dark'] body::before {
  background:
    radial-gradient(880px 620px at 4% -8%, rgba(20, 118, 150, 0.55), transparent 62%),
    radial-gradient(780px 560px at 98% 0%, rgba(86, 64, 168, 0.50), transparent 62%),
    radial-gradient(760px 580px at 92% 104%, rgba(150, 60, 88, 0.38), transparent 60%),
    radial-gradient(700px 540px at 0% 96%, rgba(16, 104, 92, 0.44), transparent 60%);
}

button, input, textarea {
  font-family: inherit;
  font-size: inherit;
  color: inherit;
}
button { cursor: pointer; border: none; background: none; }
::selection { background: var(--primary-soft); }

/* 滚动条 */
*::-webkit-scrollbar { width: 9px; height: 9px; }
*::-webkit-scrollbar-thumb {
  background: var(--line-strong);
  border-radius: 9px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
*::-webkit-scrollbar-track { background: transparent; }

/* ============================================================
   顶部栏
   ============================================================ */
.topbar {
  position: sticky;
  top: 0;
  z-index: 30;
  /* 内容从这条悬浮栏下面滚过 —— 这里必须真的模糊，玻璃才有意义（27 版的 scroll edge） */
  background: var(--glass-fill-soft);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  /* 不用 border-bottom：硬分割线会把「悬浮感」压扁，改成 0.5px 的暗边 */
  box-shadow: 0 0.5px 0 var(--glass-edge);
}

.topbar-inner {
  max-width: 1120px;
  margin: 0 auto;
  padding: 14px 20px 10px;
  display: flex;
  align-items: center;
  gap: 16px;
}

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-shrink: 0;
}
.brand-mark {
  width: 34px;
  height: 34px;
  border-radius: 11px;
  display: grid;
  place-items: center;
  color: #fff;
  background: linear-gradient(145deg, var(--primary), var(--primary-strong));
  box-shadow: 0 6px 16px -8px var(--primary), inset 0 1px 0 rgba(255, 255, 255, 0.28);
}
.brand-text {
  display: flex;
  flex-direction: column;
  line-height: 1.2;
}
.brand-name {
  font-size: calc(15.5px * var(--fs-scale));
  font-weight: 650;
  letter-spacing: 0.2px;
}
.brand-sub {
  font-size: calc(11px * var(--fs-scale));
  color: var(--text-3);
  letter-spacing: 0.6px;
}

.search-wrap {
  position: relative;
  flex: 1;
  max-width: 460px;
  margin: 0 auto;
}
.search-wrap .s-icon {
  position: absolute;
  left: 13px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-3);
  pointer-events: none;
  display: grid;
  place-items: center;
}
.search-wrap input {
  width: 100%;
  height: 42px;
  padding: 0 40px 0 40px;
  border-radius: 999px;
  border: 0;
  background: var(--glass-fill);
  box-shadow: var(--glass-shadow);
  outline: none;
  font-size: calc(14px * var(--fs-scale));
  transition: box-shadow 0.18s, background 0.18s;
}
.search-wrap input::placeholder { color: var(--text-3); }
/* 万一浏览器仍强填登录信息（黄底白字），用内阴影把底色和字色压回正常主题色 */
.search-wrap input:-webkit-autofill,
.search-wrap input:-webkit-autofill:hover,
.search-wrap input:-webkit-autofill:focus {
  -webkit-box-shadow: 0 0 0 1000px var(--surface) inset, 0 0 0 0.5px var(--glass-edge);
  -webkit-text-fill-color: var(--text);
  caret-color: var(--text);
  transition: background-color 99999s ease-in-out 0s;
}
/* 改密弹窗输入框：type=text + 圆点遮罩（页面上不能出现 type=password，
   否则密码管理器把本页当登录页、点搜索框弹账号下拉 —— 灵感速记同款方案）。
   注意：Firefox 不支持 -webkit-text-security，会明文显示，可接受。 */
.pwd-mask {
  -webkit-text-security: disc;
}
.search-wrap input:focus {
  /* 只加一圈主色光环，不改底色 —— 深色主题下改底色要再写一套覆盖，不值当 */
  box-shadow:
    0 0 0 0.5px var(--primary-line),
    0 0 0 4px var(--primary-soft),
    inset 0 1px 0 var(--glass-spec);
}
.search-wrap .s-clear {
  position: absolute;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  width: 26px;
  height: 26px;
  border-radius: 999px;
  color: var(--text-3);
  display: none;
  place-items: center;
}
.search-wrap .s-clear.on { display: grid; }
.search-wrap .s-clear:hover { background: var(--surface-3); color: var(--text); }

.top-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}
.stat-chip {
  display: none;
  align-items: center;
  gap: 6px;
  height: 34px;
  padding: 0 13px;
  border-radius: 999px;
  background: var(--glass-fill-soft);
  box-shadow: var(--glass-shadow);
  font-size: calc(12.5px * var(--fs-scale));
  color: var(--text-2);
  white-space: nowrap;
}
.stat-chip b { color: var(--text); font-weight: 650; }
.icon-btn {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  color: var(--text-2);
  background: var(--glass-fill);
  box-shadow: var(--glass-shadow);
  transition: color 0.16s, box-shadow 0.16s, transform 0.16s;
}
.icon-btn:hover { color: var(--text); transform: translateY(-1px); }
.icon-btn:active { transform: translateY(0) scale(0.94); }
/* 「已选中」的圆形按钮：主色玻璃，不是实心色块 */
.icon-btn.on {
  color: #ffffff;
  background: linear-gradient(180deg, rgba(24, 196, 178, 0.96), rgba(12, 138, 126, 0.94));
  box-shadow:
    0 0 0 0.5px rgba(8, 70, 64, 0.40),
    inset 0 1px 0 rgba(255, 255, 255, 0.55),
    0 8px 18px -10px rgba(10, 120, 110, 0.65);
}

/* ============================================================
   主体
   ============================================================ */
.board {
  position: relative;
  z-index: 1;
  max-width: 1120px;
  margin: 0 auto;
  padding: 20px 20px 120px;
  min-height: 62vh;
  /* 这块区域里的横向手势全归 JS（左滑出操作区、空白处切分类），
     纵向仍然交给浏览器滚动。不写这一行，浏览器会先抢走横向手势，
     发一个 pointercancel 过来，表现就是「左滑拉不动/拉一半断掉」。 */
  touch-action: pan-y;
}

.list {
  display: grid;
  /* 三种排序（自定义 / 按使用频率 / 按最新添加）统一走单列。
     原来「频率」和「最新」是 PC 多列自适应，同一屏从左到右再换行，
     「第 1 条」在视觉上反而不如单列一眼可读；自定义排序本来就是单列，
     索性三种拉齐，切换排序时版式不再跳。 */
  grid-template-columns: 1fr;
  max-width: 680px;
  margin: 0 auto;
  /* 卡片圆角变大之后，卡片之间的缝要收窄：圆角 24 配 14px 间距会显得松散 */
  gap: 10px;
  align-items: start;
}

/* 分组标题（按热度/最新分组时） */
.group-head {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 6px 2px -2px;
  font-size: calc(12px * var(--fs-scale));
  color: var(--text-3);
  letter-spacing: 0.8px;
}
.group-head::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--line);
}

/* ---------------- 话术卡片 ---------------- */
.item {
  position: relative;
  border-radius: var(--radius);
  overflow: hidden;
  /* 透明：卡片左滑后，操作块之间的缝隙要透出背后的环境光，不能是一块死灰 */
  background: transparent;
  contain: paint;
  transition: transform 0.22s var(--ease);
  /* 关键：卡片只允许纵向被浏览器接管，横向（左滑出按钮）
     完全由 JS 处理，手势不会再被中途 cancel 掉 */
  touch-action: pan-y;
}
.item.removing {
  animation: itemOut 0.28s var(--ease) forwards;
  pointer-events: none;
}
.item.entering { animation: itemIn 0.34s var(--ease) both; }

/* ---------------- 自定义排序：拖拽 ---------------- */
.list.can-drag .card { cursor: grab; }
.list.can-drag .card:active { cursor: grabbing; }

/* 占位槽：被拖走的卡片留在原地的空位 */
.drag-placeholder {
  border-radius: var(--radius);
  border: 1.5px dashed var(--primary-line);
  background: var(--primary-soft);
  transition: none;
}

.item.dragging {
  z-index: 80;
  pointer-events: none;
}
.item.dragging .card,
.item.dragging:hover .card {
  transform: scale(1.015);
  box-shadow:
    0 0 0 0.5px var(--primary-line),
    inset 0 1px 0 var(--glass-spec),
    0 34px 60px -30px rgba(12, 32, 45, 0.60);
}

body.dragging-active {
  user-select: none;
  -webkit-user-select: none;
  cursor: grabbing;
}
body.dragging-active .topbar { pointer-events: none; }

@keyframes itemOut {
  to { opacity: 0; transform: scale(0.94) translateY(-6px); }
}
@keyframes itemIn {
  from { opacity: 0; transform: translateY(10px) scale(0.98); }
}

/* 左滑后露出的操作区 */
.item-actions {
  position: absolute;
  top: 0;
  right: 0;
  height: 100%;
  display: flex;
  align-items: stretch;
  z-index: 0;
  /* ★ 只有卡片真的被滑开（或被跟手拖动）时才允许这一层被绘制。
     默认 visibility: hidden 而不是靠「卡片盖住」——
     之前手机上快速滑动时右下角会闪出这三个按钮，排查后确认：
     卡片自身没有任何横向位移（左边缘逐帧都在同一像素上），
     闪的是这层操作区在快速滚动的那一两帧里「没被卡片层盖住」。
     给 .card 加了 will-change: transform 之后每张卡都是独立合成层，
     快速滚动时合成器可能有一两帧来不及把卡片层合上去，
     而这层操作区在 z-index 0 上、一直躺在下面，于是当场露出来。
     现在把「可见性」收进 CSS：没滑开就根本不绘制，任何层的不同步都闪不出来。 */
  visibility: hidden;
  /* 上下留白 + 单个按钮各自圆角 = 三个独立的着色玻璃块，
     块与块之间的缝透出背后的环境光（.item 已改成 transparent）。
     宽度仍然是 3×68=204，和 app.js 里的 OPEN_W 对得上，手势逻辑一个字没动。 */
  padding: 9px 0;
  gap: 0;
}
.item.act-live .item-actions { visibility: visible; }
.act-btn {
  width: 68px;
  border-radius: 18px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  font-size: calc(11.5px * var(--fs-scale));
  font-weight: 700;
  letter-spacing: 0.1px;
  background: var(--act-bg);
  box-shadow:
    0 0 0 0.5px rgba(12, 32, 45, 0.10),
    inset 0 1px 0 rgba(255, 255, 255, 0.78),
    0 6px 14px -10px rgba(12, 32, 45, 0.45);
  transition: filter 0.16s;
}
.act-btn:active { filter: brightness(0.94); }
/* 三个动作各自一色：置顶琥珀（危险感最低）、编辑蓝、删除红。
   比原来三块实心纯色轻，但颜色区分更清楚。 */
.act-pin   { --act-bg: linear-gradient(180deg, #ffe7bb, #ffd07e); color: #8a5a00; }
.act-edit  { --act-bg: linear-gradient(180deg, #d5e7ff, #aacbf8); color: #1a56ab; }
.act-del   { --act-bg: linear-gradient(180deg, #ffdad7, #ffb3ae); color: #a8342f; }
/* 已置顶时把「置顶」换成「取消置顶」的语义色（主色玻璃），一眼能看出当前状态 */
.act-pin.on { --act-bg: linear-gradient(180deg, #b8ede4, #86ddd0); color: #0a5f57; }

[data-theme='dark'] .act-pin  { --act-bg: linear-gradient(180deg, rgba(255, 190, 80, 0.26), rgba(255, 168, 58, 0.18)); color: #ffcb7d; }
[data-theme='dark'] .act-edit { --act-bg: linear-gradient(180deg, rgba(96, 154, 244, 0.28), rgba(74, 128, 232, 0.20)); color: #a7c8ff; }
[data-theme='dark'] .act-del  { --act-bg: linear-gradient(180deg, rgba(232, 96, 92, 0.28), rgba(210, 74, 70, 0.20)); color: #ffa9a4; }
[data-theme='dark'] .act-pin.on { --act-bg: linear-gradient(180deg, rgba(46, 196, 176, 0.30), rgba(30, 167, 149, 0.22)); color: #86efdf; }
[data-theme='dark'] .act-btn {
  box-shadow:
    0 0 0 0.5px rgba(0, 0, 0, 0.45),
    inset 0 1px 0 rgba(255, 255, 255, 0.22),
    0 6px 14px -10px rgba(0, 0, 0, 0.6);
}

.card {
  position: relative;
  z-index: 1;
  height: 100%;
  /* 卡片现在只有「标题行 + 正文」两块（页脚那行已去掉），
     104px 是「一行标题 + 两行正文」的自然高度，再矮的卡片靠它兜底，
     免得同排卡片高矮不齐。手机上会覆盖成 0，让卡片彻底跟内容走。 */
  min-height: 104px;
  padding: 15px 17px 14px;
  /* ★ 玻璃：半透明填充 + 暗边 + 顶部镜面高光。
     这里**故意不加 backdrop-filter** —— 卡片背后只有那层柔和的环境光渐变，
     模糊一块本来就平滑的渐变肉眼看不出差别，但会让十几张卡各建一个合成层，
     手机滚动直接掉帧。（当初那个「闪按钮」的 bug 就和合成层有关，别再走回去。） */
  background: var(--glass-fill);
  border: 0;
  border-radius: var(--radius);
  box-shadow: var(--glass-shadow);
  display: flex;
  flex-direction: column;
  gap: 8px;
  /* ★ 这里原来是无条件写 will-change: transform，等于把列表里每一张卡
     都常驻提升成独立合成层。快速滚动时合成器一旦有一两帧没跟上，
     卡片层就会「缺位」，把下面那层操作区露出来（就是那个一闪的按钮）。
     改成只在真正跟手拖动的那几帧里提升（.gesture-live），
     既能保住拖动的流畅度，又不会让几十张卡全程各占一个层。 */
  transition: transform 0.26s var(--ease), box-shadow 0.22s;
  /* 底部那行「点击复制」删掉之后，PC 上就只剩这个光标提示「整张卡片是可点的」。
     能拖动时会被 `.list.can-drag .card` 的 grab 覆盖。 */
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  /* 和 .item 上那行是同一个意思，写在这里是为了语义清楚：
     卡片上的横向手势归 JS，纵向归浏览器 */
  touch-action: pan-y;
}
.card.no-anim { transition: none; }
/* 只在手指真正跟手拖动卡片的那几帧做层提升 */
.card.gesture-live { will-change: transform; }

@media (hover: hover) {
  /* 上浮作用在整个 .item 上，而不是只给 .card：
     只抬卡片的话，.item 底部会露出 2px 的「编辑/删除」操作区（一条红蓝色块）。 */
  .item:hover { transform: translateY(-2px); }
  .item:hover .card {
    box-shadow:
      0 0 0 0.5px var(--glass-edge),
      inset 0 1px 0 var(--glass-spec),
      inset 0 -1px 0 rgba(255, 255, 255, 0.34),
      0 20px 36px -24px rgba(12, 32, 45, 0.55);
  }
}

.card-top {
  display: flex;
  align-items: flex-start;
  gap: 8px;
}

/* ---------------- 置顶标识 ---------------- */
/* 置顶的话术在所有排序方式下都排在最前面，所以卡片上要有明确的「钉住」标识，
   否则用户切到「按使用频率」时会以为自己的置顶丢了。
   27 版的做法：琥珀色药丸（图标 + 文字）+ 卡片左侧一道发光细条，两处一起提示。 */
.pin-mark {
  display: none;
  flex-shrink: 0;
  align-items: center;
  gap: 3px;
  height: 21px;
  margin: 0;
  padding: 0 6px 0 5px;
  border-radius: 7px;
  color: #8a5a00;
  background: rgba(255, 186, 60, 0.22);
  font-size: calc(10.5px * var(--fs-scale));
  font-weight: 700;
  letter-spacing: 0.1px;
}
.pin-mark::after { content: '置顶'; }
.item.pinned .pin-mark { display: inline-flex; }
[data-theme='dark'] .pin-mark { color: #ffcb7d; background: rgba(255, 180, 60, 0.20); }

.card.pinned::before {
  content: '';
  position: absolute;
  left: 0;
  top: 20px;
  bottom: 20px;
  width: 3px;
  border-radius: 0 3px 3px 0;
  background: linear-gradient(180deg, #ffc95e, #e89a12);
  box-shadow: 0 0 12px rgba(232, 154, 18, 0.55);
}

.card-title {
  flex: 1;
  min-width: 0;
  font-size: calc(16px * var(--fs-scale));
  font-weight: 660;
  /* 负字距是「SF Pro 味」的主要来源：大字号下收紧一点才不像系统默认字体 */
  letter-spacing: -0.34px;
  line-height: 1.32;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  word-break: break-word;
}
.card-hot {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: 3px;
  height: 21px;
  padding: 0 2px;
  /* 27 版这里不再是一个灰底药丸：次数是辅助信息，让它安静地待在右上角 */
  background: transparent;
  color: var(--text-3);
  font-size: calc(12px * var(--fs-scale));
  font-weight: 640;
  font-variant-numeric: tabular-nums;
  transition: color 0.2s;
}
.card-hot.is-hot { color: var(--primary-strong); }
[data-theme='dark'] .card-hot.is-hot { color: var(--primary); }
.card-hot.pop { animation: hotPop 0.42s var(--ease); }
@keyframes hotPop {
  50% { transform: scale(1.22); }
}

.card-body {
  flex: 1;
  font-size: calc(13.5px * var(--fs-scale));
  /* 1.72 → 1.55：圆角变大、留白变松之后，正文再撑那么开两行会显得散 */
  line-height: 1.55;
  color: var(--text-2);
  white-space: pre-wrap;
  word-break: break-word;
  /* 正文两端都只显示 2 行 —— 列表要解决的是「扫一眼认出是哪条」，
     不是「在这儿读完全文」。行数写在这里一处，手机上不再另外覆盖。
     截断只是显示层：点击复制拿到的始终是完整内容，左滑「编辑」里也是全文。 */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* 分类小标：放在标题前面，当「标签 + 标题」读。
   flex-shrink: 0 + 较紧的 max-width —— 它是识别用的辅助信息，不能把标题挤到没有位置。
   颜色按分类名哈希分配（t-0 ~ t-7），见 app.js 的 catTint()：
   分类是用户自己填的，没法穷举，但同一个名字必须永远同一个颜色。 */
.card-cat {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  height: 21px;
  padding: 0 7px;
  border-radius: 7px;
  background: var(--t0-bg);
  color: var(--t0);
  font-size: calc(10.5px * var(--fs-scale));
  font-weight: 700;
  letter-spacing: 0.1px;
  max-width: 96px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.card-cat.t-0 { color: var(--t0); background: var(--t0-bg); }
.card-cat.t-1 { color: var(--t1); background: var(--t1-bg); }
.card-cat.t-2 { color: var(--t2); background: var(--t2-bg); }
.card-cat.t-3 { color: var(--t3); background: var(--t3-bg); }
.card-cat.t-4 { color: var(--t4); background: var(--t4-bg); }
.card-cat.t-5 { color: var(--t5); background: var(--t5-bg); }
.card-cat.t-6 { color: var(--t6); background: var(--t6-bg); }
.card-cat.t-7 { color: var(--t7); background: var(--t7-bg); }

/* 桌面端悬停快捷按钮 */
.hover-acts {
  position: absolute;
  top: 11px;
  right: 11px;
  display: none;
  gap: 5px;
  opacity: 0;
  transform: translateY(-3px);
  transition: opacity 0.18s, transform 0.18s;
  z-index: 3;
}
@media (hover: hover) and (pointer: fine) {
  .hover-acts { display: flex; }
  .item:hover .hover-acts { opacity: 1; transform: translateY(0); }
  .item:hover .card-hot { opacity: 0; transition: opacity 0.15s; }
}
.mini-btn {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--glass-fill);
  border: 0;
  color: var(--text-2);
  box-shadow: var(--glass-shadow);
  transition: color 0.15s, box-shadow 0.15s, transform 0.15s;
}
.mini-btn:hover { color: var(--text); transform: translateY(-1px); }
.mini-btn.danger:hover {
  color: var(--danger);
  box-shadow: 0 0 0 0.5px var(--danger-soft), inset 0 1px 0 var(--glass-spec), 0 6px 14px -10px rgba(217, 83, 79, 0.55);
}
.mini-btn.pin:hover {
  color: var(--primary);
  box-shadow: 0 0 0 0.5px var(--primary-line), inset 0 1px 0 var(--glass-spec), 0 6px 14px -10px var(--primary);
}
/* 已置顶时按钮常亮主色。要连 :hover 一起写（(0,4,0)），
   否则鼠标停在按钮上时会被上面那条 hover 规则的颜色盖掉 */
.mini-btn.pin.on,
.mini-btn.pin.on:hover {
  color: #fff;
  background: linear-gradient(180deg, rgba(24, 196, 178, 0.96), rgba(12, 138, 126, 0.94));
  box-shadow:
    0 0 0 0.5px rgba(8, 70, 64, 0.40),
    inset 0 1px 0 rgba(255, 255, 255, 0.55),
    0 6px 14px -10px rgba(10, 120, 110, 0.65);
}

/* 复制成功反馈
   ⚠ 这里的四条选择器是必需的，别简化成单写 .card.copied：
   上面 @media (hover:hover) 里的 `.item:hover .card` 权重是 (0,3,0)，
   比 `.card.copied` 的 (0,2,0) 高。而 PC 上用鼠标点击时，光标必然停在卡片上，
   于是主色描边和 4px 光晕会被悬停样式整个盖掉——只剩中间一个小徽章闪一下。
   显式把 hover 态一起写上（权重更高、位置也更靠后）才能压回去。 */
.card.copied,
.card.copied:hover,
.item:hover .card.copied,
.item:hover .card.copied:hover {
  box-shadow:
    0 0 0 1.5px var(--primary),
    0 0 0 5px var(--primary-soft),
    inset 0 1px 0 var(--glass-spec);
}
.ripple {
  position: absolute;
  inset: 0;
  border-radius: var(--radius);
  pointer-events: none;
  z-index: 2;
  background: radial-gradient(circle at var(--rx, 50%) var(--ry, 50%),
    rgba(15, 157, 143, 0.30), rgba(15, 157, 143, 0) 62%);
  animation: rippleOut 0.62s var(--ease) forwards;
}
@keyframes rippleOut {
  from { opacity: 1; transform: scale(0.5); }
  to { opacity: 0; transform: scale(1.9); }
}
.copy-badge {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) scale(0.7);
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 13px 24px;
  border-radius: 20px;
  /* 着色玻璃：不是纯色块。底色比主色暗一档，配顶部镜面高光才有「一块玻璃」的厚度 */
  background: linear-gradient(180deg, rgba(24, 196, 178, 0.96), rgba(12, 138, 126, 0.93));
  color: #fff;
  /* 比正文大一档：这个徽章要让人一眼看到，不能比卡片里的字还小 */
  font-size: calc(16px * var(--fs-scale));
  font-weight: 680;
  letter-spacing: 0.4px;
  white-space: nowrap;
  box-shadow:
    0 0 0 0.5px rgba(8, 70, 64, 0.45),
    inset 0 1px 0 rgba(255, 255, 255, 0.55),
    inset 0 -1px 0 rgba(0, 0, 0, 0.12),
    0 18px 38px -14px rgba(10, 120, 110, 0.62);
  z-index: 4;
  opacity: 0;
  pointer-events: none;
  /* 停留时长要和 app.js 里的 COPY_FEEDBACK 对齐，否则会被 JS 提前摘掉 */
  animation: badgePop 1.5s var(--ease) forwards;
}
.copy-badge svg { width: 19px; height: 19px; flex-shrink: 0; }
@keyframes badgePop {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(0.72); }
  14%  { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  86%  { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -52%) scale(0.95); }
}

/* 空状态 */
.empty {
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 76px 24px;
  text-align: center;
  color: var(--text-3);
}
.empty.on { display: flex; }
.empty-mark {
  width: 66px;
  height: 66px;
  border-radius: 22px;
  display: grid;
  place-items: center;
  color: var(--primary);
  background: var(--primary-soft);
  border: 1px solid var(--primary-line);
}
.empty-title { font-size: calc(15px * var(--fs-scale)); font-weight: 620; color: var(--text-2); }
.empty-desc { font-size: calc(13px * var(--fs-scale)); max-width: 320px; line-height: 1.7; }
.empty-btn {
  margin-top: 4px;
  height: 40px;
  padding: 0 20px;
  border-radius: 999px;
  background: linear-gradient(180deg, rgba(24, 196, 178, 0.97), rgba(12, 138, 126, 0.95));
  color: #fff;
  font-size: calc(13.5px * var(--fs-scale));
  font-weight: 620;
  box-shadow:
    0 0 0 0.5px rgba(8, 70, 64, 0.40),
    inset 0 1px 0 rgba(255, 255, 255, 0.55),
    0 12px 26px -14px rgba(10, 120, 110, 0.70);
}

/* ============================================================
   浮动新增按钮
   ============================================================ */
.fab {
  position: fixed;
  right: 22px;
  bottom: 26px;
  z-index: 28;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 52px;
  padding: 0 22px 0 18px;
  border-radius: 999px;
  /* 主操作按钮用「着色玻璃」：底色是主色，但顶部有一道镜面高光、
     外圈有一道暗边，看起来是一块有厚度的玻璃而不是一块塑料 */
  background: linear-gradient(180deg, rgba(24, 196, 178, 0.97), rgba(12, 138, 126, 0.95));
  color: #fff;
  font-size: calc(14.5px * var(--fs-scale));
  font-weight: 640;
  letter-spacing: 0.1px;
  box-shadow:
    0 0 0 0.5px rgba(8, 70, 64, 0.45),
    inset 0 1px 0 rgba(255, 255, 255, 0.55),
    0 18px 34px -12px rgba(10, 120, 110, 0.70);
  transition: transform 0.2s var(--ease), box-shadow 0.2s;
}
.fab:hover {
  transform: translateY(-2px);
  box-shadow:
    0 0 0 0.5px rgba(8, 70, 64, 0.45),
    inset 0 1px 0 rgba(255, 255, 255, 0.55),
    0 24px 42px -14px rgba(10, 120, 110, 0.75);
}
.fab:active { transform: translateY(0) scale(0.97); }

/* ============================================================
   弹层：新增 / 编辑
   ============================================================ */
.modal {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  /* 遮挡层：压暗为主、模糊为辅。反过来的话（大模糊 + 弱压暗）整个屏幕会糊成一团灰，
     底下是什么都看不出来，弹层也没有「浮在页面之上」的感觉。 */
  background: rgba(6, 16, 22, 0.48);
  backdrop-filter: blur(6px) saturate(115%);
  -webkit-backdrop-filter: blur(6px) saturate(115%);
  animation: fadeIn 0.2s var(--ease);
}
.modal[hidden] { display: none; }
@keyframes fadeIn { from { opacity: 0; } }

.modal-card {
  position: relative;
  width: 100%;
  max-width: 520px;
  max-height: 88vh;
  overflow-y: auto;
  /* 接近不透明的面板（理由见 --sheet-fill）——玻璃感交给边缘：0.5px 暗边 + 顶部高光 */
  background: var(--sheet-fill);
  border: 0;
  border-radius: 28px;
  box-shadow:
    0 0 0 0.5px var(--glass-edge),
    inset 0 1px 0 var(--glass-spec),
    0 40px 90px -30px rgba(12, 32, 45, 0.55);
  padding: 22px;
  animation: sheetUp 0.28s var(--ease);
}
@keyframes sheetUp {
  from { opacity: 0; transform: translateY(18px) scale(0.98); }
}

.modal-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 18px;
}
/* 弹层里的圆形按钮同样不能用白玻璃（白压白等于消失） */
.modal-card .icon-btn {
  background: var(--field-fill);
  box-shadow: var(--field-inset);
  color: var(--text-2);
}
.modal-card .icon-btn:hover { color: var(--text); }
.modal-head h3 {
  margin: 0;
  font-size: calc(17px * var(--fs-scale));
  font-weight: 660;
  flex: 1;
}
.field { margin-bottom: 15px; }
.field > label {
  display: block;
  font-size: calc(12.5px * var(--fs-scale));
  font-weight: 620;
  color: var(--text-2);
  margin-bottom: 7px;
  letter-spacing: 0.2px;
}
.field .hint {
  font-weight: 400;
  color: var(--text-3);
  margin-left: 4px;
}
.field input[type='text'],
.field textarea {
  width: 100%;
  padding: 11px 13px;
  border-radius: 14px;
  border: 0;
  background: var(--field-fill);
  box-shadow: var(--field-inset);
  outline: none;
  font-size: calc(14px * var(--fs-scale));
  transition: box-shadow 0.18s;
}
.field textarea {
  min-height: 132px;
  resize: vertical;
  line-height: 1.7;
  font-size: calc(13.5px * var(--fs-scale));
}
.field input:focus, .field textarea:focus {
  box-shadow:
    0 0 0 0.5px var(--primary-line),
    0 0 0 4px var(--primary-soft),
    inset 0 1px 0 var(--glass-spec);
}
.count {
  text-align: right;
  font-size: calc(11.5px * var(--fs-scale));
  color: var(--text-3);
  margin-top: 5px;
  font-variant-numeric: tabular-nums;
}

.cat-picks {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  margin-top: 9px;
}
.cat-pick {
  height: 30px;
  padding: 0 12px;
  border-radius: 999px;
  border: 0;
  /* 和卡片上的分类小标同一套色：一眼就能对上「这个分类是哪一条」。
     原来是虚线空框，看着像占位符而不像能点的东西。 */
  color: var(--t0);
  background: var(--t0-bg);
  font-size: calc(12.5px * var(--fs-scale));
  font-weight: 620;
  transition: transform 0.16s var(--ease), filter 0.16s;
}
.cat-pick:hover { transform: translateY(-1px); filter: brightness(0.97); }
.cat-pick:active { transform: translateY(0) scale(0.97); }
.cat-pick.t-1 { color: var(--t1); background: var(--t1-bg); }
.cat-pick.t-2 { color: var(--t2); background: var(--t2-bg); }
.cat-pick.t-3 { color: var(--t3); background: var(--t3-bg); }
.cat-pick.t-4 { color: var(--t4); background: var(--t4-bg); }
.cat-pick.t-5 { color: var(--t5); background: var(--t5-bg); }
.cat-pick.t-6 { color: var(--t6); background: var(--t6-bg); }
.cat-pick.t-7 { color: var(--t7); background: var(--t7-bg); }

.modal-actions {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
  margin-top: 20px;
}
.btn {
  height: 42px;
  padding: 0 20px;
  border-radius: 14px;
  font-size: calc(14px * var(--fs-scale));
  font-weight: 620;
  transition: all 0.17s var(--ease);
}
.btn-ghost {
  /* 弹层面板是不透明的，白色玻璃在这里几乎看不见 —— 用「浅灰填充 + 内描边」当次级按钮 */
  background: var(--field-fill);
  border: 0;
  color: var(--text-2);
  box-shadow: var(--field-inset);
}
.btn-ghost:hover { color: var(--text); }
.btn-primary {
  background: linear-gradient(180deg, rgba(24, 196, 178, 0.97), rgba(12, 138, 126, 0.95));
  color: #fff;
  box-shadow:
    0 0 0 0.5px rgba(8, 70, 64, 0.40),
    inset 0 1px 0 rgba(255, 255, 255, 0.55),
    0 12px 26px -14px rgba(10, 120, 110, 0.70);
}
.btn-primary:hover { transform: translateY(-1px); }
.btn-primary:disabled { opacity: 0.6; cursor: not-allowed; transform: none; }
.btn-danger {
  background: linear-gradient(180deg, rgba(226, 96, 91, 0.97), rgba(190, 62, 57, 0.95));
  color: #fff;
  box-shadow:
    0 0 0 0.5px rgba(96, 26, 24, 0.40),
    inset 0 1px 0 rgba(255, 255, 255, 0.45),
    0 12px 26px -14px rgba(190, 62, 57, 0.70);
}

/* 确认框 */
.confirm-card {
  max-width: 400px;
  text-align: center;
  padding: 26px 22px 20px;
}
.confirm-ico {
  width: 54px;
  height: 54px;
  margin: 0 auto 14px;
  border-radius: 17px;
  display: grid;
  place-items: center;
  color: var(--danger);
  background: var(--danger-soft);
}
.confirm-card h3 { margin: 0 0 8px; font-size: calc(16.5px * var(--fs-scale)); font-weight: 660; }
.confirm-card p {
  margin: 0 0 6px;
  font-size: calc(13.5px * var(--fs-scale));
  color: var(--text-2);
  line-height: 1.7;
  word-break: break-word;
}
.confirm-card .modal-actions { justify-content: center; margin-top: 20px; }

/* 菜单面板 */
.menu {
  position: fixed;
  z-index: 70;
  min-width: 186px;
  padding: 6px;
  border-radius: 20px;
  background: var(--glass-fill);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 0;
  box-shadow:
    0 0 0 0.5px var(--glass-edge),
    inset 0 1px 0 var(--glass-spec),
    0 30px 60px -26px rgba(12, 32, 45, 0.55);
  animation: menuIn 0.16s var(--ease);
}
@keyframes menuIn { from { opacity: 0; transform: scale(0.96) translateY(-4px); } }
.menu-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 11px;
  border-radius: 13px;
  font-size: calc(13.5px * var(--fs-scale));
  color: var(--text-2);
  text-align: left;
}
.menu-item:hover { background: var(--primary-soft); color: var(--text); }
.menu-item.danger:hover { background: var(--danger-soft); color: var(--danger); }
.menu-item.on { color: var(--primary-strong); font-weight: 640; }
[data-theme='dark'] .menu-item.on { color: var(--primary); }
.menu-sep { height: 1px; margin: 5px 8px; background: var(--line); }
.menu-title {
  padding: 7px 11px 5px;
  font-size: calc(11px * var(--fs-scale));
  color: var(--text-3);
  letter-spacing: 0.6px;
}

/* Toast */
.toast {
  position: fixed;
  left: 50%;
  bottom: 34px;
  z-index: 90;
  transform: translate(-50%, 20px);
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 12px 18px;
  border-radius: 18px;
  background: var(--glass-fill);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 0;
  box-shadow:
    0 0 0 0.5px var(--glass-edge),
    inset 0 1px 0 var(--glass-spec),
    0 24px 50px -22px rgba(12, 32, 45, 0.55);
  font-size: calc(13.5px * var(--fs-scale));
  font-weight: 550;
  color: var(--text);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.24s var(--ease), transform 0.24s var(--ease);
  max-width: calc(100vw - 40px);
}
.toast.on { opacity: 1; transform: translate(-50%, 0); }
.toast .t-ico { display: grid; place-items: center; color: var(--primary); flex-shrink: 0; }
.toast.err .t-ico { color: var(--danger); }

/* 手动复制兜底 */
.manual {
  position: fixed;
  inset: 0;
  z-index: 95;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(6, 16, 22, 0.46);
  backdrop-filter: blur(10px) saturate(120%);
  -webkit-backdrop-filter: blur(10px) saturate(120%);
}
.manual textarea {
  width: 100%;
  max-width: 460px;
  height: 180px;
  padding: 14px;
  border-radius: 20px;
  border: 0;
  background: var(--glass-fill);
  box-shadow:
    0 0 0 0.5px var(--glass-edge),
    inset 0 1px 0 var(--glass-spec),
    0 30px 70px -30px rgba(12, 32, 45, 0.55);
  font-size: calc(14px * var(--fs-scale));
  line-height: 1.7;
  resize: none;
  outline: none;
}

/* ============================================================
   登录页
   ============================================================ */
.login-body {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.login-card {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 396px;
  padding: 30px 26px 24px;
  border-radius: 28px;
  background: var(--glass-fill);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 0;
  box-shadow:
    0 0 0 0.5px var(--glass-edge),
    inset 0 1px 0 var(--glass-spec),
    0 40px 90px -34px rgba(12, 32, 45, 0.55);
  animation: sheetUp 0.36s var(--ease);
}
.login-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  justify-content: center;
  margin-bottom: 6px;
}
.login-title {
  font-size: calc(19px * var(--fs-scale));
  font-weight: 680;
  letter-spacing: 0.3px;
}
.login-sub {
  text-align: center;
  font-size: calc(13px * var(--fs-scale));
  color: var(--text-3);
  margin: 0 0 24px;
}
.login-card .field > label {
  font-size: calc(12.5px * var(--fs-scale));
}
.pwd-wrap { position: relative; }
.pwd-toggle {
  position: absolute;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  height: 28px;
  padding: 0 10px;
  border-radius: 8px;
  font-size: calc(12.5px * var(--fs-scale));
  color: var(--text-3);
}
.pwd-toggle:hover { background: var(--surface-3); color: var(--text); }
.check {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: calc(13px * var(--fs-scale));
  color: var(--text-2);
  margin: 11px 0;
  cursor: pointer;
  user-select: none;
}
.check input {
  width: 16px;
  height: 16px;
  accent-color: var(--primary);
  cursor: pointer;
  flex-shrink: 0;
}
.check .warn { color: var(--warn); font-style: normal; font-size: calc(11.5px * var(--fs-scale)); }
.login-err {
  margin: 12px 0 4px;
  padding: 10px 13px;
  border-radius: 11px;
  background: var(--danger-soft);
  color: var(--danger);
  font-size: calc(13px * var(--fs-scale));
  line-height: 1.6;
}
.login-submit { width: 100%; margin-top: 12px; height: 46px; font-size: calc(15px * var(--fs-scale)); }
.login-foot {
  margin: 18px 0 0;
  text-align: center;
  font-size: calc(11.5px * var(--fs-scale));
  color: var(--text-3);
  line-height: 1.7;
}
.login-foot code {
  font-size: calc(11px * var(--fs-scale));
  padding: 1px 5px;
  border-radius: 5px;
  background: var(--surface-3);
}

/* ============================================================
   响应式
   ============================================================ */
@media (min-width: 780px) {
  /* 桌面显示器离得远、屏幕大，手机那套字号在 PC 上明显偏小。
     这里把整页文字统一放大 10%，只动字号、不动间距。 */
  :root { --fs-scale: 1.1; }

  .stat-chip { display: inline-flex; }
  .topbar-inner { padding: 16px 24px 12px; gap: 22px; }
  .board { padding: 24px 24px 120px; }

  /* 字号变大后，卡片内边距和标题行要跟着松一点，不然会显得挤。
     min-height 不再单独覆盖 —— 基础规则的 104px 是按 PC 的字号算的。 */
  .card { padding: 17px 19px 16px; gap: 9px; }
  .act-btn { font-size: calc(12px * var(--fs-scale)); }

  /* 「已复制」徽章在 PC 上再单独大一档。
     基础值 15.5px 乘完 --fs-scale 只有 17px，而 PC 的卡片又宽又大、
     视线离屏幕也远，扫一眼容易漏掉这个反馈。这里给到 18.7px（+10%），
     图标、内边距、间距同步放大，保持比例。手机端维持不变。 */
  .copy-badge {
    font-size: calc(17px * var(--fs-scale));
    padding: 14px 26px;
    gap: 10px;
  }
  .copy-badge svg { width: 21px; height: 21px; }
}

@media (max-width: 779px) {
  /* 手机端整体放大一档。之前手机是 1，字偏小；
     这一处改完所有字号一起变（文件里所有 font-size 都乘这个系数）。 */
  :root { --fs-scale: 1.08; }

  .topbar-inner {
    padding: 12px 14px 8px;
    gap: 10px;
    flex-wrap: wrap;
  }
  .brand-text { display: none; }
  .search-wrap {
    order: 3;
    flex: 1 1 100%;
    max-width: none;
    margin: 4px 0 0;
  }
  .search-wrap input { height: 46px; }
  /* iOS 上输入框字号小于 16px 时，一聚焦就会把整页顶大、还不会自己缩回去 ——
     这是「屏幕突然变大」的另一条来源（和双击缩放是两回事），所以手机上钉死 16px 起。
     选择器要写成和上面基础规则同特异性（.field input[type='text'] 是 (0,2,1)），
     否则会被上面的 14px 压掉，这里写了也白写。 */
  .search-wrap input,
  .field input[type='text'],
  .field input[type='password'],
  .field textarea,
  .manual textarea {
    font-size: max(16px, calc(15px * var(--fs-scale)));
  }
  .top-actions { margin-left: auto; }
  .board { padding: 14px 14px 130px; }
  .list { gap: 9px; }
  .card { padding: 14px 16px 13px; min-height: 0; }
  /* 手机上行高再收一点（行数在基础规则里，两端统一 2 行）：
     屏幕窄、一行字少，1.55 的行距两行刚好。 */
  .card-body {
    line-height: 1.58;
    font-size: calc(14px * var(--fs-scale));
  }
  .hover-acts { display: none !important; }

  /* 手机端底部悬浮按钮改为大圆钮 */
  .fab {
    right: 16px;
    bottom: calc(18px + env(safe-area-inset-bottom, 0px));
    height: 54px;
    width: 54px;
    padding: 0;
    justify-content: center;
    border-radius: 50%;
  }
  .fab .fab-label { display: none; }
  .toast { bottom: calc(88px + env(safe-area-inset-bottom, 0px)); }

  /* 手机上编辑弹层贴底，像原生 sheet */
  .modal { align-items: flex-end; padding: 0; }
  .modal-card {
    max-width: none;
    border-radius: 28px 28px 0 0;
    max-height: 92vh;
    padding: 22px 18px calc(22px + env(safe-area-inset-bottom, 0px));
    animation: sheetSlide 0.3s var(--ease);
  }
  /* 顶部拖动把手：iOS 的 sheet 靠这道横条告诉用户「这是一块可以拽下来的面板」。
     确认框是居中的卡片，不是 sheet，所以排除掉。 */
  .modal:not(.confirm-wrap) .modal-card { padding-top: 28px; }
  .modal:not(.confirm-wrap) .modal-card::before {
    content: '';
    position: absolute;
    top: 9px;
    left: 50%;
    transform: translateX(-50%);
    width: 38px;
    height: 5px;
    border-radius: 3px;
    background: var(--line-strong);
  }
  @keyframes sheetSlide {
    from { transform: translateY(100%); }
  }
  .confirm-card { border-radius: 28px; margin: 0 14px; }
  .modal.confirm-wrap { align-items: center; padding: 20px; }
  .confirm-card { animation: sheetUp 0.28s var(--ease); }
}

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
  /* 动效被压成 0 时，徽章的动画会直接停在「淡出完成」那一帧，等于完全看不见。
     这类电脑（Windows 关掉「动画效果」）上把徽章改成静态显示，靠 JS 定时摘掉。 */
  .copy-badge { animation: none !important; opacity: 1; }
  .ripple { display: none; }
}
