/* ===========================================================================
   设计系统 —— 精品科技感
   ---------------------------------------------------------------------------
   【为什么把样式从 index.html 里拿出来】原来是一个 <style> 块内联在 HTML 里，
   而 CSP 的 style-src 因此不得不留着 'unsafe-inline'。拆出来之后那个例外可以去掉，
   顺带让样式有了自己的位置：改配色不用翻 100KB 的页面文件。

   【令牌先于组件】下面每一个具体样式都只引用变量，不写死数值。这不是为了"好看"，
   是为了让深色模式、密度调整、品牌换色各只有一处需要改 —— 而不是全局搜索
   #2563eb 然后祈祷没漏。
   =========================================================================== */

:root {
  /* --- 中性色阶。电商页面 90% 的像素是中性色，它的层次决定了"精致"与否 --- */
  --bg:        #ffffff;
  --surface:   #ffffff;   /* 卡片底 —— 与 bg 同色时靠阴影和描边分层 */
  --subtle:    #f6f7f9;   /* 输入框、次级区块 */
  --line:      #e6e8eb;
  --line-soft: #f0f1f3;
  --fg:        #14161a;   /* 正文。纯黑太硬，近黑更贵 */
  --fg-muted:  #5f6570;
  --fg-faint:  #8b919b;

  /* --- 强调色。只用在两处：主 CTA 与价格。用得越少，它出现时越有分量 --- */
  --accent:      #1355ff;
  --accent-hover:#0c46da;
  --accent-weak: #eef3ff;
  --on-accent:   #ffffff;

  /* --- 语义色。库存、成功、告警 --- */
  --ok:    #0a7d40;
  --warn:  #9a6400;
  --err:   #c02626;
  --ok-bg: #e8f6ee;
  --warn-bg:#fdf3e2;
  --err-bg:#fdeceb;

  /* --- 圆角 --- */
  --r-sm: 8px;
  --r-md: 12px;
  --r-lg: 16px;
  --r-pill: 999px;

  /* --- 阴影。克制是关键：默认几乎看不见，hover 才抬起来 --- */
  --shadow-sm: 0 1px 2px rgba(16, 22, 32, .06), 0 0 0 1px rgba(16, 22, 32, .04);
  --shadow-md: 0 6px 20px rgba(16, 22, 32, .10), 0 0 0 1px rgba(16, 22, 32, .05);
  --shadow-pop: 0 16px 48px rgba(16, 22, 32, .18);

  /* --- 间距。4 的倍数，避免"看起来差不多但就是不齐" --- */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px;
  --s5: 20px; --s6: 24px; --s8: 32px; --s10: 40px;

  --maxw: 1280px;
  --header-h: 60px;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg:        #0c0d10;
    --surface:   #141619;
    --subtle:    #1a1d22;
    --line:      #262a31;
    --line-soft: #1e2127;
    --fg:        #eceef1;
    --fg-muted:  #a2a9b4;
    --fg-faint:  #7b828d;
    --accent:      #4d82ff;
    --accent-hover:#6b97ff;
    --accent-weak: #16213c;
    --ok: #4ade80;  --ok-bg: #10281b;
    --warn:#fbbf24; --warn-bg:#2a1f08;
    --err:#f87171;  --err-bg:#2c1414;
    --shadow-sm: 0 1px 2px rgba(0,0,0,.5), 0 0 0 1px rgba(255,255,255,.05);
    --shadow-md: 0 6px 20px rgba(0,0,0,.6), 0 0 0 1px rgba(255,255,255,.07);
    --shadow-pop: 0 16px 48px rgba(0,0,0,.7);
  }
}

* { box-sizing: border-box; }

/**
 * 【hidden 属性必须真的隐藏】UA 样式表里 [hidden] { display: none } 的特异性只有
 * (0,1,0)，任何一条带 id 的规则（比如 #gate { display: grid }）都会盖过它 ——
 * 于是 el.hidden = true 看着生效了、属性也确实设上了，元素却还在页面上。
 *
 * 后台就踩了这个：登录之后令牌闸门与主界面同时显示。而测试断言的是 .hidden
 * 这个属性值，属性确实是 true —— 所以一路绿灯。
 *
 * !important 在这里是对的：hidden 表达的是这个元素现在不存在于界面上，
 * 那不该是一条可以被布局规则商量的声明。
 */
[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font: 400 15px/1.55 ui-sans-serif, system-ui, -apple-system, "Segoe UI",
        "Microsoft YaHei", "PingFang SC", sans-serif;
  /* 数字用等宽字形：价格、库存、订单号在列表里能对齐，是"专业感"里很便宜的一分 */
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
}

/* 【无障碍：焦点必须看得见】鼠标点击不显示焦点环，键盘导航显示 ——
   focus-visible 正好表达这个区别。去掉 outline 而不给替代，是最常见的无障碍事故。 */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}
:focus:not(:focus-visible) { outline: none; }

/* ---------------------------------------------------------------------------
   顶栏
   --------------------------------------------------------------------------- */
header.site {
  position: sticky; top: 0; z-index: 40;
  height: var(--header-h);
  display: flex; align-items: center; gap: var(--s4);
  padding: 0 var(--s5);
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: saturate(180%) blur(12px);
  border-bottom: 1px solid var(--line);
}
.brand {
  display: flex; align-items: center; gap: var(--s2);
  font-size: 16px; font-weight: 650; letter-spacing: -.01em;
  margin: 0; flex: none;
}
.brand svg { flex: none; }

/* 搜索占据顶栏中部 —— 电商里它是第二重要的入口，仅次于商品本身 */
.search-wrap { flex: 1; max-width: 520px; position: relative; }
/* 【特异性要压过下面那条通用的 input[type=search]】两者特异性相同，
   而通用规则写在后面就会赢 —— 结果是左内边距丢失，放大镜图标压在占位文字上。
   加一层 header 把特异性提上去，比调整顺序更抗改动。 */
header .search-wrap input {
  width: 100%; height: 38px;
  padding: 0 var(--s4) 0 38px;
  border: 1px solid var(--line); border-radius: var(--r-pill);
  background: var(--subtle); color: var(--fg); font: inherit; font-size: 14px;
}
header .search-wrap input:focus { background: var(--surface); border-color: var(--accent); }
.search-wrap .icon {
  position: absolute; left: 13px; top: 50%; transform: translateY(-50%);
  color: var(--fg-faint); pointer-events: none; display: flex;
}
.header-actions { display: flex; align-items: center; gap: var(--s2); margin-left: auto; }

/* 图标按钮：44px 触摸目标（iOS HIG 的下限），视觉上只有 36px */
.icon-btn {
  position: relative;
  width: 38px; height: 38px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid transparent; border-radius: var(--r-pill);
  background: transparent; color: var(--fg); cursor: pointer;
}
.icon-btn:hover { background: var(--subtle); }
.icon-btn .badge {
  position: absolute; top: 1px; right: 1px;
  min-width: 17px; height: 17px; padding: 0 4px;
  border-radius: var(--r-pill);
  background: var(--accent); color: var(--on-accent);
  font-size: 10px; font-weight: 700; line-height: 17px; text-align: center;
  border: 2px solid var(--bg);
}

/* ---------------------------------------------------------------------------
   按钮
   --------------------------------------------------------------------------- */
.btn, button.primary {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
  height: 38px; padding: 0 var(--s4);
  border-radius: var(--r-sm);
  font: inherit; font-size: 14px; font-weight: 550;
  cursor: pointer;
  transition: background .15s, border-color .15s, transform .06s;
}
.btn { border: 1px solid var(--line); background: var(--surface); color: var(--fg); }
.btn:hover { background: var(--subtle); border-color: var(--fg-faint); }
button.primary { border: 0; background: var(--accent); color: var(--on-accent); width: 100%; }
button.primary:hover { background: var(--accent-hover); }
/* 按下时轻微下沉 —— 触摸设备上这是"我收到了"的唯一反馈 */
.btn:active, button.primary:active { transform: translateY(1px); }
button.primary:disabled, .btn:disabled {
  background: var(--subtle); color: var(--fg-faint); cursor: not-allowed;
  border-color: var(--line); transform: none;
}

/* ---------------------------------------------------------------------------
   布局
   --------------------------------------------------------------------------- */
main {
  max-width: var(--maxw); margin: 0 auto;
  padding: var(--s6) var(--s5) var(--s10);
}

/* 类目：横向滚动，绝不换行 —— 换行会把移动端首屏顶掉，商品就看不见了 */
nav.cats {
  display: flex; gap: var(--s2);
  overflow-x: auto; scrollbar-width: none;
  padding-bottom: var(--s2); margin-bottom: var(--s4);
  scroll-snap-type: x proximity;
}
nav.cats::-webkit-scrollbar { display: none; }
.chip {
  flex: none; scroll-snap-align: start;
  height: 34px; padding: 0 var(--s4);
  display: inline-flex; align-items: center;
  border: 1px solid var(--line); border-radius: var(--r-pill);
  background: var(--surface); color: var(--fg-muted);
  font: inherit; font-size: 14px; cursor: pointer; white-space: nowrap;
  transition: background .15s, color .15s, border-color .15s;
}
.chip:hover { border-color: var(--fg-faint); color: var(--fg); }
.chip[aria-pressed="true"] {
  background: var(--fg); border-color: var(--fg); color: var(--bg); font-weight: 550;
}

/* 工具条：排序 / 筛选 / 结果数 */
.toolbar {
  display: flex; align-items: center; gap: var(--s3);
  margin-bottom: var(--s4); flex-wrap: wrap;
}
.toolbar .count { color: var(--fg-muted); font-size: 13px; margin-right: auto; }
select, input[type="search"], input[type="text"], input[type="number"],
input[type="email"], input[type="password"], textarea {
  height: 38px; padding: 0 var(--s3);
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--surface); color: var(--fg); font: inherit; font-size: 14px;
}
textarea { height: auto; padding: var(--s2) var(--s3); }
select { padding-right: var(--s6); cursor: pointer; }

/* ---------------------------------------------------------------------------
   商品网格与卡片 —— 这是整个页面最该被打磨的东西
   --------------------------------------------------------------------------- */
.grid {
  display: grid; gap: var(--s4);
  /* 移动端 2 列。单列会让用户每屏只看到一个商品，浏览效率减半 */
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (min-width: 640px)  { .grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 1000px) { .grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (min-width: 1280px) { .grid { grid-template-columns: repeat(5, minmax(0, 1fr)); gap: var(--s5); } }

.card {
  display: flex; flex-direction: column;
  background: var(--surface); border-radius: var(--r-md);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  transition: box-shadow .18s ease, transform .18s ease;
}
/* hover 抬升：桌面端"可点"的最强暗示。移动端没有 hover，所以不依赖它传达信息 */
@media (hover: hover) {
  .card:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); }
  .card:hover .thumb img, .card:hover .thumb .ph { transform: scale(1.03); }
}

/* 【为什么是 1:1 + contain，不是 4:3 + cover】
   商家上传的图比例不可控。cover 会把不合比例的图裁到只剩中间 —— 拿 3:4 的
   服装图试过：模特的头和鞋正好被裁掉，卡片上只剩一段衣服。裁掉的恰恰是
   买家要看的东西，而且线上不会有人报错，只是转化默默变低。
   contain 保证任何比例都完整可见，代价是留白。
   容器选 1:1 是因为电商主图的事实标准就是 1:1（Amazon、淘宝主图都要求）——
   主流图零留白，偏离的才让出边距。 */
.thumb {
  position: relative; aspect-ratio: 1 / 1;
  background: var(--subtle); overflow: hidden; cursor: pointer;
}
.thumb img { width: 100%; height: 100%; object-fit: contain; display: block; transition: transform .3s ease; }
/* 占位图：低饱和 + 首字母。高饱和纯色块看起来像未完成的原型 */
.thumb .ph {
  width: 100%; height: 100%;
  display: grid; place-items: center;
  font-size: 26px; font-weight: 700; letter-spacing: .04em;
  color: color-mix(in srgb, var(--fg) 26%, transparent);
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--fg) 5%, var(--subtle)),
    color-mix(in srgb, var(--fg) 10%, var(--subtle)));
  transition: transform .3s ease;
}

/* 心愿单：浮在图上，不占卡片的垂直空间 */
.wish {
  position: absolute; top: var(--s2); right: var(--s2);
  width: 32px; height: 32px; display: grid; place-items: center;
  border: 0; border-radius: var(--r-pill); cursor: pointer;
  background: color-mix(in srgb, var(--surface) 82%, transparent);
  backdrop-filter: blur(6px);
  color: var(--fg-muted); box-shadow: var(--shadow-sm);
}
.wish:hover { color: var(--err); }
.wish[aria-pressed="true"] { color: var(--err); }

.card-body { padding: var(--s3); display: flex; flex-direction: column; gap: var(--s2); flex: 1; }

/* 名称固定两行：参差不齐的卡片高度是"没设计过"最明显的信号 */
.card .name {
  font-size: 14px; font-weight: 550; line-height: 1.35; color: var(--fg);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; min-height: calc(1.35em * 2);
  cursor: pointer;
}
.card .name:hover { color: var(--accent); }

.rating { display: flex; align-items: center; gap: var(--s1); font-size: 12px; color: var(--fg-muted); }
.rating .stars { color: #f5a623; letter-spacing: -1px; }

.price-row { display: flex; align-items: baseline; gap: var(--s2); margin-top: auto; }
.price { font-size: 18px; font-weight: 700; letter-spacing: -.02em; }
.price .approx { font-size: 12px; font-weight: 400; color: var(--fg-faint); }

/* 库存用"标签"而不是一行文字：它是状态，不是叙述 */
.band {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px var(--s2); border-radius: var(--r-pill);
  font-size: 11px; font-weight: 550; line-height: 1.6;
}
.band.in_stock { color: var(--ok);   background: var(--ok-bg); }
.band.low      { color: var(--warn); background: var(--warn-bg); }
.band.out      { color: var(--err);  background: var(--err-bg); }

.card select { width: 100%; height: 34px; font-size: 13px; }
.card button.primary { height: 36px; font-size: 14px; }

/* 骨架屏：加载时给出结构而不是空白。感知性能比真实性能更影响"专业感" */
.skeleton { background: var(--surface); border-radius: var(--r-md); box-shadow: var(--shadow-sm); overflow: hidden; }
/* 必须跟 .thumb 同比例：骨架屏和真实内容不等高 = 图加载完页面往下跳（CLS） */
.skeleton .sk-img { aspect-ratio: 1/1; background: var(--subtle); }
.skeleton .sk-line { height: 11px; margin: var(--s3); border-radius: 4px; background: var(--subtle); }
.skeleton .sk-line.short { width: 45%; }
.skeleton, .skeleton * { animation: pulse 1.4s ease-in-out infinite; }
@keyframes pulse { 0%,100% { opacity: 1 } 50% { opacity: .55 } }

/* ---------------------------------------------------------------------------
   抽屉（购物车 / 账户）与弹窗
   --------------------------------------------------------------------------- */
.scrim {
  position: fixed; inset: 0; z-index: 60;
  background: rgba(10, 12, 16, .45);
  backdrop-filter: blur(2px);
  display: flex; justify-content: flex-end;
}
.drawer {
  width: min(420px, 100%); height: 100%;
  background: var(--bg); box-shadow: var(--shadow-pop);
  display: flex; flex-direction: column;
  animation: slidein .22s cubic-bezier(.22,.61,.36,1);
}
@keyframes slidein { from { transform: translateX(24px); opacity: .6 } to { transform: none; opacity: 1 } }
.drawer header {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s4) var(--s5); border-bottom: 1px solid var(--line);
}
.drawer header h2 { margin: 0; font-size: 16px; font-weight: 650; flex: 1; }
.drawer .body { flex: 1; overflow-y: auto; padding: var(--s4) var(--s5); }
.drawer .foot { border-top: 1px solid var(--line); padding: var(--s4) var(--s5); background: var(--surface); }

.modal-back {
  position: fixed; inset: 0; z-index: 70;
  background: rgba(10, 12, 16, .45); backdrop-filter: blur(2px);
  display: grid; place-items: center; padding: var(--s4);
}
.modal {
  background: var(--bg); border-radius: var(--r-lg); box-shadow: var(--shadow-pop);
  width: min(520px, 96vw); max-height: 88vh; overflow: auto; padding: var(--s6);
  animation: pop .18s cubic-bezier(.22,.61,.36,1);
}
@keyframes pop { from { transform: scale(.97); opacity: 0 } to { transform: none; opacity: 1 } }

/* 空态：图标 + 一句话 + 一个出口。空白页面是最没有设计感的东西 */
.empty { text-align: center; padding: var(--s10) var(--s4); color: var(--fg-muted); }
.empty .glyph { font-size: 30px; opacity: .35; margin-bottom: var(--s3); }

/* ---------------------------------------------------------------------------
   杂项
   --------------------------------------------------------------------------- */
.meta { color: var(--fg-muted); font-size: 12px; }
.row { display: flex; gap: var(--s2); align-items: center; }
.row > * { flex: 1; }
.stack > * + * { margin-top: var(--s2); }
h2 { font-size: 15px; font-weight: 650; margin: 0 0 var(--s3); letter-spacing: -.01em; }
section { margin-top: var(--s8); }
fieldset { border: 1px solid var(--line); border-radius: var(--r-md); padding: var(--s4); margin: var(--s3) 0 0; }
legend { font-size: 12px; color: var(--fg-muted); padding: 0 var(--s1); }
table { width: 100%; border-collapse: collapse; font-size: 13px; }
th, td { text-align: left; padding: var(--s2) var(--s3); border-bottom: 1px solid var(--line-soft); vertical-align: top; }
th { color: var(--fg-muted); font-weight: 500; font-size: 12px; }

.line { display: flex; justify-content: space-between; gap: var(--s3); padding: 3px 0; font-size: 14px; }
.line.total {
  border-top: 1px solid var(--line); margin-top: var(--s2); padding-top: var(--s3);
  font-size: 17px; font-weight: 700;
}
.line .muted { color: var(--fg-muted); }
.cart-item { display: flex; gap: var(--s3); align-items: center; padding: var(--s3) 0; border-bottom: 1px solid var(--line-soft); }
.cart-item .n { flex: 1; font-size: 13px; line-height: 1.4; }
.qty { width: 56px; flex: none; }

.status-paid { color: var(--ok); }
.status-pending_payment { color: var(--warn); }
.status-cancelled, .status-refunded, .status-partially_refunded { color: var(--fg-muted); }

#toast {
  position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%) translateY(8px);
  background: var(--fg); color: var(--bg);
  padding: var(--s3) var(--s5); border-radius: var(--r-pill);
  font-size: 14px; font-weight: 500; box-shadow: var(--shadow-pop);
  opacity: 0; transition: opacity .2s, transform .2s;
  pointer-events: none; z-index: 90; max-width: 88vw;
}
#toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

#pager { display: flex; gap: var(--s2); align-items: center; justify-content: center; margin-top: var(--s6); }
#pager:empty { display: none; }

.gallery { display: flex; gap: var(--s2); flex-wrap: wrap; }
.gallery img {
  width: 72px; aspect-ratio: 1/1; object-fit: contain; background: var(--subtle);
  border-radius: var(--r-sm);
  cursor: pointer; border: 2px solid transparent;
}
.gallery img.active { border-color: var(--accent); }
.hero { width: 100%; max-height: 340px; object-fit: contain; border-radius: var(--r-md); background: var(--subtle); }
.stars { border: 0; background: transparent; color: #f5a623; cursor: pointer; font-size: 14px; padding: 0; text-align: left; }
.sku { font-size: 11px; color: var(--fg-faint); font-family: ui-monospace, monospace; }
.err { color: var(--err); }
.ok { color: var(--ok); }

.invoice { font-size: 13px; }
.invoice table { margin: var(--s3) 0; }
.invoice .num { text-align: right; }
.invoice .totrow td { border: 0; padding: 2px var(--s2); }
.invoice .grand td { font-weight: 700; border-top: 2px solid var(--fg); }

@media print {
  body > *:not(.modal-back) { display: none !important; }
  .modal-back { position: static; background: none; padding: 0; display: block; backdrop-filter: none; }
  .modal { border: 0; width: auto; max-height: none; box-shadow: none; animation: none; }
  .no-print { display: none !important; }
}

/* 尊重"减少动画"偏好 —— 前庭功能障碍者会因视差和缩放感到不适 */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* ---------------------------------------------------------------------------
   移动端顶栏
   ---------------------------------------------------------------------------
   【窄屏塞不下五组元素】logo + 搜索 + 三个语言 chip + 货币 + 账户 + 购物车
   在 390px 上会挤成一团：语言 chip 换行盖住内容、搜索被压成一个圆、
   购物车被推出屏幕。所以在这里做取舍 ——

   搜索独占第二行（它是主入口，不能缩），语言与货币收进账户弹窗
   （低频，一次设定长期不动）。顶栏第一行只留：品牌、账户、购物车。
   --------------------------------------------------------------------------- */
@media (max-width: 760px) {
  header.site {
    height: auto; flex-wrap: wrap;
    padding: var(--s2) var(--s4) var(--s3);
    gap: var(--s2);
  }
  header .search-wrap { order: 10; flex-basis: 100%; max-width: none; }
  /* 它们被 openSettings() 搬进弹窗，见 app.js */
  #lang, #currency { display: none; }
  main { padding: var(--s4) var(--s4) var(--s8); }
  .grid { gap: var(--s3); }
  .card-body { padding: var(--s3) var(--s3) var(--s3); gap: 6px; }
  .toolbar { gap: var(--s2); }
  .toolbar .count { flex-basis: 100%; margin-bottom: 2px; }
}

/* ---------------------------------------------------------------------------
   筛选：桌面内联，移动端底部弹层
   ---------------------------------------------------------------------------
   【为什么移动端要收起来】价格区间和有货筛选在窄屏占掉两整行，把商品又往下推
   一屏。而它们是低频操作 —— 大多数人进来是浏览，不是先设定价格区间。
   常驻显示等于让 90% 的人为 10% 的人付出首屏空间。

   【为什么是底部弹层而不是居中弹窗】手机上拇指够得到的是屏幕下半部分。
   居中弹窗的按钮落在中间，单手操作要么够不着要么换手。

   【一套 DOM 两种形态】不为移动端另写一份结构 —— 两份 DOM 意味着两处要同步，
   而它们迟早会漂移。这里只用 CSS 切换同一组控件的呈现方式。
   --------------------------------------------------------------------------- */
.filter-btn { display: none; }          /* 桌面不需要它，控件本来就都在 */
.filters { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }
.sheet-scrim { display: none; }

@media (max-width: 760px) {
  .filter-btn {
    display: inline-flex; position: relative;
    margin-left: auto;                   /* 推到右侧，与排序分开 */
  }
  .filter-btn .dot {
    position: absolute; top: 6px; right: 8px;
    width: 7px; height: 7px; border-radius: var(--r-pill);
    background: var(--accent);
  }

  /* 弹层本体：从底部滑入，圆角只在上方 —— 那是"它从下面来"的视觉线索 */
  .filters {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 80;
    flex-direction: column; align-items: stretch; gap: var(--s4);
    padding: var(--s5) var(--s5) calc(var(--s5) + env(safe-area-inset-bottom, 0px));
    background: var(--bg);
    border-radius: var(--r-lg) var(--r-lg) 0 0;
    box-shadow: var(--shadow-pop);
    transform: translateY(101%);
    transition: transform .24s cubic-bezier(.22,.61,.36,1);
    visibility: hidden;
  }
  .filters.open { transform: none; visibility: visible; }
  .filters > * { width: 100%; }
  .filters input[type="number"] { width: 100% !important; }

  /* 弹层顶部的抓手：一眼看出它可以被拖走/关掉 */
  .filters::before {
    content: ''; display: block;
    width: 36px; height: 4px; margin: -6px auto 2px;
    border-radius: var(--r-pill); background: var(--line);
  }

  .sheet-scrim {
    display: block; position: fixed; inset: 0; z-index: 79;
    background: rgba(10, 12, 16, .4);
    opacity: 0; pointer-events: none; transition: opacity .24s;
  }
  .sheet-scrim.open { opacity: 1; pointer-events: auto; }
}

/* ---------------------------------------------------------------------------
   商品详情
   ---------------------------------------------------------------------------
   它之前只是个图片画廊 —— 没有价格、没有变体、没有加入购物车，用户看完得关掉
   弹窗回列表才能下单。现在它是完整的详情，排版按"决定要不要买"的顺序：
   图 → 名称 → 评分与卖家（要不要信）→ 价格与库存（多少钱）→ 购买 → 详情。
   --------------------------------------------------------------------------- */
.modal.detail { padding: 0; width: min(560px, 96vw); position: relative; }
.detail > * { margin-left: var(--s5); margin-right: var(--s5); }
.detail > .hero, .detail > .gallery { margin: 0; }

.detail-close {
  position: absolute; top: var(--s3); right: var(--s3); z-index: 2;
  background: color-mix(in srgb, var(--surface) 82%, transparent);
  backdrop-filter: blur(6px); box-shadow: var(--shadow-sm);
}
/* 详情页更不能裁：这是用户在仔细看这件东西的地方。
   高度封顶而不是按比例撑开 —— 一张竖图能把价格和加购按钮整个推出首屏，
   那就等于用户点进详情后先看到的是"要往下滚"，而不是"多少钱、能不能买"。 */
.detail .hero {
  width: 100%; height: min(40vh, 380px);
  object-fit: contain; border-radius: var(--r-lg) var(--r-lg) 0 0; background: var(--subtle);
}
.detail .gallery { padding: var(--s3) var(--s5) 0; }
.detail-name { font-size: 19px; line-height: 1.3; margin: var(--s4) 0 var(--s2); }
.detail-meta { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; margin-bottom: var(--s4); }

/* 购买区给一块底色：它是这个页面上唯一要"做决定"的地方 */
.detail-buy {
  background: var(--subtle); border-radius: var(--r-md);
  padding: var(--s4); display: flex; flex-direction: column; gap: var(--s3);
  margin-bottom: var(--s4);
}
.detail-buy .price-row { margin: 0; }
.detail-desc { color: var(--fg-muted); font-size: 14px; line-height: 1.6; margin: 0 var(--s5) var(--s4); }
.detail-links { margin-bottom: var(--s4); }
.detail-section { font-size: 15px; margin: var(--s5) var(--s5) var(--s3); }

/* 相关商品：横向滚动，不换行 —— 它在详情底部，不该把页面撑得更长 */
.related {
  display: flex; gap: var(--s3); overflow-x: auto; scrollbar-width: none;
  padding: 0 var(--s5) var(--s5);
}
.related::-webkit-scrollbar { display: none; }
.related-cell { flex: none; width: 124px; cursor: pointer; }
.related-cell .thumb { aspect-ratio: 1/1; border-radius: var(--r-sm); margin-bottom: var(--s2); }
.related-cell .name { font-size: 12px; line-height: 1.35; min-height: calc(1.35em * 2); }

@media (max-width: 760px) {
  /* 手机上详情占满屏：它是一个"页面"，不是一个浮层。
     【父容器的 padding 也要去掉】.modal-back 有 place-items:center + padding，
     只改 .modal 的宽高是贴不到边的 —— 上下会各留一条背景，看着像没做完。 */
  .modal-back:has(.detail) { padding: 0; place-items: stretch; }
  .modal.detail {
    width: 100vw; max-width: none;
    height: 100vh; max-height: 100vh; border-radius: 0;
  }
  .detail .hero { border-radius: 0; }
}
