/* ==========================================================================
   app.css — สไตล์ชีตกลางของแอดมิน EROS
   สร้าง 2026-08-23 (Step 0 "รากฐาน" ของแผน front-theme-ux)
   แผนเต็ม: ~/.claude/plans/front-theme-ux-lively-kahn.md

   หลักการ
   ─────────────────────────────────────────────────────────────────────────
   - ไฟล์นี้โหลด "ก่อน" {% block head_css %} ของแต่ละหน้าเสมอ
     => หน้าที่ยังไม่ migrate ทับกฎในนี้ได้ตามปกติ (ตั้งใจ)
   - กฎที่ "เปลี่ยนพิกเซล" ต้องอยู่ใต้ body.app-v2 เท่านั้น
     หน้าไหนเติม app-v2 ใน block body_class = หน้านั้น opt-in แล้ว
     งานที่เหลือดูได้จาก:  grep -rL app-v2 main/templates
   - ห้ามใช้ @layer — Bootstrap มาจาก CDN แบบ unlayered
     และ unlayered ชนะ layered ทุกกรณี ใส่ layer แล้วจะกลับด้าน

   ลำดับ section สำคัญ ตัวหลังต้องชนะตัวหน้า
   ─────────────────────────────────────────────────────────────────────────
   1   โทเคน --app-*                    1b  ชื่อเดิมของ base.html (compat)
   2   LEGACY ALIASES --c-* (ชั่วคราว)  3   @font-face
   4   element base                     5   Bootstrap bridge
   6   app shell                        7   คอมโพเนนต์ที่ใช้ร่วม
   8   ระบบตาราง                        9   hard floors (!important)
   11  prefers-reduced-motion  <- ท้ายสุดเสมอ
   ========================================================================== */


/* ==========================================================================
   1  โทเคน — ค่าทุกตัวยกมาจาก base.html:39-60 ตรง ๆ ไม่มีอะไรเปลี่ยนบนจอ
   ========================================================================== */
:root{
  /* -- ตัวอักษร ------------------------------------------------------- */
  /*  ต่อท้ายด้วยฟอนต์ emoji ของระบบ — เดิมไม่มี ทำให้ ⚽ ✅ 🎉 ไปตกกับ
      Noto Sans SC/Lao ที่บังเอิญโหลดมาด้วย (เพราะเดิมยิงทุกตระกูลทุกภาษา)
      ได้ glyph ขาวดำแบบบังเอิญ และจะเปลี่ยนหน้าตาทันทีที่ตระกูลนั้นไม่ถูกโหลด
      ประกาศตรง ๆ ให้เป็นฟอนต์ emoji จริง = ผลลัพธ์เหมือนกันทุกเครื่องทุกภาษา
      (คลาส .emoji-flag ใช้วิธีนี้กับธงชาติอยู่แล้ว ตรงนี้แค่ทำให้ทั้งระบบเหมือนกัน) */
  --app-font-sans: "Twemoji Country Flags","SukhumvitSetMedium","Prompt","Noto Sans",
                   "Noto Sans Lao","Noto Sans Khmer","Noto Sans Myanmar","Noto Sans SC",
                   -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",
                   Arial,sans-serif,
                   "Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
  --app-font-mono: "SFMono-Regular",Consolas,"Liberation Mono",Menlo,monospace;

  /* -- แบรนด์ --------------------------------------------------------- */
  /* ม่วง Amethyst — เปลี่ยนจาก indigo #4f46e5 เมื่อ 24 ส.ค. 2569
     ขาวบน #6D4AE0 = 5.67:1 ผ่าน AA จึงใช้เป็นพื้นปุ่มที่มีตัวอักษรขาวได้ */
  --app-primary:      #6D4AE0;
  --app-primary-rgb:  109,74,224;
  --app-primary-600:  #5836C4;
  --app-primary-700:  #3B1E93;
  --app-primary-soft: #F1EDFD;
  --app-primary-line: #C9BDF3;

  /* -- พื้นผิว -------------------------------------------------------- */
  --app-bg:        #F5F3FB;   /* เดิม #f3f4f6 (เทากลาง) */
  --app-surface:   #ffffff;
  --app-surface-2: #F9F8FD;   /* เดิม #f8fafc */

  /* -- สีตัวอักษร ----------------------------------------------------- */
  --app-text:        #334155;
  --app-text-strong: #1e293b;
  --app-text-muted:  #64748b;
  --app-text-faint:  #94a3b8;

  /* -- เส้น ----------------------------------------------------------- */
  --app-border:      #e2e8f0;
  --app-border-soft: #f1f5f9;

  /* -- สถานะ ---------------------------------------------------------- */
  --app-success:#10b981; --app-success-soft:#d1fae5;
  --app-danger: #ef4444; --app-danger-soft: #fee2e2;
  --app-warning:#f59e0b; --app-warning-soft:#fef3c7;
  --app-info:   #0ea5e9; --app-info-soft:   #e0f2fe;

  /* -- มุมโค้ง / เงา -------------------------------------------------- */
  --app-r-sm:.5rem; --app-r-md:.75rem; --app-r-lg:1rem; --app-r-xl:1.25rem;
  --app-shadow-sm: 0 1px 3px rgba(0,0,0,.05), 0 1px 2px rgba(0,0,0,.03);
  --app-shadow:    0 4px 6px -1px rgba(0,0,0,.1), 0 2px 4px -1px rgba(0,0,0,.06);
  --app-shadow-lg: 0 10px 15px -3px rgba(0,0,0,.1), 0 4px 6px -2px rgba(0,0,0,.05);

  /* -- เลย์เอาต์ ------------------------------------------------------ */
  --app-sidebar-w: 260px;
  --app-rail-w:    80px;
  --app-topbar-h:  56px;
  --app-gutter:    2rem;
  --app-gutter-sm: 1rem;

  /* -- z-index — ต้องต่ำกว่า modal ของ Bootstrap (1055) และ backdrop (1050) */
  --app-z-topbar:   1030;
  --app-z-backdrop: 1035;
  --app-z-sidebar:  1040;

  /* -- การเคลื่อนไหว -------------------------------------------------- */
  --app-ease: cubic-bezier(.4,0,.2,1);
  --app-dur:  .2s;
  --app-dur-lg: .3s;
}

/* --------------------------------------------------------------------------
   1b  ชื่อโทเคนเดิมของ base.html — ยังมี 52 หน้าอ้างถึงอยู่
       ค่าเท่าเดิมทุกตัว จึงไม่มีอะไรเปลี่ยนบนจอ
       (--sidebar-bg / --sidebar-hover ไม่อยู่ที่นี่ เพราะมาจาก Mongo
        ผ่าน sidebar_bg — ประกาศใน <style> 4 บรรทัดของ base.html)
   -------------------------------------------------------------------------- */
:root{
  --primary-color:  var(--app-primary);
  --primary-hover:  var(--app-primary-600);
  --active-item-bg: var(--app-primary);
  --content-bg:     var(--app-bg);
  --card-bg:        var(--app-surface);
  --text-main:      var(--app-text);
  --text-light:     var(--app-text-faint);

  --sidebar-width:           var(--app-sidebar-w);
  --sidebar-collapsed-width: var(--app-rail-w);
  --border-radius:           var(--app-r-md);
  --card-shadow:             var(--app-shadow);
}


/* ==========================================================================
   2  LEGACY TOKEN ALIASES — ชั่วคราว. ให้ "ลบ" อย่างเดียว ห้ามแก้ค่า
   --------------------------------------------------------------------------
   คัดลอกค่าที่วันนี้อยู่ใน :root ของ 26 หน้ามาแบบคำต่อคำ
   มีไว้เพื่อให้หน้าหนึ่ง ๆ ลบ :root ของตัวเองได้โดย "0 พิกเซลเปลี่ยน"

   กฎที่ทำให้ปลอดภัย: alias คงค่า literal เดิม — ห้าม remap ไป var(--app-*)
   ถ้า --c-primary ชี้ไป --app-primary จะมี 17 หน้าเปลี่ยนจากบานเย็นเป็น
   indigo ทันทีในคอมมิตเดียว การรวมสีต้องเกิดตอน migrate หน้านั้น ๆ
   ที่เจ้าของเห็นและอนุมัติ

   ตอนนี้บล็อกนี้ยังไม่ทำงานเลย เพราะทุกหน้าที่ใช้ var(--c-*) ประกาศเอง
   และ <style> ของหน้าเรนเดอร์ทีหลัง = ชนะ
   (ตรวจแล้ว: ไม่มีหน้าไหนใช้ --c-* ที่ไม่ได้ประกาศเอง)

   ลบทีละบรรทัดเมื่อ  python tools/css_audit.py --token --c-primary  รายงาน 0
   บล็อกว่างเมื่อไร ลบทั้ง section
   ========================================================================== */
:root{
  --c-bg-body:    #f8f9fa;   /* 25 ไฟล์ (sec_5_23 ใช้ #f8fafc) */
  --c-card-bg:    #ffffff;   /* 26 ไฟล์ — เอกฉันท์ */
  --c-text-main:  #344767;   /* 25 ไฟล์ (sec_5_12 ใช้ #0f172a) */
  /* เดิม #8392ab ซึ่งบนพื้นขาวได้คอนทราสต์แค่ 3.15:1 ต่ำกว่าเกณฑ์ AA (4.5:1)
     และมันถูกใช้เป็นสีหัวตาราง/ข้อความรองใน 25 ไฟล์ = อ่านยากทั้งระบบ
     วัดเทียบหลายเฉดแล้ว #64748b เป็นตัวที่เข้มขึ้นน้อยที่สุดที่ยังผ่านทั้ง
     บนขาว (4.76:1) และบนพื้นการ์ด #f8fafc (4.55:1)
     และเป็นค่าที่ sec_5_12 ใช้อยู่แล้ว จึงไม่ได้เพิ่มเฉดใหม่เข้าระบบ

     ⚠️ ข้อนี้ "เปลี่ยนพิกเซล" ต่างจาก alias ตัวอื่นในบล็อกนี้ที่คงค่าเดิมเป๊ะ
        ตั้งใจให้เปลี่ยน เพราะของเดิมอ่านไม่ผ่านเกณฑ์ */
  --c-text-muted: #64748b;   /* 25 ไฟล์ */
  --c-primary:    #cb0c9f;   /* 17 ไฟล์ — บานเย็น ตั้งใจ ดู CONFLICT C1 ในแผน */
  --c-border:     #e9ecef;   /* sec_5_12 (sec_5_23 ใช้ #e2e8f0) */
  --c-dark:       #344767;   /* login.html */
  --c-danger:     #ea0606;   /* sec_3_6 */
  --c-warning:    #fb8c00;   /* sec_3_6 */
  --c-success:    #2dce89;   /* sec_3_6 */
}


/* --- ยกมาจาก :root ของแต่ละหน้า (Wave 6) -----------------------------------
   52 หน้าประกาศ :root ของตัวเอง ซึ่งเป็นตัวแปรระดับเอกสาร ไม่ใช่ของหน้า
   ทำให้แก้สีทีเดียวทั้งระบบไม่ได้

   เจ็ดตัวนี้ทุกหน้าให้ค่า "ตรงกันหมด" จึงยกขึ้นมาด้วยค่าเดิมเป๊ะ
   แล้วลบประกาศในหน้าทิ้งได้โดยพิกเซลไม่เปลี่ยนแม้แต่จุดเดียว

   ⚠️ ห้าม remap ไป var(--app-*) ตรงนี้ ค่าจะเปลี่ยนทันทีหลายสิบหน้า
      การรวมสีเป็นเรื่องที่ต้องให้เจ้าของเห็นก่อน                          */
:root{
  /* indigo เดิม — ยกด้วยค่าเดิมเป๊ะเพื่อไม่ให้พิกเซลเปลี่ยน */
  --primary:       #4f46e5;   /* 14 หน้า */
  --border:        #e2e8f0;   /* 14 หน้า */
  --border-color:  #e2e8f0;   /* 9 หน้า */
  /* พื้นของคอลัมน์ที่ตรึงไว้ */
  --sticky-bg:     #ffffff;   /* 8 หน้า */
  --bg-card:       #ffffff;   /* 6 หน้า */
  --success:       #10b981;   /* 3 หน้า */
  --danger:        #ef4444;   /* 3 หน้า */
}

/* ==========================================================================
   3  @font-face — ย้ายมาจาก base.html:21-37 (path สัมพัทธ์จาก /static/css/)
   ========================================================================== */
/* WOFF2 มาก่อน — ไฟล์เดิมเป็น TTF ดิบ 82.9 KB ไม่ได้บีบอัดเลย
   ทั้งที่เป็นฟอนต์ตัวแรกในสแตก = โหลดทุกหน้า ทุกคน
   WOFF2 (Brotli) เหลือ 26.9 KB ตัวอักษรเหมือนเดิมทุกประการ
   คง TTF ไว้เป็นทางถอย เบราว์เซอร์ที่รองรับ WOFF2 จะไม่โหลดตัวนี้เลย
   สร้างด้วย tools/make_font_woff2.py */
@font-face{
  font-family:"SukhumvitSetMedium";
  src:url("../fonts/SukhumvitSet-Medium.woff2") format("woff2"),
      url("../fonts/SukhumvitSet-Medium.ttf") format("truetype");
  font-weight:400; font-style:normal; font-display:swap;
}
/* ฟอนต์สำรองของธงชาติ — ต้องมาก่อน Prompt ใน --app-font-sans
   ไม่งั้นธงในตัวเลือกภาษากลายเป็นตัวอักษร TH/EN */
/* ตัดเหลือเฉพาะธงของ 8 ภาษาที่ระบบรองรับ 76.5 KB -> 27.4 KB
   ฟอนต์เต็มมีธงกว่า 250 ประเทศซึ่งไม่มีที่ไหนในระบบใช้เลย
   ⚠️ เพิ่มภาษาใหม่เมื่อไรต้องรัน tools/make_flag_font_subset.py ใหม่
      ไม่งั้นธงของภาษานั้นจะกลายเป็นตัวอักษรสองตัว (เช่น JP) แทนธง */
@font-face{
  font-family:"Twemoji Country Flags";
  src:url("../fonts/TwemojiCountryFlags-subset.woff2") format("woff2");
  font-weight:400; font-style:normal; font-display:swap;
  unicode-range:U+1F1E6-1F1FF, U+1F3F4, U+E0062-E0063, U+E0065, U+E0067,
                U+E006C, U+E006E, U+E0073-E0074, U+E0077, U+E007F;
}


/* ==========================================================================
   4  element base — ยกมาจาก base.html:62-75 ไม่เปลี่ยนค่า
   ========================================================================== */
body{
  margin:0; padding:0;
  background-color:var(--content-bg);
  font-family:var(--app-font-sans);
  color:var(--text-main);
  font-size:.95rem;
  overflow-x:hidden;
}
input, button, select, textarea{ font-family:var(--app-font-sans); }


/* ==========================================================================
   5  Bootstrap bridge
   --------------------------------------------------------------------------
   5a  ทาสีคอมโพเนนต์ Bootstrap ใหม่ทั้งระบบ

   24 ส.ค. 2569 เจ้าของสั่ง "ม่วงทั้งระบบ เปลี่ยนทีเดียวตอนทำเปลือก"
   บล็อกนี้จึงถอดสวิตช์ body.app-v2 ออกแล้ว มีผลกับทั้ง 76 หน้าพร้อมกัน
   (ก่อนหน้านี้กั้นไว้เพราะเกณฑ์ตอนนั้นคือ "desktop diff = 0.0%")

   จุดที่เปลี่ยน: .btn-primary 71 จุด · .text-primary 201 · .bg-primary 49 ·
   .border-primary 17 · pagination · nav-pills · progress · focus ring ของฟอร์ม

   BS 5.3 คอมไพล์ #0d6efd ลงตัวแปรของปุ่มตรง ๆ การตั้ง --bs-primary
   อย่างเดียวไม่พอ ต้องเขียน --bs-btn-* เอง

   สวิตช์ app-v2 ยังอยู่ ใช้กับ section 7 (สกัดคอมโพเนนต์รายหน้า) และ 9.3 ต่อไป
   ========================================================================== */
:root{
  --bs-font-sans-serif:  var(--app-font-sans);
  --bs-body-font-family: var(--app-font-sans);

  --bs-primary:          var(--app-primary);
  --bs-primary-rgb:      var(--app-primary-rgb);
  --bs-link-color:       var(--app-primary);
  --bs-link-color-rgb:   var(--app-primary-rgb);
  --bs-link-hover-color: var(--app-primary-600);
}
.btn-primary{
  --bs-btn-bg:                    var(--app-primary);
  --bs-btn-border-color:          var(--app-primary);
  --bs-btn-hover-bg:              var(--app-primary-600);
  --bs-btn-hover-border-color:    var(--app-primary-600);
  --bs-btn-active-bg:             var(--app-primary-700);
  --bs-btn-active-border-color:   var(--app-primary-700);
  --bs-btn-disabled-bg:           var(--app-primary);
  --bs-btn-disabled-border-color: var(--app-primary);
  --bs-btn-focus-shadow-rgb:      var(--app-primary-rgb);
}
.btn-outline-primary{
  --bs-btn-color:               var(--app-primary);
  --bs-btn-border-color:        var(--app-primary);
  --bs-btn-hover-bg:            var(--app-primary);
  --bs-btn-hover-border-color:  var(--app-primary);
  --bs-btn-active-bg:           var(--app-primary);
  --bs-btn-active-border-color: var(--app-primary);
  --bs-btn-focus-shadow-rgb:    var(--app-primary-rgb);
}
.pagination{
  --bs-pagination-active-bg:           var(--app-primary);
  --bs-pagination-active-border-color: var(--app-primary);
  --bs-pagination-color:               var(--app-text-muted);
  --bs-pagination-hover-color:         var(--app-primary);
}
.nav-pills{ --bs-nav-pills-link-active-bg: var(--app-primary); }
.list-group{
  --bs-list-group-active-bg:           var(--app-primary);
  --bs-list-group-active-border-color: var(--app-primary);
}
.progress{ --bs-progress-bar-bg: var(--app-primary); }
/* :focus ของ .form-control และ .form-check-input:checked ฝัง #86b7fe / #0d6efd
   ไว้ตรง ๆ ไม่ผ่านตัวแปร จึงต้องประกาศทับเอง */
.form-control:focus,
.form-select:focus{
  border-color: rgba(var(--app-primary-rgb),.5);
  box-shadow: 0 0 0 .2rem rgba(var(--app-primary-rgb),.15);
}
.form-check-input:checked{
  background-color: var(--app-primary);
  border-color:     var(--app-primary);
}
.form-check-input:focus{
  border-color: rgba(var(--app-primary-rgb),.5);
  box-shadow: 0 0 0 .2rem rgba(var(--app-primary-rgb),.15);
}


/* ==========================================================================
   6  APP SHELL — mobile-first (Step 2, 24 ส.ค. 2569)
   --------------------------------------------------------------------------
   ของเดิม: .sidebar เป็น flex item กว้าง 260px ตายตัว "ทุกขนาดจอ"
   และ @media ในเปลือกทั้งระบบมีอยู่บรรทัดเดียวซึ่งแก้แค่ padding
   บนจอ 375px เนื้อหาจึงเหลือ ~115px ทุกหน้า และ body{overflow-x:hidden}
   ทำหน้าที่ "ซ่อน" ผลลัพธ์แทนที่จะแก้

   ตอนนี้: < 992px เป็นลิ้นชักเลื่อนเข้า-ออก (transform) พร้อม backdrop
           >= 992px ยืนอยู่กับที่แบบ sticky ย่อ/กางได้ จำสถานะด้วย cookie
   ========================================================================== */
.app-layout{
  width:100%;
  display:flex;
  min-height:100vh;
}
@supports (min-height:100dvh){ .app-layout{ min-height:100dvh; } }

/* ------------------- TOPBAR (จอเล็กเท่านั้น) ------------------- */
.app-topbar{
  position:sticky;
  top:0;
  z-index:var(--app-z-topbar);
  display:flex;
  align-items:center;
  gap:.75rem;
  height:calc(var(--app-topbar-h) + env(safe-area-inset-top));
  padding-top:env(safe-area-inset-top);
  padding-inline:.75rem;
  background:var(--sidebar-bg);
  color:#fff;
  box-shadow:0 1px 0 rgba(0,0,0,.08);
}
body.sidebar-light .app-topbar{
  color:var(--app-text-strong);
  box-shadow:0 1px 0 var(--app-border);
}
.app-burger{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  width:44px; height:44px;
  padding:0;
  font-size:1.35rem;
  color:inherit;
  background:transparent;
  border:0;
  border-radius:10px;
  cursor:pointer;
}
.app-burger:hover{ background:rgba(127,127,127,.18); }
.app-topbar__brand{
  font-weight:600;
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis;
}

/* ------------------- BACKDROP (จอเล็กเท่านั้น) ------------------- */
.sidebar-backdrop{
  position:fixed;
  inset:0;
  z-index:var(--app-z-backdrop);
  background:rgba(20,10,45,.55);
  opacity:0;
  transition:opacity var(--app-dur-lg) var(--app-ease);
}
body.sidebar-open .sidebar-backdrop{ opacity:1; }
.sidebar-backdrop[hidden]{ display:none; }

/* ------------------- SIDEBAR ------------------- */
.sidebar{
  background-color:var(--sidebar-bg);
  color:#fff;
  white-space:nowrap;
  display:flex;
  flex-direction:column;
  /* overflow:hidden ย้ายลงไปที่ .sidebar-body — ของเดิมตัดที่ระดับนี้
     เมนูเลือกภาษาซึ่งเป็น dropup กว้าง 200px จึงถูกตัดเหลือ ~64px
     ตอนเมนูย่อ = กดใช้ไม่ได้เลย */
  overflow:visible;
  box-shadow:4px 0 10px rgba(0,0,0,0.05);
}

/* ---------- < 992px : ลิ้นชักเลื่อนเข้า-ออก ---------- */
@media (max-width: 991.98px){
  .app-layout{ display:block; }          /* topbar อยู่บน เนื้อหาอยู่ล่าง */
  .sidebar{
    position:fixed;
    inset-block:0;
    inset-inline-start:0;
    /* กว้างตามเนื้อหาเช่นกัน แต่ห้ามเกิน 86vw — ต้องเหลือขอบให้เห็นว่า
       ยังมีหน้าอยู่ข้างหลัง และมีที่ให้แตะปิดลิ้นชัก */
    width:fit-content;
    min-width:min(var(--app-sidebar-w), 86vw);
    max-width:86vw;
    height:100vh;
    z-index:var(--app-z-sidebar);
    /* animate transform อย่างเดียว ไม่ใช่ width — ไม่ทำให้เกิด layout ทุกเฟรม */
    transform:translateX(-100%);
    transition:transform var(--app-dur-lg) var(--app-ease);
    padding-inline-start:env(safe-area-inset-left);
  }
  body.sidebar-open .sidebar{ transform:none; }

  body.sidebar-open{ overflow:hidden; }  /* ล็อกไม่ให้เนื้อหาข้างหลังเลื่อน */
}
@supports (height:100dvh){
  @media (max-width: 991.98px){ .sidebar{ height:100dvh; } }
}

/* ---------- >= 992px : ยืนอยู่กับที่ ย่อ/กางได้ ---------- */
@media (min-width: 992px){
  .app-topbar,
  .sidebar-backdrop{ display:none; }
  .sidebar{
    /* position:sticky แก้สามอย่างพร้อมกัน:
       1) z-index:1000 เดิมไม่มีผลเลยเพราะ element เป็น position:static
       2) เงาขอบขวาถูก .content-wrapper ทับ (มันมาทีหลังและพื้นทึบ)
       3) หน้ายาว ๆ เมนูกับตัวสลับภาษาเลื่อนหายไปท้ายเอกสาร */
    position:sticky;
    top:0;
    align-self:flex-start;
    height:100vh;
    z-index:var(--app-z-sidebar);
    flex:0 0 auto;
    /* เจ้าของสั่ง: เมนูยาวให้ "ขยายแถบ" แทนการเลื่อนซ้ายขวา
       fit-content = กว้างตามป้ายที่ยาวสุด (เนื้อในถูกกำหนดโดย
       .nav-pills{width:max-content}) ไม่แคบกว่า 260px เดิม
       และไม่เกิน 21rem กันป้ายยาวผิดปกติกินจอ */
    width:fit-content;
    min-width:var(--app-sidebar-w);
    max-width:21rem;
    /* จังหวะย่อ/กางบีบผ่าน min/max-width — ค่า fit-content เอง
       animate ไม่ได้ แต่ขอบบน-ล่างของมัน interpolate ได้
       (คงหลักเดิม: ห้าม transition:all ที่จะลาก flex-basis ไปด้วย) */
    transition:min-width var(--app-dur-lg) var(--app-ease-out),
               max-width var(--app-dur-lg) var(--app-ease-out);
    will-change:min-width, max-width;
  }
  body[data-sidebar="collapsed"] .sidebar{
    min-width:var(--app-rail-w);
    max-width:var(--app-rail-w);
  }
}
@supports (height:100dvh){
  @media (min-width: 992px){ .sidebar{ height:100dvh; } }
}

/* --- Sidebar Header ---------------------------------------------------------
   เดิม: โลโก้ 45px + ชื่อแบรนด์เป็น text node เปล่า ๆ + padding 1.5rem
   ในแถบกว้าง 260px เหลือที่ให้ชื่อแค่ ~111px และถ้าชื่อยาวกว่านั้นจะถูก
   overflow:hidden ตัดหายกลางคำโดยไม่มี ... บอกว่ายังมีต่อ
   -------------------------------------------------------------------------- */
.sidebar-header{
  padding:0.95rem 1rem;
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:0.6rem;
  background-color:rgba(0,0,0,0.10);
  border-bottom:1px solid rgba(255,255,255,0.06);
  flex-shrink:0;
  /* transition:all จะ animate padding ทุกด้านตอนย่อเมนู ระบุเฉพาะที่ต้องการ */
  transition:padding var(--app-dur-lg) var(--app-ease-out);
}
.sidebar-header .brand-text{
  display:flex;
  align-items:center;
  gap:0.6rem;
  min-width:0;          /* ต้องมี ไม่งั้นลูกที่เป็น flex item ย่อไม่ได้จริง */
  color:#fff;
}

/* โลโก้วางบนพื้นเมนูตรง ๆ ไม่มีแผ่นรอง (เจ้าของสั่ง) */
.brand-logo{
  width:40px; height:40px;
  flex:0 0 auto;
  object-fit:contain;
}

.brand-name{
  min-width:0;
  font-size:1.02rem;
  font-weight:700;
  letter-spacing:-0.01em;
  /* 1.15 แคบเกินสำหรับชื่อแบรนด์ภาษาไทยที่มีสระบน-ล่าง พอคู่กับ
     overflow:hidden ข้างล่างจะตัดวรรณยุกต์หาย */
  line-height:1.45;
  /* ยาวเกินให้ตัดด้วย ... ไม่ใช่หายเฉย ๆ แบบเดิม */
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis;
  max-width:20ch;          /* ค่าตั้งต้นที่ animate ได้ (ยาวกว่าชื่อจริง) */
  /* จางเข้า "หลัง" แถบกางเสร็จ — เหมือนป้ายเมนู
     เดิมใช้ display:none ตอนย่อ ซึ่งถูกปลดทันทีที่กดกาง ขณะแถบยังกว้าง
     แค่ 80px ชื่อจึงถูกบีบเป็น "tpt..." แวบหนึ่งทุกครั้งที่กางเมนู */
  transition:opacity var(--app-dur) var(--app-ease-out) var(--app-dur-lg),
             max-width var(--app-dur-lg) var(--app-ease-out);
}

/* ย่อเมนูเหลือแถบแคบ 80px: วางโลโก้กับปุ่มซ้อนกันเป็นสองแถว
   เดิมซ่อนโลโก้ทั้งอัน (brand-text{width:0}) เหลือแต่ปุ่มลอย ๆ = แบรนด์หายไปเลย
   80px - padding 16 = 64px ซึ่งไม่พอวางโลโก้ 38 + ปุ่ม 32 เรียงกัน จึงต้องซ้อน */
body[data-sidebar="collapsed"] .sidebar .sidebar-header{
  flex-direction:column;
  justify-content:center;
  gap:0.5rem;
  padding:0.85rem 0.5rem;
}
body[data-sidebar="collapsed"] .sidebar .sidebar-header .brand-text{
  width:auto; opacity:1; margin:0;
}
body[data-sidebar="collapsed"] .sidebar .brand-name{
  max-width:0; opacity:0; margin:0;
  transition-delay:0s, 0s;
}
.toggle-btn{
  background:color-mix(in srgb, var(--sidebar-bg), #fff 14%);
  border:none;
  color:inherit;
  font-size:1rem;
  width:34px;
  height:34px;
  display:flex;
  align-items:center;
  justify-content:center;
  border-radius:10px;
  cursor:pointer;
  flex-shrink:0;
  /* transition:all จะ animate width/height ด้วย ซึ่งเป็นค่าที่กฎ 9.2
     เปลี่ยนตอนจอเล็ก = ปุ่มค่อย ๆ พองตอนหมุนจอ  ระบุเฉพาะที่ต้องการ */
  transition:background-color var(--app-dur) var(--app-ease-out),
             color var(--app-dur) var(--app-ease-out),
             transform var(--app-dur-release) var(--app-ease-out);
}
.toggle-btn:hover{ background-color:var(--app-primary); color:#fff; }
/* ลูกศรบนปุ่มหมุนตามสถานะ บอกทิศว่ากดแล้วจะไปทางไหน */
.toggle-btn > i{
  transition:transform var(--app-dur-lg) var(--app-ease-out);
  display:block;
}
body[data-sidebar="collapsed"] .sidebar .toggle-btn > i{ transform:rotate(180deg); }

/* --- Sidebar Body --- */
.sidebar-body{
  flex-grow:1;
  /* ตัดขอบที่นี่แทนที่จะตัดที่ .sidebar — ตัวสลับภาษาอยู่ใน .sidebar-footer
     จึงไม่โดนตัด และ dropup กางออกนอกกรอบเมนูได้ */
  /* เจ้าของสั่ง: ข้อความเมนูยาวให้เลื่อนซ้ายขวาอ่านเต็มได้ ไม่ตัด ...
     (เดิม hidden ซึ่งกลืนส่วนที่ยาวเกินทิ้งเงียบ ๆ) */
  overflow-x:auto;
  scrollbar-width:thin;
  overflow-y:auto;
  overscroll-behavior:contain;
  padding:1rem 0.5rem;
  display:flex;
  flex-direction:column;
  min-height:0;
}
.sidebar-body::-webkit-scrollbar{ width:5px; }
.sidebar-body::-webkit-scrollbar-track{ background:transparent; }
.sidebar-body::-webkit-scrollbar-thumb{ background:rgba(255,255,255,0.2); border-radius:10px; }
.sidebar-body::-webkit-scrollbar-thumb:hover{ background:rgba(255,255,255,0.3); }

/* --- Sidebar Footer --- */
.sidebar-footer{
  padding:1rem;
  background-color:rgba(0,0,0,0.15);
  border-top:1px solid rgba(255,255,255,0.05);
  flex-shrink:0;
  transition:all 0.3s ease;
}
body[data-sidebar="collapsed"] .sidebar .sidebar-footer{
  padding:1rem 0.5rem;
  display:flex;
  justify-content:center;
}

/* --- Language Selector --- */
.lang-switch-btn{
  background-color:rgba(255,255,255,0.05);
  border:1px solid rgba(255,255,255,0.08);
  color:#e2e8f0;
  border-radius:12px;
  padding:0.7rem 0.8rem;
  transition:all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  width:100%;
  display:flex;
  align-items:center;
  justify-content:space-between;
  overflow:hidden;
}
.lang-switch-btn:hover,
.lang-switch-btn[aria-expanded="true"]{
  background-color:rgba(255,255,255,0.1);
  border-color:rgba(255,255,255,0.2);
  color:#fff;
  box-shadow:0 -4px 15px rgba(0,0,0,0.1);
}
.lang-flag-icon{
  font-size:1.4rem;
  line-height:1;
  filter:drop-shadow(0 1px 2px rgba(0,0,0,0.15));
  transition:transform 0.3s ease;
}
.emoji-flag{
  font-family:"Twemoji Country Flags","Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji","Twemoji Mozilla",sans-serif;
  font-variant-emoji:emoji;
}
.lang-text-wrapper{
  display:flex;
  align-items:center;
  gap:0.75rem;
  overflow:hidden;
  white-space:nowrap;
  transition:all 0.3s ease;
  flex-grow:1;
}
.lang-text-label{ font-weight:500; letter-spacing:0.3px; font-size:0.9rem; }
.lang-arrow-icon{
  font-size:0.75rem;
  opacity:0.6;
  transition:all 0.3s ease;
  margin-left:0.5rem;
}
body[data-sidebar="collapsed"] .sidebar .lang-switch-btn{
  width:48px;
  height:48px;
  padding:0;
  justify-content:center;
  background-color:transparent;
  border-color:rgba(255,255,255,0.05);
}
body[data-sidebar="collapsed"] .sidebar .lang-switch-btn:hover{ background-color:rgba(255,255,255,0.08); }
body[data-sidebar="collapsed"] .sidebar .lang-text-label,
body[data-sidebar="collapsed"] .sidebar .lang-arrow-icon{ opacity:0; width:0; margin:0; }
body[data-sidebar="collapsed"] .sidebar .lang-text-wrapper{ gap:0; flex-grow:0; }
body[data-sidebar="collapsed"] .sidebar .lang-flag-icon{ font-size:1.6rem; }
body[data-sidebar="collapsed"] .sidebar .lang-switch-btn:hover .lang-flag-icon{ transform:scale(1.1); }

/* Dropup Menu */
.lang-dropdown-menu{
  background-color:#2b2b40 !important;
  border:1px solid rgba(255,255,255,0.1) !important;
  box-shadow:0 -10px 25px rgba(0,0,0,0.2);
  padding:0.5rem;
  margin-bottom:0.75rem !important;
  border-radius:12px !important;
  min-width:200px;
}
.lang-dropdown-item{
  color:#cbd5e1;
  border-radius:8px;
  padding:0.7rem 1rem;
  transition:all 0.15s ease;
  display:flex;
  align-items:center;
  justify-content:space-between;
  font-weight:400;
}
.lang-dropdown-item:hover{
  background-color:rgba(255,255,255,0.05);
  color:#fff;
  transform:translateX(3px);
}
.lang-dropdown-item.active-lang{
  background-color:rgba(79,70,229,0.15);
  color:var(--primary-color);
  font-weight:600;
}

/* --- Navigation Items -------------------------------------------------------
   ปุ่มเป็น "เม็ดยา" ที่เว้นขอบจากขอบแถบทั้งสองข้าง ไม่ใช่แถบยาวชนขอบ
   ทำให้เห็นเป็นรายการที่แยกจากกัน และตัวที่เลือกอยู่ดูลอยขึ้นมา
   -------------------------------------------------------------------------- */
.nav-pills .nav-link{
  position:relative;
  display:flex;
  align-items:center;
  gap:0.7rem;
  padding:0.6rem 0.7rem;
  margin:0 0.15rem 0.12rem;
  border-radius:12px;
  /* เท่าของเดิมก่อนรื้อ (เจ้าของสั่ง) — ของเดิมไม่ระบุ = สืบทอด 0.95rem */
  font-size:0.95rem;
  font-weight:500;
  /* เดิม #cbd5e1 เป็นสีตายตัว ใช้ได้เฉพาะบนพื้นเข้ม
     color-mix ผสมจากสีพื้นเมนูเอง จึงอ่านออกไม่ว่าแอดมินจะตั้ง sidebar_bg
     เป็นสีอะไรจากหน้า 6.1 */
  color:color-mix(in srgb, var(--sidebar-bg), #fff 76%);
  /* transition:all จะ animate ทุกอย่างรวม padding/margin ตอนย่อเมนู
     ระบุเฉพาะที่ต้องการ และต้องมี transform ไม่งั้นจังหวะกดกระตุก */
  transition:background-color var(--app-dur) var(--app-ease-out),
             color var(--app-dur) var(--app-ease-out),
             box-shadow var(--app-dur) var(--app-ease-out),
             transform var(--app-dur-release) var(--app-ease-out);
}

/* ไอคอนมีกล่องกว้างเท่ากันทุกตัว ตัวหนังสือจึงเริ่มตรงแนวเดียวกันหมด
   ถ้าปล่อยตามความกว้างของ glyph ขอบซ้ายของข้อความจะเยื้องกันทีละนิด */
.nav-pills .nav-link > i:first-child{
  flex:0 0 auto;
  width:24px;
  font-size:1.25rem;   /* เท่าของเดิมก่อนรื้อ ให้สมดุลกับตัวหนังสือ */
  line-height:1;
  text-align:center;
  opacity:0.75;
  transition:opacity var(--app-dur) var(--app-ease-out);
}

.nav-pills .nav-link:hover{
  background-color:color-mix(in srgb, var(--sidebar-bg), #fff 13%);
  color:color-mix(in srgb, var(--sidebar-bg), #fff 97%);
}
.nav-pills .nav-link:hover > i:first-child{ opacity:1; }

/* เมนูที่เลือกอยู่
   ต้องเป็นสีทึบ ไม่ใช่สีโปร่ง — เคยใช้ม่วงโปร่ง 28% เพราะอยากให้ดูสงบ
   แต่ sidebar_bg เป็นค่าที่แอดมินตั้งเองได้ทุกสี พอพื้นเป็นแดงเข้ม #5c0000
   สีที่ผสมออกมาต่างจากพื้นแค่ 1.1:1 = แทบมองไม่ออกว่าอยู่เมนูไหน
   (วัดจริงด้วยการวาดลง canvas แล้วอ่านพิกเซล)
   ไล่สีทึบให้ผลสม่ำเสมอทุกค่าพื้น และตัวอักษรขาวบนม่วงนี้ = 5.67:1 ผ่าน AA */
.nav-pills .nav-link.active{
  background-image:linear-gradient(135deg,
      var(--app-primary) 0%, var(--app-primary-600) 100%);
  color:#fff;
  font-weight:600;
  box-shadow:0 8px 18px -8px rgba(var(--app-primary-rgb), .85);
}
.nav-pills .nav-link.active > i:first-child{ opacity:1; }
/* ขอบในบาง ๆ ไว้เผื่อกรณีแอดมินตั้งพื้นเมนูเป็นม่วงเฉดเดียวกัน
   ปกติมองไม่เห็น แต่เป็นตัวช่วยสุดท้ายให้ยังแยกปุ่มออกจากพื้นได้ */
.nav-pills .nav-link.active::after{
  content:"";
  position:absolute; inset:0;
  border-radius:inherit;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.20);
  pointer-events:none;
}

/* ลูกศรบอกว่าหมวดนี้กางได้ — เดิมทั้ง 10 หมวดไม่มีอะไรบอกเลย
   ผู้ใช้ต้องลองกดเองถึงจะรู้ว่ามีเมนูย่อยซ่อนอยู่ */
.nav-parent{ position:relative; }
.nav-parent .nav-caret{
  margin-left:auto;
  font-size:0.7rem;
  width:auto;
  opacity:0.55;
  transition:transform var(--app-dur) var(--app-ease-out);
}
.nav-parent.is-open .nav-caret,
.nav-parent[aria-expanded="true"] .nav-caret{ transform:rotate(180deg); opacity:0.9; }
/* ตอนย่อเมนูเหลือแถบแคบ ลูกศรไม่มีที่อยู่ — หุบให้เข้าจังหวะเดียวกับป้าย
   ใช้ max-width ไม่ใช่ display:none ซึ่งตัดจบทันทีและเห็นเป็นการกระตุก */
.nav-parent .nav-caret{
  max-width:1rem; overflow:hidden;
  transition:transform var(--app-dur) var(--app-ease-out),
             opacity var(--app-dur) var(--app-ease-out) var(--app-dur-lg),
             max-width var(--app-dur-lg) var(--app-ease-out);
}
body[data-sidebar="collapsed"] .sidebar .nav-caret{
  max-width:0; opacity:0; margin:0;
  transition-delay:0s, 0s, 0s;
}

.sidebar-body::-webkit-scrollbar{ height:6px; }
.sidebar-body::-webkit-scrollbar-thumb{
  background:color-mix(in srgb, var(--sidebar-bg), #fff 25%);
  border-radius:3px;
}

/* ── ป้ายชื่อเมนู กับจังหวะย่อ/ขยาย ─────────────────────────────────────────
   บั๊กที่วัดเจอ: li.nav-item กว้าง 257px ทั้งที่ ul แม่กว้าง 239px
   ทำให้ปุ่มเมนูยื่นพ้นขอบแถบไป 2px และ .sidebar-body มีสกรอลล์แนวนอน
   ซ่อนอยู่ 10px (มองไม่เห็นเพราะโดน overflow:hidden กลืน)

   ต้นเหตุ: ป้ายเป็น white-space:nowrap และเมนูย่อยมีทั้ง padding-left ลึก
   และข้อความยาว -> min-content ของ li โตกว่าแถบ  flex item ยืดตามแม่ไม่ได้
   เพราะ min-width:auto ไม่ยอมให้หดต่ำกว่าเนื้อใน

   ต้อง min-width:0 ทุกชั้นที่เป็น flex item ไม่งั้นกฎตัดคำไม่มีผล
   -------------------------------------------------------------------------- */
/* กว้างตามป้ายที่ยาวสุด แต่ไม่แคบกว่าแถบ — ปุ่มทุกตัวจึงยาวเท่ากัน
   และพื้น hover/active คลุมเต็มความยาวตอนเลื่อนไปดูท้ายข้อความ */
.sidebar .nav-pills{ width:max-content; min-width:100%; }
.sidebar .nav-pills > li{ min-width:0; }
.nav-pills .nav-link{ min-width:0; }

.hide-text{
  min-width:0;
  /* ยาวเกินให้ตัดด้วย ... ไม่ใช่ดันแถบให้กว้างขึ้น */
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis;
  /* ย่อ/ขยาย: จางหายพร้อมหุบความกว้าง จะได้ไม่กระตุกตอนตัวหนังสือหาย
     ป้ายต้องจางออก "ก่อน" แถบหุบ (delay 0) และจางเข้า "หลัง" แถบกางเสร็จ
     (delay เท่าเวลาแถบกาง) ไม่งั้นตัวหนังสือจะโผล่ทับขอบแถบระหว่างทาง */
  transition:opacity var(--app-dur) var(--app-ease-out) var(--app-dur-lg),
             max-width var(--app-dur-lg) var(--app-ease-out);
  max-width:100%;
}

/* เฉพาะป้ายที่อยู่ "ในปุ่มเมนู" เท่านั้น
   flex:1 1 auto ใช้ไม่ได้ — basis auto แปลว่า "ความกว้างของเนื้อใน"
   ซึ่งคือค่าที่ไปดันแถบให้กว้าง  ต้อง basis 0 และ width 0 คู่กัน
   (Chrome ยังเอา width:auto ไปคิด min-content ของแม่แม้ basis เป็น 0)

   ⚠️ ห้ามใส่ที่ .hide-text เฉย ๆ  คลาสนั้นใช้กับป้ายอื่นในแถบด้วย
      ตัวที่ไม่ได้อยู่ในปุ่มเมนูจะโดนบีบจนอ่านไม่ออก
      (เคยเกิดกับหัวข้อกลุ่ม "เมนูหลัก" ซึ่งเหลือ 27px จากที่ต้องใช้ 69px
       ตอนนี้หัวข้อนั้นถูกเอาออกไปแล้ว แต่กฎยังต้องเล็งให้แคบเหมือนเดิม) */
/* เจ้าของสั่งให้เลื่อนแนวนอนอ่านข้อความยาวแทนการตัด ...
   ป้ายจึงกลับไปกว้างตามเนื้อความ (flex:0 0 auto) และ .sidebar-body
   เป็นตัวเลื่อน  max-width:60ch ไม่ใช่การตัด — ยาวกว่าป้ายจริงทุกตัว
   มีไว้เพราะกฎย่อรางหุบป้ายด้วย max-width:0 ซึ่งต้องมีค่าตั้งต้นที่
   animate ได้ (none animate ไม่ได้ ป้ายจะกระตุกตอนย่อ/กาง) */
.nav-link > .hide-text{
  flex:0 0 auto;
  width:auto;
  max-width:60ch;
  text-overflow:clip;
}
/* ย่อแล้ว: หุบเหลือศูนย์แต่ "ไม่ใช้ display:none"
   เพราะป้ายคือชื่อที่ screen reader ใช้เรียกปุ่ม ถ้าเอาออกจาก DOM
   ปุ่มไอคอนล้วนจะไม่มีชื่อเลย  หุบด้วย max-width จึงยังอ่านออกด้วยเสียง */
body[data-sidebar="collapsed"] .sidebar .nav-link > .hide-text{
  max-width:0; opacity:0; margin:0;
  transition-delay:0s, 0s;
}

/* ราง 80px มีเนื้อที่จริงแค่ 64px  ปุ่มเดิมกิน 69px จึงล้นออกไป 7px
   ต้นเหตุคือ gap ของ flex ยังจ่ายช่องว่างให้ลูกที่หุบเหลือศูนย์แล้ว
   (ป้ายกับลูกศร) = เสียไป 11.2px สองช่องโดยไม่ได้อะไรกลับมา
   ตอนย่อจึงต้องตัด gap ทิ้งและวางไอคอนไว้กลางปุ่ม */
body[data-sidebar="collapsed"] .sidebar .nav-pills .nav-link{
  gap:0;
  justify-content:center;
  padding-left:0;
  padding-right:0;
}
/* เมนูย่อยมี padding-left 2.45rem ไว้บอกลำดับชั้น ซึ่งในรางแคบไม่มีที่
   และไม่มีความหมายแล้วเพราะเห็นแต่ไอคอน */
body[data-sidebar="collapsed"] .sidebar .submenu .nav-link{
  padding-left:0; padding-right:0;
}
/* เส้นนำสายตาของเมนูย่อยอิงตำแหน่ง 1.45rem ซึ่งตกอยู่กลางไอคอนพอดีในราง */
body[data-sidebar="collapsed"] .sidebar .submenu::before{ display:none; }

/* กลุ่มเมนูย่อย — เดิมเป็นกล่องพื้นดำจาง 15% ซึ่งพอพื้นเมนูเป็นสีอื่น
   จะกลายเป็นคราบเทา ใช้เส้นนำสายตาแนวตั้งแทน บอกความเป็นลูกได้
   โดยไม่ต้องซ้อนกล่องในกล่อง */
.submenu{
  position:relative;
  padding-left:0;
  background-color:transparent;
  margin:0.1rem 0 0.4rem 0;
}
.submenu::before{
  content:"";
  position:absolute;
  left:1.45rem; top:0.1rem; bottom:0.1rem;
  width:1px;
  background:color-mix(in srgb, var(--sidebar-bg), #fff 30%);
}
.submenu .nav-link{
  padding-left:2.45rem;
  font-size:0.9rem;    /* เท่าของเดิมก่อนรื้อ (เจ้าของสั่ง) */
  font-weight:400;
  margin:0 0.15rem 0.05rem;
  border-radius:10px;
}
/* จุด bullet เดิมลอย ๆ ไม่ได้สื่ออะไร เส้นนำสายตาข้างบนสื่อชัดกว่า

   หมายเหตุ: อย่าประกาศ background ทับตรงนี้
   กฎนี้อยู่ "หลัง" .nav-pills .nav-link.active และ specificity เท่ากัน (0,3,0)
   จึงชนะเสมอ  เคยใส่สีโปร่ง 22% ไว้ตรงนี้ ผลคือเมนูย่อยที่เลือกอยู่
   (ซึ่งเป็นกรณีปกติ เพราะหน้าจริงเกือบทั้งหมดอยู่ในเมนูย่อย) กลับไปจมกับพื้น
   ทั้งที่แก้เมนูหลักให้เป็นสีทึบแล้ว — ปล่อยให้ใช้สีทึบตัวเดียวกัน */
/* เมนูย่อยที่เลือกอยู่ใช้เม็ดยาเหมือนกัน แต่ย่อขนาดลงตามลำดับชั้น */
.submenu .nav-link.active{ box-shadow:0 6px 14px -8px rgba(var(--app-primary-rgb), .8); }


/* ------------------- CONTENT ------------------- */
.content-wrapper{
  flex:1 1 auto;
  min-width:0;
  max-width:100%;
  background:var(--content-bg);
  min-height:100vh;
  padding:var(--app-gutter);
  display:flex;
  flex-direction:column;
  /* P0-4 — ของเดิมเป็น overflow-x:auto ซึ่งบังคับให้ computed overflow-y
     เป็น auto ตามไปด้วย element นี้จึงกลายเป็น scroll container ที่ความสูง
     มาจากเนื้อหา แปลว่ามันไม่เคยเลื่อนเลย ผลคือ position:sticky ของทุก
     descendant ตายหมดทั้งระบบ (sec_4_1:61, sec_4_2:62, sec_9_2:181,
     sec_9_3:143, sec_11_2:35, sec_11_3:89, sec_7_10:35)

     `clip` เป็นค่าเดียวที่ตัดขอบโดย "ไม่" สร้าง scroll container
     และเป็นค่าเดียวที่ใช้คู่กับ overflow-y:visible ได้ */
  overflow-x:clip;
  overflow-y:visible;
}
@supports not (overflow: clip){
  /* เบราว์เซอร์เก่า: ปล่อยให้ล้นแล้วให้ body{overflow-x:hidden} ตัดแทน
     เหมือนพฤติกรรมเดิมทุกประการ */
  .content-wrapper{ overflow:visible; }
}
@supports (min-height:100dvh){ .content-wrapper{ min-height:100dvh; } }
@media (max-width: 991.98px){
  .content-wrapper{
    padding:var(--app-gutter-sm);
    /* เผื่อพื้นที่ให้ home indicator ของ iPhone */
    padding-bottom:calc(var(--app-gutter-sm) + env(safe-area-inset-bottom));
  }
}


/* --------------------------------------------------------------------------
   เมนูข้างพื้นสีอ่อน
   --------------------------------------------------------------------------
   sidebar_bg เป็นค่าที่แอดมินตั้งเองได้จากหน้า 6.1 จะเป็นสีอะไรก็ได้
   base.html คำนวณความสว่างด้วย Jinja แล้วติดคลาส sidebar-light ให้ body
   เมื่อค่านั้นสว่าง (luma >= 140) เพื่อสลับตัวอักษรเป็นโทนเข้ม
   ไม่งั้นถ้าใครตั้งเป็นขาว/ม่วงอ่อน ตัวอักษรขาวบนพื้นขาวจะอ่านไม่ออกเลย

   หมายเหตุ specificity: กฎ .nav-pills .nav-link.active ของ Bootstrap
   เป็น (0,3,0) จึงต้องเขียน :not(.active) กำกับกฎของเรา ไม่งั้นเมนูที่เลือก
   อยู่จะโดนหรี่เป็นสีเข้มบนพื้นม่วง = คอนทราสต์ตก
   -------------------------------------------------------------------------- */
body.sidebar-light .sidebar{
  color:var(--app-text-strong);
  box-shadow:1px 0 0 var(--app-border);
}
body.sidebar-light .sidebar-header{
  background-color:transparent;
  border-bottom:1px solid var(--app-border);
}
body.sidebar-light .sidebar-header .brand-text{ color:var(--app-text-strong); }
body.sidebar-light .toggle-btn{
  color:var(--app-text-muted);
  background:var(--app-primary-soft);
}
body.sidebar-light .toggle-btn:hover{ background:var(--app-primary); color:#fff; }
body.sidebar-light .nav-pills .nav-link:not(.active){ color:var(--app-text); }
body.sidebar-light .nav-pills .nav-link:not(.active):hover{
  background-color:var(--app-primary-soft);
  color:var(--app-primary-700);
}
body.sidebar-light .submenu{ background-color:rgba(0,0,0,0.035); }
body.sidebar-light .sidebar-footer{
  background-color:transparent;
  border-top:1px solid var(--app-border);
}
body.sidebar-light .lang-switch-btn{
  color:var(--app-text);
  background-color:var(--app-primary-soft);
  border-color:var(--app-primary-line);
}
body.sidebar-light .lang-switch-btn:hover,
body.sidebar-light .lang-switch-btn[aria-expanded="true"]{
  color:var(--app-primary-700);
  background-color:#E7E0FB;
  border-color:var(--app-primary-line);
}
body.sidebar-light .sidebar-body::-webkit-scrollbar-thumb{ background:rgba(0,0,0,0.18); }


/* --------------------------------------------------------------------------
   จอเล็ก: สถานะ "ย่อเมนู" ไม่มีความหมาย เพราะเมนูเป็นลิ้นชักที่ปิดสนิทอยู่แล้ว
   เปิดทีไรก็ควรเห็นเต็มใบ

   บล็อกนี้ต้องอยู่ "ท้าย" section 6 คือหลังกฎ collapsed ทุกตัว
   และต้อง scope ลึกเท่ากัน (body[...] .sidebar .x) ไม่งั้นแพ้ทั้งลำดับ
   และ specificity — เคยเขียนไว้ข้างบนแล้วไม่ทำงานเลย
   -------------------------------------------------------------------------- */
@media (max-width: 991.98px){
  body[data-sidebar="collapsed"] .sidebar{
    min-width:min(var(--app-sidebar-w), 86vw);
    max-width:86vw;
  }
  /* ห้ามใส่ width:auto ตรงนี้ — มันย้อนกฎ width:0 ของป้ายในปุ่มเมนู
     ซึ่งเป็นตัวที่กันไม่ให้เนื้อในดันแถบ  เคยใส่แล้วลิ้นชักมือถือล้น 10px */  /* ต้องลึกเท่ากฎรางด้านบน (0,4,1) ไม่งั้นลิ้นชักบนมือถือจะหุบป้ายเหลือศูนย์
     กลายเป็นเมนูไอคอนล้วนทั้งที่ลิ้นชักเปิดเต็มใบ */
  body[data-sidebar="collapsed"] .sidebar .nav-link > .hide-text{
    max-width:100%; opacity:1; transition-delay:0s, 0s;
  }
  body[data-sidebar="collapsed"] .sidebar .nav-caret{
    max-width:1rem; opacity:0.55; transition-delay:0s, 0s, 0s;
  }  body[data-sidebar="collapsed"] .sidebar .nav-pills .nav-link{
    gap:0.7rem; justify-content:flex-start; padding-left:0.7rem; padding-right:0.7rem;
  }
  body[data-sidebar="collapsed"] .sidebar .submenu .nav-link{
    padding-left:2.45rem; padding-right:0.7rem;
  }
  body[data-sidebar="collapsed"] .sidebar .submenu::before{ display:block; }
  body[data-sidebar="collapsed"] .sidebar .toggle-btn > i{ transform:none; }
  body[data-sidebar="collapsed"] .sidebar .sidebar-header .brand-text{
    width:auto; opacity:1; margin:0;
  }
  body[data-sidebar="collapsed"] .sidebar .sidebar-header{
    flex-direction:row;
    justify-content:space-between;
    gap:0.6rem;
    padding:0.95rem 1rem;
  }
  body[data-sidebar="collapsed"] .sidebar .brand-name{
    max-width:20ch; opacity:1; transition-delay:0s, 0s;
  }
  body[data-sidebar="collapsed"] .sidebar .sidebar-footer{
    padding:1rem; display:block;
  }
  body[data-sidebar="collapsed"] .sidebar .lang-switch-btn{
    width:100%; height:auto; padding:0.7rem 0.8rem;
    justify-content:space-between; background-color:rgba(255,255,255,0.05);
  }
  body[data-sidebar="collapsed"] .sidebar .lang-text-label,
  body[data-sidebar="collapsed"] .sidebar .lang-arrow-icon{
    opacity:1; width:auto; margin:initial;
  }
  body[data-sidebar="collapsed"] .sidebar .lang-text-wrapper{
    gap:0.75rem; flex-grow:1;
  }
  body[data-sidebar="collapsed"] .sidebar .lang-flag-icon{ font-size:1.4rem; }
}


/* ==========================================================================
   7  คอมโพเนนต์ที่ใช้ร่วม — ยกมาจาก base.html:383-406 แบบคำต่อคำ
   --------------------------------------------------------------------------
   กฎชุดนี้เป็น global อยู่แล้ววันนี้ จึงต้อง "คง" ความเป็น global ไว้
   ห้ามเอาไปไว้ใต้ body.app-v2 ไม่งั้นพิกเซลเปลี่ยนทันที 76 หน้า
   คอมโพเนนต์ที่จะสกัดจาก "หน้า" ทีหลัง (.modern-card / .table-audit /
   .stat-card ฯลฯ) ต่างหากที่ต้องอยู่ใต้ app-v2
   ========================================================================== */
.card{
  background-color:var(--card-bg);
  border-radius:var(--border-radius);
  border:none;
  box-shadow:var(--card-shadow);
  transition:transform 0.2s, box-shadow 0.2s;
  margin-bottom:1.5rem;
}
.card:hover{ box-shadow:0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -2px rgba(0,0,0,0.05); }
.card-header{
  background-color:transparent;
  border-bottom:1px solid #f1f5f9;
  padding:1.25rem 1.5rem;
  font-weight:600;
  color:var(--text-main);
}
.card-body{ padding:1.5rem; }

.table-responsive{
  width:100%;
  max-width:100%;
  overflow-x:auto !important;
  -webkit-overflow-scrolling:touch;
}
.profile-img{
  width:40px; height:40px; object-fit:cover; border-radius:50%;
  border:2px solid #fff; box-shadow:0 2px 4px rgba(0,0,0,0.1);
}

/* ปุ่มที่กำลังส่งฟอร์ม — app.js ติดคลาสนี้ให้ตอนดักกันกดซ้ำ
   จงใจไม่ไปยุ่งกับข้อความในปุ่ม: การเขียนทับ innerHTML/value ทำให้ชื่อกับค่า
   ของปุ่มหลุดจาก POST body (ของเดิมใน login.html เป็นแบบนั้น)
   และ input[type=submit] ใส่ ::after ไม่ได้ จึงบอกสถานะด้วยความจางกับเคอร์เซอร์ */
.app-submitting{ opacity:.65; cursor:progress; }
button.app-submitting::after{
  content:""; display:inline-block; vertical-align:-.125em;
  width:.9em; height:.9em; margin-left:.45em;
  border:2px solid currentColor; border-right-color:transparent; border-radius:50%;
  animation:app-spin .7s linear infinite;
}
@keyframes app-spin{ to{ transform:rotate(360deg); } }
div:where(.swal2-container) .swal2-popup{ font-family:var(--app-font-sans) !important; }


/* ปุ่มไอคอนล้วน
   ทั้งรีโปใช้ .btn-icon อยู่ 3 หน้าโดยไม่เคยมีนิยามกลาง จึงตกไปใช้ padding
   ของ .btn (6px 12px) ซึ่งกินพื้นที่จนไอคอน 16px ล้นออกข้างละ 4px
   และไอคอนไม่ได้อยู่กลางปุ่มเพราะไม่มี justify-content */
.btn-icon{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  padding:0;
  width:var(--app-btn-icon, 34px);
  height:var(--app-btn-icon, 34px);
  flex:0 0 auto;
  line-height:1;
}
.btn-icon > i{ line-height:1; }

/* ── ภาษาไทยกับการคำนวณความกว้าง ──────────────────────────────────────────
   ไทยไม่มีช่องว่างระหว่างคำ เบราว์เซอร์จึงนับทั้งวลีเป็น "คำเดียว" ที่ตัดไม่ได้
   min-content ของกล่องเลยเท่ากับความยาวข้อความเต็ม แล้วดันกล่องแม่ให้กว้าง

   ❌ เคยแก้ด้วย body{ overflow-wrap:anywhere } แล้ว "พังหนักกว่าเดิม"
      anywhere ลด min-content ลงเหลือ "หนึ่งตัวอักษร" ตารางที่ใช้ auto-layout
      จึงยุบคอลัมน์ลงจนเหลือความกว้างเท่าตัวอักษรเดียว แล้วตัวหนังสือเรียง
      ลงแนวตั้งทีละตัว อ่านไม่ได้เลย
        หัวตาราง "ผู้เล่น(คน)" กลายเป็น ผ/ู้/เ/ล่/น/(/ค/น/)
        เบอร์โทร 0953347014 กลายเป็น 09/53/34/70/14
        วันที่ 2026-08-01 กลายเป็น 2/0/2/6/-/0/8/-/0/1
        ปุ่มกรอง "ทั้งหมด" กลายเป็นแถบตั้งสูงเจ็ดบรรทัด

      บทเรียน: ตอนนั้นวัดแต่จำนวน "กล่องล้นพ่อแม่" ซึ่งลดลงจริง
      แต่ไม่มีด่านไหนจับ "ตัวอักษรเรียงตั้ง" เลยนับ regression เป็นความสำเร็จ
      (เพิ่มด่าน charStacking ใน ui_check.html แล้ว)

   ✅ ใช้เฉพาะที่ที่ "รู้ว่าเป็นป้ายสั้น ๆ ในกล่องแคบ" เท่านั้น
      และห้ามแตะตารางเด็ดขาด ตารางมีตัวเลื่อนแนวนอนของมันเองอยู่แล้ว        */
:where(.nav.flex-column, .nav-pills.flex-column) > .nav-link,
.sidebar .hide-text{
  overflow-wrap:anywhere;
}

/* ปุ่มแท็บแนวตั้งที่ป้ายเป็นภาษาไทย
   ไทยไม่มีช่องว่างระหว่างคำ เบราว์เซอร์จึงนับทั้งวลีเป็นคำเดียวที่ตัดไม่ได้
   min-content ของปุ่มเลยเท่ากับความยาวข้อความเต็ม แล้วไปดันกล่องแม่
   (sec_6_1: คอลัมน์กว้าง 133px แต่ปุ่ม "ทั่วไป & กำไร" กินไป 156px ทั้ง 10 แท็บ)

   overflow-wrap:anywhere เป็นค่าเดียวที่ลด min-content ได้จริง
   break-word ตัดคำตอนแสดงผลแต่ไม่ลด min-content จึงไม่ช่วยกรณีนี้ */
.nav.flex-column > .nav-link,
.nav-pills.flex-column > .nav-link{ overflow-wrap:anywhere; }

/* ตัวห่อตารางที่ใช้ระยะขอบแทน padding ของพ่อ
   .table-wrapper ตั้ง width:100% ไว้ พอมี .m-3 (16px) เข้ามาด้วย
   ความกว้างรวมจึงเป็น 100% + 32px = ล้นออกไป 16px ทางขวา
   (วัดที่ lottos_manage_order_lotto: พ่อ 891px ลูกก็ 891px + margin 16px)
   width:auto ทำให้ block คำนวณความกว้างจากที่เหลือหลังหักระยะขอบเอง */
.table-wrapper{ width:auto; }

/* แถบแท็บที่ตั้งใจให้เรียงแถวเดียวแล้วเลื่อนแนวนอน
   flex item หดได้เป็นค่าเริ่มต้น (flex:0 1 auto) ปุ่มจึงถูกบีบเหลือ 34px
   แล้วข้อความไทยเรียงลงแนวตั้งทีละพยางค์ ("ทั้งหมด" กลายเป็น 5 บรรทัด)
   ห้ามหด แล้วปล่อยให้แถบเลื่อนทำหน้าที่แทน */
.nav.flex-nowrap > li,
.nav.flex-nowrap > .nav-item,
.nav.flex-nowrap > .nav-link,
.nav-pills-custom > li,
.nav-pills-custom > .nav-item,
.nav-pills-custom > .nav-link{
  flex:0 0 auto;
}

/* ตารางในกล่องเลื่อนต้องกว้างตามเนื้อใน ไม่ใช่หดตามกล่อง
   กล่องที่ตั้ง overflow-x:auto ไว้แล้วยังไม่พอ ตัวตารางเองยังยุบลงมาเท่ากล่อง
   คอลัมน์จึงถูกบีบจนตัวหนังสือเรียงลงแนวตั้งทีละพยางค์ (ภาษาไทยตัดกลางคำได้)
     หัวตาราง "ยอดฝาก" กว้าง 43px = 4 บรรทัด
     "ชื่อรางวัล" กว้าง 51px = 5 บรรทัด
     "เริ่มต้น: 2026-08-22" กว้าง 51px = 4 บรรทัด

   max-content ให้คอลัมน์กว้างตามเนื้อจริง แล้วปล่อยให้กล่องเลื่อนทำหน้าที่
   min-width:100% กันตารางสั้น ๆ ไม่ให้หดเหลือแค่ความกว้างเนื้อหา

   วัดเทียบบน 7 หน้าที่มีปัญหา: ไม่แก้ 18 จุด · th nowrap 12 · max-content 8
   และไม่ทำให้หน้าล้นแนวนอนหรือกล่องล้นพ่อแม่เพิ่มเลย */
.tbl-scroll > table,
.table-responsive > table,
.table-wrapper > table{
  width:max-content;
  min-width:100%;
}

/* ==========================================================================
   8  ระบบตาราง — ใหม่ ยังไม่มีหน้าไหนใช้ (opt-in ผ่าน .tbl-scroll)
   --------------------------------------------------------------------------
   สัญญามาร์กอัป: attribute เดียวที่ตัวห่อ ไม่ต้องแก้อะไรในตาราง

     <div class="tbl-scroll" data-freeze="1">              <table>...</table></div>
     <div class="tbl-scroll" data-freeze="2" data-maxh="640"> ... </div>

   data-freeze = จำนวนคอลัมน์นำหน้าที่ตรึง (1 = ค่าเริ่มต้นของทุกหน้า)
   data-maxh   = ความสูงสูงสุด (px) — จำเป็นถ้าอยากได้ sticky header
                 เพราะ sticky ต้องมี scrollport ที่ความสูงถูกจำกัด

   ค่า left: ของคอลัมน์ที่ 2/3 มาจาก --fz-2 / --fz-3 ที่ app.js "วัด" แล้วเขียนลง
   ไม่ใช่ตัวเลขที่คนเดา — ต้นเหตุคอลัมน์ทับกันที่ sec_5_4:96 (left:120px แต่
   เซลล์กว้างจริง 120px + padding 2rem), sec_7_6:74 (left:60px), sec_8_5:113
   ========================================================================== */
.tbl-scroll{
  position:relative;
  /* width:auto ไม่ใช่ 100%
     block เต็มความกว้างพ่ออยู่แล้วโดยไม่ต้องสั่ง แต่ถ้าสั่ง 100% แล้วตัวห่อมี
     ระยะขอบด้วย (หลายหน้าใช้ .m-3) ความกว้างรวมจะเป็น 100% + 32px = ล้น 16px
     และกฎนี้อยู่ทีหลัง .table-wrapper{width:auto} จึงไปทับมันด้วย */
  width:auto;
  max-width:100%;
  overflow-x:auto;
  overflow-y:auto;
  -webkit-overflow-scrolling:touch;
  overscroll-behavior-x:contain;   /* กันปัดย้อนกลับของเบราว์เซอร์ */
  scrollbar-width:thin;
  scrollbar-color:#cbd5e1 #f1f5f9;
}
.tbl-scroll[data-maxh]{ max-height:calc(var(--tbl-maxh, 640) * 1px); }
.tbl-scroll::-webkit-scrollbar{ height:10px; width:10px; }
.tbl-scroll::-webkit-scrollbar-track{ background:#f1f5f9; border-radius:10px; }
.tbl-scroll::-webkit-scrollbar-thumb{ background:#cbd5e1; border-radius:10px; }
.tbl-scroll::-webkit-scrollbar-thumb:hover{ background:#94a3b8; }

/* sticky ใน WebKit ต้องการ border-collapse:separate แต่ .table ของ Bootstrap
   เป็น collapse — จึงต้องคืนเส้นขอบของเซลล์ให้เอง */
.tbl-scroll[data-freeze] > table,
.tbl-scroll[data-maxh]   > table{ border-collapse:separate; border-spacing:0; }
.tbl-scroll[data-freeze] > table > :not(caption) > * > *{
  border-bottom:var(--bs-border-width,1px) solid var(--bs-border-color,var(--app-border));
}

/* หัวตารางติดบน (ต้องมี data-maxh ถึงจะมี scrollport ให้ติด) */
.tbl-scroll[data-maxh] > table > thead > tr > *{
  position:sticky;
  top:var(--fz-top, 0px);
  z-index:3;
  background:var(--app-surface-2);
}
.tbl-scroll[data-maxh] > table > thead > tr:nth-child(2) > *{ top:var(--fz-row2, 0px); }

/* คอลัมน์ตรึง — ไม่มี left: ที่เป็นตัวเลขคงที่เลยแม้แต่จุดเดียว */
.tbl-scroll[data-freeze] .sticky-col,
.tbl-scroll[data-freeze="1"] > table > * > tr > :first-child{
  position:sticky; left:0; z-index:2;
  background-color:var(--fz-bg, var(--app-surface));
}
.tbl-scroll[data-freeze] .sticky-col-2{
  position:sticky; left:var(--fz-2, 0px); z-index:2;
  background-color:var(--fz-bg, var(--app-surface));
}
.tbl-scroll[data-freeze] .sticky-col-3{
  position:sticky; left:var(--fz-3, 0px); z-index:2;
  background-color:var(--fz-bg, var(--app-surface));
}
/* sec_8_1 ตรึงถึง 5 คอลัมน์ ตัวที่เกิน 3 เดิมไม่มี --fz- ให้ใช้
   เลยตกไปที่ left:0 แล้วไปทับคอลัมน์แรก (วัดได้ว่าทับกัน 3 จุด) */
.tbl-scroll[data-freeze] .sticky-col-4{
  position:sticky; left:var(--fz-4, 0px); z-index:2;
  background-color:var(--fz-bg, var(--app-surface));
}
.tbl-scroll[data-freeze] .sticky-col-5{
  position:sticky; left:var(--fz-5, 0px); z-index:2;
  background-color:var(--fz-bg, var(--app-surface));
}
/* เซลล์หัวของคอลัมน์ที่ตรึง ต้องอยู่เหนือทั้ง sticky แนวตั้งและแนวนอน */
.tbl-scroll[data-freeze] > table > thead > tr > .sticky-col,
.tbl-scroll[data-freeze] > table > thead > tr > .sticky-col-2,
.tbl-scroll[data-freeze] > table > thead > tr > .sticky-col-3,
.tbl-scroll[data-freeze] > table > thead > tr > .sticky-col-4,
.tbl-scroll[data-freeze] > table > thead > tr > .sticky-col-5,
.tbl-scroll[data-freeze="1"][data-maxh] > table > thead > tr > :first-child{
  z-index:4;
  background:var(--app-surface-2);
}
/* เงาคั่นโผล่เฉพาะตอนเลื่อนไปแล้วจริง ๆ */
.tbl-scroll[data-freeze].is-scrolled .fz-last{
  box-shadow:6px 0 6px -6px rgba(15,23,42,.25);
}
/* แถวแบบ "หัวข้อซ้าย ปุ่ม/ป้ายขวา" ต้องยอมตัดบรรทัดบนจอเล็ก
   justify-content-between ไม่ได้บอกให้ตัดบรรทัด พอของสองฝั่งรวมกันกว้างเกิน
   ฝั่งขวาจะยื่นออกนอกกรอบ (วัดได้ 46-49px ที่ sec_8_3/sec_8_4 บนจอ 375px)
   วัดกับ 10 หน้าแล้ว: กล่องล้น 2 -> 0 และไม่ทำให้ตัวอักษรเรียงตั้งเพิ่ม */
@media (max-width: 575.98px){
  .d-flex.justify-content-between{ flex-wrap:wrap; row-gap:.5rem; }
}

/* ── ตารางใหญ่บนจอเล็ก: หัวตารางต้องไม่หายตอนเลื่อน ─────────────────────
   .tbl-scroll สร้าง scroll container (จาก overflow-x:auto) ซึ่ง "ดัก"
   position:sticky ของ thead ไว้กับตัวมันเอง  ถ้ากล่องไม่ถูกจำกัดความสูง
   ตัวเลื่อนแนวตั้งจริงคือทั้งหน้า หัวตารางจึงเลื่อนหลุดจอทันที
   (วัดที่ 8.1: ตารางสูง 3,663px ผู้ใช้อยู่กลางตาราง 24 แถวโดยไม่รู้ว่า
    คอลัมน์ไหนคืออะไร ในตารางที่กว้าง ~3,900px)

   บนจอเล็กจึงบังคับจำกัดความสูงให้ทุกตารางตรึงคอลัมน์ กล่องกลายเป็น
   ตัวเลื่อนแนวตั้งเอง แล้วกฎ sticky ของหัวตารางทำงานได้จริง
   140px = แถบบน 56 + หัวข้อ/ระยะหายใจ  เดสก์ท็อปไม่ถูกแตะเลย */
@media (max-width: 991.98px){
  .tbl-scroll[data-freeze]{
    max-height:calc(100vh - 140px);
  }
  @supports (max-height:100dvh){
    .tbl-scroll[data-freeze]{ max-height:calc(100dvh - 140px); }
  }
  .tbl-scroll[data-freeze] > table > thead > tr > *{
    position:sticky;
    top:var(--fz-top, 0px);
    z-index:3;
    background:var(--app-surface-2);
  }
  .tbl-scroll[data-freeze] > table > thead > tr:nth-child(2) > *{
    top:var(--fz-row2, 0px);
  }
  /* มุมซ้ายบน (หัวของคอลัมน์ที่ตรึง) ต้องชนะทั้งสองแกน */
  .tbl-scroll[data-freeze] > table > thead > tr > :first-child{
    z-index:4;
  }
  /* เส้นแบ่งให้คอลัมน์ตรึง — เดิมข้อความคอลัมน์ถัดไปโดนตัดชิดขอบเฉย ๆ
     อ่านเป็น "8 วั" ไม่รู้ว่าเลขกับตัวหนังสือคนละคอลัมน์กัน */
  .tbl-scroll[data-freeze] > table > * > tr > :first-child{
    box-shadow:inset -1px 0 0 var(--app-border), 6px 0 6px -6px rgba(15,23,42,.18);
  }
}

/* กันคอลัมน์ตรึงกินจอบนมือถือ (ดูข้อ 2 ใน "จุดที่ต้องให้เจ้าของตัดสิน") */
@media (max-width: 767.98px){
  .tbl-scroll[data-freeze] .sticky-col,
  .tbl-scroll[data-freeze="1"] > table > * > tr > :first-child{ max-width:40vw; }

  /* ตรึงได้แค่คอลัมน์เดียวบนมือถือ
     sec_8_1 ตรึง 5 คอลัมน์ รวมกว้าง 378px แต่จอกว้าง 244px
     คอลัมน์ที่ 4-5 จึงเริ่มที่ 150px และ 245px = เลยขอบขวาไปแล้ว
     ทับกันเองและบังเนื้อหาที่กำลังเลื่อนดู (วัดได้ว่าทับ 132px)
     offset ไม่ได้ผิด แค่ตรึงเยอะเกินกว่าจอจะรับไหว

     ปล่อยคอลัมน์ที่ 2 เป็นต้นไปให้เลื่อนตามปกติ เหลือคอลัมน์แรกไว้พอ
     ให้รู้ว่าแถวไหนเป็นของใคร ซึ่งเป็นเหตุผลเดียวที่ต้องตรึงบนจอเล็ก */
  .tbl-scroll[data-freeze] .sticky-col-2,
  .tbl-scroll[data-freeze] .sticky-col-3,
  .tbl-scroll[data-freeze] .sticky-col-4,
  .tbl-scroll[data-freeze] .sticky-col-5{
    position:static;
    left:auto;
  }
}


/* ==========================================================================
   9  HARD FLOORS — !important ทั้งหมดของไฟล์นี้อยู่ที่นี่ที่เดียว
   --------------------------------------------------------------------------
   มีไว้เพื่อชนะ CSS ของหน้าที่เรนเดอร์ "ทีหลัง" app.css (block head_css)
   ทุกกฎมีคอมเมนต์ว่าไปชนะอะไรกี่จุด และถอด !important ได้เมื่อไร

   9.5 (.content-wrapper overflow-x:clip) จงใจ "ยังไม่ใส่" ใน Step 0
       เพราะมันปลุก position:sticky ให้ 7 หน้าพร้อมกัน = desktop diff ไม่เป็น 0
       ย้ายไป Step 2 ที่มี snapshot gate 76 หน้าคุมอยู่
   ========================================================================== */

/* 9.1  พื้น font-size 16px — กัน iOS Safari ซูมทั้งหน้าตอนโฟกัสช่องกรอก
        แล้วไม่ซูมกลับ ผู้ใช้ต้องบีบนิ้วออกเองทุกครั้ง
        เฉพาะจอเล็ก — เดสก์ท็อปคงขนาดเดิมเป๊ะ

        เดิมเล็งเฉพาะตัวแปร -sm เพราะกลัวไปย่อช่องที่ตั้งใจให้ใหญ่
        (font-size:16px เป็นค่าตายตัว ไม่ใช่ค่าต่ำสุด และ CSS ไม่มีทางเขียน
         "ค่าต่ำสุด" ของ font-size ได้จริง — max(16px, 1em) ก็ไม่ได้เพราะ
         1em คือขนาดของ "พ่อ" ไม่ใช่ของตัวเอง)

        แต่แบบนั้นปล่อยให้ override ของหน้าเอง 22 จุดใน 17 ไฟล์รอดไปหมด
        (13.1px - 15.2px) ซึ่งซูมทุกจุด

        กวาดทั้งรีโปแล้วพบว่ามีช่องที่ตั้งใจใหญ่กว่า 16px แค่ 2 ตัว
        จึงเปลี่ยนมาใช้พื้นแบบเต็มพร้อม "ทางออก" ผ่านตัวแปร --app-fs-mobile */
@media (max-width: 991.98px){
  .form-control, .form-select,
  .form-control-sm, .form-select-sm,
  .input-group-sm > .form-control, .input-group-sm > .form-select,
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="hidden"]):not([type="file"]),
  select, textarea{
    /* ทางออกสำหรับช่องที่ "ตั้งใจ" ให้ใหญ่กว่า 16px: ตั้ง --app-fs-mobile เอง
       ค่าเริ่มต้นคือ 16px ซึ่งเป็นเกณฑ์ที่ iOS ใช้ตัดสินว่าจะซูมหรือไม่ */
    font-size:var(--app-fs-mobile, 16px) !important;
  }
}
/* ช่องที่ใหญ่กว่า 16px โดยตั้งใจ — กวาดทั้งรีโปแล้วมีสองตัวนี้เท่านั้น
   ถ้าเพิ่มช่องใหญ่ที่ไหนอีก ต้องมาเติมที่นี่ ไม่งั้นจะถูกย่อเหลือ 16px เงียบ ๆ
   (ด่าน input_font_below_16 ใน static_gate.py จะไม่จับให้ เพราะมันมองหา
    ค่าที่ "ต่ำกว่า" 16px) */
.otp-box{ --app-fs-mobile:1.2rem; }
.ent-limit-card .limit-input{ --app-fs-mobile:1.1rem; }

/* 9.2  พื้นเป้ากด 44px — เฉพาะจอเล็กที่เป็น touch
        เดสก์ท็อป .btn-sm 275 จุดไม่ถูกแตะ
        ใช้ min-height ไม่ใช่ height เนื้อหายังกำหนดขนาดกล่องได้ตามเดิม      */
@media (max-width: 991.98px) and (pointer: coarse){
  /* ต้องครอบ <button> เปล่า ๆ ด้วย ไม่ใช่แค่ .btn
     วัดบนมือถือแล้วพบปุ่มที่เล็กเกิน 26 จุด และเกือบทั้งหมดใช้คลาสของหน้าเอง
     ที่ไม่มี .btn ติดมา: .btn-action 28x28 · .apx-btn 49x32 · .seg-btn 77x30
     .btn-action-icon 32x32 · .ap-btn 79x36 · .save-btn 109x40
     เล็งที่ชื่อคลาสทีละตัวไม่มีทางครบ เพราะหน้าใหม่ก็ตั้งชื่อใหม่อีก

     ยกเว้นปุ่มที่ซ่อนไว้ให้ฟอร์มรับ Enter (1x1, aria-hidden, tabindex=-1)
     ถ้าไม่ยกเว้น มันจะกลายเป็นกล่อง 44px ที่มองไม่เห็นแต่กดโดนได้ */
  .btn, .page-link, .nav-link, .dropdown-item, [role=button],
  button:not([aria-hidden="true"]):not([tabindex="-1"]),
  input[type="submit"], input[type="button"]{
    min-height:44px !important;
  }
  /* .page-link รวมด้วย — ของเดิมคุมแค่ min-height ปุ่มเลขหน้าเลยได้ 30x44
     คือสูงพอแต่แคบเกินจะกดแม่นด้วยนิ้ว */
  /* ความกว้างก็ต้องมีพื้นเหมือนความสูง
     รอบก่อนคุมแต่ min-height ปุ่มเลยได้ 28x44 คือสูงพอแต่แคบเกินจะกดแม่น
     (.btn-action 6 จุด · .btn-action-icon 2 · .apx-btn 1 ฯลฯ)
     min-width ขยายเฉพาะตัวที่แคบกว่า 44px อยู่แล้ว ซึ่งก็คือปุ่มไอคอนล้วน
     ปุ่มที่มีข้อความกว้างเกิน 44px อยู่แล้วจึงไม่ถูกแตะเลย */
  .btn-sm, .btn-icon, .page-link,
  button:not([aria-hidden="true"]):not([tabindex="-1"]){ min-width:44px !important; }
  /* ปุ่มไอคอนล้วนตั้งขนาดผ่านตัวแปร ต้องขยายตัวแปรด้วย ไม่งั้นได้ 44x34
     คือกว้างพอแต่เตี้ยเกิน (บั๊กที่กฎ .btn-icon ของผมเองสร้างขึ้น) */
  .btn-icon{ --app-btn-icon:44px; }
  .form-check-input{ width:22px; height:22px; }
  /* ปุ่มปิดลิ้นชักในเมนู — ไม่มีคลาส .btn จึงไม่เข้าเงื่อนไขข้างบน
     และเป็นปุ่มสำคัญเพราะเป็นทางปิดเมนูบนมือถือ */
  .toggle-btn{ width:44px !important; height:44px !important; }
}

/* 9.3  รูปห้ามล้นคอลัมน์ — Bootstrap Reboot ไม่ให้กฎนี้มา (มีแต่ .img-fluid
        ที่ต้อง opt-in และทั้งรีโปใช้แค่ 2 จุด)
        41 จาก 42 <img> ในรีโปไม่มี max-width และหลายตัวตั้ง px ตายตัว      */
/*  ติดสวิตช์ app-v2 ไม่ใช่ global — วัดแล้วพบว่ามันเปลี่ยนพิกเซลจริง

    รอบแรกเขียนเป็น global แล้วเจอสองบั๊กซ้อนกัน:
    1) `img:not([width]):not([height])` = (0,2,1) ชนะ `.bank-logo` = (0,1,0)
       ไป override height:24px เป็น auto -> โลโก้สูง 24.44px
       แก้ด้วย :where() ให้ specificity = 0
    2) max-width:100% กับ <img> ที่อยู่ในตาราง auto-layout ไปมีส่วนร่วมกับ
       การคำนวณความกว้างขั้นต่ำของคอลัมน์ -> โลโก้กว้าง 23.56px แทน 24px
       คอลัมน์ "ข้อมูลธนาคาร" หน้า 3.1 หดจาก 144 เหลือ 122 ทั้งตารางขยับ 22px
       (หน้า 7.7 ตารางหดจาก 1069 เหลือ 1038 ด้วยเหตุเดียวกัน)
       ข้อนี้ :where() ช่วยไม่ได้ เพราะเดิมไม่มีใครประกาศ max-width ไว้เลย

    รูปที่ล้นคอลัมน์เป็นบั๊กจริง (41 จาก 42 <img> ไม่มี max-width) แต่การแก้
    มันเปลี่ยนหน้าตา จึงต้องลงทีละหน้าที่เจ้าของเห็น ไม่ใช่ทั้ง 76 หน้าพร้อมกัน */
/* วัดจริงแล้วว่าปัญหาอยู่ที่ "รูปในตาราง" เท่านั้น
   ลองสี่แบบบน 10 หน้าที่มีรูปเยอะสุด แล้วนับกล่องที่ล้นพ่อแม่
     ไม่เปิดอะไร               0 จุด
     max-width:100% อย่างเดียว 10 จุด
     max-width + height:auto   10 จุด
     เฉพาะรูปนอกตาราง          0 จุด   <-- ได้การป้องกันโดยไม่มีผลข้างเคียง

   ตาราง auto-layout คำนวณความกว้างคอลัมน์จากขนาดเนื้อใน พอรูปมี max-width
   ความกว้างที่คำนวณได้เปลี่ยน ทั้งตารางจึงขยับ  นอกตารางไม่มีปัญหานี้
   จึงไม่ต้องรอสวิตช์ app-v2 อีกต่อไป — 41 จาก 42 <img> ในรีโปได้การป้องกันทันที

   ส่วนรูปในตารางยังรอ migrate รายหน้าเหมือนเดิม (ยังกันด้วย app-v2) */
:where(img, svg, video, canvas):not(table :where(img, svg, video, canvas)){
  max-width:100%;
}
:where(img[width][height]):not(table :where(img[width][height])){ height:auto; }

/* รูปในตาราง — เปลี่ยนความกว้างคอลัมน์ จึงยังต้องลงทีละหน้าที่เจ้าของเห็น */
body.app-v2 table :where(img, svg, video){ max-width:100%; height:auto; }

/* 9.4  แถบเลขหน้าให้ตัดบรรทัดแทนดันหน้าให้กว้าง
        มีแค่ 5 จาก 32 บล็อกที่ตั้ง flex-wrap เอง — แก้ 27 หน้าด้วยกฎเดียว
        เดสก์ท็อปไม่เปลี่ยน เพราะ wrap ทำงานเฉพาะตอนที่จะล้นอยู่แล้ว        */
.pagination{ flex-wrap:wrap !important; row-gap:.25rem; }

/* 9.6  หน้าห้ามปิด scroll ของตัวห่อตาราง
        ชนะ sec_7_1:72 (.table-responsive{overflow:hidden}) ที่ทำให้ตาราง
        10 คอลัมน์เลื่อนแนวนอนไม่ได้เลย
        ถอด !important เมื่อ sec_7_1 migrate                                */
.tbl-scroll, .table-responsive{ overflow-x:auto !important; }


/* ==========================================================================
   10  การตอบสนองตอนกด (press feedback)
   --------------------------------------------------------------------------
   ทั้งรีโปมี :active อยู่ "จุดเดียว" (login .btn-submit) แปลว่ากดปุ่มที่ไหน
   ก็ไม่มีอะไรตอบกลับเลย สีเปลี่ยนแบบตัดจบทันที = รู้สึกกระด้าง

   สามอย่างที่ทำให้รู้สึกลื่น:
     1. มี "จังหวะกด" ที่เห็นได้ — ย่อลงนิดเดียวก็พอ
     2. กดลงเร็ว ปล่อยช้ากว่า — เลียนแบบของจริงที่มีแรงต้าน
     3. บนมือถือ ต้องดับแถบเทาที่เบราว์เซอร์วาดทับตอนแตะ
        แล้ววาดของเราเองแทน ไม่งั้นเห็นสี่เหลี่ยมเทากระพริบทุกครั้ง
   ========================================================================== */
:root{
  /* ออกตัวเร็ว จบนุ่ม — โค้งเดียวกับที่การ์ด login ใช้ตอนเลื่อนขึ้น */
  --app-ease-out:   cubic-bezier(.22, 1, .36, 1);
  --app-dur-press:  .06s;   /* กดลง: เร็ว */
  --app-dur-release:.22s;   /* ปล่อย: ช้ากว่า */
  --app-press:      .97;
}

/* แถบไฮไลต์เทาของเบราว์เซอร์ตอนแตะบนมือถือ เป็นสี่เหลี่ยมทึบไม่มีมุมโค้ง
   ทับทับปุ่มกลม ๆ ของเราแล้วดูแข็งมาก ดับทิ้งแล้วใช้ :active ของเราแทน */
html{ -webkit-tap-highlight-color: transparent; }

/* touch-action: manipulation ตัดดีเลย์ ~300ms ที่เบราว์เซอร์รอดูว่าจะเป็น
   double-tap-to-zoom ไหม เป็นสาเหตุคลาสสิกที่ทำให้ปุ่มบนมือถือ "หน่วง" */
.btn, button, [role="button"], .page-link, .dropdown-item,
.nav-link, summary, label[for], input[type="submit"]{
  touch-action: manipulation;
}

/* จังหวะเปลี่ยนสถานะของทุกตัวที่กดได้
   .nav-link รวมอยู่ด้วยเพราะแถบแท็บในหน้ารายงาน/ตั้งค่าใช้ .nav-link เป็นปุ่ม
   (report_profit_loss, report_users, sec_6_1, manage_deposit) ซึ่งเดิม
   ไม่มีทั้ง transition และจังหวะกด = กดแล้วสีตัดจบทันที รู้สึกกระด้าง */
.btn, button, [role="button"], .page-link, .dropdown-item, input[type="submit"],
.nav-link,
/* .accordion-button ประกาศ transition ของตัวเองที่ (0,1,0) ชนะ `button` (0,0,1)
   ต้องเรียกด้วยชื่อคลาสตรง ๆ ไม่งั้นปุ่มหีบเพลงจะเป็นปุ่มเดียวที่กดแล้วนิ่ง */
.accordion-button{
  transition:
    background-color var(--app-dur) var(--app-ease-out),
    border-color     var(--app-dur) var(--app-ease-out),
    color            var(--app-dur) var(--app-ease-out),
    box-shadow       var(--app-dur) var(--app-ease-out),
    transform        var(--app-dur-release) var(--app-ease-out);
}

/* กดลง — ย่อเล็กน้อย และย่อ "เร็ว" กว่าตอนปล่อย
   :not(:disabled) กันไม่ให้ปุ่มที่กดไม่ได้ขยับ ซึ่งจะสื่อผิด */
.btn:active:not(:disabled),
button:active:not(:disabled),
[role="button"]:active,
.page-link:active,
.nav-link:active,
input[type="submit"]:active:not(:disabled){
  transform: scale(var(--app-press));
  transition-duration: var(--app-dur-press);
}

/* ปุ่มที่กว้างเต็มแถบ (เมนูข้าง, แท็บแนวตั้ง) ย่อ .97 แล้วดูขยับเยอะเกิน
   เพราะระยะที่ขอบเคลื่อนเป็นสัดส่วนกับความกว้าง  ใช้ค่าที่เบากว่า
   และเติมพื้นเข้มขึ้นด้วย จะได้ยังรู้สึกว่า "กดติด" */
.nav-pills .nav-link:active{
  transform: scale(.985);
  filter: brightness(0.94);
}

/* วงแหวนโฟกัสให้เหมือนกันทั้งระบบ — ของ Bootstrap เป็นเงาน้ำเงินค้างไว้
   ใช้ :focus-visible จึงขึ้นเฉพาะตอนเดินด้วยคีย์บอร์ด ไม่ขึ้นตอนคลิกเมาส์ */
.btn:focus-visible, button:focus-visible, [role="button"]:focus-visible,
.page-link:focus-visible, .dropdown-item:focus-visible,
input[type="submit"]:focus-visible{
  outline: 3px solid rgba(var(--app-primary-rgb), .35);
  outline-offset: 2px;
  box-shadow: none;
}

/* กาง/พับ (Bootstrap Collapse) — ค่าเริ่มต้นคือ .35s ease ซึ่งอืดและจบแข็ง */
.collapsing{
  transition: height var(--app-dur-lg) var(--app-ease-out) !important;
}

/* modal — เดิมเลื่อนลงมา 50px ใน .3s ease-out ดูตกลงมามากกว่าลอยขึ้นมา */
.modal.fade .modal-dialog{
  transform: translateY(14px) scale(.985);
  transition: transform var(--app-dur-lg) var(--app-ease-out),
              opacity   var(--app-dur-lg) var(--app-ease-out);
  opacity: 0;
}
.modal.show .modal-dialog{ transform: none; opacity: 1; }
.modal-backdrop.fade{ transition: opacity var(--app-dur-lg) var(--app-ease-out); }

/* .btn-outline-secondary ของ Bootstrap ใช้ #6c757d ซึ่งบนพื้นขาวได้ 4.69:1
   พอไปวางบนพื้นการ์ดหรือแถบตัวกรองที่มีสีอ่อน ๆ จะตกลงมาเหลือ 4.45:1
   ไม่ผ่าน AA  ใช้เฉดเข้มขึ้นหนึ่งขั้นซึ่งเป็นสี hover ของ Bootstrap เอง
   (#5c636a = 6.09:1 บนขาว / 5.82:1 บนพื้นการ์ด) จึงไม่ได้เพิ่มสีใหม่ */
.btn-outline-secondary{
  --bs-btn-color:#5c636a;
  --bs-btn-border-color:#5c636a;
  --bs-btn-hover-bg:#5c636a;
  --bs-btn-hover-border-color:#5c636a;
}

/* modal สูงเกินจอ — วัดแล้วพบว่าปุ่มยืนยันตกไปอยู่ใต้ขอบจอ 198-502px
   ใน 7 กล่อง (sec_7_7 หกใบ, sec_7_11 หนึ่งใบ) = กดบันทึกไม่ได้เลยบนจอนั้น

   Bootstrap มี .modal-dialog-scrollable ให้อยู่แล้ว แต่ต้องไปเติมทีละกล่อง
   ทั้งระบบมี 54 กล่อง และกล่องใหม่ที่เขียนทีหลังก็จะลืมอีก
   ตั้งเป็นค่าเริ่มต้นให้ทุกกล่องแทน กล่องเตี้ยไม่ได้รับผลอะไรเพราะ
   max-height ไม่บังคับความสูง

   ตรวจแล้วว่าไม่มี .dropdown-menu อยู่ใน .modal-body สักกล่องเดียว
   (เช็ค 10 หน้าที่มี modal เยอะสุด) จึงไม่มีอะไรจะโดน overflow ตัด */
/* ต้องล็อกที่ .modal-content ด้วยหน่วยของ viewport ตรง ๆ
   ห้ามใช้ max-height:100% เพราะ .modal-dialog มี height เป็น auto
   เปอร์เซ็นต์ที่อ้างกับพ่อที่ความสูงไม่แน่นอนจะกลายเป็น none = ไม่ล็อกอะไรเลย
   (ลองมาแล้ว วัดได้ว่า .modal-body ยังยาวเกินจอ 1066px)
   Bootstrap เองก็เลี่ยงปัญหานี้ด้วยการใส่ height: calc(100% - 1rem) เป็นค่าแน่นอน
   แต่ทำแบบนั้นกล่องเตี้ย ๆ จะถูกยืดเต็มจอไปด้วย */
.modal-content{
  max-height:calc(100vh - var(--bs-modal-margin, .5rem) * 2);
  overflow:hidden;          /* กันมุมโค้งโดนเนื้อในทับตอน body เลื่อน */
}
.modal-body{ overflow-y:auto; }

/* <form> ที่คั่นอยู่ระหว่าง .modal-content กับ header/body/footer
   Bootstrap วาง .modal-content เป็น flex column แล้วให้ body เป็น flex item
   ที่หดได้  พอมี <form> มาคั่น body จึงไม่ใช่ flex item ของ content อีกต่อไป
   ความสูงเลยไม่ถูกจำกัด overflow-y:auto ที่ตั้งไว้ก็ไม่มีผล

   วัดจริงที่ sec_7_7 #createPromotionModal:
     content สูง 776px (ถูกล็อกแล้ว) แต่ body สูง 1281px และ footer อยู่ที่ 1402
     ขณะที่ content ตัดที่ 852  = ปุ่มบันทึกถูกตัดหาย กดไม่ได้เลย

   ทางแก้ที่ถูกคือให้ <form> เป็น .modal-content เสียเอง แต่ต้องแก้มาร์กอัป
   ทีละที่  ต่อ chain ด้วย CSS แทน ได้ผลเหมือนกันและครอบคลุมกล่องที่เขียนใหม่
   ในอนาคตด้วย  (ตอนนี้ 7 กล่องใน 2 หน้า) */
.modal-content > form{
  display:flex;
  flex-direction:column;
  flex:1 1 auto;
  min-height:0;        /* ต้องมี ไม่งั้น min-height:auto ของ flex item กันไม่ให้หด */
  overflow:hidden;
}
@supports (max-height:100dvh){
  /* บนมือถือ 100vh รวมความสูงของแถบ URL ที่ยังไม่หดเข้าไปด้วย
     กล่องจึงสูงเกินพื้นที่ที่มองเห็นจริง  dvh อิงพื้นที่ที่เห็นจริง */
  .modal-content{ max-height:calc(100dvh - var(--bs-modal-margin, .5rem) * 2); }
}
/* กล่องที่จัดกึ่งกลางแนวตั้งเผื่อระยะขอบบนล่างไว้แล้ว ต้องหักออกให้ตรงกัน */
.modal-dialog-centered .modal-content{
  max-height:calc(100vh - var(--bs-modal-margin, .5rem) * 4);
}
@supports (max-height:100dvh){
  .modal-dialog-centered .modal-content{
    max-height:calc(100dvh - var(--bs-modal-margin, .5rem) * 4);
  }
}

/* dropdown — Bootstrap ไม่ทำ animation ให้เลย โผล่มาดื้อ ๆ
   ⚠️ animate ได้แค่ opacity เท่านั้น เพราะ Popper เขียน transform เป็น
      inline style ไว้จัดตำแหน่ง ถ้าเราใส่ transform จะไปทับแล้วเมนูหลุดที่ */
.dropdown-menu.show{ animation: app-fade-in var(--app-dur) var(--app-ease-out); }
@keyframes app-fade-in{ from{ opacity: 0; } to{ opacity: 1; } }


/* ==========================================================================
   11  prefers-reduced-motion — ต้องอยู่ท้ายไฟล์ เพราะเป็นบล็อกเดียวที่
       ต้องชนะทุก animation/transition ที่ประกาศไว้ข้างบน
       ทั้งรีโปวันนี้มี 0 บล็อก ทั้งที่มี transform hover, การ์ดยกตัว,
       และ transition:all 0.3s ที่ animate ความกว้างของเมนูข้าง
   ========================================================================== */
@media (prefers-reduced-motion: reduce){
  *, *::before, *::after{
    animation-duration:.01ms !important;
    animation-iteration-count:1 !important;
    transition-duration:.01ms !important;
    scroll-behavior:auto !important;
  }
}
