/* =====================================================================
   Grape POS — ระบบดีไซน์กลาง
   ---------------------------------------------------------------------
   แนวคิด: ร้านกาแฟที่ "วัดผล" ได้
   - สีหลักมาจากอัญชันกับใบเตย ไม่ใช่โทนกาแฟน้ำตาลที่ทุกร้านใช้
   - ตัวเลขเงินทุกตัวใช้ font-variant-numeric: tabular-nums ให้หลักตรงกันเสมอ
     (ไม่ใช้ฟอนต์ตระกูล monospace แล้ว — ดูคอมเมนต์ที่ --mono ด้านล่าง)
     เหมือนอ่านม้วนใบเสร็จ และทำให้เทียบตัวเลขในรายงานได้ด้วยตา
   - พื้นผิวเรียบ ไม่มี gradient ไม่มีเงาฟุ้ง ความน่าสนใจอยู่ที่ตัวเลข
   ===================================================================== */

:root {
  /* ---- สี ---- */
  --ink:        #0f1b2d;   /* ตัวอักษรหลัก */
  --ink-soft:   #5a6b80;   /* ตัวอักษรรอง */
  --ink-faint:  #94a3b5;   /* ป้ายกำกับ */
  --paper:      #ffffff;   /* พื้นผิวการ์ด */
  --wash:       #f4f7fc;   /* พื้นหลังหน้า */
  --line:       #e3eaf4;

  --blue:      #1656b8;   /* สีหลัก — ปุ่ม ลิงก์ จุดเน้น */
  --blue-dim:  #e7effb;
  --blue-border: #c7d9f2;
  --sky:       #35a7e8;   /* โทนฟ้ารอง — สำรองไว้ใช้ในอนาคต */
  --sky-dim:   #e3f4fd;
  --pos:       #0f7a4d;   /* ตัวเลขบวก ยอดขาย */
  --pos-dim:   #e2f4ec;
  --pos-border: #c3e3d3;
  --neg:       #c0392b;   /* เงินขาด บิลยกเลิก */
  --neg-dim:   #fdecea;
  --neg-border: #f3c9c5;
  --warn:      #b8730f;   /* คำเตือนที่ยังไม่ร้ายแรง เช่นของใกล้หมด */
  --warn-dim:  #fdf3e3;
  --warn-border: #f0dab3;
  --yellow:    #c99400;   /* ปุ่ม "แก้ไข" ในคอลัมน์จัดการเท่านั้น — ตั้งใจแยกจาก --warn (ความหมาย
                             คนละเรื่องกัน --warn ใช้เตือนสถานการณ์ เช่นของใกล้หมด/โปรยังไม่ถึงเวลา
                             --yellow ใช้ระบุการกระทำ "แก้ไข" เท่านั้น อย่าเอาไปใช้ปนกัน) เข้มพอให้
                             ตัวหนังสือสีเข้ม (--ink) อ่านออกชัดตัดกับพื้นหลังนี้ — ดูหัวข้อ "กฎเรื่อง
                             ปุ่มในคอลัมน์จัดการ" ใน CLAUDE.md
                             */
  --yellow-dim: #fdf3d9;
  --yellow-border: #f0dc9c;

  /* ---- ระยะ ---- */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px;
  --s5: 24px; --s6: 32px; --s7: 48px;

  --r:    14px;             /* ปุ่มและช่องกรอก */
  --r-lg: 18px;             /* การ์ดและกล่องใหญ่ */
  --tap: 56px;             /* ขนาดต่ำสุดของสิ่งที่นิ้วต้องกด */

  --shadow-card: 0 6px 16px -10px rgba(15, 27, 45, .35);
  --shadow-pop:  0 12px 32px -12px rgba(15, 27, 45, .45);

  /* ---- ฟอนต์ — ฟอนต์ระบบล้วนๆ ไม่โหลดจากอินเทอร์เน็ตอีกต่อไป (เดิมเคยเป็น Google Fonts
     "IBM Plex Sans Thai Looped" ผ่าน @import — เปลี่ยนแล้วตามงาน "ปรับรากฐานการแสดงผลให้รองรับ
     แท็บเล็ต" รอบที่ 1 เพราะร้านอาจเน็ตช้าหรือหลุด ฟอนต์ที่โหลดไม่ทันจะทำให้ตัวอักษรกระพริบเปลี่ยน
     หน้าตา (FOUT) หรือร้ายกว่านั้นคือค้างเป็นฟอนต์สำรองพื้นๆ ตลอดถ้าโหลดไม่สำเร็จเลย — ดู CLAUDE.md
     หัวข้อ "สแต็กเว็บ" ที่อัปเดตให้ตรงกันแล้ว)
     ลำดับฟอนต์ตั้งใจไล่ตามอุปกรณ์ที่ระบบนี้ใช้จริง (ดู CLAUDE.md หัวข้อ "อุปกรณ์ปลายทาง"):
       1. -apple-system / BlinkMacSystemFont — ฟอนต์ระบบของ iOS/iPadOS/macOS (San Francisco)
          ตัวอักษรไทยถูกแทนที่อัตโนมัติในระดับ OS ด้วยฟอนต์ไทยของ Apple เองอยู่แล้ว (Thonburi/
          Kailasa) แม้จะเรียกชื่อนี้ก็ตาม — ใส่ไว้อันดับแรกเพราะ iPad เป็นอุปกรณ์เป้าหมายหลักของ
          งานนี้
       2. "Segoe UI" — ฟอนต์ระบบของ Windows (เครื่อง dev/เครื่องเจ้าของร้านที่อาจเปิดผ่าน desktop)
          ตัวอักษรละตินอ่านเนียนตากับ UI ของ Windows เอง
       3. "Noto Sans Thai" — ฟอนต์ไทยที่ Android ทุกเครื่อง (5.0+) ใช้จริงเป็นค่าเริ่มต้นเวลาต้อง
          แสดงตัวอักษรไทย (Android เองก็ substitute ให้อัตโนมัติอยู่แล้ว แต่ระบุชื่อตรงๆ ไว้กันความ
          เสี่ยงที่ WebView บางรุ่นไม่ทำ fallback ระดับตัวอักษรให้) — อุปกรณ์ปลายทางจริงของร้านคือ
          แท็บเล็ต Android เป็นหลัก จุดนี้สำคัญที่สุดในทางปฏิบัติ
       4. "Leelawadee UI" — ฟอนต์ไทยของ Windows 10 ขึ้นไป (ทันสมัยกว่า Tahoma แต่เครื่องเก่ามากๆ
          อาจไม่มี จึงใส่ Tahoma ต่อท้ายเป็นตาข่ายรองรับอีกชั้น)
       5. Tahoma ตัวสำรองสุดท้ายก่อน sans-serif ทั่วไป — มีอยู่แทบทุกเวอร์ชันของ Windows มานาน
          รองรับตัวอักษรไทยมาตั้งแต่ยุคแรกๆ
     --mono ไม่ใช่ฟอนต์ monospace จริง (ตัดสินใจไว้ตั้งแต่ก่อนงานนี้) ยังคงชี้ไปฟอนต์เดียวกับ --font
     ตัวเลขเรียงหลักตรงกันด้วย font-variant-numeric: tabular-nums แทน (ดู .num) ชื่อตัวแปรคงไว้
     เพื่อไม่ต้องแก้ทุกจุดที่อ้างถึง */
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans Thai", "Leelawadee UI", Tahoma, sans-serif;
  --mono: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans Thai", "Leelawadee UI", Tahoma, sans-serif;

  /* ---- สเกลขนาดตัวอักษร — ใช้ทั่วทั้งระบบแทนการฮาร์ดโค้ดขนาดกระจัดกระจาย ----
     --text-sm คือขนาดเล็กที่สุดที่อนุญาตในระบบ (15px) ห้ามมีตัวอักษรไหนเล็กกว่านี้อีก ยกเว้น
     เฉพาะใบเสร็จพิมพ์กระดาษความร้อน 80mm (views/receipt.php, @media print และ
     .receipt-preview-backdrop) ที่ยกเว้นไว้ตั้งใจ เพราะเป็นข้อจำกัดทางกายภาพของกระดาษ ไม่ใช่จอ
     แท็บเล็ต — เดิมระบบมีตัวอักษรเล็กสุดแค่ 11px (.eyebrow/.status-badge/th) ซึ่งอ่านยากบน
     แท็บเล็ตที่ถือห่างจากตามากกว่ามือถือ */
  --text-sm:   15px;   /* เล็กสุดในระบบ — label, badge, eyebrow, ตัวเลือกย่อยในตะกร้า ฯลฯ */
  --text-base: 16px;   /* เนื้อหาหลัก */
  --text-lg:   18px;   /* หัวข้อย่อย (h2), ปุ่ม/ตัวเลขที่ต้องเด่นกว่าเนื้อหาปกติเล็กน้อย */
  --text-xl:   22px;   /* หัวข้อหลัก (h1) */
  --text-2xl:  30px;   /* ตัวเลขเด่น เช่นยอดในตะกร้าหน้าขาย */
  --text-3xl:  42px;   /* ตัวเลขใหญ่สุดในระบบ เช่นยอดสุทธิหน้าขาย/ยอดตะกร้า */
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

/* กัน iOS Safari ปรับขนาดตัวอักษรเองอัตโนมัติตอนพลิกแนวจอ (เช่นข้อความในคอลัมน์แคบๆ ถูกขยายเอง
   โดยที่เราไม่ได้สั่ง) ทำให้ขนาดตัวอักษรที่ตั้งใจกำหนดไว้ (--text-* ทั้งชุด) เพี้ยนไปจากที่ออกแบบ */
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }

html, body { height: 100%; }

body {
  margin: 0;
  font-family: var(--font);
  font-size: var(--text-base);
  line-height: 1.65;   /* ≥1.6 ตามกฎภาษาไทย (สระบน/ล่าง+วรรณยุกต์ต้องการที่มากกว่าอังกฤษ) */
  color: var(--ink);
  background: var(--wash);
  -webkit-font-smoothing: antialiased;
}

/* ---------------------------------------------------------------------
   ตัวเลข — หัวใจของระบบนี้
   ใส่ class="num" กับทุกตัวเลขเงิน จำนวน และเปอร์เซ็นต์
   font-variant-numeric: tabular-nums ทำให้ทุกหลักกว้างเท่ากัน
   คอลัมน์ตัวเลขในตารางจะตรงกันโดยไม่ต้องจัดชิดขวาเอง
   --------------------------------------------------------------------- */
.num {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
}
/* line-height ตึงกว่าเนื้อหาปกติตั้งใจ — ตัวเลขล้วนไม่มีสระ/วรรณยุกต์ไทยให้ต้องเผื่อที่ กฎ ≥1.6
   ใช้กับข้อความไทยเท่านั้น */
.num-lg  { font-size: var(--text-2xl); font-weight: 600; line-height: 1.15; }
.num-xl  { font-size: var(--text-3xl); font-weight: 600; line-height: 1.05; }
.num-pos  { color: var(--pos); }
.num-neg  { color: var(--neg); }
.num-warn { color: var(--warn); }

/* ---- ตัวอักษร ---- */
h1 { font-size: var(--text-xl); font-weight: 600; margin: 0 0 var(--s4); letter-spacing: -0.01em; }
h2 { font-size: var(--text-lg); font-weight: 600; margin: 0 0 var(--s3); }

.eyebrow {
  font-family: var(--mono);
  font-size: var(--text-sm);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.muted { color: var(--ink-soft); }
.small { font-size: var(--text-sm); }

/* ---- พื้นผิว ---- */
.card {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: var(--s5);
  box-shadow: var(--shadow-card);
}

.stack > * + * { margin-top: var(--s4); }

.center-page {
  min-height: 100vh;      /* สำรองสำหรับเบราว์เซอร์เก่า */
  min-height: 100dvh;     /* Safari iOS นับ 100vh รวมแถบ URL ทำให้เนื้อหาล้นจอ */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--s4);
}

/* ---- ฟอร์ม ---- */
label {
  display: block;
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--ink-soft);
  margin-bottom: var(--s2);
}

input[type=text],
input[type=password],
input[type=number],
input[type=search],
input[type=date],
input[type=time],
textarea,
select {
  width: 100%;
  min-height: var(--tap);
  padding: var(--s3) var(--s4);
  font-family: var(--font);
  font-size: var(--text-base);  /* ต่ำกว่า 16px iOS Safari จะซูมหน้าจอเองทุกครั้งที่โฟกัส ห้ามต่ำกว่านี้เด็ดขาด */
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r);
}
textarea { resize: vertical; }
input[type=number].num,
input.money { font-family: var(--mono); font-variant-numeric: tabular-nums; }

input:focus, textarea:focus, select:focus, button:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 1px;
}

/* ---- ปุ่ม ----
   สี่แบบ เท่ากันหมด: .btn (หลัก) / .btn-quiet (รอง) / .btn-ghost (เงียบ — ปุ่มเล็กในตาราง)
   / .btn-danger (อันตราย) — ทุกแบบสมบูรณ์ในตัวเอง (มี display/min-height/padding/font/radius
   ของตัวเอง) ใช้ "คลาสเดียว" พอ ไม่ต้องคู่กับ .btn อีกต่อไป (บั๊กเดิม: .btn-quiet/.btn-danger
   เคยเป็นแค่ตัว "ทับสี" ไม่มี box model ของตัวเอง ต้องคู่กับ .btn ถึงจะมีขนาด/รูปทรง จุดไหนลืมคู่
   (เช่นปุ่ม "ยกเลิก" ใน modal, ปุ่มลัดเงินพอดีหน้าขาย) จะเหลือแค่ปุ่มเปล่าของเบราว์เซอร์ ดูเหมือน
   ไม่มีสไตล์เลย) ใช้ได้ทั้งกับ <a> และ <button> เหมือนกันทุกแบบ
   ทุกแบบมีครบทุกสถานะ: ปกติ / hover / active / focus-visible (contrast ชัด ใช้คีย์บอร์ดได้จริง)
   / disabled (ผลกับ <button> เท่านั้น — <a> ไม่มี :disabled ในสเปก CSS)
   .btn/.btn-quiet/.btn-danger สูง/padding เท่ากันเป๊ะ (--tap, 0 var(--s5)) วางเรียงกันสูงเสมอกัน
   .btn-ghost เตี้ยกว่าโดยตั้งใจ (44px) สำหรับปุ่มเล็กในตาราง แต่จัดกลางแนวเดียวกันด้วย inline-flex
   ทุกแบบตั้ง text-decoration:none ครบทุกสถานะ (ปกติ/hover/focus/visited/active) กัน <a> ที่ทำ
   หน้าที่ปุ่มมีเส้นใต้ค้างมาจาก default ของเบราว์เซอร์ — ไม่แตะ <a> ที่เป็นลิงก์เนื้อหาจริงๆ เพราะ
   ไม่มีการตั้ง text-decoration ระดับ a{} กลางไว้เลย (ปล่อยให้ขีดเส้นใต้ตามปกติ) */
.btn,
.btn-quiet,
.btn-danger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  min-height: var(--tap);
  padding: 0 var(--s5);
  font-family: var(--font);
  font-size: var(--text-base);
  font-weight: 500;
  border: 1px solid transparent;
  border-radius: var(--r);
  cursor: pointer;
  text-decoration: none;
  touch-action: manipulation; /* กันดับเบิลแท็ปแล้วซูมทั้งหน้าบน iOS/Android — ปุ่มควรตอบสนองทันที */
  transition: filter .18s ease-out, background-color .18s ease-out;
}
.btn:link, .btn:visited, .btn:hover, .btn:focus, .btn:active,
.btn-quiet:link, .btn-quiet:visited, .btn-quiet:hover, .btn-quiet:focus, .btn-quiet:active,
.btn-danger:link, .btn-danger:visited, .btn-danger:hover, .btn-danger:focus, .btn-danger:active {
  text-decoration: none;
}

.btn {
  color: var(--paper);
  background: var(--blue);
}
.btn:hover  { filter: brightness(1.1); }
.btn:active { filter: brightness(.92); }
.btn:disabled { background: var(--ink-faint); cursor: not-allowed; filter: none; }

/* ปุ่มรอง — ต้องดูเป็นปุ่มจริง (ขอบ+พื้นหลังอ่อน+เงา) ไม่ใช่แค่ตัวหนังสือลอยๆ */
.btn-quiet {
  color: var(--ink);
  background: var(--paper);
  border-color: var(--line);
  box-shadow: var(--shadow-card);
}
.btn-quiet:hover    { background: var(--wash); border-color: var(--ink-faint); filter: none; }
.btn-quiet:active   { background: var(--line); filter: none; }
.btn-quiet:disabled { color: var(--ink-faint); background: var(--wash); border-color: var(--line); box-shadow: none; cursor: not-allowed; }

/* ปุ่มอันตราย (ลบ/ทำลาย) */
.btn-danger {
  color: var(--paper);
  background: var(--neg);
}
.btn-danger:hover    { filter: brightness(1.1); }
.btn-danger:active   { filter: brightness(.92); }
.btn-danger:disabled { background: var(--ink-faint); cursor: not-allowed; filter: none; }

.btn-block { width: 100%; }

/* ปุ่มเงียบ/เล็กในตาราง — เตี้ยกว่า 3 แบบบน (44px) โดยตั้งใจ ไม่มีขอบ/พื้นหลังตอนปกติ */
.btn-ghost {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  min-height: 44px;   /* ขั้นต่ำของระบบ ห้ามลดอีก — ดูกฎขนาดสัมผัสใน CLAUDE.md */
  padding: 0 var(--s3);
  font-family: var(--font);
  font-size: var(--text-base);
  font-weight: 500;
  color: var(--ink-soft);
  background: none;
  border: 0;
  border-radius: var(--r);
  cursor: pointer;
  text-decoration: none;
  touch-action: manipulation;
  transition: background-color .18s ease-out;
}
.btn-ghost:link, .btn-ghost:visited, .btn-ghost:hover, .btn-ghost:focus, .btn-ghost:active { text-decoration: none; }
.btn-ghost:hover    { background: var(--wash); }
.btn-ghost:active   { background: var(--line); }
.btn-ghost:disabled { color: var(--ink-faint); background: none; cursor: not-allowed; }

/* focus ต้องเห็นชัดใช้คีย์บอร์ดได้ทุกแบบ ทั้ง <a> และ <button> (ของเดิมมีแค่ button:focus-visible
   เลยใช้ไม่ได้กับปุ่มที่เป็น <a> เกือบครึ่งโปรเจกต์) */
.btn:focus-visible,
.btn-quiet:focus-visible,
.btn-ghost:focus-visible,
.btn-danger:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 1px;
}

/* =========================================================================
   ปุ่มในคอลัมน์ "จัดการ" — ชุดกลางเดียว ใช้ร่วมกันทุกหน้าที่มีตารางรายการ (ดูหัวข้อ
   "กฎเรื่องปุ่มในคอลัมน์จัดการ" ใน CLAUDE.md สำหรับรายชื่อหน้าที่ใช้ครบ)

   สร้างจาก PHP ผ่าน app/helpers.php เท่านั้น (action_link()/action_form()/action_button()/
   toggle_switch_form()) — ห้ามพิมพ์ class ชุดนี้มือในไฟล์ view โดยตรง กันหน้าใหม่ในอนาคตเขียน
   ไม่ตรงกัน (ทั้งหมดต้องขนาด/ระยะห่าง/ไอคอนเหมือนกันเป๊ะทุกหน้า — ตามสเปกงาน "จัดปุ่มในคอลัมน์
   จัดการให้เป็นมาตรฐานเดียวกันทุกหน้า")
   ========================================================================= */

/* กล่องห่อปุ่มทั้งหมดในเซลล์ "จัดการ" หนึ่งแถว — แทนที่ <div class="row" style="flex-wrap:wrap">
   ที่แต่ละหน้าเคยเขียนซ้ำกันเองมาก่อน */
.row-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s2);
}
/* <form> ที่ห่อปุ่ม action-btn ตัวเดียว (ลบ/กู้คืน/ปลดล็อกฯลฯ) ต้องไม่มี margin ของ <form> เริ่มต้น
   มาดันเลย์เอาต์ของ .row-actions เพี้ยน */
.row-actions-form { margin: 0; display: contents; }

.action-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  height: 44px;   /* ยกจาก 40px เดิม — ขั้นต่ำของระบบคือ 44px รวมปุ่มเล็กในตารางด้วย */
  padding: 0 var(--s3);
  font-family: var(--font);
  font-size: var(--text-sm);   /* ยกจาก 14px เดิม — ห้ามมีตัวอักษรเล็กกว่า --text-sm ในระบบ */
  font-weight: 500;
  border: 1px solid transparent;
  border-radius: var(--r);
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  touch-action: manipulation;
  transition: filter .18s ease-out, background-color .18s ease-out, border-color .18s ease-out;
}
.action-btn:link, .action-btn:visited, .action-btn:hover, .action-btn:focus, .action-btn:active {
  text-decoration: none;
}
.action-btn svg { width: 16px; height: 16px; flex-shrink: 0; }
.action-btn:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 1px;
}

/* แก้ไข — เหลืองเสมอ ทุกหน้า (ตามสเปกงาน) ตัวหนังสือ/ไอคอนใช้ --ink เข้ม ไม่ใช่ขาว เพราะพื้นหลัง
   เหลืองสว่างเกินกว่าตัวหนังสือขาวจะอ่านออกชัด (ต่างจาก .btn/.btn-danger ที่ใช้ตัวหนังสือขาวได้
   เพราะพื้นน้ำเงิน/แดงเข้มพอ) */
.action-btn-edit {
  color: var(--ink);
  background: var(--yellow);
}
.action-btn-edit:hover    { filter: brightness(1.08); }
.action-btn-edit:active   { filter: brightness(.92); }
.action-btn-edit:disabled { background: var(--ink-faint); color: var(--paper); cursor: not-allowed; filter: none; }

/* ลบ — แดงเสมอ ทุกหน้า (ตามสเปกงาน) ใช้ --neg ตัวเดียวกับ .btn-danger เดิม (สีเดียวกันทั้งระบบ
   อยู่แล้ว ไม่ต้องเพิ่มตัวแปรใหม่) */
.action-btn-delete {
  color: var(--paper);
  background: var(--neg);
}
.action-btn-delete:hover    { filter: brightness(1.1); }
.action-btn-delete:active   { filter: brightness(.92); }
.action-btn-delete:disabled { background: var(--ink-faint); cursor: not-allowed; filter: none; }

/* สีกลาง — ดูรายละเอียด/กู้คืน/และการกระทำอื่นที่ไม่ใช่แก้ไขหรือลบ (เช่น ปรับยอด/นับสต็อก/ประวัติ/
   ปลดล็อก/ยกเลิกบิล) พื้นอ่อน ขอบบาง ไม่แย่งความสนใจจากปุ่มเหลือง/แดง */
.action-btn-neutral {
  color: var(--ink);
  background: var(--paper);
  border-color: var(--line);
  box-shadow: var(--shadow-card);
}
.action-btn-neutral:hover    { background: var(--wash); border-color: var(--ink-faint); }
.action-btn-neutral:active   { background: var(--line); }
.action-btn-neutral:disabled { color: var(--ink-faint); background: var(--wash); border-color: var(--line); box-shadow: none; cursor: not-allowed; }

/* ---- toggle switch จริง (ไม่ใช่ปุ่มข้อความ) — ใช้แค่ 3 หน้าตามสเปกงาน (สินค้า/หมวดหมู่/
   โปรโมชั่น) พฤติกรรม JS อยู่ที่ views/_layout_end.php (event delegation บน form.toggle-form) ----
   ซ่อน <input type=checkbox> จริงไว้ (โปร่งใสแต่ยังกดได้ คลุมพื้นที่ทั้งป้าย) ให้คีย์บอร์ด/
   screen reader ใช้งานได้ปกติ (เป็น checkbox จริงในทุกแง่ ไม่ใช่ <div> ปลอมที่ต้องเขียน ARIA เพิ่ม) */
.toggle-switch {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 44px;
  cursor: pointer;
  flex-shrink: 0;
}
.toggle-switch input[type="checkbox"] {
  position: absolute;
  inset: 0;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.toggle-switch .track {
  width: 44px;
  height: 26px;
  background: var(--ink-faint);
  border-radius: 999px;
  position: relative;
  transition: background-color .18s ease-out;
  pointer-events: none;
}
.toggle-switch .track::before {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: 22px;
  height: 22px;
  background: var(--paper);
  border-radius: 50%;
  box-shadow: 0 1px 3px rgba(15, 27, 45, .35);
  transition: transform .18s ease-out;
}
.toggle-switch input:checked + .track { background: var(--pos); }
.toggle-switch input:checked + .track::before { transform: translateX(18px); }
.toggle-switch input:focus-visible + .track { outline: 2px solid var(--blue); outline-offset: 2px; }
.toggle-switch input:disabled + .track { opacity: .5; }
.toggle-switch input:disabled { cursor: not-allowed; }

/* =========================================================================
   คอลัมน์ "จัดการ" แบบใหม่ — สถานะ(toggle)แยกคอลัมน์ + ปุ่มหลัก 1 อัน + เมนูสามจุด (⋮) ชุดกลาง
   เดียว ใช้ร่วมกันทุกหน้า (งาน "ยุบปุ่มในคอลัมน์จัดการเป็นปุ่มหลักหนึ่งอัน + เมนูสามจุด" — ดูหัวข้อ
   "กฎเรื่องเมนูจัดการ" ใน CLAUDE.md) สร้างจาก management_cell()/action_menu_link()/
   action_menu_form()/action_menu_button() ใน app/helpers.php เท่านั้น ห้ามพิมพ์ HTML มือ — JS
   เปิด/ปิดเมนู + นำทางเมื่อคลิกแถวอยู่ที่ views/_layout_end.php
   ========================================================================= */

/* กล่องห่อปุ่มหลัก + ปุ่มเมนู ⋮ ในเซลล์ "จัดการ" หนึ่งแถว */
.mgmt-cell {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: var(--s2);
}
.menu-trigger-wrap { position: relative; display: inline-flex; }
.menu-trigger { width: 44px; padding: 0; justify-content: center; }

/* popover เมนู ⋮ — position:fixed เสมอ (ไม่ใช่ absolute) เพราะแถวอยู่ใน .table-scroll ที่
   overflow-x:auto ครอบอยู่ (ตั้ง overflow แกนหนึ่งแล้วอีกแกนที่เหลือ "visible" จะถูกบังคับเป็น
   "auto" ไปด้วยตามสเปก CSS) popover แบบ absolute จะโดนตัดถ้าโผล่พ้นกรอบตาราง — fixed ยึดกับ
   viewport ตรงๆ จึงไม่มีทางโดนกรอบไหนตัด ตำแหน่งจริง (top/left) คำนวณจาก getBoundingClientRect()
   ของปุ่ม ⋮ ด้วย JS ตอนเปิดเสมอ (ดู positionActionMenu() ที่ _layout_end.php) ไม่ใช้ CSS anchor
   เพียวๆ เพราะต้อง clamp ไม่ให้ล้นขอบจอทั้งขวา/ล่างด้วย */
.action-menu {
  position: fixed;
  z-index: 55;
  min-width: 220px;
  max-width: min(320px, calc(100vw - var(--s4) * 2));
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: var(--shadow-pop);
  padding: var(--s2) 0;
  overflow-y: auto;
  max-height: min(400px, calc(100vh - var(--s5) * 2));
}
.action-menu[hidden] { display: none; }

/* รายการในเมนู — ไอคอน+ข้อความเสมอ (ห้ามมีแต่ไอคอน) สูงอย่างน้อย 48px ตามสเปกงาน */
.action-menu-item {
  display: flex;
  align-items: center;
  gap: var(--s3);
  width: 100%;
  min-height: 48px;
  padding: 0 var(--s4);
  border: 0;
  background: none;
  font-family: var(--font);
  font-size: var(--text-base);
  color: var(--ink);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  touch-action: manipulation;
  box-sizing: border-box;
}
.action-menu-item svg { width: 18px; height: 18px; flex-shrink: 0; color: var(--ink-faint); }
.action-menu-item:active { background: var(--wash); }
.action-menu-item:focus-visible { outline: 2px solid var(--blue); outline-offset: -2px; }
/* <form> ที่ห่อรายการเมนูฟอร์ม POST ต้องไม่มี margin/width ของตัวเองมาดันเลย์เอาต์ */
.action-menu-form { margin: 0; display: block; width: 100%; }

/* รายการอันตราย (ลบ/ยกเลิกบิล) — เส้นคั่นก่อนหน้าเสมอ (สร้างจาก management_cell() อัตโนมัติ
   เมื่อมี $dangerItems) + สีแดง อยู่ล่างสุดของเมนูเสมอ */
.action-menu-divider { height: 1px; background: var(--line); margin: var(--s2) 0; }
.action-menu-item-danger { color: var(--neg); }
.action-menu-item-danger svg { color: var(--neg); }
.action-menu-item-danger:active { background: var(--neg-dim); }

/* พื้นหลังโปร่งใสคลุมทั้งจอตอนเมนูเปิดอยู่ — ใช้ดักคลิก "นอกเมนู" แบบเดียวกันทั้งจอกว้าง/แคบ
   (จอกว้างโปร่งใสเลย จอแคบ ≤768px มืดลงจริงเพราะเมนูกลายเป็น bottom sheet ทับเนื้อหา) */
.action-menu-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 54;
  background: transparent;
}
.action-menu-backdrop.is-open { display: block; }

/* ---------------------------------------------------------------------
   จอแคบ ≤768px — เมนู ⋮ เปิดเป็น bottom sheet แทน popover เล็กๆ (กดยากและมักหลุดขอบจอบนแท็บเล็ต)
   --------------------------------------------------------------------- */
@media (max-width: 768px) {
  .action-menu {
    position: fixed !important;
    left: 0 !important;
    right: 0 !important;
    bottom: 0 !important;
    top: auto !important;
    width: 100%;
    max-width: none;
    max-height: min(70vh, calc(100vh - var(--s6)));
    border-radius: var(--r-lg) var(--r-lg) 0 0;
    padding-bottom: max(var(--s3), env(safe-area-inset-bottom, 0px));
    box-shadow: var(--shadow-pop);
  }
  .action-menu-backdrop.is-open { background: color-mix(in srgb, var(--ink) 60%, transparent); }
  .action-menu-item { font-size: var(--text-base); }
}

/* ---------------------------------------------------------------------
   ไอคอน (i) กดดูเหตุผลสั้นๆ (งาน "เปลี่ยนคำเตือนหมวดหมู่ที่ยังไม่มีสินค้าเป็นไอคอนที่กดดูได้")
   popover ยึดตำแหน่งกับปุ่มที่กด — กลไก JS เดียวกับเมนู ⋮ ด้านบนทุกประการ (position:fixed คำนวณ
   จาก getBoundingClientRect() ทุกครั้งที่เปิด, เป็น bottom sheet บนจอแคบ ≤768px, ปิดด้วยแตะนอก/Esc,
   เปิดอันใหม่ปิดอันเก่าก่อนเสมอ) แยกคลาสจาก .action-menu เพราะเนื้อหาคนละแบบ (ข้อความอธิบาย+ลิงก์
   เดียว ไม่ใช่รายการเมนูให้เลือก) — เลือกทำเป็น popover ยึดไอคอนแทนที่จะใช้ modal เล็กจากชุดกลาง
   (".modal-sm") เพราะสเปกงานต้องการ bottom sheet บนจอแคบซึ่งเป็นพฤติกรรมเฉพาะของระบบนี้ (modal
   กลางเปิดกึ่งกลางจอเสมอทุกขนาดจอ ไม่มี bottom-sheet variant) และเนื้อหาสั้นมาก ผูกกับบริบทไอคอน
   ที่กดมากกว่าจะเหมาะเป็นกล่องกลางจอที่ตัดขาดจากแถวที่กด
   --------------------------------------------------------------------- */
.info-trigger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin: 0;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 50%;
  color: var(--warn);
  cursor: pointer;
  touch-action: manipulation;
  vertical-align: middle;
}
.info-trigger svg { width: 22px; height: 22px; }
.info-trigger:active { background: var(--warn-dim); }
.info-trigger:focus-visible { outline: 2px solid var(--warn); outline-offset: 2px; }

.info-popover {
  position: fixed;
  z-index: 55;
  width: 280px;
  max-width: min(320px, calc(100vw - var(--s4) * 2));
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: var(--shadow-pop);
  padding: var(--s4);
}
.info-popover[hidden] { display: none; }
.info-popover-title { margin: 0 0 var(--s1); font-weight: 600; }
.info-popover-body { margin: 0; color: var(--ink-soft); font-size: var(--text-sm); }

/* พื้นหลังโปร่งใสคลุมทั้งจอตอน popover เปิดอยู่ — ดักคลิก "นอก popover" แบบเดียวกับเมนู ⋮
   (จอกว้างโปร่งใสเลย จอแคบ ≤768px มืดลงจริงเพราะ popover กลายเป็น bottom sheet ทับเนื้อหา) */
.info-popover-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 54;
  background: transparent;
}
.info-popover-backdrop.is-open { display: block; }

@media (max-width: 768px) {
  .info-popover {
    position: fixed !important;
    left: 0 !important;
    right: 0 !important;
    bottom: 0 !important;
    top: auto !important;
    width: 100%;
    max-width: none;
    border-radius: var(--r-lg) var(--r-lg) 0 0;
    padding-bottom: max(var(--s4), env(safe-area-inset-bottom, 0px));
    box-shadow: var(--shadow-pop);
  }
  .info-popover-backdrop.is-open { background: color-mix(in srgb, var(--ink) 60%, transparent); }
}

/* แถวที่คลิกแล้วนำทางไปหน้าอื่น (แทนที่กลไก "คลิกแถวขยายดูคอลัมน์รอง" เดิมบนหน้าที่มีปุ่มจัดการ
   แบบใหม่นี้ — หน้าปลายทางโชว์ข้อมูลครบกว่าที่ขยายแถวเคยโชว์อยู่แล้ว) ต้องไม่ทริกเกอร์ถ้าคลิกโดน
   ปุ่ม/ลิงก์/ช่องกรอกในแถว (toggle, ปุ่มหลัก, เมนู ⋮) — ดู JS ที่ _layout_end.php */
tbody tr.row-nav { cursor: pointer; touch-action: manipulation; }
tbody tr.row-nav:active { background: var(--line); }

/* ข้อความสำเนาสถานะเล็กๆ ใต้ toggle switch เมื่อสถานะมีมิติมากกว่า on/off ธรรมดา (เช่น โปรโมชั่นที่
   เปิดใช้งานไว้แต่ยังไม่ถึงเวลา, สต็อกที่ติดลบ/ใกล้หมด) — ไม่ได้แทนที่ toggle switch เอง แค่เสริม
   ข้อมูลที่ toggle เพียวๆ (checked/unchecked) สื่อไม่ได้ */
.toggle-note { display: block; margin-top: var(--s1); font-size: var(--text-sm); color: var(--ink-soft); white-space: nowrap; }
.toggle-note-warn { color: var(--warn); }
.toggle-note-bad  { color: var(--neg); }

/* ข้อความสำหรับ screen reader เท่านั้น — ซ่อนจากตาแต่ยังอ่านได้ (ใช้กับป้ายสถานะของ
   .toggle-switch ที่ไม่มีตัวหนังสือให้เห็นตรงๆ เพราะสถานะสื่อผ่านตำแหน่ง/สีของสวิตช์อยู่แล้ว) */
.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;
}

/* ---- ข้อความแจ้ง ---- */
.notice {
  padding: var(--s3) var(--s4);
  border-radius: var(--r);
  font-size: var(--text-sm);
  border: 1px solid transparent;
}
.notice-bad  { background: var(--neg-dim);  border-color: var(--neg-border);  color: var(--neg); }
.notice-good { background: var(--pos-dim);  border-color: var(--pos-border);  color: var(--pos); }
.notice-info { background: var(--blue-dim); border-color: var(--blue-border); color: var(--blue); }
.notice-warn { background: var(--warn-dim); border-color: var(--warn-border); color: var(--warn); }

/* ---- ป้ายสถานะ ---- ใช้ร่วมกันทุกหน้า (สินค้า/หมวดหมู่/สต็อก/ร้านค้า/ผู้ใช้) เดิมแต่ละหน้า
   ประกาศซ้ำเองในบล็อค <style> ของตัวเอง ค่าเริ่มต้น (ไม่ใส่คลาสสี) ไม่ตรงกันด้วย (บางหน้าไม่ใส่สี
   เลย บางหน้าใส่เทาจาง) รวมเป็นจุดเดียว ดีฟอลต์ = เทาจาง (ใช้กับสถานะ "ปิดใช้งาน" ที่ไม่ได้เป็น
   ทั้งดี/แย่/เตือนชัดเจน) */
.status-badge {
  display: inline-block;
  padding: 2px var(--s2);
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--ink-soft);
  background: var(--wash);
  border: 1px solid var(--line);
  border-radius: var(--r);
  white-space: nowrap;
}
.status-badge-good { color: var(--pos);  background: var(--pos-dim);  border-color: var(--pos-border); }
.status-badge-bad  { color: var(--neg);  background: var(--neg-dim);  border-color: var(--neg-border); }
.status-badge-warn { color: var(--warn); background: var(--warn-dim); border-color: var(--warn-border); }
.status-badge-info { color: var(--blue); background: var(--blue-dim); border-color: var(--blue-border); }

/* ---- ตัวช่วยตาราง/รายการ ---- ใช้ร่วมกันทุกหน้าที่มีตาราง (เดิมแต่ละหน้าประกาศซ้ำเอง) */
/* เลื่อนแนวนอนได้เมื่อล้น พร้อม "เงาบอกว่ายังมีคอลัมน์ต่อ" ทั้งสองข้าง — เทคนิค CSS ล้วนๆ ไม่ใช้ JS
   (background 4 ชั้นซ้อนกัน: สองชั้นแรกเป็นพื้นสีทึบระยะสั้นๆ ที่ขอบ เลื่อนไปกับเนื้อหาจริงเพราะตั้ง
   background-attachment:local — ชั้นสองหลังเป็นเงาจริงที่ยึดกับกรอบ .table-scroll เอง
   (attachment:scroll ค่าเริ่มต้น) เงาจะโผล่ให้เห็นก็ต่อเมื่อพื้นทึบชั้นแรกเลื่อนหลบไปแล้วเท่านั้น
   เพราะพื้นทึบวาดทับเงาไว้พอดีตอนเนื้อหายังอยู่ชิดขอบ — ไม่มีเงาโผล่ถ้าตารางไม่ล้นจริง เพราะไม่มี
   อะไรให้ "เลื่อนหลบ" พื้นทึบไปเจอเงาได้ตั้งแต่แรก) ต้องมี position:relative ให้ position:sticky
   ของ thead th/.col-sticky ด้านล่างยึด containing block ที่ถูกต้อง */
.table-scroll {
  position: relative;
  overflow-x: auto;
  background:
    linear-gradient(to right, var(--paper) 50%, transparent) 0 0,
    linear-gradient(to left,  var(--paper) 50%, transparent) 100% 0,
    linear-gradient(to right, rgba(15, 27, 45, .14), transparent) 0 0,
    linear-gradient(to left,  rgba(15, 27, 45, .14), transparent) 100% 0;
  background-repeat: no-repeat;
  background-color: var(--paper);
  background-size: 24px 100%, 24px 100%, 10px 100%, 10px 100%;
  background-attachment: local, local, scroll, scroll;
}
.hidden-row   { opacity: .5; }
.row-link     { cursor: pointer; touch-action: manipulation; }
.row-link:hover  { background: var(--wash); }
.row-link:active { background: var(--line); }
/* ตัวเลขเงินของบิลที่ยกเลิก (views/orders.php) — ขีดฆ่าบอกว่า "ไม่นับ" โดยไม่ทำทั้งแถวจาง
   (แถวยกเลิกใช้ป้ายสถานะสีแดงแยกแทน ไม่ใช้ .hidden-row อีกต่อไป — ดู CLAUDE.md
   หัวข้อ "กฎเรื่องยกเลิกบิล") */
.amount-voided { text-decoration: line-through; color: var(--ink-soft); }

/* ---- แถวช่องค้นหา/filter ---- ใช้ร่วมกันทุกหน้าที่มีฟอร์มค้นหา/กรอง (เดิมแต่ละหน้าตั้งชื่อคลาส
   เอง เช่น .menu-filter-row/.orders-search-row/.purchases-search-row เนื้อ CSS เหมือนกันเป๊ะ) */
.filter-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3);
  align-items: flex-end;
}
.filter-row > div { min-width: 160px; }
.filter-row .grow { flex: 1; min-width: 200px; }

/* ---- ช่องค้นหา ---- ไอคอนแว่นขยาย SVG inline วางซ้อนในช่อง ใช้ร่วมกับ input[type=search]
   ที่มีความสูง/ขอบ/รัศมีเดียวกับช่องกรอกอื่นอยู่แล้ว (ดูกฎรวมของ input ด้านบน) */
.search-box { position: relative; }
.search-box .search-icon {
  position: absolute;
  top: 50%;
  left: var(--s3);
  transform: translateY(-50%);
  width: 18px;
  height: 18px;
  color: var(--ink-faint);
  pointer-events: none;
}
.search-box input {
  width: 100%;
  padding-left: calc(var(--s3) * 2 + 18px);
}

/* ---- โมดัล ---- ระบบกลางเดียวทั้งระบบ (งาน "ออกแบบ pop-up และ modal ใหม่ทั้งระบบ" — เดิมมีสองชุด
   ซ้ำกัน: ชุดนี้ที่ใช้ร่วมกับ stock.php/orders.php/purchase_new.php กับอีกชุดที่ pos.php ประกาศ
   ทับเองใน <style> ของตัวเอง (ต่างกันตรงที่ pos.php มี max-height/overflow-y ที่ชุดนี้ไม่เคยมี —
   ทำให้ modal ของ stock.php/orders.php/purchase_new.php ล้นจอเงียบๆ บนจอเตี้ยมาตลอด) รอบนี้รวม
   เป็นจุดเดียว เอาโครงที่ถูกต้อง (มี max-height+เลื่อนเนื้อหาในตัว) มาเป็นค่ากลาง แล้วลบของซ้ำใน
   pos.php ทิ้ง — ห้ามมีหน้าไหนประกาศ .modal-backdrop/.modal/.modal-header/.modal-body/.modal-footer
   ทับเองอีกต่อไป

   สามขนาดตายตัวตามสเปกงาน: เล็ก (.modal-sm — ยืนยันการกระทำ เช่นลบ/ปิดใช้งาน แค่ข้อความ+สองปุ่ม),
   กลาง (ไม่ต้องมีคลาสเพิ่ม — ค่าเริ่มต้น ฟอร์มสั้น เช่นปรับสต็อก/เพิ่มร้านค้า/เลือกตัวเลือกสินค้า/
   ยกเลิกบิล), ใหญ่ (.modal-lg — เนื้อหายาว เช่นรับเงิน/ดูรายละเอียด) ทุกขนาดมีโครงเดียวกัน:
   หัว/ท้ายอยู่นิ่งเสมอ (flex-shrink:0) เนื้อหาตรงกลางเลื่อนเองถ้าสูงเกินจอ (overflow-y:auto,
   min-height:0 — จำเป็นเพราะ flex item ปกติไม่ยอมหดเล็กกว่าเนื้อหาตัวเองถ้าไม่ตั้งค่านี้)

   ล็อกการเลื่อนของหน้าเบื้องหลังเวลามี modal เปิดอยู่ผ่านคลาส html.modal-open (เติม/ถอดด้วย JS ที่
   views/_layout_end.php ทุกครั้งที่ .modal-backdrop ไหนในหน้าเปลี่ยนสถานะ hidden — ดูคอมเมนต์ที่นั่น)
   .content คือ scroll container จริงของทั้งหน้า (ดูกฎเรื่อง .shell/.content ด้านล่าง) ต้องล็อกคู่กับ
   body/html เสมอ ไม่งั้นเลื่อนในกล่อง modal จะเลื่อนเนื้อหาหลังฉากตามไปด้วย สับสนมาก */
/* หมายเหตุ: ไม่ใส่ opacity/transform เริ่มต้นเป็น 0 แล้วค่อยทรานสิชันด้วยคลาส .is-open ที่ JS เติม
   ให้ทีหลัง (เคยลองแบบนั้นแล้วพบความเสี่ยง: ถ้า JS ยังไม่ทันรันหรือพลาดจุดใดจุดหนึ่ง modal จะค้าง
   โปร่งใส 100% มองไม่เห็นแต่ยังกดโดนอยู่ — อันตรายกว่าไม่มีอนิเมชันเลย) โชว์/ซ่อนทันทีด้วย [hidden]
   ล้วนๆ เหมือนเดิมทั้งระบบ ปลอดภัยกว่าและยังอยู่ในงบ "ไม่เกิน 200ms" ของสเปกงานอยู่ดี (0ms ก็ไม่เกิน) */
.modal-backdrop {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--s4);
  background: color-mix(in srgb, var(--ink) 60%, transparent);
  z-index: 50;
}
.modal-backdrop[hidden] { display: none; }
html.modal-open,
html.modal-open body { overflow: hidden; }
html.modal-open .content { overflow: hidden; }
.modal {
  width: 100%;
  max-width: 480px; /* ขนาด "กลาง" — ค่าเริ่มต้นเพราะใช้บ่อยที่สุด */
  max-height: calc(100dvh - var(--s4) * 2);
  background: var(--paper);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-pop);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.modal-sm { max-width: 360px; }
.modal-lg { max-width: 640px; }
.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  padding: var(--s4) var(--s5);
  border-bottom: 1px solid var(--line);
  flex-shrink: 0;
}
.modal-header h2 { font-size: var(--text-lg); }
.modal-body {
  padding: var(--s5);
  overflow-y: auto;
  min-height: 0;
  -webkit-overflow-scrolling: touch;
}
/* ปุ่มใน footer เรียงตามลำดับที่เขียนใน HTML ตรงๆ (ไม่ใช้ CSS order สลับ) — ต้องเขียนปุ่ม "ยกเลิก"
   ก่อน แล้วค่อยปุ่ม "ยืนยัน" ทีหลังเสมอ ให้ตรงกันทั้งที่ตาเห็น (ยกเลิกซ้าย ยืนยันขวา) และลำดับคีย์บอร์ด
   Tab (จะได้ตรงกับที่ตาเห็นด้วย ไม่ใช่กระโดดสลับ) */
.modal-footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s3);
  padding: var(--s4) var(--s5);
  border-top: 1px solid var(--line);
  flex-shrink: 0;
}
/* ตัวปรับ footer ให้เรียงเป็นแนวตั้งแทน — ใช้กับ modal ที่มีแถบสรุป/qty-stepper ก่อนปุ่มหลักแบบเต็ม
   ความกว้าง (modal เลือกตัวเลือกสินค้า/รับเงิน หน้าขาย — ดู .modal-footer-row/.modal-missing-hint
   ใน views/pos.php ที่ยังเป็นสไตล์เฉพาะหน้านั้นต่อไป เพราะเป็นเนื้อหาเฉพาะ ไม่ใช่โครง modal กลาง) */
.modal-footer-stack { flex-direction: column; align-items: stretch; }

/* ---------------------------------------------------------------------
   ม้วนใบเสร็จ — องค์ประกอบประจำตัวของระบบนี้
   ใช้กับตะกร้าสินค้าและใบสรุปปิดรอบ ขอบหยักเลียนแบบรอยปรุกระดาษ
   --------------------------------------------------------------------- */
.tape {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  padding: var(--s5);
  box-shadow: var(--shadow-card);
}
.tape-edge {
  height: 10px;
  background:
    radial-gradient(circle at 6px 0, transparent 5px, var(--paper) 5px) repeat-x;
  background-size: 12px 10px;
  border: 1px solid var(--line);
  border-top: 0;
  border-radius: 0 0 var(--r-lg) var(--r-lg);
  filter: drop-shadow(0 1px 0 var(--line));
  box-shadow: var(--shadow-card);
}

.tape-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s4);
  padding: var(--s2) 0;
}
.tape-rule { border-top: 1px dashed var(--line); margin: var(--s3) 0; }
.tape-total { font-size: var(--text-lg); font-weight: 600; padding-top: var(--s2); }

/* =========================================================================
   ตาราง — คลาสกลางชุดเดียว ใช้ร่วมกันทุกหน้าที่มีตาราง (ดูหัวข้อ "กฎเรื่องตารางกลาง" ใน
   CLAUDE.md) งาน "จัดตารางทุกหน้าให้ใช้งานบนแท็บเล็ตได้" รอบที่ 2/2 — ตั้งใจแก้ที่กฎฐาน
   (selector เปล่า `table`/`th`/`td`) แทนการสร้างคลาสใหม่ให้ต้องคอยแปะทุกหน้า เพราะหน้าใหม่ใน
   อนาคตที่สร้าง <table> ธรรมดาจะได้พฤติกรรมนี้ไปเลยอัตโนมัติ ไม่มีทางลืมแปะคลาส (กันปัญหาที่เคย
   เกิดกับปุ่มมาสองรอบ — แต่ละหน้าค่อยๆ เพี้ยนกันเองเพราะแก้ทีละหน้า)
   ========================================================================= */
table { width: 100%; border-collapse: collapse; }

/* หัวตารางตรึงไว้ตอนเลื่อนลง (position:sticky ยึดกับ .content ที่ overflow-y:auto อยู่แล้ว
   จาก _layout_start.php ไม่ต้องมี wrapper ความสูงคงที่พิเศษ) ต้องมีพื้นหลังทึบกันเนื้อหาโผล่ทะลุ
   ตอนเลื่อน — ใช้ z-index สูงกว่า .col-sticky ของ tbody (2) เพราะหัวมุมบนซ้ายต้องอยู่บนสุดเสมอ
   เวลาตรึงพร้อมกันทั้งแนวตั้งและแนวนอน */
thead th {
  position: sticky;
  top: 0;
  z-index: 3;
  background: var(--paper);
}
th {
  font-family: var(--mono);
  font-size: var(--text-sm);
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-faint);
  text-align: left;
  padding: var(--s2) var(--s3);
  border-bottom: 1px solid var(--line);
  white-space: nowrap; /* หัวตารางไม่ตกบรรทัด กันความสูงแถวหัวไม่เท่ากันข้ามหน้า */
}
td {
  padding: var(--s3);
  border-bottom: 1px solid var(--line);
  min-height: 48px; /* ขั้นต่ำของระบบให้แตะได้สบาย — td รองรับ height/min-height ตรงๆ ตามสเปก CSS */
}
th.right, td.right { text-align: right; } /* ตัวเลขชิดขวา ตัวอักษรชิดซ้าย (ค่าเริ่มต้น) */
tbody tr:last-child td { border-bottom: 0; }
tbody tr:nth-child(even) { background: var(--wash); } /* แถวสลับสีอ่อนๆ ไล่สายตาง่าย */

/* คอลัมน์ระบุแถว (ชื่อสินค้า/เลขที่บิล ฯลฯ) ตรึงซ้ายตอนเลื่อนแนวนอนใน .table-scroll — ต้องมีทั้ง
   th และ td ของคอลัมน์เดียวกันแปะคลาสนี้คู่กันเสมอ background:inherit ให้ซึมสีจากแถวพ่อ (zebra
   ด้านบน) ไม่ให้ดูเป็นแท่งสีทึบตัดผ่านลายทาง — thead ต้อง z-index สูงกว่า tbody เพราะเป็นมุมบน
   ซ้ายที่ตรึงพร้อมกันสองทิศทาง (แนวตั้ง+แนวนอน) */
.col-sticky {
  position: sticky;
  left: 0;
  z-index: 1;
  background: inherit;
}
thead th.col-sticky { z-index: 3; background: var(--paper); }

/* คอลัมน์รอง — ซ่อนที่ 768-1023px ลงมา (แท็บเล็ตแนวตั้ง/มือถือ เหมือน breakpoint เมนูซ้ายยุบ
   ไอคอนจากรอบที่ 1) กดที่แถว (.row-expandable) เพื่อขยายดูค่าที่ซ่อนแทน (ดู .row-detail ด้านล่าง
   และ JS ใน views/_layout_end.php) — คอลัมน์ "จัดการ" ห้ามแปะคลาสนี้เด็ดขาดไม่ว่าหน้าไหน */
@media (max-width: 1023px) {
  .col-secondary { display: none; }
}

/* แถวที่กดขยายได้ (มีคอลัมน์รองถูกซ่อนอยู่) — cursor แค่ตอนแคบพอที่คอลัมน์รองถูกซ่อนจริง กว้างกว่า
   นั้นไม่ต้องกดอะไร ข้อมูลอยู่ในตารางอยู่แล้ว */
@media (max-width: 1023px) {
  tbody tr.row-expandable { cursor: pointer; touch-action: manipulation; }
  tbody tr.row-expandable:active { background: var(--line); }
}
/* แถวรายละเอียด (สร้างจาก table_row_detail() ใน app/helpers.php) ซ่อนไว้เป็นค่าเริ่มต้นเสมอ
   (attribute hidden) โผล่เมื่อกดแถวข้างบนเท่านั้น — จอกว้าง (≥1024px) ไม่ต้องมีเลยเพราะคอลัมน์รอง
   โชว์ในตารางปกติอยู่แล้ว ซ่อนไว้เสมอไม่ว่า JS จะเคยสั่ง hidden=false ไว้ตอนจอแคบก็ตาม (กันค้าง
   เปิดค้างตอนพลิกจอ/ปรับขนาดหน้าต่าง) */
.row-detail > td { background: var(--wash); padding: var(--s4); }
.row-detail-list { display: flex; flex-direction: column; gap: var(--s2); margin: 0; }
.row-detail-item { display: flex; justify-content: space-between; align-items: baseline; gap: var(--s4); }
.row-detail-item dt { margin: 0; color: var(--ink-soft); font-size: var(--text-sm); flex-shrink: 0; }
.row-detail-item dd { margin: 0; text-align: right; }
@media (min-width: 1024px) {
  .row-detail { display: none !important; }
}

/* ตารางว่าง — มีเหตุผล+ทางไปต่อเสมอ ไม่ใช่ข้อความลอยๆ (ดู table_empty_row() ใน app/helpers.php) */
.table-empty { padding: var(--s6) var(--s5); text-align: center; }
.table-empty p { margin: 0 0 var(--s3); color: var(--ink-soft); }

/* ---- ตัวช่วยเล็กๆ ---- */
.row { display: flex; gap: var(--s3); align-items: center; }
.row-between { display: flex; gap: var(--s3); align-items: center; justify-content: space-between; }
.grow { flex: 1; }
.mt-2 { margin-top: var(--s2); }
.mt-3 { margin-top: var(--s3); }
.mt-4 { margin-top: var(--s4); }
.mt-5 { margin-top: var(--s5); }

/* ---------------------------------------------------------------------
   หน้าเอกสารพิมพ์ได้ คอลัมน์เดียวแคบ — ใช้ร่วมกัน (ใบเสร็จ/รายงานประจำวัน
   เดิมแต่ละหน้าประกาศซ้ำเองคนละชื่อ (.receipt-page-wrap/.report-daily-wrap)
   เนื้อ CSS เหมือนกันเป๊ะ)
   --------------------------------------------------------------------- */
.print-page {
  max-width: 480px;
  margin: 0 auto;
  padding: var(--s5) var(--s4) var(--s7);
}
.print-page-actions {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s3);
  margin-bottom: var(--s5);
}
@media print {
  .print-page { max-width: 100%; margin: 0; padding: 0; }
}

/* ---------------------------------------------------------------------
   Dashboard — หน้าสรุปยอดขายของเจ้าของร้าน
   --------------------------------------------------------------------- */
.dash {
  max-width: 1100px;
  margin: 0 auto;
  padding: var(--s5) var(--s4) var(--s7);
}

.dash-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--s4);
}
@media (max-width: 900px) {
  .dash-grid { grid-template-columns: repeat(2, 1fr); }
}

/* จอกว้างพอ (แท็บเล็ตขึ้นไป) ไม่ต้องอัดแน่นเหมือนมือถือ — ขยับระยะห่างขึ้นหนึ่งขั้นในสเกลเดิม
   (--s1..--s7) ทั้งขอบหน้าและช่องไฟระหว่างการ์ดในกริด ไม่ใช่ตัวเลขใหม่ */
@media (min-width: 768px) {
  .dash      { padding: var(--s6) var(--s6) var(--s7); }
  .dash-grid { gap: var(--s5); }
}

/* กล่องบังคับความสูงของ canvas ที่ Chart.js วาด เพราะ Chart.js responsive
   ต้องมี parent ที่กำหนดความสูงไว้แน่นอน ไม่งั้นกราฟจะสูงขึ้นเรื่อยๆ */
.chart-box {
  position: relative;
  height: 280px;
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* ---------------------------------------------------------------------
   แถบเมนูซ้าย (sidebar) — ใช้ร่วมกันทุกหน้าผ่าน _layout_start.php/_layout_end.php
   พับ/กางได้ จำสถานะใน localStorage คีย์ grape-pos:nav
   --------------------------------------------------------------------- */
.shell {
  display: flex;
  height: 100vh;      /* สำรองสำหรับเบราว์เซอร์เก่า */
  height: 100dvh;     /* Safari iOS นับ 100vh รวมแถบ URL ทำให้เนื้อหาล้นจอ */
  overflow: hidden;
}
.sidebar {
  width: 240px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  background: var(--paper);
  border-right: 1px solid var(--line);
  color: var(--ink);
  overflow-x: hidden;
  overflow-y: auto;
  padding-left: env(safe-area-inset-left, 0px);
  transition: width .15s ease;
}
html.nav-collapsed .sidebar { width: 64px; }

.nav-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 100%;
  min-height: var(--tap);
  padding: 0;
  color: var(--ink-soft);
  background: none;
  border: 0;
  border-bottom: 1px solid var(--line);
  cursor: pointer;
  touch-action: manipulation;
}
.nav-toggle:hover  { background: var(--wash); }
.nav-toggle:active { background: var(--line); }   /* แท็บเล็ต/มือถือไม่มี hover — ต้องเห็น :active ชัด */
.nav-toggle svg { transition: transform .15s ease; }
html.nav-collapsed .nav-toggle svg { transform: scaleX(-1); }

.nav-items { flex: 1; padding: var(--s3) 0; }

.nav-item {
  display: flex;
  align-items: center;
  gap: var(--s3);
  width: 100%;
  min-height: var(--tap);
  padding: 0 var(--s5);
  font-family: var(--font);
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
  background: none;
  border: 0;
  text-align: left;
  cursor: pointer;
  touch-action: manipulation;
}
.nav-item svg { flex-shrink: 0; }
.nav-item:hover  { background: var(--wash); }
.nav-item:active { background: var(--line); }
.nav-item.active { background: var(--blue); color: var(--paper); }

html.nav-collapsed .nav-item { justify-content: center; padding: 0; }
.nav-label { overflow: hidden; text-overflow: ellipsis; }
html.nav-collapsed .nav-label { display: none; }

.nav-footer { flex-shrink: 0; border-top: 1px solid var(--line); padding: var(--s2) 0; }
.nav-user {
  display: flex;
  align-items: center;
  gap: var(--s3);
  min-height: var(--tap);
  padding: 0 var(--s5);
  color: var(--ink-soft);
  font-size: var(--text-sm);
  white-space: nowrap;
}
.nav-user svg { flex-shrink: 0; }
html.nav-collapsed .nav-user { justify-content: center; padding: 0; }
.nav-lock-form { margin: 0; }
.nav-logout-form { margin: 0; }
.nav-logout-btn { font-family: var(--font); }

.content {
  flex: 1;
  min-width: 0;
  overflow-y: auto;
}
.content-flush { overflow: hidden; }

/* ปุ่มเปิดแถบเมนู + พื้นหลังมืดตอนแถบเมนูกลายเป็นชั้นทับ (มือถือ < 768px เท่านั้น) —
   ตำแหน่งมุมบนซ้ายตั้งใจ (เอื้อมถึงง่ายด้วยนิ้วโป้งตอนถือด้วยมือเดียวแนวตั้ง ตรงกับตำแหน่งปุ่มเมนู
   มาตรฐานของแอปส่วนใหญ่ที่ผู้ใช้คุ้นเคยอยู่แล้ว — breakpoint นี้ (<768px) แคบกว่า iPad ทุกรุ่นเสมอ
   จึงเป็นกรณีมือถือจริงๆ ที่ถือด้วยมือเดียวได้ ไม่ใช่แท็บเล็ต) ขนาด 44x44px ตรงเกณฑ์ขั้นต่ำพอดี */
.mobile-nav-btn {
  display: none;
  position: fixed;
  top: var(--s3);
  left: var(--s3);
  z-index: 47;
  width: 44px;
  height: 44px;
  align-items: center;
  justify-content: center;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: var(--shadow-card);
  cursor: pointer;
  touch-action: manipulation;
}
.mobile-nav-btn:active { background: var(--wash); }
html.mobile-nav-open .mobile-nav-btn { display: none; }
.sidebar-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 45;
  background: color-mix(in srgb, var(--ink) 60%, transparent);
}

/* =========================================================================
   ตารางอ้างอิง breakpoint ของระบบนี้ (งาน "ปรับรากฐานการแสดงผลให้รองรับแท็บเล็ต" รอบที่ 1) —
   ครอบคลุมอุปกรณ์จริงที่พบบ่อย ทดสอบแล้วทั้งสองแนว (พอร์เทรต/แลนด์สเคป) ทุกขนาด:
     < 768px   : มือถือ — เมนูซ้ายกลายเป็นลิ้นชักทับหน้าจอ เปิดด้วยปุ่มแฮมเบอร์เกอร์
     768–1023px: แท็บเล็ตแนวตั้ง (iPad พอร์เทรต 768px, Android ~800px) — เมนูซ้ายยุบเหลือแค่
                 ไอคอน (64px) ถาวร ไม่มีปุ่มกางคืน (กันเมนูเต็มกินพื้นที่คอนเทนต์ที่แคบอยู่แล้ว)
     ≥ 1024px  : แท็บเล็ตแนวนอน/จอกว้าง (iPad แลนด์สเคป 1024px, iPad Pro 1194px/1366px,
                 Android แลนด์สเคป ~1280px) — เมนูซ้ายแสดงเต็มตามค่าเริ่มต้น (ไม่ต้องมี media query
                 เพิ่ม เพราะเป็นพฤติกรรมพื้นฐานของ .sidebar ที่ยังไม่ถูก breakpoint ไหนทับ)
   ทั้งสามช่วงเลือกจาก max-width เท่านั้น (ไม่สนใจ orientation query) เพราะสิ่งที่ตัดสินใจจริงคือ
   "ความกว้างที่เหลือให้แสดงเนื้อหา" ไม่ใช่การหมุนจอ — ทดสอบแล้วว่า iPad ในแนวไหนก็ตามที่ความกว้าง
   ตกช่วงเดียวกัน จะได้ผลลัพธ์เดียวกันเป๊ะ (ดูผลทดสอบจริงท้ายงาน)
   ========================================================================= */

/* ---------------------------------------------------------------------
   Breakpoint 768–1023px — แท็บเล็ตแนวตั้ง: บังคับแถบเมนูพับเสมอ ไม่ให้กางเอง
   วางไว้ท้ายไฟล์เพื่อให้ชนะกฎฐานด้านบนตามลำดับ cascade (specificity เท่ากัน)
   --------------------------------------------------------------------- */
@media (max-width: 1023px) {
  .sidebar { width: 64px; }
  .nav-toggle { display: none; }
  .nav-item { justify-content: center; padding: 0; }
  .nav-user { justify-content: center; padding: 0; }
  .nav-label { display: none; }
}

/* ---------------------------------------------------------------------
   Breakpoint < 768px — มือถือ: แถบเมนูกลายเป็นชั้นทับเลื่อนจากซ้าย
   (คืนความกว้าง/label เต็มเพราะตอนนี้คือลิ้นชักที่ตั้งใจดึงออกมาดู ไม่ใช่รางแคบถาวร)
   --------------------------------------------------------------------- */
@media (max-width: 767px) {
  .mobile-nav-btn { display: flex; }
  .sidebar {
    width: 240px;
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    z-index: 46;
    transform: translateX(-100%);
    transition: transform .2s ease;
    box-shadow: var(--shadow-pop);
  }
  .nav-item { justify-content: flex-start; padding: 0 var(--s5); }
  .nav-user { justify-content: flex-start; padding: 0 var(--s5); }
  .nav-label { display: block; }
  html.mobile-nav-open .sidebar { transform: translateX(0); }
  html.mobile-nav-open .sidebar-backdrop { display: block; }
}

@media print {
  .sidebar { display: none !important; }
  .shell   { display: block !important; height: auto !important; overflow: visible !important; }
  .content { overflow: visible !important; height: auto !important; }
  .mobile-nav-btn, .sidebar-backdrop { display: none !important; }
  /* เงาห้ามหลุดไปกับงานพิมพ์เด็ดขาด — เครื่องพิมพ์ความร้อนจะเห็นเป็นแถบดำทึบ */
  .card, .tape, .tape-edge, .btn-quiet { box-shadow: none !important; }
}

/* ---- หน้าตั้งค่าร้าน (/settings/shop) — ฟอร์มซ้าย ตัวอย่างหัวใบเสร็จสดๆ ขวา (JS ล้วนๆ
   ไม่มี library — ดู views/settings_shop.php) ใช้ .receipt-frame/.tape/.receipt-tape ตัวเดียว
   กับหน้าใบเสร็จจริง (views/receipt.php) ให้หน้าตาตัวอย่างตรงกับของจริงเป๊ะ ----*/
.settings-shop-layout {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--s5);
}
.settings-shop-layout > form {
  flex: 1 1 420px;
  max-width: 560px;
}
.settings-shop-preview {
  flex: 0 0 auto;
  position: sticky;
  top: var(--s4);
}
