* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  font-family: -apple-system, "PingFang SC", "Microsoft YaHei", "Segoe UI", sans-serif;
  color: #1f2430;
  background: #f3f5f8;
}

/* 顶栏 */
/* safe-area：viewport-fit=cover 后页面铺到刘海/挖孔区，背景铺满、内容用内边距避开
   （横屏时挖孔在左右两侧 —— 不处理的话左上角顶出一块系统黑底，见 2026-10-14）。
   浏览器里 env() 恒为 0，这些 calc 与原值完全等价，桌面端不受影响。 */
.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: calc(52px + env(safe-area-inset-top));
  padding: env(safe-area-inset-top) calc(16px + env(safe-area-inset-right)) 0 calc(16px + env(safe-area-inset-left));
  background: #141a24;
  color: #fff;
}
.brand { display: flex; align-items: baseline; gap: 8px; }
.brand-name { font-size: 20px; font-weight: 700; letter-spacing: 1px; }
.brand-ver { font-size: 12px; color: #8fa3c0; }
.status-pills { display: flex; gap: 8px; }
.pill {
  font-size: 12px;
  padding: 4px 10px;
  background: rgba(255,255,255,.08);
  border-radius: 999px;
  white-space: nowrap;
}

/* 布局（顶部减去顶栏的 safe-area 内边距，防止 iOS standalone 下日志被顶出首屏） */
.layout { display: flex; height: calc(100vh - 52px - 150px - env(safe-area-inset-top)); min-height: 420px; }
.sidebar {
  width: 440px;
  min-width: 380px;
  overflow-y: auto;
  padding: 12px;
  padding-left: calc(12px + env(safe-area-inset-left));
  background: #fff;
  border-right: 1px solid #e3e8ef;
}
.map-wrap { flex: 1; position: relative; }
#map { width: 100%; height: 100%; background: #dfe6ee; }

/* 卡片 */
.card {
  background: #fff;
  border: 1px solid #e3e8ef;
  border-radius: 10px;
  padding: 12px;
  margin-bottom: 12px;
}
.card h2 {
  margin: 0 0 10px;
  font-size: 14px;
  color: #3a4660;
  border-left: 3px solid #1a73e8;
  padding-left: 8px;
}

/* 表单 */
label { display: block; font-size: 12px; color: #6b7688; margin: 8px 0 4px; }
select, input {
  width: 100%;
  padding: 7px 8px;
  border: 1px solid #d5dbe4;
  border-radius: 6px;
  font-size: 13px;
  background: #fbfcfe;
}
select:focus, input:focus { outline: none; border-color: #1a73e8; }
.params { margin-top: 4px; }
/* 控件下方的补充说明（如「名称编码」那一行提示）：比 label 更浅、更小，不抢视线 */
.field-hint { margin: 4px 0 0; font-size: 11px; line-height: 1.5; color: #8b94a3; }
.hidden { display: none; }
/* ★ [hidden] 必须压住任何 display 声明（2026-10-16）。
 *   UA 默认样式表里有 `[hidden] { display: none }`，但**作者样式优先级更高** ——
 *   元素只要再带一个 `display: flex/grid/inline-flex` 的类（本项目就有 `.row`），
 *   hidden 就会失效、元素照常占位。本项目目前这些元素都用 class="hidden"（`.hidden` 规则），
 *   没踩到这个坑，但**哪天有人写 `<div class="row hidden>` 或 `class="row" hidden` 就会中招** ——
 *   症状是"页面上莫名空一块"，且极难联想到是 CSS 优先级问题。提前钉死。 */
[hidden] { display: none !important; }

/* 按钮 */
.btn {
  padding: 8px 12px;
  border: 1px solid #d5dbe4;
  background: #f7f9fc;
  border-radius: 6px;
  cursor: pointer;
  font-size: 13px;
  color: #2a3140;
}
.btn:hover { background: #eef2f7; }
.btn.primary { background: #1a73e8; border-color: #1a73e8; color: #fff; }
.btn.primary:hover { background: #1663c9; }
.btn-sm { padding: 7px 10px; font-size: 12px; }
.row { display: flex; align-items: center; gap: 8px; margin-top: 10px; }
.btns { flex-wrap: wrap; }

/* 天地图密钥那一行（2026-10-16）：输入框自己占满剩余宽度，两个按钮贴着右边。
   .tk-row 必须显式写 flex-wrap —— 窄屏下"输入 + 申请 + 清除"三件套要能换行，
   否则按钮被挤到看不见（与 .btns 同一个道理）。 */
.tk-row { flex-wrap: wrap; }
.tk-row #tianditu-key { flex: 1 1 200px; min-width: 0; }
.tk-row .btn { flex: 0 0 auto; }

/* ===== 天地图密钥卡（2026-10-17：从「中继状态」里独立出来 + 可折叠）=====
   它是**本机个人配置**，跟"别人报告这台台站能不能用"是两回事，所以单独一张卡、放在投票卡下面。
   折叠规则：填了密钥 → 自动收起（标题显示「已配置」，一眼看出配过）；没填 → 展开（那时正需要引导申请）。
   手动点标题仍可随时展开/收起。 */
.tk-card .tk-card-head {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
}
.tk-card .tk-card-head:hover { color: #1a73e8; }
.tk-card .tk-card-head:focus-visible { outline: 2px solid #1a73e8; outline-offset: 2px; }
.tk-card .tk-caret {
  display: inline-block;
  font-size: 10px;
  color: #8a94a6;
  transition: transform .18s ease;      /* 收起时箭头朝右，展开时朝下 */
}
.tk-card.collapsed .tk-caret { transform: rotate(-90deg); }
.tk-card .tk-card-body { margin-top: 2px; }
.tk-card.collapsed .tk-card-body { display: none; }
/* 「已配置」角标：折叠后也能看出这台机子配过自己的密钥 */
.tk-card .tk-badge {
  font-size: 11px;
  font-weight: 500;
  color: #1a73e8;
  background: #e8f0fe;
  border: 1px solid #c6dafc;
  border-radius: 4px;
  padding: 1px 6px;
  margin-left: 2px;
}

/* 天地图密钥的掩码（2026-10-16）：tk 是凭证，**默认以星号显示**，不给人看明文。
 *
 * ⚠️ 浏览器支持极不统一，这是本项目实测的结果（2026-10-16 Chrome 155）：
 *   · Safari / iOS WebKit：支持 `-webkit-text-security: asterisk`，一行就显示成 *********；
 *   · Chrome / Edge / Firefox：**不支持**这个属性（实测 CSS.supports() 返回 false），
 *     type="password" 只会画成默认的圆点（•），不是星号。
 * 所以策略是：
 *   ① 先用 `-webkit-text-security: asterisk` —— 支持的浏览器直接得到星号，零 JS；
 *   ② 不支持的（上面那条 CSS 直接被忽略）由 js/app.js 的 _initTiandituMask()
 *      实时把输入框的值改写成等长的星号串，并接管输入事件做"只增星号"的还原。
 *   ③ 用户点「显示」按钮才临时给出明文（核对有没有填错时用）。
 *
 * ⛔ 别退回到 type="text" —— 那就是明文了，肩窥 / 录屏 / 演示都会泄。
 */
.tk-row #tianditu-key { -webkit-text-security: asterisk; }
.tk-row #tianditu-key.tk-revealed { -webkit-text-security: none; }

/* 定位 */
.coords { font-size: 12px; color: #4a5568; margin: 8px 0; font-variant-numeric: tabular-nums; }
.nearest {
  background: #f0f6ff;
  border: 1px solid #cfe0fb;
  border-radius: 8px;
  padding: 10px;
  font-size: 13px;
  color: #2a3c5a;
}
.nearest-title { font-size: 11px; color: #6b7688; margin-bottom: 4px; }
.nearest-name { font-size: 16px; font-weight: 700; margin-bottom: 4px; }
.muted { color: #8a94a6; }

/* 中继台维护表单 */
.rep-form {
  border-top: 1px solid #e3e8ef;
  margin-top: 10px;
  padding-top: 10px;
}
.rep-form h3 { margin: 0 0 8px; font-size: 13px; color: #3a4660; }
.flex1 { flex: 1; min-width: 0; }
.btn.danger { background: #fdecea; border-color: #f5c6c0; color: #b3261e; }
.btn.danger:hover { background: #fbe3e0; }
.rep-act { display: inline-flex; gap: 4px; margin-left: 6px; flex: 0 0 auto; }
.rep-act .mini {
  font-size: 11px;
  padding: 2px 6px;
  border: 1px solid #d5dbe4;
  border-radius: 5px;
  background: #f7f9fc;
  color: #2a3140;
  cursor: pointer;
}
.rep-act .mini.del { color: #b3261e; border-color: #f5c6c0; }
.picking { cursor: crosshair !important; }

/* 中继台列表 */
.rep-list { list-style: none; margin: 0; padding: 0; max-height: 240px; overflow-y: auto; }
.rep-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  padding: 8px 6px;
  border-bottom: 1px solid #eef1f5;
  cursor: pointer;
  font-size: 13px;
}
.rep-item:hover { background: #f5f8fc; }
.rep-name {
  font-weight: 600;
  flex: 1;
  min-width: 0;
  white-space: nowrap;
}
.rep-freq { color: #1a73e8; font-variant-numeric: tabular-nums; white-space: nowrap; flex: 0 0 auto; }
.rep-dist { color: #7a8496; font-size: 12px; white-space: nowrap; flex: 0 0 auto; }
.rep-item.disabled { opacity: 0.55; }
.rep-item.disabled .rep-name { color: #7a8496; text-decoration: line-through; }
/* ★ 500 米内"可能重复"：名称标黄字（悬停有提示，说明与哪一条相距多远）。
   距离用与判重同一套 Haversine + 同一个 500 米半径，**并且要求收发频率相同**
   （同地点架着多套不同频率的中继台是常态，不算重复）。见 js/database.js duplicateIndex。 */
.rep-item.dup .rep-name { color: #c07812; }
.rep-tag {
  flex: 0 0 auto;
  font-size: 11px;
  color: #b3261e;
  border: 1px solid #f5c6c0;
  border-radius: 4px;
  padding: 1px 5px;
  background: #fdf0ef;
}

/* ★ 禁用态：统一变灰、不可点、鼠标改为禁止手势（连接电台后锁定参数会用到） */
select:disabled, input:disabled {
  background: #f0f0f0;
  color: #b0b0b0;
  border-color: #dcdcdc;
  cursor: not-allowed;
  /* iOS Safari 下 disabled 输入框的文字不跟随 color，需显式指定填充色 */
  -webkit-text-fill-color: #b0b0b0;
  opacity: 1;                /* iOS 默认额外加透明度，统一改由配色控制 */
}
.btn:disabled {
  background: #f0f0f0;
  color: #b0b0b0;
  border-color: #dcdcdc;
  cursor: not-allowed;
  opacity: .8;
  box-shadow: none;
}
.btn:disabled:hover { background: #f0f0f0; }
.btn.primary:disabled { background: #f0f0f0; color: #b0b0b0; border-color: #dcdcdc; }

/* 中继台列表标题旁的数量（共 N 个） */
.card h2 .rep-count {
  margin-left: 6px;
  font-size: 11px;
  font-weight: normal;
  color: #8a94a6;
  font-variant-numeric: tabular-nums;   /* 数字位数变化时宽度不跳 */
}

/* 紧跟在数量统计之后的「可贡献 / 可更新」提示（本地条数 vs 服务器条数，见 app.js _updateSyncHint） */
.card h2 .rep-sync-hint {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: 8px;
  font-size: 11px;
  font-weight: normal;
}
.card h2 .rep-sync-hint.contribute { color: #a3610a; background: rgba(224,160,48,.18); }
.card h2 .rep-sync-hint.update { color: #15509e; background: rgba(26,115,232,.12); }
/* 条数一样多、但有「停用 / 已核实」标记等着同步（只有填了管理口令才会出现，见 app.js _updateSyncHint） */
.card h2 .rep-sync-hint.flags { color: #6a1b9a; background: rgba(106,27,154,.12); }
/* 双类提权：以后万一有媒体查询用单类选择器写了 display，也不会把这句隐藏盖掉 */
.card h2 .rep-sync-hint.hidden { display: none; }

/* 电台设置卡片锁定态（连接成功后）：标题旁提示 + label / 开关文字一并变灰 */
.card h2 .lock-hint {
  margin-left: 6px;
  font-size: 11px;
  font-weight: normal;
  color: #b0b0b0;
}
.card.locked label { color: #b0b0b0; }
.card.locked .switch { cursor: not-allowed; }

/* 电台端异常掉线：同位置显示原因（与"已连接"提示互斥，两者不会同时出现） */
.card h2 .lock-hint.lost { color: #c07812; }

/* 顶栏状态胶囊：异常断开时改为警示色，与"未连接"的常态灰区分开 */
.pill.lost {
  background: rgba(224,160,48,.22);
  color: #f0b95a;
  box-shadow: inset 0 0 0 1px rgba(224,160,48,.4);
}

/* 顶栏状态胶囊：离线时亮起（地图降级为网格底图，两个联网按钮禁用）。
   用蓝灰而不是警示色 —— 脱网是正常使用场景，不是故障。 */
.pill.offline {
  background: rgba(120,150,200,.24);
  color: #a9c4ea;
  box-shadow: inset 0 0 0 1px rgba(120,150,200,.42);
}

/* 开关 */
.switch { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; cursor: pointer; }
.switch input { width: auto; margin: 0; flex: 0 0 auto; }
/* 必须写成双类 .switch.hidden：上面的 .switch 是 display:inline-flex，
   单类 .hidden（display:none）在后面出现但同权重，谁在后面谁生效的规则会打架，
   这里显式压住 —— 与 .map-search.hidden / .rep-sync-hint.hidden 同一个教训。 */
.switch.hidden { display: none; }

/* 日志 */
.logbar {
  height: 150px;
  background: #141a24;
  color: #c9d6ea;
  padding: 8px calc(12px + env(safe-area-inset-right)) 8px calc(12px + env(safe-area-inset-left));
  display: flex;
  flex-direction: column;
}
.log-title { font-size: 12px; color: #8fa3c0; margin-bottom: 6px; }
.log-panel {
  flex: 1;
  overflow-y: auto;
  margin: 0;
  font-size: 12px;
  line-height: 1.5;
  white-space: pre-wrap;
  word-break: break-all;
}

/* 触控优化：按钮/列表/开关禁用双击缩放，减少点击延迟 */
button, select, .rep-item, .switch { touch-action: manipulation; }

@media (max-width: 760px) {
  /* 顶栏吸顶，状态可始终可见 */
  .topbar {
    height: auto;
    min-height: 52px;
    padding: calc(8px + env(safe-area-inset-top)) calc(12px + env(safe-area-inset-right)) 8px calc(12px + env(safe-area-inset-left));
    flex-wrap: wrap;
    gap: 8px;
    position: sticky;
    top: 0;
    z-index: 1000;
  }
  .status-pills { flex-wrap: wrap; gap: 6px; }
  .pill { font-size: 11px; padding: 3px 8px; }

  /* 纵向堆叠布局：地图置顶（一打开就能看到），设置/列表在下方滚动 */
  .layout {
    flex-direction: column;
    height: auto;
    min-height: 0;
  }
  .map-wrap {
    order: -1;
    height: 50vh;
    min-height: 280px;
    flex: 0 0 auto;
  }
  .sidebar {
    order: 0;
    width: 100%;
    min-width: 0;
    padding: 10px;
    border-right: none;
    border-bottom: 1px solid #e3e8ef;
  }

  /* iOS 聚焦防自动放大 + 触控目标加大（输入框保持 16px 防键盘缩放） */
  input { font-size: 16px; }
  select { font-size: 15px; }
  .btn, .btn-sm { font-size: 16px; padding: 12px 16px; }
  .row.btns { gap: 8px; }
  .row.btns .btn { flex: 1 1 auto; min-height: 44px; }

  .card { padding: 10px; }
  .rep-list { max-height: 200px; }
  .rep-item { padding: 10px 6px; font-size: 14px; }

  /* 日志在移动端收窄 */
  .logbar { height: 130px; padding: 8px 10px; }
  .log-panel { font-size: 11px; }
}

/* ===== 中继台状态报告（社区报告：可用 / 不可用） =====
   注意与「停用」「已核实」两个**数据维护标记**的视觉区分：那两个说的是"这条数据本身
   被怎么维护"，这里说的是"这台站最近好不好用"。所以用了偏中性的蓝/橙，
   并且文字前缀带「报告·」/「社区报告：」，一眼能看出是另一条轴。 */

/* 卡片（当前中继台）里的那一行汇总 */
.nearest-report { margin-top: 4px; font-size: 11px; color: #6b7688; }
.nearest-report.ok { color: #1a73e8; }
.nearest-report.down { color: #c07812; }
.nearest-report.unknown { color: #8a94a6; }

/* 报告按钮下方的提示行（当前中继台名 + 报告汇总） */
.rep-report-hint {
  margin-top: 6px;
  font-size: 11px;
  color: #6b7688;
}
.rep-report-hint.hidden { display: none; }

/* ===== 「中继状态」投票卡（参考 UI：点击投票，长按取消） =====
   两行（可用 / 不可用），每行 = 名称 + 更新于 + 票数|占比 + 进度条。
   行本体是 <button>（保留 disabled 语义与键盘可达），但视觉上是投票行，
   不套用 .btn 的边框/背景。进度条两条都用蓝色（与参考图一致）。 */
.rep-vote-card {
  margin-top: 8px;
  border: 1px solid #e3e8ef;
  border-radius: 10px;
  background: #fff;
  padding: 10px 12px 8px;
}
.rep-vote-title { font-size: 14px; font-weight: 600; color: #1f2733; margin-bottom: 4px; }
.rep-vote-tip { font-size: 11px; font-weight: 400; color: #8a94a6; margin-left: 6px; }
.rep-vote-row {
  display: block;
  width: 100%;
  text-align: left;
  background: transparent;
  border: 0;
  padding: 7px 0 9px;
  cursor: pointer;
  font: inherit;
  color: inherit;
  -webkit-touch-callout: none;            /* 长按不弹系统菜单（iOS） */
  -webkit-user-select: none;
  user-select: none;
}
.rep-vote-row + .rep-vote-row { border-top: 1px solid #eef1f5; }
.rep-vote-row:disabled { cursor: default; opacity: 0.55; }
.rep-vote-head { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.rep-vote-label { font-size: 14px; font-weight: 600; color: #1f2733; flex: none; }
.rep-vote-at { flex: 1 1 auto; font-size: 11px; color: #8a94a6; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rep-vote-num { flex: none; font-size: 13px; color: #3c4657; font-variant-numeric: tabular-nums; }
.rep-vote-bar {
  margin-top: 6px;
  height: 6px;
  border-radius: 3px;
  background: #e9edf3;
  overflow: hidden;
}
.rep-vote-fill {
  height: 100%;
  width: 0;
  border-radius: 3px;
  background: #1a8fd1;
  transition: width 0.25s ease;
}

/* 列表徽标：已判定可用 / 已判定不可用（未判定时不显示） */
.rep-tag.report-ok {
  color: #185fa5;
  border-color: #b5d4f4;
  background: #e6f1fb;
}
.rep-tag.report-down {
  color: #854f0b;
  border-color: #fac775;
  background: #faeeda;
}

/* ===== 中继台列表：已核实（锁定）记录按钮禁用灰色 ===== */

/* 已核实徽章（绿色，与「停用」红色徽章区分） */
.rep-tag.verified {
  color: #2e7d32;
  border-color: #c8e6c9;
  background: #e8f5e9;
}

/* ★ 未核实徽章（灰色，2026-10-17）：与「已核实」成对的**同一列**的两个状态，每条记录必有其一
   （见 js/app.js _renderRepeaterList 的 vtag）。两点用意：
     ① 对齐 —— 两个标记都是三个字、宽度一致，而且都排在「编辑 / 删除」左边最后一格，
        所以桌面端列表的这一列始终是齐的（以前只有已核实那几条带徽章，徽章时有时无、
        每行的按钮跟着左右跳）。
     ② 克重 —— 灰底灰字，说的是"这条数据还没人核实过"，本来就不是需要被强调的信息；
        既不去跟绿色抢注意力，也不与「停用」的红色（"这台不参与漫游"）混淆。 */
.rep-tag.unverified {
  color: #8a94a6;
  border-color: #e0e5ec;
  background: #f5f7fa;
}

/* ★ 窄屏隐藏「已核实」徽章（2026-10-14）：
   中继台列表是单行 flex（名称 · 频率 · 距离 · 停用 · 已核实 · 编辑 · 删除 全挤在一行、
   .rep-name 又不换行），手机上再塞一个绿徽章必然折行/挤压，看起来凌乱。
   隐藏后信息不丢：已核实的锁定状态本来就同时体现在「编辑/删除按钮变灰」上，
   点一下编辑/删除还会在日志里给出「已核实（锁定）…请先填管理口令」的说明。
   只隐藏列表徽章 —— 表单里的「已核实（锁定字段）」复选框照常显示（管理员要靠它维护标记）。 */
@media (max-width: 760px) {
  .rep-tag.verified { display: none; }
  /* ★ 未核实徽章同理隐藏（2026-10-17）：它在桌面端的价值是"和已核实凑成对齐的一列"，
     而窄屏单行根本排不下列，且在手机上它几乎不带信息（绝大多数记录本来就是未核实）。
     两个一起藏 —— 只藏一个的话，剩下的那个又变成"时有时无"，桌面端刚解决的对齐问题
     会在窄屏以另一种方式回来。 */
  .rep-tag.unverified { display: none; }
  /* 报告徽标同理由此隐藏：列表是单行 flex，手机上塞不下；
     信息不丢 —— 「当前中继台」卡片里有同一句「社区报告：可用 x / 不可用 y」。 */
  .rep-tag.report-ok, .rep-tag.report-down { display: none; }
}

/* 迷你按钮：编辑 / 删除 */
.rep-act .mini:hover {
  background: #eef2f7;
}
.rep-act .mini.del:hover {
  background: #fbe3e0;
}

/* ★ 禁用态：编辑/删除按钮变灰、不可点、鼠标改为禁止手势 */
.rep-act .mini:disabled,
.rep-act .mini.locked {
  background: #f0f0f0;
  color: #b0b0b0;
  border-color: #dcdcdc;
  cursor: not-allowed;
  opacity: .8;
  box-shadow: none;
}
/* 保留 title 悬浮提示（若改 none 则连提示也不显示） */
.rep-act .mini:disabled {
  pointer-events: auto;
}
/* 已核实的"删除"按钮：不显示红色警示色，统一为灰 */
.rep-act .mini.del:disabled,
.rep-act .mini.del.locked {
  color: #b0b0b0;
  border-color: #dcdcdc;
  background: #f0f0f0;
}
/* ★ 已核实记录的编辑/删除按钮（.locked）**不是 disabled，是"点得动但要先满足条件"**：
   点它会给出一条明确提示（通常是"请先填「管理口令」"），填了口令就真的能操作。
   所以光标要回到手型 —— not-allowed 与"其实能点"自相矛盾，用户会以为点坏了。 */
.rep-act .mini.locked { cursor: pointer; }

/* 中继台表单里"业务字段只读"的样子（编辑已核实记录时，业务字段整体禁用；
   见 app.js _setRepFormLocked —— 服务器对已核实记录只接受标记修改） */
.rep-form input:disabled,
.rep-form select:disabled {
  background: #f4f5f7;
  color: #8b93a1;
  -webkit-text-fill-color: #8b93a1;   /* iOS Safari：不写这条文字仍是深色 */
  cursor: not-allowed;
}
/* 「已核实」复选框未填口令时不可用（核实是权威标记，不能让普通用户在本机伪造） */
.switch.locked { opacity: .55; }
.switch input:disabled { cursor: not-allowed; }

/* 当前选中/漫游指向的中继台 */
.rep-item.active {
  background: #eaf3ff;
  /* box-shadow: inset 3px 0 0 #1a73e8; */
}
.rep-item.active .rep-name {
  color: #1a73e8;
}

/* ===== 地图：底图切换按钮（自绘，替代第三方图层控件） ===== */
.map-switch {
  position: absolute;
  top: 10px;
  left: 48px;            /* 避开官方 API 左上角的缩放控件 */
  z-index: 1200;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  max-width: calc(100% - 64px);
}
.map-switch-btn {
  font-size: 12px;
  padding: 5px 10px;
  border: 1px solid #d5dbe4;
  border-radius: 6px;
  background: rgba(255, 255, 255, .94);
  color: #2a3140;
  cursor: pointer;
  box-shadow: 0 1px 3px rgba(16, 24, 40, .12);
  touch-action: manipulation;
  white-space: nowrap;
}
.map-switch-btn:hover { background: #fff; border-color: #b9c4d4; }
.map-switch-btn.active {
  background: #1a73e8;
  border-color: #1a73e8;
  color: #fff;
}

/* ===== 地图：地点搜索（搜索框 + 结果面板） ===== */
.map-search {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 1200;
  width: min(340px, calc(100% - 20px));
}
/* 搜索框默认不显示：点一下地图（或进「地图拾取坐标」拾取模式）才出现，
   点地图以外的界面区域收起（见 js/search.js 的 show/hide）。
   窄屏媒体查询里给 .map-search 设了 display:flex，所以这里必须用两个类提高优先级，
   否则手机上 .hidden 会被媒体查询盖掉、搜索框照样显示 */
.map-search.hidden { display: none; }
.map-search-box {
  display: flex;
  align-items: stretch;
  gap: 4px;
  padding: 4px;
  background: rgba(255, 255, 255, .96);
  border: 1px solid #d5dbe4;
  border-radius: 8px;
  box-shadow: 0 1px 4px rgba(16, 24, 40, .16);
}
.map-search-input {
  flex: 1;
  min-width: 0;
  padding: 6px 8px;
  font-size: 13px;
  color: #2a3140;
  border: 0;
  border-radius: 5px;
  background: transparent;
  outline: none;
}
.map-search-input::placeholder { color: #9aa4b2; }
.map-search-btn {
  padding: 6px 12px;
  font-size: 12px;
  color: #fff;
  background: #1a73e8;
  border: 1px solid #1a73e8;
  border-radius: 6px;
  cursor: pointer;
  white-space: nowrap;
  touch-action: manipulation;
}
.map-search-btn:hover { background: #1765cc; }
.map-search-clear {
  padding: 6px 9px;
  font-size: 14px;
  line-height: 1;
  color: #667085;
  background: #f7f9fc;
  border: 1px solid #d5dbe4;
  border-radius: 6px;
  cursor: pointer;
}
.map-search-clear:hover { color: #2a3140; border-color: #b9c4d4; }

.map-search-result {
  margin-top: 6px;
  max-height: min(50vh, 320px);
  overflow-y: auto;
  background: rgba(255, 255, 255, .98);
  border: 1px solid #d5dbe4;
  border-radius: 8px;
  box-shadow: 0 4px 14px rgba(16, 24, 40, .18);
  -webkit-overflow-scrolling: touch;
}
.ms-head {
  padding: 8px 10px 6px;
  font-size: 12px;
  color: #667085;
  border-bottom: 1px solid #eef2f7;
}
.ms-head b { color: #2a3140; }
.ms-empty { padding: 12px 10px; font-size: 12px; color: #667085; }
.ms-list { list-style: none; margin: 0; padding: 0; }
.ms-item {
  padding: 8px 10px;
  border-bottom: 1px solid #f1f4f9;
  cursor: pointer;
}
.ms-item:last-child { border-bottom: 0; }
.ms-item:hover { background: #f2f7ff; }
.ms-item.active { background: #e8f1fe; }
.ms-name { font-size: 13px; color: #1f2430; }
.ms-item.active .ms-name { color: #1a73e8; font-weight: 600; }
.ms-tag {
  margin-left: 6px;
  padding: 1px 5px;
  font-size: 11px;
  color: #667085;
  background: #f0f3f8;
  border-radius: 4px;
}
.ms-addr { margin-top: 2px; font-size: 12px; color: #667085; }
.ms-meta { margin-top: 2px; font-size: 12px; color: #1a73e8; }
.ms-city .ms-name { color: #2a3140; }
.ms-tips {
  padding: 8px 10px;
  font-size: 12px;
  color: #667085;
  border-top: 1px solid #eef2f7;
}
.ms-chip {
  margin: 0 4px 4px 0;
  padding: 2px 8px;
  font-size: 12px;
  color: #1a73e8;
  background: #f2f7ff;
  border: 1px solid #cfe0fb;
  border-radius: 10px;
  cursor: pointer;
}
.ms-chip:hover { background: #e3eefe; }

/* 地图不可用时的提示（未配置密钥 / 官方 API 加载失败） */
.map-notice {
  position: absolute;
  inset: 0;
  z-index: 500;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  text-align: center;
  font-size: 13px;
  line-height: 1.8;
  color: #4a5568;
  background: #eef2f7;
}

/* 中继台信息窗内容 */
.tdt-popup { font-size: 12px; line-height: 1.6; color: #1f2430; }

/* ===== 地图：离线底图（经纬网格 + 本地瓦片，见 js/offlineMap.js）=====
   没有天地图时用它顶上：canvas 画一张经纬网当底图，标记层是普通 DOM（好点击、好写样式）。
   2026-10-17 起优先铺**本地预下载的天地图瓦片**（tiles/{z}/{x}/{y}.png），网格叠加在上面 ——
   瓦片没命中的地方自然露出下面这张浅色底，于是"有瓦片看地形、没瓦片看网格"，两种都不白屏。 */
.om-canvas {
  display: block;
  position: absolute;
  left: 0;
  top: 0;
  z-index: 1;
  /* 瓦片未加载完成 / 缺图时露出的底色。取接近"空白陆地"的浅灰，
     别用纯白 —— 网格线是浅灰画的，纯白底上会显得发虚。 */
  background: #eef2f7;
}
.om-layer {
  position: absolute;
  inset: 0;
  z-index: 400;          /* 在 canvas 之上、控件之下 */
  overflow: hidden;      /* 边缘外的标记要裁掉：否则绝对定位的图钉会把整页撑出横向滚动条 */
  pointer-events: none;  /* 只有具体标记才接收点击，空白处的拖动仍交给 canvas */
}
.om-pin {
  position: absolute;
  width: 14px;
  height: 14px;
  margin: -7px 0 0 -7px;   /* 圆心对准坐标点 */
  border-radius: 50%;
  background: #1a73e8;
  border: 2px solid #fff;
  box-shadow: 0 1px 3px rgba(16,24,40,.35);
  pointer-events: auto;
  cursor: pointer;
}
.om-pin-rep.disabled { background: #98a4b6; }
.om-pin-rep.active {
  width: 20px;
  height: 20px;
  margin: -10px 0 0 -10px;
  background: #f57c00;
  z-index: 5;
}
.om-pin-me {
  background: #2ecc40;
  cursor: default;
}
/* 搜索结果：水滴形图钉，尖端对准坐标点 */
.om-pin-search {
  width: 16px;
  height: 16px;
  margin: -16px 0 0 -8px;
  border-radius: 50% 50% 50% 0;
  transform: rotate(-45deg);
  background: #e53935;
  cursor: default;
}
.om-hud {
  position: absolute;
  right: 10px;
  bottom: 10px;
  z-index: 1200;
  padding: 5px 9px;
  background: rgba(255,255,255,.94);
  border: 1px solid #d5dbe4;
  border-radius: 6px;
  font-size: 11px;
  line-height: 1.6;
  color: #4a5568;
  font-variant-numeric: tabular-nums;
  box-shadow: 0 1px 3px rgba(16,24,40,.12);
  pointer-events: none;
}
.om-hud-strong { color: #2a3140; font-weight: 600; }
.om-popup {
  position: absolute;
  z-index: 1300;
  transform: translate(-50%, calc(-100% - 12px));
  max-width: 230px;
  padding: 8px 22px 8px 10px;
  background: #fff;
  border: 1px solid #d5dbe4;
  border-radius: 8px;
  box-shadow: 0 4px 14px rgba(16,24,40,.18);
  font-size: 12px;
  line-height: 1.6;
  color: #1f2430;
}
.om-popup-close {
  position: absolute;
  top: 2px;
  right: 2px;
  padding: 2px 5px;
  border: 0;
  background: none;
  font-size: 14px;
  line-height: 1;
  color: #8a94a6;
  cursor: pointer;
}
.om-popup-close:hover { color: #2a3140; }

@media (max-width: 760px) {
  .map-switch { top: 8px; left: 44px; gap: 4px; }
  .map-switch-btn { font-size: 11px; padding: 4px 8px; }
  /* 窄屏：搜索框移到地图底部（拇指够得着），结果面板向上展开，
     bottom 留出 24px 避开官方 API 左下角的比例尺控件 */
  .map-search {
    top: auto;
    bottom: 24px;
    left: 8px;
    right: 8px;
    width: auto;
    display: flex;
    flex-direction: column-reverse;   /* 输入框钉在底部，面板往上长 */
  }
  .map-search-result { max-height: 36vh; margin-top: 0; margin-bottom: 6px; }
}