/* ══════════════════════════════════════════════════════════════════
   sheets.css — หน้า Attendance (ชีตของ Group Class และ Private Class)
   โหลดหลัง layout.css · ทุกค่าต้องมาจาก var() ห้ามตัวเลขดิบ (ดู THEME.md)
   ══════════════════════════════════════════════════════════════════ */

/* ---- ปุ่มบนหัวหน้า -------------------------------------------------- */
.page-actions {
  display: flex;
  gap: var(--sp-2);
  align-items: center;
}

/* ---- แถบเลือกคลาส / นักเรียน --------------------------------------- */
.picker-bar {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
  margin-bottom: var(--sp-4);
}
.picker-label {
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--muted);
}
.class-picker {
  flex: 1;
  min-width: 220px;
  max-width: 520px;
  font: inherit;
  font-size: var(--fs-md);
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  padding: var(--sp-2) var(--sp-3);
}
/* ตัวเลือกใน dropdown ถูกวาดโดยระบบปฏิบัติการ ไม่ใช่ CSS ของเรา —
   ต้องระบุสีเองไม่งั้นธีมมืดได้ตัวหนังสือดำบนพื้นดำในบางเบราว์เซอร์ */
.class-picker option {
  background: var(--surface);
  color: var(--ink);
}

/* 🔴 2026-10-06 — .sheet-title (แถบเขียวหัวชีต) ถูกลบทิ้ง · ไม่มีใครเรียกมาตั้งแต่
   ฝั่ง Group ย้ายไปใช้ ui.vboxHead · คอมเมนต์เดิมยังเขียนว่า "ตอนนี้ใช้เฉพาะฝั่ง Group"
   ซึ่งไม่จริงแล้ว — เอกสารที่โกหกอันตรายกว่าไม่มีเอกสาร
   (.sheet-title-input ถูกลบไปก่อนแล้ว 2026-09-03 พร้อมแถบชื่อที่แก้ได้ของ Private)
   วิธีจับ: grep ชื่อคลาสใน js/ กับ *.html ถ้าไม่เจอเลย แปลว่า CSS นั้นตาย */

.sheet-h {
  font-size: var(--fs-lg);
  margin-bottom: var(--sp-2);
  display: flex;
  align-items: baseline;
  gap: var(--sp-1);
}
.sheet-section {
  margin-top: var(--sp-6);
}
/* ช่องที่แก้ได้ซึ่งนั่งอยู่กลางประโยคหัวข้อ ("CONTACT LIST FROM ___") */
.inline-input {
  width: auto;
  min-width: 180px;
  font: inherit;
  font-size: var(--fs-md);
}

/* ---- ช่องกรอกข้อมูลคลาส ------------------------------------------- */
.field-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: var(--sp-3) var(--sp-4);
  margin-bottom: var(--sp-5);
}
/* label ชิดบน · ช่องกรอกชิดล่างเสมอ
   จำเป็นเพราะ label แต่ละช่องยาวไม่เท่ากัน — "CONTINUE CLASS WILL BE TAUGHT BY"
   ขึ้น 2 บรรทัด ส่วน "BRANCH" บรรทัดเดียว ถ้าเรียงจากบนลงล่างเฉยๆ
   ช่องกรอกในแถวเดียวกันจะอยู่คนละระดับ แถวเลยดูเป๋
   space-between + ตัว .fld ยืดเต็มความสูงแถว (ค่า default ของ grid)
   ทำให้ทุกช่องในแถวเดียวกันจบที่ระดับเดียวกันเสมอ ไม่ว่า label จะกี่บรรทัด */
.fld {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--sp-1);
  min-width: 0;
}
.fld--wide {
  grid-column: span 2;
}
.fld--end {
  justify-content: flex-end;
  align-items: flex-start; /* ไม่งั้นปุ่มยืดเต็มความกว้างช่อง อ่านเป็นแถบเตือนแทนที่จะเป็นปุ่ม */
}
/* 🔴 2026-10-06 — ตัวพิมพ์เล็กปกติ ไม่ใช่ตัวพิมพ์ใหญ่ทั้งหมด
   ผลพลอยได้ที่วัดได้: "Continue class will be taught by" จาก 2 บรรทัดเหลือ 1
   ตัวพิมพ์ใหญ่กินความกว้างมากกว่าราว 15% ป้ายยาวจึงตกบรรทัด แล้วช่องกรอกในแถว
   เดียวกันไปอยู่คนละระดับ (ซึ่ง .fld แก้ด้วย space-between อยู่แล้ว แต่หัวยังเหลื่อม) */
.fld-label {
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--muted);
}
/* 🔴🔴 2026-10-06 (รอบสาม) — ช่องในการ์ด "ได้กรอบกลับมา"
   (Big สั่ง — "เพิ่มกรอบให้หน่อยพวกตัวเลือก Dropdown ไม่อยากให้โล้นแบบนี้"
   แนบรูปการ์ดคลาสในธีมมืด) · ⚠️ กลับด้านจากกฎที่เพิ่งตั้งเมื่อเช้าวันเดียวกัน
   ("ช่องกรอกไม่มีกรอบจนกว่าจะชี้/โฟกัส")

   🔴 กฎนั้นผิดสำหรับ dropdown โดยเฉพาะ: **dropdown มีลูกศรของมันติดมาเสมอ**
   ลูกศรคือชิ้นส่วนที่สัญญาว่า "อันนี้กดได้" ถ้าไม่มีกรอบรองรับ มันกลายเป็นสามเหลี่ยม
   ลอยอยู่ข้างข้อความโดยไม่มีอะไรบอกว่ามันเป็นของช่องไหน · ช่องพิมพ์ไม่มีปัญหานี้
   เพราะข้อความที่ดูเหมือนข้อความยังอ่านถูกอยู่ — แต่ดูข้อถัดไป

   🔴 **ต้องตีกรอบทุกช่อง ไม่ใช่เฉพาะ dropdown** — Seats กับ Book version อยู่แถว
   เดียวกับ dropdown ถ้าตีกรอบครึ่งเดียว สองช่องนั้นจะอ่านเป็นค่าอ่านอย่างเดียว
   แบบ .fld-static (Teaching Term) ทั้งที่แก้ได้ · กรอบครึ่งเดียวไม่ได้แค่ "ไม่สวย"
   มัน **ติดป้ายผิด** ให้ช่องที่เหลือ
   ⚠️ .fld-static ห้ามมีกรอบ — อันนั้นอ่านอย่างเดียวจริงๆ

   · --line เป็นค่าเดิมก่อนรอบ Minimal เป๊ะ ไม่ใช่ค่าที่เดาใหม่ (วัดแล้ว: สว่าง 1.233 ·
     มืด 1.315 ซึ่งเท่ากับมืดก่อนสลับชั้นสี 1.312) */
.fld .cell-input,
.fld .cell-select {
  border-color: var(--line);
}
/* ชี้แล้วเส้นเข้มขึ้น — ของเดิมก่อนรอบ Minimal ตั้ง rest กับ hover เป็น --line เท่ากัน
   ชี้แล้วจึงไม่มีอะไรตอบกลับเลย · ตอนนี้ยังได้ "กรอบตอนพัก" ตามที่ Big ขอ
   และยังรู้สึกว่ามันเป็นของที่กดได้
   ⚠️ ไม่ต้องเขียนกฎ :focus ซ้ำ — .cell-input:focus ใน layout.css ให้ --line-strong
   กับพื้น --surface อยู่แล้ว (ถอดออกแล้วเทสต์ยังเขียว = ของเดิมทำอยู่)
   🔴 และห้ามเขียน .fld .combo .cell-input ทับ — มันเฉพาะเจาะจง (0,3,0) พอที่จะชนะ
   ทั้ง .combo.is-open .cell-input (0,3,0 แต่อยู่ใน layout.css ซึ่งโหลดก่อน) และ
   .cell-input:focus (0,2,0) → กรอบตอนเปิดแผงจะหายไปเงียบๆ
   (โค้ดชุดก่อนหน้านี้เขียนไว้จริง แต่รอดมาได้เพราะตอนนั้นมีกฎ .fld .cell-input:focus
   คอยกันไว้อีกชั้น — พอถอดกฎนั้นออก เกราะชั้นนั้นก็ไม่มีแล้ว) */
.fld .cell-input:hover,
.fld .cell-select:hover {
  border-color: var(--line-strong);
}
/* ค่าที่คำนวณมา ไม่ใช่ช่องกรอก — ต้องดูไม่เหมือน input ไม่งั้นคนจะพยายามพิมพ์ */
.fld-static {
  padding: var(--sp-1) var(--sp-2);
  font-size: var(--fs-base);
  font-weight: 600;
  color: var(--ink);
}

/* ---- รายชื่อ + ตารางเรียน วางเคียงกัน ------------------------------ */
/* 🔴🔴 2026-10-08 — grid + จุดตัด 1400px → flex ที่ "ตัดบรรทัดเอง"
   (Big สั่ง — "อยากให้ทุกอย่างพอดีแบบไม่ต้องเลื่อน เพราะมันยุ่งยากมากในการใช้งาน
    ปุ่ม X กับ ปุ่ม Return")
   🔴 ของเดิมพังตรงไหน — วัดจริงแล้วมันกลับหัว:
     จอ 1424 (ยังแบ่งสองคอลัมน์) ตาราง Student ได้ที่ **685px**
     จอ 1264 (ตกเป็นคอลัมน์เดียว)  ตาราง Student ได้ที่ **1105px**
   **หดจอลง 160px แล้วตารางกว้างขึ้น 420px** — ช่วง 1400–1750 จึงเป็นช่วงที่บีบที่สุด
   ของทั้งหน้า ทั้งที่จอกว้างกว่า · จุดตัดที่เป็น "เลขหน้าจอ" ตอบผิดคำถาม
   คำถามจริงคือ **"ตารางซ้ายยังได้ที่พอไหม"** ไม่ใช่ "จอกว้างเท่าไหร่"
   ⚠️ และเลขหน้าจอไม่รู้จักเมนูซ้ายที่ปักหมุดซึ่งกินไปอีก 172px — flex-wrap รู้เอง
      เพราะมันถามจากที่ว่างจริงของกรอบ ไม่ใช่จากความกว้างหน้าต่าง
   🔴 การตัดบรรทัดของ flex ตัดสินจาก **flex-basis** ก่อนการหด — ตั้ง basis เท่ากับ
      ความกว้างจริงที่ตารางต้องการ มันจึงยอมตกบรรทัดแทนที่จะบีบตารางให้เลื่อน */
.sheet-split {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-5);
  align-items: flex-start;
}
/* Student — ยืดได้เมื่อมีที่เหลือ แต่ไม่ยอมอยู่ข้างๆ ถ้าได้ที่น้อยกว่า 920px
   (920 = ความกว้างจริงของตาราง ดูหัวข้อ data-list="roster" ข้างล่าง) */
.sheet-split > :first-child {
  flex: 1 1 920px;
  min-width: 0;
}
/* Class Schedule — 5 คอลัมน์รวม 550px **ห้ามยืด** (flex-grow 0)
   ⚠️ เดิมพอตกบรรทัดแล้วมันกินเต็มความกว้าง แล้ว table-layout: fixed หารที่ว่าง
      เฉลี่ยเข้าทุกคอลัมน์ — วัดได้ที่จอ 1280: No. กลายเป็น 96px · Date 315px
      · Remark 438px = ช่องไฟห่างโดยไม่มีเหตุผล (อาการเดียวกับที่ Big ชี้)
   564 ไม่ใช่ 560 — กรอบ .table-wrap กินไป 12px ของเดิมจึงล้นอยู่ 2px มาตลอด */
.sheet-split > :last-child {
  flex: 0 1 564px;
  min-width: 0;
}

/* ---- ใบเสร็จ (Private) --------------------------------------------- */
/* 🔴 1 ใบเสร็จ + ตารางของมัน = "กรอบเดียว" (Big สั่ง 2026-09-23 — "อยากให้แบ่งกรอบให้ชัดเจนข้างในอีก
   เช่น ใบเสร็จ+ตารางเรียน คือมีกรอบแบ่งให้ชัดเจนจาก ใบเสร็จ+ตารางเรียน 2 เผื่อคนใช้มามองแล้วมันงง")
   ⚠️ เดิมคั่นด้วย "เส้นบน + ระยะห่าง" เท่านั้น — คนอ่านต้องเดาเองว่าตารางนี้ของใบเสร็จไหน
   พื้นเป็น --surface-2 (จางกว่าพื้นกรอบนักเรียน --surface-3) แถบใบเสร็จกับตารางที่เป็น --surface จึงยังลอยขึ้นมาอยู่ */
/* .att-course = กรอบเดียวกันในหน้า Attendance (Big สั่ง 2026-09-23 — "แยก Attendance เป็นของใครของมันหน่อย
   เพราะตอนนี้มันเอาตารางใบเสร็จ 1 มารวมกับใบเสร็จ 2") — 1 คอร์ส = 1 ตาราง ไม่ใช่คาบทุกคอร์สต่อกันในตารางเดียว */
/* .plan-course = กรอบเดียวกันในหน้า Planning (Big สั่ง 2026-09-23 — "แก้ไขตรงนี้ให้เป็นแบบ Roster
   ที่เป็นกรอบแบ่งแต่ละใบเสร็จและตารางเรียน") — ทั้งสามหน้าที่มี "คอร์ส" จึงหน้าตาเหมือนกันหมด */
.receipt,
.att-course,
.plan-course {
  /* 🔴🔴 2026-10-07 — ระยะระหว่างคอร์ส 16 → 24px (Big สั่ง — "ลดกรอบที่ซ้อนกัน ...
     หนึ่ง Course = หนึ่งกรอบ ที่เหลือแยกด้วยระยะห่างและน้ำหนักตัวอักษรแทนเส้น")
     ⚠️ กรอบรอบนอกถูกถอดไปแล้ว (.logs-body) ระยะนี้จึงเป็นสิ่งเดียวที่บอกว่าคอร์สจบตรงไหน
     ลดกลับเป็น --sp-4 เมื่อไหร่ สองคอร์สจะอ่านติดกันเป็นก้อนเดียว */
  margin-bottom: var(--sp-6);
  /* 🔴 2026-10-06 — padding 12 → 16px (Big สั่ง — "ปุ่มมันชิดขอบไป ทำให้มันมีช่องว่าง
     แบบ Blank หน่อย") · ⚠️ ถ้าแก้ค่านี้ **ต้องแก้ margin ติดลบของตารางที่กินเต็มกรอบ
     ให้เท่ากันเสมอ** (ดู .att-course > .table-wrap) ไม่งั้นตารางจะยื่นพ้นกรอบหรือเว้นไม่เท่ากัน */
  padding: var(--sp-4);
  /* 🔴 2026-10-06 — พื้นโปร่ง ไม่ถมสีอีกชั้น · เส้นขอบบอกขอบเขตได้อยู่แล้ว
     เดิมเรียงพื้นกันสี่ชั้น (พื้นหน้า → .rl-body → .logs-body → กรอบคอร์ส) ซึ่งแต่ละชั้น
     ต่างกันนิดเดียว ตาจึงอ่านไม่ออกว่ามีกี่ชั้น รู้แค่ว่า "มันเทาๆ หลายอัน"
     ⚠️ ต้องแก้พร้อมกันทั้งสามคลาส — กฎ 2026-09-23 บอกว่าสามหน้าที่มีคอร์สต้องหน้าตา
     เหมือนกันหมด แก้แค่ .receipt จะได้ Roster กับ Attendance คนละแบบทันที */
  background: transparent;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-lg);
}
/* ═══ 🔴🔴 2026-10-08 (รอบห้า) — ความกว้างของ "การ์ดคอร์ส" คุมความกว้างของคอลัมน์ Remarks ═══
   (Big สั่ง — "กำหนดความกว้างคอลัมน์ Remark ตายตัวที่ 200px ไม่ให้ยืดตามพื้นที่ที่เหลือ")
   🔴 **ตารางนี้ไม่มี "คอลัมน์หลัก" ให้ยืดแทนตามที่สเปคสมมุติไว้** — No. / Date / TH-Time /
   Learning format / Teacher เป็นช่องควบคุมทั้งหมด และทุกตัวมีของติดท้าย (ปฏิทิน · นาฬิกา ·
   ลูกศร) ยืดตัวไหนก็ได้กับดักเดิม 2026-09-03 "ค่าอยู่ซ้ายสุด ลูกศรอยู่ขวาสุด"
   → ที่ว่างจึงถูก **ตัดทิ้งที่การ์ด** ไม่ใช่ย้ายไปซุกในคอลัมน์ไหน
   ⚠️ .att-course ไม่ถูกจำกัด — ตารางเช็คชื่อกว้างขั้นต่ำ 1260px มันต้องการทุกพิกเซลจริง
   (เหตุผลเดียวกับที่ .plan-course เลิกดึงตารางเต็มกรอบแต่ .att-course ยังดึง — 2026-10-06) */
/* ⚠️ 2026-10-08 (รอบเจ็ด) — เพดานความกว้างของการ์ด Planning **ถูกถอดออกแล้ว**
   (Big สั่ง — "หน้า Planning ก็แก้ให้เหมือน Roster เลย") · เหตุผลเดียวกับฝั่ง Roster:
   พอ Remarks ย้ายไปท้ายแถว ที่ว่างไปกองในคอลัมน์ข้อความ ไม่ต้องบีบกรอบเพื่อกันมันอีกแล้ว */
/* 🔴 หัวใบเสร็จเป็นแถบบรรทัดเดียว ไม่ใช่การ์ด .field-grid เต็มใบ
   (Big สั่ง 2026-09-07 — "มันกว้างไปดูยากซับซ้อน อยากทำให้มันดู minimal")
   ของเดิมสูงเกือบ 90px ต่อใบเสร็จ และคนหนึ่งมีได้หลายใบ */
.rcp-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-4);
  margin-bottom: var(--sp-3);
  /* 🔴🔴 2026-10-07 — แถบนี้ "ไม่ใช่กล่อง" อีกต่อไป มันคือหัวของกรอบคอร์ส
     (Big สั่ง — "ลดกรอบที่ซ้อนกัน ... ตอนนี้มีกรอบซ้อนประมาณ 5 ชั้น")
     ⚠️ เดิมมีพื้น --surface + เส้น --line + มุมโค้ง ซึ่งเป็นกล่องซ้อนอยู่ในกล่องคอร์ส
     อีกชั้น · ของที่อยู่ในกรอบเดียวกันไม่ต้องมีกรอบของตัวเองซ้ำ
     ⚠️ ใช้ร่วมกันทั้ง 3 หน้า (Roster · Attendance · Planning) — กฎ 2026-09-23 */
  padding: 0;
  background: transparent;
  border: 0;
  border-radius: 0;
}
.rcp-ico {
  display: flex;
  color: var(--muted);
}
.rcp-ico .btn-ico {
  width: 17px;
  height: 17px;
}
/* label อยู่หน้าช่องกรอกในบรรทัดเดียวกัน ไม่ใช่ซ้อนอยู่ข้างบน —
   ซ้อนข้างบนคือสิ่งที่ทำให้แถบนี้เคยสูงเป็นการ์ด */
.rcp-fld {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-width: 0;
}
.rcp-label {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}
.rcp-fld .cell-input {
  width: 150px;
  border-color: var(--line-strong);
  padding: var(--sp-1) var(--sp-2);
}
/* ตัวเลขที่ระบบนับให้ ดันไปชิดขวาพร้อมปุ่ม ⋮ */
.rcp-used {
  display: inline-flex;
  align-items: baseline;
  gap: var(--sp-1);
  margin-left: auto;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
/* Hours (นับจากตาราง) นั่งหน้า used — ตัวแรกดันชิดขวา ตัวที่สองต่อท้ายติดกัน */
.rcp-hours + .rcp-used {
  margin-left: 0;
}
.rcp-used-label {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}
.rcp-tools {
  display: flex;
}
.rcp-tools .kebab-btn {
  opacity: 1;
}

/* ---- ปุ่มเพิ่มแถวใต้ตาราง ------------------------------------------ */
.addrow-btn {
  margin-top: var(--sp-2);
}

.empty-card p {
  color: var(--muted);
  text-align: center;
}

/* ---- เซลล์ในตารางชีต ----------------------------------------------- */
.check-wrap {
  display: flex;
  justify-content: center;
}
.cell-check {
  width: 16px;
  height: 16px;
  accent-color: var(--accent);
  cursor: pointer;
}
.num {
  text-align: center;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}

/* แถวรวม — ตัวเลขนับจากของจริงในตาราง ไม่ใช่เลขที่พิมพ์มือ */
.total-row td {
  background: var(--surface-2);
  font-weight: 700;
  color: var(--ink);
  border-top: 1px solid var(--line-strong);
}
.total-row .num {
  color: var(--ink);
}
.total-label {
  text-align: right;
}

/* 🔴 ช่องที่ถือปุ่มฟองโน้ต — จัดกลางทั้งหัวและเนื้อ (หัวกลางแต่ปุ่มชิดซ้าย = อ่านว่า "บิด")
   ⚠️ ต้องล้าง margin-inline ที่ layout.css เติมให้ .note-btn ในตาราง ไม่งั้นมันเยื้องขวา 8px
   ⚠️ 2026-10-07 — เดิมกฎนี้ผูกกับ table[data-list="contacts"] ตัวเดียว · พอ Remarks ของ
   ตารางคาบทั้ง 4 ใบกลายเป็นปุ่มโน้ตด้วย การก๊อปกฎไปอีก 3 ชุดคือการมีความจริงเดียวอยู่ 4 ที่
   · เล็งที่ td.cell-note ได้เลยโดยไม่ต้องกลัวโดนตารางอื่น เพราะคลาสนี้มีเฉพาะช่องที่เราใส่เอง */
/* ⚠️ 2026-10-07 (รอบสี่) — จัดกลางเฉพาะช่องที่ถือ "ปุ่มไอคอน" (Comment ของ Contact List)
   ช่องที่ถือ "ข้อความ" (.note-line ของตารางคาบ) ต้องชิดซ้ายเหมือนคอลัมน์ข้อความอื่น */
.table-wrap td.cell-note:has(> .note-btn) { text-align: center; }
.table-wrap td.cell-note > .note-btn { margin-inline: 0; }

/* คาบที่ยกเลิก — ขีดฆ่า + เป็นสีเตือน ตรงกับที่ชีตทำด้วยมือ
   ต้องใส่ที่ตัว input ด้วย ไม่ใช่แค่ tr เพราะ text-decoration
   ไม่ตกทอดเข้าไปใน form control */
/* 🔴 `.cell-static` ก็ต้องโดนด้วย (Big สั่ง 2026-09-28 — "ใน Planning ก็ให้มันขีดฆ่าไปเลย")
   วัน/เวลา/ครู ของคาบที่มาจากแผนเป็นช่องอ่านอย่างเดียว กฎเดิมจับแต่ช่องที่กรอกได้
   แถวที่ยกเลิกเลยเห็นแค่ `-` กับติ๊ก — ดูยังไงก็ไม่ออกว่าวันนั้นถูกยกเลิกไปแล้ว */
/* 🔴🔴 2026-10-07 — ทั้งแถวจางลง · **ขีดฆ่าเฉพาะ Date กับ TH-Time** (Big สั่ง)
   ⚠️ กลับด้านจาก 2026-09-28 ที่ขีดฆ่าทุกช่องรวม Teacher กับเลข No.
   เหตุผลที่แคบลงแล้วดีกว่า: ขีดฆ่าแปลว่า "สิ่งนี้ไม่ได้เกิดขึ้น" ซึ่งจริงกับ**วันกับเวลา**
   แต่ครูที่ถูกจองไว้ไม่ได้ "ไม่มีตัวตน" · ความจางบอก "ทั้งแถวนี้ไม่นับ" ได้อยู่แล้ว
   🔴 **Remarks ยังแดงและไม่จาง** — กฎ 2026-09-28 ยังอยู่ ("ให้ตัวหนังสือเป็นสีแดงพอ")
   มันคือช่องเดียวในแถวที่เขียนว่า "ทำไมถึงยกเลิก" ซึ่งยังต้องอ่านจริงๆ
   ถ้าทำให้มันจางตามแถว เหตุผลการยกเลิกจะกลายเป็นของที่อ่านยากที่สุดในแถว */
.row-cancelled .cell-input,
.row-cancelled .cell-select,
.row-cancelled .cell-static,
.row-cancelled .time-btn,
.row-cancelled .num {
  color: var(--muted);
  text-decoration: none;
}
.row-cancelled .td-date :is(.cell-input, .cell-static),
.row-cancelled .td-time :is(.cell-input, .cell-static, .time-btn, .time-label) {
  text-decoration: line-through;
}
/* ⚠️ `input[type=date]` วาดค่าของมันไว้ใน shadow DOM — `text-decoration` ของตัว input
   **ไม่ตกทอดเข้าไป** · computed style ของ input บอกว่า line-through แต่บนจอไม่มีเส้นสักเส้น
   (เทสต์ผ่านทั้งที่รูปผิด — เจอ 2026-09-28 ตอนเทียบรูปหน้า Planning) ต้องสั่งที่ชิ้นข้างในเอง */
.row-cancelled .td-date input[type="date"]::-webkit-datetime-edit,
.row-cancelled .td-date input[type="date"]::-webkit-datetime-edit-text,
.row-cancelled .td-date input[type="date"]::-webkit-datetime-edit-day-field,
.row-cancelled .td-date input[type="date"]::-webkit-datetime-edit-month-field,
.row-cancelled .td-date input[type="date"]::-webkit-datetime-edit-year-field {
  text-decoration: line-through;
}
/* 🔴 ยกเว้น Remarks — เป็นสีแดง ไม่จางและไม่ขีดฆ่า (Big สั่ง 2026-09-28 — "ให้ตัวหนังสือ
   เป็นสีแดงพอ") · ขีดฆ่าบนวัน/เวลา/ครู อ่านว่า "วันนี้ไม่ได้เกิดขึ้น" ซึ่งถูก · แต่ Remarks
   คือที่ที่เขียนว่า **ทำไมถึงยกเลิก** ซึ่งยังต้องอ่านจริงๆ
   🔴🔴 2026-10-07 (รอบสี่) — Remarks กลับมาเป็น "ข้อความในแถว" สีแดงจึงกลับไปอยู่ที่
   ตัวหนังสือตามคำสั่งเดิม 2026-09-28 · **เหตุผลการยกเลิกอ่านได้ทันทีโดยไม่ต้องแตะอะไร**
   ⚠️ ปุ่มฟองโน้ตแบบไอคอน (.note-btn) ยังมีที่ช่อง Comment ของ Contact List ซึ่งไม่มี
   แถวที่ยกเลิก กฎสีแดงของปุ่มจึงไม่ต้องมีอีกแล้ว */
.row-cancelled .note-line {
  color: var(--danger);
  text-decoration: none;
}

/* ---- Planning: กล่องของใครของมัน ------------------------------------
   🔴 ไม่ใช่ dropdown (Big สั่ง 2026-09-07 — "อยากได้เป็น ViewBox ของใครของมัน")
   ทุกคนได้กล่องของตัวเอง เรียงลงมาในหน้าเดียว เหมือนบล็อกเขียวในชีต */
/* กล่องทั้งใบ (หัวเขียว + กรอบรายละเอียด) = การ์ดใบเดียวที่ยกขึ้นจากพื้นหน้า
   เงาอยู่ที่ .vbox ไม่ใช่ที่หัวหรือที่กรอบ เพราะถ้าใส่แยกสองที่ จะมีเงา
   พาดกลางกล่องตรงรอยต่อ · ตัว .vbox ไม่มีพื้นของตัวเอง แต่เงาวาดรอบ
   border-box ของมันซึ่งพอดีกับขอบนอกของทั้งสองชิ้นอยู่แล้ว */
.vbox {
  border-radius: var(--r-md);
  box-shadow: var(--sh-1);
}
.vbox + .vbox {
  margin-top: var(--sp-6);
}
/* กล่องที่ย่ออยู่คือ "แถวในลิสต์" ไม่ใช่ "ส่วนของหน้า" — ระยะห่างแบบส่วน
   ทำให้แถบสองแถบที่สูงแค่ 30px ลอยห่างกัน 32px จนอ่านไม่เป็นลิสต์
   (Big สั่ง 2026-09-09 — "กรอบเขียวๆมันยาวไป อยากทำให้มัน Compact") */
.vbox.is-collapsed + .vbox {
  margin-top: var(--sp-2);
}
/* หัวกล่อง — แถบบนของการ์ด ถือชื่อ + ป้ายสถานะ + ปุ่มของกล่องนี้
   ปุ่มอยู่ในหัว ไม่ใช่ลอยอยู่เหนือหน้า เพราะหน้านี้มีหลายกล่อง
   ปุ่มที่ไม่ได้เกาะกับกล่องไหนเลย จะไม่มีทางรู้ว่ามันสั่งกล่องไหน

   🔴🔴 2026-10-06 (รอบสาม) — แถบ "ตรง" ไม่มีมุมเฉียงแล้ว (Big สั่ง — "ไม่ต้องเอาเฉียงละ
   เอาตรงๆ แบบ Minimal น่าดูเลย") · ⚠️ กลับด้านจากคำสั่ง 2026-09-22 ที่ขอ banner แบบ lower-third
   ผลที่ตามมาสำคัญกว่าเรื่องรูปทรง: **กลไกทั้งหมดที่เคยทำมุมเฉียงถูกลบทิ้ง** —
   ส่วน BANNER ท้ายไฟล์ (.vb-deco · .vb-plate · --vb-skew) และ <span> ที่ vboxHead เคยสร้าง
   หัวกล่องจึงวาดพื้นกับเส้นด้วยตัวเองตรงๆ เหมือน element ธรรมดาทั่วไป
   · ไม่ต้องมี position: relative / isolation: isolate อีกแล้ว — สองอันนั้นมีไว้ให้ชิ้นตกแต่ง
     ที่ position: absolute + z-index ติดลบ เกาะและไม่หลุดไปหลังพื้นของหัว · ไม่มีชิ้นตกแต่งก็ไม่ต้องมี
   · พื้น --surface-3 เท่ากับ .vbox-body หัวกับกรอบจึงเป็นการ์ดใบเดียว
     (เส้นล่างของหัว = เส้นบนของกรอบ — .vbox-body จึงตั้ง border-top: 0 ไม่งั้นซ้อนหนา 2px) */
.vbox-head {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-4);
  background: var(--surface-3);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md) var(--r-md) 0 0;
}
/* 🔴 กรอบใหญ่ที่คลุมรายละเอียดทั้งกล่อง (Big สั่ง 2026-09-11 — "ทำกรอบใหญ่ๆ
   คลุมรายละเอียดให้หน่อย เพราะถ้าแบบนี้มันดูไม่ค่อยรู้เรื่อง")
   ก่อนหน้านี้หัวเขียวลอยอยู่ แล้วการ์ดข้อมูล / ตาราง 3 ตัว / หัวข้อย่อย
   นั่งบนพื้นหน้าเปล่าๆ ต่อกันลงมา — พอมีหลายคลาสในหน้าเดียว ไม่มีเส้นไหน
   บอกได้ว่าตารางนี้เป็นของคลาสไหน ต้องไล่ขึ้นไปหาหัวเขียวอันล่าสุดเอาเอง

   พื้นเป็น --surface-3 ไม่ใช่ --surface เพราะของข้างในเกือบทั้งหมด
   (.card / .table-wrap) พื้นเป็น --surface อยู่แล้ว — ถ้ากรอบขาวเหมือนกัน
   ของข้างในจะจมหายไปกับพื้น เหลือแต่เส้นขอบบางๆ ซึ่งคือปัญหาเดิม
   ⚠️ --surface-2 ก็ยังไม่พอ: ธีมสว่างมันคือ #f8fafc ส่วนพื้นหน้าคือ #f1f5f9
   ต่างกันจนมองไม่ออกว่ามีกรอบ · --surface-3 (#eef2f7) เข้มกว่าพื้นหน้า
   กรอบเลยอ่านเป็น "ร่องที่จมลงไป" แล้วตารางขาวๆ ลอยขึ้นมาเป็นก้อนชัดๆ */
.vbox-body {
  padding: var(--sp-4);
  background: var(--surface-3);
  /* 🔴 2026-10-06 (รอบสอง) — เส้นกรอบเลิกเขียวตามแผ่นหัว · เส้นเขียวรอบกล่องทั้งใบ
     คือเขียวที่แปลว่า "นี่คือกล่อง" ซึ่งเป็นความหมายที่สีไม่ควรแบก */
  border: 1px solid var(--line-strong);
  border-top: 0; /* ใช้เส้นล่างของหัวร่วมกัน ไม่งั้นตรงรอยต่อจะหนา 2px */
  border-radius: 0 0 var(--r-md) var(--r-md);
}
/* ย่ออยู่ = บางลงอีกชั้น · แถบนี้ไม่ได้เป็นหัวข้อของอะไรแล้ว (ข้างล่างไม่มีอะไร)
   มันเป็นแค่บรรทัดสรุป จึงไม่ต้องมีน้ำหนักเท่าหัวข้อ */
.vbox-head.is-collapsed {
  padding: var(--sp-1) var(--sp-5);
  margin-bottom: 0;
  border-radius: var(--r-md); /* ไม่มีกล่องต่อข้างล่างแล้ว มุมล่างจึงโค้งกลับ */
}
.vbox-name {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: 700;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.vbox-head.is-collapsed .vbox-name {
  font-size: var(--fs-md);
}
/* ป้ายของคนที่ออกจาก Waiting List ไปแล้ว — ดันปุ่มไปชิดขวาด้วยตัวมันเอง
   (มี <span> เปล่ามาแทนเสมอเมื่อไม่มีป้าย จะได้ไม่ต้องมีกฎ layout สองแบบ) */
/* หัวกล่องที่ย่อได้ — ชื่อกลายเป็นปุ่ม แต่ต้องดูเหมือนหัวข้อเหมือนเดิม
   ไม่ใช่ปุ่มที่มีกรอบ · สัญญาณว่ากดได้คือลูกศรกับ cursor */
.vbox-toggle {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.vbox-caret {
  display: flex;
  color: var(--muted);
  transition: transform var(--dur-1) var(--ease-out);
}
.vbox-caret .btn-ico {
  width: 16px;
  height: 16px;
}
/* ย่ออยู่ = ลูกศรชี้ขวา · กางอยู่ = ชี้ลง (หมุนเอา ไม่สลับรูป จะได้มี transition) */
.vbox-head.is-collapsed .vbox-caret {
  transform: rotate(-90deg);
}
/* ---- mini scoreboard บนหัวกล่องคลาสตอนย่ออยู่ -----------------------
   (Big สั่ง 2026-09-07) ย่อแล้วยังต้องตอบได้ว่า ใครสอน · ที่ไหน · เต็มยัง */
.score {
  display: flex;
  flex: 1;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-4);
  min-width: 0;
}
.score-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--muted);
  white-space: nowrap;
}
.score-chip .btn-ico {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
}
/* แถวที่นั่ง — ชิดกันกว่าชิปอื่น เพราะมันคือ "แถบเดียว" ที่ต้องอ่านรวดเดียว */
.score-seats {
  display: inline-flex;
  align-items: center;
  gap: 3px;
}
.seat {
  display: flex;
  color: var(--seat-off);
}
.seat.is-on {
  color: var(--seat-on);
}
/* 🔴 ตอบรับแล้วแต่ยังอยู่ใน Contact List (Big สั่ง 2026-09-28 ให้นับรวมเป็นที่นั่ง)
   จางกว่าคนที่นั่งอยู่จริง แต่ไม่เทาเท่าที่ว่าง — มันคือ "จองไว้แล้ว ยังไม่มาถึง"
   ⚠️ ใช้ --seat-on ตัวเดิมหรี่ลง ไม่ตั้ง token สีใหม่ สีที่สามในแถวเดียวจะอ่านไม่ออกว่าเรียงลำดับยังไง */
.seat.is-hold {
  color: var(--seat-on);
  opacity: 0.45;
}
.seat .btn-ico {
  width: 15px;
  height: 15px;
}
/* ⚠️ 2026-10-28 — .score-count / .score-free / .score-full ถูกลบ (CSS ตาย)
   ทั้งสามตัวมีไว้ให้ mini scoreboard บนแถบคลาสที่ย่ออยู่ ซึ่งหายไปตอน Group Roster
   กลายเป็นรายชื่อแบบตาราง · ตัวเลข "N of M" ย้ายไปเป็น .pick-count และคำว่า full /
   N seats left ย้ายไปเป็น .rl-status ของคอลัมน์ Seats
   · .score / .score-chip **ยังอยู่** — หน้า Group Attendance ใช้ชิปชุดนี้บนหัวกล่อง
   · .score-seats / .seat ยังอยู่เหมือนกัน แค่ถูกใช้ในเซลล์แทนที่จะอยู่บนแถบ */

/* 🔴 เกินจำนวนที่นั่ง — ป้ายเหนือตาราง Student (Big สั่ง 2026-09-28)
   เตือนอย่างเดียว ไม่ได้ห้าม · งานของมันคือทำให้ตัวเลขที่ขัดกันมองเห็นได้ ไม่ใช่ซ่อนไว้ */
.seat-warn {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  background: var(--warn-bg);
  border: 1px solid var(--warn-ring);
  border-radius: var(--r-md);
  color: var(--warn);
  font-size: var(--fs-sm);
}
.seat-warn-ico {
  display: flex;
  flex: none;
  color: var(--alert-ico);
}
.seat-warn-ico .btn-ico {
  width: 16px;
  height: 16px;
}

.vbox-flag {
  flex: 1;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--muted);
}
.vbox-head > span:not([class]) {
  flex: 1;
}
.vbox-tools {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}
/* ปุ่ม ⋮ ในหัวกล่องไม่ต้องจางรอ hover — ที่นี่ไม่ได้อยู่ในคอลัมน์ยาวๆ
   ที่ปุ่มเข้มทุกแถวจะกลายเป็น noise */
.vbox-head .kebab-btn {
  opacity: 1;
}

/* .row-free ถูกลบ 2026-09-17 พร้อมคอลัมน์ Status ของ Planning */

/* ---- ความกว้างคอลัมน์ของตารางชีต ----------------------------------- */
.col-no { width: 48px; }
/* ชื่อเต็มช่องเดียว (2026-09-28) — เดิมเป็น col-first 186 + col-last 150 = 336px
   สำหรับชื่อที่แทบไม่เคยยาวถึงครึ่ง · ที่ว่างนั้นคือ "ช่องโหว่" ที่ Big เห็นในรูป */
.col-person { width: 214px; }
.col-status { width: 92px; }
.col-chk { width: 80px; }
.col-remarks { width: 220px; }
.col-time { width: 132px; }
.col-who { width: 132px; }
.col-yn { width: 112px; }

/* ผลรวมความกว้างของแต่ละตาราง — ต้องกันไม่ให้ table-layout: fixed
   ถูกบีบจนข้อความขาด (เหตุผลเดียวกับ Waiting List ใน layout.css) */
/* 🔴🔴 2026-10-08 — 1156 → 920px (Big สั่ง — "จัดระเบียบระยะห่างใหม่ให้ไม่ห่างเกินไป
   ต้องการช่องไฟที่ชิดขึ้น ... อยากให้ทุกอย่างพอดีแบบไม่ต้องเลื่อน")
   ทุกตัวเลขมาจากการวัด (canvas measureText ด้วยฟอนต์ที่คำนวณได้จริง + padding ของช่อง)
   ไม่ใช่เลขกลมๆ ที่เดาเอา:
     Student 214 → **190** (ชื่อยาวสุดที่มีจริง "Ching Man Cheung (Ruby)" = 163 + 18 + 8)
     Status   92 → **72**  (หัวคอลัมน์ต้องการ 65 · <select> ต้องการ 63)
     Got book 80 → **82** · Attd. 80 → **60** · Portal 80 → **66**  (ดูข้อถัดไป)
     Email   270 → **190** (เป็น <input> ข้อความเลื่อนในช่องได้ ไม่ได้หายเงียบแบบ <select>)
   48 + 190 + 72 + 82 + 60 + 66 + 190 + act 72 = 780 + Remarks ขั้นต่ำ 140 = **920**
   ⚠️ ต่ำกว่าจอ ~1100 ยังต้องเลื่อนอยู่ — คอลัมน์ตายตัวรวมกันมีพื้นขั้นต่ำของมัน */
.table-wrap table[data-list="roster"] { min-width: 920px; }
.table-wrap table[data-list="roster"] .col-person { width: 190px; }
.table-wrap table[data-list="roster"] .col-status { width: 72px; }
/* 🔴 สามช่องติ๊กได้ความกว้างของตัวเอง — ตัวกำหนดคือ "หัวคอลัมน์" ไม่ใช่ checkbox
   (checkbox กว้าง 16px เท่ากันหมด) · คลาส .col-chk เดียวกันทั้งสามช่องแปลว่า
   ทุกช่องต้องกว้างเท่า "Got book" ซึ่งยาวที่สุด แล้ว Attd./Portal ได้ช่องไฟเปล่า 40px
   ⚠️ .col-chk ตัวกลางยังเป็น 80px เหมือนเดิม — มันใช้ร่วมกับตาราง lessons / schedule
      / total ที่ความกว้างถูกวัดมาต่างหาก (กฎเดียวกับตอนบีบ Contact List 2026-09-28) */
.table-wrap table[data-list="roster"] .col-chk-book { width: 82px; }
.table-wrap table[data-list="roster"] .col-chk-attd { width: 60px; }
.table-wrap table[data-list="roster"] .col-chk-portal { width: 66px; }
.table-wrap table[data-list="roster"] .col-email { width: 190px; }
/* 🔴🔴 Remarks = **ตัวดูดที่ว่างตัวเดียว** ของตารางนี้ (กับดัก Contact List 2026-09-29)
   ไม่มีตัวดูด → table-layout: fixed + width: 100% จะ **หารที่ว่างที่เหลือเฉลี่ยเข้าทุก
   คอลัมน์** ทุกช่องอ้วนขึ้นพร้อมกัน — วัดได้ที่จอ 1920: 214/92/80 กลายเป็น 216/93/81
   นี่คือ "ช่องไฟห่างเกินไป" ที่ Big เห็น ไม่ใช่เพราะตั้งความกว้างไว้ผิด
   ⚠️ ห้ามให้ Student หรือ Email ดูดแทน — ทั้งคู่เป็น <input> ยืดแล้วได้ช่องว่างยาวๆ
      ต่อท้ายข้อความ ส่วน Remarks เป็น textarea ที่มีประโยคจริง กว้างขึ้นแล้วอ่านได้จริง */
.table-wrap table[data-list="roster"] .col-remarks { width: auto; }
/* 🔴 ช่องท้ายแถวของ Student ถือ 2 ปุ่มแล้ว (ย้อนกลับ + ลบ) — ต้องกว้างขึ้น
   ⚠️ table-layout: fixed จะ **ตัด** ไม่ใช่ **ดัน** ถ้าไม่ขยายให้ ปุ่มจะหายไปเงียบๆ */
.table-wrap table[data-list="roster"] .col-act { width: 72px; }
/* ไอคอนไม่มีขนาดในตัว (กับดักเดิมของ .btn-ico) · สีตอนชี้เป็นสีเตือน ไม่ใช่สีแดงของปุ่มลบ
   ถอยกลับไม่ใช่การลบ — ของยังอยู่ แค่ย้ายไปอยู่ขั้นก่อนหน้า */
.backbtn {
  display: inline-flex;
  vertical-align: middle;
}
.backbtn .btn-ico {
  width: 14px;
  height: 14px;
}
.backbtn:hover {
  color: var(--warn);
  background: var(--warn-bg);
}
/* 🔴 2026-09-28 — Big: "ขยะพวกนี้เอามาทางซ้ายให้หน่อย มันอยู่ไกลกันเกินและมีช่องว่างเยอะเกินไป"
   48 + 214 + 200 + 124 + 104 + 124 + 124 + 200 + 44 = 1182 (เดิมประกาศไว้จริง 1376)
   ⚠️ บีบเฉพาะตารางนี้ ไม่ใช่แก้ .col-who / .col-remarks ตัวกลาง — คลาสพวกนั้นใช้ร่วมกับ
   ตาราง lessons / schedule / plan / total ที่ความกว้างของมันถูกวัดมาต่างหาก */
/* 🔴 2026-09-29 — Big: "Column ที่อยู่ตอนนี้เบียดมาทางซ้ายให้หมด เพราะตอนนี้มันกระจาย
   จาก Center เกิน ทำให้ต้องดูกว้างๆ"
   ต้นเหตุ: `.table-wrap table { width: 100% }` + `table-layout: fixed` → ที่ว่างที่เหลือ
   ถูก **หารเฉลี่ยเข้าไปในทุกคอลัมน์** พอตารางแคบกว่าจอ ทุกช่องเลยอ้วนขึ้นพร้อมกัน
   ทางแก้: **ให้ Remarks เป็นคอลัมน์เดียวที่ไม่ตั้งความกว้าง** (width: auto) มันจะกินที่ว่าง
   ที่เหลือคนเดียว คอลัมน์อื่นจึงคงความกว้างจริงและชิดซ้ายทั้งแถว
   ⚠️ ห้ามแก้ด้วย `width: auto` ที่ตัวตาราง — ตารางจะหดจนขอบขวาแหว่งอยู่กลางกรอบ
   48 + 200 + 96 + 112 + 116 + 112 + 112 + 236 + 44 = 1076 (+ Remarks อย่างน้อย 180)
   ⚠️ **col-yn ต้องไม่ต่ำกว่า ~112px** — "No Reply" ใน `<select>` ถูกตัดเป็น "No Repl" ที่ 96px
   โดยไม่มี ellipsis ให้เห็น · รอบแรกบีบลงไปแล้วเห็นจากรูปอย่างเดียว เทสต์ตอนนี้วัดด้วย
   canvas measureText เทียบกับที่ว่างจริงในช่อง (หัก padding + ลูกศรของระบบ) */
.table-wrap table[data-list="contacts"] { min-width: 1256px; }
.table-wrap table[data-list="contacts"] .col-person { width: 200px; }
/* 🔴 Comment ถือแค่ปุ่มฟองโน้ต 26px — ที่ว่างที่คืนมาไปเข้า Name กับ Email
   หัวคอลัมน์ ("Comment") กว้างกว่าปุ่ม จึงเป็นตัวกำหนดความกว้างขั้นต่ำ */
.table-wrap table[data-list="contacts"] .col-comment { width: 96px; }
.table-wrap table[data-list="contacts"] .col-who { width: 112px; }
.table-wrap table[data-list="contacts"] .col-yn { width: 116px; }
.table-wrap table[data-list="contacts"] .col-email { width: 236px; }
.table-wrap table[data-list="contacts"] .col-remarks { width: auto; }
/* 🔴🔴 2026-10-07 (รอบสอง) — Remarks หดจาก 220px เหลือ 96px เพราะมันถือแค่ปุ่มโน้ต 26px
   (หัวคอลัมน์ "Remarks" กว้างกว่าปุ่ม จึงเป็นตัวกำหนดความกว้างขั้นต่ำ — เหมือน col-comment)
   48 + 158 + 128 + 140 + Remarks(auto · ขั้นต่ำ 160) + 80 + 44 = 758
   ⚠️ ตารางคาบจากแผนไม่มีคอลัมน์ act (ui.table { act: false }) จึงแคบกว่านี้ 44px
   แต่ min-width ใช้ร่วมกันทั้งสองแบบ — เอาค่าของแบบที่กว้างกว่า */
/* 🔴 2026-10-08 (รอบห้า) — 758 → 698 = คอลัมน์ตายตัว 598 + Remarks ขั้นต่ำ **100**
   ⚠️ ไม่ใช่ 200 ทั้งที่ Big ขอ 200 — 200 คือค่าที่ได้ "ตอนการ์ดเต็มความกว้าง" ซึ่งมาจาก
   การจำกัดความกว้างการ์ด (ดู .logs-body) ไม่ใช่จาก min-width · ถ้าเอา 200 มาเป็น min-width
   ตารางจะเลื่อนข้างทันทีที่จอแคบลง ซึ่งคือสิ่งที่ข้อ 5 ของสเปคสั่งให้เลิก
   🔴 100 ต้องตรงกับจุดตัดของ @container ข้างล่างเป๊ะ — ไม่งั้นจะมีช่วงความกว้างที่
   "ยังไม่ยุบ แต่ตารางล้นแล้ว" โผล่มาเป็นแถบเลื่อนโดยไม่มีใครตั้งใจ */
/* 🔴 2026-10-08 (รอบหก) — 48 + 158 + 128 + Teacher**160** + Cancelled**100** + act44 = 638
   + Remarks (auto · ขั้นต่ำ 160) = 798 · ⚠️ ตารางคาบจากแผนไม่มี act จึงแคบกว่านี้ 44px */
.table-wrap table[data-list="lessons"] { min-width: 798px; }
.table-wrap table[data-list="lessons"] .col-time { width: 128px; } /* ไอคอนนาฬิกา + "18:00-20:00" */
/* 🔴 2026-10-07 — 72 → 80px · หัว "Cancelled" เคยพอดีเป๊ะที่ 72 (scrollWidth = clientWidth)
   เพราะที่ว่างส่วนเกินถูกหารเฉลี่ยเข้ามาช่วยอยู่ · พอ Teacher ดูดที่ว่างไปคนเดียว
   คอลัมน์นี้เหลือ 72 เป๊ะ ไม่มีระยะเผื่อเลย — table-layout: fixed จะ **ตัด** ไม่ใช่ **ดัน** */
/* 🔴 2026-10-08 — 80 → 100px (Big สั่ง "Cancelled ประมาณ 100px จัด checkbox กึ่งกลาง") */
.table-wrap table[data-list="lessons"] .col-chk { width: 100px; }
/* 🔴🔴 2026-10-07 (รอบสี่) — **Remarks กลับมาเป็นตัวดูดที่ว่าง** เพราะมันกลับมาเป็น
   "ข้อความในแถว" แล้ว (Big สั่ง — "ให้คอลัมน์นี้ยืดกินพื้นที่ที่เหลือ")
   ⚠️ รอบเช้าตัวดูดคือคอลัมน์ act เพราะ Remarks เหลือแค่ปุ่ม 26px ซึ่งกว้างขึ้นแล้วไม่ได้อะไร
   ตอนนี้มีข้อความจริงให้โชว์ มันจึงกลับมาเป็นตัวเลือกที่ถูกอีกครั้ง
   🔴 **ห้ามให้ Teacher เป็นตัวดูด** — combo ครูยืดเป็น 590px ทำให้ชื่ออยู่ซ้ายสุด
   แต่ลูกศรไปอยู่ขวาสุด ห่างกันครึ่งตาราง (กับดักที่จดไว้ตั้งแต่ 2026-09-03)
   ⚠️ ห้ามตั้ง width: auto ที่ตัวตาราง — ตารางจะหดจนขอบขวาแหว่งอยู่กลางกรอบ */
.table-wrap table[data-list="lessons"] .col-who { width: 160px; }
.table-wrap table[data-list="lessons"] .col-remarks { width: auto; }
/* 🔴 2026-10-08 (รอบเจ็ด) — 140 → 160px ให้ตรงกับตารางคาบของ Roster */
.table-wrap table[data-list="plan"] .col-teacher { width: 160px; }
.table-wrap table[data-list="plan"] .col-remarks { width: auto; }
.table-wrap table[data-list="schedule"] { min-width: 520px; }
/* 🔴 ตารางที่ถูกจำกัดจำนวนแถว (ui.capRows) — ความสูงจริงมาจาก JS เพราะต้องวัดแถวจริง
   ⚠️ **ไม่ต้องเขียน `overflow-y: auto`** — `.table-wrap` มี `overflow-x: auto` อยู่แล้ว
   และกฎของ CSS คือแกนที่เหลือเป็น `visible` ไม่ได้ มันจะกลายเป็น `auto` ให้เอง
   (เขียนไปก็เป็นโค้ดตาย · ลองถอดออกแล้วเทสต์ยังเขียวครบ จึงถอดทิ้งจริง)
   ⚠️ overscroll-behavior: เลื่อนจนสุดตารางแล้วต้องหยุด ไม่ใช่ลากหน้าทั้งหน้าไหลต่อ
   ไม่งั้นคนที่กำลังไล่ดูคาบจะหลุดออกไปอยู่ท้ายหน้าโดยไม่ตั้งใจ */
.table-wrap.is-capped {
  overscroll-behavior: contain;
}
/* 🔴 2026-10-08 (รอบเจ็ด) — Remarks ย้ายไปท้ายสุด และ Teacher โตเป็น 160
   No.48 + Date158 + Time132 + Format148 + Teacher160 + act44 = 690 + Remarks(auto · ขั้นต่ำ 160) */
.table-wrap table[data-list="plan"] { min-width: 810px; } /* 690 + Remarks ขั้นต่ำ 120 — ที่จอ 1024 กรอบมีที่จริง 817px */

/* ---- หมายเหตุใต้หัวชีต + ปุ่มที่หน้าตาเป็นลิงก์ --------------------- */
.sheet-note {
  margin: calc(var(--sp-3) * -1) 0 var(--sp-4);
  font-size: var(--fs-sm);
  color: var(--muted);
}
/* เป็น <button> ไม่ใช่ <a> เพราะมันสั่งเปลี่ยนหน้าในแอป ไม่ได้พาไป URL อื่น —
   ถ้าใช้ <a href="#"> คนกดขวาแล้ว "เปิดแท็บใหม่" จะได้หน้าเปล่า */
.linkish {
  appearance: none;
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  color: var(--ink-2);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}
.linkish:hover {
  color: var(--ink);
}

/* ======================================================================
   Attendance (ตารางเช็คชื่อ) — Big สั่ง 2026-09-17
   หน้าที่เคยชื่อ Attendance กลายเป็น Roster · หน้านี้คือของใหม่ 2 ชั้น:
   ชั้นแรก = รายชื่อ (ยังไม่มีตาราง) · กดชื่อ = เปิดตารางของคนนั้น/คลาสนั้น
   ====================================================================== */

/* ---- ชั้นรายชื่อ -----------------------------------------------------
   ทั้งแถวเป็น <button> เดียว — กดตรงไหนของแถวก็เปิด และกด Tab/Enter ได้
   คอลัมน์จัดด้วย grid ที่ .pick-list ประกาศไว้ครั้งเดียว ทุกแถวใช้ร่วมกัน
   (subgrid) หัวคอลัมน์กับข้อมูลเลยตรงกันเสมอโดยไม่ต้องกะความกว้าง */
.pick-list {
  display: grid;
  grid-template-columns: var(--pick-cols);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
  overflow: hidden;
}
.pick-list[data-kind="private"] {
  --pick-cols: minmax(0, 2.2fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1.2fr) auto;
}
.pick-list[data-kind="group"] {
  --pick-cols: minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1.4fr) auto;
}
.pick-head,
.pick-row {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
  align-items: center;
  column-gap: var(--sp-4);
  /* 🔴 2026-10-06 — แนวตั้งแน่นขึ้นหนึ่งระดับ (sp-3 → sp-2)
     ⚠️ "แนวนอน" ห้ามแตะ — ดูกฎ 4+8=12 ที่หัวข้อ "การจัดวางในตาราง" */
  padding: var(--sp-2) var(--sp-4);
}
/* 🔴 2026-10-06 — หัวรายชื่อพูดภาษาเดียวกับหัวตาราง (.table-wrap th) แล้ว
   เดิมเว็บนี้มีหัวคอลัมน์สองแบบ: รายชื่อเป็น "STUDENT" ตัวใหญ่หนา ส่วนตารางเป็น
   "Student" ตัวปกติ · หน้าเดียวกันมีทั้งสองแบบอยู่คนละก้อน อ่านเหมือนคนละเว็บ
   ⚠️ ขนาดยังต้องเป็น --fs-xs เท่าเดิม ห้ามขยับขึ้น --fs-sm — คอลัมน์ Status ของ
   รายชื่อเป็น max-content หัวที่กว้างขึ้นจะดันทั้งรายชื่อจนหน้าเลื่อนข้างได้ (ลองแล้ว) */
.pick-head {
  background: transparent;
  /* 🔴 2026-10-06 — เส้นใต้หัวคอลัมน์ใช้ --line-strong ไม่ใช่ --line
     (Big สั่ง — "ทำเส้นแบ่งให้หน่อยระหว่าง Column กับ List ข้อมูล")
     เส้นนี้แบ่ง "ชื่อคอลัมน์" ออกจาก "ข้อมูล" ซึ่งเป็นรอยต่อคนละระดับกับเส้นคั่นแถวต่อแถว
     ถ้าใช้เส้นเดียวกันหมด หัวคอลัมน์จะอ่านเป็นแถวแรกของข้อมูล
     ⚠️ ต่างจาก .table-wrap th ที่ยังใช้ --line ได้ เพราะ th มีพื้น --surface-2 ของตัวเอง
     ขอบเขตมันอ่านออกจากพื้นอยู่แล้ว · .pick-head พื้นโปร่ง จึงเหลือเส้นเป็นตัวบอกอย่างเดียว */
  border-bottom: 1px solid var(--line-strong);
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--muted);
}
.pick-row {
  border: 0;
  border-bottom: 1px solid var(--line);
  background: none;
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-base);
  text-align: left;
  cursor: pointer;
  transition: background var(--dur-1) var(--ease-out);
}
.pick-row:last-child {
  border-bottom: 0;
}
.pick-row:hover {
  background: var(--surface-2);
}
.pick-cell {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-width: 0;
  color: var(--muted);
}
.pick-cell > span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.pick-cell .btn-ico {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
}
/* ช่องแรก = ตัวตนของแถว (ชื่อคน / ชื่อคอร์ส) ต้องเด่นกว่าช่องอื่น */
.pick-main {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.pick-title {
  font-size: var(--fs-md);
  font-weight: 700;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.pick-sub {
  font-size: var(--fs-sm);
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* ลูกศรชี้ขวา = "กดแล้วเข้าไปข้างใน" · ICON.chevron วาดชี้ลง จึงหมุนเอา */
.pick-go {
  display: flex;
  color: var(--muted);
  transform: rotate(-90deg);
}
.pick-go .btn-ico {
  width: 16px;
  height: 16px;
}
.pick-row:hover .pick-go {
  color: var(--ink);
}
/* ค่าที่ยังไม่มี — จางกว่าค่าจริง ไม่งั้น "—" จะอ่านเหมือนข้อมูล */
.pick-cell.is-empty {
  opacity: 0.6;
}
/* ยังไม่กรอกเลขใบเสร็จ = เห็นชื่อแต่เข้าไม่ได้ (Big เลือก "แสดงแต่เป็นสีเทา" 2026-09-17)
   จางทั้งแถว · ไม่มีลูกศร "เข้าไปข้างใน" · ชี้แล้วไม่ไฮไลต์ */
.pick-row.is-locked {
  cursor: not-allowed;
  opacity: 0.55;
}
.pick-row.is-locked:hover {
  background: none;
}
.pick-row.is-locked .pick-go {
  visibility: hidden;
}
.pick-lock b {
  font-weight: 700;
  color: var(--warn);
}
.pick-count {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

/* ---- Private Roster = รายชื่อแบบตาราง (Big เลือก 2026-09-21) ------------
   เดิมเป็นแถบเขียวเต็มจอคนละแถบ ข้อมูลกองซ้ายและไม่ตรงคอลัมน์กัน · ตอนนี้ยืม
   .pick-list ของหน้า Attendance แล้วเพิ่ม "กดแถวเพื่อกางรายละเอียดใต้แถว" */
/* ⚠️ Sessions กว้างขึ้นตั้งแต่ 2026-09-24 — ช่องนี้เป็น "ใช้ไปกี่วัน จากทั้งหมดกี่วัน · กี่ชั่วโมง"
   ไม่ใช่ตัวเลขเดียวแล้ว · Status เป็น max-content เพราะมีป้ายที่สองได้ ("2 TO REBOOK") ห้ามถูกตัด */
.pick-list[data-kind="roster"] {
  --pick-cols: minmax(0, 2.2fr) minmax(0, 1fr) minmax(0, 0.9fr) minmax(0, 1.5fr) max-content auto;
}
/* ชื่อมีลูกศรนำหน้า (16 + ช่องห่าง 8 = --sp-6) — หัว "Student" ต้องเยื้องตาม ไม่งั้นหัวกับชื่อไม่ตรงกัน */
.pick-list[data-kind="roster"] .pick-head > :first-child {
  padding-left: var(--sp-6);
}
.rl-row {
  position: relative;
}
/* ปุ่มชื่อ = ตัวที่กดจริง · ::after ขยายพื้นที่กดคลุมทั้งแถว
   (แถวเป็น div เพราะมีปุ่ม ⋮ อยู่ข้างใน — ปุ่มซ้อนปุ่มใช้ไม่ได้) */
.rl-toggle {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-width: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
/* จอแคบ .pick-main ถูกตั้ง flex: 1 0 100% (ห้ามหด) — ในปุ่มนี้มีลูกศรอยู่ข้างๆ ต้องหดได้ */
.rl-toggle .pick-main {
  flex: 1 1 auto;
}
.rl-toggle::after {
  content: "";
  position: absolute;
  inset: 0;
}
.rl-toggle:focus-visible {
  outline: none;
}
/* กรอบโฟกัสวาดรอบทั้งแถว ไม่ใช่รอบชื่อ — เพราะทั้งแถวคือตัวที่กดได้ */
.rl-toggle:focus-visible::after {
  outline: 2px solid var(--ring);
  outline-offset: -2px;
}
/* ปุ่ม ⋮ ต้องอยู่เหนือพื้นที่กดของแถว ไม่งั้นกด ⋮ แล้วแถวกางแทน */
.rl-tools {
  position: relative;
  z-index: 1;
  display: flex;
}
.rl-row .kebab-btn {
  opacity: 0.55;
}
.rl-row:hover .kebab-btn,
.rl-row.is-open .kebab-btn {
  opacity: 1;
}
/* หุบ = ลูกศรชี้ขวา · กาง = ชี้ลง (หมุนเอา จะได้มี transition เหมือนหัวกล่อง) */
.rl-row .vbox-caret {
  transform: rotate(-90deg);
}
.rl-row.is-open .vbox-caret {
  transform: none;
}
/* 🔴 2026-10-06 — แถวที่กางอยู่ "ไม่ใช่พื้นเขียว" แล้ว (Big สั่ง — "ออกแบบ UI ทั้งหมด
   เล็กๆ น้อยๆ ให้มันดูง่าย ดู Minimal มากขึ้น")
   ⚠️ กลับด้านจากของเดิมที่ให้แถวที่กาง = หัวเขียวของกล่อง · เหตุผลเดิมฟังขึ้น (มันเป็นหัว
   ของกล่องที่กางออกมาจริงๆ) แต่ผลรวมคือ **เขียวในเว็บนี้แปลได้ 3 อย่างพร้อมกัน**:
   PAID/CONFIRMED (สถานะ) · หัวกล่อง (โครงสร้าง) · แถวที่เปิดอยู่ (ตำแหน่ง)
   สีที่แปลได้สามอย่าง = สีที่เลิกแปลอะไรเลย ต้องอ่านข้อความทุกครั้งอยู่ดี
   ตอนนี้ "เปิดอยู่" บอกด้วยแถบด้านซ้าย ซึ่งเป็นสัญญาณตำแหน่ง ไม่ใช่สัญญาณสถานะ
   · ใช้ inset box-shadow ไม่ใช่ border-left เพราะแถวเป็น grid/subgrid — border
     จะดันคอลัมน์ทั้งแถวเลื่อน 3px ส่วน shadow วาดทับโดยไม่กินที่ (กฎเดียวกับ .row-flash) */
.rl-row.is-open,
.rl-row.is-open:hover {
  background: var(--surface-2);
  border-bottom-color: var(--line-strong);
  box-shadow: inset 3px 0 0 var(--accent);
}
/* รายละเอียดที่กางใต้แถว — กรอบเดียวกับ .vbox-body แต่อยู่ในรายชื่อ
   จึงไม่มีกรอบ/มุมโค้งของตัวเอง (รายชื่อมีให้แล้ว) เหลือเส้นคั่นล่างอย่างเดียว */
.pick-list .rl-body {
  grid-column: 1 / -1;
  min-width: 0; /* ตารางคาบกว้างกว่าช่อง ต้องเลื่อนในกรอบของตัวเอง ไม่ใช่ดันรายชื่อ */
  border: 0;
  border-bottom: 1px solid var(--line);
  border-radius: 0;
}
.pick-list .rl-body:last-child {
  border-bottom: 0;
}
/* ชั่วโมงรวมต่อท้ายจำนวนคาบ — ข้อมูลรอง จึงจาง */
.rl-hours {
  color: var(--muted);
}
.rl-status {
  padding: 1px var(--sp-2);
  border: 1px solid currentColor;
  border-radius: var(--r-sm);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
}
.rl-status[data-kind="paid"] {
  color: var(--ok-text);
}
/* ตัวเดียวกับที่ล็อกหน้า Attendance — ต้องเด่นเพราะมันคือ "ยังต้องทำอะไรต่อ" */
.rl-status[data-kind="unpaid"] {
  color: var(--warn);
}
.rl-status[data-kind="done"] {
  color: var(--muted);
}
/* ป้ายที่นั่งของ Group Roster (2026-10-28)
   🔴 **ห้ามใช้ --seat-on กับตัวหนังสือ** — ธีมสว่างมันคือ #d97706 ได้ contrast แค่ 3.19
      บนพื้นการ์ด ซึ่งตก AA (4.5) · ของเดิม .score-full ใช้อยู่ แต่รอดมาได้เพราะมันโผล่
      เฉพาะตอนกล่องย่อ ซึ่ง mkTX ไม่เคยเดินไปถึง · ตอนนี้ป้ายอยู่บนรายชื่อตั้งแต่เปิดหน้า
   · full ใช้ --warn (ตัวเดียวกับ UNPAID) = "รับเพิ่มไม่ได้แล้ว ต้องรู้ก่อนกด Send"
   · over ใช้ --danger = ตัวเลขขัดกันจริง (ป้ายเดียวกับ .seat-warn ในตาราง Student) */
.rl-status[data-kind="full"] {
  color: var(--warn);
}
.rl-status[data-kind="over"] {
  color: var(--danger);
}
.rl-status[data-kind="left"] {
  color: var(--muted);
}

/* ---- Group Roster = รายชื่อคลาสแบบตาราง (Big สั่ง 2026-10-28) ---------
   "UI Roster ที่แสดงข้อมูลห้องให้มีกรอบด้วยแบบ Roster ของ private
    อันนี้แสดงข้อมูลขึ้นมาแบบไม่กรอบไม่มีอะไรเลย"
   ⚠️ หน้านี้เป็นหน้าสุดท้ายที่ยังเป็นแถบ .vbox ลอยบนพื้นหน้า — Private Roster ย้ายมา
      เมื่อ 2026-09-21 และ Planning เมื่อ 2026-09-22 ด้วยเหตุผลเดียวกันเป๊ะ
      (ข้อมูลกองซ้าย · ชิปไม่ตรงคอลัมน์ · ไม่มีอะไรบอกว่านี่คือลิสต์เดียวกัน)
   🔴 Seats เป็น max-content — ป้าย "3 SEATS LEFT" ห้ามถูกตัด (กฎเดียวกับคอลัมน์
      Status ของ Roster/Planning ที่เคยขึ้น "CONFIR…" เมื่อ 2026-09-22) */
.pick-list[data-kind="group-roster"] {
  --pick-cols: minmax(0, 1.9fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 2fr) minmax(0, 1.1fr) max-content auto;
}
/* ชื่อมีลูกศรนำหน้า (16 + ช่องห่าง 8 = --sp-6) — หัว "Class" ต้องเยื้องตาม */
.pick-list[data-kind="group-roster"] .pick-head > :first-child {
  padding-left: var(--sp-6);
}
/* แถวคนติดไฟในคอลัมน์ Students — ของเดิมจาก mini scoreboard แต่เล็กลง
   🔴 จุดเล็กลงจาก 15 → 11px และช่องไฟ 3 → 2px เพราะตอนนี้มันอยู่ใน "คอลัมน์"
      ไม่ใช่แถบเต็มจอ · 12 จุด (เพดานของ store.SEAT_CHOICES) = 154px พอดีคอลัมน์
   ⚠️ จุดอยู่ "หลัง" ตัวเลขเสมอ — คอลัมน์แคบลงแล้วจุดโดนตัดก่อน ตัวเลขไม่หาย */
/* 🔴🔴 ชีตของคลาสไม่มี "ถาดเทา" ของตัวเอง — วัดแล้วมันคือชั้นที่เกินมา
   เทสต์ mkM (เขียนไว้รอบ Minimal 2026-10-28 เช้า) จับได้เองว่าธีมมืดซ้อนพื้นเป็น **4 ชั้น**:
   body < .pick-list < .rl-body < .table-wrap — กฎข้อ 3 บอกว่าห้ามเกิน 2 ชั้นเหนือพื้นหน้า
   · ของเดิมถาด --surface-3 มีไว้ให้การ์ด/ตารางสีขาวลอยขึ้นมาจากพื้นหน้าเปล่าๆ
     **แต่ตอนนี้รายชื่อเป็นการ์ดที่มีพื้น --surface อยู่แล้ว** ถาดจึงกลายเป็นชั้นที่สาม
     ที่ไม่ได้บอกอะไรเพิ่ม — ของข้างในแยกตัวออกจากพื้นด้วยเส้นขอบของมันเองได้
   ⚠️ เล็งเฉพาะ group-roster · Private Roster ยังใช้ถาดเหมือนเดิม เพราะข้างในเป็น
      .logs-box / .total-block ที่พื้นโปร่ง ถ้าถอดถาดออกจะไม่เหลืออะไรคั่นเลย
   · ตรวจด้วยตาทั้งสองธีมแล้ว: การ์ดข้อมูลกับตารางยังอ่านเป็นก้อนแยกจากพื้นรายชื่อ */
.pick-list[data-kind="group-roster"] .rl-body {
  background: transparent;
}
.pick-cell .score-seats {
  gap: 2px;
  flex-shrink: 1;
  min-width: 0;
  overflow: hidden;
}
.pick-cell .score-seats .seat .btn-ico {
  width: 11px;
  height: 11px;
}

/* ---- Planning = รายชื่อแบบตาราง (Big สั่ง 2026-09-22 — "ออกแบบส่วนนี้ใหม่") ----
   ยืม .pick-list + .rl-* ของ Private Roster ทั้งชุด · ต่างแค่คอลัมน์ กับปุ่มหลายตัวในช่องท้าย */
.pick-list[data-kind="planning"] {
  /* Lessons กว้างพอให้ช่วงวันข้ามเดือน ("3 lessons · 30 Sep – 2 Oct") ไม่ถูกตัด */
  /* Status = max-content: ป้ายห้ามถูกตัด — จอ 1440 + เมนูปักหมุด เคยขึ้น "CONFIR…" (เจอ 2026-09-22)
     ⚠️ ห้ามใช้ minmax(min-content, 0.7fr) — ลองแล้วคอลัมน์ fr อื่นทั้งหมดยุบเหลือ 0 ข้อความซ้อนกันทั้งแถว
     (เทสต์ตรวจแนวคอลัมน์ไม่จับ เพราะกว้าง 0 ทุกแถว = "ตรงกัน" · mk20 วัดความกว้างจริงแล้ว) */
  --pick-cols: minmax(0, 1.6fr) minmax(0, 1.7fr) minmax(0, 1fr) minmax(0, 0.9fr) max-content auto;
}
.pick-list[data-kind="planning"] .pick-head > :first-child {
  padding-left: var(--sp-6); /* ตามลูกศรหน้าชื่อ — กฎเดียวกับ Roster */
}
/* กล่องของแต่ละคน/แต่ละคลาสห่อแถว + ตารางไว้ด้วยกัน แต่ไม่มีกล่องของตัวเอง
   ลูกข้างในจึงยังเป็นแถวของ grid (subgrid ต้องการแบบนี้) · [hidden] ยังชนะ (.vbox[hidden] เฉพาะกว่า)
   ⚠️ 2026-10-28 — Group Roster (.group-item) ใช้กฎชุดเดียวกันแล้ว · เขียนด้วย :is()
      ไม่ใช่ก๊อปสี่ชุด — "ความจริงเดียวห้ามอยู่หลายที่" */
.pick-list > :is(.plan-item, .group-item) {
  display: contents;
}
/* ⚠️ แถวแต่ละแถวอยู่ในกล่องของมันเอง — `.pick-row:last-child` / `.rl-body:last-child` ของ Roster
   จึงโดนทุกแถว (แถวเป็นลูกคนสุดท้ายของกล่องที่พับอยู่) เส้นคั่นหายหมด · คืนเส้นให้ แล้วเอาออกเฉพาะท้ายรายชื่อ */
.pick-list > :is(.plan-item, .group-item) > .pick-row,
.pick-list > :is(.plan-item, .group-item) > .rl-body {
  border-bottom: 1px solid var(--line);
}
/* ⚠️ กฎนี้เฉพาะเจาะจงกว่า .rl-row.is-open จึงชนะมัน — รอบแรกของ Minimal pass
   เปลี่ยนแค่ตัวทั่วไป แถวที่กางในหน้า Planning เลยยังมีเส้นล่างสีเขียวค้างอยู่
   **ของที่เขียนทับกันไว้ ต้องตามไปแก้ให้ครบทุกชั้นเสมอ** */
.pick-list > :is(.plan-item, .group-item) > .rl-row.is-open {
  border-bottom-color: var(--line-strong);
}
.pick-list > :is(.plan-item, .group-item):last-child > :last-child {
  border-bottom: 0;
}
/* ช่วงวันต่อท้ายจำนวนคาบ — ข้อมูลรอง */
.plan-span {
  color: var(--muted);
}
/* ช่องท้ายมีหลายปุ่ม (⋮ · Copy · Confirm) — ชิดขวา เว้นระยะ · ปุ่มบางลงให้แถวไม่หนากว่าแถว Roster */
.pick-list[data-kind="planning"] .rl-tools {
  align-items: center;
  justify-content: flex-end;
  gap: var(--sp-2);
}
.pick-list[data-kind="planning"] .rl-tools .google-btn {
  padding-block: var(--sp-1);
}

/* ---- "Waiting for planning" = รายชื่อคนที่ยังไม่มีแผน (Big สั่ง 2026-10-29) ----
   ⚠️ เดิมเป็นชิปชื่อ (.plan-chips) ซึ่งถูกลบทิ้งแล้ว · คอลัมน์ตอบคำถามว่า
      "ควรเริ่มที่ใครก่อน": ระดับที่อยากเรียน · วันที่เข้าคิว · Group/Private */
.pick-list[data-kind="plan-rest"] {
  --pick-cols: minmax(0, 1.6fr) minmax(0, 1fr) minmax(0, 1.2fr) minmax(0, 0.8fr) auto;
}
/* 🔴 2026-10-29 (รอบสอง) — ย้ายขึ้นบนสุดแล้ว ระยะจึงกลับด้าน (Big สั่ง)
   .sheet-section ให้ margin-top มา ซึ่งถูกตอนมันต่อท้ายของอื่น · พอเป็นชิ้นแรก
   ของหน้า มันกลายเป็นช่องว่างใต้ปุ่มของหน้าโดยไม่มีอะไรมาคั่น
   ⚠️ ระยะ "ระหว่างสองก้อน" มาจาก margin-top ของก้อนล่างเอง จึงห้ามล้างแบบไม่มี
      :first-child · เคยเขียน margin-bottom ให้ .plan-rest ด้วย แล้ว negative check
      พิสูจน์ว่าไม่มีผล (margin ของพี่น้องยุบรวมกัน) จึงถอดทิ้ง */
.plan-rest:first-child,
.plan-made:first-child {
  margin-top: 0;
}
.pick-list[data-kind="plan-rest"] .pick-head > :first-child {
  padding-left: var(--sp-6); /* ตามไอคอน + หน้าชื่อ — กฎเดียวกับรายชื่อที่มีลูกศร */
}
.pick-list[data-kind="plan-rest"] .rl-tools {
  align-items: center;
  justify-content: flex-end;
}
.pick-list[data-kind="plan-rest"] .rl-tools .google-btn {
  padding-block: var(--sp-1);
}
/* ไอคอน + หน้าชื่อ — ที่เดียวกับลูกศรของรายชื่อที่กางได้ แต่ **ห้ามหมุน**
   (.rl-row .vbox-caret หมุน -90deg ซึ่งจะทำให้เครื่องหมายบวกกลายเป็นกากบาท) */
.plan-add {
  display: flex;
  color: var(--muted);
}
.plan-add .btn-ico {
  width: 16px; /* .btn-ico ไม่มีขนาดในตัวเอง — ทุกที่ที่ใช้ต้องกำหนดเอง */
  height: 16px;
}
.pick-row:hover .plan-add {
  color: var(--ink);
}

/* จอแคบ: ซ่อนหัวคอลัมน์ แล้วแต่ละแถวเรียงข้อมูลพันลงมาเป็นการ์ด */
@media (max-width: 720px) {
  .pick-list {
    display: block;
  }
  .pick-head {
    display: none;
  }
  .pick-row {
    display: flex;
    flex-wrap: wrap;
    width: 100%;
    gap: var(--sp-1) var(--sp-4);
  }
  .pick-main {
    flex: 1 0 100%;
  }
  .rl-toggle {
    flex: 1 0 100%;
  }
  .pick-list[data-kind="roster"] .pick-head > :first-child,
  .pick-list[data-kind="planning"] .pick-head > :first-child,
  .pick-list[data-kind="plan-rest"] .pick-head > :first-child,
  .pick-list[data-kind="group-roster"] .pick-head > :first-child {
    padding-left: 0;
  }
  .pick-go {
    display: none;
  }
}

/* ---- ปุ่มกลับไปที่รายชื่อ --------------------------------------------- */
.back-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
}
.back-btn .btn-ico {
  width: 15px;
  height: 15px;
  transform: rotate(90deg);
}

/* ---- ตารางเช็คชื่อ Private ------------------------------------------ */
.table-wrap table[data-list="plog"] {
  /* 🔴 2026-10-28 — Course +40 · Teacher −20 · Lesson Progress ขั้นต่ำ 148 → 128 = **รวมเท่าเดิม**
     Lesson Progress เป็น textarea ที่พันบรรทัดได้ ข้อความจึงไม่หาย แค่สูงขึ้น
     ⚠️ ต่ำกว่า ~114px หัวคอลัมน์ "Lesson Progress" เองจะถูกตัด — 128 คือพื้นที่เหลือจริง ห้ามลดอีก
     (ของเดิม) ผลรวมคอลัมน์ตายตัว 944 (Day 60→52) + Present 76 + Signature 104 = 1124 + Lesson Progress อย่างน้อย 148
     (เดิม 240 — ลดตอนเพิ่มสองคอลัมน์เช็คชื่อ 2026-09-22 · Lesson Progress เป็น textarea ยืดลงล่างได้ ข้อความยาวไม่หาย แค่พันบรรทัด)
     ⚠️ 2026-09-23 — ที่จริงมีให้ 1263px ที่จอ 1440 เมนูหด (หักขอบหน้า 48 + กรอบ .vbox-body 32 + เส้นขอบ) ไม่ใช่ 1280
     ของเดิม 1272 จึงล้น 7px มาตั้งแต่แรกโดยไม่มีใครวัด · หั่น Present 76→68 · Signature 104→100 แล้วลงตัว (mk29 วัดจริง) */
  min-width: 1260px;
}
.table-wrap table[data-list="plog"] .col-chk { width: 68px; }
.table-wrap table[data-list="plog"] .col-sig { width: 100px; }
.table-wrap table[data-list="plog"] .col-date { width: 148px; }
.table-wrap table[data-list="plog"] .col-day { width: 52px; }
/* 🔴 2026-10-28 — 132 → 112px · ชื่อครูยาวสุดในลิสต์คือ "Jonathan" (8 ตัว)
   ที่ว่าง 20px ที่คืนมาเอาไปให้ Course ซึ่งต้องการจริง (ดูบรรทัดถัดไป) */
.table-wrap table[data-list="plog"] .col-who { width: 112px; }
/* 🔴 2026-10-28 — 128 → 168px · คอร์สครบ 3 ภาษาแล้ว "Upper-intermediate" ต้องการ 121px
   ⚠️ **ตารางนี้ไม่มีที่ว่างเหลือเลย** (min-width 1260 บนที่จริง 1263 ที่จอ 1440 เมนูหด)
      40px ที่ขอเพิ่มจึงต้องไปเอาจากคอลัมน์อื่น ไม่ใช่ขยายตาราง:
      Teacher −20 · Lesson Progress ขั้นต่ำ 148 → 128 (−20) → **min-width เท่าเดิม 1260** */
.table-wrap table[data-list="plog"] .col-course { width: 168px; }
.table-wrap table[data-list="plog"] .col-book { width: 84px; }
.table-wrap table[data-list="plog"] .col-chapter { width: 84px; }
.table-wrap table[data-list="plog"] .col-sess { width: 128px; } /* ช่วงเวลาจากแผน "18:00-20:00" */
.table-wrap table[data-list="plog"] .col-mode { width: 148px; } /* หัว "Online / In-person" */
/* Day คำนวณจากวันที่ ไม่ได้เป็นช่องกรอก — หน้าตาต้องไม่เหมือน input */
.day-cell {
  display: block;
  padding-inline: var(--sp-2);
  font-weight: 600;
  color: var(--muted);
}
/* ⚠️ ช่อง Lesson Progress ในชีตเป็นตัวหนังสือสีชมพู แต่ที่นี่ไม่ได้ลงสีให้
   โดยตั้งใจ — สีเขียว (--ok-text) มีความหมายแล้วว่า "คาบฟรี" ในหน้า Planning
   ใช้ซ้ำตรงนี้แปลว่าสีเดียวกันพูดสองเรื่อง */
/* หัวคอลัมน์ Lesson Progress มีบรรทัดรองอธิบายว่าต้องเขียนอะไร (ตามชีต) */
.th-sub {
  display: block;
  /* th ทั้งตารางเป็น nowrap — บรรทัดรองต้องพันได้ ไม่งั้นคอลัมน์ที่แคบลง (2026-09-22 เพิ่ม Present/Signature) ตัดมันทิ้ง */
  white-space: normal;
  font-size: var(--fs-xs);
  font-weight: 500;
  color: var(--muted);
}

/* ---- ตารางเช็คชื่อ Group -------------------------------------------- */
/* จำนวนคอลัมน์นักเรียนไม่ตายตัว · JS ส่งแค่ "จำนวนคน" มาใน --stu-n
   ความกว้างจริงคำนวณที่นี่ จะได้ไม่มีตัวเลขพิกเซลอยู่ในโค้ด JS */
.table-wrap table[data-list="grid"] {
  --att-stu: 108px;
  --att-fixed: 780px;
  min-width: calc(var(--att-fixed) + var(--att-stu) * var(--stu-n, 0));
}
.table-wrap table[data-list="grid"] .col-sdate { width: 196px; }
.table-wrap table[data-list="grid"] .col-remarks { width: 160px; }
.table-wrap table[data-list="grid"] .col-stu { width: var(--att-stu); }
.table-wrap table[data-list="grid"] .col-chapter { width: 84px; }
/* หัวของนักเรียนแต่ละคน: เลขลำดับข้างบน ชื่อข้างล่าง (ตามชีต)
   ชื่อยาวให้ตัดบรรทัดได้ — คอลัมน์แคบ แต่ชื่อต้องอ่านได้ครบ */
/* ⚠️ ต้องขึ้นต้นด้วย .table-wrap th — ตัวกฎ ".table-wrap th { white-space: nowrap }"
   เฉพาะเจาะจงกว่า ".stu-th" เฉยๆ แล้วชื่อยาวจะล้นออกนอกคอลัมน์ (เทสต์จับได้ 2026-09-17) */
.table-wrap th.stu-th {
  text-align: center;
  white-space: normal;
  vertical-align: bottom;
}
.stu-no {
  display: block;
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--muted);
}
.stu-name {
  display: block;
  font-weight: 600;
  line-height: 1.25;
  overflow-wrap: anywhere;
}
/* วันที่ของคาบ = ชื่อวันสั้นๆ นำหน้า input · ชีตเขียน "Tue, 7/Jul/2026" */
.sdate {
  display: flex;
  align-items: center;
}
.sdate .day-cell {
  width: 44px;
  flex-shrink: 0;
  padding-inline: var(--sp-2) 0;
}
.sdate .cell-input {
  min-width: 0;
}
/* แถวรวมท้ายตาราง — กี่คาบที่แต่ละคนมาเรียน */
.present-count {
  font-variant-numeric: tabular-nums;
}
/* แถบท้ายชีต "Continue class will be taught by · Standby teacher" */
.att-foot {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-6);
  margin-top: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  font-size: var(--fs-base);
}
.att-foot b {
  color: var(--ink);
}
.att-foot span {
  color: var(--muted);
}
/* บรรทัดอธิบายในกรอบ (เช่น ห้องยังไม่มีนักเรียน) */
.vbox-note {
  margin: 0 0 var(--sp-3);
  font-size: var(--fs-base);
  color: var(--muted);
}

/* ---- ตัวเลือกช่วงเวลา (ช่อง TH-Time) -------------------------------
   Big สั่ง 2026-09-17 — "ไม่ต้องพิมพ์เอง อยากได้เป็น Picker"
   ช่องเป็นปุ่มที่หน้าตาเหมือนช่องกรอก (.cell-input) · แผงยืมกรอบของ .combo-panel */
/* ⚠️ ".combo .cell-input" กันที่ขวา 24px ไว้ให้ลูกศร — ช่องนี้ไม่มีลูกศร
   ถ้าไม่คืนที่ "18:00-20:00" จะถูกตัดเป็น "18:00-…" (เห็นจากรูป 2026-09-17) */
.combo .time-btn {
  padding-right: var(--sp-2);
}
.time-btn {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  width: 100%;
  text-align: left;
  cursor: pointer;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.time-btn .btn-ico {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  color: var(--muted);
}
/* ยังไม่ได้เลือก = จางแบบ placeholder ไม่ใช่ดูเหมือนมีค่าแล้ว */
.time-btn.is-empty .time-label {
  color: var(--muted);
}
.time-label {
  overflow: hidden;
  text-overflow: ellipsis;
}
/* แผงสองคอลัมน์ · ตัวแผงเองไม่เลื่อน ให้แต่ละคอลัมน์เลื่อนของตัวเอง */
.combo-panel.time-panel {
  max-height: none;
  overflow: visible;
  padding: var(--sp-2);
  width: 248px;
}
.time-heads,
.time-cols {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-2);
}
.time-heads {
  padding: 0 var(--sp-1) var(--sp-1);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}
/* 🔴 ซ่อนแถบเลื่อนของคอลัมน์ (Big แจ้ง 2026-09-17 "UI บัค")
   แม้จะเป็น scrollbar-width: thin แต่ Chrome บน Windows ยังวาดแถบพร้อมลูกศร ▲▼
   ซึ่งกินที่คอลัมน์ที่แคบอยู่แล้ว จน "1h 30m" ตกบรรทัด
   ⚠️ รูปเช็คงานเองไม่เห็นปัญหานี้ เพราะถ่ายด้วย --hide-scrollbars
   ยังเลื่อนด้วยล้อเมาส์/ทัชแพดได้ตามปกติ · ขอบบน-ล่างจางลง (mask) บอกว่ามีต่อ
   mask อ่านแค่ความทึบ สีที่ใส่ไม่มีผล จึงใช้ token สีอะไรก็ได้ที่ทึบ */
.time-col {
  display: flex;
  flex-direction: column;
  gap: 2px;
  /* 🔴 สูงตามของข้างใน ไม่ล็อก 216px แล้ว (2026-09-22 — ช่วงเหลือ 18:00–21:00 = 6 ตัว ล็อกไว้แล้วมีที่ว่าง
     ครึ่งแผง) · max-height ยังอยู่ เผื่อวันหลังช่วงกว้างขึ้นก็กลับไปเลื่อนได้เหมือนเดิม
     · padding = --sp-4 เท่ากับระยะที่ mask จางหาย ตัวแรก/ตัวสุดท้ายจะได้ไม่ถูกจางไปครึ่งตัว */
  max-height: 216px;
  overflow-y: auto;
  padding-block: var(--sp-4);
  scrollbar-width: none;
  -webkit-mask-image: linear-gradient(to bottom, transparent, var(--ink) var(--sp-4), var(--ink) calc(100% - var(--sp-4)), transparent);
  mask-image: linear-gradient(to bottom, transparent, var(--ink) var(--sp-4), var(--ink) calc(100% - var(--sp-4)), transparent);
}
.time-col::-webkit-scrollbar {
  display: none;
}
.time-opt {
  flex-shrink: 0;
  white-space: nowrap; /* "1h 30m" ต้องอยู่บรรทัดเดียวเสมอ */
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-1);
  padding: var(--sp-1) var(--sp-2);
  border: 0;
  border-radius: var(--r-sm);
  background: none;
  font: inherit;
  font-size: var(--fs-base);
  font-variant-numeric: tabular-nums;
  color: var(--ink-2);
  text-align: left;
  cursor: pointer;
}
.time-opt:hover {
  background: var(--surface-3);
}
.time-opt.is-on,
.time-opt.is-on:hover {
  background: var(--accent);
  color: var(--accent-ink);
}
/* "2h" ข้างเวลาจบ — ตอบคำถาม "คาบนี้ยาวเท่าไร" โดยไม่ต้องลบเลขในหัว */
.time-sub {
  white-space: nowrap;
  font-size: var(--fs-xs);
  color: var(--muted);
}
.time-opt.is-on .time-sub {
  color: inherit;
}
.time-wait {
  padding: var(--sp-2);
  font-size: var(--fs-sm);
  color: var(--muted);
}
.time-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  margin-top: var(--sp-2);
  padding-top: var(--sp-2);
  border-top: 1px solid var(--line);
}
.time-hint {
  font-size: var(--fs-xs);
  color: var(--muted);
}
.time-clear {
  flex-shrink: 0;
  padding: var(--sp-1) var(--sp-2);
  border: 0;
  border-radius: var(--r-sm);
  background: none;
  font: inherit;
  font-size: var(--fs-sm);
  color: var(--muted);
  cursor: pointer;
}
.time-clear:hover {
  background: var(--surface-3);
  color: var(--ink);
}

/* ---- แถบเลือกภาษา (ui.langChips) · ของใหม่ 2026-10-28 ---------------
   Big สั่ง — "เพิ่ม Toggle ที่เลือกดูนักเรียนเฉพาะภาษานั้นๆ เหมือน CLASSPLANNER"
   🔴 ใช้คู่สี --thai-* / --eng-* / --chi-* ที่มีอยู่แล้ว · tokens.css เขียนกำกับไว้เองว่า
      สามชุดนี้แปลว่า "ของทีมภาษาไหน" — นี่คือเรื่องของมันตรงๆ ไม่ต้องตั้ง token ใหม่
   ⚠️ ไม่มีสีเขียวในชุดนี้เลย — เขียวสงวนไว้ให้ "สถานะ/การกระทำ" (กฎข้อ 1 รอบ Minimal)
   ⚠️ "All" ที่เลือกอยู่ใช้ --accent/--accent-ink ชุดเดียวกับแถวเมนูซ้ายที่เปิดอยู่
      และปุ่มที่เลือกใน radioCell — "อันนี้ถูกเลือก" พูดภาษาเดียวกันทั้งเว็บ */
.lang-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1);
}
.lang-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--line);
  border-radius: var(--r-full);
  background: var(--surface);
  color: var(--muted);
  font-size: var(--fs-sm);
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
}
.lang-chip:hover {
  border-color: var(--line-strong);
  color: var(--ink);
}
.lang-chip:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 1px;
}
/* ตัวเลขบอกจำนวนของภาษานั้น — วงกลมจางๆ ไม่ใช่เลขลอย
   (กับดักเดิมของ .logs-n: เลขลอยอ่านว่า "อันที่ 3" ไม่ใช่ "มี 3 อัน") */
.lang-chip-n {
  min-width: 18px; /* เท่า .logs-n — ป้ายตัวเลขของเว็บนี้กว้างเท่ากันหมด */
  padding: 0 var(--sp-1);
  border-radius: var(--r-full);
  background: var(--surface-3);
  color: inherit;
  font-size: var(--fs-xs);
  text-align: center;
  font-variant-numeric: tabular-nums; /* เลขไม่ขยับตอนนับเปลี่ยน */
}
/* ศูนย์คนยังกดได้ — แค่จางลงให้รู้ว่าไม่มีอะไรรออยู่ */
.lang-chip.is-none:not(.on) {
  opacity: 0.55;
}
.lang-chip.on {
  border-color: var(--line-strong);
}
.lang-chip.on.c-all {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}
.lang-chip.on.c-all .lang-chip-n {
  background: color-mix(in srgb, var(--accent-ink) 18%, transparent);
}
.lang-chip.on.c-thai {
  background: var(--thai-bg);
  border-color: var(--thai-ring);
  color: var(--thai-title);
}
.lang-chip.on.c-eng {
  background: var(--eng-bg);
  border-color: var(--eng-ring);
  color: var(--eng-title);
}
.lang-chip.on.c-chi {
  background: var(--chi-bg);
  border-color: var(--chi-ring);
  color: var(--chi-title);
}
.lang-chip.on.c-thai .lang-chip-n,
.lang-chip.on.c-eng .lang-chip-n,
.lang-chip.on.c-chi .lang-chip-n {
  background: color-mix(in srgb, currentColor 14%, transparent);
}

/* ---- ช่องค้นหา (ui.searchBox) ---------------------------------------
   Big สั่ง 2026-09-17 ที่หน้า Planning — "อยากได้ปุ่ม Search เอาไว้หาข้อมูลนักเรียน"
   ทั้งกล่องเป็น <label> — กดตรงไอคอนก็โฟกัสช่องพิมพ์ */
.search-box {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  width: min(100%, 320px);
  padding: 0 var(--sp-3);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  color: var(--muted);
  cursor: text;
}
.search-box:focus-within {
  outline: 2px solid var(--ring);
  outline-offset: 1px;
}
.search-ico {
  display: flex;
}
.search-ico .btn-ico {
  width: 16px;
  height: 16px;
}
.search-input {
  flex: 1;
  min-width: 0;
  padding: var(--sp-2) 0;
  border: 0;
  background: none;
  color: var(--ink);
  font: inherit;
  outline: none; /* กรอบโฟกัสวาดที่ .search-box ทั้งกล่องแทน */
}
.search-input::placeholder {
  color: var(--muted);
}
/* ⚠️ ของที่ถูกกรองออกใช้ [hidden] — แต่ .google-btn / .card ตั้ง display เอง
   ซึ่งเป็น author rule ที่ชนะ [hidden] ของเบราว์เซอร์ (กับดักเดียวกับ .row-menu)
   ต้องเขียนออกมาตรงๆ ไม่งั้นชิปที่ควรหายยังโชว์อยู่ */
.vbox[hidden],
.plan-rest[hidden],
.plan-made[hidden],
.plan-nomatch[hidden],
.roster-nomatch[hidden],
.pick-row[hidden],
.rl-body[hidden],
.pick-list[hidden] {
  display: none;
}

/* ---- Roster: ตารางคาบจากแผนที่ Confirm แล้ว (2026-09-21) ------------
   อยู่ใต้ใบเสร็จทุกใบ — คาบไม่ได้แยกตามใบเสร็จแล้ว (แผนเดียว · จ่ายหลายใบ) */
.plan-lessons {
  margin-top: var(--sp-6);
}
/* แถบหัวใช้ .rcp-bar ร่วมกับใบเสร็จ แต่ไม่มีช่องกรอก — ป้ายต้องไม่ตัวพิมพ์ใหญ่ยาวทั้งประโยค */
.plan-lessons-bar .rcp-label {
  text-transform: none;
  letter-spacing: 0;
  font-size: var(--fs-sm);
}
/* 🔴 2026-09-22 — 1 ใบเสร็จ = 1 คอร์ส: ตารางคาบอยู่ "ใน" ใบเสร็จของมัน ชิดแถบใบเสร็จกว่าระยะระหว่างคอร์ส
   (ใบเสร็จคั่นกันด้วยเส้น + --sp-6 อยู่แล้ว) — อ่านเป็นก้อนเดียวกัน ไม่ใช่สองกล่องลอยต่อกัน */
.receipt > .plan-lessons {
  margin-top: var(--sp-3);
}
/* 🔴 กรอบคอร์สใน Attendance พับได้ (Big สั่ง 2026-09-23 — "ให้ Course พับเข้าไปได้ เพื่อประหยัดพื้นที่
   เมื่อต้องการดูแค่อันเดียว") · ชื่อบนแถบเป็นปุ่ม แต่ต้องอ่านเหมือนข้อความเดิม สัญญาณว่ากดได้คือลูกศรกับ cursor */
.rcp-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-width: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.rcp-toggle .vbox-caret {
  color: var(--muted);
}
/* พับอยู่ = ลูกศรชี้ขวา (หมุนเอา ไม่สลับรูป จะได้มี transition เหมือนกล่องอื่นทั้งเว็บ) */
.att-course.is-shut .vbox-caret {
  transform: rotate(-90deg);
}
/* พับอยู่ = ไม่มีอะไรต่อข้างล่างแถบแล้ว จึงไม่ต้องเว้นที่ให้ */
.att-course.is-shut .rcp-bar {
  margin-bottom: 0;
}
/* ⚠️ ตารางในกรอบคอร์สของ Attendance กินเต็มความกว้างกรอบ — padding ซ้าย-ขวาของกรอบต้องไม่กินที่ของตาราง
   (plog กว้าง 1272px ซึ่งพอดีจอ 1440 อยู่แล้ว · ถ้าปล่อยให้กรอบกินอีก 24px ต้องเลื่อนขวาไปอ่าน Online / In-person) */
.att-course > .table-wrap {
  /* ⚠️ ต้องเท่ากับ padding ของ .att-course เสมอ (ตอนนี้ --sp-4) */
  margin-inline: calc(var(--sp-4) * -1);
}
/* 🔴🔴 2026-10-07 — ตารางใน "กรอบคอร์ส" เท่านั้นที่ถอดกรอบออก (Big สั่ง — "เอากรอบนอก
   และมุมโค้งออก เหลือแค่เส้นคั่นแถวบางๆ หัวคอลัมน์ใช้ตัวเล็กสีจาง ไม่มีพื้นหลังแยก")
   ⚠️⚠️ **ห้ามย้ายกฎนี้ไปไว้ที่ `.table-wrap` เฉยๆ** — คลาสนั้นใช้ร่วมกับตารางทั้งเว็บ
   (Waiting List · Student · Contact List · Class Schedule · Total · Attendance · Planning)
   ตารางพวกนั้น**ไม่ได้อยู่ในกรอบคอร์ส** กรอบของมันคือสิ่งเดียวที่บอกขอบเขต ถอดแล้วจะลอย
   ⚠️ `th` เป็น `position: sticky` อยู่ — พื้นโปร่งใช้ได้เพราะตารางคอร์สไม่มีการเลื่อนแนวตั้ง
   (ตัวที่เลื่อนแนวตั้งคือ Class Schedule ซึ่งไม่ใช่ตารางคอร์ส) · วันไหนมีใครใส่ `capRows`
   ให้ตารางคอร์ส ต้องคืนพื้นให้ `th` ด้วย ไม่งั้นแถวจะไหลทะลุหัวคอลัมน์ */
.receipt .table-wrap,
.att-course > .table-wrap,
.plan-course .table-wrap {
  background: transparent;
  border: 0;
  border-radius: 0;
}
/* ⚠️ 2026-10-28 — `background: transparent` กับ `border-bottom: --line-strong` ถูกย้าย
   ไปเป็นค่าเริ่มต้นของ .table-wrap th ทั้งเว็บแล้ว (Minimal รอบสอง) · เหลือไว้ตรงนี้
   เฉพาะสิ่งที่การ์ดคอร์ส **ต่างจริง**: ตัวเล็กกว่าและจางกว่า เพราะตารางอยู่ในกรอบแคบ
   🔴 ห้ามเอา font-size ตรงนี้ไปใส่ที่ .table-wrap th ทั้งเว็บ — ความกว้างคอลัมน์ทุกตาราง
      ถูกวัดด้วยหัวขนาด --fs-base ไว้แล้ว (min-width 1260 / 1610 / 920 ฯลฯ) */
.receipt .table-wrap th,
.att-course > .table-wrap th,
.plan-course .table-wrap th {
  font-size: var(--fs-xs);
  color: var(--muted);
}
/* 🔴 หัวการ์ดคอร์ส: ซ้าย = Course N + เลขใบเสร็จ · ขวา = สรุป → ปุ่ม → ⋮
   (Big สั่ง 2026-10-07 — "รวมแถบ Receipt กับแถบ Lessons เป็นหัวการ์ดแถวเดียว") */
.course-title {
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--ink);
  text-transform: none;
  letter-spacing: 0;
  white-space: nowrap;
}
/* สรุปของคอร์ส "5 lessons · 10h" — ดันไปชิดขวาพร้อมปุ่มกับ ⋮
   ⚠️ เดิมเขียนว่า "10h HOURS 5 USED" ซึ่งพูดคำว่าชั่วโมงสองรอบ และ "USED" ลอยๆ
   ไม่ได้บอกว่าใช้อะไรไป · ตอนนี้หน่วยอยู่ติดเลขของมันเอง */
.course-sum {
  margin-left: auto;
  font-size: var(--fs-sm);
  color: var(--muted);
  white-space: nowrap;
}
.course-sum b {
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
/* 🔴 ข้อความที่กดแล้วแก้ได้ (ui.inlineText) — ต้องอ่านเป็น "ค่า" ไม่ใช่ "ช่องว่างรอกรอก"
   แต่ก็ต้องมีสัญญาณว่ากดได้ ซึ่งคือพื้นตอนชี้เมาส์ ไม่ใช่กรอบถาวร */
.inline-edit {
  display: inline-flex;
  min-width: 0;
}
.inline-edit .cell-input {
  width: 160px;
  border-color: var(--line-strong);
  padding: var(--sp-1) var(--sp-2);
}
.inline-edit-btn {
  max-width: 220px;
  overflow: hidden;
  padding: var(--sp-1) var(--sp-2);
  font: inherit;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  text-align: left;
  text-overflow: ellipsis;
  white-space: nowrap;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  cursor: text;
}
.inline-edit-btn:hover {
  background: var(--surface-3);
  border-color: var(--line);
}
/* ยังไม่ได้กรอก = จางลงให้อ่านเป็น placeholder ไม่ใช่เลขจริง */
.inline-edit-btn.is-empty {
  color: var(--muted);
}
/* คำอธิบายเหนือตาราง — เดิมเป็น "แถบ" ที่มีไอคอนกับกรอบของตัวเอง
   (Big สั่ง — "ลดเป็น caption ตัวเล็กสีจางเหนือตาราง ไม่มีกรอบ ไม่มีไอคอน") */
.course-cap {
  margin-bottom: var(--sp-2);
  font-size: var(--fs-xs);
  color: var(--muted);
}
/* ใบเสร็จที่มีหลายตาราง — สรุปกับปุ่มของตารางนั้นอยู่ข้าง caption ของมันเอง
   (ยกขึ้นหัวการ์ดไม่ได้ เพราะหัวมีใบเดียวแต่ตารางมีหลายตัว) */
.course-cap-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
}
/* ป้ายหัวกรอบคอร์ส — มีเฉพาะคนที่มีหลายใบเสร็จ (ใบเดียวไม่ต้องมี เลขคอร์สที่มีอยู่ค่าเดียวไม่ได้บอกอะไร) */
.rcp-course {
  margin-bottom: var(--sp-2);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
/* คำอธิบายรองในแถบ (Pending / ยังไม่มีตาราง) */
.rcp-sub {
  font-size: var(--fs-sm);
  color: var(--muted);
}
/* 🔴 Planning: คนที่มีหลายคอร์ส = กรอบ Course 1, Course 2 … ในแถวเดียวของคนนั้น (Big สั่ง 2026-09-22 —
   "ให้อยู่ในชื่อเดียวกันของตัวเอง แค่จะเป็นกรอบตารางเรียนใหม่")
   ⚠️ 2026-09-23 — เดิมคั่นด้วยเส้นบน + ระยะห่างเท่านั้น · Big สั่ง "แก้ไขตรงนี้ให้เป็นแบบ Roster ที่เป็นกรอบแบ่ง
   แต่ละใบเสร็จและตารางเรียน" → ใช้กรอบชุดเดียวกับ .receipt / .att-course (ดูกฎรวมข้างบน) */
.plan-course-table {
  margin-top: var(--sp-3);
}
/* 🔴 2026-10-06 — ตาราง Planning **เลิก** กินเต็มกรอบแล้ว (Big สั่ง — "ปุ่มมันชิดขอบไป
   ทำให้มันมีช่องว่างแบบ Blank หน่อย") · ⚠️ กลับด้านจากกฎเดิมที่ให้มันเสมอ .att-course
   เหตุผลที่ต่างกันได้: **.att-course ต้องการทุกพิกเซลจริง** (ตารางเช็คชื่อ min-width 1260px
   บนที่ว่างจริงราว 1263px) ส่วนตาราง Planning min-width แค่ **1014px** บนกรอบกว้าง ~1269px
   = เหลือที่ว่างกว่า 250px การดึงเต็มกรอบจึงไม่ได้ซื้ออะไรเลย แลกมาด้วยตารางที่ชนขอบ
   ขณะที่แถบหัวกับปุ่มเว้น 16px — สองระยะในกรอบเดียวกันคือสิ่งที่อ่านว่า "ชิดขอบ"
   🔴 **ดึงเต็มกรอบเมื่อ "ไม่มีที่พอ" เท่านั้น ไม่ใช่เพราะกรอบข้างๆ ทำ** */
.plan-course > .plan-course-table {
  margin-inline: 0;
}
/* ---- ท้ายตารางแผน: + Add lesson (ซ้าย) · Save (ขวา) --------------------
   🔴 2026-10-29 — Big สั่ง "เวลาสร้างตารางเรียนอยากให้มีปุ่ม Save ด้วย และให้ถามว่า
   ตรวจสอบความถูกต้องด้วยรายละเอียดถูกต้อง"
   ⚠️ .addrow-btn มี margin-top ของตัวเองอยู่ — ระยะย้ายมาอยู่ที่แถว ไม่งั้นซ้อนกันสองชั้น */
.plan-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-top: var(--sp-2);
}
.plan-foot .addrow-btn {
  margin-top: 0;
}
.plan-save-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
}
/* ⚠️ .btn-ico ไม่มีขนาดในตัวเอง — ทุกที่ที่ใช้ไอคอนต้องกำหนดเอง (กับดักเดิมใน CLAUDE.md) */
.plan-save-btn .btn-ico {
  width: 16px;
  height: 16px;
}
/* ตรวจแล้ว = ป้ายเงียบๆ ไม่มีอะไรต้องทำต่อ · 🔴 **ไม่ย้อมเขียว** — เขียวทั้งเว็บมี 4 ที่
   และต้องมีแค่นั้น (กฎข้อ 1 ของรอบ Minimal) · hover ยังตอบกลับ = ยังกดอ่านทวนซ้ำได้ */
.plan-save-btn.is-checked {
  background: transparent;
  border-color: var(--line);
  color: var(--muted);
  font-weight: 500;
}
.plan-course-bar .plan-course-name {
  font-size: var(--fs-sm);
  color: var(--ink);
}
.plan-course-bar .rl-tools {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}
/* ปุ่ม "New course" บนหัวหน้า Planning — ไอคอน + ข้อความ (.btn-ico ไม่มีขนาดในตัวเอง) */
.new-course-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
}
.new-course-btn .btn-ico {
  width: 15px;
  height: 15px;
}
/* ช่องติ๊ก Online ที่มาจากแผน — อ่านอย่างเดียว ไม่ใช่ปุ่มที่กดไม่ติด */
.plan-lessons .cell-check:disabled,
.total-block .cell-check:disabled {
  cursor: not-allowed;
  opacity: 0.6;
}

/* ══════════════════════════════════════════════════════════════════
   Private Roster — Total (ตัวหลัก) + Purchase log (พับได้)
   🔴 Big สั่ง 2026-09-24 — "อยากให้รวมวันที่เขาซื้อทุกบิลมารวมกันเป็น Total เลย ส่วนวันที่เขาซื้อ
   ในแต่ละครั้งก็เก็บแบบนี้แหละ เอาไว้เป็น Logs ... แต่อยากให้ส่วนนี้สามารถย่อได้ เพราะไม่ใช่ตัวหลัก
   ในการดู ... ทั้งหมดนี้ให้ออกแบบอยู่ในรายชื่อ 1 คนนะ ไม่ต้องแยกกรอบออกไปไหนอีก"
   ⚠️ แถบสองอันนี้ตั้งใจ "ไม่ใช้ class .rcp-bar" ทั้งที่หน้าตาเหมือนกัน — .rcp-bar หมายถึง
      "แถบของใบเสร็จหนึ่งใบ" ซึ่งมีที่เดียวในกรอบ .receipt · ของที่ค้นหา .rcp-bar ตัวแรก
      (รวมถึงเทสต์) ต้องยังเจอใบเสร็จ ไม่ใช่เจอแถบรวมที่เพิ่งมาอยู่ข้างบน
   ══════════════════════════════════════════════════════════════════ */
.total-block {
  margin-bottom: var(--sp-5);
}
.total-bar,
.logs-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-4);
  margin-bottom: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  /* 🔴 2026-10-06 — พื้นโปร่ง (ดูเหตุผลที่ .receipt) */
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
}
/* Total คือหัวข้อของทั้งแถว ไม่ใช่หัวข้อย่อย — เส้นเข้มกว่าแถบอื่นหนึ่งระดับ */
.total-bar {
  border-color: var(--line-strong);
}
.total-bar .total-label-main {
  text-transform: none;
  letter-spacing: 0;
  font-size: var(--fs-base);
  color: var(--ink);
}
/* ตัวเลขหลายตัวเรียงกัน — ตัวแรกดันชิดขวาแทนที่จะดันทีละตัว (.rcp-used มี margin-left: auto ในตัว) */
.rcp-nums {
  display: inline-flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-4);
  margin-left: auto;
}
.rcp-nums .rcp-used {
  margin-left: 0;
}
/* "เหลืออีกกี่วัน" = ตัวเลขที่ต้องไปทำอะไรต่อ จึงเป็นสีเดียวกับป้ายที่ค้างอยู่ทั้งเว็บ */
.rcp-left b {
  color: var(--warn);
}

/* ---- ตารางรวมทุกใบเสร็จ -------------------------------------------- */
/* 48 + 132 + 158 + 128 + 140 + Remarks(auto · ขั้นต่ำ 160) + 80 (Cancelled) + 44 = 890px
   ⚠️ เคยเป็น 1158 ตอนยังมีช่องติ๊ก Online / Calendar / Sheet — Big สั่งเอาออก 2026-09-24
   ⚠️ 2026-10-07 — ลดอีก 124px ตอน Remarks กลายเป็นปุ่มโน้ต */
/* 🔴 2026-10-08 (รอบหก) — 48 + 132 + 158 + 128 + 160 + 100 + act44 = 770 + Remarks ขั้นต่ำ 100
   ⚠️ ตารางนี้มี 7 คอลัมน์ ที่จอ 1024 ยังต้องเลื่อนข้างราว 20px (ของเดิมก่อนรอบห้าเลื่อน 41px) */
.table-wrap table[data-list="total"] {
  min-width: 870px;
}
.table-wrap table[data-list="total"] .col-course { width: 132px; }
.table-wrap table[data-list="total"] .col-time { width: 128px; }
/* 🔴 ตารางนี้ "ดูดที่ว่าง" ได้แล้ว — ตอนเช้าลองให้ Remarks เป็น auto แล้วหัว "Cancelled"
   ถูกตัด เพราะตารางกว้าง 942 เบียดกรอบอยู่ · ตอนนี้เหลือ 818 จึงมีที่ให้ดูดจริง */
.table-wrap table[data-list="total"] .col-who { width: 160px; }
.table-wrap table[data-list="total"] .col-remarks { width: auto; }
/* 🔴 ต้องกว้างกว่าของตารางคาบ — หัวคอลัมน์ของตารางนี้ **ตัวใหญ่กว่า** เพราะกฎ
   ".receipt .table-wrap th { font-size: --fs-xs }" ของการ์ดคอร์สไม่ครอบมาถึง
   (ตาราง Total อยู่ใน .total-block ไม่ได้อยู่ในกรอบคอร์ส) · วัดได้ 75px เทียบ 72px */
.table-wrap table[data-list="total"] .col-chk { width: 100px; }
/* ช่อง Course — บรรทัดบนบอกคอร์ส บรรทัดล่างบอกเลขใบเสร็จ (ตรวจย้อนได้โดยไม่ต้องกาง Purchase log) */
.tot-course {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-inline: var(--sp-2); /* ของที่ไม่ใช่ช่องกรอกไม่มี padding ในตัว — กฎเดียวกับ .tag */
  min-width: 0;
}
.tot-course-top {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-1);
}
.tot-rcp {
  font-size: var(--fs-xs);
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.makeup-tag {
  text-transform: none;
  letter-spacing: 0;
}
/* 🔴 ป้ายที่กดได้ = เด้งไปหาคาบที่ยกเลิก (Big สั่ง 2026-09-28) · หน้าตาต้องเท่าเดิมเป๊ะ
   ⚠️ <button> ไม่ได้สืบฟอนต์กับพื้นมาจากตัวแม่เหมือน <span> ต้องคืนให้เองทั้งสองอย่าง
      แต่ห้ามใช้ shorthand `font: inherit` เพราะมันจะล้าง font-size/weight ของ .rl-status ทิ้งด้วย */
button.makeup-tag {
  font-family: inherit;
  line-height: inherit;
  background: none;
  cursor: pointer;
}
button.makeup-tag:hover {
  background: var(--warn-bg);
}
/* คาบทบ = วันที่เอามาเรียนแทนวันที่ยกเลิก · แถบซ้ายบางๆ พอให้กวาดตาเจอ ไม่ต้องอ่านป้าย */
.row-makeup > td:first-child {
  box-shadow: inset 3px 0 0 var(--warn-ring);
}

/* ── กรอบชั่วคราวตอนถูกพาไปหา (ui.flashRow) ───────────────────────────
   🔴 Big สั่ง 2026-09-28 — "ให้วันนั้นมีกรอบขึ้นชั่วคราวเหมือน Focus ไรงี้ เพราะบางคน
   มันดูไม่ออกหรือหาไม่เจอจริงๆ" · ใช้ --ring ซึ่งเป็นสีกรอบโฟกัสของทั้งเว็บ
   คนที่เคยเห็นกรอบนี้ตอนกด Tab จะอ่านออกทันทีว่า "ของที่ถูกเลือกอยู่คืออันนี้"
   ⚠️ outline ไม่ใช่ border — border บน <tr> ตอน border-collapse จะไปดันความสูงแถว
      ส่วน outline วาดทับโดยไม่กินที่ ตารางจึงไม่ขยับตอนกรอบขึ้น
   ⚠️ ความยาวมาจาก --flash-ms ที่ JS ตั้งให้ (ui.FLASH_MS) ไม่ใช่เลขดิบที่นี่ */
tr.row-flash {
  outline: 2px solid var(--ring);
  outline-offset: -2px;
  /* กรอบค้างยาว + สั่นสั้นๆ ตอนเริ่ม · การสั่นคือตัวเรียกสายตา ส่วนกรอบคือตัวบอกว่า "อันไหน"
     ⚠️ สั่นอย่างเดียวไม่พอ คนที่มองอยู่คนละมุมจอจะไม่ทันเห็น แล้วก็ไม่เหลืออะไรให้ดูต่อ */
  animation:
    row-flash var(--flash-ms) var(--ease-out) both,
    row-shake calc(var(--dur-3) * 2) var(--ease-out);
}
tr.row-flash > td {
  background: color-mix(in srgb, var(--ring) 12%, transparent);
  animation: row-flash-bg var(--flash-ms) var(--ease-out) both;
}
/* ค้างไว้เกือบทั้งช่วงแล้วค่อยจาง — จางตั้งแต่ต้นจะกลายเป็นแค่ของกระพริบที่มองไม่ทัน */
@keyframes row-flash {
  0%,
  70% {
    outline-color: var(--ring);
  }
  100% {
    outline-color: transparent;
  }
}
@keyframes row-flash-bg {
  0%,
  70% {
    background: color-mix(in srgb, var(--ring) 12%, transparent);
  }
  100% {
    background: transparent;
  }
}
/* 🔴 Big สั่ง 2026-09-28 — "เพิ่ม Effect การสั่นด้วย"
   แกว่งแล้วค่อยๆ เบาลงจนหยุด (4px → 2px → 0) ไม่ใช่แกว่งเท่ากันทุกจังหวะแล้วตัดจบ
   ของที่หยุดกลางทางอ่านเหมือนหน้าค้าง ส่วนของที่ค่อยๆ เบาลงอ่านว่า "เรียกเสร็จแล้ว"
   ⚠️ ระยะใช้ --sp-1 (4px) ไม่ใช่เลขดิบ · แกว่งแรงกว่านี้แถวจะชนขอบตารางที่เลื่อนได้
   ⚠️ reduced motion ไม่ต้องเขียนกันเอง — motion.css บีบทุก animation เหลือ 0.01ms อยู่แล้ว
      และกรอบไม่ได้หายตามเพราะ JS ถอดคลาสด้วย timer (ui.flashRow) */
@keyframes row-shake {
  0%,
  100% {
    transform: translateX(0);
  }
  20% {
    transform: translateX(calc(var(--sp-1) * -1));
  }
  40% {
    transform: translateX(var(--sp-1));
  }
  60% {
    transform: translateX(calc(var(--sp-1) * -0.5));
  }
  80% {
    transform: translateX(calc(var(--sp-1) * 0.5));
  }
}

/* ---- แถบเตือน "ยังติดค้างวันเรียน" ---------------------------------- */
/* 🔴 Big สั่ง 2026-09-24 — "การที่เขา Cancelled นั้นต้องเอาไปทบเรียน ... หากไม่มีข้อมูล
   ยังไม่สามารถติดต่อได้ อยากให้กดปุ่ม Remind Later และอยากให้มีแจ้งเตือนขึ้นทุกวัน" */
.makeup-alert {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  margin-bottom: var(--sp-3);
  padding: var(--sp-3);
  background: var(--warn-bg);
  border: 1px solid var(--warn-ring);
  border-radius: var(--r-md);
}
.makeup-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-2);
}
.makeup-head .rcp-ico {
  color: var(--warn);
}
.makeup-title {
  font-weight: 700;
  color: var(--warn-strong);
}
.makeup-sub {
  flex: 1 1 100%;
  font-size: var(--fs-sm);
  color: var(--warn);
}
.makeup-list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.makeup-item {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2);
}
.makeup-when {
  min-width: 200px;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
/* ปุ่มที่ "เปิดอยู่" (กำลังเตือนรายวัน) — ต้องดูต่างจากปุ่มที่ยังไม่ได้กด ไม่งั้นกดแล้วไม่รู้ว่าติดหรือยัง */
.makeup-item .google-btn.is-on {
  color: var(--warn-strong);
  border-color: var(--warn-ring);
  background: var(--warn-bg);
  font-weight: 700;
}

/* ---- Purchase log ที่พับได้ ------------------------------------------ */
.logs-bar .logs-label {
  text-transform: none;
  letter-spacing: 0;
  font-size: var(--fs-base);
  color: var(--ink);
}
.logs-bar.is-shut .vbox-caret {
  transform: rotate(-90deg);
}
/* 🔴 แถบ + คอร์สทั้งหมด = การ์ดใบเดียว (Big สั่ง 2026-09-28 — "Border ของ Purchase Log กับ Course
   ให้มันอยู่ใน Frame เดียวกันด้วย ... เพราะอันนี้ดูแล้วงง ไม่รู้มาจากไหน")
   ⚠️ เดิมใบเสร็จลอยเป็นพี่น้องของแถบ ไม่มีเส้นไหนบอกว่ามันเป็นของแถบนั้น
   วิธีต่อหัวกับเนื้อใช้กฎเดียวกับ .vbox-head + .vbox-body: เงาอยู่ที่กล่องนอกที่เดียว
   หัวตัดมุมล่าง เนื้อไม่มี border-top (ไม่งั้นเส้นซ้อนหนา 2px ตรงรอยต่อ) */
/* ⚠️ 2026-10-07 — กางอยู่ = ไม่มีกรอบแล้ว เงาจึงไม่มีขอบให้วาดรอบ (จะกลายเป็นเงาลอย
   รอบของที่มองไม่เห็น) · เหลือเงาเฉพาะตอนพับ ซึ่งตอนนั้นมันเป็นปุ่มเล็กจริงๆ */
.logs-box {
  border-radius: var(--r-md);
}
.logs-box.is-shut {
  box-shadow: var(--sh-1);
}
/* 🔴 พับอยู่ = ปุ่มเล็กๆ ไม่ใช่แถบยาวเต็มจอ (Big สั่ง 2026-09-28 — "ไม่ต้องเอายาวขนาดนี้
   ทำเป็นปุ่มไอคอนนี้ และชื่อ Receipt และบอกจำนวนพอ")
   ⚠️ กางอยู่ยังต้องเต็มความกว้าง เพราะมันเป็น "หัวของกรอบ" ที่คลุมคอร์สอยู่ (Big สั่งไว้รอบก่อนหน้า) */
.logs-box.is-shut {
  width: fit-content;
  max-width: 100%;
}
.logs-box.is-shut:hover .logs-bar {
  background: var(--surface-2);
}
/* ตัวเลขจำนวนใบเสร็จ — เป็นป้ายกลม ไม่ใช่ตัวเลขลอย จะได้ไม่อ่านเป็น "ใบที่ 3" */
.logs-n {
  min-width: 18px;
  padding: 0 var(--sp-1);
  border-radius: var(--r-full);
  /* 🔴 2026-10-06 — ต้องเป็น --surface-2 ไม่ใช่ --surface-3 · ตั้งแต่ .logs-bar กลายเป็น
     พื้นโปร่ง ป้ายนี้ไปนั่งบน .rl-body ซึ่งเป็น --surface-3 เหมือนกันเป๊ะ → ป้ายหายไปกับพื้น
     ⚠️ บั๊กนี้เกิดตอนยุบชั้นพื้นหลังรอบ Minimal เอง และรูปนิ่งดูไม่ออกเพราะตัวเลขยังอ่านได้
     สิ่งที่หายคือ "วงกลม" ซึ่งคือสิ่งเดียวที่บอกว่ามันแปลว่า "มี 3 ใบ" ไม่ใช่ "ใบที่ 3"
     --surface-2 อยู่กึ่งกลางของไล่ระดับ จึงต่างจาก --surface-3 ในทั้งสองธีม */
  background: var(--surface-2);
  color: var(--ink);
  font-size: var(--fs-xs);
  font-weight: 700;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.logs-box .logs-bar {
  margin-bottom: 0;
  box-shadow: none;
}
/* 🔴 2026-10-07 (รอบสอง) — กางอยู่ ตัดมุมตรง "เฉพาะซ้ายล่าง" มุมเดียว ไม่ใช่สองมุม
   (Big แจ้ง — "มุมนี้มันดูไม่ค่อยสมบูรณ์เท่าไหร่" · แนบรูปมุมซ้ายล่างของแถบ Receipts)
   ⚠️ ค่าเดิม `--r-md --r-md 0 0` ตกค้างมาจากตอนที่ .logs-body ยังเป็นกรอบเต็มใบ —
   มุมล่างสองมุมตัดตรงเพราะมี "กรอบมาต่อ" ข้างล่าง · พอกรอบถูกถอดไปเมื่อเช้า
   มุมขวาล่างก็เหลือเป็นมุมฉากที่ไม่มีอะไรมาต่อ = ดูเหมือนกล่องที่วาดไม่เสร็จ
   🔴 ซ้ายล่างยังต้องตรง เพราะเส้นของ .logs-body วิ่งลงมาต่อจากตรงนั้นพอดี
   (เส้นขอบซ้ายของแถบ → เส้นนำสายตาของเนื้อ = เส้นเดียวกันลากยาว) */
.logs-box:not(.is-shut) .logs-bar {
  border-radius: var(--r-md) var(--r-md) var(--r-md) 0;
}
/* 🔴🔴 2026-10-07 — กรอบชั้นนอกสุดถูกถอดออก (Big สั่ง — "เอากรอบชั้นนอกสุดที่ครอบ
   การ์ด Course ทั้งหมดออก แล้วแยกแต่ละ Course ด้วยช่องว่าง")
   ⚠️⚠️ **กลับด้านจากคำสั่ง 2026-09-28** ที่ขอให้ Purchase log กับ Course อยู่ในกรอบเดียวกัน
   ("ผู้ใช้งานจะได้รู้ว่ามันคือของ Purchase Log") — Big ยืนยันเลือก "เอาออกตามสเปค" อีกครั้ง
   🔴 สิ่งที่เสียไปจากการถอด: ไม่มีเส้นโยงลงมาจากหัว Purchase log ไปหากรอบ Course อีกแล้ว
   ตัวที่ทำหน้าที่นั้นแทนคือ **ระยะเยื้องเข้ามา** (padding-left) — ถ้าใครถอดบรรทัดนั้นออก
   ปัญหาเดิมปี 2026-09-28 จะกลับมาทันที */
.logs-body {
  /* 🔴 2026-10-08 (รอบหก) — padding-left 12 → 0 (Big สั่ง — "ขอบซ้ายขวาตรงกับแถบ Receipts")
     ⚠️ **แคบลงจากกฎ 2026-10-07 รอบสอง** ตอนนั้นเยื้อง --sp-3 เพื่อให้กรอบคอร์สตรงกับ
     **ไอคอน/ชื่อ "Receipts" ที่อยู่ในแถบ** ซึ่งเป็นการเล็งคนละเป้ากับที่ Big ขอรอบนี้
     (ขอบนอกของแถบ) · วัดได้เยื้อง 13px = padding 12 + เส้นขอบ 1
     🔴 **border-left ยังอยู่** — มันคือสิ่งเดียวที่โยงกรอบคอร์สกลับไปหาแถบ Receipts
     ตอนนี้เส้นนี้ทับแนวเดียวกับขอบซ้ายของการ์ดพอดี และโผล่ให้เห็นในช่องไฟระหว่างใบ */
  padding: var(--sp-3) 0 0 0;
  background: transparent;
  border: 0;
  /* 🔴 2026-10-07 (รอบสอง) — เส้นนี้ต้อง "ตรงกับเส้นขอบซ้ายของแถบ" เป๊ะ (margin-left: 0)
     เดิมเยื้องเข้ามา --sp-2 ทำให้มันเป็นเส้นลอยที่ไม่ได้ต่อกับอะไร และมุมซ้ายล่างของแถบ
     ก็เหลือเป็นมุมฉากค้างอยู่ · ตอนนี้ขอบซ้ายของแถบกับเส้นนี้เป็นเส้นเดียวกันลากลงมา
     ⚠️ ระยะเยื้องของคอร์สมาจาก padding-left ไม่ใช่ margin — กรอบคอร์สจึงไปตรงกับ
     ไอคอน/ชื่อ "Receipts" ในแถบพอดี (ทั้งคู่ = ขอบซ้าย + เส้น 1px + --sp-3) */
  border-left: 1px solid var(--line);
  border-radius: 0;
  margin-left: 0;
}
/* 🔴🔴 2026-10-07 (รอบสี่) — การ์ด Course เรียง **2 คอลัมน์เมื่อที่ว่างพอ**
   (Big สั่ง — "ตอนนี้ตารางใช้พื้นที่แค่ด้านซ้าย เหลือที่ว่างด้านขวาเยอะ")
   ⚠️ **เฉพาะหน้า Roster** (Big เลือกเอง) — ตารางเช็คชื่อของ Private Attendance กว้าง
   ขั้นต่ำ 1260px และตารางแผนของ Planning 830px สองหน้านั้นใส่ครึ่งจอแล้วตารางต้องเลื่อนข้าง
   ซึ่งแลกไม่คุ้ม (กฎเดียวกับ "ดึงเต็มกรอบเมื่อไม่มีที่พอเท่านั้น")
   🔴 align-items: start — การ์ดสองใบที่คาบไม่เท่ากันต้องไม่ถูกยืดให้สูงเท่ากัน
      ไม่งั้นใบที่สั้นกว่าจะมีพื้นที่ว่างห้อยอยู่ข้างล่างโดยไม่มีอะไรอยู่ในนั้น */
/* 🔴🔴 2026-10-08 (รอบหก) — **การ์ดกลับมาเต็มความกว้าง เรียงลงล่างใบละหนึ่งแถว**
   (Big สั่ง — "การ์ด receipt กว้างเต็ม container ขอบซ้ายขวาตรงกับแถบ Receipts
    ถ้ามีหลายใบให้เรียงลงล่าง ใบละเต็มความกว้าง")
   ⚠️⚠️ **กลับด้านทั้งรอบสี่ (กริด 2 คอลัมน์) และรอบห้า (เพดาน 832px)** — ทั้งคู่เกิดขึ้น
   เพราะ Remarks อยู่ **กลางแถว** พอมันดูดที่ว่าง Teacher กับ Cancelled เลยถูกดันไปขวาสุด
   🔴 **รอบนี้แก้ที่ต้นเหตุแทน: ย้าย Remarks ไปท้ายสุด** (ดู column def ใน page-roster-private.js)
   ที่ว่างจึงไปกองใน "คอลัมน์ข้อความ" ซึ่งเป็นคอลัมน์เดียวที่กว้างขึ้นแล้วได้ประโยชน์จริง
   → ไม่ต้องจำกัดความกว้างการ์ด และไม่ต้องแบ่งสองคอลัมน์อีกแล้ว
   ⚠️ ยังคง grid ไว้เพราะ gap + align-items: start ยังต้องใช้ (การ์ดคนละความสูงห้ามถูกยืด) */
.logs-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-6);
  align-items: start;
}
/* grid จัดระยะด้วย gap แล้ว — margin ของกรอบคอร์สจะกลายเป็นระยะซ้อน */
.logs-body > * {
  margin-bottom: 0;
}
/* ⚠️ 2026-10-08 — กฎ ".logs-body:has(> :only-child) { 900px }" ถูกลบทิ้ง
   ตอนนี้ **ทุกใบถูกจำกัดที่ 832px อยู่แล้ว** ไม่ว่าจะมีกี่ใบ · กฎเฉพาะกรณีจึงกลายเป็น
   ความจริงสองชุดที่บอกคนละค่า (900 กับ 832) ซึ่งเป็นที่มาของบั๊กที่หาไม่เจอ */
/* 🔴🔴 จุดตัดมาจาก "ตารางต้องไม่ต้องเลื่อนข้าง" ไม่ใช่ตัวเลขกลมๆ
   🔴 2026-10-08 — คิดใหม่จากการ์ด 832px (เดิม 792 ตอน Remarks ขั้นต่ำ 160)
   สองใบ + gap 24 = **1688px ของเนื้อที่** · เนื้อที่จริง ≈ ความกว้างจอ − 186 (เมนูแถบ + ขอบหน้า)
   → ต้องการจอ **1874px** · ⚠️ Big เคยขอ "ประมาณ 1100px" แต่ที่ 1100 แต่ละคอลัมน์เหลือ ~540px
   ซึ่ง **แคบกว่าตารางที่แคบที่สุด** ตารางจะมีแถบเลื่อนข้างในการ์ดทุกใบ จึงใช้ค่าที่วัดได้แทน
   ⚠️ เมนูซ้ายที่ปักหมุดกินเพิ่มอีก 172px — ช่วง 1874–2045px ต้องถอยกลับเป็นคอลัมน์เดียว */
/* ⚠️ 2026-10-08 (รอบหก) — กริด 2 คอลัมน์ที่ ≥1874px **ถูกถอดออกแล้ว** ตามคำสั่ง
   "ถ้ามีหลายใบให้เรียงลงล่าง ใบละเต็มความกว้าง" · เหตุผลเดิมที่ทำไว้ (ตารางใช้ที่แค่
   ครึ่งซ้าย) หมดไปเองเมื่อ Remarks ย้ายไปท้ายแถวแล้วดูดที่ว่างไปใช้เป็นเนื้อที่ข้อความ */
/* กรอบคอร์สใบสุดท้ายไม่ต้องดันพื้นล่างของกล่อง — padding ของกล่องทำหน้าที่นั้นแล้ว */
.logs-body > :last-child {
  margin-bottom: 0;
}
/* Purchase log กับ Total เป็นคนละก้อน — เว้นให้มากกว่าระยะในก้อนเดียวกัน */
.logs-box + .total-block {
  margin-top: var(--sp-5);
}
/* ป้าย "ยังต้องนัดใหม่" ต่อท้ายสถานะในรายชื่อ — สองป้ายในช่องเดียวต้องขึ้นบรรทัดใหม่ได้ */
.status-cell {
  flex-wrap: wrap;
  row-gap: var(--sp-1);
}

/* ══════════════════════════════════════════════════════════════════
   HUD — กล่องยืนยัน (ui.ask) + แจ้งเตือนมุมขวาล่าง (ui.notify)
   🔴 Big สั่ง 2026-09-22 — "อยากได้เป็น Notify ของตัวเองเลย อยากได้แบบ FiveM"
   แทน confirm() / alert() ของเบราว์เซอร์ทั้งเว็บ · หน้าตา: วงไอคอนเรืองแสง +
   หัวตัวพิมพ์ใหญ่สีตามเรื่อง + ข้อความจางข้างใต้
   · สีทั้งหมดมาจาก token เดิม — แสงเรืองผสมจากสีเดียวกันด้วย color-mix ไม่มี #hex ใหม่
   · --hud-c = สีเส้น/ไอคอน/แสง · --hud-t = สีตัวหนังสือหัว (ธีมสว่างต้องเข้มกว่าไอคอน
     ไม่งั้นส้มบนขาวอ่านไม่ผ่าน contrast)
   ══════════════════════════════════════════════════════════════════ */
.hud[data-tone="danger"] { --hud-c: var(--danger); --hud-t: var(--danger); }
.hud[data-tone="warn"] { --hud-c: var(--alert-ico); --hud-t: var(--warn); }
.hud[data-tone="ok"] { --hud-c: var(--ok-text); --hud-t: var(--ok-text); }
.hud[data-tone="info"] { --hud-c: var(--eng-mark); --hud-t: var(--eng-mark); }

/* ── พื้นแผงร่วมกันทั้งสองแบบ ── */
.hud-panel,
.hud-toast {
  position: relative;
  overflow: hidden;
  background: color-mix(in srgb, var(--surface) 94%, transparent);
  backdrop-filter: blur(10px);
  border: 1px solid color-mix(in srgb, var(--hud-c) 38%, var(--line));
  border-radius: var(--r-lg);
  box-shadow:
    var(--sh-3),
    0 0 24px color-mix(in srgb, var(--hud-c) 14%, transparent);
  color: var(--ink-2);
}
.hud-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--sp-4);
  align-items: start;
}
/* วงไอคอน — เส้นสีเต็ม + แสงเรืองรอบนอก + แสงจางข้างใน (หน้าตาแบบ FiveM) */
.hud-ico {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: var(--r-full);
  border: 2px solid var(--hud-c);
  color: var(--hud-c);
  background: color-mix(in srgb, var(--hud-c) 12%, transparent);
  box-shadow:
    0 0 14px color-mix(in srgb, var(--hud-c) 55%, transparent),
    inset 0 0 10px color-mix(in srgb, var(--hud-c) 30%, transparent);
}
/* ⚠️ .btn-ico ไม่มีขนาดในตัวเอง (กับดักเดิมใน CLAUDE.md) */
.hud-ico .btn-ico {
  width: 20px;
  height: 20px;
}
.hud-title {
  font-family: var(--font-display);
  font-size: var(--fs-md);
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  line-height: 1.3;
  color: var(--hud-t);
  text-shadow: 0 0 12px color-mix(in srgb, var(--hud-c) 40%, transparent);
}
.hud-name {
  margin-top: var(--sp-1);
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--ink);
  overflow-wrap: anywhere;
}
.hud-msg {
  margin-top: var(--sp-2);
  font-size: var(--fs-sm);
  line-height: 1.55;
  color: var(--muted);
}

/* ── กล่องยืนยัน ── */
.hud-ask {
  /* base.css รีเซ็ต margin: 0 ซึ่งฆ่า margin: auto ที่ dialog ใช้จัดกลาง (กับดักเดียวกับ .modal) */
  margin: auto;
  width: min(460px, calc(100vw - var(--sp-8)));
  padding: 0;
  border: 0;
  background: transparent;
  overflow: visible;
}
.hud-ask::backdrop {
  background: rgb(0 0 0 / 0.5);
  backdrop-filter: blur(2px);
}
.hud-ask .hud-panel {
  padding: var(--sp-5);
}
/* เส้นสีบางๆ พาดขอบบน จางออกทางขวา — บอกสีของเรื่องก่อนอ่านตัวหนังสือ */
.hud-ask .hud-panel::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 2px;
  background: linear-gradient(90deg, var(--hud-c), transparent);
  box-shadow: 0 0 10px var(--hud-c);
}
.hud-ask[open] .hud-panel {
  animation: hud-pop var(--dur-3) var(--ease-out);
}
/* ---- กล่องอ่านทวนก่อนกด Save (ui.ask ที่ส่ง body + wide) ---------------- */
.hud-ask.hud-wide {
  width: min(640px, calc(100vw - var(--sp-8)));
}
.plan-review {
  margin-top: var(--sp-3);
}
.plan-review-grid {
  display: grid;
  grid-template-columns: auto auto auto auto minmax(0, 1fr);
  gap: var(--sp-1) var(--sp-3);
  align-items: baseline;
  font-size: var(--fs-sm);
  color: var(--ink-2);
  /* 20 คาบก็ยังต้องอยู่ในจอ — กล่องยืนยันที่ยาวกว่าจอคือกล่องที่กดปุ่มไม่ถึง */
  max-height: 38vh;
  overflow-y: auto;
  padding-right: var(--sp-2);
}
/* 🔴 หัวคอลัมน์ของตารางที่เลื่อนได้ต้องมีพื้นของตัวเอง ไม่งั้นแถวไหลทะลุหัว
   (กฎเดียวกับ .table-wrap.is-capped th — 2026-10-28) */
.prv-h {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--surface);
  padding-bottom: var(--sp-1);
  border-bottom: 1px solid var(--line-strong);
  font-size: var(--fs-xs);
  color: var(--muted);
}
.prv-no {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
/* ช่องที่ยังว่าง = สีเตือน · Big เลือก "เตือน แต่ยังกดผ่านได้" ป้ายจึงบอกว่าแถวไหนขาด
   ไม่ใช่แค่บอกจำนวนรวมข้างล่าง */
.prv-gap {
  color: var(--warn);
}
/* Remarks เต็มความกว้าง ไม่ไปบีบคอลัมน์ที่เหลือ (ข้อความจริงยาวเป็นประโยค) */
.prv-note {
  grid-column: 1 / -1;
  margin-bottom: var(--sp-1);
  padding-left: var(--sp-5);
  font-size: var(--fs-xs);
  color: var(--muted);
  overflow-wrap: anywhere;
}
.plan-review-warn {
  margin-top: var(--sp-2);
  font-size: var(--fs-sm);
  color: var(--warn);
}
.plan-review-done {
  margin-top: var(--sp-2);
  font-size: var(--fs-sm);
  color: var(--muted);
}
.hud-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--sp-2);
  margin-top: var(--sp-5);
}
/* ปุ่มหลัก = คำกริยาของสิ่งที่จะเกิด สีตามเรื่อง · ไม่ใช่ .ok-btn (ปุ่มเขียวทึบได้ปุ่มเดียวต่อหน้า) */
.hud-ok {
  font: inherit;
  font-size: var(--fs-md);
  font-weight: 700;
  padding: var(--sp-2) var(--sp-4);
  border-radius: var(--r-md);
  border: 1px solid var(--hud-c);
  color: var(--hud-t);
  background: color-mix(in srgb, var(--hud-c) 14%, transparent);
  cursor: pointer;
  transition:
    background-color var(--dur-1) var(--ease-out),
    box-shadow var(--dur-1) var(--ease-out);
}
.hud-ok:hover {
  background: color-mix(in srgb, var(--hud-c) 24%, transparent);
  box-shadow: 0 0 14px color-mix(in srgb, var(--hud-c) 40%, transparent);
}
.hud-ok:focus-visible,
.hud-cancel:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}

/* ── แจ้งเตือนมุมขวาล่าง ── */
.hud-stack {
  position: fixed;
  /* 🔴 มุมขวาล่าง (Big สั่ง 2026-09-22 — "อยากให้มาอยู่ขวาล่างแทน") · เดิมขวาบนใต้แถบหัวเว็บ
     ใบใหม่ต่อท้าย = อยู่ล่างสุด ชิดมุมที่สุด · เกิน 4 ใบ ใบบนสุด (เก่าสุด) ออก */
  bottom: var(--sp-4);
  right: var(--sp-4);
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  width: min(360px, calc(100vw - var(--sp-8)));
  /* ช่องว่างระหว่างใบไม่ควรบังการคลิกของหน้าข้างใต้ */
  pointer-events: none;
}
.hud-toast {
  pointer-events: auto;
  cursor: pointer;
  padding: var(--sp-3) var(--sp-4) var(--sp-4);
  border-left: 3px solid var(--hud-c);
  animation: hud-in var(--dur-3) var(--ease-out);
}
.hud-toast .hud-row {
  gap: var(--sp-3);
  align-items: center;
}
.hud-toast .hud-ico {
  width: 34px;
  height: 34px;
}
.hud-toast .hud-ico .btn-ico {
  width: 16px;
  height: 16px;
}
.hud-toast .hud-title {
  font-size: var(--fs-sm);
}
.hud-toast .hud-msg {
  margin-top: calc(var(--sp-1) / 2);
  overflow-wrap: anywhere;
}
.hud-toast.is-leaving {
  animation: hud-out var(--dur-3) var(--ease-out) forwards;
}
/* แถบเวลาที่เหลือ — ยาว = เวลาที่ใบนี้อยู่จริง (--hud-ms ตั้งจาก JS) · ชี้เมาส์ = หยุด */
.hud-bar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background: var(--hud-c);
  box-shadow: 0 0 8px var(--hud-c);
  transform-origin: left;
  animation: hud-drain var(--hud-ms) linear forwards;
}
.hud-toast.is-paused .hud-bar {
  animation-play-state: paused;
}

/* ── พาข้ามหน้า (ui.goPage) ──
   🔴 Big สั่ง 2026-09-22 — "อยากให้ขึ้นหน้าโหลดสักแปบว่ากำลังพาไปหน้า Planning ... ถ้าคนไม่สังเกตมันจะคิดว่าตัวเองอยู่หน้า Roster"
   ทั้งจอมืดลงนิดหนึ่ง + กล่อง HUD กลางจอ · แถบล่าง "เติม" (ไม่ใช่ "หมด" แบบแจ้งเตือน) = กำลังไป
   z-index ใต้แจ้งเตือน — toast "New course started" ที่ขึ้นพร้อมกันต้องอ่านได้ */
.hud-nav {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: grid;
  place-items: center;
  padding: var(--sp-4);
  background: rgb(0 0 0 / 0.35);
  backdrop-filter: blur(2px);
  cursor: progress;
  animation: hud-fade var(--dur-2) var(--ease-out);
}
.hud-nav.is-leaving {
  opacity: 0;
  transition: opacity var(--dur-3) var(--ease-out);
}
.hud-nav .hud-panel {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  min-width: min(340px, 100%);
  padding: var(--sp-5) var(--sp-6);
  animation: hud-pop var(--dur-3) var(--ease-out);
}
/* ลูกศรชี้ขวา = "กำลังไป" · วงแหวนหมุนรอบไอคอน */
.hud-nav .hud-ico .btn-ico {
  width: 20px;
  height: 20px;
  transform: rotate(-90deg);
}
.hud-spin {
  position: relative;
  flex-shrink: 0;
}
.hud-spin::after {
  content: "";
  position: absolute;
  inset: -6px;
  border-radius: var(--r-full);
  border: 2px solid transparent;
  border-top-color: var(--hud-c);
  animation: hud-rotate calc(var(--dur-3) * 3) linear infinite; /* ไม่มี token ยาวพอ — คูณจาก --dur-3 ไม่เขียนตัวเลขดิบ */
}
.hud-nav .hud-bar {
  transform: scaleX(0);
  animation: hud-fill var(--hud-ms) var(--ease-out) forwards;
}
@keyframes hud-fade {
  from { opacity: 0; }
}
@keyframes hud-rotate {
  to { transform: rotate(360deg); }
}
@keyframes hud-fill {
  to { transform: scaleX(1); }
}
@keyframes hud-pop {
  from { opacity: 0; transform: translateY(8px) scale(0.97); }
}
@keyframes hud-in {
  from { opacity: 0; transform: translateX(24px); }
}
@keyframes hud-out {
  to { opacity: 0; transform: translateX(24px); }
}
@keyframes hud-drain {
  to { transform: scaleX(0); }
}

/* ── กล่องเลือกทาง (ui.choose) — Add student ถาม Private / Group (Big 2026-09-22) ──
   การ์ดสองใบ สีเดียวกับป้าย Group / Private ในตาราง: เลือกสีไหนตอนนี้ = เห็นป้ายสีนั้นในแถว */
.hud-choose {
  width: min(520px, calc(100vw - var(--sp-8)));
}
.hud-choices {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--sp-3);
  margin-top: var(--sp-4);
}
.hud-choice {
  --hud-c: var(--eng-mark);
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-4);
  text-align: left;
  font: inherit;
  color: var(--ink-2);
  background: color-mix(in srgb, var(--hud-c) 6%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--hud-c) 35%, var(--line));
  border-radius: var(--r-lg);
  cursor: pointer;
  transition:
    background-color var(--dur-1) var(--ease-out),
    border-color var(--dur-1) var(--ease-out),
    box-shadow var(--dur-1) var(--ease-out);
}
.hud-choice[data-choice="private"] { --hud-c: var(--tag-private-ink); }
.hud-choice[data-choice="group"] { --hud-c: var(--tag-group-ink); }
.hud-choice:hover,
.hud-choice:focus-visible {
  background: color-mix(in srgb, var(--hud-c) 14%, var(--surface));
  border-color: var(--hud-c);
  box-shadow: 0 0 18px color-mix(in srgb, var(--hud-c) 30%, transparent);
}
.hud-choice:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}
.hud-choice .hud-ico {
  flex: none;
  width: 36px;
  height: 36px;
}
.hud-choice .hud-ico .btn-ico {
  width: 18px;
  height: 18px;
}
.hud-choice-text {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  min-width: 0;
}
.hud-choice-label {
  font-family: var(--font-display);
  font-size: var(--fs-md);
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--hud-c);
}
.hud-choice-desc {
  font-size: var(--fs-sm);
  line-height: 1.45;
  color: var(--muted);
}

/* ══════════════════════════════════════════════════════════════════
   ตัวกรอง (js/filterBar.js) — ยกแบบมาจาก .fbar / .fchip / .fpop ของ Class Planner
   🔴 Big สั่ง 2026-09-22 — "เพิ่มการ Filter เอาทุกอย่างมาฟิลเตอร์ให้หมด ทำแบบ Classplan ก็ได้"
   ปุ่มกลมมีน้ำหนัก (เปิดอยู่ = เขียวทึบ + จำนวน) · ชิปพิมพ์ทั้งประโยค · แผงลอยมีเงาจริง
   ══════════════════════════════════════════════════════════════════ */
/* แถบตัวกรองอยู่บรรทัดเดียวกับช่องค้นหา/ปุ่มของหน้า · ชิปเยอะจนล้น = พันลงบรรทัดใหม่เอง (page-actions เป็น flex-wrap) */
#page-actions .fbar {
  flex: 1 1 auto;
}
.fbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2);
  min-width: 0;
}
.fbtn {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  padding: var(--sp-1) var(--sp-3);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--ink-2);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-full);
  cursor: pointer;
  transition:
    background-color var(--dur-1) var(--ease-out),
    border-color var(--dur-1) var(--ease-out),
    color var(--dur-1) var(--ease-out);
}
.fbtn:hover {
  border-color: var(--accent);
  background: var(--surface-3);
}
/* กรองอยู่ = ปุ่มไม่ใช่คำแนะนำแล้ว แต่เป็นสถานะ พร้อมจำนวนตัวกรอง */
.fbtn.on {
  color: var(--accent-ink);
  background: var(--accent);
  border-color: var(--accent);
}
.fbtn.on:hover {
  background: var(--accent-hover);
}
.fbtn-n {
  min-width: 18px;
  padding: 0 var(--sp-1);
  border-radius: var(--r-full);
  background: color-mix(in srgb, var(--accent-ink) 22%, transparent);
  font-size: var(--fs-xs);
  font-weight: 700;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.fbtn-ghost {
  border-color: transparent;
  background: none;
  color: var(--muted);
}
.fbtn-ghost:hover {
  color: var(--danger);
  border-color: var(--danger-bg);
  background: var(--danger-bg);
}
.f-ico {
  width: 15px;
  height: 15px;
  flex: none;
}
.fchip {
  display: inline-flex;
  align-items: stretch;
  max-width: 100%;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-full);
  background: var(--surface);
  overflow: hidden;
}
/* ยังไม่ได้เลือกค่า = เส้นประ (ยังไม่ได้กรองอะไร) */
.fchip.empty {
  border-style: dashed;
}
.fc-main {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  min-width: 0;
  padding: var(--sp-1) var(--sp-2) var(--sp-1) var(--sp-3);
  border: none;
  background: none;
  font: inherit;
  font-size: var(--fs-sm);
  color: var(--ink);
  cursor: pointer;
}
.fc-main:hover {
  background: var(--surface-3);
}
.fc-ico,
.fc-k,
.fc-op {
  color: var(--muted);
}
.fc-k {
  font-weight: 600;
}
/* ค่าคือ "ข่าว" ของประโยค — ส่วนเดียวที่เป็นหมึกเต็ม */
.fc-v {
  font-weight: 700;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.fchip.empty .fc-v {
  font-weight: 500;
  font-style: italic;
  color: var(--muted);
}
.fc-x {
  flex: none;
  padding: 0 var(--sp-2);
  border: none;
  border-left: 1px solid var(--line);
  background: none;
  color: var(--muted);
  font-size: var(--fs-xs);
  cursor: pointer;
}
.fc-x:hover {
  background: var(--danger-bg);
  color: var(--danger);
}
/* แผงลอย — ที่ทำงาน ไม่ใช่ลิสต์ให้เหลือบดู จึงใหญ่กว่าเมนูทั่วไปหนึ่งขนาด */
.fpop {
  position: fixed;
  z-index: var(--z-modal);
  width: 268px;
  max-height: 60vh;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-3);
  overflow: hidden;
}
.fpop-head {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  padding: var(--sp-2) var(--sp-3);
  border-bottom: 1px solid var(--line);
  font-size: var(--fs-sm);
  color: var(--ink);
}
.fpop-head .f-ico {
  color: var(--muted);
}
/* is / is not เป็นสวิตช์สองตำแหน่ง ไม่ใช่ dropdown ตัวที่สาม */
.fop {
  display: flex;
  margin-left: auto;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-full);
  overflow: hidden;
}
.fop button {
  padding: 2px var(--sp-2);
  border: none;
  background: var(--surface);
  font: inherit;
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
}
.fop button.on {
  background: var(--accent);
  color: var(--accent-ink);
}
.fpop-search {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  padding: var(--sp-2) var(--sp-3);
  border-bottom: 1px solid var(--line);
  color: var(--muted);
}
.fpop-search input {
  flex: 1;
  min-width: 0;
  border: none;
  background: none;
  font: inherit;
  font-size: var(--fs-sm);
  color: var(--ink);
  outline: none;
}
.fpop-list {
  list-style: none;
  margin: 0;
  padding: var(--sp-1);
  overflow-y: auto;
  overscroll-behavior: contain;
}
.fpop-list li[hidden],
.fpop-none[hidden] {
  display: none;
}
.fpop-list button,
.fpop-list label {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  width: 100%;
  padding: var(--sp-1) var(--sp-2);
  border: none;
  border-radius: var(--r-sm);
  background: none;
  font: inherit;
  font-size: var(--fs-sm);
  color: var(--ink-2);
  text-align: left;
  cursor: pointer;
}
.fpop-list button:hover,
.fpop-list label:hover {
  background: var(--surface-2);
}
.fpop-list .f-ico {
  color: var(--muted);
}
.fpop-checks input {
  flex: none;
  accent-color: var(--accent);
}
.fpop-text {
  padding: var(--sp-3);
}
.fpop-text input {
  width: 100%;
  padding: var(--sp-1) var(--sp-2);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  background: var(--surface);
  font: inherit;
  font-size: var(--fs-sm);
  color: var(--ink);
}
.fpop-none {
  margin: 0;
  padding: var(--sp-3);
  font-size: var(--fs-sm);
  color: var(--muted);
  text-align: center;
}
/* กรองจนไม่เหลือ — บอกตรงๆ + ปุ่มล้างในการ์ดเดียวกัน */
.fbar-nomatch {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
}

/* ══════════════════════════════════════════════════════════════════
   ลายเซ็น (js/signature.js) — เช็คชื่อ Private · Big สั่ง 2026-09-22
   "เอาไว้เช็คว่าวันนั้นเขามาจริงๆ ใช่ไหม และอยากให้เก็บลายเซ็นได้ด้วย"
   ลายเซ็นเป็นเส้น SVG (ไม่ใช่รูป) วาดด้วย currentColor → ตามธีมเอง
   ══════════════════════════════════════════════════════════════════ */
.sig-td {
  padding-block: var(--sp-1);
}
.sig-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-1);
  width: 100%;
  height: 36px;
  padding: 0 var(--sp-2);
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-md);
  background: none;
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
  transition: border-color var(--dur-1) var(--ease-out), background-color var(--dur-1) var(--ease-out);
}
.sig-btn:hover:not(:disabled) {
  border-color: var(--accent);
  color: var(--ink);
  background: var(--surface-3);
}
.sig-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.sig-btn .btn-ico {
  width: 14px;
  height: 14px;
}
/* เซ็นแล้ว = เส้นทึบ + รูปลายเซ็นย่อ (ไม่ใช่ปุ่มข้อความ) — มองปราดเดียวรู้ว่าคาบไหนเซ็นแล้ว */
.sig-btn.is-signed {
  border-style: solid;
  border-color: var(--line);
  color: var(--ink);
  padding: 2px var(--sp-1);
}
.sig-svg {
  display: block;
  width: 100%;
  height: 100%;
}
.sig-svg path,
.sig-pad path {
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.sig-svg path {
  stroke-width: 7; /* หน่วยของกระดาน 480×180 — ย่อลงเหลือ ~1.5px ในช่อง */
}
/* กระดานเซ็น: สัดส่วน 480:180 เท่ากับที่เก็บ · touch-action: none ไม่งั้นลากนิ้วบนแท็บเล็ตแล้วหน้าเลื่อนแทนที่จะเป็นเส้น */
.sig-board {
  position: relative;
  margin-top: var(--sp-4);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-lg);
  background: var(--surface-2);
  overflow: hidden;
}
.sig-pad {
  display: block;
  width: 100%;
  aspect-ratio: 480 / 180;
  color: var(--ink);
  touch-action: none;
  cursor: crosshair;
}
.sig-pad path {
  stroke-width: 3;
}
.sig-guide {
  position: absolute;
  left: var(--sp-6);
  right: var(--sp-6);
  bottom: 22%;
  border-top: 1px dashed var(--line-strong);
  padding-top: var(--sp-1);
  font-size: var(--fs-xs);
  color: var(--muted);
  pointer-events: none;
}
.sig-actions {
  display: flex;
  align-items: center;
  margin-top: var(--sp-5);
}
.sig-left {
  display: flex;
  gap: var(--sp-2);
  margin-right: auto;
}
.sig-remove {
  color: var(--danger);
}

/* ── บทบาทชั่วคราว (js/role.js) — ปุ่ม Dev ⇄ Teacher ข้างปุ่มธีม ──
   Big สั่ง 2026-09-22 ให้ลายเซ็นล็อกเฉพาะ Role ครู · ยังไม่มีล็อกอิน ปุ่มนี้มีไว้ดูมุมของครู · ลบทิ้งตอนมี Role จริง
   หน้าตาเดียวกับ .hdr-icon-btn (ความสูง/เส้น/มุม) แต่มีคำ — บทบาทต้องอ่านออก ไม่ใช่เดาจากไอคอน */
.role-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  height: 34px;
  margin-right: var(--sp-2);
  padding: 0 var(--sp-3);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--muted);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  cursor: pointer;
  transition: background-color var(--dur-1) var(--ease-out), border-color var(--dur-1) var(--ease-out), color var(--dur-1) var(--ease-out);
}
.role-btn .btn-ico {
  width: 15px;
  height: 15px;
}
.role-btn:hover {
  background: var(--surface-2);
  border-color: var(--muted);
  color: var(--ink);
}
/* ดูแบบครูอยู่ = สีเตือน — ลืมสลับกลับแล้วงงว่าทำไมแก้ไม่ได้ คือสิ่งที่จะเกิดถ้าปุ่มนี้กลมกลืนเกินไป */
.role-btn[data-role="teacher"] {
  color: var(--warn);
  background: var(--warn-bg);
  border-color: var(--alert-ico);
}

/* ลายเซ็นที่ล็อกแล้ว — ยังกดดูได้ แต่เคอร์เซอร์ไม่ชวนให้วาด */
.sig-btn.is-locked {
  border-style: solid;
}
.sig-modal.is-readonly .sig-pad {
  cursor: default;
}

/* ══════════════════════════════════════════════════════════════════
   BANNER — ถูกลบทั้งส่วนเมื่อ 2026-10-06
   (Big สั่ง — "ไม่ต้องเอาเฉียงละ เอาตรงๆ แบบ Minimal น่าดูเลย")
   เคยเป็นป้ายเฉียงแบบ lower-third ตามที่ Big ขอไว้ 2026-09-22 · สิ่งที่หายไปทั้งหมด:
   --vb-skew · .vb-deco · .vb-plate · .vb-acc-a/b · .vb-line-a/b
   พร้อม <span> ที่ vboxHead เคยสร้าง และ position: relative / isolation: isolate
   ที่ .vbox-head มีไว้รองรับมันโดยเฉพาะ
   🔴 **ถอดของตกแต่ง ต้องถอดกลไกที่รองรับมันออกด้วย** ไม่ใช่ลบแค่ชิ้นที่มองเห็น —
   ไม่งั้นจะเหลือ relative / isolate / ตัวแปร ที่ไม่มีใครใช้ค้างอยู่ให้คนอ่านรุ่นหลังงง
   ว่ามันกันอะไรอยู่ แล้วไม่มีใครกล้าลบ · ตอนนี้หัวกล่องวาดพื้นกับเส้นเองตรงๆ ดู .vbox-head ต้นไฟล์
   ══════════════════════════════════════════════════════════════════ */
/* ชื่อที่เป็นปุ่มพับ (กล่องคลาส Group) — .vbox-toggle ตั้ง font: inherit ไว้ ซึ่งมาทีหลัง .vbox-name จึงลบฟอนต์หัวข้อทิ้ง
   บอกซ้ำตรงนี้ให้ชื่อแบบปุ่มกับแบบข้อความหน้าตาเหมือนกัน */
.vbox-head .vbox-toggle {
  min-width: 0;
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: 700;
}
.vbox-head .vbox-toggle > span:last-child {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.vbox-head.is-collapsed .vbox-toggle {
  font-size: var(--fs-md);
}

/* ═══════════════════════════════════════════════════════════════════════
   🔴🔴 2026-10-08 (รอบห้า) — คอลัมน์ Remarks ยุบเป็น "ไอคอนหมายเหตุ" ตอนการ์ดแคบ
   (Big สั่ง — "ถ้าหน้าจอแคบ ให้ยุบคอลัมน์เป็นไอคอนหมายเหตุกว้างประมาณ 40px
    แสดงเฉพาะแถวที่มี remark แล้วคลิกเพื่ออ่าน")
   🔴🔴 **วัดจากความกว้างการ์ด ไม่ใช่ความกว้างจอ** (Big เลือกเอง) — @container ไม่ใช่ @media
   เหตุผล: ที่จอ 1920 การ์ด Course เรียง 2 คอลัมน์ ใบละ 832px ซึ่ง **แคบกว่า 1024 อยู่แล้ว**
   และเมนูซ้ายที่ปักหมุดกินไป 172px โดยที่ความกว้างจอไม่ขยับเลย · media query จึงถามผิดคำถาม
   ⚠️ container-type: inline-size เพิ่ม contain: layout style inline-size ให้การ์ดด้วย
   ตรวจแล้วว่าไม่มีลูกตัวไหนเป็น position: absolute ที่พึ่ง containing block เดิม
   (.rl-toggle::after อยู่ที่แถวรายชื่อ นอกการ์ด · ฟองโน้ต/เมนู ⋮ อยู่ top layer หรือ body)
   🔴 จุดตัดต้องตรงกับ min-width ของตารางเป๊ะ — ที่ความกว้างนี้ Remarks เหลือ 100px พอดี
   ถ้าตั้งคนละค่าจะมีช่วงที่ "ยังไม่ยุบ แต่ตารางล้นแล้ว" เป็นแถบเลื่อนโผล่มาเฉยๆ
   ⚠️ **ไม่ตั้ง .col-remarks { width: 40px }** — มันยังต้องเป็น "ตัวดูดที่ว่าง" ตัวเดียวของตาราง
   ไม่งั้น table-layout: fixed หารที่ว่างเฉลี่ยเข้าทุกคอลัมน์ · ผลคือไอคอนอยู่ในช่องกว้าง
   40–99px ตามที่เหลือจริง ซึ่งเป็น "ประมาณ 40px" ที่ได้โดยไม่ต้องแลกกับช่องว่างกระจาย
   ═══════════════════════════════════════════════════════════════════════ */
/* หน้าตาของช่องตอนยุบ — เหมือนกันทั้งสามตาราง */

/* ═══════════════════════════════════════════════════════════════════════
   🔴🔴 2026-10-08 — จอแคบกว่า 900px: Remarks เลิกเป็นคอลัมน์
   ลงไปอยู่ "บรรทัดของตัวเองใต้แถวนั้น" (Big เลือกเอง)
   ⚙ รอบหกทำที่ตารางคาบ + Total · **รอบเจ็ดเพิ่มตารางแผนของหน้า Planning**
     (Big สั่ง — "หน้า Planning ก็แก้ให้เหมือน Roster เลย")
   🔴 ทำไมต้องเปลี่ยนโครงแถวเป็น grid: HTML table ย้ายเซลล์ไปขึ้นบรรทัดใหม่ไม่ได้
   ต้องถอด display: table ออกก่อน แล้ววางเซลล์เองด้วย grid — เป็นรูปแบบมาตรฐาน
   ของตารางที่ต้องย่อลงจอเล็ก
   ⚠️ **colgroup หมดหน้าที่ทันทีที่แถวเป็น grid** ความกว้างจึงต้องเขียนเป็น track ซ้ำในนี้
      **แก้ความกว้างคอลัมน์ข้างบนแล้วต้องแก้ track ตรงนี้ด้วยเสมอ**
   ⚠️ เล็งด้วย data-list สามตัวนี้เท่านั้น — ตารางอื่นทั้งเว็บ (Waiting List · Student ·
      Contact List · Class Schedule · Attendance) ยังเป็นตารางปกติ
   ═══════════════════════════════════════════════════════════════════════ */
@media (max-width: 900px) {
  /* ชุดตัวเลือกร่วม — เขียนครั้งเดียวด้วย :is() ไม่ใช่ก๊อปสามชุด
     (กฎ "ความจริงเดียวห้ามอยู่หลายที่" — ก๊อปแล้ววันหลังจะแก้ไม่ครบ) */
  .table-wrap table:is([data-list="lessons"], [data-list="total"], [data-list="plan"]),
  .table-wrap table:is([data-list="lessons"], [data-list="total"], [data-list="plan"]) > thead,
  .table-wrap table:is([data-list="lessons"], [data-list="total"], [data-list="plan"]) > tbody {
    display: block;
  }
  .table-wrap table:is([data-list="lessons"], [data-list="total"], [data-list="plan"]) tr {
    display: grid;
    align-items: center;
  }
  /* track ต้องตรงกับความกว้าง <col> ข้างบนเป๊ะ · ช่องสุดท้ายรับที่ว่าง (ปุ่ม ✕ หรือว่างเปล่า)
     ⚠️ track สุดท้ายหดต่ำกว่า 44px ไม่ได้จริง (ปุ่ม ✕ ล้นออกนอก track แล้ว scrollWidth ไม่ลด)
     ที่ว่างต้องไปหาเอาจากคอลัมน์อื่น — Course ของตาราง Total จึงเหลือ 120px เฉพาะตอนจอแคบ */
  .table-wrap table[data-list="lessons"] tr {
    grid-template-columns: 48px 158px 128px 160px 100px minmax(44px, 1fr);
  }
  .table-wrap table[data-list="total"] tr {
    grid-template-columns: 48px 120px 158px 128px 160px 100px minmax(44px, 1fr);
  }
  .table-wrap table[data-list="plan"] tr {
    grid-template-columns: 48px 158px 132px 148px 160px minmax(44px, 1fr);
  }
  .table-wrap table:is([data-list="lessons"], [data-list="total"], [data-list="plan"]) tr > *:not(.cell-note) {
    grid-row: 1;
  }
  /* 🔴 เส้นคั่นย้ายจาก <td> ไปอยู่ที่ <tr> — ถ้าปล่อยไว้ที่เซลล์จะมีเส้นพาดกลางแถว
     คั่นระหว่างข้อมูลกับ remark ของแถวเดียวกัน · และแถวที่ไม่มี remark (ช่องถูกซ่อน)
     จะไม่เหลือเซลล์ไหนวาดเส้นให้เลย — เส้นคั่นต้องเป็นของ "แถว" ไม่ใช่ของ "ช่อง" */
  .table-wrap table:is([data-list="lessons"], [data-list="total"], [data-list="plan"]) tr > * {
    border-bottom: 0;
  }
  .table-wrap table:is([data-list="lessons"], [data-list="total"], [data-list="plan"]) thead tr,
  .table-wrap table:is([data-list="lessons"], [data-list="total"], [data-list="plan"]) tbody tr {
    border-bottom: 1px solid var(--line);
  }
  .table-wrap table:is([data-list="lessons"], [data-list="total"], [data-list="plan"]) tbody tr:last-child {
    border-bottom: 0;
  }
  /* 🔴 พื้นของหัวตารางและของแถวที่ชี้ ต้องย้ายไปอยู่ที่ <tr> ด้วย
     ⚠️ เหตุผล: grid + align-items: center ทำให้ช่องที่ "ไม่มีเนื้อหา" (คอลัมน์ปุ่ม ✕)
     สูงแค่ 16px ขณะที่ช่องอื่น 36px — พื้นที่ทาไว้ที่ช่องจึงแหว่งเป็นบล็อกตรงท้ายแถว
     (วัดได้ที่ตาราง Total · **เห็นจากรูปก่อน แล้วค่อยวัดยืนยัน**) */
  .table-wrap table[data-list="total"] thead tr {
    background: var(--surface-2);
  }
  .table-wrap table:is([data-list="lessons"], [data-list="total"], [data-list="plan"]) thead th {
    background: transparent;
  }
  .table-wrap table:is([data-list="lessons"], [data-list="total"], [data-list="plan"]) tbody tr:hover {
    background: var(--surface-3);
  }
  .table-wrap table:is([data-list="lessons"], [data-list="total"], [data-list="plan"]) tbody tr:hover > * {
    background: transparent;
  }
  .table-wrap table:is([data-list="lessons"], [data-list="total"], [data-list="plan"]) td.cell-note {
    grid-row: 2;
    grid-column: 1 / -1;
    padding-top: 0;
    padding-left: var(--sp-3);
  }
  /* 🔴 แถวที่ยังไม่มี remark ไม่ต้องกินบรรทัดว่าง — ไม่งั้นตารางสูงเป็นเท่าตัวบนจอเล็ก
     ซึ่งตรงข้ามกับเหตุผลที่ย้าย Remarks ลงมาตั้งแต่แรก (เห็นจากรูปเท่านั้น)
     ⚠️ **สิ่งที่แลกไป: จอแคบกว่า 900px เพิ่ม remark ใหม่ไม่ได้ อ่านได้อย่างเดียว**
     (ตรงกับที่ Big เคยเลือกเองรอบก่อน — "แสดงเฉพาะแถวที่มี remark") · ขยายหน้าต่างแล้วเพิ่มได้ตามปกติ */
  .table-wrap table:is([data-list="lessons"], [data-list="total"], [data-list="plan"]) td.cell-note:has(> .note-line.is-empty) {
    display: none;
  }
  /* หัวคอลัมน์ Remarks ไม่มีความหมายแล้วเมื่อช่องย้ายลงไปอยู่คนละบรรทัด */
  .table-wrap table:is([data-list="lessons"], [data-list="total"], [data-list="plan"]) th.th-remarks {
    display: none;
  }
  /* แถว "ตารางนี้ว่าง" คร่อมทุกคอลัมน์ด้วย colSpan ซึ่ง grid ไม่รู้จัก ต้องสั่งเอง */
  .table-wrap table:is([data-list="lessons"], [data-list="total"], [data-list="plan"]) .empty-row td {
    grid-column: 1 / -1;
  }
  /* ไม่มีคอลัมน์ Remarks แล้ว ตารางจึงต้องการความกว้างน้อยลง = ผลรวมของ track ที่ตั้งตายตัว */
  .table-wrap table[data-list="lessons"] {
    min-width: 638px; /* 48+158+128+160+100+44 */
  }
  .table-wrap table[data-list="total"] {
    min-width: 758px; /* 48+120+158+128+160+100+44 */
  }
  .table-wrap table[data-list="plan"] {
    min-width: 690px; /* 48+158+132+148+160+44 */
  }
}
