/* =====================================================================
   fm-ui.css —— 四端共用的组件层（前台 / 用户中心 / 商家中心 / 平台后台）。
   口径：
   - 一个类只在这里定义一次。皮肤（front.css / admin.css / user.css）只管自己的外壳，
     需要偏离时写「结构上下文」规则（如 .fm-card > .fm-card-title），不再复制整条组件定义。
   - 加载顺序是 fm-tokens.css → 本文件 → 皮肤 → theme.css，所以皮肤总能覆盖组件；
     本文件排在第三方模板（tabler / bootstrap）之前之外的位置由 layout.php 保证：令牌表最前，
     本文件紧随其后，因此模板的 reboot 规则不会把组件底色顶掉。
   - 数值一律取令牌，不写 hex、不写裸圆角：外观档位（theme.css 的 data-bs-*）才能真的驱动组件。
   - 小字（提示、副行、说明）最低 --ink-500（白底 5.9:1）；语义色块上的文字用 --*-text 变体，
     不用 --brand / --warn / --accent 本体（那几个是填充色，铺在浅底上只有 3.x:1）。
   - 每个组件都要有 hover / focus / disabled / loading / empty / error 态。
   ===================================================================== */

/* ---------- 底座 ---------- */
/* 组件自己声明了 display（.fm-up-add 是 grid、.fm-field 是 flex），作者样式就会顶掉 UA 的
   [hidden]{display:none}，于是「上传满额收掉 + 上传」这类联动只隐身不真的消失。
   商家中心与平台后台此前是靠 tabler / bootstrap 顺带带的同名规则才隐身的，收回共用层自己兜住。 */
[hidden]{display:none!important}

/* ---------- 按钮 ---------- */
.fm-btn{display:inline-flex;align-items:center;justify-content:center;gap:7px;padding:9px 16px;
  border:1px solid var(--line-strong);border-radius:var(--radius-sm);background:var(--card);color:var(--ink-700);
  font-family:inherit;font-size:14px;font-weight:600;line-height:1.5;cursor:pointer;white-space:nowrap;
  transition:background .16s,border-color .16s,color .16s}
.fm-btn:hover{border-color:var(--brand);color:var(--brand-text);background:var(--brand-tint)}
.fm-btn:active{transform:translateY(1px)}
.fm-btn:disabled,.fm-btn[disabled],.fm-btn.is-off{opacity:.55;cursor:not-allowed;pointer-events:none;transform:none}
.fm-btn-primary{background:var(--brand);border-color:var(--brand);color:var(--on-brand)}
.fm-btn-primary:hover{background:var(--brand-700);border-color:var(--brand-700);color:var(--on-brand)}
.fm-btn-danger{background:var(--card);border-color:var(--danger-line);color:var(--danger-text)}
/* hover 用同色相的浅底而不是把 --danger 铺成实底：实底上的文字在两档里都会撞色 */
.fm-btn-danger:hover{background:var(--danger-100);border-color:var(--danger);color:var(--danger-text)}
.fm-btn-ghost{background:transparent;border-color:transparent;color:var(--ink-500)}
.fm-btn-ghost:hover{background:var(--bg-soft);border-color:transparent;color:var(--brand-text)}
.fm-btn-sm{padding:6px 12px;font-size:13px}
.fm-btn-lg{padding:11px 24px;font-size:15px}
.fm-btn-block{width:100%}
/* 载入态：文字透明、用 currentColor 描环，按钮换什么配色环就跟着换 */
.fm-btn.is-loading{position:relative;color:transparent!important;pointer-events:none}
.fm-btn.is-loading::after{content:"";position:absolute;width:15px;height:15px;border-radius:50%;
  border:2px solid currentColor;border-top-color:transparent;color:var(--on-brand);animation:fmSpin .7s linear infinite}
.fm-btn:not(.fm-btn-primary):not(.fm-btn-danger).is-loading::after{color:var(--brand)}
@keyframes fmSpin{to{transform:rotate(360deg)}}

/* ---------- 表单 ---------- */
.fm-form{display:grid;gap:14px}
/* 一行式的确认表单：一个按钮配一句说明，不该占竖排字段的 14px 节奏 */
.fm-form.is-row{display:flex;flex-wrap:wrap;align-items:center;gap:10px}
.fm-row{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(200px,1fr))}
/* 头像 + 表单并排时按中线对齐，否则头像顶部压着 label */
.fm-row.is-middle{align-items:center}
/* 金额与「应付」这类说明文字并排时按基线，数字大一号也不会飘 */
.fm-row.is-baseline{align-items:baseline}
.fm-field{display:grid;gap:6px;min-width:0}
.fm-label{display:flex;align-items:center;gap:4px;font-size:13.2px;font-weight:600;color:var(--ink-500)}
.fm-label em{font-style:normal;color:var(--danger)}
.fm-tip{margin:0;font-size:12.5px;line-height:1.6;color:var(--ink-500)}
.fm-input,.fm-select,.fm-textarea{width:100%;padding:9px 12px;border:1px solid var(--line-strong);
  border-radius:var(--radius-sm);background:var(--card);color:var(--ink-900);
  font-family:inherit;font-size:14px;transition:border-color .16s,box-shadow .16s}
.fm-input::placeholder,.fm-textarea::placeholder{color:var(--ink-400)}
.fm-input:hover,.fm-select:hover,.fm-textarea:hover{border-color:var(--ink-300)}
.fm-input:focus,.fm-select:focus,.fm-textarea:focus{outline:0;border-color:var(--brand);box-shadow:0 0 0 3px var(--brand-a12)}
.fm-input:disabled,.fm-select:disabled,.fm-textarea:disabled{background:var(--bg-soft);color:var(--ink-400);cursor:not-allowed}
/* 下拉交给原生箭头：自绘的 linear-gradient 箭头不随外观档位变色，也不随系统深色走 */
.fm-textarea{min-height:96px;resize:vertical;line-height:1.65}
.fm-field.is-bad .fm-input,.fm-field.is-bad .fm-select,.fm-field.is-bad .fm-textarea{border-color:var(--danger);box-shadow:0 0 0 3px var(--danger-100)}
.fm-err{display:none;margin:0;font-size:12.5px;color:var(--danger-text)}
.fm-field.is-bad .fm-err{display:block}
.fm-check{display:flex;align-items:flex-start;gap:9px;font-size:13.5px;line-height:1.6;color:var(--ink-500);cursor:pointer}
.fm-check input{flex:none;width:16px;height:16px;margin-top:3px;accent-color:var(--brand)}
.fm-check input:disabled{cursor:not-allowed}
.fm-check input:disabled + *{color:var(--ink-400)}
.fm-radio-row{display:flex;flex-wrap:wrap;gap:8px}
/* 选项组：input 收进标签里，span 就是可见的胶囊；checked / focus-visible 都走兄弟选择器 */
.fm-radio{display:inline-flex}
.fm-radio input{position:absolute;width:0;height:0;opacity:0}
.fm-radio span{display:inline-flex;align-items:center;gap:5px;padding:7px 15px;border:1px solid var(--line-strong);
  border-radius:var(--pill);background:var(--card);color:var(--ink-500);font-size:13.5px;cursor:pointer;
  transition:background .16s,border-color .16s,color .16s}
.fm-radio:hover span{border-color:var(--brand);color:var(--brand-text);background:var(--brand-tint)}
.fm-radio input:checked + span{background:var(--brand);border-color:var(--brand);color:var(--on-brand);font-weight:600}
.fm-radio input:focus-visible + span{outline:2px solid var(--brand);outline-offset:2px}
.fm-radio input:disabled + span{opacity:.55;cursor:not-allowed}
.fm-radio input:disabled + span:hover{border-color:var(--line-strong);color:var(--ink-500);background:var(--card)}
/* 筛选条：字段贴底排，单独一行的标签/胶囊自然与控件底边对齐，不需要皮肤各写一份 */
.fm-filters{display:flex;flex-wrap:wrap;align-items:flex-end;gap:10px;margin:0 0 14px}
/* 筛选条当区块收尾时（比如协议页侧栏的生效文件列表）那 14px 会顶出盒子，收掉而不是回写 inline */
.fm-filters:last-child{margin-bottom:0}
.fm-filters .fm-field{min-width:150px}
/* 验证码行：图在右、输入框在左；点图换一张，所以给 hover/focus 反馈 */
.fm-cap{display:flex;align-items:center;gap:10px}
.fm-cap img{width:112px;height:38px;flex:none;border:1px solid var(--line-strong);border-radius:var(--radius-sm);
  background:var(--bg-soft);cursor:pointer}
.fm-cap img:hover{border-color:var(--brand)}
.fm-code-btn{flex:none;white-space:nowrap}

/* ---------- 卡片与容器 ---------- */
.fm-card{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow-1)}
/* 未读消息卡：左侧一道品牌色压边，比再加一个「未读」徽章轻，也不用每页写死色值 */
.fm-card.is-unread{border-left:3px solid var(--brand)}
.fm-card-pad{padding:18px 20px}
.fm-card-title{display:flex;align-items:center;justify-content:space-between;gap:10px;margin:0 0 12px;
  font-size:15px;font-weight:700;color:var(--ink-900)}
.fm-card-title small{font-size:12.8px;font-weight:400;color:var(--ink-500)}
/* 卡片头居改变体：只有「一侧一个动作」的头部条才需要 space-between，
   提示语 / 倒计时这类单段文字居中更稳；<summary> 版还要多一个可点的手型。 */
.fm-card-title.is-center{justify-content:center}
.fm-card-title.is-clickable{cursor:pointer}
/* 卡片头：卡片自己不套 padding 时，首个标题压成带分隔线的头部条（后台写法）。
   卡片已经 .fm-card-pad（前台与用户中心的写法）时标题就留在正文里，
   不再嵌一层内缩的条——那会出现「框中框」。想强制头部条就把 fm-card-pad 打在标题上。 */
.fm-card:not(.fm-card-pad) > .fm-card-title:first-child,
.fm-card-title.fm-card-pad{margin:0;padding:14px 20px;border-bottom:1px solid var(--line)}
/* 头部条与紧随的正文按一个面算间距：两条都留满就是 32px 空档，看着像两张卡。
   后台原来逐页手写 padding-top:12px / padding-bottom:0 来收，收成一条规则后 30 张卡一致。 */
.fm-card-title.fm-card-pad + .fm-card-pad{padding-top:14px}
.fm-grid{display:grid;gap:16px}
.fm-actions{display:flex;align-items:center;flex-wrap:wrap;gap:10px}
/* 一侧主操作、一侧次操作（取消 / 返回贴右）；标题配按钮时按顶边对齐，两行文字不会被拉成居中 */
.fm-actions.is-split{justify-content:space-between}
.fm-actions.is-top{align-items:flex-start}
.fm-ops,.fm-table .fm-ops{display:flex;flex-wrap:wrap;gap:6px}
.fm-ops.is-split{justify-content:space-between;align-items:center}
/* 定义列表：dt/dd 直接排列是「标签 + 值」两列一行；外面各套一层 <div> 就是自适应取值盒
   （前台详情页的预算 / 分类 / 报价数）。两种标记共用一条定义，
   用 flex 换行而不是 grid 列数区分，皮肤里不用再复制一遍。 */
.fm-kv{display:flex;flex-wrap:wrap;gap:10px 14px;margin:0;font-size:14px}
.fm-kv dt{flex:0 0 120px;color:var(--ink-500);font-size:13px}
.fm-kv dd{flex:1 1 calc(100% - 134px);margin:0;color:var(--ink-900)}
.fm-kv > div{flex:1 1 148px;padding:12px 14px;background:var(--card);border:1px solid var(--line);border-radius:var(--radius-sm)}
.fm-kv > div dt{font-size:12.5px;color:var(--ink-500)}
.fm-kv > div dd{font-size:15px;font-weight:650;color:var(--ink-900)}
/* 侧栏、联系方式这类窄容器里取值盒要一行一个；内联写 grid-template-columns 对 flex 无效，
   之前的页面就是这么写坏了的，改用 flex-basis 收。 */
.fm-kv.is-stack > div{flex:1 1 100%}

/* ---------- 表格 ---------- */
.fm-table{width:100%;border-collapse:collapse;font-size:14px}
.fm-table th{padding:10px 14px;text-align:left;white-space:nowrap;font-size:12.8px;font-weight:600;
  letter-spacing:.02em;color:var(--ink-500);background:var(--bg-soft);border-bottom:1px solid var(--line)}
.fm-table td{padding:11px 14px;border-bottom:1px solid var(--line);vertical-align:middle;color:var(--ink-700)}
.fm-table tbody tr{transition:background .14s}
.fm-table tbody tr:hover{background:var(--bg-soft)}
.fm-table tbody tr:last-child td{border-bottom:0}
.fm-table td.num{text-align:right;font-variant-numeric:tabular-nums}
.fm-cell-main{font-weight:600;color:var(--ink-900)}
.fm-cell-sub{display:block;margin-top:2px;font-size:12.4px;font-weight:400;color:var(--ink-500)}
/* 横向滚动容器：窄屏不折行、不溢出视口。独立出现时自带一层卡片框，进了卡片就收掉。 */
.fm-table-wrap{display:block;overflow-x:auto;-webkit-overflow-scrolling:touch;
  background:var(--card);border:1px solid var(--line);border-radius:var(--radius)}
.fm-card .fm-table-wrap{border:0;border-radius:0;background:none}

/* ---------- 状态标签与提示 ---------- */
.fm-badge{display:inline-flex;align-items:center;gap:4px;flex:none;font-style:normal;white-space:nowrap;
  padding:3px 9px;border-radius:var(--pill);font-size:12.2px;font-weight:600;line-height:1.6;
  background:var(--bg-soft);color:var(--ink-500)}
.fm-badge.is-ok{background:var(--ok-100);color:var(--ok-text)}
.fm-badge.is-warn{background:var(--warn-100);color:var(--warn-text)}
.fm-badge.is-bad{background:var(--danger-100);color:var(--danger-text)}
.fm-badge.is-accent{background:var(--brand-tint);color:var(--brand-text)}
.fm-badge.is-muted{background:var(--bg-soft);color:var(--ink-500)}
.fm-chip{display:inline-flex;align-items:center;gap:6px;padding:5px 12px;border:1px solid var(--line-strong);
  border-radius:var(--pill);background:var(--card);color:var(--ink-500);font-size:12.8px;line-height:1.5;cursor:default}
/* 只有真能点的 chip 才给 pointer 与 hover 反馈：表格里大量 <span class="fm-chip"> 是纯标签 */
a.fm-chip,button.fm-chip{cursor:pointer;transition:background .16s,border-color .16s,color .16s}
a.fm-chip:hover,button.fm-chip:hover{border-color:var(--brand);color:var(--brand-text);background:var(--brand-tint)}
.fm-chip.is-on{background:var(--brand);border-color:var(--brand);color:var(--on-brand);font-weight:600}
.fm-chip button{border:0;background:none;padding:0;font-size:13px;line-height:1;color:var(--ink-400);cursor:pointer}
.fm-chip button:hover{color:var(--danger-text)}
.fm-alert{display:flex;align-items:flex-start;flex-wrap:wrap;gap:10px;margin:0 0 16px;padding:12px 14px;
  border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--card);
  font-size:13.6px;line-height:1.6;color:var(--ink-700)}
.fm-alert b{font-weight:700;color:inherit}
.fm-alert-info{background:var(--brand-tint);border-color:var(--brand-line);color:var(--brand-text)}
.fm-alert-warn{background:var(--warn-100);border-color:var(--warn-line);color:var(--warn-text)}
.fm-alert-danger{background:var(--danger-100);border-color:var(--danger-line);color:var(--danger-text)}
/* 未读数角标：本体只管外观，贴到哪个锚点由皮肤决定（前台铃铛 / 后台图标按钮） */
.fm-dot{display:inline-grid;place-items:center;min-width:17px;height:17px;padding:0 4px;border-radius:var(--pill);
  font-style:normal;font-weight:700;font-size:10.5px;line-height:1;color:var(--on-danger);background:var(--danger)}
.fm-dot:empty{display:none}

/* ---------- Tabs ---------- */
.fm-tabs{display:flex;gap:2px;margin:0 0 16px;padding:0;border-bottom:1px solid var(--line);
  overflow-x:auto;scrollbar-width:none;list-style:none}
.fm-tabs::-webkit-scrollbar{display:none}
.fm-tab{padding:9px 14px;border:0;border-bottom:2px solid transparent;background:none;cursor:pointer;
  font-family:inherit;font-size:14px;color:var(--ink-500);white-space:nowrap;transition:color .16s,border-color .16s}
.fm-tab:hover{color:var(--brand-text)}
.fm-tab.is-on{color:var(--brand-text);border-bottom-color:var(--brand);font-weight:600}
.fm-tab i{font-style:normal;margin-left:5px;padding:1px 7px;border-radius:var(--pill);
  background:var(--bg-soft);color:var(--ink-500);font-size:11.5px}
.fm-tab.is-on i{background:var(--brand-tint);color:var(--brand-text)}

/* ---------- 分页 ---------- */
.fm-pager{display:flex;align-items:center;flex-wrap:wrap;gap:6px;justify-content:flex-end;margin:16px 0 6px}
.fm-pager-total{margin-right:auto;font-size:13px;color:var(--ink-500)}
.fm-pager-num,.fm-pager-btn{display:inline-flex;align-items:center;justify-content:center;min-width:34px;height:34px;
  padding:0 10px;border:1px solid var(--line-strong);border-radius:var(--radius-sm);background:var(--card);
  color:var(--ink-500);font-size:13.5px;cursor:pointer;transition:background .16s,border-color .16s,color .16s}
.fm-pager-num:hover,.fm-pager-btn:hover{border-color:var(--brand);color:var(--brand-text);background:var(--brand-tint)}
.fm-pager-num.is-cur{background:var(--brand);border-color:var(--brand);color:var(--on-brand);font-weight:600}
.fm-pager-num.is-cur:hover{color:var(--on-brand)}
.fm-pager-btn.is-off,.fm-pager-num.is-off{opacity:.45;pointer-events:none}
.fm-pager-gap{padding:0 3px;color:var(--ink-400)}

/* ---------- 空态 / 骨架 / 结果页 ---------- */
.fm-empty{padding:44px 20px;text-align:center;color:var(--ink-500)}
.fm-empty-icon{display:grid;place-items:center;width:52px;height:52px;margin:0 auto 14px;border-radius:50%;
  background:var(--bg-soft);color:var(--ink-400)}
.fm-empty-title{margin:0 0 6px;font-size:15.5px;font-weight:650;color:var(--ink-900)}
.fm-empty-desc{margin:0 auto 16px;max-width:420px;font-size:13.5px;color:var(--ink-500)}
.fm-result{max-width:520px;margin:36px auto;padding:36px 30px;text-align:center;
  background:var(--card);border:1px solid var(--line);border-radius:var(--radius-lg);box-shadow:var(--shadow-1)}
/* 结果页图标：圆底 + fm_icon() 的描边图形。is-pending 给「已提交、等审核」，
   之前这一档没有定义，⏳ 直接落在透明底上，和成功态看不出关系。 */
.fm-result-icon{display:grid;place-items:center;width:52px;height:52px;margin:0 auto 14px;border-radius:50%}
.fm-result-icon.is-ok{background:var(--ok-100);color:var(--ok-text)}
.fm-result-icon.is-pending{background:var(--warn-100);color:var(--warn-text)}
.fm-result-icon.is-bad{background:var(--danger-100);color:var(--danger-text)}
.fm-result-title{margin:0 0 8px;font-size:19px;color:var(--ink-900)}
.fm-result-desc{margin:0 0 6px;color:var(--ink-500)}
.fm-result-tip{margin:0 0 20px;font-size:13.2px;line-height:1.7;color:var(--ink-500)}
.fm-result-actions{display:flex;justify-content:center;flex-wrap:wrap;gap:10px}

/* ---------- 流程步骤 ----------
   两种写法共用一条定义：<div class="fm-step"><b>1</b><h3>…</h3><p>…</p></div> 是「怎么做」的说明卡，
   <span class="fm-step is-on">填写资料</span> 是「做到哪了」的进度格（商家中心入驻页在用）。
   所以它必须住在共用层：商家中心与平台后台都不加载 front.css，放在前台皮肤里时
   进度格会渲染成四段没有边框没有底色的裸文字。 */
.fm-steps{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:14px}
.fm-step{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);padding:18px;
  font-size:14px;line-height:1.6;color:var(--ink-700)}
.fm-step b{display:grid;place-items:center;width:30px;height:30px;margin-bottom:10px;
  border-radius:var(--radius-sm);background:var(--brand-tint);color:var(--brand-text);font-size:14px}
.fm-step h3{font-size:15px;margin-bottom:5px}
.fm-step p{font-size:13.5px;color:var(--ink-500);margin:0}
.fm-step.is-on{border-color:var(--brand);background:var(--brand-tint);color:var(--brand-text);font-weight:600}

/* ---------- 上传 ---------- */
.fm-uploader{display:flex;flex-wrap:wrap;gap:10px}
.fm-up-item{position:relative;width:88px;height:88px;overflow:hidden;border:1px solid var(--line);
  border-radius:var(--radius);background:var(--bg-soft)}
.fm-up-item img{width:100%;height:100%;object-fit:cover}
.fm-up-add{display:grid;place-items:center;gap:2px;width:88px;height:88px;text-align:center;
  border:1px dashed var(--line-strong);border-radius:var(--radius);background:var(--bg-soft);
  color:var(--ink-500);font-size:13px;line-height:1.3;cursor:pointer;transition:border-color .16s,color .16s,background .16s}
.fm-up-add:hover{border-color:var(--brand);color:var(--brand-text);background:var(--brand-tint)}
.fm-up-del{position:absolute;top:4px;right:4px;display:grid;place-items:center;width:22px;height:22px;padding:0;
  border:0;border-radius:50%;background:var(--inverse-bg);color:var(--inverse-fg);font-size:13px;line-height:1;cursor:pointer}
.fm-up-del:hover{background:var(--danger);color:var(--on-danger)}
/* 上传中：图片压暗 + 底部一道不确定的进度条。JS 只加 .is-busy，不写字面宽度。 */
.fm-up-item.is-busy img{opacity:.5}
.fm-up-progress{position:absolute;left:0;right:0;bottom:0;height:3px;background:var(--brand);
  transform-origin:left;animation:fm-bar 1.2s ease-in-out infinite}
@keyframes fm-bar{0%{transform:scaleX(.15);transform-origin:left}50%{transform:scaleX(.85)}100%{transform:scaleX(.15);transform-origin:right}}

/* ---------- 富文本（详情、公告、工单回复，四端都会渲染） ---------- */
.fm-rich{font-size:15px;line-height:1.85;color:var(--ink-700);word-break:break-word}
/* 允许 .fm-rich 直接打在 <ul>/<ol> 上：清掉 UA padding，让列表与卡片其它正文对齐 */
ul.fm-rich,ol.fm-rich{padding-left:22px;margin:0 0 12px}
.fm-rich h3,.fm-rich h4{margin:18px 0 8px;font-size:16px}
.fm-rich p{margin:0 0 12px}
.fm-rich ul,.fm-rich ol{padding-left:22px;margin:0 0 12px}
.fm-rich li{margin:4px 0}
.fm-rich img{margin:10px 0;border:1px solid var(--line);border-radius:var(--radius-sm);max-width:100%}
.fm-rich table{width:100%;margin:12px 0;border-collapse:collapse;font-size:14px}
.fm-rich th,.fm-rich td{border:1px solid var(--line);padding:8px 10px}
.fm-rich blockquote{margin:12px 0;padding:10px 16px;background:var(--bg-soft);
  border-left:3px solid var(--brand);border-radius:0 var(--radius-sm) var(--radius-sm) 0;color:var(--ink-500)}
.fm-rich a{text-decoration:underline;text-underline-offset:2px}
.fm-rich code{padding:1px 6px;border-radius:5px;background:var(--bg-soft);font-size:13.5px;font-family:var(--mono)}

/* ---------- 账号菜单（前台顶栏与两套后台顶栏共用同一份标记） ---------- */
.fm-menu{position:relative}
.fm-menu-btn{display:flex;align-items:center;gap:9px;padding:4px 10px 4px 4px;border:1px solid transparent;
  border-radius:var(--radius-sm);background:none;color:var(--ink-700);font-family:inherit;font-size:14px;cursor:pointer}
.fm-menu-btn:hover{background:var(--bg-soft);border-color:var(--line)}
.fm-menu-btn img{width:32px;height:32px;border-radius:50%;object-fit:cover}
.fm-menu-name{max-width:120px;font-size:13.8px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.fm-menu-pop{position:absolute;right:0;top:calc(100% + 8px);z-index:70;display:none;min-width:196px;padding:6px;
  background:var(--card);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow-3)}
.fm-menu.is-open .fm-menu-pop{display:block;animation:fmPop .16s ease-out}
.fm-menu-pop a,.fm-menu-pop form{display:block}
.fm-menu-pop a,.fm-menu-out{display:block;width:100%;text-align:left;padding:9px 11px;border:0;
  border-radius:var(--radius-sm);background:none;color:var(--ink-700);font-family:inherit;font-size:13.8px;cursor:pointer}
.fm-menu-pop a:hover{background:var(--bg-soft);color:var(--brand-text)}
.fm-menu-out{color:var(--danger-text)}
.fm-menu-out:hover{background:var(--danger-100);color:var(--danger-text)}
.fm-avatar{display:grid;place-items:center;flex:none;width:32px;height:32px;overflow:hidden;border-radius:50%;
  background:var(--brand-tint);color:var(--brand-text);font-weight:700;font-size:14px}
.fm-avatar img{width:100%;height:100%;object-fit:cover}
/* 资料页的身份块：72px 圆角方块，有图用图、没图用首字，两种情况占位一致 */
.fm-avatar.is-lg{width:72px;height:72px;border-radius:var(--radius);border:1px solid var(--line);font-size:24px}
@keyframes fmPop{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}

/* ---------- 浮层：toast / 模态 / 密码强度（由 front.js 动态装配） ---------- */
.fm-toast-wrap{position:fixed;top:16px;left:50%;transform:translateX(-50%);z-index:9999;display:grid;gap:9px;
  width:min(92vw,420px);pointer-events:none}
/* 状态色只做左侧那道边，不铺满整块：绿底白字在深色档会变成浅绿底白字，对比度直接掉到 2.x */
.fm-toast{background:var(--inverse-bg);color:var(--inverse-fg);padding:11px 16px;border-radius:var(--radius-sm);
  border-left:3px solid var(--ink-300);font-size:14px;box-shadow:var(--shadow-3);
  animation:fmToast .22s ease-out;display:flex;gap:8px;align-items:center;pointer-events:auto}
.fm-toast.is-ok{border-left-color:var(--ok);--toast-color:var(--ok-text);--toast-tint:var(--ok-100)}
.fm-toast.is-err{border-left-color:var(--danger);--toast-color:var(--danger-text);--toast-tint:var(--danger-100)}
.fm-toast.is-warn{border-left-color:var(--warn);--toast-color:var(--warn-text);--toast-tint:var(--warn-100)}
.fm-toast.is-info{border-left-color:var(--brand);--toast-color:var(--brand-text);--toast-tint:var(--brand-tint)}
.fm-toast:not(.is-secret){background:var(--card);color:var(--ink-900);align-items:flex-start;gap:var(--s-3);padding:var(--s-3);border-top:1px solid var(--line);border-right:1px solid var(--line);border-bottom:1px solid var(--line)}
.fm-toast-label{flex:none;color:var(--toast-color);background:var(--toast-tint);padding:4px 8px;border-radius:var(--radius-sm);font-size:12px;line-height:24px}
.fm-toast-message{flex:1;min-width:0;overflow-wrap:anywhere;line-height:24px;padding-top:4px}
.fm-toast-close{flex:none;display:grid;place-items:center;width:32px;height:32px;padding:0;border:0;border-radius:var(--radius-sm);background:transparent;color:var(--ink-500);font-size:20px;cursor:pointer}
.fm-toast-close:hover{background:var(--bg-soft);color:var(--ink-900)}
.fm-toast-close:focus-visible{outline:2px solid var(--brand);outline-offset:2px}
.fm-toast.is-hide{opacity:0;transform:translateY(-8px);transition:all .2s}
/* 一次性凭据条：不自动消失，口令整段可选中（user-select:all 让双击即复制全串），没有超时也没有 × 误点 */
.fm-toast.is-secret{flex-wrap:wrap;align-items:flex-start;padding:13px 16px}
.fm-toast-secret{flex:1 1 100%;margin:6px 0 8px;padding:3px 9px;border-radius:var(--radius-sm);
  background:rgba(255,255,255,.16);font:600 15px/1.6 var(--mono);
  letter-spacing:.08em;user-select:all;-webkit-user-select:all;word-break:break-all}
.fm-toast-x{margin-left:auto;background:transparent;border:1px solid rgba(255,255,255,.6);color:inherit;
  font-size:12px;line-height:1.6;padding:1px 9px;border-radius:var(--radius-sm);cursor:pointer;
  transition:border-color .14s,background .14s}
.fm-toast-x:hover{border-color:#fff;background:rgba(255,255,255,.14)}
.fm-toast-x:focus-visible{outline:2px solid var(--inverse-fg);outline-offset:2px}
@keyframes fmToast{from{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:none}}
/* 模态：遮罩 + 卡片。关闭只靠点遮罩/取消/Esc，不给右上角小叉，避免误触丢表单 */
.fm-modal{position:fixed;inset:0;z-index:9000;display:none;align-items:center;justify-content:center;padding:20px;
  background:var(--scrim)}
.fm-modal.is-open{display:flex;animation:fmFade .18s}
.fm-modal-box{width:min(520px,100%);max-height:88vh;overflow:auto;background:var(--card);
  border:1px solid var(--line);border-radius:var(--radius-lg);box-shadow:var(--shadow-3);animation:fmUp .2s ease-out}
.fm-modal-head{padding:18px 22px 6px;font-size:16.5px;font-weight:700;color:var(--ink-900)}
.fm-modal-body{padding:8px 22px 18px;font-size:14.5px;line-height:1.7;color:var(--ink-500)}
.fm-modal-body>*{margin:0 0 10px}
.fm-modal-foot{padding:14px 22px 20px;display:flex;gap:10px;justify-content:flex-end;flex-wrap:wrap}
@keyframes fmFade{from{opacity:0}to{opacity:1}}
@keyframes fmUp{from{opacity:0;transform:translateY(14px) scale(.98)}to{opacity:1;transform:none}}
/* 密码强度条：即时反馈比"提交后才告诉你太简单"省一次挫败 */
/* 切换按钮压在输入框右端：用网格叠放，不用 top:50% 的绝对定位——
   强度条和说明也住在这个容器里，绝对定位会把按钮掉到整块的正中间，正好压在强度条上。 */
.fm-pwd{display:grid;align-items:center}
.fm-pwd .fm-input{grid-area:1 / 1;padding-right:56px}
.fm-pwd [data-pwd-toggle]{grid-area:1 / 1;justify-self:end;margin-right:10px;
  border:0;background:none;color:var(--brand-text);font-size:12.5px;cursor:pointer;padding:4px;border-radius:var(--radius-sm)}
.fm-pwd [data-pwd-toggle]:hover{text-decoration:underline}
.fm-pwd [data-pwd-toggle]:focus-visible{outline:2px solid var(--brand);outline-offset:2px}
.fm-pwd-meter{display:flex;gap:5px;margin-top:9px}
.fm-pwd-meter i{flex:1;height:4px;border-radius:2px;background:var(--line-strong);transition:background .18s}
.fm-pwd-meter i.l1{background:var(--danger)}
.fm-pwd-meter i.l2{background:var(--warn)}
.fm-pwd-meter i.l3{background:var(--ok)}
.fm-pwd-note{margin-top:7px;font-size:12.6px;color:var(--ink-500)}

/* ---------- 间距与排版工具类（四端共用，唯一一份） ----------
   模板里八成的内联 style 只有一个 margin。与其让每个页面自己写 12 / 14 / 16 / 18，
   这里只放出页面真实在用的几档：14 是卡片与区块之间的主节奏（和 .fm-form、.fm-row 的 gap 同源），
   4~10 是卡内小节奏，16 / 20 用于区块收尾。要新增档位先回头看是不是节奏本身该重排。
   只收 margin、字号与对齐，不做颜色、定位、尺寸工具类——那才会把模板变成第二套设计系统。 */
/* 归零写在档位之前：class="fm-m0 fm-mt6" 要的是「上下之外全归零 + 上 6」，靠的是后写的档位赢 */
.fm-m0{margin:0}
.fm-mt4{margin-top:4px}
.fm-mt6{margin-top:6px}
.fm-mt8{margin-top:8px}
.fm-mt10{margin-top:10px}
.fm-mt12{margin-top:12px}
.fm-mt14{margin-top:14px}
.fm-mt16{margin-top:16px}
.fm-mt20{margin-top:20px}
.fm-mb4{margin-bottom:4px}
.fm-mb6{margin-bottom:6px}
.fm-mb8{margin-bottom:8px}
.fm-mb10{margin-bottom:10px}
.fm-mb12{margin-bottom:12px}
.fm-mb14{margin-bottom:14px}
.fm-mb16{margin-bottom:16px}
.fm-nowrap{white-space:nowrap}
.fm-right{text-align:right}
.fm-inline{display:inline}
/* 小字说明：低于 13px 的字号在后台表格里已经读不动了，页面不要再各自写一遍 */
.fm-sm{font-size:13px}
/* 长哈希 / 证件号 / 原始回包：没有连字符可断，只能按字符断，否则撑破表格 */
.fm-break{word-break:break-all}
/* 附件缩略图：三档就够——表格里的小图、后台正文里的图、审核页要辨认证件字迹的大图。
   之前每个审核页各写一个 60~280px 的上限，同类图片在一排入口里大小不一。 */
.fm-thumb{display:block;max-width:200px;max-height:120px;border-radius:var(--radius-sm)}
.fm-thumb-sm{display:block;max-width:160px;max-height:80px;border-radius:var(--radius-sm)}
.fm-doc{display:block;max-width:280px;max-height:180px;border-radius:var(--radius-sm)}
/* 方形 Logo 图：尺寸写在 img 自己的 width / height 上，这里只管裁切与描边。
   不叫 .fm-logo——那是前台顶栏品牌容器的名字，同名会让导航链接凭空多一圈描边。 */
.fm-shop-logo{object-fit:cover;border-radius:var(--radius-sm);border:1px solid var(--line)}
/* 三个容器级工具：二维码 / 结果页居中，单行表单限宽，消息气泡里的整幅块 */
.fm-center{text-align:center}
.fm-narrow{max-width:520px}
.fm-wfull{width:100%}
.fm-ml8{margin-left:8px}
/* 横向推右：一行里把次操作顶到末端，不用每页写 margin-left:auto */
.fm-push{margin-left:auto}

/* ---------- 窄屏与动效偏好（四端一致） ---------- */
@media (max-width:860px){
  .fm-card-pad{padding:16px}
  .fm-card:not(.fm-card-pad) > .fm-card-title:first-child,.fm-card-title.fm-card-pad{padding:13px 16px}
  .fm-table th,.fm-table td{padding:9px 10px}
  .fm-result{margin:20px auto;padding:28px 20px}
  .fm-toast-wrap{top:8px;width:94vw}
}
@media (prefers-reduced-motion:reduce){
  .fm-btn,.fm-chip,.fm-radio span,.fm-up-add,.fm-tab,.fm-pager-num,.fm-pager-btn,.fm-table tbody tr{transition-duration:.01ms}
  .fm-btn.is-loading::after,.fm-up-progress,.fm-toast,.fm-modal.is-open,.fm-modal-box,
  .fm-menu.is-open .fm-menu-pop{animation:none}
}
