/* Hallmark · macrostructure: Sticker Menu (hero → sticky category bar → sticker feed → bag drawer)
 * tone: playful-campus · theme: custom (vibe: "大学城食堂贴纸菜单"
 * paper oklch(96.5% .022 82) · accent oklch(62% .205 32) chili-red
 * display: ZCOOL KuaiLe + Noto Sans SC + Baloo 2)
 * enrichment: food-sticker collage (real dish images)
 * motion: marquee · cardIn · bag-slide · celebrate
 * contrast: pass (40–41) · nav: custom brand bar · footer: info trio
 * honest: pass (46) · chrome: pass (47) · tokens: pass (48)
 * responsive: pass (49) · icons: pass (30) · mobile: pass (34, 49–57)
 * pre-emit critique: P5 H4 E5 S5 R4 V5 */

* { box-sizing: border-box; margin: 0; padding: 0; }
/* 2026-09-30 癸（第七轮 §1 · P0-1）：落点偏移由「逐个锚点写 scroll-margin-top」
   改成**滚动容器级 scroll-padding-top** —— 陛下原话「需要给菜单区留出与吸顶栈等高的
   offset，而不是只给锚点」。scroll-padding-top 是滚动容器自己的内衬，对**所有**落点
   一视同仁（:target 锚点 / scrollIntoView / 刷新停在 hash / scrollIntoViewIfNeeded），
   不必逐个元素补，也不会漏掉将来新加的锚点。
   ⚠️ 它与元素的 scroll-margin-top 是**相加**的：所以下面 .layout / .menu-heading /
   .cat-section 上那三条同值 scroll-margin-top **必须一并删掉**，否则落点会翻倍到 ~262px。
   数值 = 吸顶栈实高 + 12px 呼吸位。吸顶栈 = 顶栏 --banner-height + 分类条 --h-cat-nav：
     · 未滚动：64 + 55（手机/平板）/ 59（桌面）→ 手机 119 · 桌面 123
     · 滚动后：顶栏收窄到 --banner-height-compact = 54（≤1023px）/ 56（>1023px，
       见 tokens.css:212 与 :118）→ 手机/平板 54+55=109 · 桌面 56+59=115
   （两个数值均由第七轮 5 机型 Playwright 实测复核：未滚动 headerH=64、catH 55/59；
     滚动后 headerH 54（SE/ProMax/Pixel5/iPad）、56（Laptop），catH 不变。）
   ⚠️ 两态用的是**同一个 var(--banner-height)** —— app.js syncHeader() 把它直接设在
   document.documentElement 上，与本条 scroll-padding-top 同源同元素，所以
   「内衬 − 栈高 ≡ 12px」在两态下都成立，落点的呼吸位恒定，不会随吸顶收窄而漂移。 */
html { scroll-behavior: smooth; overflow-x: clip;
  scroll-padding-top: calc(var(--banner-height) + var(--h-cat-nav) + 12px); }
body {
  font-family: var(--font-body);
  background: var(--paper);
  color: var(--ink);
  font-size: var(--text-base);
  line-height: 1.7;
  letter-spacing: 0.02em;
  overflow-x: clip;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}
body.modal-open { overflow: hidden; }
/* 菜品详情独立滚动锁：不复用 modal-open，避免与购物车/盒饭选菜的开合互相解锁 */
body.detail-open { overflow: hidden; }
a { color: inherit; text-decoration: none; }
button { font: inherit; cursor: pointer; }
svg { width: 18px; height: 18px; flex: 0 0 auto; }
.hidden { display: none !important; }
::selection { background: var(--tomato); color: var(--tomato-ink); }
:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

/* ═══════════ 顶栏 ═══════════ */
.site-header {
  position: sticky;
  top: 0;
  z-index: var(--z-nav);
  /* 第五轮 §P1：Header 用暖白 #FFFCF7，与纯白卡片、米色页面拉开三层 */
  background: var(--surface-warm);
  border-bottom: 1px solid var(--line);
  box-shadow: var(--shadow-sm);
  transition: box-shadow var(--dur-2) var(--ease-out);
}
/* 2026-09-30 甲：滚动后收窄（作者样式表胜出，无需 !important） */
.site-header.is-stuck { box-shadow: var(--shadow-md); }
.header-inner {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 var(--space-sm);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-xs);
  /* 减 1px 抵掉 .site-header 的下边框：整条顶栏渲染高恰为 64 / 56（规范 §6），
     且与 top: var(--banner-height) 的分类吸顶条严丝合缝 */
  height: calc(var(--banner-height) - 1px);
  transition: height var(--dur-2) var(--ease-out);
}
.brand { display: flex; align-items: center; min-height: var(--h-tap); line-height: 1; min-width: 0; }
.brand-text { display: flex; flex-direction: column; line-height: 1; gap: 3px; min-width: 0; }
/* 2026-09-30 辛（第六轮 §P0-3「顶部 Logo 放大 10–15%」）：
   陛下给的 `font-size:18px; letter-spacing:.12em` 是**手机基准**（手机现值 16px，
   18/16 = +12.5%，正落在「10–15%」里）。桌面现值已经是 21px —— 若照搬 18px，
   桌面 Logo 反而**缩小 14%**，与「放大」这条正相反。
   故按同一比例把整条 clamp 抬上去，两档都只增不减：
     手机 16 → 18（+12.5%）· 桌面 21 → 23（+9.5%）。
   字距同样按 .12em 走：手机 2.16px（原 1.4）· 桌面 2.76px（原 2）。
   Header 高度由 --banner-height 定、与字号无关 —— Logo 放大不会顶高顶栏。 */
.brand-text strong {
  font-family: var(--font-display);
  font-size: clamp(18px, 4.2vw, 23px);
  letter-spacing: 0.12em;
  white-space: nowrap;
}
.brand-text small { font-size: 11px; letter-spacing: 2.2px; color: var(--muted); font-weight: 700; }
/* 2026-09-30 丁（陛下反馈 §3 + §4）：
   概念只剩 Cart → Checkout → Order Tracking 三个，重复入口一律清掉。
   桌面 = LOGO | My Orders | EN | 袋上件数（空 `0`，有货 `2 · $36.80`）；
   手机 = LOGO | EN | 订单图标 | 袋上件数（.header-nav 与 .cart-total 在 <1024px 隐藏，
   订单图标 = #ordersBtn.mobile-only，件数 = #cartBadge，见文件末尾断点）。
   「Menu」不再进顶栏 —— 往下滚就是菜单。 */
.header-nav { display: flex; align-items: center; gap: var(--space-md); margin-left: auto; margin-right: var(--space-xs); }
.hnav-link {
  font-size: 14px; font-weight: 800; letter-spacing: 0.3px;
  color: var(--ink-soft); text-decoration: none;
  padding: 6px 1px;
  border-bottom: 2px solid transparent;
  transition: color var(--dur-1), border-color var(--dur-1);
  white-space: nowrap;
}
.hnav-link:hover { color: var(--tomato-deep); border-bottom-color: var(--tomato); }
.header-actions { display: flex; align-items: center; gap: var(--space-2xs); flex: 0 0 auto; }
/* 图标按钮（内联 SVG，无依赖）· 可点面积 44×44 */
/* 2026-09-30 庚（第六轮 · 手机补充 §9「Header 权重」）：
   原底色一律 --tomato-soft（粉），顶栏三个控件全是粉的 → 眼睛没有落点，
   「Bag 最重要」这件事在视觉上根本没说出来。
   改法：中性件（中文 / Orders）退成白底 + 暖灰描边 + 灰字 —— 有形状但不出声；
   Bag 保住红描边并独占红（见下），整个顶栏只有它是红的，权重自然成立。 */
.icon-btn {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--h-tap); height: var(--h-tap);
  border-radius: var(--radius-btn);
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--muted);
  font-weight: 900;
  transition: background var(--dur-1), color var(--dur-1), border-color var(--dur-1), transform var(--dur-1);
}
.icon-btn svg { width: 22px; height: 22px; }
.icon-btn:hover { background: var(--tomato); color: var(--tomato-ink); border-color: var(--tomato); }
.icon-btn:active { transform: translateY(1px); }
.lang-btn { font-size: 12px; letter-spacing: 0.5px; }
/* 购物袋按钮（第四轮 §1）：桌面 = `袋图标 2 · $36.80`（.cart-total 显示件数+金额）；
   手机 = 袋图标右上角只挂件数（.cart-total 隐藏，件数走 .cart-badge）—— 金额让给底部贴底条。
   2026-09-30 庚（第六轮 §9）：红描边保留，底色从粉退回白 —— 描边要在白底上才咬得住，
   压在粉底上红是糊的。顶栏唯一一处红，就是这里。 */
.bag-btn {
  border-color: var(--tomato);
  background: var(--surface);
  color: var(--tomato);
  width: auto; min-width: var(--h-tap);
  padding: 0 12px; gap: 7px;
  border-radius: var(--radius-full);
}
/* hover 时红底白字（.icon-btn:hover 已给），但描边要留住，否则按钮会「散开」 */
.bag-btn:hover { border-color: var(--tomato); }
.cart-total { font-family: var(--font-num); font-size: 14px; font-weight: 800; color: var(--tomato-deep); line-height: 1; }
.bag-btn:hover .cart-total { color: var(--tomato-ink); }
.chip {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  min-height: 44px; padding: 0 13px;
  border-radius: var(--radius-full);
  border: 1.5px solid var(--line-strong);
  background: var(--surface);
  color: var(--ink);
  font-size: 13px; font-weight: 800;
  transition: border-color var(--dur-1), color var(--dur-1), background var(--dur-1), transform var(--dur-1);
}
.chip:hover { border-color: var(--tomato); color: var(--tomato-deep); }
.chip:active { transform: translateY(1px); }
.chip:disabled { opacity: 0.55; cursor: not-allowed; }
.chip svg { width: 16px; height: 16px; color: var(--tomato); }
/* 第四轮 §4：购物车里那个「查订单 / 我的」第二入口已整条删除（连同 .cart-member-link 规则）——
   全站只有一套 Cart → Checkout → Order Tracking，把袋里的东西买掉只有 Checkout 一条路。 */
.cart-btn {
  position: relative;
  min-height: 44px; padding: 0 16px;
  border-radius: var(--radius-md);
  border: none;
  background: var(--tomato);
  color: var(--tomato-ink);
  font-weight: 900; font-size: 14px;
  display: inline-flex; align-items: center; gap: 7px;
  box-shadow: var(--shadow-sm);
  transition: background var(--dur-1), transform var(--dur-1);
}
.cart-btn:hover { background: var(--tomato-deep); }
.cart-btn:active { transform: translateY(1px); }
.cart-btn:disabled { opacity: 0.55; cursor: not-allowed; }
/* 2026-09-30 庚（第六轮 §9「Bag 用红色描边 + 数字 Badge」）：原芥末黄药丸。
   黄色跟「最重要」是打架的 —— 它是全站的高亮色（价签/胶带/贴底条金额），
   挂在 Bag 上等于把「重点」稀释成「又一个黄色小点」。
   改红色实心 + 白字：整个顶栏唯一的实心红块，一眼就知道袋里有东西。
   描边取 --surface-warm（顶栏底色）而不是 --surface（卡片白）—— 描边是用来「抠出」
   Badge 与顶栏的缝的，必须与顶栏同色才不会显出一圈白边。 */
/* 2026-09-30 壬（第六轮 · 手机补充「保留」节小项「Bag badge 的红色 1 稍微小一点」）：
   22 → 20px、字号 11 → 10px，仍单字符也不塌（min-width 跟着 22 → 20）。
   描边/配色/位置一概不动 —— 陛下点名「Bag Badge 不动」，这里只收个码。 */
.cart-badge {
  position: absolute; top: -4px; right: -4px;
  min-width: 20px; height: 20px; padding: 0 4px;
  border-radius: var(--radius-full);
  background: var(--tomato);
  color: var(--tomato-ink);
  border: 2px solid var(--surface-warm);
  font-size: 10px; font-weight: 900;
  display: flex; align-items: center; justify-content: center;
}
.cart-badge.hidden { display: none; }

/* ═══════════ Hero（2026-09-30 丙 · 陛下反馈 §1「Hero 缩短，食物放大」） ═══════════
   结构换成：文字块（主标题 + 一行副标题 + 三个卖点）在上，整宽食物拼图在下。
   食物不再是"压在字后面的背景图"，而是一块量得出面积的实体 —— 手机端高度取
   --hero-food-h-mobile，实测占 844px 首屏 40–50%（截图见 _evidence/uiux-feedback2-2026-09-30/）。
   同时砍掉 Hero 里的第二个 Start Order 按钮：下单入口已由顶栏 Cart 与菜品卡承担，留着只是重复。
   2026-09-30 己（第五轮 §P0-1）：上一条实测的 40–50% 正是被扣分的理由 —— 太高，
   首屏看不到菜单。高度改由 token 收到 手机 200 / 平板 240 / 桌面 280，结构不动。
   这条 4px 芥末黄底线是原有的「贴纸胶带」装饰，保留。 */
/* 2026-09-30 庚（第六轮 · 手机补充 §1）：整块定高 220（token），食物图改弹性。
   上一版是「文字自适应 + 图 200 定高」，等于图说了算 —— 图多高，首屏就被顶多远。
   现在反过来：整块 220 是硬预算，文字块先占，剩下的全归图（.hero-photo flex:1）。
   好处是以后再调标题/副题字号，被牺牲的是图不是首屏 —— 分类条不会再被顶出折叠线。 */
.hero {
  position: relative;
  background: var(--paper);
  border-bottom: 4px solid var(--mustard);
  display: flex;
  flex-direction: column;
  /* 2026-09-30 壬（奴复验修正）：手机 Hero 图**按陛下草图给足 200px**（草图：文字 → [Pickup|Delivery] → 220px 食物图 → 店铺状态 → 分类 → Our Menu）。
     原实现（庚）把整块 Hero 硬锁 220px，图被挤到 ~106px，比陛下手机规范（180–220）小了一半。
     改为 height:auto + 图定高 token：整块约 330px，分类条仍落在首屏内（实测见交付）。 */
  height: auto;
}
.hero-inner { width: 100%; max-width: 1200px; margin: 0 auto; padding: var(--space-sm) var(--space-sm) var(--space-xs); }
.hero-copy { display: flex; flex-direction: column; align-items: flex-start; gap: 7px; min-width: 0; }
.hero-title {
  font-family: var(--font-display);
  font-size: clamp(22px, 6vw, 40px);
  line-height: 1.1;
  letter-spacing: 0.5px;
  text-wrap: balance;
  overflow-wrap: anywhere;
  min-width: 0;
  color: var(--ink);
}
.hero-sub { font-size: 13px; font-weight: 700; letter-spacing: 0.2px; line-height: 1.35; color: var(--muted); }
/* 首屏三行到此结束 —— 标题 / 副题 / [Pickup][Delivery]。
   第四轮 §2：原先那三个小标签（时长 / 现炒 / 取送）已从 HTML 整块删除，不是隐藏；
   它们跟副题说的是同一件事，同屏重复四遍。
   ⚠️ 类名不许含 "points" —— test/no-points.test.js 用 /积分|points/i 扫前台文件，
   那是"去掉积分系统"的验收断言，只能让类名躲开，不能去改断言。 */
.hero-seg { margin-top: 2px; }
/* 食物：一张满宽横幅主图（2026-09-30 己 · 第五轮 §P0-2 方案 A —— 只留一张）。
   沿革：2×2 四宫格 → 大图 + 右下小图 → 本轮只剩一张。
   图片源仍是 public/images/yangzhouchaofan.webp（1000×562，现有素材里最宽的一张，
   1.78:1）。横幅为长条裁切，靠 object-fit:cover + object-position:center 咬住主体中心。
   ⚠️ 不造假图、不做拉伸合成 —— 素材本身没有 16:6 原图这一点写进交付的「已知未做」。 */
.hero-photo {
  position: relative;
  /* 2026-09-30 庚（第六轮 §1）：手机改弹性 —— 吃掉 .hero 220px 里文字块与 4px 胶带线
     剩下的全部高度（390×844 实测 ~106px）。min-height:0 必须有：flex 子项默认
     min-height:auto，图按内容高（1000×562 按宽缩放 = 219px）撑住不肯让位，整块就会
     涨回 340+，这次精修直接失效。 */
  /* 2026-09-30 壬（奴复验修正）：手机图**按 token 定高**（--hero-food-h-mobile = 200px），
     不再让它抢 Hero 剩余高度（那样只有 ~106px）。平板/桌面仍走下面的媒体查询。 */
  flex: 0 0 auto;
  min-height: 0;
  width: 100%;
  height: var(--hero-food-h-mobile);
  overflow: hidden;
  background: var(--paper-2);
}
/* 平板/桌面回到「图自己定高」—— 桌面 Hero 是自适应高，没有 220 这回事。
   2026-09-30 辛（第六轮 §P0-1 + §P0-2）：图框**收窄居中**到 3:1（平板 720×240 / 桌面 780×260）。
   为什么必须收窄：满宽 1366 做不到 3:1 —— 那要 455px 高，比现在更高，
   与「Hero 再矮 15–20%」冲突。收窄后两条同时成立，而且顺带治好了「米粒发糊」：
   旧图 1000px 被拉到 1366px（1.37× 放大）再看中间一条，现在 1600px 缩到 780px（0.49×）。
   margin:0 auto 居中：.hero-photo 是 .hero（flex column）的子项，定宽后交叉轴 auto 外边距
   会吃掉全部剩余空间，比 align-self 更稳。圆角 + overflow 让圆角真正裁到图。
   ⚠️ 手机不在此媒体查询内 —— 手机仍是满宽弹性（约 3.3:1）。 */
@media (min-width: 641px) {
  .hero { height: auto; }
  .hero-photo {
    flex: 0 0 auto;
    /* 2026-09-30 癸（陛下第七轮验收）：陛下要「两边再无留白」—— 平板/桌面 Hero 图改**满宽通栏**
       （原来 width: min(100%, token*3) 居中，1366 下只有 936px 宽，两侧各留 ~215px 米色空白，像贴了一张照片）。
       满宽后按 cover 裁上下：1366×260 下源图 1280×426 放大 1.067×、纵向裁掉 43%，主体仍在中心。
       圆角取消（通栏图带圆角会露出底色）。 */
    width: 100%;
    max-width: none;
    height: var(--hero-food-h-tablet);
    margin: 0;
    border-radius: 0;
    overflow: hidden;
  }
}
/* 2026-09-30 己（第五轮 §P0-2 方案 A）：右下角那张小宫保鸡丁已删，Hero 只留一张横幅主图。
   原话是「像 Banner + 广告贴纸」——两张图叠在一起就有了第二视觉重心。
   .hero-float 的样式随之整块删除（HTML 里的 <img> 也已移除）。
   object-position 取 center：横幅是长条裁切，主体必须咬住画面中心。 */
.hero-shot { width: 100%; height: 100%; object-fit: cover; object-position: center; display: block; }
.open-state {
  display: inline-flex; align-items: center; gap: 9px;
  background: var(--herb-soft);
  border: 1px solid var(--herb);
  color: var(--herb-deep);
  border-radius: var(--radius-full);
  padding: 9px 15px;
  font-size: 13px; font-weight: 800;
}
.open-state::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: currentColor; flex: 0 0 auto; }
.open-state.closed { color: var(--tomato-deep); border-color: var(--tomato); background: var(--tomato-soft); }
.btn-primary {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 48px; padding: 0 var(--space-md);
  border-radius: var(--radius-full);
  border: none;
  background: var(--tomato);
  color: var(--tomato-ink);
  font-weight: 900; font-size: 15px;
  box-shadow: var(--shadow-md);
  transition: background var(--dur-1), box-shadow var(--dur-1), transform var(--dur-1);
}
.btn-primary:hover { background: var(--tomato-deep); box-shadow: var(--shadow-lg); }
.btn-primary:active { transform: translateY(2px); }
.btn-primary:disabled { opacity: 0.55; cursor: not-allowed; background: var(--tomato); }
.btn-ghost {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 48px; padding: 0 20px;
  border-radius: var(--radius-full);
  border: 1.5px solid var(--tomato);
  background: transparent;
  color: var(--tomato-deep);
  font-weight: 800; font-size: 14px;
}
.btn-ghost:hover { background: var(--tomato-soft); }
.btn-ghost:active { transform: translateY(1px); }
/* ═══════════ Pickup / Delivery 选择器（2026-09-30 甲） ═══════════ */
/* 2026-09-30 丙（陛下反馈 §3「Pickup / Delivery 要成为真正的模式选择器」）：
   选中哪个，下面三行文案整块换掉（见 app.js renderFulfil()）。行数固定，
   Pickup ↔ Delivery 来回切不会跳高度，首屏不会抖。 */
.fulfil { background: var(--surface); border-bottom: 1px solid var(--line); }
.fulfil-inner {
  max-width: 1200px; margin: 0 auto;
  padding: 9px var(--space-sm) 10px;
  display: flex; flex-direction: column; gap: 7px;
}
.fulfil-info { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.fi-head { display: flex; flex-wrap: wrap; align-items: center; gap: 4px var(--space-2xs); }
.fi-main { font-size: 14px; font-weight: 800; color: var(--ink); line-height: 1.3; }
.fi-sub { font-size: 12.5px; font-weight: 600; color: var(--ink-soft); line-height: 1.35; }
.fi-fine { font-size: 11px; color: var(--muted); line-height: 1.35; }
/* 空行不占位：Pickup 没有小字、Delivery 才有，收起后两边高度一致 */
.fi-sub:empty, .fi-fine:empty { display: none; }
/* 分段控件：轨道 48px，内衬 2px → 按钮 44px（≥44 可点面积） */
.seg {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 4px; padding: 2px;
  height: var(--h-seg);
  max-width: 420px; width: 100%;
  border-radius: var(--radius-seg);
  background: var(--paper-2);
  border: 1px solid var(--line);
}
.seg-btn {
  min-height: var(--h-tap);
  border: 1px solid transparent;
  border-radius: calc(var(--radius-seg) - 2px);
  background: transparent;
  color: var(--muted);
  font-family: var(--font-body);
  font-size: 14px; font-weight: 800;
  cursor: pointer;
  transition: background var(--dur-1), color var(--dur-1), box-shadow var(--dur-1);
}
.seg-btn:hover { color: var(--tomato-deep); }
.seg-btn.is-on {
  background: var(--tomato);
  border-color: var(--tomato);
  color: var(--tomato-ink);
  box-shadow: var(--shadow-sm);
}
.fi-head .open-state { padding: 3px 9px; font-size: 11.5px; gap: 6px; }

/* ═══════════ 跑马灯：已删除（2026-09-30 己 · 第五轮 §P0-3） ═══════════
   黄色菜名 ticker 整块下线（陛下原话「整页最掉高级感的东西」）。
   连带下线：.ticker / .ticker-track / @keyframes ticker / --h-ticker / .item-card.item-flash
   （跑马灯链接点击后的落点高亮）/ shared/ticker-items.js / test/marquee-links.test.js。
   菜品导航改由下方吸顶分类条独家承担。 */


/* ═══════════ 分类导航 ═══════════ */
/* 吸顶横向滚动胶囊条（2026-09-30 甲：Sticky 在 Header 正下方） */
.cat-nav {
  position: sticky;
  top: var(--banner-height);
  z-index: var(--z-sticky);
  background: var(--surface-warm);
  border-bottom: 1px solid var(--line);
}
/* 2026-09-30 己（第五轮 §P0-4）：右侧渐隐 —— 提示「后面还有分类，可以横滑」。
   陛下原话是「现在 Desktop 右侧被截断（Bur...）」：截断本身没错，错在没有任何提示。
   渐变盖在胶囊条上、不接收指针事件，横滑手势照样穿透到 .cat-nav-inner。 */
.cat-nav::after {
  content: "";
  position: absolute;
  top: 0; right: 0; bottom: 1px;
  /* 第六轮 §P0-4：40 → 36px（陛下给定值）。端色仍取 --surface-warm（= #FFFCF7），
     就是 .cat-nav 自己的底色 —— 陛下写的是 #fffaf2，那是旧底；渐变两端必须贴合实际底色，
     否则右侧会留一条可见的色带。 */
  width: 36px;
  pointer-events: none;
  background: linear-gradient(to right, rgb(255 252 247 / 0), var(--surface-warm) 78%);
}
.cat-nav-inner {
  max-width: 1200px; margin: 0 auto;
  display: flex; gap: var(--space-2xs);
  overflow-x: auto;
  padding: 8px var(--space-sm);
  scrollbar-width: none;
  scroll-behavior: smooth;
  overscroll-behavior-x: contain;
}
.cat-nav-inner::-webkit-scrollbar { display: none; }
.cat-pill {
  flex: 0 0 auto;
  height: var(--h-cat); padding: 0 18px;           /* 第五轮 §P0-4：44 → 42px */
  border-radius: var(--radius-full);
  border: 1px solid var(--line);
  background: var(--surface-warm);                 /* 未选＝暖白底 + 细边框 */
  color: var(--muted);
  font-size: 14px; font-weight: 800;
  white-space: nowrap;
  transition: border-color var(--dur-1), color var(--dur-1), background var(--dur-1), transform var(--dur-1);
}
.cat-pill:hover { border-color: var(--tomato); color: var(--tomato-deep); background: var(--tomato-soft); }
.cat-pill:active { transform: translateY(1px); }
.cat-pill:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }
/* 分类名后的道数（`炒菜 24`）降一级：13px/600、更浅，只为提示规模，不跟分类名抢宽度 */
.cat-pill .cat-n { margin-left: 6px; font-size: 12px; font-weight: 600; opacity: 0.72; }
/* 选中＝Primary 底白字（未选＝暖白底灰字） */
.cat-pill.is-on {
  background: var(--tomato);
  border-color: var(--tomato);
  color: var(--tomato-ink);
}
.cat-pill.is-on:hover { background: var(--tomato-deep); border-color: var(--tomato-deep); color: var(--tomato-ink); }

/* ═══════════ 主布局 ═══════════ */
/* 2026-09-30 丙（陛下反馈 §4「菜单必须是页面视觉中心」）：
   原来桌面恒定 `菜单 + 350px 购物车栏` 两栏 —— 空袋子也占着右侧 350px，菜单被挤成配角。
   现在默认单栏，菜单独享整个 1200px；只有袋里真有东西（JS 给 <body> 挂 .has-bag）且 ≥1024px 时，
   右侧购物车栏才出现。手机上购物车是底部抽屉（.cart-panel 固定定位），不参与栅格，
   所以那条 110px 的下边距只在 .has-bag 时留出来给 Sticky Cart 让位。 */
.layout {
  max-width: 1200px; margin: 0 auto;
  /* 2026-09-30 壬（第六轮 · 手机补充 §1）：陛下给的样例里写的是 `.menu-section, .menu-heading`
     —— 本项目的「菜单区」是 <main class="layout" id="menu">（线上没有 .menu-section 这个类名，
     照抄会写出一条永远不命中的死规则）。同一个落点偏移给它也挂一份：任何指向 #menu 的
     跳转/锚点都不会把 Our Menu 送进吸顶区。
     2026-09-30 癸（第七轮 §1）：那条 scroll-margin-top 已**上收**到 html 的 scroll-padding-top
     （见文件开头）—— 容器级一处就覆盖 #menu / .menu-heading / .cat-section 全部落点。
     两者是相加的，留着会翻倍，所以这里只留痕、不保留声明。 */
  /* 2026-09-30 己（第五轮 §P1「收紧 Our Menu 上方留白」）：顶内边距 24px→16px，
     分类条与「Our Menu」之间的空白收 8px —— 配合 §P0-1 的首屏目标（不滚动就看见菜单）。 */
  padding: var(--space-sm) var(--space-sm) var(--space-2xl);
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-md);
  align-items: start;
}
/* 2026-09-30 己（第五轮 §P1）：标题上外边距 4px→0（上方留白统一由 .layout 的 padding 管），
   下方 24px→18px；「Our Menu」到第一排菜品之间不再空一大块。 */
/* 2026-09-30 壬（第六轮 · 手机补充 §1 —— 当前唯一真 P0）：
   陛下截图里 `Our Menu 99 dishes` 被吸顶分类条切掉一截。根因不是 margin 不够，
   是**没人给这个标题定过落点**：上一轮只给 .cat-section 写了 scroll-margin-top，
   于是点第一个分类胶囊（Stir-Fry）时页面按「分类块顶边贴吸顶栈下沿」落点 ——
   而 Our Menu 标题正好在它上方 46px 处，落点一算，标题就卡进吸顶区里被切一半。
   修法两步（本文件 + app.js）：标题自己也带落点偏移；点第一个分类时改为滚到标题。
   落点公式与 .cat-section 同源：吸顶栈（顶栏 + 分类条）+ 12px 呼吸位。
   2026-09-30 癸（第七轮 §1）：那条 scroll-margin-top 同批**上收**到 html 的
   scroll-padding-top（容器级，一处覆盖所有落点）；留在元素上会与容器内衬相加。 */
.menu-heading {
  display: flex; align-items: baseline; gap: var(--space-xs); margin: 0 0 18px;
}
.menu-heading h1 { font-family: var(--font-display); font-size: var(--text-xl); letter-spacing: 1px; }
.menu-heading span { color: var(--muted); font-size: 13px; }
/* 2026-09-30 己（第五轮 §P0-5）：点分类后第一排商品被顶栏压住 —— 根因是 app.js 里写死
   `- 140` 的滚动落点，和吸顶栈（顶栏 64/56 + 分类条 59）对不上，顶栏一收窄就更对不上。
   现在落点交给 CSS 的 scroll-margin-top，用 var(--banner-height) 跟着吸顶栈实时走，
   JS 只调 scrollIntoView()，不再自己算数。 */
/* 2026-09-30 癸（第七轮 §1）：本块的 scroll-margin-top 同批上收到 html 的 scroll-padding-top。 */
.cat-section {
  margin-bottom: var(--space-lg);
}
.cat-title { display: flex; flex-direction: column; gap: 2px; margin-bottom: var(--space-sm); }
.cat-title h2 { font-family: var(--font-display); font-size: var(--text-lg); letter-spacing: 1px; white-space: nowrap; }
/* 2026-09-30 丙：.cat-sub（标题下的另一语言分类名）已删 —— 陛下 §6 禁止半混合，见 app.js renderMenu() */
.cat-title::after {
  content: "";
  display: block;
  width: 100%;
  height: 4px;
  background: var(--mustard);
  border-radius: 2px;
  margin-top: var(--space-2xs);
}
.cat-note {
  background: var(--sky-soft);
  border: 1.5px dashed var(--sky);
  color: var(--ink-soft);
  font-size: 13px;
  padding: 10px 14px;
  border-radius: var(--radius-sm);
  margin: 0 0 var(--space-sm);
  line-height: 1.7;
}

/* ═══════════ 贴纸菜品卡 ═══════════ */
/* 菜品卡（2026-09-30 乙：方案 §11 —— 手机一行两列 gap 12 / 圆角 16 / 阴影 0 2px 10px / 右下圆形 +） */
.item-grid {
  display: grid;
  /* Mobile <640px：一行两列（方案 §11 + §37）；Tablet/Desktop 三列见文件末尾断点 */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}
.item-card {
  position: relative;
  background: var(--surface);
  /* 2026-09-30 己（第五轮 §P1「Card 边框更轻」）：描边改用 --line-card（rgba(0,0,0,.06)），
     不再用偏暖的 --line —— 卡片边缘退后，让菜品图占视觉。 */
  border: 1px solid var(--line-card);
  border-radius: var(--radius-card);
  padding: 10px;
  /* 2026-09-30 己（第五轮 §P0-6）：图片 ↓10px 中文名（原 8px）；名 → 底栏同样是这 10px。 */
  display: flex; flex-direction: column; gap: 10px;
  box-shadow: var(--shadow-card);
  /* 2026-09-30 丁：卡片本身不再是点击目标（陛下 §7），手型光标只留给图片与 + 按钮 */
  cursor: default;
  transition: transform var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out), border-color var(--dur-1);
  animation: cardIn var(--dur-3) var(--ease-out) both;
}
.item-card:nth-child(1) { animation-delay: 0.02s; }
.item-card:nth-child(2) { animation-delay: 0.06s; }
.item-card:nth-child(3) { animation-delay: 0.1s; }
.item-card:nth-child(4) { animation-delay: 0.14s; }
.item-card:nth-child(5) { animation-delay: 0.18s; }
.item-card:nth-child(6) { animation-delay: 0.22s; }
.item-card:nth-child(7) { animation-delay: 0.26s; }
.item-card:nth-child(8) { animation-delay: 0.3s; }
.item-card:nth-child(9) { animation-delay: 0.34s; }
.item-card:nth-child(10) { animation-delay: 0.38s; }
.item-card:nth-child(11) { animation-delay: 0.42s; }
.item-card:nth-child(12) { animation-delay: 0.46s; }
/* 2026-09-30 乙：贴纸胶带装饰已移除（方案 §11 卡片结构无此项；两列窄卡上按不动且吃高度） */
.item-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); border-color: var(--line-strong); }
/* 卡片自身不可聚焦了（陛下 §7），键盘走到图 / + / − 上时整张卡描边提示位置 */
.item-card:focus-within { border-color: var(--tomato); }
@keyframes cardIn { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }

/* 2026-09-30 丁（陛下反馈 §7「点菜品图片才弹 Bottom Sheet，不要整卡都弹，避免误触」）：
   图片外层是一个原生 <button>，宽度撑满卡片、去掉按钮默认外观，让图片自己说话。
   line-height:0 抹掉 inline 元素基线缝隙，否则图下方会多出 4–5px。 */
.dish-open {
  display: block; width: 100%;
  padding: 0; margin: 0;
  border: none; background: none;
  border-radius: var(--radius-img);
  line-height: 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: opacity var(--dur-1), transform var(--dur-1);
}
.dish-open:hover { opacity: 0.94; }
.dish-open:active { transform: scale(0.99); }
.dish-open:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
/* 统一比例 4:3（方案 §11「4:3 或 1:1」）→ 所有卡图同高，不再有的横有的竖 */
.dish-img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--radius-img);
  background: var(--paper-2);
  border: 1px solid var(--line);
  display: block;
}
.dish-ph {
  width: 100%;
  aspect-ratio: 4 / 3;
  border-radius: var(--radius-img);
  background: var(--mustard-soft);
  border: 1.5px dashed var(--mustard-deep);
  color: var(--mustard-deep);
  font-family: var(--font-display);
  font-size: 36px;
  display: flex; align-items: center; justify-content: center;
  user-select: none;
}
/* 2026-09-30 己（第五轮 §P0-6）：中文名 ↓2px 英文名（原 1px）。 */
.item-name { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
/* 2026-09-30 丙（陛下反馈 §5「菜品卡固定统一」）：主名固定占两行高（2.4em = 2 × 1.2 行高）。
   不固定的话，一行名（Pork w/ Egg）与两行名（Braised Beef & Carrots）的卡差 20px，
   实测同一屏里 257/277/287/307 四种高度；固定后同栏卡片等高，切中英也不跳。
   2026-09-30 己（第五轮 §P0-9）：主名 16px→15px、行高 1.25→1.2（两行 40px→36px），
   min-height 跟着改成 2.4em —— 必须**恰好**等于两行高，否则一行名/两行名又不等高了。 */
/* 2026-09-30 庚（第六轮 · 手机补充 §5「商品卡名字限两行」）：min-height → 硬 height + 截断。
   上一版只给 min-height:2.4em —— 它保住了「一行名不塌」，却挡不住「三行名顶出去」：
   实测 94 张卡里 `Sanxian Steamed Buns (Shrimp, Pork & Chicken)` 占三行，
   同一屏卡片高度 249.5 / 267.5 两种，底部价格与 + 不在一条线上，正是陛下扣分的那点。
   现在 height:2.4em（= 2 × 1.2 行高）配合 -webkit-line-clamp:2 —— 第 3 行被吃掉加省略号，
   名区恒等于两行高，全卡高度由名区决定 → 卡片等高 → 价格/标签/+ 天然同一水平线。 */
.item-name .primary {
  font-family: var(--font-display); font-size: 15px; font-weight: 700; letter-spacing: 0.2px; line-height: 1.2; overflow-wrap: anywhere;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden;
  height: 2.4em;
}
/* 2026-09-30 己（第五轮 §P0-9「中文菜名太弱」）：副名 12px/400 → 13px/500。
   色值走 --muted（oklch(50% 0.034 62)），与陛下点名的 #666 同级（ΔL≈1.6%，偏暖一档），
   按铁律不写死 hex。
   ⚠️ 中英层级互换不在这里做 —— renderMenu() 已按 state.lang 决定谁挂 .primary，
   所以「中文模式 = 中文 15px/700 深色 + 英文 13px/500 灰」自动成立，不需要 lang 选择器。 */
/* 2026-09-30 庚（第六轮 §5）：副名同样收成固定一行 —— 名区总高才是个常量
   （36 + 2 + 16.25 = 54.25px）。不锁的话中文模式下英文副名会折成两行，
   名区又变成变量，§5 要的「所有卡同一水平线」立刻破功。 */
.item-name .secondary {
  font-size: 13px; color: var(--muted); font-weight: 500; line-height: 1.25; overflow-wrap: anywhere;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 1;
  overflow: hidden;
  height: 1.25em;
}
/* 中文模式的主名是汉字，不能落回 Baloo/ZCOOL 这类拉丁展示体（KuaiLe 只有 400 一档，700 会被合成）。
   ⚠️ 只在 html[lang^="zh"] 下改字体族，英文模式的 .primary 仍是品牌体。 */
html[lang^="zh"] .item-name .primary { font-family: var(--font-body); }
/* 2026-09-30 壬（第六轮 · 手机补充 §4「中文名最多 1 行 · 英文名最多 2 行」）：
   上一版把行数绑在**层级**上（.primary 恒 2 行 / .secondary 恒 1 行），可层级是跟着语言换的 ——
   中文模式下被限成 1 行的恰恰是英文名，中文名反倒占着两行，正好与陛下要的反过来。
   这里按**内容语言**重绑一次（选择器口径与上面那条字体族一致，只在中文模式生效）：
     中文名（.primary）1 行 · 1.2em    英文名（.secondary）2 行 · 2.5em
   名区总高 = 18 + 2 + 32.5 = 52.5px（英文模式仍是 36 + 2 + 16.25 = 54.25px）。
   两语言各自内部 94 张卡仍恒等高（高度是硬 height，不是 max），切换语言只差 1.75px；
   价格与 + 的贴底对齐由 .item-foot 的 margin-top:auto + align-self:end 负责，与名区高度无关。 */
html[lang^="zh"] .item-name .primary { -webkit-line-clamp: 1; height: 1.2em; }
html[lang^="zh"] .item-name .secondary { -webkit-line-clamp: 2; height: 2.5em; }
.tags { display: flex; flex-wrap: wrap; gap: 4px; }
/* 方案 §25：Badge 高 22–26px、圆角 999px、字号 11–12px */
.tag {
  font-size: 11px; font-weight: 800; letter-spacing: 0.3px; line-height: 1;
  height: 22px; padding: 0 9px;
  display: inline-flex; align-items: center;
  border-radius: var(--radius-full);
}
.tag.hot { background: var(--tomato); color: var(--tomato-ink); }
.tag.signature { background: var(--ink); color: var(--paper); }
.tag.spicy { background: var(--tomato-soft); color: var(--tomato-deep); border: 1px solid var(--tomato); }
.tag.value { background: var(--mustard-soft); color: var(--mustard-deep); border: 1px solid var(--mustard); }
/* 方案 §11：描述最多两行，超出省略号 */
.item-desc {
  font-size: 12.5px; color: var(--muted); line-height: 1.45;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
/* 陛下反馈 §5：卡片底部固定成「左边 价格 + 一行属性 / 右边 + Add」，不再堆药丸和两行描述。
   ⚠️ .tags / .tag 仍然保留 —— 菜品详情抽屉（openDetail）还在用它们，别顺手删。 */
/* 2026-09-30 己（第五轮 §P0-6 + §P0-11）：底栏改成现代 Food App 的两行三件套 ——
     第一行：价格（独占整行）
     第二行：属性 · 标签（左）        +（右下）
   即 §P0-6 的「价格 ↓4px 标签」与 §P0-11 的「$31.26 左、+ 右下」。
   + 按钮跨两行（grid-row: 1 / span 2）再 align-self: end —— 它只占右列宽度、
   不额外撑高底栏：底栏高度 = max(价格 17 + 4 + 属性 16.2, 40) = 40px（原 59px）。
   ⚠️ .tags / .tag 仍然保留 —— 菜品详情抽屉（openDetail）还在用它们，别顺手删。 */
.item-foot {
  margin-top: auto; display: grid; grid-template-columns: minmax(0, 1fr) auto;
  align-items: end; column-gap: var(--space-2xs); row-gap: 4px;
}
/* .item-meta 只作分组语义存在，display:contents 让价格/属性直接参与上面的栅格。 */
.item-meta { display: contents; }
/* 2026-09-30 庚（第六轮 · 手机补充 §5 草图旁的注记「← 价格与 + 永远贴底对齐」）：
   价格与属性上下对调 —— 上一版是「价格在上、属性在下」，于是贴底的是属性行，
   与陛下草图上 `$31.26   +` 同处最底行、`+` 与价格底部齐平画的不一致。
   现在：属性行在上（第 1 行）· 价格在下（第 2 行）· `+` 仍跨两行且 align-self:end，
   于是 价格底边 = 第 2 行底边 = `+` 底边，三者同一条水平线；卡片总高不变（仍 40px 底栏），
   94 张卡等高这条不受影响。 */
.item-foot .price { grid-area: 2 / 1 / 3 / 2; }
.item-foot .add-btn,
.item-foot .step { grid-row: 1 / span 2; grid-column: 2; align-self: end; }
/* ⚠️ 取舍：属性行不再独占整行，右列被 + 按钮占掉 ~46px（步进器展开时 ~100px），
   窄卡上长属性会省略成「Stir-Fry ·…」。换来的是价格独占一行 + 底栏 59px→40px；
   缺省只显示分类名/短描述，实测两列手机卡仍读得全。 */
.item-attr {
  grid-area: 1 / 1 / 2 / 2;
  font-size: 12px; color: var(--muted); line-height: 1.35;
  min-width: 0; max-width: 100%;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* 方案 §4/§11：价格 17–20px、粗（700–800） */
.price {
  font-family: var(--font-num);
  font-size: 17px; font-weight: 800;
  color: var(--tomato-deep);
  line-height: 1;
  display: inline-flex; align-items: baseline; gap: 2px;
  white-space: nowrap;
  min-width: 0;
}
/* 2026-09-30 己（第五轮 §P0-10）：`.price small { … }`（卡上那个 NZD 小字）随 NZD 一并删除，
   币种说明改在 Checkout 的 Order Summary 与页脚各注一次。 */
/* 方案 §11：右下圆形 +（40×40，不写 ADD TO CART） */
.add-btn {
  position: relative;
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--h-add); height: var(--h-add);
  padding: 0;
  border: none;
  border-radius: var(--radius-full);
  background: var(--tomato);
  color: var(--tomato-ink);
  transition: background var(--dur-1), color var(--dur-1), transform var(--dur-1);
}
/* 视觉 40×40，实际可点 46×46（方案 §40：可点面积 ≥44×44） */
.add-btn::after { content: ""; position: absolute; inset: -3px; border-radius: var(--radius-full); }
.add-btn:hover { background: var(--tomato-deep); }
.add-btn:active { transform: scale(0.95); }
.add-btn:disabled { opacity: 0.55; cursor: not-allowed; }
.add-btn svg { width: 20px; height: 20px; }
/* 点 + 后原位置变 − 1 +（方案 §11「不跳页面」）；data-qty/data-delta 复用购物车同一套委托 */
/* min-height 对齐 + 按钮的 40px：步进器本身只有 30px（--h-step），不撑高的话
   「已加菜的卡」比「没加菜的卡」矮 10px —— 加一道菜整行卡片就缩一下，很跳。 */
.step { display: inline-flex; align-items: center; gap: 2px; flex: 0 0 auto; min-height: var(--h-add); }
.step-btn {
  position: relative;
  width: var(--h-step); height: var(--h-step);
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-full);
  background: var(--surface);
  color: var(--ink);
  transition: background var(--dur-1), color var(--dur-1), border-color var(--dur-1), transform var(--dur-1);
}
.step-btn::after { content: ""; position: absolute; inset: -7px; }  /* 可点 44×44 */
.step-btn:hover { border-color: var(--tomato); color: var(--tomato-deep); }
.step-btn:active { transform: scale(0.94); }
.step-btn svg { width: 16px; height: 16px; }
.step-val {
  min-width: 20px; text-align: center;
  font-family: var(--font-num); font-size: 15px; font-weight: 800; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.step.lg .step-btn { width: var(--h-add); height: var(--h-add); }
.step.lg .step-btn::after { inset: -3px; }
.step.lg .step-val { min-width: 28px; font-size: 18px; }

/* ═══════════ 菜品详情（2026-09-30 乙：方案 §12） ═══════════
   手机＝底部滑上抽屉（60–90vh，顶部圆角 24）/ 桌面 ≥1024px＝居中 Modal 500–600px。
   只做展示与加购数量，金额一律复用购物车同一套 fmtCents/priceCents，不新算钱。 */
.detail-overlay {
  position: fixed; inset: 0;
  z-index: var(--z-overlay);
  background: oklch(28% 0.03 55 / 0.55);
  display: none;
  align-items: flex-end; justify-content: center;
}
.detail-overlay.show { display: flex; }
.detail-card {
  position: relative;
  overflow: hidden;                     /* 让大图被卡片圆角裁切，无需各自设圆角 */
  width: 100%;
  /* 2026-09-30 丁（陛下反馈 §7「把 Bottom Sheet 做扎实」）：原来是写死的 78vh，
     没有描述的菜（大多数）就会在数量行和 Add to Bag 之间空出一大块。
     改成贴着内容长、最高 90vh（超了 .detail-body 自己滚），与桌面 Modal 的 height:auto 对齐。 */
  height: auto;
  max-height: var(--h-sheet-max);
  background: var(--surface-warm);   /* 第五轮 §P1：与 Modal 同层 */
  border-radius: var(--radius-sheet) var(--radius-sheet) 0 0;
  box-shadow: var(--shadow-lg);
  display: flex; flex-direction: column;
  animation: sheetUp var(--dur-sheet) var(--ease-out);
}
@keyframes sheetUp { from { transform: translateY(100%); } to { transform: none; } }
@keyframes detailIn { from { opacity: 0; transform: translateY(10px) scale(0.98); } to { opacity: 1; transform: none; } }
.detail-close {
  position: absolute; top: 10px; right: 10px; z-index: 2;
  width: 36px; height: 36px;
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0; border: none;
  border-radius: var(--radius-full);
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow-sm);
}
.detail-close::after { content: ""; position: absolute; inset: -4px; }  /* 可点 44×44 */
.detail-close svg { width: 18px; height: 18px; }
.detail-body { flex: 1 1 auto; overflow-y: auto; overscroll-behavior: contain; }
.detail-img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; display: block; background: var(--paper-2); }
.detail-ph {
  width: 100%; aspect-ratio: 4 / 3;
  background: var(--mustard-soft);
  color: var(--mustard-deep);
  font-family: var(--font-display); font-size: 64px;
  display: flex; align-items: center; justify-content: center;
  user-select: none;
}
.detail-info { padding: var(--space-sm); display: flex; flex-direction: column; gap: 10px; }
.detail-name-zh { font-family: var(--font-display); font-size: 22px; font-weight: 800; line-height: 1.2; }
.detail-name-en { font-size: 13px; color: var(--muted); font-weight: 500; line-height: 1.3; }
.detail-price { font-family: var(--font-num); font-size: 22px; font-weight: 800; color: var(--tomato-deep); line-height: 1; }
.detail-desc { font-size: 13.5px; color: var(--ink-soft); line-height: 1.6; }
.detail-row {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-xs);
  min-height: var(--h-tap);
  padding-top: 10px;
  border-top: 1px solid var(--line);
}
.detail-row-label { font-size: 11px; font-weight: 800; letter-spacing: 1px; text-transform: uppercase; color: var(--muted); }
.detail-opt {
  display: inline-flex; align-items: center; gap: 6px;
  min-height: var(--h-tap); padding: 0 var(--space-sm);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-input);
  background: var(--surface);
  color: var(--ink);
  font-size: 14px; font-weight: 700;
}
.detail-opt:hover { border-color: var(--tomato); color: var(--tomato-deep); }
.detail-opt svg { width: 16px; height: 16px; }
.detail-picked { flex: 1 1 auto; min-width: 0; font-size: 13px; color: var(--ink-soft); line-height: 1.5; }
.detail-foot {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: var(--space-xs);
  padding: 10px var(--space-sm) calc(10px + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--line);
  background: var(--surface);
}
/* 底部固定主按钮（方案 §12：Add to Bag · $18.90） */
.detail-add {
  flex: 1 1 auto;
  min-height: var(--h-cta);
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  border: none;
  border-radius: var(--radius-btn);
  background: var(--tomato);
  color: var(--tomato-ink);
  font-family: var(--font-body);
  font-size: 16px; font-weight: 700;
  transition: background var(--dur-1), transform var(--dur-1);
}
.detail-add:hover { background: var(--tomato-deep); }
.detail-add:active { transform: scale(0.99); }
.detail-add:disabled { opacity: 0.5; cursor: not-allowed; }

/* ═══════════ 打包袋（购物车） ═══════════ */
.cart-panel {
  background: var(--surface);
  border: 2px solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--space-md);
  position: sticky;
  top: calc(var(--banner-height) + 20px);
  max-height: calc(100vh - 104px);
  display: flex; flex-direction: column;
  box-shadow: var(--shadow-md);
}
.cart-panel-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--space-sm); }
.cart-panel-head h2 { font-family: var(--font-display); font-size: var(--text-lg); letter-spacing: 1px; }
.cart-count { color: var(--muted); font-size: 13px; font-weight: 700; }
/* 2026-09-30 丙：滚动容器是外层 #cartList（桌面侧栏 / 手机底部抽屉共用同一个元素），
   不是里层的 ul。之前滚动写在 ul 上、外层 div 却被 .cart-panel 的 flex 压到 323px，
   于是 4 条菜时第 4 条整条看不见、第 3 条的步进器被切一半，而且 scrollHeight == clientHeight
   连滚动条都不出现 —— 用户没有任何办法把第 4 条弄出来。截图复验时抓到的。
   flex:1 + min-height:0 是让 flex 子项能真正收缩并交出滚动条的标准写法。 */
#cartList { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding-right: 2px; }
.cart-list { list-style: none; display: flex; flex-direction: column; gap: var(--space-xs); }
.cart-empty { text-align: center; color: var(--muted); padding: 36px 10px; font-size: 14px; }
.cart-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 8px 10px;
  padding-bottom: var(--space-xs);
  border-bottom: 1.5px dashed var(--line-strong);
}
/* 2026-09-30 己（第五轮 §P0-12②）：商品名限两行 —— 长名（如盒饭那几道）原来能把整条
   购物车行撑到三行以上，Cart 一路变高、Checkout 按钮被顶出视野。超出走省略号。 */
.ci-name {
  font-weight: 800; font-size: 14px; overflow-wrap: anywhere;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.ci-price { font-size: 12.5px; color: var(--muted); margin-top: 2px; }
.ci-controls { display: flex; align-items: center; gap: 9px; grid-column: 1 / 2; }
.qty-btn {
  width: 44px; height: 44px;
  border-radius: 50%;
  border: 1.5px solid var(--line-strong);
  background: var(--surface);
  color: var(--ink);
  font-size: 18px; font-weight: 900;
  display: inline-flex; align-items: center; justify-content: center;
  line-height: 1;
  transition: border-color var(--dur-1), color var(--dur-1), background var(--dur-1), transform var(--dur-1);
}
.qty-btn:hover { border-color: var(--tomato); color: var(--tomato-deep); background: var(--tomato-soft); }
.qty-btn:active { transform: translateY(1px); }
.qty-btn:disabled { opacity: 0.55; cursor: not-allowed; }
.qty-btn svg { width: 16px; height: 16px; }
.ci-qty { min-width: 26px; text-align: center; font-weight: 900; font-size: 14px; font-family: var(--font-num); }
.ci-line { text-align: right; display: flex; flex-direction: column; align-items: flex-end; gap: 2px; }
.ci-line-total { font-family: var(--font-num); font-weight: 800; color: var(--tomato-deep); font-size: 14px; }
.ci-remove { background: none; border: none; color: var(--muted); padding: 5px; display: inline-flex; border-radius: 8px; transition: color var(--dur-1), background var(--dur-1); }
.ci-remove:hover { color: var(--tomato); background: var(--tomato-soft); }
.ci-remove:active { transform: translateY(1px); }
.ci-remove:disabled { opacity: 0.55; cursor: not-allowed; }
.ci-remove svg { width: 15px; height: 15px; }
.cart-totals { margin-top: var(--space-sm); display: flex; flex-direction: column; gap: 6px; font-size: 14px; }
.cart-totals .row { display: flex; justify-content: space-between; gap: var(--space-xs); align-items: center; }
.cart-totals .row.total { font-size: 18px; font-weight: 900; border-top: 2px solid var(--line); padding-top: 10px; margin-top: 4px; }
/* 2026-09-30 辛（第六轮 §P0-7）：这一行再降一级 —— 12px / --muted-3（= #999）/ 行高 1.6 → 1.4。
   行高同时收紧：原来 1.6 是为了两行时好读，但它在 Total 上方凭空多占 6px 垂直空间，
   而现在 Cart 的重点只剩 Total 与 Checkout（陛下原话），注解就该缩到最小。
   沿革：第五轮 §P0-12③ 先把 --muted 降到 --muted-2（≈#888），本轮再降到 #999。 */
.fee-note { font-size: 12px; color: var(--muted-3); margin-top: 8px; line-height: 1.4; }
.cart-panel .btn-primary { width: 100%; margin-top: var(--space-sm); }
.cart-backdrop { display: none; }

/* ═══════════ 手机贴底购物袋（陛下反馈 §4「手机：页面里不要出现固定 Cart 区域」）═══════════
   袋子一非空，屏幕底部常驻一整条 `2 items   $36.80   View Bag →`，点整条开袋 ——
   加完菜不用再回头找购物车。空袋时整条不显示（JS 切 .is-on），把首屏还给菜单。
   整条是一个 <button>，不再是「左边信息 + 右边按钮」两块，避免手机上出现两个点按目标。
   z-index 比遮罩低一级：袋子抽屉拉开时，遮罩会盖住并挡住它，不会和抽屉内容打架。 */
#mobileCartBar {
  position: fixed;
  /* 2026-09-30 庚（第六轮 · 手机补充 §7「height 64–68 · 左右 margin 16 · 圆角约 24」）：
     原 12px 边距 / 56px 高 / 999 全圆。全圆在 56px 高时还像个胶囊，到 66px 就变成
     一根胖香肠 —— 陛下给的 24px 正是让它读起来是「一张卡片」而不是「一颗药丸」。 */
  left: var(--bg-bagbar);
  right: var(--bg-bagbar);
  bottom: max(var(--bg-bagbar), env(safe-area-inset-bottom));
  z-index: calc(var(--z-cart) - 2);
  height: var(--h-bagbar);
  border-radius: var(--r-bagbar);
  border: none;
  background: var(--ink);
  color: var(--paper);
  display: none;
  align-items: center; justify-content: space-between; gap: 8px;
  padding: 0 16px 0 18px;
  box-shadow: var(--shadow-lg);
  cursor: pointer;
  text-align: left;
  transition: transform var(--dur-1) var(--ease-out);
}
#mobileCartBar:active { transform: scale(0.985); }
#mobileCartBar .mcb-left { display: flex; align-items: center; gap: 9px; min-width: 0; }
#mobileCartBar .mcb-left > svg { color: var(--mustard); flex: 0 0 auto; width: 20px; height: 20px; }
/* 第四轮 §4：文案就是 `2 items   $36.80   View Bag →` ——
   件数/金额在左，动作词在右，读起来是一条句子而不是三个标签。 */
#mobileCartBar .mcb-text { display: flex; align-items: baseline; gap: 8px; min-width: 0; font-size: 13.5px; font-weight: 800; line-height: 1; white-space: nowrap; }
#mobileCartBar #mcbCount { color: var(--footer-text); font-weight: 700; }
#mobileCartBar .mcb-total { color: var(--mustard); font-weight: 800; font-size: 16px; font-family: var(--font-num); }
#mobileCartBar .mcb-cta {
  display: inline-flex; align-items: center; gap: 2px; flex: 0 0 auto;
  color: var(--paper); font-size: 13.5px; font-weight: 900; white-space: nowrap;
}
#mobileCartBar .mcb-go { color: var(--mustard); flex: 0 0 auto; width: 20px; height: 20px; }
/* 极窄机（<360px）：先舍掉 "View Bag" 字样，留下箭头与件数金额 —— 信息必须留住 */
@media (max-width: 359px) {
  #mobileCartBar .mcb-cta span { display: none; }
  #mobileCartBar .mcb-text { font-size: 13px; }
}

/* ═══════════ 结算弹层 ═══════════ */
.overlay {
  position: fixed; inset: 0;
  z-index: var(--z-overlay);
  background: oklch(28% 0.03 55 / 0.55);
  display: none;
  align-items: center; justify-content: center;
  padding: var(--space-sm);
}
.overlay.show { display: flex; }
.modal-card {
  /* 第五轮 §P1：Modal 底 #FFFCF7（暖白），与页面、纯白卡片分三层 */
  background: var(--surface-warm);
  width: 100%; max-width: 640px;
  max-height: 92vh;
  overflow: auto;
  border-radius: var(--radius-lg);
  border: 2px solid var(--line);
  box-shadow: var(--shadow-lg);
}
.modal-head {
  position: sticky; top: 0;
  background: var(--surface);
  padding: 18px 20px;
  border-bottom: 1px solid var(--line);
  display: flex; align-items: center; justify-content: space-between;
  z-index: 2;
}
.modal-head h2 { font-family: var(--font-display); font-size: var(--text-lg); letter-spacing: 1px; }
.modal-close {
  width: 44px; height: 44px;
  border-radius: 50%;
  border: 1.5px solid var(--line-strong);
  background: var(--paper);
  color: var(--ink);
  font-size: 22px;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background var(--dur-1), color var(--dur-1), border-color var(--dur-1);
}
.modal-close:hover { background: var(--tomato-soft); color: var(--tomato-deep); border-color: var(--tomato); }
.modal-close:active { transform: translateY(1px); }
.modal-body { padding: 20px; }
/* 2026-09-30 辛（第六轮 · Checkout ②「Modal 底部多留 8–12px」）：
   `Place Order` 是 #checkoutBody 的最后一个子元素，原先离卡片下沿只有 20px，略贴底。
   只给结账这一个 body 加：20 + 10 = 30px（落在陛下要的 8–12px 里）。
   用 id 选择器而不是改 .modal-body —— 详情弹窗共用那条基础规则，不该跟着一起动。 */
#checkoutBody { padding-bottom: 30px; }
.form-group { margin-bottom: var(--space-sm); }
.form-label { display: block; font-size: 13px; font-weight: 800; margin-bottom: 6px; color: var(--ink-soft); }
.form-control {
  width: 100%;
  min-height: 46px;
  padding: 10px 13px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  font: inherit;
  color: var(--ink);
}
.form-control:hover { border-color: var(--tomato); }
.form-control:focus { outline: 2px solid var(--focus); outline-offset: 1px; border-color: var(--focus); }
.form-control:disabled { opacity: 0.55; cursor: not-allowed; background: var(--paper-2); }
/* 2026-09-30 己（第五轮 §P0-13⑤）：Notes 默认 70px（原 72px），仍可拖拽扩展（resize: vertical）。 */
textarea.form-control { min-height: 70px; resize: vertical; }
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-xs); }
/* 2026-09-30 辛（第六轮 · Checkout ①「Contact Details 太挤」）：只改 Contact 这一行的列宽。
   名称 48fr / 电话 52fr，电话格里的 `Find account` 定宽 110px ——
   陛下给了两个口径（Name 48% / Phone 31% / 按钮 21%，或按钮固定 110px），取后者：
   按钮按百分比缩放会随中英文案长短抖动，定宽才稳。
   实测（Modal 640 − 内衬 20×2 − 描边 2×2 = 596 可用）：名称 280px（47.0%）、
   电话框 184px（30.8%）、按钮 110px —— 与陛下那三个数（48% / 31% / 110px）基本重合。
   ⚠️ min-width:0 与 padding 15px 都是**为了「固定」二字**，缺一不可：
     · flex 项默认 min-width:auto = 内容最小宽，会把 110px 的基宽顶回 112px（英文案需要 110+2 描边）；
     · 于是英文 112 / 中文 110，跨语言仍差 2px —— 正是定宽要消灭的那个抖动。
     关掉 min-width 后基宽说了算，再把左右内衬各收 1px（16→15），
     英文案正好 15+78+15 = 108 = 可用宽，既不裁字也不会再撑开。 */
.form-row.contact-row { grid-template-columns: 48fr 52fr; }
/* ⚠️ 定宽必须关在横排里：手机上 .lookup-row 是 flex-direction:column，
   那时 flex-basis 是**高度**，按钮会被撑成 110px 高。 */
@media (min-width: 641px) {
  .contact-row .lookup-btn { flex: 0 0 110px; min-width: 0; padding: 0 15px; }
}
.field-hint { font-size: 12px; color: var(--muted); margin-top: 5px; line-height: 1.5; min-height: 1lh; }
.field-hint.error { color: var(--error); font-weight: 800; }
.lookup-row { display: flex; gap: 8px; align-items: stretch; }
.lookup-row .form-control { flex: 1; min-width: 0; }
/* 2026-09-30 己（第五轮 §P0-13②）：`Look up member` → `Find account` 的同时**降视觉权重**：
   边框由 --line-strong（暖深）换成 --line-card（rgba(0,0,0,.06)）、字重 800→500、字号 13→12.5px、
   颜色 --ink-soft→--muted。旁边 46px 高的输入框才是这一行的主体，这颗按钮只作附属动作，不许抢眼。
   ⚠️ 高度 46px 与输入框齐平保留（对齐不能丢），降的只是「轻重」。 */
.lookup-btn {
  min-height: 46px; height: 46px;
  padding: 0 16px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line-card);
  background: var(--surface);
  color: var(--muted);
  font-weight: 500; font-size: 12.5px;
  white-space: nowrap;
  transition: border-color var(--dur-1), color var(--dur-1), background var(--dur-1);
}
.lookup-btn:hover { border-color: var(--tomato); color: var(--tomato-deep); background: var(--tomato-soft); }
.lookup-btn:active { transform: translateY(1px); }
.lookup-btn:disabled { opacity: 0.55; cursor: not-allowed; }
.radio-group { display: flex; gap: 10px; }
.radio-card {
  flex: 1;
  position: relative;
  border: 2px solid var(--line);
  border-radius: var(--radius-md);
  padding: 13px 10px;
  text-align: center;
  font-weight: 900; font-size: 14px;
  background: var(--surface);
  min-height: 58px;
  cursor: pointer;
  line-height: 1.3;
  transition: border-color var(--dur-1), background var(--dur-1), color var(--dur-1), transform var(--dur-1);
}
.radio-card:hover { border-color: var(--tomato); }
.radio-card:active { transform: translateY(1px); }
.radio-card.selected { border-color: var(--tomato); background: var(--tomato-soft); color: var(--tomato-deep); }
.radio-card:disabled { opacity: 0.55; cursor: not-allowed; }
.radio-card input { position: absolute; opacity: 0; pointer-events: none; }
/* 2026-09-30 己（第五轮 §P0-13④）：支付方式的图标 —— 统一内联 SVG（见 app.js 的 SVG_CARD / SVG_BANK）。
   走 currentColor，选中时跟着 .radio-card.selected 的 --tomato-deep 一起变色；
   20×20 居中压在文字上方，整站没有第二套支付图标、不含 emoji。 */
.radio-card svg { width: 20px; height: 20px; display: block; margin: 0 auto 6px; }
.radio-card small { display: block; font-weight: 600; color: var(--muted); font-size: 12px; margin-top: 2px; }
.order-summary {
  background: var(--paper);
  border: 1.5px dashed var(--line-strong);
  border-radius: var(--radius-md);
  padding: 14px;
  margin-top: var(--space-sm);
  display: flex; flex-direction: column; gap: 6px;
  font-size: 14px;
}
.os-items { border-bottom: 1.5px dashed var(--line); padding-bottom: 8px; margin-bottom: 2px; font-size: 13px; color: var(--ink-soft); }
.os-row { display: flex; justify-content: space-between; gap: var(--space-xs); align-items: center; }
.os-row.total { font-size: 17px; font-weight: 900; border-top: 2px solid var(--line-strong); padding-top: 10px; }
/* 2026-09-30 己（第五轮 §P0-10）：卡上那个重复 94 遍的 `NZD` 删掉后，币种在 Checkout 只注这一处 ——
   贴在 Order Summary 合计下方，小字浅色，是注解不是金额。 */
.os-note { font-size: 12px; color: var(--muted-2); text-align: right; margin-top: 2px; }
.submit-btn { width: 100%; margin-top: 18px; }
.form-error { color: var(--error); font-size: 13px; font-weight: 800; min-height: 1lh; margin-top: 4px; }

/* ═══════════ 支付 ═══════════ */
.payment-banner {
  background: var(--mustard-soft);
  border: 1.5px solid var(--mustard);
  border-radius: var(--radius-md);
  padding: 14px;
  margin-top: var(--space-sm);
}
.payment-banner h3 { font-size: 15px; color: var(--mustard-deep); margin-bottom: 6px; font-weight: 900; }
.payment-banner p { font-size: 13px; color: var(--ink-soft); }
.stripe-box { margin-top: var(--space-sm); }
.stripe-box h3 { font-size: 15px; color: var(--ink-soft); margin-bottom: 8px; font-weight: 800; }
#cardElement {
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  padding: 14px;
  background: var(--surface);
  min-height: 56px;
}
#cardElement:focus-within { outline: 2px solid var(--focus); outline-offset: 1px; }
.pay-btn { width: 100%; margin-top: 14px; }
.payment-error { color: var(--error); font-size: 13px; font-weight: 800; min-height: 1lh; margin-top: 8px; }
.order-no-line { font-size: 13px; color: var(--muted); margin-top: 4px; }

/* ═══════════ 成功页 ═══════════ */
.success-icon {
  width: 68px; height: 68px;
  border-radius: 50%;
  background: var(--herb-soft);
  color: var(--herb-deep);
  display: flex; align-items: center; justify-content: center;
  margin: 8px auto 14px;
  position: relative;
}
.success-icon svg { width: 32px; height: 32px; }
.success-icon.celebrate::before,
.success-icon.celebrate::after {
  content: "";
  position: absolute;
  width: 10px; height: 10px;
  border-radius: 2px;
  animation: spark var(--dur-3) var(--ease-out) both;
}
.success-icon.celebrate::before { top: -6px; left: 6px; background: var(--mustard); transform: rotate(18deg); }
.success-icon.celebrate::after { bottom: -4px; right: 2px; background: var(--tomato); transform: rotate(-14deg); }
@keyframes spark { from { opacity: 0; transform: scale(0.4); } to { opacity: 1; transform: scale(1); } }
.success-title { text-align: center; font-family: var(--font-display); font-size: 26px; letter-spacing: 1px; }
.success-sub { text-align: center; color: var(--muted); margin-top: 6px; font-size: 14px; line-height: 1.7; }
.success-card {
  background: var(--paper);
  border: 1.5px dashed var(--line-strong);
  border-radius: var(--radius-md);
  padding: var(--space-sm);
  margin-top: 18px;
}
.sc-row { display: flex; justify-content: space-between; gap: 14px; padding: 6px 0; font-size: 14px; align-items: center; }
.sc-row span { color: var(--muted); }
.sc-row strong { text-align: right; font-family: var(--font-num); }
.success-actions { display: flex; gap: 10px; margin-top: 18px; }
.success-actions .btn-primary { flex: 1; }

/* ═══════════ Find us（陛下反馈 §9：地址区缩成一张卡片，不做 Footer 信息墙）═══════════
   五项：Church Corner / Inside Basics Asian Supermarket / 8 Brake St / Mon–Sat · 11am–7pm / Directions。
   原 Google Maps iframe 已删（第三方请求换来的是首屏之外一张不会有人滚到的大图）；
   信息量不减 —— Directions 还是一键开地图。此项列在交付报告的「已知取舍」。 */
.find-us {
  background: var(--mustard-soft);
  border-top: 3px dashed var(--mustard);
  border-bottom: 3px dashed var(--mustard);
  padding: var(--space-lg) var(--space-sm);
}
.find-us-inner { max-width: 1200px; margin: 0 auto; }
.find-us-card {
  position: relative;
  display: flex; flex-direction: column; gap: 5px;
  max-width: 520px;
  background: var(--surface);
  border: 2px solid var(--line-strong);
  border-radius: var(--radius-lg);
  padding: var(--space-md) var(--space-lg);
  box-shadow: var(--shadow-sm);
}
/* 贴纸胶带（沿用原 Find us 卡片上的那一条，位置跟着卡片走） */
.find-us-card::before {
  content: '';
  position: absolute;
  top: -12px;
  left: 24px;
  width: 88px;
  height: 26px;
  background: var(--mustard);
  opacity: 0.85;
  transform: rotate(-2deg);
  border-radius: 4px;
  box-shadow: var(--shadow-sm);
}
.fu-head {
  display: flex; align-items: center; gap: 7px;
  font-family: var(--font-display);
  font-size: var(--text-lg);
  color: var(--tomato-deep);
  line-height: 1.15;
}
.fu-head svg { width: 20px; height: 20px; color: var(--tomato); flex: none; }
.fu-line { font-size: 14px; color: var(--ink); line-height: 1.5; }
.btn-map { align-self: flex-start; margin-top: var(--space-xs); text-decoration: none; }

/* ═══════════ 页脚（陛下反馈 §2 / §9：四栏信息墙拆掉 —— 一行品牌 / 一行链接 / 一行小字）
   免佣金这个卖点从首屏降权到这里的最后一行小字：顾客不会因为免佣金下单。
   第四轮 §6：全站就只留这一行，首屏与菜单区一律不得再出现。 */
.footer {
  background: var(--ink);
  color: var(--footer-text);
  padding: var(--space-lg) var(--space-sm) var(--space-xl);
  margin-top: var(--space-md);
}
.footer-inner {
  max-width: 1200px; margin: 0 auto;
  display: flex; flex-direction: column; align-items: center;
  gap: var(--space-xs);
  text-align: center;
}
.foot-brand { font-family: var(--font-display); font-size: 17px; letter-spacing: 2px; color: var(--mustard); }
.foot-brand span { color: var(--footer-text); font-family: var(--font-body); font-size: 14px; letter-spacing: 1px; margin-left: 7px; }
.foot-links { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px var(--space-sm); font-size: 13.5px; }
.foot-links a {
  color: var(--footer-text); text-decoration: none;
  border-bottom: 1px solid transparent; padding-bottom: 1px;
  transition: color var(--dur-1), border-color var(--dur-1);
}
.foot-links a:hover { color: var(--mustard); border-bottom-color: var(--mustard); }
.foot-note {
  max-width: 620px;
  border-top: 1px solid var(--footer-line);
  padding-top: var(--space-xs);
  margin-top: var(--space-3xs);
  font-size: 11.5px;
  line-height: 1.55;
  color: var(--footer-muted);
}
.foot-note:empty { display: none; }
/* 2026-09-30 己（第五轮 §P0-10）：第二行 `Prices in NZD` —— 与上一行同属「小字注脚」，
   但不要再画一条分隔线（页脚出现两条横线会显碎），并把它往上一行靠 8px，
   读起来是一段注脚的两行，而不是两个独立区块。 */
.foot-curr { border-top: none; padding-top: 0; margin-top: calc(-1 * var(--space-2xs)); }

/* ═══════════ Toast ═══════════ */
.toast {
  /* 2026-09-30 庚（第六轮 · 手机补充 §8「高约 48px · 位于 View Bag 上方 8–12px」）：
     原 padding 12/20 让高度随字号浮动（实测 46.94），现在定高 48 + flex 居中，
     高恒等于 token。bottom 由贴底条反推：16（条下边距）+ 66（条高）+ 10（间隙）= 92px，
     写成 calc 而不是 92 —— 以后改条高，Toast 自己跟着走，不会飘到条里去。 */
  position: fixed; left: 50%;
  bottom: calc(var(--bg-bagbar) + var(--h-bagbar) + 10px);
  transform: translateX(-50%) translateY(18px);
  background: var(--ink);
  color: var(--paper);
  height: var(--h-toast);
  display: flex; align-items: center;
  padding: 0 20px;
  border-radius: var(--radius-full);
  font-size: 13.5px; font-weight: 800;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-2) var(--ease-out), transform var(--dur-2) var(--ease-out);
  z-index: var(--z-toast);
  box-shadow: var(--shadow-md);
  white-space: nowrap;
  max-width: 90vw;
  overflow: hidden;
  text-overflow: ellipsis;
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

/* ═══════════ 响应式 ═══════════ */
@media (max-width: 1023px) {
  /* 第四轮 §1：手机顶栏 = `LOGO  EN  订单图标  袋上件数`。
     Logo 左，语言 / 我的订单 / 购物袋三个图标右。「Menu」已从顶栏彻底拿掉（往下就是菜单），
     「我的订单」在手机上换成订单图标（#ordersBtn.mobile-only），文字链 .header-nav 反而隐藏。
     金额不再挤在顶栏（放不下，也没必要），改由底部贴底条报 `2 items $36.80`；
     顶栏只在小袋图标右上角挂件数 #cartBadge —— 这就是袋上那个 `2`。 */
  .header-nav { display: none; }
  .cart-total { display: none; }
  /* Sticky Cart 占屏幕底部 ~72px，只有袋里有东西、它真出现了，才需要给页面留出让位空间 */
  body.has-bag .layout { padding-bottom: 110px; }
  .cart-panel {
    /* top: auto 必须有 —— 上面桌面那条 sticky 规则留了 top: calc(var(--banner-height) + 20px)，
       这里只改 position/bottom 而不收回 top，就成了「top 和 bottom 同时给死」的过约束盒子：
       高度被 max-height: 82vh 一卡，盒子就改成从 top 往下量，底边落在 776px 而不是 844px。
       于是合上的抽屉并没真出屏 —— 屏幕最底下 26px 一直糊着一条白边（实测 elementFromPoint
       在 y=836 命中的就是 #cartPanel），还盖住了底部贴底条的下缘。收回 top 后：
       拉开 = 贴死屏幕底边（本该如此，遮罩 z-index 高于贴底条，会把贴底条挡住）；
       合上 = translateY(106%) 把整条推出去，一像素不留。 */
    position: fixed; left: 0; right: 0; bottom: 0;
    top: auto;
    z-index: var(--z-cart);
    transform: translateY(106%);
    transition: transform var(--dur-2) var(--ease-in-out);
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    max-height: 82vh;
    display: flex; flex-direction: column;
    box-shadow: var(--shadow-lg);
    padding-bottom: max(18px, env(safe-area-inset-bottom));
  }
  .cart-panel.open { transform: none; }
  .cart-panel #cartList { max-height: 44vh; }
  .cart-backdrop {
    display: block;
    position: fixed; inset: 0;
    background: oklch(28% 0.03 55 / 0.45);
    border: none;
    z-index: calc(var(--z-cart) - 1);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--dur-2) var(--ease-out);
  }
  .cart-backdrop.show { opacity: 1; pointer-events: auto; }
  /* 空袋不显示（陛下反馈 §10：加完菜才常驻；空袋时把首屏还给菜单） */
  #mobileCartBar.is-on { display: flex; }
}
@media (max-width: 640px) {
  .form-row { grid-template-columns: 1fr; }
  /* 第六轮 · Checkout ①：`.form-row.contact-row` 是两个类，权重压过上面这条单类规则，
     所以手机要单独拉回单列 —— 否则手机上姓名/电话仍是 48:52 并排，电话框会被挤扁。 */
  .form-row.contact-row { grid-template-columns: 1fr; }
  /* 2026-09-30 辛（第六轮 §P0-3）：原 `.brand-text strong { 16px / 1.4px }` 手机覆盖已删 ——
     基准值升到 18px 之后，这条会把手机又压回 16px，正好抵消陛下要放大的那一档。 */
  /* ── 2026-09-30 癸（第七轮 §2 · P0）：手机品牌区太宽 —— 品牌名被截断 / 压到右侧按钮 ──
     实测（iPhone SE 375）：顶栏内容宽 343px，右侧 .header-actions 固定吃 **148px**
     （44 + 6 + 44 + 6 + 48，三个按钮一个都没缩），留给品牌区的是
     343 − 148 − 12(gap) = 183px；再减 Logo 32 + 边距 8 = **143px 给这行字**。
     而 `THE LOCAL KITCHEN` 17 字在 18px/0.12em 下需要 **197.7px** —— 超 54.7px。
     它不是「省略号截断」：.brand-text strong 只有 white-space:nowrap，
     没有 overflow:hidden / text-overflow，所以是**直接溢出去盖住**右边三个按钮的。
     又因为基准值带 clamp(18px, 4.2vw, 23px)，375 与 430 屏的下限都锁在 18px —— 小屏最挤。
     修法：按**屏宽**给两档字号，只压字号与字距，**不换行、不截断、不删 Logo、不缩按钮**：
        ≤400px（SE 375 / 小屏安卓）：14px/0.06em = 139.5px ≤ 143 ✓
        401–640px（Pixel 5 393 / 14 Pro Max 430）：15px/0.06em = 149.4px
           实测 393 屏可用 161px ✓；430 屏可用 198px ✓（都收得住）
     （这行字在 HTML 里写死，不随语言切换；中文品牌名在 .brand-text small 里，本轮没动。）
     为什么不用样例里的 `.brand{max-width:47%}`：47% × 343 = 161px 是给**整个** .brand
     （Logo 32 + 边距 8 + 文字）的上限，扣完只剩 121px 给文字 —— 比现在的 143px 更窄，
     只会更糟。而「按钮绝不被挤扁」已有更强的保证：.header-actions 是 flex:0 0 auto、
     .brand 是 min-width:0 —— 品牌区再宽也压不动按钮，只会盖上去。
     所以本轮不设宽度上限，改把这行字**收进**可用宽度里。 */
  .brand-text strong { font-size: 15px; letter-spacing: 0.06em; }
  .header-actions { gap: 6px; }
  .cart-count { display: none; }
  /* ── 2026-09-30 庚（第六轮 · 手机补充 §1/§3）──
     §1 Hero 只有 220px 硬预算，文字块每省 1px 都是给食物图的。逐项省的账：
        .hero-inner 内边距 16/12 → 10/8（−10）· .hero-copy 间距 7 → 4（−3）
        .hero-title 6vw(23.4) → 20px（−3.7）· .hero-seg 上边距 2 → 0（−2）
        .hero-sub 13/1.35(17.55) → 12.5/1.25(15.6)（−1.9）
     合计 −20.6 → 文字块 128.3 → 107.7，食物图拿到 220 − 4 − 107.7 ≈ 108px。
     ⚠️ 分段控制器（.seg）保持 48px 不动 —— 它里面是 44px 的可点按钮，
     规范 §40 的 ≥44×44 是硬线，不能为了给图让位把它压到 40。 */
  .hero-inner { padding: 10px var(--space-sm) 8px; }
  .hero-copy { gap: 4px; }
  .hero-title { font-size: 20px; }
  .hero-sub { font-size: 12.5px; line-height: 1.25; }
  .hero-seg { margin-top: 0; }
  /* §3 分类条：上下内边距 8 → 6 → 5（2026-09-30 壬 · 第六轮手机补充 §2）。
     条高 = 胶囊 44（token） + 5×2 + 底边框 1 = 55px，正落陛下要的 54–56。
     再往下压只能动胶囊高度，而 §40「可点面积 ≥44×44」是硬线 —— 不许。 */
  /* 间距 8 → 4：这是让「第 4 个分类露一部分」唯一还能动的旋钮。
     陛下给死的两个数是 padding 0 16px 与 font-size 15–16，都不许碰；三个分类名
     15px 下合计 92 + 115 + 147 = 354px，加左右内边距 32 已经是 386 / 390 屏，
     所以间距每省 1px，第 4 个胶囊才往里挪 1px。实测间距 4 → 第 4 个露 8px。 */
  .cat-nav-inner { padding: 5px var(--space-sm); gap: var(--space-3xs); }
  .cat-pill { padding: 0 16px; font-size: 15px; }
  /* §3「一屏看到 3 个分类」：手机隐去道数。这不是省事 —— 是唯一装得下的办法。
     带上道数（`Stir-Fry 24`）三个胶囊是 112 + 135 + 160 = 407px，必然只露两个半
     （正是陛下看到的现状）；隐去后 354px，三个才整整齐齐进屏。
     依据：陛下第一屏草图写的是 [Stir-Fry] [Rice Bowls] [Rice & Noodles →]，里面没有数字。
     道数是「规模提示」，手机横滑场景下不如「多露出一个分类」值钱。桌面保留，一个字节没动。 */
  .cat-pill .cat-n { display: none; }
  /* 2026-09-30 乙：手机首屏瘦身 —— 菜品列表上方留白压到最小，让第一排菜品露出折叠线 */
  .layout { padding-top: var(--space-xs); }
  .menu-heading { margin: 0 0 var(--space-xs); }
  .menu-heading h1 { font-size: var(--text-lg); }   /* 26px→20px：首屏给第一排菜品让位 */
  .cat-section { margin-bottom: var(--space-md); }
  .lookup-row { flex-direction: column; }
  .lookup-btn { width: 100%; }
  .cat-title h2 { white-space: normal; }

  /* ── 2026-09-30 癸（第七轮 §4）：手机商品卡信息区高度 −10% ──
     陛下给的样例是 `.product-info { min-height: 130px }` 往下试 —— 本项目**没有**
     .product-info 这个类（信息区 = 图以下的「名字区 + 底栏」），所以按实测几何等比收，
     结构/标记/类名一律不动，只调四组尺寸：
       卡内竖直 gap  10 → 6px  （图→名、名→底栏两段一起收，−8px）
       卡下内衬      10 → 8px  （−2px）
       名字区 gap     2 → 1px  （−1px）
       主名 行高 1.2 → 1.15 · height 2.4em → 2.3em（两行 36 → 34.5px，−1.5px）
       副名 行高 1.25 → 1.2 · height 1.25em → 1.2em（16.25 → 15.6px，−0.65px）
       中文模式：主名 1.2em → 1.15em · 副名 2.5em → 2.4em（两行 32.5 → 31.2px）
     实测信息区（图以下）124.25 → 111.1px = **−10.6%**；卡片总高 243.9 → 230.75px。
     §4 的原则一条没破：中文 ≤1 行 · 英文 ≤2 行 · 属性 ≤1 行（三条 -webkit-line-clamp
     原样保留），Price / Add 仍由 .item-foot 的 margin-top:auto + align-self:end 贴底，
     与名区高度无关 ——「不为统一留过多空白」靠的是名区**定高**，不是把卡撑高。
     ⚠️ 只作用于手机（≤640px）：桌面三列卡几何一个像素没动 —— §4 讲的是手机商品卡。
     ⚠️ 再往下压的两处硬线：底栏 40px 是 + 按钮的可点高度，名区两行英文是陛下明令保留的
     —— 所以 −10.6% 已是这一版能到的下限，再压就要么截断第二行、要么缩按钮。 */
  .item-card { gap: 6px; padding: 10px 10px 8px; }
  .item-name { gap: 1px; }
  .item-name .primary { line-height: 1.15; height: 2.3em; }
  .item-name .secondary { line-height: 1.2; height: 1.2em; }
  html[lang^="zh"] .item-name .primary { height: 1.15em; }
  html[lang^="zh"] .item-name .secondary { height: 2.4em; }
}
/* 2026-09-30 癸（第七轮 §2）：小屏再降一档。SE 375 给这行字的可用宽度只有 143px
   （内容 343 − 三个按钮 148 − gap 12 − Logo 32 − 边距 8），
   15px/0.06em = 149.4px 会溢出 6.4px，14px 的 139.5px 才收得住。
   只动字号 —— Logo / 三个按钮 / 其余顶栏几何一个像素没动。 */
@media (max-width: 400px) {
  .brand-text strong { font-size: 14px; }
}
@media (min-width: 641px) {
  /* .hero / .hero-photo 的平板还原规则已挪到 .hero 定义处（第六轮 §1）——
     和它要撤销的那条 flex 规则放在一起，改一个不会漏掉另一个。 */
  /* Tablet 640–1024px：三列（方案 §37） */
  .item-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (min-width: 1024px) {
  /* 第六轮 §P0-1/§P0-2：桌面图框 260px 高、宽度取其 3 倍（780px）——3:1 由此成立。
     2026-09-30 癸（第七轮 §3「桌面 Hero 图加宽 15–20%」）：780 → **936px**（+20%）。
     陛下给的样例是 max-width 900–1000px，936 取的是「加宽 15–20%」的上沿。
     ⚠️ **只放宽、不放高**：height 仍是 --hero-food-h-desktop(260px) —— 陛下原话
     「比例与高度风格保持，不要重新变高」，所以比例由 3:1 松到 3.6:1，图框绝不长高。
     用 calc 而不是写死 936：将来陛下给了真图要改高度时，宽度自动跟着走（×3.6）。 */
  .hero-photo {
    height: var(--hero-food-h-desktop);
    /* 2026-09-30 癸（陛下终审）：改**满宽通栏** —— 陛下原话「宽度加宽到两边再无留白」。
       1366 下由 936px（两侧各 ~215px 空白）→ 100% 通栏；高度仍 260px（不放高）。
       代价：比例由 3.6:1 收到 5.25:1，源图 1280×426 纵向裁掉 43%（主体居中仍在）。 */
    width: 100%;
    max-width: none;
  }
  /* 2026-09-30 己（第五轮 §P0-7「图片高度降 10–15%，一屏多看一排」）：
     4:3（1.333）→ 3:2（1.5），同宽下高度 = 宽/比例，降幅 1 − 1.333/1.5 = 11.1%。
     ⚠️ 只改桌面：手机两列卡本来就窄，再压扁菜品图会看不清。
     .dish-ph（占位符）必须同步，否则缺图那几张卡又跟别人不等高。 */
  .dish-img, .dish-ph { aspect-ratio: 3 / 2; }
  #mobileCartBar { display: none; }
  /* 桌面顶栏 = LOGO | My Orders | EN | 袋上 `2 · $36.80` —— 有文字链就不必再来个订单图标；
     件数已经写进金额里（`2 · $36.80`），右上角挂件就成了重复，一并隐藏。 */
  .mobile-only { display: none !important; }
  .cart-badge { display: none; }
  /* 陛下反馈 §4：桌面默认单栏、菜单独享整宽；只有袋里真有东西才让出右侧给购物车。
     第六轮 §P0-5「Cart 再缩 20–30px」：350 → **320px**（−30px）。
     实测（1366 视口，Playwright 量的，不是算的）：.layout 宽 1200，左右各 16px 内衬，
     内容盒 1168，减去 24px 列间隙 → 两列实得 1144。
     菜单 824 : 购物车 320 = **72.0% : 28.0%**，正落在陛下要的 72–74% / 26–28% 上。
     先前写 330px 时实测是 71.2% / 28.8% —— 购物车恰好越过 28% 上沿，
     所以取 320px（缩 30px，区间的上沿）而不是缩 20px。
     菜单列比原来宽 30px，长菜名更少折行。 */
  .cart-panel { display: none; }
  body.has-bag .layout { grid-template-columns: minmax(0, 1fr) 320px; }
  body.has-bag .cart-panel { display: flex; }
  /* Desktop：菜品详情改居中 Modal（方案 §12：500–600px） */
  .detail-overlay { align-items: center; padding: var(--space-sm); }
  .detail-card {
    width: var(--w-modal-detail);
    max-width: 100%;
    height: auto;
    max-height: 88vh;
    border-radius: var(--radius-modal);
    animation: detailIn var(--dur-2) var(--ease-out);
  }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  .item-card { animation: none; }
}

/* ---------- Uber Direct 配送（2026-09-28）---------- */
.radio-card .uber-note {
  display: block;
  font-style: normal;
  font-size: 11.5px;
  font-weight: 700;
  margin-top: 6px;
  padding: 3px 8px;
  border-radius: 999px;
  background: var(--mustard-soft, #f6e3b4);
  color: var(--ink-soft, #5b5b5b);
  line-height: 1.35;
}
.radio-card .uber-note.is-off { background: rgba(0, 0, 0, .06); color: var(--muted, #8a8a8a); }
.radio-card .uber-note.is-on { background: var(--tomato-soft, #ffe1d9); color: var(--tomato-deep, #a8321c); }

/* 2026-09-30 取消本店自有配送：Uber 未就绪时，取餐方式下方只留一句中性说明 */
.checkout-soon-note { font-size: 12px; color: var(--muted); margin-top: 8px; line-height: 1.5; }

.track-box { margin: 14px 0 4px; text-align: center; }
.track-link {
  display: inline-block;
  padding: 11px 18px;
  border-radius: 12px;
  background: var(--tomato, #e2503a);
  color: #fff;
  font-weight: 800;
  font-size: 15px;
  text-decoration: none;
}
.track-link:active { transform: translateY(1px); }
.track-note {
  color: var(--muted, #8a8a8a);
  font-size: 13px;
  font-weight: 600;
  padding: 10px 12px;
  border-radius: 12px;
  background: rgba(0, 0, 0, .045);
}

/* ═══════════ 盒饭二级菜单选菜弹层（2026-09-30） ═══════════ */
.picker-overlay {
  position: fixed; inset: 0;
  z-index: var(--z-overlay);
  background: oklch(28% 0.03 55 / 0.55);
  display: none;
  align-items: flex-end; justify-content: center;
}
.picker-overlay.show { display: flex; }
.picker-card {
  background: var(--surface-warm);   /* 第五轮 §P1：与 Modal 同层 */
  width: 100%; max-width: 640px;
  max-height: 88vh;
  display: flex; flex-direction: column;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  border: 2px solid var(--line);
  border-bottom: none;
  box-shadow: var(--shadow-lg);
  padding-bottom: env(safe-area-inset-bottom);
  animation: picker-up var(--dur-2) var(--ease-out);
}
@keyframes picker-up {
  from { transform: translateY(28px); opacity: 0; }
  to { transform: none; opacity: 1; }
}
.picker-head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-xs);
  padding: 16px 20px 10px;
  border-bottom: 1px solid var(--line);
}
.picker-head h2 { font-family: var(--font-display); font-size: var(--text-lg); letter-spacing: .5px; }
.picker-hint { padding: 8px 20px 0; font-size: 12.5px; color: var(--muted); }
.picker-list { overflow: auto; padding: 12px 20px 4px; display: grid; gap: 8px; }
.picker-item {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 14px;
  border: 1.5px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  font: inherit; text-align: left; cursor: pointer;
  min-height: 56px;
  transition: border-color var(--dur-1), background var(--dur-1);
}
.picker-item:hover { border-color: var(--tomato); background: var(--tomato-soft); }
.picker-item.selected { border-color: var(--tomato); background: var(--tomato-soft); box-shadow: inset 0 0 0 1px var(--tomato); }
.picker-mark {
  width: 24px; height: 24px; flex: 0 0 auto;
  border-radius: 50%;
  border: 2px solid var(--line-strong);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 14px; font-weight: 900; color: transparent;
}
.picker-mark.square { border-radius: 7px; }
.picker-item.selected .picker-mark { border-color: var(--tomato); background: var(--tomato); color: var(--tomato-ink); }
.picker-names { display: grid; gap: 2px; min-width: 0; }
.picker-names .zh { font-weight: 800; font-size: 14.5px; }
.picker-names .en { font-size: 12.5px; color: var(--muted); }
.picker-foot {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-xs);
  padding: 12px 20px 18px;
  border-top: 1px solid var(--line);
}
.picker-count { font-weight: 800; font-size: 13px; color: var(--ink-soft); }
.picker-actions { display: flex; gap: 10px; }
@media (min-width: 640px) {
  .picker-overlay { align-items: center; padding: var(--space-sm); }
  .picker-card { border-radius: var(--radius-lg); border-bottom: 2px solid var(--line); max-height: 84vh; }
}

/* ═══════════ 打包袋：盒饭所选菜（2026-09-30） ═══════════ */
.ci-choice {
  grid-column: 1 / -1;
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: 12.5px; color: var(--ink-soft);
}
.ci-choice .ci-choice-label { color: var(--muted); }
.ci-choice .ci-choice-names { font-weight: 700; }
/* 打包袋「改 / 选菜」按钮；2026-09-30 乙：菜品详情里的盒饭选菜入口复用同一套样式 */
.ci-choice-btn {
  border: 1.5px solid var(--line-strong);
  background: var(--surface);
  border-radius: var(--radius-full);
  padding: 5px 12px;
  min-height: 34px;
  font: inherit; font-size: 12.5px; font-weight: 800;
  color: var(--tomato-deep);
  cursor: pointer;
  transition: background var(--dur-1), border-color var(--dur-1);
}
.ci-choice-btn:hover { background: var(--tomato-soft); border-color: var(--tomato); }
.ci-choice.missing .ci-choice-names { color: var(--tomato-deep); }

/* 2026-09-30 左上角品牌 Logo（陛下原图，已抠底） */
.brand-logo{border-radius:50%;flex:0 0 auto;display:block;object-fit:contain;
  width:38px;height:38px;margin-right:10px;
  filter:drop-shadow(0 1px 2px rgba(0,0,0,.10));}
.brand{display:inline-flex;align-items:center;}
@media (max-width:640px){.brand-logo{width:32px;height:32px;margin-right:8px;}}
