:root { --bg:#f4f5f7; --card:#fff; --line:#e6e8eb; --brand:#2563eb; --txt:#1f2329; --muted:#7a8190;
  --bg-soft:#f7f8fa; --up:#d64545; --down:#2f9e5f; }
* { box-sizing:border-box; }
body { margin:0; font-family:-apple-system,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif; background:var(--bg); color:var(--txt); }
header { background:var(--card); border-bottom:1px solid var(--line); padding:14px 24px; display:flex; align-items:center; gap:16px; flex-wrap:wrap; position:sticky; top:0; z-index:5; }
header h1 { font-size:18px; margin:0; }
#count { color:var(--muted); font-size:13px; }
.filters { display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
input, select { height:32px; padding:0 10px; border:1px solid var(--line); border-radius:6px; font-size:13px; background:#fff; }
input:focus, select:focus { outline:none; border-color:var(--brand); }
.w-min { width:118px; }
.w-120 { width:120px; }
.w-160 { width:160px; }
button.primary { height:32px; padding:0 16px; border:none; border-radius:6px; background:var(--brand); color:#fff; font-size:13px; cursor:pointer; }
button.primary:disabled { opacity:.45; cursor:default; }
button.danger { height:32px; padding:0 16px; border:none; border-radius:6px; background:#dc2626; color:#fff; font-size:13px; cursor:pointer; }
button.danger:hover { background:#b91c1c; }
button.danger:disabled { opacity:.4; cursor:default; }
/* 顶部页签（内容区的二级菜单）—— 速卖通店铺一级下有 6 项，窄窗口时换行而不是横向溢出 */
.tabs { display:flex; gap:4px; flex-wrap:wrap; padding:10px 24px 0; }
.tab { padding:8px 20px; border:none; border-radius:8px 8px 0 0; background:transparent; color:var(--muted); font-size:14px; cursor:pointer; transition:color .12s, background .12s; }
.tab:hover:not(.active) { color:var(--txt); background:rgba(255,255,255,.6); }
.tab.active { background:#fff; color:var(--txt); font-weight:600; box-shadow:0 -1px 3px rgba(0,0,0,.06); }

/* ===== 一级导航（左侧栏）=====
   页面是「左侧一级菜单 + 右侧内容区」，内容区顶部再放二级菜单（沿用上面的 .tabs）。
   用 app-shell 布局：外层锁视口高、只有内容区自己滚动 —— 于是侧栏永远占满视口高（背景不会在
   页面比视口短时露出灰底、也不会在内容变长时被顶走），二级菜单条与页头仍能 sticky 在内容区顶部。 */
html, body { height:100%; }
.app { display:flex; height:100vh; overflow:hidden; }
.sidebar { flex:none; width:196px; background:var(--card); border-right:1px solid var(--line);
  overflow-y:auto; padding:16px 12px; }
.brand { padding:2px 8px 16px; }
.brand-name { display:block; font-size:16px; font-weight:700; letter-spacing:.2px; }
.brand-sub { display:block; margin-top:2px; font-size:11.5px; color:var(--muted); }
.nav-list { display:grid; gap:4px; }
.nav-item { display:block; width:100%; padding:9px 12px; border:none; border-radius:8px; text-align:left;
  background:transparent; color:var(--muted); font-family:inherit; font-size:13.5px; cursor:pointer;
  transition:background .12s, color .12s; }
.nav-item:hover:not(.active) { background:#f6f8ff; color:var(--txt); }
.nav-item.active { background:#eef4ff; color:var(--brand); font-weight:600; }
.content { flex:1 1 auto; min-width:0; overflow-y:auto; }

/* ===== 速卖通店铺（店铺搜索 / 新店增长榜）=====
   两个二级页共用 #view_ae 一个内容区：页头与数据源状态条固定，筛选表按当前二级页显隐
   （两张表同时在 DOM 里，见 js/app.js 的说明），结果渲染进 #ae_body。

   视觉上刻意与采集台区分：采集台是「一屏多卡的采集列表」，这里是「一次查询一份结果集」——
   所以用筛选卡片 + 数据表（店铺搜索）/ 榜单卡片（新店增长榜），而不是复用商品卡片。
   配色沿用全站变量，正向数值（销量、增长）按中文习惯用红色。

   设计层级：页头（标题/状态）→ 筛选卡片（条件）→ 激活筛选条（已生效条件，可单条清除）
   → 结果（表格/榜单）→ 分页。加载用骨架屏（.ix-skel）+ 顶部进度条（.ix-progress），
   不用「正在获取数据…」一行字 —— 那一行会让人以为页面卡死了，骨架屏则是在说「结构在，就差数据」。 */
.ix-wrap { padding:0 0 48px; }
.ix-head { display:flex; align-items:flex-start; gap:16px; padding:18px 24px 0; }
.ix-head-txt { min-width:0; }
.ix-head h2 { margin:0; font-size:20px; letter-spacing:-.01em; }
.ix-sub { margin:5px 0 0; color:var(--muted); font-size:13px; line-height:1.6; }
.ix-state { margin-left:auto; flex:none; display:flex; align-items:center;
  gap:8px; flex-wrap:wrap; justify-content:flex-end; max-width:52%; }
.ix-pill { font-size:12px; line-height:1; padding:6px 11px; border-radius:999px; background:#f1f2f4; color:var(--muted); }
.ix-pill.ok { background:#ecfdf5; color:#047857; }
.ix-pill.err { background:#fee2e2; color:#b91c1c; }
.ix-mini { height:28px; padding:0 12px; border:1px solid var(--line); border-radius:7px; background:#fff;
  color:var(--txt); font-family:inherit; font-size:12.5px; cursor:pointer; transition:border-color .12s, color .12s; }
.ix-mini:hover { border-color:var(--brand); color:var(--brand); }
.ix-hint { margin:8px 0 0; font-size:12.5px; color:var(--muted); line-height:1.7; }
/* 状态条里 .ix-state 是横向 flex，提示跟在 pill 后面并排；沿用竖排的 margin-top 会让它掉下去一行 */
.ix-state .ix-hint { margin:0; }

/* ---- 筛选卡片 ---- */
.ix-filters { padding:14px 24px 0; }
.ix-fbox { background:var(--card); border:1px solid var(--line); border-radius:12px; padding:16px 18px 14px;
  box-shadow:0 1px 3px rgba(16,24,40,.05), 0 1px 2px rgba(16,24,40,.03); }
.ix-frow { display:flex; flex-wrap:wrap; gap:12px 16px; }
.ix-frow + .ix-frow { margin-top:12px; }
.ix-f { display:flex; flex-direction:column; gap:5px; min-width:0; }
/* 独占一整行的筛选字段（店铺名 / 链接标题搜索，2026-10-13 用户要求拉长并独占一行）。
   · `flex:1 1 100%` —— 在 `.ix-frow` 的 wrap 布局里，basis 100% 会让它必然独占一行
     （同行的其它字段只能被挤到下一行），**不需要**改 `.ix-frow` 的 flex-wrap 或加换行元素；
   · 输入框 `width:100%` —— 只让它「独占一行」还不够：`.ix-in` 本身写死 170px，
     独占后会变成「左边一个短输入框 + 右边一大片空白」，比原来更难看。
   ⚠️ 这条规则只作用于**带 .ix-f-grow 的那个字段**；给 `.ix-frow > .ix-f:first-child` 之类
     写规则是错的 —— 那会让「谁独占一行」由 DOM 顺序隐式决定，调整字段次序就会静默改版式。 */
.ix-f-grow { flex:1 1 100%; }
/* 输入框：比原来的 170px 长得多，但**不拉满整行**（2026-10-13 同日用户反馈「占用一整行太长了」）。
   用 width:100% + max-width 封顶：窄屏自动收缩（不会横向溢出），宽屏停在 420px ——
   速卖通标题/店名动辄几十个字符，420px（约 30 个汉字）足以核对，再长光标要跑很远。
   ⚠️ 字段本身**仍独占一行**（那是上一轮明确要的），只是输入框不再跟着拉满；
   改回去就是把 max-width 去掉 —— probe-ae-ui.js §5 有两条断言分别钉住「≥320px」与「≤460px」。 */
.ix-f-grow .ix-in { width:100%; max-width:420px; }
.ix-f-label { font-size:11.5px; letter-spacing:.02em; color:var(--muted); }
.ix-in, .ix-f select { height:30px; padding:0 10px; border:1px solid var(--line); border-radius:7px;
  background:#fff; color:var(--txt); font-family:inherit; font-size:12.5px; transition:border-color .12s, box-shadow .12s; }
.ix-in:focus, .ix-f select:focus { outline:none; border-color:var(--brand); box-shadow:0 0 0 3px rgba(37,99,235,.12); }
.ix-in { width:170px; }
.ix-f select { min-width:170px; max-width:260px; }
.ix-range { display:flex; align-items:center; gap:6px; }
.ix-range i { font-style:normal; color:var(--muted); }
.ix-range .ix-in { width:96px; }

/* ---- 分类：可搜索下拉（替代原生 <select>）----
   分类是一级 + 二级上千项，原生下拉只能靠敲首字母跳、中文项跳不过去 —— 于是这个筛选
   几乎没人用。改成「输入即筛」。三条别退回 <select> 的理由见 js/app.js 的 ixCatComboboxHtml。

   定位坑：**祖先链上不能有 overflow:hidden**，否则面板会被裁掉。`.ix-fbox` / `.ix-frow` / `.ix-f`
   都没有 overflow，所以这里是安全的；但哪天给其中任何一个加上（比如为了圆角裁切），
   面板会静默消失 —— 排查时先看这一条，别先怀疑数据没到。
   面板的 z-index 取 15：高于表格 sticky 表头（2/3）与顶部 header（5），低于提交进度浮层（20）。 */
.ix-dd { position:relative; display:inline-block; width:200px; }
.ix-dd-in { width:100%; padding-right:30px; }
.ix-dd-row.hi { background:#eef4ff; }
/* `.has-val` 的具体性(0,2,0) 与 `.ix-dd-row.hi`(0,2,0) 相同，靠先后顺序决定 → 必须写在前面，
   否则「已选中的那个控件 + 悬停高亮」会互相打架（实测症状是高亮底色偶尔不出现）。 */
.ix-dd.has-val .ix-dd-in { border-color:#cfd9f5; background:#f7faff; }
.ix-dd-caret { position:absolute; top:50%; right:4px; transform:translateY(-50%);
  width:22px; height:22px; padding:0; border:none; border-radius:5px; background:transparent;
  color:var(--muted); cursor:pointer; display:flex; align-items:center; justify-content:center; }
.ix-dd-caret svg { width:10px; height:6px; transition:transform .12s; }
.ix-dd.open .ix-dd-caret { color:var(--brand); }
.ix-dd-caret:hover { background:#f1f2f4; color:var(--txt); }
.ix-dd-hint { margin-top:4px; font-size:11.5px; line-height:1.5; color:var(--muted); }
.ix-dd-pop { display:none; position:absolute; top:calc(100% + 4px); left:0; z-index:15;
  width:max(100%, 260px); border:1px solid var(--line); border-radius:9px; background:#fff;
  /* 只有把底边留白给出来，`overflow:hidden` 才不会把行/底栏自己的阴影切掉 —— 用户看到的效果是
     「面板底部边缘的投影突然没了」，像被别的东西压住一截。另加 `overflow:hidden` 是必须的：
     圆角要裁住里面滚动的列表，否则列表滚动时方角会从圆角里冒出来。 */
  padding-bottom:3px; box-shadow:0 10px 26px rgba(16,24,40,.14); overflow:hidden; }
.ix-dd.open .ix-dd-pop { display:block; }
.ix-dd-list { max-height:240px; overflow-y:auto; padding:4px; }
.ix-dd-row { display:flex; align-items:baseline; padding:6px 8px; border-radius:6px;
  font-size:12.5px; color:var(--txt); cursor:pointer; }
.ix-dd-row.hi { background:#eef4ff; }
.ix-dd-nm { flex:0 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.ix-dd-sub { flex:0 1 auto; min-width:0; margin-left:8px; overflow:hidden; text-overflow:ellipsis;
  white-space:nowrap; color:var(--muted); font-size:11.5px; }
.ix-dd-none { padding:14px 10px; text-align:center; color:var(--muted); font-size:12.5px; }
.ix-dd-foot { padding:5px 10px; border-top:1px solid var(--line); background:#fafbfc;
  color:var(--muted); font-size:11.5px; }
.ix-dd-foot:empty { display:none; }
.ix-dd-tip { position:absolute; top:calc(100% + 4px); left:0; z-index:16; padding:5px 9px;
  border:1px solid #fed7aa; border-radius:7px; background:#fffbeb; color:#b45309;
  font-size:11.5px; line-height:1.4; white-space:nowrap; box-shadow:0 6px 16px rgba(16,24,40,.12); }

/* ---- 分类级联多选控件（对齐上游两套交互：产品页级联多选 / 店铺族父子两级）----
   与 .ix-dd 同一视觉家族（边框/圆角/字号/阴影沿用变量），差异只在「多列面板 + chip 区」。
   宽度比旧的 200px 宽：多列面板必须放得下，且 chip 要能显示「一级 / 二级」路径。 */
.ix-casc { position:relative; display:inline-block; width:260px; }
.ix-casc-head { position:relative; }
.ix-casc-in { width:100%; padding-right:30px; }
.ix-casc.has-val .ix-casc-in { border-color:#cfd9f5; background:#f7faff; }
/* chip 区：已选项。**只在控件内**显示（结果区上方那条 .ix-active 只出摘要，见 app.js 的注释） */
.ix-casc-tags { display:flex; flex-wrap:wrap; gap:4px; margin-bottom:4px; max-height:62px; overflow-y:auto; }
.ix-casc-tag { display:inline-flex; align-items:center; gap:2px; max-width:100%; padding:1px 2px 1px 6px;
  border:1px solid #dbe4f7; border-radius:5px; background:#f4f7fe; color:var(--txt); font-size:11.5px;
  line-height:1.7; }
.ix-casc-tag-t { max-width:170px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.ix-casc-tag-x { width:15px; height:15px; padding:0; border:none; border-radius:3px; background:transparent;
  color:var(--muted); cursor:pointer; font-size:12px; line-height:1; }
.ix-casc-tag-x:hover { background:#e2eafb; color:#b42318; }
/* 面板：横向多列，每列独立滚动 */
/* 🔴 必须补这条 `display:block`：面板的基类 `.ix-dd-pop` 默认 `display:none`，
   而它的显示由 `.ix-dd.open .ix-dd-pop` 打开 —— 我们的容器类名是 `.ix-casc`（不是 `.ix-dd`），
   那条选择器**匹配不上**，于是面板永远是 none：控件「打开了」却一个像素看不见（最典型的静默失效）。 */
.ix-casc.open .ix-casc-pop { display:block; }
.ix-casc-pop { width:max(100%, 520px); }
.ix-casc-cols { display:flex; align-items:stretch; max-height:270px; overflow-x:auto; }
.ix-casc-col { flex:0 0 168px; max-height:270px; overflow-y:auto; padding:4px;
  border-right:1px solid var(--line); }
.ix-casc-col:last-child { border-right:none; }
.ix-casc-colhd { position:sticky; top:0; z-index:1; padding:3px 6px 5px; background:#fff;
  color:var(--muted); font-size:11px; }
.ix-casc-row { display:flex; align-items:center; gap:6px; padding:5px 7px; border-radius:6px;
  font-size:12.5px; color:var(--txt); cursor:pointer; }
.ix-casc-row.hi { background:#eef4ff; }
.ix-casc-row.on { color:var(--brand); }
.ix-casc-ck { flex:0 0 13px; width:13px; height:13px; border:1px solid var(--line); border-radius:3px;
  background:#fff; position:relative; }
.ix-casc-ck.on { border-color:var(--brand); background:var(--brand); }
.ix-casc-ck.on::after { content:''; position:absolute; left:3.5px; top:.5px; width:4px; height:8px;
  border:solid #fff; border-width:0 2px 2px 0; transform:rotate(45deg); }
.ix-casc-nm { flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.ix-casc-more { flex:0 0 auto; width:0; height:0; border-left:4px solid var(--muted);
  border-top:3.5px solid transparent; border-bottom:3.5px solid transparent; opacity:.55; }

.ix-fbar { display:flex; align-items:flex-end; flex-wrap:wrap; gap:12px 16px; margin-top:14px;
  padding-top:12px; border-top:1px dashed var(--line); }
.ix-fbar .primary { height:30px; padding:0 18px; border-radius:7px; box-shadow:0 1px 2px rgba(37,99,235,.25);
  transition:box-shadow .12s, transform .1s; }
.ix-fbar .primary:hover:not(:disabled) { box-shadow:0 2px 6px rgba(37,99,235,.3); transform:translateY(-1px); }
.ix-count { margin-left:auto; align-self:center; font-size:12.5px; color:var(--muted); }

/* ---- 激活筛选条（已生效条件，可单条清除）----
   为什么单独做这一行：条件藏在表单里时，「现在到底按什么查的」要逐个控件核对，
   而错一次条件就意味着一批错数据。激活条把「当前结果集是怎么来的」摊在结果上方。 */
.ix-active { display:flex; flex-wrap:wrap; gap:8px; margin-bottom:12px; }
.ix-active:empty { display:none; margin:0; }
.ix-chip-x { display:inline-flex; align-items:center; height:26px; padding:0 6px 0 10px; gap:4px;
  border:1px solid #cfd9f5; border-radius:999px; background:#f2f6ff; color:#1d4ed8; font-size:12px; }
.ix-chip-x button { flex:none; width:18px; height:18px; border:none; border-radius:50%; background:transparent;
  color:inherit; font-size:13px; line-height:1; cursor:pointer; transition:background .12s; }
.ix-chip-x button:hover { background:#dbe6ff; }

/* ---- 结果区 ---- */
.ix-body { padding:14px 24px 0; }
.ix-empty { background:var(--card); border:1px solid var(--line); border-radius:12px;
  padding:44px 24px; text-align:center; color:var(--muted); font-size:13px; line-height:1.7;
  box-shadow:0 1px 2px rgba(16,24,40,.03); }
.ix-empty-ico { display:block; width:44px; height:44px; margin:0 auto 12px; color:#c3c9d4; }
.ix-empty-err { border-color:#fecaca; background:#fff8f8; }
.ix-empty-err > p:first-child { margin:0; color:#b91c1c; font-size:13.5px; }

/* 顶部进度条：换条件/翻页且已有数据时压在结果上方 —— 表还在，但一看就知道正在换一批。 */
.ix-progress { position:relative; height:3px; margin:-2px 0 10px; border-radius:3px; background:#e9edf5; overflow:hidden; }
.ix-progress i { position:absolute; top:0; bottom:0; left:0; width:30%; border-radius:3px;
  background:var(--brand); animation:ix-progress 1s ease-in-out infinite; }
@keyframes ix-progress { 0% { left:-30%; } 100% { left:100%; } }

/* ---- 表格（店铺搜索）----
   wrap 自己纵向滚动是为了「表头 + 首列」都能粘住：浏览器只会把 sticky 挂在最近的滚动容器上，
   若让 .content 滚，表头就要跟着整页走，长表翻到第 40 行就不知道自己在看哪一列了。 */
.ix-tablewrap { background:var(--card); border:1px solid var(--line); border-radius:12px; overflow:auto;
  max-height:max(320px, calc(100vh - 392px)); box-shadow:0 1px 3px rgba(16,24,40,.05), 0 1px 2px rgba(16,24,40,.03); }
.ix-table { border-collapse:separate; border-spacing:0; width:100%; font-size:12.5px; white-space:nowrap; }
.ix-table thead th { position:sticky; top:0; z-index:2; background:#f8fafc; color:#8a94a6;
  font-size:11.5px; font-weight:600; letter-spacing:.02em; text-align:left; padding:9px 12px;
  border-bottom:1px solid var(--line); }
.ix-table td { padding:10px 12px; border-bottom:1px solid #f2f3f5; vertical-align:middle; }
.ix-table tbody tr:last-child td { border-bottom:none; }
.ix-table tbody tr:hover td { background:#f5f8ff; }
.ix-num { text-align:right; font-variant-numeric:tabular-nums; }
/* 🔴 **表头的对齐必须按列类逐条恢复**（2026-10-15 用户反馈：「内容和表头不齐 —— 表头靠左、内容靠右」）。
   病因是选择器优先级，不是哪条规则写错了：
     · `.ix-table thead th { text-align:left }` = **(0,1,1)**
     · `.ix-num` / `.ix-c-act` / `.ix-c-fav` / `.ix-c-ov` = (0,1,0)
   ⇒ 只要写在 `th` 上、又想让它右对齐/居中，**一律被 th 那条压成左对齐**；而 `td` 不匹配
   `.ix-table thead th`，所以内容照旧右对齐/居中 —— 同一列的「表头」与「内容」于是分成两种对齐。
   波及的不止总览热销表：**店铺搜索表、产品搜索表**上的数字列与动作列全都这样错位（同一套复用样式）。
   下面这几条是 (0,2,1)，是本文件里唯一能压过 th 那条的位置。
   ⚠️ 两种「看着能修、实际更糟」的写法，别再走：
     ① 把 `.ix-num` 整个提权成 `.ix-table .ix-num` —— 连 td 一起提权，病因被盖住，
        下一个动 th 规则的人会重新踩一遍；
     ② 在 th 上写行内 `style="text-align:right"` —— 内联样式确实赢，但它让「对齐从哪来」
        在 CSS 里查不到，和本仓「样式只在 CSS 里」的约定冲突。
   哨兵（真浏览器读 computed text-align，**逐列比对表头与内容**，不是断言「某列应当是 right」）：
     · `tools/probe-overview-layout.js` §3 → 总览热销表
     · `tools/probe-ae-ui.js` §4 → 产品搜索表 + 店铺搜索表（一次遍历页面上每张 .ix-table） */
.ix-table thead th.ix-num { text-align:right; }
.ix-table thead th.ix-c-act,
.ix-table thead th.ix-c-fav,
.ix-table thead th.ix-c-ov { text-align:center; }
.ix-strong { color:#dc2626; font-weight:600; }
.ix-sortable { cursor:pointer; user-select:none; }
.ix-sortable:hover { color:var(--brand); }
.ix-arrow { margin-left:5px; font-style:normal; font-size:11px; opacity:.4; }
.ix-sortable:hover .ix-arrow { opacity:.9; }
.ix-c-shop { position:sticky; left:0; z-index:1; background:var(--card); box-shadow:1px 0 0 var(--line); }
.ix-table thead .ix-c-shop { z-index:3; background:#f8fafc; }
.ix-table tbody tr:hover .ix-c-shop { background:#f5f8ff; }
.ix-shopcell { display:flex; align-items:center; gap:10px; }
.ix-logo { flex:none; width:34px; height:34px; border-radius:9px; object-fit:cover; background:#f1f2f4; }
.ix-logo-ph { display:inline-block; }
.ix-logo-lg { width:46px; height:46px; border-radius:12px; }
.ix-shopinfo { display:flex; flex-direction:column; gap:2px; min-width:0; }
.ix-shopname { display:flex; align-items:center; gap:6px; max-width:300px; font-weight:600; }
.ix-shopname a { display:block; color:var(--txt); text-decoration:none; overflow:hidden; text-overflow:ellipsis; }
.ix-shopname a:hover { color:var(--brand); text-decoration:underline; }
.ix-shopid { color:var(--muted); font-size:11.5px; }
.ix-tag { flex:none; padding:1px 6px; border-radius:6px; background:#f1f2f4; color:var(--muted);
  font-size:11px; font-weight:500; }
/* `.ix-tag-choice`（优选标签的红色胶囊）2026-10-13 已删：全站不再产出「优选」标签
   （店铺搜索 / 新店增长榜 / 产品搜索 / 收藏列表都去掉了）。留着的死样式会让人以为还有这个标签。 */
.ix-ships { max-width:190px; overflow:hidden; text-overflow:ellipsis; color:var(--muted); }

/* ---- 表格：产品搜索 ----
   与店铺表格共用一切版式（表头、粘性首列、数字右对齐、hover），只多三点：
   首列带缩略图且更宽、商品名折两行（商品名很长，截成一行基本认不出是什么东西）、7 天环比带涨跌色。 */
.ix-c-product { position:sticky; left:0; z-index:1; background:var(--card); box-shadow:1px 0 0 var(--line); }
.ix-table thead .ix-c-product { z-index:3; background:#f8fafc; }
.ix-table tbody tr:hover .ix-c-product { background:#f5f8ff; }
.ix-prodcell { display:flex; align-items:center; gap:10px; }
.ix-thumb { flex:none; width:56px; height:56px; border-radius:9px; object-fit:cover; background:#f1f2f4; }
img.ix-thumb { cursor:zoom-in; }   /* 只给真图（占位符是 span），光标先一步说明「这图能看大图」 */
.ix-prodinfo { display:flex; flex-direction:column; gap:4px; min-width:0; }
.ix-prodname { display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
  width:330px; white-space:normal; font-weight:600; line-height:1.45; }
.ix-prodname a { color:var(--txt); text-decoration:none; }
.ix-prodname a:hover { color:var(--brand); text-decoration:underline; }
.ix-prodmeta { display:flex; align-items:center; gap:5px; color:var(--muted); font-size:11.5px; }
.ix-prodshop { max-width:190px; overflow:hidden; text-overflow:ellipsis; }
/* 7 天环比：涨红跌绿（中文习惯），0 / 缺失时整段不渲染，免得一排 +0% 变噪声 */
.ix-rate { margin-left:5px; font-size:11px; }
.ix-rate.up { color:#dc2626; }
.ix-rate.down { color:#15803d; }

/* ---- 表格：产品搜索的「采集」列 ----
   这一列**右侧吸附**（与首列左侧吸附同一套做法，方向相反）。理由：这张表有 12 列，
   窄窗口下必然横向溢出，而这是整张表的「动作」列 —— 要往右拉到最底才能点到，
   等于把主要入口藏起来了。吸附列的 z-index 必须抬起来（1 / 表头 3），否则会被相邻单元格盖住；
   背景色也得自己给（吸附元素脱离普通单元格的绘制顺序，不给就是透明的）。 */
/* 🔴 右侧动作列**可以有多列**：产品搜索只有「采集」，店铺搜索有「收藏」+「总览」（2026-10-13 新增）。
   多列的做法是**逐列往左让位**：最右那列 right:0，它左边那列 right = **它的真实宽度**。
   ⚠️ 两列都写 right:0 会**完全重叠**，看起来就像少了一列（而且两列内容会叠在一起）。
   🔴 让位量**必须由 JS 量出来写进 `--ix-c-ov-w`**（见 app.js 的 ixSyncStickyRight，渲染后/窗口变化时各同步一次），
      不要写死像素：这张表在窄窗口下是超宽的，Chrome 会按比例把超出量从各列扣回去 ——
      实测写 `width:112px` 的列最终只有 101px（内容 min-content 宽度），写死的 84px 让位量于是差出 14px 的缝。
      下面的 84px 只是**兜底值**（JS 还没跑、或区块被隐藏量不到时用）。
   三个动作列共用一套底座（吸附 + 背景 + 居中），各自只差 right。 */
.ix-c-act, .ix-c-fav, .ix-c-ov { position:sticky; z-index:1; background:var(--card);
  white-space:nowrap; text-align:center; }
.ix-c-act { right:0; width:1%; box-shadow:-1px 0 0 var(--line); }
.ix-c-fav { right:var(--ix-c-ov-w, 84px); box-shadow:-1px 0 0 var(--line); }
.ix-c-ov { right:0; }
.ix-table thead .ix-c-act, .ix-table thead .ix-c-fav, .ix-table thead .ix-c-ov { z-index:3; background:#f8fafc; }
.ix-table tbody tr:hover .ix-c-act,
.ix-table tbody tr:hover .ix-c-fav,
.ix-table tbody tr:hover .ix-c-ov { background:#f5f8ff; }
/* 「总览」按钮：与「收藏」并排，做成低一级的描边小按钮（`.ix-mini` 已是这套），
   悬停才描品牌色，避免两个实心按钮在每行里抢视线。 */
.ix-ovopen { height:26px; padding:0 10px; font-size:12px; }
/* 采集按钮：描边款、悬停才填充 —— 每行都有一个，实心会盖过「提交选中到店小秘」那些真·主按钮 */
.ix-pick { height:26px; padding:0 12px; border:1px solid var(--brand); border-radius:6px; background:#fff;
  color:var(--brand); font-size:12px; cursor:pointer; transition:background .12s, color .12s, border-color .12s; }
.ix-pick:hover:not(:disabled) { background:var(--brand); color:#fff; }
/* 采集中 / 已采集：都退成灰底灰字、都不可点。区别只在文案 ——
   「已采集」是终态（服务端已有这件商品），「采集中…」是等回包，二者不该长得一样都会被误读。 */
.ix-pick.busy, .ix-pick.done { border-color:var(--line); background:#f1f2f4; color:var(--muted); cursor:default; }
.ix-pick-na { color:var(--muted); }

/* 轻量提示条（app.js 的 ixToast）：右下角浮一小条，几秒后自己淡出。
   挂 <body> 下、fixed 定位 —— 与商品图放大浮层同一个理由：内容区是唯一的滚动容器，
   放进页面里会被它带着滚，也可能被某个祖先的 overflow 裁掉。
   z-index 取 60：要压过表格与吸附列，但仍低于第三方图片工具条（AiPrice 用 2147483647）。 */
.ix-toast { position:fixed; right:20px; bottom:24px; z-index:60; max-width:min(520px, 60vw);
  padding:10px 14px; border-radius:10px; background:#1f2329; color:#fff; font-size:13px; line-height:1.5;
  box-shadow:0 8px 24px rgba(16,24,40,.18); opacity:0; visibility:hidden; transform:translateY(6px);
  transition:opacity .16s, transform .16s, visibility .16s; word-break:break-word; }
.ix-toast.show { opacity:1; visibility:visible; transform:none; }
.ix-toast.ok { background:#15803d; }
.ix-toast.err { background:#dc2626; }
/* 琥珀色：这次采集**成功了**，但有半边没做到（详情没取到、只登记了元数据）。
   与绿色区分开是刻意的 —— 用绿色会让人以为详情也拿到了，直到提交时才发现要多等一轮。 */
.ix-toast.warn { background:#b45309; }

/* ---- 轻量模态框（app.js 的 ixDialog）----
   与 .ix-toast 同一套视觉语言（深色卡片、圆角、大阴影），但**居中 + 有遮罩**：
   它承载的是「必须被看到」的前置条件说明（未收藏 ⇒ 没有总览可看），不是一闪而过的反馈。
   节点挂 <body> 下、fixed 定位 —— 与放大浮层/提示条同一个理由：内容区是唯一的滚动容器，
   放页面流里会被它带着滚、也可能被某个祖先的 overflow 裁掉。
   z-index 取 70：高于提示条（60），低于第三方工具条（AiPrice 用 2147483647）。
   关掉时用 visibility 而不是 display:none：保留节点、避免每次开合都重新建（也不需要重绑事件）。 */
.ix-dlg { position:fixed; inset:0; z-index:70; display:flex; align-items:center; justify-content:center;
  padding:24px; background:rgba(16,24,40,.42); opacity:0; visibility:hidden;
  transition:opacity .14s, visibility .14s; }
.ix-dlg.show { opacity:1; visibility:visible; }
.ix-dlg-card { width:min(420px, 100%); padding:20px 22px 16px; border-radius:12px; background:var(--card);
  box-shadow:0 16px 40px rgba(16,24,40,.24); transform:translateY(-4px);
  transition:transform .14s; }
.ix-dlg.show .ix-dlg-card { transform:none; }
.ix-dlg-title { font-size:14.5px; font-weight:600; color:var(--txt); }
/* 正文给足行高与断行：将来带上店铺名（可能很长、可能含拉丁字母）时不会撑破卡片 */
.ix-dlg-text { margin-top:8px; font-size:12.5px; line-height:1.7; color:var(--muted); word-break:break-word; }
.ix-dlg-foot { display:flex; justify-content:flex-end; margin-top:16px; }
.ix-dlg-ok { height:30px; padding:0 16px; }

/* 商品缩略图的悬浮放大浮层（见 app.js 的 ixZoomShow）——挂在 <body> 下、fixed 定位，
   这样才绕得开 .ix-tablewrap 的 overflow:auto（在单元格里放大必被裁）。
   · 可交互（pointer-events 只在 .show 时 auto）：鼠标要能真的「停」在放大图上，才走得过去用旁边的
     图片搜索入口（AiPrice 的悬浮条 / 右键「以图搜图」）—— 见 app.js 的 IX_ZOOM_HIDE_DELAY 与
     ixZoomKeepAlive()。不吃事件的话鼠标一离开缩略图就算「哪儿都不在」，那些入口一步都够不到。
     不显示时是 none，所以平时完全不挡底下的行 hover 与可点性。
   · 切显隐用 visibility 而不是 display:none：定位要在量到真实尺寸之后才算得准，
     而 display:none 的元素量出来是 0×0，浮层会落在左上角。
   · z-index 压得很低（40）：第三方图片工具条（AiPrice 用 2147483647）要能盖在浮层**上面**，
     否则它那个搜索按钮会被放大图挡住、点不到。 */
.ix-zoom { position:fixed; left:0; top:0; z-index:40; padding:6px; border-radius:12px;
  background:var(--card); border:1px solid var(--line);
  box-shadow:0 12px 32px rgba(16,24,40,.2), 0 2px 6px rgba(16,24,40,.08);
  visibility:hidden; opacity:0; pointer-events:none; transition:opacity .12s ease; }
.ix-zoom.show { visibility:visible; opacity:1; pointer-events:auto; }
.ix-zoom-img { display:block; width:100%; height:100%; object-fit:contain; border-radius:7px; }

/* ---- 榜单（新店增长榜）---- */
.ix-ranks { display:grid; gap:10px; }
.ix-rank { display:grid; align-items:center; gap:12px 14px; background:var(--card); border:1px solid var(--line);
  border-radius:12px; padding:14px 16px; box-shadow:0 1px 2px rgba(16,24,40,.04);
  grid-template-columns:36px 48px minmax(200px,1.4fr) 136px 100px;
  grid-template-areas:"no logo main grow side" "chips chips chips chips chips";
  transition:box-shadow .14s, border-color .14s, transform .14s; }
.ix-rank:hover { border-color:#d7dfeb; box-shadow:0 5px 16px rgba(16,24,40,.07); transform:translateY(-1px); }
.ix-rank-no { grid-area:no; width:34px; height:34px; border-radius:10px; background:#eef0f4; color:var(--muted);
  display:flex; align-items:center; justify-content:center; font-size:15px; font-weight:700;
  font-variant-numeric:tabular-nums; }
.ix-medal-1 .ix-rank-no { background:linear-gradient(135deg,#fbbf24,#f59e0b); color:#fff; box-shadow:0 2px 6px rgba(245,158,11,.35); }
.ix-medal-2 .ix-rank-no { background:linear-gradient(135deg,#cbd5e1,#94a3b8); color:#fff; }
.ix-medal-3 .ix-rank-no { background:linear-gradient(135deg,#fdba74,#ea580c); color:#fff; }
.ix-rank .ix-logo-lg { grid-area:logo; }
.ix-rank-main { grid-area:main; min-width:0; }
.ix-rank-name { display:flex; align-items:center; gap:6px; font-size:14px; font-weight:600; }
.ix-rank-name a { color:var(--txt); text-decoration:none; }
.ix-rank-name a:hover { color:var(--brand); text-decoration:underline; }
.ix-rank-meta { margin-top:4px; color:var(--muted); font-size:12px; overflow:hidden;
  text-overflow:ellipsis; white-space:nowrap; }
/* 增长值用一块浅红面板托起来：它是这张卡片的「答案」，不能被旁边一堆灰色指标淹没 */
.ix-rank-grow { grid-area:grow; text-align:center; background:linear-gradient(180deg,#fff8f6,#fff2ef);
  border:1px solid #ffdcd6; border-radius:12px; padding:9px 12px; }
.ix-rank-grow b { display:block; color:#dc2626; font-size:19px; font-variant-numeric:tabular-nums; }
.ix-rank-grow span { font-size:11.5px; color:#c07a6e; }
.ix-rank-side { grid-area:side; display:flex; flex-direction:column; align-items:flex-end; gap:6px; }
.ix-move { padding:2px 9px; border-radius:999px; font-size:12px; }
.ix-move.up { background:#fff1f0; color:#cf1322; }
.ix-move.down { background:#f0fdf4; color:#15803d; }
.ix-move.new { background:#eff6ff; color:#1d4ed8; }
.ix-move.flat { background:#f1f2f4; color:var(--muted); }
.ix-rank-date { font-size:11.5px; color:var(--muted); }
.ix-chips { grid-area:chips; display:flex; flex-wrap:wrap; gap:7px; padding-top:11px;
  border-top:1px dashed var(--line); }
.ix-chip { display:inline-flex; align-items:baseline; gap:5px; padding:3px 9px; border-radius:8px;
  background:#f5f6f8; border:1px solid #e9ecf1; font-size:12px; }
.ix-chip i { font-style:normal; color:var(--muted); }
.ix-chip b { font-weight:600; font-variant-numeric:tabular-nums; }
@media (max-width:960px) {
  .ix-rank { grid-template-columns:36px 48px 1fr;
    grid-template-areas:"no logo main" "grow grow grow" "side side side" "chips chips chips"; }
  .ix-rank-grow { text-align:left; }
  .ix-rank-side { flex-direction:row; align-items:center; }
  .ix-state { max-width:100%; }
}

/* ---- 骨架屏（加载态）---- */
.ix-skelwrap { padding:6px 0 10px; }
.ix-skel-headline { height:34px; margin:8px 16px 4px; border-radius:8px; background:#f2f4f8; }
.ix-skel-row { display:grid; grid-template-columns:220px repeat(6, 1fr); gap:16px; padding:15px 16px;
  border-bottom:1px solid #f2f3f5; }
.ix-skel-row:last-child { border-bottom:none; }
.ix-skel { display:block; height:13px; border-radius:6px;
  background:linear-gradient(90deg, #eceef2 25%, #f6f7f9 40%, #eceef2 60%);
  background-size:400% 100%; animation:ix-shimmer 1.15s ease infinite; }
@keyframes ix-shimmer { 0% { background-position:100% 0; } 100% { background-position:0 0; } }
.ix-skelcard { grid-template-columns:34px 46px 1fr 136px !important; align-items:center; }
.ix-skelcard:hover { transform:none; box-shadow:0 1px 2px rgba(16,24,40,.04); border-color:var(--line); }
.ix-skel-badge { width:34px; height:34px; border-radius:10px; }
.ix-skel-logo { width:46px; height:46px; border-radius:12px; }
.ix-skel-lines { display:flex; flex-direction:column; gap:9px; }
.ix-skel-grow { height:44px; border-radius:12px; }

/* ---- 分页 ---- */
.ix-pager { display:flex; align-items:center; justify-content:center; gap:12px; padding:16px 0 4px; }
.ix-pageinfo { font-size:13px; color:var(--muted); }

/* ---- 「我的店铺收藏」：结果行上的收藏按钮（三态）----
   与产品表的「采集」按钮同一套做法（描边款、悬停才填充）——每行都有一个，
   实心会盖过页面上真正的主按钮。三态：
     未收藏（☆ 收藏）/ 已收藏（★ 已收藏，点击 = 移除，琥珀色）/ 不可用（—，见 .off）
   不可用态刻意**降级成不可点的「—」**而不是禁用的按钮：套餐不含这个能力时给一个按钮，
   只会换回一次必然失败的请求；而已收藏态故意用琥珀而不是绿色 —— 它不是「成功」，是一个可撤销的标记。 */
.ix-fav { height:26px; padding:0 12px; border:1px solid var(--line); border-radius:6px; background:#fff;
  color:var(--txt); font-size:12px; cursor:pointer; white-space:nowrap;
  transition:background .12s, color .12s, border-color .12s; }
.ix-fav:hover:not(:disabled) { border-color:var(--brand); color:var(--brand); background:#f7faff; }
.ix-fav.on { border-color:#f0b429; background:#fffbeb; color:#b45309; }
.ix-fav.on:hover:not(:disabled) { border-color:#dc2626; background:#fff5f5; color:#dc2626; }
.ix-fav.busy { border-color:var(--line); background:#f1f2f4; color:var(--muted); cursor:default; }
.ix-fav.off { border-color:transparent; background:transparent; color:var(--muted); cursor:default; padding:0 6px; }
.ix-fav-na { color:var(--muted); }

/* ---- 收藏页（我的店铺收藏）---- */
.fav-bar { display:flex; align-items:center; flex-wrap:wrap; gap:10px 14px; padding:14px 24px 0; }
.fav-count { font-size:14px; font-weight:600; font-variant-numeric:tabular-nums; }
/* 每日新增额度（服务端第二条限额，2026-10-08）：比「已收藏 x/N」弱一档 —— 它是限速，不是主计数。
   空值时 :empty 不占位（服务端没下发这条额度时整块收起，不留一个空档）。
   用满时转琥珀色，与「这是可等待恢复的限速、不是故障」的语义一致（不是红）。 */
.fav-daily { font-size:12.5px; color:var(--muted); font-variant-numeric:tabular-nums; }
.fav-daily:empty { display:none; }
.fav-daily.full { color:#b45309; }
.fav-plan { font-size:12.5px; color:var(--muted); }
.fav-msg { font-size:12.5px; }
.fav-msg.ok { color:#15803d; }
.fav-msg.err { color:#dc2626; }
/* 「额度已满」的持久提示位：空时不占位（:empty 无内边距），有内容时才在列表上方立一行 */
.fav-hint { margin:0; padding:0 24px; font-size:12.5px; color:#b45309; }
.fav-hint:empty { display:none; }
.fav-hint:not(:empty) { padding-top:10px; }

.fav-list { display:grid; gap:10px; }
.fav-row { display:grid; grid-template-columns:48px minmax(0,1fr) auto; align-items:center; gap:12px 14px;
  background:var(--card); border:1px solid var(--line); border-radius:12px; padding:14px 16px;
  box-shadow:0 1px 2px rgba(16,24,40,.04); transition:box-shadow .14s, border-color .14s; }
.fav-row:hover { border-color:#d7dfeb; box-shadow:0 5px 16px rgba(16,24,40,.07); }
.fav-main { min-width:0; }
.fav-name { display:flex; align-items:center; gap:6px; font-size:14px; font-weight:600; }
.fav-name a { min-width:0; max-width:520px; overflow:hidden; text-overflow:ellipsis;
  white-space:nowrap; color:var(--txt); text-decoration:none; }
.fav-name a:hover { color:var(--brand); text-decoration:underline; }
.fav-meta { margin-top:4px; color:var(--muted); font-size:12px; overflow:hidden;
  text-overflow:ellipsis; white-space:nowrap; }
.fav-chips { display:flex; flex-wrap:wrap; gap:7px; margin-top:9px; }
.fav-side { display:flex; flex-direction:column; align-items:flex-end; gap:7px; }
.fav-side .ix-mini { height:26px; }
.fav-del { color:#b91c1c; border-color:#fecaca; }
.fav-del:hover { border-color:#dc2626; color:#dc2626; background:#fff5f5; }
/* 「移除」是硬删除（服务端物理删行、快照一起没）⇒ 就地两段式确认；等待确认时给实心态 */
.fav-del.confirming { background:#dc2626; border-color:#dc2626; color:#fff; font-weight:600; }
.fav-del.confirming:hover { background:#b91c1c; border-color:#b91c1c; color:#fff; }
.fav-snap { font-size:11.5px; color:var(--muted); white-space:nowrap; }

/* 骨架行：结构与真行一致（48px 图 + 两行文字），这样数据到了不会跳一下。
   .fav-row 是 3 列 grid，骨架行只有两列，故单独覆盖列模板。 */
.fav-skel { grid-template-columns:48px minmax(0,1fr); }
.fav-skel-logo { width:46px; height:46px; border-radius:12px; }
.fav-skel-lines { display:flex; flex-direction:column; gap:9px; }
.fav-skel-w60 { width:60%; }

@media (max-width:720px) {
  .fav-row { grid-template-columns:48px minmax(0,1fr); }
  .fav-side { grid-column:1 / -1; flex-direction:row; align-items:center; }
}
/* ===== 设置页 ===== */
.settings-wrap { max-width:720px; margin:0 auto; padding:24px 24px 56px; }
.settings-head h2 { margin:0; font-size:20px; }
.settings-head .desc { margin:5px 0 20px; color:var(--muted); font-size:13px; }
.settings-card { background:#fff; border:1px solid var(--line); border-radius:12px; padding:20px 22px; margin-bottom:16px; box-shadow:0 1px 2px rgba(16,24,40,.04); }
.card-head { display:flex; align-items:flex-start; gap:12px; }
.card-head .step { flex:none; width:24px; height:24px; border-radius:8px; background:#eef4ff; color:var(--brand); font-size:13px; font-weight:700; display:flex; align-items:center; justify-content:center; margin-top:1px; }
.card-head-txt h3 { margin:0; font-size:15px; }
.card-head-txt .hint { margin:3px 0 0; }
.pill { margin-left:auto; flex:none; font-size:12px; line-height:1; padding:5px 11px; border-radius:999px; background:#f1f2f4; color:var(--muted); transition:background .15s, color .15s; }
.pill.ok { background:#ecfdf5; color:#047857; }
.pill.err { background:#fee2e2; color:#b91c1c; }
.pill.busy { background:#eef4ff; color:var(--brand); }
.pill.custom { background:#fef3c7; color:#b45309; }
.field-row { display:flex; gap:10px; margin-top:14px; }
.field-row input { flex:1; height:36px; font-family:ui-monospace,'Cascadia Mono',Consolas,monospace; }
.field-row button { height:36px; flex:none; }
.search-box { position:relative; margin-top:14px; }
.search-box svg { position:absolute; left:11px; top:50%; transform:translateY(-50%); color:var(--muted); pointer-events:none; }
.search-box input { width:100%; height:36px; padding-left:32px; }
.win-list { margin-top:10px; max-height:300px; overflow-y:auto; border:1px solid var(--line); border-radius:8px; background:#fff; }
.win-list::-webkit-scrollbar { width:8px; }
.win-list::-webkit-scrollbar-thumb { background:#d5d9e0; border-radius:4px; }
.win-empty { padding:26px 0; text-align:center; color:var(--muted); font-size:13px; }
.win-row { display:flex; align-items:center; gap:10px; padding:9px 12px; cursor:pointer; border-bottom:1px solid var(--line); transition:background .1s; user-select:none; }
.win-row:last-child { border-bottom:none; }
.win-row:hover { background:#f6f8ff; }
.win-row.active { background:#eef4ff; }
.win-row .radio { flex:none; width:15px; height:15px; border:2px solid #cbd5e1; border-radius:50%; position:relative; transition:border-color .12s; }
.win-row.active .radio { border-color:var(--brand); }
.win-row.active .radio::after { content:''; position:absolute; inset:2.5px; border-radius:50%; background:var(--brand); }
.win-name { font-size:13px; font-weight:500; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.win-remark { font-size:12px; color:var(--muted); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.win-run { margin-left:auto; flex:none; display:flex; align-items:center; gap:5px; font-size:11px; color:#047857; }
.win-run::before { content:''; width:6px; height:6px; border-radius:50%; background:#10b981; }
/* 浏览器模式单选（当前浏览器 / 比特浏览器） */
.mode-list { margin-top:14px; display:grid; gap:10px; }
.mode-row { display:flex; align-items:flex-start; gap:10px; padding:12px 14px; border:1px solid var(--line); border-radius:10px; cursor:pointer; transition:border-color .12s, background .12s; user-select:none; background:#fff; }
.mode-row:hover { background:#f6f8ff; }
.mode-row.active { border-color:var(--brand); background:#eef4ff; }
.mode-row .radio { flex:none; width:15px; height:15px; margin-top:2px; border:2px solid #cbd5e1; border-radius:50%; position:relative; transition:border-color .12s; }
.mode-row.active .radio { border-color:var(--brand); }
.mode-row.active .radio::after { content:''; position:absolute; inset:2.5px; border-radius:50%; background:var(--brand); }
.mode-txt { display:grid; gap:3px; min-width:0; }
.mode-txt b { font-size:13.5px; }
.mode-txt span { font-size:12.5px; color:var(--muted); line-height:1.5; }
.mode-tag { margin-left:auto; flex:none; align-self:center; font-size:11px; line-height:1; padding:4px 8px; border-radius:999px; background:#ecfdf5; color:#047857; }
.settings-foot { display:flex; align-items:center; gap:12px; }
.btn-save { margin-left:auto; height:36px; padding:0 26px; }
.msg { font-size:13px; margin:8px 0 0; color:var(--muted); }
.msg.ok { color:#047857; } .msg.err { color:#b91c1c; }
.msg.warn { color:#b45309; }
input:focus-visible, select:focus-visible { outline:none; border-color:var(--brand); box-shadow:0 0 0 3px rgba(37,99,235,.15); }
.check { display:flex; align-items:center; gap:4px; font-size:13px; color:var(--muted); }
.check input { width:14px; height:14px; }
.toolbar { display:flex; align-items:center; gap:14px; padding:10px 24px; background:var(--card); border-bottom:1px solid var(--line); }
.pager { display:flex; align-items:center; gap:12px; padding:8px 24px; background:var(--card); border-bottom:1px solid var(--line); font-size:13px; color:var(--muted); }
.page-btn { height:28px; padding:0 12px; border:1px solid var(--line); border-radius:6px; background:var(--bg); color:var(--text); font-size:13px; cursor:pointer; }
.page-btn:hover:not(:disabled) { border-color:var(--brand); color:var(--brand); }
.page-btn:disabled { opacity:.4; cursor:default; }
.pager select { height:28px; border:1px solid var(--line); border-radius:6px; background:var(--bg); color:var(--text); font-size:13px; padding:0 4px; }
.selinfo { font-size:13px; color:var(--muted); }
.toolbar .msg.ok { color:#047857; } .toolbar .msg.err { color:#b91c1c; }
/* 批量补全逐件结果写在 submit_msg 里（多行）：保留换行，并限高可滚，避免长列表把工具条撑高 */
.toolbar .msg { white-space:pre-line; display:block; max-height:96px; overflow:auto; }
/* 工具条里的中性按钮（补全核对 / 发送差异），与 primary/danger 同一高度 */
.toolbar .enr-b { height:32px; padding:0 14px; border:1px solid var(--line); border-radius:6px; background:var(--bg); color:var(--text); font-size:13px; cursor:pointer; }
.toolbar .enr-b:hover:not(:disabled) { border-color:var(--brand); color:var(--brand); }
.toolbar .enr-b:disabled { opacity:.4; cursor:default; }
.cb { position:absolute; top:8px; right:34px; z-index:3; width:18px; height:18px; accent-color:var(--brand); }
.card .del { position:absolute; top:8px; right:8px; z-index:3; width:24px; height:24px; border:none; border-radius:6px; background:rgba(0,0,0,.42); color:#fff; font-size:13px; line-height:1; cursor:pointer; }
.card .del:hover { background:#dc2626; }
.card-click { display:block; text-decoration:none; color:inherit; }
.card.sel { border-color:var(--brand); box-shadow:0 0 0 2px rgba(37,99,235,.35); }
main { padding:20px 24px; }
#grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(220px,1fr)); gap:16px; }
.card { position:relative; background:var(--card); border:1px solid var(--line); border-radius:10px; overflow:hidden; transition:box-shadow .15s; cursor:pointer; }
.card:hover { box-shadow:0 4px 16px rgba(0,0,0,.08); }
.thumb { position:relative; width:100%; aspect-ratio:1/1; background:#f0f1f3; display:flex; align-items:center; justify-content:center; overflow:hidden; }
.thumb img { width:100%; height:100%; object-fit:cover; }
.badge { position:absolute; top:8px; left:8px; z-index:2; font-size:11px; line-height:1; padding:3px 6px; border-radius:4px; color:#fff; }
.b-new { background:#10b981; } .b-page { background:#f59e0b; } .b-detail { background:#3b82f6; }
/* 平台角标：与「详情/列表」角标同处缩略图顶部，靠右放置避免重叠 */
.b-plat { left:auto; right:8px; background:#64748b; }
.b-plat-1688 { background:#ff6a00; }
.b-plat-aliexpress { background:#e62e04; }
/* 店铺卡片里的平台角标：跟着店名行内联排布，不参与绝对定位 */
.store-plat { flex:none; font-size:11px; line-height:1; padding:3px 6px; border-radius:4px; color:#fff; background:#64748b; }
.store-plat-1688 { background:#ff6a00; }
.store-plat-aliexpress { background:#e62e04; }
.info { padding:10px 12px 12px; }
.title { font-size:13px; line-height:1.4; height:2.8em; overflow:hidden; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; margin-bottom:6px; }
.meta { display:flex; align-items:baseline; gap:8px; margin-bottom:4px; }
.price { color:#e0352b; font-weight:700; font-size:16px; }
.sales { color:var(--muted); font-size:12px; }
.shop { color:var(--muted); font-size:12px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.foot { display:flex; align-items:center; justify-content:space-between; margin-top:8px; font-size:11px; color:var(--muted); }
.st { font-size:11px; padding:2px 6px; border-radius:4px; }
.st-un { background:#eef2ff; color:#4f46e5; } .st-sub { background:#ecfdf5; color:#047857; } .st-rep { background:#fef3c7; color:#b45309; } .st-fail { background:#fee2e2; color:#b91c1c; }
#empty { text-align:center; color:var(--muted); padding:60px 0; display:none; }
.auth { align-items:center; gap:12px; margin:14px 24px 0; padding:12px 16px; border:1px solid #f0c4bc; background:#fdf1f0; border-radius:8px; color:#a32d2d; font-size:13px; }
.auth button { margin-left:auto; padding:7px 14px; border:none; border-radius:6px; background:#185fa5; color:#fff; font-size:12.5px; cursor:pointer; }

/* ===== 店铺页 =====
   多行卡片 + 跨卡片逐行对齐：每张卡片的行序、行数、行高完全恒定，行高由
   .store-card 的 grid-template-rows 写死（内容再多也不会把某一行撑高），
   某字段无数据时用 .slot-void 留空占位，不删行、不并格 —— 于是所有卡片的
   第 1 行都在同一水平线上、第 2 行也在同一水平线上，依此类推。 */
#store_grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(400px,1fr)); gap:16px; align-items:stretch; }
/* 6 行固定行高：店名 22 / 公司 18 / 统计 26 / 首采·最近 18 / 操作 32 / 提示 16 */
.store-card { display:grid; grid-template-rows:22px 18px 26px 18px 32px 16px; row-gap:8px;
  background:var(--card); border:1px solid var(--line); border-radius:10px; padding:14px 16px;
  box-shadow:0 1px 2px rgba(16,24,40,.04); transition:box-shadow .15s; }
.store-card:hover { box-shadow:0 4px 14px rgba(0,0,0,.07); }
.store-card > * { min-width:0; }
/* 第 1 行：店名（长名省略号）+「↗」；无链接时用同宽不可见占位，右侧不塌陷 */
.store-head { display:flex; align-items:center; gap:8px; overflow:hidden; }
.store-name { flex:1 1 auto; min-width:0; font-size:14px; font-weight:600; line-height:20px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.store-link { flex:none; font-size:12px; color:var(--brand); text-decoration:none; }
.store-link:hover { text-decoration:underline; }
/* 第 2 行：公司 —— 无数据也保留整行高度，下面各行不会上移 */
.store-company { font-size:12.5px; color:var(--muted); line-height:18px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
/* 第 3 行：统计固定 3 等宽槽（总数 / 未提交 / 已提交），永远同一行、同一顺序，计数为 0 也保留 */
.store-stats { display:grid; grid-template-columns:repeat(3,110px); justify-content:start; gap:8px; align-items:center; }
.store-chip { font-size:11px; padding:3px 5px; border-radius:999px; background:#f1f2f4; color:var(--muted); text-align:center; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.store-chip.sc-un { background:#eef2ff; color:#4f46e5; }
.store-chip.sc-sub { background:#ecfdf5; color:#047857; }
/* 第 4 行：首采靠左、最近靠右（右边缘贴齐卡片右侧，所有卡片同一竖直位置） */
.store-foot { display:grid; grid-template-columns:1fr auto; gap:8px; align-items:center; }
.store-date { font-size:11.5px; color:var(--muted); line-height:16px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.store-date:last-child { text-align:right; }
.store-date .sc-label { color:#9aa1ad; margin-right:5px; }
/* 空槽位占位：不可见但占据原位，保证布局不压缩 */
.slot-void { display:inline-block; visibility:hidden; }
/* 第 5 行：操作按钮（高度固定，与提示行互不挤压） */
.store-actions { display:flex; align-items:center; gap:8px; overflow:hidden; }
.store-actions .primary, .store-actions .go { flex:none; height:30px; padding:0 14px; font-size:12.5px; border-radius:6px; cursor:pointer; }
.store-actions .go { border:1px solid var(--brand); background:#eef4ff; color:var(--brand); }
.store-actions .go:hover { background:#dbeafe; }
.store-actions .go:disabled { opacity:.45; cursor:default; }
/* 第 6 行：上架进度/结果提示（单行省略号，悬停看全文；空态也占住行高） */
.store-msg { font-size:12px; color:var(--muted); line-height:16px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.store-msg.err { color:#b91c1c; }
.store-msg.ok { color:#047857; }
#store_empty { text-align:center; color:var(--muted); padding:60px 0; }

/* ===== 批量提交进度（浮动面板，采集台 / 店铺页共用）=====
   提交是长请求，整批结束前 /submit 不返回，进度另经 GET /submit/progress 轮询得到。
   做成浮动面板而非内联到工具栏：两个入口（采集台、店铺页）共用一份进度，
   且不会挤动店铺页卡片的固定行高布局。 */
.sprog { position:fixed; right:20px; bottom:20px; z-index:20; width:420px; max-width:calc(100vw - 40px);
  padding:12px 14px; background:#fff; border:1px solid var(--line); border-radius:12px;
  box-shadow:0 8px 28px rgba(16,24,40,.16); font-size:12.5px; color:var(--txt); }
.sprog-head { display:flex; align-items:center; gap:8px; margin-bottom:9px; }
.sprog-dot { flex:none; width:8px; height:8px; border-radius:50%; background:var(--brand); animation:sprog-pulse 1.2s ease-in-out infinite; }
.sprog.done .sprog-dot { background:#10b981; animation:none; }
.sprog.err .sprog-dot { background:#dc2626; animation:none; }
.sprog.stop .sprog-dot { background:#f59e0b; animation:none; }
@keyframes sprog-pulse { 0%,100% { opacity:1 } 50% { opacity:.25 } }
.sprog-title { flex:1 1 auto; min-width:0; font-size:13px; font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.sprog-x { flex:none; width:20px; height:20px; padding:0; border:none; border-radius:6px; background:transparent; color:var(--muted); font-size:12px; line-height:1; cursor:pointer; }
.sprog-x:hover { background:#f1f2f4; color:var(--txt); }
/* 「结束」按钮：只在 running 时由 JS 显示（display 由 renderProgress 控制），
   高度与标题行对齐，避免出现/消失时把下面的进度条推来推去 */
.sprog-stop { flex:none; height:20px; padding:0 9px; border:1px solid #f0b8b8; border-radius:6px;
  background:#fff5f5; color:#b91c1c; font-size:12px; line-height:18px; cursor:pointer; }
.sprog-stop:hover { background:#ffe8e8; border-color:#e79a9a; }
.sprog-stop:disabled { background:#f4f2f2; border-color:#e2dede; color:#a8a2a2; cursor:default; }
.sprog-bar { position:relative; height:6px; border-radius:999px; background:#eef0f3; overflow:hidden; }
.sprog-bar i { display:block; height:100%; width:0; border-radius:999px; background:var(--brand); transition:width .3s ease; }
.sprog.done .sprog-bar i { background:#10b981; }
.sprog.err .sprog-bar i { background:#dc2626; }
.sprog.stop .sprog-bar i { background:#f59e0b; }
/* 解析阶段（含定位店小秘窗口）：总数已定但一件未完成，用条纹流动表示"在动"，避免 0% 卡住观感 */
.sprog-bar i.indet { background-image:linear-gradient(45deg,rgba(255,255,255,.4) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.4) 50%,rgba(255,255,255,.4) 75%,transparent 75%);
  background-size:14px 14px; animation:sprog-move .8s linear infinite; }
@keyframes sprog-move { from { background-position:0 0 } to { background-position:14px 0 } }
.sprog-nums { display:flex; align-items:center; gap:6px; flex-wrap:wrap; margin-top:9px; font-size:12px; color:var(--muted); }
.sprog-nums b { color:var(--txt); font-size:13px; }
.sprog-nums span { padding:2px 7px; border-radius:999px; background:#f1f2f4; white-space:nowrap; }
.sprog-nums .sp-ok { background:#ecfdf5; color:#047857; }
.sprog-nums .sp-rep { background:#fef3c7; color:#b45309; }
.sprog-nums .sp-bad { background:#fee2e2; color:#b91c1c; }
.sprog-line { margin-top:6px; font-size:12px; color:var(--muted); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.sprog-line:empty { display:none; }
.sprog-time { color:#9aa1ad; font-size:11.5px; }

/* ---- 店铺总览页（§9 落点唯一在 renderFavOverview / ixRenderOverview）----
   五段（2026-10-08 热销挪到第二位）：指标带(B) + 热销商品(E) + 趋势网格(C) + 国家/品类两列(D)。
   🔴 D 区两栏**统一用横向条形**（方案 §4：饼图 >6 扇区即不可读）；
      条宽由 JS 算成内联 width%（相对本组最大值），这里只负责外观。
   🔴 **限宽居中**（2026-10-07 布局收紧）：实现对齐了 mockup（store-overview-mock.html 的
      max-width:1200 居中）——曾写成全宽竖排五段，1600px 窗口下首屏只露 2.5 张趋势图、
      整页 ~1900px 长。fav-bar 与正文同一限宽，左右缘对齐。
   ⚠️ 2026-10-08 把限宽从 1280 放宽到 **1440**、段间距 14→18：用户反馈「布局有一些紧凑」。
      两处是一套（一起放宽才不出现「变宽了但段间还是挤」）。 */
#fav_pane_overview .fav-bar, #fav_ov_body { max-width:1440px; margin:0 auto; }
.ix-ov { display:flex; flex-direction:column; gap:18px; }
.ix-ov-hd { display:flex; align-items:center; gap:10px; padding:2px 0; }
.ix-ov-name { flex:1 1 auto; min-width:0; font-size:14px; font-weight:600; color:var(--txt);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
/* D 区两栏（国家 | 品类）：等宽对分，flex-wrap 兜窄屏折行 */
.ix-ov-row { display:flex; gap:18px; flex-wrap:wrap; }
.ix-ov-row > .ix-ov-sec { flex:1 1 300px; min-width:260px; }
.ix-ov-sec { border:1px solid var(--line); border-radius:10px; padding:14px 16px; background:var(--card); }
/* E 区热销商品**独占整行**（2026-10-08）：它有 48px 缩略图 + 序号 + 标题 + 数值，
   挤在三列布局里标题只剩几十 px。与 .ix-ov-row 搭配才有意义，单独用时它只是个标记。 */
.ix-ov-sec-top { width:100%; }
.ix-ov-title { font-size:13px; font-weight:600; color:var(--txt); margin-bottom:12px; }
/* 带「数据日期」的区标题（2026-10-08 加在指标带上）：左侧标题、右侧口径小字。
   只给这一个区加 flex 容器，**不动 .ix-ov-title 本身** —— 其它区（趋势/分布/热销）的标题
   是纯文本，改了会影响它们的基线对齐。 */
.ix-ov-title-hd { display:flex; align-items:baseline; gap:8px; }
.ix-ov-title-hd > span:first-child { flex:0 0 auto; }
.ix-ov-metric-date { flex:1 1 auto; min-width:0; text-align:right; font-size:11.5px;
  font-weight:400; color:var(--muted); }
/* E 区热销商品的**两档时间口径**（近 180 天 / 近 7 天，2026-10-08）。标题行沿用 .ix-ov-title-hd：
   控件紧跟区名（不是靠右）—— 与指标带「数据日期」同一套阅读顺序；降级说明靠右，与日期小字同位。
   ⚠️ 档位键序在 app.js 的 IX_OV_TOP_WINDOWS，这里只管外观。 */
.ix-ov-win-tabs { display:inline-flex; gap:4px; }
.ix-ov-win { padding:2px 10px; font-size:11.5px; line-height:18px; border:1px solid var(--line);
  border-radius:6px; background:transparent; color:var(--muted); cursor:pointer; }
.ix-ov-win:hover { border-color:var(--brand); color:var(--brand); }
.ix-ov-win.on { border-color:var(--brand); color:var(--brand); background:var(--bg-soft); font-weight:600; }
/* 服务端还没下发该档 ⇒ 弱化（**不隐藏**：隐藏会让人以为这个功能不存在） */
.ix-ov-win.empty { color:#b4b2a9; }
.ix-ov-win.empty:hover { border-color:var(--line); color:var(--muted); }
/* 降级说明：用琥珀而不是红/绿（后两者在本页已被「涨红跌绿」占用，别混语义） */
.ix-ov-top-note { flex:1 1 auto; min-width:0; text-align:right; font-size:11.5px;
  font-weight:400; color:#a16207; }
.ix-ov-body { min-height:30px; }
/* B 区指标带：6 张 KPI 卡（顺序由 app.js 的 IX_OV_METRICS 决定，这里只管外观）。
   auto-fit + minmax 让窄屏自动折行，不写死列数。 */
.ix-ov-kpis { display:grid; grid-template-columns:repeat(auto-fit, minmax(118px, 1fr)); gap:12px; }
.ix-ov-kpi { display:flex; flex-direction:column; gap:4px; padding:11px 13px; border-radius:8px;
  background:var(--bg-soft); border:1px solid var(--line); min-width:0; }
.ix-ov-kpi-label { font-size:11.5px; color:var(--muted); white-space:nowrap; }
.ix-ov-kpi-val { font-size:18px; font-weight:600; color:var(--txt); line-height:1.25;
  font-variant-numeric:tabular-nums; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
/* 增量徽标：**涨红跌绿**（中文习惯）。首日/无基线走灰，视觉上明确「不是 0 增长」。 */
.ix-ov-kpi-delta { font-size:11.5px; color:var(--muted); font-variant-numeric:tabular-nums; }
.ix-ov-kpi-delta.up { color:var(--up); }
.ix-ov-kpi-delta.down { color:var(--down); }
.ix-ov-kpi-delta.first { color:var(--muted); }
/* 中性「—」：该项**有基线**但数据源不给日增序列（好评率 / 平均价，2026-10-08 实测）。
   与 .first（真首日）视觉一致、语义不同 —— 文案与 title 才是区分点，这里不另起颜色。 */
.ix-ov-kpi-delta.none { color:var(--muted); }
.ix-ov-note { margin-top:9px; font-size:11.5px; color:var(--muted); line-height:1.5; }
/* C 区趋势：4 图**网格铺开**（宽屏 4 列、窄屏自动 2/1 列）。
   🔴 2026-10-08 从 canvas 换成 **SVG**：位图在 125%/150% 系统缩放下必然糊，
      而趋势区恰恰是「看走势」的地方，糊了等于不可用。矢量 + DOM 文字后任意缩放都锐。
   .ix-ov-trend-svg 的宽高由 JS 按 plot 容器实际宽度算好后写进属性（1 CSS px = 1 SVG 单位），
   这里**只兜一个初值**，别在这里写死 —— 写死会被 SVG 的固有宽高反过来撑坏 grid。 */
.ix-ov-trends { display:grid; grid-template-columns:repeat(auto-fit, minmax(230px, 1fr)); gap:14px 16px; }
.ix-ov-trend { min-width:0; }
.ix-ov-trend-hd { display:flex; align-items:baseline; gap:7px; margin-bottom:2px; }
.ix-ov-trend-dot { flex:none; width:8px; height:8px; border-radius:50%; align-self:center; }
.ix-ov-trend-name { font-size:12px; color:var(--muted); }
.ix-ov-trend-last { margin-left:auto; font-size:15px; font-weight:600; color:var(--txt);
  font-variant-numeric:tabular-nums; line-height:1.2; }
/* y轴刻度（lo / hi）：**独立成行**，所以与图不可能重叠。
   画在 SVG 里的话，只要 y轴从「贴0」改成「自适应」，刻度就会被折线压住。 */
.ix-ov-trend-scale { display:flex; justify-content:space-between; margin:1px 0 0;
  font-size:10px; color:var(--muted); font-variant-numeric:tabular-nums; }
.ix-ov-trend-plot { position:relative; height:132px; }
.ix-ov-trend-svg { display:block; overflow:visible; }
.ix-ov-trend-grid { stroke:#eef0f3; stroke-width:1; }
.ix-ov-trend-area { opacity:.08; }
.ix-ov-trend-line { fill:none; stroke-width:1.8; stroke-linejoin:round; stroke-linecap:round; }
/* 数据点：实心序列色 + 白描边。fill 由 JS 按序列色内联给（一个 CSS 类没法逐序列变色），
   这里只给描边 —— 白圈边是让它在浅色面积上也能看见。 */
.ix-ov-trend-dot-pt { stroke:#fff; stroke-width:1; }
/* 光晕 + 实心末点：视线自然落在最新值上（也省掉逐点画圆点后的视觉噪音） */
.ix-ov-trend-halo { opacity:.18; }
.ix-ov-trend-end { stroke:#fff; stroke-width:1.5; }
/* 首尾日期：DOM 文字（不是画在图里的），两端对齐 + 留一点内边距，避免贴到卡片边 */
.ix-ov-trend-x { display:flex; justify-content:space-between; margin-top:1px;
  font-size:10.5px; color:var(--muted); font-variant-numeric:tabular-nums; }
/* 悬浮读数浮层：挂 document.body（position:fixed 坐标由 JS 给），不受任何祖先 overflow 裁切 */
.ix-ov-trend-tip { position:fixed; z-index:60; padding:3px 8px; border-radius:6px; background:var(--card);
  border:1px solid var(--line); box-shadow:0 2px 8px rgba(0,0,0,.12); font-size:11.5px;
  font-weight:600; font-variant-numeric:tabular-nums; pointer-events:none; display:none;
  white-space:nowrap; }
.ix-ov-bars { display:flex; flex-direction:column; gap:9px; }
.ix-ov-bar-row { display:flex; align-items:center; gap:10px; font-size:12px; }
/* 名字列给足宽度：品类名是「父 / 子」两级拼接（如「手机配件 / 手机壳」），
   88px 会把稍长的一律截成「手机配件 / 手…」——那正好是最需要区分的那一段。 */
.ix-ov-bar-name { flex:none; width:124px; color:var(--txt); white-space:nowrap; overflow:hidden;
  text-overflow:ellipsis; }
.ix-ov-bar-track { flex:1 1 auto; min-width:40px; height:12px; border-radius:999px;
  background:#eef0f3; overflow:hidden; }
.ix-ov-bar-fill { display:block; height:100%; border-radius:999px; background:var(--brand); }
.ix-ov-bar-val { flex:none; width:96px; text-align:right; color:var(--muted);
  white-space:nowrap; font-variant-numeric:tabular-nums; }
/* E 区热销商品：2026-10-12 起**从卡片网格改成表格 + 分页**（用户要求），
   原来那套 `.ix-ov-top / -row / -img / -main / -rank / -title / -val` 卡片样式已随之一并删除 ——
   别留着一堆没人用的死样式（它们会让人以为现役版式还是卡片）。
   表格版式整套复用产品搜索那一套（粘性首列 .ix-c-product、数字右列 .ix-num、
   右侧吸附的动作列 .ix-c-act、缩略图 .ix-thumb 与其悬浮放大浮层）—— 复用而不是另起一套，
   是因为「同一份数据在两处以不同版式呈现」最容易悄悄跑偏。
   这里只补两样上面没有的：序号徽标、分页条。 */
.ix-ov-rank { display:inline-block; min-width:18px; margin-right:6px; padding:1px 5px;
  border-radius:5px; background:var(--bg-soft); color:var(--muted); font-size:11px; font-weight:700;
  text-align:center; font-variant-numeric:tabular-nums; }
/* 🔴 首列标题要**随列宽铺开**（2026-10-13 用户反馈：列很宽、标题却折行）。
   根因是产品搜索那套里 `.ix-prodname` 写死了 `width:330px` + `-webkit-line-clamp:2` ——
   表格把余量都分给了首列，标题却在 330px 处就换行，右边一大片空白。
   这里做三件事（只作用于总览热销表，不动产品搜索表）：
     ① `table-layout:fixed`：列宽改由 CSS 决定（首列吃余量、数字列与采集列定宽），
        否则 `nowrap` 的长标题会把首列撑到内容宽度、把整张表顶出横向滚动；
     ② 打通 flex 链（`.ix-prodcell` 满宽 + `.ix-prodinfo` 可伸缩），标题才有「列宽」可用；
     ③ 标题改**单行省略**：热销榜一行一眼扫过去，折行会把行高不齐、也不好比较。
   🔴 **定宽账（2026-10-15 加「首次发现」列后为 5 个数字列）**：
      `5 × 120px（价格/销量/评分/评价数/首次发现）+ 96px（采集）= 696px`，
      余下全部归首列 ⇒ 1100px 容器下首列仍有 ~404px，**不加横向滚动条**。
      ⚠️ 以后再加列/改宽**必须重算这笔账**：它是 `table-layout:fixed` 的，超了就是整表横向滚动，
      而 `tools/probe-overview-layout.js` 会在真浏览器里量 1280 / 1100 两档的 `scrollWidth` 钉住它。
   ⚠️ 「首次发现」列刻意**复用 `.ix-num`**（而不是另起一个类）：它的内容（`YYYY-MM-DD` / 「—」）
      同样是定长右对齐文本，复用能让列宽随其它数字列一起被上面这笔账管住 ——
      另起一个类就等于多出一处「改宽时容易漏掉」的地方。 */
.ix-table-ovtop { table-layout:fixed; }
.ix-table-ovtop th.ix-num { width:120px; }
.ix-table-ovtop th.ix-c-act { width:96px; }   /* 覆盖基类的 width:1%（auto 布局下是「尽量窄」，fixed 下会真变成 1%） */
.ix-table-ovtop .ix-prodcell { width:100%; }
.ix-table-ovtop .ix-prodinfo { flex:1 1 auto; }
.ix-table-ovtop .ix-prodname { display:block; width:auto; white-space:nowrap; overflow:hidden;
  text-overflow:ellipsis; }
.ix-ov-top-pager { display:flex; align-items:center; justify-content:flex-end; gap:10px; margin-top:10px; }
.ix-ov-pg-info { color:var(--muted); font-size:11.5px; font-variant-numeric:tabular-nums; }
.ix-empty-sm { padding:10px 0; color:var(--muted); font-size:12.5px; }
.ix-btn-sm { padding:3px 10px; font-size:12px; }

/* 「标签」列 + 列说明的「!」徽标（2026-10-12，用户要求）。
   徽标必须是**可悬停的独立元素**：把 title 挂在 <th> 上时，鼠标落在表头任意位置都会弹出说明，
   而用户要的是「看到这个符号才知道这里有说明」。用 <span> 而不是 <i>，并用 cursor:help 提示可悬停。 */
.ix-c-tag { white-space:nowrap; color:var(--txt); }
.ix-th-help { display:inline-block; min-width:14px; height:14px; margin-left:5px; padding:0 3px;
  border-radius:7px; background:#e7ecf5; color:#4b5563; font-size:11px; line-height:14px;
  font-weight:700; text-align:center; cursor:help; vertical-align:1px; }

/* `.ix-in-lg`（产品搜索「链接标题搜索」的 300px 加长版）2026-10-13 已删：
   该输入框改为「独占整行」后宽度由 `.ix-f-grow` 给 100%，这个固定宽度成了死样式。 */

/* ===== 店小秘补全（卡片按钮 + 差异计划弹窗，2026-10-13） ===== */
.card .foot .enr {
  border:1px solid #d6deeb; background:#fff; color:#4b5563; border-radius:4px;
  font-size:11px; padding:2px 7px; cursor:pointer; }
.card .foot .enr:hover { border-color:#8fa3c8; color:#2b3a55; }
.enr-card { width:min(480px, 100%); }
.enr-status { margin:10px 0 6px; font-size:13px; line-height:1.55; color:var(--txt); white-space:pre-wrap; word-break:break-all; }
.enr-plan { margin:4px 0 8px; max-height:220px; overflow:auto; }
.enr-field { padding:7px 9px; border-radius:7px; background:var(--muted-bg, #f3f5f9); margin-bottom:6px; font-size:12px; }
.enr-fill { background:#ecfdf5; }
.enr-conflict { background:#fef3c7; }
.enr-skip { background:#f3f5f9; }
.enr-field-head { font-weight:700; color:#2b3a55; }
.enr-field-reason { margin-top:3px; color:var(--muted, #6b7280); line-height:1.5; word-break:break-all; }
/* D6 查回比对明细：逐字段状态 */
.enr-vhead { margin:8px 0 4px; font-weight:700; font-size:12px; color:#2b3a55; }
.enr-vitem { padding:3px 8px; margin-bottom:3px; border-radius:6px; font-size:12px; line-height:1.5; background:#f3f5f9;
  color:var(--muted, #6b7280); word-break:break-all; }
.enr-v-match { background:#ecfdf5; color:#047857; }
.enr-v-equivalent { background:#fef9c3; color:#92400e; }
.enr-v-mismatch { background:#fef2f2; color:#b91c1c; }

/* ════════ 网站模式登录/注册门（site_auth_gate，W1）══════════════════════════
   全屏遮罩 + 居中卡片；两页签（登录/注册）。「加入企业」暂不上网站（用户要求）。
   字段形态照抄桌面客户端 Home.razor（企业名称/租户标识/登录账号/密码/昵称/验证码）。 */
#site_auth_gate {
  position: fixed; inset: 0; z-index: 200;
  display: flex; align-items: flex-start; justify-content: center;
  padding: 48px 16px; overflow-y: auto;
  background: linear-gradient(160deg, #eef2f8 0%, #e7edf6 55%, #dfe7f2 100%);
}
.gate-card {
  width: 100%; max-width: 400px; background: #fff;
  border: 1px solid #e3e8f0; border-radius: 14px;
  box-shadow: 0 12px 40px rgba(23, 43, 77, .10);
  padding: 30px 30px 24px;
}
.gate-brand { display: flex; flex-direction: column; align-items: center; margin-bottom: 18px; }
.gate-name { font-size: 21px; font-weight: 700; color: #17233d; }
.gate-sub { font-size: 13px; color: #6b7280; margin-top: 5px; }
.gate-notice {
  margin: 0 0 12px; padding: 8px 12px; border-radius: 8px;
  font-size: 13px; background: #fff7ed; color: #9a3412; border: 1px solid #fed7aa;
}
.gate-tabs { display: flex; gap: 6px; margin-bottom: 16px; border-bottom: 1px solid #eef0f4; }
.gtab {
  flex: 1; padding: 9px 0; font-size: 14px; color: #6b7280;
  background: none; border: none; border-bottom: 2px solid transparent;
  cursor: pointer;
}
.gtab.on { color: #17233d; font-weight: 600; border-bottom-color: #2f6fed; }
.gate-card form { display: flex; flex-direction: column; }
.gfld { font-size: 12px; color: #6b7280; margin: 10px 0 5px; }
.gfld .opt { font-style: normal; color: #9ca3af; }
.gate-card input {
  height: 38px; padding: 0 12px; font-size: 14px;
  border: 1px solid #d9dfe8; border-radius: 8px; outline: none; width: 100%;
  box-sizing: border-box; color: #17233d; background: #fff;
}
.gate-card input:focus { border-color: #2f6fed; }
.gbtn {
  margin-top: 16px; height: 40px; border: none; border-radius: 8px;
  font-size: 15px; font-weight: 600; color: #fff; background: #2f6fed; cursor: pointer;
}
.gbtn:hover { background: #265cd1; }
.gbtn:disabled { opacity: .6; cursor: default; }
.ghint { font-size: 12px; color: #9ca3af; margin: 10px 0 0; line-height: 1.6; }
.ghint.intro { color: #6b7280; }
.cap-row { display: flex; gap: 10px; align-items: center; }
.cap-row input { flex: 1; }
.cap-img {
  height: 38px; min-width: 96px; border-radius: 8px; border: 1px solid #d9dfe8;
  background: #f7f9fc; cursor: pointer; object-fit: cover;
}
.gmsg { min-height: 18px; font-size: 13px; margin: 12px 0 0; text-align: center; word-break: break-all; }
.gmsg.ok { color: #047857; }
.gmsg.err { color: #b91c1c; }
