/**
 * 卖家端「定制区域」编辑器样式（personalizeGoods，方案 §4.6）。
 *
 * ⚠️ **刻意独立成文件**，不并进 admin.css —— 那个文件有并行会话在改（方案 §5 标了别碰）。
 *    这里的类名一律 `psreg-` 前缀，免得和别处撞。
 *
 * 坐标系：画布按**正方形**渲染（stage 宽度 = 高度），里面叠一层 1000×1000 的矩形层。
 * 位置/尺寸全由 JS 按 `scale = stage.clientWidth / 1000` 算成 px 写上去 ——
 * CSS 这边不管坐标，只负责外观。
 */

.psreg { border: 1px solid var(--border); border-radius: .75rem; padding: .85rem; background: #fff; }

/* ---------- 颜色缩略图网格（对应 jamkarl 的 col-md-2 thumbnail 那排）----------
   交互：**点这张图本身**开 modal，与 jamkarl 的 <img data-filename class="img_colorArr"> 一致。 */
.psreg-grid { display: flex; flex-wrap: wrap; gap: .6rem; }
.psreg-thumb {
  width: 108px; cursor: pointer; text-align: center; user-select: none;
  border: 1px solid var(--border); border-radius: .5rem; padding: .35rem; background: #fff;
}
.psreg-thumb:hover { border-color: var(--accent); }
.psreg-thumb.is-noimg { cursor: not-allowed; opacity: .55; }
.psreg-thumb-img {
  position: relative; height: 78px; display: flex; align-items: center; justify-content: center;
  background: var(--light); border-radius: .3rem; overflow: hidden;
}
.psreg-thumb-img img { max-width: 100%; max-height: 100%; object-fit: contain; }
.psreg-thumb-img > i { font-size: 1.4rem; }
.psreg-thumb-name { font-size: .72rem; color: var(--navy); margin-top: .25rem; word-break: break-all; }

/* "这个色配过没有" —— 对应 jamkarl 的 color_has_temuTmp / no_color_has_temuTmp */
.psreg-thumb-state {
  position: absolute; top: 4px; right: 4px; width: 8px; height: 8px;
  border-radius: 50%; background: #d0d3d9; box-shadow: 0 0 0 2px #fff;
}
.psreg-thumb-state[data-state="on"] { background: var(--accent); }

/* "All colours"（默认键）那块用别的底色区分：它不是某个颜色 */
.psreg-thumb-all .psreg-thumb-img { background: #eef2ff; }
.psreg-thumb-all .psreg-thumb-img > i { color: var(--accent); }

/* ---------- 画布 ---------- */
.psreg-body { display: flex; gap: .85rem; align-items: flex-start; flex-wrap: wrap; }
.psreg-canvas-wrap { flex: 1 1 420px; min-width: 300px; }

/* 正方形：宽度定死，高度用 padding-top 顶出来，里面的绝对层才不会塌 */
.psreg-stage {
  position: relative; width: 100%; padding-top: 100%;
  background: var(--light); border-radius: .5rem; overflow: hidden; touch-action: none;
}
.psreg-stage > img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; }
.psreg-stage.is-disabled { opacity: .5; }
.psreg-layer { position: absolute; inset: 0; }

/* ---------- 框 ---------- */
.psreg-box {
  position: absolute; box-sizing: border-box; cursor: move;
  border: 2px dashed rgba(4,83,243,.75); background: rgba(4,83,243,.08);
  border-radius: .25rem; touch-action: none;
}
/* 图案框用另一种颜色，和文字框一眼区分 */
.psreg-box-image { border-color: rgba(16,150,90,.8); background: rgba(16,150,90,.10); }
.psreg-box.is-sel { border-style: solid; box-shadow: 0 0 0 2px rgba(4,83,243,.18); }
/* 重叠：与服务端同一判据，这里只是即时提示 */
.psreg-box.is-bad { border-color: #d93026; background: rgba(217,48,38,.12); }

.psreg-box-tag {
  position: absolute; top: -1px; left: -1px; padding: 0 .28rem;
  font-size: .62rem; line-height: 1.5; color: #fff; background: var(--accent);
  border-radius: .2rem 0 .3rem 0; pointer-events: none;
}
.psreg-box-image .psreg-box-tag { background: #10965a; }

/* 右下角的缩放手柄 */
.psreg-grip {
  position: absolute; right: -5px; bottom: -5px; width: 11px; height: 11px;
  background: #fff; border: 2px solid var(--accent); border-radius: 50%;
  cursor: nwse-resize; touch-action: none;
}
.psreg-box-image .psreg-grip { border-color: #10965a; }

/* ---------- 工具栏 / 提示 ---------- */
.psreg-tools { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .6rem; }
.psreg-hint { margin-top: .45rem; }

/* ---------- 右侧属性面板 ---------- */
.psreg-props {
  flex: 0 0 240px; min-width: 220px; padding: .7rem;
  border: 1px solid var(--border); border-radius: .5rem; background: #fbfbfd;
}
.psreg-props .form-label { color: var(--navy); }
.psreg-props .form-control-color { width: 100%; height: 2rem; padding: .1rem; }


/* ==========================================================================
   买家端 Personalize modal（partials/buy/personalize_modal.php + personalize-buyer.js）
   --------------------------------------------------------------------------
   与上面那套卖家编辑器是**两套 UI**（卖家画框 / 买家往框里填），所以类名分开用 psbuy-，
   但样式放同一个文件：它们是一件事的两端，分两个文件反而容易只改一边。
   仍然**不要并进 admin.css**（方案 §5：那个文件有并行会话在改）。
   ========================================================================== */

/*
 * ⚠️ **只有"行"那一栏滚动**，其余（用量提示 / 尺码表 / 合计 / 按钮组）全部固定
 * （用户 2026-09-19）。
 *
 * 这条 flex 高度链能立住，靠的是 Bootstrap 的 `modal-dialog-scrollable` 给
 * `.modal-content` 一个**确定高度**：
 *
 *   .modal-content (max-height:100%, 由 scrollable 给出确定高度)
 *     └ .modal-body        display:flex; flex-direction:column; min-height:0
 *         └ .psbuy-panel   flex:1 1 auto; min-height:0
 *             ├ 提示 / 尺码表 / 合计 / 按钮   固定（不参与伸缩）
 *             └ .psbuy-body  flex:1 1 auto; min-height:0   ← 唯一可伸缩
 *                 └ .psbuy-fields  overflow-y:auto          ← 唯一会滚的
 *
 * **我前两版翻车的原因**：用 `max-height` 给 `.modal-content` 限高 —— 那只约束上限、
 * **不产生确定高度**，下面整条链解不出来，`overflow:hidden` 就把内容裁了
 * （用户看到"主图和底部被切"）。这一版的高度是 scrollable 给的**确定值**。
 *
 * `min-height:0` 每个 flex 子项都要给：默认 `min-height:auto` 时它宁可撑破父级
 * 也不肯缩，滚动条永远长不出来。
 */

/*
 * ⚠️ **高度必须用 `height`，不能用 `max-height`** —— 这是这块前后翻车三次的根因：
 *
 *   `max-height` 只约束**上限**，不产生**确定高度**。子项（flex 链上的
 *   `.psbuy-panel` / `.psbuy-body` / `.psbuy-fields`）在解百分比/伸缩时拿不到基准，
 *   于是谁都不肯缩 —— 滚动条永远长不出来，或者被 `overflow:hidden` 整块裁掉。
 *
 * `modal-dialog-scrollable` 给 dialog 的是 `height: calc(100% - …)`（确定值），
 * 但给 `.modal-content` 的只有 `max-height: 100%`（不确定）。这里把 content 也钉成
 * `height: 100%`，整条链才立得住：
 *
 *   .modal-dialog   height（scrollable / 下面那条）   ← 确定
 *     └ .modal-content  height: 100%                  ← 确定
 *         └ .modal-body  flex:1 1 auto; min-height:0  ← 有界
 *             └ .psbuy-panel  flex:1 1 auto           ← 有界
 *                 └ .psbuy-body  flex:1 1 auto        ← 有界
 *                     └ .psbuy-fields  overflow-y:auto ← 唯一会滚的
 */
/*
 * 两个弹窗**同宽同高**（用户 2026-09-19："详情页内的 Personalize modal 宽度要对齐
 * goodslist 的 buymodal/personalizemodal"）—— 同一个面板在两处出现，尺寸不一致
 * 会让人以为点错了东西。数值与 `goods_buy.php` 里那份保持一致，改要一起改。
 */
/* ⚠️ 高度必须是 `height` 而不是 `max-height` —— 后者只约束上限、不产生确定高度，
   下面那条 flex 链就解不出来，`overflow:hidden` 会把内容裁掉（用户看到的
   "主图和底部被切"）。这一条与上一条同源，改要一起改。 */
/*
 * ⚠️ `#buyModal`（blank 的快速购买弹窗）**2026-10-13 才加进来**（用户第 13 条：
 *   「pc 端的 buymodal blank 的 css 没有对齐 links 的 buymodal」）。
 *
 * 它原先**两样都没有** —— 没有固定高度、也没有 `.modal-content{height:100%}`，
 * 于是同样是"快速购买弹窗"，link 那个 92vh 撑满，blank 那个按内容高度缩着、
 * 还垂直居中；两个并排看就是两个尺寸。数值与原有两条**完全一致**，不要再各写一套。
 */
#personalizeModal .modal-dialog,
#goodsBuyModal .modal-dialog,
#buyModal .modal-dialog {
  width: 100%; max-width: min(1370px, 96vw);
  height: 92vh; margin-top: 2vh; margin-bottom: 2vh;
}

#personalizeModal .modal-content,
#goodsBuyModal .modal-content,
#buyModal .modal-content { height: 100%; }

/* 正文变成 flex 列，让面板根能撑满。**不要**在这里自己设 `overflow` ——
   scrollable 已经给了 `overflow-y:auto`，那是"万一还超就滚"的安全兜底；
   换成 `hidden` 就会裁内容。 */
#personalizeModal .modal-body,
#goodsBuyModal .modal-body.ps-open {
  display: flex; flex-direction: column; min-height: 0;
}

.psbuy-panel { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }

/* ⚠️ 摘要行（合计 + Add to Cart / Buy Now）要**压在画布之上**：
   画布是 `position:sticky`，自带层叠上下文，一旦行高超出就会盖住它
   （用户 2026-09-19："Add to Cart 有一半被 img 遮挡"）。这里抬一级兜底 ——
   就算行高没算准，也是"按钮压住图"而不是"图盖住按钮"。 */
.psbuy-actions { position: relative; z-index: 3; background: #fff; }

/*
 * **就地确认条** —— 不遮屏、不打断，上下文就在眼前（用户 2026-09-19：
 * "Discard this? 为什么也使用这个 modal？大大的提示"）。
 * 删哪一行就在哪一行里面冒一条，比全屏弹窗轻得多。
 */
.psbuy-confirm {
  display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  margin-top: .5rem; padding: .45rem .55rem;
  border: 1px solid #f0c8c8; background: #fff6f6; border-radius: .4rem;
  font-size: .76rem;
}
.psbuy-confirm-msg { flex: 1 1 auto; min-width: 130px; color: #8a2b2b; line-height: 1.35; }


.psbuy-body { display: flex; gap: 1rem; align-items: flex-start; flex-wrap: wrap; flex: 1 1 auto; min-height: 0; }
/*
 * ⚠️ **画布跟着滚动粘住**（用户 2026-09-19：变体一多、右栏滚下去，左边的 skuimg
 *    就被顶出视口了，填定制信息时看不见自己在改哪）。
 *    `position: sticky` + `align-self: flex-start` 让它在右栏滚动期间一直停在顶部；
 *    换行布局下（窄屏 flex-wrap）sticky 不会生效，那时左右本来也是上下排的。
 */
/* 画布：**不参与伸缩、有明确上限**（`flex-grow:0`）—— 多出来的宽度留给右边那栏的行。
   拉伸画布没意义（它是正方形，越大越占高，反而挤压行），行变宽才有用：定制信息一行能读完。
   `flex-shrink:1` 保留，窄屏时它还能收。 */
.psbuy-canvas-wrap { flex: 0 1 520px; min-width: 280px; align-self: flex-start; position: sticky; top: .25rem; }
/* 正方形画布：宽度定死、高度用 padding-top 顶出来，里面的绝对层才不会塌 */
.psbuy-stage {
  position: relative; width: 100%; padding-top: 100%;
  background: var(--light); border-radius: .5rem; overflow: hidden;
}
.psbuy-stage > img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; }
.psbuy-layer { position: absolute; inset: 0; }

/* 区域框：**只读**——买家改不了位置，所以光标是默认、没有手柄 */
.psbuy-box {
  position: absolute; box-sizing: border-box; overflow: hidden;
  border: 1px dashed rgba(4,83,243,.55); background: rgba(255,255,255,.15);
  border-radius: .25rem; display: flex; align-items: center; justify-content: center;
}
.psbuy-box-image { border-color: rgba(16,150,90,.6); }
.psbuy-box img { max-width: 100%; max-height: 100%; object-fit: contain; }
.psbuy-box-text {
  display: block; width: 100%; text-align: center; line-height: 1.15;
  font-weight: 600; word-break: break-word; padding: 0 .15rem;
}
/* 空框里显示卖家的 label，提示这里该填什么 */
.psbuy-box-hint { font-size: .68rem; color: var(--muted); text-align: center; padding: 0 .2rem; }

/*
 * 右栏（#psbuyFields）**自己垂直滚动**（用户 2026-09-19："psbuyFields 给他垂直滚动！"）。
 *
 * ⚠️ `max-height` 这一行是**关键，别删**：它让这一栏的高度**不依赖任何父级** ——
 *    `68vh` 走视口，跟 `.psbuy-panel` / `.modal-body` 那串 flex 解不解得出高度**无关**。
 *    前面几版一直指望"父级给出确定高度、子项再 overflow"，链条一断就不滚了；
 *    把高度上限直接挂在这一栏自己身上，任何情况下都滚得起来。
 *
 * `align-items: flex-start`（在 .psbuy-body 上）保证它按内容量身高、被 max-height 截住，
 * 而不是被拉伸到整行高。
 */
/*
 * ⚠️ 右栏要**吃掉剩余宽度**（用户 2026-09-19）：弹窗从 800px(modal-lg) 放宽到
 *    1370px 之后，原先写死的 `flex: 0 0 260px` 让多出来的空间**全给了画布**，
 *    行那一栏还是 260px 宽 —— 布局就塌了。
 *    现在：画布有上限（见 `.psbuy-canvas-wrap`），右栏 `flex: 1 1 400px` 伸缩。
 */
.psbuy-fields {
  flex: 1 1 400px; min-width: 300px;
  max-height: 68vh;        /* 兜底：JS 量出来之前的初值（fitFields() 会覆盖成实测 px） */
  overflow-y: auto;
  padding-right: .25rem;
}

/*
 * 正文**不滚** —— 只有 `#psbuyFields` 滚（用户 2026-09-19）。
 * 用 `auto` 而不是 `hidden`：万一 JS 还没量上（首帧），宁可多一条滚动条也别把内容裁掉。
 * 量好之后正文的内容正好装得下，滚动条不会出现。
 */
#personalizeModal .modal-body,
#goodsBuyModal .modal-body.ps-open { overflow-y: auto; }

.psbuy-chip {
  border: 1px solid var(--border); background: #fff; color: var(--navy);
  border-radius: .4rem; padding: .2rem .5rem; font-size: .78rem; line-height: 1.2; cursor: pointer;
}
.psbuy-chip.is-on { border-color: var(--accent); box-shadow: 0 0 0 2px rgba(4,83,243,.12); }

/* 层级树：颜色 → 尺码 → 件。每层是一个可折叠节点，头部整条可点。 */
.psbuy-node { border: 1px solid var(--border); border-radius: .5rem; background: #fff; margin-bottom: .5rem; overflow: hidden; }
/* 头是一个 div，里面两颗按钮：左边"折叠/展开"，右边"清空"（见 personalize-buyer.js）。
   拆开是因为折叠是纯 UI、清空才动数据 —— 上一版把两件事绑在一颗按钮上，
   一折就弹确认框还要删内容（用户 2026-09-19）。 */
.psbuy-node-head { display: flex; align-items: center; width: 100%; background: #fff; }
.psbuy-node-toggle {
  flex: 1 1 auto; display: flex; align-items: center; gap: .4rem; min-width: 0;
  border: 0; background: transparent; color: var(--navy); text-align: left;
  padding: .45rem .6rem; font-size: .82rem; font-weight: 600; line-height: 1.3; cursor: pointer;
}
.psbuy-node-toggle:hover { background: var(--light); }
.psbuy-node-clear {
  flex: 0 0 auto; margin-right: .5rem; width: 22px; height: 22px; line-height: 1; padding: 0;
  border: 1px solid var(--border); background: #fff; color: var(--muted);
  border-radius: .25rem; font-size: .68rem; cursor: pointer;
}
.psbuy-node-clear:hover { border-color: #e5484d; color: #e5484d; background: #fff5f5; }

/* 颜色头上的「Same for all sizes」：把一个尺码那份抄给其它尺码（用户 2026-09-19：
   从行列表中间挪到节点头上、橡皮之前 —— 它管的是**整个颜色**，埋在里面太不显眼）。 */
.psbuy-node-same {
  flex: 0 0 auto; width: 22px; height: 22px; line-height: 1; padding: 0;
  border: 1px solid var(--border); background: #fff; color: var(--muted);
  border-radius: .25rem; font-size: .66rem; cursor: pointer;
}
.psbuy-node-same:hover { border-color: var(--accent); color: var(--accent); background: #eaf1ff; }

/* 颜色节点上的「×」= 整个颜色从选择里去掉（件数一起没）。
   与橡皮擦的区别：橡皮只清内容、件数留着；× 连件数一起去掉。 */
.psbuy-node-del {
  flex: 0 0 auto; margin-right: .5rem; width: 22px; height: 22px; line-height: 1; padding: 0;
  border: 1px solid var(--border); background: #fff; color: var(--muted);
  border-radius: .25rem; font-size: .62rem; cursor: pointer;
}
.psbuy-node-del:hover { border-color: #e5484d; color: #e5484d; background: #fff5f5; }
.psbuy-node-title { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.psbuy-node-meta { flex: 0 0 auto; font-size: .72rem; font-weight: 500; color: var(--muted); }
/*
 * 折叠时朝右（▸）、展开时朝下（▾）—— 用 aria-expanded 驱动，JS 不用单独加类。
 *
 * ⚠️ **aria-expanded 挂在 `.psbuy-node-toggle`（那颗 button）上，不是 `.psbuy-node-head`**
 *    （见 personalize-buyer.js 的 node()：head 是 div，里面装 [折叠/清空/×] 三颗 button，
 *    aria-expanded 是 button 自己的无障碍属性）。旧选择器写成
 *    `.psbuy-node-head[aria-expanded="true"]` —— 那个属性永远不在 head 上，
 *    于是**箭头永远朝右**：明明展开着，看图标却像折叠的
 *    （用户 2026-09-19 就是照这个图标报的"color div 是折叠的"）。
 */
.psbuy-node-toggle .psbuy-caret { transition: transform .15s ease; font-size: .7rem; color: var(--muted); }
.psbuy-node-toggle[aria-expanded="true"] .psbuy-caret { transform: rotate(90deg); }
/*
 * ⚠️ **这一层不要边框**（用户 2026-09-19："border 太多了，A different design per size
 *    外面的 closest border 不要"）。
 *
 * 面板里现在有三层框：颜色(body) / 变体(.psbuy-variant) / 行(.psbuy-group)。
 * 三层套下来是"盒子里套盒子套盒子"，而**变体那层已经足够表达分组**了 ——
 * 颜色这一层靠节点头（名字 + 件数）就能认出来，不需要再包一个框。
 * 所以这里退成纯容器：不描边、不上底色，只留间距。
 */
.psbuy-node-body { padding: .35rem .5rem .1rem; margin: 0 .35rem .4rem; }
/* 尺码 / 件级嵌套在上一层下面：左边一条竖线就够，再多几层框会糊成一片 */
.psbuy-node.is-size { margin-left: .5rem; margin-bottom: .4rem; }
.psbuy-node.is-size > .psbuy-node-head { font-size: .78rem; font-weight: 500; }
.psbuy-node.is-piece { margin-left: .5rem; margin-bottom: .35rem; background: #fbfcfe; border-style: dashed; }
.psbuy-node.is-piece > .psbuy-node-head { background: transparent; font-size: .76rem; font-weight: 500; padding: .35rem .5rem; }
.psbuy-node.is-piece > .psbuy-node-body { padding: .45rem .5rem .1rem; }

/* 单色单尺码：不出树形外壳，直接一组输入框（JS 走 isFlat 那条路） */
.psbuy-inputs { padding: 0 .1rem; }

/* 「一份 / 逐件」的切换条 —— 只在尺码里件数 >1 时出现 */
.psbuy-modeline {
  display: flex; align-items: center; gap: .5rem;
  margin-bottom: .5rem; padding-bottom: .4rem; border-bottom: 1px dashed var(--border);
}
.psbuy-modeline-label { flex: 1 1 auto; font-size: .74rem; color: var(--muted); }
.psbuy-modeline-btn {
  flex: 0 0 auto; border: 1px solid var(--accent); background: #fff; color: var(--accent);
  border-radius: .35rem; padding: .15rem .5rem; font-size: .72rem; font-weight: 600; cursor: pointer;
}
.psbuy-modeline-btn:hover { background: #eaf1ff; }

/*
 * 件组：一组 = 若干件共用一份内容。
 *
 * ⚠️ **斑马纹**（用户 2026-09-19："每个块区域区别太少、都是浅色，容易看串"）：
 *    相邻行交替底色，扫一眼就知道哪块是哪块。行多了以后这一点比什么都重要 ——
 *    全白/全浅的时候，13 行长一个样，改哪一行全靠数。
 */
/* 底色由 JS 按**变体**内联给（`tintFor()`）—— 同一个「颜色 × 尺码」的所有行同色，
   不同变体不同色。不再是按行的斑马纹：斑马纹只隔开相邻行，看不出"是不是同一个尺码"。 */
.psbuy-group {
  border: 1px solid #ccd6e3;          /* 比默认 border 深一档 —— 底色浅的时候才看得出边界 */
  border-radius: .4rem; padding: .45rem .5rem; margin-bottom: .45rem; background: #fff;
}

/* 每个**变体**（颜色 × 尺码）一个框（用户 2026-09-19："不同变体也用 border 抱起来"）。
   三层各管一件事：`#psbuyFields` 管整列、`.psbuy-node-body` 管"同色"、
   这里管"同尺码"。 */
.psbuy-variant {
  border: 1px solid #b9c7d9; border-radius: .5rem;
  padding: .45rem .5rem .1rem; margin-bottom: .6rem; background: #fff;
}

/* 组标题的分割线：同色的浅/深两档本来就差别很小，光靠底色分不出"这组到哪儿为止"。 */
.psbuy-vdivider { display: flex; align-items: center; gap: .5rem; margin: .1rem 0 .5rem; }
.psbuy-vdivider::before,
.psbuy-vdivider::after { content: ''; flex: 1 1 auto; height: 1px; background: #c3cfdd; }
.psbuy-vdivider span {
  flex: 0 0 auto; font-size: .7rem; font-weight: 700; color: #7a8ba0;
  text-transform: uppercase; letter-spacing: .04em;
}
.psbuy-group-head { display: flex; align-items: center; gap: .5rem; margin-bottom: .4rem; }
.psbuy-group-title { flex: 1 1 auto; font-size: .75rem; font-weight: 600; color: var(--navy); }
.psbuy-group-step { flex: 0 0 auto; display: inline-flex; align-items: center; gap: .15rem; }
.psbuy-step-btn {
  width: 20px; height: 20px; line-height: 1; padding: 0;
  border: 1px solid var(--border); background: #fff; color: var(--navy);
  border-radius: .25rem; font-size: .8rem; cursor: pointer;
}
.psbuy-step-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.psbuy-step-btn:disabled { opacity: .35; cursor: default; }
.psbuy-group-qty {
  width: 44px; text-align: center; font-size: .75rem;
  border: 1px solid var(--border); border-radius: .25rem; padding: .1rem .2rem;
}
.psbuy-group-meta { flex: 0 0 auto; font-size: .7rem; color: var(--muted); min-width: 34px; text-align: right; }

/* 「删掉这一行」= 把这些件从 buymodal 的选择里也去掉 */
.psbuy-del-btn {
  flex: 0 0 auto; width: 20px; height: 20px; line-height: 1; padding: 0;
  border: 1px solid var(--border); background: #fff; color: var(--muted);
  border-radius: .25rem; font-size: .62rem; cursor: pointer;
}
.psbuy-del-btn:hover { border-color: #e5484d; color: #e5484d; background: #fff5f5; }

/* 「复制这一行」—— 内容与 qty 各来一份，变体总数跟着涨 */
.psbuy-copy-btn {
  flex: 0 0 auto; width: 22px; height: 22px; line-height: 1; padding: 0;
  border: 1px solid var(--border); background: #fff; color: var(--muted);
  border-radius: .25rem; font-size: .66rem; cursor: pointer;
}
.psbuy-copy-btn:hover { border-color: var(--accent); color: var(--accent); background: #eaf1ff; }

/*
 * ==========================================================================
 * MOBILE (<= 576px)
 *
 * ⚠️ These belong in THIS file rather than assets/css/site-mobile.css: this
 *    sheet is pulled in by a `<link>` inside the modal partial — i.e. in the
 *    BODY — and a body `<link>` beats a head `<link>` on source order at equal
 *    specificity. A "fix" over in site-mobile.css would look right and do
 *    nothing at all.
 * ==========================================================================
 */
@media (max-width: 575.98px) {
  /* The two columns are `flex: 0 1 520px; min-width:280px` (canvas) and
     `flex: 1 1 400px; min-width:300px` (fields). At 390px the canvas has a 280px
     floor and the fields still demand 300px, so the pair cannot fit on one row;
     the fields end up pushed off the right edge. Stack them instead. */
  /*
   * ⚠️⚠️ A2 第一次改法**已回滚**（2026-10-14 真机截图）：把这里改成
   *    `flex-direction: row` + 画布 50%，结果**画布缩了、右半边空着，
   *    所有输入框被挤到 Add to Cart / Buy Now 下面**。原因没那么简单
   *    （`.psbuy-fields` 的高度/滚动链跟这条竖排是绑在一起的），
   *    在没搞清整条 flex 链之前**不要**再动这里。用户原话：
   *    「image 要缩小，image 和 customtext/customimg 要一行内显示」——
   *    重做时先量 `.psbuy-body` / `.psbuy-fields` 的实际盒子，别再猜。
   *
   * ⇒ **2026-10-14：量完了，row 布局已重做** —— 见**本文件末尾**那个
   *    `@media (max-width: 575.98px)`（单独放末尾是为了压过 `≤767.98` 块里
   *    排得更靠后的 `!important`）。下面的 `flex: 0 0 auto` 就是那次定位的结果。
   */
  /*
   * ⚠️⚠️ **A2 的两次翻车，根因是同一个**（`4f8b5307` 的 row 改法、`5cfa8ec6` 的
   *     nowrap 改法）—— **手机上 `.psbuy-body` 仍然是"唯一可伸缩、有界"的那一项**
   *     （`flex: 1 1 auto; min-height: 0` 来自 `:200`）。
   *
   *     它高度受限，内容又装不下时只有两条路，两条都错：
   *       · `flex-wrap: wrap`（基础规则）⇒ 折到右边**另起一列**，右栏被顶出屏幕；
   *       · `nowrap`（只加这一条）⇒ 竖向**溢出**，输入框画到 Add to Cart / Buy Now 下面
   *         —— 与 `4f8b5307` 一模一样的坏状态。
   *
   *     真机探针两次实测：
   *       `IMG_3737`（wrap）  → `fields=w358,r744⚠超出+354`
   *       `IMG_3738`（nowrap）→ `fields=w358,r374`（横向对了，竖向下溢）
   *
   * ⇒ **手机上它根本不该有界**：`:454` 那边的注释早就写着"堆叠后 modal body 自己滚"。
   *    `flex: 0 0 auto` 让它变**内容高** —— 既没有东西可折、也没有东西可压，
   *    滚动交给 `.modal-body`（它本来就是 `scrollable`）。
   *    `nowrap` 留着当护栏（body 一旦内容高，它其实已无事可做）。
   */
  .psbuy-body { flex-direction: column; flex-wrap: nowrap; flex: 0 0 auto; gap: .75rem; }

  .psbuy-canvas-wrap {
    flex: 0 0 auto;
    min-width: 0;
    width: 100%;
    /* `position:sticky` only existed to keep the canvas in view beside a
       scrolling field list; once stacked it means nothing. */
    position: static;
    align-self: stretch;
  }

  /* `max-height:68vh` + `overflow-y:auto` gives the fields their own scrollbar
     *beside* the canvas. Stacked, the modal body already scrolls, and a nested
     scroller traps the thumb halfway down a long form. */
  .psbuy-fields {
    flex: 1 1 auto;
    min-width: 0;
    width: 100%;
    max-height: none;
    overflow: visible;
    padding-right: 0;
  }

  /* CDR (acrylic) canvas — a single 300px floor that will not fit either. */
  .psreg-canvas-wrap { flex: 1 1 100%; min-width: 0; }
}

/* ---------------------------------------------------------------------------
   TOUCH (<= 991.98px) — 把小方钮放大到点得着（2026-10-10 移动端修复）

   下面这些是**买家配置订单时每一步都要点的控件**：每行的数量 −/+（`.psbuy-step-btn`）、
   删行（`.psbuy-del-btn`）、复制行（`.psbuy-copy-btn`）、颜色头上的清空 / Same for all /
   删除（`.psbuy-node-*`）。它们原本是为**鼠标**画的 20–22px 方块 —— 触摸上既难点中
   也点不准，而这一整屏全是这种钮。

   只加**尺寸**、不动布局：这些行都是 `align-items:center` 的 flex，钮撑大之后自己在行里
   仍然居中。取 32px 而不是 44px 是**折中** —— 一屏要排这么多小钮，真给到 44px 会把
   右栏顶得又长又散；32px 已是"能稳稳点中"的下限。断点取 991.98 与
   `site-mobile.css` 的"触屏 = ≤lg"（竖屏平板同样是拇指设备）保持一致。
   --------------------------------------------------------------------------- */
@media (max-width: 991.98px) {
  .psbuy-step-btn,
  .psbuy-del-btn,
  .psbuy-copy-btn,
  .psbuy-node-clear,
  .psbuy-node-same,
  .psbuy-node-del { width: 32px; height: 32px; font-size: .85rem; }

  .psbuy-group-step { gap: .3rem; }
  .psbuy-group-qty { width: 52px; padding: .3rem .2rem; }

  /* 颜色切换 chip 也抬到 32px 的点击高度（原来是 ~22px）。 */
  .psbuy-chip { min-height: 32px; display: inline-flex; align-items: center; padding: .3rem .6rem; }

  /*
   * 弹窗宽度：Bootstrap 的 `.modal-dialog{margin:.5rem}`（site-mobile.css §1）给了左右
   * 各 .5rem 外边距，而这条链上的 `max-width: min(1370px,96vw)`（见本文件顶部
   * `#personalizeModal .modal-dialog, #goodsBuyModal .modal-dialog`）在 390px 上算出
   * 374.4px —— 比可用宽度（390 − 16 = 374px）**多 0.4px**，右边缘被
   * `html,body{overflow-x:clip}` 悄悄裁掉。窄屏直接按"可用宽度"取上界。
   */
  #personalizeModal .modal-dialog,
  #goodsBuyModal .modal-dialog { max-width: calc(100vw - 1rem); }
}

/* ---------------------------------------------------------------------------
   MOBILE (<= 767.98) — 画布那块的**折叠头**外观（2026-10-10）

   容器本身是 `.psbuy-canvas-wrap[data-mcoll]`（见 `partials/buy/personalize_panel.php`），
   收起/展开由 `m-collapse.js` + `site-mobile.css` §10 管。这里只负责头那行长什么样。
   断点必须与 §10 的 `max-width:767.98px` 一致 —— 否则会出现"折起来了、但头没样式"
   这种半截状态。
   ⚠️ 头那张签的名字里带的 `d-md-none`：≥768 它被 Bootstrap 藏掉（桌面不要这行）。
   --------------------------------------------------------------------------- */
@media (max-width: 767.98px) {
  .psbuy-canvas-head {
    display: flex; align-items: center;
    padding: .5rem .6rem;
    font-size: .82rem; font-weight: 600; color: var(--navy);
    border: 1px solid var(--border); border-radius: .5rem; background: #fff;
  }
}

/* ---------------------------------------------------------------------------
   MOBILE 兜底：窄屏**任何东西都不许顶出弹窗**（用户 2026-10-11 第 10 条）

   「点击 personalize 之后，移动端变体 qty +/- / × 会超出 div；
     preview & size chart 展开的时候没有 width:100% 导致变体超出了手机 width」

   ⚠️ 一半是我上一轮自己加的：2026-10-10 把那几颗小方钮从 20–22px 放大到 **32px**
      （为触控），行又没允许换行 → 一行里 [标题][−][3][+][件数] 加起来超过 342px。
     放大尺寸是对的方向，缺的是**让行能换、让标题能缩**。
   ⚠️ `min-width:0` 是必须的：flex 子项默认 `min-width:auto`（= 内容宽），
     宁可把容器撑破也不肯缩 —— 这一条在本仓踩过很多次。
   --------------------------------------------------------------------------- */

@media (max-width: 767.98px) {
  /*
   * B2（2026-10-14）：面板顶部那条「阶梯价 + ships-by」（`personalize_panel.php`）。
   *
   * 面板是 flex 列，子项默认 `flex-shrink:1` —— 高度紧的时候浏览器会**先压扁它**
   * （`.psbuy-body` 有 `min-height:0`，是那个"正确"的可伸缩项）。价带被压扁会直接
   * 裁字。买弹窗那边不用管这条：那份 strip 是 `.row` 的子项，Bootstrap 已经给了
   * `.row > * { flex-shrink:0 }`。
   */
  .psbuy-panel > .gq-top-strip { flex-shrink: 0; }

  /* 画布那块（含展开后的尺码表）整宽，别比手机宽 */
  .psbuy-canvas-wrap { width: 100% !important; min-width: 0 !important; flex: 0 0 auto !important; }
  .psbuy-canvas-head { width: 100%; }
  /*
   * 主图（`#psbuyStage`）现在**一直可见**（2026-10-13 从折叠块里搬出来了）——
   * 它是 1:1（`padding-top:100%`），不限高的话手机上就是"一个正方形占满一屏"，
   * 下面的定制字段要使劲滚。限宽即限高，居中。
   * ⚠️ 不用 30vh 那一档（那是买弹窗主图的）—— 用户这次要的是"**大**图"。
   */
  #psbuyStage { max-width: min(100%, 46vh); margin: 0 auto; }
  .psbuy-body,
  .psbuy-fields { width: 100%; min-width: 0; }
  /* 兜底：面板里不许有横向溢出 */
  .psbuy-panel { overflow-x: hidden; }

  /* 变体行：允许换行 + 每格都能缩 */
  .psbuy-variant,
  .psbuy-group-head,
  .psbuy-node-head { flex-wrap: wrap; row-gap: .35rem; }
  .psbuy-variant > *,
  .psbuy-group-head > *,
  .psbuy-node-head > * { min-width: 0; max-width: 100%; }
  /* 标题那格吃掉剩余宽度、长名字截断（别把它自己顶宽） */
  .psbuy-group-title,
  .psbuy-node-title { flex: 1 1 8rem; }
}

/* ==========================================================================
   手机端：**图缩小 + 与定制输入同一行**（用户 2026-10-14 第 3 条）

   用户原话（从 A2 第一天起就是这个诉求）：
     「image 要缩小，image 和 customtext/customimg 要**一行内**显示」。

   ⚠️ 为什么单独放在**文件最末尾**、而不是并进上面那个 `≤575.98` 块：

     上面 `≤767.98` 那块里有 `.psbuy-canvas-wrap { width:100% !important; … }`
     （`:569`）和 `.psbuy-body,.psbuy-fields { width:100% }`（`:578-579`）。
     CSS 同权重（都是 `!important` 或都不是）时**后写的赢**，而 `≤767.98`
     在两块里**排在后面** ⇒ 想在更窄的屏上改成"画布只占 40%"，必须排到它**之后**。
     放在 575.98 那块里改是**改不动的** —— 这也是这个功能第一次尝试时没料到的坑之一。

   ⚠️ 第一次尝试（`4f8b5307`，已回滚）用 row + 画布 50%，结果输入框被挤到
     Add to Cart / Buy Now **下面**。根因不是 row 本身，而是当时
     `.psbuy-body` 还是 `flex: 1 1 auto; min-height: 0`（**高度受限**）：
     row + 基础规则的 `flex-wrap: wrap` ⇒ 两项一行放不下就折到**第二行**，
     又因为 body 有界，第二行直接溢出到按钮下面。
     现在 `:460` 已把 body 改成 **`flex: 0 0 auto`（内容高）**，并且这里再显式
     `nowrap` —— 两项**必定**在同一行，body 随内容长高、由 `.modal-body` 滚。
     ⇒ 真机上应当是"左边小图、右边输入框，两者并排"。
   ========================================================================== */
@media (max-width: 575.98px) {
  /* 一行两项；`nowrap` 是护栏：body 已内容高，本来也没东西可折。 */
  .psbuy-body {
    flex-direction: row;
    flex-wrap: nowrap;
    align-items: flex-start;
    flex: 0 0 auto;      /* 重申：内容高（覆盖 `:200` 的有界值） */
    gap: .6rem;
  }

  /* 左：小图（连它下面那截可折叠的色块/尺码表一起）。40% ≈ 手机上 143px 见方。 */
  .psbuy-canvas-wrap {
    width: 40% !important;        /* ← 必须 `!important`：压过 `:569` 那条同权重的 */
    flex: 0 0 40% !important;
    min-width: 0 !important;
    align-self: flex-start;
    position: static;             /* 并排时 sticky 没有意义（`:449` 同口径） */
  }

  /* 右：定制输入吃掉剩下的宽度。`width:auto` 压过 `:579` 的 `width:100%`。 */
  .psbuy-fields {
    width: auto !important;
    flex: 1 1 auto;
    min-width: 0;
  }
}

/* ==========================================================================
   定制面板：**新结构（所有宽度，PC 与手机一致）** —— 用户 2026-10-15 第 1 条

   用户原话：「pc端也 img 做到 customtext 内！而且輸入 customtext、customimg
             要和 pc 段之前的 img 處理一樣，帶正確位置預覽！」
   ⇒ **左列（大图 + Color & size chart）在 PC 上也整个去掉**，
     图改成挂在**每个颜色的行头里**（`.psbuy-node-thumb`），方框照旧按该盒子
     的宽度缩放绘制（`paintPreview()`），所以位置一定是对的。

   ⚠️ **必须放在文件最末尾**：上面的 `≤767.98` 块（`:561-597`）与 `≤575.98` 块
      （`:622`）用的是 `!important` 且排得靠后，同权重下只有排在它们**之后**才压得住。
      这里用 `!important` 的地方，都是因为对手也用了。

   ⚠️ **左列只能"藏"不能"删"**：`mount()` 里有
      `if (!stage || !layer || !fields || !addBtn) return;`，而 `#psbuyStage` 就住在
      `.psbuy-canvas-wrap` 里 —— 把容器从 DOM 拿掉，**整个面板一个都不渲染**。
      所以是 `display:none` 大图、容器留着（色块 + 尺码表还在它里面，跟着排到最后）。
   ========================================================================== */
.psbuy-body { flex-direction: column; }
.psbuy-fields {
  order: 1;
  width: 100% !important;
  min-width: 0;
  max-height: none;      /* 通栏了，不再是自己那一列滚 */
  overflow: visible;
}
.psbuy-canvas-wrap {
  order: 2;              /* 色块 + 尺码表挪到底部 */
  width: 100% !important;
  flex: 0 0 auto !important;
  min-width: 0 !important;
  position: static;      /* 不再并排，sticky 无意义 */
  align-self: stretch;
}
.psbuy-stage { display: none; }        /* 大图不再单独占一列；图进每一行 */
.psbuy-panel { flex: 0 0 auto; }       /* 内容高 —— 溢出的子元素才撑得大 body 的 scrollHeight */

/* ---- 行头最前面的必填标记（`*` = 没填完 / `✓` = 填完） ---- */
.psbuy-node-flag { display: inline-block; font-weight: 700; line-height: 1.1; }
.psbuy-node-flag[data-ok="0"] { color: #d93026; }
.psbuy-node-flag[data-ok="1"] { color: #10965a; }

/*
 * ---- 行内缩略图 ----
 * 结构与 `.psbuy-stage` 同一套（`img` + 绝对定位的 layer），只是小。
 * 方框位置由 JS 按**这个盒子的 clientWidth** 缩放（`paintPreview()`）——
 * 所以尺寸只改这里就够，JS 不用跟着改，位置也一定对。
 * 点它放大（`openPreviewZoom()`，浮层重画一遍同一份逻辑）。
 */
.psbuy-node-thumb {
  display: block;
  position: relative;
  width: 56px; height: 56px;
  flex: 0 0 56px;
  overflow: hidden;
  border-radius: .35rem;
  background: var(--light);
  cursor: zoom-in;
}
.psbuy-node-thumb > img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; }
.psbuy-node-thumb-layer { position: absolute; inset: 0; }

/*
 * 滑动条（`[Back][+][×]`）**桌面端永远不出现** —— 桌面没有滑动手势，
 * 行头的 `+`/`×` 照旧常显。手机端那条规则在下面的 `≤575.98` 里放出来。
 */
.psbuy-row-acts { display: none; }

/* ---- 点小图放大的浮层（自建：要放大的是"底图 + 方框"的合成体，不是单张图） ---- */
.psbuy-zoom {
  position: fixed; inset: 0; z-index: 2147483000;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: .5rem;
  background: rgba(0, 0, 0, .72);
  cursor: zoom-out;
}
.psbuy-zoom-box {
  position: relative;
  width: min(86vw, 420px);
  aspect-ratio: 1 / 1;
  background: var(--light);
  border-radius: .6rem;
  overflow: hidden;
}
.psbuy-zoom-box > img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; }
.psbuy-zoom-layer { position: absolute; inset: 0; }
.psbuy-zoom-note { color: #fff; font-size: .82rem; }

/* ==========================================================================
   手机端差异（用户 2026-10-14 第 2/4 条 + 2026-10-15 第 2/3 条）
   ========================================================================== */
@media (max-width: 575.98px) {
  /*
   * ---- 滑动后整行**只剩三颗按钮**（用户 2026-10-15 第 2 条）----
   *
   * 用户：「手機端滑動是整個 domRow 就只剩下 back / + / × 3 個 button 代替所有
   *        dom 內容」。所以露出时把这一行的**正常内容整块藏起来**，只留那条 bar。
   */
  .psbuy-row-acts { display: none; }

  /*
   * ⚠️ **滑动露出时高度必须不变**（用户 2026-10-15 第 1 条：
   *    「height 要保持原 dom 的 height 不变！」）。
   *
   * 所以这里**不能** `display:none` —— 那会把整行压成只剩按钮那一条高，
   * 下面所有行会往上跳一大截。
   * 改用 `visibility:hidden`：**占位照留**（高度、布局、间距全在，只是看不见），
   * 按钮那条 `position:absolute; inset:0` 盖在原来那块的上面 ——
   * 于是整行的高度/位置与滑动前**逐像素一致**，什么都没跳。
   */
  .psbuy-group { position: relative; }
  .psbuy-group.is-row-open .psbuy-group-head,
  .psbuy-group.is-row-open .psbuy-inputs { visibility: hidden; }

  .psbuy-group.is-row-open .psbuy-row-acts {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .3rem .35rem;
    position: absolute;
    inset: 0;              /* 盖住原来那一整块，而不是挤出一个新高度 */
  }

  /*
   * ---- 行头标题太长就截断（用户 2026-10-15）----
   * 标题现在是 **上游色号 · 色名**（`UB1389448-A104BK-10 · black`），
   * 色号可能很长 —— 用户原话「可以 hide 一部分如果太长的话」。
   * `min-width:0` 是必须的：flex 子项默认 `min-width:auto`，宁可撑破也不肯缩。
   */
  .psbuy-node-title {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /*
   * ---- 橡皮（清这个颜色的定制内容）也去掉（用户 2026-10-15 第 4 条）----
   * 「手機端 customizedmodal 內的橡皮 button 也可以刪除不要」。
   * ⚠️ 只是隐藏；DOM 与 `clearColor()` 都还在（桌面端照旧有）。
   */
  .psbuy-node-clear { display: none; }
  .psbuy-row-acts .psbuy-act {
    min-height: 36px;
    border-radius: .45rem;
    border: 1px solid var(--border);
    background: #fff;
  }
  .psbuy-row-acts .psbuy-act-copy,
  .psbuy-row-acts .psbuy-act-del { min-width: 44px; }
  .psbuy-row-acts .psbuy-act-back { flex: 1 1 auto; }        /* Back 占满剩下的，好点 */
  .psbuy-row-acts .psbuy-act-del { color: #d93026; border-color: #f0c8c8; }

  /*
   * ---- 行头原有那两颗 `+`/`×` 都不要了（用户 2026-10-15 第 3 条）----
   *
   * 「手機端原來的 X 就不需要了已經在滑動中了」—— 用户在"两个都去掉"里选的，
   * 所以**行里那颗 × 和颜色头上那颗 × 都藏掉**，删除统一走滑动条里那颗。
   * ⚠️ 只是 `display:none`，DOM 与事件都还在（复制那颗 `+` 也一并藏，
   *    因为它同样已经在滑动条里了）。
   */
  .psbuy-group .psbuy-copy-btn,
  .psbuy-group .psbuy-del-btn { display: none; }
  .psbuy-node-del { display: none; }
}
