/* =============================================================================
   cases.css — 落地案例（瀑布流 + 筛选 + 大图）
   -----------------------------------------------------------------------------
   与 app.css 共用变量（--brand / --bg / --text 等），所以主题切换是自动的。

   布局选型说明：瀑布流用 CSS `columns` 而不是 JS 计算绝对定位。
   理由：
     1. 图片宽高在入库时就存了，用 aspect-ratio 预留位置，加载时不跳动；
     2. JS 绝对定位要在 resize、图片加载、字体变化时全部重算，
        手机上滚动时的抖动几乎无法避免；
     3. columns 是浏览器原生布局，性能与正确性都由引擎保证。
   代价：条目按"列优先"排列（第 1 条在最左列顶部，第 2 条在它下面），
   对照片墙来说不影响观感。
   ============================================================================= */

/* ---------------------------------------------------------------- 页面骨架 */
/*
 * ⚠️ 这两条 overflow/height 覆盖**不能删**，它们修的是一个真实且极隐蔽的 bug：
 *
 *   cases.html 同时引了 app.css（聊天页样式）。app.css 里有
 *       html, body { height: 100% }
 *       body { overflow: hidden }     ← "只让消息区滚动，避免整页被键盘顶动"
 *   那是给聊天页的**固定布局**写的（整页不滚，只有消息区滚）。
 *   而画廊是**长页面**，必须整页滚动 —— 继承过来之后用户完全滚不动。
 *
 *   为什么自动化测试查不出来：CSS 规范里，当 html 的 overflow 是 visible 时，
 *   body 的 overflow 会**传播到视口**，于是视口变成不可滚动；
 *   但 `window.scrollTo()` 这类**程序化滚动依然有效**。
 *   所以测试里 scrollY 能正常变化、断言全绿，而用户的滚轮与触摸板毫无反应。
 *   修完之后，验证必须用**真实滚轮事件**（Input.dispatchMouseEvent type=mouseWheel），
 *   不能用 window.scrollTo —— 后者永远会"通过"。
 *
 *   同时要解除 html/body 的 height:100%：那是把高度锁死在视口高度，
 *   内容再长也不会撑开文档，滚动条根本不会出现。
 *   用 .cases-html 类而不是 :has()，避免依赖较新的选择器支持。
 */
html.cases-html {
  height: auto;
  overflow-y: auto;
}
.cases-body {
  margin: 0;
  height: auto;
  min-height: 100%;
  overflow: visible;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB",
               "Microsoft YaHei", "Segoe UI", Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
  padding-bottom: env(safe-area-inset-bottom);
}

.cases-topbar {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 8px;
  height: 52px;
  padding: 0 6px;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: saturate(180%) blur(12px);
  -webkit-backdrop-filter: saturate(180%) blur(12px);
  border-bottom: 1px solid var(--line);
}

.cases-back,
.cases-search-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border: 0;
  border-radius: 12px;
  background: transparent;
  color: var(--text);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.cases-back:active,
.cases-search-btn:active { background: var(--hover); }

.cases-title {
  flex: 1;
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  text-align: center;
}

/* 管理入口（仅管理员可见）。
   做成醒目的实心药丸而不是又一个图标按钮 —— 它是"要去干活"的动作，
   而搜索是"要去找东西"，两者视觉上不该一样。 */
.cases-manage-btn {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  flex: none;
  height: 30px;
  padding: 0 11px;
  margin-right: 2px;
  border-radius: 999px;
  background: var(--brand);
  color: #fff;
  font-size: 12.5px;
  font-weight: 600;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}
.cases-manage-btn:active { filter: brightness(.9); }
.cases-manage-btn[hidden] { display: none !important; }

/* 空态里的上传按钮 */
.cases-empty .cases-btn.primary {
  display: inline-flex;
  align-items: center;
  height: 38px;
  padding: 0 20px;
  margin-top: 4px;
  border: 1px solid var(--brand);
  border-radius: 19px;
  background: var(--brand);
  color: #fff;
  font-size: 14px;
  text-decoration: none;
}
.cases-empty .cases-btn.primary[hidden] { display: none !important; }

/* 右上角的登录身份角标。
   三种形态，颜色区分，一眼能看出当前能不能编辑：
     · 管理员 · 可编辑   → 绿
     · 管理员 · 请用电脑 → 橙（触屏设备，标签不可点删）
     · 普通业主          → 灰（能看不能改）
   放在顶栏而不是页面里：它表示的是"当前会话的身份"，属于全局状态。 */
.who-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  flex: none;
  height: 28px;
  padding: 0 9px 0 4px;
  margin-right: 2px;
  border-radius: 14px;
  background: var(--hover);
  color: var(--muted);
  font-size: 12px;
  white-space: nowrap;
  max-width: 42vw;
}
.who-badge[hidden] { display: none !important; }
.who-ava {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--muted);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  flex: none;
}
.who-txt {
  overflow: hidden;
  text-overflow: ellipsis;
}
/* 管理员：绿色，表示"可编辑" */
.who-badge.is-admin { background: rgba(22, 163, 74, .13); color: #16a34a; }
.who-badge.is-admin .who-ava { background: #16a34a; }
/* 管理员但在触屏设备上：橙色警告 —— 功能不可用，且要让人知道原因 */
.who-badge.is-readonly { background: rgba(217, 119, 6, .14); color: #d97706; }
.who-badge.is-readonly .who-ava { background: #d97706; }

/* 窄屏优先保证标题与按钮，角标只留头像圆点 */
@media (max-width: 520px) {
  .who-badge { padding: 0 4px; gap: 0; }
  .who-badge .who-txt { display: none; }
}

/* 管理员状态条。
   两种形态，颜色区分紧急程度：
     · 默认（品牌色）= 正在打标，正常排队，不用管
     · .is-warn（橙红） = 有照片真的没处理成功，需要人工介入
   之前只有一种形态、文案又不区分，把"排队中"说成"没处理成功"，
   让业务方以为出了问题。 */
.cases-todo {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  background: rgba(var(--brand-rgb), .10);
  color: var(--brand);
  font-size: 13px;
  text-decoration: none;
  border-bottom: 1px solid var(--line);
  -webkit-tap-highlight-color: transparent;
}
.cases-todo[hidden] { display: none !important; }
.cases-todo:active { background: rgba(var(--brand-rgb), .18); }
.cases-todo.is-warn {
  background: rgba(217, 119, 6, .14);
  color: #b45309;
}
.todo-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--brand); flex: none;
  animation: todo-pulse 1.8s ease-in-out infinite;
}
@keyframes todo-pulse { 50% { opacity: .35; } }
.todo-text { flex: 1; }
.todo-go { flex: none; font-weight: 600; }

/* ---------------------------------------------------------------- 搜索 */
.cases-search {
  display: flex;
  gap: 8px;
  padding: 8px 14px 4px;
}
.cases-search input {
  flex: 1;
  height: 38px;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--card);
  color: var(--text);
  font-size: 14px;
  outline: none;
}
.cases-search input:focus { border-color: var(--brand); }
.cases-search-clear {
  border: 0;
  background: transparent;
  color: var(--brand);
  font-size: 14px;
  cursor: pointer;
  padding: 0 4px;
}

/* ---------------------------------------------------------------- 筛选栏
 *
 * 2026-10 紧凑化改造（业务方："占了太多屏幕空间，字体减小50%，排版紧凑，
 * 一行无法放入所有标签的分成两行"）。
 *
 * 改造前的实测：字号 13px、胶囊高 28px、行距 34px，7 个类目共占约 310px ——
 * 在 1400×950 的窗口里吃掉了三分之一的竖向空间，照片反而没地方了。
 *
 * 改造后：字号 10px、胶囊高 18px、行距 22px，7 行约 155px，**减半**。
 *
 * ⚠️ 为什么不做成字号的绝对一半（6.5px）：那个尺寸的中文已经完全认不出来，
 *    而筛选栏是"找东西"的地方，认不出字就失去意义了。
 *    所以"减半"落在**整体占位**上：字号 13→10、高度 28→18、间距 6→3、
 *    内边距 11→6、圆角 14→9，合起来占用面积正好减半。
 *
 * 2026-10-11 二次调整（业务方："向下拖动图片时，标签不要一直被固定在顶部，
 * 我想要更多的图片被展示在屏幕上"）：
 *   **取消 sticky**。筛选栏原来是 `position: sticky; top: 52px` ——
 *   28 个类目行滚到哪儿都钉在顶上，一屏 950px 里它自己占 160px，
 *   真正能看照片的只剩七百多。筛选栏是**选一次就不动**的东西，
 *   没有理由一直占着屏幕；要找它往上滚一下就行。
 *   （代价：滚到中间想改筛选条件要先滚回顶部。相比"每屏少看两张照片"，
 *     这个代价小得多 —— 业务方是在**逐张审 966 张照片**，
 *     照片的显示面积是主要矛盾。） */
.cases-filters {
  background: var(--bg);
  border-bottom: 1px solid var(--line);
  padding: 4px 0 5px;
  /* 一屏内换行（原来靠横向滚动）。
     横向滚动的问题：标签明明存在，用户却完全看不见 —— 没有任何提示
     告诉他还右边还有东西，看起来就是"标签少了"。 */
  overflow-x: hidden;
  overflow-y: visible;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
}

.filter-row {
  display: flex;
  align-items: center;
  /* 一行放不下就折到第二行（业务方要求） */
  flex-wrap: wrap;
  align-content: flex-start;
  gap: 3px;
  padding: 1px 10px;
  /* 最多两行：超过的部分由 JS 插一个「+N」按钮展开，
     不做成直接裁掉 —— 静默隐藏筛选项等于让用户找不到标签。
     ⚠️ 42px 是算出来的，不是拍的：
         1(上内边距) + 18(胶囊高) + 3(行间距) + 18(胶囊高) + 1(下内边距) = 41px
        留 1px 余量。我第一版随手写了 39px，**两行根本放不下** ——
        于是只显示一行、还错误地弹出「+N」，看起来像标签被吃掉了。
        JS 里的 ROW_MAX_H 必须和这里保持一致。 */
  max-height: 42px;
  overflow: hidden;
  position: relative;
}
.filter-row.is-open { max-height: none; }

.filter-label {
  flex: 0 0 auto;
  font-size: 10px;
  line-height: 17px;
  color: var(--muted);
  width: 46px;
  /* 类目名永远在左上角，标签换行时不跟着跑 */
  align-self: flex-start;
  padding-top: 1px;
}
.filter-chip {
  flex: 0 0 auto;
  height: 18px;
  padding: 0 6px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--card);
  color: var(--text);
  font-size: 10px;
  line-height: 16px;
  cursor: pointer;
  transition: background .12s, border-color .12s, color .12s;
  -webkit-tap-highlight-color: transparent;
  white-space: nowrap;
}
.filter-chip:active { transform: scale(.96); }
.filter-chip[aria-pressed="true"] {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
}
/* 「+N」展开按钮：与标签同高但用虚线边框，不会和真标签混淆 */
.filter-more {
  flex: 0 0 auto;
  height: 18px;
  padding: 0 6px;
  border: 1px dashed var(--line);
  border-radius: 9px;
  background: transparent;
  color: var(--muted);
  font-size: 10px;
  line-height: 16px;
  cursor: pointer;
  font-family: inherit;
}
.filter-more:hover { color: var(--text); border-color: var(--text); }
/* 计数为 0 的标签在"未选中"时隐藏 —— 点进去必然空结果，只会浪费用户一次点击。
   已选中的即使变 0 也要显示，否则用户看不到自己选了什么、也取消不掉。 */
.filter-chip[data-n="0"] { display: none; }

/* 已选条件 */
.cases-active {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 10px 14px 0;
}
.active-tag {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 26px;
  padding: 0 8px 0 10px;
  border-radius: 13px;
  background: color-mix(in srgb, var(--brand) 14%, transparent);
  color: var(--brand);
  font-size: 12.5px;
}
.active-tag button {
  border: 0;
  background: transparent;
  color: inherit;
  font-size: 15px;
  line-height: 1;
  padding: 0 2px;
  cursor: pointer;
}
.active-clear-all {
  height: 26px;
  padding: 0 10px;
  border: 1px solid var(--line);
  border-radius: 13px;
  background: transparent;
  color: var(--muted);
  font-size: 12.5px;
  cursor: pointer;
}

/* ---------------------------------------------------------------- 瀑布流 */
.cases-main { padding: 10px 10px 40px; }

.cases-grid {
  column-count: 2;
  column-gap: 8px;
}
@media (min-width: 560px) { .cases-grid { column-count: 3; } }
@media (min-width: 900px) { .cases-grid { column-count: 4; } }
@media (min-width: 1280px) { .cases-grid { column-count: 5; } }

.case-card {
  position: relative;
  display: block;
  width: 100%;
  margin: 0 0 8px;
  padding: 0;
  border: 0;
  border-radius: 10px;
  overflow: hidden;
  background: var(--card);
  cursor: zoom-in;
  /* 断列：没有它图片会被拆到两列之间 */
  break-inside: avoid;
  -webkit-column-break-inside: avoid;
  page-break-inside: avoid;
  -webkit-tap-highlight-color: transparent;
}
.case-card img {
  display: block;
  width: 100%;
  height: auto;
  /* 用 aspect-ratio 预留位置：图片没加载完也不会跳动，
     这对瀑布流特别重要 —— 否则滚到一半整个网格会重排。 */
  background: var(--hover);
}
.case-card .case-cap {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: 18px 8px 7px;
  font-size: 12px;
  color: #fff;
  background: linear-gradient(to top, rgba(0,0,0,.55), transparent);
  opacity: 0;
  transition: opacity .15s;
  pointer-events: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.case-card:hover .case-cap { opacity: 1; }
@media (hover: none) { .case-card .case-cap { display: none; } }

/* 卡片上的标签。
   为什么放在卡片上：原先标签只在点开大图时才有，画廊里是一墙"光板照片"，
   完全看不出 AI 标了什么（实测导致用户以为"识别不了"）。
   只显示空间+风格各一个：手机上卡片约 180px 宽，五类全堆会盖住照片。 */
.case-chips {
  position: absolute;
  left: 6px;
  right: 6px;
  bottom: 6px;
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  pointer-events: none;
}
.case-chip {
  padding: 1px 7px;
  border-radius: 9px;
  background: rgba(0, 0, 0, .55);
  color: #fff;
  font-size: 11px;
  line-height: 17px;
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  white-space: nowrap;
}
/* 分类用颜色区分，和看大图时的配色保持一致，形成记忆 */
/* 项目工地用醒目的棕橙色：它是"这批照片属于哪个项目"的归属信息，
   和其它"画面内容"类标签性质不同，视觉上也该能一眼分开。 */
.case-chip[data-cat="site"] { background: rgba(150, 88, 30, .88); }
.case-chip[data-cat="space"] { background: rgba(20, 130, 96, .80); }
.case-chip[data-cat="style"] { background: rgba(64, 84, 200, .80); }
/* 卡片上现在要显示 4 个标签，所以配色必须**互不相近**，
   否则一眼扫过去分不清哪个是产品、哪个是手法。
   这两色与大图（.lb-tag）里的橙/紫保持同一色系。 */
.case-chip[data-cat="product"] { background: rgba(190, 108, 20, .84); }
.case-chip[data-cat="technique"] { background: rgba(146, 70, 172, .82); }
.case-chip.is-none { background: rgba(190, 120, 20, .80); }

/* ---- 标签右键菜单（管理员 · 电脑端）----
   左键 = 删除（划掉），右键 = 改成别的标签。
   为什么把"改"放在右键：改是非破坏性操作，但要选具体换成什么、需要展开列表；
   删是高频快速操作，一点就好。两者分开按键，就不会"想改结果删掉了"。 */
.tag-menu {
  position: fixed;
  /* ⚠️ 必须**高于大图遮罩**（.lightbox 是 z-index:100）。
   *
   * 踩过的坑：这里原本写的 90，在大图里右键时菜单其实弹出来了，
   * 但被遮罩盖在下面 —— 用户看到的是"右键完全没反应"。
   * 而测试只断言了 `menu.hidden === false`（DOM 状态），
   * 所以一路绿灯。**"元素存在"和"用户看得见"是两件事。**
   * 现在取 300，高于大图(100)、提示条(200)和操作条(60)。 */
  z-index: 300;
  min-width: 168px;
  max-width: 260px;
  /* 限高：类目最多有 20 个标签，不限高会铺满整屏、把照片全挡住。
     40vh 大约能显示 8~10 项，再多就滚动。 */
  max-height: 40vh;
  overflow-y: auto;
  padding: 6px;
  /* 背景必须**不透明**：菜单会浮在照片和大图上，
     半透明会让文字和背后的画面糊在一起，完全读不了。
     （这里原先是 var(--card)，而那个变量一直没定义 ——
       按 CSS 规范整条声明被丢弃，背景直接透明，
       正是业务方截图里"看不清"的原因。） */
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--card);
  box-shadow: 0 10px 34px rgba(0, 0, 0, .30);
}
.tag-menu[hidden] { display: none !important; }
.tag-menu-head {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px 9px 9px;
  font-size: 12px;
  color: var(--text);
  border-bottom: 1px solid var(--line);
  margin-bottom: 6px;
}
/* 类目色点：和卡片上标签的颜色一致，一眼确认"改的是这一类" */
.tag-menu-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  flex: none;
  background: var(--brand);
}
.tag-menu-cat { font-weight: 600; white-space: nowrap; }
.tag-menu-tip { color: var(--muted); margin-left: auto; font-size: 11px; white-space: nowrap; }
.tag-menu-list { display: flex; flex-direction: column; gap: 2px; }
.tag-menu-item {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  padding: 8px 10px;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: var(--text);
  font-size: 14px;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
}
.tag-menu-item:hover { background: var(--hover); }
/* 当前已选的标签：给个明确的「✓ 已选」，而不是灰掉 ——
   灰掉在深色照片背景上几乎看不见，会让人以为菜单坏了。 */
.tag-menu-item.is-cur { cursor: default; }
.tag-menu-item.is-cur::after {
  content: '✓ 已选';
  margin-left: auto;
  font-size: 11px;
  color: var(--brand);
}
/* 删除项单独用红色，和"改成别的"区分开，避免误点 */
.tag-menu-item.is-del { color: #d9534f; }
.tag-menu-item.is-del:hover { background: rgba(217, 83, 79, .12); }
.tag-menu-sep { height: 1px; background: var(--line); margin: 5px 2px; }

/* ---- 未审核筛选（管理员 · 电脑端）----
   业务方习惯在**公开页面**上翻图（客户看到的就是这个），所以审核入口
   直接做在这里，而不是只放管理后台 —— 后者他找不到。 */
#btn-unreviewed.is-on {
  background: var(--brand);
  color: #fff;
  border-color: var(--brand);
}
#btn-unreviewed.is-warn:not(.is-on) {
  border-color: #e0a33a;
  color: #b8801f;
}
#btn-unreviewed.is-warn:not(.is-on) #unrev-label { font-weight: 600; }

/* 卡片右上角的"未审核"小圆点。
   只给管理员看，而且刻意做得很小 —— 这一页是给客户看的作品集，
   审核是内部事，不能因为这个标记把页面弄得像个后台系统。 */
.case-unrev-dot {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #e0a33a;
  box-shadow: 0 0 0 2px rgba(255, 255, 255, .85);
  pointer-events: none;
  z-index: 3;
}
/* 选中模式下圆点会和勾选标记打架，藏掉 */
body.is-selecting .case-unrev-dot { display: none; }

/* 大图里的「确认无误」。放在底栏、和标题标签同一行。 */
.lb-ok-btn {
  flex: none;
  margin-left: auto;
  margin-right: 12px;
  padding: 8px 16px;
  border: 1px solid var(--brand);
  border-radius: 8px;
  background: var(--brand);
  color: #fff;
  font-size: 13px;
  font-family: inherit;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}
.lb-ok-btn:hover { background: var(--brand-strong); border-color: var(--brand-strong); }
/* 已确认过的：换成描边样式，一眼看出状态 */
.lb-ok-btn.is-done {
  background: transparent;
  color: var(--brand);
}
.lb-ok-btn.is-done:hover { background: rgba(77, 107, 254, .10); }
.lb-ok-btn[hidden] { display: none !important; }

/* 卡片左上角的「问AI」。
   和右上角的「改标签」对称：左边是"拿这张照片去做别的事"，
   右边是"改这张照片的标签"，两个动作用位置区分开，不用看字也不会点错。 */
.case-ask {
  position: absolute;
  top: 8px;
  left: 8px;
  z-index: 3;
  padding: 3px 9px;
  border: 0;
  border-radius: 999px;
  background: rgba(0, 0, 0, .58);
  color: #fff;
  font-size: 12px;
  font-family: inherit;
  cursor: pointer;
  /* 平时半透明，悬停才完全显形 —— 画廊是给客户看的作品集，
     两个常驻按钮已经很显眼了，不该再抢照片本身的注意力。 */
  opacity: .72;
  transition: opacity .12s, background .12s;
}
.case-card:hover .case-ask { opacity: 1; }
.case-ask:hover { background: var(--brand); }
/* 已送出：短暂变绿，给一个"点到了"的确认 */
.case-ask.is-sent { background: #2e9e5b; opacity: 1; }
/* 批量选择模式下这个按钮没意义（点卡片是勾选），藏掉免得误触 */
body.is-selecting .case-ask { display: none; }

/* ---- 色系：小方格色块 ----
   业务方的要求："增加一个色系标签，24色。用小方格色块，每张图最多三个色"。

   为什么用真色块而不是彩色文字：24 个颜色名（雾霾蓝/藕粉/橄榄绿…）
   光看字很难在脑子里还原成颜色，而筛选照片时找的就是"这个色"。
   色块一眼就能扫出来。

   极浅的颜色（白色/米白/浅灰）在白底上会"消失"，所以给它们加描边 ——
   靠 data-light 属性区分，由 JS 按相对亮度算出来。 */
.tag-swatch {
  display: inline-block;
  width: 12px;
  height: 12px;
  border-radius: 3px;
  flex: none;
  margin-right: 5px;
  vertical-align: -1px;
  background: #ccc;
}
.tag-swatch[data-light="1"] {
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .28);
}

/* 卡片上的色系：**只画方块不写名字**。
 * 最多 3 个，三个 12px 方块加起来还不到一个文字标签宽；
 * 写名字会把照片盖掉一大半（卡片上已经有 4 个文字标签了）。 */
/* ---------------------------------------------------------------- 色系小方格
 *
 * 业务方要求："用小方格色块，每张图最多三个色"。色值由服务端下发，
 * 见 cases.js 的 tagHexOf（**公开**接口 + 管理员词表两个来源）。
 */
.case-colors {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 0 2px;
}

/* 窄卡片上**不显示色块**。
 *
 * 业务方（2026-10-12）："在手机端，图片列表上，还是有 3 个色系[色块]，
 * 这个 3 个色系[色块]独自占了一行，影响图片查看，所以要删除。"
 *
 * 实测确认了原因（iPhone SE 375 / iPhone 12 390）：
 *   卡片只有 174-181px 宽，而 4 个文字标签已经占满一行（约 150px），
 *   3 个 12px 的色块（约 44px）放不下 → 换行。
 *   覆盖层从 19px 变成 35px（**多 84%**），占掉卡片高度的 30%。
 *   8 张卡片里有 5 张会这样。
 *
 * 断点取 900px 而不是 560px：
 *   换行取决于**卡片宽度**，而卡片宽度由列数决定 ——
 *   900px 以下都是 2-3 列（卡片 174-215px），色块都会换行；
 *   900px 以上是 4-5 列（卡片 215-267px），才放得下。
 *   只按"手机"（560px）隐藏的话，560-900px 那一档（小平板、
 *   窄窗口）会继续带着这个毛病。
 *
 * ⚠️ 色系信息**没有丢**：大图（点开照片）里有完整的颜色名 + 色块，
 *    筛选栏里也有 —— 那两处空间充足。卡片上省掉的是"缩略图上
 *    占了三分之一高度、还看不清"的那一份。
 *
 * 如果以后想让手机上也显示，更好的做法不是恢复这一行，
 * 而是**减少卡片上的文字标签数量**（3 个文字 + 3 个色块能排进一行，
 * 158px < 174px），把位置腾出来。 */
@media (max-width: 899px) {
  .case-card .case-colors { display: none; }
}

/* 窄屏上文字标签只显示 **3 个**。
 *
 * 业务方（2026-10-12）："（长标签名换行）第一个（方案）" ——
 * 即"窄屏只显示 3 个文字标签"，而不是"把标签名截断成半截字"。
 *
 * 为什么需要：卡片只有 174-181px 宽，4 个标签里只要有一个长名字
 * （「天然木皮」「背景灯带」「不锈钢」这种 4 字词）就会换行 ——
 * 实测 iPhone SE 上 8 张有 1 张会换，iPhone 12 上 0 张。
 * 换行后覆盖层从 19px 涨到 42px，正好压在照片主体上。
 *
 * 为什么用 CSS 的 :nth-child 而不是在 JS 里少渲染一个：
 *   · 这是**纯展示**问题，和"这张照片有哪些标签"无关 ——
 *     数据该下发 4 个就下发 4 个，改窗口大小、旋转屏幕时
 *     不需要重新请求或重画，CSS 自动切换。
 *   · 用 JS 判断屏幕宽度的话，"什么时候重算"会变成一个新问题
 *     （resize 节流？旋转？分屏？），而且首屏可能算出错的宽度。
 *
 * ⚠️ 被隐藏的那个标签在**大图里仍然完整显示** ——
 *    卡片上少一个不等于数据没了，点开就能看到全部。 */
@media (max-width: 899px) {
  .case-chips > .case-chip:nth-child(4) { display: none; }
}
.case-color-dot {
  width: 12px;
  height: 12px;
  border-radius: 3px;
  display: block;
  background: #ccc;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .22);
}
.case-color-dot[data-light="1"] {
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .3), 0 1px 2px rgba(0, 0, 0, .18);
}
/* 词表里查不到色值（理论上不该发生）：画个问号方块而不是留白 */
.case-color-dot.is-unknown {
  background: repeating-linear-gradient(45deg, #ddd 0 3px, #bbb 3px 6px);
}

/* 筛选栏里的色块跟着新字号一起缩小（胶囊只有 18px 高，13px 的方块会顶出去） */
.filter-chip .tag-swatch { width: 11px; height: 11px; margin-right: 4px; border-radius: 2px; }
/* 大图底部的保持大一点：那里空间充足，而且看颜色是主要目的 */
.lb-tag .tag-swatch { width: 13px; height: 13px; }

/* ---- 批量选择（管理员，仅电脑端）---- */

/* 顶栏的「选择」按钮，样式跟「上传」保持一致，形成一排管理操作 */
.cases-sel-btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  flex: none;
  height: 32px;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--card);
  color: var(--text);
  font-size: 13px;
  font-family: inherit;
  cursor: pointer;
  white-space: nowrap;
}
.cases-sel-btn[hidden] { display: none !important; }
.cases-sel-btn:hover { border-color: var(--brand); color: var(--brand); }
.cases-sel-btn.is-on {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
}

/* 进入选择模式后：卡片可点、光标变成选择态，并**禁用**标签的点删与悬停动效 ——
   否则"点标签删标签"和"点卡片勾选"两种语义会打架，
   管理员很可能在勾选时误删掉一个标签。 */
body.is-selecting .case-card { cursor: pointer; }
body.is-selecting .case-chip { pointer-events: none !important; }
body.is-selecting .case-edit { display: none !important; }
/* 「查看大图」的悬停条在选择模式下必须隐藏，两个原因：
   ① 它写的是"查看大图"，但此时点卡片是**勾选**，根本不放大 —— 文案与行为矛盾；
   ② 它是从底部铺开的渐变条，正好盖住右下角的勾选圆点。 */
body.is-selecting .case-card .case-cap { display: none !important; }
body.is-selecting .case-card:active { opacity: .92; }

/* 勾选标记：右下角一个**小圆点**，直径 8px（原先 24px 的 1/3）。
 *
 * 为什么放右下角：左上/右上被「待标注」提示与「改标签」按钮占着，
 * 右上角的圆圈还会和悬停出现的「改标签」按钮重叠，看起来像两个控件打架。
 * 右下角在卡片上一直是空的，放这里最干净。
 *
 * 为什么缩到 8px 且**不放对勾图标**：8px 的圆里塞一个对勾，对勾只有 4~5px，
 * 在屏幕上就是一个模糊的小点，反而看不清。这个尺寸下"空心=未选、实心=已选"
 * 的对比最清晰。
 *
 * 它只是**指示器**，不承接点击（pointer-events:none）——
 * 点击由整张卡片处理，所以圆圈再小也不影响操作，不用担心点不中。 */
.sel-mark {
  position: absolute;
  right: 8px;
  bottom: 8px;
  z-index: 3;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  border: 1.5px solid rgba(255, 255, 255, .95);
  background: rgba(0, 0, 0, .42);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .4);
  pointer-events: none;
  /* ⚠️ 默认必须隐藏，只有进入选择模式才显示。
   *
   * 这里踩过一个很隐蔽的坑：元素在非选择模式下也渲染了出来，
   * 而那时点卡片走的是「打开大图」的逻辑 —— 于是用户看到角落有个圆圈、
   * 以为能点它选中，点下去却是图片被放大。
   * 而卡片底部的「查看大图」渐变条正好压在右下角，
   * 又把圆圈盖得不清楚，看起来就更像"圆圈被条子挡住了点不到"。
   *
   * 教训：**新控件的"什么时候出现"要和它的"点了做什么"严格对齐** ——
   * 只要它可见，用户就会去点，而那一刻的行为必须和外观承诺的一致。 */
  display: none;
}
body.is-selecting .sel-mark { display: block; }
.case-card.is-sel .sel-mark {
  background: var(--brand);
  border-color: #fff;
  /* 选中时加一圈光晕，8px 的小点在整墙照片里也能一眼看到 */
  box-shadow: 0 0 0 2px rgba(var(--brand-rgb), .45);
}
/* 选中的卡片压暗四周 + 描边，一眼能分辨 */
.case-card.is-sel { outline: 3px solid var(--brand); outline-offset: -3px; }
.case-card.is-sel .case-thumb { opacity: .72; }

/* 底部操作条 */
.sel-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding: 10px 14px calc(10px + env(safe-area-inset-bottom));
  background: var(--card);
  border-top: 1px solid var(--line);
  box-shadow: 0 -4px 18px rgba(0, 0, 0, .12);
}
.sel-bar[hidden] { display: none !important; }
.sel-count { font-size: 14px; font-weight: 600; color: var(--text); }
.sel-acts { display: flex; gap: 8px; flex-wrap: wrap; }
.sel-btn {
  height: 34px;
  padding: 0 14px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--bg);
  color: var(--text);
  font-size: 13px;
  font-family: inherit;
  cursor: pointer;
}
.sel-btn:hover { border-color: var(--brand); color: var(--brand); }
/* 删除的"确认"态：变红 + 加粗，让人一眼看出这是不可恢复的操作 */
.sel-btn.danger { border-color: #d9534f; color: #d9534f; }
.sel-btn.danger.is-confirm {
  background: #d9534f;
  border-color: #d9534f;
  color: #fff;
  font-weight: 700;
}
.sel-btn:disabled { opacity: .45; cursor: not-allowed; }

/* 有底部操作条时要给页面留出空间，否则最后一行会被遮住 */
body.is-selecting { padding-bottom: 64px; }

/* ---- 标签可点击删除（管理员，仅电脑端）----
   一步操作：**直接点标签就删掉**，没有 × 按钮、没有二次确认。

   ⚠️ 悬停反馈**绝对不能改变元素尺寸或位置**（不要用 transform: scale）。
   踩过的坑：原先写了 `:hover{transform:scale(1.06)}` 和 `:active{transform:scale(.96)}`，
   结果业务方反馈"点标签，图片放大了，还显示了标签文字，就是删不掉"。

   原因在 DOM 规范里：**click 事件派发到 mousedown 与 mouseup 两个目标的最近公共祖先**。
   按下时命中了标签，但标签在 :active 里缩小了 4%，松开时鼠标已经在标签外 ——
   于是 click 被派发到**卡片**上，触发"打开大图"。表现为点标签反而放大了图片。

   所以现在只用**颜色**表达"点它会删掉"：变红 + 划线。元素本身纹丝不动。 */
.case-chip.is-deletable {
  /* 父容器 .case-chips 是 pointer-events:none（避免遮住卡片点击），
     所以可点的标签必须自己把事件打开 */
  pointer-events: auto;
  cursor: pointer;
  /* 给足点击区域：标签本身只有约 40×19px，太小的目标在鼠标上很容易点偏 */
  padding: 2px 9px;
  margin: -2px -1px;
  transition: background .12s, color .12s;
}
.case-chip.is-deletable:hover {
  text-decoration: line-through;
  background: rgba(200, 32, 32, .92);   /* 变红 = 点它会删掉 */
  color: #fff;
}
/* 按下时保持完全一致的外观与尺寸，不做任何缩放 —— 见上面的规范说明 */
.case-chip.is-deletable:active {
  background: rgba(150, 20, 20, .95);
}
.case-chip.is-deletable:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
  text-decoration: line-through;
}

/* 管理员功能**只面向电脑端**（业务方明确要求），所以刻意不做触屏兜底 ——
   否则管理员用手机打开画廊时，标签会带上"可删除"的视觉暗示，
   而手机上又没有悬停提示，误触就删掉了。手机上就是干净的浏览体验。 */

/* ---- 卡片上的「改标签」按钮（管理员，悬停出现）---- */
.case-edit {
  position: absolute;
  top: 6px;
  right: 6px;
  display: none;
  align-items: center;
  gap: 3px;
  height: 26px;
  padding: 0 9px;
  border: 0;
  border-radius: 13px;
  background: rgba(0, 0, 0, .62);
  color: #fff;
  font-size: 11.5px;
  font-family: inherit;
  cursor: pointer;
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  -webkit-tap-highlight-color: transparent;
}
.case-card:hover .case-edit,
.case-card:focus-within .case-edit { display: inline-flex; }
/* 同上：不做触屏兜底，管理员在电脑上操作 */

/* 保存中：标签变暗作为反馈 */
.case-card.is-saving .case-chips { opacity: .45; }

/* ---------------------------------------------------------------- 标签修正面板 */
.sheet-mask {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: rgba(0, 0, 0, .45);
  animation: fade-in .15s ease;
}
.sheet-mask[hidden] { display: none !important; }
@keyframes fade-in { from { opacity: 0; } }

.tag-sheet {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 95;
  max-height: 86vh;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  border-radius: 18px 18px 0 0;
  box-shadow: 0 -8px 30px rgba(0, 0, 0, .22);
  animation: sheet-up .2s cubic-bezier(.2, .8, .3, 1);
  padding-bottom: env(safe-area-inset-bottom);
}
.tag-sheet[hidden] { display: none !important; }
@keyframes sheet-up { from { transform: translateY(100%); } }

.tag-sheet-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 14px 8px;
  border-bottom: 1px solid var(--line);
}
.tag-sheet-head h2 { margin: 0; font-size: 15px; font-weight: 600; }
.tag-sheet-close {
  width: 34px; height: 34px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: 50%;
  background: transparent; color: var(--text); cursor: pointer;
}
.tag-sheet-close:active { background: var(--hover); }

.tag-sheet-body {
  flex: 1;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 10px 14px 6px;
}
.ts-group { margin-bottom: 12px; }
.ts-label { font-size: 12px; color: var(--muted); margin-bottom: 6px; }
/* 自由文本类别（项目工地）的输入框。
   它不是从固定词表里选，而是人工填写，所以样式要和那排按钮明显不同，
   否则用户会以为"这里也是点一个就选上了"。 */
.ts-input {
  width: 100%;
  height: 38px;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--card);
  color: var(--text);
  font-size: 14px;
  font-family: inherit;
}
.ts-input:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px rgba(var(--brand-rgb), .15);
}
.ts-tip { font-size: 11px; color: var(--muted); margin-top: 6px; }
.ts-picks { display: flex; flex-wrap: wrap; gap: 6px; }
.ts-btn {
  height: 30px;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: 15px;
  background: var(--card);
  color: var(--text);
  font-size: 13px;
  font-family: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.ts-btn:active { transform: scale(.96); }
.ts-btn.on { background: var(--brand); border-color: var(--brand); color: #fff; font-weight: 600; }
/* AI 判断不了、只能人工填的标签（与复核页的提示一致） */
.ts-btn.manual-only::after {
  content: '';
  display: inline-block;
  width: 4px; height: 4px;
  margin-left: 4px;
  border-radius: 50%;
  background: currentColor;
  opacity: .45;
  vertical-align: middle;
}

.tag-sheet-foot { padding: 10px 14px 14px; border-top: 1px solid var(--line); }
.tag-sheet-foot .cases-btn { width: 100%; height: 42px; font-size: 15px; }
.tag-sheet-foot .cases-btn:disabled { opacity: .5; }

/* 轻提示 */
.toast {
  position: fixed;
  left: 50%;
  bottom: calc(30px + env(safe-area-inset-bottom));
  transform: translateX(-50%);
  z-index: 200;
  max-width: 88vw;
  padding: 10px 16px;
  border-radius: 10px;
  background: rgba(0, 0, 0, .86);
  color: #fff;
  font-size: 13px;
  line-height: 1.5;
  text-align: center;
  animation: toast-in .18s ease;
}
.toast[hidden] { display: none !important; }
@keyframes toast-in { from { opacity: 0; transform: translate(-50%, 8px); } }

/* ---------------------------------------------------------------- 骨架屏 */
.cases-skeleton { column-count: 2; column-gap: 8px; }
@media (min-width: 560px) { .cases-skeleton { column-count: 3; } }
@media (min-width: 900px) { .cases-skeleton { column-count: 4; } }
.sk-item {
  break-inside: avoid;
  margin-bottom: 8px;
  border-radius: 10px;
  background: linear-gradient(100deg, var(--hover) 30%, var(--line) 50%, var(--hover) 70%);
  background-size: 220% 100%;
  animation: sk 1.3s linear infinite;
}
@keyframes sk { to { background-position: -220% 0; } }

/* ---------------------------------------------------------------- 空态 / 更多 */
.cases-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 72px 24px;
  color: var(--muted);
  text-align: center;
}
.cases-empty p { margin: 0; font-size: 14px; }

.cases-loadmore { text-align: center; padding: 16px 0 8px; }
.cases-loading { text-align: center; padding: 16px; color: var(--muted); font-size: 13px; }

.cases-btn {
  height: 38px;
  padding: 0 20px;
  border: 1px solid var(--line);
  border-radius: 19px;
  background: var(--card);
  color: var(--text);
  font-size: 14px;
  cursor: pointer;
}
.cases-btn:active { background: var(--hover); }

/* ---------------------------------------------------------------- 大图 */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: rgba(0, 0, 0, .94);
  display: flex;
  flex-direction: column;
  touch-action: none;
}
.lb-stage {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  padding: 8px;
}
.lb-stage img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: 6px;
  /* 加载大图时先给个提示，而不是一片黑 */
  transition: opacity .18s;
}
.lb-stage img.is-loading { opacity: .35; }

.lb-close, .lb-nav {
  position: absolute;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border: 0;
  border-radius: 50%;
  background: rgba(255,255,255,.12);
  color: #fff;
  cursor: pointer;
  backdrop-filter: blur(6px);
  -webkit-tap-highlight-color: transparent;
}
.lb-close { top: calc(10px + env(safe-area-inset-top)); right: 12px; }
.lb-prev { left: 10px; top: 50%; transform: translateY(-50%); }
.lb-next { right: 10px; top: 50%; transform: translateY(-50%); }
.lb-close:active, .lb-nav:active { background: rgba(255,255,255,.26); }

/* 只有一张图时，左右箭头没有意义 */
.lightbox[data-single="1"] .lb-nav { display: none; }

.lb-bar {
  display: flex;
  align-items: flex-end;
  gap: 12px;
  padding: 10px 16px calc(14px + env(safe-area-inset-bottom));
  background: linear-gradient(to top, rgba(0,0,0,.72), transparent);
  color: #fff;
}
.lb-meta { flex: 1; min-width: 0; }
.lb-title {
  font-size: 15px;
  font-weight: 600;
  margin-bottom: 5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.lb-tags { display: flex; flex-wrap: wrap; gap: 5px; }
.lb-tag {
  padding: 2px 8px;
  border-radius: 10px;
  background: rgba(255,255,255,.16);
  font-size: 11.5px;
  line-height: 17px;
}
/* 分类用不同色调区分，方便一眼看出是"风格"还是"空间" */
.lb-tag[data-cat="style"]     { background: rgba(120,140,255,.34); }
.lb-tag[data-cat="site"]      { background: rgba(230,150,70,.40); }
.lb-tag[data-cat="space"]     { background: rgba(90,200,160,.30); }
.lb-tag[data-cat="product"]   { background: rgba(255,180,90,.30); }
.lb-tag[data-cat="technique"] { background: rgba(210,120,220,.30); }
.lb-tag[data-cat="finish"]    { background: rgba(120,200,220,.30); }

/* ---- 从属标签的分组显示（业务方 2026-10-14）----
 *
 * 业务方要求："标签与定制产品要形成从属关系，如果定制产品的标签被删除，
 *            则从属关系的标签也被自动删除。"
 *
 * 显示成：[衣柜 圆弧 天然木皮]  [电视柜 格栅]  现代  卧室  白色
 * 产品当头（稍亮、加个左边框），挂在它下面的标签紧随其后、略微缩进。
 *
 * ⚠️ 和 .lb-tag.is-deletable 同一个坑（完整记录见下面那段注释）：
 *    这里**只加颜色/边框/内边距**，绝不能改变元素尺寸或位置 ——
 *    一旦 mousedown 与 mouseup 之间元素变形，click 就会落到祖先上，
 *    在大图里表现为"点标签删不掉，反而把大图关掉了"。
 *    内边距会改尺寸，所以子标签只用左边框 + 背景色来区分。
 */
.lb-group {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  /* 组与组之间留大一点的间距，否则 [衣柜 圆弧] [电视柜 格栅] 会连成一串 */
  margin-right: 4px;
}
.lb-group .lb-tag.is-group-head {
  background: rgba(255,180,90,.46);        /* 比普通产品标签更亮，一眼看到"这是父级" */
  border-left: 2px solid rgba(255,200,120,.9);
}
.lb-group .lb-tag.is-child {
  /* 子标签：用左边框表达"我挂在前面那个下面"，不缩进（缩进会改位置） */
  border-left: 2px solid rgba(255,255,255,.30);
  opacity: .92;
}

/* ---- 大图页标签可点删除（管理员，仅电脑端）----
   ⚠️ 与卡片页 .case-chip.is-deletable 同一个坑（完整记录见那里）：**任何改变元素尺寸或
   位置的属性都不能加**，包括悬停/按下时放大、缩小哪怕几个百分点。
   原因：DOM 规范规定 click 派发到 mousedown 与 mouseup 两目标的**最近公共祖先** ——
   元素一旦在按下与松开之间变形，命中点就从标签滑到容器上；在大图页会表现为
   "点标签删不掉，反而把大图关掉了"。所以这里只用颜色 + 划线表达"点它会删掉"，
   元素本身纹丝不动：尺寸/位置在基础态、:hover、:active 全程完全一致。 */
.lb-tag.is-deletable {
  cursor: pointer;
  /* 顺带加大点击热区；字体大小仍由 .lb-tag 的 11.5px 继承，绝不改动，
     否则同一排标签会忽大忽小，也会让悬停时的尺寸断言失败 */
  padding: 3px 10px;
  margin: -3px -3px;
  transition: background .12s, color .12s;
}
.lb-tag.is-deletable:hover {
  background: rgba(200, 32, 32, .92);   /* 变红 = 点它会删掉 */
  color: #fff;
  text-decoration: line-through;
}
/* 按下时只把红色加深，外观与尺寸保持一致（不做任何缩放）—— 见上方规范说明 */
.lb-tag.is-deletable:active {
  background: rgba(150, 20, 20, .95);
}
.lb-tag.is-deletable:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
  text-decoration: line-through;
}

.lb-counter { font-size: 12.5px; opacity: .75; white-space: nowrap; }

/* 详情面板：手机上标题+标签可能有好几行，把计数挤出去，所以换个排法 */
@media (max-width: 480px) {
  .lb-bar { flex-direction: column; align-items: stretch; gap: 6px; }
  .lb-counter { text-align: right; }
}

/* 图片右滑/左滑手势的位移反馈 */
.lb-stage img.swiping { transition: none; }
