/* CSS กลางของทั้งโปรเจกต์ — โหลดต่อจาก bootstrap.min.css เสมอ
   ไฟล์นี้ทำสามอย่างเท่านั้น
   1) ประกาศฟอนต์ไทยที่เก็บไว้ในโปรเจกต์ (ไม่มี CDN)
   2) ตั้งโทนรวมของระบบผ่านตัวแปรของ Bootstrap 5.3 เอง
   3) แต่งชิ้นส่วนโครงที่ Bootstrap ไม่มีให้ตรง ๆ — แถบเมนูซ้ายสีกรมท่า, navbar,
      แผ่นเนื้อหา, หน้าเข้าสู่ระบบ
   เนื้อหาข้างในแต่ละหน้ายังใช้คลาสของ Bootstrap ตรง ๆ ไม่ประดิษฐ์ระบบ class ของตัวเอง

   แนวทาง: Data-Dense Dashboard + พาเลต B2B navy — ตัวหนังสือแน่น แถวตารางเตี้ย
   สีเข้มคุมสายตาอยู่ที่เมนูซ้าย พื้นที่ทำงานสว่างเพื่ออ่านข้อมูลนาน ๆ ได้ */

/* ---------- ฟอนต์ (ไฟล์อยู่ใน framework/assets/fonts/ ไม่ได้ดึงจาก Google) ---------- */

@font-face {
  font-family: 'IBM Plex Sans Thai';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/ibm-plex-sans-thai-400-thai.woff2') format('woff2');
  unicode-range: U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC;
}
@font-face {
  font-family: 'IBM Plex Sans Thai';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/ibm-plex-sans-thai-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Sans Thai';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/ibm-plex-sans-thai-500-thai.woff2') format('woff2');
  unicode-range: U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC;
}
@font-face {
  font-family: 'IBM Plex Sans Thai';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/ibm-plex-sans-thai-500-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Sans Thai';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/ibm-plex-sans-thai-600-thai.woff2') format('woff2');
  unicode-range: U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC;
}
@font-face {
  font-family: 'IBM Plex Sans Thai';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/ibm-plex-sans-thai-600-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Sans Thai';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('fonts/ibm-plex-sans-thai-700-thai.woff2') format('woff2');
  unicode-range: U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC;
}
@font-face {
  font-family: 'IBM Plex Sans Thai';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('fonts/ibm-plex-sans-thai-700-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FEFF, U+FFFD;
}

/* ---------- โทนรวม ---------- */

:root {
  /* พาเลต B2B navy — กรมท่าเป็นสีคุม ฟ้าเข้มเป็นสีของการกระทำ */
  --app-navy: #0f172a;
  --app-navy-soft: #1e293b;
  --app-slate: #334155;
  --app-accent: #0369a1;
  --app-accent-hi: #38bdf8;
  /* พื้นฟ้าจาง ๆ สำหรับกล่องที่ต้องเด่นกว่ากล่องข้างเคียง — เข้มพอให้เห็นว่าต่างจาก
     --app-subtle ที่อยู่ข้าง ๆ แต่ยังอ่อนพอให้ตัวหนังสือสีเข้มอ่านได้เหมือนเดิม */
  --app-accent-soft: #e2f0fb;
  /* พื้นรองต้องเทาพอที่แผ่นเนื้อหาสีขาวจะลอยขึ้นมาให้เห็นว่าตรงไหนคือที่ทำงาน
     ของเดิม #f8fafc ต่างจากขาวไม่ถึง 3% ทั้งจอเลยกลายเป็นขาวแผ่นเดียว มองนาน ๆ ไม่ไหว */
  --app-shell-bg: #e8edf4;
  --app-surface: #ffffff;
  /* พื้นเทาอ่อนสำหรับของที่อยู่ "ใน" แผ่นขาว — หัวตาราง กล่องยอดเงิน กล่องหมายเหตุ
     แยกตัวแปรจาก --app-shell-bg เพราะสองอย่างนี้ต้องเข้มไม่เท่ากัน */
  --app-subtle: #f1f5f9;
  --app-line: #dbe3ec;
  --app-muted: #e8ecf1;
  --app-muted-fg: #64748b;

  /* เงาสองชั้น: ชั้นแรกเป็นเส้นคมบาง ๆ แทนขอบ ชั้นสองเป็นเงานุ่มกระจายกว้าง
     รวมกันแล้วแผ่นขาวลอยขึ้นมาโดยไม่มีกรอบทึบ ๆ ตัดขอบ — เป็นครึ่งหนึ่งของคำว่า minimal
     อีกครึ่งคือรัศมีขอบมนข้างล่าง */
  --app-shadow: 0 1px 2px rgba(15, 23, 42, .04), 0 4px 14px rgba(15, 23, 42, .05);
  --app-shadow-lift: 0 18px 40px rgba(2, 6, 23, .35);

  /* ---------- รัศมีขอบมน ----------
     ไล่เป็นชั้นตามขนาดของกล่อง ไม่ใช่ค่าเดียวใช้ทุกที่ — ปุ่มสูง 32px ที่มนเท่าแผ่นเนื้อหา
     จะกลายเป็นแคปซูล ส่วนแผ่นใหญ่ที่มนเท่าปุ่มจะดูเหมือนไม่ได้ตั้งใจมน
     (ค่าอ้างอิงจาก Bento Box Grid ในฐานข้อมูล ui-ux-pro-max: การ์ดแดชบอร์ด 16-24px
     ตัดลงมาให้พอดีกับความหนาแน่นของหน้างานจริง) */
  --app-radius-lg: 1rem;       /* แผ่นเนื้อหา · modal · การ์ดใหญ่ */
  --app-radius: .625rem;       /* กล่องใน · ปุ่ม · ช่องกรอก · dropdown */
  --app-radius-sm: .5rem;      /* ของเล็ก — ปุ่มเล็ก รายการในเมนู */

  /* **รัศมีต้องอ่านเทียบกับความสูงของกล่อง ไม่ใช่ค่าที่ดูดีตอนอยู่เดี่ยว ๆ**
     ปุ่ม btn-sm สูงราว 31px ถ้าใช้ 12px จะกินไป 39% ของความสูง ออกมาเป็นเม็ดยา
     ไม่ใช่ปุ่ม — 10px บนปุ่มปกติ (38px) กับ 8px บนปุ่มเล็กคือจุดที่ยังอ่านว่า
     "สี่เหลี่ยมมุมมน" อยู่ · แผ่นใหญ่ใช้ 16px ได้เพราะสูงเป็นร้อย px */

  --app-navbar-h: 3.5rem;      /* 56px ตามสเปก Data-Dense Dashboard */
  --app-sidebar-w: 15rem;      /* 240px */
  --app-rail-w: 4.25rem;       /* ความกว้างตอนพับเมนูเหลือแถบไอคอน */

  /* Bootstrap 5.3 อ่านค่าชุดนี้จาก CSS variable จึงเปลี่ยนทั้งระบบได้จากที่เดียว
     ห้ามแตะ --bs-body-bg เพราะ .card .dropdown-menu .modal ใช้ค่านี้เป็นพื้นสีขาวของตัวเอง
     พื้นหลังเทาจึงไปตั้งที่ body แทน */
  --bs-body-font-family: 'IBM Plex Sans Thai', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --bs-body-font-size: .9375rem;    /* 15px — แน่นพอสำหรับตารางงาน แต่ยังอ่านสระไทยออก */
  --bs-body-line-height: 1.6;       /* สระบน-ล่างของไทยต้องการที่ว่างมากกว่าอังกฤษ */
  --bs-body-color: #1e293b;
  --bs-emphasis-color: #020617;
  --bs-secondary-color: #64748b;
  --bs-border-color: var(--app-line);

  /* **จุดเดียวที่ทำให้ทั้งแอปขอบมนพร้อมกัน** — Bootstrap 5.3 อ่านรัศมีของ .btn .card
     .form-control .form-select .dropdown-menu .alert .modal .list-group .toast
     จากตัวแปรชุดนี้ทั้งหมด ทับที่นี่ที่เดียวได้ทุกคอมโพเนนต์โดยไม่ต้องไล่แก้ทีละอัน
     และไม่ต้อง build Bootstrap ใหม่ */
  --bs-border-radius: var(--app-radius);
  --bs-border-radius-sm: var(--app-radius-sm);
  --bs-border-radius-lg: var(--app-radius-lg);
  --bs-border-radius-xl: var(--app-radius-lg);

  --bs-link-color-rgb: 3, 105, 161;
  --bs-link-hover-color-rgb: 7, 89, 133;
}

/* ป้ายสถานะเป็นแคปซูล ไม่ใช่สี่เหลี่ยมมุมมน — ของสั้น ๆ ที่มนเท่ากล่องใหญ่จะดูเป็น
   สี่เหลี่ยมที่ลบมุมทิ้ง ส่วนแคปซูลอ่านออกทันทีว่าเป็นป้าย ไม่ใช่ปุ่ม */
.badge {
  --bs-badge-border-radius: var(--bs-border-radius-pill);
  font-weight: 500;
}

body {
  background-color: var(--app-shell-bg);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 {
  font-weight: 600;
  color: var(--bs-emphasis-color);
  letter-spacing: -.01em;
}

/* ปุ่มหลัก = สีของการกระทำในพาเลต ไม่ใช่ฟ้าเริ่มต้นของ Bootstrap
   Bootstrap 5.3 ประกาศค่าพวกนี้เป็น --bs-btn-* อยู่แล้ว จึงทับได้โดยไม่ต้อง build ใหม่ */
.btn-primary {
  --bs-btn-bg: var(--app-accent);
  --bs-btn-border-color: var(--app-accent);
  --bs-btn-hover-bg: #075985;
  --bs-btn-hover-border-color: #075985;
  --bs-btn-active-bg: #0c4a6e;
  --bs-btn-active-border-color: #0c4a6e;
  --bs-btn-disabled-bg: var(--app-accent);
  --bs-btn-disabled-border-color: var(--app-accent);
  --bs-btn-focus-shadow-rgb: 3, 105, 161;
}

.btn-secondary {
  --bs-btn-bg: #ffffff;
  --bs-btn-color: var(--app-slate);
  --bs-btn-border-color: #cbd5e1;
  --bs-btn-hover-bg: #f1f5f9;
  --bs-btn-hover-color: var(--app-navy);
  --bs-btn-hover-border-color: #94a3b8;
  --bs-btn-active-bg: #e2e8f0;
  --bs-btn-active-color: var(--app-navy);
  --bs-btn-active-border-color: #94a3b8;
}

.form-control:focus,
.form-select:focus {
  border-color: #7dd3fc;
  box-shadow: 0 0 0 .2rem rgba(3, 105, 161, .18);
}

/* กติกาข้อ 1 ของรายการตรวจ: โฟกัสของคีย์บอร์ดต้องมองเห็นเสมอ ห้ามถอดออก */
:focus-visible {
  outline: 2px solid var(--app-accent);
  outline-offset: 2px;
}

/* ---------- โครงหน้า: เมนูซ้ายตรึงเต็มความสูง เนื้อหาเลื่อนอิสระ ---------- */

.app-shell {
  min-height: 100vh;
}

@media (min-width: 768px) {
  .app-shell {
    margin-left: var(--app-sidebar-w);
  }
}

/* ---------- แถบเมนูซ้าย ---------- */

.app-sidebar {
  /* จอเล็ก Bootstrap ใช้ค่านี้เป็นพื้นของ offcanvas ให้เอง */
  --bs-offcanvas-bg: var(--app-navy);
  --bs-offcanvas-color: #cbd5e1;
  --bs-offcanvas-width: 16rem;
}

.app-sidebar .offcanvas-header {
  border-bottom: 1px solid rgba(148, 163, 184, .18);
}

.app-sidebar .offcanvas-body {
  padding: 0;
  /* md ขึ้นไป Bootstrap ตั้ง flex-grow:0 ไว้ ต้องคืนเป็น 1 ไม่งั้นตัวลูกไม่ยืดเต็มแถบ */
  flex-grow: 1;
}

/* จอ md ขึ้นไปแถบนี้ตรึงติดซ้ายเต็มความสูง ไม่เลื่อนตามเนื้อหา
   จอเล็กกว่านี้ Bootstrap เปลี่ยน .offcanvas-md กลับเป็น offcanvas ให้เอง */
@media (min-width: 768px) {
  .app-sidebar {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    width: var(--app-sidebar-w);
    z-index: 1030;
  }
}

/* width:100% กับ flex:1 จำเป็น ไม่ใช่ของเกิน — ที่จอ md ขึ้นไป Bootstrap สั่ง
   .offcanvas-md .offcanvas-body { display: flex } ซึ่งเป็นแถวแนวนอน ตัวนี้จึงกลายเป็น
   flex item ที่กว้างเท่าเนื้อหาข้างใน ไม่ใช่เต็มความกว้างของแถบ ผลคือพื้นกรมท่าแคบกว่า
   ตัวแถบ เหลือเป็นช่องพื้นแอปคั่นระหว่างเมนูกับ navbar */
.app-sidebar-inner {
  display: flex;
  flex: 1 1 auto;
  width: 100%;
  flex-direction: column;
  min-height: 100%;
  padding: .75rem;
  background-color: var(--app-navy);

  /* แผงกรมท่าประกาศสีตัวหนังสือของตัวเอง ไม่ปล่อยให้ลูกแต่ละตัวประกาศเอง
     ถ้าไม่มีบรรทัดนี้ ของที่ยังไม่มีคลาสของตัวเองจะได้สี --bs-body-color (#1e293b)
     ซึ่งเป็นสีเข้มบนพื้นเข้ม = อ่านไม่ออก และจะพลาดแบบนี้ทุกครั้งที่เพิ่มของใหม่ในแถบ */
  color: #cbd5e1;
}

/* จอ md ขึ้นไปตัวแถบถูกตรึงเต็มจอแล้ว พื้นสีกรมท่าจึงต้องยาวเต็มจอตาม
   (ที่ md+ Bootstrap บังคับ .offcanvas-md ให้พื้นโปร่งด้วย !important สีจึงอยู่ที่ชั้นในนี้) */
/* **height ไม่ใช่ min-height** — ตัวแถบถูกตรึงด้วย position:fixed + bottom:0 อยู่แล้ว
   ถ้าข้างในสูงกว่าจอ มันจะล้นออกไปนอกกรอบที่ตรึงไว้โดยไม่มีตัวเลื่อน แล้วท้ายแถบ
   (ชื่อผู้ใช้กับปุ่มออกจากระบบ) หายไปเลยโดยไม่มีทางเลื่อนลงไปหา — **เกิดจริงตอนเมนู
   ยาวขึ้นจากรายงานกับกลุ่มระบบ** · ล็อกความสูงเท่าจอแล้วให้ nav ข้างในเป็นตัวเลื่อนแทน
   ท้ายแถบจึงติดอยู่กับที่เสมอไม่ว่าเมนูจะยาวแค่ไหน

   การพับกลุ่มช่วยให้ไม่ต้องเลื่อนบ่อย แต่**ไม่ใช่ตัวแก้ปัญหานี้** — กางครบทุกกลุ่ม
   บนจอเตี้ย ๆ ก็ยังยาวเกินจออยู่ดี ตัวที่รับประกันว่าปุ่มออกจากระบบไม่หายคือตรงนี้ */
@media (min-width: 768px) {
  .app-sidebar-inner {
    height: 100vh;
    overflow: hidden;
  }
}

/* flex item ที่ต้องเลื่อนได้ต้องมี min-height: 0 ด้วย เพราะค่าเริ่มต้นคือ auto
   ซึ่งแปลว่า "สูงอย่างน้อยเท่าเนื้อหา" — overflow-y อย่างเดียวจึงไม่มีผลอะไรเลย */
.app-sidebar-nav {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;

  /* แถบเลื่อนบาง ๆ กลืนไปกับพื้นกรมท่า ไม่ใช่แถบเทาหนาของระบบปฏิบัติการ */
  scrollbar-width: thin;
  scrollbar-color: rgba(148, 163, 184, .35) transparent;
}

.app-sidebar-nav::-webkit-scrollbar {
  width: .375rem;
}

.app-sidebar-nav::-webkit-scrollbar-thumb {
  border-radius: .25rem;
  background-color: rgba(148, 163, 184, .35);
}

/* หัวแถบเมนู — ชื่อระบบซ้าย ปุ่มพับขวา · สูงเท่า navbar เพื่อให้เส้นบนสุดของทั้งสอง
   ก้อนตรงกันพอดี · ซ่อนบนจอเล็กด้วยกฎของตัวเอง ไม่ใช่ d-none/d-md-flex ของ Bootstrap
   ที่มี !important ติดมา (จอเล็กใช้หัวของ offcanvas ที่มีปุ่มปิดอยู่แล้ว) */
.app-sidebar-head {
  display: none;
  align-items: center;
  gap: .5rem;
  height: var(--app-navbar-h);
  margin-bottom: .5rem;
}

@media (min-width: 768px) {
  .app-sidebar-head {
    display: flex;
  }
}

.app-sidebar-brand {
  /* flex เพราะโหมดตั้งค่ามีไอคอนลูกศรนำหน้าชื่อ — โหมดปกติมีลูกตัวเดียวจึงไม่ต่างจากเดิม */
  display: flex;
  align-items: center;
  gap: .375rem;
  flex: 1 1 auto;
  min-width: 0;            /* ให้ชื่อยาว ๆ ถูกตัดด้วย ellipsis แทนที่จะดันปุ่มตกขอบ */
  padding: 0 .25rem;
  color: #fff;
  text-decoration: none;
}

.app-sidebar-name {
  display: block;
  min-width: 0;            /* ตัวเองต้องยอมหดด้วย ไม่งั้น ellipsis ข้างล่างไม่ทำงานใน flex */
  font-size: .9375rem;
  font-weight: 600;
  line-height: 1.2;
  /* ชื่อยาวกว่าที่ว่างต้องถูกตัดด้วยจุดไข่ปลา ไม่ใช่ตัดคำลงบรรทัดสองแล้วดันหัวแถบสูงขึ้น
     จนไม่ตรงกับ navbar (อาการเดียวกับที่เคยเจอตอนชื่อระบบไม่ยอมหายในโหมดพับ) */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.app-sidebar-label {
  font-size: .6875rem;
  font-weight: 600;
  letter-spacing: .08em;
  color: #64748b;
  text-transform: uppercase;
  padding: 0 .625rem;
  margin: .5rem 0 .375rem;
}

.app-sidebar .nav-link {
  position: relative;
  /* flex เพื่อให้ไอคอนกับข้อความอยู่กึ่งกลางกันเสมอ แม้ข้อความจะตัดสองบรรทัด */
  display: flex;
  align-items: center;
  gap: .5rem;
  border-radius: .5rem;
  padding: .5rem .625rem;
  font-size: .9rem;
  color: #94a3b8;
  transition: background-color .15s ease, color .15s ease;
}

/* ในแถบเมนูไอคอนอยู่ในแนวเดียวกับข้อความด้วย flex แล้ว ไม่ต้องดันลงอีก */
.app-sidebar .nav-link .app-icon {
  vertical-align: baseline;
}

/* ทางลัดไปหากลุ่มบนหน้าตั้งค่าที่เปิดอยู่ — ลูกของรายการหัวข้อ

   เยื้องเข้ามาให้อ่านออกว่าเป็นของหัวข้อข้างบน และไม่มีไอคอนเพราะมันไม่ใช่ปลายทาง
   คนละหน้า แต่เป็นตำแหน่งบนหน้าเดียวกัน — ไอคอนจะทำให้ดูเท่ากับรายการหัวข้อ
   ทั้งที่ทำคนละอย่าง · เส้นซ้ายบาง ๆ ทำหน้าที่บอกความเป็นลูกแทน */
.app-sidebar-sub {
  margin: .125rem 0 .25rem .9375rem;
  padding-left: .5625rem;
  border-left: 1px solid rgba(148, 163, 184, .22);
  gap: 0;
}

.app-sidebar-sub .nav-link {
  padding: .3125rem .5rem;
  font-size: .8125rem;
  color: #7c8ba1;
}

.app-sidebar-sub .nav-link:hover,
.app-sidebar-sub .nav-link:focus-visible {
  color: #cbd5e1;
}

.app-sidebar .nav-link:hover,
.app-sidebar .nav-link:focus-visible {
  background-color: rgba(148, 163, 184, .12);
  color: #e2e8f0;
}

/* หน้าที่เปิดอยู่บอกด้วยทั้งสีและขีดซ้าย ไม่พึ่งสีอย่างเดียว */
.app-sidebar .nav-link.active {
  background-color: rgba(56, 189, 248, .13);
  color: #7dd3fc;
  font-weight: 600;
  padding-left: 1rem;
}

.app-sidebar .nav-link.active::before {
  content: "";
  position: absolute;
  left: .3125rem;
  top: .5rem;
  bottom: .5rem;
  width: 3px;
  border-radius: 2px;
  background-color: var(--app-accent-hi);
}

/* ไอคอน — ขนาดผูกกับ font-size ของที่มันอยู่ข้าง ๆ และสีมาจาก currentColor
   ไอคอนจึงโตและเปลี่ยนสีตามข้อความรอบตัวเองโดยไม่ต้องมีคลาสของแต่ละที่
   1.15em ไม่ใช่ 1em เพราะกล่องของ Lucide มีขอบในตัว ถ้าเท่ากันเป๊ะจะดูเล็กกว่าตัวอักษร */
.app-icon {
  width: 1.15em;
  height: 1.15em;
  flex: 0 0 auto;
  /* ตัวอักษรไทยมีสระบน-ล่าง เส้นฐานจึงไม่ได้อยู่กลางบรรทัด
     ดันไอคอนลงเล็กน้อยให้จุดกึ่งกลางตรงกับตัวอักษร ไม่ใช่ตรงกับกล่องบรรทัด */
  vertical-align: -.2em;
}

/* ไอคอนใหญ่ของหน้าที่ยังไม่มีข้อมูล — จาง ๆ พอเป็นภาพประกอบ ไม่แย่งสายตาจากปุ่ม */
.app-icon-empty {
  width: 2.5rem;
  height: 2.5rem;
  color: #94a3b8;
  vertical-align: baseline;
}

/* ปุ่มกับลิงก์ที่มีทั้งไอคอนและข้อความ — ระยะห่างมาจากที่เดียว ไม่ใช่ me-1 กระจายทุกหน้า */
.btn .app-icon,
.dropdown-item .app-icon {
  margin-right: .375rem;
}

/* คำอธิบายใต้ช่องกรอก — ซ่อนไว้ก่อน กดปุ่ม ? บนแถบบนแล้วโผล่พร้อมกันทั้งหน้า
   (app.js ติดคลาส .is-help ที่ <html>)

   ฟอร์มที่ทุกช่องมีคำอธิบายห้อยอยู่ตลอดเวลาอ่านเป็นกำแพงตัวหนังสือ ทั้งที่คนที่กรอก
   เป็นแล้วไม่ได้อ่านมันอีกเลย · ของที่อ่านครั้งเดียวจึงไม่ควรกินที่ตลอดไป

   **แสดงด้วย display: revert ไม่ใช่ block** เพราะ .app-help ไปติดทั้ง <div> <p>
   และ <span> (กล่องนิติบุคคลในฟอร์มคู่ค้า) — บังคับ block จะดัน span ที่ควรอยู่
   ท้ายบรรทัดเดียวกับปุ่มลงไปเป็นบรรทัดใหม่ · revert คืนค่าที่ธาตุนั้นควรเป็นตามชนิดของมันเอง

   **อนิเมชันอยู่คนละคลาสกับการแสดง** — .is-help ถูกติดตั้งแต่ในหัวหน้า (ก่อนเฟรมแรก)
   สำหรับคนที่เปิดค้างไว้ ถ้าอนิเมชันผูกกับคลาสนั้น หน้าจะเฟดคำอธิบายให้ดูใหม่ทุกครั้ง
   ที่เปลี่ยนหน้า ซึ่งเป็นเสียงรบกวน · app.js เติม .is-help-anim ตอนคนกดปุ่มเท่านั้น */
.app-help {
  display: none;
}

.is-help .app-help {
  display: revert;
}

.is-help-anim .app-help {
  animation: app-help-in .18s ease both;
}

@keyframes app-help-in {
  from { opacity: 0; transform: translateY(-.15rem); }
  to   { opacity: 1; transform: none; }
}

/* หัวข้อบนแผ่นเนื้อหาของหน้าตั้งค่า (settings/shell_head.php)
   เส้นใต้คั่นหัวข้อออกจากกล่องค่าตั้งค่า ทั้งสองอย่างจึงไม่อ่านติดกันเป็นก้อนเดียว

   **คลาสชุด .app-subnav* ถูกลบทิ้งแล้ว** — เมนูย่อยของหน้าตั้งค่าที่เคยเป็นคอลัมน์
   บนแผ่นขาวย้ายไปอยู่ในแถบเมนูซ้ายหลัก จึงใช้คลาสของแถบนั้น (.app-sidebar-label,
   .nav-link) ทั้งหมดและไม่ต้องมีชุดที่สองอีก · ที่เหลือคือบรรทัดชื่อกลุ่มเหนือ <h2>
   ซึ่งเปลี่ยนชื่อเป็น .app-settings-group ตามงานที่มันทำจริง ไม่ใช่ตามเมนูที่หายไปแล้ว */
.app-settings-head {
  margin-bottom: 1rem;
  padding-bottom: .75rem;
  border-bottom: 1px solid var(--app-line);
}

.app-settings-group {
  font-size: .6875rem;
  font-weight: 600;
  letter-spacing: .08em;
  color: var(--app-muted-fg);
  text-transform: uppercase;
  margin: 0 0 .375rem;
}

/* หัวบัตรของหัวข้อบัญชี — อวตารตัวใหญ่คู่กับชื่อผู้ใช้และสิทธิ์ */
.app-profile-head {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.app-profile-avatar {
  width: 3.5rem;
  height: 3.5rem;
  flex: 0 0 3.5rem;
  border-radius: 50%;
  background-color: var(--app-navy);
  color: #fff;
  font-size: 1.375rem;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* หัวข้อของกลุ่มเมนู — ไม่ใช่ลิงก์ จึงต้องดูต่างจากรายการที่กดได้ชัดเจน
   ตัวเล็กกว่า สีจางกว่า และไม่มี hover เพื่อไม่ให้คนพยายามกด */
/* หัวข้อกลุ่มเป็น <button> ที่พับกลุ่มเก็บได้ จึงต้องล้างหน้าตาปุ่มของเบราว์เซอร์ออกให้หมด
   ก่อน แล้ววาดใหม่ให้เหมือนหัวข้อธรรมดา — ไม่ใช่ปุ่มที่อยากให้กด แต่กดได้ */
.app-nav-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .5rem;
  width: 100%;
  border: 0;
  background: none;
  text-align: left;
  font-size: .8125rem;
  font-weight: 600;
  color: #cbd5e1;
  padding: .375rem .625rem .25rem;
  margin: 0;
  border-radius: .375rem;
  transition: background-color .15s ease, color .15s ease;
}

.app-nav-head:hover,
.app-nav-head:focus-visible {
  background-color: rgba(148, 163, 184, .12);
  color: #e2e8f0;
}

/* ลูกศรชี้ลงตอนกาง ชี้ขวาตอนพับ — หมุนเอาไม่ต้องมีไอคอนสองตัว
   aria-expanded เป็นตัวบอกสถานะจริง คลาสจึงเกาะไปกับมันแทนที่จะมีคลาสของตัวเอง */
.app-nav-caret {
  flex: 0 0 auto;
  opacity: .55;
  transition: transform .2s ease;
}

.app-nav-head[aria-expanded="false"] .app-nav-caret {
  transform: rotate(-90deg);
}

/* กลุ่มที่มีหน้าเปิดอยู่ข้างในทำหัวข้อให้สว่างขึ้น คนจะได้รู้ว่าตัวเองอยู่โมดูลไหน
   แม้เมนูย่อยจะเลื่อนพ้นสายตาไปแล้ว */
.app-nav-group.active > .app-nav-head {
  color: #e2e8f0;
}

/* **ห้ามให้รายการในแถบเมนูตัดขึ้นแถวใหม่** — `.nav` ของ Bootstrap ตั้ง `flex-wrap: wrap`
   มาให้ ซึ่งพอทิศเป็นแนวตั้ง (`flex-column`) การ "ตัดแถวใหม่" ของมันคือการแตกเป็น
   คอลัมน์ข้าง ๆ กัน · ระหว่างที่ collapse กำลังเลื่อนเปิด `.collapsing` บีบ `height: 0`
   ไว้ก่อนแล้วค่อยไต่ขึ้น รายการทั้งกลุ่มจึงไปเรียงเป็นแนวนอนให้เห็นแวบหนึ่ง
   แล้วค่อยยุบกลับเป็นคอลัมน์เดียวตอนความสูงเต็ม — ดูเหมือนเมนูกระตุกทุกครั้งที่กางกลุ่ม

   ตั้งที่ `.app-sidebar .nav` ไม่ใช่เฉพาะกลุ่มที่พับได้ เพราะไม่มีรายการไหนในแถบนี้
   ที่ควรตัดขึ้นแถวใหม่เลย และวันหลังถ้ามีใครเอา collapse ไปครอบลิสต์ชั้นนอกด้วย
   จะได้ไม่เจออาการเดียวกันซ้ำ */
/* ---------- แถบเมนูแบบพับเหลือไอคอน (rail) ----------
   คลาส .is-rail อยู่บน <html> ไม่ใช่ body เพราะสคริปต์กันจอกระพริบใน page_head.php
   ติดมันตั้งแต่ก่อน <body> จะมีตัวตน · ทั้ง .app-shell และ .app-sidebar อ่านความกว้าง
   จาก --app-sidebar-w อยู่แล้ว การพับจึงเป็นการเปลี่ยนตัวแปรตัวเดียว ไม่ต้องแก้ทั้งสองที่

   **เฉพาะจอ md ขึ้นไป** จอเล็กกว่านั้นแถบเป็น offcanvas ที่เปิด-ปิดทั้งแผงอยู่แล้ว
   การมีสองกลไกซ้อนกันบนจอเดียวคือของที่อธิบายให้ใครฟังไม่ได้ */
@media (min-width: 768px) {
  :root.is-rail {
    --app-sidebar-w: var(--app-rail-w);
  }

  /* ข้อความทุกชิ้นหายไป เหลือแต่ไอคอน — ตัวไอคอนเองไม่ได้ถูกแตะ
     ป้ายกำกับกลับมาเป็น tooltip ผ่าน title ที่ app.js เติมให้ตอนพับ */
  .is-rail .app-sidebar-name,
  .is-rail .app-sidebar-label,
  .is-rail .app-nav-head,
  .is-rail .app-sidebar .nav-link > span,
  .is-rail .app-logout > span,
  .is-rail .app-sidebar-user > .text-truncate,
  /* ทางลัดกลุ่มหายไปทั้งชุดตอนพับ ไม่ใช่เหลือกล่องเปล่า — มันไม่มีไอคอน
     (โดยตั้งใจ ดูกฎของ .app-sidebar-sub) พอข้อความหายจึงไม่เหลืออะไรให้กด */
  .is-rail .app-sidebar-sub {
    display: none;
  }

  /* หัวข้อกลุ่มหายไปแล้ว ลูกของกลุ่มจึงต้องโผล่ทั้งหมดเสมอ ไม่งั้นกลุ่มที่พับไว้จะกลาย
     เป็นไอคอนที่หายไปเฉย ๆ โดยไม่มีอะไรบอกว่ามันอยู่ไหน */
  .is-rail .app-sidebar .app-nav-group > .nav {
    display: flex !important;   /* ทับ .collapse ที่เป็น display:none ตอนไม่ show */
    height: auto !important;    /* ทับ .collapsing ตอนกำลังเลื่อนค้างอยู่ */
    margin-left: 0;
    padding-left: 0;
    border-left: 0;
  }

  /* ไอคอนอยู่กลางแถบ และพื้นที่กดยังเป็นสี่เหลี่ยมเต็มความกว้าง ไม่ใช่แค่ตัวไอคอน
     (44×44px ขึ้นไปตามเกณฑ์ขนาดพื้นที่กดของ ui-ux-pro-max) */
  .is-rail .app-sidebar .nav-link,
  .is-rail .app-logout {
    justify-content: center;
    padding-left: 0;
    padding-right: 0;
  }

  /* ตอนพับ ชื่อระบบหายไปทั้งลิงก์ เหลือปุ่มตัวเดียวกลางหัวแถบ
     ซึ่งอ่านออกเองว่ากดเพื่อกางกลับ ไม่ต้องมีป้ายอธิบาย */
  .is-rail .app-sidebar-brand {
    display: none;
  }

  .is-rail .app-sidebar-head {
    justify-content: center;
  }

  /* ตอนพับ ชื่อกับตำแหน่งหายไป (กฎข้างบน) เหลืออวตารกับปุ่มตั้งค่า — เรียงลงมาเป็น
     คอลัมน์ ไม่ใช่นอนข้างกัน เพราะ rail กว้าง 68px ใส่อวตาร 32px คู่กับปุ่ม 44px ไม่ลง
     ท้ายแถบจึงกลายเป็นไอคอนสามอันเรียงลงมา: อวตาร · ตั้งค่า · ออกจากระบบ */
  .is-rail .app-sidebar-user {
    flex-direction: column;
    justify-content: center;
    padding: 0;
  }

  /* margin-left: auto ของตอนกางใช้กับแกนนอน พอทิศเป็นแนวตั้งมันจะดันปุ่มไปชิดขวา
     ของคอลัมน์แทนที่จะอยู่กลาง */
  .is-rail .app-sidebar-settings {
    margin-left: 0;
  }

  /* ขีดซ้ายของรายการที่เปิดอยู่ใช้ไม่ได้ตอนไอคอนอยู่กลาง — เปลี่ยนเป็นพื้นทึบทั้งปุ่มแทน
     ซึ่งอ่านออกว่า "อันนี้แหละที่เปิดอยู่" โดยไม่ต้องพึ่งตำแหน่งของขีด */
  .is-rail .app-sidebar .nav-link.active::before,
  .is-rail .app-sidebar .app-nav-sub.active::before {
    display: none;
  }

  .is-rail .app-sidebar .app-nav-sub.active {
    padding-left: 0;
  }
}

.app-sidebar .nav {
  flex-wrap: nowrap;
}

/* คู่กับ nowrap ข้างบน — flex item ยอมหดได้เป็นค่าเริ่มต้น (`flex-shrink: 1`)
   ระหว่างที่ `.collapsing` บีบความสูงอยู่ รายการทั้งกลุ่มจึงถูกกดให้แบนก่อนแล้วค่อยยืด
   ขึ้นมาทีหลัง · ล็อกไม่ให้หดแล้วปล่อยให้ `overflow: hidden` ของ `.collapsing`
   เป็นตัวบัง การกางจึงเป็นการ "เผยออกมา" เฉย ๆ ไม่ใช่การยืดตัวของรายการ */
.app-sidebar .nav > .nav-item {
  flex: 0 0 auto;
}

/* เมนูย่อยเยื้องเข้ามาให้เห็นชั้น และมีเส้นแนวตั้งบอกขอบเขตของกลุ่ม */
.app-sidebar .app-nav-group > .nav {
  margin-left: .625rem;
  padding-left: .5rem;
  border-left: 1px solid rgba(148, 163, 184, .18);
}

/* ขีดซ้ายของรายการที่เปิดอยู่ต้องขยับตามระยะเยื้อง ไม่งั้นจะไปทับเส้นของกลุ่ม */
.app-sidebar .app-nav-sub.active {
  padding-left: .875rem;
}

.app-sidebar .app-nav-sub.active::before {
  left: .1875rem;
}

.app-sidebar-foot {
  margin-top: auto;
  padding: .75rem .625rem .25rem;
  border-top: 1px solid rgba(148, 163, 184, .18);
}

.app-sidebar-user {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin-bottom: .625rem;
  padding: 0 .125rem;
}

/* ออกจากระบบเป็นทางออก ไม่ใช่ปุ่มที่อยากให้กด — หน้าตาปกติจึงกลืนไปกับแถบเมนู
   เหมือนรายการเมนูอันหนึ่ง แล้วค่อยเป็นสีแดงตอนเอาเมาส์ไปชี้ ให้รู้ว่ากดแล้วหลุดออกจากระบบ
   (ใช้โครงเดียวกับ .app-sidebar .nav-link เป๊ะ ๆ ต่างแค่สีตอน hover) */
.app-logout {
  display: flex;
  align-items: center;
  gap: .5rem;
  border-radius: .5rem;
  padding: .5rem .625rem;
  font-size: .9rem;
  color: #94a3b8;
  text-decoration: none;
  transition: background-color .15s ease, color .15s ease;
}

.app-logout:hover,
.app-logout:focus-visible {
  background-color: rgba(239, 68, 68, .16);
  color: #fca5a5;
}

/* flex item ที่ต้องตัดข้อความต้องมี min-width: 0 ด้วย ค่าเริ่มต้นของ flex item คือ auto
   ซึ่งแปลว่า "กว้างอย่างน้อยเท่าเนื้อหา" — .text-truncate อย่างเดียวจึงไม่พอ
   ชื่อผู้ใช้ยาว ๆ จะดันแถบเมนูให้กว้างเกิน 240px แทนที่จะโดนตัด */
.app-sidebar-user > * {
  min-width: 0;
}

/* ปุ่มตั้งค่าท้ายแถวชื่อผู้ใช้ — ดันไปชิดขวาเอง ชื่อยาว ๆ จึงยังถูกตัดด้วยจุดไข่ปลา
   ตามเดิม (min-width: 0 ข้างบนกับ flex: 0 0 auto ของ .app-icon-btn ทำงานคู่กัน) */
.app-sidebar-settings {
  margin-left: auto;
}

/* หน้าตั้งค่าเปิดอยู่ = ปุ่มสว่างค้างไว้ ไม่ใช่รอให้เอาเมาส์ไปชี้
   ใช้สีเดียวกับตอน hover ของปุ่มไอคอนในแถบเมนู จึงไม่ได้เพิ่มสีใหม่เข้าระบบ */
.app-sidebar .app-sidebar-settings.active {
  background-color: rgba(148, 163, 184, .14);
  color: #ffffff;
}

/* ชื่อกับสิทธิ์ใช้คลาสชุดเดียวกับตอนที่อยู่บนแถบบน แต่ที่นี่พื้นเป็นกรมท่า
   จึงต้องทับสีเป็นโทนสว่าง ไม่งั้นได้สีเข้มบนเข้มเหมือนที่เคยพลาดกับหัวข้อกลุ่มเมนู */
.app-sidebar .app-user-name { color: #e2e8f0; }
.app-sidebar .app-user-role { color: #94a3b8; }

/* อวตารบนพื้นกรมท่าใช้พื้นกรมท่าไม่ได้ ต้องเป็นแผ่นสว่างจาง ๆ ถึงจะเห็นขอบตัวเอง */
.app-sidebar .app-avatar {
  background-color: rgba(148, 163, 184, .18);
  color: #e2e8f0;
}

/* ---------- navbar ---------- */

.app-navbar {
  min-height: var(--app-navbar-h);
  padding-top: 0;
  padding-bottom: 0;
  background-color: var(--app-surface);
  border-bottom: 1px solid var(--app-line);
}

/* ---------- ปุ่มไอคอนล้วนบนแถบบน ----------
   เดิมใช้ .btn .btn-secondary ซึ่งมีพื้นขาว + เส้นขอบ + เงา ทำให้ปุ่มสลับเมนูเด่นกว่า
   ชื่อหน้าที่อยู่ข้าง ๆ ทั้งที่เป็นของที่กดนาน ๆ ครั้ง — **น้ำหนักทางสายตาต้องเรียงตาม
   ความสำคัญ ไม่ใช่ตามว่าอะไรกดได้** · ตอนพักจึงกลืนไปกับแถบ แล้วค่อยมีพื้นตอนชี้

   ไอคอนอยู่กลางจริงเพราะ place-items: center ไม่ใช่เพราะ padding บังเอิญเท่ากัน ·
   ต้องล้าง vertical-align ของ .app-icon ด้วย (ค่า -.2em นั้นมีไว้ให้ไอคอนตรงกับ
   เส้นกลางของตัวอักษรไทยเวลาอยู่ปนกับข้อความ ซึ่งในปุ่มไอคอนล้วนกลายเป็นตัวดันให้เบี้ยว) */
.app-icon-btn {
  display: inline-grid;
  place-items: center;
  /* flex item ยอมหดได้เป็นค่าเริ่มต้น — บนแถบบนที่มีชื่อหน้ายาว ๆ อยู่ข้าง ๆ ปุ่มจะถูกบีบ
     จนต่ำกว่า 44px ซึ่งเป็นเกณฑ์ขนาดพื้นที่กดขั้นต่ำที่ความกว้างข้างล่างตั้งไว้ */
  flex: 0 0 auto;
  width: 2.75rem;          /* 44px ตามเกณฑ์ขนาดพื้นที่กดขั้นต่ำ */
  height: 2.75rem;
  padding: 0;
  border: 0;
  border-radius: var(--app-radius-sm);
  background: none;
  color: var(--app-muted-fg);
  cursor: pointer;
  transition: background-color .15s ease, color .15s ease;
}

.app-icon-btn:hover,
.app-icon-btn:focus-visible {
  background-color: var(--app-subtle);
  color: var(--app-navy);
}

.app-icon-btn .app-icon {
  vertical-align: baseline;
  width: 1.25em;
  height: 1.25em;
}

/* ตัวเดียวกันนี้ไปอยู่บนพื้นกรมท่าของแถบเมนูด้วย — สีเทาของแถบบนจะจมหายบนพื้นเข้ม
   จึงต้องมีชุดสีของตัวเอง ไม่ใช่ค่าเดียวใช้ทั้งสองที่ */
.app-sidebar .app-icon-btn {
  color: #94a3b8;
}

.app-sidebar .app-icon-btn:hover,
.app-sidebar .app-icon-btn:focus-visible {
  background-color: rgba(148, 163, 184, .14);
  color: #ffffff;
}

/* ซ่อน/แสดงตามขนาดจอด้วยกฎของตัวเอง ไม่ใช่ d-none/d-md-* ของ Bootstrap
   เพราะ utility พวกนั้นมี !important ซึ่งจะทับ display: inline-grid ข้างบนทิ้ง */
.app-menu-toggle { display: inline-grid; }
.app-rail-toggle { display: none; }

@media (min-width: 768px) {
  .app-menu-toggle { display: none; }
  .app-rail-toggle { display: inline-grid; }
}

/* ไอคอนสองตัวซ้อนช่องเดียวกัน แล้วสลับด้วยการจาง + หมุนเล็กน้อย
   หมุนคนละทางเพื่อให้อ่านเป็น "ตัวหนึ่งหมุนออก อีกตัวหมุนเข้า" ไม่ใช่กะพริบสลับ */
.app-rail-icon {
  grid-area: 1 / 1;
  transition: opacity .18s ease, transform .18s ease;
}

.app-rail-open {
  opacity: 0;
  transform: rotate(-90deg) scale(.6);
}

.is-rail .app-rail-close {
  opacity: 0;
  transform: rotate(90deg) scale(.6);
}

.is-rail .app-rail-open {
  opacity: 1;
  transform: none;
}

/* `.text-truncate` บนชื่อหน้าใช้ไม่ได้จริงถ้าไม่มีบรรทัดนี้ — แถบบนเป็น flex แถวเดียว
   (`navbar-expand` สั่ง `flex-wrap: nowrap`) และ flex item มี `min-width: auto`
   เป็นค่าเริ่มต้น ซึ่งแปลว่า "กว้างอย่างน้อยเท่าเนื้อหา" ชื่อหน้ายาว ๆ จึงดันแถบให้กว้าง
   เกินจอแทนที่จะโดนตัด แล้วทั้งหน้าเลื่อนไปทางขวาได้
   (ปัญหาเดียวกับที่ `.app-sidebar-user > *` เจอมาก่อน) */
.app-navbar h1 {
  min-width: 0;
}

/* บริษัทที่ทำงานอยู่กินมุมขวาของแถบทั้งก้อน — ไม่มีป้ายกำกับตัวเล็กกำกับอีกแล้ว
   ป้ายเดิมตั้ง letter-spacing กับ text-transform ซึ่งเป็นกติกาของตัวอักษรละติน
   พอใช้กับคำไทยแล้วสระกับวรรณยุกต์ถูกดันห่างจากพยัญชนะจนอ่านเป็นคำแปลก ๆ
   ชื่อบริษัทกับไอคอนอาคารบอกอยู่แล้วว่านี่คืออะไร ไม่ต้องมีคำมากำกับซ้ำ */
.app-company {
  height: var(--app-navbar-h);
  margin-right: -.5rem;   /* ชนขอบขวาของแถบพอดี เพื่อให้พื้นที่กดไปสุดมุม */
}

.app-company-btn {
  display: flex;
  align-items: center;
  gap: .5rem;
  height: 100%;
  max-width: 15rem;
  padding: 0 .75rem;
  border: 0;
  border-radius: 0;
  background: none;
  color: var(--bs-emphasis-color);
  font-size: .875rem;
  font-weight: 600;
  line-height: 1.2;
}

/* พื้นที่กดกินความสูงทั้งแถบ การเล็งจึงไม่ต้องแม่น — และมีสัญญาณตอบกลับเวลาชี้ */
button.app-company-btn:hover,
button.app-company-btn:focus-visible,
button.app-company-btn[aria-expanded="true"] {
  background-color: var(--app-subtle);
}

button.app-company-btn:focus-visible {
  outline: 2px solid var(--bs-primary);
  outline-offset: -2px;
}

/* ชื่อบริษัทยาว ๆ ต้องถูกตัด ไม่ใช่ดันของอื่นจนหน้าเลื่อนแนวนอน */
.app-company-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.app-company-icon {
  color: var(--app-muted-fg);
  flex: 0 0 auto;
}

/* ลูกศรขึ้น-ลงบอกว่ากดแล้วมีตัวเลือกให้เลือก ไม่ใช่แค่ข้อความที่บังเอิญกดได้ */
.app-company-caret {
  color: var(--app-muted-fg);
  font-size: .75rem;
  flex: 0 0 auto;
  margin-left: auto;
}

/* เข้าถึงได้บริษัทเดียว: หน้าตาเดียวกันแต่ไม่ตอบสนองการกด เพราะไม่มีอะไรให้เลือก */
.app-company-btn-static {
  cursor: default;
}

@media (max-width: 767.98px) {
  .app-company-btn {
    max-width: 11rem;
    padding: 0 .5rem;
  }
}

.app-avatar {
  width: 2rem;
  height: 2rem;
  flex: 0 0 2rem;
  border-radius: .5rem;
  background-color: var(--app-navy);
  color: #fff;
  font-size: .8125rem;
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.app-user-name {
  display: block;
  font-size: .8125rem;
  font-weight: 600;
  line-height: 1.25;
  color: var(--bs-emphasis-color);
}

.app-user-role {
  display: block;
  font-size: .6875rem;
  line-height: 1.25;
  color: var(--app-muted-fg);
}

/* ---------- พื้นที่เนื้อหา ---------- */

.app-main {
  padding: 1.25rem 1rem 3rem;
}

@media (min-width: 768px) {
  .app-main {
    padding: 1.5rem 1.5rem 3rem;
  }
}

.app-page {
  background-color: var(--app-surface);
  /* **ไม่มีเส้นขอบแล้ว** — เงาสองชั้นของ --app-shadow ทำหน้าที่ยกแผ่นขึ้นจากพื้นเทา
     ได้ชัดพออยู่แล้ว การมีทั้งเส้นขอบและเงาคือการบอกเรื่องเดียวกันสองครั้ง
     ซึ่งเป็นสิ่งแรกที่ต้องตัดออกเวลาทำให้ minimal */
  border-radius: var(--app-radius-lg);
  box-shadow: var(--app-shadow);
  padding: 1.125rem 1rem;
}

@media (min-width: 768px) {
  .app-page {
    padding: 1.25rem 1.5rem;
  }
}

/* หัวข้อย่อยในแผ่นเนื้อหา — ชื่อหน้าอยู่บน navbar แล้ว ในแผ่นจึงเริ่มที่ระดับ h2 */
.app-page > h2:first-child,
.app-page > .h2:first-child {
  margin-top: 0;
}

.app-page h2,
.app-page .h2 {
  font-size: 1rem;
  margin-top: 1.5rem;
  margin-bottom: .75rem;
}

/* ตารางข้อมูล: หัวตารางเป็นป้ายกำกับ ไม่ใช่เนื้อหา จึงเบากว่าตัวข้อมูล
   แถวเตี้ย 36px ตามสเปกความหนาแน่น กวาดตาได้หลายสิบแถวโดยไม่ต้องเลื่อน */
.app-page .table {
  --bs-table-hover-bg: #f4f7fa;
  border-color: var(--app-line);
}

.app-page .table > thead > tr > th {
  background-color: var(--app-subtle);
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--app-muted-fg);
  white-space: nowrap;
  border-bottom-color: var(--app-line);
}

.app-page .table > :not(caption) > * > * {
  padding: .4375rem .625rem;
}

.app-page dt {
  font-weight: 600;
  color: var(--app-muted-fg);
}

/* กล่องย่อยในแผ่นขาว — ใช้กับกลุ่มตัวเลขหรือข้อความที่ต้องแยกตัวจากเนื้อหารอบข้าง
   แผ่นขาวผืนใหญ่ที่ไม่มีอะไรคั่นเลยกวาดตาหายาก พอ ๆ กับพื้นที่สว่างเกิน */
/* ---------- กลุ่มช่องในฟอร์ม — ที่เดียวของทั้งระบบ ----------

   ทุกฟอร์มที่บันทึกข้อมูล (ใบแจ้งงาน เอกสารทั้งสี่ ทะเบียนคู่ค้า/เครื่อง/สินค้า และ
   หน้าตั้งค่า) แบ่งช่องเป็นกลุ่มด้วยรูปนี้รูปเดียว

       <fieldset class="app-formgroup">
         <legend class="app-formgroup-title">ชื่อกลุ่ม</legend>
         <div class="row g-3"> ...ช่อง... </div>
       </fieldset>

   ใช้ <legend> ไม่ใช่ <h2> เพราะโปรแกรมอ่านหน้าจอจะอ่านชื่อกลุ่มนำหน้าทุกช่องในกลุ่มนั้น
   ให้เอง ซึ่งหัวข้อธรรมดาทำไม่ได้ · และไม่ขัดกับกฎที่ว่าหัวข้อในเนื้อหาเริ่มที่ <h2>
   เพราะ legend ไม่ใช่ heading

   เดิมคลาสชุดนี้เขียนเป็น "h6 border-bottom pb-2 mb-3" พิมพ์ซ้ำทุกที่ที่ใช้ ซึ่งแปลว่า
   ฟอร์มที่เพิ่มทีหลังลอกไปไม่ครบแล้วหัวข้อคนละขนาดกัน — และฟอร์มครึ่งหนึ่งไม่มีกลุ่มเลย */
.app-formgroup {
  margin-bottom: 1.5rem;
  /* fieldset ของเบราว์เซอร์มี min-width: min-content ติดมา ซึ่งทำให้กล่องดันกว้างกว่า
     พ่อแม่เมื่อข้างในมีตารางที่เลื่อนแนวนอนได้ (.table-responsive ของบรรทัดรายการ) */
  min-width: 0;
}

/* ทับ reboot ของ Bootstrap ที่ตั้ง legend เป็น float: left · width: 100% ·
   font-size: calc(1.275rem + .3vw) ซึ่งโตกว่าหัวข้อรอบตัวและขนาดวิ่งตามความกว้างจอ */
.app-formgroup-title {
  float: none;
  width: auto;
  display: block;
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.4;
  padding-bottom: .5rem;
  margin-bottom: 1rem;
  border-bottom: 1px solid var(--app-line);
}

/* แถบแท็บของหน้าตั้งค่าที่มีหลายกลุ่ม (settings/form.php)
   เลื่อนแนวนอนแทนการพับลงบรรทัดสอง — .nav ของ Bootstrap ตั้ง flex-wrap: wrap มาให้
   ซึ่งพอเป็น .nav-tabs แถวที่สองจะไปนอน *ใต้* เส้นขอบล่างของแถวแรก อ่านเหมือนแท็บ
   ที่หลุดออกมาจากแถบ ไม่ใช่แท็บที่ขึ้นบรรทัดใหม่ (กับดักเดียวกับ .nav ในแถบเมนูซ้าย)
   · flex: 0 0 auto ที่ .nav-item เพราะ flex item ยอมหดเป็นค่าเริ่มต้น ชื่อกลุ่มยาว ๆ
   จะถูกบีบจนอ่านไม่ออกแทนที่จะดันให้เกิดการเลื่อน */
/* หน้าตั้งค่าไม่มีแท็บแล้ว (6 ก.ย. 2026) — กฎ .app-settabs ทั้งชุดถูกลบทิ้งพร้อมกัน
   รวมเงาไล่ที่ขอบที่เพิ่งเพิ่มเข้ามาวันเดียวกัน · เงานั้นแก้อาการ (แท็บที่เลยขอบมองไม่เห็น)
   แต่ไม่ได้แก้สาเหตุ (ค่าตั้งค่าที่ต้องกดหาถึงจะเจอ) — พอทุกกลุ่มเรียงลงมาในหน้าเดียว
   ปัญหาหายไปทั้งคู่ · **กฎ CSS ที่ไม่มีใครใช้แล้วคือกฎที่คนอ่านโค้ดเข้าใจผิดว่ายังมีของ
   ใช้มันอยู่** จึงลบทิ้ง ไม่ปล่อยค้างไว้ */

/* กล่องพรีวิวโลโก้ในหน้าข้อมูลบริษัท — สูงคงที่ไม่ว่าจะมีรูปหรือยัง
   ที่ว่างที่ยุบ-ขยายตอนเลือกไฟล์จะดันปุ่มข้างล่างหนีมือที่กำลังจะกดต่อ */
.app-logo-box {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 6rem;
  padding: .75rem;
}

.app-logo-img {
  max-width: 100%;
  max-height: 4.5rem;
}

.app-inset {
  background-color: var(--app-subtle);
  /* กล่องในแผ่นขาวยังต้องมีเส้นขอบ ต่างจากแผ่นนอก — พื้นเทาอ่อนบนขาวต่างกันไม่ถึง 5%
     ถ้าถอดเส้นออกด้วยจะเหลือแค่รอยเปื้อนจาง ๆ ไม่ใช่กล่อง · เส้นบางกว่าเดิมด้วยการใช้
     สีอ่อนลง แทนที่จะถอดทิ้ง */
  border: 1px solid var(--app-line);
  border-radius: var(--app-radius);
  padding: .75rem .875rem;
}

/* กล่องที่มีเรื่องต้องรีบ (การ์ด "เลยนัดรับ" บนหน้าแรก) — ยกด้วยขอบแดงบนพื้นเดิม
   ไม่ใช่เทพื้นแดงทั้งกล่อง เพราะตัวเลขข้างในต้องอ่านออกและเป็นสีแดงอยู่แล้ว
   ผู้เรียกติดคลาสนี้เฉพาะตอนที่มีของจริง ค้างไว้ทุกวันแล้วคนจะเลิกมอง */
.app-inset-alert {
  border-color: var(--bs-danger-border-subtle);
  box-shadow: 0 0 0 1px var(--bs-danger-border-subtle);
}

/* ---------- หัวข้อที่พับเก็บได้ในเนื้อหา (.app-disclose) ----------

   ของที่คนส่วนใหญ่ไม่ได้มาใช้ทุกครั้ง แต่ต้องหาเจอเมื่อต้องการ — ซ่อนไว้ก่อน เหลือแค่
   บรรทัดหัวข้อที่กดได้ · หลักเดียวกับ `.app-help` ที่ซ่อนคำอธิบายใต้ช่องกรอกไว้ก่อน
   **ของที่อ่านหรือใช้นาน ๆ ครั้งไม่ควรกินที่ตลอดไป**

   **ใช้ collapse ของ Bootstrap ที่โหลดอยู่แล้ว ไม่เขียนตัวพับเอง** — ได้ `aria-expanded`
   กับการเลื่อนความสูงมาฟรี หลักเดียวกับกลุ่มในแถบเมนูซ้าย · ต่างกันที่ `.app-nav-head`
   ทำสีมาสำหรับพื้นกรมท่าของแถบเมนู ตัวนี้อยู่บนแผ่นขาว จึงเป็นคนละคลาส

   **ไม่จำสถานะไว้** ต่างจากแถบเมนูที่จำกลุ่มที่เปิดอยู่ — ตรงนั้นคือทางเดินประจำวัน
   ส่วนตรงนี้คือของที่เปิดครั้งเดียวแล้วจบ การจำไว้จะทำให้มันกางค้างอยู่ตลอดกาลหลังจาก
   ที่คนกดดูไปครั้งหนึ่ง ซึ่งย้อนกลับไปเป็นปัญหาเดิมที่การพับเกิดมาแก้ */
.app-disclose {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  border: 0;
  background: none;
  padding: 0;
  margin: 0;
  color: var(--bs-link-color);
  font: inherit;
  text-align: left;
}

.app-disclose:hover,
.app-disclose:focus-visible {
  color: var(--bs-link-hover-color);
  text-decoration: underline;
}

/* ลูกศรชี้ลงเมื่อกางอยู่ ชี้ขวาเมื่อพับ — ทิศเดียวกับหัวกลุ่มในแถบเมนู
   ผู้ใช้จึงไม่ต้องเรียนรู้สัญลักษณ์ใหม่ */
.app-disclose-caret { transition: transform .2s ease; }
.app-disclose[aria-expanded="false"] .app-disclose-caret { transform: rotate(-90deg); }

/* จางเข้าเมื่อกาง — collapse ของ Bootstrap ขยับแต่ความสูง ไม่มีอะไรจางให้

   Bootstrap เดินสามสถานะ: `.collapse` (พับ) → `.collapsing` (กำลังเลื่อน · ไม่มี
   `.collapse` แล้ว) → `.collapse.show` (กาง) · ความทึบจึงเป็น 0 ตลอดสองสถานะแรก
   แล้วไต่ขึ้นตอนกางเสร็จ และไต่ลงเองตอนหุบเพราะกลับไปเป็น `.collapsing`

   **ผูกกับคลาสของ collapse ทั้งสองตัว ไม่ใช่ตั้ง opacity: 0 ไว้ที่คลาสตัวเองเฉย ๆ** —
   แบบหลังสั้นกว่าแต่แปลว่าธาตุที่ติด `.app-disclose-panel` โดยลืมติด `.collapse`
   จะหายไปถาวรทั้งที่ยังกินที่อยู่ ซึ่งเป็นความพังที่มองไม่เห็นตอนอ่านโค้ด

   `prefers-reduced-motion` ตัดทรานซิชันนี้ทิ้งเองด้วยกฎ `*` ข้างบน */
.app-disclose-panel.collapse,
.app-disclose-panel.collapsing {
  opacity: 0;
  transition: opacity .25s ease;
}

.app-disclose-panel.collapse.show { opacity: 1; }

/* ---------- แถบเทียบขนาดในตาราง (รายงานรายวัน) ----------
   ไม่ใช้ .progress ของ Bootstrap เพราะนี่ไม่ใช่ความคืบหน้าของอะไร เป็นการเทียบขนาด
   ระหว่างแถวเพื่อให้กวาดตาหาวันที่ยอดสูงได้โดยไม่ต้องอ่านตัวเลขทุกบรรทัด

   ตัวเลขจริงอยู่ในคอลัมน์ข้าง ๆ เสมอ แถบจึงเป็นของประดับที่ aria-hidden ได้
   และหายไปบนจอแคบ (d-none d-md-table-cell) โดยไม่มีข้อมูลไหนหายไปด้วย */
.app-bar {
  height: .5rem;
  border-radius: .25rem;
  /* --app-line ไม่ใช่ --app-subtle เพราะรางแถบต้องเห็นบนพื้นขาวของแผ่นเนื้อหา
     ส่วน --app-subtle ต่างจากขาวน้อยเกินจนรางหายไปทั้งคอลัมน์ */
  background: var(--app-line);
  overflow: hidden;
}

.app-bar > span {
  display: block;
  height: 100%;
  min-width: 2px;          /* วันที่ได้เงินน้อยมากต้องยังเห็นว่ามีแถบ ไม่ใช่ว่างเปล่า */
  border-radius: inherit;
  background: var(--app-accent);
}

/* ---------- กล่องเรื่องบนหน้าใบงาน (เสนอราคา · เบิกอะไหล่ · รับเงิน) ----------
   สามกล่องเป็น .app-inset เทาเหมือนกันหมด กล่องที่ตรงกับสถานะปัจจุบันของงานจึงต้อง
   เด่นขึ้นมา ไม่งั้นคนต้องอ่านแถบขั้นตอนแล้วเดาเองว่าตอนนี้ต้องทำกล่องไหน

   **ห้ามใช้พื้นขาวเป็นตัวเน้นตรงนี้** เคยทำแล้วมองไม่เห็นเลย — การ์ดพวกนี้อยู่บน
   .app-page ซึ่งขาวอยู่แล้ว การ์ดที่เน้นเลยกลายเป็นรูสีขาวบนพื้นขาว เหลือแค่เส้นขอบ
   1px · หลัก "ขาวลอยบนเทา" ใช้ได้กับแผ่นเนื้อหาบนพื้นแอป ไม่ใช่กับกล่องในแผ่นขาว

   เน้นด้วยพื้นฟ้าจาง + กรอบสีเน้น 2px (border 1px + ring จาก box-shadow อีก 1px)
   **สีไม่ใช่สัญญาณเดียว** กล่องที่เน้นมีป้าย "ขั้นนี้" กำกับด้วย สำหรับคนที่แยกสีไม่ออก */
.app-track.is-active {
  background-color: var(--app-accent-soft);
  border-color: var(--app-accent);
  box-shadow: 0 0 0 1px var(--app-accent);
}

.app-track-now {
  display: inline-block;
  margin-left: .375rem;
  padding: .0625rem .375rem;
  border-radius: .75rem;
  background-color: var(--app-accent);
  color: #fff;
  font-size: .6875rem;
  font-weight: 600;
  line-height: 1.4;
  vertical-align: .0625rem;
}

/* ---------- แถวล่างของกล่องตัวกรอง (คู่กับ components/filter_foot.php) ----------
   ปุ่มค้นหาเคยเป็นคอลัมน์หนึ่งในกริดของแต่ละหน้า ตำแหน่งจึงเลื่อนตามจำนวนช่องกรอง
   ตอนนี้แยกเป็นแถวของตัวเองที่คั่นด้วยเส้น ปุ่มชิดขวาเสมอทุกหน้า */

.filter-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem .75rem;
  margin-top: .75rem;
  padding-top: .75rem;
  border-top: 1px solid var(--app-line);
}

.filter-foot-date {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem .75rem;
}

.filter-foot-range {
  display: flex;
  align-items: center;
  gap: .5rem;
}

/* ช่องวันที่ไม่ต้องกว้างเต็มคอลัมน์ กว้างเท่าที่ตัวเลขต้องการก็พอ */
.filter-foot-range input[type="date"] {
  width: auto;
}

/* ปุ่มชิดขวาเสมอ แม้หน้านั้นจะไม่มีตัวกรองวันที่มาดันจากทางซ้าย */
.filter-foot-actions {
  display: flex;
  gap: .5rem;
  margin-left: auto;
}

/* ---------- ช่องเลือกหลายค่า (คอมโพเนนต์กลาง — คู่กับ picker.js) ----------
   หน้าตาเลียนแบบ .form-control ของ Bootstrap เพื่อให้ยืนอยู่ในแถวเดียวกับช่องอื่นได้
   โดยไม่ต้องมาไล่จูนความสูงทีละหน้า */

.picker {
  position: relative;
}

.picker-box {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .25rem;
  min-height: calc(1.5em + .5rem + 2px);   /* เท่า .form-control-sm */
  padding: .1875rem .5rem;
  background-color: var(--app-surface);
  border: 1px solid var(--app-line);
  border-radius: var(--bs-border-radius-sm);
  cursor: text;
}

/* กรอบตอนโฟกัสอยู่ที่ตัวกล่อง ไม่ใช่ที่ช่องพิมพ์ข้างใน — ชุดเดียวกับ .form-control:focus */
.picker-box:focus-within {
  border-color: #7dd3fc;
  box-shadow: 0 0 0 .2rem rgba(3, 105, 161, .18);
}

.picker-chip {
  display: inline-flex;
  align-items: center;
  gap: .25rem;
  padding: .0625rem .25rem .0625rem .4375rem;
  font-size: .8125rem;
  line-height: 1.5;
  background-color: var(--app-subtle);
  border: 1px solid var(--app-line);
  border-radius: .375rem;
}

.picker-x {
  padding: 0 .125rem;
  border: 0;
  background: none;
  color: var(--app-muted-fg);
  line-height: 1;
}

.picker-x:hover {
  color: var(--bs-emphasis-color);
}

.picker-input {
  flex: 1 1 6rem;
  min-width: 6rem;
  padding: .0625rem 0;
  border: 0;
  background: none;
  font-size: .875rem;
  color: inherit;
}

/* กรอบโฟกัสไปโผล่ที่กล่องข้างนอกแล้ว ตรงนี้จึงถอดได้โดยไม่ทำให้มองไม่เห็นว่าโฟกัสอยู่ไหน */
.picker-input:focus,
.picker-input:focus-visible {
  outline: none;
}

.picker-menu {
  position: absolute;
  z-index: 1000;
  top: 100%;
  left: 0;
  right: 0;
  margin: .25rem 0 0;
  padding: .25rem;
  max-height: 15rem;
  overflow-y: auto;
  list-style: none;
  background-color: var(--app-surface);
  border: 1px solid var(--app-line);
  border-radius: .5rem;
  box-shadow: 0 8px 24px rgba(15, 23, 42, .12);
}

.picker-item {
  display: block;
  width: 100%;
  padding: .3125rem .5rem;
  border: 0;
  border-radius: .375rem;
  background: none;
  text-align: left;
}

.picker-item:hover,
.picker-item:focus-visible {
  background-color: var(--app-subtle);
}

.picker-item-label {
  display: block;
  font-size: .875rem;
}

.picker-item-sub {
  display: block;
  font-size: .75rem;
  color: var(--app-muted-fg);
}

.picker-empty {
  padding: .375rem .5rem;
  font-size: .8125rem;
  color: var(--app-muted-fg);
}

/* ---------- แถบขั้นตอน (คอมโพเนนต์กลาง) ----------
   ใช้กับเอกสารที่เดินเป็นลำดับขั้น — ใบแจ้งงานเป็นตัวแรก ใบสั่งซื้อ/ใบเบิกของจะใช้ตัวเดียวกัน
   ไม่มี icon library ในโปรเจกต์ จุดกับเส้นจึงวาดด้วย CSS ล้วน
   สถานะบอกด้วยทั้งสี รูปทรงจุด และตัวเลข/เครื่องหมายถูกข้างใน ไม่พึ่งสีอย่างเดียว */

.step-track {
  --step-dot: 1.375rem;
  display: flex;
  list-style: none;
  padding: 0;
  margin: 0 0 .5rem;
}

.step-track-item {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  text-align: center;
}

/* เส้นเชื่อมลากจากขอบจุดก่อนหน้ามาถึงขอบจุดของตัวเอง จึงไม่ทับตัวจุด */
.step-track-item::before {
  content: "";
  position: absolute;
  top: calc(var(--step-dot) / 2 - 1px);
  left: calc(-50% + var(--step-dot) / 2);
  width: calc(100% - var(--step-dot));
  height: 2px;
  background-color: var(--app-line);
}

.step-track-item:first-child::before {
  display: none;
}

/* เส้นที่อยู่ "ก่อน" ขั้นที่ทำแล้วหรือขั้นปัจจุบัน คือทางที่งานเดินผ่านมาจริง */
.step-track-item.is-done::before,
.step-track-item.is-current::before {
  background-color: var(--app-accent);
}

/* ขั้นที่กดได้เพื่อข้ามไปสถานะนั้น — เป็น <button> จริงเพื่อให้คีย์บอร์ดกับโปรแกรม
   อ่านหน้าจอใช้ได้ แต่ถอดหน้าตาปุ่มออกทั้งหมด เพราะสิ่งที่คนเห็นคือจุดกับป้ายเหมือนเดิม
   พื้นที่กดจึงกว้างเท่าช่องของขั้นนั้น ไม่ใช่แค่ตัวจุด */
.step-track-btn {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: inherit;
  cursor: pointer;
}

.step-track-btn:hover:not(:disabled) .step-track-dot {
  border-color: var(--app-accent);
  color: var(--app-accent);
}

.step-track-btn:hover:not(:disabled) .step-track-label {
  color: var(--bs-emphasis-color);
}

/* ขั้นที่ทำแล้วมีพื้นทึบอยู่ hover จึงต้องไม่ไปล้างสีตัวอักษรบนพื้นนั้นให้อ่านไม่ออก */
.step-track-item.is-done .step-track-btn:hover:not(:disabled) .step-track-dot {
  color: #fff;
}

.step-track-btn:focus-visible {
  outline: 2px solid var(--app-accent);
  outline-offset: 2px;
  border-radius: .375rem;
}

/* ขั้นที่งานค้างอยู่ตอนนี้ (JS ปิดให้หลังเปลี่ยนสถานะโดยไม่รีโหลด) — ไม่จางลง
   เพราะมันคือขั้นปัจจุบันที่ต้องเด่นที่สุดบนแถบ แค่กดไม่ได้เท่านั้น */
.step-track-btn:disabled {
  cursor: default;
  opacity: 1;
}

.step-track-dot {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--step-dot);
  height: var(--step-dot);
  border-radius: 50%;
  border: 2px solid var(--app-line);
  background-color: var(--app-surface);
  color: var(--app-muted-fg);
  font-size: .6875rem;
  font-weight: 700;
  line-height: 1;
}

.step-track-label {
  display: block;
  margin-top: .375rem;
  font-size: .75rem;
  line-height: 1.35;
  color: var(--app-muted-fg);
}

/* ป้ายเอกสารใต้ชื่อขั้น — มีเฉพาะขั้นที่ต้องทำเอกสารให้เรียบร้อยก่อนถึงจะเดินต่อได้
   (เสนอราคา · เบิกอะไหล่ · ชำระเงิน) สีบอกว่าทำครบแล้ว ทำไปบางส่วน หรือยังไม่ได้ทำ */
.step-track-gate {
  display: block;
  margin-top: .125rem;
  font-size: .6875rem;
  line-height: 1.3;
  font-weight: 600;
}

.step-track-gate.is-done { color: #15803d; }
.step-track-gate.is-part { color: #b45309; }
.step-track-gate.is-todo { color: var(--app-muted-fg); }

.step-track-item.is-done .step-track-dot {
  border-color: var(--app-accent);
  background-color: var(--app-accent);
  color: #fff;
}

.step-track-item.is-current .step-track-dot {
  border-color: var(--app-accent);
  color: var(--app-accent);
  box-shadow: 0 0 0 .25rem rgba(3, 105, 161, .16);
}

.step-track-item.is-current .step-track-label {
  color: var(--bs-emphasis-color);
  font-weight: 600;
}

/* ค้างอยู่ที่แขนงข้าง (ใบแจ้งงาน = รออะไหล่) ยังเป็นขั้นเดิม แต่ต้องดูออกว่าไม่ได้เดินหน้า */
.step-track-item.is-hold .step-track-dot {
  border-color: #d97706;
  color: #b45309;
  box-shadow: 0 0 0 .25rem rgba(217, 119, 6, .18);
}

.step-track-item.is-hold .step-track-label {
  color: #b45309;
}

/* แถบกรองสถานะบนหน้ารายการ — กินที่ที่เหลือ ไม่ใช่กว้างเท่าป้ายทั้งแถวรวมกัน

   แถวนั้นเป็น `d-flex flex-wrap justify-content-between` ที่มีแถบกรองกับปุ่มหลักอยู่ด้วยกัน ·
   `<nav>` ที่ไม่ได้ตั้ง flex มี `flex-basis: auto` ซึ่งแปลว่า **ขนาดตั้งต้นคือความกว้างตอนเรียง
   ป้ายทั้งหมดในบรรทัดเดียว** — เบราว์เซอร์ตัดบรรทัดจากขนาดนั้น *ก่อน* จะพิจารณาหด พอป้าย
   เยอะขึ้นทีละอันจนเกินความกว้างแถว ปุ่มจึงถูกดันตกไปบรรทัดสอง แล้วโดน `space-between`
   วางชิดซ้าย = ปุ่มหลักลอยอยู่มุมซ้ายล่าง (เกิดจริงตอนเพิ่มสถานะ `ลูกค้าไม่ซ่อม` เป็นป้ายที่ 12)

   `flex: 1 1 0` ทำให้ขนาดตั้งต้นเป็น 0 แถบกับปุ่มจึงอยู่บรรทัดเดียวกันได้เสมอ แล้วแถบค่อยยืด
   กินที่ที่เหลือ · **การพับไปเกิดที่ป้ายข้างในแถบ ซึ่งเป็นที่ที่ควรพับตั้งแต่แรก** */
.app-pillbar {
  flex: 1 1 0;
  min-width: 0;
}

/* ขั้นที่ใบนี้ไม่ได้เดินผ่าน เพราะแยกไปเส้นแขนง — เส้นประจาง ๆ ไม่ใช่ติ๊กถูก

   ก่อนหน้านี้ขั้นพวกนี้ขึ้น is-done สีน้ำเงินเพราะเลขขั้นน้อยกว่าขั้นปัจจุบัน แถบจึงบอกว่า
   เบิกอะไหล่และซ่อมเสร็จแล้ว ทั้งที่งานที่ลูกค้าไม่ให้ซ่อมไม่เคยแตะสองขั้นนั้นเลย */
.step-track-item.is-skip {
  opacity: .38;
}

.step-track-item.is-skip .step-track-dot {
  border-style: dashed;
}

/* เส้นที่ลากเข้าสู่ขั้นที่ถูกข้าม และเข้าสู่ขั้นที่บรรจบ ไม่ใช่ทางที่งานเดินมา
   — งานมาจากเส้นแขนงข้างล่าง · ต้องอยู่หลังกฎ is-done/is-current เพราะ specificity เท่ากัน */
.step-track-item.is-skip::before,
.step-track-item.is-rejoin::before {
  background-color: var(--app-line);
}

/* เส้นแยกของแขนงที่ข้ามขั้นกลางไปจริง (ลูกค้าไม่ซ่อม: ขั้น 3 → ขั้น 7)
   แยกออกจากขั้นที่งานค้างอยู่ ลงมาผ่านโหนดงานที่ต้องทำ แล้วชี้กลับขึ้นไปที่ขั้นที่บรรจบ

   **ตำแหน่งสองตัวมาจาก PHP เป็น --fork-x1 / --fork-x2** ซึ่งคิดจากกึ่งกลางจุดของขั้นนั้น
   ((n - 0.5) / จำนวนขั้น) — CSS ไม่รู้ว่าแถบมีกี่ขั้นและไม่ต้องรู้ แถบที่ขั้นเปลี่ยนจำนวน
   ในอนาคตจึงยังวาดถูกโดยไม่ต้องมาแก้ไฟล์นี้

   แขนงที่บรรจบที่ขั้นถัดไปพอดี (รออะไหล่) ไม่วาดเส้น — เส้นที่โค้งออกแล้วกลับเข้าที่เดิม
   ทันทีคือเสียงรบกวน ไม่ใช่ข้อมูล · ตัวตัดสินอยู่ที่ service_status_fork() */
.step-fork {
  --fork-drop: 1.75rem;
  --fork-color: #d97706;
  position: relative;
  height: 4.25rem;
  margin: -.125rem 0 .25rem;
}

/* เก็บเงินครบแล้ว = ทางนี้เดินจบแล้ว เส้นเปลี่ยนเป็นสีของเส้นทางที่ผ่านมาแล้ว */
.step-fork.is-done {
  --fork-color: var(--app-accent);
}

/* เส้นนอนของแขนง ลากจากขาที่แยกออกถึงขาที่บรรจบ */
.step-fork::before {
  content: "";
  position: absolute;
  left: var(--fork-x1);
  right: calc(100% - var(--fork-x2));
  top: var(--fork-drop);
  border-top: 2px dashed var(--fork-color);
}

.step-fork-leg {
  position: absolute;
  top: 0;
  height: var(--fork-drop);
  margin-left: -1px;               /* กึ่งกลางเส้นให้ตรงกับกึ่งกลางจุดของขั้น */
  border-left: 2px dashed var(--fork-color);
}

.step-fork-leg.is-out { left: var(--fork-x1); }
.step-fork-leg.is-in  { left: var(--fork-x2); }

/* หัวลูกศรของขาที่บรรจบ ชี้ขึ้นไปที่จุดของขั้นปลายทาง */
.step-fork-leg.is-in::after {
  content: "";
  position: absolute;
  left: -.3125rem;
  top: -1px;
  border-left: .375rem solid transparent;
  border-right: .375rem solid transparent;
  border-bottom: .4375rem solid var(--fork-color);
}

/* โหนดคร่อมกลางเส้น — พื้นทึบเพื่อบังเส้นประที่ลอดอยู่ข้างหลัง */
.step-fork-node {
  position: absolute;
  top: calc(var(--fork-drop) - .75rem);
  left: calc((var(--fork-x1) + var(--fork-x2)) / 2);
  transform: translateX(-50%);
  padding: 0 .5rem;
  background-color: var(--app-surface);
  text-align: center;
  white-space: nowrap;
}

.step-fork-pill {
  display: inline-block;
  padding: .125rem .625rem;
  border: 2px solid var(--fork-color);
  border-radius: var(--bs-border-radius-pill);
  background-color: var(--app-surface);
  color: var(--fork-color);
  font-size: .75rem;
  font-weight: 700;
  line-height: 1.35;
}

.step-fork-sub {
  display: block;
  margin-top: .1875rem;
  font-size: .6875rem;
  font-weight: 600;
  line-height: 1.3;
  color: var(--fork-color);
}

/* งานที่หลุดออกจากเส้นทาง (ยกเลิก) — ทั้งเส้นจางลง ไม่มีขั้นไหนเป็นปัจจุบัน */
.step-track.is-off {
  opacity: .55;
}

.step-track-caption {
  color: var(--app-muted-fg);
}

/* จอแคบ: เส้นแนวนอนห้าขั้นอ่านไม่ออก พับเป็นแนวตั้ง จุดซ้าย ป้ายขวา */
@media (max-width: 575.98px) {
  /* แถบพับเป็นแนวตั้งแล้ว เส้นแยกแนวนอนวาดไม่ได้และไม่จำเป็น —
     ป้ายบนขั้น (is-hold) กับบรรทัดใต้แถบยังบอกครบว่างานอยู่แขนงไหน */
  .step-fork {
    display: none;
  }

  .step-track {
    flex-direction: column;
    gap: .875rem;
  }

  .step-track-item {
    display: flex;
    align-items: center;
    gap: .625rem;
    flex: 0 0 auto;
    width: 100%;
    text-align: left;
  }

  .step-track-item::before {
    top: -.875rem;
    left: calc(var(--step-dot) / 2 - 1px);
    width: 2px;
    height: .875rem;
  }

  .step-track-label {
    margin-top: 0;
    font-size: .8125rem;
  }
}

/* ---------- เส้นเวลาประวัติ ---------- */

.sv-timeline {
  list-style: none;
  margin: 0;
  padding-left: 1.125rem;
  border-left: 2px solid var(--app-line);
}

.sv-timeline-item {
  position: relative;
  padding-bottom: .875rem;
}

.sv-timeline-item:last-child {
  padding-bottom: 0;
}

.sv-timeline-item::before {
  content: "";
  position: absolute;
  left: -1.4375rem;
  top: .45rem;
  width: .5rem;
  height: .5rem;
  border-radius: 50%;
  background-color: var(--app-accent);
  box-shadow: 0 0 0 3px var(--app-surface);
}

/* แถวเก่ากว่าแถวบนสุดคือเรื่องที่จบไปแล้ว จุดจึงเป็นสีเทา */
.sv-timeline-item:not(:first-child)::before {
  background-color: #cbd5e1;
}

.sv-timeline-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .375rem;
}

.sv-timeline-note {
  margin-top: .25rem;
  padding: .375rem .625rem;
  background-color: var(--app-subtle);
  border: 1px solid var(--app-line);
  border-radius: .375rem;
}

/* ---------- หน้าเข้าสู่ระบบ (ไม่มี navbar/sidebar) ---------- */

.app-auth {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  color: #e2e8f0;
  background:
    radial-gradient(900px 420px at 50% -10%, rgba(3, 105, 161, .55) 0%, rgba(3, 105, 161, 0) 65%),
    var(--app-navy);
}

.app-auth-box {
  width: 100%;
  max-width: 23rem;
}

.app-auth-title {
  color: #ffffff;
}

.app-auth-sub {
  color: #94a3b8;
}

.app-auth-card {
  /* พื้นการ์ดเป็นสีขาว ตัวหนังสือข้างในจึงต้องกลับมาเป็นสีเข้ม ไม่ใช่สีอ่อนของพื้นกรมท่า */
  color: var(--bs-body-color);
  background-color: var(--app-surface);
  border-radius: .75rem;
  box-shadow: var(--app-shadow-lift);
  padding: 1.75rem 1.5rem;
}

.app-auth-foot {
  color: #64748b;
}

/* ช่องรหัสช่าง 4 หลัก — ตัวโต เว้นระยะให้ทวนเลขที่พิมพ์ได้ง่าย */
.app-code-input {
  letter-spacing: .6em;
  text-indent: .6em;      /* ชดเชยช่องไฟตัวสุดท้ายให้ข้อความอยู่กลางจริง */
  font-weight: 600;
}

/* ---------- จุดบอกความคืบหน้าของเอกสารที่ออกจากใบงาน (คู่กับ convert/) ----------
   วาดด้วย CSS ไม่ใช้อักขระวงกลมหรือ emoji เพราะหน้าตาต่างกันทุกเครื่องและคุมสีไม่ได้ */

.conv-dot {
  display: inline-block;
  width: .75rem;
  height: .75rem;
  margin-right: .375rem;
  border: 2px solid var(--app-muted-fg);
  border-radius: 50%;
  vertical-align: baseline;
}

.conv-dot.is-done {
  background-color: var(--bs-success);
  border-color: var(--bs-success);
}

/* ทำบางส่วน = ครึ่งวงกลม ใช้ gradient แทนการซ้อนสอง element */
.conv-dot.is-part {
  background: linear-gradient(90deg, var(--bs-success) 50%, transparent 50%);
  border-color: var(--bs-success);
}

/* ใบที่ยกเลิกหรือถูกปฏิเสธ — จบแล้วแต่ไม่ใช่จบแบบสำเร็จ จึงไม่ใช้สีเดียวกับ is-done */
.conv-dot.is-dead {
  background-color: var(--bs-danger);
  border-color: var(--bs-danger);
  opacity: .55;
}

/* ใช้คู่กับลิงก์ในบรรทัดข้อความ — เล็กลงและขยับให้อยู่กลางบรรทัดพอดี */
.conv-dot.is-inline {
  width: .5rem;
  height: .5rem;
  margin-right: .25rem;
  border-width: 1px;
  vertical-align: middle;
}

/* คอลัมน์จุดการแปลงเอกสารบนหน้ารายการงานซ่อม (ใบเสนอราคา · ใบเบิก · ใบเสร็จ)
   ที่นี่จุดอยู่กลางช่องตัวเดียว ไม่ได้นำหน้าข้อความ จึงต้องล้าง margin-right
   ที่ .conv-dot มีไว้สำหรับตอนอยู่หน้าลิงก์ ไม่งั้นจุดจะเยื้องซ้ายจากกึ่งกลางช่อง */
.app-convcol {
  width: 3.25rem;
}

.app-convcol .conv-dot {
  margin-right: 0;
  vertical-align: middle;
}

/* ---------- หน้าพิมพ์เอกสาร (คู่กับ quotation/print.php) ----------
   หน้าพวกนี้ไม่มี sidebar/navbar ของแอป เป็นกระดาษเปล่ากับเนื้อเอกสารเท่านั้น */

.app-print {
  background-color: var(--app-surface);
  color: #000;
}

/* กว้างเท่ากระดาษ A4 ลบขอบ เพื่อให้สิ่งที่เห็นบนจอใกล้เคียงกับสิ่งที่ออกมาจากเครื่องพิมพ์ */
.app-print-sheet {
  max-width: 190mm;
  margin: 0 auto;
  padding: 1.5rem 1rem 3rem;
}

.app-print-actions {
  display: flex;
  justify-content: flex-end;
  gap: .5rem;
  max-width: 190mm;
  margin: 1rem auto 0;
  padding: 0 1rem;
}

.app-print-sum {
  min-width: 18rem;
}

/* ---------- แผ่นกระดาษของแบบฟอร์มเอกสาร: A4 หรือ A5 ----------

   `.app-print-sheet` เดิมกว้าง 190mm เฉย ๆ แล้วปล่อยให้ **เบราว์เซอร์** ตัดสินขนาดกระดาษ
   กับขอบเอง — ซึ่งแปลว่าเครื่องที่ตั้งค่าเริ่มต้นเป็น Letter จะได้ใบที่สัดส่วนไม่ตรง
   และคนที่กด "พอดีหน้า" ในกล่องพิมพ์จะได้ตัวหนังสือคนละขนาดกับเครื่องข้าง ๆ

   ติดคลาสนี้แล้วแผ่นถูกล็อกเป็น A4 แนวตั้ง · เติม .is-a5 อีกคลาสได้ A5
   ทั้งสองแบบเหมือนกันทุกอย่างยกเว้นขนาดกระดาษกับขนาดตัวอักษร — **ไม่มีเนื้อหาไหนหายไป
   ตอนย่อลง A5** ใบที่คนสองคนถืออยู่คนละขนาดต้องอ่านได้เรื่องเดียวกัน

   **ใช้ named page ไม่ใช่ `@page` เปล่า ๆ** ด้วยเหตุผลเดียวกับใบส่งซ่อม —
   `@page` ที่ไม่มีชื่อมีผลกับ *ทุกหน้าพิมพ์ในระบบ* รวมใบส่งซ่อมที่ตั้งขอบ 8mm ของตัวเองไว้ */
.app-doc-sheet {
  /* กว้างเท่ากระดาษจริง ไม่ใช่เท่าพื้นที่พิมพ์ — ขอบเป็น padding จึงเห็นบนจอด้วยว่า
     ข้อความจะอยู่ห่างจากริมกระดาษแค่ไหน · ทับ max-width ของ .app-print-sheet */
  width: 210mm;
  max-width: 100%;
  min-height: 297mm;
  margin: 1rem auto;

  /* ---------- ระยะขอบของแผ่นมีที่มาที่เดียว ----------

     สามเหลี่ยมมุมต้องเลื่อนออกไป **เท่ากับ padding ตัวนี้พอดี** ถึงจะไปนั่งชิดมุมแผ่น
     (ดู `.app-doc .app-doc-corner`) — สองค่านั้นเป็นค่าเดียวกันโดยนิยาม ไม่ใช่สองค่า
     ที่บังเอิญเท่ากัน จึงต้องอ่านจากตัวแปรตัวเดียว · พิมพ์ 12mm ไว้อีกที่แล้ววันที่มีใคร
     แก้ขอบกระดาษ มุมจะค้างอยู่ที่เดิมโดยไม่มีอะไรฟ้อง

     **แต่บล็อก print ยังต้องสั่งระยะเลื่อนของมุมเป็น 0 เอง ตกตามตัวแปรนี้ไม่ได้** —
     `.is-a5` ทับค่าไว้ที่ความจำเพาะสูงกว่าในทุกสื่อ ดูเหตุผลเต็มในบล็อกนั้น */
  --doc-sheet-pad: 12mm;
  padding: var(--doc-sheet-pad);
  background: #fff;

  /* เงาทำให้แผ่นลอยขึ้นจากพื้นเทาของ .app-print เหมือนกระดาษวางบนโต๊ะ
     หลักเดียวกับที่ .app-page ลอยอยู่บนพื้นแอป */
  box-shadow: 0 1px 2px rgba(15, 23, 42, .08), 0 8px 24px rgba(15, 23, 42, .10);

  /* หัว–ตัว–ท้าย: ตัวกินที่ที่เหลือทั้งหมด ท้ายกระดาษจึงถูกดันไปติดขอบล่างของแผ่นเสมอ
     ไม่ใช่ลอยขึ้นมากลางหน้าเมื่อรายการมีบรรทัดเดียว · เทคนิคเดียวกับ .app-slip */
  display: flex;
  flex-direction: column;
}

.app-doc-sheet.is-a5 {
  width: 148mm;
  min-height: 210mm;
  --doc-sheet-pad: 8mm;

  /* **ต้องประกาศ padding ซ้ำตรงนี้ ทั้งที่กฎฐานประกาศไปแล้วด้วยตัวแปรตัวเดียวกัน** —
     `@media print` ท้ายไฟล์มี `.app-print-sheet { padding: 0 }` ซึ่งความจำเพาะเท่ากับ
     กฎฐาน (0,1,0) แต่มาทีหลังจึงชนะ · บรรทัดนี้มีสองคลาส (0,2,0) จึงเป็นตัวเดียวที่กัน
     ไม่ให้ A5 ตกไปได้ padding 0 ตอนพิมพ์ ซึ่งจะขยับขอบกระดาษของใบ A5 ทุกด้าน 8mm

     (A5 จึงพิมพ์ด้วยขอบรวม 16mm = @page 8mm + padding นี้อีก 8mm ต่างจาก A4 ที่ได้
     12mm ตามที่ตั้งใจ · **เป็นพฤติกรรมที่มีมาก่อนหน้านี้แล้ว ไม่ได้เกิดจากงานสามเหลี่ยมมุม**
     และแก้ตรงนี้เมื่อไรกระดาษ A5 ที่พิมพ์อยู่ทุกวันจะเปลี่ยน จึงคงไว้ตามเดิม) */
  padding: var(--doc-sheet-pad);
  font-size: .8125rem;
}

@media print {
  @page app-doc-page {
    size: A4 portrait;
    margin: 12mm;
  }

  @page app-doc-page-a5 {
    size: A5 portrait;
    margin: 8mm;
  }

  /* บนกระดาษจริง ขอบมาจาก @page แล้ว แผ่นจึงต้องไม่มี padding ของตัวเองซ้อนอีกชั้น

     **min-height ยังต้องมี** ไม่งั้นท้ายกระดาษจะเลื่อนขึ้นมาชนรายการ (ทั้งหมดนี้มีไว้เพื่อ
     ให้มันอยู่ล่างสุด) · ค่าที่ใส่คือความสูงของพื้นที่พิมพ์ **ลบไว้ 2mm** — ใส่เท่ากันเป๊ะแล้ว
     การปัดเศษ mm→px ของเบราว์เซอร์ทำให้ล้นไปอีกหน้าที่ว่างเปล่าได้ · 2mm มองไม่เห็นด้วยตา
     A4: 297 − 12×2 = 273 → 271 · A5: 210 − 8×2 = 194 → 192 */
  .app-doc-sheet {
    page: app-doc-page;
    width: auto;
    max-width: none;
    min-height: 271mm;
    margin: 0;

    --doc-sheet-pad: 0mm;
    box-shadow: none;
  }


  .app-doc-sheet.is-a5 {
    page: app-doc-page-a5;
    min-height: 192mm;
  }
}

/* แถบปุ่มบนหน้าพิมพ์แบบฟอร์มกว้างเท่าแผ่นที่กำลังดูอยู่ ไม่ใช่ 190mm ตายตัวของหน้าพิมพ์อื่น
   — ปุ่มที่ล้นออกนอกขอบกระดาษอ่านเหมือนของที่วางผิดที่ · เห็นเฉพาะบนจอ ไม่ถูกพิมพ์ */
.app-print-doc .app-print-actions {
  max-width: 210mm;
}

.app-print-doc.is-a5 .app-print-actions {
  max-width: 148mm;
}

/* หัวกระดาษต้องเว้นที่ให้สามเหลี่ยมมุม — มันเป็น position: absolute จึงไม่กินที่ของตัวเอง
   หัวเรื่องที่ชิดขวาจะไปนอนอยู่ใต้มันโดยไม่มีอะไรฟ้อง (เจอจริง: คำว่า "ใบเสนอราคา"
   ถูกสามเหลี่ยมทับ) · เว้นเท่าความกว้างสามเหลี่ยม + .5rem ให้ตัวหนังสือไม่เฉียดขอบสี
   เส้นใต้หัวเรื่องกับตารางเมตาหดตามไปด้วย ซึ่งตรงกับแบบฟอร์มจริงที่ไม่ได้ชิดริมกระดาษอยู่แล้ว

   ---------- กฎเดียวของทั้งสี่ใบที่มีมุม ----------

   เคยเป็นสี่กฎที่พิมพ์ตัวเลขไว้ตรง ๆ (ใบเต็มหน้า 3.75rem · A5 2.75rem · ใบส่งซ่อม
   2.75rem · ใบรับเครื่องคืน 3.75rem) ซึ่ง **ทุกตัวคือความกว้างสามเหลี่ยมบวก .5rem
   พอดีเป๊ะอยู่แล้ว** ความสัมพันธ์นั้นจึงเขียนเป็นสูตรได้ แล้วเหลือที่เดียวที่ต้องแก้
   ถ้าวันหลังตัดสินใจว่าระยะเผื่อควรมากกว่านี้

   `.app-doc-headrow` เป็นคลาสกำกับที่ติดบนแถวหัวกระดาษของทั้งสี่ใบ **ไม่มี CSS อื่น
   ของตัวเอง** (รูปเดียวกับ .app-slip-head / .app-handover-head) — มีไว้ให้กฎนี้เกาะ
   โดยไม่ต้องไล่รายชื่อคลาสหัวกระดาษของทุกใบไว้ในตัวเลือก ซึ่งเป็นรายชื่อที่วันหนึ่ง
   จะมีใบใหม่ตกหล่น

   ผูกกับ `.is-accent` เพราะนั่นแปลว่า "ใบนี้มีมุมสีจริง" — ใบที่ไม่มีมุมได้ความกว้าง
   เต็มบรรทัดคืน แทนที่จะเว้นที่ให้ของที่ไม่ได้ถูกวาด · ใบเสนอราคากับใบเสร็จติดคลาสนี้
   เสมอเพราะค่าตั้งต้นของมันมีสี (`doc_accent_default()`) ระยะจึงเท่าเดิมทุกใบ

   ความจำเพาะ 0,2,0 — ต่ำกว่ากฎของสไตล์โมเดิร์น (`.app-doc.is-modern .app-doc-head`
   = 0,3,0) ที่เว้น 3.5rem ให้ป้ายกลมแทนสามเหลี่ยมที่ถูกซ่อน ตัวนั้นจึงยังชนะตามเดิม
   **โดยไม่ขึ้นกับลำดับในไฟล์** */
.is-accent .app-doc-headrow {
  padding-right: calc(var(--doc-corner-size) + .5rem);
}

/* บล็อกหัวเรื่องต้องกว้างพอให้เลขที่เอกสารอยู่บรรทัดเดียว

   ค่าตั้งต้นของ flex คือให้ทั้งสองฝั่งหดตามสัดส่วน — ฝั่งซ้ายมีที่อยู่หลายบรรทัดจึงกินที่
   เกือบหมด แล้วคอลัมน์ค่าของตารางเมตาแคบจนเบราว์เซอร์ตัด `QT-2569-0004` เป็นสองบรรทัด
   ตรงขีด (เห็นชัดบน A5 ที่กระดาษแคบกว่า) · เลขที่เอกสารที่ขึ้นบรรทัดใหม่กลางเลขอ่านผิดได้

   ไม่ใช้ white-space: nowrap เพราะช่องเดียวกันนี้มี "ชื่องาน" ที่ยาวได้ถึง 255 ตัวอักษร
   ซึ่งต้องตัดบรรทัดได้ · การกันที่ไว้แก้ที่ต้นเหตุ ส่วน nowrap ย้ายปัญหาไปเป็นข้อความล้น */
.app-doc-titleblock {
  flex: 1 1 auto;
  min-width: 42%;
}

/* คู่กัน: ฝั่งซ้ายต้องยอมหดได้ ไม่งั้น min-width ข้างบนดันบล็อกซ้ายล้นออกนอกกระดาษ
   (fieldset/flex item ที่ไม่ได้ตั้ง min-width: 0 จะไม่หดต่ำกว่าความกว้างของเนื้อหา) */
.app-doc-head > :first-child {
  min-width: 0;
}

/* คอลัมน์ซ้ายของหัวกระดาษ = หัวร้าน + บล็อกลูกค้าซ้อนกัน (ใบเสนอราคา · ใบเสร็จ)

   ลูกค้าอยู่ *ในคอลัมน์จริง* จึงตัดบรรทัดที่ความกว้างเดียวกับที่อยู่ร้าน โดยไม่ต้องมี
   max-width คิดสัดส่วนเอง (สมัยที่ลากขึ้นมาด้วย translate ต้องมี ไม่งั้นทับตารางเมตา)

   **mb-4 ของ docparty.php เป็น !important ของ Bootstrap** — ต้องทับด้วย !important
   ส่วนระยะห่างจากหัวร้านเป็น margin-top ของบล็อกเอง ไม่ใช่ gap ของคอลัมน์ */
.app-doc-headleft .app-doc-partyblock {
  margin-top: 1.25rem;
  margin-bottom: 0 !important;
}

.app-doc-sheet.is-a5 .app-doc-headleft .app-doc-partyblock {
  margin-top: .75rem;
}

/* หนึ่งก้อนของที่อยู่ (คั่นด้วยช่องว่าง) ไม่ถูกหักกลางคำ — ดู doc_party_addr_html() */
.app-doc-nobr {
  display: inline-block;
  max-width: 100%;
}

.app-doc-sheet.is-a5 .app-doc-meta th {
  padding-right: .6rem;
}

/* หัว–ตัว–ท้ายของกระดาษหนึ่งแผ่น — .app-doc ต้องยืดเต็มแผ่นก่อน ตัวถึงจะมีที่ให้กิน */
.app-doc-body {
  flex: 1 1 auto;
}

.app-doc-foot {
  flex: 0 0 auto;
  margin-top: 1.5rem;
}

/* ---------- A5: ย่อทุกอย่างลงตามสัดส่วนกระดาษ (148/210 ≈ 70%) ----------

   ขนาดตัวอักษรในกระดาษเป็น rem ทั้งชุด ซึ่ง **ไม่ย่อตาม font-size ของแผ่น** จึงต้องระบุ
   ทีละตัว · ปล่อยไว้แล้วตารางรายการกับตารางเมตาจะล้นออกนอกความกว้าง 148mm */
.app-doc-sheet.is-a5 .app-doc-title {
  font-size: calc(1.25rem * var(--doc-fs));
}

/* ชื่อร้านใช้ .fs-4 ของ Bootstrap ซึ่งประกาศ !important มาเอง ทับได้ทางเดียวคือ !important
   (รูปเดียวกับใบส่งซ่อม) */
.app-doc-sheet.is-a5 .fs-4 {
  font-size: calc(1.05rem * var(--doc-fs)) !important;
}

.app-doc-sheet.is-a5 .app-doc-logo {
  max-width: 6.5rem;
  max-height: 2.5rem;
}

.app-doc-sheet.is-a5 .app-doc-meta,
.app-doc-sheet.is-a5 .app-doc-items,
.app-doc-sheet.is-a5 .app-doc-sum,
.app-doc-sheet.is-a5 .app-doc-label {
  font-size: calc(.75rem * var(--doc-fs));
}

.app-doc-sheet.is-a5 .app-doc-items thead th,
.app-doc-sheet.is-a5 .app-doc-items tbody td {
  padding: .3rem .35rem;
}

/* ตารางหกคอลัมน์บน A5 — ปลดความกว้างตายตัวของหัวตารางออกแล้วให้เบราว์เซอร์จัดเอง

   ความกว้างพวกนั้นเขียนเป็น `style="width: 8rem"` บน <th> ซึ่งเป็น **rem ที่ไม่ย่อตาม
   ขนาดกระดาษ** (ตัวหนังสือในตารางย่อลงบน A5 แต่ตัวเลขในแอตทริบิวต์ style ไม่ย่อ) ·
   ห้าคอลัมน์รวมกัน 24.5rem บนพื้นที่พิมพ์ 132mm เหลือให้ช่องรายละเอียดร้อยพิกเซลนิด ๆ
   เติมคอลัมน์ที่หกเข้าไปแล้วชื่อรายการพับสามสี่บรรทัด ดันใบไปหน้าที่สอง (วัดจริงแล้ว)

   **ต้อง !important เพราะทับ inline style** ซึ่งเป็นข้อยกเว้นที่โปรเจกต์เลี่ยงมาตลอด —
   ทางที่สะอาดกว่าคือย้ายความกว้างทั้งห้าไปเป็นคลาส แต่นั่นแตะกระดาษที่พิมพ์ถูกอยู่แล้ว
   ทั้งสองใบเพื่อแก้เคสที่เกิดเฉพาะตอนเปิดคอลัมน์ที่หกบนกระดาษที่เล็กกว่า

   **จำกัดด้วย .has-unit** ตารางห้าคอลัมน์ของร้านที่ไม่ได้เปิดจึงไม่ถูกแตะเลยสักพิกเซล */
.app-doc-sheet.is-a5 .app-doc-items.has-unit thead th {
  width: auto !important;
}

.app-doc-sheet.is-a5 .app-doc-pay,
.app-doc-sheet.is-a5 .app-doc-sign-cap {
  font-size: calc(.6875rem * var(--doc-fs));
}

/* A5 แคบเกินกว่าจะวางประโยคนำกับช่องติ๊กสี่ช่องในบรรทัดเดียว — ดันประโยคขึ้นบรรทัดของตัวเอง
   แล้วช่องติ๊กทั้งสี่ลงมาเรียงกันได้พอดี · ปล่อยไว้จะได้แถวที่พับสี่บรรทัดโดยมี "อื่น ๆ"
   ห้อยอยู่ตัวเดียว ซึ่งกินความสูงที่กระดาษ A5 ไม่มีให้ */
.app-doc-sheet.is-a5 .app-doc-pay-lead {
  flex: 0 0 100%;
}

.app-doc-sheet.is-a5 .app-doc-fill {
  min-width: 4rem;
}

/* หัวกระดาษกับท้ายกระดาษบีบเข้าหากันบน A5 — สัดส่วนเดิมทำมาเพื่อ A4 ที่สูงกว่า 40% */
.app-doc-sheet.is-a5 .app-doc-head {
  margin-bottom: .75rem !important;
}


.app-doc-sheet.is-a5 .app-doc-sign-line {
  height: 1.5rem;
  max-width: 11rem;
}

.app-doc-sheet.is-a5 .app-doc-sign-paren,
.app-doc-sheet.is-a5 .app-doc-sign-date {
  font-size: calc(.6875rem * var(--doc-fs));
}

.app-doc-sheet.is-a5 .app-doc-sign-paren { margin-top: .2rem; }
.app-doc-sheet.is-a5 .app-doc-sign-date  { margin-top: .35rem; }

/* ครึ่งกระดาษ A5 กว้าง ~66mm ต่อฝั่ง — เส้นชื่อ 9rem บวกวงเล็บล้นออกนอกคอลัมน์ */
.app-doc-sheet.is-a5 .app-doc-sign-name {
  min-width: 6rem;
}

.app-doc-sheet.is-a5 .app-doc-foot {
  margin-top: 1rem;
}

/* ท้ายกระดาษบน A5 บีบทุกระยะลง — แผ่นนี้เต็มอยู่แล้วโดยเฉพาะใบเสร็จที่มีทั้งแถวติ๊ก
   วิธีชำระและบล็อกเซ็นอยู่ในท้ายกระดาษ · สองบรรทัดที่เติมเข้ามาดันใบไปอีกแผ่นได้จริง */
.app-doc-sheet.is-a5 .app-doc-footer {
  margin-top: .3rem;
  padding-top: .25rem;
  font-size: calc(.625rem * var(--doc-fs));
  line-height: 1.35;
}

/* ---------- แบบฟอร์มเอกสาร: ใบเสนอราคา · ใบเสร็จรับเงิน ----------

   สองใบนี้เป็นกระดาษที่ส่งออกไปนอกร้าน จึงจัดหน้าตามแบบฟอร์มที่ร้านใช้จริง —
   หัวเรื่องตัวใหญ่ทางขวาคู่กับตารางเลขที่/วันที่ · หัวข้อย่อยเป็นสีของเอกสาร ·
   ปิดท้ายด้วยบล็อกเซ็นชื่อสองฝั่ง

   **สีเป็นของชนิดเอกสาร ไม่ใช่ค่าตั้งค่า** — ใบเสนอราคาส้ม ใบเสร็จเขียว
   คนหน้าร้านคว้ากระดาษจากถาดเครื่องพิมพ์แล้วรู้ทันทีว่าเป็นใบอะไรโดยไม่ต้องอ่าน
   ปล่อยให้ตั้งเองได้เมื่อไร ประโยชน์ข้อนี้หายทันทีที่มีใครตั้งสองใบเป็นสีเดียวกัน */
.app-doc {
  position: relative;
  --doc-accent: #64748B;

  /* ---------- สีที่แยกออกจากสีเน้นได้ (application/doclook/) ----------

     ทั้งสามตัวตั้งต้นเท่าที่กระดาษพิมพ์ออกมาวันนี้เป๊ะ — มุมกระดาษใช้สีเน้นเดียวกับหัวเรื่อง
     เส้นเป็นดำ และหัวตารางไม่มีพื้น · ร้านที่ไม่ตั้งอะไรจึงได้ใบเดิมทุกพิกเซล และค่าที่ตั้ง
     ถูกฉีดเป็น inline style ทับตรงนี้ตามลำดับความสำคัญปกติ **ไม่ต้อง !important**

     แยกออกมาเพราะสามอย่างนี้ทำคนละหน้าที่: มุมเป็นแถบสีทึบที่ใช้แยกใบจากถาดเครื่องพิมพ์ ·
     เส้นเป็นโครงของตาราง ซึ่งร้านที่อยากได้กระดาษจืด ๆ ต้องการให้จางลงโดยที่หัวเรื่อง
     ยังเข้ม · พื้นหัวตารางเป็นของประดับที่ **ไม่รับประกันว่าจะพิมพ์ออก** (เบราว์เซอร์ปิด
     การพิมพ์ภาพพื้นหลังไว้เป็นค่าตั้งต้น) จึงต้องเป็นค่าที่ปิดไว้ก่อนเสมอ

     --doc-corner อ้าง var(--doc-accent) ไม่ใช่ค่าคงที่ — custom property ถูกแทนค่าตอน
     ใช้งานจริง ใบที่ทับ --doc-accent (is-quotation / is-receipt / inline style) จึงได้
     มุมสีเดียวกับหัวเรื่องเองโดยไม่ต้องประกาศซ้ำ */
  --doc-corner: var(--doc-accent);
  --doc-rule: #000;
  --doc-thead-bg: transparent;

  /* ขนาดสามเหลี่ยมมุม — ดู .app-doc-corner · ค่าของใบเต็มหน้า A4 */
  --doc-corner-size: 3.25rem;

  /* ---------- ระยะที่สามเหลี่ยมมุมเลื่อนออกไปหามุมแผ่น ----------

     สองใบนี้ต่างจากใบส่งซ่อม/ใบรับเครื่องคืนตรงชั้นเดียว: ที่นั่นสามเหลี่ยมเกาะ **ตัวใบ**
     ซึ่งเป็นก้อนนอกสุดอยู่แล้ว ส่วนที่นี่ .app-doc เป็น **ชั้นในของแผ่น** มุมจึงไปนั่ง
     ห่างจากขอบแผ่นเท่ากับ padding ของมันบนจอ · เลื่อนกลับออกไปเท่านั้นพอดีแล้วได้
     มุมชิดขอบแผ่นเหมือนกันทั้งสี่ใบ

     ตอนพิมพ์บล็อก `@media print` สั่งค่านี้เป็น 0 มุมจึงกลับมาเกาะขอบพื้นที่พิมพ์
     เหมือนเดิมทุกประการ · **เนื้อหาไม่ขยับสักมิลลิเมตร** เพราะ padding-right ที่เว้นที่ให้มุมยังเท่าเดิม —
     สิ่งที่เห็นบนจอกับสิ่งที่ออกจากเครื่องพิมพ์จึงยังวางตรงกันทุกก้อน ยกเว้นตัวสามเหลี่ยมเอง

     ค่าตกกลับ 0mm มีไว้ให้แผ่นที่ไม่ใช่ .app-doc-sheet — ใบเสร็จที่ handover.php
     แนบท้ายอยู่บน .app-print-sheet เปล่า ๆ ซึ่งไม่มีขอบให้เลื่อนออกไปหา */
  --doc-corner-bleed: var(--doc-sheet-pad, 0mm);

  /* ---------- ขนาดตัวอักษรทั้งแผ่นด้วยตัวแปรตัวเดียว ----------

     ทุก font-size ในขอบเขตกระดาษเขียนเป็น `calc(<ค่าเดิม> * var(--doc-fs))` คลาสขนาด
     จึงตั้งแค่ตัวแปรตัวนี้ตัวเดียว แล้วได้ทั้ง A4 และ A5 พร้อมกัน **โดยไม่ต้องเขียนบล็อก
     ซ้ำสี่ชุด** (เล็ก/ใหญ่ × A4/A5) ซึ่งเป็นชุดที่วันหนึ่งจะมีอันหนึ่งไม่ตรงกับอันอื่น

     เดิมทำไม่ได้เพราะขนาดในกระดาษเป็น rem ล้วน ซึ่งอิงรากของหน้า **ไม่ไหลตาม font-size
     ของแผ่น** — ตั้งขนาดที่แผ่นแล้วหวังให้ลูกย่อตามจึงไม่เกิดอะไรขึ้นเลย

     **ตัวแปรอยู่ที่ .app-doc ไม่ใช่ที่ .app-doc-sheet** เพราะใบเสร็จให้ผู้เรียกเป็นเจ้าของ
     แผ่นเอง (handover.php แนบใบเสร็จเป็นแผ่นที่สาม) ตัวกระดาษจึงติดคลาสบนแผ่นไม่ได้ ·
     ที่ .app-doc ทั้งสองใบเป็นเจ้าของเองเสมอ

     ค่าตั้งต้น 1 = ขนาดเดิมทุกตัว · ขนาด "ปกติ" ไม่มีคลาสของตัวเอง ร้านที่ไม่แตะจึงได้
     HTML เดิมเป๊ะ ไม่ใช่ HTML ที่มีคลาสเพิ่มมาแล้วบังเอิญไม่มีกฎ */
  --doc-fs-root: 1;
  --doc-fs: var(--doc-fs-root);

  /* ขนาดพื้นของเนื้อความ (เรื่อง · จำนวนเงินตัวอักษร · หมายเหตุ · บล็อกลูกค้า) —
     A4 เคยสืบทอดจาก body เฉย ๆ ค่าที่ใส่จึงเท่ากับ --bs-body-font-size เป๊ะ
     ตอน --doc-fs: 1 · A5 มีกฎของตัวเองอยู่ข้างล่างซึ่ง specificity สูงกว่า */
  font-size: calc(.9375rem * var(--doc-fs));

  /* ยืดเต็มแผ่นที่เป็น flex column อยู่แล้ว — ไม่มีบรรทัดนี้ .app-doc-body จะไม่มีที่ให้กิน
     และท้ายกระดาษจะลอยขึ้นมาชนรายการเหมือนเดิม */
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
}

/* สีอ่อนสองระดับกับสีตัวหนังสือเข้ม เขียนค่าไว้ตรง ๆ ไม่ได้คำนวณด้วย color-mix() —
   เบราว์เซอร์ที่ยังไม่รองรับจะได้ตัวแปรที่ใช้ไม่ได้ แล้วแถบสีหายไปทั้งแถบโดยไม่มีอะไรฟ้อง
   ค่าพวกนี้ถูกใช้เฉพาะสไตล์ที่มีพื้นสี (โมเดิร์น) สไตล์คลาสสิกไม่แตะเลยสักตัว */
.app-doc.is-quotation {
  --doc-accent: #EA8C2A;
  --doc-tint:   #FDF2E5;
  --doc-tint-2: #F8E1C6;
  --doc-ink:    #8A4A08;
}

.app-doc.is-receipt {
  --doc-accent: #2E9E5B;
  --doc-tint:   #EAF6EF;
  --doc-tint-2: #D3ECDD;
  --doc-ink:    #145C38;
}

/* ช่วงแคบโดยตั้งใจ (−8% / +5%) — เป้าหมายคือ *ให้พออ่านออก* กับ *ให้บรรทัดเยอะ ๆ ลง
   หน้าเดียว* ไม่ใช่การจัดหน้าใหม่ · **ฝั่งใหญ่แคบกว่าเพราะวัดมาแล้ว**: ที่ 1.08 ใบเสร็จ A5
   ที่เปิดคอลัมน์ครบพร้อมท้ายกระดาษสองบรรทัดกลายเป็นสี่หน้า ที่ 1.05 ยังสองหน้าตามเดิม ·
   **ค่าต้องตรงกับ doc_font_scales() ใน application/doclook/look.php** ซึ่งเป็นตัวที่
   หน้าตั้งค่าอ่านไปทำรายการให้เลือก — ไฟล์นี้เป็นคนใช้ค่านั้นจริง */
.app-doc.is-fs-sm { --doc-fs-root: .92; }
.app-doc.is-fs-lg { --doc-fs-root: 1.05; }

/* A5 ของขนาดพื้น — ต้องมีกฎของตัวเองเพราะ .app-doc-sheet.is-a5 ตั้ง font-size ไว้ที่
   *แผ่น* ซึ่งแพ้ .app-doc ที่อยู่ข้างใน (คนละ element ไม่ใช่ specificity) */
.app-doc-sheet.is-a5 .app-doc {
  font-size: calc(.8125rem * var(--doc-fs));

  /* มุมย่อตามกระดาษ — 3.25rem ของ A4 กินความสูงเกือบหนึ่งเซนติเมตรบนแผ่นที่เตี้ยกว่า 40%
     · ค่าเดียวกับใบส่งซ่อมซึ่งเป็นครึ่งแผ่นเหมือนกัน */
  --doc-corner-size: 2.25rem;
}

/* ---------- ข้อความท้ายกระดาษของร้าน (company.doc_footer) ----------

   คนละก้อนกับ "เงื่อนไข" ของใบซึ่งเป็นของใบนั้นใบเดียว — ตัวนี้เป็นเว็บไซต์ ไลน์ไอดี
   หรือคำขอบคุณที่เหมือนกันทุกใบ จึงอยู่ล่างสุดใต้บล็อกเซ็นชื่อ ซึ่งเป็นที่ที่แบบฟอร์มจริง
   วางข้อความแบบนี้ · ว่างไว้ = ไม่ render อะไรเลย ไม่ใช่กล่องว่างที่กินที่ */
.app-doc-footer {
  margin-top: .5rem;
  padding-top: .35rem;
  border-top: 1px solid var(--app-border);
  font-size: calc(.75rem * var(--doc-fs));
  text-align: center;
  white-space: pre-line;
  color: var(--app-muted-fg);
}

/* ---------- ตำแหน่งบล็อกบนกระดาษเป็นแบบตายตัว (29 ก.ย. 2026) ----------

   เคยมีกฎ translate(var(--lay-<บล็อก>-x/y)) ให้ร้านลากบล็อกไปวางเอง — ถอดออกทั้งชุด
   เพราะ translate ไม่เปลี่ยนความกว้างใน flow ข้อความที่ตัดบรรทัดใหม่จึงทับกันเอง
   (ที่อยู่ลูกค้าทับเลขผู้เสียภาษีของร้าน) · ตำแหน่งจริงอยู่ใน markup ของกระดาษแล้ว
   ที่ยังปรับได้คือความยาวเส้นกับขนาดตัวอักษรรายบล็อก ดู doclook/layout.php */

/* ขนาดตัวอักษรรายบล็อก — doc_layout_vars() ฉีด --fs-<บล็อก> ที่ตัวใบ (doc_layouts.fs)
   :where() = ความจำเพาะ 0 บล็อกที่มี font-size ของตัวเองจึงชนะ แล้วย่อผ่าน --doc-fs แทน */
:where(.app-doc) :where(.app-doc-shopblock) { --doc-fs: calc(var(--doc-fs-root, 1) * var(--fs-shop, 1)); font-size: calc(1em * var(--fs-shop, 1)); }
:where(.app-doc) :where(.app-doc-titleblock) { --doc-fs: calc(var(--doc-fs-root, 1) * var(--fs-title, 1)); font-size: calc(1em * var(--fs-title, 1)); }
:where(.app-doc) :where(.app-doc-partyblock) { --doc-fs: calc(var(--doc-fs-root, 1) * var(--fs-party, 1)); font-size: calc(1em * var(--fs-party, 1)); }
:where(.app-doc) :where(.app-doc-items) { --doc-fs: calc(var(--doc-fs-root, 1) * var(--fs-items, 1)); font-size: calc(1em * var(--fs-items, 1)); }
:where(.app-doc) :where(.app-doc-sumbar) { --doc-fs: calc(var(--doc-fs-root, 1) * var(--fs-sum, 1)); font-size: calc(1em * var(--fs-sum, 1)); }
:where(.app-doc) :where(.app-doc-baht) { --doc-fs: calc(var(--doc-fs-root, 1) * var(--fs-baht, 1)); font-size: calc(1em * var(--fs-baht, 1)); }
:where(.app-doc) :where(.app-doc-note) { --doc-fs: calc(var(--doc-fs-root, 1) * var(--fs-note, 1)); font-size: calc(1em * var(--fs-note, 1)); }
:where(.app-doc) :where(.app-doc-pay) { --doc-fs: calc(var(--doc-fs-root, 1) * var(--fs-pay, 1)); font-size: calc(1em * var(--fs-pay, 1)); }
:where(.app-doc) :where(.app-doc-signblock) { --doc-fs: calc(var(--doc-fs-root, 1) * var(--fs-sign, 1)); font-size: calc(1em * var(--fs-sign, 1)); }
:where(.app-doc) :where(.app-doc-footer) { --doc-fs: calc(var(--doc-fs-root, 1) * var(--fs-footer, 1)); font-size: calc(1em * var(--fs-footer, 1)); }
/* ชื่อร้าน A4 ใช้ .fs-4 ของ Bootstrap (!important) — ค่าเดียวกับ Bootstrap ตอน 100% · A5 มีกฎที่จำเพาะกว่าอยู่แล้ว */
:where(.app-doc) :where(.app-doc-shopblock) .fs-4 { font-size: calc((1.275rem + .3vw) * var(--fs-shop, 1)) !important; }
@media (min-width: 1200px) {
  :where(.app-doc) :where(.app-doc-shopblock) .fs-4 { font-size: calc(1.5rem * var(--fs-shop, 1)) !important; }
}
:where(.app-slip) :where(.app-doc-shopblock) { --doc-fs: calc(var(--doc-fs-root, 1) * var(--fs-shop, 1)); font-size: calc(1em * var(--fs-shop, 1)); }
:where(.app-slip) :where(.app-slip-metablock) { --doc-fs: calc(var(--doc-fs-root, 1) * var(--fs-meta, 1)); font-size: calc(1em * var(--fs-meta, 1)); }
:where(.app-slip) :where(.app-slip-partyblock) { --doc-fs: calc(var(--doc-fs-root, 1) * var(--fs-party, 1)); font-size: calc(1em * var(--fs-party, 1)); }
:where(.app-slip) :where(.app-slip-machineblock) { --doc-fs: calc(var(--doc-fs-root, 1) * var(--fs-machine, 1)); font-size: calc(1em * var(--fs-machine, 1)); }
:where(.app-slip) :where(.app-slip-symptom) { --doc-fs: calc(var(--doc-fs-root, 1) * var(--fs-symptom, 1)); font-size: calc(1em * var(--fs-symptom, 1)); }
:where(.app-slip) :where(.app-slip-note) { --doc-fs: calc(var(--doc-fs-root, 1) * var(--fs-note, 1)); font-size: calc(1em * var(--fs-note, 1)); }
:where(.app-slip) :where(.app-slip-terms) { --doc-fs: calc(var(--doc-fs-root, 1) * var(--fs-terms, 1)); font-size: calc(1em * var(--fs-terms, 1)); }
:where(.app-slip) :where(.app-doc-signblock) { --doc-fs: calc(var(--doc-fs-root, 1) * var(--fs-sign, 1)); font-size: calc(1em * var(--fs-sign, 1)); }
:where(.app-handover) :where(.app-doc-shopblock) { --doc-fs: calc(var(--doc-fs-root, 1) * var(--fs-shop, 1)); font-size: calc(1em * var(--fs-shop, 1)); }
:where(.app-handover) :where(.app-handover-metablock) { --doc-fs: calc(var(--doc-fs-root, 1) * var(--fs-meta, 1)); font-size: calc(1em * var(--fs-meta, 1)); }
:where(.app-handover) :where(.app-handover-partyblock) { --doc-fs: calc(var(--doc-fs-root, 1) * var(--fs-party, 1)); font-size: calc(1em * var(--fs-party, 1)); }
:where(.app-handover) :where(.app-handover-machineblock) { --doc-fs: calc(var(--doc-fs-root, 1) * var(--fs-machine, 1)); font-size: calc(1em * var(--fs-machine, 1)); }
:where(.app-handover) :where(.app-handover-story) { --doc-fs: calc(var(--doc-fs-root, 1) * var(--fs-story, 1)); font-size: calc(1em * var(--fs-story, 1)); }
:where(.app-handover) :where(.app-handover-items) { --doc-fs: calc(var(--doc-fs-root, 1) * var(--fs-items, 1)); font-size: calc(1em * var(--fs-items, 1)); }
:where(.app-handover) :where(.app-handover-warranty) { --doc-fs: calc(var(--doc-fs-root, 1) * var(--fs-warranty, 1)); font-size: calc(1em * var(--fs-warranty, 1)); }
:where(.app-handover) :where(.app-handover-sum) { --doc-fs: calc(var(--doc-fs-root, 1) * var(--fs-sum, 1)); font-size: calc(1em * var(--fs-sum, 1)); }
:where(.app-handover) :where(.app-slip-note) { --doc-fs: calc(var(--doc-fs-root, 1) * var(--fs-note, 1)); font-size: calc(1em * var(--fs-note, 1)); }
:where(.app-handover) :where(.app-handover-terms) { --doc-fs: calc(var(--doc-fs-root, 1) * var(--fs-terms, 1)); font-size: calc(1em * var(--fs-terms, 1)); }
:where(.app-handover) :where(.app-doc-signblock) { --doc-fs: calc(var(--doc-fs-root, 1) * var(--fs-sign, 1)); font-size: calc(1em * var(--fs-sign, 1)); }

/* สามเหลี่ยมมุมขวาบน ทำจาก border ไม่ใช่ background โดยตั้งใจ — พื้นหลังหายทั้งหมด
   เมื่อเบราว์เซอร์ไม่ได้เปิด "พิมพ์ภาพพื้นหลัง" ซึ่งเป็นค่าตั้งต้น ส่วน border พิมพ์ออกเสมอ
   (บทเรียนเดียวกับเส้นบรรทัดในกล่องอาการของใบส่งซ่อม)

   **กฎเดียวของทั้งสี่ใบ ขนาดมาจาก `--doc-corner-size` ที่ *ตัวใบ* ประกาศ** เคยเป็นสามกฎ
   ที่พิมพ์ขนาดไว้ตรง ๆ (ใบเต็มหน้า 3.25rem · A5 2.25rem · ใบส่งซ่อม 2.25rem) ซึ่งอ่าน
   เหมือนสามค่าที่ไม่เกี่ยวกัน ทั้งที่กติกาจริงมีข้อเดียวคือ **มุมย่อตามขนาดกระดาษ ไม่ใช่
   ตามชนิดใบ** — ใบรับเครื่องคืนได้ 3.25rem เท่าใบเต็มหน้าเพราะมันเป็น A4 เต็มแผ่น
   ส่วนใบส่งซ่อมได้ 2.25rem เพราะสูงคงที่ 134mm เท่าครึ่งแผ่น

   ตัวแปรอยู่ที่ตัวใบไม่ใช่ที่แผ่น เพราะใบส่งซ่อมมีสองใบบนแผ่น A4 แผ่นเดียว —
   เหตุผลเดียวกับที่ `position: relative` ต้องอยู่ที่ตัวใบ · กระดาษชนิดใหม่จึงประกาศ
   ตัวแปรตัวเดียวจบ ไม่ต้องเขียนกฎ border-width ของตัวเอง และ `padding-right` ของ
   หัวกระดาษตามมาให้เองจากสูตรเดียวกัน */
.app-doc-corner {
  position: absolute;
  top: 0;
  right: 0;
  width: 0;
  height: 0;
  border-top: var(--doc-corner-size) solid var(--doc-corner);
  border-left: var(--doc-corner-size) solid transparent;
}


/* ---------- ใบเสนอราคา/ใบเสร็จ: เลื่อนมุมออกไปชิดขอบแผ่น ----------

   ครอบด้วย .app-doc เพราะใบส่งซ่อม/ใบรับเครื่องคืนไม่ได้อยู่ในนั้นและไม่ต้องเลื่อน
   (มุมของสองใบนั้นเกาะตัวใบซึ่งชิดขอบอยู่แล้ว) — ได้ CSS เดิมเป๊ะ ไม่ใช่กฎที่เพิ่มมา
   แล้วบังเอิญคำนวณได้ศูนย์

   สองคลาสจึงชนะ `top`/`right` ของกฎฐาน (0,1,0) และไม่มีกฎไหนทับสองค่านี้ของสามเหลี่ยม
   อีกเลย — เคยมีเลขแผ่นที่ถูกทับไว้สามชุด (A5 · โมเดิร์น · A5 + โมเดิร์น) ซึ่งบังคับให้
   ต้องเลื่อนด้วย transform แทน ตอนนี้เลขนั้นถูกถอดออกไปแล้ว เหลือกลไกเดียวที่ตรงกว่า */
.app-doc .app-doc-corner {
  top: calc(-1 * var(--doc-corner-bleed));
  right: calc(-1 * var(--doc-corner-bleed));
}

/* ---------- ตอนพิมพ์ มุมกลับไปเกาะขอบพื้นที่พิมพ์เหมือนเดิมทุกประการ ----------

   **ต้องสั่งเป็น 0 ตรง ๆ ปล่อยให้ตกตาม `--doc-sheet-pad` ไม่ได้** — `.app-doc-sheet.is-a5`
   (0,2,0) ตั้งตัวแปรนั้นเป็น 8mm ทับกฎฐาน (0,1,0) อยู่ **ในทุกสื่อ** เพราะ media query
   ไม่เพิ่มความจำเพาะให้ใคร · ไม่สั่งแล้วสามเหลี่ยมของใบ A5 จะถูกดันออกไปนอกกรอบแผ่น
   8mm ซึ่งคือระยะขอบของ @page พอดี แล้วโดนตัดหายทั้งอัน

   **และต้องอยู่ตรงนี้ ไม่ใช่ในบล็อก @media print ที่อยู่ท้าย .app-doc-sheet** — ตรงนั้น
   มาก่อน `.app-doc` ในไฟล์ ความจำเพาะเท่ากัน (0,1,0) ตัวที่มาทีหลังจึงชนะ กฎเลยไม่มีผล
   อะไรเลยทั้งที่อ่านดูเหมือนมี (เจอจริงตอนวัด ไม่ใช่ตอนอ่านโค้ด) */
@media print {
  .app-doc {
    --doc-corner-bleed: 0mm;
  }
}

.app-doc-logo {
  max-width: 9rem;
  max-height: 3.5rem;
  margin-bottom: .35rem;
}

/* หัวกระดาษฝั่งร้าน — แถวโลโก้กับชื่อร้าน

   คลาสนี้ติดเฉพาะตอนที่บริษัทอัปโลโก้ไว้จริง (docprinthead.php) ร้านที่ไม่มีโลโก้จึงได้
   HTML เดิมเป๊ะ ไม่ใช่ HTML ที่มีคลาสเพิ่มมาแล้วบังเอิญไม่มีกฎ

   **แถวนี้มีแค่โลโก้กับชื่อ** ที่อยู่ เบอร์โทร และเลขภาษีอยู่นอกแถว จึงเริ่มที่ขอบซ้าย
   เต็มความกว้างเหมือนก่อนมีการย้ายโลโก้ — รายละเอียดหลายบรรทัดที่ถูกเยื้องเข้าไป
   เท่าความกว้างโลโก้จะเสียที่ไปฟรี ๆ และตัดบรรทัดถี่ขึ้นมากโดยไม่ได้อ่านง่ายขึ้นเลย

   center ไม่ใช่ flex-start เพราะแถวนี้มีของสองชิ้นที่สูงไม่เท่ากันและเป็นเรื่องเดียวกัน
   (ตราของร้านกับชื่อของร้าน) — ยึดขอบบนแล้วชื่อบรรทัดเดียวจะไปเกาะหัวโลโก้
   ดูเหมือนสองอย่างที่บังเอิญวางใกล้กัน */
.app-doc-shop {
  display: flex;
  align-items: center;
  gap: .85rem;
}

.app-doc-shop .app-doc-logo {
  /* ระยะห่างเป็นหน้าที่ของ gap แล้ว · ล็อกไม่ให้หด เพราะตัวที่ต้องยอมคือชื่อ (ย่อฟอนต์)
     ไม่ใช่ตรา ซึ่งบีบแล้วเสียสัดส่วน */
  flex: 0 0 auto;
  margin-bottom: 0;
}

/* ชื่อร้านอยู่บรรทัดเดียวเสมอ — ยาวเกินก็ย่อฟอนต์ ไม่ตัดบรรทัด

   ย่อด้วย em บน <span> ข้างใน **ไม่ใช่ค่าคงที่บนตัว div** เพราะ .fs-4 ของชื่อร้านถูกทับ
   ด้วย !important อยู่แล้วสามที่ (A5 · ใบส่งซ่อม · สลิป 80mm) แต่ละเปลือกจึงมีขนาดฐาน
   ของตัวเอง · em คูณกับขนาดที่เปลือกนั้นตัดสินมาแล้ว ทุกกระดาษจึงย่อตามสัดส่วนเดียวกัน
   โดยไม่ต้องลอกค่าไหนมาซ้ำ และไม่ต้องใช้ !important สักตัว

   ขั้นการย่อมาจากการวัดจริงบนกระดาษ A4 — ที่ .78em ชื่อยาว 25 หน่วยความกว้างยังลง
   บรรทัดเดียวพอดี · ต่ำกว่า .68em ชื่อร้านจะเล็กกว่าที่อยู่ใต้มัน ซึ่งอ่านเหมือนจัดหน้าพลาด
   ชื่อที่ยาวกว่านั้นจึงยอมให้ล้นออกไปแทน (ไม่มีร้านไหนยาวขนาดนั้นในทางปฏิบัติ) */
.app-doc-shop-name {
  white-space: nowrap;
  min-width: 0;
}

.app-doc-shop-fit.is-fit-1 { font-size: .88em; }
.app-doc-shop-fit.is-fit-2 { font-size: .78em; }
.app-doc-shop-fit.is-fit-3 { font-size: .68em; }

/* ย่อจนสุดขั้นแล้วยังไม่ลง — ปลดการห้ามตัดบรรทัดทิ้ง ยอมสองบรรทัดดีกว่าย่อต่อ
   ต่ำกว่า .68em ชื่อร้านจะเล็กกว่าที่อยู่ใต้มัน (.small = .875rem) ซึ่งอ่านเหมือน
   จัดหน้าพลาด ไม่ใช่เหมือนตั้งใจ · ตัวเลือกที่เหลือคือปล่อยให้ล้นออกไปทับตารางเลขที่
   ทางขวา ซึ่งแย่กว่าทั้งสองทาง */
.app-doc-shop-fit.is-fit-wrap {
  white-space: normal;
}

/* **A5 วางโลโก้ข้างชื่อเหมือน A4 แล้ว** (6 ก.ย. 2026)

   เดิม A5 ซ้อนบนล่าง โดยให้เหตุผลว่าโลโก้ที่ไปนั่งข้างชื่อกินคอลัมน์ซ้ายจนที่อยู่ตัดบรรทัด
   จาก 6 เป็น 9 — **เหตุผลนั้นหมดอายุไปแล้วในวันเดียวกัน** ตอนที่ `.app-doc-shop`
   ถูกทำให้เป็นแถวที่มี *แค่โลโก้กับชื่อ* ส่วนที่อยู่ เบอร์โทร เลขภาษี ย้ายออกไปนอกแถว
   จึงกินความกว้างเต็มบล็อกเหมือนเดิม · สิ่งที่แคบลงจริงเหลือแค่คอลัมน์ชื่อร้าน ซึ่งเป็น
   สิ่งที่ตัวย่อฟอนต์ใน docprinthead.php มีหน้าที่จัดการอยู่แล้ว

   ที่ต้องแก้คู่กันคือตัวย่อนั้นต้องรู้เรขาคณิตของ A5 (บล็อกซ้าย 240px · ฟอนต์ 16.8px)
   ไม่ใช่คิดด้วยค่าคงที่ของ A4 ตัวเดียว — **วัดจริงด้วย Chrome headless แล้ว**
   ค่า A4 ที่วัดได้คือ 348.95px ซึ่งตรงกับ 349 ที่ฝังไว้เดิมเป๊ะ

   **สลิป 80mm ยังซ้อนบนล่างต่อไป** กระดาษกว้าง 72mm ที่เอาโลโก้ 34mm ไปวางข้างชื่อ
   จะเหลือที่ให้ชื่อไม่ถึงครึ่ง และ .app-slip80-shop จัดกลางทั้งก้อนอยู่แล้ว แถวแนวนอน
   จึงขัดกับมันสองทาง · **ใบส่งซ่อมกับใบรับเครื่องคืนไม่เคยอยู่ในรายการนี้** ทั้งคู่เป็น
   A4 เต็มความกว้าง (`@page repair-slip { size: A4 portrait }` วางสองใบซ้อนแนวตั้ง)
   ไม่ใช่ A5 อย่างที่ชื่อ "ครึ่งแผ่น" ชวนให้เข้าใจ */
.app-slip80-shop .app-doc-shop {
  display: block;
}

.app-slip80-shop .app-doc-shop .app-doc-logo {
  margin-bottom: .35rem;
}

/* ซ้อนบนล่างแล้วชื่อได้ความกว้างเต็มบล็อก — การย่อฟอนต์กับการห้ามตัดบรรทัดจึงกลายเป็น
   โทษล้วน ๆ · ชื่อยาวบนสลิป 72mm ที่ห้ามตัดบรรทัดจะล้นออกนอกม้วนกระดาษไปเลย */
.app-slip80-shop .app-doc-shop-name {
  white-space: normal;
}

.app-slip80-shop .app-doc-shop-fit {
  font-size: 1em;
}

.app-doc-title {
  font-size: calc(1.75rem * var(--doc-fs));
  font-weight: 600;
  line-height: 1.2;
  color: var(--doc-accent);
}

/* ชื่อภาษาอังกฤษกำกับใต้ชื่อไทย (doclook.title_en) — ตัวเล็กกว่ามากและไม่หนา เพราะเป็น
   คำแปลกำกับ ไม่ใช่หัวเรื่องที่สอง · ปิดไว้เป็นค่าตั้งต้น ใบที่ไม่ได้เปิดจึงไม่มี element นี้
   เลย ไม่ใช่มีแล้วซ่อน */
.app-doc-title-en {
  font-size: calc(.8125rem * var(--doc-fs));
  font-weight: 400;
  letter-spacing: .06em;
  line-height: 1.2;
  color: var(--doc-accent);
}

.app-doc-sheet.is-a5 .app-doc-title-en {
  font-size: calc(.6875rem * var(--doc-fs));
}

/* เส้นใต้ชื่อเอกสารยาวเท่าตารางเมตาที่อยู่ข้างล่าง ไม่ใช่เท่าความยาวของคำ —
   สองอย่างนี้เป็นก้อนเดียวกันในสายตา เส้นที่สั้นกว่าจะดูเหมือนขีดฆ่ามากกว่าขีดคั่น */
.app-doc-titlebar {
  padding-bottom: .35rem;
}

/* margin-left: auto — ตั้งความยาวแล้วเส้นหดจากซ้าย ยังชิดขวาตามชื่อเอกสาร */
.app-doc-titlerule {
  border-top: 1px solid var(--doc-rule);
  margin-left: auto;
  margin-bottom: .5rem;
}

/* ตารางเลขที่/วันที่/ผู้ขาย — ป้ายสีเน้น ค่าตัวหนา ไม่มีเส้นตาราง

   **กว้างเท่าเนื้อหาแล้วดันไปชิดขวา ไม่ใช่ width: 100%** — ตอนที่กว้างเต็มบล็อก ป้าย
   ไปเกาะขอบซ้ายสุดแล้วคอลัมน์ค่าจบกลางทาง เหลือที่ว่างค้างอยู่ทางขวาใต้ชื่อเอกสาร
   ซึ่งชิดขวาอยู่ · ทั้งก้อนจึงดูเหมือนหลุดไปคนละฝั่งกับหัวเรื่องของตัวเอง

   `width: auto` ของตารางยังยุบลงมาเท่าความกว้างที่มีให้เมื่อเนื้อหายาวเกิน (auto table
   layout) — ชื่องานยาว ๆ จึงยังตัดบรรทัดในกรอบเหมือนเดิม ไม่ล้นออกนอกกระดาษ ·
   ข้อความข้างในยังชิดซ้ายตามเดิม ป้ายกับค่าจึงยังเรียงตรงกันเป็นคอลัมน์ */
.app-doc-meta {
  width: auto;
  margin-left: auto;
  font-size: calc(.875rem * var(--doc-fs));
}

.app-doc-meta th {
  color: var(--doc-accent);
  font-weight: 400;
  white-space: nowrap;
  padding: 0 1rem .15rem 0;
  vertical-align: top;
}

.app-doc-meta td {
  padding: 0 0 .15rem;
  vertical-align: top;
}

/* ช่องว่างระหว่างสองก้อนของตารางเมตา (เลขที่–วันที่–ผู้ขาย กับ ชื่องาน–ผู้ติดต่อ) */
.app-doc-meta .app-doc-meta-gap th,
.app-doc-meta .app-doc-meta-gap td {
  padding-top: .6rem;
}

/* หัวข้อย่อยบนกระดาษ (ลูกค้า · หมายเหตุ) — สีของเอกสาร ตัวเล็กกว่าเนื้อความ */
.app-doc-label {
  color: var(--doc-accent);
  font-size: calc(.875rem * var(--doc-fs));
  margin-bottom: .15rem;
}

/* ตารางรายการ: เส้นแนวนอนอย่างเดียว ไม่มีเส้นตั้ง — เส้นตั้งทำให้กระดาษดูเป็นตารางบัญชี
   ทั้งที่สิ่งที่คนอ่านคือรายการกับยอดเงินไม่กี่บรรทัด */
.app-doc-items {
  width: 100%;
  font-size: calc(.875rem * var(--doc-fs));
}

.app-doc-items thead th {
  border-top: 1px solid var(--doc-rule);
  border-bottom: 1px solid var(--doc-rule);
  font-weight: 400;
  color: #000;
  padding: .4rem .5rem;

  /* พื้นตั้งต้นโปร่งใส — ตัวหนังสือยังเป็นดำเสมอไม่ว่าพื้นจะออกหรือไม่ออก ด้วยเหตุผล
     เดียวกับที่สไตล์โมเดิร์นไม่ใช้ตัวขาวบนพื้นทึบ (ดูบล็อกข้างล่าง) */
  background: var(--doc-thead-bg);
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

.app-doc-items tbody td {
  border-bottom: 1px solid var(--app-border);
  padding: .5rem;
  vertical-align: top;
}

.app-doc-items tbody tr:last-child td {
  border-bottom-color: var(--doc-rule);
}

/* เส้นคั่นก่อนยอดท้ายใบ — เส้นบนเต็มความกว้างกระดาษ ไม่ใช่แค่เหนือตารางยอดที่ชิดขวา
   เส้นที่ยาวแค่ 22rem จะอ่านเป็นขีดที่ลอยอยู่มุมขวา ไม่ใช่ตัวแบ่งของแผ่น

   **เป็นธาตุของตัวเอง ไม่ใช่ border-top ของบล็อกยอด** เพราะร้านต้องขยับเส้นโดยไม่ขยับ
   ตัวเลขยอดได้ (คีย์ `sum-rule` ในทะเบียน doclook/blocks.php) · สูง 0 ไม่กินที่
   ระยะห่างระหว่างเส้นกับตารางยังเป็น padding-top ของ .app-doc-sumbar เหมือนเดิม */
.app-doc-sumrule {
  border-top: 1px solid var(--doc-rule);
}

.app-doc-sumbar {
  padding-top: .6rem;
}

/* สไตล์โมเดิร์นแบ่งสองส่วนนี้ด้วยเส้นหนาใต้ตารางรายการกับแถบสีของยอดรวมอยู่แล้ว —
   เส้นที่สามคือการบอกเรื่องเดียวกันรอบที่สาม */
.app-doc.is-modern .app-doc-sumrule {
  display: none;
}

.app-doc.is-modern .app-doc-sumbar {
  padding-top: 0;
}

.app-doc-sum {
  width: 100%;
  max-width: 22rem;
  font-size: calc(.875rem * var(--doc-fs));
}

.app-doc-sum th {
  color: var(--doc-accent);
  font-weight: 400;
  text-align: right;
  padding: .3rem 1rem .3rem 0;
}

.app-doc-sum td {
  text-align: right;
  padding: .3rem 0;
  white-space: nowrap;
}

/* เส้นคั่นเหนือแถวสุดท้าย (ยอดรวมทั้งสิ้น) — แยก "ตัวเลขที่เอามาบวกกัน" ออกจาก
   "ตัวเลขที่ต้องจ่ายจริง" ซึ่งเป็นบรรทัดเดียวที่ลูกค้ามองหา · แถวสุดท้ายคือยอดรวมเสมอ
   ไม่ว่าจะมีส่วนลดหรือไม่ จึงไม่ต้องมีคลาสให้ PHP คอยติดตามจำนวนแถว */
.app-doc-sum tr:last-child th,
.app-doc-sum tr:last-child td {
  border-top: 1px solid var(--doc-rule);
  padding-top: .45rem;
}

/* รหัสสินค้านำหน้ารายละเอียดในช่องเดียวกัน — ตัวหนาแต่สีจางกว่า จึงกวาดตาหาได้เร็ว
   โดยไม่แย่งความสำคัญไปจากชื่อรายการที่คนอ่านจริง ๆ · ไม่แยกเป็นคอลัมน์ของตัวเองเพราะ
   บรรทัดค่าแรงกับของนอกทะเบียนไม่มีรหัส คอลัมน์นั้นจะว่างครึ่งตารางในใบส่วนใหญ่ */
.app-doc-sku {
  font-weight: 600;
  color: var(--app-muted-fg);
}

/* ขีดคั่นเป็น ::after ไม่ใช่อักขระใน markup — สี่ที่ที่แสดงรหัส (กระดาษสองใบ + หน้าใบที่
   ออกแล้วอีกสองหน้า) จะได้ไม่ต้องพิมพ์ตัวเดียวกันไว้สี่ชุดแล้ววันหนึ่งแก้ไม่ครบ ·
   และขีดไม่มีทางลอยอยู่หน้าบรรทัดที่ไม่มีรหัส เพราะไม่มี span ให้มันเกาะตั้งแต่แรก

   ไม่หนา เพราะเป็นตัวคั่น ไม่ใช่ข้อมูล — รหัสกับชื่อรายการต้องเด่นกว่าเส้นที่แบ่งมัน */
.app-doc-sku::after {
  content: "|";
  font-weight: 400;
  margin: 0 .25rem 0 .4rem;
}

/* "บาท" ต่อท้ายยอด — ตัวเล็กและไม่หนา เพราะเป็นหน่วย ไม่ใช่ตัวเลข */
.app-doc-unit {
  font-size: calc(.8125rem * var(--doc-fs));
  font-weight: 400;
  margin-left: .35rem;
}

/* ---------- สไตล์ "โมเดิร์น" ของกระดาษเอกสาร (application/docstyle/) ----------

   เปลี่ยนหน้าตาด้วย CSS ล้วน **markup ของกระดาษไม่ขยับสักบรรทัด** — ใบเสนอราคากับ
   ใบเสร็จจึงยังใช้ paper.php ไฟล์เดิมไฟล์เดียว ค่าตั้งค่าใหม่ ลายเซ็น หรือช่องที่เพิ่มทีหลัง
   ตกถึงทั้งสองแบบพร้อมกันเองโดยไม่ต้องไล่แก้สองที่ · สไตล์ที่สกินเอาไม่อยู่จริง ๆ ค่อยวาง
   ไฟล์ papers/<code>.php ของตัวเอง ซึ่งเป็นทางที่เปิดไว้แล้วที่ doc_style_template()

   **ตัวหนังสือบนพื้นสีเป็นสีเข้ม ไม่ใช่สีขาว** เป็นข้อที่สำคัญที่สุดของทั้งบล็อกนี้ —
   print-color-adjust ได้ผลกับเบราว์เซอร์ปัจจุบัน แต่ไม่ใช่สัญญา เครื่องพิมพ์ขาวดำบางรุ่น
   หรือไดรเวอร์เก่ายังทิ้งพื้นได้ · ตัวขาวบนพื้นทึบที่พื้นไม่ออก = หัวตารางหายไปทั้งแถว
   ไม่ใช่แค่จืดลง · ตัวเข้มบนพื้นอ่อนอ่านครบทุกตัวอักษรทั้งสองสถานะ ซึ่งเป็นสิ่งที่เอกสาร
   ที่เป็นหลักฐานทางการเงินต้องรับประกันให้ได้ก่อนความสวย

   **สีมาจาก --doc-accent ตามชนิดใบเหมือนเดิม** สไตล์ไม่ได้ตั้งสีเอง — ใบเสนอราคายังส้ม
   ใบเสร็จยังเขียว คนหน้าร้านคว้ากระดาษจากถาดแล้วยังรู้ว่าใบอะไรโดยไม่ต้องอ่าน

   **ขนาดตัวอักษรของบล็อกนี้เป็น em ไม่ใช่ rem** A5 จึงย่อตาม font-size ของแผ่นเอง ·
   เขียนเป็น rem แล้วจะชนกฎ .app-doc-sheet.is-a5 ที่ specificity เท่ากันแล้วบล็อกนี้ชนะ
   เพราะอยู่ทีหลังในไฟล์ = A5 ล้นหน้า (กับดักรูปเดียวกับ .is-rejoin ที่ต้องอยู่หลัง .is-done)
   ส่วนค่าที่เป็น rem จริง ๆ ไม่ได้ มีบล็อก A5 ของตัวเองต่อท้ายซึ่ง specificity สูงกว่า */

.app-doc.is-modern {
  /* บังคับให้พื้นสีพิมพ์ออกแม้ผู้ใช้ไม่ได้ติ๊ก "พิมพ์ภาพพื้นหลัง" ซึ่งเป็นค่าตั้งต้น
     — ที่แรกในโปรเจกต์ที่ใช้ทางนี้ ที่อื่นเลี่ยงพื้นหลังด้วย border มาตลอด */
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

/* แถบหัวกระดาษ — เต็มความกว้างของ **พื้นที่พิมพ์** ไม่ใช่ริมกระดาษ เพราะขอบมาจาก @page
   ซึ่งข้ามไม่ได้ · เครื่องพิมพ์จริงก็พิมพ์ชิดริมกระดาษไม่ได้อยู่แล้ว */
.app-doc.is-modern .app-doc-head {
  background: var(--doc-tint);
  border-bottom: 3px solid var(--doc-accent);
  border-radius: .5rem .5rem 0 0;
  padding: 1rem 1.15rem .85rem;

  /* ยังเว้นที่ขวาไว้ แต่ให้ป้ายเลขแผ่นแทนสามเหลี่ยมที่ถูกซ่อน */
  padding-right: 3.5rem;
}

/* สามเหลี่ยมมุมหายไปในสกินนี้ — สีของมันซ้อนกับแถบหัวที่เป็นสีเดียวกันอยู่แล้ว

   เคยมีป้ายกลม 1/2 มาแทน โดยให้เหตุผลว่าเลขแผ่นคือสิ่งเดียวที่แยกต้นฉบับออกจากสำเนา
   — **ตรวจแล้วไม่จริง** คำว่า "ต้นฉบับ" / "สำเนา" พิมพ์อยู่ใต้ชื่อเอกสารทุกสกิน
   (`$dtSub` ของ doctitle.php) ป้ายนั้นจึงถูกถอดออกไปพร้อมเลขบนสามเหลี่ยม 9 ก.ย. 2026 */
.app-doc.is-modern .app-doc-corner {
  display: none;
}


.app-doc.is-modern .app-doc-title {
  font-size: 2.1em;
  font-weight: 700;
  letter-spacing: .04em;
  color: var(--doc-ink);
}

/* เส้นใต้ชื่อเอกสารไม่ต้องมีแล้ว — เส้นหนาของแถบหัวทำหน้าที่คั่นอยู่แล้ว
   มีทั้งสองเส้นคือการบอกเรื่องเดียวกันสองครั้ง */
.app-doc.is-modern .app-doc-titlebar {
  padding-bottom: 0;
  margin-bottom: .35rem;
}

.app-doc.is-modern .app-doc-titlerule {
  display: none;
}

.app-doc.is-modern .app-doc-meta th {
  color: var(--doc-ink);
  font-weight: 600;
}

/* หัวตาราง: พื้นอ่อน ตัวเข้ม มุมบนมน แทนเส้นดำบน–ล่างของคลาสสิก */
.app-doc.is-modern .app-doc-items thead th {
  background: var(--doc-tint-2);
  color: var(--doc-ink);
  font-weight: 600;
  border-top: 0;
  border-bottom: 2px solid var(--doc-accent);
  padding: .5rem .6rem;
}

.app-doc.is-modern .app-doc-items thead th:first-child { border-radius: .4rem 0 0 0; }
.app-doc.is-modern .app-doc-items thead th:last-child  { border-radius: 0 .4rem 0 0; }

/* แถวสลับสี — กวาดตาตามบรรทัดยาว ๆ ไปถึงคอลัมน์ยอดเงินได้โดยไม่หลุดแถว */
.app-doc.is-modern .app-doc-items tbody tr:nth-child(even) td {
  background: var(--doc-tint);
}

.app-doc.is-modern .app-doc-items tbody td {
  border-bottom: 1px solid var(--app-border);
}

.app-doc.is-modern .app-doc-items tbody tr:last-child td {
  border-bottom: 2px solid var(--doc-accent);
}

/* แถวสุดท้ายของยอดท้ายใบคือ "จำนวนเงินรวมทั้งสิ้น" เสมอ — เป็นตัวเลขที่คนอ่านใบมองหา
   ก่อนอย่างอื่น จึงเป็นที่เดียวของยอดท้ายใบที่มีพื้นสี */
.app-doc.is-modern .app-doc-sum {
  border-collapse: separate;
  border-spacing: 0;
}

.app-doc.is-modern .app-doc-sum tr:last-child th,
.app-doc.is-modern .app-doc-sum tr:last-child td {
  background: var(--doc-tint-2);
  color: var(--doc-ink);
  font-weight: 700;
  padding-top: .45rem;
  padding-bottom: .45rem;

  /* แถบสีทำหน้าที่แยกยอดรวมออกจากบรรทัดที่เอามาบวกกันอยู่แล้ว — เส้นคั่นของสไตล์คลาสสิก
     ที่ลากทับขอบมนอีกเส้นคือการบอกเรื่องเดียวกันสองครั้ง ซึ่งเป็นสิ่งแรกที่ต้องตัดออก
     เวลาทำให้ minimal (กติกาเดียวกับที่ .app-page ไม่มีทั้งเส้นขอบและเงา) */
  border-top: 0;
}

.app-doc.is-modern .app-doc-sum tr:last-child th {
  border-radius: .4rem 0 0 .4rem;
  padding-left: .6rem;
}

.app-doc.is-modern .app-doc-sum tr:last-child td {
  border-radius: 0 .4rem .4rem 0;
  padding-right: .6rem;
}

.app-doc.is-modern .app-doc-label {
  color: var(--doc-ink);
  font-weight: 600;
}

/* แถวติ๊กวิธีชำระกับบล็อกเซ็นชื่อ **ไม่ถูกแตะเลย** — ช่องติ๊กกับเส้นเซ็นทำจาก border
   และรูปลายเซ็นวางทับเส้นด้วย position: absolute แตะเมื่อไรลายเซ็นเลื่อนออกจากเส้น */

/* A5 ของโมเดิร์น — specificity สูงกว่าทั้งบล็อกข้างบนและบล็อก .is-a5 ทั่วไป
   จึงชนะทั้งคู่ไม่ว่าลำดับในไฟล์จะเป็นยังไง ซึ่งเป็นเหตุผลที่เขียนแยกไว้ตรงนี้ */
.app-doc-sheet.is-a5 .app-doc.is-modern .app-doc-head {
  padding: .7rem .8rem .6rem;
  padding-right: 2.9rem;
}


.app-doc-sheet.is-a5 .app-doc.is-modern .app-doc-items thead th,
.app-doc-sheet.is-a5 .app-doc.is-modern .app-doc-items tbody td {
  padding: .3rem .35rem;
}
/* ---------- แถวติ๊กวิธีชำระเงินท้ายใบเสร็จ ----------

   กล่องติ๊กเป็น <span> ที่มี border ไม่ใช่ <input type=checkbox> — ช่องติ๊กจริงพิมพ์ออกมา
   หน้าตาต่างกันทุกเบราว์เซอร์และบางตัวไม่พิมพ์เครื่องหมายถูกข้างในเลย · ที่นี่ไม่มีอะไร
   ให้ผู้ใช้กด กระดาษบอกอย่างเดียวว่าใบนี้รับเงินมาด้วยวิธีไหน */
.app-doc-pay {
  font-size: calc(.8125rem * var(--doc-fs));
  border-top: 1px solid var(--app-border);
  padding-top: .75rem;
}

.app-doc-check {
  display: inline-block;
  width: .95rem;
  height: .95rem;
  border: 1px solid #000;
  margin-right: .3rem;
  vertical-align: -.15em;
  text-align: center;
  line-height: .85rem;
  font-weight: 700;
}

/* กึ่งกลางมีผลเฉพาะตอนประโยคนำกินเต็มบรรทัดของตัวเอง (A5 — ดู flex: 0 0 100%) ·
   บน A4 มันเป็นสมาชิกของแถว flex ที่จัดกึ่งกลางอยู่แล้ว บรรทัดนี้จึงไม่เปลี่ยนอะไร */
.app-doc-pay-lead {
  text-align: center;
}

/* ช่องที่เว้นไว้ให้เขียนด้วยมือเมื่อระบบไม่มีค่านั้น — เส้นใต้ที่ยาวพอเขียนจริง */
.app-doc-fill {
  display: inline-block;
  min-width: 6rem;
  border-bottom: 1px solid #000;
  text-align: center;
  padding: 0 .35rem;
}

/* ---------- บล็อกเซ็นชื่อสองฝั่ง ----------

   ความสูงของเส้นคือ *ที่ว่างเหนือเส้น* ที่ปากกาจะลงไปเซ็น — 2.25rem = 9.5mm บนกระดาษจริง
   ซึ่งพอสำหรับลายเซ็นไทยเต็ม ๆ · เดิม 2.75rem (11.6mm) ตอนที่เส้นนี้ต้องรับทั้งลายเซ็น
   และชื่อ **พอมีวงเล็บชื่อตัวบรรจงอยู่ใต้เส้นแล้ว เส้นไม่ต้องเผื่อที่เขียนชื่อซ้ำอีก** ·
   บล็อกทั้งก้อนมีสี่ชั้น (เส้น · วงเล็บ · ตำแหน่ง · วันที่) การลดชั้นแรกลงจึงทำให้
   ทั้งก้อนกระชับโดยไม่มีชั้นไหนหายไป */
.app-doc-sign-line {
  border-bottom: 1px solid var(--doc-rule);
  height: 2.25rem;

  /* **ความกว้างพอดีตัว ไม่ใช่เต็มครึ่งกระดาษ** — เส้นที่ยาวกว่าสิ่งที่จะเขียนบนมันมาก ๆ
     อ่านเป็นช่องว่างที่ลืมกรอก ไม่ใช่ช่องที่เตรียมไว้ให้ · สามชั้นในบล็อกไล่ความกว้าง
     ลงมาตามลำดับ (เส้นเซ็น → วงเล็บชื่อ → เส้นวันที่) ทั้งก้อนจึงอ่านเป็นทรงเดียว
     แทนที่จะเป็นเส้นยาวหนึ่งเส้นกับของสั้น ๆ สามอันห้อยอยู่ข้างใต้ */
  max-width: 15rem;
  margin: 0 auto;
}

/* วงเล็บชื่อตัวบรรจงใต้เส้นเซ็น — ลายเซ็นไทยส่วนใหญ่อ่านไม่ออก บรรทัดนี้คือที่เดียว
   ที่บอกว่าใครเซ็น จึงเป็นชั้นที่ขาดไม่ได้ ไม่ใช่ของประดับ */
.app-doc-sign-paren {
  font-size: calc(.8125rem * var(--doc-fs));
  margin-top: .35rem;
  white-space: nowrap;
}

/* เส้นในวงเล็บให้เขียนชื่อด้วยมือ — เมื่อมีลายเซ็นอิเล็กทรอนิกส์ ชื่อถูกพิมพ์ลงไปแล้ว
   เส้นจึงหายไป (.is-filled) เพราะเส้นใต้ชื่อที่พิมพ์มาแล้วอ่านเหมือนช่องที่ยังต้องกรอก */
.app-doc-sign-name {
  display: inline-block;
  min-width: 9rem;
  border-bottom: 1px solid var(--doc-rule);
  padding: 0 .25rem;
}

.app-doc-sign-name.is-filled {
  border-bottom: 0;
}

/* วันที่เป็นเส้นสั้นต่อท้ายคำ ไม่ใช่เส้นเต็มบรรทัดที่มีป้ายอยู่ใต้ — เขียนแค่ตัวเลข
   เส้นเต็มบรรทัดกินความสูงไปหนึ่งชั้นโดยไม่ได้ใช้ ซึ่ง A5 ไม่มีให้ */
.app-doc-sign-date {
  font-size: calc(.8125rem * var(--doc-fs));
  margin-top: .6rem;
}

/* ป้ายบอกว่าใครควรเซ็นตรงนี้ — อยู่ **เหนือ** เส้น จึงเป็น margin-bottom ไม่ใช่
   padding-top · ระยะเท่า .mb-2 ของบรรทัด "ในนาม" ที่เคยอยู่ตรงนี้

   บล็อกเซ็น**เตี้ยลงหนึ่งชั้น** เพราะยุบห้าแถวเหลือสี่ (วัดบน A5: 146.4 → 117.6px)
   — ทิศทางที่ปลอดภัย กระดาษที่เคยพิมพ์ลงหน้าเดียวจึงยังลงแน่นอน */
.app-doc-sign-cap {
  font-size: calc(.8125rem * var(--doc-fs));
  margin-bottom: .5rem;
}

/* ---------- ลายเซ็นที่เซ็นไว้แล้ว บนกระดาษ ----------

   วางทับเส้นเดิม ไม่ได้แทนที่เส้น — เส้นยังอยู่เพราะกระดาษที่มีลายเซ็นลอยอยู่กลางที่ว่าง
   อ่านเหมือนรูปที่หลุดมา ไม่ใช่เอกสารที่ถูกเซ็น · position: relative บนตัวเส้น แล้วรูป
   absolute ทับลงไป ความสูงของบล็อกจึงไม่ขยับเลยไม่ว่าจะมีลายเซ็นหรือไม่มี
   — ซึ่งเป็นเรื่องเดียวกับที่ใบเสนอราคาต้องพิมพ์ลงหน้าเดียวเสมอ                        */
.app-doc-sign-line {
  position: relative;
}

.app-doc-sign-img,
.app-print-sign-img {
  position: absolute;
  left: 50%;
  bottom: 0;
  transform: translateX(-50%);
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

/* ใบส่งซ่อมกับใบรับเครื่องคืนใช้ .app-print-sign เป็นเส้น ไม่ใช่ .app-doc-sign-line */
.app-print-sign {
  position: relative;
}

/* ---------- ปุ่มเซ็นที่วางทับเส้นบนกระดาษ (components/signhit.php) ----------

   เส้นเซ็นคือจุดที่คนมองอยู่แล้วเวลาจะเซ็น การกดตรงนั้นได้เลยจึงสั้นกว่าการไล่หาปุ่ม
   บนแถบเครื่องมือ · ปุ่มเป็น absolute ทับเส้นพอดี **ความสูงของบล็อกเซ็นจึงไม่ขยับเลย**
   ใบเสนอราคายังพิมพ์ลงหน้าเดียวเท่าเดิม — หลักเดียวกับ .app-doc-sign-img

   ตอนพิมพ์ปุ่มหายด้วย .d-print-none บนตัว markup (ไม่ได้ซ่อนที่นี่) เหลือเส้นเปล่า
   เหมือนก่อนมีระบบลายเซ็นทุกตัวอักษร */
.app-sign-hit {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;

  /* โปร่งใสบนพื้นกระดาษ — กรอบประจาง ๆ พอให้รู้ว่ากดได้ แต่ไม่แย่งสายตาไปจากตัวเอกสาร
     ซึ่งเป็นสิ่งที่คนเปิดหน้านี้มาอ่านจริง ๆ */
  background: transparent;
  border: 1px dashed var(--app-line);
  border-radius: var(--app-radius-sm, .5rem);
  padding: 0;
  cursor: pointer;

  color: var(--app-muted-fg);
  font-size: .75rem;
  line-height: 1;

  transition: background-color .12s ease, border-color .12s ease, color .12s ease;
}

.app-sign-hit:hover,
.app-sign-hit:focus-visible {
  background: rgba(3, 105, 161, .06);
  border-color: var(--app-accent);
  color: var(--app-accent);
}

/* ป้ายบนจอเล็กกว่าเส้น A5 จึงต้องหดตาม ไม่งั้นตัวหนังสือล้นออกนอกกรอบ */
.app-doc-sheet.is-a5 .app-sign-hit {
  font-size: .6875rem;
}

/* ---------- แผ่นเซ็นบนจอ (คู่กับ components/signpad.php + signpad.js) ----------

   พื้นขาวสนิท ไม่ใช่ .app-inset สีเทา — คนเซ็นควรเห็นสิ่งเดียวกับที่จะออกมาบนกระดาษ
   ซึ่งเป็นกระดาษสีขาว · เส้นบรรทัดใต้กรอบบอกว่าให้เซ็นตรงไหน แบบเดียวกับเส้นบนกระดาษ */
.app-signpad {
  position: relative;
  background: #fff;
  border: 1px solid var(--app-border);
  border-radius: var(--app-radius);
  padding: .75rem .75rem 1.75rem;
}

.app-signpad canvas {
  display: block;
  width: 100%;
  /* กันหน้าเลื่อนตามนิ้วระหว่างลากเซ็นบนมือถือ — ถ้าไม่ตั้ง เบราว์เซอร์จะตีความการลาก
     เป็นการ scroll แล้วลายเซ็นจะขาดเป็นท่อน ๆ ทุกครั้งที่ลากยาว */
  touch-action: none;
  cursor: crosshair;
}

.app-signpad-base {
  position: absolute;
  left: 1.5rem;
  right: 1.5rem;
  bottom: 1.1rem;
  border-bottom: 1px dashed var(--app-border);
}

/* ---------- toast แจ้งผล ----------

   ตัวครอบสร้างโดย App.notify() ไม่ได้อยู่ใน markup ของหน้าไหน · ที่นี่มีแค่สองอย่าง
   ที่ Bootstrap ไม่ได้ให้มา

   z-index: modal ของ Bootstrap อยู่ที่ 1055 ส่วน .toast-container ไม่ได้ตั้งค่าของตัวเอง
   toast ที่เด้งตอน modal เปิดอยู่จึงไปนอนหลังฉากดำ — เกิดจริงกับทุกปุ่มใน modal
   ที่เขียนข้อมูล (เปลี่ยนสถานะใบงาน · โอนเจ้าของเครื่อง · ยกเลิกใบเสร็จ) */
#appToasts {
  z-index: 1090;
}

/* ---------- กล่องยืนยันต้องอยู่เหนือ modal ตัวอื่นเสมอ ----------

   มันเปิด**ซ้อนบน** modal ตัวอื่นได้จริงหลายที่ — ปุ่มบันทึกของแผ่นเซ็นติด data-confirm
   อยู่ ดังนั้นกดแล้วจะมี modal สองตัวเปิดพร้อมกัน

   Bootstrap 5.3 ให้ modal ทุกตัว z-index 1055 **เท่ากันหมด** พอเท่ากัน เบราว์เซอร์
   ตัดสินด้วย**ลำดับใน DOM** ตัวที่อยู่หลังทับตัวที่อยู่หน้า · หน้าแอปปกติรอดมาตลอด
   เพราะ page_foot.php วาง #appConfirm ไว้ท้ายสุด แต่ signscripts.php (หน้าพิมพ์กับ
   หน้าสาธารณะ) require confirm.php ก่อน signpad.php **กล่องยืนยันจึงไปนอนหลังแผ่นเซ็น**

   แก้ด้วยการประกาศ z-index ให้ชัด ไม่ใช่สลับลำดับการ require — ลำดับที่บังเอิญถูก
   จะพังอีกครั้งวันที่มีหน้าใหม่ include คนละลำดับ และไม่มีอะไรฟ้อง

   1060 = เหนือ modal (1055) แต่ต่ำกว่า toast (1090) ซึ่งต้องอยู่บนสุดเสมอ
   เพราะข้อความแจ้งผลเด้งตอน modal เปิดอยู่ได้

   **ฉากดำของกล่องยืนยันยังอยู่ที่ 1050 ตามเดิม** แผ่นเซ็นข้างหลังจึงไม่ถูกหรี่ลง
   ซึ่งถูกแล้ว — คนที่กำลังจะกดยืนยันควรเห็นลายเซ็นที่ตัวเองเพิ่งวาดอยู่ตรงนั้น */
#appConfirm {
  z-index: 1060;
}

/* จอแคบ: toast อยู่ที่เดียวกับปุ่มบันทึก ตัวที่ค้างอยู่ (error) จึงบังปุ่มได้
   ยืดเต็มความกว้างแล้วคนเห็นทั้งข้อความและปุ่มปิดในครั้งเดียว ไม่ต้องเดาว่าปิดยังไง */
@media (max-width: 575.98px) {
  #appToasts {
    left: 0;
    right: 0;
  }

  #appToasts .toast {
    width: 100%;
  }
}

/* เส้นสำหรับเซ็นชื่อ — เว้นที่ให้ปากกาจริงพอเขียน

   **`height` คือตัวที่ทำให้มี "ช่องเซ็น" จริง ๆ ไม่ใช่แค่เส้น** — กล่องนี้เคยไม่มีความสูง
   เลย (สูง 1px เท่าเส้นขอบ) ที่ว่างเหนือเส้นจึงเป็นแค่ระยะห่างของบล็อกที่อยู่ข้างบน
   ซึ่งบนใบส่งซ่อมเหลือ 10.6px = 2.8mm · ปากกาลงไม่ได้ และเงื่อนไขการรับซ่อมที่อยู่
   เหนือขึ้นไปก็ไปนั่งอยู่ในที่ที่ควรเป็นลายเซ็นพอดี — ซึ่งคือ "ช่องลายเซ็นถูกทับ"
   ที่คนใช้เห็น ไม่ใช่ตัวหนังสือซ้อนทับเส้นตรง ๆ

   **และกล่องสูง 1px ทำให้ลายเซ็นอิเล็กทรอนิกส์ไม่โผล่เลย** — `.app-print-sign-img`
   เป็น `max-height: 100%` ของกล่องนี้ รูป 200x60 จึงถูกย่อเหลือ 0x0 (วัดจริง
   ด้วย data URL ที่โหลดสำเร็จแล้ว) · ใบเสนอราคากับใบเสร็จไม่เจอเพราะ
   `.app-doc-sign-line` มี `height: 2.75rem` มาตั้งแต่แรก — ใช้ค่าเดียวกันที่นี่
   ทั้งสี่ใบจะได้มีที่เซ็นเท่ากันและย่อรูปลายเซ็นด้วยไม้บรรทัดอันเดียวกัน */
.app-print-sign {
  border-bottom: 1px dotted #000;
  height: 2.75rem;
  margin: 0 1rem 2.5rem;
}

/* ---------- แผ่นกระดาษบนจอของใบส่งซ่อมกับใบรับเครื่องคืน ----------

   สองใบนี้เคยไม่มีแผ่นเลย เนื้อหาลอยอยู่บนพื้นเทาของ .app-print เฉย ๆ ต่างจาก
   ใบเสนอราคา/ใบเสร็จที่มี .app-doc-sheet เป็นกระดาษขาวให้เห็น — **ผลคือสามเหลี่ยมมุม
   ไม่มีขอบกระดาษให้ชิด** ทั้งที่กติกาของมันคือชิดขอบแผ่นเหมือนกันทั้งสี่ใบ

   ขอบ 8mm เท่า @page ของสองใบนี้ ที่เดียวเหมือน .app-doc-sheet — สิ่งที่เห็นบนจอกับ
   สิ่งที่ออกจากเครื่องพิมพ์จึงกว้างเท่ากันจริง (ของเดิมแคบกว่าตอนพิมพ์อยู่ราว 36mm) */
.app-slip-sheet,
.app-handover-sheet {
  width: 210mm;
  max-width: 100%;
  min-height: 297mm;
  margin: 1rem auto;

  --doc-sheet-pad: 8mm;
  padding: var(--doc-sheet-pad);
  background: #fff;
  box-shadow: 0 1px 2px rgba(15, 23, 42, .08), 0 8px 24px rgba(15, 23, 42, .10);
}

@media print {
  /* บนกระดาษจริงขอบมาจาก @page แล้ว แผ่นจึงต้องคืนทุกค่าที่ตั้งไว้ให้ *จอ* ทิ้ง

     **`min-height` สำคัญที่สุด** — 297mm ที่ตั้งไว้บนจอสูงกว่าพื้นที่พิมพ์ (281mm ที่ขอบ
     8mm) แผ่นที่ยังยืนยันความสูงนั้นตอนพิมพ์จะดันกระดาษเปล่าออกมาอีกหนึ่งแผ่นต่อหนึ่งใบ */
  .app-slip-sheet,
  .app-handover-sheet {
    width: auto;
    max-width: none;
    min-height: 0;
    margin: 0;
    padding: 0;
    box-shadow: none;
    --doc-sheet-pad: 0mm;
  }
}

/* ---------- ใบส่งซ่อม: A5 สองใบต่อกระดาษ A4 หนึ่งแผ่น ----------

   ต้นฉบับให้ลูกค้า สำเนาติดที่เครื่อง · ความสูงถูกล็อกไว้ ไม่ใช่ปล่อยให้เนื้อหาดันเอง —
   ใบที่สูงไม่เท่ากันแปลว่าเส้นตัดขยับตามใบงานแต่ละใบ ซึ่งตัดด้วยมือทีละปึกไม่ได้

   134mm x 2 + รอยตัด 8mm = 276mm ซึ่งพอดีทั้งกับขอบ 8mm ที่ @page ตั้งไว้ (เหลือที่ 281mm)
   และกับขอบมาตรฐานของเบราว์เซอร์ที่ราว 10mm (เหลือ 277mm) เผื่อไว้สำหรับเบราว์เซอร์ที่ยัง
   ไม่รู้จัก named page แล้วตกกลับไปใช้ค่าของตัวเอง — ถ้าคิดพอดีเป๊ะกับ 8mm ใบล่างจะตกไปหน้าสอง */
.app-slip {
  display: flex;
  flex-direction: column;
  height: 134mm;
  overflow: hidden;
  font-size: calc(.8rem * var(--doc-fs));
}

/* กินที่ที่เหลือทั้งหมด ช่องเซ็นจึงถูกดันไปติดขอบล่างของใบเสมอ ไม่ลอยขึ้นมากลางกระดาษ
   เมื่ออาการที่แจ้งสั้น ๆ · เป็น flex เองอีกชั้นเพื่อส่งที่ว่างต่อให้กล่องอาการ */
.app-slip-body {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;

  /* **ด่านสุดท้ายว่าช่องเซ็นห้ามถูกทับ** — min-height: 0 ข้างบนทำให้บล็อกนี้ยุบได้ก็จริง
     แต่ลูกที่ยุบต่อไม่ได้แล้ว (หมายเหตุ · เงื่อนไข · หัวกระดาษ) จะล้นออกมาทับ
     บล็อกเซ็นชื่อที่อยู่ล่างสุดของใบ ส่วน overflow ของ .app-slip ตัดที่ขอบ 134mm
     ซึ่งอยู่ **ใต้** ช่องเซ็น จึงไม่ได้กันอะไรเลย — ผลคือตัวหนังสือนั่งทับเส้นเซ็น
     แทนที่ส่วนเกินจะถูกตัด (เจอจริง: หมายเหตุ 8 บรรทัด + เงื่อนไข 8 บรรทัด ทับ 21px)

     เพดานจำนวนบรรทัดใน slip.php กันเคสปกติไว้แล้ว บรรทัดนี้กันเคสที่เหลือ —
     หัวกระดาษที่มีทั้งโลโก้ ที่อยู่ร้านหลายบรรทัด และที่อยู่ลูกค้ายาว ๆ ซึ่งไม่มีเพดาน
     และไม่ควรมี · อะไรจะขาดก็ขาดไป แต่ที่ที่ลูกค้าเซ็นต้องว่างเสมอ */
  overflow: hidden;
}

/* หัวกระดาษของกลาง (docprinthead.php) ตั้งชื่อร้านไว้ที่ .fs-4 ซึ่งเป็นขนาดของใบเต็มหน้า
   บนใบครึ่งแผ่นมันกินความสูงไปเกือบเซนติเมตรโดยไม่ได้อ่านง่ายขึ้น — ทับด้วยความจำเพาะ
   ที่สูงกว่า ไม่ใช่ไปแก้ component ซึ่งใบเสนอราคากับใบเสร็จยังใช้ขนาดเดิมอยู่ */
.app-slip .fs-4 {
  /* ต้องมี !important เพราะยูทิลิตี้ขนาดตัวอักษรของ Bootstrap ประกาศ !important มาเอง
     ความจำเพาะที่สูงกว่าอย่างเดียวจึงชนะไม่ได้ */
  font-size: calc(1rem * var(--doc-fs)) !important;
}

/* ---------- ตัวแปรที่บล็อกเซ็นชื่อของกลางต้องใช้ ----------

   `components/docsign.php` เขียนสีเส้นเป็น `var(--doc-rule)` และขนาดตัวอักษรเป็น
   `calc(... * var(--doc-fs))` ซึ่งประกาศไว้ที่ `.app-doc` — สองใบนี้ไม่ได้อยู่ในนั้น

   **ตัวแปรที่ไม่มีค่าไม่ได้แปลว่า "ค่าเดิม" แต่แปลว่าทั้งประกาศนั้นใช้ไม่ได้** เส้นเซ็นจะ
   หายทั้งเส้นและขนาดตัวอักษรจะตกกลับไปค่าที่สืบทอดมาโดยไม่มีอะไรฟ้อง · ค่าที่ตั้งตรงนี้
   เท่าค่าตั้งต้นของ .app-doc เป๊ะ */
.app-slip,
.app-handover {
  --doc-rule: #000;
  --doc-fs: 1;

  /* ---------- สีเน้นของสองใบนี้ (7 ก.ย. 2026) ----------

     เดิมเป็นกระดาษขาวดำล้วน ต่างจากใบเสนอราคา/ใบเสร็จที่มีสามเหลี่ยมมุมสีมาแต่แรก
     เพื่อให้คนหน้าร้านคว้ากระดาษจากถาดเครื่องพิมพ์แล้วรู้ว่าเป็นใบอะไรโดยไม่ต้องอ่าน

     **ค่าตั้งต้นว่าง = PHP ไม่ฉีดอะไรและไม่ติดคลาส `is-accent`** ตัวแปรสองตัวนี้จึงมีไว้
     รอค่าที่ร้านตั้งเท่านั้น · `--doc-corner` อ้าง `var(--doc-accent)` เหมือน `.app-doc`
     มุมจึงได้สีเดียวกับหัวเรื่องโดยไม่ต้องมีช่องตั้งค่าที่สอง

     ต้องเป็น `relative` เพราะสามเหลี่ยมเป็น `absolute` ที่เกาะขอบบนขวาของ**ตัวใบ** —
     ใบส่งซ่อมมีสองใบบนแผ่น A4 แผ่นเดียว ถ้าไปเกาะแผ่น มุมจะไปกองอยู่ที่ใบบนทั้งคู่ */
  position: relative;
  --doc-accent: #64748B;
  --doc-corner: var(--doc-accent);

  /* ขนาดสามเหลี่ยมมุม — ดู .app-doc-corner · ค่าของใบเต็มแผ่น ซึ่งใบรับเครื่องคืนเป็น
     (มันไม่ได้ตั้ง height ตัวเอง จึงกิน A4 ทั้งแผ่น ต่างจากใบส่งซ่อมที่ล็อกไว้ 134mm) */
  --doc-corner-size: 3.25rem;

  /* ระยะที่มุมเลื่อนออกไปหาขอบแผ่น — กลไกและเหตุผลเดียวกับ .app-doc ทุกข้อ */
  --doc-corner-bleed: var(--doc-sheet-pad, 0mm);
}

/* **เฉพาะใบรับเครื่องคืน ไม่ใช่ใบส่งซ่อม** — ใบส่งซ่อมมีสองใบบนแผ่น A4 แผ่นเดียว
   ตัวใบจึงเป็นกระดาษของตัวเองหลังตัดออกมา มุมที่เกาะขอบใบคือมุมที่ชิดกระดาษอยู่แล้ว ·
   และ `overflow: hidden` ที่ล็อกใบไว้ 134mm **ตัดสามเหลี่ยมที่ล้นออกนอกใบทิ้งทั้งอัน**
   (เจอจริงตอนวัด: เลื่อนออกไปแล้วมุมหายทั้งสองใบ) */
.app-handover .app-doc-corner {
  top:   calc(-1 * var(--doc-corner-bleed));
  right: calc(-1 * var(--doc-corner-bleed));
}

/* ตอนพิมพ์ มุมกลับไปเกาะขอบพื้นที่พิมพ์ — ต้องสั่ง 0 ตรง ๆ ไม่ใช่หวังให้ตกตาม
   --doc-sheet-pad ด้วยเหตุผลเดียวกับที่เขียนไว้ที่ .app-doc */
@media print {
  .app-handover {
    --doc-corner-bleed: 0mm;
  }
}

/* ใบส่งซ่อมสูงคงที่ 134mm = ครึ่งแผ่น มุมจึงย่อเท่า A5 ของใบเสนอราคา · ต้องอยู่ **หลัง**
   บล็อกข้างบน เพราะความจำเพาะเท่ากัน (0,1,0) ตัวที่มาทีหลังเป็นตัวที่ชนะ — และกฎฐาน
   ของ .app-slip อยู่เหนือขึ้นไป จะไปเติมตรงนั้นไม่ได้ */
.app-slip {
  --doc-corner-size: 2.25rem;
}

/* ชื่อใบกับป้ายหัวข้อรับสีเน้น **เฉพาะตอนที่ร้านตั้งสีไว้** — ไม่มีคลาสนี้ = สีเทาจาง
   ตัวเดิมทุกตัวอักษร · แยกเป็นคลาสแทนที่จะให้ `--doc-accent` ตั้งต้นเป็นสีเทา เพราะ
   ชื่อใบวันนี้เป็นสีตัวหนังสือปกติ ไม่ใช่สีเทา การรวบเป็นตัวแปรเดียวจึงเปลี่ยนของเดิม */
.app-slip.is-accent .app-slip-title,
.app-slip.is-accent .app-slip-label,
.app-handover.is-accent .app-handover-label {
  color: var(--doc-accent);
}

/* ชื่อชนิดเอกสารบนก้อนขวา คู่กับเลขที่และวันที่ที่มันพูดถึง — เดิมอยู่ใต้ที่อยู่ร้านทางซ้าย
   ซึ่งเป็นคนละก้อนกับเลขที่ ทำให้ใบเดียวบอกว่าตัวเองคือใบอะไรจากสองที่ */
.app-slip-title {
  font-size: calc(.72rem * var(--doc-fs));
  font-weight: 600;
  letter-spacing: .08em;
}

/* ป้ายบอกว่านี่คือใบไหน — บรรทัดเดียวที่ต่างกันระหว่างสองใบ จึงต้องอ่านออกตั้งแต่ระยะไกล */
.app-slip-tag {
  display: inline-block;
  border: 1px solid #000;
  border-radius: .25rem;
  padding: 0 .4rem;
  font-size: calc(.7rem * var(--doc-fs));
  font-weight: 600;
  margin-bottom: .15rem;
}

/* ตารางข้อมูลบนใบเล็ก ๆ ไม่มีเส้นคั่นทุกแถว — เส้นเยอะกว่าตัวหนังสือแล้วอ่านยากขึ้น */
.app-slip-table > :not(caption) > * > * {
  border-bottom-width: 0;
  padding: .1rem .25rem;
}

.app-slip-table th {
  width: 6.5rem;
  font-weight: 500;
  color: var(--app-muted-fg);
}

/* ---------- ตำแหน่งตั้งต้นของสองบล็อกกลางใบ ----------

   คอลัมน์ป้ายของตารางลูกค้าแคบกว่าของตารางเครื่อง เพราะป้ายยาวสุดคือ "ลูกค้า"
   ไม่ใช่ "ช่างที่รับผิดชอบ" — ชื่อกับที่อยู่จึงเริ่มชิดซ้ายแทนที่จะเยื้องเข้าไปเกือบสามเซนติเมตร

   ส่วนบล็อกเครื่องเยื้องเข้ามาเล็กน้อย ให้เห็นว่าเป็นคนละก้อนกับบล็อกลูกค้าโดยไม่ต้อง
   มีเส้นคั่น · ทั้งสองค่าเป็นแค่**จุดเริ่ม** ร้านลากต่อเองได้ที่หน้าตั้งค่างานซ่อม */
.app-slip-partyblock .app-slip-table th {
  width: 4rem;
}

.app-slip-machineblock {
  padding-left: 6mm;
}

/* ป้ายหัวข้อของช่องกรอกบนกระดาษ — ตัวเล็ก จาง ห่างตัวอักษรนิดหน่อย ให้อ่านออกว่าเป็น
   ป้ายไม่ใช่เนื้อหา · แยกเป็นคลาสของตัวเองเพราะช่องอื่นที่เพิ่มทีหลังจะได้หน้าตาเดียวกัน */
.app-slip-label {
  font-size: calc(.6rem * var(--doc-fs));
  letter-spacing: .06em;
  color: var(--app-muted-fg);
  margin-bottom: .1rem;
}

/* กล่องอาการที่แจ้ง — ไม่มีกรอบ มีแต่เส้นบรรทัดจาง ๆ แบบฟอร์มที่เขียนด้วยปากกาได้
   กรอบสี่เหลี่ยมรอบข้อความสั้น ๆ ทำให้ที่ว่างข้างในดูเหมือนความผิดพลาด ส่วนเส้นบรรทัด
   บอกตรง ๆ ว่าตรงนั้นมีไว้ให้เขียนต่อ */
.app-slip-symptom {
  display: flex;
  flex-direction: column;

  /* กินที่ว่างที่เหลือของใบ — ใบงานส่วนใหญ่มีอาการสั้น ๆ ไม่กี่คำ ที่ว่างตรงกลางกระดาษ
     จึงมีเสมอ และที่ว่างที่มีเส้นให้เขียนคือสิ่งที่มีประโยชน์กว่าที่ว่างเปล่า ๆ */
  flex: 1 1 auto;

  /* อาการที่ยาวมากถูกตัดที่กล่องนี้ ไม่ใช่ปล่อยให้ดันเงื่อนไขกับช่องเซ็นตกขอบใบไปทั้งคู่
     (ใบสูงคงที่ตายตัวเพราะรอยตัดต้องอยู่ที่เดิมทุกใบ อะไรสักอย่างต้องถูกตัดเสมอ
     และสองอย่างนั้นคือสิ่งที่ลูกค้าต้องเซ็นรับ) · ข้อความเต็มอยู่ที่หน้าใบงานตามเดิม */
  overflow: hidden;

  /* **พื้นที่ขั้นต่ำของช่องอาการ — ป้ายกำกับ + เส้นบรรทัดสามเส้น**

     `overflow` ที่ไม่ใช่ `visible` ทำให้ขนาดต่ำสุดอัตโนมัติของ flex item กลายเป็น 0
     (กติกาของ CSS ไม่ใช่ของ Bootstrap) กล่องนี้ซึ่งเป็น `flex: 1 1 auto` จึงยุบได้จนหมด
     ผลคือหมายเหตุกับเงื่อนไขซึ่งเป็น `flex: 0 1 auto` และยุบไม่ได้ กินที่จนช่องอาการ
     **หายไปทั้งกล่อง** ทั้งที่มันคือเหตุผลที่ใบนี้มีอยู่ (วัดจริง: หมายเหตุ 4 บรรทัด +
     เงื่อนไข 6 บรรทัด เหลือช่องอาการ 8px = ไม่มีทั้งป้าย ทั้งข้อความ ทั้งเส้น)

     **ลำดับการเสียสละของใบที่สูงคงที่: ช่องเซ็น → ช่องอาการ → เงื่อนไข** — พื้นล่างนี้
     ทำให้ส่วนที่ล้นไปตกที่เงื่อนไขซึ่งเป็นข้อความชุดเดียวกันทุกใบและร้านย่อเองได้
     ไม่ใช่ที่อาการซึ่งเป็นของใบนี้ใบเดียวและพิมพ์ใหม่ไม่ได้

     18mm = ป้ายกำกับ + เส้นสองเส้น · ลดจาก 24mm มาจ่ายค่าความสูงของช่องเซ็น
     ที่เพิ่งได้มา — ใบที่ไม่มีที่ให้เซ็นใช้ไม่ได้เลย ส่วนใบที่มีเส้นบรรทัดน้อยลงหนึ่งเส้น
     ยังใช้ได้ทุกประการ */
  min-height: 18mm;
}

/* **ไม่มีเส้นบรรทัดแล้ว** (7 ก.ย. 2026) — เดิมกล่องนี้เป็นกระดาษเส้น 12 เส้นให้เขียนต่อ
   ด้วยปากกา (`.app-slip-rules`) ซึ่งบนใบส่วนใหญ่ที่คีย์อาการมาจากระบบแล้วกลายเป็นเส้นเปล่า
   เกือบเต็มกล่อง · ระยะบรรทัด 6mm ยังอยู่ เพราะเป็นระยะที่อ่านง่ายบนตัวอักษรขนาดนี้
   ไม่ได้อยู่เพื่อให้ตรงกับเส้นอีกต่อไป */
.app-slip-symptom-text {
  line-height: 6mm;
  min-height: 6mm;
}

/* หมายเหตุของใบงานบนใบส่งซ่อม — บรรทัดเดียวกับป้ายกำกับ ไม่ใช่กล่องของตัวเอง
   เพราะใบสูงคงที่ ทุกมิลลิเมตรที่บล็อกนี้กินไปคือเส้นบรรทัดที่หายไปจากกล่องอาการ
   ป้าย "หมายเหตุ" เป็น inline จึงต้องล้าง margin-bottom ที่ .app-slip-label ตั้งไว้
   สำหรับตอนเป็นบล็อก ไม่งั้นบรรทัดจะเหลื่อมกัน */
.app-slip-note {
  font-size: calc(.7rem * var(--doc-fs));
  line-height: 1.35;
}

.app-slip-note .app-slip-label {
  display: inline;
  margin-bottom: 0;
  margin-right: .25rem;
}

.app-slip-terms {
  font-size: calc(.65rem * var(--doc-fs));
  color: var(--app-muted-fg);
  line-height: 1.35;
}

/* ---------- บล็อกเซ็นชื่อบนใบครึ่งแผ่น ----------

   ใช้ components/docsign.php ตัวเดียวกับใบเสนอราคา แต่ระยะของใบเต็มหน้ากินที่เกินไป
   บนใบที่สูงคงที่ 134mm — บีบลงทุกชั้นโดยไม่ตัดชั้นไหนทิ้ง (ป้าย · เส้น · วงเล็บ · วันที่)
   ที่ที่หายไปคือช่องว่างของกล่องอาการ ซึ่งเป็นที่ว่างสำรอง ไม่ใช่เนื้อหา

   **ต้อง !important เพราะทับ .mt-4 ของ Bootstrap ซึ่งประกาศ !important มาเอง** —
   กติกาเดียวกับที่ A5 ทับ margin ของ .app-doc-head · ส่วนระยะระหว่างสองคอลัมน์คุมด้วย
   ตัวแปร --bs-gutter-y ของ .row ตรง ๆ ไม่ต้องยิง !important ใส่ */
.app-slip .app-doc-signblock {
  --bs-gutter-y: 0;
  margin-top: .5rem !important;
}

.app-slip .app-doc-sign-cap {
  font-size: calc(.68rem * var(--doc-fs));
  margin-bottom: .25rem;
}

/* 1.9rem = 8mm ยังพอให้เซ็นด้วยปากกาได้จริง และเป็นที่ที่รูปลายเซ็นย่อลงไปนั่งได้
   โดยยังอ่านออก */
.app-slip .app-doc-sign-line {
  height: 1.9rem;
  max-width: 11rem;
}

.app-slip .app-doc-sign-paren,
.app-slip .app-doc-sign-date {
  font-size: calc(.68rem * var(--doc-fs));
}

.app-slip .app-doc-sign-paren { margin-top: .2rem; }
.app-slip .app-doc-sign-date  { margin-top: .25rem; }

/* ครึ่งใบครึ่งแผ่นกว้างราว 62mm — เส้นชื่อ 9rem บวกวงเล็บล้นออกนอกคอลัมน์ */
.app-slip .app-doc-sign-name {
  min-width: 6rem;
}

/* รอยตัดคั่นกลางแผ่น — เป็นของกระดาษ ไม่ใช่ขอบของใบใดใบหนึ่ง
   ระยะเว้นข้างละ 4mm อยู่ตรงนี้ที่เดียว ทั้งสองใบจึงไม่มีตัวหนังสือชิดแนวที่กรรไกรจะลง
   (กรรไกรตัดเบี้ยวได้เป็นเรื่องปกติ ระยะนี้คือเผื่อความเบี้ยวนั้น) */
.app-slip-cut {
  position: relative;
  border-top: 1px dashed #b0b0b0;
  margin: 4mm 0;
}

/* ป้ายกำกับคร่อมเส้นอยู่ทางซ้าย พื้นขาวทับเส้นให้ตัวหนังสืออ่านออก — ใช้พื้นสีขาวได้
   เพราะกระดาษขาวอยู่แล้ว ไม่ต้องพึ่งการตั้งค่า "พิมพ์ภาพพื้นหลัง" ของเบราว์เซอร์ */
.app-slip-cut-mark {
  position: absolute;
  top: 0;
  left: 8mm;
  transform: translateY(-50%);
  padding: 0 .4rem;
  background: #fff;
  font-size: .6rem;
  letter-spacing: .06em;
  color: var(--app-muted-fg);
  white-space: nowrap;
}

/* ---------- ใบรับเครื่องคืน: A4 เต็มหน้า ต้นฉบับกับสำเนาคนละแผ่น ----------

   ต่างจากใบส่งซ่อมตรงที่ไม่ล็อกความสูง — ใบนี้มีตารางอะไหล่กับตารางประกันที่ยาวไม่เท่ากัน
   ทุกใบ และไม่มีรอยตัดให้ต้องตรงตำแหน่ง แผ่นจึงยาวตามเนื้อหาได้ตามปกติ */
.app-handover-tag {
  font-size: calc(.7rem * var(--doc-fs));
  letter-spacing: .08em;
  color: var(--app-muted-fg);
}

/* หัวข้อของก้อนข้อความและของตาราง — ตัวเล็กกว่า <h*> เพราะกระดาษมีหัวข้อเยอะ
   ถ้าใหญ่เท่าหัวข้อบนหน้าจอจะแย่งสายตาไปจากเนื้อหาที่คนต้องอ่านจริง */
.app-handover-label {
  font-size: calc(.7rem * var(--doc-fs));
  font-weight: 600;
  letter-spacing: .04em;
  color: var(--app-muted-fg);
  margin-bottom: .15rem;
}

.app-handover-block {
  margin-bottom: .6rem;
}

.app-handover-terms {
  margin-top: .75rem;
  font-size: calc(.7rem * var(--doc-fs));
  color: var(--app-muted-fg);
  line-height: 1.4;
}

/* ---------- สกิน "มินิมอล" ของใบส่งซ่อมและใบรับเครื่องคืน ----------

   `doc_styles()` โค้ด `minimal` · เกาะ .app-slip / .app-handover ผ่าน $slStyle / $hoStyle
   ที่ print.php กับ handover.php อ่านค่าตั้งค่ามาให้ — **markup ไม่มีเงื่อนไขอะไรเลย
   เป็นสกินล้วน** กติกาเดียวกับ .app-doc.is-modern ของใบเสนอราคา/ใบเสร็จ

   หลักของแบบนี้: ไม่มีกรอบ เว้นที่ว่างมากขึ้น เส้นบางลง แล้วให้ *ลำดับความสำคัญ*
   มาจากขนาดกับน้ำหนักตัวอักษรแทนที่จะมาจากเส้นและกล่อง

   **ป้ายกำกับใช้ letter-spacing ไม่ใช่ text-transform: uppercase** — ป้ายบนกระดาษ
   สองใบนี้เป็นภาษาไทยเกือบทั้งหมด ซึ่ง uppercase ไม่มีผลเลยสักตัวอักษร ผลที่ได้จะเป็น
   ป้ายที่เปลี่ยนหน้าตาเฉพาะคำอังกฤษที่ปนอยู่ (`serial`) แล้วดูเหมือนพิมพ์ผิด

   **ไม่มีพื้นสีสักที่** เส้นขอบกับตัวหนังสือล้วน — พิมพ์ออกครบทุกเครื่องโดยไม่ต้องติ๊ก
   "พิมพ์ภาพพื้นหลัง" ในกล่องพิมพ์ กติกาเดียวกับที่เส้นบรรทัดของกล่องอาการเป็น border
   ไม่ใช่ gradient

   **ราคาที่จ่ายบนใบส่งซ่อม: เส้นบรรทัดในกล่องอาการหายไปราวสองเส้น** — ใบสูงคงที่ 134mm
   ที่ว่างที่เพิ่มให้หัวกระดาษกับป้ายจึงต้องมาจากที่ไหนสักแห่ง และกล่องอาการเป็นตัวเดียว
   ที่ยืดหดได้ (`flex: 1 1 auto`) · ไม่ล้นออกนอกใบเพราะ min-height 18mm กับ overflow
   ของมันยังคุมอยู่ตามเดิม — ลำดับการเสียสละไม่เปลี่ยน */

/* หัวกระดาษปิดท้ายด้วยเส้นหนาหนึ่งเส้น แทนที่จะปล่อยให้ลอย — เส้นเดียวนี้คือสิ่งเดียว
   ที่แบ่งกระดาษเป็นส่วน ๆ ในแบบนี้ ที่เหลือแบ่งด้วยช่องไฟ
   (.app-slip-head / .app-handover-head เป็นคลาสกำกับแถวหัวกระดาษ ไม่มี CSS ของตัวเอง
   ในแบบมาตรฐาน มีไว้ให้สกินเกาะโดยไม่ต้องอ้าง utility ของ Bootstrap ซึ่งเปลี่ยนได้) */
.app-slip.is-minimal .app-slip-head,
.app-handover.is-minimal .app-handover-head {
  border-bottom: 1.5px solid #111;
  padding-bottom: .35rem;
}

/* ป้ายบอกว่าเป็นใบไหน — ถอดกรอบทิ้ง เหลือตัวอักษรจางที่ห่างกันหน่อย
   กล่องที่มีเส้นขอบรอบคำสองคำคือของชิ้นแรกที่ต้องหายไปเมื่อทำให้ minimal */
.app-slip.is-minimal .app-slip-tag {
  border: 0;
  border-radius: 0;
  padding: 0;
  font-weight: 400;
  font-size: calc(.62rem * var(--doc-fs));
  letter-spacing: .14em;
  color: var(--app-muted-fg);
}

.app-handover.is-minimal .app-handover-tag {
  letter-spacing: .14em;
}

/* เลขที่เอกสารเป็นตัวโปร่ง ไม่ใช่ตัวหนา — บนกระดาษที่ไม่มีอะไรมาแข่งสายตา ขนาดอย่างเดียว
   ก็ทำให้มันเด่นพอแล้ว ส่วนน้ำหนักหนาทำให้มันดูเป็นปุ่มมากกว่าเป็นเลขที่ */
.app-slip.is-minimal .app-slip-head .fw-semibold,
.app-handover.is-minimal .app-handover-head .fw-semibold {
  font-weight: 400 !important;
  letter-spacing: .03em;
}

/* ป้ายหัวข้อของช่องต่าง ๆ — เล็กลง จางลง ห่างขึ้น */
.app-slip.is-minimal .app-slip-label,
.app-handover.is-minimal .app-handover-label {
  font-weight: 400;
  letter-spacing: .14em;
  color: #94a3b8;
}

/* ชื่อช่องในตารางข้อมูลไม่ต้องมีน้ำหนัก — คอลัมน์ซ้ายอยู่ตำแหน่งเดิมทุกแถวอยู่แล้ว
   คนอ่านแยกออกจากตำแหน่ง ไม่ต้องใช้สีหรือน้ำหนักมาช่วยอีกชั้น */
.app-slip.is-minimal .app-slip-table th {
  font-weight: 400;
  color: #94a3b8;
}

/* ช่องไฟระหว่างแถวมากขึ้นเล็กน้อย — ค่าที่จ่ายไปคือเส้นบรรทัดของกล่องอาการ
   จึงเพิ่มทีละ .05rem ไม่ใช่ทีละ .25rem */
.app-slip.is-minimal .app-slip-table > :not(caption) > * > * {
  padding: .15rem .25rem;
}

.app-slip.is-minimal .app-slip-terms,
.app-handover.is-minimal .app-handover-terms {
  color: #94a3b8;
}

/* เส้นเซ็นเป็นเส้นทึบบาง ไม่ใช่เส้นประ — เส้นประเป็นภาษาของแบบฟอร์มที่มีกรอบ
   ซึ่งเป็นสิ่งที่แบบนี้ถอดออกไปทั้งใบ */
/* สองใบนี้ย้ายมาใช้บล็อกเซ็นของกลางแล้ว เส้นจึงเป็น .app-doc-sign-line ไม่ใช่
   .app-print-sign อีกต่อไป — เส้นของสกินมินิมอลเข้มกว่าค่าปกติเล็กน้อยเหมือนเดิม */
.app-slip.is-minimal .app-doc-sign-line,
.app-handover.is-minimal .app-doc-sign-line {
  border-bottom: 1px solid #111;
}

/* ใบรับเครื่องคืนไม่ได้ล็อกความสูง จึงเว้นที่ว่างได้เต็มที่กว่าใบส่งซ่อม
   — ก้อนข้อความห่างขึ้นและเส้นในตารางจางลง */
.app-handover.is-minimal .app-handover-block {
  margin-bottom: 1rem;
}

.app-handover.is-minimal .table > :not(caption) > * > * {
  border-bottom-color: #eef2f6;
}

@media print {
  /* named page เพื่อให้ขอบกระดาษชุดนี้มีผลกับใบส่งซ่อมเท่านั้น — @page ที่ไม่มีชื่อจะไปเปลี่ยน
     ขอบของใบเสนอราคาและใบเสร็จซึ่งพิมพ์ได้ดีอยู่แล้วด้วยค่าของเบราว์เซอร์ */
  @page repair-slip {
    size: A4 portrait;
    margin: 8mm;
  }

  .app-slip-sheet {
    page: repair-slip;
    padding: 0;
  }

  /* ใบหนึ่งใบห้ามถูกตัดข้ามหน้า — ครึ่งบนอยู่หน้าหนึ่งครึ่งล่างอยู่อีกหน้าคือกระดาษที่ใช้ไม่ได้ */
  .app-slip {
    break-inside: avoid;
  }
}

/* ---------- ใบรับเครื่องคืน: กระดาษของตัวเอง ----------

   **ใบนี้เคยไม่มี `@page` เลย** ขนาดกระดาษกับขอบจึงมาจากค่าตั้งต้นของเบราว์เซอร์
   ไม่ใช่จากเอกสาร — วัดจริงด้วย Chrome ได้ Letter 215.9x279.4mm ไม่ใช่ A4 และเตี้ยกว่า
   A4 อยู่ 17.6mm · ใบเดียวกันจึงพิมพ์ออกมาไม่เท่ากันบนเครื่องที่ตั้งค่าต่างกัน

   ขอบ 8mm เท่าใบส่งซ่อม **ไม่ใช่ 12mm เท่าใบเสนอราคา** เพราะ 12mm บีบความกว้างเหลือ
   186mm แล้วเรื่องที่ช่างพิมพ์ไว้ตัดบรรทัดถี่ขึ้นจนใบล้นเร็วกว่าเดิม (วัดแล้ว: 12mm
   แย่กว่าที่เป็นอยู่วันนี้ ส่วน 8mm รับข้อความได้ราวสองเท่า) */
@media print {
  @page handover-page {
    size: A4 portrait;
    margin: 8mm;
  }

  .app-handover-sheet {
    page: handover-page;
  }

  /* ยอดสรุป → เส้นเซ็น ห้ามถูกตัดแยกจากกัน — ใบนี้ยาวเกินหนึ่งแผ่นได้ตามการออกแบบ
     แต่ตอนล้น `break-inside: avoid` ของบล็อกเซ็นอย่างเดียวส่งเฉพาะเส้นเซ็นไปแผ่นสอง
     แล้วลูกค้าเซ็นรับโดยไม่เห็นยอดเงินที่กำลังรับรอง (เจอจริงตอนวัด ไม่ใช่ตอนอ่านโค้ด) */
  .app-handover-tail {
    break-inside: avoid;
  }
}

@media print {
  /* หมึกดำบนกระดาษขาว ไม่ต้องพึ่งการตั้งค่า "พิมพ์สีพื้นหลัง" ของเบราว์เซอร์ */
  .app-print {
    background: #fff;
  }

  .app-print-sheet {
    max-width: none;
    padding: 0;
  }

  /* หลายแผ่นในเอกสารเดียว (ใบรับเครื่องคืน: ต้นฉบับ · สำเนา · ใบเสร็จ) —
     ติดคลาสนี้กับทุกแผ่น**ยกเว้นแผ่นสุดท้าย** ไม่งั้นจะได้กระดาษเปล่าห้อยท้ายมาอีกแผ่น
     ผู้เรียกเป็นคนตัดสินว่าแผ่นไหนคือแผ่นสุดท้าย เพราะ CSS ไม่รู้ว่ามีใบเสร็จแนบมาหรือไม่ */
  .app-print-break {
    break-after: page;
  }

  /* แถวในตารางห้ามถูกตัดกลางแถวข้ามหน้า อ่านไม่รู้เรื่องทั้งสองหน้า

     ---------- .app-doc-signblock ต้องอยู่ในรายการนี้ด้วย ----------

     `.app-print-sign` ทุกวันนี้เหลือเป็น **เส้นเซ็นในสลิป 80mm** เท่านั้น
     (receipt/paper80.php · service/slip80.php · service/handoverpaper80.php) ส่วนบล็อก
     เซ็นชื่อของกระดาษเต็มแผ่นทั้งสี่ใบย้ายไปเป็น `.app-doc-signblock` ของ
     `components/docsign.php` ตั้งแต่ 6 ก.ย. 2026 แล้ว — **กฎนี้จึงเลิกคุ้มครองมันไป
     เงียบ ๆ ตั้งแต่วันนั้น** โดยไม่มีอะไรฟ้อง เพราะกฎยังอ่านดูเหมือนมีผลอยู่
     (รูปเดียวกับกับดัก `--doc-corner-bleed` ที่กฎอยู่ผิดที่แล้วแพ้ลำดับในไฟล์)

     ผลคือใบที่เนื้อหาสูงเกินพื้นที่พิมพ์ไปเล็กน้อย จะถูกขอบหน้าตัดกลางบล็อกเซ็นชื่อ
     แล้วหน้าสองได้เศษของบล็อกมาแผ่นหนึ่ง

     **เป็น *ช่วง* ไม่ใช่เพดาน** — เกินไปมาก ๆ บล็อกจะไหลไปหน้าสองทั้งก้อนเองอยู่แล้ว
     ที่พังคือช่วงที่บล็อกคาบเส้นพอดี · วัดจาก PDF จริง (Chrome + preferCSSPageSize):

       ใบเสนอราคา A4  13–15 บรรทัด   (16 ขึ้นไปไปทั้งก้อนเอง)
       ใบเสร็จ A4     ตั้งแต่ 10 บรรทัด
       ใบเสร็จ A5     6 บรรทัด        (5 ยังไม่ตัด)
       ใบเสนอราคา A5  ไม่เจอในช่วง 5–8 บรรทัด

     **ครึ่งบนของเส้นเซ็นอยู่หน้าหนึ่ง ครึ่งล่างอยู่อีกหน้า = กระดาษที่เซ็นไม่ได้**
     ย้ายทั้งบล็อกไปหน้าถัดไปเสียเลยดีกว่า เหตุผลเดียวกับที่ `.app-slip` ห้ามถูกตัด

     คุ้มครองแค่ตัวบล็อกเซ็น ไม่ใช่ `.app-doc-foot` ทั้งก้อน — แถวติ๊กวิธีชำระเงินของ
     ใบเสร็จสูงอีก 22mm และเป็นแถวสั้น ๆ ที่ถูกตัดแล้วยังอ่านออก · ล็อกทั้ง foot
     แปลว่าหน้าแรกอาจจบเร็วขึ้นอีก 22mm โดยไม่ได้ป้องกันอะไรเพิ่ม */
  tr, .app-print-sign, .app-doc-signblock {
    break-inside: avoid;
  }

  a {
    text-decoration: none;
    color: #000;
  }
}

/* ---------- สลิปความร้อน 80mm ----------

   กระดาษของ receipt/paper80.php · service/slip80.php · service/handoverpaper80.php

   **ม้วนกระดาษไม่มีขอบล่าง** `size: 80mm auto` จึงยาวตามเนื้อหา — ไม่มี min-height
   ไม่มีโครงหัว–ตัว–ท้ายที่ดันท้ายกระดาษลงล่าง และไม่มีเงื่อนไข "ลบไว้ 2mm กันล้นหน้า"
   ซึ่งเป็นสามอย่างที่แผ่น A4/A5 ต้องมี

   **ใช้ named page ไม่ใช่ `@page` เปล่า ๆ** ด้วยเหตุผลเดียวกับใบส่งซ่อมและแบบฟอร์มเอกสาร
   — `@page` ที่ไม่มีชื่อมีผลกับ *ทุกหน้าพิมพ์ในระบบ* · ตอนนี้มีสี่: app-doc-page ·
   app-doc-page-a5 · repair-slip · slip80

   **ทุกอย่างเป็นเส้นขอบกับตัวหนังสือ ไม่มีพื้นสีสักที่** — เครื่องความร้อนพิมพ์ขาวดำ
   1 บิต และเบราว์เซอร์ไม่พิมพ์พื้นหลังเป็นค่าตั้งต้นอยู่แล้ว (บทเรียนเดียวกับเส้นบรรทัด
   ในกล่องอาการของใบส่งซ่อม และสามเหลี่ยมมุมของแบบฟอร์มเอกสาร) · สีเทาของ
   --app-muted-fg ก็ถูกบังคับเป็นดำ เพราะ 203dpi ปั่นเทาออกมาเป็นจุดโปร่งที่จางจนอ่านยาก

   ตัวเลขทุกช่องเป็น tabular-nums หลักจึงตรงกันเป็นคอลัมน์โดยไม่ต้องใช้ตาราง */
.app-slip80 {
  /* box-sizing: border-box มาจาก Bootstrap — 80mm ลบขอบข้างละ 4mm เหลือพื้นที่พิมพ์
     72mm ซึ่งเป็นค่ามาตรฐานของหัวพิมพ์ 80mm เกือบทุกรุ่น */
  width: 80mm;
  max-width: 100%;
  margin: 0 auto;
  padding: 3mm 4mm 6mm;
  background: #fff;
  color: #000;
  font-size: .72rem;
  line-height: 1.45;
}

/* บนจอ แผ่นลอยขึ้นจากพื้นเทาเหมือนกระดาษวางบนโต๊ะ หลักเดียวกับ .app-doc-sheet */
@media screen {
  .app-slip80 {
    box-shadow: 0 1px 2px rgba(15, 23, 42, .08), 0 8px 24px rgba(15, 23, 42, .10);
  }

  /* แถบปุ่มไม่กว้าง 190mm ค้างไว้เหนือแผ่นกว้าง 80mm — ปุ่มที่ลอยห่างจากกระดาษที่มัน
     พูดถึงอ่านเหมือนของที่วางผิดที่ · จัดกลางเพราะสามปุ่มยังกว้างกว่าแผ่นอยู่ดี */
  .app-print-slip80 .app-print-actions {
    max-width: 120mm;
    justify-content: center;
    flex-wrap: wrap;
  }
}

@media print {
  /* **ไม่ตั้ง `size` โดยตั้งใจ ขนาดกระดาษมาจากไดรเวอร์ของเครื่องพิมพ์** — ม้วนกระดาษ
     คือกระดาษที่กว้างคงที่แต่ยาวไม่จำกัด ซึ่ง CSS เขียนไม่ได้: `size` รับได้แค่ `auto`
     ล้วน ๆ หรือความยาวสองค่า **`80mm auto` ไม่ใช่ไวยากรณ์ที่ถูกต้อง** เบราว์เซอร์ทิ้ง
     ทั้งกฎแล้วตกกลับไปเป็นกระดาษเริ่มต้น (วัดจริงด้วย Chrome: ได้ Letter 215.9x279.4mm)

     ที่เหลือคือเลือกไม่ได้ระหว่างสองอย่างที่แย่กว่า — ตั้งความสูงตายตัวสูง ๆ แล้วเครื่อง
     เลื่อนกระดาษทิ้งทุกใบ หรือให้ JS วัดความสูงแล้วเขียน  ใหม่ตอนกดพิมพ์ ซึ่งเป็น
     ตรรกะที่ต้องดูแลตลอดไปเพื่อแทนค่าที่ไดรเวอร์รู้อยู่แล้ว · **ม้วนกระดาษเป็นสมบัติของ
     เครื่องพิมพ์ ไม่ใช่ของเอกสาร** ปล่อยให้ไดรเวอร์ตอบจึงถูกที่สุด

     ผลข้างเคียงที่รู้อยู่: กด "บันทึกเป็น PDF" ในโหมดสลิปจะได้แถบแคบ ๆ บนแผ่น A4
     เพราะตอนนั้นไม่มีเครื่องความร้อนให้ถาม — ไม่ใช่ความผิดพลาด */
  @page slip80 {
    margin: 0;
  }

  /* ขอบมาจาก padding ของแผ่นเอง ไม่ใช่จาก @page — เครื่องความร้อนมีขอบที่พิมพ์ไม่ได้
     ของตัวเองอยู่แล้วราว 3–4mm การซ้อน margin ของ @page เข้าไปอีกชั้นทำให้เนื้อหา
     เบี้ยวเข้ากลางโดยที่วัดจากตัวเลขไหนไม่ได้เลย */
  .app-print-slip80 .app-print-sheet {
    page: slip80;
  }

  .app-slip80 {
    max-width: none;
    box-shadow: none;
  }
}

/* หัวร้านใช้ docprinthead.php ตัวเดียวกับกระดาษเต็มแผ่น — ตรรกะหาชื่อร้านจึงมีที่เดียว
   และบรรทัดใหม่ที่เติมทีหลังขึ้นสลิปเองฟรี · ที่ต่างคือจัดกลางและย่อขนาด ซึ่งเป็นเรื่อง
   ของ CSS ล้วน ๆ ไม่ต้องมี markup ชุดที่สอง
   .fs-4 ของ Bootstrap ประกาศ !important มาเอง ทับได้ทางเดียวคือ !important
   (รูปเดียวกับชื่อร้านบนใบส่งซ่อมและบนแผ่น A5) */
.app-slip80-shop {
  text-align: center;
}

.app-slip80-shop .fs-4 {
  font-size: .95rem !important;
}

.app-slip80-shop .small {
  font-size: .66rem;
}

.app-slip80-shop .app-doc-logo {
  max-width: 34mm;
  max-height: 14mm;
}

.app-slip80-title {
  margin-top: 2mm;
  font-size: .95rem;
  font-weight: 700;
  text-align: center;
}

.app-slip80-title-en {
  font-size: .7rem;
  letter-spacing: .06em;
  text-align: center;
}

.app-slip80-tag {
  font-size: .66rem;
  text-align: center;
}

/* ข้อความท้ายกระดาษของร้าน — บนม้วนกระดาษไม่มีบล็อกเซ็นสองฝั่งให้วางใต้ ต่อท้ายสุดเลย
   ว่างไว้ = ไม่ render อะไร ม้วนจึงไม่ยาวขึ้นแม้แต่มิลลิเมตรเดียวสำหรับร้านที่ไม่ได้กรอก */
.app-slip80-footer {
  margin-top: 3mm;
  padding-top: 1.5mm;
  border-top: 1px dashed #000;
  font-size: .66rem;
  text-align: center;
  white-space: pre-line;
  overflow-wrap: anywhere;
}

/* ใบที่ยกเลิกแล้ว — กรอบเส้นไม่ใช่พื้นแดง ทั้งเพราะไม่มีสีและเพราะพื้นหลังไม่ถูกพิมพ์ */
.app-slip80-void {
  border: 1px solid #000;
  margin-top: 2mm;
  padding: 1mm;
  font-weight: 600;
  text-align: center;
}

.app-slip80-rule {
  border-top: 1px dashed #000;
  margin: 2mm 0;
}

/* หัวข้อซ้าย–ค่าขวา · grid ไม่ใช่ table เพราะไม่มีหัวตารางและไม่มีความหมายเชิงตาราง
   ให้โปรแกรมอ่านหน้าจอ — dl บอกความสัมพันธ์ได้ตรงกว่าอยู่แล้ว */
.app-slip80-kv {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0 3mm;
  margin: 0 0 2mm;
}

.app-slip80-kv dt {
  font-weight: 400;
}

.app-slip80-kv dd {
  margin: 0;
  text-align: right;

  /* ที่อยู่กับชื่องานยาว ๆ ต้องตัดกลางคำได้ ไม่งั้นมันดันคอลัมน์จนล้นออกนอกม้วน */
  overflow-wrap: anywhere;
}

.app-slip80-kv.is-num dd {
  font-variant-numeric: tabular-nums;
}

/* บรรทัดรายการ: ชื่อเต็มความกว้างแถวหนึ่ง แล้ว "จำนวน × ราคา" กับยอดรวมคนละมุมของ
   แถวถัดไป — ตารางหลายคอลัมน์บีบลง 72mm แล้วชื่อรายการเหลือกว้างสองสามตัวอักษร */
.app-slip80-item {
  margin-bottom: 1.5mm;
}

.app-slip80-item-name {
  overflow-wrap: anywhere;
}

.app-slip80-item-note {
  padding-left: 3mm;
  font-size: .66rem;
  overflow-wrap: anywhere;
}

.app-slip80-item-calc {
  display: flex;
  justify-content: space-between;
  gap: 3mm;
  padding-left: 3mm;
  font-variant-numeric: tabular-nums;
}

/* ยอดสุทธิ — เส้นบนล่างแทนพื้นทึบ ด้วยเหตุผลเดียวกับที่ทุกอย่างบนสลิปเป็นเส้น */
.app-slip80-total {
  display: flex;
  justify-content: space-between;
  gap: 3mm;
  border-top: 1px solid #000;
  border-bottom: 1px solid #000;
  margin-bottom: 2mm;
  padding: 1mm 0;
  font-size: .82rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.app-slip80-block {
  margin-bottom: 2mm;
}

.app-slip80-label {
  font-size: .66rem;
  font-weight: 600;
}

.app-slip80-strong {
  font-weight: 600;
}

.app-slip80-muted,
.app-slip80-fine {
  font-size: .66rem;
}

.app-slip80-pre {
  white-space: pre-line;
  overflow-wrap: anywhere;
}

/* เส้นเซ็นฝั่งลูกค้าเส้นเดียว ฝั่งร้านเป็นชื่อตัวหนังสือใต้เส้น — เส้นสองเส้นเคียงกันบน
   72mm ได้เส้นละ 32mm ซึ่งเซ็นไม่ลง · .app-print-sign ตัวเดิมให้ทั้งเส้นประ ความสูง
   2.75rem และตำแหน่งของรูปลายเซ็นที่วางทับ มาครบอยู่แล้ว ที่แก้คือระยะขอบเท่านั้น */
.app-slip80-sign {
  margin-top: 4mm;
  text-align: center;
}

.app-slip80-sign .app-print-sign {
  margin: 0 6mm 1mm;
}

.app-slip80-sign-cap {
  font-size: .66rem;
}

/* สีที่มาจากคอมโพเนนต์ของกระดาษเต็มแผ่นถูกบังคับเป็นดำทั้งหมด — --doc-accent เป็นสีของ
   ชนิดเอกสาร (ส้ม/เขียว) ที่คนหน้าร้านใช้แยกกระดาษจากถาดเครื่องพิมพ์ ซึ่งเป็นประโยชน์ที่
   ไม่มีอยู่บนม้วนขาวดำตั้งแต่แรก · --app-muted-fg เป็นเทาที่ 203dpi ปั่นออกมาเป็นจุดโปร่ง */
.app-slip80 .app-doc-label,
.app-slip80 .app-doc-sku,
.app-slip80 .text-body-secondary {
  color: #000;
}

.app-slip80 .app-doc-label {
  font-size: .66rem;
}

/* docparty.php โหมด block ห่อตัวเองด้วย .mb-4 ตายตัว ซึ่งเป็น 1.5rem บนม้วนกระดาษ
   ที่วัดเป็นมิลลิเมตร — ยูทิลิตี้ระยะห่างของ Bootstrap ประกาศ !important มาเอง
   การทับจึงต้องใช้ !important เหมือนกัน (กฎเดียวกับ .fs-4 ข้างบน) */
.app-slip80 .mb-4 {
  margin-bottom: 2mm !important;
}

/* ---------- เคารพการตั้งค่าลดการเคลื่อนไหวของผู้ใช้ ---------- */

@media (prefers-reduced-motion: reduce) {
  * {
    transition-duration: .01ms !important;
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
  }
}

/* ---------- แผงลากจัดวางกระดาษ (doclook/layoutpanel.php) ----------

   กระดาษที่เห็นคือ paper.php ตัวจริง ไม่ใช่โครงจำลอง — ย่อด้วย transform: scale()
   ให้พอดีความกว้างของแผง โดย layout.js เป็นคนคำนวณอัตราย่อแล้วตั้ง --lay-zoom
   (CSS คำนวณเองไม่ได้เพราะต้องเทียบความกว้างของสองธาตุ) */

/* ตัววัดพิกเซลต่อมิลลิเมตรของเบราว์เซอร์ — ต้องมีขนาดจริง ห้าม display:none
   ไม่งั้น getBoundingClientRect() คืน 0 แล้วระยะลากเพี้ยนทั้งหน้า */
.lay-probe {
  width: 10mm;
  height: 1px;
  margin: 0;
  padding: 0;
  visibility: hidden;
}

.lay-stage {
  overflow: auto;
  padding: .5rem;
  background: var(--app-subtle);
  border: 1px solid var(--app-border);
  border-radius: var(--app-radius);
}

/* width: max-content กัน max-width: 100% ของ .app-doc-sheet ไม่ให้บีบกระดาษให้แคบลง
   — กระดาษต้องกว้างเท่าของจริงเสมอ แล้วค่อยย่อทั้งแผ่นด้วย scale ไม่งั้นการตัดบรรทัด
   บนหน้าจอจะไม่ตรงกับตอนพิมพ์ ซึ่งทำให้ตำแหน่งที่ลากไว้เชื่อไม่ได้ */
.lay-paper {
  width: max-content;
  transform-origin: top left;
  transform: scale(var(--lay-zoom, 1));
}

.lay-paper .app-doc-sheet {
  max-width: none;
  margin: 0;
}

/* เส้นที่ลากขอบขวาเพื่อปรับความยาวได้ · เส้นประโปร่งไว้ตลอด (outline ไม่กินที่
   อยู่แล้ว แต่การเปลี่ยนความหนาตอน hover จะทำให้ขอบกระพริบ) */
.lay-target {
  cursor: default;                 /* ตำแหน่งตายตัว — ลากได้แค่ขอบขวา (is-edge) */
  outline: 2px dashed transparent;
  outline-offset: 3px;
  touch-action: none;              /* ไม่งั้นนิ้วเลื่อนหน้าแทนที่จะลากขอบเส้น */
}

.lay-target:hover      { outline-color: rgba(3, 105, 161, .45); }
.lay-target.is-dragging { outline-color: var(--bs-primary); }

/* นิ้ว/เมาส์อยู่ใกล้ขอบขวาของเส้นที่ปรับความยาวได้ — เคอร์เซอร์เป็นตัวบอกอย่างเดียว
   ที่มี เพราะไม่มีที่จับลอยให้เห็น (ดู nearEdge() ใน layout.js) */
.lay-target.is-edge { cursor: col-resize; }

/* เป้าระดับบรรทัดบางกว่าและชิดกว่า — บรรทัดในบล็อกห่างกันแค่ 4–6 มม. กรอบหนาที่เยื้อง
   ออก 3px ของบล็อกจะไปทับกรอบของบรรทัดข้างเคียงจนดูไม่ออกว่ากำลังจะจับอันไหน */
.lay-target.is-line {
  outline-width: 1px;
  outline-offset: 1px;
}

/* เส้นคั่นสูง 1px จับแทบไม่โดน — ขยายพื้นที่จับเฉพาะบนแผง กระดาษที่พิมพ์ไม่เปลี่ยน */
.lay-paper .app-doc-sumrule,
.lay-paper .app-doc-titlerule { position: relative; z-index: 1; }

.lay-paper .app-doc-sumrule::before,
.lay-paper .app-doc-titlerule::before {
  content: '';
  position: absolute;
  inset: -6px 0;
}

/* แถวที่ถูกขยับจากตำแหน่งเดิม — ให้กวาดตาเจอได้ว่าตั้งอะไรไว้บ้างโดยไม่ต้องอ่านทุกช่อง */
.lay-stage + .table-responsive tr.is-moved > th { font-weight: 600; }

/* แถวที่ใบตัวอย่างไม่มีธาตุให้ลาก — จางลงพร้อม title บอกเหตุผล ไม่ใช่ซ่อนทิ้ง
   เพราะค่าที่กรอกไว้จะถูกใช้ทันทีที่ใบจริงมีบรรทัดนั้น */
.lay-stage + .table-responsive tr.is-missing > th { opacity: .55; cursor: help; }

/* บรรทัดเยื้องเข้าไปอยู่ใต้บล็อกของมัน — ลำดับในตารางเป็นตัวบอกกลุ่มอยู่แล้ว
   (blocks.php เรียงบล็อกแล้วตามด้วยบรรทัดของมัน) การเยื้องแค่ทำให้อ่านออกด้วยตา */
.lay-stage + .table-responsive tr.lay-line > th {
  padding-left: 1.75rem;
  color: var(--app-muted-fg);
}

/* ---------- ช่องค้นหาทั้งระบบบนแถบบน (components/navbar.php) ----------
   จอ md ขึ้นไปเป็นช่องกรอก · จอเล็กเหลือปุ่มไอคอนที่พาไปหน้าค้นหา
   ซ่อน/แสดงด้วย display ของคลาสตัวเอง ไม่ใช่ d-none/d-md-* (ดูเหตุผลใน navbar.php) */
.app-search {
  display: none;
  position: relative;
  align-items: center;
  flex: 0 1 18rem;
  min-width: 0;
  margin-left: auto;
}
.app-search .form-control {
  padding-left: 2rem;
  padding-right: 2rem;
  background-color: var(--app-subtle);
  border-color: transparent;
}
.app-search .form-control:focus { background-color: var(--app-surface); border-color: var(--app-accent); }
.app-search-icon {
  position: absolute;
  left: .65rem;
  color: var(--app-muted-fg);
  pointer-events: none;
}
.app-search-kbd {
  position: absolute;
  right: .5rem;
  padding: 0 .35rem;
  font-size: .7rem;
  color: var(--app-muted-fg);
  background: var(--app-surface);
  border: 1px solid var(--app-line);
  border-radius: .3rem;
  pointer-events: none;
}
.app-search .form-control:focus ~ .app-search-kbd { display: none; }
.app-search-btn { margin-left: auto; }
@media (min-width: 768px) {
  .app-search { display: flex; }
  .app-search-btn { display: none; }
}

/* ---------- หน้าผลค้นหาทั้งระบบ ---------- */
.app-search-group + .app-search-group { margin-top: 1.5rem; }
.app-search-hit {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .25rem .75rem;
  padding: .5rem .75rem;
  border-radius: var(--app-radius-sm);
  color: inherit;
  text-decoration: none;
}
.app-search-hit:hover, .app-search-hit:focus-visible { background: var(--app-subtle); }
.app-search-hit mark { padding: 0; background: #fde68a; color: inherit; }

/* ---------- หัวคอลัมน์ที่กดเรียงได้ (framework/listtools.php) ---------- */
.app-sort { color: inherit; text-decoration: none; white-space: nowrap; }
.app-sort:hover { color: var(--app-accent); }
.app-sort-arrow { display: inline-block; width: 1em; color: var(--app-muted-fg); font-size: .75em; }
th[aria-sort="ascending"] .app-sort-arrow,
th[aria-sort="descending"] .app-sort-arrow { color: var(--app-accent); }

/* ---------- ตารางที่กลายเป็นการ์ดบนจอเล็ก (.app-table-stack) ----------
   ป้ายของแต่ละช่องมาจาก <th> ของตารางนั้นเอง (app.js เติม data-label ให้)
   ช่องที่ติด d-none ไว้ยังถูกซ่อนตามเดิม เพราะ utility ของ Bootstrap เป็น !important */
@media (max-width: 575.98px) {
  .app-table-stack thead { display: none; }
  .app-table-stack,
  .app-table-stack tbody,
  .app-table-stack tr { display: block; width: 100%; }
  .app-table-stack tr {
    position: relative;
    margin-bottom: .5rem;
    padding: .5rem .75rem;
    background: var(--app-surface);
    border: 1px solid var(--app-line);
    border-radius: var(--app-radius);
  }
  .app-table-stack > tbody > tr > td {
    display: block;
    padding: .15rem 0;
    border-bottom-width: 0;
    text-align: left !important;   /* ทับ text-end ของช่องตัวเลข — การ์ดอ่านจากซ้ายเสมอ */
    white-space: normal;
  }
  .app-table-stack > tbody > tr > td[data-label]::before {
    content: attr(data-label);
    display: block;
    font-size: .75rem;
    color: var(--app-muted-fg);
  }
  /* ช่องเมนูท้ายแถว (หัวคอลัมน์ไม่มีป้าย) ไปอยู่มุมขวาบนของการ์ด */
  .app-table-stack > tbody > tr > td:last-child:not([data-label]) {
    position: absolute;
    top: .25rem;
    right: .25rem;
    width: auto;
  }
}

/* ---------- กราฟแท่งของรายงาน (report_chart() ใน report/table.php) ----------
   เส้นกริดเป็น hairline สีเส้นของแอป · แท่งเป็นสีการกระทำ ติดลบเป็นสีแดง
   ตัวหนังสือบนแกนใช้สีตัวหนังสือรอง ไม่ใช่สีของแท่ง */
.app-chart { width: 100%; height: auto; display: block; overflow: visible; }
.app-chart-grid { stroke: var(--app-line); stroke-width: 1; shape-rendering: crispEdges; }
.app-chart-base { stroke: var(--app-muted-fg); stroke-width: 1; shape-rendering: crispEdges; }
.app-chart-bar { fill: var(--app-accent); }
.app-chart-bar.is-neg { fill: #b91c1c; }
.app-chart-col:hover .app-chart-bar { fill: var(--app-navy-soft); }
.app-chart-hit { fill: transparent; }
.app-chart-tick { font-size: 11px; fill: var(--app-muted-fg); font-variant-numeric: tabular-nums; }

/* ---------- ปฏิทินนัดรับ (service/calendar.php) ---------- */
.app-cal { width: 100%; table-layout: fixed; border-collapse: separate; border-spacing: 4px; }
.app-cal th { font-size: .75rem; font-weight: 500; color: var(--app-muted-fg); text-align: center; }
.app-cal td {
  height: 6.5rem;
  vertical-align: top;
  padding: .25rem .35rem;
  background: var(--app-subtle);
  border-radius: var(--app-radius-sm);
}
.app-cal td.is-out { background: transparent; }
.app-cal td.is-today { background: var(--app-accent-soft); box-shadow: inset 0 0 0 1px var(--app-accent); }
.app-cal-day { font-size: .75rem; color: var(--app-muted-fg); }
.app-cal td.is-today .app-cal-day { color: var(--app-accent); font-weight: 600; }
.app-cal-job {
  display: block;
  margin-top: .15rem;
  padding: .05rem .35rem;
  font-size: .75rem;
  line-height: 1.35;
  color: var(--bs-body-color);
  text-decoration: none;
  background: var(--app-surface);
  border-left: 3px solid var(--app-accent);
  border-radius: .25rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.app-cal-job.is-late { border-left-color: #b91c1c; }
.app-cal-job:hover { background: var(--app-accent-soft); }
.app-cal-more { font-size: .7rem; color: var(--app-muted-fg); }
@media (max-width: 767.98px) {
  /* จอเล็กเหลือเฉพาะวันที่มีงาน เรียงลงมาเป็นรายการ — ตาราง 7 คอลัมน์กว้าง 50px อ่านไม่ออก */
  .app-cal thead { display: none; }
  .app-cal, .app-cal tbody, .app-cal tr { display: block; }
  .app-cal td { display: block; height: auto; margin-bottom: 4px; }
  .app-cal td.is-empty, .app-cal td.is-out { display: none; }
}

/* ---------- รูปถ่ายของใบงาน (service/view.php) ---------- */
.app-photos { display: grid; grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr)); gap: .5rem; }
.app-photo {
  position: relative;
  aspect-ratio: 1 / 1;
  max-width: 100%;
  overflow: hidden;
  background: var(--app-subtle);
  border-radius: var(--app-radius-sm);
}
.app-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.app-photo-del {
  position: absolute;
  top: .25rem;
  right: .25rem;
  width: 2rem;
  height: 2rem;
  display: grid;
  place-items: center;
  padding: 0;
  color: #fff;
  background: rgba(15, 23, 42, .65);
  border: 0;
  border-radius: 999px;
}
.app-photo-del:hover { background: #b91c1c; }

/* ---------- QR บนกระดาษ (framework/qr.php) ----------
   ขนาดจริงอยู่ที่ตัวกระดาษแต่ละใบ ที่นี่แค่ทำให้ SVG ไม่ยืดตามพ่อแม่ */
.app-qr { display: block; width: 100%; height: auto; }
.app-slip-qr { width: 17mm; flex: 0 0 auto; margin-left: 2mm; }
.app-slip-qr-cap { font-size: calc(6.5pt * var(--doc-fs, 1)); line-height: 1.15; text-align: center; color: #000; }
.app-slip80 .app-slip-qr { width: 28mm; margin: 2mm auto 0; }

/* ---------- หน้าติดตามงานของลูกค้า (service/publicstatus.php) ---------- */
.app-track-page { max-width: 34rem; margin: 1.5rem auto; padding: 0 1rem; }
.app-track-card { background: var(--app-surface); border-radius: var(--app-radius-lg); box-shadow: var(--app-shadow); padding: 1.25rem; }
.app-track-steps { list-style: none; margin: 0; padding: 0; }
.app-track-steps li { position: relative; padding: 0 0 1rem 2rem; color: var(--app-muted-fg); }
.app-track-steps li::before {
  content: "";
  position: absolute; left: .35rem; top: .35rem;
  width: .8rem; height: .8rem; border-radius: 999px;
  border: 2px solid var(--app-line); background: var(--app-surface);
}
.app-track-steps li::after {
  content: "";
  position: absolute; left: .72rem; top: 1.2rem; bottom: 0;
  border-left: 2px solid var(--app-line);
}
.app-track-steps li:last-child::after { display: none; }
.app-track-steps li.is-done { color: var(--bs-body-color); }
.app-track-steps li.is-done::before { background: var(--app-accent); border-color: var(--app-accent); }
.app-track-steps li.is-done::after { border-left-color: var(--app-accent); }
.app-track-steps li.is-current { color: var(--bs-emphasis-color); font-weight: 600; }
.app-track-steps li.is-current::before { background: var(--app-surface); border-color: var(--app-accent); box-shadow: 0 0 0 4px var(--app-accent-soft); }

/* ---------- สติกเกอร์สินค้า (inventory/labels.php) ---------- */
.app-labels { display: grid; grid-template-columns: repeat(3, 1fr); gap: 3mm; }
.app-label {
  box-sizing: border-box;
  height: 30mm;
  padding: 2mm 2.5mm;
  border: 1px dashed #cbd5e1;
  overflow: hidden;
  break-inside: avoid;
  display: flex; flex-direction: column; justify-content: space-between;
  color: #000;
  background: #fff;
}
.app-label-name { font-size: 8pt; line-height: 1.2; font-weight: 600; max-height: 2.4em; overflow: hidden; }
.app-label-code svg { display: block; width: 100%; height: 11mm; }
.app-label-foot { display: flex; justify-content: space-between; font-size: 7.5pt; font-variant-numeric: tabular-nums; }
.app-labels.is-roll { grid-template-columns: 50mm; gap: 3mm; }
.app-labels.is-roll .app-label { width: 50mm; }
@page app-label-roll { size: 50mm 30mm; margin: 0; }
@page app-label-a4 { size: A4 portrait; margin: 8mm; }
@media print {
  .app-labels { page: app-label-a4; }
  .app-labels.is-roll { page: app-label-roll; gap: 0; }
  .app-labels.is-roll .app-label { break-after: page; }
  .app-label { border-color: transparent; }
}

/* ใบเสร็จแบบตัวไม่หนา (`receipt.doc_plain`) — สี่จุดที่ตั้งต้นเป็นตัวหนา: ชื่อลูกค้า ·
   รหัสสินค้า · ยอดรวมทั้งสิ้น · ยอดตัวหนังสือ · `.fw-semibold` ของ Bootstrap เป็น
   !important การทับจึงต้อง !important เหมือนกัน · วางท้ายไฟล์ให้ชนะกฎ `is-modern`
   ที่ความเจาะจงเท่ากัน */
.app-doc.is-plain .app-doc-partyblock .fw-semibold,
.app-doc.is-plain .app-doc-baht,
.app-doc.is-plain .app-doc-sku,
.app-doc.is-plain .app-doc-sum tr:last-child th,
.app-doc.is-plain .app-doc-sum tr:last-child td {
  font-weight: 400 !important;
}

/* ใบรับเครื่องคืนแบบตัวไม่หนา (`service.handover_plain`) — คนละเปลือกกับ `.app-doc`
   จึงต้องมีกฎของตัวเอง · สี่จุดเดียวกับที่ตั้งต้นหนา: รหัสอะไหล่ · วันหมดประกัน ·
   รวมค่าซ่อม · คงเหลือ · เลขที่บนหัวกระดาษยังหนาตามเดิม (ใบเสร็จก็ไม่ได้ปลด) */
.app-handover.is-plain .app-doc-sku,
.app-handover.is-plain .app-handover-warranty .fw-semibold,
.app-handover.is-plain .app-handover-sum .fw-semibold,
.app-handover.is-plain .app-handover-sum .fw-semibold > * {
  font-weight: 400 !important;
}
