/* ============================================================
 * base.css — ตัวแปรสี ฟอนต์ และ component พื้นฐานที่ใช้ร่วมกันทุกหน้า
 * ============================================================ */

/* ---------- ตัวแปร ---------- */
:root {
  /* ---------- ธีมหลักของระบบ (Global Theme) ----------
   * ⚠⚠ **เปลี่ยนจากเขียวหัวเป็ดเป็นน้ำเงิน Modern Technology / Business Cloud
   *   13 ก.ย. 2569 (ผู้ใช้สั่ง)** — จานสีมาจากสเปคของผู้ใช้ ห้ามเปลี่ยนเฉดเอง
   * ⚠ ชั้น 1: ตัวแปรความหมาย (--primary · --focus · --accent ...) = ค่าตามสเปค
   *   ชั้น 2: สเกล --brand-* เดิม (ใช้อยู่ ~170 จุด) ชี้มาที่ชั้น 1
   *   ⟹ เปลี่ยนธีมรอบหน้า แก้ที่บล็อกนี้ที่เดียว ไม่ต้องไล่แก้กฎ
   * ⚠ หน้าที่ของแต่ละขั้นในสเกลเดิม (ไล่จากจุดที่เรียกใช้จริง):
   *   600 = ปุ่มหลัก ลิงก์ แท็บที่เลือก · 700 = ตัวหนังสือเข้มบนพื้นอ่อน
   *   500 = เส้นโฟกัส · 400 = ขอบช่องกรอกตอนโฟกัส/ขอบตอนชี้
   *   300 = ขอบตอนชี้แบบอ่อน · 100/50 = พื้นอ่อนตอนชี้/ที่เลือก
   *   800→950 = ไล่สีพื้นเมนูซ้าย
   * ⚠ Cyan (--accent) ใช้น้อยโดยตั้งใจ — แถบบอกเมนูที่อยู่ + ป้ายสถานะ "รอ"
   *   ไม่ใช่สีปุ่ม ไม่งั้นกลายเป็นนีออน */
  --primary:        #1565C0;
  --primary-dark:   #0D47A1;
  --primary-hover:  #1976D2;
  --primary-light:  #E3F2FD;
  --accent:         #00ACC1;
  --focus:          #1565C0;
  /* ตัวหนังสือบนพื้นน้ำเงินเข้มของเมนูซ้าย — ขาวอมฟ้า (เดิม #cfe6e2 ขาวอมเขียว)
     ⚠ อยู่ที่ :root ไม่ใช่ .sidebar — ป้ายรุ่นบางตัวอยู่นอกเมนูซ้าย */
  --side-ink:       #D5E3F4;
  --side-ink-rgb:   213, 227, 244;

  --brand-950: #061E47;
  --brand-900: #082A63;
  --brand-800: #0A3A85;
  --brand-700: var(--primary-dark);
  --brand-600: var(--primary);
  --brand-500: var(--focus);
  --brand-400: var(--primary-hover);
  --brand-300: #90CAF9;
  --brand-100: var(--primary-light);
  --brand-50:  #F1F8FE;

  --logo-blue:  #1b4fa0;
  --logo-red:   #e2231a;
  --logo-green: #17703a;

  /* กลาง ๆ — โทนเทาอมฟ้าตามธีมน้ำเงิน (13 ก.ย. 2569)
     ⚠ --ink = Text · --ink-3 = Text Secondary · --line = Border ตามสเปค
       --ink-2 / --line-2 ไม่มีในสเปค ⟹ เลือกขั้นกลางในตระกูลสีเดียวกัน */
  --ink:       #263238;
  --ink-2:     #455A64;
  --ink-3:     #607D8B;
  --line:      #D6E0E8;
  --line-2:    #EAF0F5;
  /* ⚠ ขอบรูปถ่ายโดยเฉพาะ **เข้มกว่าเส้นทั่วไปมาก โดยตั้งใจ** — รูปถ่ายมีขอบ
     เป็นสีอะไรก็ได้ (เพดาน ผนัง โต๊ะไม้สีครีม) เส้นระดับ --line/--line-2
     ได้อัตราส่วนกับพื้นขาวแค่ 1.13–1.25 ซึ่งเท่ากับไม่มีขอบ
     ผู้ใช้ทัก 2 รอบว่ายังไม่เห็นกรอบ (6 ส.ค. 2569) รอบแรกขยับเป็น 1.68 ยังไม่พอ
     ⚠ 2.65 บนพื้นขาว = เห็นชัดแน่ ๆ แต่ยังเป็นเส้นเทาบาง ไม่ใช่กรอบดำ
       อยากอ่อนลงหนึ่งขั้นใช้ #9fb0b8 (2.24) · เข้มขึ้นใช้ #6b7f89 (4.18)
     ⚠ แก้ที่ตัวแปรนี้ที่เดียว ห้ามไปแก้ตรง .photo — วันหลังมีที่อื่นต้องใช้
       ขอบแบบเดียวกันอีก (เช่น รูปในหน้าสต๊อก) จะได้ไม่หลุดออกจากกัน */
  --photo-edge: #8fa2ab;
  --bg:        #F4F7FA;
  --surface:   #FFFFFF;

  /* ---------- สีประจำนิติบุคคล (ผู้ใช้สั่ง 7 ก.ย. 2569 "เลข inv ให้แบ่ง bg
   *            เป็น 3 กลุ่ม เพื่อเห็นชัดเจน") ----------
   * ⚠⚠ **ห้ามยืมสีสถานะ (ok/warn/danger/info) มาใช้แทน** — สีพวกนั้นแปลว่า
   *   "ดี/เตือน/ผิดพลาด" ทั้งระบบ · เอามาแปะชื่อนิติบุคคลเมื่อไหร่ คนจะอ่านว่า
   *   ใบของสมาร์ทเน็ตมีปัญหา ทั้งที่ไม่มีอะไรเลย ⟹ ต้องมีชุดของตัวเอง
   * ⚠ ส้ม (--warn-bg) ถูกใช้กับป้าย "รับชำระ" ที่อยู่ใต้เลข INV ช่องเดียวกัน
   *   ⟹ ห้ามใช้ส้มกับชิปนิติ
   *
   * ⚠⚠ **ชุดสีนี้ผู้ใช้เคาะเอง 7 ก.ย. 2569** ("สมาร์ทโซลูชั่นให้เป็นสีเขียวอ่อน
   *   อีก 2 อัน เป็น น้ำเงินอ่อน ม่วงอ่อน") — **ห้ามเปลี่ยนเฉดเองโดยไม่ถาม**
   *   ⚠ ของเดิมผมเลือกเทา/เขียว/ฟ้า พร้อมเหตุผลว่า "สมาร์ทโซลูชั่นเป็นรายใหญ่
   *     X จาก Y ใบ จึงให้สีเทากลาง ๆ ไม่ให้ตะโกน" — **เหตุผลนั้นถูกสั่ง
   *     ทับไปแล้ว** จดไว้เพื่อไม่ให้ใครหยิบมาอ้างกลับ ไม่ใช่กติกาที่ยังใช้อยู่
   *   ⚠ ม่วงอ่อนของสมาร์ทเน็ต **คนละชั้นกับป้ายม่วงทึบ "ใบย้อนหลัง"** (#8457b8
   *     พื้นทึบตัวหนังสือขาว) วัดแล้วห่างกัน ΔE 58.7 ⟹ ไม่สับสน
   * ⚠ ตัวหนังสือต้องได้อัตราส่วนกับพื้นของตัวเอง ≥ 7 — วัดด้วยด่านใน
   *   tools/probe-billcover.html ไม่ใช่กะเอา
   *
   * ⚠⚠ **"สีไม่เท่ากัน" ไม่เท่ากับ "แยกออก"** — ชุดแรกที่ผมเลือก (เทา #e6ecef
   *   กับฟ้า #dce7f7) ต่างกันจริงและด่านผ่าน แต่ ΔE แค่ 7.1 ⟹ ดูภาพจริงแล้ว
   *   2 สีนี้กลืนกัน ซึ่งแพ้คำสั่ง "เพื่อเห็นชัดเจน" ตั้งแต่ต้น
   *   ⟹ ชุดที่ใช้อยู่วัดแล้วคู่ที่ใกล้กันสุด ΔE 13.7 · ด่านบังคับ ≥ 8 ไว้
   * ⚠ **สมาร์ทพลัส กับ สมาร์ทเน็ต คือคู่ที่ใกล้กันที่สุดในชุด** (13.7) — 2 ชื่อนี้
   *   คล้ายกันที่สุด คนอ่านผ่าน ๆ สลับกันง่ายสุด ⟹ เวลาปรับเฉด ให้ระวัง
   *   คู่นี้ก่อนคู่อื่น อย่าให้เข้าใกล้เกณฑ์
   *
   * ⚠⚠ **ตัวเลขเข้ม พื้นจาง — ไม่ใช่เข้มทั้งคู่** (ผู้ใช้สั่ง 7 ก.ย. 2569
   *   "ขอเลข inv สีเข้มๆ") ⟹ ชุดแรกได้ 7.5-8.3:1 ซึ่งอ่านออกสบายแต่ยัง
   *   "จาง" ในสายตาเขา · ตอนนี้อยู่ที่ 11.6-13.4:1
   *   ⚠ **ตอนเปลี่ยนเฉดตามที่เขาสั่งรอบหลัง ต้องลากความเข้มมาด้วย** — สีอ่อน
   *     ชุดใหม่ถ้าใช้ตัวหนังสือระดับเดิมจะได้แค่ 8.3-9.8:1 = ถอยหลังจากที่
   *     เขาเพิ่งสั่ง ⟹ ไล่ตัวหนังสือให้เข้มขึ้นตามพื้นที่เปลี่ยนไปเสมอ
   *   ⚠ **กลุ่มถูกบอกด้วยสีพื้น ไม่ใช่สีตัวหนังสือ** ⟹ ตัวหนังสือเข้มจน
   *     เกือบดำได้โดยไม่เสียการแยกกลุ่มเลย · ด่านบังคับ ≥ 7:1 ไว้แล้ว
   *     ห้ามลดกลับไปจาง ๆ โดยไม่ได้ถามเขาก่อน */
  --co-1-bg: #d6f2d9;  --co-1-ink: #082718;   /* สมาร์ทโซลูชั่น   — เขียวอ่อน  13.4:1 */
  --co-2-bg: #d2e2fc;  --co-2-ink: #0b1d40;   /* สมาร์ทพลัส      — น้ำเงินอ่อน 12.7:1 */
  --co-3-bg: #efd9fb;  --co-3-ink: #361452;   /* สมาร์ทเน็ต  — ม่วงอ่อน   11.6:1 */

  /* สถานะ — **ความหมายเดิมทุกตัว** เปลี่ยนแค่เฉดตามสเปคธีมใหม่ (13 ก.ย. 2569)
     ⚠ ok/warn/danger = Success/Warning/Danger ตามสเปค · ยังเป็นเขียว/ส้ม/แดง
     ⚠ info คงเดิม — เป็นเกรด B ของ D-Report ด้วย ต้องแยกจากเกรดอื่นได้
     ⚠⚠ **pend ย้ายจากเขียวหัวเป็ดไปเป็นฟ้าอมเขียว (ตระกูล --accent)** — ไม่ใช่
       เพราะมันเขียว แต่เพราะ Success ใหม่ (#16A085) เข้าใกล้ pend เดิม (#12857a)
       จนป้าย "รอ" กับป้าย "สำเร็จ" ดูเป็นสีเดียวกัน ⟹ ต้องขยับหนีเพื่อรักษาความต่าง
       ตัวหนังสือ #00838F บนพื้น #E0F7FA ได้ ~4.1:1 ใกล้ของเดิม */
  --ok:      #16A085;
  --ok-bg:   #e3f7ee;
  --warn:    #F39C12;
  --warn-bg: #fdf0df;
  --danger:  #E53935;
  --danger-bg:#fde9e9;
  --info:    #2f7fd1;
  --info-bg: #e7f1fc;
  --pend:    #00838F;
  --pend-bg: #E0F7FA;

  /* ⚠ 3 ตัวนี้เพิ่ม 6 ส.ค. 2569 — ก่อนหน้านั้น CSS เรียก var() หาชื่อเหล่านี้
     อยู่ 8 จุดโดยที่**ไม่มีใครประกาศไว้เลย** เบราว์เซอร์จึงทิ้งทั้งบรรทัดเงียบ ๆ
     ผลที่เห็นได้จริง: กรอบรูปภาพไม่มีมุมโค้งและไม่มีพื้นรองตอนภาพยังไม่ขึ้น
     ⚠ ตัวแปรที่ไม่มีอยู่จริงไม่มี error ให้จับ ต้องไล่ด้วยการเทียบรายชื่อ
       ที่ประกาศกับที่เรียกใช้ (ดูวิธีในบันทึกงาน) */
  --surface-2: #F7F9FC;   /* พื้นรองอ่อน — ช่องภาพก่อนโหลด · กล่องพรีวิว */
  --r-2:       10px;      /* มุมโค้งของรูป/กล่องภาพ อยู่ระหว่าง sm กับ ปกติ */
  --muted:     #607D8B;   /* = --ink-3 คนละชื่อที่ใช้ปนกันมา */
  --radius:    12px;
  --radius-sm: 8px;
  --radius-lg: 18px;
  /* ⚠ เงาย้อมน้ำเงินเข้ม (--brand-950 = 6,30,71) ไม่ใช่เขียวเข้มแบบเดิม
     ⚠ ม่านหลังกล่อง (modal/sheet) ใช้ --scrim-rgb ตัวเดียวกัน */
  --scrim-rgb: 6, 30, 71;
  --shadow-1: 0 1px 2px rgba(var(--scrim-rgb),.06), 0 1px 3px rgba(var(--scrim-rgb),.05);
  --shadow-2: 0 4px 12px rgba(var(--scrim-rgb),.08), 0 2px 4px rgba(var(--scrim-rgb),.05);
  --shadow-3: 0 16px 40px rgba(var(--scrim-rgb),.18);
  /* เงาเรืองของปุ่มหลัก = --primary (21,101,192) */
  --primary-rgb: 21, 101, 192;

  --sidebar-w: 262px;

  /* ⚠ --ecg-tile (ลายคลื่นหัวใจ) ถอดออก 13 ก.ย. 2569 — ไม่มีที่ใช้แล้วหลังผู้ใช้สั่งเอาลายออก */
  --topbar-h:  68px;
}

/* ---------- reset ---------- */
*, *::before, *::after { box-sizing: border-box; }

/* กฎ [hidden] ของเบราว์เซอร์มี specificity ต่ำมาก
 * ถ้า class ไหนตั้ง display (เช่น .alert{display:flex}) จะทับทิ้งทันที
 * ทำให้ใส่ hidden แล้วไม่ซ่อน — บังคับให้ชนะเสมอที่นี่ที่เดียว
 */
[hidden] { display: none !important; }

html, body { height: 100%; }

/* ⚠⚠ **2 บรรทัดนี้คือคันโยกขนาดตัวหนังสือของทั้งระบบ** (ผู้ใช้สั่งขยาย
     26 ส.ค. 2569 "ทำไมมันดูเล็กๆ" → เคาะให้ฐานเป็น 16px)
     เดิม html ปล่อยตามเบราว์เซอร์ (16px) · body 15px
     ⟹ **ตัวหนังสือในระบบมี 2 ฐานปนกันมาตลอด** ของที่ตั้งเป็น rem คิดจาก 16
       ของที่ไม่ได้ตั้งอะไรเลยตกทอดมาจาก body ได้ 15 · ขยายแค่ body
       จึงไม่พอ ตารางที่ใช้ .92rem จะไม่ขยับสักนิด

   ⚠ ขยับ html ด้วย = **ระยะห่างที่เขียนเป็น rem โตตามไปทั้งระบบ** ซึ่งตั้งใจ
     ให้เป็นแบบนั้น (ขยายตัวหนังสืออย่างเดียวแล้วยัดในกล่องขนาดเดิม อ่านแย่ลง)
     ⟹ แลกมาด้วยความเสี่ยงล้นกรอบ · วัดแล้วด้วย tools/probe-salescard.html
       และ probe-back.html ทั้งจอกว้างและจอมือถือ

   ⚠⚠ **เอกสารที่ส่งลูกค้าไม่ขยับตาม** — css/print.css ตั้ง `.paper` เป็น
     font-size: 21px ของตัวเอง และทั้งใบใช้ mm/pt/px ไม่ใช่ rem (ตรวจแล้ว
     578 mm · 94 pt · 249 px · rem แค่ 3 จุดที่เป็นของประกอบบนจอ)
     ⟹ กระดาษกับไฟล์ Excel เหมือนเดิมเป๊ะ **ห้ามเผลอเปลี่ยน .paper ตามมา** */
html { font-size: 17px; }

body {
  margin: 0;
  font-family: "Sarabun", "Segoe UI", system-ui, -apple-system, sans-serif;
  font-size: 16px;
  line-height: 1.6;
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 { margin: 0; font-weight: 600; line-height: 1.35; }
h1 { font-size: 1.5rem; }
h2 { font-size: 1.2rem; }
h3 { font-size: 1.05rem; }
p  { margin: 0 0 .75rem; }

a { color: var(--brand-600); text-decoration: none; }
a:hover { text-decoration: underline; }

img { max-width: 100%; display: block; }

button, input, select, textarea { font: inherit; color: inherit; }

/* ตัวเลขให้เรียงตรงกันในตาราง */
.tabular, td.num, th.num { font-variant-numeric: tabular-nums; }

/* ---------- ปุ่ม ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  padding: .55rem 1.05rem;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  transition: background .15s, border-color .15s, box-shadow .15s, transform .05s;
}
.btn:hover  { background: var(--line-2); }
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: 2px solid var(--brand-400); outline-offset: 2px; }
.btn[disabled], .btn.is-loading { opacity: .55; pointer-events: none; }

.btn-primary { background: var(--brand-600); color: #fff; }
/* ⚠ ชี้แล้ว "สว่างขึ้น" ตามสเปคธีมน้ำเงิน (Primary Hover #1976D2) ไม่ใช่เข้มลงแบบเดิม */
.btn-primary:hover { background: var(--primary-hover); }

/* ⚠ **พื้นเขียวอ่อนเป็นค่าปริยาย ไม่ใช่แค่ตอน hover** (ผู้ใช้สั่ง 6 ส.ค. 2569
     ให้ไล่เก็บทุกอย่างที่ "กดได้ / พิมพ์ได้" ให้เป็นเขียวอ่อนเหมือนช่องกรอก)
     ปุ่มขาวบนการ์ดขาวขอบเทาจาง อ่านไม่ออกว่าเป็นปุ่ม — ปัญหาเดียวกับที่เคย
     เจอกับช่องกรอกและชิปมาแล้ว ครั้งนี้เก็บให้ครบทั้งระบบ
   ⚠ hover ต้องเลื่อนไป brand-100 เพราะ brand-50 ถูกใช้เป็นพื้นปกติไปแล้ว */
.btn-outline { border-color: var(--line); background: var(--brand-50); }
.btn-outline:hover { border-color: var(--brand-400); background: var(--brand-100); }

.btn-ghost { background: transparent; }
.btn-ghost:hover { background: var(--line-2); }

/* ปุ่มลบแบบเบา — ไม่ใช้ btn-danger สีแดงเต็มใบ เพราะเป็นทางเลือกรอง
 * ในกล่องที่ปุ่มหลักคือ "บันทึก" ปุ่มแดงเต็มใบจะดึงสายตาผิดตัว */
.btn-ghost.danger { color: var(--danger); }
.btn-ghost.danger:hover { background: var(--danger-bg); }

.btn-danger { background: var(--danger); color: #fff; }
.btn-danger:hover { background: #bf3a3a; }

/* ปุ่ม "ได้รับ PO แล้ว" — ส้ม เข้าคู่กับป้ายสถานะ "รอ PO" ที่เป็นโทนส้ม
 * อ่านออกทันทีว่าปุ่มนี้คือตัวปลดสถานะนั้น โดยไม่ไปชนกับเขียว (ปุ่มปกติ)
 * และแดง (ลบ/ผิดพลาด) ที่มีความหมายอยู่แล้ว
 *
 * ⚠⚠ **สีนี้ตัวอักษรต้องเข้ม ห้ามเป็นขาว** (ผู้ใช้เลือก #ffba00 เมื่อ 10 ส.ค. 2569)
 *   ขาวบน #ffba00 ได้ **1.71:1** ตก WCAG AA หนักมาก (ต้อง ≥ 4.5)
 *   ตัวอักษรสีเข้ม --ink ได้ **9.72:1** ผ่านสบาย
 *   สีเหลืองสดเป็นสีที่สว่างเกินกว่าจะรองรับตัวหนังสือขาวได้ — กฎนี้จริงเสมอ
 *   ไม่เฉพาะสีนี้ ถ้าวันหลังเปลี่ยนสีพื้น ให้คิดอัตราก่อนเสมอ
 *   (เดิมเป็น #b45f0d ขาวบนนั้นได้ 4.58:1 จึงใช้ขาวได้)
 *
 * ⚠ ใช้ทั้งปุ่ม "ยืนยันคำสั่งซื้อ" ท้ายหน้า และปุ่ม "ใส่เลข PO" ในการ์ด
 *   โดยตั้งใจ — เป็นงานเรื่องเดียวกัน สีเดียวกันจึงอ่านเป็นชุดได้ */
.btn-po { background: #ffba00; color: var(--ink); }
.btn-po:hover { background: #e8a900; }
/* ⚠ ปุ่มรับ PO ย้ายลงไปเป็นปุ่มลงมือท้ายหน้า (btn-send) เงาของ btn-send
   เป็นเขียวแบรนด์ วางใต้ปุ่มส้มแล้วสีขุ่นเหมือนเปื้อน ต้องเปลี่ยนเป็นส้มตาม */
.btn-po.btn-send { box-shadow: 0 8px 18px -8px rgba(255, 186, 0, .75); }
.btn-po.btn-send:hover { box-shadow: 0 12px 22px -8px rgba(255, 186, 0, .85); }

/* ปุ่มลงมือหลักของหน้า (เช่น "ส่งใบเสนอราคา") — ใหญ่ขึ้นและมีเงาสีแบรนด์
 * ยังใช้สีหลักของแบรนด์ (น้ำเงิน) ไม่ใช้แดง เพราะในระบบนี้แดง = ผิดพลาด/ลบ
 * ถ้าปุ่มปกติกลายเป็นแดง คนจะลังเลก่อนกดทุกครั้ง
 * ให้เด่นด้วยขนาด+เงา แล้วลดปุ่มข้าง ๆ เป็นแบบโปร่งแทน จะเหลือปุ่มทึบใบเดียวบนหน้า */
.btn-send {
  padding: .65rem 1.6rem;
  font-size: 1rem; font-weight: 700;
  box-shadow: 0 8px 18px -8px rgba(var(--primary-rgb), .75);
}
.btn-send:hover {
  transform: translateY(-1px);
  box-shadow: 0 12px 22px -8px rgba(var(--primary-rgb), .85);
}
.btn-send:active { transform: translateY(0); }

.btn-sm { padding: .32rem .7rem; font-size: .86rem; }
/* ปุ่มไอคอนเปล่า — ต้องเซ็ต border เองด้วย
 * ถ้าไม่เซ็ต <button> จะได้กรอบดำ default ของเบราว์เซอร์
 * ส่วน <a> ไม่มี ทำให้ปุ่มในแถวเดียวกันหน้าตาไม่เหมือนกัน */
.btn-icon {
  padding: .4rem; width: 34px; height: 34px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent; color: var(--ink-3);
}
/* ⚠ **ห่อ :hover ด้วย (hover: hover) — บนมือถือ :hover ค้างหลังแตะ**
   นิ้วแตะปุ่มกระดิ่งแล้วพื้นเขียวติดค้างจนกว่าจะไปแตะที่อื่น ทำให้อ่านเหมือน
   ปุ่มถูกกดค้างอยู่ (ผู้ใช้เจอเอง 8 ส.ค. 2569 — รายงานว่า "กระดิ่งมี bg เขียว")
   จอสัมผัสไม่มีตัวชี้ที่ลอยอยู่เหนือปุ่มได้อยู่แล้ว hover จึงไม่มีความหมาย
   ⚠ ที่อื่นยังไม่ได้ห่อ (.tab:hover · .table tr:hover · .btn-link:hover)
     ตารางกลายเป็นการ์ดบนมือถือจึงไม่ค่อยเห็นผล แต่ถ้าเจออาการเดียวกันที่ไหน
     ให้ห่อแบบนี้ อย่าไปถอด :hover ทิ้ง — เมาส์ยังต้องมี */
/* ⚠⚠ **ปุ่มลบต้องเห็นว่าแดงตั้งแต่ยังไม่เอาเมาส์ไปชี้** (ผู้ใช้สั่ง 30 ส.ค. 2569
   "ใส่สีแดงอ่อน ๆ ด้วย") — ของเดิม .btn-icon.danger มีสีเฉพาะตอน hover
   ที่เหลือเป็นเทาเหมือนไอคอนเอกสารข้าง ๆ ⟹ **บนจอสัมผัสไม่มีวันเห็นเลย**
   ว่าอันไหนคือปุ่มลบ (มือถือไม่มี hover · ดูหมายเหตุข้างล่าง)
   ⚠ แดงอ่อนกว่า --danger (#d64545) ที่ใช้ตอน hover — พอเอาเมาส์ไปชี้จะเข้มขึ้น
     เป็นการบอกว่า "กำลังจะกดอันนี้" อีกชั้น
   ⚠ กระทบปุ่มลบใบร่างในหน้า "ใบเสนอราคาของฉัน" ด้วย (ใช้คลาสเดียวกัน)
     ซึ่งตั้งใจ — ปุ่มลบทั้งระบบควรหน้าตาเหมือนกัน */
.btn-icon.danger { color: #e07b7b; }

/* ⚠⚠ **ป้ายในช่องจังหวัดของคู่ค้าเป็นสีส้ม ไม่ใช่เทาเหมือนป้ายอื่น**
   (ผู้ใช้สั่ง 30 ส.ค. 2569 "ให้แสดง สีส้มชัด ๆ") — ช่องนี้ต่างจากช่องอื่น
   ตรงที่ **กรอกแล้วไม่ขึ้นบนใบ** คนกรอกจึงต้องรู้ตั้งแต่ยังไม่ได้พิมพ์ว่า
   มันคนละเรื่องกับช่องที่อยู่ที่อยู่เหนือมันพอดี
   ⚠ **ต้องมี opacity: 1 ด้วย** — Firefox หรี่ป้ายลงเองเป็นค่าตั้งต้น
     ใส่แต่สีแล้วจะได้ส้มจาง ๆ ที่อ่านว่าเป็นช่องที่ปิดอยู่
   ⚠ ใช้ #b45f0d ไม่ใช่ --warn (#e07b1a) — ตัวหลังอ่อนเกินไปบนพื้นช่องกรอก
     (เหตุผลเดียวกับที่ .pill-warn เลือกสีนี้เป็นสีตัวหนังสือ)
   ⚠ **ต้องมีทั้ง 2 ฟอร์ม** (เซล #bizProvince · office #qiBizProvince)
     — กฎใน samecheck เฝ้าเรื่องช่องนี้อยู่แล้ว ห้ามใส่ที่เดียว */
#bizProvince::placeholder,
#qiBizProvince::placeholder { color: #b45f0d; opacity: 1; }
@media (hover: hover) {
  .btn-icon:hover { background: var(--brand-50); color: var(--brand-600); }
  .btn-icon.danger:hover { background: var(--danger-bg); color: var(--danger); }
}

.btn-block { width: 100%; }

/* ---------- ฟอร์ม ---------- */
.field { display: flex; flex-direction: column; gap: .35rem; margin-bottom: 1rem; }
/* ป้ายอยู่ซ้าย ตัวเลือกอยู่ขวาบรรทัดเดียวกัน — ใช้กับตัวเลือกสั้น ๆ (chip 2 ปุ่ม)
   จอแคบ flex-wrap จะพาตัวเลือกลงบรรทัดใหม่เอง */
.field.inline { flex-direction: row; align-items: center; gap: .75rem; flex-wrap: wrap; }
.field > label { font-size: .875rem; font-weight: 500; color: var(--ink-2); }
.field .hint  { font-size: .78rem; color: var(--ink-3); }

/* คำทักใต้ช่องกรอก — "น่าจะพิมพ์ผิด" ไม่ใช่ "ห้ามบันทึก"
 * ⚠⚠ **สีส้มอำพัน ไม่ใช่แดง** — แดงในระบบนี้แปลว่าอันตราย/กั้นไว้
 *   ตัวนี้บันทึกต่อได้เสมอ ใช้แดงจะไปแย่งสายตาจากของที่กั้นจริง
 *   (เหตุผลชุดเดียวกับที่ผู้ใช้เคาะให้ป้าย "ใหม่" เป็นส้ม 25 ส.ค. 2569)
 * ⚠ #b45f0d บนพื้นขาวได้ 4.58:1 ผ่าน AA — ห้ามเปลี่ยนเป็น var(--warn)
 *   ตัวนั้นได้แค่ 2.99:1 (ดูหมายเหตุที่ .warn-text) */
.field-warn {
  margin-top: .25rem;
  font-size: .8rem; font-weight: 600;
  color: #b45f0d;
}
.field .req   { color: var(--danger); }

/* ---------- ช่องติ๊ก ----------
 * ⚠ กติกาเดียวกับชิปและช่องกรอก (ผู้ใช้สั่ง 6 ส.ค. 2569)
 *     ยังไม่ติ๊ก = พื้นเขียวอ่อน "ตรงนี้กดได้"
 *     ติ๊กแล้ว  = พื้นเขียวเข้ม + ถูกสีขาว "เลือกอันนี้อยู่"
 *   ของเดิมเป็นช่องติ๊กมาตรฐานของเบราว์เซอร์ ซึ่งพื้นขาวและหน้าตาคนละแบบ
 *   บน Windows / iOS / Android — ระบบเดียวกันแต่ 3 หน้าตา
 *
 * ⚠ **ต้องอยู่เหนือกฎ .chip input** — ชิปซ่อนช่องติ๊กจริงไว้ (opacity:0
 *   กว้าง 0) แล้ววาดตัวชิปเอง ถ้ากฎนี้อยู่ทีหลังจะไปเปิดช่องที่ตั้งใจซ่อน
 *   กลายเป็นกล่องเล็ก ๆ โผล่ในชิปทุกอัน (ความจำเพาะเท่ากัน ตัวหลังชนะ)
 * ⚠ 18px ไม่ใช่ 20 — ตารางจัดเตรียมสินค้ามีกฎบังคับ 20px ของมันเองอยู่แล้ว
 *   สำหรับจอเล็ก ที่นี่จึงเอาขนาดที่ไม่ดันความสูงแถวในตารางปกติ
 * ⚠ ขาวบน brand-600 = 5.53 ผ่าน AA
 */
/* ⚠ **ต้องมองออกว่า "กดได้" ตั้งแต่ยังไม่ติ๊ก** (ผู้ใช้สั่ง 10 ส.ค. 2569)
     เดิมเป็น brand-50 (#eef8f6) + ขอบ --line (#dfe7ea) ซึ่งจางทั้งคู่
     กล่อง 18px บนการ์ดขาวจึงแทบหายไปเลย คนหาช่องที่ต้องติ๊กไม่เจอ
     ขยับมาใช้ brand-100 + ขอบ brand-300 ให้เป็นกติกาเดียวกับช่องกรอก
     — **เขียว = แตะแล้วทำอะไรได้** ซึ่งเป็นกฎที่วางไว้ตั้งแต่ 4-5 ส.ค. 2569
     ⚠ ขอบ 1.5px ไม่ดันขนาดกล่อง เพราะทั้งไฟล์เป็น border-box */
input[type="checkbox"] {
  appearance: none; -webkit-appearance: none;
  width: 18px; height: 18px; flex: none;
  display: inline-grid; place-content: center;
  border: 1.5px solid var(--brand-300);
  border-radius: 6px;
  background: var(--brand-100);
  cursor: pointer;
  transition: background-color .14s ease, border-color .14s ease;
}
input[type="checkbox"]::before {
  content: ""; width: 11px; height: 11px;
  transform: scale(0);
  background: #fff;
  clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
}
input[type="checkbox"]:checked {
  background: var(--brand-600); border-color: var(--brand-600);
}
input[type="checkbox"]:checked::before { transform: scale(1); }
input[type="checkbox"]:hover:not(:disabled) { border-color: var(--brand-500); }
input[type="checkbox"]:disabled { opacity: .5; cursor: default; }
input[type="checkbox"]:focus-visible { outline: 2px solid var(--brand-400); outline-offset: 2px; }

/* ---------- ช่องกรอกทุกชนิด ----------
 * ⚠ **พื้นเขียวจางเป็นค่าปริยาย ไม่ใช่ของพิเศษของช่องตัวเลข** (ผู้ใช้สั่ง
 *   5 ส.ค. 2569 ให้ไล่เก็บช่องที่ยังขาวอยู่ให้หมด)
 *   เหตุผลเดิมที่ทำให้ช่องตัวเลขเป็นเขียวใช้ได้กับทุกช่องเท่ากัน — ช่องขาว
 *   บนการ์ดขาวขอบเทาจาง ผู้ทดลองใช้งานอ่านเป็นข้อความ ไม่รู้ว่าแตะแล้วแก้ได้
 *   (4 ส.ค. 2569) การให้เฉพาะช่องตัวเลขยิ่งแย่ เพราะกลายเป็นว่าในฟอร์มเดียวกัน
 *   มี 2 มาตรฐาน คนอ่านต้องเดาว่าทำไมบางช่องเขียวบางช่องไม่เขียว
 *
 * ⚠ โฟกัสแล้วเป็นขาว = "กำลังพิมพ์ช่องนี้อยู่" เป็นตัวชี้ตำแหน่งที่ชัดกว่า
 *   ขอบเรืองแสงอย่างเดียว โดยเฉพาะบนมือถือที่คีย์บอร์ดบังครึ่งจอ
 *
 * ⚠ ยกเว้น [type="search"] — ช่องค้นหาไม่ใช่ "ข้อมูลที่ต้องกรอก" แต่เป็น
 *   ตัวกรองสิ่งที่เห็นอยู่ ทาสีเหมือนกันจะกลายเป็นว่าทุกอย่างบนจอเขียวหมด
 *   แล้วสีไม่ได้ชี้อะไรอีกต่อไป
 */
.input, .select, .textarea {
  width: 100%;
  padding: .55rem .75rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  /* ⚠⚠ **พื้นขาวตามสเปคธีมน้ำเงิน 13 ก.ย. 2569 (ผู้ใช้สั่ง — Input Background
     #FFFFFF)** ทับคำสั่งพื้นเขียวจาง 5 ส.ค. ข้างบน · ช่องที่แก้ไม่ได้ยังเป็น
     พื้นเทา (--line-2) จึงยังแยกจากช่องที่กรอกได้ */
  background: var(--surface);
  transition: border-color .15s, box-shadow .15s, background-color .15s;
}
.input[type="search"] { background: var(--surface); }
.input:focus, .select:focus, .textarea:focus {
  outline: none;
  border-color: var(--focus);
  background: var(--surface);
  box-shadow: 0 0 0 3px var(--brand-100);
}
/* ต้องอยู่หลังกฎโฟกัส — ช่องที่แก้ไม่ได้ห้ามดูเหมือนช่องที่กรอกได้ */
.input[readonly], .input[disabled] { background: var(--line-2); color: var(--ink-3); }

/* กำหนดสี placeholder เอง — ค่า default ของเบราว์เซอร์โปร่งแสงจนอ่านยาก
 * แต่ยังตั้งใจให้จางกว่าข้อความจริง เพื่อให้กวาดตาแล้วรู้ทันทีว่าช่องไหนยังว่าง
 *
 * ⚠ ไม่ใช่ --ink-3 แล้ว — พื้นช่องกรอกเปลี่ยนเป็นเขียวจาง (5 ส.ค. 2569)
 *   --ink-3 บนพื้นนั้นเหลือ 3.86:1 ตกเกณฑ์ AA ชัดเจน (บนขาวเดิมก็ 4.18 ซึ่ง
 *   ตกอยู่แล้ว) #5f727c ได้ 4.63 บนเขียว และ 5.02 บนขาว ผ่านทั้ง 2 พื้น
 *   ยังจางกว่าข้อความจริง (--ink 14.8) มากพอที่จะแยกออกด้วยตาเปล่า */
.input::placeholder, .textarea::placeholder {
  color: #5f727c;
  opacity: 1;              /* Firefox ลด opacity ให้เองโดยอัตโนมัติ ต้องสั่งกลับ */
}

/* select ที่ยังไม่ได้เลือก — ทำ option ว่างให้ดูเป็น placeholder เหมือนช่องพิมพ์
 * (select ใส่ placeholder ตรง ๆ ไม่ได้ ต้องทำเป็น option ตัวแรกที่ value="") */
.select:has(option[value=""]:checked) { color: var(--ink-3); }
.textarea { min-height: 84px; resize: vertical; }
.input.is-error, .select.is-error { border-color: var(--danger); }

/* ---------- ช่องกรอกที่มีคำกำกับติดอยู่ข้างหน้า ----------
 * placeholder หายทันทีที่พิมพ์ พอกรอกครบทั้งฟอร์มแล้วเปิดกลับมาดู
 * จะไม่รู้ว่าช่องไหนคืออะไร ("ห้องคลอด (LR)" คือแผนกหรือชื่อหน่วยงาน?)
 * คำกำกับติดอยู่ข้างหน้าจึงอยู่ตลอด แต่ไม่กินบรรทัดเพิ่มแบบ label ด้านบน
 *
 * โครงสร้าง: <div class="ifield"><span class="ifield-tag">ชื่อ</span><input class="input"></div>
 */
.ifield {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: stretch;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  transition: border-color .15s, box-shadow .15s;
}
.ifield:focus-within {
  border-color: var(--focus);
  box-shadow: 0 0 0 3px var(--brand-100);
}
.ifield-tag {
  display: flex; align-items: center;
  /* กว้างเท่ากันทุกช่อง ขอบซ้ายของช่องกรอกที่เรียงกันจะได้ตรงแนวเดียว */
  min-width: 54px;
  padding: 0 .6rem 0 .8rem;
  border-right: 1px solid var(--line-2);
  color: var(--ink-3); font-size: .875rem; font-weight: 500;
  white-space: nowrap;
  user-select: none;
}
/* อ./จ. อยู่ 2 ช่องในบรรทัดเดียว จอแคบไม่มีที่ให้เผื่อ */
.two-col .ifield-tag { min-width: 0; }
/* ช่องข้างในไม่ต้องมีกรอบของตัวเอง กรอบอยู่ที่ .ifield แล้ว
   ⚠ พื้นเขียวอยู่ที่ "ช่องกรอก" ไม่ใช่ทั้งกรอบ — ป้ายชื่อช่องทางซ้ายยังขาว
     สีจึงทำหน้าที่เดียวคือชี้ว่าตรงไหนแตะแล้วพิมพ์ได้ (ผู้ใช้สั่ง 5 ส.ค. 2569)
   ⚠ .ifield ต้อง overflow:hidden — ช่องข้างในไม่มีมุมโค้ง พอมีพื้นสีแล้ว
     มุมเหลี่ยมจะโผล่พ้นมุมโค้งของกรอบ */
.ifield { overflow: hidden; }
/* ⚠ **ต้องมี .textarea ด้วย** — ช่องเลขเครื่องกับช่องรายการในหน้ารับของเข้า
     เป็น textarea ลืมไว้แล้วมันจะเหลือกรอบของตัวเองซ้อนอยู่ในกรอบ .ifield อีกชั้น */
.ifield > .input,
.ifield > .select,
.ifield > .textarea { border: 0; border-radius: 0; }
.ifield > .input:focus,
.ifield > .select:focus,
.ifield > .textarea:focus { box-shadow: none; }
/* ⚠ ล้าง min-height 84px ของ .textarea ทิ้ง — ในกรอบนี้ป้ายชื่อช่องอยู่ซ้าย
     และ align-items:stretch ทำให้ป้ายยืดสูงตามช่องไปด้วย ปล่อยไว้ = ป้าย
     "รายการ" ลอยอยู่กลางกล่องสูง 3 บรรทัด ความสูงจริงคุมด้วย rows= แทน */
.ifield > .textarea { min-height: 0; }

/* ช่องกรอกเล็กในตาราง (จำนวน / ราคา)
   ⚠ .select.sm เติมเข้ามาทีหลัง (18 ส.ค. 2569 ตอนทำตัวเลือกปีในแถบสรุปวันลา)
     — เดิมมีแค่ .input.sm กับ .textarea.sm ใครใส่ class="select sm" ก่อนหน้านี้
     จะไม่มีอะไรเกิดขึ้นและไม่มีอะไรฟ้อง */
.input.sm  { padding: .32rem .5rem; font-size: .9rem; }
.select.sm { padding: .32rem 1.6rem .32rem .5rem; font-size: .9rem; }
.input.num { text-align: right; font-variant-numeric: tabular-nums; }

/* ช่องแก้ชื่อสินค้าในตารางรายการ — โหมดแก้ในบรรทัดหลังออกเลข (14 ส.ค. 2569)
   ⚠ **ต้องทับ min-height ของ .textarea (84px)** ไม่งั้นแถวในตารางสูงเป็น 3 เท่า
     ของแถวที่แก้ไม่ได้ ตารางจะกระโดดตอนสลับเข้าโหมดแก้
   ⚠ ใช้ textarea ไม่ใช่ input เพราะชื่อสินค้าขึ้นบรรทัดใหม่เองได้ (ดู cleanName) */
.textarea.sm {
  min-height: 0; padding: .32rem .5rem; font-size: .95rem;
  font-weight: 600; line-height: 1.35;
}
/* ชื่อจริงในคลังใต้ชื่อที่แสดง — ขึ้นเฉพาะตอนถูกแก้จนต่างจากคลัง
   ⚠ **บนหน้าจอเท่านั้น ไม่มีในกระดาษ** (ผู้ใช้เลือกเอง 14 ส.ค. 2569)
     กระดาษพิมพ์ชื่อที่ลูกค้าเรียก ส่วนบรรทัดนี้เป็นของภายในไว้ตัดสต๊อก */
.stock-name { font-style: italic; }

/* รายละเอียดของแต่ละรายการ — ช่องพิมพ์ในแถวสินค้า (js/itemsEditor.js)
 * ⚠ **ต้องดูออกว่าเป็นของบรรทัดนี้ ไม่ใช่ของทั้งใบ** — ช่องเดิมเป็นกล่องเดียว
 *   ท้ายตาราง ย้ายมาอยู่ในแถวแล้วถ้าหน้าตาเหมือนช่องอิสระ คนจะยังอ่านว่า
 *   "พิมพ์ที่ไหนก็ได้" · เยื้องขวาให้อยู่ใต้ชื่อสินค้าเป็นแนวเดียวกัน
 * ⚠ กว้างเต็มช่องเสมอ — ช่องชื่อเป็นคอลัมน์ที่ยืดได้ ตั้งความกว้างตายตัวแล้ว
 *   จะล้นตารางบนจอแคบ */
.item-detail {
  display: block;
  width: 100%;
  margin-top: .35rem;
}
/* ตอนแก้ไม่ได้ — เป็นข้อความเฉย ๆ แต่ยังต้องอ่านออกว่าคนละก้อนกับชื่อสินค้า */
div.item-detail { color: var(--ink-2); }

/* ---------- ช่องตัวเลขทุกช่องในระบบ ----------
 * ⚠ ตัวเลขที่กรอกได้ต้องอ่านออกว่า "กรอกได้" และเด่นกว่าข้อความรอบข้าง
 *   เดิมช่องเป็นขาวบนการ์ดขาว ขอบเทาจาง ตัวเลขขนาดเท่าตัวหนังสือธรรมดา
 *   ผู้ทดลองใช้งานอ่านเป็นข้อความ ไม่รู้ว่าแตะแล้วแก้ได้ (4 ส.ค. 2569)
 *
 *   พื้นเขียวจางทำหน้าที่ 2 อย่าง — บอกว่าเป็นช่องให้เติม และในตารางสินค้า
 *   มันกลายเป็นแถบสีอ่อน ๆ ชี้ว่าคอลัมน์ไหนแก้ได้บ้างโดยไม่ต้องเขียนอธิบาย
 *   โฟกัสแล้วพื้นเป็นขาว = กำลังพิมพ์ช่องนี้อยู่
 *
 * ⚠ เจาะจง [type="number"] ไม่ใช่ .num — ช่องตัวเลขบางหน้า (สต๊อก) ไม่ได้ติด
 *   คลาส .num ถ้าผูกกับคลาสจะหลุดเป็นหน้า ๆ แล้วหน้าตาไม่เหมือนกันทั้งระบบ
 * ⚠ brand-700 บน brand-50 = 7.22:1 ผ่าน AA สบาย */
/* ⚠ เหลือแค่ "น้ำหนักตัวเลข" — พื้นเขียวกับขอบย้ายไปเป็นค่าปริยายของทุกช่อง
   แล้ว (5 ส.ค. 2569) ถ้ายังตั้งขอบเขียวไว้เฉพาะช่องตัวเลข ฟอร์มเดียวกัน
   จะมีขอบ 2 สี ซึ่งเป็นเรื่องที่กำลังแก้อยู่พอดี
   ตัวหนา+สีเข้มยังอยู่ เพราะเป็นเรื่อง "อ่านตัวเลขให้ถูก" ไม่ใช่ "กรอกได้มั้ย"
   brand-700 บน brand-50 = 7.22:1 ผ่าน AA สบาย */
.input[type="number"] {
  font-weight: 700;
  color: var(--brand-700);
}


/* ---------- ช่องจำนวนแบบมีปุ่ม − / + ----------
 * จำนวนในใบเสนอราคาส่วนใหญ่ขยับทีละ 1 (เครื่องมือแพทย์สั่งกันไม่กี่เครื่อง)
 * บนมือถือการแตะเข้าช่องแล้วรอคีย์บอร์ดเด้งช้ากว่ากดปุ่มมาก
 * ปุ่มขนาบสองข้างเลยดีกว่าวางไว้ที่อื่น — นิ้วเล็งได้โดยไม่ต้องมองแป้น
 */
.stepper {
  display: grid;
  grid-template-columns: 36px minmax(0, 1fr) 36px;
  gap: 4px;
  width: 100%; max-width: 150px;
  margin-left: auto;            /* คอลัมน์จำนวนชิดขวาบนจอใหญ่ */
}
.stepper button {
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink-2);
  font-size: 1.1rem; line-height: 1; font-family: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color .15s, border-color .15s, color .15s, transform .1s;
}
.stepper button:hover:not(:disabled) {
  border-color: var(--brand-400); background: var(--brand-50); color: var(--brand-700);
}
.stepper button:active:not(:disabled) { transform: scale(.93); }
.stepper button:disabled { opacity: .4; cursor: default; }
.stepper .input { text-align: center; padding-left: .25rem; padding-right: .25rem; }

/* .stepper ตัวเดิมออกแบบมาสำหรับคอลัมน์ "จำนวน" ในตารางสินค้า จึงชิดขวาเสมอ
   นอกตารางต้องเริ่มที่ขอบซ้ายของ label เหมือนช่องกรอกอื่นในฟอร์ม */
.stepper.flush { margin-left: 0; max-width: 132px; }

/* ---------- คอลัมน์หน่วยนับในตารางรายการ ----------
 * ใส่ 18 ส.ค. 2569 หลังใบ QTM-69XXXX พิมพ์ช่องจำนวนออกมาเป็น "1 1"
 * (หน่วยถูกกรอกเป็นเลข 1 แล้วไม่มีที่ไหนบนจอโชว์ให้เห็น) ดู unitBox
 * ใน js/itemsEditor.js
 *
 * ⚠ **คอลัมน์ของตัวเอง ไม่ใช่ช่องซ้อนใต้ stepper** — รอบแรกทำแบบซ้อนแล้ว
 *   ผู้ใช้สั่งแยกออกมา (หัวตารางกว้าง 110px เขียนไว้ในหน้า ไม่ใช่ที่นี่)
 * ⚠ จัดกลางเหมือนช่องเลขใน .stepper — คอลัมน์ตัวเลขที่อยู่ติดกันควรอ่าน
 *   เป็นชุดเดียวกัน ไม่ใช่ตัวหนึ่งชิดขวาอีกตัวชิดซ้าย */
.unit-in {
  display: block;
  width: 100%;
  text-align: center;
}
/* แถวที่แก้ไม่ได้ (office เปิดดูใบที่ออกเลขแล้ว) — โชว์ค่าเฉย ๆ
   เหมือนที่พิมพ์บนกระดาษ ไม่ใช่ปล่อยช่องว่าง */
.unit-txt { color: var(--ink-2); }

/* ⚠ ซ่อนลูกศรขึ้น-ลงของช่องตัวเลข **ทั้งระบบ** (ผู้ใช้สั่ง 5 ส.ค. 2569)
 * เดิมซ่อนเฉพาะใน .stepper เพราะที่นั่นมีปุ่ม − / + ของเราเองอยู่แล้ว
 * เหตุผลที่ขยายเป็นทั้งระบบ: ลูกศรของเบราว์เซอร์เล็กมาก กดพลาดเป็นค่าข้าง ๆ
 * ได้ง่าย และมันโผล่เฉพาะตอนเอาเมาส์ชี้ ทำให้ความกว้างของช่องขยับตอน hover
 * ช่องไหนที่ต้องขยับทีละ 1 จริง ๆ ใช้ .stepper ซึ่งมีปุ่มใหญ่กดง่ายกว่า */
input[type=number] { -moz-appearance: textfield; appearance: textfield; }
input[type=number]::-webkit-outer-spin-button,
input[type=number]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* ---------- กล่อง "สินค้าที่ไม่มีในสต๊อก" ----------
 * ⚠ เดิมมีกฎทาสีช่องกรอกของตัวเองอยู่ที่นี่ ถอดออก 5 ส.ค. 2569 ตอนที่
 *   พื้นเขียวกลายเป็นค่าปริยายของทุกช่องในระบบ เหลือแต่สถานะ "กรอกไม่ครบ"
 *   ซึ่งเป็นของเฉพาะกล่องนี้จริง ๆ
 */

/* ช่องที่กรอกไม่ครบ — กรอบแดงชี้ว่าอยู่ตรงไหน ไม่ต้องไล่อ่านเอง */
/* ⚠ **ไม่ผูกกับฟอร์มไหนแล้ว** — เดิมเขียนเป็น `.ci-form .ifield.is-bad`
     ตอนมีฟอร์มเดียวที่ใช้ พอหน้ารับสินค้าเข้ามาใช้ .ifield ชุดเดียวกัน
     การต้องจำว่า "ต้องห่อด้วยคลาสอะไรกรอบแดงถึงจะขึ้น" คือกับดัก
     `is-bad` เป็นคลาสสถานะที่ JS เป็นคนใส่ ไม่มีทางไปโดนของอื่นโดยบังเอิญ */
.ifield.is-bad { border-color: var(--danger); }
.ifield.is-bad > .input { background: var(--danger-bg); }
.ifield-tag .req { color: var(--danger); margin-left: .15rem; }

/* ---------- ผลค้นสต๊อกใต้ช่องชื่อ (พิมพ์ไปเห็นไป) ----------
 * ⚠ **ใช้ร่วมกัน 2 ที่ ชื่อคลาสจึงเป็นกลาง ไม่ผูกกับหน้าไหน**
 *     js/itemsEditor.js   กล่อง "เพิ่มรายการ" ในใบเสนอราคา
 *     js/pages/stockIn.js ฟอร์มรับสินค้าเข้า
 *   (เคยชื่อ .ci-hit ตอนมีที่ใช้ที่เดียว — ci = custom item ซึ่งไม่จริงแล้ว)
 *
 * ⚠ **ไม่ใช้ position:absolute ลอยทับ** อย่างที่ autocomplete ทั่วไปทำกัน —
 *   ทั้ง 2 ที่อยู่ในของที่สกรอลล์ได้ (modal / หน้าเว็บ) ของที่ลอยจะค้างที่เดิม
 *   ตอนเลื่อน และบนมือถือจะทับช่องถัดไปจนกดผิด — ดันฟอร์มลงไปตรง ๆ เห็นหมด
 * ⚠ จำกัดความสูง — 6 รายการบนจอเล็กคือดันช่องจำนวน/หน่วยหายจนคนนึกว่าต้องเลือก
 */
.pick-hits {
  display: flex; flex-direction: column; gap: 1px;
  margin-top: .4rem;
  max-height: 42vh; overflow-y: auto;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--line);
}
/* หัวรายการผลค้น — บอกจำนวนกับเหตุผลที่มันโผล่มา + ปุ่มปิด
 * ⚠ มีเพราะ **ผลค้นปิดเองไม่ได้** เมื่อคำที่พิมพ์ไปตรงกับรหัสของตัวอื่น
 *   (ผู้ใช้เจอ 14 ส.ค. 2569 พิมพ์ชื่อของใหม่ "R1" แล้วเจอรหัส …/16R1 ค้างเต็มจอ)
 * ⚠ ปุ่มปิดอยู่ **ในรายการ** ไม่ใช่ลอยข้างช่องกรอก — มันปิดรายการนี้
 *   ไม่ใช่ล้างช่องชื่อ วางคนละที่แล้วคนกดผิดแน่นอน */
.pick-hits-head {
  display: flex; align-items: center; justify-content: space-between; gap: .5rem;
  padding: .3rem .35rem .3rem .65rem;
  background: var(--surface-2);
  font-size: .78rem; color: var(--ink-3);
}
.pick-hit {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .2rem .6rem;
  width: 100%; padding: .5rem .65rem;
  border: 0; background: var(--surface);
  text-align: left; cursor: pointer; font: inherit;
}
/* หน้าค้นหารวมใช้คลาสนี้กับ <a> — ต้องล้างสี/ขีดเส้นของลิงก์ทิ้ง
   ⚠ ใช้ <a> ไม่ใช่ <button> เพราะคนต้องกดค้างเปิดแท็บใหม่ได้
     และเห็นปลายทางก่อนกด — ผลค้นที่เปิดใหม่ไม่ได้คือผลค้นที่ใช้งานจริงไม่ได้ */
a.pick-hit { color: inherit; text-decoration: none; }
/* pre-line = ชื่อสินค้าที่คนกด Enter ไว้ตอนตั้งชื่อ ต้องขึ้นบรรทัดตามนั้น
   ⚠ ผลค้นคือที่ที่คนใช้ตัดสินว่า "ใช่ตัวนี้มั้ย" ถ้าที่นี่แสดงไม่ตรงกับที่ตั้งไว้
     คนจะเลือกจากชื่อที่ไม่มีอยู่จริง */
.pick-hit-name { font-weight: 600; white-space: pre-line; }
.pick-hit:active { background: var(--brand-50); }
@media (hover: hover) {
  /* ⚠ ต้องกั้น hover — บนจอสัมผัสสีค้างหลังแตะ อ่านเป็น "เลือกไว้แล้ว"
     (บั๊กเดียวกับ .btn-icon:hover ที่ผู้ใช้แจ้ง 7 ส.ค. 2569) */
  .pick-hit:hover { background: var(--brand-50); }
  .pick-hit.is-off:hover { background: var(--surface-2); }
}
/* แถวที่กดไม่ได้ในกล่องเลือกเครื่อง — เครื่องที่ใบอื่นจองไว้แล้ว
 *
 * ⚠⚠ **ต้องยังอ่านเลขออกชัด ๆ ห้ามจางจนกลายเป็นซ่อน** — เหตุผลที่ยังแสดง
 *   ตัวที่กดไม่ได้คือ office ที่หาเครื่องไม่เจอต้องรู้ว่ามันหายไปไหน
 *   จางเกินไปเมื่อไหร่ก็เท่ากับซ่อน ซึ่งเป็นสิ่งที่ตั้งใจไม่ทำตั้งแต่แรก
 *   จึงใช้ --ink-2 (8.6:1) ไม่ใช่ --ink-3 เหมือน .muted ทั่วไป
 * ⚠ ขีดฆ่าที่ตัวเลข ไม่ใช่ opacity ทั้งแถว — opacity ลากป้าย "ใบไหนจอง"
 *   ที่อยู่ข้าง ๆ จางไปด้วย ทั้งที่นั่นคือข้อมูลที่ต้องอ่านที่สุดในแถวนั้น */
.pick-hit.is-off { background: var(--surface-2); cursor: not-allowed; }
.pick-hit.is-off .pick-hit-name {
  color: var(--ink-2); text-decoration: line-through;
}

/* ---------- ยอดรวมท้ายเอกสาร ----------
 * ชิดขวาเสมอ เพราะตาเราไล่ตัวเลขจากขวาไปซ้าย
 * "รวมทั้งสิ้น" ต้องเด่นกว่าทุกบรรทัด เพราะเป็นตัวเลขที่ลูกค้าดูจริง
 */
.totals {
  margin: 0 0 0 auto;
  width: min(360px, 100%);
  display: grid;
  grid-template-columns: 1fr auto;
  gap: .45rem 1.5rem;
  align-items: center;
}
.totals dt { color: var(--ink-2); font-size: .9rem; }
.totals dd { margin: 0; text-align: right; font-variant-numeric: tabular-nums; }
.totals .input { width: 118px; }   /* คอลัมน์ขวาเป็น auto ต้องกำหนดกว้างเอง */
.totals .sep { padding-top: .6rem; border-top: 1px solid var(--line-2); }
/* ⚠ ยอดรวมต้องเป็นของที่ใหญ่ที่สุดบนหน้า — เดิม 1.1rem ซึ่งใหญ่กว่าตัวหนังสือ
   ทั่วไปแค่ 10% ผู้ทดลองใช้งานรายงานว่า "ตัวหนังสือเหมือนกันหมด" (4 ส.ค. 2569)
   office เปิดหน้านี้มาเพื่อดูตัวเลขนี้เป็นอย่างแรก มันต้องหาเจอโดยไม่ต้องอ่าน */
.totals .grand {
  padding-top: .55rem;
  font-size: 1.45rem; font-weight: 700; line-height: 1.3;
  color: var(--brand-700);
}
/* ป้าย "รวมทั้งสิ้น" ไม่ต้องโตตามตัวเลข — คำอ่านครั้งเดียวก็จำได้ ตัวเลขสิที่เปลี่ยนทุกใบ */
.totals dt.grand { font-size: .95rem; font-weight: 600; }

/* ช่องพิมพ์ยอดเองของโหมดภาษี "อื่นๆ" (js/pages/quoteReview.js)
 * ⚠ **ต้องดูออกว่าพิมพ์ได้ ตั้งแต่ยังไม่เอาเมาส์ไปจิ้ม** — บรรทัดรอบ ๆ มัน
 *   เป็นตัวเลขอ่านอย่างเดียวทั้งหมด ช่องที่หน้าตากลมกลืนไปกับพวกนั้นจะไม่มี
 *   ใครรู้ว่าแก้ได้ แล้วโหมดนี้ก็ไร้ประโยชน์ไปเลย
 * ⚠ ไม่ทำเป็นกรอบทึบเข้ม — 3 ช่องเรียงกันในกล่องเล็ก ๆ กรอบหนาจะกลบตัวเลข
 *   ซึ่งเป็นสิ่งที่คนมาอ่านจริง ๆ */
.totals-in {
  background: var(--brand-50);
  border-color: var(--brand-300);
}
/* ช่องของบรรทัด "รวมทั้งสิ้น" ต้องโตตามตัวเลขที่มันแทน ไม่งั้นยอดที่เด่นที่สุด
   บนหน้าจะหดลงเหลือเท่าบรรทัดธรรมดาทันทีที่เข้าโหมดนี้ */
.totals .grand .totals-in {
  font-size: 1.2rem; font-weight: 700;
  color: var(--brand-700);
}

.field-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0 1rem; }
@media (max-width: 640px) {
  .field-row { grid-template-columns: 1fr; }
  /* ⚠ ระยะห่างแนวตั้งของแถวเป็น 0 โดยตั้งใจ — ลูกที่เป็น .field มี
     margin-bottom ของตัวเองอยู่แล้ว ใส่ gap เพิ่มจะห่างเป็น 2 เท่า
     แต่ .ifield ไม่มี margin เลย พอตกลงมาซ้อนกันบนมือถือจึงติดกันสนิท
     (ผู้ใช้เจอเอง 5 ส.ค. 2569 ที่ ยี่ห้อ/รุ่น และ จำนวน/หน่วย)
     เลยเว้นเฉพาะคู่ .ifield ที่อยู่ติดกันจริง ๆ ไม่ไปแตะแถวแบบอื่น */
  .field-row > .ifield + .ifield { margin-top: .6rem; }
}

/* ช่องกรอกสั้น 2 ช่องเรียงข้างกัน — อยู่แถวเดียวเสมอแม้จอแคบ
 * (minmax(0,…) ให้หดได้ ไม่ล้นจอ) */
.two-col {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: .6rem;
}

/* ---------- Chip เลือกได้อันเดียว (single-select) ----------
 * ใช้แทน dropdown เมื่อตัวเลือกน้อย (2-6) และคำสั้น
 * กด 1 ครั้งจบ เห็นตัวเลือกทั้งหมดพร้อมกัน กดง่ายด้วยนิ้วบนมือถือ
 * โครงสร้าง: <label class="chip"><input type="radio"><span>ป้าย</span></label>
 */
.chip-group { display: flex; flex-wrap: wrap; gap: .5rem; }


/* ---------- การ์ด "เอกสาร" ในใบเสนอราคา ----------
 * บรรทัดละ 1 หัวข้อ: ป้าย · ปุ่มเลือก · จำนวนชุด (ผู้ใช้เคาะ 5 ก.ย. 2569)
 *
 * ⚠⚠ **ป้ายต้องกว้างเท่ากันทุกบรรทัด** ไม่งั้นปุ่มเลือกของแต่ละบรรทัดเยื้องกัน
 *   ตามความยาวคำ ("ขอคู่เทียบ" สั้นกว่า "พิมพ์เอกสาร") แล้วอ่านเป็นคนละกลุ่ม
 *   ⟹ ตรึงความกว้างขั้นต่ำไว้ ไม่ใช้ grid เพราะจำนวนชุดต้องหลุดลงบรรทัดใหม่
 *     ได้เองบนจอแคบ ซึ่ง grid ทำไม่ได้ถ้าไม่เขียนคอลัมน์ใหม่ทั้งชุด
 * ⚠ **ห้ามใช้ .field-row** (grid 2 คอลัมน์) — ของเดิมใช้อยู่ แล้วบนคอม
 *   2 หัวข้อไปนั่งข้างกัน จำนวนชุดของหัวข้อซ้ายจึงไปดันหัวข้อขวาให้เยื้องลง
 */
/* ⚠⚠ **ความกว้างป้ายอยู่ที่เดียว** — เดิมเลข 9rem ถูกเขียนซ้ำ 3 ที่ (ป้าย ·
   ระยะเยื้องของตัวเลือกลูก · ฐานความกว้างของมัน) แก้ที่เดียวแล้วลืมอีก 2 ที่
   = ตัวเลือกลูกเยื้องไม่ตรงกับปุ่มของตัวแม่ ซึ่งเป็นอาการที่ด่าน ⑥ จับได้
   แต่ต้องไปนั่งไล่ว่าเลขไหนหลุด ⟹ ประกาศครั้งเดียวแล้วให้ทุกที่อ้างตัวนี้ */
.doc-lines { display: flex; flex-direction: column; gap: .85rem;
             --doc-label-w: 11rem; }
.doc-line  { display: flex; align-items: center; gap: .75rem; flex-wrap: wrap; }
.doc-line > .doc-label {
  /* ⚠⚠ **ต้องกว้างพอสำหรับป้ายยาวสุด ("พิมพ์ใบเสนอราคา+สเปก")** — ล้นเมื่อไหร่
     มันจะดันปุ่มของบรรทัดนั้นไปขวา เยื้องจากบรรทัดอื่น (ด่าน ⑥ จับได้)
     ⟹ **เปลี่ยนคำ *หรือขนาด/น้ำหนักตัวอักษร* เมื่อไหร่ ต้องรันโพรบดูว่าเลขนี้
     ยังพอมั้ย** · 9rem พอตอนป้ายเป็น .875rem/500 แต่ไม่พอตอนเป็น 1rem/600 */
  min-width: var(--doc-label-w);
  /* ⚠ ผู้ใช้ทัก 6 ก.ย. 2569 พร้อมภาพ: "หัวข้อให้เห็นชัดเจน ตอนนี้เล็กเหมือน
     ไม่สำคัญ" ⟹ ตัวหนา + สีเข้มเท่าตัวหนังสือหลัก + ขนาดปกติ (ผู้ใช้เคาะแบบ ก)
     ⚠ **ไม่ใช่ .field > label อีกต่อไป** — ป้ายในการ์ดนี้ไม่ใช่ป้ายช่องกรอก
       ธรรมดา มันคือหัวข้อของงานที่ office ต้องไปทำจริง จึงต้องอ่านได้ก่อนปุ่ม */
  font-size: 1rem; font-weight: 600; color: var(--ink);
}
/* ⚠ กลุ่มปุ่มก็ต้องกว้างเท่ากันทุกบรรทัดด้วยเหตุผลเดียวกับป้าย — คำในปุ่มยาว
   ไม่เท่ากัน ("ใช่/ไม่ขอ" สั้นกว่า "พิมพ์/ไม่พิมพ์") ถ้าไม่ตรึง ช่องจำนวนชุด
   ของแต่ละบรรทัดจะเยื้องกัน แล้วคอลัมน์ตัวเลขอ่านไม่เป็นแนวเดียว */
.doc-line > .chip-group { min-width: 12.5rem; }
/* ⚠ เคยมีปุ่ม "1 ราย · 2 ราย" ต่อจากปุ่ม "ใช่" อยู่ครู่หนึ่ง (6 ก.ย. 2569)
   ผู้ใช้สั่งถอดออกวันเดียวกัน ⟹ กฎ .doc-pick ถูกลบไปด้วย · ตอนนี้แต่ละบรรทัด
   มีกลุ่มปุ่มชุดเดียวเหมือนเดิม (ยกเว้นตัวเลือกลูกซึ่งอยู่ในกล่อง .doc-sub) */
/* ⚠ จำนวนชุดต้องดูเป็นของบรรทัดนั้น ไม่ใช่ของถัดไป — เว้นซ้ายมากกว่าระยะในกลุ่ม */
.doc-qty { display: flex; align-items: center; gap: .5rem; margin-left: .5rem; }

/* ---------- ตัวเลือกลูก: "พิมพ์คู่เทียบ" ที่โผล่เมื่อเลือก "ขอคู่เทียบ" ----------
 * (ผู้ใช้เคาะ 6 ก.ย. 2569 ให้อยู่บรรทัดเดียวกับตัวแม่)
 * ⚠ เป็นกล่องเดียว ⟹ ซ่อนทีเดียวได้ทั้งชุด ไม่ต้องไล่ซ่อนทีละชิ้นแล้วลืมชิ้นนึง
 * ⚠ ให้ทั้งชุดตกลงบรรทัดใหม่พร้อมกัน (flex-basis 100%) ไม่ใช่ไหลไปทีละชิ้น
 *   จนปุ่มกับช่องจำนวนไปคนละบรรทัด ซึ่งอ่านไม่ออกว่าอันไหนคู่กับอันไหน
 * ⚠ เยื้องซ้ายให้ตรงกับกลุ่มปุ่มของตัวแม่ (ป้าย + ระยะห่าง .75rem)
 *   จะได้อ่านเป็นของที่ห้อยอยู่ใต้ตัวแม่ ไม่ใช่หัวข้อใหม่
 * ⚠ **ใช้ --doc-label-w ห้ามเขียนเลขซ้ำ** — ป้ายกว้างเท่าไหร่ต้องเยื้องเท่านั้น
 *   เป๊ะ ๆ เขียนเลขแยกแล้ววันหลังแก้ป้ายอย่างเดียว = เยื้องผิดเงียบ ๆ */
/* ⚠⚠ **ฐานความกว้างต้องหักระยะเยื้องออกด้วย** — ใส่ 100% เฉย ๆ แล้วบวก
   margin-left เข้าไป จะเกินกรอบและดันการ์ดล้น (ด่าน ⑥ จับได้ 1344 > 1179) */
.doc-sub {
  display: flex; align-items: center; gap: .75rem; flex-wrap: wrap;
  flex: 1 0 calc(100% - var(--doc-label-w) - .75rem);
  margin-left: calc(var(--doc-label-w) + .75rem);
}
.doc-sub > .doc-qty { margin-left: 0; }
/* คำบอกเฉย ๆ ที่ไม่ใช่ช่องกรอก — ตอนนี้ไม่มีหัวข้อไหนใช้ ("2 เจ้า" ถูกแทนด้วย
   ปุ่มติ๊ก 1/2 ราย 6 ก.ย. 2569) เก็บกฎไว้เพราะ docCard ยังรองรับฟิลด์ note อยู่ */
.doc-note { white-space: nowrap; }

@media (max-width: 700px) {
  /* จอแคบ: ป้ายขึ้นบรรทัดของตัวเองอยู่แล้ว ⟹ ตัวลูกไม่ต้องเยื้องตามป้าย */
  .doc-sub { margin-left: 0; }
}
/* ⚠⚠ **หน่วยห้ามตกบรรทัด** (ผู้ใช้ทัก 5 ก.ย. 2569 พร้อมภาพ) — "คู่เทียบ" ยาวกว่า
   "ชุด" พอที่จะถูกหั่นเป็น "คู่ / เทียบ" เมื่อที่ว่างหด อ่านแล้วสะดุดและดูเหมือน
   หน้าจอพัง · เป็นคำสั้น ๆ ไม่กี่ตัวอักษร ยอมให้ดันความกว้างดีกว่าถูกหั่น
   ⚠⚠ **บรรทัดนี้ไม่มีด่านพิสูจน์ว่า "อาการหาย"** — โพรบสร้างสภาพที่ทำให้คำถูกหั่น
     ไม่ได้เลย (บีบการ์ดจนเหลือ 260px แล้วถอดกฎนี้ออก ก็ยังไม่หั่น) แปลว่า
     ของจริงมีอะไรบีบมันอีกชั้นที่โพรบยังจำลองไม่ได้ · ที่มีคือด่านตรวจว่า
     **กฎนี้ยังอยู่** เท่านั้น ⟹ ถ้าวันหลังผู้ใช้เจออาการนี้อีก ให้กลับมาหา
     ต้นเหตุที่แท้จริงก่อน อย่าเพิ่งเชื่อว่าบรรทัดนี้แก้ได้ (CLAUDE.md §⑤) */
.doc-qty > .muted { white-space: nowrap; }

/* จอแคบ: ป้ายขึ้นบรรทัดของตัวเอง แล้วปุ่มกับจำนวนชุดไหลตามข้างล่าง
   ⚠ ถ้าปล่อยให้ป้ายอยู่บรรทัดเดียวกับปุ่ม ปุ่ม 2 อันจะถูกบีบจนคำตัด

   ⚠⚠ **700px ไม่ใช่ 560px** — บรรทัดเดียวต้องใช้ที่ราว 500px
   (ป้าย 7rem + กลุ่มปุ่ม 12.5rem + ช่องจำนวน ~150px + ระยะห่าง) บวกขอบการ์ด
   อีก ⟹ ที่ 620px ช่องจำนวนของบรรทัดแรกตกลงไปอยู่บรรทัดใหม่ชิดซ้าย
   ซึ่งอ่านเหมือนเป็นของบรรทัดถัดไป · **เจอเพราะกวาดหลายความกว้าง**
   ตอนทดสอบแค่ 1280 กับ 360 ผ่านทั้งคู่ ของพังอยู่ตรงกลางที่ไม่มีด่านยืนอยู่
   ⚠ เลขนี้ต้องตรงกับ BP ใน tools/probe-doccard.html — ที่นั่นใช้ตัดสินว่า
     ควรวัดผังแบบไหน ไม่ตรงกันเมื่อไหร่ ด่านจะตกด้วยเหตุผลผิด */
@media (max-width: 700px) {
  .doc-line > .doc-label { min-width: 100%; }
  /* ⚠ ปลดความกว้างที่ตรึงไว้ด้วย — 12.5rem บนจอ 360px กินที่จนจำนวนชุด
     ถูกดันตกบรรทัดทั้งที่ยังพอมีที่ */
  .doc-line > .chip-group { min-width: 0; }
  .doc-qty { margin-left: 0; }
}


/* การ์ด Office — ออกในนาม / ภาษี / ผู้ลงนาม เรียงบรรทัดเดียว
 * ⚠ ระยะห่างแนวนอน (2rem) ต้องมากกว่าระยะในกลุ่ม (.75rem ของ .field.inline)
 *   ไม่งั้นป้าย "ภาษี" จะดูเหมือนเป็นของกลุ่มชิปที่อยู่ก่อนหน้า
 * ⚠ ช่องผู้ลงนามยืดได้แต่ไม่ต่ำกว่า 240px — สั้นกว่านี้ชื่อ-นามสกุลไทยเต็ม ๆ
 *   จะถูกตัดจนอ่านไม่ออกว่าใครเซ็น
 */
.office-row {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: .75rem 2rem;
}
.office-row .field { margin-bottom: 0; }
.office-row .ifield { flex: 1 1 240px; }

/* ---------- ชิป (ตัวเลือกแบบกดติ๊ก) ----------
 * ⚠ **กติกาสีของทั้งระบบ อยู่ที่นี่ที่เดียว** (ผู้ใช้เคาะ 5 ส.ค. 2569)
 *     พื้นเขียวอ่อน (brand-50) = "ช่องนี้กดเลือกได้"  ← เหมือนช่องกรอกทุกชนิด
 *     พื้นเขียวเข้ม (brand-600) = "เลือกอันนี้อยู่"
 *   เดิมชิปที่ยังไม่เลือกเป็นพื้น**ขาว** ซึ่งกลืนไปกับการ์ด แยกไม่ออกจากตัวหนังสือ
 *   ธรรมดา ผู้ใช้ตีกลับเองว่า "ไม่เป็นแนวการออกแบบเดียวกัน" กับช่องกรอก
 *   ⚠ **ห้ามเขียนกฎสีชิปทับเฉพาะบางหน้า/บางการ์ด** — เคยลองแล้วเมื่อ 5 ส.ค. 2569
 *     (ชิปคู่เทียบใน #pairPickBox) ผลคือชิป 2 แบบในหน้าเดียวกัน อ่านไม่ออกว่า
 *     อันไหนคือ "เลือกแล้ว" ถอดออกวันเดียวกัน
 * ⚠ คู่สีที่คำนวณไว้แล้ว — เปลี่ยนเมื่อไหร่ต้องคำนวณใหม่ ห้ามเดา
 *     ink-2 บน brand-50  = 7.89   (ยังไม่เลือก)
 *     ink-2 บน brand-100 = 7.13   (hover)
 *     #fff  บน brand-600 = 5.53   (เลือกแล้ว)
 */
.chip {
  position: relative;
  display: inline-flex; align-items: center; gap: .4rem;
  min-height: 40px;
  padding: .45rem .95rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--brand-50);
  color: var(--ink-2);
  font-size: .9rem; font-weight: 500;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color .16s ease, border-color .16s ease,
              color .16s ease, transform .12s ease;
}
.chip input { position: absolute; opacity: 0; width: 0; height: 0; }
/* ⚠ hover ต้องเข้มขึ้นอีกขั้น (brand-100) — พื้นปกติเป็น brand-50 ไปแล้ว
   ใช้ brand-50 ซ้ำ = เอาเมาส์ไปวางแล้วไม่มีอะไรเกิดขึ้น */
.chip:hover { border-color: var(--brand-300); background: var(--brand-100); }
.chip:active { transform: scale(.97); }

.chip:has(input:checked) {
  background: var(--brand-600);
  border-color: var(--brand-600);
  color: #fff;
  font-weight: 600;
}
/* เครื่องหมายถูกนำหน้าเฉพาะตัวที่เลือก */
.chip:has(input:checked)::before {
  content: "✓";
  font-size: .82rem; line-height: 1;
}
.chip:has(input:focus-visible) { outline: 2px solid var(--brand-400); outline-offset: 2px; }


/* จอเล็ก: บังคับให้อยู่แถวเดียวเสมอ ไม่ว่ามีกี่ปุ่ม
 * grid-auto-flow: column + minmax(0,1fr) = แบ่งความกว้างเท่ากันและหดได้
 * (flex-wrap จะดันปุ่มสุดท้ายตกบรรทัดทันทีที่ความกว้างรวมเกินจอ)
 *
 * ⚠️ บล็อกนี้ต้องอยู่ "ท้ายสุด" ของหมวด chip
 * ถ้าย้ายขึ้นไปก่อนกฎ .chip ตัวหลัก จะถูกทับทิ้งเพราะ specificity เท่ากัน
 * แล้วตัวอักษรจะใหญ่เท่าเดสก์ท็อปจนล้นปุ่ม ต้องตัดเป็น "โ..."
 */
@media (max-width: 560px) {
  .chip-group {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: minmax(0, 1fr);
    gap: 8px;
  }
  /* ⚠⚠ **ทางออกสำหรับกลุ่มที่จำนวนปุ่มไม่คงที่** — กฎข้างบนออกแบบไว้ตอนที่ทุก
     กลุ่มมี 3-6 ปุ่มคำสั้น ๆ (ไม่มี/A/B/C/D/+/−) พอกลุ่มไหนมีปุ่มตามจำนวน
     ข้อมูลจริง มันจะหารความกว้างเท่า ๆ กันจนเหลือปุ่มละไม่กี่ px
     **อ่านไม่ออกสักปุ่ม** (เกิดจริง 15 ส.ค. 2569 — รายชื่อพนักงาน 16 คน
     เหลือปุ่มละ ~5px เห็นเป็นแถบสีเรียงกัน)
     ⚠ ติดคลาสนี้ให้กลุ่มที่ **จำนวนปุ่มมาจากข้อมูล ไม่ใช่จากโค้ด** เสมอ */
  .chip-group.flow { display: flex; flex-wrap: wrap; }
  .chip-group.flow .chip { min-width: 0; }
  .chip {
    justify-content: center;
    gap: 0;
    min-height: 48px;              /* กดสบายด้วยนิ้วโป้ง */
    padding: .55rem .35rem;
    border-radius: 14px;           /* มุมนุ่ม ดูเป็นการ์ดปุ่ม ไม่ใช่เม็ดยา */
    /* ⚠ เคยเป็น #fbfdfd (เกือบขาว) — ตามกติกา "เลือกได้ = เขียวอ่อน" แล้ว
       ต้องเป็น brand-50 เท่าเดสก์ท็อป ไม่ใช่จางลงเพราะจอเล็ก */
    background: var(--brand-50);
    font-size: .85rem;
  }
  .chip > span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

  /* ตัวที่เลือกลอยขึ้นมาแทนการใส่เครื่องหมายถูก
     — ✓ กินที่ในปุ่มจนชื่อยาวต้องถูกตัด */
  .chip:has(input:checked) {
    transform: translateY(-1px);
    box-shadow: 0 6px 14px -6px rgba(var(--primary-rgb), .55);
  }
  .chip:has(input:checked)::before { content: none; }
  .chip:active { transform: scale(.96); box-shadow: none; }

}

/* ---------- การ์ด ---------- */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-1);
}
.card-head {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  /* จอแคบ: ยอมให้ของทางขวาตกลงบรรทัดใหม่ ดีกว่าบีบจนหัวข้อถูกตัด */
  flex-wrap: wrap;
  padding: 1rem 1.25rem;
  border-bottom: 1px solid var(--line-2);
}
.card-head h2, .card-head h3 { display: flex; align-items: center; gap: .5rem; }
.card-body { padding: 1.25rem; }
.card-body.tight { padding: 0; }

/* ---------- รายการ "ป้าย : ค่า" ----------
 * ใช้ในกล่องรายละเอียด (เช่น กดดูสินค้าในสต๊อก)
 * จอแคบพับเป็นบรรทัดเดียวต่อคู่ ไม่ให้ค่าถูกบีบจนขึ้นบรรทัดใหม่มั่ว
 */
.kv {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: .5rem 1.25rem;
  margin: 0;
}
/* ⚠ **ป้ายกำกับต้องจางกว่าค่าอย่างเห็นได้ชัด** (ผู้ใช้สั่ง 5 ส.ค. 2569)
   ป้าย ("เรื่อง" · "ผู้เสนอ") บอกแค่ว่าช่องนี้คืออะไร อ่านครั้งเดียวก็จำได้
   ส่วนค่าคือสิ่งที่คนเปิดหน้ามาหา ถ้าน้ำหนักใกล้กัน ตาจะไม่รู้ว่าควรเกาะตรงไหน
   คู่ที่ใช้: ป้าย 400/ink-3 (4.18) · ค่า 600/ink (16.06) — ต่างกันทั้งสีและน้ำหนัก */
.kv dt { color: var(--ink-3); font-size: .86rem; font-weight: 400; white-space: nowrap; }
.kv dd { margin: 0; font-weight: 600; color: var(--ink); }

/* ---------- ระดับความสำคัญของ "ค่า" ในตาราง dt/dd ----------
 * ⚠ ปัญหาที่แก้: ป้ายกำกับต่างกันชัด (เทา เล็ก) แต่ **ตัวข้อมูลเองเหมือนกันหมด**
 *   ทุกช่องเป็น 15px/500 ไม่ว่าจะเป็นชื่อโรงพยาบาลหรือรหัสสวัสดิการ
 *   ตาไม่มีที่เกาะ ต้องอ่านทีละบรรทัดจนจบถึงจะรู้ว่าอะไรสำคัญ
 *   ผู้ทดลองใช้งานรายงานเอง 4 ส.ค. 2569
 *
 * ⚠ วิธีแก้คือ **ลดของที่ไม่สำคัญ** ไม่ใช่ขยายของที่สำคัญ
 *   ได้ลำดับชั้นเท่ากันโดยการ์ดไม่สูงขึ้น ซึ่งสำคัญเพราะเพิ่งย่อการ์ดมาทั้งวัน
 *   (และผู้ใช้สั่ง "เล็กลงอีก" แทบทุกครั้งที่เสนอของใหญ่ ดู [[login-design-system]])
 *
 * ⚠ **แก้ทิศ 5 ส.ค. 2569** — ของเดิมลดค่าในตารางอ้างอิงลงเป็น 400/ink-2 ซึ่ง
 *   ไปใกล้กับป้ายกำกับ (400/ink-3) จนแยกไม่ออกว่าอันไหนป้ายอันไหนค่า
 *   ผู้ใช้ตีกลับเอง ("เนื้อหาสาระต้องเป็นตัวเข้มกว่าหัวข้อ ไม่ใช่ใกล้เคียงกัน")
 *   วิธีลดความสำคัญของทั้งตารางยังใช้ต่อ แต่ลดที่ **ขนาด** (.88rem) อย่างเดียว
 *   ส่วนน้ำหนักกับสีต้องคงระยะห่างจากป้ายไว้เท่าตาราง .kv ปกติเสมอ
 */
.kv.ref dd { font-size: .88rem; }
/* ⚠ .strong ไม่ต่างจากค่าปกติแล้วตั้งแต่ 5 ส.ค. 2569 — เก็บชื่อคลาสไว้เฉย ๆ
   ให้ HTML เดิม (ออกในนาม · เลข PO · ราคากลาง) ไม่ต้องไล่แก้ ไม่ใช่ยังทำงานอยู่ */

/* ⚠ .lead ต้องอยู่ "หลัง" .kv.ref dd — specificity เท่ากันทั้งคู่ (0,2,1)
   ตัวที่เขียนทีหลังชนะ ถ้าสลับที่ ค่าเด่นในตารางอ้างอิงจะถูกย่อกลับเป็นตัวเล็ก */
.kv dd.lead, .kv.ref dd.lead {
  font-size: 1.12rem; font-weight: 600; line-height: 1.45; color: var(--ink);
}
@media (max-width: 480px) {
  .kv { grid-template-columns: 1fr; gap: .15rem; }
  .kv dd { margin-bottom: .6rem; }
}

/* ---------- วงกลมเลขลำดับขั้น ----------
 * ใช้คู่กับป้ายสถานะ แท็บกรอง และไทม์ไลน์ (ดู stepNo() ใน js/ui.js)
 *
 * ⚠ #fff บน --brand-600 ได้ 5.53:1 ผ่าน AA สำหรับตัวหนังสือปกติ
 *   ห้ามเปลี่ยนไปใช้เขียวอ่อนกว่านี้ ตัวเลขเล็กอยู่แล้วจะอ่านไม่ออก
 * ⚠ tabular-nums ให้เลขทุกตัวกว้างเท่ากัน วงกลมจะได้ไม่ขยับตอนเลขเปลี่ยน
 */
.step-no {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; flex: none;
  margin-right: .35rem;
  vertical-align: middle;
  border-radius: 999px;
  background: var(--brand-600); color: #fff;
  font-size: .72rem; font-weight: 700; line-height: 1;
  font-variant-numeric: tabular-nums;
}
/* ⚠ ในช่องสถานะต้องใหญ่กว่าที่อื่น — วัดแล้วป้าย "จบงาน" สูง ~23px
   (วงกลมถูก 16 + ขอบป้าย 4.8) วงกลมเลข 20px จึงดูเล็กกว่าอย่างเห็นได้ชัด
   ทั้งที่ 2 อันนี้ทำหน้าที่เดียวกันคือหัวแถวของสถานะ ต้องมีน้ำหนักเท่ากัน
   26px ทำให้ใหญ่กว่าป้ายเล็กน้อย (3px) ซึ่งถูกแล้ว เพราะมันคือตัวนำสายตา
   ⚠ ไทม์ไลน์ในหน้าใบยังใช้ 20px เหมือนเดิม — ที่นั่นวงกลมอยู่กับตัวหนังสือ
     ธรรมดา ไม่ได้อยู่กับป้าย ใหญ่กว่านั้นจะข่มบรรทัดที่มันกำกับอยู่ */
.st-main .step-no { width: 26px; height: 26px; font-size: .82rem; }

/* ในพาดหัวต้องโตตาม ไม่งั้นวงกลม 20px ข้างตัวหนังสือหัวเรื่องจะดูเป็นเศษฝุ่น */
h1 .step-no { width: 28px; height: 28px; font-size: .95rem; margin-right: .5rem; }

/* ป้าย "ส่งมอบแล้ว N วัน" ใต้สถานะในหน้าวางบิล (ผู้ใช้สั่ง 28 ส.ค. 2569
   "ให้ชัดเจนกว่านี้ เพราะเป็นสาระสำคัญ")
   ⚠ เดิมเป็น .small muted ซึ่งจมหายไปกับบรรทัดรอง ๆ ทั้งที่เป็นตัวเลขที่คน
     เปิดหน้านี้มาดูโดยตรง ⟹ ขนาดปกติ สีหมึกเข้ม น้ำหนัก 600
   ⚠⚠ **ส้มอ่อนมีกรอบ — ผู้ใช้เคาะเอง 28 ส.ค. 2569** หลังผมเสนอ 3 สีให้เลือก
     ก่อนหน้านี้ผมกันสีส้มไว้เพราะสีเตือนแปลว่า "ช้าแล้ว" ซึ่งยังไม่มีเกณฑ์ว่า
     กี่วันถือว่าช้า · ผู้ใช้ตัดสินใจแล้วว่าเอาส้ม ⟹ ทำตาม และถอดด่านที่ห้าม
     สีเตือนออกจาก probe-stepsize ด้วย (ไม่เก็บด่านที่ขัดกับของที่ตกลงกันแล้ว)
   ⚠ **กรอบคือตัวแยกจากป้าย .pill-warn** ที่อยู่ช่องเดียวกัน ("วางบิลแล้ว"
     เป็นโทน warn สีเดียวกันเป๊ะ) — ป้ายในระบบนี้ไม่มีอันไหนมีกรอบเลย
     กรอบกับตัวหนาจึงเป็นสิ่งเดียวที่บอกว่า 2 ก้อนนี้คนละเรื่องกัน
   ⚠⚠ **ต่อท้ายป้ายสถานะในบรรทัดเดียวกัน** (ผู้ใช้สั่ง 28 ส.ค. 2569
     "ในคอมให้เรียงต่อกันไม่ตกบรรทัด") — เดิมเป็น flex-basis:100% จึงตกบรรทัด
     เสมอแม้จอกว้างจะมีที่เหลือ · ตอนนี้กว้างเท่าเนื้อ (auto) แล้วพึ่ง
     flex-wrap ของ .st-wrap เป็นตาข่ายรับ ⟹ จอแคบยังตกบรรทัดได้เหมือนเดิม
   ⚠ nowrap ที่ตัวป้าย — ห้ามให้ "ส่งมอบแล้ว 3 วัน" แตกกลางวลี ถ้าที่ไม่พอ
     ต้องตกไปทั้งป้าย ไม่ใช่แตกเป็น 2 บรรทัด
   ⚠ ความกว้างคอลัมน์สถานะต้องพอให้เรียงต่อกันได้จริง (ดูหัวตาราง finance.js) */
.since-del {
  display: inline-flex; align-items: center; gap: .3rem;
  flex-basis: auto; white-space: nowrap;
  padding: .18rem .6rem;
  border-radius: 999px;
  background: var(--warn-bg); border: 1px solid #ffd8a8;
  color: #b45f0d; font-weight: 600;
}
.since-del .ico { color: #b45f0d; flex: none; }
/* ตัวหนังสือของป้ายสถานะในตารางวางบิลต้องเท่าป้ายส่งมอบ (ผู้ใช้สั่ง 28 ส.ค. 2569
 * "รอวางบิลก็ให้ตัวหนังสือเท่ากัน")
 * ⚠ **ใช้ inherit ไม่ใช่ตั้งเลขให้เท่ากัน 2 ที่** — ตั้ง .82rem ทั้งคู่แล้ว
 *   วันหลังใครแก้ที่เดียวก็ไม่เท่ากันอีกโดยไม่มีใครรู้ · ทั้งคู่รับจากช่องเดียวกัน
 *   ⟹ เท่ากันโดยโครงสร้าง ไม่ใช่เท่ากันโดยบังเอิญ
 * ⚠ เฉพาะตารางวางบิล (.t-bill) — ป้ายในหน้าอื่นยังเป็น .8rem เหมือนเดิม
 *   ขยายทั้งระบบคือการแก้ของที่ผู้ใช้ไม่ได้สั่ง */
.table.t-bill .st-main .pill { font-size: inherit; }

/* ป้ายสถานะหลัก = วงกลมเลข + ป้าย ติดกันเสมอ ห้ามแยกบรรทัด
   (ดูเหตุผลที่ statusCell ใน js/quoteCells.js) */
.st-main { display: inline-flex; align-items: center; white-space: nowrap; }
/* ทั้งช่องสถานะเป็นแถวเดียว — ป้ายหลัก ป้ายเสริม และคำว่า "รอ …" เรียงต่อกัน
   ⚠ flex-wrap: wrap เป็นตาข่ายรับ ไม่ใช่ nowrap — จอแคบที่ใส่ไม่พอต้องตกบรรทัดได้
     ถ้าบังคับ nowrap ป้ายจะถูกบีบจนตัวหนังสือแตกแทนที่จะขึ้นบรรทัดใหม่ */
.st-wrap {
  display: flex; flex-wrap: wrap; align-items: center;
  /* ⚠ ชิดซ้ายเสมอ ไม่จัดกึ่งกลาง (ผู้ใช้สั่ง 4 ส.ค. 2569) — กึ่งกลางทำให้
     วงกลมเลขของแต่ละแถวเริ่มไม่ตรงกันเพราะป้ายยาวไม่เท่ากัน
     กวาดตาลงคอลัมน์แล้วเลขเต้นไปมา ชิดซ้ายเลขเรียงเป็นแนวเดียว */
  justify-content: flex-start; gap: .3rem;
}
/* จบงาน — ถูกใหญ่แทนป้ายสี ดูรายละเอียดที่ statusCell ใน js/quoteCells.js */
/* วงกลมถูกในป้าย "จบงาน" — ล้อทรงเดียวกับ .step-no ที่เป็นวงกลมเลขของขั้น 1-4
   เล็กกว่าเพราะอยู่ในป้าย ไม่ได้อยู่นอกป้ายเหมือนเลข
   ⚠ พื้น #0a7a4f ไม่ใช่ var(--ok) — ถูกสีขาวบน --ok ได้ 3.36:1 ตก AA
     ตัวนี้ 5.37:1 ผ่าน และเป็นสีเดียวกับตัวอักษรของ pill-success อยู่แล้ว
     ถูกกับตัวหนังสือจึงเป็นเขียวเฉดเดียวกัน อ่านเป็นก้อนเดียว */
.step-done {
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; flex: none;
  border-radius: 999px;
  background: #0a7a4f; color: #fff;
}
.step-done .ico { stroke-width: 3; }

/* ---------- ป้ายสถานะ ---------- */
.pill {
  display: inline-flex; align-items: center; gap: .3rem;
  padding: .18rem .6rem;
  border-radius: 999px;
  font-size: .8rem; font-weight: 500;
  white-space: nowrap;
}
.pill-success { background: var(--ok-bg);     color: #0a7a4f; }
.pill-pending { background: var(--pend-bg);   color: var(--pend); }
.pill-warn    { background: var(--warn-bg);   color: #b45f0d; }
.pill-danger  { background: var(--danger-bg); color: #b53434; }
.pill-info    { background: var(--info-bg);   color: #1f5f9e; }
.pill-muted   { background: var(--line-2);    color: var(--ink-3); }
/* ป้าย "ย้อนกลับ-บันทึกใหม่" ในช่องความคืบหน้า (ผู้ใช้สั่ง 28 ส.ค. 2569
 * รอบแรก "ทำเป็นเหมือนป้าย รับชำระแล้ว ใช้คำว่า ย้อนกลับ"
 * รอบสอง "เปลี่ยนเป็น ย้อนกลับ-บันทึกใหม่ และ bg สีเขียว"
 * รอบสาม "เปลี่ยนสี bg เป็น ส้ม")
 *
 * ⚠⚠ **ส้มทึบตัวขาว ไม่ใช่ส้มอ่อน** — ส้มอ่อน (--warn-bg) เป็นสีของป้าย
 *   "ส่งมอบแล้ว N วัน" กับป้าย "วางบิลแล้ว" ที่อยู่ในแถวเดียวกัน ใช้ซ้ำแล้ว
 *   ปุ่มกับป้ายบอกสถานะจะอ่านเป็นของชนิดเดียวกัน แล้วคนกดโดนโดยนึกว่า
 *   เป็นป้ายเฉย ๆ ซึ่งเป็นการถอนงานจริง
 * ⚠ #b45f0d คือส้มทึบที่ระบบใช้อยู่แล้ว (pill-fresh) — ขาวบนสีนี้ผ่าน AA
 *   (เหตุผลเต็มอยู่ที่ .pill-fresh ข้างบน) ห้ามใช้ --warn (#e07b1a) ตรง ๆ
 *   ตัวนั้นสว่างเกินจนตัวหนังสือขาวอ่านไม่ออก
 * ⚠ ผู้ใช้สั่งเปลี่ยนสีมาแล้ว 3 รอบ (ขาว → เขียว → ส้ม) — ห้ามเปลี่ยนกลับเอง
 * ⚠ เป็น <button> จริง ไม่ใช่ <span> — ต้องกดด้วยแป้นพิมพ์และมีวงโฟกัสได้ */
.pill.pill-undo {
  background: #b45f0d; color: #fff;
  border: 0; font-weight: 600; cursor: pointer;
}
.pill.pill-undo:hover { background: #99500a; }
.pill.pill-undo .ico { color: #fff; flex: none; }
/* ป้าย "ตรวจแล้ว" ในหน้าค่าคอมเซล — **กดแล้วบันทึกการจ่ายจริง**
 * (ผู้ใช้สั่ง 2 ก.ย. 2569 "เปลี่ยนเป็นป้ายเล็ก ๆ เหมือนรายการบน
 *  อยู่แถวเดียวกันกับตัวเลข" · ของเดิมเป็นปุ่มเต็มตัวอยู่บรรทัดถัดไป)
 *
 * ⚠⚠ **เขียวทึบ ไม่ใช่เขียวอ่อนแบบ .pill-success** — เหตุผลเดียวกับ .pill-undo
 *   เป๊ะ ๆ: ในช่องเดียวกันมีป้ายสถานะ "รอตรวจ" ที่กดไม่ได้อยู่ · โทนอ่อน
 *   เหมือนกันเมื่อไหร่ คนจะอ่านปุ่มกับป้ายเป็นของชนิดเดียวกัน แล้วกดโดน
 *   โดยนึกว่าเป็นป้ายเฉย ๆ ซึ่งที่นี่แปลว่า**จ่ายเงินจริง**
 * ⚠ #0a7a4f คือเขียวที่ระบบใช้เป็นตัวหนังสือของ .pill-success อยู่แล้ว
 *   ⟹ กลับข้างเป็นขาวบนพื้นนี้ผ่าน AA (ท่าเดียวกับที่ .pill-fresh ใช้กับส้ม)
 * ⚠ เป็น <button> จริง ไม่ใช่ <span> — ต้องกดด้วยแป้นพิมพ์ได้และมีวงโฟกัส */
.pill.pill-pay {
  background: #0a7a4f; color: #fff;
  border: 0; font-weight: 600; cursor: pointer;
}
.pill.pill-pay:hover { background: #086340; }
.pill.pill-pay .ico { color: #fff; flex: none; }
/* ป้าย "ใหม่" — ใบของฉันที่มีอะไรขยับตั้งแต่ครั้งก่อน (ผู้ใช้เคาะสีส้ม 25 ส.ค. 2569)
 * ⚠⚠ **ส้มทึบ ไม่ใช่ส้มอ่อนแบบ .pill-warn** — ในช่องสถานะเดียวกันมีป้ายงานค้าง
 *   ที่เป็น warn อยู่หลายอัน (รอราคา · ยังไม่มีเลข PO · รอตัดสต๊อก)
 *   ใช้โทนเดียวกันแล้วป้ายนี้จะกลืนหายไปในกองส้มอ่อน ซึ่งเป็นเหตุผลเดียว
 *   ที่ป้ายนี้มีอยู่ (ให้กวาดตาเจอ)
 * ⚠ #b45f0d บนพื้นขาวได้ 4.58:1 ⟹ กลับข้างเป็นขาวบน #b45f0d ก็ผ่าน AA เท่ากัน
 *   (เหตุผลเดียวกับที่ .pill-warn เลือกสีนี้ ดูหมายเหตุที่ base.css:169) */
.pill-fresh   { background: #b45f0d;          color: #fff; font-weight: 700; }
/* ป้าย "ขอคู่เทียบย้อนหลัง" — คำขอของเซลที่ยังไม่มีใครทำ
 * (ผู้ใช้สั่ง 31 ส.ค. 2569 "ป้ายรายการขอคู่เทียบย้อนหลัง เป็นสีแดงกระพริบ")
 *
 * ⚠⚠ **ป้ายเดียวในระบบที่กระพริบ ห้ามเอาไปใช้กับเรื่องอื่นอีก** — ของที่
 *   กระพริบ 2 อย่างขึ้นไปพร้อมกันในตารางเดียว = ไม่มีอะไรกระพริบ
 *   และตารางที่กระพริบทั้งจอคือตารางที่คนเลิกอ่าน
 * ⚠ แดงทึบตัวขาว ไม่ใช่แดงอ่อนแบบ .pill-danger — ในช่องเดียวกันมีป้าย danger
 *   อยู่แล้ว 2 อัน (เลยกำหนด · ไม่พร้อม N รายการ) ใช้โทนเดียวกันจะกลืนกัน
 *   #b53434 คือแดงที่ระบบใช้เป็นตัวหนังสือของ .pill-danger อยู่แล้ว
 *   ขาวบนสีนี้ได้ 5.9:1 ⟹ ผ่าน AA
 * ⚠⚠ **กระพริบด้วย opacity ไม่ใช่ visibility หรือ display** — 2 ตัวหลัง
 *   ทำให้ป้ายหายไปจากผังจริง ๆ ในบางเบราว์เซอร์ แถวจะขยับขึ้นลงตามจังหวะ
 *   กระพริบ ซึ่งอ่านตารางไม่ได้เลย
 * ⚠ ไม่จางจนหายสนิท (ต่ำสุด .35) — จังหวะที่หายไปคือจังหวะที่คนกวาดตาผ่าน
 *   พอดีแล้วไม่เห็น · ป้ายต้องอ่านออกตลอดเวลา กระพริบแค่เรียกสายตา
 * ⚠⚠ **ต้องเคารพ prefers-reduced-motion** — ผู้ใช้บางคนตั้งค่านี้ไว้เพราะ
 *   ภาพกระพริบทำให้เวียนหัวหรือชักได้จริง · ปิดอนิเมชันแล้วป้ายยังอยู่ครบ
 *   สีแดงทึบยังสะดุดตาพอโดยไม่ต้องขยับ */
.pill-blink {
  background: #b53434; color: #fff; font-weight: 700;
  animation: pill-blink 1.1s ease-in-out infinite;
}
@keyframes pill-blink {
  0%, 100% { opacity: 1; }
  50%      { opacity: .35; }
}
@media (prefers-reduced-motion: reduce) {
  .pill-blink { animation: none; }
}
/* ป้าย "สินค้านี้ยังไม่เคยมีในรายการ" ในตารางรายการ ตอน office ออกเลข
 * (ผู้ใช้สั่ง 29 ส.ค. 2569 "ให้ขึ้นป้ายเตือนชัด ๆ อาจเป็นสีแดง")
 *
 * ⚠⚠ **แดงทึบตัวขาว ไม่ใช่แดงอ่อนแบบ .pill-danger** — ในแถวเดียวกันมีป้าย
 *   "ของไม่พอ — ต้องสั่งเข้าอีก N" ที่เป็นแดงอ่อนอยู่แล้ว ใช้โทนเดียวกัน
 *   แล้วป้ายใหม่จะกลืนไปกับป้ายเดิม ซึ่งเป็นเหตุผลเดียวที่ป้ายนี้มีอยู่
 *   (เหตุผลเดียวกับที่ .pill-fresh เลือกส้มทึบ)
 * ⚠ **ไม่กระพริบ** (ผู้ใช้เคาะ 29 ส.ค. 2569) — ของที่กระพริบอ่านยากกว่า
 *   และจะไปแย่งสายตาจากป้าย "ของไม่พอ" ที่มีอยู่ตอนนี้
 * ⚠ #fff บน #b53434 ได้ 6.0:1 ผ่าน AA — #b53434 คือแดงที่ระบบใช้อยู่แล้ว
 *   (ตัวหนังสือของ .pill-danger) ห้ามใช้ --danger (#d64545) ตรง ๆ
 *   ตัวนั้นสว่างกว่า ตัวขาวบนพื้นนั้นอ่านไม่ผ่าน AA */
.pill-danger-solid { background: #b53434; color: #fff; font-weight: 700; }
/* ป้าย "ใบย้อนหลัง" ในตารางวางบิล (ผู้ใช้เคาะสีม่วงทึบ 29 ส.ค. 2569
 * หลังบอกว่า "ใส่สีให้เด่นชัดเจนกว่านี้" — ของเดิมเป็นป้ายเทา)
 *
 * ⚠⚠ **ม่วงคือสีเดียวที่ระบบยังไม่ได้ใช้** จึงไม่ไปทับความหมายที่ตั้งไว้แล้ว
 *   (แดง = งานค้างต้องรีบทำ · ส้ม = ยังมีอะไรค้าง · เขียว = เรียบร้อย)
 *   ป้ายนี้ไม่ใช่คำเตือน มันบอก **ชนิดของใบ** ว่ากรอกมือ ไม่ได้เดินผ่านระบบ
 *   ⟹ ใช้สีเตือนเมื่อไหร่ = ไปแย่งสายตากับป้าย "ของไม่พอ" ที่อยู่แถวเดียวกัน
 * ⚠⚠ **ม่วงอ่อนลงแล้ว 30 ส.ค. 2569 (ผู้ใช้สั่ง "สีม่วงให้อ่อนกว่านี้")**
 *   #6b3fa0 (ขาวบนพื้น 7.4:1) → #8457b8 (5.2:1) · ยังเป็นม่วงทึบตัวขาวเหมือนเดิม
 *   ⚠⚠ **อ่อนกว่านี้ไม่ได้แล้วถ้ายังใช้ตัวหนังสือขาว** — วัดทีละขั้นแล้ว
 *     ขั้นถัดไป #8d63c0 ได้ 4.49:1 ซึ่ง**ตกเกณฑ์ AA (4.5) พอดี**
 *     ⟹ อยากได้อ่อนกว่านี้อีก ต้องเปลี่ยนเป็น "พื้นอ่อน ตัวหนังสือเข้ม"
 *       ซึ่งเป็นคนละแบบกับที่ผู้ใช้เคาะไว้ ต้องถามก่อน ห้ามเปลี่ยนเอง
 *   ⚠ ด่านในโพรบเช็คทั้งเฉดและค่าความต่างสี ⟹ ลากให้อ่อนเกินแล้วจะฟ้อง
 * ⚠ ผู้ใช้เคาะทั้งสีเดิมและสีนี้เอง — ห้ามเปลี่ยนเฉดเองโดยไม่ถาม */
.pill-purple { background: #8457b8; color: #fff; font-weight: 700; }
/* ป้ายแดงกับปุ่มกดปิด อยู่บรรทัดเดียวกันเมื่อมีที่พอ · จอแคบตกบรรทัดได้
   ⚠ ปุ่มต้องอยู่ **ติดกับป้าย** ไม่ใช่ลอยท้ายช่อง — คนต้องอ่านออกว่าปุ่มนี้
     ปิดป้ายอันไหน ในแถวที่มีป้ายอื่น (ของไม่พอ · ลดจากราคากลาง) ปนอยู่ */
.no-stock { display: flex; flex-wrap: wrap; align-items: center; gap: .35rem; }
/* ป้าย "ขายไม่ได้" — สีดำตามที่ผู้ใช้สั่ง (15 ส.ค. 2569)
 * ⚠ **พื้นทึบ ไม่ใช่พื้นจาง+ตัวอักษรเข้มแบบป้ายอื่น** — ป้ายอื่นทั้งหมดในระบบ
 *   เป็นพื้นจาง เพราะเป็น "สถานะที่ยังเดินต่อได้" ส่วนตัวนี้คือจุดจบของดีล
 *   ต้องอ่านออกตั้งแต่กวาดตาผ่านว่าไม่เหมือนพวกนั้น
 * ⚠ **ไม่ใช่สีแดง** — แดงในระบบนี้แปลว่างานค้างที่ต้องรีบทำ (ของไม่พอ ·
 *   เลยกำหนด) แต่ขายไม่ได้คือไม่ต้องทำอะไรต่อแล้ว ใช้แดงจะไปแย่งสายตา
 *   จากงานที่ยังต้องทำจริง
 * ⚠ #fff บน #2b2f33 ได้ 12.6:1 — ผ่านสบาย ๆ และไม่ใช้ดำสนิทเพราะบนจอ
 *   ดำสนิทคู่กับขาวจะแข็งจนอ่านนาน ๆ แล้วล้า (โทนเดียวกับ --ink ของระบบ) */
.pill-dark    { background: #2b2f33;          color: #fff; }
/* ค่าคอมมิชชั่นของสินค้า — ป้ายนี้มีที่ใช้ที่เดียวคือตารางกับกล่องสินค้า
 * ⚠ **ตั้งชื่อตามหน้าที่ ไม่ใช่ยืม tone อื่นมาใช้** — ใช้โทนเดียวกับ pill-pending
 *   ก็จริง แต่ถ้าเรียก tone ว่า "pending" คนอ่านโค้ดจะนึกว่าค่าคอมเป็นสถานะรอ
 * ⚠ **ทำไมไม่ใช่เหลือง/ส้ม** (ผู้ใช้เสนอมา 7 ส.ค. 2569) — ระบบนี้ไม่มีเหลือง
 *   และตัวที่ใกล้ที่สุดคือส้ม #e07b1a ซึ่งเป็นสีของ "จอง N" ที่อยู่คอลัมน์
 *   ติดกันพอดี จะได้ส้ม 2 จุดในแถวเดียวที่แปลคนละเรื่อง
 * ⚠ **ทำไมไม่ใช่ฟ้า** — ป้าย "นำเข้า" ข้าง ๆ เป็นฟ้า (info) 2 ป้ายฟ้าติดกัน
 *   กลืนกันจนกวาดตาแล้วแยกไม่ออก ซึ่งเป็นอาการที่ผู้ใช้ทักมา
 *   เขียวอมฟ้าจึงเป็นโทนเดียวที่เหลือซึ่งไม่ชนกับใครในตารางนี้ */
.pill-comm    { background: var(--pend-bg);   color: var(--pend); }

/* ป้าย "จบงาน" — เด่นกว่าป้ายอื่นด้วยขอบเขียวทึบ อ่านเป็นตราประทับว่าจบแล้ว
 * ⚠ แยกคลาสจาก .pill-success ไม่ใช่แก้ตัวนั้น — pill-success ยังถูกใช้อีก 3 ที่
 *   (สินค้าในประเทศ · เปิดใช้งาน · รับครบ) ซึ่งเป็นแค่ป้ายบอกประเภท
 *   ไม่ใช่ปลายทางของงาน ไม่ควรเด่นเท่ากัน
 * ⚠ พื้น #d9f4e8 เข้มกว่า --ok-bg เดิมนิดเดียว — เข้มกว่านี้ (#d0f0e1) ตัวอักษร
 *   จะเหลือ 4.41 ตก AA · ค่านี้ได้ 4.62 ยังผ่าน
 * ⚠ ขอบใช้ inset shadow ไม่ใช่ border — border เพิ่มขนาดกล่องจริง ทำให้ป้ายนี้
 *   สูงกว่าป้ายอื่นในแถวเดียวกัน 3px แล้วเรียงไม่ตรงแนว */
/* ป้าย "ไม่แสดงในใบจริง" — โครงเดียวกับ .pill-done (ผู้ใช้สั่ง 6 ส.ค. 2569
 * ให้ชัดกว่าเดิม เดิมเป็น pill-muted เทาจาง จมหายไปกับหัวการ์ด)
 * ⚠ **ส้ม ไม่ใช่เขียว** ถึงจะยืมรูปแบบมาจากป้ายจบงาน — เขียวในระบบนี้แปลว่า
 *   "เรียบร้อย" ส่วนอันนี้คือของที่ **จะไม่ไปถึงลูกค้า** ซึ่งเป็นสิ่งที่ต้องสะดุดตา
 *   ก่อนกดส่ง ใช้ชุดสีเดียวกับ "ยังไม่มีเลข PO" / "รอตัดสต๊อก" ที่แปลว่า
 *   "ยังมีอะไรค้างอยู่" เหมือนกัน
 * ⚠ #a35608 ไม่ใช่ #b45f0d ที่ pill-warn ใช้ — บนพื้น warn-bg ตัวนั้นได้ 4.08
 *   ต่ำกว่าเกณฑ์ AA (4.5) ตัวนี้ได้ 4.81 และขาวบนวงกลมได้ 5.40 */
.pill-hidden {
  background: var(--warn-bg); color: #a35608; font-weight: 700;
  box-shadow: inset 0 0 0 1px rgba(163, 86, 8, .5);
}
.step-hidden {
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; flex: none;
  border-radius: 999px;
  background: #a35608; color: #fff;
}
.step-hidden .ico { stroke-width: 3; }

.pill-done {
  background: #d9f4e8; color: #0a7a4f; font-weight: 700;
  /* ⚠ 1px ไม่ใช่ 1.5px — หนากว่านี้เส้นขอบทึบคู่กับพื้นทึบทำให้อ่านเป็น "ปุ่มกด"
     ทั้งที่มันคือป้ายบอกสถานะที่กดไม่ได้ (ผู้ใช้ทัก 4 ส.ค. 2569)
     เส้นบางยังทำหน้าที่เดิมคือแยกป้ายนี้ออกจากป้ายอื่นที่ไม่มีขอบ */
  box-shadow: inset 0 0 0 1px #078566;
}

/* ป้ายในตารางเล็กกว่าที่อื่นเล็กน้อย — ช่องสถานะต้องใส่ได้ถึง 3 ป้ายในแถวเดียว
 * (จบงาน · ยังไม่มีเลข PO · รอตัดสต๊อก) ขนาดปกติกินที่ ~344px ซึ่งพอดีเป๊ะ
 * กับคอลัมน์ที่มี = ตกบรรทัดจริงบนหลายจอ ย่อลงนิดเดียวได้คืนมา ~40px
 * ⚠ ย่อแค่ระยะขอบกับตัวหนังสือ ไม่แตะสี — สีคือตัวบอกความหมาย ห้ามลดทอน */
.table .pill { padding: .16rem .5rem; font-size: .76rem; }
.table .st-wrap { gap: .25rem; }

/* ---------- ตาราง ---------- */
.table-wrap { overflow-x: auto; }
.table { width: 100%; border-collapse: collapse; font-size: .92rem; }
.table th {
  text-align: left; font-weight: 600; color: var(--ink-3);
  padding: .7rem 1rem;
  border-bottom: 1px solid var(--line);
  background: var(--surface-2);   /* หัวตาราง — เทาอมฟ้าจาง (เดิม #fafcfc อมเขียว) */
  white-space: nowrap;
}
.table td { padding: .7rem 1rem; border-bottom: 1px solid var(--line-2); vertical-align: middle; }

/* ---------- ติ๊ก "แสดงในใบเสนอ" ใต้ชื่อรายการ ----------
 * ⚠ อยู่ใต้บรรทัดที่โชว์ยี่ห้อ·รุ่นจริง ไม่ได้แทนที่มัน — คนเลือกของต้องเห็นว่า
 *   หยิบรุ่นไหนมา ส่วนบรรทัดนี้ตอบคนละคำถามคือ "จะให้ลูกค้าเห็นด้วยมั้ย"
 * ⚠ ช่องติ๊กใหญ่กว่าตัวหนังสือรอบข้างโดยตั้งใจ (14px) — บนมือถือนิ้วต้องกดโดน
 *   ทั้ง label กดได้ ไม่ใช่แค่ตัวช่อง จึงห่อด้วย <label> ไม่ใช่วางลอย ๆ
 */
.show-doc {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: .1rem .75rem;
  margin-top: .25rem;
}
.show-doc-opt {
  display: inline-flex; align-items: center; gap: .3rem;
  cursor: pointer; user-select: none;
  /* ⚠ ใช้ --ink-2 ไม่ใช่สีจางของ .muted — คำที่กดได้ต้องอ่านออกว่ากดได้
     ส่วนคำว่า "แสดงในใบเสนอ" ปล่อยจางไว้ เพราะเป็นแค่ป้ายกำกับ */
  color: var(--ink-2);
}
.show-doc-opt input { width: 14px; height: 14px; margin: 0; cursor: pointer; }

/* ⚠⚠ **ป้ายยี่ห้อ/รุ่นตัวใหญ่เท่าชื่อรายการ** (ผู้ใช้สั่ง 27 ส.ค. 2569)
   ของเดิมใช้ .small สีจาง แยกไม่ออกว่าติ๊กอยู่มั้ย โดยเฉพาะบนมือถือที่
   ตารางกลายเป็นการ์ด
   ⚠⚠ **ขนาดที่ผู้ใช้เคาะจริง (27 ส.ค. 2569 รอบ 2): ชื่อ ≈ คำว่า "แสดงในใบเสนอ"
     > ป้าย** — รอบแรกทำป้ายเท่าชื่อแล้วผู้ใช้บอก "ใหญ่เกิน" และสั่งให้คำว่า
     "แสดงในใบเสนอ" เป็นตัวใหญ่แทน · ป้ายไม่ต้องพึ่งขนาดแล้วเพราะมีสีกับ
     ไอคอนเป็นตัวบอกสถานะอยู่ ⟹ **ห้ามขยายป้ายกลับไปเท่าชื่ออีก**
   ⚠ ใช้ em ไม่ใช่ rem — ป้ายต้องเล็กกว่า**บริบทที่มันอยู่** ซึ่งบนมือถือ
     (ตารางกลายเป็นการ์ด) ใหญ่กว่าบนเดสก์ท็อป · rem จะล็อกเป็นขนาดเดียว
     ทั้ง 2 จอ แล้วบนมือถือจะเล็กเกินไป
   ⚠ ป้ายที่กดได้ต้องมี cursor เป็นมือ — .pill ปกติเป็นป้ายบอกสถานะเฉย ๆ
   ⚠⚠ **ต้องเขียน .table นำหน้าด้วย** — มีกฎ `.table .pill` ย่อป้ายในตาราง
     ให้เล็กลงอยู่แล้ว (ดูข้างบน) ซึ่งน้ำหนักเท่ากันเป๊ะกับ `.show-doc .pill`
     ปล่อยไว้จะชนะกันด้วย**ลำดับบรรทัด**ล้วน ๆ ⟹ ย้ายบล็อกไปมาวันหลัง
     ป้ายจะหดกลับเงียบ ๆ · บรรทัดที่ไม่มี .table ไว้เผื่อที่ที่ไม่ได้อยู่ในตาราง */
.show-doc .pill,
.table .show-doc .pill {
  font-size: .84em; padding: .16rem .55rem;
  cursor: pointer;
}
/* คำว่า "แสดงในใบเสนอ" — ใหญ่เท่าชื่อสินค้า (ผู้ใช้สั่ง 27 ส.ค. 2569)
   ⚠ ยังจางอยู่ เพราะเป็นป้ายกำกับ ไม่ใช่ของที่กดได้ · ใหญ่ขึ้นเพื่อให้
     กวาดตาเจอว่าบรรทัดนี้พูดเรื่องอะไร ไม่ใช่เพื่อให้เด่นกว่าป้าย */
.show-doc .show-doc-cap { font-size: inherit; }
/* ⚠ ตัวหนังสือใหญ่ขึ้นแล้ว วงกลมไอคอนต้องโตตาม ไม่งั้นดูเหมือนจุดฝุ่น */
.show-doc .step-hidden,
.show-doc .step-done { width: 17px; height: 17px; }

/* ตารางที่กว้างตามเนื้อหา ไม่ยืดเต็มการ์ด (ผู้ใช้สั่ง 4 ส.ค. 2569)
 *
 * ⚠ ตารางปกติกว้าง 100% เสมอ คอลัมน์แรกจึงกินที่ว่างที่เหลือทั้งหมด
 *   ผลคือตารางที่มี 3 คอลัมน์สั้น ๆ (รายการ · S/N · จำนวน) จะมีชื่อของอยู่
 *   ซ้ายสุด แล้วช่องติ๊กลอยไปไกลสุดขอบขวา ต้องกวาดตาข้ามที่ว่างทั้งจอ
 *   ตัวนี้ให้ตารางหดตามเนื้อหา ทุกคอลัมน์จึงเกาะกันอยู่ทางซ้าย
 *
 * ⚠ ต้องมี max-width:100% — ชื่อเครื่องมือแพทย์ยาว ๆ จะดันตารางล้นออกนอกการ์ด
 *   แล้วเกิดแถบเลื่อนแนวนอน ซึ่งผู้ใช้สั่งห้ามไว้ (ดูหมายเหตุ .table.t-quote) */
.table.t-fit { width: auto; max-width: 100%; }
.table tbody tr:last-child td { border-bottom: 0; }
.table tbody tr:hover { background: var(--brand-50); }

/* แถวที่กดเข้าไปดู/แก้ได้ทั้งแถว — ไม่ต้องเล็งปุ่มดินสอเล็ก ๆ */
.table tbody tr.row-link { cursor: pointer; }
.table tbody tr.row-link:focus-visible {
  outline: 2px solid var(--brand-500);
  outline-offset: -2px;
}
/* ⚠⚠ **ช่องกรอกในแถวที่กดได้ ต้องไม่ทำหน้าเป็นปุ่ม** (1 ก.ย. 2569 ตอนทำ
     ตารางใบปะหน้ากดดูรายการสินค้า) — cursor: pointer ตกทอดเข้าไปในช่องกรอก
     ด้วย ⟹ ช่องที่พิมพ์ได้จะชี้เหมือนปุ่ม ทั้งที่กดแล้วกล่องไม่เด้ง
     (ตัวรับกันช่องกรอกไว้โดยตั้งใจ) ⟹ เมาส์บอกคนละเรื่องกับสิ่งที่เกิดขึ้น
   ⚠ ช่องติ๊กปล่อยให้เป็น pointer ตามเดิม — มันเป็นของที่กดจริง ๆ */
.table tbody tr.row-link input.cover-in { cursor: text; }
/* ตารางใบเสนอราคา (ของฉัน + สถานะ) — คุมความกว้างเป๊ะ ไม่ปล่อยให้เบราว์เซอร์เกลี่ย
 *
 * ⚠ ทำไมต้อง table-layout: fixed — แบบ auto จะโยนที่ว่างที่เหลือทั้งหมดให้
 *   คอลัมน์ที่ไม่ได้กำหนดความกว้าง ผลคือบนจอกว้างช่องหน่วยงานยืดไป 600-700px
 *   แล้ว "เรื่อง" ถูกผลักไปไกลจนต้องกวาดตาข้ามที่ว่าง (ผู้ใช้เจอเอง 4 ส.ค. 2569)
 *
 * ⚠ **ห้ามใส่ min-width** (ผู้ใช้สั่ง 4 ส.ค. 2569 — "อย่าให้มี slide bar ทุกกรณี")
 *   เคยใส่ 1240px เพื่อกันคอลัมน์ถูกบีบ ผลคือจอที่พื้นที่เนื้อหาแคบกว่านั้น
 *   เกิดแถบเลื่อนแนวนอน ซึ่งแย่กว่าตัวหนังสือแน่นนิดหน่อยมาก —
 *   แถบเลื่อนทำให้ "คอลัมน์ขวาสุดหายไปจากสายตา" โดยผู้ใช้ไม่รู้ว่ามีอยู่
 *
 *   ทุกช่องเป็น % รวมกัน 100% พอดี ตารางจึงพอดีกรอบเสมอไม่ว่าจอกว้างเท่าไร
 *   ที่หายไปคือความมั่นใจว่าเนื้อในจะไม่ถูกบีบ — ชดเชยด้วยการลดระยะขอบช่อง
 *   บนจอที่แคบลง (ดูด้านล่าง) ซึ่งได้ที่คืนมาช่องละ 16px รวม 7 ช่อง = 112px
 *
 * ⚠ ใช้เฉพาะจอ 641px ขึ้นไป — ต่ำกว่านั้นหน้าสถานะกลายเป็นการ์ด (.cards)
 */
@media (min-width: 641px) {
  .table.t-quote { table-layout: fixed; }
  /* ตาราง "งานที่ต้องเตรียมของ" — เลขที่กับหน่วยงานห้ามตกบรรทัด (ผู้ใช้สั่ง
     13 ก.ย. 2569 "ขยายคอลัมน์ อย่าให้ตกบรรทัด")
     ⚠ ตารางนี้เป็น auto ไม่ใช่ fixed ⟹ nowrap ดันคอลัมน์ให้กว้างตามชื่อจริง
       ส่วนที่ขาดไปคอลัมน์ "รายการ" เป็นคนหด (ชื่อสินค้าตกบรรทัดได้)
     ⚠ เฉพาะจอใหญ่ — มือถือเป็นการ์ด ชื่อหน่วยงานได้ทั้งแถวและต้องตัด 2 บรรทัดได้ */
  .table.t-jobs td[data-m="hide"],
  .table.t-jobs td[data-m="name"] > div { white-space: nowrap; }
}

/* จอที่พื้นที่เนื้อหาแคบลง (โน้ตบุ๊ก 1366 หักแถบเมนูซ้ายแล้วเหลือ ~1050)
   ลดระยะขอบในช่องแทนการบีบตัวหนังสือ — ตัวเลขกับป้ายยังเต็มตัวอ่านออกหมด */
@media (max-width: 1400px) {
  .table.t-quote th, .table.t-quote td { padding-left: .5rem; padding-right: .5rem; }
  /* ⚠ หัวตารางปกติ nowrap — จอแคบมากต้องยอมให้ตกบรรทัด ไม่งั้น "รวมทั้งสิ้น"
     ล้นออกนอกช่องแล้วไปทับหัวข้อข้าง ๆ หัวตกบรรทัดยังอ่านออก ทับกันอ่านไม่ออก */
  .table.t-quote th { white-space: normal; }
}

.table .num { text-align: right; }
/* คอลัมน์ที่เนื้อในเป็นป้ายก้อนเดียว (สถานะ) — จัดกลางแล้วอ่านเป็นแถวได้ง่ายกว่า
 * ตัวหนังสือยาวไม่เท่ากันทุกแถว ชิดซ้ายแล้วขอบขวาจะกะรุ่งกะริ่ง */
.table .mid { text-align: center; }
.table .actions { text-align: right; white-space: nowrap; }
/* ตารางวางบิลเท่านั้น — ช่องเอกสารมีทั้งปุ่มลงมือและไอคอนเอกสารในช่องเดียว
 * ยาวกว่าตารางอื่นจนจอโน้ตบุ๊กแคบใส่ไม่พอ · nowrap ทำให้ **ล้นออกนอกช่อง**
 * (ไม่ใช่ตกบรรทัด) ซึ่งไปทับคอลัมน์ข้าง ๆ · วัดด้วย probe-colwidth.html
 * ⚠ ตกบรรทัดในช่องนี้ไม่เสียหาย ปุ่มยังกดได้เหมือนเดิม */
.table.t-bill .actions { white-space: normal; }

/* ---------- ตารางใบปะหน้า 12 คอลัมน์ (ผู้ใช้สั่ง 29 ส.ค. 2569) ----------
 *
 * ⚠⚠ **ต้อง table-layout: fixed** (ผู้ใช้สั่ง 29 ส.ค. 2569 รอบบ่าย
 *   "ทำอะไรในช่องต่าง ต้องไม่ขยับตำแหน่ง")
 *   ตารางแบบปกติ (auto) คิดความกว้างคอลัมน์**จากเนื้อในทุกแถว** ⟹ พอคนติ๊ก
 *   ช่องเดียวแล้ว "ยัง" กลายเป็น "จ่ายแล้ว" หรือสวัสดิการจาก "-" เป็น
 *   "11,970.00" เบราว์เซอร์จัดคอลัมน์ใหม่ทั้งตาราง **ทุกคอลัมน์เลื่อนพร้อมกัน**
 *   ⟹ fixed + ความกว้างที่ <colgroup> ในหน้า = เนื้อในเปลี่ยนยังไงก็ไม่ขยับ
 *   ⚠ **ห้ามถอด fixed ออกเพราะ "เนื้อในยาวไม่เท่ากัน"** (เหตุผลเดิมที่เคย
 *     เขียนไว้ตรงนี้เอง) — ความยืดหยุ่นที่ได้คือสิ่งเดียวกับที่ผู้ใช้สั่งให้เอาออก
 *
 * ⚠⚠ **ตัวเลขข้างล่างวัดมาแล้ว ไม่ได้กะเอา** (probe-billcover ด่าน ⑧
 *   วัดทุกครั้งที่รัน ที่กรอบ 1050px = จอโน้ตบุ๊กแคบสุด)
 *     ระยะขอบ 1.4rem ตามตารางอื่น → เนื้อในล้นออกนอกช่อง
 *     .8rem + .5rem (ของจริงตอนนี้) → ไม่มีช่องไหนล้น
 *   ⚠ ตอนเป็น auto วัดด้วยการบีบกรอบแล้วอ่าน scrollWidth ได้ 965px —
 *     **วิธีนั้นใช้กับ fixed ไม่ได้แล้ว** เพราะ fixed ไม่สนใจเนื้อใน
 *     ⟹ ของที่ต้องวัดเปลี่ยนเป็น "เนื้อในล้นช่องมั้ย" (td.scrollWidth)
 *
 * ⚠ **ตารางนี้เป็นข้อยกเว้นของกฎ "ห้ามมีแถบเลื่อนแนวนอน"** ที่ผู้ใช้สั่งไว้
 *   4 ส.ค. 2569 (ดูหมายเหตุ .table.t-quote) — 29 ส.ค. เขาเลือกเองว่า
 *   "ถ้าล้นจอให้ใช้วิธีลดขนาดตัวอักษรได้มั้ย" คือลดฟอนต์ก่อน แล้วที่เหลือ
 *   ยอมให้เลื่อน · **ห้ามแก้ด้วยการตัดคอลัมน์ทิ้ง** เขาสั่งมาครบ 12 ช่อง
 */
.table.t-cover { font-size: .8rem; table-layout: fixed; }
/* จอที่แคบกว่าโน้ตบุ๊ก (มือถือ/แท็บเล็ต) ให้เลื่อนแนวนอนแทนการบีบคอลัมน์จนอ่านไม่ออก
   ⚠ 12 คอลัมน์ที่ 400px = ช่องละ ~33px ซึ่งเป็นตัวเลขที่อ่านไม่ได้เลยสักช่อง
   ⚠ ตัวเลขนี้ = กรอบที่วัดแล้วว่าเนื้อในไม่ล้น ⟹ แคบกว่านี้คือล้นแน่ */
.table.t-cover { min-width: 1050px; }
.table.t-cover th, .table.t-cover td {
  padding-left: .5rem; padding-right: .5rem;
}
/* หัวคอลัมน์ตกบรรทัดได้ — "% สวัสดิการ" กับ "จ่ายสวัสดิการ" ยาวกว่าเนื้อในช่อง
   บังคับ nowrap แล้วหัวจะเป็นตัวกำหนดความกว้างคอลัมน์แทนเนื้อใน = กว้างเกินจำเป็น */
.table.t-cover th { white-space: normal; }
/* ⚠⚠ **ป้ายในตารางนี้ต้องตกบรรทัดได้** — ป้าย "รับชำระ 27 ส.ค. 69 10:00"
   ใต้เลข INV กว้าง 159px แต่ช่องกว้าง 84px · `.pill` ตั้ง `nowrap` ไว้
   ⟹ ล้นออกนอกช่องทันที (ด่าน "เนื้อในล้นออกนอกช่อง" ฟ้อง 49 ช่องรวด)
   ⚠ ก่อนเป็นป้าย ข้อความนี้เป็นตัวหนังสือเปล่าซึ่งตกบรรทัดเองได้อยู่แล้ว
     ⟹ ให้ตกบรรทัดเหมือนเดิม จะได้ไม่ต้องไปขยายคอลัมน์แย่งที่ช่องอื่น
   ⚠ ทับเฉพาะในตารางนี้ ห้ามแก้ `.pill` ตัวกลาง — ป้ายที่อื่นทั้งระบบ
     ตั้งใจให้อยู่บรรทัดเดียว */
/* ⚠ และพอตกบรรทัดแล้ว **ทรงแคปซูลใช้ไม่ได้** — `border-radius: 999px` กับ
   ข้อความ 3 บรรทัดกลายเป็นวงรีอ้วน ๆ (วัดด้วยภาพจริงแล้ว) ⟹ ในตารางนี้
   ให้เป็นป้ายสี่เหลี่ยมมุมมน ซึ่งเป็นทรงที่รับหลายบรรทัดได้ */
.table.t-cover .pill {
  white-space: normal;
  border-radius: .35rem;
  padding: .1rem .35rem;
  line-height: 1.35;
}

/* ช่องกรอกในตาราง — เตี้ยกว่าช่องกรอกในฟอร์ม ไม่งั้นแถวสูงขึ้นเท่าตัว
   ⚠ ต้อง font-size: inherit — ช่องกรอกใช้ฟอนต์ของระบบปฏิบัติการเป็นค่าตั้งต้น
     (ไม่สืบทอดมาจากตาราง) ⟹ ตัวเลขในช่องกรอกจะใหญ่กว่าตัวเลขในช่องข้าง ๆ */
.cover-in {
  /* ⚠⚠ **ต้องเป็น 100% ของช่อง ไม่ใช่ความกว้างตายตัวเป็น rem** — ช่องกว้าง
     ตามคอลัมน์ที่ <colgroup> กำหนดไว้แล้ว · ตั้งเป็น rem แล้วบนจอแคบมันจะ
     กว้างกว่าช่อง = ล้นไปทับคอลัมน์ข้าง ๆ ส่วนบนจอกว้างก็เหลือที่ว่างข้าง ๆ
     ⚠ ต้องมี min-width: 0 คู่กัน — ช่องกรอกมีความกว้างขั้นต่ำในตัวของมันเอง
       (ราว 20 ตัวอักษร) ซึ่งชนะ width:100% ได้ในกล่อง flex */
  width: 100%;
  min-width: 0;
  /* ⚠⚠ **ตรึงความกว้างสูงสุดไว้ที่ "พอดีเลข 7 หลัก"** (ผู้ใช้สั่ง 30 ส.ค. 2569
   *   "ย่อช่องลงให้แสดงตัวเลข 7 หลัก ตัวอย่าง 25001.05")
   *   ไม่ตรึงไว้ ช่องจะยืดเต็มคอลัมน์ ⟹ บนจอกว้าง (1600px+) กลายเป็นกล่อง
   *   ยาว ๆ ที่มีเลข 3 ตัวลอยอยู่มุมขวา · บนจอ 1280 ไม่เห็นปัญหาเพราะคอลัมน์
   *   แคบพออยู่แล้ว (วัดแล้วช่องกว้าง 67px ส่วน 25001.05 กิน 70px)
   * ⚠ ใช้ `em` ไม่ใช่ `rem` — ต้องผูกกับฟอนต์ของ**ตาราง** (.8rem) ไม่ใช่ฟอนต์
   *   ราก · วันที่ลดฟอนต์ตารางอีก ช่องต้องเล็กตามเองโดยไม่ต้องมาแก้ที่นี่
   * ⚠ ตัวเลขนี้วัดมาแล้ว ไม่ได้กะ — probe-billcover วัดซ้ำทุกครั้งว่า
   *   ใส่ 25001.05 ได้ครบ และไม่กว้างเกินจำเป็น */
  max-width: 5.4em;
  box-sizing: border-box;
  padding: .25rem .4rem;
  text-align: right;
  font-size: inherit;
  font-variant-numeric: tabular-nums;
}
/* ติ๊ก 1% / จ่ายสวัสดิการ — ตัวหนังสือกับกล่องติ๊กต้องกดได้ทั้งคู่ (เป็น label) */
.cover-ck {
  display: inline-flex; align-items: center; gap: .25rem;
  white-space: nowrap; cursor: pointer; user-select: none;
}
/* ช่องภาษี = ติ๊ก + ช่องตัวเลข ในช่องเดียว
   ⚠ ชิดขวาให้ตรงกับคอลัมน์ตัวเลขอื่น ๆ · flex-wrap กันจอแคบแล้วล้นออกนอกช่อง */
.cover-tax {
  display: flex; flex-wrap: nowrap; align-items: center;
  justify-content: flex-end; gap: .3rem;
}
/* ⚠⚠ **ติ๊กต้องไม่ยอมหด ช่องกรอกต้องยอมหด** — ไม่ตั้ง flex ให้ 2 ตัวนี้
 *   เบราว์เซอร์จะหดตัวที่หดได้ก่อน ซึ่งบางทีคือคำว่า "1%" ที่หดแล้วอ่านไม่ออก
 * ⚠ เลิกใช้ flex-wrap: wrap ตอนย้ายมาใช้ table-layout: fixed — ตกบรรทัด
 *   ทำให้ **ความสูงของแถวเปลี่ยน** ซึ่งก็คือการขยับตำแหน่งแบบหนึ่ง
 *   (ผู้ใช้สั่ง "ทำอะไรในช่องต่าง ต้องไม่ขยับตำแหน่ง" — ไม่ได้พูดถึงแค่แนวนอน) */
.cover-tax .cover-ck { flex: 0 0 auto; }
.cover-tax .cover-in { flex: 1 1 2rem; }
/* ⚠ ปุ่มในช่องนี้ยอมให้ตัวหนังสือตกบรรทัดได้ด้วย (28 ส.ค. 2569)
 * ⚠⚠ **ไม่งั้นความกว้างขั้นต่ำของคอลัมน์ = ความยาวคำว่า "บันทึกวางบิล"**
 *   ซึ่งเป็นค่าคงที่เป็นพิกเซล ⟹ พอคิดเป็น % มันโตขึ้นเรื่อย ๆ เมื่อจอเล็กลง
 *   (110px = 12% ที่จอ 1280 แต่ = 16% ที่จอ 1100) แล้วคอลัมน์นี้ก็ไปกินที่
 *   ของคอลัมน์ความคืบหน้าที่ผู้ใช้สั่งให้ขยาย
 *   ⟹ ปล่อยให้ปุ่มสูง 2 บรรทัดบนจอแคบ แลกกับคอลัมน์ที่กว้างพอทุกจอ */
.table.t-bill .actions .btn { white-space: normal; }
/* ช่อง "เรื่อง" กับ "หน่วยงาน" ของตารางวางบิลแคบ เพราะยกที่ให้คอลัมน์สถานะ
 * คำไทยยาว ๆ อย่าง "ครุภัณฑ์การแพทย์" หรือ "โรงพยาบาลสมเด็จพระยุพราชสระแก้ว"
 * เป็นก้อนเดียวที่เบราว์เซอร์ตัดไม่ได้ ⟹ ล้นออกนอกช่องบนโน้ตบุ๊กจอแคบ
 * anywhere = ยอมให้ตัดกลางคำเมื่อไม่มีทางอื่นเท่านั้น จอปกติยังตัดตามคำเหมือนเดิม */
.table.t-bill td[data-m="subj"],
.table.t-bill td[data-m="name"] { overflow-wrap: anywhere; }

/* ความกว้าง 2 คอลัมน์ที่ต้องเปลี่ยนตามจอ — หน่วยงาน (ช่อง 2) · ความคืบหน้า (ช่อง 5)
 *
 * ⚠⚠ **ที่จอ 1280 ใส่ไม่พอทั้งคู่ ต้องเลือกว่าจะให้ใครตกบรรทัด** (วัดแล้ว
 *   28 ส.ค. 2569): ตารางกว้าง ~932px · ตราประทับต้องการ 172px ·
 *   ชื่อหน่วยงานสั้นสุดที่ใช้จริงต้องการ 105-122px · รวมกับอีก 4 คอลัมน์
 *   แล้วเกินที่มี ⟹ จอแคบให้ตราประทับก่อน (21%) ชื่อยาวยอมตก 2 บรรทัด
 *   ⟹ **จอ ≥1360 ถึงมีที่พอทั้งคู่** ค่อยย้าย 3% จากความคืบหน้ามาให้หน่วยงาน
 *   ⚠ ผู้ใช้สั่ง "ขยายคอลัมน์หน่วยงาน ลดคอลัมน์ความคืบหน้า" จากจอ 1400
 *     ⟹ สิ่งที่เขาเห็นคือค่าชุดนี้ ไม่ใช่ค่าที่เขียนใน finance.js
 *
 * ⚠ **ต้องมี !important** — ความกว้างตั้งไว้เป็น inline style ที่ <th>
 *   ในหน้า (js/pages/finance.js) ซึ่งชนะกฎในสไตล์ชีตเสมอ · ที่ยังคง inline
 *   ไว้เพราะเลขทั้ง 6 ช่องต้องรวมกันได้ 100 พอดี อ่านติดกันแถวเดียวถึงจะ
 *   ตรวจด้วยตาได้ · ที่นี่แก้แค่ 2 ช่องที่เปลี่ยนตามจอ
 * ⚠ ผลรวมต้องยังเป็น 100: 9+15+34+13+18+11 (จอกว้าง) · 9+12+34+13+21+11 (จอแคบ) */
@media (min-width: 1360px) {
  .table.t-bill th:nth-child(2) { width: 15% !important; }
  .table.t-bill th:nth-child(5) { width: 18% !important; }
}
.table tr.is-low td { background: #fffaf3; }
.table tr.is-low:hover td { background: #fff4e6; }

/* แถวของใบที่ "มีอะไรขยับตั้งแต่ครั้งก่อน" — ของใหม่ในความรับผิดชอบของฉัน
 * (ผู้ใช้สั่ง 25 ส.ค. 2569 · สีส้มตามที่เคาะ)
 *
 * ⚠⚠ **คนละอันกับ .is-new** ซึ่งแปลว่า "ใบที่เพิ่งออกเลขเมื่อกี้" (`?new=` บน URL)
 *   2 อันนี้ขึ้นพร้อมกันได้และคนละความหมาย ⟹ **ห้ามยุบรวมกัน** และต้องคนละสี
 *   (.is-new เป็นสีแบรนด์ ตัวนี้เป็นส้ม)
 *
 * ⚠ ขีดซ้ายเป็นตัวชี้หลัก + พื้นอ่อนบาง ๆ — ท่าเดียวกับ .is-new ที่จูนมาแล้ว
 *   พื้นเข้มกว่านี้จะไปสู้กับ :hover และกับแถว is-low ที่ใช้พื้นสีอยู่แล้ว
 * ⚠⚠ **ขีดต้องอยู่ที่ td:first-child ของจอใหญ่ แต่ที่ tr บนมือถือ** —
 *   จอเล็ก `<tr>` กลายเป็นการ์ด (ดู .table.cards) ถ้าขีดค้างอยู่ที่ td แรก
 *   มันจะไปโผล่กลางการ์ดแทนที่จะเป็นขอบซ้ายของทั้งใบ */
.table tbody tr.is-fresh td { background: #fff7ee; }
.table tbody tr.is-fresh td:first-child { box-shadow: inset 3px 0 0 #b45f0d; }
.table tbody tr.is-fresh:hover td { background: #ffefdc; }

/* แถวของใบที่ "เพิ่งทำเสร็จ" — office ออกเลขแล้วถูกส่งมาหน้าสถานะ
 * ต้องเห็นทันทีว่าใบที่เพิ่งทำอยู่ไหนและกลายเป็นสถานะอะไร ไม่งั้นเหมือนใบหายไป
 * ⚠ ใช้แถบซ้ายเป็นตัวชี้หลัก ไม่ใช่พื้นสีเข้ม — พื้นเข้มจะไปสู้กับ :hover
 *   และกับแถว is-low (สต๊อกต่ำ) ที่ใช้พื้นสีอยู่แล้ว
 * ⚠ คงอยู่จนกว่าจะออกจากหน้า ไม่ใช่จางหายเอง — ตารางนี้ฟังข้อมูลสด
 *   อนิเมชันจะรีสตาร์ททุกครั้งที่มีคนแก้อะไร กลายเป็นกระพริบไม่หยุด */
.table tbody tr.is-new td { background: var(--brand-50); }
.table tbody tr.is-new td:first-child { box-shadow: inset 3px 0 0 var(--brand-500); }
.table tbody tr.is-new:hover td { background: var(--brand-100); }

/* ไอคอนแทน "ยังไม่มีข้อมูล" ในช่องตาราง — จางกว่าเนื้อหาจริงชัดเจน
 * เพื่อให้แถวที่มีข้อมูลแล้วเด่นขึ้นมาเวลากวาดสายตา */
.cell-none {
  display: inline-flex;
  color: var(--ink-3);
  opacity: .5;
}

/* เลข PO ใต้เลขที่ใบ — หน้าสถานะเท่านั้น (ผู้ใช้สั่ง 4 ส.ค. 2569)
 *
 * ⚠ ใช้ --ink-2 ไม่ใช่ --ink-3 ที่ใช้กับข้อความรองทั่วไป — ตัวนี้เล็ก .78rem
 *   --ink-3 บนพื้นขาวได้อัตราส่วน 4.18 ต่ำกว่าเกณฑ์ AA (4.5) ส่วน --ink-2 ได้ 8.6
 *   เลข PO ต้องอ่านให้ถูกทีละหลักเพื่อเอาไปอ้างกับโรงพยาบาล
 *   ไม่ใช่คำที่เดาจากบริบทได้เหมือนชื่อคนหรือวันที่
 *
 * ⚠ break-all เพราะคอลัมน์นี้กว้าง 10% และเลข PO ของแต่ละหน่วยงานยาวไม่เท่ากัน
 *   ยอมให้ตัดกลางตัวเลขดีกว่าล้นออกไปทับคอลัมน์หน่วยงาน */
.po-no {
  font-size: .78rem;
  font-weight: 500;
  color: var(--ink-2);
  line-height: 1.35;
  margin-top: .1rem;
  word-break: break-all;
}

/* ---------- ตารางที่กลายเป็นการ์ดบนมือถือ ----------
 * ตาราง + ชื่อสินค้าภาษาไทยยาว ๆ ไปด้วยกันไม่ได้บนจอแคบ
 * ชื่อจะพันเป็น 5-6 บรรทัดจนเห็นได้แค่ 2 รายการ และคอลัมน์ขวา
 * (ราคา/คงเหลือ) หลุดออกนอกจอ ซึ่งเป็นข้อมูลที่ต้องใช้ตัดสินใจพอดี
 *
 * ≤640px จึงเลิกเป็นตาราง เปลี่ยนเป็นการ์ดละ 1 รายการ
 * ใช้ markup ชุดเดียวกับจอใหญ่ แค่สั่งตำแหน่งใหม่ด้วย grid-area
 *
 * วิธีใช้: <table class="table cards"> แล้วติด data-m ที่ <td> แต่ละช่อง
 *   name  = ชื่อ (เต็มความกว้าง)   meta = บรรทัดรอง (ต้องใส่ has-meta ที่ table)
 *   price = มุมซ้ายล่าง            qty  = มุมขวาล่าง
 *   hide  = ซ่อนบนมือถือ (เช่นรหัส ที่ย้ายไปต่อหน้าบรรทัดยี่ห้อแทน)
 */
@media (max-width: 640px) {
  .table.cards thead { display: none; }

  .table.cards tbody tr {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas: "name name" "price qty";
    gap: .2rem .75rem;
    padding: .8rem 1rem;
    border-bottom: 1px solid var(--line-2);
  }
  .table.cards.has-meta tbody tr {
    grid-template-areas: "name name" "meta meta" "price qty";
  }
  /* has-subj = มีบรรทัด "เรื่อง" คั่นใต้ชื่อ (หน้าใบเสนอราคาของฉัน)
     ⚠ ต้องเป็นคลาสแยก ไม่ใช่ใส่ subj ให้ has-meta ทั้งหมด — ตารางสต๊อก
       ก็ใช้ has-meta แต่ไม่มีช่องเรื่อง จะได้แถวว่างเปล่าเพิ่มมาฟรี ๆ */
  .table.cards.has-meta.has-subj tbody tr {
    grid-template-areas: "name name" "subj subj" "meta meta" "price qty";
  }
  /* has-log = มีบรรทัด "ล่าสุด" (ใครทำอะไรเมื่อไหร่) ต่อท้ายก่อนยอดเงิน
     ⚠ subj ("เรื่อง") อยู่ใต้ชื่อหน่วยงานทันที เพราะ 2 อันนี้ตอบคำถามเดียวกัน
       คือ "ใบนี้ของใคร เรื่องอะไร" — แยกไปอยู่ท้ายการ์ดจะต้องกวาดตาหา
     ⚠ ประกาศเฉพาะคู่ has-meta+has-log ซึ่งตอนนี้มีแค่หน้าสถานะใบเสนอราคา
       ถ้าใส่ที่ has-meta เปล่า ๆ ตารางสต๊อกจะมีแถวว่างเพิ่มมาโดยไม่มีใครใช้ */
  .table.cards.has-meta.has-log tbody tr {
    grid-template-areas: "name name" "subj subj" "meta meta" "log log" "price qty";
  }
  .table.cards td[data-m="log"]  { grid-area: log; }
  .table.cards td[data-m="subj"] { grid-area: subj; }

  .table.cards td { display: block; padding: 0; border: 0; }
  .table.cards td[data-m="name"]  { grid-area: name; }
  /* จอใหญ่ป้ายกลุ่มอยู่บรรทัดใต้ประเภท (คอลัมน์แคบ) จอเล็กกว้างพอให้อยู่บรรทัดเดียวกัน */
  /* จอเล็กแถวกลายเป็นการ์ด ทุกอย่างเรียงชิดซ้าย ป้ายสถานะต้องตามด้วย
     ไม่งั้นจะลอยอยู่กลางการ์ดคนเดียว (ทับค่า .mid ที่ใช้ตอนเป็นตาราง) */
  .table.cards td[data-m="meta"]  { grid-area: meta; text-align: left; }
  .table.cards td[data-m="meta"] > div { display: inline; }
  /* (ชิดซ้ายเป็นค่าตั้งต้นของ .st-wrap แล้ว ไม่ต้องสั่งซ้ำที่นี่) */
  .table.cards td[data-m="price"] { grid-area: price; text-align: left; }
  .table.cards td[data-m="qty"]   { grid-area: qty; text-align: right; align-self: end; }
  .table.cards td[data-m="avail"] { grid-area: avail; text-align: right; align-self: end; }
  .table.cards td[data-m="total"] { grid-area: total; text-align: right; align-self: end; }
  .table.cards td[data-m="del"]   { grid-area: del; text-align: left; }
  .table.cards td[data-m="hide"]  { display: none; }

  /* ตารางสต๊อก — แถวล่างมี 3 ตัวเลขเรียงกัน (ราคากลาง · คงเหลือ · พร้อมขาย)
   * พอดีบนจอ 390px เพราะทุกตัวสั้นและมี data-label กำกับหน้าอยู่แล้ว
   * ⚠ **ต้องอยู่ใต้กฎ .has-meta** — น้ำหนักตัวเลือกเท่ากันเป๊ะ (3 คลาส + 2 อีลิเมนต์)
   *   ตัวที่เขียนทีหลังเท่านั้นที่ชนะ ย้ายบล็อกนี้ขึ้นไปข้างบนเมื่อไหร่
   *   grid-areas จะไม่มีผลเลยและช่อง avail จะหลุดออกนอกการ์ดแบบเงียบ ๆ */
  /* ⚠ **หมวดสินค้าอยู่ท้ายแถวเดียวกับ meta ไม่ใช่แถวของตัวเอง** (14 ส.ค. 2569)
     ทั้งคู่เป็นข้อมูลระบุตัวสินค้าและสั้นทั้งคู่ แยกแถวจะดันการ์ดสูงขึ้นฟรี ๆ
     (ท่าเดียวกับ "meta meta po" ของตารางงานที่ต้องเตรียมของ)
     ⚠ ต้องประกาศ grid-area ให้ td[data-m="kind"] ด้วย ไม่งั้นมันจะถูกวางเอง
       ต่อท้ายกริดแล้วการ์ดยาวขึ้นมาหนึ่งแถวโดยไม่มีอะไรบอก */
  .table.cards.t-stock tbody tr {
    grid-template-columns: minmax(0, 1fr) auto auto;
    grid-template-areas:
      "name  name name"
      "meta  meta kind"
      "price qty  avail";
  }
  .table.cards.t-stock td[data-m="price"],
  .table.cards.t-stock td[data-m="qty"] { text-align: left; }
  /* ⚠ ชิดขวาให้ป้ายไปอยู่มุมขวาของบรรทัด meta — ชิดซ้ายแล้วมันจะไปเบียด
     ป้ายนำเข้า/ในประเทศ จนอ่านเป็นป้ายชุดเดียวกัน
     ⚠ **ไม่โชว์ data-label "หมวด"** — บนการ์ดมันอยู่ท้ายบรรทัดที่มีป้ายอื่นอยู่
       แล้ว คำนำหน้าจะยาวกว่าตัวป้ายเอง (ต่างจากตัวเลข 3 ตัวแถวล่างที่ต้องมี) */
  .table.cards.t-stock td[data-m="kind"] { grid-area: kind; text-align: right; }
  .table.cards.t-stock td[data-m="kind"][data-label]::before { content: none; }

  /* ตารางใบลา (การ์ดสรุปบนหน้ารวม HR) — บนมือถือแถวกลายเป็นการ์ด 4 บรรทัด
   *   ประเภท              [สถานะ]
   *   วันที่ลา             รวม N วัน
   *   เหตุผล …
   *   ยื่นเมื่อ …          [ยกเลิกใบ]
   *
   * ⚠ ประเภทกับสถานะอยู่บรรทัดบนสุดคู่กัน — 2 อย่างนี้คือสิ่งที่คนกวาดตาหา
   *   ก่อนเสมอ ("ใบไหนคือใบไหน และมันผ่านหรือยัง")
   * ⚠ วันที่ลาสำคัญกว่าวันที่ยื่น จึงอยู่บรรทัดที่ 2 ส่วนวันที่ยื่นลงไปท้ายการ์ด
   *   คู่กับปุ่ม — มันเป็นข้อมูลอ้างอิง ไม่ใช่สิ่งที่ต้องอ่านทุกครั้ง
   * ⚠ **ต้องอยู่ใต้กฎ .table.cards tbody tr ข้างบน** — น้ำหนักตัวเลือกเท่ากัน
   *   ตัวที่เขียนทีหลังเท่านั้นที่ชนะ ย้ายบล็อกนี้ขึ้นไปข้างบนเมื่อไหร่
   *   grid-areas จะไม่มีผลและช่องจะหลุดออกนอกการ์ดแบบเงียบ ๆ
   *   (บทเรียนเดียวกับที่เขียนไว้ที่ .t-stock)
   */
  .table.cards.t-leave tbody tr {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "type   status"
      "dates  days"
      "reason reason"
      "sent   act";
  }
  .table.cards.t-leave td[data-m="type"]   { grid-area: type; }
  .table.cards.t-leave td[data-m="status"] { grid-area: status; text-align: right; }
  /* ⚠ **ปลด nowrap เฉพาะบนมือถือ** — ช่องนี้ตั้ง nowrap ไว้เพื่อคุมความกว้าง
     คอลัมน์บนจอใหญ่ (ดู js/leaveTable.js) พอบนการ์ดมีคำว่า "วันที่ลา" นำหน้า
     เพิ่มมา (ผู้ใช้สั่ง 18 ส.ค. 2569) บรรทัดเดียวไม่พอบนจอ 390px แล้วมันจะ
     ดันการ์ดกว้างเกินจอแทนที่จะตัดบรรทัด
     ⚠ ใส่ทั้ง 2 ตารางพร้อมกันเสมอ — ผู้ใช้สั่ง "ทั้งผู้ลาและผู้อนุมัติ" */
  .table.cards.t-leave td[data-m="dates"],
  .table.cards.t-approve td[data-m="dates"] { white-space: normal; }
  .table.cards.t-leave td[data-m="dates"]  { grid-area: dates; }
  .table.cards.t-leave td[data-m="days"]   { grid-area: days; text-align: right; }
  .table.cards.t-leave td[data-m="reason"] { grid-area: reason; }
  .table.cards.t-leave td[data-m="sent"]   { grid-area: sent; }
  /* ⚠ align-self:end ให้ปุ่มนั่งชิดขอบล่างของการ์ด ตรงกับบรรทัด "ยื่นเมื่อ"
     ที่เตี้ยกว่ามัน — ไม่งั้นปุ่มจะลอยขึ้นไปกลางช่องแล้วดูไม่ตรงกับอะไรเลย */
  .table.cards.t-leave td[data-m="act"] { grid-area: act; text-align: right; align-self: end; }

  /* ตารางหน้าอนุมัติใบลา — บนมือถือแถวกลายเป็นการ์ด 4 บรรทัด
   *   ชื่อผู้ยื่น          [ประเภท]
   *   วันที่ลา             รวม N วัน
   *   เหตุผล …
   *   ยื่นเมื่อ …
   *
   * ⚠ ชื่อผู้ยื่นอยู่บนสุด ต่างจากตารางใบลาของตัวเองที่ขึ้นประเภทก่อน — ที่นี่
   *   คนอ่านคือผู้อนุมัติ คำถามแรกคือ "ใครขอ" ไม่ใช่ "ลาแบบไหน"
   * ⚠ **ต้องอยู่ใต้กฎ .table.cards tbody tr** — น้ำหนักตัวเลือกเท่ากัน
   *   ตัวที่เขียนทีหลังเท่านั้นที่ชนะ (บทเรียนเดียวกับ .t-stock และ .t-leave)
   */
  .table.cards.t-approve tbody tr {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "who    type"
      "dates  days"
      "reason reason"
      "sent   sent"
      "act    act";
  }
  /* ⚠ ปุ่มได้เต็มความกว้างบนมือถือ ไม่ใช่เบียดมุมขวา — 2 ปุ่มที่ต้องกดแม่น ๆ
     ในแถบแคบคือที่ที่นิ้วพลาดง่ายที่สุด และการอนุมัติผิดคนแก้คืนไม่ได้ด้วยปุ่ม */
  .table.cards.t-approve td[data-m="act"] {
    grid-area: act; display: flex; gap: .5rem; margin-top: .5rem;
  }
  .table.cards.t-approve td[data-m="act"] .btn { flex: 1; justify-content: center; }
  .table.cards.t-approve td[data-m="who"]    { grid-area: who; }
  .table.cards.t-approve td[data-m="type"]   { grid-area: type; text-align: right; }
  .table.cards.t-approve td[data-m="dates"]  { grid-area: dates; }
  .table.cards.t-approve td[data-m="days"]   { grid-area: days; text-align: right; }
  .table.cards.t-approve td[data-m="reason"] { grid-area: reason; }
  .table.cards.t-approve td[data-m="sent"]   { grid-area: sent; }

  /* ตาราง "งานที่ต้องเตรียมของ" ท้ายหน้ารวมสต๊อก — ชื่อหน่วยงานเต็มบรรทัด
     แล้วตัวนับ 3 ตัวเรียงกันข้างล่าง (ใช้ grid-area เดิมที่ประกาศไว้แล้ว
     total/qty/avail จึงไม่ต้องเพิ่ม data-m ใหม่)
     ⚠ ต้องอยู่ใต้กฎ .table.cards tbody tr เหมือนบล็อกข้างบน */
  /* ⚠ เพิ่มแถว "items" คั่นกลาง (8 ส.ค. 2569) — ชื่อรายการกับบรรทัดคิว
     ต้องได้เต็มความกว้าง ยัดลงแถว meta ไม่ได้ บรรทัดคิวมีได้ถึง 3 บรรทัด
     ⚠ "รับ PO" อยู่แถวเดียวกับเจ้าของใบ ไม่ใช่แถวของตัวเอง — ทั้งคู่สั้น
       และเป็นข้อมูลประกอบเหมือนกัน แยกแถวจะดันการ์ดสูงขึ้นฟรี ๆ */
  .table.cards.t-jobs tbody tr {
    grid-template-columns: minmax(0, 1fr) auto auto;
    grid-template-areas:
      "name  name  name"
      "items items items"
      "meta  meta  po"
      "total qty   avail";
  }
  .table.cards.t-jobs td[data-m="items"] { grid-area: items; }
  /* ⚠ วันกับเวลาแยกบรรทัดบนจอใหญ่ แต่บนมือถือช่องกว้างพอ ให้อยู่บรรทัดเดียว
     ไม่งั้นการ์ดสูงขึ้นโดยไม่ได้ข้อมูลเพิ่ม */
  .table.cards.t-jobs td[data-m="po"] { grid-area: po; text-align: right; }
  .table.cards.t-jobs td[data-m="po"] > div { display: inline; margin-left: .3rem; }
  .table.cards.t-jobs td[data-m="total"],
  .table.cards.t-jobs td[data-m="qty"] { text-align: left; }

  /* ตารางในกล่องดูรายการของใบ — ชื่อรายการยาว ต้องได้ทั้งบรรทัด
     แล้วจำนวนกับสถานะอยู่บรรทัดล่างคู่กัน */
  .table.cards.t-jobview tbody tr {
    grid-template-columns: auto 1fr;
    grid-template-areas:
      "name name"
      "qty  total";
  }
  .table.cards.t-jobview td[data-m="qty"] { text-align: left; }
  .table.cards.t-jobview td[data-m="total"] { text-align: right; }

  /* ตารางรายการสินค้าในใบเสนอราคา — มีช่องกรอก 3 ช่องเพิ่มมา
   * จอแคบใส่เรียงกันแนวนอนไม่ได้ ชื่อจะถูกบีบจนเหลือคำละบรรทัด
   * จึงแยกเป็น 4 ชั้น: ชื่อ / จำนวน+หน่วย / ราคา+ยอดรวม / ลบ
   * (ปุ่มลบอยู่คนละแถวกับช่องกรอก จะได้ไม่กดโดนตอนแก้ตัวเลข)
   *
   * ⚠ **จำนวนกับหน่วยต้องอยู่แถวเดียวกัน** — บนกระดาษ 2 ค่านี้พิมพ์ติดกัน
   *   ในช่องเดียว ("1 เครื่อง") แยกคนละแถวเมื่อไหร่คนจะไม่เห็นว่ามันเป็นคู่กัน
   *   ซึ่งเป็นเหตุผลทั้งหมดที่คอลัมน์หน่วยถูกทำขึ้นมา (18 ส.ค. 2569)
   * ⚠ ปุ่มลบได้แถวของตัวเอง ชิดซ้าย ไม่ยืดเต็มแถว — ปุ่มลบที่กว้างเต็มการ์ด
   *   คือปุ่มที่นิ้วปัดจอไปโดนได้ และการลบบรรทัดกดคืนไม่ได้ */
  .table.cards.t-items tbody tr {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr);
    grid-template-areas:
      "name  name"
      "qty   unit"
      "price total"
      "del   del";
    row-gap: .55rem;
  }
  .table.cards.t-items td[data-m="qty"],
  .table.cards.t-items td[data-m="unit"],
  .table.cards.t-items td[data-m="price"] { text-align: left; align-self: start; }
  .table.cards.t-items td[data-m="del"]   { justify-self: start; }
  /* จอใหญ่คอลัมน์จำนวนชิดขวา จอเล็กต้องชิดซ้ายให้ตรงกับป้าย "จำนวน" ข้างบน */
  .table.cards.t-items .stepper { margin-left: 0; max-width: none; }
  /* ป้ายกำกับของช่องกรอกต้องอยู่บรรทัดบน ไม่ใช่ต่อหน้าช่อง */
  .table.cards.t-items td[data-label]::before { display: block; margin-bottom: .15rem; }

  /* ---------- รายงานหน่วยงาน ----------
   * ตัวเลข **4 ตัว** — มากกว่าที่การ์ดมาตรฐานรองได้ (ของเดิมมี 3)
   *   ช่องที่ 4 จึงหลุดออกนอกกริดไปกองท้ายการ์ดคนเดียว และลำดับสลับกัน
   *   (ผู้ใช้เจอจริง 9 ส.ค. 2569 — "เตรียมของเสร็จ" ไปอยู่ก่อน "ได้ PO")
   *
   * ⚠ **2×2 ไม่ใช่ 4 ตัวเรียงแนวนอน** — บนจอ 390px ช่องละ ~90px
   *   ป้าย "เตรียมของเสร็จ 21" ยาวเกินกว่านั้น จะตกบรรทัดไม่เท่ากันทุกช่อง
   * ⚠ **ลำดับต้องตรงกับหัวตารางบนจอใหญ่** — มันเป็นกรวย
   *   ยื่น → ได้ PO → เตรียมเสร็จ → ส่งสำเร็จ สลับที่เมื่อไหรความหมายก็หาย
   * ⚠ **ต้องอยู่ใต้กฎ .has-meta** — น้ำหนักตัวเลือกเท่ากันเป๊ะ ตัวที่เขียนทีหลังชนะ
   *   (เหตุผลเดียวกับ .t-stock ข้างบน)
   */
  .table.cards.t-report tbody tr {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-template-areas:
      "name  name"
      "meta  qty"
      "price avail";
    row-gap: .35rem;
  }
  /* ทุกช่องชิดซ้าย — จอใหญ่ชิดขวาเพราะอ่านเทียบตัวเลขตามแนวดิ่ง
     บนการ์ดไม่มีแนวให้เทียบ ชิดขวาจะกลายเป็นตัวเลขลอยห่างจากป้ายของตัวเอง */
  .table.cards.t-report td[data-m="qty"],
  .table.cards.t-report td[data-m="avail"],
  .table.cards.t-report td[data-m="price"],
  .table.cards.t-report td[data-m="meta"] { text-align: left; align-self: start; }

  /* ---------- เช็คลิสต์จัดเตรียมสินค้า (ผู้ใช้สั่ง 5 ส.ค. 2569) ----------
   * 5 คอลัมน์บนจอ 390px บีบชื่อเครื่องมือแพทย์จนเหลือคำละบรรทัด
   * ยาวลงมา 5-6 บรรทัดต่อรายการ ทั้งที่ขั้นนี้ต้องไล่ติ๊กทีละรายการ
   *
   * เรียงใหม่เป็น ชื่อ → ตัวเลข → ช่องติ๊ก คือลำดับที่คนทำงานอ่านจริง:
   * "ของอะไร → ต้องใช้เท่าไหร่ มีเท่าไหร่ → ตกลงพร้อมหรือยัง"
   * ⚠ ช่องติ๊กอยู่ล่างสุด ไม่ใช่บนสุดเหมือนตอนเป็นตาราง — นิ้วโป้งอยู่ล่างจอ
   *   และมันคือสิ่งที่กดหลังจากอ่านครบแล้ว
   */
  .table.cards.t-prep tbody tr {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    grid-template-areas:
      "name  name"
      "qty   have"
      "ready late";
    row-gap: .5rem;
  }
  .table.cards.t-prep td[data-m="ready"],
  .table.cards.t-prep td[data-m="late"] {
    /* ⚠ ทับ .mid ที่ใช้ตอนเป็นตาราง — ในการ์ดทุกอย่างชิดซ้าย
       ช่องติ๊กที่จัดกลางจะลอยไม่ตรงกับคอลัมน์ตัวเลขข้างบน */
    display: flex; align-items: center; gap: .45rem;
    text-align: left;
  }
  .table.cards.t-prep td[data-m="qty"],
  .table.cards.t-prep td[data-m="have"] { text-align: left; }
  .table.cards.t-prep td[data-m="ready"]::before,
  .table.cards.t-prep td[data-m="late"]::before { order: 2; }
  /* ⚠ ช่องติ๊กใหญ่ขึ้นเฉพาะบนมือถือ — ขนาดปกติ 13px กดพลาดง่ายมาก
     และการติ๊กผิดที่นี่ไปเปลี่ยนวันที่พร้อมส่งที่ลูกค้าเห็น */
  .table.cards.t-prep input[type=checkbox] { width: 20px; height: 20px; margin: 0; }

  /* แถวของขาด — จอใหญ่ทาสีที่ช่อง จอเล็กช่องเป็นก้อนย่อยในการ์ด
     ทาที่ช่องจะได้พื้นเป็นหย่อม ๆ ต้องย้ายมาทาทั้งการ์ดแทน */
  .table.cards tbody tr.is-low { background: #fffaf3; }
  .table.cards tbody tr.is-low td { background: transparent; }

  /* แถวของใหม่ — เหตุผลเดียวกับ is-low ข้างบน แต่ต้องย้าย **ขีดซ้าย** มาด้วย
     ⚠⚠ ขีดอยู่ที่ td:first-child บนจอใหญ่ ถ้าไม่ล้างทิ้งตรงนี้ มันจะไปโผล่
       กลางการ์ดตรงก้อนแรก แทนที่จะเป็นขอบซ้ายของการ์ดทั้งใบ */
  .table.cards tbody tr.is-fresh { background: #fff7ee;
    box-shadow: inset 3px 0 0 #b45f0d; }
  .table.cards tbody tr.is-fresh td { background: transparent; box-shadow: none; }

  /* ป้ายกำกับหน้าค่า — จอใหญ่มีหัวตารางบอกอยู่แล้ว จอเล็กไม่มี */
  .table.cards td[data-label]::before {
    content: attr(data-label) " ";
    color: var(--ink-3); font-size: .82rem; font-weight: 400;
  }

  /* ชื่อยาวตัดที่ 2 บรรทัด — กดเข้าไปดูตัวเต็มได้ */
  .table.cards .clamp {
    display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
    overflow: hidden;
  }

  /* รหัสสินค้าย้ายมาต่อหน้าบรรทัดยี่ห้อ/รุ่น แทนคอลัมน์ที่ซ่อนไป */
  .table.cards .sub[data-code]::before { content: attr(data-code) " · "; }
  .table.cards .only-m { display: block; }

  /* แถว "ไม่มีข้อมูล"/"กำลังโหลด" ใช้ colspan ต้องคงเป็นแถวตารางปกติ */
  .table.cards tbody tr:has(td[colspan]) { display: table-row; padding: 0; }
  .table.cards tbody tr td[colspan] { display: table-cell; }
}

/* บรรทัดที่โผล่เฉพาะจอมือถือ (ดู .table.cards ด้านบน) */
.only-m { display: none; }

/* ตรงข้ามกับ .only-m — ซ่อนบนจอมือถือ ใช้กับของที่มีความหมายเฉพาะบนคอม
 * (เช่นปุ่มพิมพ์ ไม่มีใครต่อเครื่องพิมพ์กับมือถือ)
 * ⚠ อย่าใช้ .no-print แทน ชื่อคล้ายกันแต่คนละเรื่อง — อันนั้นแปลว่า
 *   "ไม่ต้องติดไปกับกระดาษตอนสั่งพิมพ์" (ดู @media print ใน app.css) */
@media (max-width: 640px) { .hide-m { display: none !important; } }

/* ---------- สถานะว่าง / กำลังโหลด ---------- */
.empty {
  padding: 3rem 1.5rem; text-align: center; color: var(--ink-3);
}
.empty .icon { font-size: 2.4rem; opacity: .5; margin-bottom: .5rem; }

.spinner {
  width: 18px; height: 18px;
  border: 2px solid var(--brand-100);
  border-top-color: var(--brand-500);
  border-radius: 50%;
  animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.loading { display: flex; align-items: center; justify-content: center; gap: .6rem; padding: 2.5rem; color: var(--ink-3); }

/* ---------- ม่านทึบตอนกำลังทำงาน ----------
 * ใช้กับงานที่กดซ้ำไม่ได้และอาจรอนาน (ส่งใบเสนอราคาจากมือถือเน็ตช้า)
 * z-index สูงกว่า modal เพราะบางทีเปิดทับกล่องยืนยันที่ยังปิดไม่ทัน
 */
.busy-overlay {
  position: fixed; inset: 0; z-index: 9500;
  display: flex; align-items: center; justify-content: center;
  padding: 1.25rem;
  background: rgba(var(--scrim-rgb), .38);
  backdrop-filter: blur(2px);
  animation: fade .15s;
  cursor: progress;
}
.busy-box {
  display: flex; flex-direction: column; align-items: center; gap: .85rem;
  min-width: 200px; max-width: 320px;
  padding: 1.7rem 2rem;
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-3);
  text-align: center;
  font-weight: 500; color: var(--ink-2);
  animation: pop .2s ease-out;
}
.spinner.big { width: 36px; height: 36px; border-width: 3px; }

.skeleton {
  background: linear-gradient(90deg, var(--line-2) 25%, var(--surface-2) 50%, var(--line-2) 75%);
  background-size: 200% 100%;
  animation: shimmer 1.2s infinite;
  border-radius: 6px;
}
@keyframes shimmer { to { background-position: -200% 0; } }

/* ---------- แถบความคืบหน้าตอนอัปไฟล์ ----------
 * ⚠ **แถบเดินตามจำนวนไฟล์ที่เสร็จ ไม่ใช่แถบวิ่งไปเรื่อย ๆ** — แถบวิ่งบอกแค่
 *   "ยังทำงานอยู่" ซึ่งตัวหมุนบอกได้อยู่แล้ว · คนที่เลือก 5 ภาพอยากรู้ว่า
 *   เหลืออีกกี่ภาพ ไม่ใช่แค่ว่ายังไม่เสร็จ
 * ⚠ มี transition ที่ความกว้าง — กระโดดทีเดียวจาก 20% ไป 40% ตาจับไม่ทัน
 *   ว่าขยับ กลายเป็นแถบที่ดูเหมือนค้าง
 * ⚠ ลายวิ่งบนแถบใช้ .skeleton ที่มีอยู่แล้ว **ห้ามเขียน keyframes ใหม่**
 *   ระบบมีลายวิ่งชุดเดียวพอ
 * ⚠ เคารพ prefers-reduced-motion — บางเครื่องตั้งปิดภาพเคลื่อนไหวไว้ */
.up-bar {
  height: 6px; border-radius: 999px;
  background: var(--line-2); overflow: hidden;
  margin-top: .4rem;
}
.up-bar > i {
  display: block; height: 100%; width: 0;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--brand-500) 25%,
              var(--brand-300) 50%, var(--brand-500) 75%);
  background-size: 200% 100%;
  animation: shimmer 1.2s linear infinite;
  transition: width .25s ease;
}
@media (prefers-reduced-motion: reduce) {
  .up-bar > i { animation: none; transition: none; }
}

/* ---------- กล่องแจ้งเตือนในหน้า ----------
 * ใช้พื้นอ่อน + เส้นขอบบาง แทนแถบสีหนา ๆ ด้านซ้าย
 * ยังแยกความหมายด้วยสีได้ แต่ไม่ตะโกนใส่ผู้ใช้
 */
.alert {
  display: flex; align-items: flex-start; gap: .6rem;
  padding: .8rem 1rem;
  border-radius: var(--radius-sm);
  border: 1px solid rgba(214,69,69,.28);
  background: var(--danger-bg);
  color: #93312f;
  font-size: .87rem; line-height: 1.6; text-align: left;
}
.alert.warn { background: var(--warn-bg); color: #8a4b0a; border-color: rgba(224,123,26,.3); }
.alert.info { background: var(--info-bg); color: #1c5590; border-color: rgba(47,127,209,.26); }
.alert.ok   { background: var(--ok-bg);   color: #0a7a4f; border-color: rgba(22,160,133,.3); }   /* = --ok #16A085 */
/* ⚠ ขั้นแรงสุด — ใช้เมื่อกดต่อแล้ว**เสียเงินจริงและย้อนไม่ได้** ไม่ใช่แค่ผิดปกติ
   ⚠ อย่าใช้พร่ำเพรื่อ เหตุผลเดียวที่มันได้ผลคือมันหายาก · กล่องยืนยันที่มี
     .alert.warn 2 กล่องซ้อนกันคนอ่านแยกไม่ออกว่าอันไหนสำคัญกว่า ซึ่งเป็น
     เหตุผลที่เพิ่มขั้นนี้เข้ามา (7 ส.ค. 2569) */
.alert.danger {
  background: var(--danger-bg); color: #a32c2c;
  border-color: rgba(214,69,69,.42); border-left-width: 4px;
}
.alert b    { display: block; margin-bottom: .1rem; }
.alert svg  { flex: none; margin-top: .15rem; }

/* รายชื่อนิติบุคคลในกล่องเตือน — **ชื่อเด่นด้วยขนาด ไม่ใช่ด้วยการขึ้นบรรทัด**
   ⚠ ผู้ใช้สั่ง 7 ส.ค. 2569 ให้ชื่อใหญ่และชัด แล้วสั่งต่อว่าให้อยู่บรรทัดเดียว
     กับรายละเอียด — เคยแยก 2 บรรทัดอยู่รอบหนึ่ง 2 รายกินไป 4 บรรทัด
     ยาวเกินไปสำหรับข้อมูลแค่นี้
   ⚠ **ห้ามใช้ min-width จัดชื่อเป็นคอลัมน์** — ชื่อไทยยาวไม่เท่ากัน
     (สมาร์ทพลัส vs สมาร์ทเน็ต) ตั้งความกว้างตายตัวแล้วจอ 390px จะตัดคำกลางชื่อ
   ⚠ ระยะห่างมาจาก margin-right ของชื่อ ไม่ใช่จุดคั่น "·" — จุดคั่นระหว่าง
     ตัวอักษร 2 ขนาดจะลอยไม่ตรงกับอะไรเลย ขนาดที่ต่างกันแยกให้แล้ว */
.alert .ent-row + .ent-row { margin-top: .3rem; }
.alert .ent-name {
  font-size: 1.02rem; font-weight: 700;
  letter-spacing: -.01em; margin-right: .45rem;
}

/* ---------- Toast ---------- */
#toasts {
  position: fixed; z-index: 9000;
  right: 1.25rem; bottom: 1.25rem;
  display: flex; flex-direction: column; gap: .6rem;
}
.toast {
  display: flex; align-items: flex-start; gap: .6rem;
  min-width: 260px; max-width: 380px;
  padding: .8rem 1rem;
  border-radius: var(--radius-sm);
  border-left: 4px solid var(--brand-500);
  background: var(--surface);
  box-shadow: var(--shadow-2);
  animation: toast-in .22s ease-out;
}
.toast.ok    { border-left-color: var(--ok); }
.toast.err   { border-left-color: var(--danger); }
.toast.warn  { border-left-color: var(--warn); }
.toast.out   { animation: toast-out .18s ease-in forwards; }
@keyframes toast-in  { from { opacity: 0; transform: translateY(8px); } }
@keyframes toast-out { to   { opacity: 0; transform: translateX(20px); } }

/* ---------- Modal ---------- */
.modal-backdrop {
  position: fixed; inset: 0; z-index: 8000;
  display: flex; align-items: center; justify-content: center;
  padding: 1.25rem;
  background: rgba(var(--scrim-rgb), .45);
  backdrop-filter: blur(2px);
  animation: fade .18s;
}
@keyframes fade { from { opacity: 0; } }
.modal {
  width: 100%; max-width: 560px;
  max-height: calc(100vh - 3rem);
  display: flex; flex-direction: column;
  background: var(--surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-3);
  animation: pop .2s ease-out;
}
@keyframes pop { from { opacity: 0; transform: translateY(12px) scale(.98); } }
.modal.wide { max-width: 880px; }
.modal-head {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: 1.1rem 1.35rem; border-bottom: 1px solid var(--line-2);
}
/* ⚠ หัวกล่องบางกล่องเป็น **ชื่อสินค้า** ซึ่งขึ้นบรรทัดใหม่เองได้แล้ว
   (ดู cleanName ใน js/products.js) ถ้าไม่ตั้ง pre-line บรรทัดที่คนกดไว้จะยุบ
   เป็นช่องว่างเงียบ ๆ แล้วดูเหมือนช่องกรอกไม่ได้บันทึกอะไรเลย
   หัวกล่องอื่นเป็นข้อความบรรทัดเดียวที่เขียนตายตัว ไม่กระทบ */
.modal-head h2 { white-space: pre-line; }
.modal-body { padding: 1.35rem; overflow-y: auto; }
.modal-foot {
  display: flex; justify-content: flex-end; gap: .6rem;
  padding: 1rem 1.35rem; border-top: 1px solid var(--line-2);
}

/* จอมือถือ: ลดขอบทุกชั้น — เดิมกิน 5.2rem จากความกว้าง 360px
 * เหลือที่ให้เนื้อหาไม่ถึง 300px ชื่อสินค้าเลยพันหลายบรรทัดเกินจำเป็น */
@media (max-width: 640px) {
  .modal-backdrop { padding: .55rem; }
  .modal { max-height: calc(100vh - 1.1rem); }
  .modal-head { padding: .9rem 1rem; }
  .modal-body { padding: 1rem; }
  .modal-foot { padding: .8rem 1rem; }
  .modal-head h2 { font-size: 1.05rem; }
}

/* ---------- utility ---------- */
.row     { display: flex; align-items: center; gap: .6rem; }
.row.wrap{ flex-wrap: wrap; }
.spacer  { flex: 1; }
.muted   { color: var(--ink-3); }
/* ⚠ #b45f0d ไม่ใช่ var(--warn) — ส้มตัวนั้นบนพื้นขาวได้แค่ 2.99:1 อ่านไม่ผ่าน AA
   ตัวนี้ 4.58:1 ผ่าน (เหตุผลเดียวกับปุ่มสีส้มที่ base.css:142) */
.warn-text { color: #b45f0d; font-weight: 600; }
/* ข้อความ "เลยกำหนดแล้ว" — คู่กับ .warn-text แต่แรงกว่าหนึ่งขั้น
   ⚠ ใช้ --danger ไม่ใช่แดงสด — ต้องเป็นสีเดียวกับปุ่มอันตรายทั้งระบบ
     ไม่งั้นแดงในระบบจะมี 2 เฉดที่แปลไม่ออกว่าต่างกันตรงไหน */
.late-text { color: var(--danger); font-weight: 600; }
/* ข้อความหลายบรรทัดที่พิมพ์มาเอง — คงการขึ้นบรรทัดตามที่กรอก
   (ใช้ในการ์ดเงื่อนไข/หมายเหตุ ตอนที่แก้ไม่ได้ จึงไม่มี textarea ให้) */
.pre-line { white-space: pre-line; }

/* เลขเครื่อง (S/N) ใต้ชื่อรายการในการ์ดติดตั้ง — กดที่ตัวเลขเพื่อแก้ได้
 *
 * ⚠ ใช้ --ink-2 (8.6:1) ไม่ใช่ --ink-3 (4.18:1 ตกเกณฑ์ AA ที่ขนาดนี้)
 *   เลขเครื่องเป็นรหัสที่ต้องอ่านถูกทีละตัว เดาจากบริบทไม่ได้
 * ⚠ tabular + break-all — S/N ยาวไม่เท่ากันและบางอันมีหลายเครื่องคั่นด้วย ,
 *   ปล่อยให้ตัดกลางดีกว่าดันคอลัมน์จนตารางล้นบนมือถือ
 * ⚠ ไม่ทำเป็นลิงก์สีน้ำเงิน — ในตารางนี้ไม่มีอะไรอื่นเป็นลิงก์ สีน้ำเงินโดด ๆ
 *   จะอ่านเหมือน "กดแล้วไปหน้าอื่น" ทั้งที่แค่เปิดกล่องแก้ค่า */
.sn-line {
  color: var(--ink-2);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  word-break: break-all;
  cursor: pointer;
  margin-top: .15rem;
  text-decoration: underline dotted var(--line);
  text-underline-offset: 3px;
}
.sn-line:hover { text-decoration-color: var(--ink-3); }

/* บรรทัดเลขเครื่องแบบอ่านอย่างเดียว — การ์ดติดตั้ง/ส่งมอบ
   ⚠ ต้องถอด "ทั้งมือชี้และเส้นใต้ประ" ไม่ใช่อย่างใดอย่างหนึ่ง — 2 อันนี้คือ
     สิ่งที่บอกว่ากดได้ เหลือไว้อันเดียวคนก็ยังกด แล้วไม่มีอะไรเกิดขึ้น
     (ย้ายการใส่เลขไปการ์ดจัดเตรียมแล้ว 14 ส.ค. 2569 ดู snLine) */
.sn-line.is-ro { cursor: default; text-decoration: none; }

/* ทางพิมพ์เลขเอง ตอนที่ยังไม่มีเลข — จางกว่าเลขจริงเพราะเป็นคำเชิญให้กด
   ไม่ใช่ข้อมูล ถ้าเข้มเท่ากันจะอ่านผิดว่าเลขเครื่องคือคำว่า "+ ใส่ S/N" */
.sn-line.muted { color: var(--ink-3); font-weight: 500; }

/* บันทึกช่วยจำใต้ชื่อรายการ — ใช้ทั้งการ์ดจัดเตรียมสินค้าและกล่องดูงานในหน้ารวมสต๊อก
 *
 * ⚠ **เป็นบล็อกมีพื้นและแถบซ้าย ไม่ใช่บรรทัดข้อความจาง ๆ** (ผู้ใช้สั่ง 7 ส.ค. 2569
 *   ว่า "กวาดตามาต้องอ่านได้เลย ในมือถือด้วย") — ของเดิมเป็น .small สี --ink-2
 *   มีแค่เส้นใต้ประ ซึ่งตากวาดผ่านไปเลยเพราะไม่ต่างจากบรรทัดยี่ห้อ/รุ่นที่อยู่
 *   ตำแหน่งเดียวกัน **สิ่งที่ทำให้สะดุดตาคือรูปทรง ไม่ใช่สี** ตัวหนังสือเข้มขึ้น
 *   อย่างเดียวไม่พอ เพราะการ์ดบนมือถือมีข้อความเข้มอยู่แล้วหลายบรรทัด
 *
 * ⚠ ไม่ใช้ tabular-nums / break-all แบบ .sn-line — นี่เป็นประโยคภาษาไทย
 *   ที่คนพิมพ์จากการโทรคุย ต้องตัดคำตามปกติ ไม่ใช่ตัดกลางคำแบบรหัส
 * ⚠ พื้น --brand-50 ไม่ใช่ --warn-bg — บันทึกเป็น "ข้อมูลเพิ่มเติม" ไม่ใช่คำเตือน
 *   และแถวที่ของขาดเป็นพื้นส้ม (.is-low) อยู่แล้ว ใช้ส้มซ้อนส้มจะกลืนกันหมด
 * ⚠ ไม่ใส่คลาส .small ที่ตัว element — ขนาดคุมจากที่นี่ที่เดียว
 *   (.small = .85rem เล็กไปสำหรับของที่ต้องอ่านได้ตอนกวาดตา) */
.note-box {
  display: block;
  margin-top: .4rem;
  padding: .38rem .6rem;
  border-left: 3px solid var(--brand-400);
  border-radius: 0 6px 6px 0;
  background: var(--brand-50);
  color: var(--ink);
  font-size: .92rem;
  line-height: 1.45;
}
.note-box svg { color: var(--brand-600); vertical-align: -2px; }
/* ชื่อคนกับวันที่ยังเบาได้ — เป็นที่มาของข้อความ ไม่ใช่ตัวข้อความ */
.note-box .muted { font-size: .85rem; }
/* กดแก้ได้เฉพาะ office — เซลได้บล็อกเดียวกันแต่ไม่มี role จึงไม่มีมือชี้ */
.note-box[role="button"] { cursor: pointer; }
.note-box[role="button"]:hover { background: var(--brand-100); }

/* บรรทัดคิวจองใต้ชื่อรายการ — "ใบนี้คิวที่เท่าไหร่ ใครอยู่ก่อน"
 * ⚠ เบากว่า .note-box หนึ่งขั้นโดยตั้งใจ — เป็นข้อมูลประกอบการตัดสินใจ
 *   ไม่ใช่ข้อความที่คนเขียนถึงกัน บล็อกทึบ 2 อันซ้อนกันใต้ชื่อเดียวจะแย่งกันเอง
 * ⚠ ตัวคั่นทำด้วย border ไม่ใช่ตัวอักษร " · " — บนมือถือรายการตกบรรทัดได้
 *   จุดคั่นจะไปค้างต้นบรรทัดใหม่ ซึ่งอ่านเหมือนหัวข้อย่อยที่ไม่มีเนื้อ */
.queue-line { margin-top: .25rem; color: var(--ink-2); }
.queue-line b { color: var(--ink); }
.queue-item {
  display: inline-block;
  margin-left: .5rem;
  padding-left: .5rem;
  border-left: 1px solid var(--line);
}
.queue-item a { font-variant-numeric: tabular-nums; }

.note-add { cursor: pointer; margin-top: .3rem; }
/* ระหว่างเขียนฐาน — กันกดซ้ำโดยไม่ต้องแทน node (ตัว node อาจถูกวาดใหม่ทันทีที่เสร็จ) */
.note-box[data-busy], .note-add[data-busy] { opacity: .5; pointer-events: none; }

/* เลขเครื่องในตารางสรุปของการ์ดส่งมอบ — อ่านอย่างเดียว ไม่ต้องมีเส้นใต้/มือชี้ */
.sn-cell { font-variant-numeric: tabular-nums; word-break: break-all; }
.small   { font-size: .85rem; }
.strong  { font-weight: 600; }
.center  { text-align: center; }
.right   { text-align: right; }
.nowrap  { white-space: nowrap; }
.hidden  { display: none !important; }
.mt-0{margin-top:0}.mt-1{margin-top:.5rem}.mt-2{margin-top:1rem}.mt-3{margin-top:1.5rem}
.mb-0{margin-bottom:0}.mb-1{margin-bottom:.5rem}.mb-2{margin-bottom:1rem}.mb-3{margin-bottom:1.5rem}

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
