/* ============================================================
   珠海星图 · Demo A —— 手写 CSS,不用任何 UI 框架
   视觉基调:iOS 液态玻璃(大圆角 / 毛玻璃 / 细描边 / 柔阴影)
   移动端优先(基准 390×844),≥900px 切桌面布局
   ============================================================ */

/* ══════════════════════════════════════════════════════════
   自托管字体 · Self-hosted webfonts
   ──────────────────────────────────────────────────────────
   一套:MiSans(小米的),中西文同一套字形,v110 起全站统一。

   为什么必须自托管(用户反馈「全部字体换成无衬线有设计感的字体」):
   系统能给的选项里,只有 iOS/macOS 的苹方是合格的。Windows 上
   没有任何一款系统无衬线能达到那个标准,默认只剩微软雅黑 ——
   雅黑是【屏显字】:笔画平、字面撑满、字腔松、字重一旦加粗就糊成
   一团。放到一块 384px 宽的玻璃侧栏里,一行中文看着又肥又挤。

   之前是 Inter(拉丁)+ 思源黑体(中文)两套,靠 unicode-range
   分流:拉丁走 Inter,汉字落到思源。v110 按用户要求
   「整个网站的字体全部统一成小米的」换成 MiSans 一家 ——
   同一个界面里数字、英文、汉字出自同一套设计,基线也对得上。

   切片不走 CDN:早期版本引 cdn.jsdelivr.net 时实测过
   一次慢加载就把整页卡成白屏(见 index.html 里 MapLibre 那条注)。
   本地直读,同步可达,不引入任何新的失败模式。

   font-display:swap —— 字体没到位时先用系统字渲染,
   到位后整体替换,不挡首屏。 */

/* ══════════════════════════════════════════════════════════
   字体:MiSans(小米的),全站统一
   ──────────────────────────────────────────────────────────
   v110 从 Inter + Noto Sans SC 换成 MiSans VF,用户要求
   「整个网站的字体全部统一成小米的」。

   为什么是【可变字体】而不是静态的 Regular + Semibold 两份:
     静态两个字重 = 112 个切片,落盘 3.8MB,而且同一个汉字
       要按字重各下一次(400 一次、600 再下一次)。
     VF = 56 个切片,落盘 4.4MB,一个 @font-face 家族覆盖
       wght 150–700,同一个汉字【只下一次】两个字重共用。
   落盘只多 0.6MB,换来文件数减半、首屏请求数减半。

   切片来自小米官方 unicode-range 切分表,浏览器只取页面
   真正用到的那几片(通常 200–500KB),比之前思源 1.1MB
   全量下载还轻。
   56 条 @font-face 规则不在这里重复写 —— 放在
   vendor/fonts/misans/misans.css,由 index.html 用 <link> 引。
   那边切片和 CSS 同目录,url() 的相对路径天然对得上,
   不用改写(改写错了就是 404,浏览器静默回退系统字,
   肉眼看是"字体没生效")。

   ⚠ 这里【不要】再写一遍 @import。第一版同时写了 <link> 和 @import,
     同一份 CSS 被加载两次,document.fonts 里注册出 112 个 face
     (56 × 2)。重复注册不会报错、不会警告,只是白白多出一倍。

   许可(《MiSans 字体知识产权许可协议》):
     免费商用 ✓、可作嵌入式字体 ✓,
     但【必须在软件中特别注明使用了 MiSans】——
     这条是硬性义务,已在设置面板底部署名。
   官方 FAQ: https://hyperos.mi.com/font/faq
   切片包:   https://github.com/dsrkafuu/misans (Apache-2.0)
   下载:     node build/fetch-misans.mjs

   font-display:swap —— 字体没到位时先用系统字渲染,
   到位后整体替换,不挡首屏。 */

/* ══════════════════════════════════════════════════════════
   设计令牌 · Design Tokens
   ──────────────────────────────────────────────────────────
   配色走 iOS 的「语义化颜色」思路,而不是直接堆十六进制:
     --label-*   文字(一级/二级/三级/四级)
     --fill-*    填充(背景/分隔线)
     --accent    强调色
   两套主题由 <html data-theme="light|dark"> 驱动,
   右上角可手动切换并记忆;未设置时跟随系统。
   ══════════════════════════════════════════════════════════ */
:root {
  /* 字体栈:MiSans 一家,中西文同一套字形,不再靠"两个字体按字符分流"。
     v110 之前是 Inter(拉丁) + Noto Sans SC(中文),靠 unicode-range 分流;
     现在 MiSans VF 一个家族同时覆盖中西文,同一个界面里
     数字、英文、汉字出自同一套设计,基线也对得上 ——
     这就是"统一"在排版上的实际含义,不是"都叫同一个名字"。

     兜底顺序保留系统字:字体文件 404 / 加载失败时不能整页变成
     不可读的默认字。⚠ "Microsoft YaHei" 必须排在 "SF Pro Text"
     之后 —— 早期版本把它排在前面,结果 Windows 上连数字和英文
     都是雅黑渲染,西文笔画偏方、字宽偏窄。 */
  --font: "MiSans VF", "MiSans", -apple-system, BlinkMacSystemFont,
          "SF Pro Text", "PingFang SC", "Helvetica Neue", "Segoe UI",
          "Microsoft YaHei", Arial, sans-serif;

  /* ── 间距刻度(4pt / 8pt 网格)──
     联网查到的通用规范里,移动端间距网格是 8pt 主刻度 + 4pt 子刻度,
     并且明确「不要用 5/7/10/13/15/18/25 这类随手值」。
     全站只允许用下面 8 个值,组合出所有内边距、外边距和间隙。 */
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px;
  --sp-5: 20px; --sp-6: 24px; --sp-7: 32px; --sp-8: 40px;

  /* ── 圆角刻度 ── */
  --r-xs: 8px; --r-sm: 12px; --r-md: 16px; --r-lg: 20px; --r-xl: 26px;
  --r-full: 999px;

  /* ── 角色字阶:全站唯一的字号来源 ─────────────────────────
     依据(联网检索到的移动端正文与卡片排版规范,出处见
     docs/apple-ui-spec.md「字阶依据」一节):
       · 正文字号下限 14–16px,低于 14px 只留给说明性文字
       · 行高:正文 1.4–1.6,标题 1.1–1.2
       · 同屏字号种类收敛到 3 以内,字重只留 2 档

     Apple HIG《Typography》给的是【系统完整量程】11pt–34pt 共 11 档,
     那是给 Dynamic Type 滑杆用的,整档照搬进一个应用只会更乱。
     HIG 同时规定最小 11pt。这两条之间取值,就是下面这 7 级:

     角色         字号  行高  比值   字重   典型用途
     ────────────────────────────────────────────────────────
     display      20px  24px  1.20  600    【每个可见面板的主标题】
     heading      16px  21px  1.31  600    统计数字、弹窗标题、章节小标题
     body         15px  21px  1.40  400    列表行、搜索结果、菜名、按钮
     secondary    14px  20px  1.43  400    副信息、区域、备注
     tertiary     13px  19px  1.46  400    分组标签、计数、区域、评价
     caption      12px  17px  1.42  400    徽标、配额、统计数字下的小字
     micro        11px  16px  1.45  400    标签药丸、版权、微提示(下限)

     ⚠ display 那一档的【级差】是硬约束,不是随手定的:
       联网查到的移动端排版规范里,标题与正文的级差给的是 1.3×
       (vgonpa《Mobile Visual Design》),卡片标题区间 18–24px。
       v84 我为了「整体降字号」把标题 17→16、行名 15→14,
       两档同步下调,比值从 1.13 只走到 1.14 —— 数字上标题仍然 > 行名,
       肉眼却完全读不出标题感。用户原话:
         「珠海 · 美食足迹这几个字,为什么比下面的拱北口岸这些店名的字小啊」
       而且我当时那条 ui-test 断言只查了 `标题字号 > 行名字号`,
       16 > 14 就放过去了 —— **断言量错了东西**:它验的是大小关系,
       没验级差。现在按 1.3× 验,并且把面板主标题统一提到 display 档。
       20 / 15 = 1.33、20 / 14 = 1.43,都过线。

     行高【写死成 px 而不是写倍率】,是刻意的:
     「1.4」这种写法一旦有人改了字号,行高不会跟着变,版面就静默地
     挤回去了 —— 用户反馈的「太挤了」正是这么来的:
     上一版 17/22 = 1.29、15/20 = 1.33、13/18 = 1.38,三档全部低于 1.4。
     现在每一档的比值都标在注释里,改任何一个都会被 review 看见。 */
  --fs-display:   20px;  --lh-display:   24px;   /* 1.20 · 标题口径 */
  --fs-heading:   16px;  --lh-heading:   21px;   /* 1.31 */
  --fs-body-t:    15px;  --lh-body-t:    21px;
  --fs-secondary: 14px;  --lh-secondary: 20px;
  --fs-tertiary:  13px;  --lh-tertiary:  19px;
  --fs-caption:   12px;  --lh-caption:   17px;
  --fs-micro:     11px;  --lh-micro:     16px;

  /* 字重:只用 400 / 600 两档。
     HIG 明确要求避开 Ultralight / Thin / Light;
     联网到的规范也说「最多 2–3 档字重,再多就分不出层级了」。
     500 这一档【不提供】—— 汉字在 400→500 之间做字体合成会糊,
     真要用中间调,只有两个字重可用,那就只给两档。

     ⚠ v110 起 500 这档现在真的可用了(下面单独说明),所以 --fw-medium
     保留并且是真值 —— 原来这里和文件后面各写了一份一模一样的 5 个令牌,
     后一份是为了改注释才加的,却把前一份留着没删,于是这个 :root 块里
     同一个令牌出现两次。值相同所以一直没出事,但两处的注释互相打架:
     一处说「保留但没人用」,一处说「现在真的可用」。已合并成一份。 */
  --fw-regular: 400;
  --fw-medium:  500;
  --fw-semi:    600;
  --fw-semibold: 600;
  --fw-bold:    600;   /* 别名:有人写 700 时自动落到 600 */

  /* 字间距:HIG 的 tracking 表(负字距用于 13–23pt)只针对【拉丁字形】。
     本项目是中文界面 —— 照搬负字距会让汉字笔画粘连,所以中文一律 0。
     MiSans 自己的西文字距已经调好,不要再叠加。 */
  --ls-tight: 0; --ls-normal: 0; --ls-wide: .3px;

  /* ── 状态色(与 app.js 的 CLUTTER 保持一致)── */
  --go: #FF3B30;      /* 去过 · 还想再去 */
  --gray: #8a8f99;    /* 去过 · 不再去了 */
  --wish: #007AFF;    /* 想去 · 还没去 */
  --accent: #0a84ff;

  --sat: env(safe-area-inset-top);
  --sab: env(safe-area-inset-bottom);
  --sal: env(safe-area-inset-left);
  --sar: env(safe-area-inset-right);

  /* ── 主题切换图标(24×24 矢量,用作 mask)──────────────
     颜色不在 SVG 里指定,统一由 currentColor 提供,
     所以同一份形状在亮/暗两态都成立。 */
  --ico-moon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z'/%3E%3C/svg%3E");
  --ico-sun: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='5'/%3E%3Cg stroke='black' stroke-width='2' stroke-linecap='round' fill='none'%3E%3Cpath d='M12 1.8v2.4M12 19.8v2.4M22.2 12h-2.4M4.2 12H1.8M19.07 4.93l-1.7 1.7M6.63 17.37l-1.7 1.7M19.07 19.07l-1.7-1.7M6.63 6.63l-1.7-1.7'/%3E%3C/g%3E%3C/svg%3E");

  /* ── 工具条图标(24×24 描边矢量,用作 mask)────────────
     HIG《Toolbars》:"Prefer the standard symbols for each, and don't use a
     text label that says Back or Close." —— 工具条项该用标准符号。

     原来这里直接写 Unicode 字符 ✛ / ☰ / ⚙︎ / ☷,有三个实际问题:
       ① 同一个字符在不同系统落在不同字体里,笔画粗细对不上;
       ② ⚙︎ 要靠变体选择符 U+FE0E 才能稳定,漏了就变成彩色 emoji;
       ③ 字符没法跟主题走 —— mask 方案让形状和颜色彻底分开,
          亮/暗两态只换 currentColor,和上面的月牙/太阳是同一套机制。
     路径按 SF Symbols 对应符号 1.7px 描边重绘,24×24 视口。
     由 `node build/gen-icons.mjs` 生成,不要手改(编码容易出错:
     「# → %23」和「% → %25」两个转义会互相打架,踩过一次)。 */
  --ico-pin: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 21.2c0 0 6.2-5.6 6.2-10.5a6.2 6.2 0 1 0-12.4 0C5.8 15.6 12 21.2 12 21.2Z'/%3E%3Ccircle cx='12' cy='10.4' r='2.4'/%3E%3C/svg%3E");
  --ico-list: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='4.6' cy='7' r='1.15' fill='black' stroke='none'/%3E%3Ccircle cx='4.6' cy='12' r='1.15' fill='black' stroke='none'/%3E%3Ccircle cx='4.6' cy='17' r='1.15' fill='black' stroke='none'/%3E%3Cpath d='M9.4 7h9.6M9.4 12h9.6M9.4 17h9.6'/%3E%3C/svg%3E");
  --ico-sliders: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 5.5h5.5M12 5.5H21M3 12h8.5M15 12h6M3 18.5h4M10.5 18.5H21'/%3E%3Ccircle cx='10.2' cy='5.5' r='2.4' fill='black' stroke='none'/%3E%3Ccircle cx='13.2' cy='12' r='2.4' fill='black' stroke='none'/%3E%3Ccircle cx='8.7' cy='18.5' r='2.4' fill='black' stroke='none'/%3E%3C/svg%3E");
  --ico-gear: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='3.1'/%3E%3Cpath d='M12 2.6v2.6M12 18.8v2.6M2.6 12h2.6M18.8 12h2.6'/%3E%3Cpath d='M5.3 5.3l1.9 1.9M16.8 16.8l1.9 1.9M18.7 5.3l-1.9 1.9M7.2 16.8l-1.9 1.9'/%3E%3C/svg%3E");
  --ico-strip: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='3 5.9 18 12.2' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 15.4 12 8.4l7 7'/%3E%3C/svg%3E");
}

/* ── 亮色(默认)─────────────────────────────────────────
   iOS 浅色的要点:背景不是纯白而是极浅的灰,文字是接近黑的深灰,
   层次靠「明度差」而不是靠阴影。 */
:root,
:root[data-theme="light"] {
  color-scheme: light;
  --label-1: #1c1c1e;          /* 主要文字 */
  --label-2: rgba(45, 45, 50, .80);   /* 次要 */
  --label-3: rgba(45, 45, 50, .76);   /* 三级 */
  --label-4: rgba(60, 60, 67, .20);   /* 占位/禁用 */

  --bg-page: #f2f2f7;           /* 页面底色 */
  --bg-raised: #ffffff;
  --separator: rgba(60, 60, 67, .14);

  /* 毛玻璃:浅色下靠「白 + 模糊」,投影要极轻 */
  --glass-tint: 255, 255, 255;
  --glass-a: .62;
  --glass-a-strong: .80;
  /* 承载文字的面板(详情卡/图例/清单/搜索下拉)走 regular 材质。
     ⚠ 这里的数值【不能再往上加】—— 加到 .90 以上玻璃感就完全消失
       (用户反馈过「之前提到要液体玻璃的效果,现在又没有了」),
       往下加也别低于 .62,不然底图亮处会透上来把字吃掉。
       真正的可读性保障来自三件事,不是提高不透明度:
         ① 三个浮层面板【互斥】,不会有两层玻璃叠在一起互相穿透
            (HIG《Modality》"Let people dismiss a modal view before
            presenting another one");
         ② 面板里的文字有一层同色 scrim 垫底(--scrim);
         ③ 144 项 WCAG AA 验收兜底,任何一次改动都必须重跑。
       .74 是上一版实测出来的位置,用户反馈「看起来像全白的」——
       玻璃叠在 Apple 极简底图上时,底图本身就是浅色的,
       .74 的白叠上去和纯白只差一点点,等于没有透。
       降到 .66 之后,底图的街块色和路网能明确地透上来。 */
  --glass-a-panel: .66;
  --blur: blur(34px) saturate(190%);
  --glass-shadow: 0 1px 2px rgba(0, 0, 0, .04), 0 8px 24px rgba(0, 0, 0, .08);
  --spec-top: rgba(255, 255, 255, .95);
  --bevel: inset 0 1px 0 rgba(255, 255, 255, .8);
  --edge-hairline: 0 0 0 .5px rgba(0, 0, 0, .06);
  --bevel-op: 1;

  /* ══ 玻璃边缘(液态玻璃感的另一半)═════════════════════════
     之前只有 Hyalite 挂过的元素才有边缘高光(--hyalite-edge),
     侧栏 / 搜索下拉 / 下拉面板这些大框一律没有,只剩下
     --glass-shadow 那一层外投影 —— 暗色下外投影几乎不可见,
     于是一块深色平板,这就是用户说的「暗色下不像玻璃」。

     真实玻璃的边缘长这样(从亮边往内):
       ① 极细的亮线(玻璃表面把光弹回来)
       ② 亮线内侧一条暗带(全反射前的临界暗边)
       ③ 再往内一点点回暖(厚度感)
     这三层全是 inset box-shadow —— **合成器属性,几乎不要钱**,
     而且在所有浏览器里都生效(Safari/Firefox 没有折射,靠的就是它)。

     ⚠ v114:亮色也去掉了最外那道「绕一圈」的白线。
       原来这里第一层是 inset 0 0 0 .5px .92 的【全环】,注释里写着
       「亮线必须到 .92 才看得见」—— 那是照着暗色推的。暗色下底图深,
       一道 .22 的弱亮线刚好当边缘;可亮色下底图本来就浅,
       .92 的白环就是货真价实的一圈描边。
       上一轮只把暗色换成了 --rim-dark(无全环),亮色原样留着 ——
       于是暗色干净了、亮色照旧,用户在亮色截图里又标了一次。
       现在两态结构一致:都只有【顶缘一道内高光】+【内侧暗带】,
       没有绕全边的环。暗色那边已经证明过「压亮线、提暗带,
       边缘反而更清楚」(见下面 --rim-hi 暗色值 .22 / 暗带 .34)。 */
  --rim: inset 0 1.5px 0 0 var(--rim-hi-2),
         inset 0 -2px 3px -2px var(--rim-lo),
         inset 0 2px 4px -2px var(--rim-lo-2);
  /* 亮色 */
  --rim-hi-2: rgba(255, 255, 255, .62);
  --rim-lo: rgba(0, 0, 0, .13);
  --rim-lo-2: rgba(0, 0, 0, .07);
  /* 镜面光束(亮色):从 .16 再压到 .10。
     原来 .34 → 用户看到「发白」;.16 仍然能在截图里看出左上角一片亮;
     出图对比后压到 .10,玻璃感靠透不靠亮,底图浅的时候尤其如此。 */
  --sheen-a: rgba(255, 255, 255, .10);
  --sheen-b: rgba(255, 255, 255, .03);

  /* 文字 scrim:半透明背景上放文字,只靠 blur 是不够的,
     底图亮的地方会把字吃掉,所以要补一层同色底。
     它用的是【玻璃自己的 tint】而不是固定白 —— 早先写死白色,
     结果 .pill.danger 把 tint 换成深酒红之后,白 scrim 压在酒红上
     混成了泥巴色。改成跟随 tint 之后,任何 tint 都能得到同色系的 scrim。

     合成不透明度(亮色):1-(1-.62)(1-.34)=.75 顶 / 1-(1-.62)(1-.20)=.70 底。
     这个值是算出来的,不是拍的:WCAG 公式下,.70 白压在纯黑底图上
     相对亮度 .448,配 #1c1c1e 正文(亮度 .0116)仍有 8.1:1,
     远高于 4.5:1 的门槛;再薄就会掉到 4.3:1 以下,所以 0.70 是安全下限附近。 */
  --scrim-a: .34;
  --scrim-b: .20;
  --scrim: linear-gradient(180deg,
              rgba(var(--glass-tint), var(--scrim-a)),
              rgba(var(--glass-tint), var(--scrim-b)));

  --go: #f2562e; --gray: #7a7f88; --wish: #0a6fe0; --accent: #007aff;
  /* 开关用 iOS 系统绿,不用 accent 蓝。
     HIG《Color》"Don't use the same color to mean different things" ——
     蓝色在这个界面里已经被「想去 / 筛选 / 链接」占用了,
     开关再占一次,同一块屏幕上两个不同语义共用一个颜色。 */
  --track-off: rgba(120, 120, 128, .32);
  --sw-green: #34c759;

  /* ⚠ v114 关键修正:亮色也定义 --glass-edge。
     这条之前【只有暗色有】,于是产生了一个很难发现的分裂:
       消费端写的是 var(--glass-edge, var(--hyalite-edge, …))
       暗色 --glass-edge 有值  → 短路,--hyalite-edge 根本不参与
       亮色 --glass-edge 不存在 → 回退到 Hyalite 内联写在元素上的
                                 --hyalite-edge
     而 Hyalite 那条的第一层就是:
       inset 0 0 0 .5px rgba(255,255,255,0.202)   ← 绕整圈的白线
     所以【同一个搜索框,暗色干净、亮色有一圈白边】,
     跟主题无关、与代码无关,纯粹是这两个自定义属性谁先有值的问题。

     这就是前两轮一直找不到的原因:前两轮都在改 --rim,
     可亮色下的 .search 压根没用 --rim,用的是 Hyalite 的边。
     靠读 CSS 猜不出来 —— 是 CDP.getMatchedStylesForNode 把元素上的
     内联自定义属性原样吐出来才看见的(build/probe-who-draws.mjs)。

     现在两态都显式给 --glass-edge,--hyalite-edge 全站不再被消费,
     玻璃面只剩一种边缘来源。 */
  --glass-edge: var(--rim);

  /* ══ 滚动条 · 全站唯一一套 ══════════════════════════
     用户原话「这个滚动条太丑了,帮我思考优化方式,通透一些。
     所有的滚动条都是按照一个统一标准设计」。
     改之前先量过(build/probe-v114.mjs):全站会滚的容器里
     只有 #setup 一处在溢出,滑块占 15px —— 那是系统默认的宽度,
     而且轨道是一条实心灰槽,在深色玻璃面板上又黑又粗,最抢眼。

     HIG《Scroll views》本来就要求用系统滚动指示器、不要自创,
     所以这里不是"设计一根新滑块",而是把默认那根调到接近
     iOS/macOS 的 overlay 观感:细、半透明、轨道透明、药丸形。
     写成全局一条 + 一组令牌,以后新增面板自动一致,
     不会再出现"这个面板好看、那个面板是系统默认"的分裂。 */
  --sb-w: 6px;                       /* 滑块占位;15px → 6px */
  --pill-hi: rgba(255, 255, 255, .22);   /* 玻璃按钮悬停时叠的一层提亮 */
  --su-head-bg: rgba(255, 255, 255, .95);  /* 设置面板吸顶头部:盖得住滚过去的内容 */
  --sb-thumb: rgba(120, 120, 128, .32);
  --sb-thumb-hi: rgba(120, 120, 128, .52);
  --sb-corner: transparent;
}

/* 标准滚动条:Firefox 走 scrollbar-width/color,
   Chromium / Safari 走 ::-webkit-* 伪元素。
   ⚠ 两者必须【分开声明】,不能一起给。
     一起给的时候 Blink 以 scrollbar-width 为准,::-webkit-scrollbar
     的 width 被整条忽略 —— 实测声明 6px、量出来 10px(scrollbar-width:thin
     在 Blink 里就是 ~10px)。这正是本项目吃过好几次的
     「声明了一条规则 ≠ 那条规则在起作用」。
     所以标准属性只发给不支持伪元素的浏览器(实质就是 Firefox),
     支持伪元素的一律走下面那套,拿到真正的 --sb-w。 */
@supports not selector(::-webkit-scrollbar) {
  * {
    scrollbar-width: thin;
    scrollbar-color: var(--sb-thumb) transparent;
  }
}
::-webkit-scrollbar {
  width: var(--sb-w);
  height: var(--sb-w);
}
::-webkit-scrollbar-track,
::-webkit-scrollbar-corner { background: var(--sb-corner); }
::-webkit-scrollbar-thumb {
  background: var(--sb-thumb);
  border-radius: var(--r-full, 999px);
  /* 贴边时给一点内缩,免得药丸紧贴面板边框像个黑条 */
  border: 0;
}
::-webkit-scrollbar-thumb:hover { background: var(--sb-thumb-hi); }
::-webkit-scrollbar-thumb:active { background: var(--sb-thumb-hi); }
/* 横向滑动不需要竖向指示器(分类胶囊那类),这里只保证不留残影 */
::-webkit-scrollbar-button { display: none; width: 0; height: 0; }

/* ── 暗色 ────────────────────────────────────────────────
   iOS 暗色的要点:背景用纯黑或近黑(不是深灰),层次靠「提亮」,
   所以阴影基本不用,改成用更亮的表面色表达层级。 */
:root[data-theme="dark"] {
  color-scheme: dark;
  --label-1: #ffffff;
  --label-2: rgba(235, 235, 245, .74);
  --label-3: rgba(235, 235, 245, .64);
  --label-4: rgba(235, 235, 245, .20);

  --bg-page: #000000;
  --bg-raised: #1c1c1e;
  --separator: rgba(235, 235, 245, .14);

  --glass-tint: 28, 28, 30;
  --glass-a: .78;
  --glass-a-strong: .90;
  --glass-a-panel: .78;     /* 暗色底图杂,比亮色略实 */
  --blur: blur(28px) saturate(160%);
  /* 暗色下不用投影,用内高光 + 细微亮边表达玻璃厚度 */
  --glass-shadow: inset 0 1px 0 rgba(255, 255, 255, .10);
  --spec-top: rgba(255, 255, 255, .12);
  --bevel: inset 0 1px 0 rgba(255, 255, 255, .12);
  --edge-hairline: 0 0 0 .5px rgba(255, 255, 255, .08);
  /* 暗色下的滚动条:底色换成偏冷的浅灰,不能沿用亮色那支
     rgba(120,120,128,.32) —— 那个亮度在近黑面板上会显得发灰发脏。 */
  --sb-thumb: rgba(235, 235, 245, .26);
  --sb-thumb-hi: rgba(235, 235, 245, .44);
  --pill-hi: rgba(255, 255, 255, .14);
  --su-head-bg: rgba(28, 28, 30, .95);
  --bevel-op: 1;

  /* 暗色下的边缘要反过来调。
     暗色玻璃不是「白得更少」这么简单:亮色模式里,边缘的亮线是
     整块玻璃唯一的高光,所以要拉到接近纯白才认得出边界;
     暗色模式里,底图本身已经很暗,一条 .92 的白线会变成刺眼的
     描边(用户明确反感"一圈白边"),真正让人看出厚度的是
     亮线内侧那条【暗带】—— 光在玻璃-空气界面全反射回来的地方。
     所以这里把亮线压到 .22、把暗带提到 .34,边缘反而更清楚。
     v114:--rim 也去掉了全环那层,和亮色对齐 —— 它在暗色只是
     --rim-dark 的兜底,留着就等于给「没挂 --glass-edge 的元素」
     留了一条旧的绕圈白线,两套结构迟早会再漂一次。 */
  --rim: inset 0 1.5px 0 0 var(--rim-hi-2),
         inset 0 -2px 3px -2px var(--rim-lo),
         inset 0 2px 4px -2px var(--rim-lo-2);
  --rim-hi-2: rgba(255, 255, 255, .10);
  --rim-lo: rgba(0, 0, 0, .34);
  --rim-lo-2: rgba(0, 0, 0, .22);
  /* 暗色专用的边缘:【没有最外那道 0.5px 亮线】。
     用户原话「暗色模式下,外圈有一圈多余的白边,删掉白边」。
     量过来源:折射面上的那圈是 Hyalite 内联写的
     rgba(255,255,255,.23) 0.5px inset,没挂折射的框走 --rim(.22) ——
     两个来源数值几乎一样,所以整屏都顶着同一道环,看着像"描边"。
     去掉之后边缘改由这两层撑:
       · 顶部一道内高光(玻璃表面把光弹回来)
       · 一条暗带(全反射的临界暗边)—— 暗色里它才是厚度感的主来源
     边缘因此"从里往外"而不是"从外往里"收,深色面板上不再显出一圈白框。 */
  --rim-dark:
      inset 0 1.5px 0 0 var(--rim-hi-2),
      inset 0 -2px 3px -2px var(--rim-lo),
      inset 0 2px 4px -2px var(--rim-lo-2);
  /* 镜面光束(暗色):这一条是用户第二次标出来的「还有一圈白色的边」。
     ⚠ 先定位来源再动手 —— 前两次都改错了地方:
       第一次以为是 --rim(改完还在),第二次以为是 Hyalite 的边(也不是)。
       真正量出来的是这束光:沿元素中线扫像素,
         .search   外侧 44 · 内侧边缘 58 · 内侧中心 35
       左侧比中心亮 22、右侧却是 30(比中心还暗)——【左右不对称】,
       所以它不是一圈描边,是一束方向性很强的光。
       .16 的冷白压在亮度 35 的暗面板上,16% × 225 ≈ +36,肉眼就是白边。
       现在压到 .06,并且把渐变的结束点提前(见 .glass::after 的
       148deg / 26% / 48%),让它只落在左上角,不再沿整条左边铺开。 */
  --sheen-a: rgba(214, 228, 255, .06);
  --sheen-b: rgba(190, 210, 255, .02);


  --go: #ff7a4d; --gray: #989da6; --wish: #5aa2ff; --accent: #0a84ff;
  --track-off: rgba(120, 120, 128, .32);
  --sw-green: #30d158;

  /* 暗色下所有玻璃面统一改用 --rim-dark(没有最外那道 0.5px 白线)。
     放在 :root 上,自定义属性会继承到每一个元素,
     所以下面五处消费规则一行都不用各自再写一遍暗色覆盖。 */
  --glass-edge: var(--rim-dark);
}

/* 未手动选择时跟随系统 */
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) {
    color-scheme: light;
    --label-1: #1c1c1e; --label-2: rgba(45,45,50,.80);
    --label-3: rgba(45,45,50,.76); --label-4: rgba(60,60,67,.20);
    --bg-page: #f2f2f7; --bg-raised: #fff; --separator: rgba(60,60,67,.14);
    --glass-tint: 255,255,255; --glass-a: .62; --glass-a-strong: .80;
    --blur: blur(34px) saturate(190%);
    --glass-shadow: 0 1px 2px rgba(0,0,0,.04), 0 8px 24px rgba(0,0,0,.08);
    --spec-top: rgba(255,255,255,.9);
    --bevel: inset 0 1px 0 rgba(255,255,255,.8);
    --edge-hairline: 0 0 0 .5px rgba(0,0,0,.06);
    --go: #f2562e; --gray: #7a7f88; --wish: #0a6fe0; --accent: #007aff;
    --track-off: rgba(120, 120, 128, .32);
    --sw-green: #34c759;
  }
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    --glass-edge: var(--rim-dark);
    color-scheme: dark;
    --label-1: #fff; --label-2: rgba(235,235,245,.74);
    --label-3: rgba(235,235,245,.64); --label-4: rgba(235,235,245,.20);
    --bg-page: #000; --bg-raised: #1c1c1e; --separator: rgba(235,235,245,.14);
    --glass-tint: 28,28,30; --glass-a: .78; --glass-a-strong: .90;
    --blur: blur(28px) saturate(160%);
    --glass-shadow: inset 0 1px 0 rgba(255,255,255,.10);
    --spec-top: rgba(255,255,255,.12);
    --bevel: inset 0 1px 0 rgba(255,255,255,.12);
    --edge-hairline: 0 0 0 .5px rgba(255,255,255,.08);
    --go: #ff7a4d; --gray: #989da6; --wish: #5aa2ff; --accent: #0a84ff;
    --track-off: rgba(120, 120, 128, .32);
    --sw-green: #30d158;
    --sb-thumb: rgba(235, 235, 245, .26);
    --sb-thumb-hi: rgba(235, 235, 245, .44);
    --pill-hi: rgba(255, 255, 255, .14);
    --su-head-bg: rgba(28, 28, 30, .95);
  }
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

/* ── 焦点环:键盘要有,鼠标/触摸不要 ──────────────────────
   用户反馈「所有按钮都有一圈边」——其中一圈就是浏览器默认焦点环:
   鼠标点过按钮之后它会留在 :focus 上,画出一圈 -webkit-focus-ring。
   但焦点环不能直接删:WCAG 2.4.7 Focus Visible 要求键盘用户必须看得见焦点。

   正解是按「怎么进来的」区分:
     :focus         鼠标/触摸点出来的 → 去掉,靠按下态和选中态表达
     :focus-visible 键盘 Tab 过来的   → 画清楚,2px 实心 + 2px 间隙
   :focus-visible 是浏览器自己判断的,不需要写 JS。 */
:focus { outline: none; }
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: inherit;
}
/* 键盘用户走过的路径要更明显:焦点环不能被 overflow 裁掉 */
.topbar :focus-visible, .fmc-panel :focus-visible { z-index: 2; }

/* 必须有这一条:下面很多元素带 hidden 属性,但我又用 #id / .class 写了 display,
   id/class 选择器的优先级高于浏览器默认的 [hidden]{display:none},
   结果就是「弹窗和提示条永远显示在屏幕上」。 */
[hidden] { display: none !important; }

html, body {
  margin: 0; padding: 0;
  height: 100%;
  overflow: hidden;                 /* 地图应用不该有页面级滚动 */
  /* 必须用 --bg-page,它跟 data-theme 走。之前这里写死 #e8e8ee,
     暗色用 @media (prefers-color-scheme: dark) 另给 #0b0b0f ——
     于是「系统是暗色 + 用户手动切到亮色」时,body 仍是近黑色,
     瓦片没覆盖到的地方(海面边缘、境外、缩放边界)就漏出一条黑边。 */
  background: var(--bg-page);
  font-family: var(--font);
  font-style: normal;
  color: var(--label-1);
  -webkit-font-smoothing: antialiased;
  overscroll-behavior: none;
}

/* 全局禁掉「浏览器自己造字形」。
   font-synthesis 现在【整条关掉】,而不是只关 italic。原因是自托管的
   思源黑体只提供了 400 / 600 两个字面:任何落在中间的字重(比如
   500),浏览器都会在 400 的基础上做一次「伪加粗」—— 笔画被整体
   描粗一点点,汉字的结构就糊了,思源那种干净的字腔全没了。
   宁可让 500 落到 400,也不要一个糊掉的中间态。
   伪斜体同理:苹果地图的地名注记是绝对正体的。 */
body { font-synthesis: none; }
i, em, cite, dfn, address { font-style: normal; }

/* 正文里的强调词用 Semibold,不是 Bold。
   浏览器给 <b> 的默认是 700 —— 全站 15 处 <b> 全是 700,
   在一片 Regular 正文里砸出 15 块「黑斑」,这正是用户说的「不要加粗」。
   HIG 口径:强调态 Title3 及以下统一 Semibold。 */
b, strong { font-weight: var(--fw-semi); }

/* ── 全局按压 / 悬停反馈 ──────────────────────────────────
   苹果的交互反馈分两档,这里照做:
     · hover(仅指针设备)—— 轻微提亮,提示「这是可点的」
     · active(按下)    —— 背景变实 + 轻微缩小,手指/鼠标能立刻确认按到了
   hover 用 @media (hover:hover) 包住 —— 触屏设备上
   「hover 态粘住不放」是移动端最常见的幽灵态来源。

   ⚠⚠ 但 hover【绝对不能改 background-color】—— 改完整个界面会卡。
   实测(build/probe-where.mjs,真实鼠标移动,1679×899):

     真实扫过 12 行店名,最差的一帧耗时
       面板用 Hyalite SVG 滤镜   150.9ms   ← 掉约 9 帧,肉眼就是「一卡」
       换成原生 blur(20px)       39.4ms
       完全关掉 backdrop-filter    9.0ms

   原因不是「画得慢」——Paint 只有 3.4ms、RasterTask 8.6ms,都很便宜。
   贵的是合成:**元素带 backdrop-filter 时,它内部任何一次重绘,
   都要把整块滤镜在整块区域上重跑一遍。**
   清单行是 #side 的子元素,#side 是 .glass、带 Hyalite 的
   url(#hyalite-N)(含 feDisplacementMap 的 SVG 滤镜)——
   鼠标扫过一行,浏览器就把 320×870 的位移滤镜整个重算一次。

   所以这里换一种做法:加一层 ::before 覆盖层,只动它的 opacity。
   opacity 是【合成器属性】,不触发重绘、不作废面板的 backdrop 缓存,
   视觉上和高亮底色完全一样,代价却接近零。
   —— 这就是「保住液态玻璃 + 不卡」的唯一解:不能靠去掉滤镜,
     去了玻璃感就没了;要动的是【什么属性被改变】。 */
@media (hover: hover) {
  /* 玻璃控件用 brightness,不用 background-color ——
     这些按钮的背景是 `background: scrim + rgba(tint, a)` 的玻璃,
     :hover 再设 background-color 会把玻璃整个盖掉,
     按钮就「变透明 + 只剩一圈白边」(用户反馈的原话)。 */
  .glass.pill:not(:disabled):hover,
  .search:hover { filter: brightness(1.07); }

  /* 行类元素:覆盖层 + opacity,不动背景 */
  .lg-row, .r, .side-list .it, .fmc-chip, .fmc-cap, .fa-leg { position: relative; }
  .lg-row::before, .r::before, .side-list .it::before,
  .fmc-chip::before, .fmc-cap::before, .fa-leg::before {
    content: ''; position: absolute; inset: 0;
    background: rgba(127, 127, 140, .14);
    border-radius: inherit;
    opacity: 0; pointer-events: none;
    transition: opacity .18s ease;
    z-index: 0;
  }
  .lg-row:not(.on):hover::before, .r:hover::before, .side-list .it:hover::before,
  .fmc-chip:hover::before, .fmc-cap:hover::before, .fa-leg:hover::before { opacity: 1; }
  /* 内容必须压在覆盖层之上 */
  .lg-row > *, .r > *, .side-list .it > *,
  .fmc-chip > *, .fmc-cap > *, .fa-leg > * { position: relative; z-index: 1; }
}
button:not(:disabled):active,
.glass.pill:not(:disabled):active { filter: brightness(.94); transform: scale(.96); }
/* 按下态也走覆盖层,同样是为了不重绘玻璃。
   原来这里给 .it / .r / .lg-row 设 background-color,按一下就掉一帧。 */
.r:active::before, .lg-row:active::before, .side-list .it:active::before,
.fmc-chip:active::before, .fmc-cap:active::before, .fa-leg:active::before { opacity: 1; }
/* 过渡挂在【实际的行元素】上,不只是 button ——
   搜索结果 .r、图例行 .lg-row、清单行 .it 里有的不是 button,
   只给 button 加过渡的话这些行按下去会「跳」一下,没有过渡。
   高亮走 ::before 自己的 opacity 过渡(见上面),这里只管 transform。 */
button, .r, .lg-row, .side-list .it, .fmc-chip, .fmc-cap, .fa-leg {
  transition: transform .16s ease;
}
@media (prefers-reduced-motion: reduce) {
  button, .r, .lg-row, .side-list .it,
  .fmc-chip, .fmc-cap, .fa-leg { transition: none; }
  .lg-row::before, .r::before, .side-list .it::before,
  .fmc-chip::before, .fmc-cap::before, .fa-leg::before { transition: none; }
}

button, input { font-family: inherit; color: inherit; }

/* 按钮默认样式重置。
   ⚠ 这一条是补一个真实漏网:<button> 的 UA 默认样式带一圈 border,
     之前只在 .glass / .icon-btn / .btn-sm / .seg button 上各自写了 border:0,
     结果详情卡底部那三个 <button class="pill"> 谁也不属于 ——
     没有 .glass(所以没吃到 .glass 里的 border:0),也没别的规则覆盖,
     浏览器那圈默认描边就原样留在了屏幕上(用户反馈的「边框巨丑」)。
     放在全局而不是继续逐个补:漏一次就会丑一次,不如一开始就掐掉。
     安全性:.glass / .pill / .btn-sm 都是类选择器(0,1,0),
     优先级高于 button(0,0,1),各自的背景不受影响。 */
button { border: 0; background: none; }

/* ══ 底图调色层 #veil ════════════════════════════════
   硬约束:高德只提供 style=8 一种栅格瓦片,没有浅色/暗色可选
   (实测 style 7 / 9~12 全部 404),所以亮暗两套观感只能靠后期。

   为什么不对 canvas 做 filter 反相:
   状态标记(暖橙/蓝的点)是画在【同一张 canvas】上的,
   对 canvas 反相会把状态色一起反掉 —— 语义就错了。

   为什么不叠一层彩色蒙版:
   试过用青蓝渐变模拟 Apple Maps 的深海蓝,结果是整座珠海变成
   「地中海」,非常丑 —— 那是在给地图化妆,而不是在做减法。

   现在的做法是【克制的两件事】,两套主题共用:
     ① 轻微降饱和 —— 让底图安静下来,不与标记争夺注意力;
     ② 极轻暗角 —— 把视线收到画面中心。
   亮色和暗色的差异交给顶部工具条的 scrim 与底图亮度自然形成,
   不对底图整体染色。 */
#map { position: fixed; inset: 0; }

#veil {
  position: fixed; inset: 0;
  pointer-events: none;             /* 蒙版不能吃掉地图手势 */
  z-index: 1;
  opacity: 1;
  transition: opacity .45s ease;
  /* 不染色。底图保持高德本来的颜色,只做两件克制的事:
     ① 轻微降饱和 —— 让底图安静下来,不抢标记;
     ② 极轻的暗角 —— 把注意力收到画面中心。
     之前那层蓝色蒙版把整个珠海染成了「地中海」,是过度设计。 */
  backdrop-filter: saturate(.88) contrast(1.02) brightness(1.0);
  -webkit-backdrop-filter: saturate(.88) contrast(1.02) brightness(1.0);
  background: radial-gradient(ellipse 120% 90% at 50% 45%, transparent 55%, rgba(0,0,0,.16) 100%);}
/* 标记层在 canvas 里,而 veil 是 canvas 之上的一层蒙版,
   所以标记一定会被压暗一档。这里在绘制标记图层时把亮度提回来,
   让状态色在暗色底图上依然鲜明 —— 状态色是本产品最重要的信息,
   不能因为底图变暗就丢掉语义。 */
.maplibregl-ctrl-top-right { z-index: 2; position: relative; }
.maplibregl-marker { position: relative; z-index: 2; }

/* 地图控件(缩放按钮 / 版权)也走玻璃,否则它是整屏唯一的「非玻璃」元素。
   玻璃基类在文件后面的 .glass 里定义,这里只补控件特有的定位。 */
.maplibregl-ctrl-attrib {
  background: var(--scrim), rgba(var(--glass-tint), var(--glass-a)) !important;
  border-radius: var(--r-xs) 0 0 0;
  font-size: var(--fs-caption);
  color: var(--label-3) !important;
  backdrop-filter: var(--blur);
  -webkit-backdrop-filter: var(--blur);
}
.maplibregl-ctrl-attrib a { color: var(--label-2) !important; }
.maplibregl-ctrl-bottom-right { bottom: calc(var(--sab) + 4px); }
.maplibregl-ctrl-bottom-left  { bottom: calc(var(--sab) + 4px); left: auto; right: 0; }
.maplibregl-ctrl-top-right { top: calc(var(--sat) + 8px); right: 8px; }

/* 署名(建筑轮廓 © OpenStreetMap)必须【永远可见】。
   它是 OSM 数据许可的强制要求,不能删、也不能被面板盖住 ——
   用户反馈「这个还被挡住了」,那就把「被挡住」这个状态消掉,而不是把它删了。
   两个场景各给一条:
     ① 底部抽屉展开时,抬到抽屉上沿之上(Apple 地图就是这么处理的)
     ② 桌面右侧卡片展开时,卡片上沿就够不着底边,只要限高就够
   --card-h-live 由 JS 在卡片开合时写到 <html> 上。
   只针对移动端底部抽屉;桌面端详情卡是右上角侧栏,加了 max-height 之后
   本来就够不着底边,不需要这条。 */
@media (max-width: 899px) {
  html:has(#card.open) .maplibregl-ctrl-attrib {
    bottom: calc(var(--sab) + var(--card-h-live, 50dvh) + 10px);
  }
}
/* 3D 模式下 pitch 后罗盘可用,但我们默认隐藏多余的控件堆 */
.maplibregl-ctrl-top-right .maplibregl-ctrl:not(.maplibregl-ctrl-group) { display: none; }

/* ── 液态玻璃基类 · Liquid Glass ─────────────────────────
   三层结构,缺一不可:
     ::before  → 折射层(backdrop-filter: url(#…)),只在边缘一圈可见
     ::after   → 镜面高光层(顶部亮线 + 四周内倒角),让边缘「有厚度」
     本体      → 磨砂底(blur + saturate)+ 极淡染色 + 分层投影
   关键取舍:折射只作用于边缘环(见 --edge),因为面板里全是文字,
   中心如果一起扭曲会毁掉可读性 —— 真实 iOS 液态玻璃也是中心清、边缘弯。
   ─────────────────────────────────────────────────────── */
/* ── 液态玻璃基类 · Liquid Glass ─────────────────────────
   折射交给 Hyalite(vendor/hyalite.js,MIT)按元素真实尺寸与圆角计算:
   它把透镜图写进 --hyalite,把锐利边缘高光写进 --hyalite-edge,
   这里只负责消费这两个变量 + 提供不支持时的毛玻璃兜底。

   为什么不再手写 SVG 滤镜:
   折射必须按元素精确几何计算距离场(SDF),方向场来自「半径 + bevel」的
   放大矩形法线,零位移编码在 RGB(128,128)。固定参数的模糊矩形做不到 ——
   中心会无意义偏移、边缘只会糊不会「往里拉」,而且 resize 后直接错位。

   注意:--hyalite 是【继承】的自定义属性,只能在本元素消费;
   子元素再读一次就会套用为父元素算的滤镜(作者在 gotchas 里明确警告过)。 */
.glass {
  position: relative;
  border-radius: var(--r-lg);
  /* 折射 + 磨砂。不支持的浏览器走 fallback,依然是干净的 iOS 毛玻璃。 */
  backdrop-filter: var(--hyalite, var(--blur));
  -webkit-backdrop-filter: var(--hyalite, var(--blur));
  /* 玻璃本体:淡染色 + 一层极淡的文字 scrim。
     scrim 是关键 —— 半透明背景上放文字,只靠 blur 不够,
     底图亮的地方会透上来把字吃掉,必须给文字区补一层同色底。 */
  background:
    var(--scrim),
    rgba(var(--glass-tint), var(--glass-a));
  /* 【不要硬描边】。之前那圈 0.5px 描边在亮色下像黑边、暗色下像白边,
     非常廉价。玻璃的「厚度」应该由内高光表达,而不是一圈线。 */
  border: 0;
  /* 边缘 = Hyalite 的折射边(有折射时)或 CSS 的 --rim(没有折射时),
     两者都只是 inset box-shadow —— 合成器属性,几乎不要钱,
     而且在 Safari / Firefox 里也一样生效(那边根本没有折射)。
     这一层是【大面板唯一的玻璃来源】:侧栏 278k px 挂不起折射
     (实测稳态最差 35.9ms,基线 6.5ms,见 build/probe-glass-real.mjs),
     玻璃感就靠它撑着。外投影叠在后面,负责把面板从地图上"抬"起来。 */
  box-shadow: var(--glass-edge, var(--hyalite-edge, var(--rim))), var(--glass-shadow);
}

/* 面板内容层级。折射在元素自身的 backdrop 里,不再用伪元素,
   所以这里只需要保证内容不被别的绝对定位层压住。 */
.glass > *,
.glass button, .glass input, .glass a, .glass label,
.glass span, .glass p, .glass li, .glass div, .glass form,
.search input, .search .icon-btn, .search .qclear,
.legend > *, .side > *, .card > *, .modal > * {
  position: relative;
  z-index: 1;
}

/* 镜面光泽:一束从左上斜下来的光,掠过玻璃表面。
   这是液态玻璃观感里【折射之外】的第二半 —— 折射弯的是背后的世界,
   光泽亮的是玻璃自己。两者都在,才叫"一块玻璃"而不是"一层半透明"。
   为什么以前看不出来:
     · Hyalite 的 rim 只画在折射面,没挂折射的大面板完全没有
     · 顶部那道 inset 1px 高光是【内阴影】,只沿边一条,
       而真实玻璃的高光是一整片斜向的渐变,面积大得多
   z-index:0 压在内容(z-index:1)下面,pointer-events:none 不吃事件。
   两档明暗各自调 —— 暗色下光束要用冷色,而且比亮色更明显,
   因为深色面板本来就缺"这里有一块会反光的东西"的线索。
   ⚠ 渐变的两个结束点是量出来的,不是画出来的:
     原来 34% / 62%,光带沿整条左边铺了 10px 宽,读成一圈白边
     (实测 .search 内侧边缘 58 vs 内侧中心 35)。
     收到 26% / 48% 之后它只落在左上角,像一块被照到的地方,
     不再是沿着轮廓的一圈线。 */
.glass::after {
  content: '';
  position: absolute; inset: 0;
  border-radius: inherit;
  background: linear-gradient(148deg,
    var(--sheen-a) 0%,
    var(--sheen-b) 26%,
    transparent 48%);
  pointer-events: none;
  z-index: 0;
}

/* ── 顶部悬浮区 ───────────────────────────────────────── */
.topbar {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 20;
  display: flex; flex-direction: column; gap: 10px;
  padding: calc(var(--sat) + 10px) calc(var(--sar) + 12px) 0 calc(var(--sal) + 12px);
  pointer-events: none;             /* 只有子元素吃事件,间隙不挡地图 */
}
.topbar > * { pointer-events: auto; }

.search {
  display: flex; align-items: center; gap: 8px;
  height: 46px; padding: 0 12px;
  border-radius: 23px;             /* 胶囊 */
}
.search .ico { width: 19px; height: 19px; fill: var(--label-2); flex: none; }
.search input {
  flex: 1; min-width: 0;
  border: 0; background: transparent; outline: none;
  font-size: 16px;                 /* ≥16px:iOS 聚焦时不会自动放大页面 */
  letter-spacing: 0;
}
.search input::placeholder { color: var(--label-3); }
.search input::-webkit-search-cancel-button { display: none; }
/* 「取消」:iOS 搜索栏的固定退出口。
   之前只有一枚 ✕ 清除按钮,用户想退出搜索只能去点地图空白处 ——
   那是一个需要「猜」出来的手势。搜索是一个有明确终点的临时状态,
   就该给一个看得见的终点。 */
#qcancel {
  flex: none;
  border: 0; background: none; cursor: pointer;
  padding: 0 var(--sp-1);
  margin-right: calc(var(--sp-2) * -1);
  color: var(--accent);
  font-size: var(--fs-secondary);
  line-height: var(--lh-secondary);
  font-weight: var(--fw-regular);
  letter-spacing: 0;
  white-space: nowrap;
  transition: opacity .18s ease;
}
#qcancel:active { opacity: .55; }

.icon-btn {
  position: relative;
  border: 0; background: transparent; cursor: pointer;
  width: 26px; height: 26px; flex: none;
  border-radius: 50%;
  /* ✕ 是图形字形,不是文字 —— 刻意不走 7 级角色字阶,见 categories.css
     顶部那段说明(同类还有 ☰ / ★ / ▾ / emoji 和输入框的 16px)。 */
  font-size: 13px; color: var(--label-2);
  display: grid; place-items: center;
}
.icon-btn::before {
  content: ''; position: absolute; inset: 0;
  background: rgba(127, 127, 140, .16);
  border-radius: inherit; opacity: 0; pointer-events: none;
  transition: opacity .18s ease;
}
@media (hover: hover) { .icon-btn:hover::before { opacity: 1; } }
.icon-btn:active { transform: scale(.92); }

/* ── 搜索结果下拉 ────────────────────────────────────────
   HIG《Search fields》:"When at the top, position an inline search field above
   the list it searches" —— 我们本来就是这个结构,问题出在【行的排版】。
   实测原状:一行 406×52,店名用 margin-left:auto 把「香洲·情侣路 / 1次到访」
   推到最右,中间空出一大片;名字又高亮成蓝色加粗,像一条横幅而不像列表项。

   改成上下两行:主标题 Headline 17/Semibold,副信息 Subhead 15/--label-2
   放在名字下一行。右侧不再有任何东西,空白消失。
   HIG 同页还要求 "Simplify search results. Provide the most relevant search
   results first… consider categorizing them" —— 分组标题保留并压到 Footnote。

   顶角不圆:让它看起来是【从搜索框底下长出来的】,而不是又一块浮空的面板。 */
#results {
  border-radius: 0 0 var(--r-md) var(--r-md);
  overflow: hidden;
  max-height: min(52dvh, 392px);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;    /* 滚到底不把地图一起带着滚 */
  touch-action: pan-y;
}
#results .r {
  /* HIG 触控目标下限 44pt(HIG《Controls》)。行内上下各 12pt 内边距
     —— 这是查到的移动端列表行通用内边距规范;加上 21px 的正文行高,
     一行正好落在 45px,比 44pt 下限还多 1px。 */
  display: flex; align-items: flex-start; gap: var(--sp-3);
  min-height: 44px;
  padding: var(--sp-3) var(--sp-4) var(--sp-3) var(--sp-3);
  cursor: pointer;
  border-bottom: 1px solid rgba(127, 127, 140, 0.12);
}
#results .r:last-child { border-bottom: 0; }
/* 主副信息竖排 —— 这一条是本次改版的核心,消灭行中间那片空白。
   gap 用 4pt 子刻度:21px 行高 + 19px 行高 + 4px = 44px,正好一行的触控高度。 */
#results .r .col { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--sp-1); }
#results .r .nm {
  font-size: var(--fs-body-t);        /* 17pt */
  font-weight: var(--fw-regular);     /* 行标签是 Regular,不是 Semibold */
  line-height: var(--lh-body-t);
  letter-spacing: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
#results .r .mt {
  font-size: var(--fs-tertiary);      /* 13pt */
  line-height: var(--lh-tertiary);
  color: var(--label-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* 命中高亮只加字重,不换颜色。
   原来是 accent 蓝 + 700:颜色和字重同时跳,一行里最抢眼的反而是搜索词,
   而用户要认的是店名。Apple 搜索结果的匹配强调靠字重,不靠换色。 */
#results .r mark {
  background: none; color: inherit;
  font-weight: var(--fw-semi);
}
#results .empty { padding: var(--sp-4); font-size: var(--fs-secondary); color: var(--label-2); line-height: var(--lh-secondary); }

.tools { display: flex; flex-wrap: wrap; gap: 8px; }

.pill {
  color: var(--label-1);
  /* HIG《Buttons》把尺寸分成五档:
       Mini 28 / Small 32 / Regular 44 / Large 52 / Extra large 64,
     并要求 "a button needs a hit region of at least 44x44 pt"。
     原来是 36px —— 卡在 Small 和 Regular 之间,既不在档位上,
     命中区也差 8pt。而同一页还写着:
       "it's usually best to avoid displaying small or mini buttons
        in a vertical stack or horizontal row"
     工具条正是一排横排按钮,「小尺寸 + 横排」是它点名要避开的组合。
     所以直接给到 Regular 档的 44pt,而不是靠 ::after 把热区撑大 ——
     撑热区只解决「点不准」,解决不了「一排小按钮」这个版式问题。 */
  height: 44px; padding: 0 var(--sp-4);
  border-radius: var(--r-full);
  font-size: var(--fs-secondary);
  line-height: var(--lh-secondary);
  font-weight: var(--fw-regular);
  letter-spacing: 0;
  cursor: pointer;
  display: inline-flex; align-items: center; gap: var(--sp-2);
  white-space: nowrap;
  position: relative;      /* 给下面的 hover 遮罩当定位上下文 */
  transition: transform .16s cubic-bezier(.32, .72, 0, 1), box-shadow .2s ease;
}
/* v116 三态反馈:静止 / 悬停 / 按下。
   用户原话「鼠标触碰到这几个按钮的时候的动效做明显一点」。
   原来只有一条 `.pill:active { transform: scale(.94) }` ——
   没有 hover 态,所以指针移上去时按钮毫无反应,直到点下去才知道
   它是能点的;而且按压缩放是整颗按钮一起缩,文字也跟着抖。

   HIG《Buttons》："Use a button's background or border to indicate selection…
   provide a visual response when the control is pressed."
   所以:
     悬停  = 底色提一档 + 抬升 1px(轻,表示"我在响应你")
     按下  = 底色保持 + 缩到 .95(重,表示"我被按住了")
   底色变化走 ::before 遮罩而不是改 background ——
   玻璃面一旦重画 background,backdrop-filter 要重算一次,
   指针扫过一排按钮就是五次重算。遮罩只合成,不重算。 */
.pill::before {
  content: ''; position: absolute; inset: 0;
  border-radius: inherit;
  background: var(--pill-hi);
  opacity: 0; pointer-events: none;
  transition: opacity .18s ease;
}
@media (hover: hover) {
  .pill:hover::before { opacity: 1; }
  .pill:hover { transform: translateY(-1px); }
}
.pill:active::before { opacity: 1; }
.pill:active { transform: scale(.95); }
/* 触屏没有 hover,指针落下即按:这一档不能少,否则 iPad 上等于没有反馈 */
@media (hover: none) {
  .pill:active { transform: scale(.95); }
}
@media (prefers-reduced-motion: reduce) {
  .pill:hover, .pill:active { transform: none; }
}
/* 工具条图标:mask 方案。形状来自 --ico-*,颜色跟 currentColor,
   亮/暗两态只换颜色不换图 —— 和 #btnTheme 的月牙/太阳是同一套机制。
   17px 是按 SF Symbols 的【光学大小】定的:符号在 24pt 视口里
   四周留白比字形本身多,按 1em 铺开会比旁边的汉字明显大一圈。

   ⚠ mask-image 这一行不能少。之前只写了 mask 的 repeat / position /
     size,把 --ico 挂在元素的 style 上,却从没把它接到 mask-image ——
     结果就是一个 17×17 的纯色方块(截图里是四个黑方块)。
     「声明了变量」和「用上了变量」是两件事。 */
.pill.has-ico .ico, .fmc-trigger-ico {
  display: block; flex: none;
  width: 17px; height: 17px;
  background-color: currentColor;
  -webkit-mask-image: var(--ico); mask-image: var(--ico);
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-size: contain; mask-size: contain;
}
.pill.solid {
  background: var(--accent); border-color: transparent; color: #fff;
  box-shadow: 0 4px 14px rgba(10, 132, 255, .34);
}
.pill.on { background: var(--accent); border-color: transparent; color: #fff; }
/* 破坏性按钮现在是 .btn-sm.danger,住在图例面板里,不再是顶栏药丸 —— 见文件末尾。 */

/* ── 准星(落点模式)───────────────────────────────────── */
/* 整个准星是一个 0×0 的锚点,用 transform: translate3d 跟随指针 ——
   pointermove 每帧都在触发,走 transform 不会引发重排。 */
#crosshair {
  position: fixed; left: 0; top: 0;
  width: 0; height: 0;
  transform: translate3d(var(--x, 50vw), var(--y, 50vh), 0);
  pointer-events: none; z-index: 15;
}
#crosshair i { position: absolute; background: rgba(255, 255, 255, .95); box-shadow: 0 0 0 1px rgba(0, 0, 0, .3); }
#crosshair .ch-h { left: -100vw; width: 200vw; top: 0; height: 1px; }
#crosshair .ch-v { top: -100vh; height: 200vh; left: 0; width: 1px; }
#crosshair .ch-d {
  left: -5px; top: -5px; width: 10px; height: 10px;
  border-radius: 50%; background: var(--accent);
  box-shadow: 0 0 0 2px #fff, 0 0 14px rgba(10, 132, 255, .9);
}
#crosshair.on .ch-h, #crosshair.on .ch-v { animation: chpulse 1.1s ease-in-out infinite; }
@keyframes chpulse { 0%, 100% { opacity: .9; } 50% { opacity: .35; } }

/* ── 图例 ─────────────────────────────────────────────── */
/* ⚠ 这条以前不存在。.lg-head 是普通块,里面的 <strong> 和关闭按钮
   各自成行 —— ✕ 掉到「设置」下面一整行,看着像布局没写完
   (用户截图里就是这个样子)。
   下面那条 .lg-head strong { flex: 1 } 之所以"看起来该生效",
   是因为它确实写了,但父级不是 flex 容器,flex 属性对块级布局无效 ——
   **声明了一条规则 ≠ 那条规则在起作用**。 */
.lg-head {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: var(--sp-2);
}
.lg-head strong {
  font-size: var(--fs-display);
  line-height: var(--lh-display);
  font-weight: var(--fw-semi);
  letter-spacing: 0;
  flex: 1;
  min-width: 0;
  /* v114:标题不许换行,也不许撑开。
     原来是靠 `flex:1` 把 ✕ 顶到右边 —— 可 flex:1 只保证「有剩余空间时」伸展,
     标题一旦长到超过一行,strong 就会换行把行高撑开,✕ 被挤到第二行,
     于是又回到用户截图里那个「掉行」的样子。
     加上 nowrap + 省略号,标题无论多长都只占一行,
     ✕ 恒定待在面板右上角。 */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.lg-head .icon-btn { flex: none; margin-left: auto; }
/* v114:面板内容比视口高(实测 825px 视口里内容 1159px),一滚动标题和 ✕
   就跟着滚走了。吸顶之后 ✕ 才是真正的「始终在这个框的右上角」。
   top 用负值抵消面板的 padding-top,标题才会贴住面板顶缘,
   而不是浮在下面 12px 处。底色必须有,否则内容会从标题背后透出来。 */
#setup .lg-head {
  position: sticky;
  top: calc(-1 * var(--sp-3));
  margin-top: calc(-1 * var(--sp-3));
  /* 12 → 8:标题下面现在有真线了,不用再靠留白撑开一段。
     原来 padding 12 + 下边距 8 = 20px 的「空白缓冲」,现在交给那条线。 */
  padding-top: var(--sp-2);
  padding-bottom: var(--sp-2);
  margin-bottom: 0;
  z-index: 2;
  /* 吸顶头部必须【盖得住】下面滚过去的内容。
     原来跟面板一样用 --glass-a-panel(.66),实测截图里
     「状态」那三行会从「设置」标题背后透出来,像印错了的重影。
     玻璃感靠边缘那圈高光和背景模糊,中间这一小块给实色
     反而更干净 —— 这也是 iOS 导航栏的做法。 */
  background: var(--scrim), var(--su-head-bg);
  backdrop-filter: var(--blur);
  -webkit-backdrop-filter: var(--blur);
  border-radius: var(--r-md) var(--r-md) 0 0;
  /* 分隔线改用和分区线一样的 --separator(原来那根 1px box-shadow
     用的 --label-4,又淡又是另一个色阶,和面板里其它分隔线不是一路,
     整面板看着就不齐)。这根线要真的能看见,所以是 border 不是阴影。 */
  border-bottom: 1px solid var(--separator);
  box-shadow: none;
}
.lg-list { list-style: none; margin: 0; padding: 0; }
.lg-row {
  width: 100%; display: flex; align-items: center; gap: var(--sp-2);
  border: 0; background: transparent; cursor: pointer;
  min-height: 44px;
  padding: var(--sp-3) var(--sp-2);
  border-radius: 10px;
  font-size: var(--fs-body-t);
  line-height: var(--lh-body-t);
  font-weight: var(--fw-regular);
  letter-spacing: 0;
  text-align: left;
  opacity: .38; transition: opacity .2s ease, background .2s ease;
}
.lg-row.on { opacity: 1; }
.lg-row .txt { flex: 1; }
.lg-row .n {
  font-size: var(--fs-caption);
  line-height: var(--lh-caption);
  color: var(--label-2);
  font-variant-numeric: tabular-nums;
}
.dot { width: 15px; height: 15px; flex: none; border-radius: 50%; }
.dot.go { background: var(--go); box-shadow: 0 0 0 2px rgba(255, 59, 48, .3); }
.dot.gray { background: var(--gray); }
.dot.wish { background: transparent; border: 2.5px solid var(--wish); width: 13px; height: 13px; }
.lg-note { font-size: var(--fs-tertiary); line-height: var(--lh-tertiary); color: var(--label-2); margin: var(--sp-3) 0 0; }
.lg-note b { color: var(--label-1); }
/* ── 设置面板的说明文字:全站统一到【副文案 = Footnote 13px / label-2】──
   用户原话「这内部的字体不统一,很乱」。这不是审美意见,探针
   (build/probe-setup-type.mjs)量出来是实打实的六档并存:

     11px  .lg-sw-t em(默认关) · ul.tips li · .quota
     12px  .lg-fine(当前:… / 建筑高度…)
     13px  .lg-note · #setup .tips li · .quota-line

   11 / 12 / 13 三个数在 2px 的范围内来回跳,眼睛分不清"这一处为什么小一号",
   只会读成"这里的字和那里不一样" —— 差 1px 不是层级,是噪声。
   层级差要够大才叫层级,否则就只是不统一。
   所以:说明类文字一律 13px + --label-2,一档到底。 */

/* 「切换行」的主标签:面板里唯一比说明文字大一档的东西,15px。 */
.lg-sw {
  font-size: var(--fs-body-t);
  line-height: var(--lh-body-t);
  color: var(--label-1);
  cursor: pointer;
  min-height: 44px;
}
/* ── Apple Switch ──────────────────────────────────────
   原来直接给 <input type=checkbox> 设 accent-color,渲染出来是
   浏览器默认的 15×15 小方框 —— 在一排 iOS 控件里是最刺眼的一个「非苹果」。

   ⚠ 尺寸出处要说清楚:HIG 的《Toggles》页(注意:Apple 并没有
     `switches` 这个 slug,真实 slug 是 `toggles`)通篇【没有任何数值】,
     51×31 的轨道 + 27×27 的旋钮来自 UIKit 的 UISwitch,不是 HIG。

   命中区:输入框被 <label class="lg-sw"> 包着(min-height 44px),
   点整行都会切换,所以热区是那一整行,不是这个 51×31 的盒子。
   —— 这也是为什么审计脚本要把「被 label 包裹」算进去,
      只量 input 自身会报一个不存在的 31px 违规。

   仍然保留真正的 <input>,只是 appearance:none ——
   键盘 Tab、Space 切换、读屏播报全部照常,纯 div 伪开关做不到这些。 */
.lg-sw input[type="checkbox"] {
  appearance: none; -webkit-appearance: none;
  position: relative; flex: none;
  width: 51px; height: 31px;
  margin: 0;
  border-radius: var(--r-full);
  background: var(--track-off);
  transition: background-color .25s cubic-bezier(.4, 0, .2, 1);
  cursor: pointer;
}
.lg-sw input[type="checkbox"]::after {
  content: ''; position: absolute;
  top: 2px; left: 2px;
  width: 27px; height: 27px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 3px 8px rgba(0, 0, 0, .15), 0 1px 1px rgba(0, 0, 0, .16);
  transition: transform .25s cubic-bezier(.4, 0, .2, 1);
}
.lg-sw input[type="checkbox"]:checked { background: var(--sw-green); }
.lg-sw input[type="checkbox"]:checked::after { transform: translateX(20px); }
@media (prefers-reduced-motion: reduce) {
  .lg-sw input[type="checkbox"], .lg-sw input[type="checkbox"]::after { transition: none; }
}
/* 细则文字用 --label-2,不是 --label-3。
   HIG《Labels》给三级文字的定义是 "Text that describes an unavailable item
   or behavior" —— 描述「不可用/受限」的文字。
   而图例里这几条是实打实要读的内容:建筑高度是估算的、坐标系是 GCJ-02、
   OSM 覆盖率只有 2%。那是**补充说明**,按 HIG 自己的定义属于 secondary label。

   这不只是分类问题:12px 的 --label-3 在玻璃上只有 6.32:1,
   而 HIG《Dark mode》说 "strive for a contrast ratio of 7:1, especially in
   small text" —— 12px 正好是它说的「尤其是小字」那一档。
   --label-2 是 7.19:1,过线。

   ⚠ 不能反过来去把 --label-3 的数值提到 7:1:实测那样会让 label-3(7.68)
     反而【超过】label-2(7.19),三级层次整个反过来。
     修 token 归属是对的,硬调数值是错的。 */
/* 细则文字(当前底图 / 建筑高度估算 / 坐标系):13px,和其他说明文字同档。
   原来是 12px -- 见上面那段「说明文字统一到 13px」的说明。 */
.lg-fine { font-size: var(--fs-tertiary); line-height: var(--lh-tertiary); color: var(--label-2); margin: var(--sp-3) 0 0; }
.lg-fine b { color: var(--label-2); }

/* 设置面板:和图例同一套外观与定位。
   两者共用 .sec / .tips / .lg-key-row / .quota 这些内部结构,
   所以外观规则写在两边共同的父级选择器上,只列一次。 */
#setup {
  /* 现在装着图例 + 地图要素 + 联网搜店 + 恢复出厂,内容比原来多,
     宽度给到 380,窄屏再由上面那条 max-width:899px 的通栏规则兜住。 */
  position: fixed; z-index: 25;
  /* 和 #legend 同一套锚定:left / top 由 app.js 的 anchorPanel() 写到
     --lg-x / --lg-y(从各自的按钮下方算出来)。
     max-height 跟着实际 top 走 —— 之前写死减 108px,面板一旦被顶到
     更下面,底部就会溢出屏幕。 */
  left: var(--lg-x, calc(var(--sal) + 12px));
  top: var(--lg-y, calc(var(--sat) + 108px));
  width: min(380px, calc(100vw - 24px));
  max-height: calc(100dvh - var(--lg-y, 108px) - var(--sab) - 12px);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  touch-action: pan-y;
  padding: var(--sp-3) var(--sp-4) var(--sp-4);
  border-radius: var(--r-md);
}
#setup .sec-h {
  font-size: var(--fs-heading);
  line-height: var(--lh-heading);
  font-weight: var(--fw-semi);
  letter-spacing: 0;
  margin-bottom: var(--sp-2);
}
/* 图例的分区标题。
   用户反馈「这个标题怎么比下面的选项还小」—— 原来「地图要素」走的是
   .sec-h(13px / --fs-tertiary),而它下面的开关行是 15px(--fs-body-t),
   分区标题反而比正文小一号。
   HIG 那一页讲的是「面板标题 > 内容」,分区标题同理:
   分区标题至少要和它统辖的行同级或更大。这里给 heading 16px。 */
.lg-sec + .lg-sec { margin-top: var(--sp-4); padding-top: var(--sp-4);
                    border-top: 1px solid var(--separator); }
.lg-sec-h {
  font-size: var(--fs-heading);
  line-height: var(--lh-heading);
  font-weight: var(--fw-semi);
  color: var(--label-1);
  letter-spacing: 0;
  margin-bottom: var(--sp-2);
}
/* 开关行做成两列网格:左标签右开关,三行开关的标签左边缘完全对齐。 */
.lg-switches { display: flex; flex-direction: column; }
.lg-sw { display: grid; grid-template-columns: 1fr auto; align-items: center;
        gap: var(--sp-3); }
/* DOM 里 <input> 在 <span> 前面(键盘 Tab 先到开关,顺序对)，
   但 iOS 设置页是「名字在左、开关在右」——
   DOM 顺序管键盘,视觉顺序管认读,两件事各按各的来,这里用 grid 翻过来。 */
.lg-sw > input { grid-column: 2; grid-row: 1; }
.lg-sw-t { grid-column: 1; grid-row: 1; display: flex; flex-direction: column; min-width: 0; }
.lg-sw-t em { font-style: normal; font-size: var(--fs-tertiary);
             line-height: var(--lh-tertiary); color: var(--label-2); }
/* ══ 设置面板 · 排版系统(v116 重做)════════════════════════
   用户原话:「边距和间距不统一,大小标题和内容的主次不清晰,
   文字内容需要更加清晰+精简,按钮的配色需要统一」。

   实测原状(build/probe-setup-type.mjs,亮色):
     字号 5 档 —— 20 / 16 / 15 / 13 / 12
     分区标题两个类名 —— .lg-sec-h 和 .sec-h 做同一件事
     下外边距 4 种 —— 0 / 4 / 8 / 12px
     按钮 5 种配色 —— 保存蓝 / 测试绿 / 清除灰 / 清空红 / 重置链接

   收敛成:
     字号 3 档 —— 20 标题 / 15 正文 / 13 辅助
     字重 2 档 —— 600 分区与数据 / 400 正文
     间距 2 档 —— 8 段内 / 16 段间(全部落在 --sp-2 / --sp-3 上)

   为什么 15/400 配 15/600:中文没有大小写,又不能用 letter-spacing
   (项目约束:中文 letter-spacing 一律 0),所以分区标题和正文只能
   【同字号 + 靠字重拉开】。原来 16/600 配 15/400 只差 1px,
   视觉上几乎分不出主次,不如把 16 收掉,让 15 的字重差真正起作用。 */
#setup {
  /* 段内 8 / 段间 16。只有这两档,不再有 4px / 12px 这种半档。 */
  --su-gap: var(--sp-2);
  --su-sec-gap: var(--sp-3);
  /* ⚠ --su-head-bg 定义在 :root 上,不定义在这里。
     写在 #setup 里会【盖掉暗色】:#setup 是 :root 的子元素,
     自己声明的同名自定义属性优先于继承来的,于是暗色模式下
     吸顶头部会拿到亮色值(白底黑字,直接看不清)。 */
}
/* 分区标题:面板里只有这一种标题。以前 .sec-h 和 .lg-sec-h 并存,
   两套规则各写一遍数值,迟早会漂 —— 现在 HTML 里全部用 .lg-sec-h。 */
#setup .lg-sec-h {
  /* 16px,不是 15。分区标题必须大于它统辖的行(15px)——
     同字号的话「地图要素」和「清晰地图」在一条基线上,分组感就没了。
     和 --fs-display(面板大标题 20px)差一级,正好是 iOS 的 title / section header 关系。 */
  font-size: var(--fs-heading);        /* 16 */
  line-height: var(--lh-heading);      /* 21 */
  font-weight: var(--fw-semi);          /* 600 */
  letter-spacing: 0;
  color: var(--label-1);
  margin: 0 0 var(--su-gap);
  padding: 0;
}
/* 段内正文:行标题 */
#setup .lg-row .txt,
#setup .lg-sw-t,
#setup .lg-lead {
  font-size: var(--fs-body-t);          /* 15 */
  line-height: var(--lh-body-t);        /* 21 */
  font-weight: var(--fw-regular);
  letter-spacing: 0;
  color: var(--label-1);
}
/* 引导句:紧跟在分区标题下面,自己不再加边距。
   原来是 15px 上下 —— 那是浏览器 <p> 的默认值漏了出来,
   既不是 --sp-2 也不是 --sp-3,凭空多出第三种节奏。 */
#setup .lg-lead { margin: 0 0 var(--su-gap); }
/* 副标题:挂在行标题下面的那一行小字 */
#setup .lg-sw-t em {
  font-style: normal;
  font-size: var(--fs-tertiary);       /* 13 */
  line-height: var(--lh-tertiary);
  color: var(--label-2);
  margin-top: 1px;
}
/* 辅助说明 / 状态行:13 / label-2。两者的区别只有字号和留白,
   不再靠加粗或颜色再分一档 —— 之前 .lg-fine 13 和 .n 12 并排,
   计数比说明还小一级,读起来像注释。计数是数据,提到 15/600。 */
#setup .lg-note,
#setup .lg-status,
#setup .quota-line,
#setup .more > summary {
  font-size: var(--fs-tertiary);
  line-height: var(--lh-tertiary);
  color: var(--label-2);
  letter-spacing: 0;
  margin: var(--su-gap) 0 0;
}
/* 状态行比普通说明再淡一档:它是"现在是什么样",不是"请注意"。 */
#setup .lg-status { color: var(--label-3); }
/* 计数:提到和行标题同字号,用字重和 label-2 表明它是数据不是文案 */
#setup .lg-row .n {
  font-size: var(--fs-body-t);
  line-height: var(--lh-body-t);
  font-weight: var(--fw-semi);
  color: var(--label-2);
  font-variant-numeric: tabular-nums;
}
/* 段间:间距全部由下面的 hr 承担(见本文件更靠后的 #setup hr)。
   这里【不要】再写一份 .sec 下边距或 hr 规则 ——
   同权重下后来者覆盖先来者,同一个选择器写两遍,
   改到前面那条不生效,是最难查的一类"声明了 ≠ 生效"。 */
#setup .sec, #setup .lg-sec { margin: 0; }

/* ── 按钮:全站一套语义配色,不再一个按钮一个颜色 ──────────
   四档,各司其职,和 iOS 设置里的用法一致:
     primary   填充蓝 —— 这段流程里唯一的主动作(保存)
     tint      中性   —— 次动作(测试 / 清除),不抢视线
     danger    红     —— 不可撤销的破坏性动作
     link      纯文字 —— 轻量、可逆的小动作(重置 / 申请链接)
   之前「测试」是绿的,和系统绿开关撞色:同一个绿在屏幕上说了两件事。 */
#setup .btn-sm {
  height: 32px; padding: 0 var(--sp-3);
  border-radius: 8px;
  border: 0; cursor: pointer;
  font-size: var(--fs-tertiary);
  line-height: var(--lh-tertiary);
  font-weight: var(--fw-semi);
  letter-spacing: 0;
  white-space: nowrap;
  transition: opacity .15s ease, transform .15s ease;
}
#setup .btn-sm:active { transform: scale(.96); }
#setup .btn-sm.primary {
  background: var(--accent); color: #fff;
}
#setup .btn-sm.tint,
#setup .btn-sm.ghost,
#setup .btn-sm.ghost2 {
  background: var(--label-4); color: var(--label-1);
}
#setup .btn-sm.danger {
  background: rgba(255, 69, 58, .12); color: #c9342a;
}
:root[data-theme="dark"] #setup .btn-sm.danger { color: #ff8a80; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) #setup .btn-sm.danger { color: #ff8a80; }
}
#setup .linkbtn {
  background: none; border: 0; padding: 0;
  color: var(--accent); font-size: var(--fs-tertiary);
  line-height: var(--lh-tertiary);
  font-weight: var(--fw-regular);
  letter-spacing: 0; cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-color: var(--label-4);
}

#setup .sec-h:first-child { margin-top: 0; }
#setup .tips { margin: var(--sp-2) 0 var(--sp-3); padding-left: 1.15em; }
#setup .tips li {
  font-size: var(--fs-tertiary);
  line-height: var(--lh-tertiary);
  color: var(--label-2);
  /* 4px → 段内那唯一的一档 8px */
  margin-bottom: var(--su-gap);
}
#setup .tips b { color: var(--label-1); }
#setup .tips a { color: var(--accent); }
/* 分隔线:面板里唯一承担"分组断开"的东西,上下各一段 --su-sec-gap。
   之前这里写的是 --sp-4(比 --su-sec-gap 大一档),叠在分区自己的
   下边距上,一条线两侧就成了 32px —— 和段内的 8px 差四倍。 */
#setup hr {
  border: 0; border-top: 1px solid var(--separator);
  margin: var(--su-sec-gap) 0;
}
/* 按钮行:输入框右边留 --su-gap,按钮之间同理。原来是 --sp-1(4px),
   4px 不在任何一档节奏里。 */
#setup .btn-sm { margin-top: 0; margin-right: var(--su-gap); }

/* 额度:两句话,不是一张表 */
.quota-line {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--sp-2);
  margin: var(--sp-3) 0 var(--sp-2);
  font-size: var(--fs-tertiary);
  line-height: var(--lh-tertiary);
  color: var(--label-2);
}
.quota-line b { color: var(--label-1); font-variant-numeric: tabular-nums; font-weight: var(--fw-semi); }
/* 「重置」是链接级操作,不该长得像第二个主按钮 */
.linkbtn {
  border: 0; background: none; cursor: pointer; padding: 0;
  color: var(--accent); font: inherit;
  text-decoration: underline; text-underline-offset: 2px;
}

/* 「额度怎么算的?Key 存在哪?」默认收起。
   之前的 6 条保密说明整片铺在设置里,那是写给开发者的设计说明,
   不是产品界面 —— 用户要的是「这东西是什么、怎么配、配了会怎样」。 */
.more { margin-top: var(--sp-2); }
.more > summary {
  cursor: pointer; list-style: none;
  font-size: var(--fs-tertiary);
  line-height: var(--lh-tertiary);
  color: var(--accent);
  padding: var(--sp-1) 0;
  min-height: 32px;
  display: flex; align-items: center; gap: 4px;
}
.more > summary::-webkit-details-marker { display: none; }
.more > summary::before {
  content: ''; width: 5px; height: 5px;
  border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
  transform: rotate(-45deg); transition: transform .18s ease;
}
.more[open] > summary::before { transform: rotate(45deg); }

/* ── 店铺清单:移动端=底部抽屉 ─────────────────────────── */
#side {
  position: fixed; z-index: 26;
  left: calc(var(--sal) + 10px); right: calc(var(--sar) + 10px);
  bottom: 0;
  max-height: 62dvh;
  display: flex; flex-direction: column;
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  padding-bottom: var(--sab);
  transform: translateY(101%);
  transition: transform .34s cubic-bezier(.32, .72, 0, 1);
}
#side.open { transform: translateY(0); }
/* 手机上的 strip:不是缩到只剩标题栏那么窄 —— 手机是全宽的,
   一条 400px 宽、50px 高的玻璃条正好当「点这里看清单」的入口。 */
#side.strip {
  transform: translateY(0);
  max-height: none; right: auto;
  width: min(calc(100vw - var(--sal) - var(--sar) - 20px), 300px);
}
#side.strip .side-list { display: none; }
#side.strip .side-head { border-bottom: 0; }
.side-head {
  position: relative;
  padding: var(--sp-3) var(--sp-2) var(--sp-3) var(--sp-4);
  flex: none;
  border-bottom: 1px solid rgba(127, 127, 140, .16);
  /* 双击展开/收起。这是手机上唯一的清单入口了(工具条的「清单」按钮
     按用户要求删掉了),所以标题栏必须可聚焦:
     cursor 给指针用户,tabindex 给键盘和读屏用户。 */
  cursor: default;
  user-select: none;
}
.sh-row { display: flex; align-items: center; gap: var(--sp-2); }
.sh-btns { display: flex; align-items: center; gap: 2px; margin-left: auto; flex: none; }
.sh-btns .icon-btn { width: 30px; height: 30px; }
/* 视觉 30px,命中区 44pt。HIG 的 44×44 是命中区不是视觉盒 ——
   ::before 已经被 hover 高亮占用了,热区只能用 ::after。 */
.sh-btns .icon-btn::after {
  content: ''; position: absolute; left: 50%; top: 50%;
  width: 44px; height: 44px; margin: -22px 0 0 -22px;
  pointer-events: none;
}
.sh-btns .icon-btn > * { position: relative; z-index: 1; }
.side-head:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: var(--r-lg); }
/* 侧栏的「向上收起」角标。
   图形只有一个向上的 chevron,没有横线、没有第二条笔画 ——
   用户原话「图标也要改成上收模式,就一个向上的角标就可以」。

   盒子 17×17 而不是 15×15:chevron 在 24 视口里只占 14 宽 × 7 高,
   靠 gen-icons.mjs 里的 viewBox 裁切把它铺满之后,
   15px 盒里画出来是 11.7 × 5.9,再小就发虚了。 */
.ico-strip {
  display: block; width: 17px; height: 17px; background-color: currentColor;
  -webkit-mask-image: var(--ico-strip); mask-image: var(--ico-strip);
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-size: contain; mask-size: contain;
  /* 收起后同一支 chevron 转 180° 朝下 = 「展开」。
     一个形状覆盖两态,不用再造第二个图标;
     aria-label / title 同步改,读屏和 tooltip 不会说反。 */
  transition: transform .24s cubic-bezier(.32, .72, 0, 1);
}
#sideShrink[aria-expanded="false"] .ico-strip { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) {
  .ico-strip { transition: none; }
}
.side-head .sh-s { padding-right: var(--sp-2); }
.side-head::before {           /* 抽屉把手 */
  content: ''; position: absolute; top: 6px; left: 50%; transform: translateX(-50%);
  width: 34px; height: 4px; border-radius: 2px; background: rgba(127, 127, 140, .35);
}
/* 面板标题就是标题,不是分组头。
   我上一版按 iOS「分组列表」把它压到 13px(分组头小于行),用户直接否了:
   「为什么标题的字比下面的字还小啊」。他是对的 ——
   「珠海 · 美食足迹」是这块面板的【标题】,iOS 里标题就该大于它统辖的每一行。
   真正被压小的应该是行(用户同时说了「店名字号再调小一些」)。 */
/* 面板主标题走 display 档(20px)。
   之前这里是 heading 16px,对着 14px 的行名只有 1.14× —— 数字上大,
   肉眼看不出是标题(用户反馈「标题比下面的具体店名字小,不合规范」)。
   规范给的标题/正文级差是 1.3×,20/14 = 1.43,过线。 */
.sh-t {
  /* 降一级到 heading(16px)。上一轮按「面板标题必须明显大于行名」
     拉到 20px 是对的(级差 1.43×),但用户看着还是嫌大 ——
     侧栏只是个索引,不是页面的主角,20px 让它压过了地图。
     16 / 14 = 1.14 又太弱(这正是上一轮返工的原因),
     所以真正的解法不在字号上,在宽度上:宽度自适应之后
     标题和店名都有地方摆,16px 就够读、也不喧宾夺主。 */
  font-size: var(--fs-heading);
  line-height: var(--lh-heading);
  font-weight: var(--fw-semi);
  color: var(--label-1);
  letter-spacing: 0;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.side-head .sh-s { margin-top: 2px; }
.sh-s {
  font-size: var(--fs-tertiary);       /* 标题下的统计,刻意压到比行名还小 */
  line-height: var(--lh-tertiary);
  color: var(--label-3);
  margin-top: var(--sp-1);
}
/* 旧设计里 ✕ 是绝对定位在标题栏右上角的(#sideClose { position:absolute;
   right:10px; top:12px }),现在两颗按钮都进了 .sh-btns 的 flex 流。
   这条老规则必须删 —— 它把 ✕ 从 flex 流里拉出去,结果两颗按钮
   叠在一起(实测 shrink 在 l=382、close 在 l=372,后者反而更靠左),
   而 .sh-btns 被量成 30px 宽,本来应该是 30+2+30=62px。 */
.side-list {
  list-style: none; margin: 0; padding: var(--sp-2);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;   /* 列表滚到底不穿透到地图 */
  touch-action: pan-y;
}
.side-list li { border-radius: var(--r-sm); }
/* 列表行内边距 12pt —— 查到的通用规范里列表行的标准内边距就是 12pt。
   原来写的是 10px,不在 4pt 网格上;改成 12 之后,21px 行高 + 24px
   内边距 = 45px,同样高于 HIG 的 44pt 触控下限。 */
.side-list .it {
  display: flex; align-items: center; gap: var(--sp-3);
  width: 100%; border: 0; background: transparent; cursor: pointer;
  padding: var(--sp-3); text-align: left; border-radius: var(--r-sm);
}
/* 选中态同样走覆盖层,不改背景 —— 见文件开头那��性能实测。
   选中是「持续状态」不是「瞬时反馈」,所以颜色比 hover 深一档。 */
.side-list .it.sel::before { opacity: 1; background: rgba(127, 127, 140, .16); }
/* 列表行:行名用 Regular,并按用户要求调小一档(17 → 15)。
   标题 17/600 在上、行 15/400 在下,层级清楚;
   15pt 放在 384px 宽的侧栏里也留得住长店名(「情侣路 · 城市客厅海鲜」)。 */
.side-list .nm {
  font-size: var(--fs-secondary);     /* 14px */
  line-height: var(--lh-secondary);
  font-weight: var(--fw-regular);
  letter-spacing: 0;
  flex: 1; min-width: 0;
  /* 店名是这一行里唯一必须完整读到的东西。给它 nowrap + 省略号:
     折行会让每一行的高度不再一致,扫视列表时视线会跳;
     整名在详情卡里都看得到,所以极端情况下截断比折行好。
     桌面侧栏已按实测加宽到 320px(见下面的 #side),正常数据不会触发。 */
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.side-list .ar {
  font-size: var(--fs-caption);
  line-height: var(--lh-caption);
  color: var(--label-3);
  flex: none;
}
.side-list .badge {
  font-size: var(--fs-micro);
  line-height: var(--lh-micro);
  color: var(--label-2);
  flex: none;
  font-variant-numeric: tabular-nums;
  border: 1px solid rgba(127, 127, 140, .3); border-radius: 8px; padding: 1px 6px;
}
.side-list .empty { padding: var(--sp-4); font-size: var(--fs-secondary); line-height: var(--lh-secondary); color: var(--label-2); }

/* ── 详情卡:移动端从底部滑出 ─────────────────────────────
   HIG《Sheets》对 sheet 的行为要求(原文):
     · "large is the height of a fully expanded sheet and medium is about
       half of the fully expanded height"  → 两档 detent
     · "People expect to swipe vertically to dismiss a sheet instead of
       tapping a dismiss button"           → 下拉关闭,不是点按钮
     · grabber "can appear at the top edge of a sheet" → 顶部把手
   把手做成一个独立的拖拽区(JS 里 .card-grabber),宽度 36px / 高 5px,
   点它切换 detent、拖它改高度 —— 这两件事 HIG 都写明了。 */
#card {
  position: fixed; z-index: 28;
  left: calc(var(--sal) + 10px); right: calc(var(--sar) + 10px);
  bottom: 0;
  /* 高度由 JS 按内容算,这里只给上限。两档 detent:
     medium ≈ large 的一半(HIG 原话是 "about half of the fully expanded")。 */
  --card-h: 62dvh;
  height: var(--card-h);
  max-height: 88dvh;
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  padding: 0 0 calc(var(--sab) + var(--sp-4));
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  /* pan-y 只允许纵向手势,横向留给地图,避免卡片上滑动时误拖地图 */
  touch-action: pan-y;
  transform: translateY(101%);
  transition: transform .38s cubic-bezier(.32, .72, 0, 1), height .3s cubic-bezier(.32, .72, 0, 1);
}
#card.open { transform: translateY(0); }
@media (prefers-reduced-motion: reduce) {
  #card { transition: none; }
}
/* 把手。hit 区域比视觉大 —— 苹果给 grabber 的热区远大于那根线本身 */
.card-grabber {
  position: sticky; top: 0; z-index: 2;
  display: grid; place-items: center;
  height: 22px; margin: 0 -18px;      /* 横向撑满,热区才够大 */
  padding-top: 8px;
  cursor: grab;
  touch-action: none;                    /* 把手上的手势全归我们,不给浏览器 */
}
.card-grabber::before {
  content: ''; display: block;
  width: 36px; height: 5px; border-radius: 3px;
  background: rgba(127, 127, 140, .35);
}
.card-grabber:active { cursor: grabbing; }
/* 拖动中:关掉过渡,让卡片跟手,不然每帧都在追赶动画 */
#card.dragging { transition: none; }
#card.dragging .card-grabber::before { background: rgba(127, 127, 140, .55); }

/* 卡片内的字号以前大量是刻度外的值(19px / 14px / 12.5px / 11.5px),
   而且 .c-nm 用的是 700 粗体。HIG《Typography》Large (default) 档:
     Title 3  20/25  强调字重 Semibold
     Headline 17/22  强调字重 Semibold
     Body     17/22
     Callout  16/21   Subhead 15/20   Footnote 13/18   Caption 1 12/16
   同页还写明 "avoid Ultralight, Thin, and Light font weights"。
   本项目 CSS 顶部那条注释写的也是这条:「Title3 及以下统一用 Semibold」。
   所以 700 一律降到 600,刻度外的字号一律归位。 */
/* 卡片顶部:把手 + 标题之间要留白。
   用户两次反馈「标题直接在这个框里顶格了,上面需要有空间」——
   第一次只给 .c-top 加了 8px,不够;这次把手本身加高 + 标题再留 14px,
   标题才有「从卡片里浮出来」的感觉而不是「被卡边切掉」。 */
.card-grabber {
  position: sticky; top: 0; z-index: 2;
  display: grid; place-items: center;
  height: 32px; margin: 0 -16px;
  padding-top: var(--sp-3);
  cursor: grab;
  touch-action: none;                    /* 把手上的手势全归我们,不给浏览器 */
}
/* 桌面端没有 sheet 把手(那是可拖拽底部抽屉才有的),标题直接顶在卡边。
   所以顶部的留白要在这里单独给,不能指望把手撑开。 */
/* 卡片内边距 16pt —— 查到的卡片排版规范里,卡片内边距的标准值就是 16pt。
   横向已经是 16,这里把纵向的 14 / 22 归到网格上(16 / 24)。 */
/* 标题行:店名在左,[缩到只剩标题栏][✕] 在右。
   和侧栏标题栏(.sh-row)用的是同一套图标按钮 —— 同一个东西在两处
   长得一样,用户在侧栏学会的操作,到卡片不用重新认一遍。 */
.c-top { padding: var(--sp-4) var(--sp-2) 0 var(--sp-4); }
.c-top-row { display: flex; align-items: flex-start; gap: var(--sp-2); }
.c-top-row .c-nm { flex: 1; min-width: 0; }
.c-head-btns { display: flex; align-items: center; gap: 2px; flex: none; }
.c-head-btns .icon-btn { position: relative; width: 30px; height: 30px; }
.c-head-btns .icon-btn::after {   /* 44pt 命中区,视觉仍是 30 */
  content: ''; position: absolute; left: 50%; top: 50%;
  width: 44px; height: 44px; margin: -22px 0 0 -22px; pointer-events: none;
}
@media (hover: hover) { .c-head-btns .icon-btn:hover::before { opacity: 1; } }
/* ── 紧凑布局(手机):图例/设置改成通栏,贴在工具条下方 ──
   HIG《Popovers》明确写着 "Avoid displaying popovers in compact views."
   之前在 430px 屏上强行把 340px 的面板夹进视口,
   锚点算出来在 x=78,用户看到的正是「弹出在偏左」。
   手机上索性通栏,和 iOS 自己的 sheet 一个做法。 */
@media (max-width: 899px) {
  #setup {
    left: calc(var(--sal) + 12px) !important;
    right: calc(var(--sar) + 12px);
    width: auto;
    border-radius: var(--r-lg);
    max-height: calc(100dvh - var(--lg-y, 120px) - var(--sab) - 12px);
  }
}

@media (min-width: 900px) {
  .c-top { padding-top: var(--sp-6); }
}
.c-nm {
  font-size: var(--fs-display);        /* 20px,面板主标题档 */
  font-weight: var(--fw-semi);
  line-height: var(--lh-display);
  letter-spacing: 0;
}
.c-sub {
  font-size: var(--fs-tertiary);
  line-height: var(--lh-tertiary);
  color: var(--label-2);
  margin-top: var(--sp-1);
  padding-right: var(--sp-2);
}
.c-pills { display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-2); margin-top: var(--sp-3); }
.c-pill {
  font-size: var(--fs-micro);
  line-height: var(--lh-micro);
  padding: var(--sp-1) var(--sp-2); border-radius: 10px;
  border: 1px solid rgba(127, 127, 140, .28); color: var(--label-2);
}
.c-pill.go { color: var(--go); border-color: color-mix(in srgb, var(--go) 42%, transparent); background: color-mix(in srgb, var(--go) 12%, transparent); }
.c-pill.gray { color: var(--gray); border-color: color-mix(in srgb, var(--gray) 42%, transparent); background: color-mix(in srgb, var(--gray) 12%, transparent); }
.c-pill.wish { color: var(--wish); border-color: color-mix(in srgb, var(--wish) 42%, transparent); background: color-mix(in srgb, var(--wish) 12%, transparent); }
/* 统计行:三个数字 + 一个日期。原来日期「2026-08-16」跟前三个数字
   同字号同粗细,视觉上它比实际重要 —— 数字该主导,日期该退后。 */
/* 统计格:「到访次数 / 记录菜品 / 平均分 / 最近到访」四格。
   两端对齐(用户:「这一排两端对齐」「每个模块部分,两端对齐」):
   原来 gap:20px + 默认左对齐,四格挤在左边,右边空一大块,
   和上面那排标签(粤菜 / 街坊店,占满整行)对不上 ——
   看上去就是"上面那行是通栏的、下面这行是半截的"。
   space-between 把四格摊到整行:第一格贴左,最后一格贴右,
   中间两格按剩余空间均分。这样整卡左右两侧各有一条对齐线。
   最后一格是日期(2026-06-21),它天然比三个数字宽,
   靠 space-between 正好让它的右缘和卡的内容右缘齐平。 */
.c-stats {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-3);
  margin: var(--sp-4) 0 var(--sp-2);
}
.c-stat .v {
  font-size: var(--fs-heading);       /* 16px,不再跟店名抢第一 */
  font-weight: var(--fw-semi);
  line-height: var(--lh-heading);
  font-variant-numeric: tabular-nums;  /* 数字等宽,跳变时不抖 */
  letter-spacing: 0;
}
.c-stat .k {
  font-size: var(--fs-caption);
  line-height: var(--lh-caption);
  color: var(--label-2);
  margin-top: var(--sp-1);
}
/* 日期不是数字,降一级并压到次要色,别跟三个计数抢 */
.c-stat.date .v {
  font-size: var(--fs-secondary);
  line-height: var(--lh-secondary);
  font-weight: var(--fw-regular);
  color: var(--label-2);
}
/* 区块之间 24pt —— 查到的规范里,卡片内「区块间距」的标准值是 24pt。
   原来是 16pt,配合 1.29 的行高,四个区块从上到下全是同样的密度,
   眼睛扫不出「到访次数」和「菜品」是两块不同的东西(用户:太挤了)。 */
.c-sec { padding: 0 var(--sp-4); margin-top: var(--sp-6); }
/* 分区标题。统一走 heading 16px —— 之前这里是 tertiary 13px,
   比它下面的正文还小一号,用户反馈「不同等级的标题字号混乱」。
   规则:面板标题 = heading,面板内分区标题 = heading,
        分区标题下再挂的小标签才轮到 tertiary。 */
.c-sec h4 {
  font-size: var(--fs-heading);
  line-height: var(--lh-heading);
  color: var(--label-1);
  font-weight: var(--fw-semi);
  margin: 0 0 var(--sp-3);
  letter-spacing: 0;
}
/* 菜品行。
   原来是「左边 66px 窄列放菜名 + 右边一列放星星/评价/备注」,
   两个毛病(用户反馈「星星符号和评价文字格式不和谐」):
     ① .ds 是 display:flex,会占满整行,于是星星在第一行、
        「不再点」被挤到第二行 —— 一条评价被拆成两行,很别扭。
        改法:菜名 flex:1 撑开,星星+评价作为 trailing accessory 同一行。
     ② 66px 窄列放不下「豉汁皇蒸面」这种 5 字菜名,长名会挤/换行。
        改成整行可伸展后,名字有多长都放得下。 */
/* 行内上下各 12pt:21px 菜名 + 24px 内边距 = 45px,一行一条评价,
   手指点得到,也终于能从上一行里分出来。 */
.dish {
  display: flex; flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-4);
  align-items: center;          /* 菜名和右侧星星评价共用一条中线 */
  padding: var(--sp-3) 0;
  border-top: 1px solid rgba(127, 127, 140, .14);
}
.dish:first-of-type { border-top: 0; }
.dish .dn {
  font-size: var(--fs-body-t);
  line-height: var(--lh-body-t);
  font-weight: var(--fw-regular);
  flex: 1 1 auto;
  min-width: 0;
}
/* 星级 + 评价:同一行的 trailing accessory,共用中线。
   align-items:center 让 6px 的圆点和 12px 的文字视觉对齐 ——
   之前是 baseline,圆点坐在文字基线上,整行看着是歪的。 */
.dish .dm {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  white-space: nowrap;
}
.dish .ds { display: inline-flex; align-items: center; gap: 2px; }
.dish .ds i { width: 6px; height: 6px; border-radius: 50%; background: rgba(127, 127, 140, .26); }
.dish .ds i.f { background: var(--go); }
.dish .ag {
  font-size: var(--fs-caption);
  line-height: var(--lh-caption);
  color: var(--label-2);
}
.dish .dn2 {
  flex: 1 0 100%;           /* 备注单独占一行 */
  font-size: var(--fs-secondary);
  line-height: var(--lh-secondary);
  color: var(--label-2);
  margin-top: var(--sp-1);
}
.vis { padding: var(--sp-3) 0; border-top: 1px solid rgba(127, 127, 140, .14); }
.vis:first-of-type { border-top: 0; }
.vis .vh { display: flex; align-items: baseline; gap: var(--sp-2); }
.vis .vd {
  font-size: var(--fs-tertiary);
  line-height: var(--lh-tertiary);
  font-weight: var(--fw-semi);
  font-variant-numeric: tabular-nums;
}
.vis .vm { font-size: var(--fs-micro); line-height: var(--lh-micro); color: var(--label-3); }
.vis .vn {
  font-size: var(--fs-secondary);
  line-height: var(--lh-secondary);
  color: var(--label-2);
  margin-top: 4px;
}
.c-empty { font-size: var(--fs-secondary); line-height: var(--lh-secondary); color: var(--label-2); padding: var(--sp-2) 0; }
/* 按钮区。
   HIG《Buttons》把按钮分成 Filled / Tinted / Gray / Plain 四级,
   核心差别是「填充的强度」而不是「描边」—— 苹果的按钮【一律没有描边】,
   之前这三个按钮除了漏掉的 UA 默认描边,彼此还长得一模一样,
   用户无从判断哪个是主操作。
   现在:编辑 = Filled(accent 实底白字),再打一个点 = Gray(中性灰底),
   收起 = Plain(纯文字无底)。
   HIG《Sheets》:"Place the Cancel button at the top leading edge" ——
   收起是取消动作,放最左;主操作放最右。 */
/* 底部只剩主操作「编辑」,靠右。
   原来这里是三颗:收起(圆箭头) / 在这里再打一个点 / 编辑。
   用户把前两颗都删了 —— 一张卡只有一个主动作时,
   把它独占右下角(和 iOS 照片详情一样)比三颗并排清楚得多。 */
.c-act {
  display: flex; align-items: center; justify-content: flex-end; gap: var(--sp-2);
  padding: var(--sp-4) var(--sp-4) 0;
  margin-top: var(--sp-3);
  border-top: 1px solid rgba(127, 127, 140, .14);
}
/* 底部只剩一颗主操作「编辑」了(原来三颗:收起 / 再打一个点 / 编辑,
   用户把前两颗删了)。文字 14px;视觉高 36px,命中区 44pt。 */
.c-act .pill {
  /* 视觉 36px + 命中区 44px(用 ::after 撑开)。
     HIG 要求的 44 是【命中区】不是视觉盒 —— 一颗 44px 高、
     压满 16px 文字的按钮,在 384px 宽的卡片里会显得笨重。
     和筛选胶囊 .fmc-chip 用的是同一套处理,全站按钮由此统一。 */
  position: relative;
  height: 36px;
  padding: 0 var(--sp-3);
  border-radius: var(--r-sm);
  font-size: var(--fs-secondary);
  line-height: var(--lh-secondary);
  font-weight: var(--fw-regular);
  letter-spacing: 0;
  box-shadow: none;
  backdrop-filter: none;              /* 卡内不需要再磨一遍玻璃 */
  -webkit-backdrop-filter: none;
}

/* Filled:accent 实底 + 白字 */
.c-act .pill[data-act="edit"] {
  background: var(--accent);
  color: #fff;
  font-weight: var(--fw-semi);
  padding: 0 var(--sp-4);
}
/* 填充主按钮的按压:变深 + 轻微缩。
   通用规则里的 background-color 压不过这里的 background 简写,
   所以 Filled 按钮必须自己写一份,不然按下去毫无反应。 */
.c-act .pill[data-act="edit"]:active {
  background: color-mix(in srgb, var(--accent) 84%, #000);
  transform: scale(.97);
}

/* 命中区:三个按钮统一 44pt,视觉 36px。
   HIG 的原话是 "at least 44x44 pt" —— 是【正方形】,宽高都要够。
   圆形按钮视觉只有 36px 宽,::after 必须给 min-width:44px 撑开,
   只写 height:44px 会留下一个 36×44 的热区,横向还是难点。 */
.c-act .pill::after {
  content: ''; position: absolute; left: 50%; top: 50%;
  width: 100%; min-width: 44px; height: 44px;
  margin: -22px 0 0 -50%;
  transform: translateX(-50%);
  pointer-events: none;
}

/* hover:和工具条上的玻璃按钮同一套反馈 —— 提亮 + 轻微上浮。
   之前这三个只有 :active,鼠标划过去完全没有反应,
   看上去像是「坏了」,而不是「这是个按钮」。 */
@media (hover: hover) {
  .c-act .pill[data-act="edit"]:hover { background: #0a7ae8; }
}

/* 「缩到只剩标题栏」(mini 态):卡片收成一条窄条,只留店名。
   对应 iOS 上把 sheet 拖到最小 detent,或者侧栏的 strip 态。 */
#card.mini { --card-h: auto; }
#card.mini .c-sub, #card.mini .c-pills, #card.mini .c-stats,
#card.mini .c-sec, #card.mini .c-act { display: none; }
#card.mini .c-top-row .c-nm { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ── 比例尺 ─────────────────────────────────────────────
   显示的是【真实距离】,不是 zoom 级别。
   裸百分比/zoom 对找饭没有意义:人想知道的是
   「屏幕这一小段是 200 米还是 2 公里」——
   那才是判断该不该继续放大、当前精度够不够的依据。
   紧贴缩放控件放在右下角,和版权署名同一区域但分开。 */
/* 放在缩放控件的【正上方】,右边缘对齐。
   之前是「缩放控件左边」——用 right: sar+46px 去猜控件宽度,
   控件尺寸一变就错位(用户反馈「没有对齐这个放大缩小按钮,很违和」)。
   上下堆叠 + 共用右边缘,不依赖控件的宽度,怎么变都对得上。 */
/* ⑧ 用户反馈「删掉背后的白色透明背景和框」。
   之前是 .glass 玻璃条 + 圆角,在地图上像贴了一块膏药。
   现在去掉底色、描边、投影,只留文字和那条线 ——
   它是地图的一部分,不是浮在上面的控件。 */

/* ── 菜品区:接 categories.js 的 mountDishes ──────────────
   ④ 每一行鼠标划过去有动效(和别处的行一样走 opacity 覆盖层,
      不改 background —— 改背景会让整块面板的 backdrop 重画,很卡)
   ⑤ 标题右侧那颗 + :新增一道菜的评价(含打分) */
.c-dishes { position: relative; }
/* ⑤ 标题右侧那颗 + :新增一道菜的评价(含打分)
   用户原话「这个加号放在这行最右边」「每个模块部分,两端对齐」。
   原来它紧贴在「到访记录」四个字后面(flex 只加了 gap),
   于是这行是"标题 + 按钮"挤在左边、右边空着。
   margin-left:auto 把它推到整行最右端,和上面那排标签的右缘对齐 ——
   卡片里凡是"一行里有一个操作"的都该这么排:
   左边是这行的名字,右边是操作,中间留白。 */
.c-sec-h-add { display: flex; align-items: center; gap: var(--sp-2); }
.c-sec-h-add .c-add { margin-left: auto; }
.c-add {
  position: relative;
  display: inline-grid; place-items: center;
  width: 24px; height: 24px; flex: none;
  border: 0; border-radius: 50%;
  background: rgba(120, 120, 128, .18);
  color: var(--label-1);
  font-size: 17px; line-height: 1;
  cursor: pointer;
  margin-left: var(--sp-1);
  transition: background-color .18s ease, transform .16s ease;
}
.c-add::after {                 /* 命中区 44pt,视觉 24px */
  content: ''; position: absolute; left: 50%; top: 50%;
  width: 44px; height: 44px; margin: -22px 0 0 -22px; pointer-events: none;
}
@media (hover: hover) { .c-add:hover { background: rgba(120, 120, 128, .30); } }
.c-add:active { transform: scale(.9); }

/* 菜品行的悬停动效 —— 覆盖层只动 opacity,合成器属性,零重绘成本 */
.dh-row { position: relative; cursor: default; }
.dh-row::before {
  content: ''; position: absolute; inset: 0;
  background: rgba(127, 127, 140, .14);
  border-radius: var(--r-sm);
  opacity: 0; pointer-events: none;
  transition: opacity .18s ease;
}
@media (hover: hover) { .dh-row:hover::before { opacity: 1; } }
/* 双击提示:光标换成 text,让「这里能双击改」在动手之前就成立 */
@media (hover: hover) { .dh-row { cursor: text; } }
.dh-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.dh-row > * { position: relative; z-index: 1; }
/* 已经展开的那一行,高亮常亮 */
.dh-row.open::before { opacity: 1; background: rgba(127, 127, 140, .10); }

.c-dishform:not([hidden]) { margin-top: var(--sp-3); }
/* v116:缩放百分比 = 缩放按钮组【下方】的一枚小标签。
   用户原话「右下角的百分比下面有个多余的标尺需要删掉,
   并且百分比有点奇怪,放在放大缩小符号的下面吧」。
   · <i> 那根 26px 实尺标尺整条删掉(#scale i 的规则也一并删)。
     它和百分比上下堆在一起时,读起来像两套互相打架的刻度。
   · 从「按钮正上方的无底浮层」改成「按钮正下方、同一套玻璃的小药丸」——
     归属一眼就清楚:它讲的就是这组按钮。
   · 定位【不】由 JS 写。这里原来写的是「right / bottom 都由 app.js 的
     alignScale() 量着控件写」—— 那个函数已经删了(app.js:2646 的注释
     记着"以前那套 alignScale() 量来量去,量的就是贴哪"被去掉了)。
     现在是 bindScale() 把本节点 appendChild 进 .maplibregl-ctrl-group,
     贴边交给父级的 flex 流式排版,JS 一个坐标都不写。
     所以下面那条 position:static 不是"兜底值",是【必须】的 —— 见下。 */
#scale {
  /* v116:app.js 会把这个节点搬进 .maplibregl-ctrl-bottom-right 的
     .maplibregl-ctrl-group,当 +/− 下面的第三格。所以它是【组内的一格】,
     不是浮在地图上的浮层 —— 玻璃/圆角/投影全由父级提供。
     position 必须显式写 static:#scale 身上还挂着 .glass,
     而 .glass 是 position:relative,不清掉的话它会脱离组里的流式排版。 */
  position: static;
  display: flex; align-items: center; justify-content: center;
  width: 100%; height: 26px;
  border-top: 1px solid var(--label-4);
  font-size: var(--fs-micro);
  line-height: var(--lh-micro);
  font-weight: var(--fw-semi);
  color: var(--label-2);
  font-variant-numeric: tabular-nums;
  pointer-events: none;
  white-space: nowrap;
  user-select: none;
}

/* ── 落点提示条 ───────────────────────────────────────── */
#drophint {
  position: fixed; z-index: 30;
  left: 50%; transform: translateX(-50%);
  bottom: calc(var(--sab) + 18px);
  display: flex; align-items: center; gap: 12px;
  padding: var(--sp-2) var(--sp-2) var(--sp-2) var(--sp-4);
  border-radius: 22px;
  font-size: var(--fs-tertiary); white-space: nowrap;
  max-width: calc(100vw - 24px);
}
#drophintTxt { overflow: hidden; text-overflow: ellipsis; }

/* ── 表单弹窗 ─────────────────────────────────────────── */
#modalWrap { position: fixed; inset: 0; z-index: 40; display: grid; place-items: center; padding: 16px; }
.modal-back { position: absolute; inset: 0; background: rgba(0, 0, 0, .34); animation: fade .22s ease; }
@keyframes fade { from { opacity: 0; } }
#modal {
  position: relative;
  width: min(420px, 100%);
  max-height: 86dvh; overflow-y: auto;
  border-radius: var(--r-lg);
  padding: var(--sp-4);
  animation: pop .28s cubic-bezier(.32, .72, 0, 1);
}
@keyframes pop { from { opacity: 0; transform: translateY(14px) scale(.97); } }
.md-head { display: flex; align-items: flex-start; margin-bottom: var(--sp-4); }
.md-t { font-size: var(--fs-display); line-height: var(--lh-display); font-weight: var(--fw-semi); }
.md-s { font-size: var(--fs-micro); line-height: var(--lh-micro); color: var(--label-2); margin-top: var(--sp-1); }
.md-head .icon-btn { margin-left: auto; }
.fld { display: block; margin-bottom: var(--sp-4); }
.fld > span { display: block; font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--label-2); margin-bottom: var(--sp-2); }
.fld > span i { color: #ff453a; font-style: normal; }
.fld input {
  width: 100%; height: 44px; padding: 0 var(--sp-3);
  border-radius: var(--r-sm);
  border: 1px solid rgba(127, 127, 140, .3);
  background: rgba(127, 127, 140, .1);
  font-size: 16px; outline: none;
  transition: border-color .2s ease, background .2s ease;
}
.fld input:focus { border-color: var(--accent); background: rgba(127, 127, 140, .06); }
.seg {
  display: flex; gap: 4px; padding: 3px;
  background: rgba(127, 127, 140, .12); border-radius: var(--r-sm);
}
.seg button {
  flex: 1; height: 36px; border: 0; cursor: pointer;
  background: transparent; border-radius: 11px;
  font-size: var(--fs-secondary); font-weight: var(--fw-regular); color: var(--label-2);
  transition: background .2s ease, color .2s ease;
}
.seg button.on { background: rgba(var(--glass-tint), var(--glass-a-strong)); color: var(--label-1); box-shadow: 0 1px 4px rgba(0, 0, 0, .12); }
.md-fine { font-size: var(--fs-micro); line-height: var(--lh-micro); color: var(--label-3); margin: var(--sp-1) 0 0; }
.md-act { display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px; }

/* ── 瓦片失败提示 ─────────────────────────────────────── */
#tilefail {
  position: fixed; z-index: 50;
  left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: min(400px, calc(100vw - 32px));
  padding: 20px; border-radius: var(--r-lg);
  text-align: left;
}
.tf-t { font-size: var(--fs-secondary); font-weight: var(--fw-semi); margin-bottom: var(--sp-2); }
.tf-s { font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--label-2); margin: 0 0 var(--sp-2); }
.tf-s b { color: var(--label-1); }
.tf-act { display: flex; gap: var(--sp-2); justify-content: flex-end; margin-top: var(--sp-4); }

/* ── Toast ────────────────────────────────────────────── */
#toast {
  position: fixed; z-index: 60;
  left: 50%; transform: translateX(-50%);
  bottom: calc(var(--sab) + 74px);
  background: rgba(var(--glass-tint), .88);
  color: #fff; font-size: var(--fs-tertiary);
  padding: var(--sp-2) var(--sp-4); border-radius: var(--r-sm);
  backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
  box-shadow: 0 6px 24px rgba(0, 0, 0, .3);
  animation: pop .22s ease;
  max-width: calc(100vw - 32px); text-align: center;
}

/* ============================================================
   桌面布局(≥900px):左侧清单常驻,搜索与工具并排,详情卡从右侧滑出
   ============================================================ */
.only-mobile { display: inline-flex; }

@media (min-width: 900px) {
  .topbar {
    left: calc(var(--sal) + var(--side-gap, 344px));
    right: calc(var(--sar) + 12px);
    /* 顶栏改成三段:搜索框居中,工具条整体靠右。
       原来是 row + 搜索框 flex:1,搜索框被左边的侧栏和右边的按钮
       一起挤,永远不居中;用户要的是「搜索框水平居中,
       工具条靠右」,这两件事用 flex 一次排不出来 ——
       居中需要一个占满整栏的轨道,靠右需要另一条。
       现在的做法:
         · .search-wrap  flex:1 → 吃掉侧栏右边到工具条左边的整段
         · .search-slot  width:fit-content + margin-inline:auto → 在这一段里居中
         · .tools        flex:none → 贴右,内部等距
       侧栏收起(strip)时 --side-gap 变 12px,搜索框会跟着往左移一点
       —— 这是对的:侧栏让出来的空间就还给搜索框,居中始终相对
       「侧栏右缘 ↔ 工具条左缘」这条中线,而不是相对整个视口。 */
    flex-direction: row; align-items: flex-start; gap: 10px;
  }
  .search-wrap { flex: 1 1 auto; min-width: 0; display: flex; justify-content: center; }
  .search-slot { width: fit-content; min-width: 0; max-width: 100%; margin-inline: auto; }
  /* 宽度该由谁决定,就让谁决定:slot 的宽度来自内容,
     搜索框和下拉都填满它。这里不再写死上限。
     居中之后 slot 是 fit-content,所以它自己的宽度就是搜索框的宽度,
     max-width:100% 保证窄到放不下时它会缩,而不是撑破栏宽。 */
  .search { flex: 1; min-width: 0; }
  /* 工具条:贴右、内部等距。
     「按钮之间等距离」这一条 flex 的 gap 已经保证了 ——
     .tools-lead(想吃啥)和后面几颗按钮用的是同一个 8px gap,
     所以它不额外加 margin,免得只有这一处间距不一样。 */
  .tools { flex: none; align-items: center; }
  /* #results 绝对定位在 .search-slot 里,left:0/right:0 自动等于搜索框宽度 */
  .only-mobile { display: none !important; }

  /* 桌面端:清单常驻,顶边让开状态栏。
     宽度 320px 是量出来的,不是拍的:把行内边距提到规范的 12pt 之后,
     14 条种子数据里最长的「情侣路 · 城市客厅海鲜」需要 309px 才不折行
     (实测 build/probe-side-rows.mjs:文本 138px + 区域 60 + 徽标 36 +
      内边距 24 + 间隙 24 + 圆点 16),原来 296px 差 13px,一折就折成两行。
     --side-gap 是工具条/图例/署名让开它的左边距 = 12 + 320 + 12。 */
  #side {
    left: calc(var(--sal) + 12px);
    top: calc(var(--sat) + 12px);
    right: auto;
    /* ⚠ 原来这里写的是 `bottom: calc(var(--sab) + 12px)` + `max-height: none`。
       position:fixed 的元素同时给 top 和 bottom,高度就被【两端钉死】,
       一定等于视口高度 —— 哪怕里面只放了 5 家店、面板内容只有 300px。
       这正是用户反馈「这个框要随着店名最后一个的位置自适应调整高度」的原因:
       收起列表(display:none)之后面板依然是一整屏的白玻璃。

       改成「只钉 top,高度由内容撑,再给一条上限」:
         · 15 家店时列表正常展开,面板高度 = 标题栏 + 列表实际高度,
           最后一家店下面留 12px 就到边;
         · 店多到超过上限时,列表自己内部滚动(overflow-y:auto),
           面板不会顶出视口。 */
    bottom: auto;
    max-height: calc(100dvh - var(--sat) - var(--sab) - 24px);
    /* 宽度由 JS 按最长的店名量出来(fitSideWidth),夹在 280–360px。
       不写死:店名是用户自己加的,写死必然在某个长度上折行或者留大片空白。 */
    width: var(--side-w, 320px);
    border-radius: var(--r-lg);
    transform: none;                    /* 桌面不用抽屉位移动画 */
    transition: transform .3s cubic-bezier(.32, .72, 0, 1);
    padding-bottom: 0;
  }
  /* 收起:把店名列表折起来,标题栏原地留着。
     用户原话「收起的时候顶部栏不动,下面的店名消失,多余的白色背景也一起被收起」。
     因为上面已经不再钉 bottom,列表一收,面板高度自然缩回标题栏那一行 ——
     「多余的白底一起收起」不需要额外写任何 height,它是上一条改动的直接结果。 */
  /* strip:只剩标题栏。

     ⚠ top 必须往下挪。:root:has(#side.strip) 把 --side-gap 放回 12px,
       顶栏(连同搜索框)于是回到左上角,正好压在还留在左上角的
       strip 面板上面 —— 面板不是「点不到」,是压根被盖住了。
       探针实测:elementFromPoint(标题栏中心) 返回的是 .search.glass。
       所以 strip 一律停到工具条下方。 */
  #side.strip { top: calc(var(--sat) + 68px);
                width: auto; min-width: var(--side-w, 320px);
                max-width: min(var(--side-w, 320px), 46vw); }
  #side.strip .side-list,
  #side.strip .side-head::before { display: none; }
  #side.strip .side-head { border-bottom: 0; padding-bottom: var(--sp-2); }
  /* 「双击展开」跟着统计行内联,不再绝对定位 ——
     原来 ::after 摆在 right:16px,而两颗按钮(.sh-btns)就在右上角,
     两者直接叠成一团。绝对定位的元素必须先确认那个位置是空的。 */
  #side.strip .sh-s::after {
    content: ' · 双击展开'; color: var(--label-3); font-weight: var(--fw-regular);
  }
  /* 清单收起后,工具条 / 图例 / 设置 / 署名把左边 344px 让回来,
     否则它们会留在一块已经不存在的面板旁边。 */
  :root:has(#side.strip) { --side-gap: 12px; }
  .side-head::before { display: none; }   /* 桌面端没有抽屉把手 */
  .side-head { padding-top: var(--sp-3); }

  /* 定位统一由 app.js 的 positionSetup() 负责 —— 它按「设置」按钮的
     实测位置写 --lg-x / --lg-y。CSS 这边再写一次 left 就会把它盖掉,
     面板又弹回侧栏右边(用户反馈过「弹出在偏左」)。 */
  #setup { top: var(--lg-y, calc(var(--sat) + 72px)); }

  /* 桌面端把署名挪到地图区左下角。
     容器已经用 addControl(..., 'bottom-left') 挂到 left 了,这里只要把它
     推离左侧的清单面板。Apple 地图的署名也是固定在左下角。 */
  .maplibregl-ctrl-bottom-left { left: calc(var(--sal) + var(--side-gap, 344px)); right: auto; }

  /* 搜索框要占满剩余宽度。
     .search-wrap 默认是 flex:0 1 auto(按内容宽),所以输入框的固有宽度
     直接决定了搜索框只有 236px —— 结果下拉跟着一起窄。
     给它 flex:1 之后,输入框才会吃掉 topbar 剩下的空间。 */
  .topbar .search-wrap { flex: 1; max-width: 560px; }

  /* 桌面端详情卡改成右侧栏。
     关键:top 必须在【工具条下方】—— 之前用 sat + 12px,和 .topbar
     完全重叠,卡片一打开就把搜索框和工具按钮盖住了。
     工具条高度约 46px(search)+ 间距,这里留 62px 让位。

     高度改成自适应:HIG《Popovers》要求 "Make a popover only big enough to
     display its contents"。原来 top+bottom 双向拉伸,内容只到 530px
     面板却有 826px,底下挂着 300px 空白 —— 既不像 sheet 也不像 popover。
     现在用 height:auto + max-height,内容多少面板就多高;
     内容超长时才内部滚动。 */
  #card {
    left: auto;
    right: calc(var(--sar) + 12px);
    top: calc(var(--sat) + 62px);
    bottom: auto;
    width: 384px;
    height: auto;
    /* 底部留出 56px:缩放控件 + 署名那一行。
       卡片是「只做到能显示内容的大小」(HIG《Popovers》),
       但内容很长时也必须有上限,不能一路顶到屏幕最底把控件压掉。 */
    max-height: calc(100dvh - var(--sat) - 74px - 56px);
    border-radius: var(--r-lg);
    padding-bottom: var(--sp-4);
    transform: translateX(calc(100% + 24px));
    transition: transform .38s cubic-bezier(.32, .72, 0, 1);
    z-index: 26;                 /* 低于 .topbar(30),卡片永远不压工具条 */
  }
  /* 侧栏不是可缩放的 sheet,把手在这里没有意义,直接隐藏 */
  #card .card-grabber { display: none; }
  #card.open { transform: translateX(0); }
  #card::before { display: none; }

  #drophint { bottom: calc(var(--sab) + 22px); }
  #toast { bottom: calc(var(--sab) + 22px); }
  .maplibregl-ctrl-bottom-left { display: none; }
}

/* 超宽屏:地图主体还是主角,别让 UI 铺满 */
@media (min-width: 1600px) {
  #card { width: 420px; }
  #side { width: 336px; }
  /* 这一段原来只挪了 .topbar 和 #legend,漏了 #setup ——
     设置面板继续吃 --side-gap 的 344px,而侧栏已经加宽到 336px
     (右边缘 12 + 336 = 348px),设置面板会压在侧栏上 4px。
     两个一起挪到 360px,和侧栏右边缘留 12px。 */
  .topbar { left: calc(var(--sal) + 360px); }
  /* #legend / #setup 的 left 同样交给 anchorPanel() */
  .maplibregl-ctrl-bottom-left { left: calc(var(--sal) + 360px); }
}

/* ── 降级:减少透明 ─────────────────────────────────────
   用户在系统里关掉了「降低透明度」→ 玻璃退成实心面板,
   文字可读性优先于视觉通透。 */
@media (prefers-reduced-transparency: reduce) {
  .glass, .search, #results, .pill, .btn-sm, .maplibregl-ctrl-group {
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
    background: var(--bg-raised) !important;
    box-shadow: none !important;
  }
  /* 蒙版也一并关掉,否则底图会整体变暗 */
  #veil { display: none !important; }
}

/* ── 降级:减少动效 ───────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}

/* 高对比度:去掉细边框,避免在 forced-colors 下不可见 */
@media (forced-colors: active) {
  .glass, .pill, .fld input { border: 1px solid CanvasText; }
}

/* ── 联网搜店:结果分区 + key 配置面板 ─────────────────── */
/* 分区标题:本地命中 vs 高德真实门店 */
#results .r-sep {
  padding: var(--sp-2) var(--sp-4) 5px;
  font-size: var(--fs-tertiary);     /* 13pt */
  font-weight: 400;                  /* Footnote 的默认字重是 Regular */
  letter-spacing: 0;
  color: var(--label-3);
  background: rgba(127, 127, 140, .08);
  position: sticky; top: 0;
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}
/* 高德结果的点:虚线圆环,和已落点的门店区分开 */
.dot.poi {
  background: transparent;
  border: 2px dashed #0a84ff;
  width: 13px; height: 13px;
}
#results .r.poi .nm { color: var(--accent); }


/* key 配置区 */
.lg-key { margin-top: 2px; }
.lg-key-h {
  display: flex; align-items: center; gap: 8px;
  font-size: var(--fs-tertiary); line-height: var(--lh-tertiary); font-weight: var(--fw-semi); margin-bottom: var(--sp-1);
}
.lg-key-h .pill-s {
  font-size: var(--fs-micro); font-weight: var(--fw-semi); letter-spacing: 0;
  padding: 2px 7px; border-radius: 999px;
  background: rgba(127, 127, 140, .16); color: var(--label-3);
}
.lg-key-h .pill-s.on { background: rgba(42, 157, 92, .18); color: #4ade80; }
.lg-key-row { display: flex; gap: var(--sp-2); margin-top: var(--sp-2); }
.lg-key-row input {
  flex: 1; min-width: 0;
  padding: var(--sp-2) var(--sp-3); font-size: var(--fs-caption);
  /* 原来这里是 ui-monospace / SFMono-Regular / Menlo。
     Key 是凭证,等宽字确实便于逐字符核对(0/O、1/l 不混);
     但用户要求「整个网站的字体全部统一成小米的」,这一格是界面上
     唯一一块异色字体,视觉上就是一个补丁。
     折中:字体统一,改用【等宽数字 + 微量字距】保住可核对性 ——
     tabular-nums 让 0/1/8 占同样宽度(不随内容跳),
     0.05em 字距把相邻字符拉开,比换字体的代价小得多。 */
  font-variant-numeric: tabular-nums;
  letter-spacing: .05em;
  border-radius: 9px;
  border: 1px solid rgba(127, 127, 140, .26);
  background: rgba(255, 255, 255, .12);
  color: var(--label-1);
}
@media (prefers-color-scheme: dark) {
  .lg-key-row input { background: rgba(255, 255, 255, .10); }
}
.lg-key-row input:focus { outline: 2px solid rgba(10, 132, 255, .5); outline-offset: 1px; }
/* .btn-sm 的底色/字色由下面「玻璃次级按钮」那条统一定。
   原来这里写的是 background: #0a84ff; color: #fff(蓝色实心主按钮),
   结果被后面那条同名的 .btn-sm 悄悄改掉了背景、却没改字色 ——
   **白字留在了近白底上**,#keySave 看上去就是一块空白。
   用户原话「这个是什么??为什么我肉眼看是一片白」。
   两条同名规则各改一半,拼出一个谁都没设计过的组合;
   这类"静默覆盖"和之前 #legend 被删导致对比度脚本空跑是同一类问题:
   改的人以为生效了,实际生效的是另一条规则。 */
.btn-sm {
  padding: var(--sp-2) var(--sp-3);
  font-size: var(--fs-tertiary);
  line-height: var(--lh-tertiary);
  font-weight: var(--fw-semi);
  letter-spacing: 0;
  border-radius: 9px; border: 0; cursor: pointer;
  white-space: nowrap;
}
/* 主按钮:这一行里唯一的主操作,必须是实心,不能和「测试 / 清除」一样
   都是玻璃片 —— 那三个并排时用户不知道该按哪个。
   ⚠ 底色不是 iOS systemBlue #0a84ff:白字压在上面只有 3.65:1,
     达不到 WCAG AA 的 4.5:1,更别提本项目一直守着的那档 7:1 目标。
     #0a4fa8 是同色系深一档,白字 7.79:1 —— 看着还是「苹果蓝」,
     但这次它是读得清的。iOS 自己用 systemBlue 配白字是系统惯例,
     不等于它过了无障碍标准。 */
.btn-sm.primary {
  background: #0a4fa8; color: #fff;
  box-shadow: 0 2px 8px rgba(10, 79, 168, .32);
}
.btn-sm:active { filter: brightness(.9); }
.btn-sm.ghost {
  margin-top: 6px;
  background: rgba(127, 127, 140, .16); color: var(--label-1);
}
.lg-fine a { color: #0a84ff; text-decoration: none; border-bottom: 1px solid rgba(90, 200, 250, .40); }

/* 字体署名。许可要求的「特别注明」,放在设置面板最底下:
   和上面的说明文字同档(13px / label-2),不额外造一档字号 ——
   署名不是内容,是页脚,给它一个特殊字号就又成了新的不统一。 */
.credit { margin-top: var(--sp-4); padding-top: var(--sp-3); }
.credit-l { margin: 0; }

/* 配额表:让「哪个接口贵、哪个便宜」一眼可见。
   11px → 13px:表里是 5,000 / 100 这种要真读进去的数字,
   11px 在玻璃上要眯眼看,而且和周围的说明文字差一档又成了新的不统一。 */
.quota {
  width: 100%; margin: 9px 0 5px;
  border-collapse: collapse; font-size: var(--fs-tertiary);
  line-height: var(--lh-tertiary);
}
.quota caption {
  text-align: left; font-size: var(--fs-micro); font-weight: var(--fw-semi);
  letter-spacing: 0; color: var(--label-3);
  padding-bottom: var(--sp-1);
}
.quota td { padding: var(--sp-1) 0; border-bottom: 1px solid rgba(127, 127, 140, .14); }
.quota td:last-child { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.quota .hint { display: block; font-size: var(--fs-micro); line-height: var(--lh-micro); color: var(--label-3); margin-top: var(--sp-1); }
.quota tr.tight td { color: #c2571a; }
.quota tr.tight td:last-child { font-weight: var(--fw-semi); }

/* 「深度搜索」按钮:唯一会消耗 100 次/日额度的入口,必须看起来像个小决定,而不是默认动作 */
#results .r.deep {
  width: 100%; text-align: left; border: 0; background: rgba(10, 132, 255, .07);
  display: flex; align-items: center; gap: 10px; cursor: pointer;
  font: inherit; color: inherit;
}
#results .r.deep .nm { color: var(--accent); font-weight: 600; }
#results .r.deep .mt { opacity: .8; }
#results .r.deep:active::before { opacity: 1; background: rgba(10, 132, 255, .15); }
#results .r.deep.busy { opacity: .5; cursor: default; }
.btn-sm.ghost2 {
  background: rgba(42, 157, 92, .16); color: #4ade80;
}
.btn-sm:disabled { opacity: .6; }

/* 今日已用量 */
.quota tr.usage td { border-bottom: 0; padding-top: 2px; }
.quota tr.usage span { display: inline-block; margin-right: 14px; }
.quota tr.usage b { font-size: var(--fs-tertiary); }
.quota tr.usage .tight { color: #c2571a; }
.quota tr.usage.warn b { color: #c2571a; }

/* 保密提示块 */
.sec { margin-top: var(--sp-1); }
.sec-h { font-size: var(--fs-heading); line-height: var(--lh-heading);
         font-weight: var(--fw-semi); margin-bottom: var(--sp-2); }
ul.tips { margin: 0 0 var(--sp-2); padding-left: var(--sp-5); }
ul.tips li { font-size: var(--fs-tertiary); line-height: var(--lh-tertiary); color: var(--label-2); margin-bottom: var(--sp-1); }
ul.tips b { color: var(--label-1); }
@media (prefers-color-scheme: dark) {
  ul.tips li { color: rgba(235, 235, 245, .72); }
  ul.tips b { color: #f2f2f7; }
}

/* ══════════════════════════════════════════════════════════
   液态玻璃 · 分档微调
   统一原则:同一层级的面板,折射带宽度、圆角、投影必须一致,
   否则整屏会「参差不齐」。这里按「尺寸」而不是按「组件」分档。
   ══════════════════════════════════════════════════════════ */

/* 分档:不同尺寸的玻璃用不同折射强度。
   注意 CSS 只管磨砂和圆角,折射强度由 app.js 里传给 Hyalite 的参数决定
   (小药丸 bevel 更小、色散关闭;大面板用默认值)。 */

/* 小药丸:面积小,折射带必须更窄 —— bevel 太宽会把按钮里的字压变形。
   色散也关掉:这么小的面积上彩边会炸成彩色噪点。 */
.pill {
  --r-lg: 16px;
  --blur: blur(16px) saturate(200%) brightness(1.10);
  /* 小控件底色要更实:底图是浅色路网,太透的玻璃在浅色地图上会「消失」 */
  background: var(--scrim), rgba(var(--glass-tint), var(--glass-a));
  /* 注意:这里【不要】用 var(--hyalite-edge, …)。
     Hyalite 写入的 --hyalite-edge 是深色 inset 阴影,在 36px 高的小按钮上
     会糊成一团明显的黑边(用户反馈的「黑色阴影」就是它)。
     小按钮用自己写的浅色倒角更干净,也更符合 iOS 观感。 */
  /* 边缘给完整的 --rim(和 .glass 用同一条),小药丸才和面板像同一块玻璃。
     原来这里只有一道 inset 1px,在暗色下几乎看不见 ——
     一排「打点 / 设置 / 明暗」在深色地图上看着是散的,不像一整排玻璃。
     走 --glass-edge 间接,暗色下自动换成没有白环的 --rim-dark ——
     这一条以前漏了,于是工具条按钮在暗色下成了整屏唯一还顶着白环的东西。 */
  box-shadow: var(--glass-edge, var(--rim)), var(--glass-shadow);
  --lg-class: "pill";
}
.pill .txt, .pill span { position: relative; z-index: 2; }

/* 搜索框:面积大、又装输入文字,磨砂最重,保证打字时视野清晰 */
.search {
  --r-lg: 22px;
  --blur: blur(26px) saturate(200%) brightness(1.06);
  /* 同理:输入框压在地图上,底色不足会读不出边界。 */
  background: var(--scrim), rgba(var(--glass-tint), var(--glass-a));
  /* 边缘:用【Hyalite 自己算出来的那条折射边】,不加 CSS 的 --rim。
     两者只能要一个 —— 叠起来就是两圈高光,正是用户反馈过的「白边」。
     搜索框是少数挂着折射的面板之一(1.5 万 px²,挂得起),
     所以它的边本来就该来自透镜,比任何手写的 inset 都准。
     没有折射的浏览器(Safari / Firefox)回落到纯外投影,
     仍然不出现内圈白边 —— 这是上一轮按用户反馈定下的,保持不变。 */
  box-shadow: var(--glass-edge, var(--hyalite-edge, var(--glass-shadow)));
  --lg-class: "search";
}

/* 大面板:侧栏 / 图例 / 结果下拉 / 详情卡
   ─────────────────────────────────────────────────────────
   材质选「regular」而不是「clear」,这是有官方依据的,不是审美偏好。
   HIG《Materials》对 Liquid Glass 两个变体写得很直接:
     · regular —— "blurs and adjusts the luminosity of background content
       to maintain legibility of text and other foreground elements…
       Use the regular variant when background content might create
       legibility issues, or when components have a significant amount
       of text, such as alerts, sidebars, or popovers."
     · clear   —— "highly translucent, which is ideal for prioritizing the
       visibility of the underlying content… Use this variant for components
       that float above media backgrounds — such as photos and videos"
   翻译成人话:文字多的侧边栏类组件(详情卡/图例/清单/搜索下拉)Apple 指定用
   regular;clear 是留给照片视频上方那种「优先看底下」的浮层。

   之前用的 --glass-a .62(亮)/ .78(暗)其实落在 clear 那一档,后果实测到了:
   开着图例再点标记,两层玻璃一叠,两边的字互相穿透,完全不可读。
   提高不透明度只增不减对比度,对比度验收不会因此退化。 */
#side, #results, #card {
  /* 磨砂加重:面板里全是文字,折射拉进来的底图不能压过正文 */
  --blur: blur(34px) saturate(160%) brightness(.98);
  --lg-class: "panel";
  --glass-a: var(--glass-a-panel);
}
/* 弹窗是最高层,用最强不透明,否则底图会干扰表单阅读 */
.modal {
  --glass-a-strong: .72;
  --lg-class: "modal";
}

/* 面板内部的次级按钮也走玻璃,保持语言一致。
   ⚠ 这条曾经和上面那条同名 .btn-sm 互相覆盖:上面给蓝底白字,
     这条把背景改成白色玻璃却没碰 color,于是「保存」变成白字白底。
     现在底色与字色都在这一条里写全,不再分散在两处。 */
.btn-sm {
  position: relative;
  background: rgba(255, 255, 255, .14);
  color: var(--label-1);
  border: 0;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .22);
  backdrop-filter: blur(10px) saturate(180%);
  -webkit-backdrop-filter: blur(10px) saturate(180%);
}
.btn-sm > * { position: relative; z-index: 2; }
/* 主按钮的玻璃滤镜要拿掉 —— 蓝底实心按钮再叠一层 backdrop 只会在边缘
   露出一圈半透明的糊边(实心按钮本来就不该透出背后的地图)。 */
.btn-sm.primary {
  background: #0a4fa8;
  color: #fff;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  box-shadow: 0 2px 8px rgba(10, 79, 168, .32);
}

/* 破坏性动作(清空本地数据)。
   曾经把它做成顶栏药丸,并给玻璃喂一个深酒红 tint -- 结果白色文字
   压在一块深色玻璃上,在一排浅色玻璃按钮中间就是一团泥巴。
   现在它待在图例面板里,改成 iOS 标准的 destructive 按钮:
   淡红底 + 深红字(不是实心红块,实心红块在玻璃语言里太重)。 */
.btn-sm.danger {
  background: rgba(255, 69, 58, .14);
  color: #b3261e;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .28);
}
.btn-sm.danger.on {
  background: #ff453a;
  color: #fff;
  box-shadow: 0 4px 14px rgba(255, 69, 58, .34);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .btn-sm.danger { color: #ff8a80; }
}
:root[data-theme="dark"] .btn-sm.danger { color: #ff8a80; }
:root[data-theme="dark"] .btn-sm.danger.on { background: #ff453a; color: #fff; }

/* ══ 无障碍降级 ══ */
/* 用户在系统里关掉了透明度 → 退回实心面板,不做半透明也不做折射 */
@media (prefers-reduced-transparency: reduce) {
  :root { --glass-a: .96; --glass-a-strong: .98; }
  .glass {
    --blur: none;
    background: rgba(22, 24, 32, .97);
    box-shadow: 0 10px 30px rgba(0, 0, 0, .5), 0 0 0 .5px rgba(255, 255, 255, .12);
  }
  .glass::before { display: none; }
}
/* 不支持 backdrop-filter 的老浏览器:实心深色,保证可读 */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  :root { --glass-a: .90; --glass-a-strong: .95; }
  .glass { --blur: none; }
  .glass::before { display: none; }
}
/* 尊重「减弱动态效果」:折射是静态的,但高光过渡仍应关掉 */
@media (prefers-reduced-motion: reduce) {
  .glass, .glass::before, .glass::after { transition: none !important; }
}

/* ══ SVG defs 容器必须彻底隐藏,但不能 display:none
   (display:none 会让部分浏览器无法解析 url(#…) 引用) ══ */
.lg-defs {
  position: absolute; width: 0; height: 0;
  overflow: hidden; pointer-events: none;
}

/* ══ 地图控件也统一成玻璃,否则它是整屏唯一的「非玻璃」元素 ══ */
.maplibregl-ctrl-group {
  background: var(--scrim), rgba(var(--glass-tint), var(--glass-a)) !important;
  border: none !important;
  border-radius: var(--r-sm) !important;
  box-shadow: var(--glass-edge, var(--hyalite-edge, var(--rim))), var(--glass-shadow) !important;
  backdrop-filter: var(--blur);
  -webkit-backdrop-filter: var(--blur);
  overflow: hidden;
}
.maplibregl-ctrl-group button + button { border-top: 1px solid var(--separator) !important; }
/* 缩放按钮的命中区。MapLibre 默认是 29×29 —— 比 HIG《Buttons》
   要求的 "at least 44x44 pt" 小了一大截,在触屏上是真的难点。
   视觉保持 29px(放大图标在 44px 的盒子里会显得笨重),
   命中区用 ::after 扩出去,这也是 Apple 自己对待 compact 控件的做法。 */
.maplibregl-ctrl-zoom-in, .maplibregl-ctrl-zoom-out {
  position: relative;
  width: 44px; height: 44px;
}
.maplibregl-ctrl-zoom-in::after, .maplibregl-ctrl-zoom-out::after {
  content: ''; position: absolute; left: 50%; top: 50%;
  width: 44px; height: 44px; margin: -22px 0 0 -22px;
}
.maplibregl-ctrl-group button .maplibregl-ctrl-icon { opacity: .75; }
:root[data-theme="dark"] .maplibregl-ctrl-group button .maplibregl-ctrl-icon { filter: invert(1); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .maplibregl-ctrl-group button .maplibregl-ctrl-icon { filter: invert(1); }
}
.maplibregl-ctrl-attrib {
  background: rgba(18, 20, 28, .52) !important;
  color: rgba(255, 255, 255, .62) !important;
  border-radius: 8px;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}
.maplibregl-ctrl-attrib a { color: rgba(255, 255, 255, .72) !important; }

/* ══ 主题切换按钮(右上角)══════════════════════════════
   完整的样式在下面「Z-Index 体系」之后统一写了一份,
   这里的旧副本已经删掉,避免两处规则互相覆盖。 */

/* ══════════════════════════════════════════════════════════
   层级体系 · Z-Index Scale
   每一层只做一件事,互不重叠。数值之间留足间隔,
   避免以后加东西时随手写个 999 把层级搞乱。
   ──────────────────────────────────────────────────────────
     1  #veil            底图调色(压在地图上,不透明度极低)
     2  地图原生控件
    20  .topbar          顶部:搜索 + 工具按钮
    24  .fmc             品类/时段筛选胶囊
    25  #setup           设置(含图例与地图要素 —— v104 起图例并入这里)
    26  #card            门店详情(桌面端在右侧,顶边让开工具条)
    26  #side            门店清单(桌面端左侧,顶边让开状态栏)
    28  #results         搜索下拉(必须盖住地图,不能被卡片盖)
    30  .topbar          再次提升:搜索框永远最上,任何面板都不许压它
    40  #modalWrap       表单弹窗(全屏遮罩,最高)
    50  #toast           轻提示(盖住一切但可穿透点击)
   ══════════════════════════════════════════════════════════ */
.topbar { z-index: 30; }
#side   { z-index: 26; }
#card   { z-index: 26; }
#setup  { z-index: 25; }
.fmc    { z-index: 24; }
#results{ z-index: 28; }
#toast  { z-index: 50; pointer-events: none; }

/* 桌面端:清单与详情卡同一层,但靠左右分居,不会互相遮挡。
   顶边统一让开状态栏 + 顶部工具条。
   (#legend 的 z-index 和 top 已随图例并入 #setup 一起删掉 —— 元素没了,
    规则留着不会报错,但它会让人以为图例面板还在。) */
@media (min-width: 900px) {
  .topbar { z-index: 30; }
  #card   { top: calc(var(--sat) + 62px); }   /* 让开工具条 */
  #side   { top: calc(var(--sat) + 12px); }   /* 左侧,不受工具条影响 */
}

/* ══ 主题切换按钮(右上角)══════════════════════════════
   HIG 明确:图像/图标按钮【不要加系统描边】,用留白代替边框。
   所以这枚按钮没有任何 border,只有一枚同尺寸的玻璃底 + 图标。 */
#btnTheme {
  /* 44×44:HIG《Buttons》的 Regular 档尺寸。
     原来是 36×36 —— 显式的 width/height 覆盖了 .pill 的 44px,
     于是工具条里就剩它一个够不着 44pt。纯图标按钮不缩小,
     HIG 对它没有"图标可以更小"的例外,小的是里面的图形(17px),不是按钮。 */
  width: 44px; height: 44px; padding: 0; flex: none;
  justify-content: center; align-items: center;
  border: 0;                       /* HIG:不要描边 */
  color: var(--label-1);           /* 图标走 currentColor,两态自动跟随 */
}
/* 图标:太阳 / 月亮。
   这里踩过一个坑 —— 早先用 `box-shadow: inset -5px 1.5px 0 0 var(--bg-page)`
   在实心圆上挖一道月牙缺口,结果那个「缺口」填的是【页面底色】,
   可按钮浮在地图上、背后是高德瓦片,于是缺口变成一块突兀的浅色,
   远看就是一个黑色团块,根本不像月亮。

   正确做法是用 SVG 做 mask:形状由矢量路径给出,颜色交给 currentColor,
   这样图标既能跟着主题变色,又完全不依赖背后是什么。 */
#btnTheme .theme-ico {
  width: 17px; height: 17px;
  display: block; flex: none;
  background-color: currentColor;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-size: contain; mask-size: contain;
}
/* 亮色态:月牙(点一下切到暗色) */
:root[data-theme="light"] .theme-ico,
:root:not([data-theme]) .theme-ico {
  -webkit-mask-image: var(--ico-moon); mask-image: var(--ico-moon);
}
/* 暗色态:太阳(点一下切到亮色) */
:root[data-theme="dark"] .theme-ico {
  -webkit-mask-image: var(--ico-sun); mask-image: var(--ico-sun);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .theme-ico {
    -webkit-mask-image: var(--ico-sun); mask-image: var(--ico-sun);
  }
}

/* ══ 「现在想吃」触发器(搜索框右侧)═══════════════════
   HIG 触控目标:44×44pt 是默认值,可见圆形可以更小,
   但可点区域必须撑满。用 ::after 把热区扩出去。 */
.fmc-now-trigger {
  position: relative;
  width: 36px; height: 36px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 17px; line-height: 1;
  border: 0;                       /* HIG:图标按钮不要描边 */
  border-radius: var(--r-full);
  color: var(--label-1);
  background: var(--scrim), rgba(var(--glass-tint), var(--glass-a));
  backdrop-filter: var(--hyalite, var(--blur));
  -webkit-backdrop-filter: var(--hyalite, var(--blur));
  box-shadow: var(--glass-edge, var(--hyalite-edge, var(--rim))), var(--glass-shadow);
  cursor: pointer;
}
.fmc-now-trigger::after {
  content: ""; position: absolute; inset: -4px;   /* 36 + 8 = 44pt 热区 */
  border-radius: var(--r-full);
}
.fmc-now-trigger:active { transform: scale(.94); }

/* 下拉面板 */
.fmc-now-panel {
  position: absolute;
  top: calc(100% + 10px); right: 0;
  min-width: 232px;
  padding: var(--sp-2);
  border-radius: var(--r-md);
  border: 0;
  background: var(--scrim), rgba(var(--glass-tint), var(--glass-a-strong));
  backdrop-filter: var(--blur);
  -webkit-backdrop-filter: var(--blur);
  box-shadow: var(--glass-edge, var(--hyalite-edge, var(--rim))),
              0 8px 32px rgba(0, 0, 0, .18);
  z-index: 34;                     /* 高于 topbar,popover 语义 */
  display: none;
}
.fmc-now-panel.open { display: block; }
.fmc-now-head {
  padding: 6px 10px 8px;
  font-size: var(--fs-micro);     /* 11pt = Apple 最小字号 */
  color: var(--label-3);
  letter-spacing: 0;                /* 中文不照搬负字距 */
}
.fmc-now-item {
  display: flex; align-items: center; gap: var(--sp-2);
  width: 100%; padding: var(--sp-3) var(--sp-3);
  border: 0; border-radius: var(--r-sm);
  background: transparent;
  color: var(--label-1);
  font-size: var(--fs-secondary);      /* 15pt */
  letter-spacing: 0;
  text-align: left; cursor: pointer;
  min-height: 44px;                 /* HIG 触控目标 */
}
.fmc-now-item { position: relative; }
.fmc-now-item::before {
  content: ''; position: absolute; inset: 0;
  background: rgba(127, 127, 140, .16);
  border-radius: inherit; opacity: 0; pointer-events: none;
  transition: opacity .18s ease;
}
.fmc-now-item:active::before { opacity: 1; }
.fmc-now-item > * { position: relative; z-index: 1; }
.fmc-now-item .emo { font-size: 18px; flex: none; width: 24px; text-align: center; }
.fmc-now-item .sub { display: block; font-size: var(--fs-caption); color: var(--label-3); margin-top: 1px; }

/* ══ 搜索框 + 「现在想吃」的一行 ══
   两者并排成一行,现在想吃贴着搜索框右侧。
   高 HIG 的圆角同心原则:这个容器的圆角要和里面的搜索框一致。 */
.topbar .search-wrap {
  position: relative;
  display: flex; align-items: flex-start; gap: var(--sp-2);
  max-width: 560px;
}
.topbar .search-slot { position: relative; flex: 1; min-width: 0; }
.topbar .search { flex: 1; min-width: 0; }
#nowSlot { position: relative; flex: none; }

/* 搜索结果下拉:绝对定位在 .search-slot 里,顶边贴着搜索框底边、宽度等于搜索框。
   改成绝对定位有两个原因:
     ① 不再依赖 .topbar 的 flex 方向 —— 之前它是 .topbar 的直接子节点,
        宽屏 .topbar 变 row,结果它被当成工具条里的一个 item 排到了搜索框
        右边,还被压到 204px 宽,店名竖着排(用户实测截图)。
     ② 宽度自动等于搜索框,不需要两处各写一个 max-width 去猜。 */
#results {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 2;
}

/* ══ 工具条:按钮之间用留白分组,不用竖线 ══
   HIG 说相邻控件的留白是 12pt(这里 8px 视觉 + ::after 扩热区)。
   刻意不加分隔竖线 —— 细线在小尺寸上会显得脏。 */
.tools { gap: 8px; }
.tools .pill { position: relative; }
/* 顶栏现在只剩无风险动作,按钮之间不再需要分组留白 */

/* 主题按钮:和别的按钮同尺寸(44pt),只是没有文字 */
#btnTheme { width: 44px; height: 44px; padding: 0; justify-content: center; }