/* 多机构报备入口 —— 样式
   视觉合同：docs/design-brief.md（HeroUI · 浅色）+ 设计稿归档 revision-20260917。
   token 已与 HeroUI 浅色逐字核对（--accent #0485f7 / --radius-card 8px / --radius-input 12px），
   全部颜色只允许出现在本文件顶部这一段。

   ⚠️ **浏览器下限 ≈ Chrome 113 / Safari 17.2 / Firefox 128**：卡在这条线上的是本页的
   `@property` + `linear()` 缓动（morph 按钮的宽度弹簧）与 `color-mix(in oklch)`。
   低于这条线时**失效是静默的**（控制台不报错）：`linear()` 不被支持会让 `.mlb.is-ready`
   那一整条 transition 因自定义属性替换失败而作废（连带按钮底色、消息条淡入一起没了），
   `color-mix()` 不被支持则消息条没有投影、加载遮罩没有白雾、底卡没有遮罩底色。
   只做桌面端、内网办公电脑，按这条线报部署要求即可；真要往下兼容，把 --ease-* 换回 cubic-bezier。 */

/* ─── HeroUI 浅色令牌 ─── */
:root {
  --bg:       #f5f5f5;
  --surface:  #ffffff;
  --fg:       #18181b;
  --muted:    #71717a;
  --border:   #dedee0;
  --hairline: #e4e4e7;
  --accent:   #0485f7;
  --accent-soft: #e8f2fe;
  --success:  #17c964;
  /* 警告色阶（HeroUI yellow 系）。浅底款 --warn-100/200 给重试钮与第二档失败外观，
     深色款 --warn-600/700/800 给它们上面的字 —— 取值照第五版设计稿（revision4-20260918） */
  --warn-100: #fdedd3;
  --warn-200: #fbdba7;
  --warn:     #f5a524;
  --warn-600: #c4841d;
  --warn-700: #936316;
  --warn-800: #62420e;
  --danger:   #ff383c;

  --font-body: Inter, system-ui, -apple-system, 'Segoe UI', 'Helvetica Neue', Arial, 'PingFang SC', 'Microsoft YaHei', sans-serif;
  --font-mono: ui-monospace, 'SF Mono', Menlo, monospace;

  --radius-card: 8px;
  --radius-input: 12px;

  /* 手风琴展开节奏：进场 200ms / 退场 140ms（项目动效约定），缓动同全局 ease-out */
  --acc-expand: 200ms;
  --acc-collapse: 140ms;
  --acc-chevron: 200ms;
  --acc-ease: cubic-bezier(0.23, 1, 0.32, 1);
}
*, *::before, *::after { box-sizing: border-box; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
  /* iOS 横屏不许擅自放大正文字号（S7 移动端适配） */
  -webkit-text-size-adjust: 100%;
}
button { font: inherit; cursor: pointer; }
/* 触控全局：去掉 300ms 点按迟疑与 iOS 灰色点按高光（S7）。
   桌面无副作用 —— 这两条对鼠标路径没有影响 */
button, .osel-opt, .org { touch-action: manipulation; -webkit-tap-highlight-color: transparent; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }

/* 四边内衬与刘海屏安全区取大（S7）。桌面 env() 恒为 0 —— 渲染结果与第三版逐像素相同 */
main {
  max-width: 880px; margin-inline: auto;
  padding: max(24px, env(safe-area-inset-top, 0px)) max(24px, env(safe-area-inset-right, 0px))
           calc(56px + env(safe-area-inset-bottom, 0px)) max(24px, env(safe-area-inset-left, 0px));
}

/* ─── 页头：只有标题 + 一行说明 ───
   没有「机构接口可达性总指示灯」—— 那是 v10 owner 拍板删掉的，理由见 brief §页面结构 1：
   每家机构的状态已经在「02 目标机构」里逐个显示，再做总灯是重复的。 */
.page-head { margin: 0 0 20px; }
.page-head h1 { margin: 0 0 2px; font-size: 20px; font-weight: 600; letter-spacing: 0.01em; }
.page-head p { margin: 0; font-size: 13px; color: var(--muted); }

/* ─── 填报卡：内部靠分区标题 + 留白分块，不用分割线 ─── */
.card {
  position: relative;
  background: var(--surface);
  border-radius: var(--radius-card);
  padding: 24px 28px 20px;
}
.card + .card { margin-top: 20px; }

.section + .section { margin-top: 22px; }
.section-label {
  display: flex; align-items: baseline; gap: 10px;
  margin: 0 0 10px;
}
.section-label .idx {
  font-family: var(--font-mono); font-size: 11px; color: var(--muted);
  letter-spacing: 0.06em;
}
.section-label .name { font-size: 13px; font-weight: 600; letter-spacing: 0.02em; }
.section-label .note { font-size: 12px; font-weight: 400; color: var(--muted); }

.field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 14px; }
.grid2 .full { grid-column: 1 / -1; }
/* 四列栅格：只用于「01 客户信息」（姓名 / 性别 / 电话 / 微信 一行放下，备注跨列）。
   ≤720px 降两列 —— 见文末媒体查询 */
.grid4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px 14px; }
.grid4 .full { grid-column: 1 / -1; }
.field label { font-size: 12px; color: var(--muted); letter-spacing: 0.02em; }
.field label .req { color: var(--danger); margin-left: 2px; }
.input {
  width: 100%; padding: 9px 14px;
  border: 0; border-radius: var(--radius-input);
  background: var(--bg); color: var(--fg);
  font: inherit; font-size: 14px; line-height: 1.5;
  transition: box-shadow .15s ease, background .15s ease;
}
.input::placeholder { color: var(--muted); }
.input:hover { background: color-mix(in oklch, var(--bg) 82%, var(--border)); }
.input:focus { outline: none; box-shadow: inset 0 0 0 1.5px var(--accent); background: var(--surface); }
/* 必填缺项：可见错误态（总纲 §4.3 —— 空值校验 + 字段下给话） */
.input.err { box-shadow: inset 0 0 0 1.5px var(--danger); background: var(--surface); }
.input.err:focus { box-shadow: inset 0 0 0 1.5px var(--danger); }
.field .sub-hint { font-size: 11.5px; color: var(--muted); min-height: 17px; }
.field .sub-hint.err { color: var(--danger); }
.field .sub-hint.warn { color: var(--fg); }

/* ─── 自定义选项下拉：一体式手风琴 ─────────────────────────────
   收起时是一只普通输入框；点开时触发行与面板拼成一整张卡：
   触发行变卡顶（表面色 + 上/左/右 accent 内描边），面板绝对定位贴在卡底
   （左/右/底 accent 内描边 + 轻投影），0fr↔1fr 长高但不在文档流里，
   下方内容原位不动。选项三态：可选 / 选中 / 置灰带原因
   开合状态的载体是 data-open 属性（面板常驻 DOM），不是「元素在不在」 */
.osel { position: relative; }
.osel[data-open="true"] { z-index: 40; }
.osel-trigger {
  width: 100%; padding: 9px 14px;
  border: 0; border-radius: var(--radius-input);
  background: var(--bg); color: var(--fg);
  font: inherit; font-size: 14px; line-height: 1.5; text-align: left;
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  box-shadow: inset 1.5px 0 0 0 transparent, inset -1.5px 0 0 0 transparent, inset 0 1.5px 0 0 transparent;
  transition: background .15s ease, box-shadow var(--acc-collapse) var(--acc-ease);
}
.osel[data-open="false"]:not(:has(.osel-trigger:disabled)):hover .osel-trigger {
  background: color-mix(in oklch, var(--bg) 82%, var(--border));
}
.osel[data-open="true"] .osel-trigger {
  background: var(--surface);
  border-radius: var(--radius-input) var(--radius-input) 0 0;
  box-shadow: inset 1.5px 0 0 0 var(--accent), inset -1.5px 0 0 0 var(--accent), inset 0 1.5px 0 0 var(--accent);
  transition: background .15s ease, box-shadow var(--acc-expand) var(--acc-ease);
}
.osel-trigger:disabled { opacity: 0.55; cursor: not-allowed; }
.osel-value { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.osel-value.placeholder { color: var(--muted); }
.osel-chev {
  flex: none; color: var(--muted);
  transform: scaleY(1); transform-origin: center;
  transition: transform var(--acc-chevron) var(--acc-ease);
}
.osel-chev path { vector-effect: non-scaling-stroke; }
.osel[data-open="true"] .osel-chev { transform: scaleY(-1); }

/* 面板绝对定位悬于触发行正下方（零缝隙），不在文档流里，展开不推下方内容；
   grid-template-rows 0fr↔1fr 让面板自身长高，无需 JS 测高；
   左/右/底三边 accent 内描边与触发行的三边拼成完整焦点环；
   关闭时 visibility 延迟到收起完毕再隐藏，0 高度时不留描边残影 */
.osel-panel {
  position: absolute; left: 0; right: 0; top: 100%;
  display: grid; grid-template-rows: 0fr;
  background: var(--surface);
  border-radius: 0 0 var(--radius-input) var(--radius-input);
  box-shadow: inset 1.5px 0 0 0 var(--accent), inset -1.5px 0 0 0 var(--accent),
              inset 0 -1.5px 0 0 var(--accent),
              0 10px 28px color-mix(in oklch, var(--fg) 10%, transparent);
  visibility: hidden;
  transition: grid-template-rows var(--acc-collapse) var(--acc-ease),
              visibility 0s linear var(--acc-collapse);
}
.osel[data-open="true"] .osel-panel {
  grid-template-rows: 1fr;
  visibility: visible;
  transition: grid-template-rows var(--acc-expand) var(--acc-ease);
}
.osel-panel-inner {
  overflow: hidden; min-height: 0;
  opacity: 0; filter: blur(2px);
  transition: opacity var(--acc-collapse) var(--acc-ease), filter var(--acc-collapse) var(--acc-ease);
}
/* 触发行与选项区之间的发丝分区线，位于收起容器内，随面板一同折叠 */
.osel-panel-inner::before {
  content: ''; display: block; height: 1px;
  margin: 0 14px 3px; background: var(--hairline);
}
.osel[data-open="true"] .osel-panel-inner {
  opacity: 1; filter: blur(0);
  transition: opacity var(--acc-expand) var(--acc-ease), filter var(--acc-expand) var(--acc-ease);
}
.osel-panel-list { padding: 3px 6px 6px; max-height: 280px; overflow-y: auto; }

.osel-group {
  font-size: 11px; color: var(--muted); letter-spacing: 0.04em;
  padding: 8px 12px 4px;
}
/* 「交集为空」的第 ② 态（一个候选都换不了）的出口文案 —— 总纲 §6.2 两态表：
   只做「灰项写明原因」不够，操作员会卡在一屏全灰上不知道下一步干什么 */
.osel-exit {
  margin: 4px 6px 6px; padding: 9px 12px;
  font-size: 12px; line-height: 1.6;
  background: color-mix(in oklch, var(--warn) 13%, var(--surface));
  border-radius: 8px;
}
.osel-opt {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 8px 12px; border-radius: 8px; cursor: pointer;
  transition: background .12s ease;
}
.osel-opt .opt-check {
  width: 15px; height: 15px; flex: none; margin-top: 3px;
  color: var(--accent); visibility: hidden;
}
.osel-opt.sel .opt-check { visibility: visible; }
.osel-opt .opt-body { min-width: 0; flex: 1; }
.osel-opt .opt-name { font-size: 14px; line-height: 1.5; }
.osel-opt:not(.off):hover { background: var(--bg); }
.osel-opt.sel { background: var(--accent-soft); }
.osel-opt.sel:hover { background: color-mix(in oklch, var(--accent-soft) 88%, var(--accent)); }
.osel-opt.off { cursor: default; }
.osel-opt.off .opt-name { color: var(--muted); }
.osel-opt .opt-reason {
  font-size: 11.5px; line-height: 1.5; color: var(--muted);
  margin-top: 1px;
}

/* ─── 机构勾选：素 chip，异常才出声 ─── */
.orgs { display: flex; flex-wrap: wrap; gap: 10px; }
.org {
  display: inline-flex; align-items: center; gap: 9px;
  padding: 8px 13px; border-radius: var(--radius-card);
  background: var(--bg); border: 1px solid transparent;
  font-size: 14px; font-weight: 500;
  transition: background .15s ease, border-color .15s ease;
}
.org:hover { background: color-mix(in oklch, var(--bg) 80%, var(--border)); }
.org.on { background: var(--accent-soft); }
.org.on:hover { background: color-mix(in oklch, var(--accent-soft) 88%, var(--accent)); }
/* 禁选态：由 gate.selectable === false 驱动（三个非 OK 值走同一条路径），
   位置保留不塌陷，说明文字逐字渲染 gate.note */
.org.off, .org.off:hover {
  opacity: 0.5; cursor: not-allowed;
  background: var(--bg);
}
.org .check {
  width: 15px; height: 15px; border-radius: 5px; flex: none;
  border: 1.5px solid var(--border); background: var(--surface);
  display: grid; place-items: center; color: var(--surface);
  transition: background .15s ease, border-color .15s ease;
}
.org.on .check { background: var(--accent); border-color: var(--accent); }
.org .org-note { font-size: 11.5px; font-weight: 400; color: var(--muted); }
.org.on .org-note { color: color-mix(in oklch, var(--fg) 70%, var(--accent)); }

/* ─── 提交行：一屏装得下时是普通页脚，装不下时才是吸底操作条 ──────
   默认（未挂 can-stick）：表单页脚，背景透明，负边距抵消内衬，
   与输入栅格左右齐平，完全不参与吸底。
   JS 测得整页超出一屏才挂 can-stick：sticky 吸在视口底部；
   再由哨兵判定是否正吸住，吸住才落成毛玻璃胶囊（下方内容隐约透出）。
   static 与 sticky 在文档流里占位相同，两种切换都只切外观、不动布局 */
.submit-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px 16px; flex-wrap: wrap;
  margin: 20px -10px 0 -18px;
  padding: 8px 10px 8px 18px;
  border-radius: 16px;
  transition: background .18s ease, box-shadow .18s ease;
  /* 吸住线（下面的 .can-stick 用），同时也是 JS 哨兵 rootMargin 的唯一真相源 ——
     两处必须同值，否则毛玻璃会早亮 / 晚亮一段。改这里就等于改哨兵（S7 审核修订 #4） */
  --stick-bottom: 14px;
}
.submit-row.can-stick {
  /* 吸底线计入 home 指示条安全区（S7）；桌面 env() 恒 0 = 原来的 14px */
  position: sticky; bottom: calc(var(--stick-bottom) + env(safe-area-inset-bottom, 0px)); z-index: 30;
}
.submit-row.can-stick.is-stuck {
  background: color-mix(in oklch, var(--surface) 68%, transparent);
  backdrop-filter: blur(16px) saturate(1.15);
  -webkit-backdrop-filter: blur(16px) saturate(1.15);
  box-shadow: 0 8px 28px color-mix(in oklch, var(--fg) 12%, transparent),
              inset 0 0 0 1px color-mix(in oklch, var(--surface) 60%, transparent);
}
.will { font-size: 13px; color: var(--muted); }
.will strong { color: var(--fg); font-family: var(--font-mono); font-weight: 600; }
/* 通用 .btn / .btn-ghost / .btn-warn 已随重试确认框一并删除（§4.4）——
   它们当时只服务那张确认卡，撤掉后是死代码 */

/* ─── 提交按钮：MorphLoadingButton（素材 docs/design-effects/morph-loading-button，MIT/ISC）───
   保留原素材的动效机制：宽度按物理弹簧形变（430/34/0.72，近临界阻尼）、
   内容上下交叉换场、悬停微胀、图标同槽位弹换、结果面停留（悬停冻结倒计时）。
   外观按 HeroUI 浅色自定：accent 实心胶囊全程不换底色，状态由图标 / 文案 / 宽度承担。
   加载态按红线置灰（opacity .5 + not-allowed），一眼看出现在点不动 */
@property --mlb-w    { syntax: "<length>"; inherits: false; initial-value: 132px; }
@property --mlb-grow { syntax: "<length>"; inherits: false; initial-value: 0px; }

.mlb {
  /* ── 旋钮 ── */
  --h: 40px;          /* 高度，固定不随文案变 */
  --pad: 24px;        /* 左右内边距 */
  --icon: 16px;       /* 图标槽位边长 */
  --gap: 8px;         /* 图标与文字间距 */
  --min-w: 132px;     /* 最窄宽度，短文案时顶住 */
  --grow: 8px;        /* 悬停外胀量（左右内边距各分一半） */
  --fs: 14px;
  --radius: 999px;    /* 胶囊：morph 的签名形态，全页唯一一处全圆角 */

  /* 三条弹簧缓动（从 framer-motion 物理参数解析解采样，参数不可混用）：
     宽度形变 430/34/0.72（ζ=0.97，几乎不过冲，280ms 内到位）；
     悬停缩放/胀开 600/28/1（ζ=0.57，冲高 111% 再落回，是「弹」的手感）；
     图标切换 600/25/1（更弹，ζ=0.51，冲到 115%） */
  --ease-morph: linear(0, .0182, .064, .1269, .1992, .2753, .3514, .425, .4942, .5582, .6165, .6689, .7156, .7568, .7929, .8244, .8516, .875, .8951, .9122, .9268, .9391, .9495, .9582, .9656, .9717, .9768, .981, .9845, .9874, .9898, .9917, .9933, .9946, .9957, .9966, .9973, .9978, .9983, .9987, .999, .9992, .9994, .9995, .9996, .9997, .9998, .9999, .9999, .9999, .9999, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
  --ease-scale: linear(0, .0273, .0985, .1993, .3172, .4418, .5651, .6809, .785, .8749, .9494, 1.0083, 1.0524, 1.083, 1.1018, 1.1107, 1.1117, 1.1066, 1.0972, 1.085, 1.0714, 1.0574, 1.0438, 1.0313, 1.0202, 1.0107, 1.003, .9971, .9927, .9898, .9881, .9875, .9876, .9884, .9896, .991, .9926, .9941, .9956, .997, .9982);
  --ease-icon: linear(0, .0275, .1003, .2045, .3277, .4592, .5901, .7137, .8251, .9211, 1.0001, 1.0615, 1.1062, 1.1353, 1.1508, 1.155, 1.15, 1.1382, 1.1218, 1.1025, 1.0821, 1.0618, 1.0428, 1.0257, 1.0111, .9991, .9898, .9831, .9787, .9765, .976, .9769, .9788, .9814, .9844, .9876, .9907, .9936, .9962, .9985, 1.0003);
  --content: 200ms cubic-bezier(.22, 1, .36, 1);   /* 内容交叉淡入 */

  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  flex: none;
  height: var(--h);
  min-width: var(--min-w);
  --mlb-w: var(--min-w);
  --mlb-grow: 0px;
  /* 总宽 = JS 量出的基准宽 + 悬停胀开量；内边距同步各分一半，内容是「被往外推开」 */
  width: calc(var(--mlb-w) + var(--mlb-grow));
  padding: 0 calc(var(--pad) + var(--mlb-grow) / 2);
  border: 0; border-radius: var(--radius);
  background: var(--accent); color: var(--surface);
  box-shadow: inset 0 1px 0 color-mix(in oklch, var(--surface) 22%, transparent);
  overflow: hidden;               /* 内容换场时不许溢出胶囊 */
  font-size: var(--fs); font-weight: 500; letter-spacing: 0.02em;
  line-height: 1.5; white-space: nowrap;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transform: scale(var(--scale, 1));
  --scale: 1;
}
/* 悬停：放大 2% + 外胀 8px + 底色加深；加载中/置灰时全部停掉 */
.mlb:hover  { --scale: 1.02; --mlb-grow: var(--grow); background: color-mix(in oklch, var(--accent) 88%, black); }
.mlb:active { --scale: .96; }
.mlb.is-loading, .mlb.is-off { opacity: .5; cursor: not-allowed; }
.mlb.is-loading, .mlb.is-loading:hover, .mlb.is-loading:active,
.mlb.is-off, .mlb.is-off:hover, .mlb.is-off:active {
  --scale: 1; --mlb-grow: 0px; background: var(--accent);
}
/* 全局 :focus-visible 会把圆角压成 4px，这里保住胶囊轮廓 */
.mlb:focus-visible { border-radius: var(--radius); outline-offset: 3px; }

/* 过渡只在「准备好」之后开，免得首帧从默认样式渐入一次；
   补的是两个注册过的自定义属性（@property），不是 width —— width 上挂着两条不同的弹簧 */
.mlb.is-ready {
  transition:
    --mlb-w var(--ease-morph) 500ms,
    --mlb-grow 400ms var(--ease-scale),
    transform 400ms var(--ease-scale),
    background-color 150ms linear,
    opacity 150ms ease;
}
/* 量宽度时临时用：关掉缩放和过渡，量到的才是没被放大过的真宽度 */
.mlb.is-measuring { transform: none !important; transition: none !important; }

/* 屏幕阅读器区：可见的那份 aria-hidden，否则读两遍 */
.mlb-sr {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

/* 内容栈：多个「面」叠在按钮里居中，谁都不影响按钮宽度（宽度归 JS 量） */
.mlb-stack { position: absolute; inset: 0; }
.mlb-face {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  opacity: 0; transform: translateY(7px); filter: blur(2px);   /* 默认 = 还没进场 */
  pointer-events: none;
}
.mlb-face.is-active { opacity: 1; transform: none; filter: blur(0); }
.mlb-face.is-leaving { opacity: 0; transform: translateY(-7px); filter: blur(2px); }  /* 退场往上飘，与进场同时进行 */
.mlb.is-ready .mlb-face {
  transition: opacity var(--content), transform var(--content), filter var(--content);
}
.mlb-inner { display: flex; align-items: center; gap: var(--gap); }
.mlb-label { display: block; }

/* 图标：纸飞机 / 对勾共用 16×16 槽位，靠缩放+透明度弹着换，槽位尺寸全程不变 */
.mlb-slot { position: relative; flex: none; width: var(--icon); height: var(--icon); }
.mlb-ico { display: block; width: var(--icon); height: var(--icon); }
.mlb-slot .mlb-ico { position: absolute; inset: 0; }
.mlb-ico--send  { opacity: 1; }
.mlb-ico--check { opacity: 0; transform: scale(.5); }
.mlb.is-hover .mlb-ico--send  { opacity: 0; transform: scale(.5); }
.mlb.is-hover .mlb-ico--check { opacity: 1; transform: none; }
.mlb.is-ready .mlb-slot .mlb-ico {
  transition: opacity 400ms var(--ease-icon), transform 400ms var(--ease-icon);
}
/* 转圈：转外层容器，SVG 尺寸不变 */
.mlb-spin { flex: none; width: var(--icon); height: var(--icon); animation: mlb-spin 800ms linear infinite; }
@keyframes mlb-spin { to { transform: rotate(360deg); } }

/* ─── 报备消息：顶部居中的标准 message 组件 ─────────────────────
   **一套组件 = 3 个外观 × 3 个开关**（spec-message-refactor.md §4.1 / §4.6）：
   外观 ok / err / wait 定图标与配色；开关（重试 / 关闭 / 自动消失）由 app.js 按场景给。
   条即全部：无卡片外框、无描边、无汇总面板。 */
.msgs {
  /* 上沿让开状态栏与灵动岛（S7）；桌面 env() 恒 0 = 原来的 20px */
  position: fixed; top: calc(20px + env(safe-area-inset-top, 0px)); left: 50%; transform: translateX(-50%);
  z-index: 55; pointer-events: none;
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  width: max-content; max-width: calc(100vw - 48px);
}
.msg {
  pointer-events: auto;
  display: flex; align-items: center; gap: 10px;
  max-width: min(560px, calc(100vw - 48px));
  padding: 10px 16px;
  background: var(--surface); border-radius: var(--radius-input);
  box-shadow: 0 8px 24px color-mix(in oklch, var(--fg) 14%, transparent);
  font-size: 13.5px; line-height: 1.6;
  opacity: 0; transform: translateY(-10px);
  transition: opacity .18s ease, transform .24s cubic-bezier(0.23, 1, 0.32, 1);
}
.msg.in { opacity: 1; transform: translateY(0); }
.msg.out { opacity: 0; transform: translateY(-10px); }
.msg-ic { width: 16px; height: 16px; flex: none; display: grid; place-items: center; }
.msg-ok .msg-ic { color: var(--success); }
.msg-err .msg-ic { color: var(--danger); }
/* 失败外观的**第二档**：warn（琥珀）。它不是出错，是「系统替操作员撤了一个选择」
   （「已自动取消渠道 / 报备人」那一类）。**外观仍是三种** —— 颜色是严重度维度，不是类目维度 */
.msg-err.tone-warn { background: var(--warn-100); }
.msg-err.tone-warn .msg-ic { color: var(--warn-700); }
.msg-err.tone-warn .m-close:hover { background: var(--warn-200); color: var(--warn-800); }
/* ⚠️ warn 档里的「上游原话」那一行不能沿用 .m-reason 的 --muted：`#71717a` 压在
   `--warn-100` 上只有 **4.20:1**，低于 WCAG AA 对常规字号的 4.5:1（同样的灰在白底红条上是
   4.83:1，合格）。取 `--warn-700`（**4.52:1**）—— 正是本文件开头那条「深色款给浅底上面的字」，
   与该档图标的颜色同一枚 token。
   2026-09-18 对抗式审核抓出（新增「已报备过」条才出现的组合）；只作用于本档，不动
   「已自动取消」那条（它没有 .m-reason）。 */
.msg-err.tone-warn .m-reason { color: var(--warn-700); }
.msg-text { min-width: 0; }
.msg-text .m-reason { font-size: 12.5px; line-height: 1.55; color: var(--muted); word-break: break-word; }
/* 条内动作：触控区保 44px，负边距收起占位，不撑高消息条 */
.msg .retry-btn {
  flex: none;
  min-height: 44px; margin: -6px 0; padding: 0 14px;
  display: inline-flex; align-items: center;
  font-size: 12.5px; color: var(--warn-700);
  background: var(--warn-100);
  border: 0; border-radius: 10px;
  transition: background .15s ease, color .15s ease;
}
.msg .retry-btn:hover { background: var(--warn-200); color: var(--warn-800); }
.msg .m-close {
  flex: none; width: 44px; height: 44px; margin: -10px -12px -10px -2px;
  display: grid; place-items: center;
  border: 0; border-radius: 10px;
  background: transparent; color: var(--muted);
  transition: background .15s ease, color .15s ease;
}
.msg .m-close:hover { background: var(--bg); color: var(--fg); }
.msg .m-close svg { display: block; }

/* 等待条：**转圈**，与成功 / 失败条一样是单行小条。
   ⚠️ 吃豆人归表单加载遮罩（.load-mask），**消息条一律不用它** —— 它是横向 112×50 的大构图，
   塞进消息条会把三种外观里的一条撑成另一样子（owner 2026-09-18 钦定；
   spec-message-refactor.md §4.7 原写「本次保留吃豆人」，按设计稿本体与验收 11 的基线改为转圈）。
   转圈是**纯装饰**，不表达进度 —— 提交是同步的，前端拿不到中间态，别在它旁边写「已完成 N / M」 */
.msg-wait .spinner {
  width: 14px; height: 14px; border-radius: 50%;
  border: 2px solid var(--border); border-top-color: var(--muted);
  animation: spin .8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }


/* ─── 渠道与报备人「刷新」：贴在分区标题右端的低权重维护按钮 ───
   它是两条行级「取数失败，请重试」文案 + 机构层两条可重试 note 所指动作的**唯一落点** */
.refresh-btn {
  flex: none;
  min-height: 44px; margin: -6px 0 -6px auto; padding: 0 12px;
  display: inline-flex; align-items: center; gap: 6px;
  border: 0; border-radius: 10px;
  background: transparent; color: var(--muted);
  font-size: 12.5px; letter-spacing: 0.02em;
  transition: background .15s ease, color .15s ease;
}
.refresh-btn:hover:not(:disabled) { background: var(--bg); color: var(--fg); }
.refresh-btn:disabled { opacity: 0.5; cursor: not-allowed; }

/* ─── 表单加载遮罩：吃豆人 ────────────────────────────────────
   只用于「首屏首次加载」与「点刷新后的重新取数」（总纲 §7.9）—— **不覆盖提交过程**，
   否则与 §8.11「等待期间页面可正常使用、不卡死」互斥。绝对定位贴在卡里，圆角随卡，
   数据落定前表单不可操作，页面其余区域照常可滚动。半透明白 + 毛玻璃，下面的字段隐约透出；
   吃豆人几何与时长照搬 loaders.css 原版 .pacman */
.load-mask {
  position: absolute; inset: 0; z-index: 50;
  display: grid; place-items: center;
  border-radius: var(--radius-card);
  background: color-mix(in oklch, var(--surface) 58%, transparent);
  backdrop-filter: blur(18px) saturate(1.12);
  -webkit-backdrop-filter: blur(18px) saturate(1.12);
  opacity: 1; visibility: visible;
  transition: opacity .3s ease;
}
.load-mask.hide {
  opacity: 0; visibility: hidden;
  transition: opacity .3s ease, visibility 0s linear .3s;
  /* visibility 要 300ms 后才翻，这期间 opacity:0 的遮罩照样吃指针事件 ——
     不关掉的话，加载刚完的 300ms 内卡片点哪儿都没反应（键盘此时已经解锁了，两边对不上） */
  pointer-events: none;
}
.load-box { display: flex; flex-direction: column; align-items: center; gap: 10px; }
.pacman-stage { width: 200px; height: 100px; display: grid; place-items: center; }
/* 动效颜色由 --color 一处控制。**不用主色蓝** —— 蓝是「可点」的信号，
   而它出现的位置都是等待中、点不动的场景（brief §提交中动效）。
   取值 **#71717a（次级灰）**：交底给了两个可选值（`#191919` 深色 / `#71717a` 次级灰），
   owner 2026-09-17 看过实际渲染后选了次级灰 —— 深色在白色卡片上分量偏重。 */
.pacman { --color: #71717a; position: relative; transform: scale(1.6); }
.pacman > div:nth-child(1),
.pacman > div:nth-child(2) {
  width: 0; height: 0;
  border: 25px solid var(--color);
  border-right-color: transparent;
  border-radius: 25px;
  position: relative; left: -30px;
}
.pacman > div:nth-child(1) { animation: rotate_pacman_half_up .5s 0s infinite; }
.pacman > div:nth-child(2) { animation: rotate_pacman_half_down .5s 0s infinite; margin-top: -50px; }
.pacman > div:nth-child(n + 3) {
  width: 10px; height: 10px; margin: 2px;
  border-radius: 100%;
  background-color: var(--color);
  position: absolute; top: 25px; left: 70px;
  transform: translate(0, -6.25px);
  animation: pacman-balls 1s infinite linear;
}
.pacman > div:nth-child(3) { animation-delay: -0.66s; }
.pacman > div:nth-child(4) { animation-delay: -0.33s; }
.pacman > div:nth-child(5) { animation-delay: 0s; }
@keyframes rotate_pacman_half_up {
  0%, 100% { transform: rotate(270deg); }
  50% { transform: rotate(360deg); }
}
@keyframes rotate_pacman_half_down {
  0%, 100% { transform: rotate(90deg); }
  50% { transform: rotate(0deg); }
}
@keyframes pacman-balls {
  75% { opacity: 0.7; }
  100% { transform: translate(-100px, -6.25px); }
}
.load-text { margin: 0; font-size: 13px; color: var(--muted); letter-spacing: 0.04em; }

/* ═══ 移动端（S7）══════════════════════════════════════════════════════
   设计稿：`.open-design-handoff/revision3-20260918/`（第四版）。
   分界线 **720px 与设计稿一致**；`≥721px` 的规则**一行不动** ——
   桌面形态与第三版设计稿逐像素相同（全部安全区 env() 在桌面恒为 0）。

   ⚠️ 设计稿是原型：**它没有页头**（`<main>` 里第一个元素就是 `.card`），
   而我们**有** `<header class="page-head">`。所以安全区那件事在两边落在不同元素上：
   设计稿由 `.card` 的 `padding-top` 吃，我们**改由页头吃**（`.card` 退回普通 20px 内衬），
   否则页头会顶到状态栏 / 灵动岛 —— 这一态设计稿根本没画。 */

/* ─── 移动端底部动作面板（bottom sheet）─────────────────────────────
   手机尺寸时性别 / 渠道 / 报备人三个下拉共用这一张底卡：自底部弹起、列表可滚动、
   抓手下滑或点遮罩关闭；贴底铺满，底部内衬吃掉 home 指示条安全区。
   层叠在消息条（55）之上 —— 两者不会同时出现，但底卡出现时要在最上层。 */
.sheet-mask {
  position: fixed; inset: 0; z-index: 70;
  display: flex; flex-direction: column; justify-content: flex-end;
  background: color-mix(in oklch, var(--fg) 32%, transparent);
  opacity: 0; visibility: hidden;
  transition: opacity .2s ease, visibility 0s linear .32s;
  /* ⚠️ **关闭后的 320ms 里 `visibility` 还是 `visible`**（上面那条延迟就是为淡出留的），
     这期间 opacity:0 的遮罩照样吃指针事件 —— 表现是「选完渠道立刻点下一个控件，这一下没了」。
     和 `.load-mask.hide` 是同一类坑（那边早修过，注释也写明了）。
     默认不给事件，`.show` 时才给 —— 遮罩自己要靠点击关闭，不能一路 `none` */
  pointer-events: none;
}
.sheet-mask.show {
  opacity: 1; visibility: visible; pointer-events: auto;
  transition: opacity .24s ease;
}
.sheet {
  background: var(--surface);
  border-radius: 16px 16px 0 0;
  box-shadow: 0 -12px 40px color-mix(in oklch, var(--fg) 16%, transparent);
  max-height: 76vh; max-height: 76dvh;
  display: flex; flex-direction: column;
  padding-bottom: env(safe-area-inset-bottom, 0px);
  transform: translateY(105%);
  transition: transform .32s cubic-bezier(0.23, 1, 0.32, 1);
  outline: none;
}
.sheet-mask.show .sheet { transform: translateY(0); }
/* 抓手区：整片可按住下滑，touch-action none 接管手势，不与列表滚动争 */
.sheet-grip { flex: none; padding: 8px 0 10px; touch-action: none; cursor: grab; }
.sheet-handle {
  display: block; width: 36px; height: 5px; margin: 0 auto;
  border-radius: 3px; background: var(--border);
}
.sheet-title {
  display: block; margin-top: 8px; text-align: center;
  font-size: 12.5px; color: var(--muted); letter-spacing: 0.04em;
}
/* 列表与页面滚动解耦：惯性滚动 + overscroll 收敛，不连锁拉动背景 */
.sheet-list {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  -webkit-overflow-scrolling: touch; overscroll-behavior: contain;
  padding: 4px 10px 14px;
}
/* 底卡里的选项行加大到 52px 触控高度，字号随 iOS 习惯抬到 16px */
.sheet-list .osel-opt { align-items: center; min-height: 52px; padding: 10px 12px; border-radius: 12px; }
.sheet-list .osel-opt .opt-check { margin-top: 0; }
.sheet-list .osel-opt .opt-name { font-size: 16px; }
.sheet-list .osel-opt .opt-reason { font-size: 12.5px; }
.sheet-list .osel-opt:not(.off):active { background: var(--bg); }
.sheet-list .osel-group { padding: 10px 14px 4px; }
/* 「交集为空第②态」的出口文案在底卡里同样要有（§8.12 前端半截）——
   设计稿抽公共生成函数时把这一块漏了，是本份必须自己补回来的一处 */
.sheet-list .osel-exit { margin: 4px 2px 8px; padding: 11px 12px; font-size: 13px; }

/* ─── 手机布局（iPhone 14 390px / 14 Pro 393px 及更窄）─────────────
   桌面靠白卡把表单从灰底里托出来；手机屏寸土寸金，撤掉卡框，
   控件反转为白瓷片（surface 落 bg），与桌面「灰输入框落白卡」互为镜像，
   依旧无描边、靠表面差分层。所有栅格单列到底；输入字号 16px 防 iOS
   聚焦自动放大；触控高度 ≥44px；消息条计入安全区 */
@media (max-width: 720px) {
  /* 满幅模型：main 退成零内衬画框——遮罩、瓷片、玻璃底板一律贴死屏幕边缘，
     没有「外边距」；内容与屏边的安全距离全部下放给卡片内边距 */
  main {
    padding: 0 0 calc(40px + env(safe-area-inset-bottom, 0px)) 0;
  }

  /* 页头：**设计稿没有这一块**。满幅后 main 上内衬为 0，安全区必须有人吃 ——
     由页头吃（左右内衬与卡片内容对齐），否则标题会顶到状态栏 / 灵动岛。
     上下留白交给卡片自己的 padding-top，所以这里 margin 归零 */
  .page-head {
    margin: 0;
    padding: max(20px, env(safe-area-inset-top, 0px)) max(20px, env(safe-area-inset-right, 0px))
             0 max(20px, env(safe-area-inset-left, 0px));
  }

  /* 去卡框：表单直接落在页面灰底上，分区只靠编号标题 + 留白。卡片左右顶到屏幕
     （吃豆人遮罩随卡满幅），内容内衬由卡片自己给出 —— 上内衬回到普通 20px，
     安全区已经由页头吃掉了 */
  .card {
    background: transparent; border-radius: 0;
    padding: 20px max(20px, env(safe-area-inset-right, 0px))
             0 max(20px, env(safe-area-inset-left, 0px));
  }
  .section + .section { margin-top: 28px; }
  /* 遮罩随卡满幅；单列后卡片远超一屏，吃豆人改为粘在视口垂直中心，
     滚到哪里都看得见，不再埋在整张卡的几何中心 */
  .load-mask { border-radius: 0; display: block; }
  .load-box {
    position: sticky; top: 50vh; transform: translateY(-50%);
    width: max-content; margin-inline: auto;
  }

  /* 单列到底：390px 下一行两列每格只剩 ~160px，点不准也看不全 */
  .grid2, .grid4 { grid-template-columns: 1fr; gap: 14px; }

  /* 控件反白：白瓷片落灰底；hover 从白往灰走（手机上主要服务触控板/鼠标场景） */
  .input, .osel-trigger {
    background: var(--surface);
    font-size: 16px; padding: 11px 14px; min-height: 46px;
  }
  .input:hover,
  .osel[data-open="false"]:not(:has(.osel-trigger:disabled)):hover .osel-trigger {
    background: color-mix(in oklch, var(--surface) 80%, var(--border));
  }
  .org { min-height: 46px; padding: 10px 14px; background: var(--surface); }
  .org:hover { background: color-mix(in oklch, var(--surface) 80%, var(--border)); }
  .org.on { background: var(--accent-soft); }
  .org.on:hover { background: color-mix(in oklch, var(--accent-soft) 88%, var(--accent)); }
  .refresh-btn { min-height: 46px; }
  .refresh-btn:hover:not(:disabled) { background: var(--surface); }

  /* 分区标题行：说明小字独占一行，刷新按钮留在首行右端不挤标题 */
  .section-label { flex-wrap: wrap; }
  .section-label .note { flex-basis: 100%; margin-top: 1px; }

  /* 提交区：玻璃底板贴死屏幕左右与下沿（负边距顶破卡片的内容内衬），吸住后
     落成全宽毛玻璃带，下方滑过的白瓷片依旧隐约透出；按钮本身保持桌面那枚
     胶囊原样——999px 全圆角、JS 量宽的宽度弹簧、悬停胀缩与图标换场全部不动，
     只是居中坐进玻璃带、加高到 48px 触控。玻璃罩进 home 指示条区域 */
  .submit-row {
    /* 手机上的吸住线是**视口下沿**（玻璃带贴死屏幕、自身内衬吃掉 home 条），
       所以哨兵也要跟着归零 —— 不然毛玻璃会比真吸住早亮 14px（S7 审核修订 #4） */
    --stick-bottom: 0px;
    flex-direction: column; align-items: center; gap: 10px;
    margin: 22px calc(-1 * max(20px, env(safe-area-inset-right, 0px))) 0
            calc(-1 * max(20px, env(safe-area-inset-left, 0px)));
    padding: 12px max(20px, env(safe-area-inset-right, 0px))
             calc(12px + env(safe-area-inset-bottom, 0px))
             max(20px, env(safe-area-inset-left, 0px));
    border-radius: 0;
  }
  .submit-row.can-stick { bottom: 0; }
  .submit-row.can-stick.is-stuck {
    box-shadow: 0 -8px 24px color-mix(in oklch, var(--fg) 8%, transparent),
                inset 0 1px 0 color-mix(in oklch, var(--surface) 60%, transparent);
  }
  .will { text-align: center; }
  .mlb { --h: 48px; }

  /* 消息条：左右贴边全宽，上沿让开状态栏与灵动岛 */
  .msgs {
    left: 16px; right: 16px; transform: none;
    top: calc(12px + env(safe-area-inset-top, 0px));
    width: auto; max-width: none; align-items: stretch;
  }
  .msg { width: 100%; max-width: none; }

  /* 隐藏滚动条：移动端按原生 App 惯例不露轨——页面与底部面板列表一律
     无轨道无滑块，滚动能力与惯性全部保留（桌面端滚动条不受影响） */
  html { scrollbar-width: none; }
  ::-webkit-scrollbar { width: 0; height: 0; display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .msg { transition: none; }
  /* 等待条的转圈停住（不是消失）—— 停住后仍是个「缺一角的圆」，仍读得出在等 */
  .msg-wait .spinner { animation: none; }
  .osel-panel, .osel-panel-inner, .osel-chev { transition: none !important; }
  /* 底卡（S7）：弹起 / 上滑 / 抓手跟手全部直切 */
  .sheet-mask, .sheet { transition: none !important; }
  .pacman > div { animation: none !important; }
  .pacman > div:nth-child(3) { left: 56px; }
  .pacman > div:nth-child(4) { left: 30px; }
  .pacman > div:nth-child(5) { left: 4px; }
  /* morph 提交按钮：形变/胀开/缩放直切，内容只留淡入淡出，转圈停住 */
  .mlb-spin { animation: none; }
  .mlb.is-ready {
    transition: --mlb-w 0s, --mlb-grow 0s, transform 0s,
                background-color 150ms linear, opacity 150ms ease;
  }
  .mlb.is-ready .mlb-face { transition: opacity 120ms linear; }
  .mlb.is-ready .mlb-slot .mlb-ico { transition: opacity 120ms linear, transform 120ms linear; }
  .mlb-face { filter: none; }
}
