/* PeopleFlow Design System v1 — สร้างตามสัญญาใน design/
   ★ ไฟล์นี้คือ CSS กลางของทุกหน้าใต้ /design/ — ห้ามก็อปไปวางในหน้าใดหน้าหนึ่ง */
/* ═══════════════════════════════════════════════════════════════════════════
   PHASE 2 · TYPOGRAPHY — ฟอนต์อยู่ในรีโป ไม่เรียก CDN ตอนใช้งาน

   ★ variable font น้ำหนัก 400–700 ในไฟล์เดียว · แยกเป็น 3 ชุดย่อยตาม unicode-range
     เบราว์เซอร์จะโหลดเฉพาะชุดที่หน้านั้นใช้จริง (หน้าไทยล้วนโหลดแค่ 26 KB)
   ⚠ ลำดับสำคัญ: ประกาศชุด `thai` ก่อน — ถ้าชุด latin มาก่อนและครอบคลุมอักขระเดียวกัน
     เบราว์เซอร์บางตัวจะเลือกผิดชุด
   ═══════════════════════════════════════════════════════════════════════════ */
@font-face {
  font-family: 'Noto Sans Thai';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/fonts/noto-sans-thai-thai.woff2') format('woff2');
  unicode-range: U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC;
}
@font-face {
  font-family: 'Noto Sans Thai';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/fonts/noto-sans-thai-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Noto Sans Thai';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/fonts/noto-sans-thai-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ═══════════════════════════════════════════════════════════════════════════
   สร้างตาม design/CLAUDE_IMPLEMENTATION.md — เป็นสัญญา ไม่ใช่ข้อเสนอแนะ
   ค่าทุกตัวยกมาจาก TOKENS.md · COMPONENTS.md · DESIGN.md · RESPONSIVE.md ตรง ๆ
   จุดที่เบี่ยงเบนโดยตั้งใจ มีรายการอยู่ท้ายหน้า (Visual QA handoff)
   ═══════════════════════════════════════════════════════════════════════════ */
:root {
  /* ── Brand ──
     ★★★★ น้ำเงิน ฮิว 211° — ค่าฮิวมาจาก **สีจริงของมาสคอต** (#0078FC) ที่วัดจากไฟล์
       ไม่ได้เลือกเฉดด้วยตา · ทุกระดับไล่หาความสว่างที่ผ่านคอนทราสต์ตามเกณฑ์
     ⚠ เดิมเป็นม่วง #6D4AFF · เปลี่ยนตามที่เจ้าของโปรเจคเคาะ (ความตั้งใจแรกคือน้ำเงิน)
     ⚠⚠ 500/600/700 ทั้งสามระดับเป็น **พื้นของปุ่ม** ตัวหนังสือขาวต้องอ่านออกทุกระดับ
       จึงเลือกจากตัวเลข ไม่ใช่จาก "ดูแล้วสวย" */
  --pf-primary-50:  #EEF6FF;
  --pf-primary-100: #DAEBFE;
  --pf-primary-200: #BCDBFD;
  --pf-primary-300: #92C4F9;
  --pf-primary-400: #58A4F6;
  --pf-primary-500: #0B6BD2;   /* ขาว 5.17 */
  --pf-primary-600: #0A5EB8;   /* ขาว 6.38 */
  --pf-primary-700: #08509D;   /* ขาว 7.93 */

  /* ── Neutral ── */
  --pf-gray-0:   #FFFFFF;
  --pf-gray-25:  #FCFCFD;
  --pf-gray-50:  #F7F8FA;
  --pf-gray-100: #F1F3F7;
  --pf-gray-200: #E4E7EC;
  --pf-gray-300: #D0D5DD;
  --pf-gray-400: #98A2B3;
  --pf-gray-500: #667085;
  --pf-gray-600: #475467;
  --pf-gray-700: #344054;
  --pf-gray-800: #1D2939;
  --pf-gray-900: #101828;

  /* ── Semantic ── */
  --pf-success-50: #ECFDF3; --pf-success-100: #D1FADF;
  --pf-success-500: #12B76A; --pf-success-600: #039855; --pf-success-700: #027A48;
  --pf-warning-50: #FFFAEB; --pf-warning-100: #FEF0C7;
  --pf-warning-500: #F79009; --pf-warning-600: #DC6803; --pf-warning-700: #B54708;
  --pf-danger-50: #FEF3F2; --pf-danger-100: #FEE4E2;
  --pf-danger-500: #F04438; --pf-danger-600: #D92D20; --pf-danger-700: #B42318;
  /* ★★★ คู่สีของ **ปุ่มแดงทึบ** (การกระทำที่ย้อนกลับไม่ได้ · กล่องยืนยันระดับ 3)
     ⚠ ห้ามใช้ `--pf-danger-500` เป็นพื้นปุ่มแล้ววางตัวขาว — วัดแล้วได้ **3.76 : 1** (ต้อง 4.5)
       `danger-500` เป็นสีของ **เส้นขอบ/ไอคอน** ไม่ใช่พื้นที่ต้องรองรับตัวหนังสือ
     ★ ค่านี้ยกมาจากชุดเดิมที่ผ่านการวัดมาแล้ว ไม่ได้เลือกใหม่ (`--danger` / `--on-danger`) */
  --pf-danger-solid: #C62828;  --pf-on-danger: #FFFFFF;   /* 5.62 : 1 */
  /* ⚠⚠⚠ สี info ย้ายจากน้ำเงินไปฟ้าอมเขียว (ฮิว 186°) **เพราะแบรนด์กลายเป็นน้ำเงิน**
     วัดแล้วถ้าปล่อยไว้ที่ #2E90FA จะห่างจากสีแบรนด์แค่ **45–48**
     ซึ่งคนแยกไม่ออกว่าอันไหน "ปุ่มของระบบ" อันไหน "ป้ายบอกข้อมูล"
     ★ ปัญหานี้ไม่ได้เกิดตอนแบรนด์เป็นม่วง เพราะม่วงกับน้ำเงินห่างกันพอ
       เปลี่ยนสีแบรนด์จึงไม่ใช่แค่แก้โทเคนแบรนด์ — ต้องดูว่ามันไปชนใครบ้าง
     ★★ 186° ห่างจากแบรนด์ สว่าง 64 · มืด 84 · และยังอ่านว่า "ข้อมูล" ได้อยู่ */
  --pf-info-50: #EAFBFD; --pf-info-100: #D0F7FB;
  --pf-info-500: #0FA4B4; --pf-info-600: #0AA4B5; --pf-info-700: #067E8B;   /* 700 บน 50 = 4.53 */

  /* ── Pastel accents ── */
  --pf-pastel-brand: #E4F1FE;   /* พาสเทลของแบรนด์ — น้ำเงิน ฮิว 211° */
  --pf-pastel-green:  #E8F8F0;
  --pf-pastel-blue:   #EAFBFD;   /* พาสเทลของสี info — ฟ้าอมเขียว ฮิว 186° */
  --pf-pastel-yellow: #FFF7E8;
  --pf-pastel-pink:   #FFF0F5;
  --pf-pastel-cyan:   #EAF9FA;
  /* ★ ม่วง — ยกมาจากแอปตอนย้ายหน้าภาพรวม (รอบ 278 · D42 ขั้น 5.2)
     ระบบดีไซน์ไม่มีม่วงมาก่อน แต่แอปใช้มาตลอดเป็นสีของกล่องไอคอน "คีย์แทนโดย HR"
     ⚠ เป็นสี **ตกแต่งเพื่อแยกการ์ดสี่ใบออกจากกัน** ไม่ใช่สีบอกสถานะ
       ห้ามเอาไปใช้กับป้ายสถานะ — ระบบมี success/warning/danger/info ครบอยู่แล้ว */
  --pf-pastel-violet: #F0EAFF;
  --pf-violet-700:    #4B2A8A;   /* 8.97 บนพาสเทลม่วง */
  /* ★ ระดับ 500 ของม่วง/ส้ม — เพิ่มเมื่อ 2026-09-04 ตอนทำแสง hover ให้ตามสีไอคอน
     ⚠ ใช้ได้ที่เดียวคือ **แสงและเส้นขอบตอน hover** (`--pf-accent`) ซึ่งผสมลงพื้นโปร่ง
       ห้ามเอาไปวางตัวหนังสือหรือทำพื้นปุ่ม — ยังไม่เคยวัดคอนทราสต์ของคู่ไหนเลย
     ★ ไม่ได้คิดสีใหม่ — เดินบันไดเดียวกับ success/warning/danger ที่มีอยู่แล้ว */
  --pf-violet-500:    #7A5AF8;
  /* ★ ส้ม — คู่สุดท้ายที่ยังขาดตอนย้ายป้ายสถานะเข้าระบบ (ขั้น 5.3-ฉ · รอบ 284)
     ใช้กับสถานะ "ถูกระงับ" ซึ่งยุบไปรวมกับเหลือง (เตือน) หรือแดง (ผิดพลาด) ไม่ได้
     — มันไม่ใช่คำเตือนและไม่ใช่ความผิดพลาด แต่เป็นสภาพที่ใครบางคนตั้งใจทำไว้
     ★ ค่านี้ยกมาจากชุดเดิมที่วัดแล้ว (`--st-hold-*`) ไม่ได้คิดสีใหม่ */
  --pf-pastel-orange: #FFF0E5;
  --pf-orange-700:    #7A3E12;   /* 7.46 บนพาสเทลส้ม */
  --pf-orange-500:    #EF6820;   /* ★ เงื่อนไขเดียวกับ --pf-violet-500 ข้างบน */

  /* ── Semantic surfaces ── */
  --pf-bg:               #F7F8FC;
  --pf-bg-secondary:     #F1F3F7;
  --pf-surface:          #FFFFFF;

  /* ★★★★ "กระดาษ" กับ "หมึก" — **เหมือนกันทั้งสองธีมโดยตั้งใจ**
     🔴 ลายเซ็นรับทราบเป็นของที่ถูก **พิมพ์ลงกระดาษ** ไม่ใช่ของที่อ่านบนจออย่างเดียว
       ถ้าปล่อยให้ตามธีม คนที่เซ็นตอนเปิดธีมมืดจะได้เส้นสีอ่อน
       แล้วพอพิมพ์ออกมาบนกระดาษขาว **ลายเซ็นจะหายไปทั้งอัน**
     ⚠ สองตัวนี้จึงไม่มีค่าคู่ในบล็อกธีมมืด — เป็นข้อยกเว้นที่ตั้งใจ ไม่ใช่ลืม
     ⚠ ห้ามเอาไปใช้กับอย่างอื่นที่ควรตามธีม · ใช้เฉพาะของที่ต้องพิมพ์ได้ */
  --pf-paper:            #FFFFFF;
  --pf-ink:              #101828;
  --pf-surface-subtle:   #FCFCFD;
  --pf-surface-hover:    #F9FAFB;
  /* ★ ลำดับชั้นพื้นผิวสี่ระดับ (DESIGN.md §3) — ธีมสว่างแยกชั้นด้วย **เงา** ไม่ใช่สีพื้น
     ⚠ ธีมมืดทำกลับกัน: เงาแทบมองไม่เห็นบนพื้นมืด จึงแยกด้วยพื้นที่สว่างขึ้นทีละชั้น
       ทั้งสองวิธีตอบโจทย์เดียวกัน แต่คนละกลไก — ห้ามลอกค่าข้ามธีม */
  --pf-surface-raised:   #FFFFFF;
  --pf-surface-floating: #FFFFFF;
  /* ★ ตัวหนังสือบนพื้นแบรนด์ และแบรนด์ที่ใช้เป็นตัวหนังสือ — แยกกันคนละหน้าที่ */
  --pf-on-primary:       #FFFFFF;   /* 5.15 บน primary-500 */
  --pf-primary-ink:      #0B67CB;   /* ขาว 5.49 · พาสเทลแบรนด์ 4.77 · primary-100 4.52 */
  --pf-border:           #E7EAF0;
  --pf-border-strong:    #D9DEE8;
  --pf-text-primary:     #101828;
  --pf-text-secondary:   #475467;
  /* ⚠⚠ เข้มขึ้นจาก #667085 (รอบ 314 · P232) — ค่าเดิมได้ **4.48** บน `--pf-bg-secondary`
     ซึ่งตกเกณฑ์ 4.5 · เจอในกล่องยืนยัน "คำนวณรอบนี้?" ของหน้าเงินเดือน
     ★ ค่าใหม่ดีขึ้น **ทุกพื้น** ไม่ใช่แค่พื้นที่ตก:
       bg 4.69→4.85 · bg-secondary 4.48→**4.64** · surface 4.97→5.15
       surface-subtle 4.85→5.02 · surface-hover 4.76→4.93
     ⚠ เลือกให้มีระยะเผื่อ ไม่เลือกค่าที่ผ่านแบบเฉียดฉิว (#647083 ได้ 4.515) */
  --pf-text-muted:       #636E81;
  --pf-text-placeholder: #98A2B3;
  /* เส้นคั่นแถวตาราง — COMPONENTS.md ระบุ #EEF0F4 */
  --pf-row-border: #EEF0F4;
  /* เส้นตารางกราฟ — DESIGN.md §8 ระบุ #E9EDF5 */
  --pf-chart-grid: #E9EDF5;

  /* ── Spacing ── */
  --pf-space-1: 4px;  --pf-space-2: 6px;  --pf-space-3: 8px;  --pf-space-4: 12px;
  --pf-space-5: 16px; --pf-space-6: 20px; --pf-space-7: 24px; --pf-space-8: 32px;
  --pf-space-9: 40px; --pf-space-10: 48px; --pf-space-11: 64px;

  /* ── Radius ── */
  --pf-radius-xs: 6px;  --pf-radius-sm: 8px;  --pf-radius-md: 10px;
  --pf-radius-lg: 14px; --pf-radius-xl: 18px; --pf-radius-2xl: 24px;
  --pf-radius-pill: 999px;

  /* ★★★★ แถบทับบนภาพ/วิดีโอ — **หน้าตาเดิมทั้งสองธีม** โดยตั้งใจ
     ⚠⚠ ใช้ `--pf-gray-900` เป็นพื้นกับ `--pf-gray-0` เป็นตัวอักษรไม่ได้ —
       สองตัวนั้น **สลับกันในธีมมืด** ⇒ แถบจะกลายเป็นพื้นสว่างจ้าทับวิดีโอ
       (`npm run theme` จับได้เอง 2026-09-01 · ข้อความของมันบอกทางแก้นี้ไว้เลย)
     ★ ของที่ทับบนภาพต้องมืดเสมอ เพราะพื้นหลังคือภาพจากกล้อง ไม่ใช่พื้นของธีม */
  --pf-scrim-bg: #101828;
  --pf-scrim-ink: #FFFFFF;

  /* ── Elevation ── */
  /* ★ เงาที่ขอบกล่องเลื่อน — บอกว่า "ยังมีเนื้อหาต่อไปทางนี้"
     ⚠ ต้องเป็นสีทึบจาง ๆ ไม่ใช่สีพื้น — สีพื้นจะกลืนไปกับการ์ดจนมองไม่เห็นอะไรเลย */
  --pf-scroll-shade: rgba(16,24,40,.13);
  --pf-shadow-0: none;
  --pf-shadow-1: 0 1px 2px rgba(16,24,40,.04), 0 4px 12px rgba(16,24,40,.04);
  --pf-shadow-2: 0 2px 4px rgba(16,24,40,.05), 0 8px 24px rgba(16,24,40,.07);
  --pf-shadow-3: 0 8px 16px rgba(16,24,40,.08), 0 20px 40px rgba(16,24,40,.10);

  /* ── Layout ── */
  --pf-sidebar-width: 248px;
  --pf-sidebar-collapsed: 72px;
  --pf-page-padding-desktop: 24px;
  --pf-page-padding-tablet: 20px;
  --pf-page-padding-mobile: 16px;
  --pf-grid-gap: 16px;
  --pf-grid-gap-mobile: 12px;
  --pf-section-gap: 24px;

  /* ── Controls ── */
  --pf-control-lg: 44px; --pf-control-md: 40px; --pf-control-sm: 36px;
  --pf-touch-target: 44px;

  /* ── Motion ── */
  --pf-duration-fast: 120ms;
  --pf-duration-standard: 160ms;
  --pf-duration-emphasis: 220ms;
  /* ★ การจางเข้าตอนเลื่อนถึง เป็นคนละเรื่องกับการตอบสนองการกด
     120–220ms คือ "ระบบรับรู้แล้ว" ต้องเร็วจนไม่รู้สึกว่ารอ
     ส่วนตัวนี้คือ "เนื้อหากำลังเข้ามา" เร็วเท่ากันแล้วจะเหมือนกระพริบ ไม่ใช่จาง */
  /* ⚠ ปรับรอบ 238 หลังเจ้าของโปรเจคดูของจริง: 520ms/14px **เร็วและเบาเกินจนแทบไม่เห็น**
     ตอนคนกำลังเลื่อนหน้าอยู่ ตาจับการเปลี่ยนแปลงได้ยากกว่าตอนหน้านิ่งมาก
     ★ ระยะขยับต้องมากพอที่จะอ่านออกว่า "ของกำลังเข้ามา" ไม่ใช่แค่ "ภาพกระพริบ" */
  --pf-duration-reveal: 760ms;
  --pf-reveal-shift: 22px;
  --pf-ease-out: cubic-bezier(.2,.8,.2,1);

  /* ── Semantic Elevation — แสงสะท้อนจากสีความหมายของการ์ดเอง ──
     ★★★★ ค่าความเข้มของแสง ไม่ใช่ค่าสี — สีมาจาก --pf-accent ที่แต่ละการ์ดตั้งเอง

     ⚠⚠⚠ ธีมมืดไม่ได้ต้องการ "ค่าเดิมแต่จางลง" — มันต้องการ **กลไกคนละอย่าง**
       ธีมสว่าง = **เงาสี** ทอดลงใต้การ์ด · มืดกว่าพื้น จึงเห็นได้
       ธีมมืด   = **แสงเปล่ง** รอบการ์ด · ต้องสว่างกว่าพื้น ไม่งั้นไม่มีอะไรให้เห็นเลย
       เพราะบนพื้นเกือบดำ ไม่เหลือที่ให้ "มืดลง" อีกแล้ว — เงาสีจึงหายไปทั้งดุ้น
     ★ จึงต้องแยกทั้ง **ความเข้ม** และ **รูปทรง** ตามธีม ไม่ใช่แค่ตัวเลขเดียว
       lift/spread ติดลบมาก = เงาหุบเข้าใต้การ์ด (สว่าง)
       lift/spread เกือบศูนย์ = แสงลามรอบตัว (มืด)
     ⚠ ตอนแรกผมตั้งธีมมืดให้ **จางกว่า** ธีมสว่าง เพราะกลัวนีออน ผลคือมองไม่เห็นเลย
       เจ้าของโปรเจคทักเอง · มีเครื่องตรวจค้ำแล้วว่าธีมมืดต้องเข้มกว่า (P179) */
  --pf-glow-hover: 26%;
  --pf-glow-focus: 20%;
  --pf-glow-edge: 34%;    /* ความเข้มของขอบตอนชี้ */
  --pf-glow-rim: 0%;      /* เส้นแสงบนขอบบน — ธีมสว่างไม่ใช้ (0% = โปร่งสนิท) */
  --pf-glow-lift: 10px;   /* ระยะที่แสงทอดลงข้างล่าง */
  --pf-glow-spread: -10px;/* ติดลบมาก = หุบเข้าใต้การ์ด */
  /* ⚠⚠ ค่านี้ย้อม **พื้นการ์ด** ไม่ใช่แค่เงา — ตัวอักษรจึงถูกกระทบด้วย
     8% ทำให้ตัวอักษรจางบนการ์ดที่เลือกเหลือ 4.45 (ต้อง 4.5) · เครื่องตรวจจับได้
     ★ สถานะที่ถูกเลือกต้องอ่านง่ายกว่าปกติ ไม่ใช่ยากกว่า */
  --pf-glow-selected: 5%;
  --pf-glow-ring: 42%;
  /* สีความหมายประจำการ์ด — ค่าเริ่มต้นคือแบรนด์ · ทับด้วย .pf-accent--* */
  --pf-accent: var(--pf-primary-500);

  /* ── กรอบของเครื่องมือ (แถบสาธิต) — ต้องหน้าตาเดิมทั้งสองธีม ──
     ★ สเกลเทากลับหัวระหว่างธีม การอ้าง gray-900/gray-0 ตรง ๆ จึงพลิกแถบเป็นสีขาว
       โทเคนคู่นี้ชี้ไป "ปลายเข้ม" กับ "ปลายสว่าง" ของธีมนั้น ๆ แทน — ผลจึงคงที่ */
  --pf-chrome-bg:  var(--pf-gray-900);
  --pf-chrome-ink: var(--pf-gray-0);

  /* ── เวทีกล้อง ──
     ★★★ ภาพจากกล้องเป็นภาพจริง ไม่ใช่พื้นผิวของธีม — จึงต้องมืดเท่ากันทั้งสองธีม
       (บทเรียน P183: อย่าอ้างสเกลเทา แล้วปล่อยให้มันกลับหัวตอนสลับธีม)
     ⚠ ตัวหนังสือบนเวทีนี้จึงต้องสว่างเสมอ ไม่ว่าผู้ใช้จะตั้งธีมอะไรไว้ */
  --pf-scan-stage: #0A0C10;
  --pf-scan-ink:   #FFFFFF;
  --pf-scan-ink-2: #B8C0CC;

  /* ── เวทีหน้าเข้าสู่ระบบ + ชื่อแบรนด์ (ยกมาจากแอป รอบ 270 · D42 ขั้น 3.1) ──
     ★ เวทีเป็นพื้นไล่เฉดที่มี **ทิศแสง** — จุดที่แสงตกกับเงาของฉากเป็นคนละค่า
       ใช้สีเดียวไล่ opacity ไม่ได้ เพราะจะได้เทาแทนที่จะได้ฟ้าที่มีมิติ
     ⚠⚠ `--pf-logo-flow` **ไม่ใช่** `--pf-primary-500` และไม่ใช่ `--pf-primary-ink`
       primary-500  ธีมสว่างผ่าน แต่ธีมมืดวัดได้ 3.40 — มันคือสีพื้น ไม่ใช่สีหมึก
       primary-ink  เข้มจนอ่านว่า "กรมท่า" บนการ์ดขาว ไม่ใช่สีแบรนด์
       logo-flow    สว่าง 5.13 · มืด 5.31 — ผ่านทั้งคู่ และยังสดพอเป็นสีแบรนด์
       ★ นี่คือ P189-ข ในรูปเดิม: เอาโทเคนที่ออกแบบมาเป็น *พื้น* ไปใช้เป็น *ตัวอักษร* */
  --pf-stage-lit: #FBFDFF;
  --pf-stage-dim: #E3EEFC;
  --pf-logo-flow: #0B6BD2;

  --pf-font: "Noto Sans Thai", "Noto Sans", system-ui, -apple-system, "Segoe UI", sans-serif;

  /* ── สเกลตัวอักษร (PHASE 2) ─────────────────────────────────────────
     ★★★★ ความสูงบรรทัด **วัดจากไฟล์ฟอนต์จริง** ไม่ได้ลอกจากสเปก
       Noto Sans Thai: usWinAscent 1.061 em + usWinDescent 0.450 em = 1.511 em
       คือกล่องบรรทัดขั้นต่ำที่เบราว์เซอร์ใช้ · เตี้ยกว่านี้ = ตัวอักษรล้นกล่อง
       แล้ววรรณยุกต์/สระบนจะโดนตัดทุกที่ที่มี overflow:hidden
     ⚠⚠ สเปกให้ค่ามาจากภาษาอังกฤษ (1.33 เท่า) ซึ่ง **5 ใน 7 ขนาดเตี้ยเกินไป**
       ขนาดตัวอักษรคงเดิมทุกตัว — เปลี่ยนเฉพาะความสูงบรรทัด (เจ้าของโปรเจคเคาะแล้ว)
     ★ ตรวจซ้ำได้ด้วย  node tools/font-metrics.js site/fonts/noto-sans-thai-thai.woff2 */
  --pf-lh-ratio: 1.511;

  --pf-size-caption: 12px;   --pf-lh-caption: 19px;   /* สเปก 18 · ต้อง 18.1 */
  --pf-size-compact: 13px;   --pf-lh-compact: 20px;   /* สเปก 20 · ต้อง 19.6 ✓ ตามสเปก */
  --pf-size-body:    14px;   --pf-lh-body:    22px;   /* สเปก 22 · ต้อง 21.2 ✓ ตามสเปก */
  --pf-size-card:    14px;   --pf-lh-card:    22px;   /* สเปก 20 · ต้อง 21.2 */
  --pf-size-section: 16px;   --pf-lh-section: 25px;   /* สเปก 24 · ต้อง 24.2 */
  --pf-size-page:    24px;   --pf-lh-page:    37px;   /* สเปก 32 · ต้อง 36.3 */
  --pf-size-kpi:     28px;   --pf-lh-kpi:     43px;   /* สเปก 34 · ต้อง 42.3 */
  --pf-size-kpi-m:   23px;   --pf-lh-kpi-m:   35px;   /* มือถือ · ต้อง 34.8 */

  --pf-w-regular: 400; --pf-w-medium: 500; --pf-w-semibold: 600; --pf-w-bold: 700;

  color-scheme: light;
}

/* ═══════════════════════════════════════════════════════════════════════════
   DARK THEME — ชั้นโทเคน ไม่ใช่ระบบดีไซน์ชุดที่สอง

   ★★★★ กติกาที่ยึดตลอดบล็อกนี้
     ① ประกาศทับเฉพาะ **ค่าโทเคน** — ไม่มีกฎ CSS ของคอมโพเนนต์แม้แต่ข้อเดียว
        คอมโพเนนต์ทุกตัวจึงเปลี่ยนตามเองโดยไม่ต้องรู้ว่ามีธีมมืดอยู่
     ② ธีมสว่างไม่ถูกแตะเลยสักบรรทัด
     ③ พื้นมืดเป็นเทาอมน้ำเงินซ้อนชั้น **ไม่ใช่ดำสนิท**

   ⚠⚠⚠ ค่าที่สเปกให้มา 15 ใน 25 คู่วัดแล้วไม่ผ่านเกณฑ์อ่านออก
     ที่แก้มี 5 กลุ่ม — เขียนตัวเลขกำกับไว้ทุกจุด
     ที่ **ไม่แก้** เพราะวัดผิดเกณฑ์เอง: พื้นผิวซ้อนชั้นไม่ต้องได้ 3:1 ระหว่างกัน
       (ความแยกมาจากขอบและเงา ไม่ใช่จากคอนทราสต์ของพื้น) และเส้นคั่นตกแต่งก็เช่นกัน
   ═══════════════════════════════════════════════════════════════════════════ */
:root[data-theme="dark"] {
  color-scheme: dark;

  /* ── ① พื้น · ② ลำดับชั้นพื้นผิว (ตามสเปกทุกค่า) ── */
  --pf-bg:              #0B0D12;   /* App Background */
  --pf-bg-secondary:    #10131A;   /* Secondary Background */
  --pf-surface:         #151922;   /* Surface */
  --pf-surface-raised:  #1B202B;   /* Elevated Surface */
  --pf-surface-floating:#222835;   /* Floating Surface */
  /* แมปกับชื่อที่ระบบใช้อยู่แล้ว — ไม่ต้องแก้คอมโพเนนต์สักตัว */
  --pf-surface-subtle:  #10131A;
  --pf-surface-hover:   #1B202B;

  /* ── ③ ลำดับชั้นตัวอักษร ── */
  --pf-text-primary:    #F5F7FA;   /* สเปก · 16.39 บนการ์ด */
  --pf-text-secondary:  #B8C0CC;   /* สเปก ·  9.59 บนการ์ด */
  /* ⚠ แก้จากสเปก #7D8796 — ผ่านบนการ์ด (4.84) แต่ตกบนชั้นยก (4.49) และกล่องลอย (4.06)
     กล่องลอยคือที่อยู่ของ dropdown และ tooltip · สีที่ผ่านบ้างไม่ผ่านบ้างคือสีที่จะหลุด */
  --pf-text-muted:      #868F9D;   /* 5.38 / 4.99 / 4.52 บน การ์ด / ชั้นยก / กล่องลอย */
  /* ⚠ แก้จากสเปก #687282 — 3.62 บนการ์ด · placeholder นับเป็นตัวอักษรตามเกณฑ์ */
  --pf-text-placeholder:#88909C;   /* 5.46 / 5.06 / 4.58 */
  /* ★ ตัวที่กดไม่ได้ 2.45 — **ไม่แก้** เกณฑ์ยกเว้นให้ และของที่กดไม่ได้ควรดูจางจริง */
  --pf-text-disabled:   #505866;

  /* ── ④ เส้นขอบ ── */
  --pf-border:          #293140;   /* สเปก Default — เส้นคั่นตกแต่ง ไม่ต้อง 3:1 */
  --pf-border-subtle:   #202633;   /* สเปก Subtle */
  /* ⚠ แก้จากสเปก #353E4D (1.63 บนการ์ด) — ตัวนี้คือ **ขอบช่องกรอก**
     ช่องว่าง ๆ ที่ไม่มีขอบ = มองไม่เห็นว่ามีของอยู่ตรงนั้น จึงต้องได้ 3:1 ทุกพื้น */
  --pf-border-strong:   #6C727D;   /* 3.64 / 3.37 / 3.05 บน การ์ด / ชั้นยก / กล่องลอย */
  --pf-row-border:      #202633;
  --pf-chart-grid:      #232A38;

  /* ── ⑤ แบรนด์ ──
     ★★★ ตัวหนังสือบนปุ่มหลักเป็น **สีขาว** ทั้งสองธีม (เจ้าของโปรเจคเคาะ 2026-08-21)
       ปุ่มหลักคือปุ่มเดียวกันในสองสภาพแสง ไม่ใช่ปุ่มคนละตัว — ตัวหนังสือจึงไม่ควรสลับสี

     ⚠⚠ ผลคือ **พื้นแบรนด์ต้องเข้มลง** เพราะขาวบนม่วงของสเปก (#7C5CFF) ได้แค่ 4.35
       และยิ่ง hover ที่สเปกให้สว่างขึ้น (#8B70FF) ยิ่งแย่ — เหลือ 3.58
     ★ จึงไล่เฉดใหม่บนเส้นสีเดิม โดยให้ **hover เป็นเฉดที่สว่างที่สุดที่ขาวยังผ่าน**
       ได้ทั้ง "ชี้แล้วสว่างขึ้น" ตามสัญชาตญาณ และตัวหนังสือขาวที่อ่านออกทุกสถานะ */
  --pf-primary-500: #166DCA;   /* ปกติ · ขาว 5.14 */
  --pf-primary-600: #1175E0;   /* ชี้ · ขาว 4.53 — สว่างสุดที่ยังผ่าน */
  --pf-primary-700: #1461B3;   /* กด · ขาว 6.21 */
  --pf-on-primary:  #FFFFFF;
  /* ⚠ แบรนด์ที่ใช้เป็น **ตัวหนังสือ** บนพื้นมืด (ลิงก์ · เมนูที่เลือก) ต้องสว่างกว่า fill
     #7C5CFF เป็นตัวหนังสือได้แค่ 4.05 บนการ์ด และ 3.51 บนพื้นอ่อนของแบรนด์ */
  --pf-primary-ink: #2B8FFA;   /* การ์ด 5.36 · พื้นแอป 5.93 · กล่องลอย 4.50 · พาสเทลแบรนด์ 4.73 */
  --pf-primary-50:  #0D1A2C;   /* = แบรนด์ 14% ทับพื้นแอป (สเปก Primary Soft) */
  --pf-primary-100: #15253A;   /* = แบรนด์ 14% ทับการ์ด */
  --pf-primary-200: #152C47;
  --pf-primary-300: #92C4F9;
  --pf-primary-400: #6EB0F7;
  --pf-pastel-brand: #15253A;

  /* ── ⑥ สีความหมาย · ⑦ พื้นอ่อนของสีความหมาย ──
     ★ สเปกไม่ได้ให้ค่ามา (บอกแค่ว่าต้องมี) — เลือกจากบันไดสีเดิมของธีมสว่าง
       ระดับ 400 คือระดับที่อ่านออกบนพื้นมืดโดยไม่แสบตา (วัดแล้วทุกตัว ≥ 6.3)
     ⚠ พื้นอ่อนต้องเป็นสีเข้มอมสีนั้น ไม่ใช่พาสเทลของธีมสว่าง (จะสว่างจ้าจนอ่านไม่ได้) */
  --pf-success-700: #32D583;  --pf-success-600: #32D583;  --pf-success-500: #12B76A;
  --pf-success-50:  #10261C;  --pf-success-100: #16351F;
  --pf-warning-700: #FDB022;  --pf-warning-600: #FDB022;  --pf-warning-500: #F79009;
  --pf-warning-50:  #2A2110;  --pf-warning-100: #3A2D12;
  --pf-danger-700:  #F97066;  --pf-danger-600:  #F97066;  --pf-danger-500:  #F04438;
  --pf-danger-50:   #2C1618;  --pf-danger-100:  #3E1D20;
  /* ★ ธีมมืดใช้ **ตัวเข้มบนแดงอ่อน** ไม่ใช่ขาวบนแดงเข้ม — ขาวบนแดงเข้มในธีมมืด
     จะสว่างกว่าพื้นหลังรอบ ๆ จนปุ่มลบเด่นกว่าทุกอย่างในหน้า ทั้งที่ควรกดยาก */
  --pf-danger-solid: #FF8A8A;  --pf-on-danger: #251013;   /* 7.96 : 1 */
  --pf-info-700:    #0AAFC2;  --pf-info-600:    #0AAFC2;  --pf-info-500:    #11C1D4;
  --pf-info-50:     #0B2E36;  --pf-info-100:    #134450;

  /* พาสเทลของชิปไอคอน — ธีมมืดใช้พื้นเข้มอมสี */
  --pf-pastel-green:  #10261C;
  --pf-pastel-blue:   #101F30;
  --pf-pastel-yellow: #2A2110;
  --pf-pastel-pink:   #2C1618;
  --pf-pastel-cyan:   #0F2628;
  --pf-pastel-violet: #2C2045;
  --pf-violet-700:    #DDC9FF;   /* 9.91 บนพาสเทลม่วงธีมมืด */
  --pf-pastel-orange: #3D2819;
  --pf-orange-700:    #FFD0A8;   /* 9.79 บนพาสเทลส้มธีมมืด */
  /* ★ ระดับ 500 ไม่เปลี่ยนตามธีม เหมือน success/warning/danger-500 ที่ประกาศไว้ข้างบน
     — มันไม่ใช่สีของตัวหนังสือหรือพื้น จึงไม่ต้องไล่เฉดใหม่ให้พื้นมืด */
  --pf-violet-500:    #7A5AF8;
  --pf-orange-500:    #EF6820;

  /* ── ⑧ เงา ──
     ⚠ เงาสีดำโปร่งบนพื้นมืดแทบมองไม่เห็น — ธีมมืดจึงแยกชั้นด้วย **พื้นผิวที่สว่างขึ้น**
       เงาเหลือไว้ให้ของที่ลอยจริง ๆ และเข้มกว่าธีมสว่างมาก */
  /* ⚠ ธีมมืดต้องเข้มกว่ามาก — เงาจาง ๆ บนพื้นเข้มคือมองไม่เห็น */
  --pf-scroll-shade: rgba(0,0,0,.55);
  --pf-shadow-1: 0 1px 2px rgba(0,0,0,.40);
  --pf-shadow-2: 0 2px 4px rgba(0,0,0,.44), 0 8px 24px rgba(0,0,0,.40);
  --pf-shadow-3: 0 8px 16px rgba(0,0,0,.50), 0 20px 40px rgba(0,0,0,.46);

  /* ── ⑨ ช่องกรอก · ⑩ ตาราง · ⑪ กราฟ · ⑫ tooltip/popover ──
     ★ ทั้งสี่กลุ่มไม่มีโทเคนของตัวเอง — ใช้ตัวข้างบนร่วมกัน (เจตนา ไม่ใช่ตกหล่น)
       เพิ่มโทเคนเฉพาะกลุ่ม = คนต้องจำว่ากลุ่มไหนใช้ตัวไหน แล้ววันหนึ่งจะไม่ตรงกัน
     ── ⑬ เลือก/ชี้/กด · ⑭ โฟกัส · ⑮ กดไม่ได้ ── */
  --pf-gray-0:   #151922;   /* "ขาว" ของธีมมืด = พื้นการ์ด */
  --pf-gray-25:  #10131A;
  --pf-gray-50:  #1B202B;   /* พื้นตอนชี้ */
  --pf-gray-100: #232A38;   /* พื้นแถบวัด · ป้ายกลาง */
  --pf-gray-200: #293140;
  --pf-gray-300: #353E4D;
  --pf-gray-400: #6C727D;
  --pf-gray-500: #868F9D;
  --pf-gray-600: #B8C0CC;
  --pf-gray-700: #D5DBE4;   /* ★ ตัวหนังสือบนปุ่มรอง — ต้องสว่าง ไม่ใช่ #344054 ของธีมสว่าง */
  --pf-gray-800: #E8ECF2;
  --pf-gray-900: #F5F7FA;

  /* ⚠⚠⚠ ค่าชุดนี้เคยตั้งไว้ **ต่ำกว่า** ธีมสว่าง (18/14/55) ด้วยเหตุผลที่ฟังดูดี
       ว่า "แสงสีบนพื้นมืดเด่นกว่า" — ซึ่งจริงกับ **แสง** แต่ที่เราวาดคือ **เงา**
       เงาสีบนพื้นเกือบดำไม่มีที่ให้มืดลงอีก → หายไปทั้งดุ้น มองไม่เห็นเลยจริง ๆ
     ★ ธีมมืดจึงต้องกลับด้าน: เข้มขึ้น + ลามรอบตัว (spread เกือบศูนย์) + มีเส้นแสงขอบบน
       ผลคือมันกลายเป็น "ของที่เปล่งแสง" แทน "ของที่ทอดเงา" ซึ่งคือสิ่งที่ตาคาดหวังบนจอมืด */
  --pf-glow-hover: 46%;
  --pf-glow-focus: 56%;
  --pf-glow-edge: 58%;
  --pf-glow-rim: 30%;
  --pf-glow-lift: 3px;
  --pf-glow-spread: -1px;
  /* ⚠ เหตุผลเดียวกับธีมสว่าง — 12% ทำให้ตัวอักษรจางบนการ์ดสีเหลืองเหลือ 4.47 */
  --pf-glow-selected: 8%;
  --pf-glow-ring: 55%;

  /* ★ เวทีธีมมืด — ไล่จาก "น้ำเงินเข้มที่มีแสง" ไปหา "น้ำเงินเข้มที่ไม่มีแสง"
     ⚠⚠ ห้ามใช้ค่าของธีมสว่าง — พาเนลสว่างในธีมมืดจะกลายเป็นแผงไฟกลางจอ
     ★ "Flow" ธีมมืดใช้เฉดหมึกสว่าง — เฉดพื้นจมหายไปกับการ์ดมืด (วัดได้ 3.40) */
  --pf-stage-lit: #1B2A3D;
  --pf-stage-dim: #101823;
  --pf-logo-flow: #2B8FFA;

  /* ★ กลับด้านการอ้าง เพื่อให้ **ผลลัพธ์** เหมือนธีมสว่าง: แถบเข้ม ตัวอักษรสว่าง */
  --pf-chrome-bg:  var(--pf-gray-0);
  --pf-chrome-ink: var(--pf-gray-900);
}

/* ⚠⚠ ทำไมไม่มี `@media (prefers-color-scheme: dark)` ที่นี่
     ถ้าใส่ ต้องประกาศโทเคนทั้งชุดซ้ำอีกรอบ — แล้ววันหนึ่งสองชุดจะไม่ตรงกัน
     ★ ให้ JS อ่านค่าที่ระบบปฏิบัติการตั้งไว้ แล้วเซ็ต `data-theme` ให้แทน
       ผลลัพธ์เหมือนกัน แต่มีโทเคนชุดเดียวในไฟล์ (ดู pf-shell.js) */

*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; }
/* ⚠⚠ `<figure>` มีระยะขอบปริยายของเบราว์เซอร์ **40px ซ้ายขวา**
   ในกริดกว้าง 291px จึงเหลือที่ให้ลูกแค่ 211px — เนื้อหาในเฟรมมือถือเลยเลื่อนแนวนอนได้
   ★ ไม่มีใครเห็น เพราะดูเผิน ๆ เหมือน "ระยะห่างที่ตั้งใจ" · เจอตอนไล่วัดทีละชั้น
   ⚠ `figcaption` ไม่มีระยะขอบปริยาย แต่ใส่ไว้ด้วยเพื่อกันเบราว์เซอร์ที่ต่างออกไป */
figure, figcaption { margin: 0; }
body {
  background: var(--pf-bg); color: var(--pf-text-primary);
  font-family: var(--pf-font); font-size: var(--pf-size-body); line-height: var(--pf-lh-body); font-weight: 400;
  -webkit-font-smoothing: antialiased;
}
button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; border: 0; background: none; }
a { color: inherit; text-decoration: none; }
table { border-collapse: collapse; width: 100%; }
svg { display: block; }
:focus-visible { outline: 2px solid var(--pf-primary-500); outline-offset: 2px; }

/* ═══ ลิงก์ที่เป็นลิงก์จริง ๆ — `.pf-link` (เพิ่ม 2026-08-31 · ปิด P24) ═══════
   ★★★★ ทำไมต้องมีคลาส ไม่ตั้งสีให้ `a` ตรง ๆ
     `a { color: inherit }` ข้างบน **ถูกแล้ว** เพราะลิงก์ส่วนใหญ่ในระบบนี้
     ไม่ใช่คำในประโยค แต่เป็นทั้งแถวเมนู ทั้งการ์ด ทั้งปุ่มล่างจอ ซึ่งมีสีของตัวเอง
     ตั้งสีให้ `a` ทุกตัว = ต้องไปเขียนทับกลับที่ทุกที่เหล่านั้น แล้ววันหนึ่งจะลืมสักที่
   ⚠⚠ ผลข้างเคียงที่ปล่อยไว้จนถึงวันนี้: ลิงก์ที่ **เป็นคำในประโยคจริง ๆ**
     กลืนไปกับข้อความรอบข้างสนิท · วัด 2026-08-31 เจอ 10 จุด และ 8 จุด
     อยู่ในหน้าที่คน **ยังไม่ล็อกอิน** เห็น — "ลืมรหัสผ่าน?" ที่มองไม่เห็น
     คือคนที่กลับเข้าระบบไม่ได้เลย

   ★★★ ทำไมขีดเส้นใต้ด้วย ไม่ใช้แค่สี
     สีอย่างเดียวไม่ผ่าน WCAG 1.4.1 (ห้ามใช้สีเป็นตัวบอกความหมายอย่างเดียว)
     และคนตาบอดสีจะไม่เห็นความต่างเลย
   ★ `--pf-primary-ink` คือโทเคนที่ตั้งไว้เพื่อการนี้อยู่แล้ว — คอมเมนต์ของมันเขียนเอง
     ว่า "แบรนด์ที่ใช้เป็นตัวหนังสือ (ลิงก์ · เมนูที่เลือก)" · วัดคอนทราสต์ไว้แล้วสองธีม */
.pf-link {
  color: var(--pf-primary-ink);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
  border-radius: var(--pf-radius-xs);
}
/* ⚠ ชี้แล้วต้องเปลี่ยน **ความหนาเส้น** ไม่ใช่เปลี่ยนสีอย่างเดียว
   เพราะโทเคนนี้มีเฉดเดียวต่อธีม และการหรี่สีลงทำให้คอนทราสต์ตกใต้เกณฑ์ */
.pf-link:hover { text-decoration-thickness: 2px; }

/* ═══ แถบเลื่อน — ★★★★ ผูกกับธีม ทั้งระบบ (เจ้าของเลือกแบบ ③ · รอบ 262)
     เทียบสี่แบบไว้ที่ `site/design/scrollbar.html` แล้วเลือกแบบ "ผูกกับธีม เห็นตลอด"
     เหตุผลที่ไม่เอาแบบ "จางจนกว่าจะชี้": แถบเมนูยาวเกินจอจริง
     คนเปิดครั้งแรกต้องรู้ทันทีว่ายังมีเมนูอีก · แบบจางซ่อนข้อมูลนั้นไว้จนกว่าจะเอาเมาส์ไปวาง
     ซึ่งคนใช้ทัชสกรีนหรือคีย์บอร์ดจะไม่มีวันเห็นเลย

   🔴 บล็อกนี้ **หายไปตอนรื้อ UI** (D42 ขั้น 2) เพราะมันอยู่ใน `_components.scss`
     ที่ถูกลบทั้งไฟล์ · ผลคือธีมมืดกลับไปได้แถบสีอ่อนของ OS อีกครั้ง
     ซึ่งคือสิ่งที่เจ้าของทักมาตั้งแต่รอบ 262 · เอากลับมาแล้วพร้อมโทเคน `--pf-*`
     ⚠ เครื่องตรวจที่เฝ้าเรื่องนี้อ่านไฟล์ที่ถูกลบ จึงตายก่อนถึงข้อ ไม่ได้ฟ้อง (P279)

   ⚠⚠ ต้องเขียน **สองชุด** เพราะเบราว์เซอร์คนละของกัน
     `scrollbar-color` = มาตรฐาน (Firefox อ่านเฉพาะตัวนี้)
     `::-webkit-scrollbar-*` = Chrome/Safari/Edge — และเป็นทางเดียวที่คุมความโค้งได้
     ⚠ ตัดชุดใดชุดหนึ่งทิ้ง = ธีมมืดบนเบราว์เซอร์ฝั่งนั้นกลับไปได้แถบสีอ่อนของ OS เหมือนเดิม

   ⚠⚠⚠ ขอบของหัวแถบเป็น **transparent + background-clip: padding-box**
     ห้ามใส่สีพื้นลงไปตรง ๆ — ตัวเลื่อนอยู่บนพื้นหลายสี (พื้นแถบข้าง · พื้นหน้า · พื้นกล่องซ้อน)
     ใส่สีตายตัวเมื่อไหร่ จะมีพื้นที่หนึ่งได้กรอบสีผิดรอบตัวเลื่อน
     แล้วไม่มีใครเห็นจนกว่าจะเปิดหน้านั้นพอดี

   ⚠ ใช้ `--pf-border-strong` ไม่ใช่ `--pf-border` — ตัวหลังจางเกินจนแทบมองไม่เห็นบนพื้นขาว
     (มันมีไว้เป็นเส้นคั่นตกแต่ง ไม่ได้ถูกวัดให้เห็นได้ในฐานะตัวควบคุม) */
* { scrollbar-width: thin; scrollbar-color: var(--pf-border-strong) transparent; }
*::-webkit-scrollbar { inline-size: 10px; block-size: 10px; }
*::-webkit-scrollbar-track { background-color: transparent; }
*::-webkit-scrollbar-thumb {
  background-color: var(--pf-border-strong);
  border-radius: var(--pf-radius-pill); border: 3px solid transparent; background-clip: padding-box;
}
*::-webkit-scrollbar-thumb:hover { background-color: var(--pf-text-muted); }
/* มุมที่แถบแนวตั้งกับแนวนอนมาบรรจบกัน — ปล่อยไว้จะได้สี่เหลี่ยมสีเทาของ OS */
*::-webkit-scrollbar-corner { background-color: transparent; }

/* ── Typography scale (DESIGN.md §6 + ความสูงบรรทัดที่วัดจากฟอนต์จริง) ── */
.pf-page-title    { font-size: var(--pf-size-page);    line-height: var(--pf-lh-page);    font-weight: var(--pf-w-bold);     margin: 0; letter-spacing: -.01em; }
.pf-section-title { font-size: var(--pf-size-section); line-height: var(--pf-lh-section); font-weight: var(--pf-w-semibold); margin: 0; }
.pf-card-title    { font-size: var(--pf-size-card);    line-height: var(--pf-lh-card);    font-weight: var(--pf-w-semibold); margin: 0; }
.pf-body          { font-size: var(--pf-size-body);    line-height: var(--pf-lh-body);    font-weight: var(--pf-w-regular); }
.pf-body-compact  { font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact); font-weight: var(--pf-w-regular); }
.pf-caption       { font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption); font-weight: var(--pf-w-regular); color: var(--pf-text-muted); }
/* ═══ แผนที่ปักหมุด (pf-map*) ═══════════════════════════════════════════
   ★★★★ MapLibre **ไม่กำหนดความสูงให้กล่องแผนที่** — ลืมแล้วได้กล่องสูง 0px
     แล้วงงว่าทำไมแผนที่ไม่ขึ้น (คู่มือของระบบเดิมเตือนไว้เองเป็นข้อที่ 4)
   ⚠ สไตล์ของตัวไลบรารีเอง (ปุ่มซูม · เครดิต · หมุด) อยู่ใน `maplibre-gl.css`
     ที่นำเข้าใน `styles.scss` — ที่นี่มีเฉพาะกล่องที่ **เราเป็นเจ้าของ** */
.pf-map {
  position: relative; overflow: hidden;
  border: 1px solid var(--pf-border); border-radius: var(--pf-radius-md);
  background: var(--pf-surface-subtle);
  block-size: 360px;
}
/* ★ จอแคบเตี้ยลง — ฟอร์มทั้งอันต้องยังเลื่อนอ่านได้ในโมดัล ไม่ใช่ให้แผนที่กินทั้งจอ */
@media (max-width: 640px) { .pf-map { block-size: 240px; } }
.pf-map-canvas { inline-size: 100%; block-size: 100%; }
/* ผ้าคลุมตอนกำลังโหลดหรือเปิดไม่ได้ — ★ ต้องอ่านออกทั้งสองธีม */
.pf-map-veil {
  position: absolute; inset: 0; display: grid; place-items: center;
  padding: var(--pf-space-5); background: var(--pf-surface-subtle);
}
.pf-map-acts {
  display: flex; align-items: center; gap: var(--pf-space-3);
  flex-wrap: wrap; margin-block-start: var(--pf-space-3);
}

/* ★★★ ข้อความเล็กที่ **เตือน** ไม่ใช่แค่ประกอบ — ใช้ในช่องตารางและใต้ช่องกรอก
   ⚠⚠ `.pf-caption` เปล่า ๆ เป็นสีจาง ซึ่งอ่านว่า "ข้อมูลเสริม ไม่ต้องสนใจก็ได้"
     พอเอาไปใช้บอกว่า "ตั้งรัศมีแคบไปจนพนักงานสแกนไม่ผ่าน" มันจะจมหายไปกับพื้น
   ⚠ ของเดิมใน ui-v1 ใช้คลาสชื่อ `warn-txt` ซึ่ง **ไม่มีอยู่ใน pf.css เลย**
     = ข้อความนั้นเป็นสีจางมาตลอดโดยไม่มีใครรู้ (ตระกูล P205 — คลาสที่มีแต่ชื่อ)
   ★ ไม่ทำเป็นกล่องมีพื้นเหมือน `.pf-note` โดยตั้งใจ — ในช่องตารางมันจะกินที่จนแถวสูงผิดรูป */
.pf-caption--warn { color: var(--pf-warning-700); }
.pf-kpi-value     { font-size: var(--pf-size-kpi);     line-height: var(--pf-lh-kpi);     font-weight: var(--pf-w-bold); letter-spacing: -.02em; }
.pf-num           { font-variant-numeric: tabular-nums; }

/* ═══ แถบสาธิต (มีเฉพาะหน้านี้ ไม่ใช่ส่วนหนึ่งของระบบดีไซน์) ═══ */
.demo-bar {
  position: sticky; top: 0; z-index: 90;
  display: flex; align-items: center; gap: var(--pf-space-4); flex-wrap: wrap;
  height: 44px; padding: 0 var(--pf-space-7);
  /* ★★★★ แถบสาธิตต้องเป็นแถบเข้ม **ทั้งสองธีม** — มันคือกรอบของเครื่องมือ ไม่ใช่ตัวผลิตภัณฑ์
     ⚠⚠⚠ เดิมเขียน `background: --pf-gray-900; color: --pf-gray-0` ซึ่งคือการ **ใช้สเกลเทากลับด้าน**
       ธีมสว่าง: 900 คือปลายเข้ม · 0 คือขาว → แถบเข้ม ตัวอักษรขาว ถูกต้อง
       ธีมมืด:  สเกลเทา **กลับหัว** ทั้งสเกล
                แถบจึงกลายเป็น **สีขาวสว่างจ้า** อยู่บนสุดของหน้าจอมืด
     ★ วัดได้ ธีมมืด พื้น rgb(245,247,250) ตัวอักษร rgb(21,25,34) — สลับขั้วกับธีมสว่างสนิท */
  background: var(--pf-chrome-bg); color: var(--pf-chrome-ink);
  font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact);
}
.demo-bar b { font-weight: 600; }
.demo-bar .sp { flex: 1; }
.demo-bar a { color: var(--pf-primary-300); font-weight: 600; }

/* ═══ App shell ═══ */
.pf-shell { display: grid; grid-template-columns: var(--pf-sidebar-width) minmax(0, 1fr); }

.pf-sidebar {
  /* ★★★★ `--pf-shell-top` = ความสูงของสิ่งที่อยู่เหนือเปลือก
     ค่าปริยาย 44px คือ **แถบสาธิต** ของ `/design/` ซึ่งมีเฉพาะในแกลเลอรี
     ⚠⚠ แอปจริงไม่มีแถบนั้น — ถ้าใช้ 44px ตามเดิม แถบข้างจะเตี้ยไป 44px
       และมีช่องว่างเปล่าอยู่บนซ้ายของทุกหน้า (วัดจริง 2026-08-26: แถบข้าง 856px
       ในจอสูง 900px และเริ่มที่ y=44) · `.pf-shell--app` ตั้งเป็น 0 แทน */
  position: sticky; top: var(--pf-shell-top, 44px); align-self: start;
  height: calc(100vh - var(--pf-shell-top, 44px));
  /* ⚠ `min-width: 0` — แถบข้างเป็นลูกของกริดและมี `overflow: auto`
     ค่าปริยาย `min-width: auto` ทำให้มันยุบต่ำกว่าเนื้อหาไม่ได้ แล้วดันคอลัมน์ให้กว้าง
     เห็นชัดตอนย่อเป็น 72px ที่ ≤1023px — ชื่อเมนูที่ยาวจะดันแถบออก (P230) */
  /* ⚠⚠ `overflow: hidden` **ไม่ใช่ auto** (แก้ 2026-09-02)
     🔴 เดิมทั้งแถบเลื่อนเป็นก้อนเดียว ⇒ กล่องท้ายแถบไหลตามเมนูลงไป
       ต้องเลื่อนจนสุดถึงจะเห็น · เจ้าของสั่งว่า "ควรลอยเป็น footer เหนือเมนู เห็นตลอดเวลา"
     ★ ตัวที่เลื่อนคือ `.pf-nav` เท่านั้น — ท้ายแถบจึงตรึงอยู่กับที่โดยไม่ต้องใช้ position */
  min-width: 0; overflow: hidden; background: var(--pf-surface); border-right: 1px solid var(--pf-border);
  display: flex; flex-direction: column;
}
.pf-logo {
  height: 72px; flex: none; display: flex; align-items: center; gap: var(--pf-space-4);
  padding: 0 var(--pf-space-5); border-bottom: 1px solid var(--pf-border);
}
/* ★★★ ตราโลโก้จริง — เดิมเป็นสี่เหลี่ยมมนสีแบรนด์ที่มีตัวอักษร "P" อยู่ข้างใน
   ⚠ ตัวอักษรใน HTML ยังอยู่เพื่อให้ยังอ่านได้ถ้าโหลดรูปไม่สำเร็จ
     แต่ซ่อนด้วย font-size: 0 เพราะตราจริงมีสีของตัวเอง (น้ำเงิน + จุดเขียว)
     วางบนสี่เหลี่ยมสีแบรนด์แล้วจะจมหายไปกับพื้น */
.pf-logo-mark {
  width: 36px; height: 36px; flex: none;
  background: url("/brand/logo-mark.webp") center / contain no-repeat;
  font-size: 0;
}
/* ⚠ 15px ต้องได้บรรทัด 22.7 → 23 (สเกลไม่มีขนาดนี้ · ใช้เฉพาะโลโก้) */
.pf-logo-name { font-size: 15px; line-height: 23px; font-weight: var(--pf-w-bold); }
.pf-logo-org { font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption); color: var(--pf-text-muted); }

/* ★★★ เมนูคือส่วนเดียวที่เลื่อน — `min-height: 0` บังคับ ไม่งั้นลูกของ flex column
   ยุบต่ำกว่าเนื้อหาไม่ได้ แล้วมันจะดันท้ายแถบทะลุขอบล่างแทนที่จะเลื่อนข้างใน (P230) */
.pf-nav {
  padding: var(--pf-space-5) var(--pf-space-4); display: grid; gap: var(--pf-space-2);
  flex: 1; min-height: 0; overflow: auto; align-content: start;
}
.pf-nav-label {
  padding: var(--pf-space-4) var(--pf-space-4) var(--pf-space-2);
  font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption); font-weight: 600; color: var(--pf-text-muted);
}
.pf-nav-item {
  height: var(--pf-control-md); display: flex; align-items: center; gap: var(--pf-space-4);
  padding: 0 var(--pf-space-4); border-radius: var(--pf-radius-md);
  font-size: 14px; font-weight: 500; color: var(--pf-text-secondary);
  transition: background var(--pf-duration-standard) var(--pf-ease-out),
              color var(--pf-duration-standard) var(--pf-ease-out);
}
.pf-nav-item:hover { background: var(--pf-gray-50); color: var(--pf-text-primary); }
.pf-nav-item[aria-current="page"] {
  background: var(--pf-pastel-brand); color: var(--pf-primary-ink); font-weight: 600;
}
/* ★★★★ กฎฐานของไอคอน — ยกมาจากแอปตอนย้าย `icon.ts` (รอบ 280 · D42 ขั้น 5.3-ข)
   🔴 ก่อนรอบนี้ `.pf-ico` **ไม่มีกฎฐานเลย** มีแต่กฎเฉพาะในแถบเมนู
     หน้าดีไซน์รอดมาได้เพราะ `<svg>` ทุกตัวเขียน `width="16" height="16"` ติดไว้เอง
     ⚠⚠ ซึ่งแปลว่า **ขนาดไอคอนกระจายอยู่ใน HTML หลายร้อยจุด** ไม่ได้อยู่ที่เดียว
       เปลี่ยนขนาดไอคอนทั้งระบบทีเดียวไม่ได้ · และไอคอนที่ลืมใส่ก็จะไม่มีขนาด
   ★ วิธีของแอปดีกว่า: ผูกขนาดกับ `font-size` ของกล่องที่มันอยู่ (`1em`)
     ไอคอนจึงโตตามข้อความข้าง ๆ เสมอโดยไม่ต้องตั้งค่าอะไรอีก
   ⚠ กฎนี้ชนะ attribute `width`/`height` บน `<svg>` — เป็นเรื่องปกติของ CSS
     หน้าดีไซน์ไม่กระทบ เพราะมีแค่ 2 จุดที่ใส่ `class="pf-ico"` จริง ๆ */
.pf-ico {
  width: 1em; height: 1em; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.8;
  stroke-linecap: round; stroke-linejoin: round;
}
.pf-ico--lg { width: 1.35em; height: 1.35em; }
.pf-nav-item .pf-ico { width: 20px; height: 20px; flex: none; }
.pf-nav-count {
  margin-left: auto; min-height: 20px; padding: 0 var(--pf-space-3);
  border-radius: var(--pf-radius-pill); display: inline-flex; align-items: center;
  font-size: 12px; font-weight: 500; background: var(--pf-gray-100); color: var(--pf-text-secondary);
}
.pf-nav-item[aria-current="page"] .pf-nav-count { background: var(--pf-gray-0); color: var(--pf-primary-ink); }

/* ★ `flex: none` — ท้ายแถบต้องไม่ถูกบีบเมื่อเมนูยาว · `margin-top: auto` ไม่ต้องใช้แล้ว
   เพราะ `.pf-nav` กินที่ที่เหลือด้วย `flex: 1` อยู่แล้ว */
.pf-sidebar-foot { flex: none; padding: var(--pf-space-4); }

/* ═══ กล่องท้ายแถบข้าง — ที่นั่ง + โปรไฟล์ + ธีม (เจ้าของขอ 2026-09-02) ══════
   🔴 คำสั่ง: *"ทำให้มันเป็นกล่องครอบไว้เหมือนแบบใน design เลยครับ มีการแสดงจำนวน
     ที่นั่งและหลอดสูงสุด แล้วก็เพิ่มโปรไฟล์ระบบกับธีมเข้าไปด้วย ทำให้สมดุล ไม่เพี้ยนตำแหน่ง"*
   ⚠⚠ ของเดิมเป็นปุ่มสองตัวลอย ๆ และ **จัดชิดคนละแบบ** (ตัวบนมี inline style
     `justify-content:flex-start` ตัวล่างไม่มี ⇒ ตัวล่างจัดกึ่งกลาง) นั่นคือ "เพี้ยน" ที่เห็น
   ★★★ ทุกชิ้นในกล่องชิดซ้ายเส้นเดียวกัน · เมนูเลื่อนได้แต่กล่องนี้ลอยอยู่กับที่เสมอ
     (ตัวที่เลื่อนคือ `.pf-nav` — ดู `.pf-sidebar`)
   ✳ 2026-09-02 รอบสอง: ปุ่มธีมย้ายไปมุมขวาบนของการ์ด — คลาสจัดไอคอนที่เคยมีตรงนี้
     ถูกลบทิ้งแล้ว เพราะไม่มีปุ่มไหนต้องการมันอีก */
.pf-sidefoot {
  display: grid; gap: var(--pf-space-3); padding: var(--pf-space-4); min-inline-size: 0;
}
/* ★★★ แถวหัวของการ์ด — ชื่อแพ็กซ้าย · ปุ่มสลับธีมมุมขวา (เจ้าของสั่ง 2026-09-02)
   🔴 เดิมปุ่มธีมเป็นปุ่มเต็มความกว้างใบที่สอง ⇒ อ่านเหมือน "กล่องรอง" ซ้อนในกล่องหลัก
   ⚠ ใช้แถวจริง **ไม่ใช่ `position: absolute`** — บทบาทที่ไม่เห็นหลอดที่นั่ง (พนักงาน)
     จะไม่มีอะไรอยู่แถวบน แล้วปุ่มลอยจะไปทับปุ่มโปรไฟล์พอดี
   ★ `margin-inline-start: auto` ทำให้ปุ่มชิดขวาเสมอ แม้ไม่มีชื่อแพ็กอยู่ข้างซ้าย */
.pf-sidefoot-head { display: flex; align-items: center; gap: var(--pf-space-3); min-inline-size: 0; }
.pf-sidefoot-head > .pf-card-title { flex: 1; min-inline-size: 0; }
.pf-sidefoot-head > .pf-btn { flex: none; margin-inline-start: auto; }

/* ★★★★ ไม่มีข้อมูลแพ็ก (บทบาทที่ไม่เห็นที่นั่ง) = การ์ด **เหลือแถวเดียว**
   🔴 เจ้าของทัก 2026-09-02: *"ยังเห็นมันแสดงว่างอยู่ตรงจำนวนที่นั่ง"*
     แถวหัวที่ไม่มีชื่อแพ็กเหลือแต่ปุ่มธีมลอยอยู่ขวา ⇒ อ่านเหมือน **ของโหลดไม่ขึ้น**
     ทั้งที่ความจริงคือ "บทบาทนี้ไม่มีข้อมูลนั้น" — สองอย่างนี้ต้องหน้าตาไม่เหมือนกัน
   ★ สองคอลัมน์ + `order` ⇒ ปุ่มโปรไฟล์ซ้าย · ปุ่มธีมขวา บรรทัดเดียวกัน
     ⚠ ทำที่ CSS ไม่ใช่แตกเทมเพลตเป็นสองสาขา — มาร์กอัปชุดเดียวจึงไม่มีทางหลุดกันคนละทาง */
.pf-sidefoot--slim { grid-template-columns: minmax(0, 1fr) auto; align-items: center; }
/* ★★★★ เฉพาะ **ปุ่มโปรไฟล์** ที่ไปอยู่ซ้ายคู่กับปุ่มธีม — ไม่ใช่ปุ่มทุกใบในการ์ด
   🔴🔴 เดิมเขียน `> .pf-btn` เฉย ๆ ซึ่งถูกตอนที่การ์ดมีแค่ **สองชิ้น**
     (แถวหัว + ปุ่มโปรไฟล์) · วันที่ปุ่ม "ตั้งค่าให้พร้อม" ถูกเอากลับมา (2026-09-07)
     การ์ดกลายเป็นสี่ชิ้น แล้วกฎเดิมก็ลากปุ่มตั้งค่าไปอยู่แถวเดียวกับปุ่มโปรไฟล์ด้วย
     ⇒ อวตาร · กุญแจ · พระจันทร์ กระจายอยู่บรรทัดเดียวกันแบบอ่านไม่ออก
     (เจ้าของทัก 2026-09-08 · เห็นเฉพาะองค์กรใหม่ที่ยังไม่มีข้อมูลที่นั่ง จึงตกมาโหมด slim)
   ⚠ กฎที่อ้าง "ลูกทุกตัว" จะพังเงียบทุกครั้งที่มีใครเพิ่มลูกใหม่ — ระบุให้เจาะจง */
.pf-sidefoot--slim > .pf-btn:not(.pf-setup-btn) { order: 1; }
.pf-sidefoot--slim > .pf-sidefoot-head { order: 2; }
/* ★ ปุ่มตั้งค่าและเส้นคั่นกินเต็มความกว้าง และอยู่ **เหนือ** แถวโปรไฟล์
   ⚠ ไม่ตั้ง order ให้เส้นคั่น = มันได้ 0 แล้วไปโผล่ก่อนทุกอย่าง ซึ่งไม่ใช่ที่ของมัน */
.pf-sidefoot--slim > .pf-setup-btn,
.pf-sidefoot--slim > .pf-sidefoot-sep { order: 0; grid-column: 1 / -1; }
/* ★ หลอดในกล่องนี้กินเต็มความกว้าง — ต่างจาก `.pf-progress` ปกติที่รางกว้าง 72px ตายตัว */
.pf-sidefoot .pf-progress .track { width: auto; flex: 1; }
.pf-sidefoot > .pf-btn { justify-content: flex-start; width: 100%; min-inline-size: 0; }
/* ⚠⚠ อีเมลยาวดันกล่องทะลุขอบการ์ด (เจ้าของทัก 2026-09-02 · เห็นตอนชี้เมาส์)
   🔴 `.pf-truncate` ตั้ง `max-width: 22ch` ไว้ ซึ่ง **ยังกว้างกว่าการ์ด** เมื่อรวมอวตาร 26px
     กับไอคอนกุญแจ 18px ⇒ ปุ่มไม่ยอมหด แล้วล้นออกไปราว 15px
   ★ ในการ์ดนี้ให้ป้าย **กินที่ที่เหลือแล้วตัดด้วยจุดไข่ปลา** ไม่ใช่ถูกจำกัดด้วย ch */
.pf-sidefoot > .pf-btn > .pf-truncate {
  flex: 1; min-inline-size: 0; max-inline-size: none; text-align: start;
}
/* ★ เส้นคั่นระหว่าง "ข้อมูลแพ็ก" กับ "ปุ่มของฉัน" — คนละเรื่องกัน ไม่ควรอ่านต่อกันเป็นก้อนเดียว */
.pf-sidefoot-sep { border-top: 1px solid var(--pf-border); margin: var(--pf-space-1) 0 0; }

/* ★★★★ โทนของหลอด — บอกว่า "เพิ่มคนต่อไปเสียเงินไหม" ไม่ใช่แค่สถิติ (กฎเหล็กข้อ 3)
   ⚠ สีไม่ใช่สัญญาณเดียว — ข้อความใต้หลอดบอกจำนวนที่เหลือ/ที่เกินอยู่แล้ว (DESIGN §10)
   ⚠⚠ ต้องเขียน `.pf-progress` ซ้ำหน้าคลาสโทน (สามคลาส) ไม่ใช่ `.pf-progress--near` เดี่ยว ๆ
     `.pf-progress .track i` ตั้งสีฐานไว้ด้วยความจำเพาะเท่ากัน และอยู่ **หลัง** บล็อกนี้ในไฟล์
     ⇒ เขียนสองคลาสแล้วสีฐานชนะ · หลอดไม่เปลี่ยนสีเลยและไม่มีอะไรฟ้อง (P320 ซ้ำรอบที่สอง)
   ★ แก้ด้วยการ **เพิ่มความจำเพาะ** ไม่ใช่ย้ายตำแหน่ง — ย้ายแล้วพังอีกวันที่ใครจัดเรียงไฟล์ */
.pf-progress.pf-progress--near .track i { background: var(--pf-warning-500); }
.pf-progress.pf-progress--over .track i { background: var(--pf-danger-500); }

/* ═══ Main ═══ */
.pf-main { min-width: 0; }
.pf-page {
  padding: var(--pf-page-padding-desktop);
  display: grid; gap: var(--pf-section-gap); align-content: start;
}
/* ★★★★ ลูกของกริดมี `min-width: auto` เป็นค่าปริยาย — มันจะ **ไม่ยอมหด** ให้เล็กกว่าเนื้อหา
   ⚠⚠ วัดเจอจริงตอนแอปเริ่มใช้ `.pf-page` (รอบ 304 · P225)
     ที่ 768px หน้าพนักงาน/สรรหา/ประเมิน/จุดทำงาน/ใบหน้า **เลื่อนแนวนอนได้ 90px**
     ตัวที่ล้นคือกล่องแนวนอนที่หดไม่ได้ — `pf-page-header` · `pf-actions` · `pf-filterbar`
     · `pf-kpi-grid` · ปุ่มที่มีข้อความไทยยาว
   ★ หน้าดีไซน์ไม่เจอปัญหานี้เพราะเนื้อหาตัวอย่างสั้นกว่า — ไม่ได้แปลว่ากฎไม่จำเป็น
     มันแปลว่า **ตัวอย่างไม่ได้ทดสอบเคสนี้** (ตระกูล P212) */
.pf-page > * { min-width: 0; }
.pf-page-header { display: flex; align-items: flex-start; gap: var(--pf-space-5); flex-wrap: wrap; }
.pf-page-header .sp { flex: 1; }
.pf-page-desc { color: var(--pf-text-secondary); font-size: var(--pf-size-body); line-height: var(--pf-lh-body); margin: var(--pf-space-1) 0 0; }
.pf-actions { display: flex; align-items: center; gap: var(--pf-space-3); flex-wrap: wrap; }

/* ═══ Button (COMPONENTS.md) ═══ */
.pf-btn {
  height: var(--pf-control-md); display: inline-flex; align-items: center; justify-content: center;
  gap: var(--pf-space-3); padding: 0 14px; border-radius: var(--pf-radius-md);
  font-size: 14px; font-weight: 600; white-space: nowrap;
  transition: background var(--pf-duration-standard) var(--pf-ease-out),
              border-color var(--pf-duration-standard) var(--pf-ease-out),
              box-shadow var(--pf-duration-standard) var(--pf-ease-out);
}
.pf-btn--primary { background: var(--pf-primary-500); color: var(--pf-on-primary); }
.pf-btn--primary:hover { background: var(--pf-primary-600); }
.pf-btn--secondary {
  background: var(--pf-surface); border: 1px solid var(--pf-border-strong); color: var(--pf-gray-700);
}
.pf-btn--secondary:hover { border-color: var(--pf-gray-400); box-shadow: var(--pf-shadow-1); }
.pf-btn--ghost { background: transparent; color: var(--pf-text-secondary); }
.pf-btn--ghost:hover { background: var(--pf-gray-50); }
/* ★★★★ ปุ่มไอคอนล้วน — ต้อง **เป็นจัตุรัส** และแตะได้จริง
   ⚠⚠ เดิมตั้งแค่ `width` · พอใช้คู่กับ `--sm` (สูง 36) จะได้ 40x36 ซึ่งเบี้ยว
     วัดแล้วเจอตอนทำปุ่มเปิดแผนที่/แชร์ในหน้าจุดทำงาน (2026-08-28)
   ★ ตั้งความสูงให้เท่าความกว้างเสมอ แล้วให้ตัวปรับขนาดคุมทั้งคู่พร้อมกัน */
.pf-btn--icon {
  width: var(--pf-control-md); height: var(--pf-control-md);
  padding: 0; position: relative; flex: none;
}
.pf-btn--icon.pf-btn--sm { width: var(--pf-control-sm); height: var(--pf-control-sm); }
/* ★★★ ไอคอนในปุ่มที่ไม่มีตัวอักษรต้องใหญ่ขึ้น — `.pf-ico` เป็น 1em
     ปุ่ม `--sm` ใช้ฟอนต์ 13px จึงได้ไอคอน 13x13 ซึ่งเล็กเกินกว่าจะสื่อความหมาย
   ⚠ ในปุ่มที่มีตัวอักษรอยู่ด้วย ขนาด 1em ถูกแล้ว — ไอคอนต้องเสมอบรรทัดกับคำ */
.pf-btn--icon .pf-ico { width: 18px; height: 18px; }
/* ★★★★ เป้าสัมผัสบนอุปกรณ์ที่ใช้นิ้ว — ระบบดีไซน์กำหนด touch 44
   ⚠ ขยาย **พื้นที่กด** ไม่ใช่ขยายรูป · ปุ่มยังดูเท่าเดิมแต่พลาดยากขึ้น
   ★ ใช้ `pointer: coarse` ไม่ใช่ความกว้างจอ — แท็บเล็ตจอใหญ่ก็ใช้นิ้ว */
@media (pointer: coarse) {
  .pf-btn--icon { min-width: var(--pf-control-lg); min-height: var(--pf-control-lg); }
}
.pf-btn[disabled] { opacity: .48; pointer-events: none; }
.pf-btn--sm { height: var(--pf-control-sm); padding: 0 var(--pf-space-4); font-size: 13px; }
/* ★ ปุ่มใหญ่ — ใช้กับปุ่มเดียวที่เป็น **งานหลักของทั้งหน้า** (สแกนเข้า/ออกในหน้าของฉัน)
   ⚠ ไม่ใช่ปุ่มเน้น แต่เป็นปุ่มที่คนกดขณะยืนอยู่หน้างานและมองจอแค่แวบเดียว
     `--pf-control-lg` = 44px ซึ่งเป็นขนาดนิ้วโป้งขั้นต่ำที่ RESPONSIVE.md กำหนดไว้ */
.pf-btn--lg { height: var(--pf-control-lg); padding: 0 var(--pf-space-7); font-size: var(--pf-size-section); }
.pf-dot-notify {
  position: absolute; top: 8px; right: 8px; width: 8px; height: 8px; border-radius: 50%;
  background: var(--pf-danger-500); box-shadow: 0 0 0 2px var(--pf-surface);
}

/* ═══ Input / Select (COMPONENTS.md) ═══ */
.pf-field {
  height: var(--pf-control-md); display: flex; align-items: center; gap: var(--pf-space-3);
  padding: 0 12px; border: 1px solid var(--pf-border-strong); border-radius: var(--pf-radius-md);
  background: var(--pf-surface); color: var(--pf-text-muted);
  transition: border-color var(--pf-duration-standard) var(--pf-ease-out),
              box-shadow var(--pf-duration-standard) var(--pf-ease-out);
}
.pf-field:focus-within { border-color: var(--pf-primary-500); box-shadow: 0 0 0 2px var(--pf-primary-100); }
/* ⚠ `min-width: 0` ด้วยเหตุผลเดียวกับ `.pf-field--textarea textarea` (P230)
   `min-content` ของ `<input>` มาจากแอตทริบิวต์ `size` (ปริยาย 20) ไม่ใช่จากค่าที่พิมพ์ */
.pf-field input, .pf-field select {
  border: 0; outline: 0; background: none; width: 100%; min-width: 0; font-size: 14px; color: var(--pf-text-primary);
}
.pf-field input::placeholder { color: var(--pf-text-placeholder); }

/* ★★★★ ต้องปิดหน้าตาดั้งเดิมของ <select> ให้หมด
   ⚠⚠⚠ เจ้าของโปรเจคเห็นเองบนจอ: ช่อง "ทุกแผนก" มีกรอบซ้อนกรอบ และลูกศรคนละแบบ
     ต้นเหตุ — เราจัดสไตล์ให้ **กล่องครอบ** (.pf-field) แต่ไม่เคยแตะตัว <select>
     เบราว์เซอร์จึงวาดคอนโทรลของระบบปฏิบัติการทับลงไปข้างใน พร้อมขอบและลูกศรของมันเอง
   ★ อาการนี้เห็นเฉพาะบนเครื่องจริง — เปิดดูภาพ mockup ไม่มีวันเจอ */
.pf-field select {
  appearance: none; -webkit-appearance: none; -moz-appearance: none;
  cursor: pointer; padding-right: var(--pf-space-5);
  /* ⚠ Firefox ยังใส่เงาตัวอักษรตอน :disabled ถ้าไม่สั่งทับ */
  text-shadow: none;
}
.pf-field select::-ms-expand { display: none; }

/* ★★★ ลูกศรวาดด้วย CSS ไม่ใช่ SVG ในแต่ละหน้า
   ⚠ ก่อนแก้: 6 หน้ามี <select> รวม 15 ตัว แต่มีลูกศรใส่มือแค่ 11 ตัว — ไม่ตรงกันอยู่แล้ว
   ★ ผูกกับกล่องครอบ จึงได้ลูกศรเหมือนกันทุกที่โดยไม่ต้องจำใส่ */
.pf-field:has(select)::after {
  content: ""; flex: none; width: 7px; height: 7px; margin-left: -14px;
  border-right: 1.6px solid var(--pf-text-muted);
  border-bottom: 1.6px solid var(--pf-text-muted);
  transform: translateY(-2px) rotate(45deg);
  pointer-events: none;
}
/* หน้าที่ยังมีลูกศรใส่มืออยู่ ต้องไม่ขึ้นสองอัน */
.pf-field select ~ svg { display: none; }

/* ═══ Card (COMPONENTS.md) ═══ */
.pf-card {
  background: var(--pf-surface); border: 1px solid var(--pf-border);
  border-radius: var(--pf-radius-lg); box-shadow: var(--pf-shadow-1);
  padding: var(--pf-space-5); min-width: 0;
}
.pf-card--flush { padding: 0; overflow: hidden; }
/* ★ การ์ดที่ "กดได้" — ใช้กับการ์ดเลือกแพ็กเกจ (ยกมาจาก `.card--hover` · รอบ 290)
   ⚠⚠ `transform` บนการ์ดสร้าง containing block ให้ลูกที่เป็น `position: fixed`
     จึงใช้ได้เฉพาะการ์ดที่ **ไม่มีกล่องลอยอยู่ข้างใน** (ปฏิทิน · listbox) — ดู P59
   ★ การ์ดแพ็กเกจมีแต่ข้อความกับปุ่ม จึงปลอดภัย */
.pf-card--hover { transition: box-shadow var(--pf-duration-standard) var(--pf-ease-out),
                              transform var(--pf-duration-standard) var(--pf-ease-out),
                              border-color var(--pf-duration-standard) var(--pf-ease-out); }
.pf-card--hover:hover {
  box-shadow: var(--pf-shadow-3); transform: translateY(-2px); border-color: var(--pf-primary-300);
}
.pf-card-head {
  display: flex; align-items: center; gap: var(--pf-space-4); flex-wrap: wrap;
  padding: var(--pf-space-5) var(--pf-space-5) var(--pf-space-4);
}
.pf-card-head .sp { flex: 1; }
.pf-card-body { padding: 0 var(--pf-space-5) var(--pf-space-5); }
/* ★★★ กล่องข้อความที่เป็นของชิ้นแรกในเนื้อการ์ด ต้องมีระยะจากหัวการ์ด (เจ้าของทัก 2026-09-04)
   🔴 `.pf-card-body` ไม่มี padding บน (อาศัย padding ล่างของหัวการ์ด 16px)
     ตัวหนังสือชนตัวหนังสือที่ 16px อ่านปกติ — แต่ **ตัวหนังสือชนกล่องที่มีพื้นสี**
     ที่ระยะเท่ากันอ่านเหมือนติดกัน เพราะขอบกล่องมาถึงก่อนตัวอักษรจริง
   ⚠ ใช้ `:first-child` โดยตั้งใจ — ของชิ้นแรกไม่เคยได้ `gap` จากพ่อที่เป็น flex
     จึงบวกระยะให้ได้โดยไม่ทำให้การ์ดที่ใช้ flex+gap มีระยะซ้อนสองชั้น */
.pf-card-body > .pf-note:first-child { margin-block-start: var(--pf-space-3); }
/* ⚠ กล่องข้อความสองใบติดกันก็ต้องไม่ชนกันเอง (เช่นแจ้งข้อมูล + แจ้งเตือนสิทธิ์) */
.pf-card-body > .pf-note + .pf-note { margin-block-start: var(--pf-space-3); }
/* ★★★ และของที่อยู่ **ถัดจาก** กล่องข้อความก็ต้องไม่ชนมันเหมือนกัน (เจ้าของทักรอบสอง)
   🔴 รอบแรกผมแก้แต่ระยะ **บน** ของกล่อง เพราะตอนวัดมีกล่องสองใบซ้อนกันอยู่
     เลขที่วัดได้ (45px) จึงเป็นระยะจากกล่องใบแรกข้ามใบที่สองไปถึงกริด — ไม่ใช่ระยะจริง
     ⇒ พอเหลือกล่องใบเดียว กริดก็ไปติดกล่องพอดี · **วัดผิดจุดแล้วสรุปว่าแก้แล้ว**
   ⚠ ต้องยกเว้นเนื้อการ์ดที่เป็น flex+gap ไม่งั้นระยะจะซ้อนสองชั้น
     (แบบแผนเดียวกับที่ `--wide`/`--fill` ต้องถูกยกเว้นในกฎ media · P367) */
.pf-card-body > .pf-note + * { margin-block-start: var(--pf-space-5); }
.pf-kpi-grid--wide > .pf-card > .pf-card-body > .pf-note + *,
.pf-kpi-grid--fill > .pf-card > .pf-card-body > .pf-note + * { margin-block-start: 0; }

/* ═══ KPI (COMPONENTS.md) ═══ */
.pf-kpi-grid { display: grid; gap: var(--pf-grid-gap); grid-template-columns: repeat(5, minmax(0, 1fr)); }
/* ★★ จำนวนการ์ดไม่คงที่ → ให้กริดจัดเอง (รอบ 278 · D42 ขั้น 5.2)
   ⚠⚠ `.pf-kpi-grid` เปล่า ๆ ตรึงไว้ **5 คอลัมน์** — หน้าที่มี 4 ใบจะเหลือช่องว่างหนึ่งช่อง
     และที่จอแคบมันไม่ยุบให้ · หน้าพนักงานของระบบดีไซน์เองก็ต้องเขียน
     `style="grid-template-columns:…"` ทับ ซึ่งแปลว่ากฎเปล่ายังไม่พอสำหรับของจริง
   ★ ทำเป็นตัวปรับแทนการเขียน style ฝังในเทมเพลต — ฝัง style แล้วเปลี่ยนทีเดียวทั้งระบบไม่ได้ */
.pf-kpi-grid--auto { grid-template-columns: repeat(auto-fit, minmax(215px, 1fr)); }
/* ★★★★ `--wide` — กริดของ **การ์ดสรุป** ไม่ใช่การ์ด KPI (เจ้าของทัก 2026-09-02)
   🔴 การ์ด KPI มีไอคอน + เลขตัวเดียว ⇒ 215px พอ
     แต่การ์ดสรุปมีแถว "ป้าย ⟷ ค่า" ซึ่งต้องการที่มากกว่านั้นมาก
     วัดที่ 790px: `--auto` ให้ 3 คอลัมน์ × 215px ⇒ **ชื่อคนตัดกลางคำ**
     ("นายชยุตม์ กล้า / ณรงค์" · "ชื่อ- / สกุล" · "วันเริ่ม / งาน") — ข้อมูลดูเหมือนพัง
   ★ 260px วัดมาให้ลงตัวสองด้าน: ที่ 790 ได้ 2 คอลัมน์ (327px ต่อใบ · ไม่ตัดคำ)
     และที่ 1440 ได้ 4 คอลัมน์พอดี ⇒ การ์ดสี่ใบอยู่แถวเดียว ไม่มีใบที่ตกไปอยู่ลำพัง
   ⚠ ไม่ไปขยับ `--auto` เพราะหน้าแพ็กเกจใช้มันกับการ์ด KPI จริง ๆ และ 215px ที่นั่นพอดีอยู่แล้ว */
.pf-kpi-grid--wide { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
/* ⚠⚠ ต้องไปเติม `:not(.pf-kpi-grid--wide)` ในกฎ media ของ 1439 และ 767 ด้วย
   🔴 ลืมรอบแรก ⇒ ที่ 375px กฎ "บังคับสองคอลัมน์" ของมือถือชนะ ⇒ การ์ดเหลือ 166px
     ซึ่ง **แคบกว่าขั้นต่ำ 260 ที่ประกาศไว้เอง** แล้วป้ายที่ตั้ง nowrap ก็ล้นกล่องทันที
   ★ ตัวปรับใหม่ทุกตัวของตระกูลนี้ต้องถูกยกเว้นในกฎ media ทั้งสองที่ — ไม่งั้นมันจะถูกทับเงียบ ๆ */
/* ★★★ ปุ่มท้ายการ์ดชิดล่างเสมอ — การ์ดในแถวเดียวกันสูงเท่ากันแต่เนื้อหาไม่เท่า
   ⇒ ปล่อยให้ปุ่มลอยตามเนื้อหา = การ์ดที่เนื้อหาน้อยมีช่องว่างค้างข้างล่าง (ตระกูล P318)
   ⚠ ต้องปูทางทั้งสามชั้น (การ์ด → เนื้อการ์ด → ปุ่ม) — ขาดชั้นใดชั้นหนึ่ง `auto` ไม่มีผล */
.pf-kpi-grid--wide > .pf-card { display: flex; flex-direction: column; }
/* ⚠⚠ `gap` คือ **ระยะขั้นต่ำ** ที่ `margin-block-start: auto` ยุบทิ้งไม่ได้ (เจ้าของทัก 2026-09-02)
   🔴 `auto` กินที่ว่างที่เหลือ — การ์ดที่ **สูงที่สุดในแถว** ไม่มีที่ว่างเหลือเลย
     ⇒ `auto` กลายเป็น 0 ⇒ ปุ่มไปติดกล่องข้อมูลพอดี (เห็นชัดที่ใบ "สิทธิ์การลา" กับ "สลิปล่าสุด")
     ★ ใบที่เนื้อหาน้อยดูดีอยู่แล้วเพราะ `auto` มีที่ให้กิน — จึงเห็นปัญหาเฉพาะใบที่ข้อมูลเยอะ
   ★ `gap` + `auto` ทำงานร่วมกัน: gap เป็นพื้น · auto ดันเพิ่มเมื่อมีที่เหลือ */
.pf-kpi-grid--wide > .pf-card > .pf-card-body {
  flex: 1; display: flex; flex-direction: column; gap: var(--pf-space-5);
}
.pf-kpi-grid--wide > .pf-card > .pf-card-body > .pf-btn:last-child {
  margin-block-start: auto; padding-block-start: 0;
}
/* ★ ตัวปรับ "แคบกว่า" — การ์ด 5 ใบที่มีแต่ตัวเลขสั้น ๆ (หน้าลงเวลา)
   ⚠ `--auto` (215px) ให้ 4 คอลัมน์ที่ 1440 แล้วใบที่ 5 ตกลงไปแถวล่างอยู่ใบเดียว
     ซึ่งอ่านเหมือน "ใบนี้เป็นอีกกลุ่ม" ทั้งที่เป็นชุดเดียวกัน · วัดที่ 1440: 180px → 5 คอลัมน์
   ⚠ หน้าดีไซน์เขียนค่านี้เป็น `style="grid-template-columns:…"` ติดในแท็ก จึงไม่มีชื่อคลาสให้ลอก
     แก้หน้าดีไซน์ให้ใช้คลาสนี้แล้ว (กฎข้อ 1) */
.pf-kpi-grid--compact { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
/* ★★★ กริดที่ **ไม่ยืดใบเดียวให้เต็มแถว** (เจ้าของทัก 2026-09-03)
   🔴 `auto-fit` ยุบรางที่ว่างทิ้ง ⇒ มีการ์ดใบเดียวเมื่อไหร่ มันกินทั้งแถว (~1,480px ที่จอ 1440)
     ปุ่มในการ์ดที่ยืดตามก็กลายเป็นแถบยาวเต็มจอ ซึ่งอ่านเหมือนช่องกรอกว่าง ไม่ใช่ปุ่ม
   ★ `auto-fill` เก็บรางว่างไว้ ⇒ ใบเดียวกว้างเท่ารางเดียว · พอการ์ดเยอะขึ้นก็เหมือน auto-fit ทุกอย่าง
   ⚠ ต้องยกเว้นในกฎ media ทั้งสองที่ด้านล่างด้วย ไม่งั้นถูกทับเงียบ ๆ (คำเตือนของ --wide ข้างบน) */
.pf-kpi-grid--fill { grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
/* 🔴🔴 กฎระยะห่างข้างล่างผูกไว้กับ `--wide` อย่างเดียว — เจ้าของทักทันทีที่ย้ายมาใช้ `--fill`
     ว่า *"ไม่มีระยะห่าง"* เพราะการ์ดหลุดจาก `display:flex` + `gap` ที่ `--wide` มีอยู่
   ★★ กติกา: **ตัวปรับกริดตัวใหม่ทุกตัวต้องมาต่อกฎทรงการ์ดด้วย ไม่ใช่แค่ตั้งจำนวนคอลัมน์**
     ⚠ คำเตือนของ `--wide` เขียนเรื่อง media query ไว้แล้ว แต่ไม่ได้เขียนเรื่องนี้
       จึงเติมไว้ตรงนี้ — ของที่ต้องทำ **สองอย่าง** ต้องเขียนไว้ทั้งสองอย่าง */
.pf-kpi-grid--fill > .pf-card { display: flex; flex-direction: column; }
.pf-kpi-grid--fill > .pf-card > .pf-card-body {
  flex: 1; display: flex; flex-direction: column; gap: var(--pf-space-5);
}
/* ★ ดันแถวปุ่มลงล่างสุด — การ์ดในแถวเดียวกันสูงเท่ากันแต่เนื้อหาไม่เท่า
   ⚠ เล็งที่ `:last-child` ไม่ใช่ `.pf-btn:last-child` แบบของ `--wide`
     เพราะที่นี่ปุ่มถูกห่อด้วย div (กันไม่ให้ยืดเต็มการ์ด) ⇒ ตัวสุดท้ายไม่ใช่ปุ่มแล้ว */
.pf-kpi-grid--fill > .pf-card > .pf-card-body > :last-child { margin-block-start: auto; }
/* 🔴 เคยมี `.pf-kpi-grid--4` ตรงนี้ ตอนที่หน้าภาพรวมมี KPI 4 ใบ ไม่ใช่ 5 ใบตามดีไซน์
   ลบทิ้งพร้อมกับที่ยกการ์ดทั้ง 5 ใบมาจาก `/design/dashboard.html` — ไม่มีหน้าไหนใช้แล้ว
   ★ `.pf-kpi-grid` เปล่า ๆ คือของดีไซน์ · media query เดิม (1023 → 3 · 767 → 2) จัดการต่อเอง */
/* ★ ปุ่มเต็มความกว้าง — ใช้ในการ์ดที่ปุ่มคือทางออกเดียวของการ์ดนั้น */
.pf-btn--block { width: 100%; }

/* ★★★ ตราของการ์ดในหน้ารายการ — สี่เหลี่ยมมนสีแบรนด์ที่มีไอคอนอยู่ข้างใน
   🔴 เจ้าของทัก 2026-09-03: *"ไม่ค่อยมีสี"* — หน้ารายการแบบประเมินเป็นข้อความล้วนบนพื้นขาว
     ไม่มีอะไรให้ตาเกาะ และไล่หาใบที่ต้องการยากเมื่อมีหลายใบ
   ★ ใช้คู่สีเดียวกับ `.pf-avatar` (pastel-brand + primary-ink) ซึ่งผ่านเกณฑ์ความคมชัดอยู่แล้ว
     ⚠ ไม่ตั้งคู่สีใหม่ — สีใหม่ทุกคู่ต้องไปพิสูจน์ความคมชัดใหม่ทั้งสองธีม */
.pf-card-mark {
  inline-size: 40px; block-size: 40px; flex: none;
  border-radius: var(--pf-radius-md);
  display: grid; place-items: center;
  background: var(--pf-pastel-brand); color: var(--pf-primary-ink);
}
.pf-card-mark .pf-ico { width: 20px; height: 20px; }
.pf-kpi {
  min-height: 112px; padding: var(--pf-space-5);
  background: var(--pf-surface); border: 1px solid var(--pf-border);
  border-radius: var(--pf-radius-lg); box-shadow: var(--pf-shadow-1);
  display: grid; align-content: start; gap: var(--pf-space-3);
  transition: border-color var(--pf-duration-standard) var(--pf-ease-out),
              box-shadow var(--pf-duration-standard) var(--pf-ease-out);
}
/* ★ KPI ทุกใบเป็นการ์ดที่มีสีความหมายอยู่แล้ว (ชิปไอคอน) — ให้แสงมาจากสีเดียวกัน
   ⚠ ใบที่ไม่ได้ประกาศ .pf-accent--* จะได้สีแบรนด์เป็นค่าเริ่มต้น ซึ่งถูกต้อง
     เพราะการ์ดที่ไม่มีความหมายเฉพาะ ก็คือการ์ดของระบบ
   ⚠⚠ ที่นี่เคยมี `.pf-kpi:hover` เขียนเงาไว้ซ้ำอีกชุดหนึ่ง — **แหล่งความจริงที่สอง**
     วันที่ปรับแสงทั้งระบบ (ซึ่งเกิดขึ้นจริงในรอบ 228) การ์ด KPI จะค้างอยู่ที่ค่าเก่า
     ★ ย้ายไปรวมกับ Semantic Elevation ท้ายไฟล์แล้ว — `:is(.pf-elevate, .pf-kpi)` */
.pf-kpi-top { display: flex; align-items: flex-start; gap: var(--pf-space-4); }
.pf-kpi-icon {
  width: 40px; height: 40px; flex: none; border-radius: var(--pf-radius-md);
  display: grid; place-items: center;
}
.pf-kpi-label { font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption); font-weight: 500; color: var(--pf-text-muted); }
.pf-kpi-compare { font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption); font-weight: 400; color: var(--pf-text-muted); }

/* ═══ ตัวเลขเปลี่ยนแปลง (delta) ═══════════════════════════════════════════
   ★★★★ ลูกศรบอก **ทิศ** · สีบอก **ดี/แย่** — ไม่ใช่เรื่องเดียวกัน
     "มาสาย ↑ 33%" ต้องเป็นสีแดง · "ยอดขาย ↑ 33%" ต้องเป็นสีเขียว ทั้งที่ลูกศรเหมือนกัน
     จึงไม่มีกฎแบบ `[data-dir="up"] { color: เขียว }` ที่นี่ — ผิดตั้งแต่ตั้งชื่อคลาส
   ⚠ ตัวชี้วัดที่ยังไม่ประกาศว่าทิศไหนดี จะได้ `--flat` (สีกลาง) เสมอ
     ไม่รู้ว่าดีหรือแย่ ต้องไม่ทาสีเดา — สีเขียวคือคำสัญญาว่า "ดีขึ้น"
   ★ สีไม่ใช่สัญญาณเดียว: มีลูกศรเป็นตัวอักษรจริง + aria-label เต็มประโยคเสมอ */
.pf-delta {
  display: inline-flex; align-items: center; gap: var(--pf-space-1);
  padding: 2px var(--pf-space-2); border-radius: var(--pf-radius-pill);
  font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption);
  font-weight: var(--pf-w-medium); font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* ★ บรรทัดเทียบที่เป็นข่าวร้าย — ใช้กับ "สาย X นาที" ในหน้าของฉัน (ยกมารอบ 291)
   ⚠ เดิมเขียนเป็น `.delta.bad` ซึ่ง `.bad` ตัวจริงอยู่ใต้ `.toast` เท่านั้น
     นอก `.toast` มันจึงไม่ทาสีอะไรเลย — ตระกูล P30 ที่อยู่มาตั้งแต่ต้น */
.pf-kpi-compare--bad { color: var(--pf-danger-700); }
.pf-delta--good { color: var(--pf-success-700); background: var(--pf-success-50); }
.pf-delta--bad  { color: var(--pf-danger-700);  background: var(--pf-danger-50); }
.pf-delta--flat { color: var(--pf-text-muted);  background: var(--pf-surface-subtle); }

/* แถวของค่ากับตัวเลขเปลี่ยนแปลง — เดลตาห้ามดันค่าให้ตกบรรทัด */
.pf-kpi-line { display: flex; align-items: baseline; gap: var(--pf-space-3); flex-wrap: wrap; }

/* แถบเลือกช่วงเทียบ — ★ ต่อท้ายแถบตัวกรองเดิม ไม่ใช่แถบใหม่
   ⚠ เส้นคั่นบอกว่า "ฝั่งซ้ายเลือกช่วงที่ดู · ฝั่งขวาเลือกช่วงที่เอามาเทียบ"
     สองเรื่องนี้คนสับสนกันบ่อยมากถ้าวางเรียงกันเฉย ๆ */
.pf-compare-group {
  display: flex; align-items: center; gap: var(--pf-space-3);
  padding-left: var(--pf-space-4); border-left: 1px solid var(--pf-border);
}
.pf-compare-label {
  font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption);
  color: var(--pf-text-muted); white-space: nowrap;
}
/* ═══ หน้าลงเวลา + สแกนใบหน้า ═══════════════════════════════════════════════
   ★★★★ หน้านี้ถูกตัดสินด้วย **ตอนที่มันไม่สำเร็จ** ไม่ใช่ตอนที่สำเร็จ
     คนยืนอยู่หน้าออฟฟิศตอน 08:00 แล้วสแกนไม่ผ่าน — ทุกวินาทีคือเงินของเขา
     ★ ทุกสถานะจึงต้องมี **ทางออกอย่างน้อยหนึ่งทาง** เสมอ (P172 · มีเครื่องตรวจค้ำ)

   ⚠⚠⚠ ข้อจำกัดของแพลตฟอร์มที่เขียนไว้ใน CSS นี้ ไม่ใช่รสนิยม (สเปก §7.8)
     ① `position: fixed; inset: 0` ห้ามใช้ `height: 100dvh`
        Safari ยุบ/ขยายแถบ URL แล้ว dvh เปลี่ยนกลางคัน → ภาพกล้องเพี้ยน
     ② มิเรอร์ (`scaleX(-1)`) ต้องอยู่ที่ **กล่องครอบ** ห้ามอยู่ที่ `<video>`
        ใส่ที่ video แล้ว layer ของวิดีโอพังบน WebKit — ยืนยันแล้วในระบบต้นทาง
   ═══════════════════════════════════════════════════════════════════════════ */
.pf-scan {
  position: absolute; inset: 0;                /* ★ ในเฟรมสาธิต · ของจริงเป็น fixed */
  background: var(--pf-scan-stage); color: var(--pf-scan-ink);
  display: grid; grid-template-rows: auto 1fr auto; overflow: hidden;
}
/* กล่องครอบภาพ — ★ มิเรอร์อยู่ที่นี่ ไม่ใช่ที่ตัว video (สเปก §7.8 กับดักข้อ 2) */
.pf-scan-video {
  position: absolute; inset: 0; transform: scaleX(-1); z-index: 0;
  background:
    radial-gradient(120% 80% at 50% 22%, color-mix(in srgb, var(--pf-scan-ink) 10%, transparent), transparent 60%),
    radial-gradient(90% 70% at 30% 90%, color-mix(in srgb, var(--pf-primary-500) 16%, transparent), transparent 70%),
    var(--pf-scan-stage);
}
.pf-scan > * { position: relative; z-index: 1; }

.pf-scan-bar {
  display: flex; align-items: center; justify-content: space-between; gap: var(--pf-space-3);
  padding: var(--pf-space-4) var(--pf-space-5);
}
.pf-scan-x {
  width: 36px; height: 36px; flex: none; border-radius: 50%; border: 0; cursor: pointer;
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--pf-scan-ink) 14%, transparent); color: var(--pf-scan-ink);
}
.pf-scan-x:focus-visible { outline: 2px solid var(--pf-scan-ink); outline-offset: 2px; }

.pf-scan-stagearea { display: grid; place-items: center; gap: var(--pf-space-5); padding: 0 var(--pf-space-5); }

/* กรอบวงรี — ★ หน้าคนเป็นแนวตั้ง กรอบจึงต้องเป็นวงรีแนวตั้ง ไม่ใช่สี่เหลี่ยม
   ⚠ สีของกรอบคือ "สถานะ" — แต่ห้ามเป็นสัญญาณเดียว มีข้อความใต้กรอบเสมอ */
.pf-scan-oval {
  width: 208px; aspect-ratio: 3 / 4; border-radius: 50%;
  border: 3px solid color-mix(in srgb, var(--pf-scan-ink) 55%, transparent);
  box-shadow: 0 0 0 9999px color-mix(in srgb, var(--pf-scan-stage) 62%, transparent);
  display: grid; place-items: center; transition: border-color var(--pf-duration-standard) var(--pf-ease-out);
}
.pf-scan-oval[data-tone="ok"]   { border-color: var(--pf-success-500); }
.pf-scan-oval[data-tone="warn"] { border-color: var(--pf-warning-500); }
.pf-scan-oval[data-tone="bad"]  { border-color: var(--pf-danger-500); }
.pf-scan-oval[data-tone="busy"] { border-color: var(--pf-primary-400); }

/* ข้อความสถานะ — ★ ต้องมีเสมอ ห้ามมีแต่วงกลมหมุน (P172: หมุนตลอดกาลคือทางตัน) */
.pf-scan-status { text-align: center; display: grid; gap: var(--pf-space-2); max-width: 280px; }
.pf-scan-status > b { font-size: var(--pf-size-section); line-height: var(--pf-lh-section); font-weight: var(--pf-w-semibold); }
.pf-scan-status > span { font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact); color: var(--pf-scan-ink-2); }

.pf-scan-foot { display: grid; gap: var(--pf-space-3); padding: var(--pf-space-5); }
.pf-scan-chips { display: flex; gap: var(--pf-space-2); flex-wrap: wrap; justify-content: center; }
.pf-scan-chip {
  display: inline-flex; align-items: center; gap: var(--pf-space-2);
  padding: 4px var(--pf-space-3); border-radius: var(--pf-radius-pill);
  background: color-mix(in srgb, var(--pf-scan-ink) 12%, transparent); color: var(--pf-scan-ink);
  font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption);
  font-variant-numeric: tabular-nums;
}
.pf-scan-chip[data-tone="bad"] { background: color-mix(in srgb, var(--pf-danger-500) 34%, transparent); }

/* หน้าพร้อมลงเวลา — นาฬิกาเป็นพระเอก */
.pf-punch-home { display: grid; gap: var(--pf-space-5); align-content: center; justify-items: center; text-align: center; padding: var(--pf-space-7) var(--pf-space-5); }
.pf-clock {
  font-size: 56px; line-height: 1.16; font-weight: var(--pf-w-bold);
  letter-spacing: -.03em; font-variant-numeric: tabular-nums; color: var(--pf-text-primary);
}
.pf-clock-date { font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact); color: var(--pf-text-muted); }

/* ปุ่มลงเวลา — ★ 64px ไม่ใช่ 44px · กดด้วยนิ้วโป้งขณะเดิน ถือของ ใส่ถุงมือ */
.pf-punch {
  width: 100%; min-height: 64px; border-radius: var(--pf-radius-lg); border: 0; cursor: pointer;
  background: var(--pf-primary-500); color: var(--pf-on-primary);
  font-family: inherit; font-size: var(--pf-size-section); line-height: var(--pf-lh-section);
  font-weight: var(--pf-w-semibold);
  display: flex; align-items: center; justify-content: center; gap: var(--pf-space-3);
}
.pf-punch:hover { background: var(--pf-primary-600); }
.pf-punch:focus-visible { outline: 2px solid var(--pf-primary-600); outline-offset: 3px; }
/* ★ "ออกงาน" เป็นปุ่มรอง — ยังใหญ่ 64px เท่าเดิม แต่ไม่ใช่สีทึบ
   ⚠ ลดโอกาสกดผิดตอนตั้งใจจะเข้างาน · และการออกงานไม่เร่งด่วนเท่าการเข้างาน
   ⚠⚠ ตอนแรกผมเขียน `background: var(--pf-gray-700)` ที่นี่ — **ตกกฎของตัวเองทันที**
     ธีมมืด gray-700 คือปลายสว่าง จะได้ปุ่มสีอ่อนตัวอักษรขาว = อ่านไม่ออก (P183)
     `theme-check` จับได้ในนาทีเดียว ★ นี่คือเหตุผลที่เขียนเครื่องตรวจไว้ */
.pf-punch--out {
  background: var(--pf-surface); color: var(--pf-text-primary);
  border: 1px solid var(--pf-border-strong);
}
.pf-punch--out:hover { background: var(--pf-surface-hover); }

/* แถวสถานะของวันนี้ */
.pf-punch-today { width: 100%; display: grid; gap: var(--pf-space-3); }
.pf-punch-row {
  display: flex; align-items: center; justify-content: space-between; gap: var(--pf-space-3);
  padding: var(--pf-space-3) var(--pf-space-4);
  background: var(--pf-surface); border: 1px solid var(--pf-border);
  border-radius: var(--pf-radius-md);
  font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact);
}
.pf-punch-row b { font-variant-numeric: tabular-nums; }

/* ผลลัพธ์ — เต็มจอ ไม่ใช่ toast ที่หายไปก่อนคนอ่านทัน */
.pf-scan-result { display: grid; gap: var(--pf-space-5); align-content: center; justify-items: center; text-align: center; padding: var(--pf-space-7) var(--pf-space-5); }
.pf-scan-mark { width: 72px; height: 72px; border-radius: 50%; display: grid; place-items: center; }
.pf-scan-mark[data-tone="ok"]   { background: var(--pf-success-50); color: var(--pf-success-700); }
.pf-scan-mark[data-tone="bad"]  { background: var(--pf-danger-50); color: var(--pf-danger-700); }
.pf-scan-mark[data-tone="warn"] { background: var(--pf-warning-50); color: var(--pf-warning-700); }
.pf-scan-time { font-size: 40px; line-height: 1.2; font-weight: var(--pf-w-bold); font-variant-numeric: tabular-nums; letter-spacing: -.02em; }

/* แกลเลอรีสถานะ — ★ วางทุกสถานะไว้ข้างกันเพื่อให้ "ตอนพัง" ถูกออกแบบเท่ากับ "ตอนสำเร็จ" */
/* ⚠ `minmax(300px, …)` ทำให้คอลัมน์ **ไม่ยอมแคบกว่า 300px** แม้กล่องแม่จะเหลือ 220
   `min(300px, 100%)` แปลว่า "300 ถ้ามีที่พอ · ไม่งั้นเท่าที่มี" — ต่างกันตรงนี้ */
.pf-state-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr)); gap: var(--pf-space-6); }
/* ★ จอแคบให้เหลือคอลัมน์เดียว — บีบเฟรมให้เหลือ 194px แล้วมันไม่ใช่ "มือถือ" อีกต่อไป
   มือถือที่แคบที่สุดที่มีจริงคือ 320px · แคบกว่านั้นคือหน้าจอที่ไม่มีอยู่จริง */
@media (max-width: 767px) { .pf-state-grid { grid-template-columns: minmax(0, 1fr); } }
/* ⚠⚠⚠ `min-width: 0` อย่างเดียว **ไม่พอ** สำหรับกริด
   คอลัมน์ที่ไม่ได้ประกาศ = `auto` ซึ่งกว้างเท่า **max-content** ของลูก
   ลูกกว้าง 300px คอลัมน์ก็ 300px ทั้งที่กล่องมีที่แค่ 220px → ล้นออกไปเฉย ๆ
   ★ `minmax(0, 1fr)` คือประโยคที่บอกว่า "แคบกว่าเนื้อหาได้" — ต้องเขียนให้ชัดเสมอ */
.pf-state-cell { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--pf-space-3); align-content: start; min-width: 0; }
.pf-state-cell > figcaption { display: grid; gap: var(--pf-space-1); }
.pf-state-cell .t { font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact); font-weight: var(--pf-w-semibold); color: var(--pf-text-primary); }
.pf-state-cell .d { font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption); color: var(--pf-text-muted); }
/* ⚠⚠ `.pf-frame--sm` เคยอยู่ตรงนี้ — **ก่อน** `.pf-frame` ในไฟล์
   ความเฉพาะเจาะจงเท่ากัน ลำดับในไฟล์จึงชนะ → เฟรมเล็กถูก `.pf-frame` ทับกลับเป็น 360×720
   ไม่มีอะไรร้อง เพราะ CSS ทั้งสองบรรทัดถูกต้องทั้งคู่ · เห็นตอนวัดว่าหน้าล้นจอ 76px
   ★ ย้ายไปไว้ต่อจาก `.pf-frame` แล้ว (ดูส่วนมือถือ) */

/* ═══ กล่องข้อความบอกสถานะ (note) ═══════════════════════════════════════════
   ★ ทำเป็นของกลางตั้งแต่แรก ไม่ใช่ `.pf-compare-warn` ที่ใช้ได้ที่เดียว
     ที่ต้องใช้แน่ ๆ อีก: หน้าที่โหลดข้อมูลไม่สำเร็จ (P172) · งวดที่ปิดแล้ว · โควตาใกล้หมด
   ⚠ ใช้คู่กับข้อความที่บอก **ว่าต้องทำอะไรต่อ** ไม่ใช่แค่บอกว่ามีปัญหา */
.pf-note {
  display: flex; align-items: flex-start; gap: var(--pf-space-3);
  font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption);
  border: 1px solid transparent;
  border-radius: var(--pf-radius-md); padding: var(--pf-space-3) var(--pf-space-4);
}
.pf-note--warning { color: var(--pf-warning-700); background: var(--pf-warning-50); border-color: var(--pf-warning-100); }
.pf-note--danger  { color: var(--pf-danger-700);  background: var(--pf-danger-50);  border-color: var(--pf-danger-100); }
.pf-note--info    { color: var(--pf-info-700);    background: var(--pf-info-50);    border-color: var(--pf-info-100); }
/* ★★★★ โทน "เรียบร้อยแล้ว" — ตัวที่ **ขาดไปตั้งแต่ต้น** (เพิ่ม 2026-08-27)
   มีครบสามโทนที่บอกว่า *มีอะไรต้องทำ* (เตือน · ผิดพลาด · ข้อมูล)
   แต่ไม่มีโทนที่บอกว่า **ทำเสร็จแล้ว ไม่ต้องทำอะไรต่อ**
   ⚠⚠ ผลคือทุกที่ที่อยากบอกข่าวดี ต้องใช้ `.pf-note` เปล่า ๆ ซึ่งไม่มีพื้นไม่มีขอบ
     แล้วมันอ่านเหมือน "ข้อความประกอบ" ไม่ใช่ "สถานะ" — เจ้าของโปรเจคทักมาที่
     กล่องผูกบัญชี ว่าอยากให้ "เป็นสี" เพราะตาอ่านไม่ออกว่ามันคือสถานะ
   ★ ใช้คู่สีชุดเดียวกับ `.pf-badge--success` และ `.pf-delta--good` — ไม่ได้ตั้งค่าใหม่
     ⚠ ห้ามเอาสีเขียวไปใช้แทน "ทำแล้ว" ในความหมายของความคืบหน้า (นั่นเป็นสีแบรนด์
       ตามที่ `.pf-ring-fill` และ `.pf-setup-mark` เขียนเหตุผลไว้) — เขียวแปลว่า
       **สถานะของข้อมูลนั้นดี** เท่านั้น */
.pf-note--success { color: var(--pf-success-700); background: var(--pf-success-50); border-color: var(--pf-success-100); }

/* ★★★★ ใช้ `.pf-note` เป็น **แถวรายการ** ไม่ใช่แค่กล่องเตือน (เพิ่ม 2026-08-27)
   ⚠⚠⚠ เกิดจริง: หน้า "คำขอของฉัน" วางทุกอย่างไว้ในคอลัมน์ซ้ายคอลัมน์เดียว
     วัดที่จอ 1280 — คอลัมน์นั้นกว้าง **1175px** แต่ข้อความจริงยาวไม่ถึงครึ่ง
     ผลคือแถบสีเต็มความกว้าง มีตัวหนังสือกองอยู่มุมซ้าย ที่เหลือว่างโล่ง
     เจ้าของโปรเจคอ่านว่า "เพี้ยน ไม่สมดุล" — ซึ่งตรงกับที่วัดได้เป๊ะ
   ★ `.pf-note` เป็น flex อยู่แล้ว จึงไม่ต้องสร้างคอมโพเนนต์ใหม่
     ตัวนี้แค่เพิ่ม "ราวขวา" ให้ป้ายสถานะกับปุ่มไปเกาะ แล้วแถวจะเต็มทั้งสองฝั่ง
   ⚠ ต้องมี `--row` ที่ตัวแม่ด้วย ไม่ใช่ใส่แค่ราว — `.pf-note` ฐานไม่มี flex-wrap
     จอแคบแล้วราวจะบีบคอลัมน์ซ้ายจนตัวหนังสือเหลือบรรทัดละไม่กี่ตัว */
.pf-note--row { flex-wrap: wrap; align-items: center; }
/* ★★★ รายการย่อยในกล่องเตือน — ใช้ตอนต้องบอกว่า **อะไรบ้าง** ที่เปลี่ยนไป
   ⚠⚠ เดิมทุกหน้าเขียน `style="margin:… ;padding-left:var(--pf-space-6)"` ฝังในเทมเพลต
     ซึ่งแปลว่าระยะเยื้องของ bullet มีหลายค่าในระบบเดียว และไม่มีใครเห็นว่ามันมีอยู่
   ★ กล่องเตือนที่บอกแค่ "มีบางอย่างเปลี่ยน" โดยไม่บอกว่าอะไร คือกล่องที่คนกดปิดทิ้ง
     รายการนี้จึงเป็นส่วนที่ทำให้แถบเตือนมีประโยชน์จริง ไม่ใช่ของประดับ */
.pf-note-list {
  margin: var(--pf-space-3) 0 0; padding-inline-start: var(--pf-space-6);
  display: grid; gap: var(--pf-space-1);
}
.pf-note-rail {
  flex: none; margin-inline-start: auto;
  display: flex; align-items: center; gap: var(--pf-space-3); flex-wrap: wrap;
}
/* ★ จอแคบ: ราวลงมาอยู่บรรทัดของตัวเอง ชิดซ้ายตามการอ่านปกติ
   ⚠ ปล่อยให้มันเกาะขวาบนมือถือ = ปุ่ม "ถอนใบนี้" ไปอยู่มุมขวาห่างจากเนื้อหาที่มันหมายถึง */
@media (max-width: 560px) {
  /* 🔴🔴 `flex-wrap: wrap` ที่ตัวกล่อง — **ขาดไปตั้งแต่แรก** (วัดเจอ 2026-09-04)
     `.pf-note` เป็น flex แบบ nowrap ⇒ ราวที่กว้าง 100% **ลงบรรทัดใหม่ไม่ได้**
     มันไปกินความกว้างทั้งแถวแทน แล้วบีบไอคอนกับข้อความเหลือ **0px**
     วัดที่ 375px: ราว 318px · พี่น้องอีกสองตัวได้ 0 และ 0 → ข้อความไหลลงมาทีละอักษร
     ⚠⚠ พังพร้อมกันทุกหน้าที่มีราว — ตั้งค่าองค์กร · แพ็กเกจ · วันหยุด · คำขอของฉัน
       · พนักงาน · วันเกิดและครบรอบ (วัดครบทั้งหกหน้า ได้ 0px เหมือนกันหมด)
     ★ กฎ `inline-size: 100%` ข้างล่างเขียนขึ้นโดยตั้งใจให้ราวลงบรรทัดของตัวเอง
       — มันถูกอยู่แล้ว แค่ขาดครึ่งที่ทำให้มันทำงานได้จริง */
  .pf-note { flex-wrap: wrap; }
  .pf-note-rail { margin-inline-start: 0; inline-size: 100%; }
}
.pf-kpi-compare b { font-weight: 600; }
.pf-up { color: var(--pf-success-700); }
.pf-down { color: var(--pf-danger-700); }

.pf-ic-brand { background: var(--pf-pastel-brand); color: var(--pf-primary-ink); }
.pf-ic-green  { background: var(--pf-pastel-green);  color: var(--pf-success-700); }
.pf-ic-blue   { background: var(--pf-pastel-blue);   color: var(--pf-info-700); }
/* ★★★ ชื่อพ้อง `amber` / `red` / `orange` — โทนที่ `app/logic/leave.ts` ผลิต (เหตุผลเดียวกับ P213)
   หน้าตั้งค่าใช้ `'pf-ic-' + look(code).tone` · ขาดตัวไหน กล่องไอคอนจะโปร่งใสโดยไม่มี error
   ⚠ อยู่ใน selector list เดียวกับตัวหลัก **ห้ามแยกบล็อกแล้วลอกค่า** (บทเรียน D42) */
.pf-ic-yellow, .pf-ic-amber { background: var(--pf-pastel-yellow); color: var(--pf-warning-700); }
.pf-ic-pink,   .pf-ic-red   { background: var(--pf-pastel-pink);   color: var(--pf-danger-700); }
.pf-ic-orange               { background: var(--pf-pastel-orange); color: var(--pf-orange-700); }
/* ★ TOKENS.md มี --pf-pastel-cyan แต่ไม่มีสีหมึกคู่ของมัน
   สัญญาห้ามคิดค่าใหม่ → หยิบจากชุดเดิม `--pf-success-700` (วัดได้ 5.01) ไม่ประดิษฐ์สีขึ้นเอง */
.pf-ic-cyan   { background: var(--pf-pastel-cyan);   color: var(--pf-success-700); }
.pf-ic-violet { background: var(--pf-pastel-violet); color: var(--pf-violet-700); }

/* ═══ แถบแจ้งเตือนในหน้า (COMPONENTS.md แถว `Alert`) ═══════════════════════
   ยกมาจาก `_components.scss` `.alert` ตอนขั้น 5.4 (D42 · รอบ 285)
   🔴 **ระบบดีไซน์ไม่เคยมีคอมโพเนนต์นี้เลย** — ตรวจแล้วทั้ง `pf.css` และ `site/design/*.html`
     ไม่มีคลาสไหนทำหน้าที่ "อธิบายสถานการณ์ทั้งหน้า" · จึงเป็นการยกของเข้ามา ไม่ใช่เปลี่ยนชื่อ
   ★ ใช้คู่พาสเทล/หมึกชุดเดียวกับ `.pf-badge--*` เป๊ะ — แถบเตือนกับป้ายที่พูดเรื่องเดียวกัน
     จะได้เป็นสีเดียวกันโดยโครงสร้าง ไม่ใช่โดยความบังเอิญ
   ⚠ `align-items: flex-start` ไม่ใช่ `center` — ข้อความยาวหลายบรรทัดต้องให้ไอคอน
     อยู่ตรงบรรทัดแรก ไม่ใช่ลอยกลางก้อน */
.pf-alert {
  display: flex; gap: var(--pf-space-4); align-items: flex-start;
  padding: var(--pf-space-4) var(--pf-space-5);
  border-radius: var(--pf-radius-md); border: 1px solid transparent;
  font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact);
}
.pf-alert .pf-ico { margin-top: 2px; }
.pf-alert--info { background: var(--pf-pastel-brand);  color: var(--pf-primary-ink); }
.pf-alert--ok   { background: var(--pf-success-50);    color: var(--pf-success-700); }
.pf-alert--warn { background: var(--pf-warning-50);    color: var(--pf-warning-700); }
.pf-alert--bad  { background: var(--pf-danger-50);     color: var(--pf-danger-700); }
/* ทางออกท้ายแถบ — ★ แถบที่บอกว่ามีปัญหาแต่ไม่บอกว่าให้ไปทำอะไรต่อ คือแถบที่ไร้ประโยชน์
   ⚠ `margin-left: auto` ดันไปชิดขวา · `white-space: nowrap` กันไม่ให้ตัดบรรทัดกลางคำ */
.pf-alert-go {
  margin-left: auto; white-space: nowrap; font-weight: 600; color: inherit;
  display: inline-flex; align-items: center; gap: var(--pf-space-1);
  transition: gap var(--pf-duration-fast) var(--pf-ease-out);
}
.pf-alert-go:hover { gap: var(--pf-space-3); }

/* ═══ Badge (COMPONENTS.md) ═══ */
.pf-badge {
  min-height: 24px; padding: 0 var(--pf-space-3); border-radius: var(--pf-radius-pill);
  display: inline-flex; align-items: center; gap: var(--pf-space-2);
  font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption); font-weight: 500; white-space: nowrap;
}
/* ★★★ ชื่อสองชุดต่อสีเดียว — **นิยามเดียว ไม่ได้ลอกค่า** (ขั้น 5.3-ฉ · รอบ 284)
   ชุดแรกเป็นชื่อเชิงความหมายทั่วไปที่หน้าดีไซน์ใช้ (success/warning/danger/neutral)
   ชุดที่สองเป็นคำที่ตรรกะกลางของแอปคืนออกมาจริง (`ok`/`warn`/`bad`/`gone`)
   ซึ่ง `statusLookOf()` · `stageLook()` · `look()` ใช้ร่วมกันอยู่แล้วทั้งระบบ
   ⚠⚠ **ห้ามแยกเป็นสองบล็อกแล้วเขียนค่าเหมือนกัน** — นั่นคือสองแหล่งที่ตรงกันชั่วคราว
     ซึ่งเป็นบทเรียนที่แพงที่สุดของโปรเจคนี้ (หัวข้อ D42) · จัดกลุ่ม selector แทน */
/* ★★★ ชื่อชุดที่สาม = **ชื่อสี** (`blue` · `green` · `amber` · `violet` · `red` · `orange`)
   `app/logic/leave.ts` ตั้งโทนของ *ประเภทการลา* ด้วยชื่อสี ไม่ใช่ชื่อความหมาย
   เพราะ "ลาป่วย" กับ "ลากิจ" ไม่มีอันไหนดีหรือแย่กว่ากัน — มันแค่ต้องแยกออกจากกันด้วยตา
   🔴 ชุดนี้ **หายไปตอนย้าย** จนป้ายประเภทการลาโปร่งใสหมด (P213 · เจอรอบ 286)
   ⚠ อยู่ในบล็อกเดียวกับชื่ออื่นเสมอ ห้ามแยกไปเขียนค่าซ้ำ (เหตุผลเดียวกับที่เขียนไว้ข้างบน) */
.pf-badge--success, .pf-badge--ok,   .pf-badge--green  { background: var(--pf-success-50); color: var(--pf-success-700); }
.pf-badge--warning, .pf-badge--warn, .pf-badge--amber  { background: var(--pf-warning-50); color: var(--pf-warning-700); }
.pf-badge--danger,  .pf-badge--bad,  .pf-badge--red    { background: var(--pf-danger-50);  color: var(--pf-danger-700); }
.pf-badge--info,                     .pf-badge--blue   { background: var(--pf-info-50);    color: var(--pf-info-700); }
.pf-badge--neutral, .pf-badge--gone                    { background: var(--pf-gray-100);   color: var(--pf-gray-700); }
/* ── สามโทนที่ระบบดีไซน์ยังไม่มีในป้าย แต่มีอยู่แล้วในกล่องไอคอน (`.pf-ic-*`) ──
   ★ ใช้คู่พาสเทล/หมึกตัวเดียวกับ `.pf-ic-violet` · `.pf-ic-cyan` เป๊ะ
     ป้ายกับกล่องไอคอนของสถานะเดียวกันจึงเป็นสีเดียวกันโดยโครงสร้าง ไม่ใช่โดยความบังเอิญ
   ⚠ ยุบสามตัวนี้ไปรวมกับห้าตัวบนไม่ได้ — เจ้าของเคาะไว้รอบ 258 ว่าสถานะต้องมีสีประจำตัว
     "รอเริ่มงาน" · "ทดลองงาน" · "ถูกระงับ" เป็นคนละเรื่องกันทั้งสามสถานะ */
.pf-badge--wait,  .pf-badge--violet { background: var(--pf-pastel-violet); color: var(--pf-violet-700); }
.pf-badge--trial                    { background: var(--pf-pastel-cyan);   color: var(--pf-success-700); }
.pf-badge--hold,  .pf-badge--orange { background: var(--pf-pastel-orange); color: var(--pf-orange-700); }
/* ★ สีต้องไม่ใช่สัญญาณเดียว (DESIGN.md §10) — ทุกป้ายมีจุดนำหน้า */
.pf-badge i { width: 6px; height: 6px; border-radius: 50%; flex: none; background: currentColor; }
/* ★ ป้ายที่มีไอคอนใช้ไอคอนแทนจุด — ไอคอนสื่อความหมายได้มากกว่าจุดสีเปล่า ๆ
   ⚠ ต้องกำหนดขนาดตรงนี้ ไม่ใช่ปล่อยตาม `.pf-ico` (1em) — ในป้ายตัวอักษรเล็ก 12px
     ไอคอน 12px จะดูใหญ่เท่าตัวหนังสือจนแย่งสายตา · 13px คือค่าที่ชุดเดิมใช้และดูพอดี */
.pf-badge .pf-ico { width: 13px; height: 13px; flex: none; margin-inline-start: -2px; }

/* ═══ Data table (COMPONENTS.md) ═══ */
/* ⚠⚠ `overflow-x: auto` อย่างเดียว **ไม่พอ** ถ้ากล่องนี้เป็นลูกของกริด/เฟล็กซ์
   ค่าปริยาย `min-width: auto` ทำให้มันไม่ยอมแคบกว่าตาราง แล้ว overflow ก็ไม่มีอะไรให้ทำ
   ผลคือทั้งหน้าเลื่อนแนวนอนแทนที่จะเป็นตารางเลื่อนในกล่องตัวเอง
   ★ วัดที่ 375px: ตารางกว้าง 927px ดันหน้า leave ล้น 216px — เห็นก็ต่อเมื่อไปวัด */
.pf-table-scroll { overflow-x: auto; min-width: 0; }
.pf-table thead th {
  height: 40px; background: var(--pf-surface-subtle); color: var(--pf-text-muted);
  font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption); font-weight: 600; text-align: left; white-space: nowrap;
  padding: 0 var(--pf-space-5); border-bottom: 1px solid var(--pf-border);
  position: sticky; top: 0; z-index: 1;
}
.pf-table td {
  height: 48px; padding: var(--pf-space-3) var(--pf-space-5);
  font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact); border-bottom: 1px solid var(--pf-row-border);
  white-space: nowrap;
}
.pf-table tbody tr:last-child td { border-bottom: 0; }

/* ═══ ≤640px · ตารางกลายเป็นการ์ด (RESPONSIVE.md เคาะไว้แล้ว) ═══════════════
   ★★★★ บนมือถือ **ห้ามเลื่อนแนวนอน** — ต้องพอดีจอทุกขนาด
     ตารางแปดคอลัมน์ยัดลงจอ 375px ไม่ได้ในทางกายภาพ · การให้เลื่อนคือการยอมแพ้
     คนถือมือถือข้างเดียวเลื่อนซ้าย-ขวาเพื่ออ่านค่าจ้างของคนเดียว = ใช้ไม่ได้จริง
   ★ หนึ่งแถว = หนึ่งการ์ด · ช่องแรกเป็นชื่อเรื่อง · ที่เหลือเป็น "ป้าย ⟷ ค่า"
   ⚠ ป้ายมาจาก `<thead>` โดย `pf-ui.js` ใส่ให้อัตโนมัติ — ห้ามพิมพ์ใส่รายหน้า */
@media (max-width: 640px) {
  .pf-table-scroll { overflow-x: visible; }
  .pf-table, .pf-table tbody, .pf-table tr, .pf-table td { display: block; width: 100%; }
  .pf-table thead { display: none; }
  .pf-table tbody tr {
    background: var(--pf-surface); border: 1px solid var(--pf-border);
    border-radius: var(--pf-radius-md); padding: var(--pf-space-4);
    margin-bottom: var(--pf-space-3);
  }
  .pf-table tbody tr:hover { background: var(--pf-surface); }   /* ไม่มีเมาส์บนมือถือ */
  .pf-table td {
    height: auto; padding: var(--pf-space-2) 0; border-bottom: 0;
    white-space: normal;                                        /* ★ ต้องปลด ไม่งั้นการ์ดก็ยังล้น */
    display: flex; align-items: baseline; justify-content: space-between; gap: var(--pf-space-4);
    text-align: left;
  }
  .pf-table td::before {
    content: attr(data-label); flex: none; color: var(--pf-text-muted);
    font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption);
  }
  /* ⚠ ซ่อนเฉพาะช่องที่ **ตั้งใจ** ให้ว่าง (ชื่อเรื่อง · ปุ่ม) — ช่องที่ลืมใส่จะโล่งให้เห็น */
  .pf-table td[data-label=""]::before { display: none; }
  .pf-table td[data-label=""] { display: block; }
  .pf-table td:first-child { padding-top: 0; padding-bottom: var(--pf-space-3); }
  .pf-table td.pf-r { text-align: left; }
  /* ★ ช่องจัดการที่ชิดขวาบนจอกว้าง ต้องกลับมาชิดซ้ายบนการ์ด เหมือนช่องอื่นทุกช่อง
     (คู่กับกฎ `.pf-table .pf-r .pf-actions` ข้างล่าง — แก้ตัวหนึ่งต้องดูอีกตัว) */
  .pf-table td.pf-r .pf-actions { justify-content: flex-start; }
  /* ★ ปุ่มตัวอักษรในช่องจัดการกินเต็มความกว้างบนการ์ด — กดง่ายด้วยนิ้วเดียว
     ⚠⚠ **ยกเว้นปุ่มไอคอนล้วน** — มันต้องเป็นจัตุรัส
       วัดแล้วเจอ 2026-08-28: ปุ่มเปิดแผนที่/แชร์ยืดเป็น 284x36 บนจอ 375
       ไอคอนเดียวลอยกลางปุ่มยาวทั้งบรรทัด อ่านไม่ออกว่ามันคือปุ่มอะไร
     ★ และปุ่มไอคอนหลายตัวควรอยู่แถวเดียวกัน ไม่ใช่ซ้อนกันคนละบรรทัด */
  .pf-table td:last-child .pf-btn:not(.pf-btn--icon) { width: 100%; }
}
.pf-table tbody tr:hover { background: var(--pf-surface-hover); }
.pf-table .pf-r { text-align: right; font-variant-numeric: tabular-nums; }
/* ★★★ ปุ่ม **หลายตัว** ในช่องที่จัดชิดขวา (เจ้าของทัก 2026-09-09 · หน้าบิล)
   🔴 `text-align: right` ขยับลูกของ flex ไม่ได้ — `.pf-actions` เป็น flex
     ⇒ หัวคอลัมน์ชิดขวา แต่ปุ่มยังกองอยู่ซ้ายสุดของช่องที่กว้างมาก อ่านแล้วไม่เป็นคู่กัน
   ★ ช่องที่มีปุ่ม **ตัวเดียว** ไม่เจออาการนี้ เพราะปุ่มเปล่าเป็น inline-flex
     ซึ่ง `text-align` ขยับได้ — นั่นคือเหตุที่ตารางในแกลเลอรีดูถูกมาตลอด
   ⚠ บนการ์ด (จอแคบ) กลับไปชิดซ้าย — ดูกฎคู่กันในบล็อกมือถือข้างบน */
.pf-table .pf-r .pf-actions { justify-content: flex-end; }
.pf-sortable { display: inline-flex; align-items: center; gap: var(--pf-space-2); color: inherit; font: inherit; }
/* ★ `stroke-width` ต้องเขียนที่นี่ ไม่ใช่ปล่อยให้เป็นแอตทริบิวต์บน <svg> รายจุด
   ลูกศรตัวนี้เล็ก 12px — เส้น 1.8 ตามค่าปริยายของ `.pf-ico` บางจนดูจาง
   หน้าดีไซน์เขียน `stroke-width="2.4"` ติดในแท็กไว้ทุกจุด ซึ่ง CSS ชนะเสมอ
   ผลคือแอปได้ 1.8 ส่วนหน้าดีไซน์ได้ 2.4 — ต่างกันโดยไม่มีใครตั้งใจ (วัดรอบ 281) */
.pf-sortable svg { opacity: .6; stroke-width: 2.4; }

/* ═══════════════════════════════════════════════════════════════════════════
   ตารางกว้างเกินจอ — โน้ตบุ๊ก/แท็บเล็ต (เจ้าของทัก 2026-09-02)

   🔴 คำทัก: "layouts responsive อยู่ในขั้นแย่ ... เริ่มปรับของโน้ตบุ๊กกับแท็บเล็ตก่อน"
     วัดทุกหน้าที่ 768 · 1024 · 1280 · 1440 แล้วได้สามอาการที่เป็นคนละเรื่องกัน:
       ① ปุ่มช่อง "การกระทำ" ห่อเป็น 3 บรรทัด ⇒ แถวสูง **120px** แทนที่จะเป็น 48
       ② ตาราง 7–10 คอลัมน์ล้น 100–800px โดย **ไม่มีอะไรบอกว่ามันเลื่อนได้**
       ③ เลื่อนไปขวาแล้ว **ไม่รู้ว่ากำลังดูแถวของใคร** — ชื่อเลื่อนหายไปทางซ้าย
     ★ บล็อกนี้แก้ ② กับ ③ · ข้อ ① อยู่บล็อกถัดไป
   ⚠ ปรับ 2026-09-03: ข้อ ② เหลือ **แถบเลื่อนที่เห็นตลอด** เป็นตัวบอกอย่างเดียว
     เจ้าของสั่งถอดเงาขอบขวาออก — รายละเอียดอยู่บล็อกถัดลงไป

   ⚠⚠ ทางที่ **ไม่เลือก**: ซ่อนคอลัมน์รองตอนจอแคบ
     ซ่อนแล้วผู้ใช้ไม่มีทางเห็นข้อมูลนั้นเลย และไม่มีอะไรบอกด้วยว่ามีคอลัมน์ที่ถูกซ่อน
     ⇒ "ตารางที่เลื่อนได้ + รู้ว่าเลื่อนได้ + รู้ว่าแถวของใคร" ยังเห็นครบทุกช่อง
   ═══════════════════════════════════════════════════════════════════════════ */

/* 🔴🔴 **ขอบขวาไม่มีเงา — เจ้าของสั่งถอดออก 2026-09-03** ("เอาเงาออกไปเลยดีกว่า")
   ประวัติสองรอบก่อนหน้า เก็บไว้กันคนกลับไปทำซ้ำ:
     รอบ ① `linear-gradient` กว้าง 32px → อ่านออกมาเป็น **แถบเทาแบน** คร่อมคอลัมน์สุดท้าย
     รอบ ② `box-shadow: inset` → เป็นเงาจริงแล้ว แต่เจ้าของยังไม่เอา
   ★★ ตัวบอกว่า "เลื่อนได้" ที่เหลืออยู่คือ **แถบเลื่อนแนวนอน** ซึ่งระบบนี้ตั้งให้
     *เห็นตลอด* ไว้แล้ว (ดูบล็อก scrollbar ด้านบน — เลือกแบบ "ผูกกับธีม เห็นตลอด"
     ไว้ตั้งแต่ต้น) ⇒ ถอดเงาออกแล้วยังไม่กลายเป็น "ไม่มีอะไรบอกเลย" แบบก่อนรอบ 2026-09-02
   ⚠ `data-edge` **ยังต้องมีอยู่** — เงาใต้คอลัมน์ที่ตรึง (บล็อกถัดไป) ใช้ค่า left/both
     อย่าไปถอด `scrollEdge()` หรือ attribute ทิ้งเพราะเห็นว่าขอบขวาไม่ใช้แล้ว */

/* ★★★★ ตรึงคอลัมน์แรก — ทุกตารางในระบบมีคอลัมน์แรกเป็น "นี่คือแถวของใคร"
     พนักงาน · ผู้สมัคร · ชื่อกะ · ชื่อจุด · หัวข้อ · รอบ  (ตรวจครบทั้ง 13 ตารางแล้ว)
   ⚠ เฉพาะ ≥641px — ต่ำกว่านั้นตารางกลายเป็นการ์ด ไม่มีการเลื่อนแนวนอนให้ตรึง */
@media (min-width: 641px) {
  .pf-table :is(thead th, tbody td):first-child { position: sticky; left: 0; z-index: 2; }
  /* ⚠ ช่องที่ตรึงต้องมีพื้นทึบ ไม่งั้นคอลัมน์อื่นจะเลื่อนมาซ้อนให้เห็นทะลุ */
  .pf-table tbody td:first-child { background: var(--pf-surface); }
  /* ★★★ ลำดับชั้นต้องครบทั้งสามระดับ ไม่ใช่ตั้งเฉพาะช่องมุม
     ⚠⚠ ตั้งแค่ช่องมุมแล้วปล่อย `thead th` ตัวอื่นไว้ที่ 1 จะเกิดรูเงียบ ๆ:
       ช่องแรกของ **ตัวตาราง** (2) สูงกว่าหัวตาราง (1) ⇒ ถ้าวันไหนตารางเลื่อนแนวตั้งได้
       ชื่อพนักงานจะลอยทับหัวคอลัมน์ · วันนี้ยังไม่เกิดเพราะกล่องไม่มีความสูงจำกัด
       แต่มันคือกับดักที่รอคนใส่ `max-height` ในอนาคต */
  .pf-table thead th { z-index: 3; }
  .pf-table thead th:first-child { z-index: 4; }
  /* ⚠⚠ พื้นของตัวเองบังสีชี้แถว — ต้องเขียนซ้ำ ไม่งั้นชี้แล้วคอลัมน์แรกไม่เปลี่ยนสี */
  .pf-table tbody tr:hover td:first-child { background: var(--pf-surface-hover); }
  /* ⚠ แถวหัวกลุ่มใช้ colspan กินทั้งแถว — ตรึงแล้วตัวหนังสือจะลอยตามการเลื่อน */
  .pf-table tbody tr.pf-table-group td { position: static; }
  /* ★ เงาใต้คอลัมน์ที่ตรึง — ขึ้นเฉพาะตอนเลื่อนไปแล้วจริง ๆ (`data-edge` มาจาก pf-table) */
  .pf-table-scroll[data-edge="left"] .pf-table :is(th, td):first-child,
  .pf-table-scroll[data-edge="both"] .pf-table :is(th, td):first-child {
    box-shadow: 8px 0 12px -8px var(--pf-scroll-shade);
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   ปุ่ม "การกระทำ" ในแถวตาราง — ยุบเหลือไอคอนบนโน้ตบุ๊ก/แท็บเล็ต

   🔴 วัดจริงก่อนแก้ (หน้าพนักงาน · ปุ่มสามตัวมีข้อความ):
     คอลัมน์ทั้งแปดต้องการ **1,294px** แต่ได้จริง 1,140px ที่จอ 1440
     ⇒ `flex-wrap` ทำงานตามหน้าที่ = ห่อเป็น 3 บรรทัด ⇒ แถวสูง 120px แทน 48px
     ★ ตารางที่แถวสูง 120px ไม่ใช่ตาราง — มันคือรายการการ์ดที่มีเส้นตาราง
   ★★★ ยุบเหลือไอคอน: ปุ่มสามตัวกว้างรวม ~124px แทน ~330px ⇒ คืนที่ให้ข้อมูล 206px

   ⚠⚠ ซ่อนข้อความด้วย `clip` **ไม่ใช่** `display: none`
     `display: none` ลบข้อความออกจากต้นไม้การเข้าถึงไปด้วย ⇒ ปุ่มไม่มีชื่อ
     คนที่ใช้โปรแกรมอ่านหน้าจอจะได้ยินแค่ "ปุ่ม" ซ้ำ ๆ สามครั้งโดยไม่รู้ว่าปุ่มอะไร
   ★ และทุกปุ่มต้องมี `title` — คนใช้เมาส์ต้องรู้ว่าไอคอนคืออะไรโดยไม่ต้องกดลอง
     (เครื่องตรวจ mock บังคับข้อนี้ไว้แล้ว — ปุ่มในแถวตารางที่ไม่มี title = แดง)

   ⚠ ขอบล่าง 641px — ต่ำกว่านั้นตารางเป็นการ์ด ซึ่งมีที่ให้ข้อความเต็มอยู่แล้ว
   ⚠ ขอบบน 1535px — เจ้าของสั่ง 2026-09-02 ว่า "pc ยังไม่ให้ทำ"
     ★★ แต่วัดแล้ว **จอ pc ก็ห่อเหมือนกัน** — ข้อความจะพอดีต้องกว้างราว 1,632px ขึ้นไป
       จดไว้เพื่อไม่ให้ใครสรุปว่า 1535 คือความกว้างที่ข้อความพอดี · มันไม่ใช่
   ═══════════════════════════════════════════════════════════════════════════ */
@media (min-width: 641px) and (max-width: 1535px) {
  .pf-table .pf-row-acts { flex-wrap: nowrap; }
  /* ⚠ ต้องมี `position: relative` — ป้ายที่ซ่อนเป็น `absolute` ถ้าไม่มีที่ยึด
     มันจะไปยึดกับกล่องที่ positioned ตัวถัดไป แล้วดันหน้าให้ล้น */
  .pf-table .pf-row-acts .pf-btn { position: relative; }
  .pf-table .pf-row-acts .pf-btn-label {
    position: absolute; width: 1px; height: 1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  }
  /* ★ `aspect-ratio: 1` แทนการตั้ง width เป็นตัวเลข — ปุ่ม --sm (36) กับปุ่มปกติ (40)
       ได้จัตุรัสถูกต้องทั้งคู่โดยไม่ต้องเขียนสองกฎ (บทเรียนเดียวกับ .pf-btn--icon) */
  .pf-table .pf-row-acts .pf-btn:not(.pf-btn--icon) {
    aspect-ratio: 1; padding: 0; gap: 0; flex: none;
  }
  /* ⚠ ต้องชนะ `.pf-row-acts > .pf-btn .pf-ico { 15px }` — ไอคอน 15px ในปุ่มเปล่าเล็กเกินไป */
  .pf-table .pf-row-acts .pf-btn .pf-ico { width: 18px; height: 18px; }
}
/* ★★★ แท็บเล็ตใช้นิ้ว — เป้าสัมผัสต้อง 44px ตามระบบดีไซน์
   ⚠ กฎ `pointer: coarse` ที่มีอยู่คุมเฉพาะ `.pf-btn--icon` · ปุ่มที่ยุบตรงนี้ไม่ใช่คลาสนั้น
     ⇒ ถ้าไม่เขียนข้อนี้ แท็บเล็ตจะได้ปุ่มจริง 36px ซึ่งเล็กกว่าเกณฑ์ที่โปรเจคตั้งเอง */
@media (min-width: 641px) and (max-width: 1535px) and (pointer: coarse) {
  .pf-table .pf-row-acts .pf-btn {
    min-width: var(--pf-control-lg); min-height: var(--pf-control-lg);
  }
}

/* ═══ แถวหัวกลุ่มในตาราง ═══════════════════════════════════════════════════
   ยกมาจาก `_components.scss` `.tbl .group` ตอนขั้น 5.3-ค (D42 · รอบ 281)
   ★ ใช้กับกรณี "พ้นสภาพแต่จ่ายตามวันที่ทำงาน" ที่ต้องเห็นพร้อมเหตุผล (D4)
   ⚠ `colspan` ทำให้ช่องนี้กว้างเต็มแถว — บนมือถือที่ตารางกลายเป็นการ์ด
     ต้องปลด `::before` ทิ้ง ไม่งั้นจะขึ้นป้ายชื่อคอลัมน์ซ้อนหัวกลุ่ม */
/* ★★★★ แถวที่ "มีอยู่แต่ปิดใช้" — ต้องอยู่ที่นี่ **ไม่ใช่ในบล็อก styles: ของหน้า** (P214 · รอบ 289)
   🔴 `<tr>` ถูกวาดโดย `pf-table` ไม่ใช่โดยหน้าที่ส่งชื่อคลาสเข้ามา
     Angular ผูก `styles:` ของแต่ละคอมโพเนนต์ด้วยแอตทริบิวต์ `_ngcontent-*`
     กฎที่เขียนในหน้าจึงกลายเป็น `.dim[_ngcontent-ของหน้า]` ซึ่ง **ไม่มีวันตรงกับ `<tr>` ตัวนั้น**
     วัดจริง: `<tr>` มีคลาสครบ · กฎอยู่ในเอกสารจริง · แต่ `opacity` ยังได้ **1** (ควรเป็น .65)
   ⚠ ไม่มี error ไม่มี warning — เป็นตระกูล "เขียนแล้วไม่มีผล เงียบ ๆ" (P197)
   ★ กติกาที่ได้: **คลาสที่ส่งข้ามคอมโพเนนต์ ต้องนิยามใน `pf.css` เท่านั้น** */
.pf-table tbody tr.pf-row--off { opacity: .65; }
/* ★★★★ แถวหัวกลุ่ม — **ต้องอ่านออกว่าเป็นเส้นแบ่ง** ไม่ใช่แค่แถวสีจาง
   🔴 เดิมพื้นหลังเป็น `--pf-surface-subtle` ซึ่งวัดได้ #FCFCFD บนพื้น #FFF
     = ต่างกันสามจุดสี · **แทบมองไม่เห็น** (เจ้าของทัก 2026-09-05 ว่ายังหายาก)
   ★ สิ่งที่ทำให้เห็นจริงคือ **เส้นบน** ไม่ใช่พื้นหลังที่เข้มขึ้น
     เส้นบอกว่า "ของข้างบนจบแล้ว" ซึ่งเป็นสิ่งที่หัวกลุ่มมีไว้บอก
   ⚠ ไม่ทำให้พื้นเข้มมาก เพราะแถวหัวกลุ่มไม่ใช่ข้อมูล — เข้มกว่าข้อมูลจะแย่งสายตา
   ⚠⚠ กลุ่มแรกไม่ต้องมีเส้นบน — ไม่มีอะไรอยู่เหนือมันให้คั่น (รูปเดียวกับ .pf-form-sec) */
.pf-table tbody tr.pf-table-group td {
  height: 34px; background: var(--pf-gray-50); color: var(--pf-text-secondary);
  font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption);
  font-weight: 700; letter-spacing: .04em; white-space: nowrap;
  border-top: 2px solid var(--pf-border);
}
.pf-table tbody tr.pf-table-group:first-child td { border-top: 0; }
.pf-table tbody tr.pf-table-group:hover td { background: var(--pf-gray-50); }

/* 🔴 เคยมีบล็อก `.pf-pager*` ซ้อนอยู่ตรงนี้ (รอบ 281) พร้อมคอมเมนต์ว่า "ระบบดีไซน์ไม่เคยมี
   ตัวแบ่งหน้าเลย" ซึ่งไม่จริง — `.pf-pagination` อยู่ข้างล่างในไฟล์เดียวกันนี้มาก่อนแล้ว
   และ `/design/*.html` ใช้มัน 4 ที่ · ลบทิ้งตอนขั้น 5 · ตัวแบ่งหน้ามีชุดเดียวคือ `.pf-pagination` */

.pf-person { display: flex; align-items: center; gap: var(--pf-space-4); min-width: 0; }
.pf-avatar {
  width: 32px; height: 32px; flex: none; border-radius: 50%; display: grid; place-items: center;
  background: var(--pf-pastel-brand); color: var(--pf-primary-ink);
  font-size: 12px; font-weight: 600;
}
/* ★ อวตารเล็ก — ใช้ในตารางที่แถวมีข้อมูลแน่นและไม่ควรถูกดันให้สูงขึ้น (ยกมารอบ 286)
   ⚠ ระบบดีไซน์มีขนาดเดียว เพราะหน้าตัวอย่างมีตารางแบบเดียว
     ตารางลงเวลาของจริงมี 6 คอลัมน์และหลายร้อยแถว — 32px ดันความสูงรวมจนเห็นข้อมูลต่อหน้าน้อยลง */
.pf-avatar--sm { width: 26px; height: 26px; font-size: var(--pf-size-caption); }

/* ═══ อวตารที่มีรูปจริง + สภาพ "ยังไม่มีรูป" (2026-08-28) ═══════════════════
   ★★★★ เงื่อนไขบังคับของ D30 — **โหลดรูปไม่ขึ้นต้องตกไปที่ตัวอักษรย่อ ไม่ใช่ภาพแตก**
     D30 เคาะเกณฑ์ว่า "ของชิ้นนี้ล่มแล้ว พนักงานยังลงเวลาได้ไหม" · รูปโปรไฟล์ตอบว่าได้
     จึงดึงจากภายนอกได้ **โดยมีเงื่อนไขเดียวกับแผนที่ (P13)**: ของนอกล่ม → หน้าต้องยังทำงาน
   ★ ทำด้วย CSS ล้วน: ตัวอักษรย่ออยู่ใน `.pf-avatar` ตามปกติ · `<img>` วางทับด้านบน
     รูปโหลดไม่ขึ้น → เบราว์เซอร์ไม่วาด `<img>` → เห็นตัวอักษรย่อที่อยู่ข้างใต้พอดี
   ⚠ ห้ามแก้เป็น `background-image` — พื้นหลังที่โหลดไม่ขึ้นไม่มีเหตุการณ์ให้จับ
     และจะได้วงกลมเปล่าที่ไม่มีทั้งรูปและตัวอักษร */
.pf-avatar { position: relative; overflow: hidden; }
.pf-avatar img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; border-radius: inherit;
}

/* ★ อวตารขนาดใหญ่สำหรับกล่องแก้ไข — ที่เดียวที่คนดูรูปจริง ๆ ไม่ใช่แค่แยกแถว */
.pf-avatar--lg { width: 72px; height: 72px; font-size: var(--pf-size-section); }

/* ★★★ โทนสีของวงกลม — คงที่ต่อคนเสมอ (คำนวณที่ `app/logic/avatar.ts`)
   ⚠ สุ่มทุกครั้งที่โหลดหน้า = ตาจำคนจากสีไม่ได้ ซึ่งทำให้สีไม่มีประโยชน์เลย
   ★ ใช้สีพาสเทลที่มีอยู่แล้ว ไม่ได้ตั้งค่าสีใหม่ — ตัวอักษรบนพาสเทลผ่านคอนทราสต์อยู่แล้ว */
.pf-avatar--t0 { background: var(--pf-pastel-brand); }
.pf-avatar--t1 { background: var(--pf-pastel-blue); }
.pf-avatar--t2 { background: var(--pf-pastel-green); }
.pf-avatar--t3 { background: var(--pf-pastel-yellow); }
.pf-avatar--t4 { background: var(--pf-pastel-violet); }
.pf-avatar--t5 { background: var(--pf-pastel-pink); }

/* ═══ แถบตัวตนของหน้าโปรไฟล์ (2026-08-28) ═══════════════════════════════════
   ★★★★ ทำไมต้องมี — หน้าโปรไฟล์เดิมเป็นกล่อง label→value เรียงลงมาอย่างเดียว
     พอข้อมูลครบจริง (24 ช่อง) มันกลายเป็นกำแพงยาว **ที่ไม่มีจุดเริ่มต้นให้ตามอง**
     ⚠ และของที่สำคัญที่สุดของหน้านี้ — "นี่คือฉันไหม" — กระจายอยู่กลางรายการ
   ★ แถบนี้ตอบคำถามเดียว: ใคร · ตำแหน่งอะไร · สถานะอะไร — จบในสายตาเดียว
   ⚠ ไม่ใช่การ์ดใหม่ ใช้ `.pf-card` เดิมเป็นฐาน · ที่นี่เพิ่มแค่การจัดวางข้างใน */
.pf-idcard { display: flex; align-items: center; gap: var(--pf-space-5); flex-wrap: wrap; }
.pf-idcard-main { min-width: 0; flex: 1 1 240px; display: grid; gap: var(--pf-space-2); }
.pf-idcard-name {
  font-size: var(--pf-size-section); line-height: var(--pf-lh-section);
  font-weight: var(--pf-w-semibold); color: var(--pf-text-primary);
  overflow-wrap: anywhere;
}
/* ★ บรรทัดรอง: ตำแหน่ง · แผนก · รหัส — คั่นด้วยจุดกลาง ไม่ใช่ขึ้นบรรทัดใหม่ทีละอย่าง */
.pf-idcard-meta {
  font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption);
  color: var(--pf-text-muted); display: flex; gap: var(--pf-space-3); flex-wrap: wrap;
}
.pf-idcard-meta > * { display: inline-flex; align-items: center; gap: var(--pf-space-3); }
.pf-idcard-meta > * + *::before { content: '·'; color: var(--pf-border-strong); }
.pf-idcard-rail { flex: none; margin-inline-start: auto; display: flex; align-items: center; gap: var(--pf-space-3); flex-wrap: wrap; }
@media (max-width: 560px) {
  /* ⚠ จอแคบ: ราวขวาลงมาชิดซ้าย — เหตุผลเดียวกับ `.pf-note-rail` */
  .pf-idcard-rail { margin-inline-start: 0; inline-size: 100%; }
}

/* ═══ กริดของกล่องข้อมูล (2026-08-28) ═══════════════════════════════════════
   ★★★ สองคอลัมน์บนจอกว้าง · คอลัมน์เดียวบนจอแคบ — **ไม่ใช่แค่ความสวย**
     ของเดิมเรียงลงมาคอลัมน์เดียว วัดที่ 1280 ได้กล่องกว้าง ~1175px
     แต่เนื้อในแต่ละแถวคือ "ป้าย ... ค่า" ที่ยาวไม่ถึงครึ่ง → ที่ว่างกลางกล่องมหาศาล
     และผู้ใช้ต้องเลื่อนยาวเพื่อดูของที่พออยู่ในหน้าจอเดียวได้
     ⚠ ตระกูลเดียวกับที่เจ้าของทักหน้า "คำขอของฉัน" ว่า "เพี้ยน ไม่สมดุล"
   ★ `auto-fit` + `minmax` แทน `repeat(2, 1fr)` ตายตัว — กล่องสุดท้ายที่เหลือใบเดียว
     จะกินเต็มแถวเอง ไม่ทิ้งช่องว่างข้างขวาให้ดูเหมือนของหาย
   ⚠ `min(360px, 100%)` จำเป็น — `360px` เปล่า ๆ จะดันกริดล้นบนจอ 320px */
.pf-info-grid {
  display: grid; gap: var(--pf-section-gap);
  grid-template-columns: repeat(auto-fit, minmax(min(360px, 100%), 1fr));
  align-items: start;
}
/* ★ กล่องข้างในเป็น `.pf-money` ซึ่งมี margin-top ของตัวเอง — ในกริดไม่ต้องการ
   ⚠ ปล่อยไว้ = แถวบนกับแถวล่างห่างไม่เท่ากัน เพราะ gap ของกริดบวกกับ margin */
.pf-info-grid > section > .pf-money { margin-top: var(--pf-space-4); }

/* ═══ ป้ายประเภท DISC (2026-08-29) ═══════════════════════════════════════════
   ★★★★ เจ้าของขอให้ **บอกประเภทตรง ๆ** ว่าเป็นไทป์อะไร ไม่ใช่ให้อ่านเอาจากแท่ง
     "เห็นแล้วเข้าใจทันที" คือเกณฑ์ · ตัวอักษร D·I·S·C จึงต้องเด่นกว่าตัวเลข

   ★★★ กล่องตัวอักษรใช้ทรงเดียวกับไอคอน KPI (`.pf-kpi-icon`) ตามที่เจ้าของชี้
     ⚠ **ไม่ลอกค่าสี** — ใช้คลาส `.pf-ic-*` เดิมทั้งชุด (D42 · ลอกค่าแล้วมันจะเพี้ยนคนละทาง)
       โทนของแต่ละแกนมาจาก `DISC_LABEL[ax].tone` ที่ `app/logic/disc.ts`

   ⚠⚠ ป้ายของแกนที่ **ไม่ใช่แกนเด่น** ต้องจางลง ไม่ใช่หายไป
     ซ่อนทิ้ง = อ่านไม่ออกว่าคะแนนที่เหลือกระจายยังไง ซึ่งเป็นครึ่งหนึ่งของข้อมูล */
.pf-type-row { display: flex; gap: var(--pf-space-3); flex-wrap: wrap; margin-block: var(--pf-space-4); }
.pf-type {
  display: flex; align-items: center; gap: var(--pf-space-3);
  padding: var(--pf-space-2) var(--pf-space-4) var(--pf-space-2) var(--pf-space-2);
  border: 1px solid var(--pf-border); border-radius: var(--pf-radius-md);
  background: var(--pf-surface);
}
/* ★ แกนที่ไม่เด่น — จางลงทั้งใบ แต่ยังอ่านออก (ไม่ใช้ opacity ต่ำจนตัวเลขหาย) */
.pf-type--off { opacity: .55; }
.pf-type-mark {
  width: 34px; height: 34px; flex: none; border-radius: var(--pf-radius-sm);
  display: grid; place-items: center;
  font-weight: var(--pf-w-bold); font-size: var(--pf-size-section);
  /* ⚠ ตัวอักษรละตินตัวเดียวในกล่องสี่เหลี่ยม — ต้องไม่มี line-height ของฟอนต์ไทยมาดันเบี้ยว */
  line-height: 1;
}
.pf-type-name { font-weight: var(--pf-w-medium); color: var(--pf-text-primary); }
.pf-type-score { color: var(--pf-text-muted); font-size: var(--pf-size-caption); }
/* ★★ ป้ายบอกว่า "นี่คือแกนเด่น" — เสมอกันหลายแกนก็ติดได้หลายใบ (topDiscAxes คืนทุกแกนที่เท่ากัน) */
.pf-type--top { border-color: var(--pf-primary-300); }

/* ═══ ผลแบบสำรวจฝั่ง HR (2026-08-28) ═══════════════════════════════════════
   ★★★★ แถวมาตรวัด 1–5 แบบ **อ่านอย่างเดียว** — ไม่ใช่ `.pf-segment` ซึ่งกดได้
     ⚠⚠ ใช้ปุ่มที่กดไม่ได้มาแสดงผล = ผู้ใช้พยายามกดแล้วไม่มีอะไรเกิดขึ้น
       (ตระกูลเดียวกับ P266 — ของที่หน้าตาเหมือนกดได้ ต้องกดได้)
   ★ จุดทึบ/จุดจาง อ่านค่าออกได้ในสายตาเดียวโดยไม่ต้องอ่านตัวเลข */
.pf-scale-read { display: flex; align-items: center; gap: var(--pf-space-3); }
.pf-scale-dots { display: flex; gap: 4px; }
.pf-scale-dots i {
  inline-size: 10px; block-size: 10px; border-radius: 50%;
  background: var(--pf-border); display: block;
}
.pf-scale-dots i.on { background: var(--pf-primary-500); }

/* ★★★ ข้อที่ตอบ "อีกทาง" — `attitude.ts` บอกเองว่านี่คือสิ่งเดียวที่แบบสำรวจนี้บอกได้จริง
   ⚠ ทำให้จางกว่าเปอร์เซ็นต์ = ซ่อนของที่มีค่าที่สุดไว้ใต้ตัวเลขที่แยกคนไม่ได้ */
.pf-flag-list { margin: var(--pf-space-4) 0 0; padding: 0; list-style: none; display: grid; gap: var(--pf-space-3); }
.pf-flag {
  padding: var(--pf-space-3) var(--pf-space-4);
  border: 1px solid var(--pf-warning-100); border-radius: var(--pf-radius-sm);
  background: var(--pf-warning-50); color: var(--pf-warning-700);
}
.pf-flag b { display: block; }
.pf-flag span { color: inherit; opacity: .85; font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact); }

/* ═══ ฟอร์มสมัครงานสาธารณะ — แบบสำรวจหลายข้อ (2026-08-28) ═══════════════════
   ★★★★ ฟอร์มนี้มี **30 ข้อที่ต้องตอบ** (DISC 20 + ทัศนคติ 10) บนหน้าเดียว
     ⚠⚠ ใช้ `.pf-form-item` ธรรมดาต่อกัน 30 อัน = กำแพงที่ตาแยกไม่ออกว่าข้อไหนจบตรงไหน
       แล้วคนตอบจะข้ามข้อโดยไม่รู้ตัว ซึ่งจบที่ "กดส่งไม่ได้" โดยหาไม่เจอว่าขาดข้อไหน
   ★ กล่องต่อข้อ + เลขข้อ ทำให้ **นับด้วยตาได้** ว่าเหลือข้อไหน */
.pf-quiz { display: grid; gap: var(--pf-space-4); margin-top: var(--pf-space-4); }
.pf-quiz-item {
  border: 1px solid var(--pf-border); border-radius: var(--pf-radius-md);
  padding: var(--pf-space-4); background: var(--pf-surface);
  display: grid; gap: var(--pf-space-3);
}
.pf-quiz-no {
  font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption);
  color: var(--pf-text-secondary); font-weight: var(--pf-w-medium);
}
/* ★★★ หัวข้อรายข้อที่องค์กรตั้งเอง (069) — อยู่ **ใต้เลขข้อ ไม่ใช่ต่อท้ายบรรทัดเดียวกัน**
   ⚠ ต่อท้าย "ข้อ 1 · <หัวข้อยาว ๆ>" แล้วหัวข้อจะถูกอ่านว่าเป็นส่วนหนึ่งของเลขข้อ
     และพอตกบรรทัดบนมือถือ เลขข้อจะลอยอยู่คนละที่กับคำถามของมัน
   ★ ตัวหนาและสีเข้มกว่าเลขข้อ — คำถามคือของหลัก เลขข้อเป็นแค่ตัวนับ */
.pf-quiz-title {
  display: block; margin-top: var(--pf-space-1);
  font-size: var(--pf-size-body); line-height: var(--pf-lh-body);
  font-weight: var(--pf-w-semibold); color: var(--pf-text-primary);
}
/* ★ หลายคอลัมน์บนจอกว้าง — DISC มีสี่ตัวเลือกสั้น ๆ ต่อข้อ เรียงลงมาสี่แถวเปลืองที่มาก
   ⚠ ทัศนคติมีสองตัวเลือกที่ยาวกว่า · `auto-fit` จัดการทั้งสองแบบได้โดยไม่ต้องแยกคลาส
     (auto-fit สร้างคอลัมน์ได้ไม่เกินจำนวนลูก → ทัศนคติได้สองคอลัมน์เสมอ)

   ★★ ค่า min ต้องพา **สี่ตัวเลือกลงแถวเดียว** — ไม่ใช่แค่ "มากกว่าหนึ่งคอลัมน์"
     ⚠⚠ 220px ให้ 3 คอลัมน์ในช่อง 681px → ทุกข้อกลายเป็น 3+1 ตัวโดดห้อยท้าย
       ×20 ข้อ = อ่านสะดุด 20 ครั้ง และแต่ละข้อสูงขึ้นเป็นสองแถวโดยไม่จำเป็น
     ★ 150px → 4 คอลัมน์ที่ ~164px · ป้ายยาว ๆ ตกบรรทัดที่สอง 24 จาก 80 ตัว (วัดที่ 1280)
       ⚠ ยอมให้ตกบรรทัด **โดยตั้งใจ** — ความสูงแถวในหนึ่งข้อเท่ากันอยู่แล้วเพราะเป็นกริด
         และ 2×2 (ไม่ตกบรรทัดเลย) ทำให้หัวข้อ DISC สูงขึ้นเกือบเท่าตัวจาก 20 ข้อ */
.pf-quiz-opts {
  display: grid; gap: var(--pf-space-3);
  grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr));
}

/* ★★★★ ฟอร์มสมัครงานต้องกว้างกว่ากล่องล็อกอิน — มันคือฟอร์ม 10 หัวข้อ 30 คำถาม
   ⚠⚠ **ตัวที่คุมความกว้างคือ `.pf-auth-shell` (max-width 420px) ไม่ใช่ `.pf-auth-card`**
     ตั้ง max-width ที่การ์ดข้างในจึงไม่มีผลอะไรเลย — วัดจริงแล้วได้คอลัมน์เดียวที่ 1280
     (ตัวเลือก DISC สี่ตัวเรียงลงมาสี่แถว ทั้งที่มีที่ว่างข้าง ๆ เหลือเฟือ)
   ★ จึงต้องขยายที่ **เปลือก** และผูกกับหน้านี้เท่านั้น ไม่ไปแตะหน้าล็อกอิน

   🔴🔴 **แก้ max-width อย่างเดียวไม่พอ — และรอบแรกพลาดตรงนี้** (P265 · 2026-08-28)
     ที่ `min-width:900px` เปลือกกลายเป็น **กริดสองคอลัมน์** (ฟอร์ม + เวทีรูป)
     หน้าสมัครงานไม่มีเวที → คอลัมน์ที่สองว่างเปล่า แต่กริดยังกันที่ไว้ให้
     ⚠⚠ วัดจริงที่ 1280: เปลือกกว้าง 780 ถูกต้อง แต่ **การ์ดข้างในเหลือ 362px**
       — แคบกว่าตอนยังไม่แก้อีก · DISC กลับไปเรียงลงมาสี่แถวเหมือนเดิม
     ⚠ รอบแรกวัดแค่ `.pf-auth-shell` แล้วสรุปว่าผ่าน — **วัดผิดชิ้น**
       ชิ้นที่ต้องวัดคือชิ้นที่ตาเห็น ไม่ใช่ชิ้นที่เราไปแก้
     ★ `display:block` = กลับไปใช้ทรงเดียวกับจอแคบ ซึ่งเป็นคอลัมน์เดียวอยู่แล้ว */
.pf-auth-shell:has(> .pf-apply-card) { max-width: 780px; display: block; }

/* ═══ ช่องเลือกรูปติดใบสมัคร (2026-08-28) ═══════════════════════════════════
   ★ รูปตัวอย่างอยู่ซ้าย ปุ่มอยู่ขวา — ผู้สมัครเห็นผลของสิ่งที่เพิ่งเลือกทันที
   ⚠⚠ `flex-wrap` บังคับ — ที่ 375px ปุ่มสองปุ่มกับวงกลม 72px เรียงแถวเดียวไม่พอ
     และปุ่มที่ถูกบีบจนอ่านไม่ออกแย่กว่าปุ่มที่ตกลงมาอยู่บรรทัดใหม่ */
.pf-photo-pick {
  display: flex; align-items: center; gap: var(--pf-space-5); flex-wrap: wrap;
  margin-block-start: var(--pf-space-4);
}
.pf-photo-pick-acts { display: flex; gap: var(--pf-space-3); flex-wrap: wrap; align-items: center; }
/* ★ วงกลมตอนยังไม่มีรูป — พื้นจาง ๆ พอให้เห็นว่า "ตรงนี้จะเป็นรูป" โดยไม่แย่งสายตา */
.pf-photo-empty { background: var(--pf-surface-subtle); color: var(--pf-text-muted); }

/* ★★ ท้ายฟอร์มสมัคร — ข้อความแจ้ง · ช่องยินยอม · ปุ่มส่ง
   ⚠⚠ `.pf-auth-form form { display:grid; gap }` ใช้กับหน้านี้ไม่ได้ เพราะไม่มี `<form>` ห่อ
     (จะห่อก็ไม่ได้ง่าย ๆ — 132 ช่องกรอก กด Enter กลางฟอร์มแล้วส่งทันทีคือของที่แย่กว่า)
   ⇒ ระยะห่างของสามชิ้นท้ายนี้ต้องตั้งเอง · ก่อนหน้านี้ทั้งหมดเป็น margin 0 ชนกันหมด */
.pf-apply-card .pf-auth-form > .pf-note { margin-block-start: var(--pf-space-6); }
.pf-apply-card .pf-auth-form > .pf-check { margin-block-start: var(--pf-space-5); }
.pf-apply-card .pf-auth-form > .pf-btn { margin-block-start: var(--pf-space-6); }

/* ═══ แถวไฟล์แนบ (2026-08-28) ═══════════════════════════════════════════════
   ★★ ต่างจาก `.pf-gap` ตรงที่มีปุ่มหลายปุ่มต่อแถว และมีข้อมูลรองสองบรรทัด
   ⚠ ชื่อไฟล์ยาวได้มาก — ต้องตัดด้วย ellipsis ไม่ใช่ดันแถวให้ล้น */
/* ═══ แกลเลอรีรูป — ใช้ที่รูปทรัพย์สิน (068 · 2026-09-04) ═══════════════════
   ★★★ ทำไมไม่ใช้ `.pf-file-*` ที่มีอยู่แล้ว
     บล็อกนั้นเป็น **รายการชื่อไฟล์** — เหมาะกับเอกสารที่ต้องอ่านชื่อก่อนเปิด
     รูปทรัพย์สินมีไว้ตอบว่า "สภาพเป็นยังไง" ⇒ ต้องเห็น **ตัวรูป** ไม่ใช่ชื่อไฟล์
     ⚠ ยัดรูปลงบล็อกนั้น = เห็นชื่อไฟล์จากมือถือ (IMG_20260904.jpg) ซึ่งไม่บอกอะไรเลย

   ⚠ `aspect-ratio` แทนความสูงคงที่ — ย่อความกว้างแล้วสัดส่วนต้องไม่เพี้ยน
   ★ `object-fit: cover` เพื่อให้รูปแนวตั้งกับแนวนอนเรียงเป็นตารางเดียวกันได้ */
.pf-shots {
  display: grid; gap: var(--pf-space-3);
  grid-template-columns: repeat(auto-fill, minmax(min(140px, 100%), 1fr));
  margin-top: var(--pf-space-3);
}
.pf-shot {
  position: relative; border: 1px solid var(--pf-border);
  border-radius: var(--pf-radius-md); overflow: hidden;
  background: var(--pf-surface-subtle);
}
/* ★★ ทั้งภาพย่อเป็นปุ่ม — กดแล้วดูเต็มรูป
   ⚠ ภาพย่อครอบตัด (cover) เพื่อให้ตารางเรียงตรงกัน ⇒ **เห็นไม่ครบเป็นเรื่องปกติ**
     ทางแก้คือมีทางดูเต็ม ไม่ใช่เลิกครอบ (เลิกครอบแล้วการ์ดจะสูงไม่เท่ากันจนดูรก) */
.pf-shot-open {
  display: block; inline-size: 100%; padding: 0; border: 0; background: none;
  cursor: pointer;
}
.pf-shot-open img {
  display: block; inline-size: 100%; aspect-ratio: 4 / 3; object-fit: cover;
}
/* ★ รูปเต็มในกล่องขยาย — `contain` ไม่ใช่ `cover` เพราะจุดประสงค์คือเห็นทั้งรูป
   ⚠ จำกัดความสูงไว้ ไม่งั้นรูปแนวตั้งจะดันปุ่มปิดตกจอ */
.pf-shot-full {
  display: block; inline-size: 100%; max-block-size: 60vh; object-fit: contain;
  border-radius: var(--pf-radius-md); background: var(--pf-surface-subtle);
}
/* ★★ ปุ่มลบมุมขวาบน — ★ ยังเป็นปุ่มลบมาตรฐาน (แดง + มีข้อความใน title) P391
   ⚠ ปุ่มลอยบนรูปต้องมีพื้นทึบ ไม่งั้นรูปสว่างจะกลืนไอคอน */
.pf-shot-del {
  position: absolute; inset-block-start: var(--pf-space-2);
  inset-inline-end: var(--pf-space-2);
}

.pf-file-list { margin: var(--pf-space-4) 0 0; padding: 0; list-style: none; display: grid; gap: var(--pf-space-2); }
.pf-file {
  display: flex; align-items: center; gap: var(--pf-space-3); flex-wrap: wrap;
  padding: var(--pf-space-3); border: 1px solid var(--pf-border);
  border-radius: var(--pf-radius-sm); background: var(--pf-surface);
}
.pf-file-main { min-width: 0; flex: 1 1 200px; display: grid; gap: 2px; }
.pf-file-name {
  font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact);
  font-weight: var(--pf-w-medium); color: var(--pf-text-primary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pf-file-meta {
  font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption);
  color: var(--pf-text-muted); font-variant-numeric: tabular-nums;
}
.pf-file-acts { flex: none; display: flex; gap: var(--pf-space-2); flex-wrap: wrap; }
@media (max-width: 560px) {
  /* ★ จอแคบ: ปุ่มลงมาอยู่บรรทัดของตัวเองเต็มความกว้าง — ปุ่มเล็กเรียงกันสามอันกดพลาดง่าย */
  .pf-file-acts { inline-size: 100%; }
}
/* ★ ป้ายชนิดไฟล์ — ให้ตาแยก PDF กับรูปได้โดยไม่ต้องอ่านนามสกุลท้ายชื่อยาว ๆ */
.pf-file-kind {
  flex: none; font-size: var(--pf-size-caption); line-height: 1;
  padding: var(--pf-space-2) var(--pf-space-3); border-radius: var(--pf-radius-xs);
  background: var(--pf-gray-50); color: var(--pf-text-secondary);
  text-transform: uppercase; letter-spacing: .03em;
}
.pf-person-name { font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact); font-weight: 600; }
.pf-person-meta { font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption); color: var(--pf-text-muted); }

/* ★★★ ชื่อในเซลล์ + ป้ายกำกับที่อธิบายชื่อนั้น · หรือป้ายหลายใบเรียงกัน
   🔴 ทำไมต้องมีคลาส ไม่ใช่วางป้ายต่อท้ายข้อความเฉย ๆ (เจ้าของทัก 2026-09-08)
     `.pf-badge` เป็น inline-flex สูง 24px · ข้อความในเซลล์สูง 20px
     วางติดกันเฉย ๆ ทั้งคู่เรียงด้วย **baseline** ⇒ ป้ายลอยสูงกว่ากึ่งกลางชื่อ
     วัดจริงที่ 1280px — ก่อนแก้ เยื้อง 2.5px · หลังแก้ 0.00px ทุกเซลล์
   ⚠⚠ ที่ **ไม่** แก้ด้วย `.pf-badge { vertical-align: middle }` เพราะนั่นแตะป้าย
     ทุกใบทั้งระบบ จากอาการที่เกิดเฉพาะตอนมันอยู่ข้างข้อความ — กว้างเกินเหตุ
     และป้ายส่วนใหญ่อยู่ในกล่อง flex อยู่แล้ว ซึ่ง vertical-align ไม่มีผลเลย
   ★ ระยะห่างมาจากโทเคน ไม่ใช่ช่องว่างในเทมเพลต — ช่องว่างใน HTML หายไปเงียบ ๆ
     ได้เมื่อคนจัดรูปแบบโค้ดใหม่ แล้วป้ายสองใบจะไปติดกัน */
.pf-name-tag { display: flex; align-items: center; gap: var(--pf-space-3); min-width: 0; }

.pf-progress { display: flex; align-items: center; gap: var(--pf-space-3); }
.pf-progress .track {
  width: 72px; height: 6px; border-radius: var(--pf-radius-pill);
  background: var(--pf-gray-100); overflow: hidden;
}
.pf-progress .track i { display: block; height: 100%; background: var(--pf-primary-500); border-radius: inherit; }

.pf-pagination {
  display: flex; align-items: center; gap: var(--pf-space-4); flex-wrap: wrap;
  padding: var(--pf-space-4) var(--pf-space-5); border-top: 1px solid var(--pf-border);
  font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact); color: var(--pf-text-muted);
}
.pf-pagination .sp { flex: 1; }
.pf-page-btn {
  min-width: var(--pf-control-sm); height: var(--pf-control-sm); padding: 0 var(--pf-space-4);
  border: 1px solid var(--pf-border-strong); border-radius: var(--pf-radius-sm);
  background: var(--pf-surface); font-size: 13px; font-weight: 600; color: var(--pf-gray-700);
}
.pf-page-btn[aria-current="page"] {
  background: var(--pf-primary-500); border-color: var(--pf-primary-500); color: var(--pf-on-primary);
}
.pf-page-btn[disabled] { opacity: .48; pointer-events: none; }

/* ═══ Filter bar (COMPONENTS.md) ═══ */
.pf-filterbar { display: flex; align-items: center; gap: var(--pf-space-3); flex-wrap: wrap; }
.pf-segment {
  display: inline-flex; padding: var(--pf-space-1); gap: var(--pf-space-1);
  background: var(--pf-gray-100); border-radius: var(--pf-radius-md);
}
.pf-segment button {
  height: 32px; padding: 0 var(--pf-space-4); border-radius: var(--pf-radius-sm);
  font-size: 13px; font-weight: 600; color: var(--pf-text-secondary);
}
.pf-segment button[aria-pressed="true"] {
  background: var(--pf-surface); color: var(--pf-text-primary); box-shadow: var(--pf-shadow-1);
}

/* ═══ Chart (DESIGN.md §8) ═══ */
/* ★ ใช้ได้ทั้ง <svg> และ <div> ที่ ECharts วาดลงไป — ECharts ต้องการความสูงที่ระบุชัด */
.pf-chart { width: 100%; height: 220px; min-width: 0; }
.pf-chart .grid { stroke: var(--pf-chart-grid); stroke-width: 1; }
.pf-chart .axis { fill: var(--pf-text-muted); font-size: 10px; font-family: var(--pf-font); }
/* ★★★ กราฟสัดส่วน (โดนัท) — **จำกัดความกว้างแล้วจัดกึ่งกลาง**
   ⚠ ที่ต้องมีกฎนี้เพราะวงแหวน **โตตามกล่องไม่ได้**: ECharts คิดรัศมีจาก
     min(กว้าง, สูง) ซึ่งความสูงตรึงไว้ 200px ⇒ วงกว้าง 156px คงที่ตลอด
     กล่องกว้าง 745px จึงได้ วงชิดซ้าย · ป้ายชิดขวา · รูว่างกลาง ~230px
   ★ 340px = วง 156 + ช่องไฟ + ป้ายชื่อวง (วัดจาก /design ซึ่งกล่องกว้าง 339px พอดี)
     กล่องแคบกว่านี้กฎนี้ไม่ทำอะไรเลย — โตได้เต็มเหมือนเดิม
   ⚠ ห้ามใช้กับกราฟแกน (เส้น/แท่ง) — พวกนั้นยิ่งกว้างยิ่งอ่านง่าย ตัดทิ้งคือทำให้แย่ลง */
.pf-chart.pf-chart--donut { max-width: 340px; margin-inline: auto; }
.pf-legend { display: flex; gap: var(--pf-space-5); flex-wrap: wrap; font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption); color: var(--pf-text-secondary); }
.pf-legend span { display: inline-flex; align-items: center; gap: var(--pf-space-2); }
.pf-legend i { width: 12px; height: 2px; border-radius: 1px; }
.pf-tooltip {
  background: var(--pf-surface); border: 1px solid var(--pf-border);
  border-radius: var(--pf-radius-md); box-shadow: var(--pf-shadow-2);
  padding: var(--pf-space-4); font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption); display: inline-grid; gap: var(--pf-space-2);
}

/* ═══ Feed (PATTERNS.md) ═══ */
.pf-feed { display: grid; }
.pf-feed-row { display: flex; gap: var(--pf-space-4); padding: var(--pf-space-4) 0; border-bottom: 1px solid var(--pf-row-border); }
.pf-feed-row:last-child { border-bottom: 0; }
.pf-feed-icon { width: 36px; height: 36px; flex: none; border-radius: var(--pf-radius-md); display: grid; place-items: center; }
.pf-feed-title { font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact); font-weight: 500; }
.pf-feed-sub { font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption); color: var(--pf-text-muted); }
.pf-feed-time { margin-left: auto; font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption); color: var(--pf-text-muted); white-space: nowrap; }

.pf-kv { display: flex; align-items: center; justify-content: space-between; gap: var(--pf-space-4);
  padding: var(--pf-space-4) 0; border-bottom: 1px solid var(--pf-row-border); font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact); }
.pf-kv:last-child { border-bottom: 0; }
.pf-kv .k { color: var(--pf-text-secondary); }
.pf-kv .v { font-weight: 600; }

/* ═══ Empty / Loading (COMPONENTS.md) ═══ */
/* ═══════════════════════════════════════════════════════════════════════════
   สภาพว่าง (`pf-empty`)

   🔴 เจ้าของทักเอง (2026-09-02): "ไอคอนไม่อยู่ตรงกลางการ์ดทำให้ดูแปลก"
     ★★★★ วัดแล้วพบว่า **แนวนอนตรงกลางถูกแล้ว** — ที่เพี้ยนคือ **แนวตั้ง**
       วัดจริงในหน้าคำขอ: การ์ดสูง 502px · ช่องบน 73px · **ช่องล่าง 276px**
       ⇒ ก้อนว่างลอยอยู่ครึ่งบน แล้วเหลือที่ว่างโล่ง ๆ ข้างล่างเกือบครึ่งการ์ด
     ⚠ ต้นเหตุ: การ์ดถูกยืดให้สูงเท่าคอลัมน์ข้าง ๆ (กริดยืดลูกเป็นค่าปริยาย)
       แต่ `.pf-empty` มีระยะขอบตายตัว จึงเกาะอยู่ด้านบนเสมอ
   ★ `min-block-size: 100%` + `align-content: center` — เต็มความสูงแล้วจัดกลาง
     ⚠ ใช้ `min-` ไม่ใช่ `block-size` เพราะพ่อที่ **ไม่มีความสูงแน่นอน** จะได้ตกไปเป็น auto
       แทนที่จะยุบเหลือศูนย์ · ของที่พังตอนพ่อเปลี่ยนรูปแบบ คือของที่จะพังตอนเพิ่มหน้าใหม่
   ═══════════════════════════════════════════════════════════════════════════ */
.pf-empty {
  display: grid; justify-items: center; align-content: center; text-align: center;
  gap: var(--pf-space-3); padding: var(--pf-space-9) var(--pf-space-5);
  min-block-size: 100%;
}
.pf-empty-icon { width: 40px; height: 40px; display: grid; place-items: center; border-radius: var(--pf-radius-md); }
/* ★★★ มาสคอตในสภาพว่าง (2026-09-02)
   ★ 132px — ใหญ่พอให้เห็นสีหน้า แต่ยังเล็กกว่าหัวเรื่องในสายตา
     ⚠ ใหญ่กว่านี้แล้วมันแย่งที่ข้อความที่บอก **ทางออก** ซึ่งเป็นของที่ผู้ใช้มาหา
       (คอมเมนต์เดิมที่ `.pf-mascot-empty` เขียนกฎข้อนี้ไว้แล้วตั้งแต่ 2026-08-24)
   ⚠⚠ ห้ามเกิน ~420px — ไฟล์กว้าง 640px · ใหญ่กว่านั้นบนจอความละเอียดสูงแล้วเบลอ
     (site/brand/README ข้อ ③ — ต้องสร้างไฟล์ใหญ่ขึ้น ไม่ใช่ยืดของเดิม) */
/* ⚠⚠ ต้องเป็น `.pf-empty .pf-empty-mascot` (สองคลาส) ไม่ใช่คลาสเดียว
   `.pf-mascot-img` ตั้ง `width: 100%` ไว้ และอยู่ **หลัง** กฎนี้ในไฟล์
   ⇒ ความจำเพาะเท่ากัน ลำดับตัดสิน แล้วมาสคอตกว้าง 573px เต็มการ์ด (วัดจริง 2026-09-02)
   ★ เพิ่มความจำเพาะแทนการย้ายตำแหน่ง — ย้ายแล้วมันจะพังอีกวันที่ใครจัดเรียงไฟล์ใหม่ */
.pf-empty .pf-empty-mascot { inline-size: 132px; max-inline-size: 60%; height: auto; }
.pf-empty-title { font-size: var(--pf-size-section); line-height: var(--pf-lh-section); font-weight: var(--pf-w-semibold); }
.pf-empty-desc { font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact); color: var(--pf-text-muted); max-width: 38ch; }
.pf-skel { border-radius: var(--pf-radius-xs); background: var(--pf-gray-100); position: relative; overflow: hidden; }
.pf-skel::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.85), transparent);
  transform: translateX(-100%); animation: pf-shimmer 1.4s var(--pf-ease-out) infinite;
}
@keyframes pf-shimmer { to { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) { .pf-skel::after { animation: none; } }

/* ═══ กำลังพาออกไปเว็บอื่น (เจ้าของขอ 2026-09-09) ═══════════════════════════
   ★★★★ ปัญหาที่มันแก้: การจ่ายเงินกับการจัดการบัตรเกิดที่ **เว็บของผู้ให้บริการ**
     ระหว่างที่เบราว์เซอร์กำลังเปลี่ยนหน้า จอของเรายังค้างอยู่เฉย ๆ หลายวินาที
     ⇒ ผู้ใช้อ่านว่า "กดแล้วไม่มีอะไรเกิดขึ้น" แล้ว **กดซ้ำ** ซึ่งที่นี่แปลว่า
       สร้างใบจ่ายเงินใบที่สอง หรือขอลิงก์กุญแจเข้าบัญชีจ่ายเงินซ้ำ

   ⚠⚠ ต้องขึ้น **เฉพาะตอนที่กำลังจะออกไปจริง** ไม่ใช่ทุกครั้งที่รอ
     ใช้กับการรอธรรมดา = ผู้ใช้จะชินแล้วเลิกอ่าน แล้ววันที่ออกไปจริงเขาจะไม่ทันสังเกต
   ★ บอกด้วยว่า **จะไปไหน** — "กำลังพาไป…" ที่ไม่บอกปลายทาง ไม่ได้ลดความงงเลย */
.pf-leaving {
  position: fixed; inset: 0; z-index: 120;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--pf-space-4);
  background: color-mix(in srgb, var(--pf-surface) 72%, transparent);
  backdrop-filter: blur(6px);
  color: var(--pf-text-secondary);
  font-size: var(--pf-size-body); line-height: var(--pf-lh-body);
  text-align: center; padding: var(--pf-space-6);
}
.pf-leaving-spin {
  inline-size: 36px; block-size: 36px; flex: none;
  border: 3px solid var(--pf-border);
  border-block-start-color: var(--pf-primary-500);
  border-radius: 50%;
  animation: pf-leaving-spin 720ms linear infinite;
}
@keyframes pf-leaving-spin { to { transform: rotate(360deg); } }
/* ⚠ คนที่ตั้งค่าไม่เอาการเคลื่อนไหว ต้องยังรู้ว่าระบบกำลังทำงานอยู่
   ⇒ หยุดหมุน แต่ **ไม่ซ่อน** — วงแหวนที่นิ่งยังบอกว่ามีอะไรค้างอยู่ */
@media (prefers-reduced-motion: reduce) {
  .pf-leaving-spin { animation: none; }
}

/* ═══ Modal / Bottom sheet (COMPONENTS.md) ═══ */
/* ⚠ `min-width: 0` — กล่องนี้เป็นลูกของกริด ค่าปริยาย `min-width: auto` ทำให้มัน
   ไม่ยอมแคบกว่าเนื้อหา (กล่องโมดัล 480px + ระยะขอบ) แล้วดันทั้งหน้าให้ล้นที่ 375px
   ★ ตระกูลเดียวกับ P182 — ค่าปริยายของกริดกัดอีกครั้ง คนละที่ เรื่องเดียวกัน */
.pf-overlay {
  background: rgba(16,24,40,.32); border-radius: var(--pf-radius-lg); padding: var(--pf-space-8);
  /* ★ ต้องเป็น `minmax(0, auto)` ไม่ใช่ `auto` เปล่า ๆ
     `auto` ทำให้คอลัมน์กว้างเท่ากล่องโมดัล (480/560px) แล้วดันทั้งหน้าที่จอ 375px
     ⚠ `max-width: 100%` ของโมดัลแก้ไม่ได้ เพราะ 100% นับจากคอลัมน์ที่โตไปแล้ว */
  /* ★ `place-content` ไม่ใช่ `align-items` — กล่องนี้ถูกยืดให้สูงเท่าแถวของกริดแม่
     สิ่งที่ต้องจัดกลางคือ **แถวของตัวเอง** ในพื้นที่ที่ถูกยืด ไม่ใช่ลูกในแถว
     ⚠ เครื่องตรวจ P182 ชี้จุดนี้ให้ — ผลลัพธ์เหมือนกันในกรณีนี้ แต่คนละความหมาย */
  display: grid; grid-template-columns: minmax(0, auto); place-content: center;
  min-width: 0;
}
@media (max-width: 767px) { .pf-overlay { padding: var(--pf-space-4); } }

/* ═══ ≤560px · โมดัลกลายเป็นแผ่นเลื่อนขึ้นจากล่าง (RESPONSIVE.md เคาะไว้แล้ว) ═══
   ★ กล่องกลางจอบนมือถือมีปัญหาสองอย่าง: นิ้วโป้งเอื้อมปุ่มด้านบนไม่ถึง
     และคีย์บอร์ดเด้งขึ้นมาแล้วกล่องถูกดันจนหลุดจอ
   ⚠ นี่เป็นข้อที่ **เคาะไว้ตั้งแต่ต้น** แต่ระบบดีไซน์ยังไม่ได้ทำตาม จนกระทั่งวัดแล้วเจอ */
@media (max-width: 560px) {
  .pf-overlay { padding: 0; align-items: end; }
  .pf-modal {
    width: 100%; max-width: 100%;
    border-radius: var(--pf-radius-2xl) var(--pf-radius-2xl) 0 0;
    padding: var(--pf-space-6) var(--pf-space-5);
  }
}
.pf-modal {
  /* ⚠ `max-width: 100%` ชนะ `min-width: auto` ไม่ได้ — ต้องเขียน `min-width: 0` ให้ชัด
     ไม่งั้นกล่องโมดัลจะกว้าง 480px บนจอ 375px แล้วดันทั้งหน้า */
  width: 480px; max-width: 100%; min-width: 0;
  border-radius: var(--pf-radius-xl); padding: var(--pf-space-7);
  background: var(--pf-surface); box-shadow: var(--pf-shadow-3); display: grid; gap: var(--pf-space-5);
  /* ★★★★ ต้องเขียน `color` ให้ชัด **ห้ามพึ่งการสืบทอด** (P209 · รอบ 282)
     ในแอปกล่องนี้อยู่ใน `<dialog>` ซึ่ง UA stylesheet ตั้ง `color: CanvasText` ไว้
     → **ไม่สืบจาก body** · วัดจริง: ธีมสว่างได้ `#000` (ควรเป็น `#101828`)
       ธีมมืดได้ `#FFFFFF` (ควรเป็น `#F5F7FA`)
     ⚠⚠ ทั้งคู่ผ่านเกณฑ์คอนทราสต์สบาย ๆ (21:1 และ 17.66:1) จึงไม่มีเครื่องตรวจไหนเห็น
       และ `hex-check` ก็ไม่เห็น เพราะ **ไม่มี hex อยู่ในไฟล์ของเราเลย — เบราว์เซอร์เป็นคนใส่**
     ★ ผลจริงคือวันที่เจ้าของปรับ `--pf-text-primary` ตัวหนังสือในกล่องยืนยันจะไม่ขยับตาม
       ซึ่งคือสิ่งที่ D42 ตั้งใจกำจัด: "มีแหล่งเดียว" ที่ของบางชิ้นไม่ได้อ่านจากแหล่งนั้น */
  color: var(--pf-text-primary);
}
.pf-sheet {
  width: 100%; max-height: 88vh; border-radius: var(--pf-radius-2xl) var(--pf-radius-2xl) 0 0;
  padding: var(--pf-space-6) var(--pf-space-5); background: var(--pf-surface);
  box-shadow: var(--pf-shadow-3); display: grid; gap: var(--pf-space-5);
}
.pf-handle { width: 36px; height: 4px; border-radius: var(--pf-radius-pill); background: var(--pf-gray-300); justify-self: center; }

/* ═══ โมดัลของจริงในแอป — `<dialog showModal()>` ═════════════════════════════
   ยกมาจาก `_components.scss` `dialog.modal` ตอนขั้น 5.3-ง (D42 · รอบ 282)

   🔴 **หน้าดีไซน์ใช้ `<div class="pf-overlay">` ซึ่งแอปใช้ตามไม่ได้ และไม่ควรใช้ตาม**
     `.pf-overlay` เป็น div ธรรมดา จึงอยู่ใน stacking context ปกติ
     ★ `position:fixed` **เลิกอ้างขอบจอทันทีที่ ancestor ตัวใดตัวหนึ่งมี**
       `transform` / `filter` / `backdrop-filter` — ซึ่งแอปมีจริง (แถบบนใช้ backdrop-filter)
     ผลคือกล่องลอยที่เปิดจากในกล่องซ้อน (ตัวเลือกวันที่) **กดไม่โดน**
     วัดแล้วในเครื่องตรวจ `focus`: แขวนที่ `<body>` ขณะมีกล่องซ้อนเปิด → กดไม่โดนจริง
   ★ `<dialog showModal()>` เรนเดอร์ใน **top layer** ของเบราว์เซอร์ จึงไม่โดนกับดักนี้เลย
     และได้ focus trap + Esc + `::backdrop` มาฟรี ไม่ต้องเขียนเอง
   ⚠ นี่คือจุดที่ **จงใจไม่ตรงกับหน้าดีไซน์** — หน้าดีไซน์เป็นภาพนิ่ง ไม่มีเรื่องพวกนี้
     กล่องข้างในยังใช้ `.pf-modal` ตัวเดียวกันทุกประการ ต่างแค่ตัวที่ห่ออยู่ */
.pf-modal-host { border: 0; padding: 0; background: transparent; max-width: min(93vw, 480px); }
.pf-modal-host::backdrop { background: rgba(16,24,40,.32); }
.pf-modal-host[open] { animation: pf-modal-in var(--pf-duration-standard) var(--pf-ease-out); }
@keyframes pf-modal-in { from { opacity: 0; transform: translateY(14px) scale(.97); } }
/* ★★ ต้องปลดเพดานที่ `<dialog>` ด้วย ไม่ใช่แค่ที่กล่องข้างใน
   กล่องลูกกว้างเกินพ่อไม่ได้ · P44 — สลิปเงินเดือนส่ง wide มาตั้งแต่แรกแต่ได้ 480px มาตลอด
   ⚠ และต้องกำหนด `width` ไม่ใช่แค่ `max-width` — `<dialog>` หดตามเนื้อหา
     กล่องที่หดตามเนื้อหาทำให้กริดสองคอลัมน์ข้างในเหลือคอลัมน์เดียวเสมอ */
.pf-modal-host--wide { width: min(93vw, 720px); max-width: min(93vw, 720px); }
/* ⚠ ต้องตั้ง `width` ด้วย ไม่ใช่แค่ `max-width` — `.pf-modal` ตัวฐานตั้ง `width: 480px` ไว้
   ปลดแค่เพดานแล้วกล่องก็ยังกว้าง 480px อยู่ดี = ตัวเลือก wide ไม่มีผล (P44 อีกชั้นหนึ่ง)
   วัดจริงในเครื่องตรวจ browser: พ่อขยายเป็น 698px แล้วแต่ลูกยังคาที่ 465.6px */
.pf-modal--wide { width: 100%; max-width: min(93vw, 720px); }
/* ⚠ บล็อกมือถือต้องอยู่ **หลัง** สองบรรทัดบน — specificity เท่ากัน ลำดับตัดสิน (P204) */
@media (max-width: 560px) {
  .pf-modal-host, .pf-modal-host--wide { max-width: 100vw; width: 100vw; margin: auto 0 0; }
  .pf-modal-host[open] { animation: pf-sheet-in var(--pf-duration-standard) var(--pf-ease-out); }
  @keyframes pf-sheet-in { from { transform: translateY(100%); } }
}

/* หัวเรื่องบอกระดับของกล่องยืนยัน (1 ย้อนได้ · 2 มีผลกับเงิน · 3 ย้อนไม่ได้) */
.pf-modal-level {
  font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption);
  font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--pf-text-muted); display: flex; align-items: center; gap: var(--pf-space-2);
}
/* ข้อความอธิบายใต้หัวเรื่องในกล่อง — คู่กับ `.pf-page-desc` ของหน้าปกติ
   ⚠ หน้าดีไซน์เขียน `style="color:var(--pf-text-secondary)"` ติดในแท็ก จึงไม่มีชื่อคลาสให้ลอก
     ตั้งชื่อตามแบบเดิมของระบบ (`pf-<บริบท>-desc`) แทนการทำคลาสยูทิลิตี้สีตัวอักษร
     ★ ยูทิลิตี้แบบ `.pf-text-secondary` จะเปิดประตูให้ระบบกลายเป็น utility framework
       ซึ่งไม่ใช่แนวของ `pf.css` — ทุกคลาสที่นี่ผูกกับ "ของชิ้นไหน" ไม่ใช่ "ทาสีอะไร" */
.pf-modal-desc {
  color: var(--pf-text-secondary);
  font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact);
  margin: var(--pf-space-1) 0 0;
}
/* แถวปุ่มท้ายกล่อง — หน้าดีไซน์เขียนเป็น style inline ไว้ จึงไม่มีชื่อคลาสให้ลอก
   ⚠ ต้องมี `flex-wrap` — ปุ่ม "ยืนยันและเพิ่มพนักงาน" ยาวจนล้นกล่องที่ 375px ถ้าไม่ห่อ */
.pf-modal-acts {
  display: flex; gap: var(--pf-space-3); justify-content: flex-end; flex-wrap: wrap;
}
/* ★★ แถบระดับโทนแดง — ใช้กับ `pf-alert` ("สิ่งที่คุณกดไม่สำเร็จ")
   ⚠ ต้องต่างจากกล่องยืนยันให้เห็นใน **หนึ่งวินาที** ที่เขามอง — กล่องยืนยันถามว่า
     "จะทำไหม" ส่วนกล่องนี้บอกว่า "ไม่สำเร็จ" · อ่านผิดกันได้ = กดปิดแล้วเดินจากไป
   ⚠ ใช้ `--pf-danger-700` ซึ่งเป็นคู่ที่ `theme-check` ตรวจอยู่แล้วบนพื้นการ์ด */
.pf-modal-level--bad { color: var(--pf-danger-700); }

/* ═══ การ์ดเตือน + แถวเตือน — ยกจาก `/design/attendance.html` ════════════
   ⚠ หน้าดีไซน์เขียนสีไว้เป็น `style="border-color:…;background:…"` ติดในแท็ก
     จึงไม่มีชื่อคลาสให้ลอก · ตั้งชื่อเป็นตัวปรับของการ์ด แล้วแก้หน้าดีไซน์ให้ใช้ตัวเดียวกัน
     (กฎข้อ 1 — ของที่มีใน CSS แต่ดีไซน์ไม่แสดง สุดท้ายทุกคนเดาเอง แล้วเพี้ยนคนละทาง)
   ★ ใช้กับ "เรื่องที่ยังตัดสินไม่จบ" เท่านั้น ไม่ใช่ข้อความช่วยทั่วไป
     ข้อความช่วยที่ไม่ต้องทำอะไรต่อ ใช้ `.pf-note` ซึ่งเบากว่าและไม่แย่งสายตา */
.pf-card--warning { border-color: var(--pf-warning-500); background: var(--pf-warning-50); }
/* ★ คู่ของ `--warning` สำหรับ "เรื่องที่ต้องรู้ แต่ไม่ต้องทำอะไรต่อ"
   ⚠ ใช้ผิดตัวเมื่อไหร่ ผู้ใช้จะเลิกแยกออกว่าอันไหนต้องลงมือ (สีเตือนที่ขึ้นตลอด = ไม่ใช่คำเตือน)
   ยกจาก `/design/payroll.html` ซึ่งเขียนสีไว้ติดในแท็ก จึงไม่มีชื่อคลาสให้ลอก */
.pf-card--info { border-color: var(--pf-info-500); background: var(--pf-info-50); }
.pf-card--info .pf-card-title { color: var(--pf-info-700); }
.pf-card--info .pf-body-compact { color: var(--pf-gray-700); margin-top: 2px; }
/* หน้าปัจจุบันในเส้นทาง — ★ ไม่ใช่ลิงก์ จึงต้องต่างจากตัวอื่นด้วยน้ำหนักและสี */
.pf-breadcrumb-now { color: var(--pf-text-primary); font-weight: var(--pf-w-semibold); }
.pf-card--warning .pf-card-title { color: var(--pf-warning-700); }
.pf-card--warning .pf-body-compact { color: var(--pf-gray-700); margin-top: 2px; }
/* ⚠ `align-items: flex-start` ไม่ใช่ center — ข้อความสองบรรทัดกับปุ่มบรรทัดเดียว
   ถ้าจัดกลาง ไอคอนจะลอยไปอยู่กลางย่อหน้าแทนที่จะอยู่ระดับหัวเรื่อง */
.pf-note-row { display: flex; gap: var(--pf-space-4); align-items: flex-start; flex-wrap: wrap; }
.pf-note-row > .pf-kpi-icon { flex: none; }

/* คอลัมน์ข้างของ `.pf-two-col` ที่มีการ์ดซ้อนกันหลายใบ
   ★ `align-content: start` — ไม่งั้นการ์ดสองใบจะถูกยืดให้สูงเท่าตารางฝั่งซ้าย */
.pf-side-stack { display: grid; gap: var(--pf-grid-gap); align-content: start; }

/* การ์ดสรุปที่ **กดแล้วกรองได้** — `/design/employees.html` กำกับไว้ว่าทุกใบต้องกดได้
   ⚠ ต้องเป็น <button> จริง ไม่ใช่ <article> ที่ผูก (click) — ไม่งั้นแป้น Tab ข้าม
     และเสียงอ่านจะไม่ประกาศว่ากดได้ · `aria-pressed` บอกว่าใบไหนคือตัวกรองที่ใช้อยู่
   ★ รีเซ็ตค่าปริยายของปุ่มให้เหมือน `.pf-kpi` เดิมทุกอย่าง เหลือแค่พฤติกรรมที่กดได้ */
.pf-kpi--action {
  font: inherit; color: inherit; text-align: start; cursor: pointer; width: 100%;
}
.pf-kpi--action:hover { border-color: var(--pf-gray-400); box-shadow: var(--pf-shadow-1); }
.pf-kpi--action[aria-pressed="true"] {
  border-color: var(--pf-primary-500); box-shadow: 0 0 0 2px var(--pf-primary-100);
}

/* ปุ่มเล็กหลายตัวในช่องเดียว — ★ ต้องห่อบรรทัด ไม่งั้นคอลัมน์การกระทำดันตารางกว้างขึ้น */
/* ═══ คอลัมน์จัดการของตาราง — ★ ปุ่มในนี้ **ต้องมีขอบเสมอ** ═══════════════
   🔴 เจ้าของทักเอง (2026-09-02): "ปุ่มการกระทำดูโล่งไปหน่อย"
     วัดแล้วพบว่า **16 ปุ่มใน 6 หน้า** ใช้ `pf-btn--ghost` ซึ่งไม่มีทั้งพื้นและขอบ
     ⇒ อ่านเป็น "ข้อความสีน้ำเงิน" ไม่ใช่ "ปุ่ม" · และตาไม่รู้ว่าขอบเขตกดอยู่ตรงไหน
   ★★★★ ระบบดีไซน์กำหนดไว้แล้วว่าคอลัมน์นี้ใช้ `pf-btn--secondary` (มีขอบ)
     — หน้าจุดทำงานทำถูกมาตลอด · อีกหกหน้าหลุดไปทีละหน้าโดยไม่มีอะไรฟ้อง
   ⚠ `npm run mock` มีกฎค้ำแล้วตั้งแต่ 2026-09-02 ว่าห้ามใช้ ghost ในบล็อกนี้ */
.pf-row-acts { display: flex; gap: var(--pf-space-2); justify-content: flex-end; flex-wrap: wrap; }
/* ★★ ปุ่มในคอลัมน์จัดการชิดกันแน่น — ไม่ให้ตัวอักษรของสองปุ่มอ่านต่อกันเป็นประโยคเดียว
   ⚠ ระยะน้อยกว่านี้ = "ดูใบสมัคร แก้ไข ย้ายขั้น" อ่านรวดเดียวเหมือนข้อความ ไม่ใช่สามปุ่ม */
.pf-row-acts > .pf-btn { white-space: nowrap; }
/* ★★★ ไอคอนนำหน้าข้อความ (เจ้าของสั่ง 2026-09-02) — ระยะแคบกว่าปุ่มปกติ
   ⚠⚠ `.pf-btn` ตั้ง `gap: --pf-space-3` (12px) ไว้สำหรับปุ่มขนาดเต็ม
     ใช้ระยะนั้นกับปุ่ม `--sm` ในคอลัมน์แคบ ๆ = ไอคอนกับคำดูเหมือนคนละชิ้น
     และแถวสามปุ่มจะกว้างจนดันคอลัมน์อื่นแคบลง
   ★ ไอคอน 15px — เล็กกว่าปุ่มปกติ (18px) เพราะมันเป็น **ตัวช่วยอ่าน** ไม่ใช่ตัวหลัก
     ⚠ ใหญ่กว่านี้แล้วตาไปจับไอคอนก่อนคำ ซึ่งกลับด้านกับที่ต้องการ */
.pf-row-acts > .pf-btn { gap: var(--pf-space-2); }
.pf-row-acts > .pf-btn .pf-ico { inline-size: 15px; block-size: 15px; }

/* ═══ ค่าที่ปิดบัง + ปุ่มเปิดดู — หนึ่งบรรทัด (เจ้าของทัก 2026-09-02) ═══════════
   🔴 คำทัก: *"ตำแหน่งของฟอร์มกับปุ่มเปิดดู ดูเพี้ยน ไม่สมดุลไม่เท่ากัน ปุ่มไม่กึ่งกลาง"*
     เดิมยืม `.pf-row-acts` มาใช้ ซึ่ง **ไม่มี `align-items`** ⇒ ค่าปริยายคือ `stretch`
     ⇒ ตัวเลขถูกยืดเต็มความสูงแถวแล้วตัวหนังสือไปเกาะขอบบน ส่วนคำในปุ่มอยู่กึ่งกลาง 36px
       ⇒ สองอย่างในบรรทัดเดียวกันแต่ **ไม่ได้อยู่ระดับเดียวกัน**
   ★★★ และปุ่มต้องชิดขวาเสมอ ไม่ใช่ต่อท้ายตัวเลข
     ⚠ ต่อท้ายตัวเลข = ปุ่มของสองช่องอยู่คนละตำแหน่ง เพราะค่าปิดบังยาวไม่เท่ากัน
       ("x-xxxx-xxxxx-xx-5" กับ "กสิกรไทย xxx-x-xx648-9") — นั่นคือ "ไม่สมดุล" ที่เห็น
     ⇒ ให้ค่ากินที่ที่เหลือ ปุ่มจึงเรียงตรงกันเป็นคอลัมน์เดียวกับขอบขวาของช่องกรอกข้างล่าง
   ⚠ `min-block-size` เท่าปุ่ม — ช่องที่ยังไม่มีปุ่ม (ยังไม่ได้บันทึกเลข) ต้องสูงเท่ากัน
     ไม่งั้นสองคอลัมน์จะเหลื่อมกันทั้งชุด ซึ่งเป็นอีกครึ่งของคำว่า "ไม่เท่ากัน" */
.pf-reveal {
  display: flex; align-items: center; gap: var(--pf-space-3);
  min-block-size: var(--pf-control-sm);
}
/* ⚠ `min-inline-size: 0` บังคับ — ลูกของ flex ยุบไม่ได้ตามค่าปริยาย (P230)
   ⚠⚠ `flex: 1 1 auto` **ไม่ใช่ `flex: 1`** — `flex: 1` ย่อเป็น basis 0 ⇒ ความกว้างมาจาก
     การ grow อย่างเดียว · พอช่องแคบลงมาก ตัวเลขจะถูกบีบจนตัดบรรทัด **ทีละตัวอักษร**
     (วัดเจอตอนวัดในกล่องซ้อนที่ถูกจัดหน้าแคบ ๆ 2026-09-02)
   ⚠ `break-word` ไม่ใช่ `anywhere` — `anywhere` ทำให้ min-content เหลือหนึ่งตัวอักษร
     แล้ว flex จะยอมบีบจนหมด · `break-word` ตัดเฉพาะตอนที่คำเดียวยาวเกินบรรทัดจริง ๆ */
.pf-reveal > b { flex: 1 1 auto; min-inline-size: 0; overflow-wrap: break-word; }
.pf-reveal > .pf-btn { flex: none; }

/* ═══════════════════════════════════════════════════════════════════════════
   ★★★★ บอร์ดสายงาน (`pf-board`) — คอลัมน์ละขั้นของกระบวนการสรรหา
   เสนอตอนขั้น 6 (2026-08-27) · ระบบดีไซน์เดิมไม่มีบล็อกนี้เลย

   สิ่งที่บังคับรูปร่าง — ไม่ใช่รสนิยม
     ① **6 ขั้น** (สมัคร · คัดกรอง · สัมภาษณ์ · ยื่นข้อเสนอ · รับ · ไม่รับ)
        6 × 240px = 1,440px — กว้างกว่าพื้นที่เนื้อหาที่จอ 1440 เสมอ
        → ต้องเลื่อนแนวนอน **ในกล่องของตัวเอง** ไม่ใช่ให้ทั้งหน้าเลื่อน (P1)
     ② **สิ่งที่ต้องสะดุดตาคือ "ค้างมานานแค่ไหน"** ไม่ใช่ชื่อผู้สมัคร
        หมุนเวียนของลูกค้า ~18%/เดือน · งานที่พลาดจริงคือใบที่ค้างแล้วลืม
        การ์ดจึงไล่สายตาเป็น ชื่อ → ตำแหน่ง → รอใคร → **ค้างกี่วัน (ตัวหนา)**
     ③ **ย้ายขั้นด้วยปุ่ม ไม่ใช่ลาก** — ลากต้องมีตัวรับ ต้องมีสถานะกำลังลาก
        ต้องมีทางเลิกลาก และบนมือถือใช้ไม่ได้เลย · ปุ่มทำงานได้ทุกที่ทันที
     ④ ขั้นที่ **จบแล้ว** (รับ/ไม่รับ) ไม่มีปุ่มย้ายและไม่นับวันค้าง
        ไม่งั้นคนที่ไม่รับไปเมื่อ 3 เดือนก่อนจะขึ้นว่า "ค้าง 90 วัน" ตลอดไป

   ⚠ ห้ามใส่ `overflow:hidden` ที่คอลัมน์ — การ์ดมีป้ายที่ยื่นออกมาได้
   ═══════════════════════════════════════════════════════════════════════════ */
.pf-board {
  display: flex; gap: var(--pf-space-5); overflow-x: auto;
  /* ⚠ `min-width: 0` บังคับ — กล่องนี้เป็นลูกของ flex/grid เสมอ (ต้นเหตุของ P1 · P230) */
  min-width: 0; padding-bottom: var(--pf-space-3);
}
.pf-board-col {
  /* ⚠ 264px ไม่ใช่ 240 — การ์ดมีช่องรูป 36px + ระยะ จึงต้องกว้างขึ้น
     ไม่งั้นชื่อคนถูกตัดกลางคำตั้งแต่คนที่ชื่อยาวปานกลาง */
  flex: 0 0 264px; display: flex; flex-direction: column; gap: var(--pf-space-4); min-width: 0;
}
/* ★ หัวคอลัมน์ติดอยู่กับที่ตอนเลื่อนดูการ์ดยาว ๆ — ไม่งั้นเลื่อนลงแล้วไม่รู้ว่าอยู่ขั้นไหน */
.pf-board-head {
  position: sticky; top: 0; z-index: 1;
  display: flex; align-items: center; gap: var(--pf-space-3);
  padding-bottom: var(--pf-space-3); background: var(--pf-bg);
  border-bottom: 2px solid var(--pf-border);
}
/* ★★ เส้นใต้หัวคอลัมน์ใช้ **สีของขั้นนั้น** — ทำให้กวาดตาหาคอลัมน์ได้โดยไม่ต้องอ่าน
   ⚠ ใช้ `currentColor` ของชิปไม่ได้ เพราะเส้นอยู่คนละ element */
.pf-board-col[data-tone="ok"]    .pf-board-head { border-bottom-color: var(--pf-success-500); }
.pf-board-col[data-tone="info"]  .pf-board-head { border-bottom-color: var(--pf-info-500); }
.pf-board-col[data-tone="warn"]  .pf-board-head { border-bottom-color: var(--pf-warning-500); }
.pf-board-col[data-tone="bad"]   .pf-board-head { border-bottom-color: var(--pf-danger-500); }
.pf-board-col[data-tone="wait"]  .pf-board-head { border-bottom-color: var(--pf-violet-700); }
.pf-board-col[data-tone="gone"]  .pf-board-head { border-bottom-color: var(--pf-gray-400); }
/* ★★★★ ต้องครอบ **ครบทั้ง 8 โทน** ของ StageLook ไม่ใช่เท่าที่ตัวอย่างในแกลเลอรีใช้
   ⚠⚠ เดิมขาด `hold` กับ `trial` เพราะตอนเสนอบอร์ดผมเลือกโทนใส่เองในตัวอย่าง
     แต่โค้ดจริงอ่านจาก `stageLookOf()` ซึ่งให้ `hold` กับขั้น "ยื่นข้อเสนอ"
     → คอลัมน์นั้นได้เส้นสีเทาเริ่มต้น (--pf-border) ปนอยู่กับอีกห้าคอลัมน์ที่มีสี
     ซึ่งทำลายเหตุผลเดียวที่เส้นนี้มีอยู่: กวาดตาหาคอลัมน์โดยไม่ต้องอ่าน
   ★ วัดเจอในเบราว์เซอร์ ไม่ใช่จากการอ่านโค้ด — CSS ที่ไม่ match ไม่มีใครฟ้อง */
.pf-board-col[data-tone="hold"]  .pf-board-head { border-bottom-color: var(--pf-orange-700); }
.pf-board-col[data-tone="trial"] .pf-board-head { border-bottom-color: var(--pf-success-700); }
.pf-board-name {
  font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact);
  font-weight: var(--pf-w-semibold); color: var(--pf-text-primary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pf-board-count { margin-inline-start: auto; flex: none; }

/* ★★★★ การ์ดผู้สมัคร — สองคอลัมน์: รูป · เนื้อ (เจ้าของโปรเจคสั่งเพิ่ม 2026-08-27)
   ⚠ ช่องรูปเป็น `.pf-avatar` ตัวเดียวกับตารางทุกหน้า — ไม่ได้ทำวงกลมใหม่
     วันที่มีที่เก็บรูปจริง เปลี่ยนข้างในจากอักษรย่อเป็น <img> ได้โดยไม่ต้องแตะ CSS นี้ */
.pf-board-card {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--pf-space-3);
  align-items: start; padding: var(--pf-space-4);
  background: var(--pf-surface); border: 1px solid var(--pf-border);
  border-radius: var(--pf-radius-lg); box-shadow: var(--pf-shadow-1);
  transition: border-color var(--pf-duration-standard) var(--pf-ease-out),
              box-shadow var(--pf-duration-standard) var(--pf-ease-out);
}
.pf-board-card:hover { border-color: var(--pf-gray-400); box-shadow: var(--pf-shadow-2); }
/* ★★★ ใบที่ค้างเกินเกณฑ์ — ขอบซ้ายหนา ไม่ใช่พื้นสีทั้งใบ
   พื้นสีทั้งใบจะแย่งน้ำหนักกับปุ่มในใบเอง และทำให้ใบปกติดูเหมือนถูกปิดใช้ */
.pf-board-card--overdue {
  border-inline-start: 3px solid var(--pf-danger-500);
  padding-inline-start: calc(var(--pf-space-4) - 2px);
}
/* เนื้อการ์ด — อยู่คอลัมน์ขวาของรูป · ไล่สายตา ชื่อ → ตำแหน่ง → วันสมัคร → ค้างกี่วัน */
.pf-board-body { display: grid; gap: 2px; min-width: 0; }
.pf-board-body > b {
  font-size: var(--pf-size-body); line-height: var(--pf-lh-body);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* บรรทัดที่มีไอคอนนำ — ตำแหน่ง · วันสมัคร (รูปแบบเดียวกับการ์ดในหน้ารายชื่อผู้สมัคร) */
.pf-board-line {
  display: flex; align-items: center; gap: var(--pf-space-2); min-width: 0;
  color: var(--pf-text-muted);
  font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption);
}
.pf-board-line > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pf-board-line .pf-ico { flex: none; width: 13px; height: 13px; }
.pf-board-line.pf-num { font-variant-numeric: tabular-nums; }
/* ★ บรรทัด "ค้างมากี่วัน" — ตัวที่ต้องสะดุดตาที่สุดในใบ (เหตุผลข้อ ② ข้างบน) */
.pf-board-stalled {
  margin-top: var(--pf-space-2);
  font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact);
  font-weight: var(--pf-w-semibold); font-variant-numeric: tabular-nums;
  color: var(--pf-text-secondary);
}
.pf-board-card--overdue .pf-board-stalled { color: var(--pf-danger-700); }
/* ★ ปุ่มย้ายขั้นเต็มความกว้างของใบ — พื้นที่กดใหญ่พอสำหรับนิ้วบนมือถือ
   ⚠ อยู่ใต้ทั้งสองคอลัมน์ (ขึ้นบรรทัดใหม่เต็มความกว้าง) ไม่ใช่ในคอลัมน์เนื้อ
     ไม่งั้นปุ่มจะแคบลง 36px + ระยะ ซึ่งพื้นที่กดเล็กเกินไปบนมือถือ */
.pf-board-card > .pf-btn { grid-column: 1 / -1; margin-top: var(--pf-space-3); width: 100%; }
.pf-board-card > .pf-badge { grid-column: 1 / -1; margin-top: var(--pf-space-2); }
.pf-board-empty {
  color: var(--pf-text-muted); padding: var(--pf-space-5) 0; text-align: center;
  font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact);
}
/* ⚠ จอแคบ: คอลัมน์แคบลงแต่ **ยังเลื่อนแนวนอน** ไม่ยุบเป็นแถวเดียว
   ยุบเป็นแถวเดียวแล้วบอร์ดจะกลายเป็นรายการยาว ซึ่งตารางทำได้ดีกว่าอยู่แล้ว */
@media (max-width: 767px) { .pf-board-col { flex-basis: 232px; } }
/* ปุ่มของการกระทำที่ย้อนกลับไม่ได้ (กล่องระดับ 3) — ระบบดีไซน์ยังไม่มีตัวนี้
   ⚠ ใช้คู่ `--pf-danger-solid` / `--pf-on-danger` **ห้ามใช้ `--pf-danger-500` + ขาว**
     วัดแล้วได้ 3.76 : 1 · เครื่องตรวจ `contrast` จับได้ตอนรอบ 282 */
.pf-btn--danger { background: var(--pf-danger-solid); color: var(--pf-on-danger); }
.pf-btn--danger:hover:not([disabled]) { filter: brightness(.92); }

/* ★★★ ปุ่มทำลายแบบ **เงียบแต่มีสี** — ไอคอนแดงบนพื้นโปร่ง (เจ้าของขอ 2026-09-03)
   🔴 ที่มา: รอบก่อนปุ่มลบเป็นแถบแดงเต็มความกว้าง ⇒ หน้าลายตา จึงเปลี่ยนเป็นไอคอนเทาเงียบ ๆ
     แล้วเจ้าของทักว่า *"ทำให้ไอคอนถังขยะมีสีสันเป็นสีแดงหน่อย จะได้ดูไม่เรียบไป"*
     ⇒ ทางสายกลาง: **สีบอกว่ามันคือของทำลาย · ขนาดบอกว่ามันไม่ใช่พระเอกของหน้า**

   🔴🔴 **ขอบเขตของคลาสนี้แคบลงแล้ว (2026-09-04)**
     เจ้าของดูปุ่มลบของทุกหน้าเทียบกันแล้วบอกว่า *"มันควรเป็นแบบหน้าวันหยุดประจำปี"*
     ⇒ **ปุ่มลบของที่บันทึกแล้ว** ใช้ `pf-btn--danger pf-btn--sm` + ถังขยะ + คำ + title
       (ดู `docs/COMPONENTS.md` แถว "ปุ่มลบ" · มีเคสค้ำใน `npm run buttons`)
     ★ คลาสนี้เหลือใช้กับ **ปุ่มเอาแถวออกจากฟอร์มที่ยังไม่กดบันทึก** เท่านั้น
       (ไอคอน `x` ไม่ใช่ถังขยะ · กดแล้วยังไม่มีอะไรถูกทำลาย)
     ⚠ ที่เจ้าของเคยปฏิเสธคือ "แถบแดง**เต็มความกว้าง**" ไม่ใช่สีแดง — ปุ่มแดงกะทัดรัด
       ที่แบ่งแถวกับปุ่มอื่นจึงไม่ขัดกับคำทักรอบ 2026-09-03

   ⚠⚠ ประกาศเป็นตัวเดี่ยว **ไม่ใช่** ใช้คู่กับ `.pf-btn--ghost`
     ทั้งสองคลาสมีน้ำหนักตัวเลือกเท่ากัน (0,1,0) ⇒ ใครอยู่ท้ายไฟล์ชนะ
     เขียนคู่กันเมื่อไหร่ ผลจะขึ้นกับลำดับบรรทัด ซึ่งเป็นกับดักที่โปรเจคนี้เจอมาแล้วสองรอบ
     (P320 · P336 — ทั้งสองครั้งคือกฎน้ำหนักเท่ากันแย่งกันเงียบ ๆ) */
.pf-btn--danger-soft {
  background: transparent;
  color: var(--pf-danger-700);
}
.pf-btn--danger-soft:hover:not([disabled]) { background: var(--pf-danger-50); }

/* ═══ หน้าเว็บหน้าร้าน (landing) ═══════════════════════════════════════════
   ★ ใช้โทเคนและคอมโพเนนต์ชุดเดียวกับในแอป — หน้าร้านกับของข้างในต้องเป็นระบบเดียวกัน
   ⚠ ทุกกล่องใช้ `minmax(0, 1fr)` ตั้งแต่แรก (P184) ไม่ใช่รอให้ล้นแล้วค่อยแก้ */
.pf-lp { display: grid; grid-template-columns: minmax(0, 1fr); }
.pf-lp-nav {
  position: sticky; top: 0; z-index: 40;
  display: flex; align-items: center; gap: var(--pf-space-5);
  padding: var(--pf-space-4) var(--pf-page-padding-desktop);
  background: color-mix(in srgb, var(--pf-bg) 88%, transparent);
  backdrop-filter: blur(10px); border-bottom: 1px solid var(--pf-border);
}
.pf-lp-brand { font-size: var(--pf-size-section); line-height: var(--pf-lh-section); white-space: nowrap; }

/* ═══ เวทีมาสคอตในหัวเรื่อง ═══════════════════════════════════════════════
   ★ มาสคอตลอยเดี่ยว ๆ บนหน้าขายซอฟต์แวร์เงินเดือน ทำให้ดูเป็นของเล่น
     ล้อมด้วยชิปที่เป็น **ตัวเลขจริงจากระบบ** แล้วมันกลายเป็น "จริงจัง และเป็นมิตร"
   ⚠ ชิปทุกใบต้องเป็นข้อเท็จจริงที่มีอยู่จริงในหน้าจอ ไม่ใช่ตัวเลขที่แต่งให้ดูดี */
/* ⚠⚠ เคยมีวงกลมไล่สีอยู่หลังตัวละคร (`.pf-hero-orb`) — **เจ้าของโปรเจคให้เอาออก**
     "พื้นหลังมันดูแปลก" · ตัวละครมีพื้นโปร่งอยู่แล้ว การวางบนพื้นหน้าจึงสะอาดกว่า
   ★ อย่าเอากลับมาโดยไม่ถาม · ถ้าจะเพิ่มพื้นหลังใหม่ ต้องผ่านสายตาเจ้าของก่อน

   ★★★ ความกว้างของเวทีคือสิ่งที่ยึดชิปไว้กับตัวละคร
     ชิปวางด้วย % ของเวที — ตอนมีวงกลม เวทีถูกวงกลมกำหนดขนาดให้โดยบังเอิญ
     พอเอาวงกลมออก เวทีจะกว้างเท่าคอลัมน์ แล้วชิปจะกระเด็นไปไกลจากตัวละคร
     จึงต้องกำหนดความกว้างเอง ให้พอ ๆ กับตัวละคร (330px) บวกที่ให้ชิปเกยขอบ */
.pf-hero-stage {
  position: relative; display: grid; place-items: center;
  /* ⚠⚠ P182 ซ้ำ — คอลัมน์ที่ไม่ประกาศเป็น `auto` ซึ่งกว้างเท่า **เนื้อหา** ไม่ใช่เท่าเวที
       วัดที่ 375px: เวที 324.8 แต่คอลัมน์โตเป็น 330 ตามตัวละคร
       แล้ว `max-width: 100%` ของตัวละครไปวัดกับ 330 (ช่องของมันเอง) จึงไม่หดเลย
     ★ `minmax(0, 1fr)` คือบรรทัดที่ห้ามลืมทุกครั้งที่ทำกริด */
  grid-template-columns: minmax(0, 1fr);
  width: min(100%, 420px); margin-inline: auto;
  min-height: 380px; padding: var(--pf-space-7) 0;
}
.pf-hero-stage .pf-mascot { position: relative; z-index: 2; }

/* ชิปลอย — ★ วางด้วย % เพื่อให้ตามขนาดเวทีไปเอง ไม่ต้องมี breakpoint */
.pf-hero-chip {
  position: absolute; z-index: 3;
  display: grid; grid-template-columns: minmax(0, 1fr); gap: 2px;
  padding: var(--pf-space-3) var(--pf-space-4);
  background: var(--pf-surface); border: 1px solid var(--pf-border);
  border-radius: var(--pf-radius-md); box-shadow: var(--pf-shadow-2);
  white-space: nowrap;
}
.pf-hero-chip > span {
  font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption); color: var(--pf-text-muted);
  display: flex; align-items: center; gap: var(--pf-space-2);
}
.pf-hero-chip > b {
  font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact);
  font-weight: var(--pf-w-semibold); font-variant-numeric: tabular-nums;
}
.pf-hero-chip > span > i {
  width: 7px; height: 7px; border-radius: 50%; flex: none; background: var(--pf-success-500);
}
.pf-hero-chip--a { top: 6%;  right: -2%; }
.pf-hero-chip--b { bottom: 22%; left: -4%; }
.pf-hero-chip--c { bottom: 4%; right: 2%; }

/* ═══ การลอย ═══════════════════════════════════════════════════════════════
   ★★★ วัดจากวิดีโออ้างอิง: แต่ละชิ้นขึ้นลง **6–10px คนละจังหวะ**
     ถ้าทุกชิ้นลอยพร้อมกันจะดูเหมือนทั้งหน้าสั่น ไม่ใช่ของลอย
   ⚠⚠ ขยับได้แค่ transform — ขยับ top/bottom ทำให้คำนวณเลย์เอาต์ใหม่ทุกเฟรม = กระตุก
   ⚠ ช้าไว้ (5–8 วิ) · เร็วกว่านี้จะกวนสายตาคนที่กำลังอ่านพาดหัวอยู่ข้าง ๆ */
@keyframes pf-float {
  0%, 100% { transform: translate3d(0, 0, 0); }
  50%      { transform: translate3d(0, -9px, 0); }
}
@keyframes pf-float-soft {
  0%, 100% { transform: translate3d(0, 0, 0); }
  50%      { transform: translate3d(0, -6px, 0); }
}
.pf-hero-stage .pf-mascot { animation: pf-float-soft 6.5s ease-in-out infinite; }
/* ★★ ประกาศการลอยที่ **คลาสฐาน** ครั้งเดียว แล้วให้ตัวแปรย่อยเปลี่ยนแค่จังหวะ
   ⚠ ถ้าประกาศ animation เต็มรูปที่ตัวแปรย่อยทีละใบ กฎ prefers-reduced-motion
     ที่ปิดด้วยคลาสฐานจะดูเหมือนไม่ครอบคลุม — และเครื่องตรวจก็อ่านไม่ออกว่าครอบ
   ★ เขียนแบบนี้แล้วทั้งเบราว์เซอร์และเครื่องตรวจเห็นตรงกัน */
.pf-hero-chip   { animation: pf-float 6s ease-in-out infinite; }
.pf-hero-chip--a { animation-duration: 5.5s; }
.pf-hero-chip--b { animation-duration: 7s; animation-delay: .9s; }
.pf-hero-chip--c { animation-duration: 6s; animation-delay: 2.1s; }

@media (prefers-reduced-motion: reduce) {
  /* ★ ไม่ใช่แค่ช้าลง — ต้องหยุดสนิท
     คนที่ตั้งค่านี้ไว้บางคนเวียนหัวจริงจากภาพที่ขยับไม่หยุด */
  .pf-hero-stage .pf-mascot,
  .pf-hero-chip { animation: none; }
}

/* ═══ ค่อย ๆ ปรากฏตอนเลื่อนถึง ═══════════════════════════════════════════
   ★★★★ สังเกตว่า **ไม่มีกฎไหนซ่อนอะไรเลย จนกว่าจะมีธง `data-reveal-on`**
     ธงนั้น `pf-reveal.js` เป็นคนติด และมันติดก็ต่อเมื่อครบสามอย่าง:
     มี IntersectionObserver · ผู้ใช้ไม่ได้ขอลดการเคลื่อนไหว · มีของให้เปิดจริง
   ⚠⚠⚠ ห้ามย้าย `opacity: 0` ออกมาไว้ที่ `[data-reveal]` เฉย ๆ เด็ดขาด
     JS โหลดไม่ขึ้นเมื่อไหร่ = **หน้าขาวทั้งหน้า** โดยไม่มี error ให้ใครเห็น (P172)
     ค่าเริ่มต้นของหน้าเว็บต้องเป็น "อ่านได้" เสมอ อนิเมชันเป็นของที่เพิ่มเข้ามาทีหลัง

   ⚠ ขยับได้แค่ `opacity` กับ `transform` — ทั้งคู่ทำงานบน compositor
     ขยับ `height`/`margin` เพื่อให้ "ค่อย ๆ ดันลงมา" จะคำนวณเลย์เอาต์ใหม่ทุกเฟรม
     ตลอดการเลื่อนหน้า ซึ่งเป็นจังหวะที่เบราว์เซอร์ยุ่งที่สุดอยู่แล้ว */
/* ★★★★ หน่วยที่จางคือ **ลูกของ `[data-reveal]`** ไม่ใช่ตัวมันเอง
   ⚠⚠ รอบ 238 เริ่มด้วยการจางทั้ง section แล้วเจ้าของโปรเจคทักว่า
     *"เลื่อนลงไปข้อมูลก็แสดงก่อนเลยแล้ว"* — วัดแล้วถูกต้อง
     section สูง 445–679px แต่ยิงจากขอบบนใบเดียว → เนื้อหาที่อยู่ล่างลงไปอีก
     300–600px จางเสร็จตั้งแต่ตายังไม่ไปถึง
   ★ แยกเป็นลูก (หัวเรื่อง / ตาราง / กล่องเตือน) แล้วแต่ละชิ้นยิงตามตำแหน่งตัวเอง
     ของจึงทยอยเข้ามาใกล้ ๆ จุดที่ตากำลังอ่านอยู่ ไม่ใช่เข้ามาพร้อมกันล่วงหน้า
   ★ HTML ยังติดป้ายแค่ 7 ที่เหมือนเดิม — ตัวเลือก `> *` เป็นคนขยายให้ */
:root[data-reveal-on] [data-reveal] > * {
  opacity: 0;
  transform: translate3d(0, var(--pf-reveal-shift), 0);
  transition:
    opacity var(--pf-duration-reveal) var(--pf-ease-out),
    transform var(--pf-duration-reveal) var(--pf-ease-out);
}
:root[data-reveal-on] [data-reveal] > .pf-in { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  /* ★ ชั้นที่สอง — JS ไม่ติดธงให้อยู่แล้ว แต่เขียนไว้เผื่อผู้ใช้เปลี่ยนค่าระหว่างเปิดหน้าค้างไว้
     ⚠ ปิดแค่ `transition` ไม่พอ — ต้องคืน `opacity: 1` ด้วย
       ไม่งั้นคนที่ตั้งค่านี้จะได้หน้าว่างถาวรแทนที่จะได้หน้านิ่ง ๆ */
  :root[data-reveal-on] [data-reveal] > * {
    opacity: 1; transform: none; transition: none;
  }
}
@media (max-width: 767px) {
  /* จอแคบ เวทีเล็กลง ชิปจะทับตัวละคร — ดึงเข้ามาชิดขอบแทน */
  .pf-hero-stage { min-height: 320px; }
  .pf-hero-chip--a { top: 2%; right: 0; }
  .pf-hero-chip--b { bottom: 26%; left: 0; }
  .pf-hero-chip--c { bottom: 2%; right: 0; }
  .pf-hero-chip { padding: var(--pf-space-2) var(--pf-space-3); }
}
/* ⚠⚠ ปุ่มในแถบบนห้ามถูกบีบ — วัดแล้วปุ่มไอคอนโดนบีบจาก 40px เหลือ **20px**
   ★ `flex: none` ที่นี่ไม่ใช่เรื่องความสวย มันคือขนาดพื้นที่กดขั้นต่ำ
     ปุ่มกว้าง 20px บนมือถือคือปุ่มที่กดไม่โดน */
.pf-lp-nav .pf-btn { flex: none; }
@media (max-width: 560px) {
  /* ★ จอแคบซ่อน **ชื่อแบรนด์ที่เป็นตัวอักษร** — ตรามุมซ้ายยังบอกว่าเป็นใครอยู่
     ⚠ ไม่ซ่อนปุ่ม เพราะปุ่มคือทางไปต่อของผู้ใช้ · ชื่อแบรนด์ไม่ใช่ */
  .pf-lp-brand { display: none; }
}
.pf-lp-wrap { width: 100%; max-width: 1120px; margin: 0 auto; min-width: 0; padding: 0 var(--pf-page-padding-desktop); }
.pf-lp-section { padding: var(--pf-space-9) 0; display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--pf-space-7); }
.pf-lp-head { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--pf-space-3); max-width: 62ch; }
.pf-lp-eyebrow {
  font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption);
  font-weight: var(--pf-w-semibold); letter-spacing: .06em; color: var(--pf-primary-ink);
}
.pf-lp-h2 { font-size: var(--pf-size-page); line-height: var(--pf-lh-page); font-weight: var(--pf-w-bold); letter-spacing: -.01em; margin: 0; }
.pf-lp-sub { color: var(--pf-text-secondary); margin: 0; }

/* ★ พาดหัวใหญ่แต่ยังคุมความยาวบรรทัด — ยาวเกิน 24ch ตาจะกระโดดหาบรรทัดถัดไปไม่เจอ */
.pf-lp-hero { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--pf-space-8); align-items: center; padding: var(--pf-space-9) 0; }
@media (min-width: 900px) { .pf-lp-hero { grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr); } }
.pf-lp-title {
  font-size: clamp(32px, 5.2vw, 52px); line-height: 1.24; font-weight: var(--pf-w-bold);
  letter-spacing: -.02em; margin: 0; text-wrap: balance; max-width: 18ch;
}
.pf-lp-lead { font-size: var(--pf-size-section); line-height: 1.62; color: var(--pf-text-secondary); margin: 0; max-width: 46ch; }
.pf-lp-actions { display: flex; gap: var(--pf-space-4); flex-wrap: wrap; }
.pf-lp-actions .pf-btn { min-height: var(--pf-control-lg); padding: 0 var(--pf-space-6); }

/* ═══ เทียบภาพแบรนด์ ═══════════════════════════════════════════════════════
   ★ ใช้โชว์ไฟล์แบรนด์คู่กับตัวอย่างการใช้งานจริง ในหน้าระบบดีไซน์ */
.pf-brand-compare { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr)); gap: var(--pf-grid-gap); }
.pf-brand-cell { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--pf-space-3); align-content: start; }
.pf-brand-shot {
  border: 1px solid var(--pf-border); border-radius: var(--pf-radius-lg);
  background: var(--pf-surface); padding: var(--pf-space-4); overflow: hidden;
}
.pf-brand-shot > img { display: block; width: 100%; height: auto; }
.pf-brand-cell > figcaption { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--pf-space-1); }
.pf-brand-cell .t { font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact); font-weight: var(--pf-w-semibold); }
.pf-brand-cell .d { font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption); color: var(--pf-text-muted); }
.pf-swatch-row { display: flex; gap: var(--pf-space-3); flex-wrap: wrap; align-items: center; }
.pf-swatch-dot {
  display: inline-flex; align-items: center; gap: var(--pf-space-2);
  font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption);
  color: var(--pf-text-secondary); font-variant-numeric: tabular-nums;
}
.pf-swatch-dot > i { width: 16px; height: 16px; border-radius: 4px; flex: none; border: 1px solid var(--pf-border); }

/* ═══ มาสคอต ═══════════════════════════════════════════════════════════════
   ★ ตัวจริงที่เจ้าของโปรเจคออกแบบ · 5 อารมณ์ · ไฟล์ .webp ที่ /brand/
   ⚠ เป็นภาพ ไม่ใช่เวกเตอร์ — ไฟล์กว้าง 640px ใช้ได้ถึงประมาณ 420px บนจอความละเอียดสูง
     ใหญ่กว่านั้นจะเริ่มเบลอ · ต้องสร้างไฟล์ใหญ่ขึ้นแทนการยืด
   ★★ ตัวละครมี "สีเดียว" ไม่ย้อมสีตามสถานการณ์ — ความหมายให้สิ่งรอบตัวมันบอก */
.pf-mascot { display: inline-grid; place-items: center; line-height: 0; }
.pf-mascot-img { display: block; width: 100%; height: auto; max-width: 100%; }

/* วางในหน้าว่าง — ★ มาสคอตห้ามแย่งที่ข้อความบอกทางออก */
.pf-mascot-empty {
  display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--pf-space-4);
  align-content: center;   /* ★ หน้าว่างจัดกลางแนวตั้งในการ์ด ไม่ใช่ลอยชิดบน (P182) */
  justify-items: center; text-align: center; padding: var(--pf-space-8) var(--pf-space-5);
}
.pf-mascot-empty > b { font-size: var(--pf-size-card); line-height: var(--pf-lh-card); font-weight: var(--pf-w-semibold); }
.pf-mascot-empty > span { font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact); color: var(--pf-text-secondary); max-width: 42ch; }

/* ตารางโชว์อารมณ์ */
.pf-mascot-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(190px, 100%), 1fr)); gap: var(--pf-grid-gap); }
/* ★ ชิดบน — การ์ดสูงไม่เท่ากันแล้วรูปต้องเริ่มที่ระดับเดียวกัน (P182) */
.pf-mascot-cell { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--pf-space-3); align-content: start; justify-items: center; text-align: center; }
.pf-mascot-cell > b { font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact); font-weight: var(--pf-w-semibold); }
/* ⚠⚠ ต้องเป็น :not([class]) — ไม่งั้นกฎนี้ไปทับสีของของที่มีคลาสของตัวเองในเซลล์เดียวกัน
   ★ ตระกูลเดียวกับ P185 — ตัวเลือกที่เล็งแท็ก ไปโดนของที่ไม่ได้ตั้งใจ */
.pf-mascot-cell > span:not([class]) { font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption); color: var(--pf-text-muted); }

/* ═══ โลโก้ = ตรา + ตัวหนังสือ ═══════════════════════════════════════════════
   ★★★★ ตัวหนังสือเป็น **ข้อความจริงในฟอนต์ของเรา** ไม่ใช่ภาพ
     ⚠ ให้โมเดลสร้างภาพเขียนคำว่า PeopleFlow มีโอกาสสะกดผิดสูงมาก
       และถ้าผิดจะแก้ไม่ได้นอกจากสร้างใหม่ · เป็นข้อความแล้วสะกดผิดไม่ได้
     ★ ได้ผลพลอยได้: คมทุกขนาด · คัดลอกได้ · เปลี่ยนสีตามธีมได้ · ค้นหาเจอ */
.pf-logo-lockup { display: inline-flex; align-items: center; gap: var(--pf-space-3); min-width: 0; }
.pf-logo-word {
  font-size: var(--pf-size-section); line-height: var(--pf-lh-section);
  font-weight: var(--pf-w-bold); letter-spacing: -.01em; white-space: nowrap;
  color: var(--pf-text-primary);
}

/* ═══ กล่องข้อเสนอทดลองใช้ ═══════════════════════════════════════════════
   ★★ ข้อเสนอที่ดีที่สุดต้องอยู่ **ติดกับปุ่ม** ไม่ใช่ลอยเป็นชิปเหนือพาดหัว
     ตาคนกวาดหาพาดหัวก่อนเสมอ · อะไรที่อยู่เหนือพาดหัวจะถูกข้ามไปโดยไม่รู้ตัว
   ⚠ ขอบเป็นสีแบรนด์แต่พื้นเป็นสีอ่อน — ถ้าทำพื้นทึบจะไปแย่งน้ำหนักกับปุ่มหลักที่อยู่ข้างใน */
.pf-trial {
  display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--pf-space-5);
  padding: var(--pf-space-5); border-radius: var(--pf-radius-lg);
  border: 1px solid color-mix(in srgb, var(--pf-primary-500) 34%, var(--pf-border));
  background: color-mix(in srgb, var(--pf-primary-500) 5%, var(--pf-surface));
}
.pf-trial-head { display: flex; align-items: center; gap: var(--pf-space-4); }
.pf-trial-head > div { display: grid; grid-template-columns: minmax(0, 1fr); gap: 2px; min-width: 0; }
.pf-trial-head > div > b { font-size: var(--pf-size-card); line-height: var(--pf-lh-card); font-weight: var(--pf-w-semibold); }
.pf-trial-head > div > span { font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact); color: var(--pf-text-secondary); }
/* ★ ตัวเลข 7 เป็นพระเอกของกล่องนี้ — ใหญ่พอให้อ่านได้จากมุมตา */
.pf-trial-days {
  flex: none; width: 56px; height: 56px; border-radius: var(--pf-radius-md);
  display: grid; place-content: center; text-align: center;
  background: var(--pf-primary-500); color: var(--pf-on-primary);
}
.pf-trial-days > b { font-size: 24px; line-height: 1.1; font-weight: var(--pf-w-bold); font-variant-numeric: tabular-nums; }
.pf-trial-days > span { font-size: var(--pf-size-caption); line-height: 1.2; }
.pf-trial-list { margin: 0; padding: 0; list-style: none; display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--pf-space-2); }
.pf-trial-list > li {
  position: relative; padding-left: var(--pf-space-6);
  font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption); color: var(--pf-text-secondary);
}
.pf-trial-list > li::before {
  content: "✓"; position: absolute; left: 0; color: var(--pf-success-600); font-weight: var(--pf-w-bold);
}

/* ═══ เส้นเวลา "หนึ่งวันในระบบ" ═══════════════════════════════════════════
   ★★★ แทนภาพหน้าจอเดียว — เพราะพาดหัวบอกว่า "ทั้งหมดอยู่ในที่เดียว"
     ภาพที่โชว์ฟีเจอร์เดียวจึงขัดกับคำพูดของตัวเอง
   ⚠ เส้นต่อระหว่างจุดวาดด้วย ::before ของ li ไม่ใช่ border ของ ol
     เพราะต้องหยุดที่จุดสุดท้าย ไม่ลากเลยลงไป */
.pf-flow { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--pf-space-5); align-self: start; }
.pf-flow-head { display: flex; align-items: center; justify-content: space-between; gap: var(--pf-space-3); }
.pf-flow-head > b { font-size: var(--pf-size-card); line-height: var(--pf-lh-card); font-weight: var(--pf-w-semibold); }
.pf-flow-list { margin: 0; padding: 0; list-style: none; display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--pf-space-5); }
.pf-flow-list > li {
  position: relative; display: flex; align-items: flex-start; gap: var(--pf-space-4); min-width: 0;
}
.pf-flow-list > li:not(:last-child)::before {
  content: ""; position: absolute; left: 17px; top: 36px; bottom: calc(var(--pf-space-5) * -1);
  width: 2px; background: var(--pf-border);
}
.pf-flow-list .pf-kpi-icon { width: 36px; height: 36px; position: relative; z-index: 1; }
.pf-flow-list > li > div { flex: 1; min-width: 0; display: grid; grid-template-columns: minmax(0, 1fr); gap: 2px; }
.pf-flow-list > li > div > b { font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact); font-weight: var(--pf-w-semibold); }
.pf-flow-list > li > div > span { font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption); color: var(--pf-text-muted); }
.pf-flow-list > li > time {
  flex: none; font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption);
  color: var(--pf-text-secondary); font-variant-numeric: tabular-nums; padding-top: 2px;
}
.pf-flow-foot {
  margin: 0; padding-top: var(--pf-space-4); border-top: 1px solid var(--pf-border);
  font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption); color: var(--pf-text-muted);
}

.pf-lp-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); gap: var(--pf-grid-gap); }
.pf-lp-stat { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--pf-space-2); align-content: start; }
.pf-lp-stat > b { font-size: var(--pf-size-kpi); line-height: var(--pf-lh-kpi); font-weight: var(--pf-w-bold); font-variant-numeric: tabular-nums; letter-spacing: -.02em; }
.pf-lp-stat > span { font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact); color: var(--pf-text-secondary); }
.pf-lp-stat > i { font-style: normal; font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption); color: var(--pf-text-muted); }

.pf-lp-feature { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--pf-space-3); align-content: start; }
.pf-lp-feature .pf-kpi-icon { width: 36px; height: 36px; }
.pf-lp-feature > b { font-size: var(--pf-size-card); line-height: var(--pf-lh-card); font-weight: var(--pf-w-semibold); }
.pf-lp-feature > span { font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact); color: var(--pf-text-secondary); }

/* แพ็กเกจ — ★ ใบที่แนะนำเน้นด้วย "ขอบ + ป้าย" ไม่ใช่ด้วยขนาดที่ใหญ่กว่า
   ⚠ ใบที่สูงกว่าเพื่อนทำให้เปรียบเทียบราคายาก ซึ่งตรงข้ามกับหน้าที่ของหน้านี้ */
/* ═══ การ์ดแพ็กเกจ — ★★★★ สี่ระดับที่ต้องตรงกันข้ามการ์ด ═══════════════════
   ชื่อแพ็ก · ราคา · bullet แรก · ปุ่ม
   วัดรอบ 237 ก่อนแก้: **เหลื่อมกัน 31px ทั้งสี่ระดับ** ทั้งที่กล่องการ์ดสูงเท่ากันเป๊ะ
   ⚠ เจ้าของโปรเจคเห็นเองจากหน้าจอ — ตาคนจับความไม่ตรงของ *ราคา* ได้ไวมาก
     เพราะมันคือสิ่งที่คนกวาดตาเทียบกันสามใบพร้อมกัน

   ── เหตุที่ ① ป้าย "คนเลือกมากที่สุด" อยู่ในสายเลย์เอาต์ ──
   ป้ายสูง 23px + ระยะใต้ป้าย 8px = **31px** ที่ดันทุกอย่างในการ์ดกลางลงไป
   ★ แก้ด้วยการยกป้ายออกจากสาย (`position: absolute`) ไม่ใช่ไปเว้นที่ให้อีกสองใบ
     เว้นที่ให้ทุกใบ = การ์ดที่ไม่มีป้ายจะมีช่องว่างลอย ๆ ที่ไม่มีใครอธิบายได้

   ── เหตุที่ ② ปุ่มไม่ได้ถูกตรึงไว้ที่ก้นการ์ด ──
   `align-content: start` + แถวเป็น `auto` ทั้งหมด → ปุ่มลอยตามจำนวน bullet
   การ์ดที่มี 4 ข้อ ปุ่มสูงกว่าการ์ดที่มี 5 ข้อ **28px**
   ★ `grid-template-rows: auto 1fr auto` ให้ **แถวกลาง (ul) กินที่ว่างทั้งหมด**
   ⚠⚠ ต้องถอน `align-content: start` ที่นี่ด้วย — มันสั่งให้ทุกแถวหดไปกองข้างบน
     ซึ่งทำให้ `1fr` ไม่มีความหมาย · (`align-content: start` ยังถูกต้องสำหรับฟอร์ม
     ตาม P182 — ที่นั่นไม่มีแถวไหนควรยืด แต่ที่นี่มี) */
.pf-plan {
  display: grid; grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto 1fr auto;
  gap: var(--pf-space-5); position: relative;
}
.pf-plan[data-highlight="1"] { border-color: var(--pf-primary-500); box-shadow: var(--pf-shadow-2); }
/* ★ ป้ายมุม — ยกออกจากสายเลย์เอาต์ ระดับอื่นจึงตรงกันทั้งสามใบ
   ⚠ `top` ติดลบครึ่งหนึ่งของความสูงตัวเอง เพื่อให้คร่อมขอบการ์ดพอดี
     ห้ามใช้ค่าคงที่เป็น px — ป้ายเปลี่ยนขนาดฟอนต์เมื่อไหร่จะเลื่อนทันที */
.pf-plan > .pf-badge {
  position: absolute; top: 0; left: var(--pf-space-5);
  transform: translateY(-50%); margin: 0;
}
.pf-plan-price { display: flex; align-items: baseline; gap: var(--pf-space-2); flex-wrap: wrap; }
.pf-plan-price > b { font-size: var(--pf-size-kpi); line-height: var(--pf-lh-kpi); font-weight: var(--pf-w-bold); font-variant-numeric: tabular-nums; letter-spacing: -.02em; }
.pf-plan-price > span { font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact); color: var(--pf-text-muted); }
.pf-plan ul { margin: 0; padding: 0; list-style: none; display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--pf-space-3); }
.pf-plan li { display: flex; gap: var(--pf-space-3); align-items: flex-start; font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact); }
.pf-plan li svg { flex: none; margin-top: 2px; color: var(--pf-success-600); }

/* ★ คำตอบเรื่องภาษีใต้การ์ดราคา (D40) — เงียบ แต่ต้องอ่านออก
   ⚠ ห้ามทำให้จางกว่า `--pf-text-muted` เพื่อความสวย มันคือข้อมูลที่คนซื้อต้องใช้ */
.pf-price-note {
  margin: var(--pf-space-5) 0 0; text-align: center;
  font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact);
  color: var(--pf-text-muted);
}

.pf-lp-faq { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--pf-space-5); max-width: 72ch; }
.pf-lp-faq > div { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--pf-space-2); }
.pf-lp-faq > div > b { font-size: var(--pf-size-card); line-height: var(--pf-lh-card); font-weight: var(--pf-w-semibold); }
.pf-lp-faq > div > p { margin: 0; color: var(--pf-text-secondary); font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact); }

@media (max-width: 767px) {
  .pf-lp-wrap, .pf-lp-nav { padding-left: var(--pf-page-padding-mobile); padding-right: var(--pf-page-padding-mobile); }
  .pf-lp-section, .pf-lp-hero { padding: var(--pf-space-8) 0; }
  .pf-lp-actions .pf-btn { width: 100%; }
}
.pf-lp-foot {
  border-top: 1px solid var(--pf-border); padding: var(--pf-space-7) 0;
  display: flex; gap: var(--pf-space-5); flex-wrap: wrap; align-items: center;
  font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption); color: var(--pf-text-muted);
}

/* ═══ Mobile frames ═══ */
/* ★★★★ เฟรมมือถือต้อง **พอดีกล่องที่มันอยู่** ทุกขนาด — ห้ามมีแถบเลื่อนแนวนอน
   ⚠⚠⚠ รอบก่อนผมเลือกทางผิด: ตรึงเฟรมไว้ 360px แล้วให้กล่องเลื่อนได้
     เหตุผลตอนนั้นคือ "มือถือที่ถูกย่อคือภาพที่โกหก" — ฟังดูดี แต่ผลคือ
     ทุกใบในแกลเลอรีมีแถบเลื่อนของตัวเอง ซึ่งเจ้าของโปรเจคเห็นทันทีว่าไม่ใช่
   ★ ที่ถูกคือทำให้มัน **ยืดหดจริง** — เพราะเนื้อหาข้างในเป็น CSS ของเราเอง
     360px คือเพดาน ไม่ใช่ค่าตายตัว · แคบกว่านั้นก็ยังเป็นความกว้างของมือถือจริงอยู่ดี
   ⚠ `aspect-ratio` แทนความสูงคงที่ ไม่งั้นย่อความกว้างแล้วสัดส่วนเพี้ยน */
.pf-frames, .pf-demo-row {
  display: flex; gap: var(--pf-space-7); flex-wrap: wrap;
  min-width: 0;
}
.pf-demo-row { align-items: flex-start; }
.pf-frame {
  width: 360px; max-width: 100%; min-width: 0; flex: 0 1 360px;
  aspect-ratio: 360 / 720; height: auto; display: flex; flex-direction: column;
  border: 1px solid var(--pf-border); border-radius: var(--pf-radius-2xl); overflow: hidden;
  background: var(--pf-bg); box-shadow: var(--pf-shadow-2);
}
/* ★ ต้องอยู่ **หลัง** `.pf-frame` — ความเฉพาะเจาะจงเท่ากัน ลำดับในไฟล์เป็นตัวตัดสิน
   ⚠ เคยวางไว้ก่อนหน้า แล้วเฟรมเล็กถูกทับกลับเป็น 360px เงียบ ๆ จนหน้าล้นจอ 76px */
.pf-frame--sm { width: 300px; flex-basis: 300px; aspect-ratio: 300 / 600; }
.pf-m-header {
  height: 56px; flex: none; display: flex; align-items: center; gap: var(--pf-space-4);
  padding: 0 var(--pf-page-padding-mobile);
  background: var(--pf-surface); border-bottom: 1px solid var(--pf-border);
}
.pf-m-body {
  /* ⚠ `min-width: 0` — ตัวเลื่อนที่อยู่ในกล่อง flex ต้องยุบได้ ไม่งั้นดันเฟรมให้กว้าง (P230) */
  flex: 1; min-width: 0; overflow: auto; padding: var(--pf-page-padding-mobile);
  /* ⚠ ต้องประกาศคอลัมน์ให้ชัด — คอลัมน์ `auto` จะกว้างเท่าเนื้อหาที่กว้างที่สุด
     แล้วเนื้อหาในเฟรมมือถือจะดันเฟรมให้กว้างกว่าจอ (วัดได้ 492px ในเฟรม 343px) */
  display: grid; grid-template-columns: minmax(0, 1fr);
  gap: var(--pf-grid-gap-mobile); align-content: start;
}
.pf-m-kpis { display: grid; grid-template-columns: 1fr 1fr; gap: var(--pf-grid-gap-mobile); }
.pf-m-kpi { min-height: 128px; }
.pf-m-kpi .pf-kpi-icon { width: 36px; height: 36px; }
.pf-m-kpi .pf-kpi-value { font-size: var(--pf-size-kpi-m); line-height: var(--pf-lh-kpi-m); font-weight: var(--pf-w-bold); letter-spacing: -.02em; }
/* ★ คารูเซลตั้งใจให้เลื่อนแนวนอน — แต่ยังต้องมี `min-width: 0`
   ไม่งั้นตัวมันเองจะกว้างเท่าเนื้อหา แล้วดันกล่องแม่แทนที่จะเลื่อนอยู่ข้างใน */
.pf-carousel { display: flex; gap: var(--pf-grid-gap-mobile); overflow-x: auto; min-width: 0; scroll-snap-type: x mandatory; padding-bottom: var(--pf-space-2); }
.pf-carousel > * { flex: none; width: 156px; scroll-snap-align: start; }
.pf-m-card {
  min-height: 72px; background: var(--pf-surface); border: 1px solid var(--pf-border);
  border-radius: var(--pf-radius-lg); box-shadow: var(--pf-shadow-1);
  padding: 14px; display: grid; gap: var(--pf-space-3);
}
.pf-m-card .r { display: flex; align-items: center; gap: var(--pf-space-3); }
.pf-bottom-nav {
  height: 72px; flex: none; padding-bottom: env(safe-area-inset-bottom);
  display: grid; grid-template-columns: repeat(5, 1fr); align-items: center;
  background: var(--pf-surface); border-top: 1px solid var(--pf-border);
}
.pf-bottom-nav a {
  min-height: var(--pf-touch-target); display: grid; justify-items: center; align-content: center; gap: var(--pf-space-1);
  /* ⚠ 11px ต้องได้บรรทัด 16.6 → 17 · ป้ายใต้ไอคอนบอตทอมมีวรรณยุกต์ได้ (เช่น "เพิ่มเติม") */
  font-size: 11px; line-height: 17px; font-weight: var(--pf-w-medium); color: var(--pf-text-muted);
}
.pf-bottom-nav a[aria-current="page"] { color: var(--pf-primary-ink); }
/* ★ 22px มาจากมาร์กอัปของแกลเลอรีเอง (`<svg width="22" height="22">`)
   ⚠ `.pf-ico` ฐานเป็น `1em` · ในแถบล่าง font-size คือ 11px → ไอคอนจะเหลือ 11px
     วัดจริง 2026-08-26: ได้ 11px ทั้งห้าช่อง — เล็กกว่าดีไซน์ครึ่งหนึ่ง */
.pf-bottom-nav .pf-ico { width: 22px; height: 22px; }

/* ═══════════════════════════════════════════════════════════════════════════
   PHASE 3 · COMPONENT POLISH
   ★ ทุกค่าที่นี่ประกอบจากโทเคนที่มีอยู่แล้ว — ไม่มีตัวเลขใหม่
     สเปกให้ขนาดของ input/select ไว้ แต่ไม่ได้ให้สไตล์ของ label/hint/error
     จึงประกอบจากสเกลตัวอักษร + สีความหมายที่ประกาศไว้แล้ว (ระบุในรายการเบี่ยงเบน)
   ═══════════════════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════════════
   SEMANTIC ELEVATION — การ์ดแต่ละใบเปล่งแสงเป็นสีความหมายของตัวเอง

   ★★★★ ทำไมใช้ color-mix ไม่ใช่ rgba(var(--token), α)
     ⚠⚠⚠ `rgba(var(--x), .2)` ใช้ได้เฉพาะเมื่อ `--x` เป็น **เลขสามตัว** ไม่ใช่ hex
       ถ้าจะทำแบบนั้น ต้องเก็บสีทุกตัวสองรูป (hex + เลขสามตัว) = แหล่งความจริงสองที่
       ซึ่งคือบั๊ก P174 ที่เราเพิ่งโดนมา · และมันพังเงียบสนิท (CSS ถูกทิ้งทั้งบรรทัด)
     ★ `color-mix()` รับ hex ตรง ๆ → โทเคนยังมีชุดเดียว และธีมมืดไหลตามเองทั้งหมด

   ★★★ หกชั้นตามลำดับใน Flow
     Base Elevation   เงากลาง ไม่มีสี — มาจาก --pf-shadow-*
     Semantic Accent  --pf-accent ที่ .pf-accent--* ตั้งให้
     Hover Ambient    เงากลาง + แสงสีจาง ๆ ใต้การ์ด
     Focus Glow       วงโฟกัส + แสงสีรอบ ๆ
     Active           ยุบลงหนึ่งระดับ แสงลดลง (เหมือนกดของจริงลงบนพื้น)
     Selected         สีค้างไว้แบบเงียบ ๆ ไม่มีแสง — เพราะมันเป็นสถานะ ไม่ใช่การตอบสนอง
   ═══════════════════════════════════════════════════════════════════════════ */
/* ═══════════════════════════════════════════════════════════════════════════
   ★★★★ การ์ด KPI — **สีแสงมาจากสีไอคอนของการ์ดเอง** ไม่ต้องประกาศซ้ำทีละใบ

   เจ้าของทักเมื่อ 2026-09-04: หน้าภาพรวมชี้แล้วขอบเปลี่ยนสีตามไอคอน
   แต่หน้าอื่น ๆ น้ำเงินหมดทุกใบ
   วัดของจริงก่อนแก้: การ์ด KPI **37 ใบ** ทั้งระบบ — มีแค่ **5 ใบ** ของหน้าภาพรวม
   ที่เขียน `pf-accent--*` ไว้ · อีก 32 ใบตกมาที่ค่าปริยาย ซึ่งคือแบรนด์ (น้ำเงิน)

   ⚠⚠ ทางแก้ที่ผิดคือไปไล่เติมคลาสให้อีก 32 ใบ
     นั่นคือการเขียนกฎเดียวกัน 37 ครั้ง แล้ววันหนึ่งจะมีใบที่ไอคอนเขียวแต่แสงเหลือง
     โดยไม่มีอะไรฟ้อง (กฎเหล็กข้อ 2) · กฎที่ถูกคือ "แสงตามไอคอน" — เขียนที่นี่ครั้งเดียว

   ★★★ `:where()` ทำให้กฎพวกนี้มีน้ำหนักเท่า `.pf-accent--*` พอดี (0,1,0)
     และวางไว้ **ก่อน** ⇒ ใบที่อยากได้สีต่างจากไอคอนจริง ๆ ยังประกาศทับได้เหมือนเดิม
     ⚠ ห้ามย้ายบล็อกนี้ไปไว้ล่าง — จะกลายเป็นว่าไอคอนชนะคลาสที่คนเขียนเจาะจง
       `npm run logic` มีข้อค้ำเรื่องลำดับนี้อยู่

   ⚠ ใบที่ไม่มีไอคอนสี (หน้าลงทะเบียนใบหน้า · คำขอของฉัน) ตกมาที่ค่าปริยายตามเดิม
     — ไม่มีไอคอนก็ไม่มีสีให้ตาม · ยกเว้นใบที่ตัวเลขขึ้นแดงอยู่ ซึ่งบอกความหมายชัดกว่า
   ═══════════════════════════════════════════════════════════════════════════ */
:where(.pf-kpi):has(.pf-ic-brand)  { --pf-accent: var(--pf-primary-500); }
:where(.pf-kpi):has(.pf-ic-green)  { --pf-accent: var(--pf-success-500); }
/* ⚠ ฟ้าอมเขียว — หมึกของมันคือ `--pf-success-700` จึงเป็นตระกูลเขียว ไม่ใช่ฟ้า */
:where(.pf-kpi):has(.pf-ic-cyan)   { --pf-accent: var(--pf-success-500); }
:where(.pf-kpi):has(.pf-ic-blue)   { --pf-accent: var(--pf-info-500); }
:where(.pf-kpi):has(.pf-ic-yellow) { --pf-accent: var(--pf-warning-500); }
:where(.pf-kpi):has(.pf-ic-amber)  { --pf-accent: var(--pf-warning-500); }
:where(.pf-kpi):has(.pf-ic-pink)   { --pf-accent: var(--pf-danger-500); }
:where(.pf-kpi):has(.pf-ic-red)    { --pf-accent: var(--pf-danger-500); }
:where(.pf-kpi):has(.pf-ic-violet) { --pf-accent: var(--pf-violet-500); }
:where(.pf-kpi):has(.pf-ic-orange) { --pf-accent: var(--pf-orange-500); }
/* ★★ ใบที่ **ไม่มีไอคอนเลย** แต่ตัวเลขขึ้นแดง — แดงคือความหมายของใบนั้นแล้ว
   หน้าลงทะเบียนใบหน้ามีสามใบที่ "ต้องเป็นศูนย์" · ใบที่กำลังบอกว่าผิดกฎหมายอยู่
   ไม่ควรเรืองน้ำเงินเหมือนใบที่บอกจำนวนคนเฉย ๆ

   🔴🔴 `:not(:has(.pf-kpi-icon))` ไม่ใช่ของประดับ — **ทดสอบแล้วพังจริงถ้าไม่มี**
     `.pf-neg` ในระบบนี้แปลว่า "ตัวเลขสีแดง" ซึ่งใช้อยู่สองความหมาย
       ① ค่าที่ควรเป็นศูนย์แต่ไม่เป็น (หน้าลงทะเบียนใบหน้า) — แดงคือความหมายจริง
       ② เลขติดลบตามปกติ — หน้าเงินเดือน "เงินหักรวม −฿2,075" ซึ่ง **ปกติดี**
     รอบแรกผมเขียนกฎนี้ไว้กว้าง ๆ แล้วเปิดหน้าเงินเดือนดู: การ์ด "เงินหักรวม"
     ไอคอนเหลืองแต่เรืองแดง · หน้าสรรหาก็โดนใบหนึ่งเหมือนกัน
   ★ ใบที่มีไอคอนของตัวเองอยู่แล้ว ให้ไอคอนเป็นคนบอกเสมอ — มันคือตัวตนของใบนั้น
     ส่วน `.pf-neg` เป็นสถานะชั่วคราวที่แปลได้หลายอย่าง
   ★ อยู่ใน `:where()` ทั้งก้อน ⇒ น้ำหนัก 0 · แพ้ทั้งกฎไอคอนและ `.pf-accent--*` */
:where(.pf-kpi:not(:has(.pf-kpi-icon)):has(.pf-neg)) { --pf-accent: var(--pf-danger-500); }

.pf-accent--primary { --pf-accent: var(--pf-primary-500); }
.pf-accent--success { --pf-accent: var(--pf-success-500); }
.pf-accent--warning { --pf-accent: var(--pf-warning-500); }
.pf-accent--danger  { --pf-accent: var(--pf-danger-500); }
.pf-accent--info    { --pf-accent: var(--pf-info-500); }
.pf-accent--violet  { --pf-accent: var(--pf-violet-500); }
.pf-accent--orange  { --pf-accent: var(--pf-orange-500); }
/* ★ ไม่มีสีความหมาย = ไม่มีแสงสี · ใช้เงากลางอย่างเดียว */
.pf-accent--neutral { --pf-accent: transparent; }

:is(.pf-elevate, .pf-kpi) {
  transition:
    box-shadow var(--pf-duration-standard) var(--pf-ease-out),
    border-color var(--pf-duration-standard) var(--pf-ease-out),
    transform var(--pf-duration-standard) var(--pf-ease-out);
}

/* Hover — เงากลาง **บวก** แสงสีจากสีความหมายของการ์ดเอง
   ★★★ ทั้งระยะทอด (lift) และการหุบ (spread) เป็นโทเคน เพราะสองธีมต้องการคนละทรง
     สว่าง 10px / -10px = เงาหุบเข้าใต้การ์ด · มืด 3px / -1px = แสงลามรอบตัว
   ★ เส้นแสงขอบบน (rim) เป็นของธีมมืดล้วน — ธีมสว่างตั้ง 0% ไว้ ทำให้โปร่งสนิท
     ⚠ เขียนเป็นกฎแยกของธีมมืดก็ได้ แต่แล้วจะมีนิยาม hover สองชุด ซึ่งจะไม่ตรงกันวันหนึ่ง */
:is(.pf-elevate, .pf-kpi):hover {
  border-color: color-mix(in srgb, var(--pf-accent) var(--pf-glow-edge), var(--pf-border));
  box-shadow:
    var(--pf-shadow-2),
    inset 0 1px 0 0 color-mix(in srgb, var(--pf-accent) var(--pf-glow-rim), transparent),
    0 var(--pf-glow-lift) 28px var(--pf-glow-spread)
      color-mix(in srgb, var(--pf-accent) var(--pf-glow-hover), transparent);
}

/* Focus — วงโฟกัสต้องเห็นชัดเสมอ แล้วค่อยมีแสงประกอบ
   ★ วงโฟกัสไม่ใช่เรื่องความสวย มันคือสิ่งเดียวที่คนใช้คีย์บอร์ดมองเห็น */
:is(.pf-elevate, .pf-kpi):focus-visible,
:is(.pf-elevate, .pf-kpi):focus-within {
  outline: none;
  border-color: color-mix(in srgb, var(--pf-accent) 72%, var(--pf-border));
  box-shadow:
    0 0 0 2px color-mix(in srgb, var(--pf-accent) var(--pf-glow-ring), transparent),
    var(--pf-shadow-2),
    inset 0 1px 0 0 color-mix(in srgb, var(--pf-accent) var(--pf-glow-rim), transparent),
    0 calc(var(--pf-glow-lift) + 2px) 34px var(--pf-glow-spread)
      color-mix(in srgb, var(--pf-accent) var(--pf-glow-focus), transparent);
}

/* Active — ของที่ถูกกดต้องดู "ใกล้พื้นขึ้น" ไม่ใช่สว่างขึ้น
   ★ แสงหุบเข้าไปอีก 4px เสมอ ไม่ว่าธีมไหน — เพราะ "ใกล้พื้น" แปลว่าเงาสั้นลง */
:is(.pf-elevate, .pf-kpi):active {
  transform: translateY(1px);
  box-shadow:
    var(--pf-shadow-1),
    0 calc(var(--pf-glow-lift) / 2) 12px calc(var(--pf-glow-spread) - 4px)
      color-mix(in srgb, var(--pf-accent) var(--pf-glow-hover), transparent);
}

/* Selected — สถานะที่ค้างอยู่ ไม่ใช่การตอบสนองชั่วขณะ จึงไม่มีแสง มีแต่สีจาง ๆ
   ⚠ ใส่แสงให้สถานะที่ค้างอยู่ = หน้าจอเรืองตลอดเวลา แล้วแสงจะเลิกมีความหมาย */
.pf-elevate[aria-selected="true"],
.pf-elevate[data-selected="1"] {
  background: color-mix(in srgb, var(--pf-accent) var(--pf-glow-selected), var(--pf-surface));
  border-color: color-mix(in srgb, var(--pf-accent) 46%, var(--pf-border));
}

/* ★ แถบสีบางที่ขอบบน — บอกว่าการ์ดใบนี้เป็นเรื่องอะไร โดยไม่ต้องพึ่งสีพื้น
   (DESIGN.md §10 · สีต้องไม่ใช่สัญญาณเดียว → ยังมีไอคอนและข้อความกำกับอยู่) */
.pf-elevate--rail { position: relative; overflow: hidden; }
.pf-elevate--rail::before {
  content: ""; position: absolute; inset: 0 0 auto; height: 3px;
  background: var(--pf-accent); opacity: .9;
}
.pf-accent--neutral.pf-elevate--rail::before { display: none; }

@media (prefers-reduced-motion: reduce) {
  :is(.pf-elevate, .pf-kpi) { transition: none; }
  :is(.pf-elevate, .pf-kpi):active { transform: none; }
}

/* ── Forms ── */
.pf-form-grid { display: grid; gap: var(--pf-space-5); grid-template-columns: 1fr 1fr; }
.pf-form-grid .pf-col-2 { grid-column: 1 / -1; }
@media (max-width: 767px) { .pf-form-grid { grid-template-columns: 1fr; } }

/* ★★★★ `align-content: start` ไม่ใช่ของประดับ — มันคือสิ่งที่กันฟอร์มไม่ให้เบี้ยว
   ⚠⚠⚠ ช่องกรอกเป็น **กริดซ้อนในกริด** · ใบนอกยืดใบในให้สูงเท่าแถว (ค่าปริยาย stretch)
     แล้วใบในที่มีแถวน้อยกว่าจะ **เอาที่ว่างไปแบ่งใส่ทุกแถวของตัวเอง**
     ผลคือช่องที่ไม่มีข้อความช่วยเหลือ จะถูกดันลงครึ่งหนึ่งของส่วนต่าง
   วัดจริงก่อนแก้ (index.html · ฟอร์มสาธิต):
     "ชื่อ-สกุล" (มีข้อความช่วย) ป้ายสูง 19px  กล่องเริ่มที่ 2170
     "แผนก"     (ไม่มี)          ป้ายสูง 31.5px กล่องเริ่มที่ 2182.5  → **เบี้ยว 12.5px**
   ★ เจ้าของโปรเจคเห็นเองว่า "ระดับของฟอร์มมันต้องดูอยู่ระดับเดียวกัน" — ถูกต้อง
     และมันขึ้นกับ **ข้อมูล** (ช่องไหนมีข้อความช่วย) จึงโผล่มาเองวันหลังได้ตลอด
   ⚠ ห้ามแก้ด้วยการใส่ข้อความช่วยให้ครบทุกช่องเพื่อให้สูงเท่ากัน — นั่นคือดัดข้อมูลให้เข้ากับบั๊ก */
.pf-form-item { display: grid; gap: var(--pf-space-2); min-width: 0; align-content: start; }
/* ★★★★ ป้ายช่องกรอกเป็น **ข้อความธรรมดา** ไม่ใช่ flex (แก้ 2026-08-31 · ปิด P26)
   ⚠⚠ เดิมเป็น `display:flex; gap:6px` ซึ่งทำสองอย่างพร้อมกันโดยไม่ได้ตั้งใจ
     ① flex **กลืนช่องว่างที่เขียนไว้ในมาร์กอัป** — `ชื่อ-สกุล <span>*</span>`
        ตั้งใจให้มีเว้นวรรคหนึ่งตัว แต่ flex ทิ้งมันแล้วใส่ gap 6px แทน
     ② 6px กว้างกว่าเว้นวรรคจริงเกือบเท่าตัว → ดอกจันลอยจนดูเหมือนคนละคำ
        (วัด 37 จุดใน 10 หน้า · เห็นชัดสุดที่ "ช่องทางติดต่อเรื่องข้อมูลส่วนบุคคล *")
   ★ เป็นข้อความธรรมดาแล้ว ช่องว่างที่คนเขียนมาร์กอัปเขียนไว้คือช่องว่างที่เห็นจริง
     ซึ่งเป็นพฤติกรรมที่คนแก้มาร์กอัปคาดหวังอยู่แล้ว ไม่ต้องรู้ว่าข้างหลังเป็น flex
   ⚠ ห้ามกลับไปใช้ flex เพื่อจัดไอคอนในป้าย — วัดแล้วไม่มีป้ายไหนมีไอคอนเลย (0 จุด)
     ถ้าวันหนึ่งต้องมี ให้ห่อไอคอนกับข้อความไว้ใน span เดียว ไม่ใช่ทำทั้งป้ายเป็น flex */
.pf-label {
  font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption);
  font-weight: var(--pf-w-medium); color: var(--pf-gray-700);
  display: block;
}
.pf-required { color: var(--pf-danger-600); font-weight: var(--pf-w-semibold); }
.pf-hint {
  font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption);
  color: var(--pf-text-muted);
}
/* ★ คำกำกับที่อยู่ **ในบรรทัดเดียวกับป้ายช่อง** เช่น "ที่ตั้ง (ไม่บังคับ)"
   ต่างจาก `.pf-hint` ตรงที่ไม่ขึ้นบรรทัดใหม่ และต้องลบน้ำหนักของ `.pf-label` ทิ้ง
   ⚠ ตัวนี้เป็น **ลูกของ `.pf-label`** ไม่ใช่พี่น้อง — เว้นวรรคมาจากมาร์กอัปเหมือนดอกจัน */
.pf-hint-inline {
  font-weight: var(--pf-w-regular); color: var(--pf-text-muted);
  font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption);
}
.pf-error-text {
  font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption);
  color: var(--pf-danger-700); display: flex; align-items: flex-start; gap: var(--pf-space-2);
}
.pf-field--error { border-color: var(--pf-danger-500); }
.pf-field--error:focus-within { border-color: var(--pf-danger-600); box-shadow: 0 0 0 2px var(--pf-danger-100); }
.pf-field--disabled { opacity: .48; pointer-events: none; background: var(--pf-gray-50); }
.pf-field--textarea { height: auto; align-items: stretch; padding: var(--pf-space-4) 12px; }
/* ⚠⚠ `min-width: 0` จำเป็น ไม่ใช่ของประดับ (รอบ 312 · P230)
   `.pf-field` เป็น flex · ลูกของ flex มี `min-width: auto` เป็นค่าปริยาย = **ยุบไม่ได้**
   และ `min-content` ของ `<textarea>` มาจาก `cols` (ปริยาย 20) ไม่ใช่จากเนื้อหา
   → ในกล่องแคบ textarea จะดัน `.pf-field` ให้กว้างเกินพ่อ แล้วหน้าเลื่อนแนวนอน
   ★ `width: 100%` ไม่ช่วย — มันบอกความกว้างที่ *อยากได้* ไม่ใช่ความกว้างที่ *ยอมยุบถึง* */
.pf-field--textarea textarea {
  border: 0; outline: 0; background: none; width: 100%; min-width: 0; resize: vertical; min-height: 76px;
  font-size: var(--pf-size-body); line-height: var(--pf-lh-body); color: var(--pf-text-primary);
}
.pf-affix { color: var(--pf-text-muted); font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact); white-space: nowrap; }

/* ═══ หัวข้อย่อยในฟอร์ม (2026-08-28) ═══════════════════════════════════════
   ★★★★ ทำไมต้องมี — ฟอร์มพนักงานโตจาก 13 ช่องเป็น 24 ช่องในรอบเดียว
     กริดสองคอลัมน์ที่มีช่องเรียงติดกัน 24 ช่อง ไม่มีอะไรบอกว่าตรงไหนคือเรื่องอะไร
     ⚠⚠ ผลไม่ใช่แค่ "ดูรก" — มันทำให้ปุ่ม "ไปที่หัวข้อนี้" ของรายการที่ยังขาด
       ไม่มีที่ให้กระโดดไป · หัวข้อคือ **จุดหมายของลิงก์** ไม่ใช่ของตกแต่ง
   ★ เป็นลูกของ `.pf-form-grid` โดยตรง และกินเต็มความกว้างเสมอ
   ⚠ `scroll-margin-top` จำเป็น — กระโดดในกล่องที่เลื่อนได้แล้วหัวข้อจะไปติดขอบบนพอดี
     จนอ่านไม่ออกว่านี่คือหัวข้อของอะไร */
/* ★★★ เส้นคั่นกลุ่มต้องมีที่ว่าง **ทั้งสองข้าง** ไม่ใช่ข้างเดียว
   ⚠⚠ เดิมมีแต่ `padding-top` (ใต้เส้น) ⇒ บรรทัดสุดท้ายของกลุ่มก่อนหน้า
     **ชนเส้นพอดี** — วัดได้ 0px เหนือเส้น แต่ 12px ใต้เส้น (เจ้าของทัก 2026-09-05)
   ★ เหนือเส้นกว้างกว่าใต้เส้นโดยตั้งใจ (16 vs 12) — ที่ว่างเหนือเส้นทำหน้าที่
     "ปิดกลุ่มที่แล้ว" ส่วนใต้เส้นแค่หายใจก่อนอ่านหัวข้อ · เท่ากันเป๊ะจะอ่านว่า
     เส้นลอยอยู่กลางอากาศ ไม่รู้ว่าเป็นท้ายของอันบนหรือหัวของอันล่าง */
.pf-form-sec {
  grid-column: 1 / -1;
  display: flex; align-items: baseline; gap: var(--pf-space-3); flex-wrap: wrap;
  margin-top: var(--pf-space-5);
  padding-top: var(--pf-space-4); border-top: 1px solid var(--pf-border);
  scroll-margin-top: var(--pf-space-5);
}
/* ★ หัวข้อแรกไม่ต้องมีเส้นคั่น — ไม่มีอะไรอยู่เหนือมันให้คั่น
   ⚠ ต้องล้าง `margin-top` ด้วย ไม่ใช่แค่ padding/border — ไม่งั้นหัวข้อแรก
     จะถูกดันลงมาจากขอบการ์ดโดยไม่มีเส้นให้เห็นว่าทำไม

   🔴🔴 เดิมผูกกับ `.pf-form-grid >` ซึ่ง **ไม่ครอบฟอร์มสมัครงาน**
     ที่นั่นพ่อคือ `.pf-auth-form` ⇒ กฎนี้ไม่เคยมีผลเลย และหัวข้อแรกมีเส้นลอย
     อยู่บนสุดมาตลอด (วัดเจอ 2026-09-05 ตอนแก้ระยะห่างของเส้นคั่น)
   ★ เลิกผูกกับพ่อ — "เป็นลูกคนแรกของกล่องไหน ก็ไม่ควรมีเส้นเหนือหัว" เหมือนกันหมด */
.pf-form-sec:first-child {
  margin-top: 0; padding-top: 0; border-top: 0;
}
.pf-form-sec h4 {
  margin: 0; font-size: var(--pf-size-body); line-height: var(--pf-lh-body);
  font-weight: var(--pf-w-semibold); color: var(--pf-text-primary);
}
.pf-form-sec p {
  margin: 0; font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption);
  color: var(--pf-text-muted);
}

/* ═══ รายการ "ยังขาดอะไร" (2026-08-28) ═════════════════════════════════════
   ★★★★ ต่างจาก `.pf-note-list` ตรงที่ **แต่ละข้อมีปุ่มไปแก้ของตัวเอง**
     `.pf-note-list` เป็น bullet เฉย ๆ — บอกว่ามีอะไร แต่ไม่พาไปไหน
     ⚠⚠ รายการยาว ๆ ที่บอกว่าขาดอะไรแต่ไม่พาไปแก้ คือรายการที่คนกดปิดทิ้ง
       ต้นแบบคือฟอร์มสมัครงานของระบบเดิม ("เหลืออีก 5 รายการ · ไปที่หัวข้อนี้")
       ซึ่งเจ้าของโปรเจคชี้ให้ดูเองว่าอยากได้แบบนี้
   ★★ สองระดับ ไม่ใช่ระดับเดียว — `--costly` คือข้อที่ขาดแล้ว **มีคนเสียเงินหรือเสียสิทธิ์**
     ⚠ ทำให้ทุกข้อดูเร่งด่วนเท่ากัน = ไม่มีข้อไหนเร่งด่วน · HR ที่มีเวลาแก้สองข้อ
       ต้องรู้ว่าสองข้อไหน (`employeeGaps()` เรียงมาให้แล้ว ที่นี่แค่ทำให้ตาเห็น) */
.pf-gap-list { margin: var(--pf-space-4) 0 0; padding: 0; list-style: none; display: grid; gap: var(--pf-space-2); }
.pf-gap {
  display: flex; align-items: center; gap: var(--pf-space-3); flex-wrap: wrap;
  padding: var(--pf-space-2) var(--pf-space-3);
  border-radius: var(--pf-radius-sm); background: var(--pf-surface);
  border: 1px solid var(--pf-border);
}
/* ★ ขีดซ้ายหนา = ข้อที่มีผลกับเงิน/สิทธิ์ · ไม่ใช้สีพื้นทั้งแถบเพราะจะแย่งสายตากับกล่องแม่ */
.pf-gap--costly { border-inline-start: 3px solid var(--pf-danger-500); }
.pf-gap b { font-weight: var(--pf-w-semibold); color: var(--pf-text-primary); }
/* ★★★ **ผล** ของการขาด — นี่คือส่วนที่ทำให้รายการนี้มีประโยชน์ ไม่ใช่ชื่อช่อง
   ⚠ ห้ามซ่อนบรรทัดนี้บนจอแคบเพื่อประหยัดที่ · "ยังไม่ได้กรอกเลขบัญชี" อ่านแล้วไม่รู้ว่าต้องรีบ */
.pf-gap i { font-style: normal; color: var(--pf-text-secondary); min-width: 0; }
.pf-gap .pf-btn { flex: none; margin-inline-start: auto; }
@media (max-width: 560px) {
  .pf-gap .pf-btn { margin-inline-start: 0; }
}

/* ★★★★ ช่องติ๊ก — **ตัวแรกของระบบ** (2026-08-27 · หน้าลงทะเบียนใบหน้า)
   ⚠ ก่อนหน้านี้ไม่มีที่ไหนใช้ checkbox เลย · ที่แรกที่ต้องใช้คือ **ช่องยินยอมตาม ม.26**
     ซึ่งเป็นช่องที่พลาดไม่ได้ที่สุดในระบบ → สไตล์ต้องอยู่ที่นี่ ไม่ใช่ฝังในคอมโพเนนต์
   ★ ใช้ accent-color เพื่อให้ช่องติ๊กของเบราว์เซอร์ใช้สีแบรนด์
     แทนการวาดกล่องเองด้วย ::before ซึ่งจะเสียพฤติกรรมคีย์บอร์ดและ :focus-visible ไปฟรี ๆ */
.pf-check {
  display: flex; align-items: flex-start; gap: var(--pf-space-3);
  cursor: pointer; min-width: 0;
  font-size: var(--pf-size-body); line-height: var(--pf-lh-body);
  color: var(--pf-text-primary);
}
/* ★★★★ วาดกล่องเอง — **ไม่ใช่แค่ย้อมสีช่องติ๊กของระบบปฏิบัติการ**
   ⚠⚠ รอบแรกใช้แค่ `accent-color` ซึ่งเปลี่ยนได้แต่สีเครื่องหมายถูก
     รูปทรง · ขนาด · มุม · เส้นขอบ ยังเป็นของ OS ทั้งหมด → หน้าตาไม่ตรงกับทั้งระบบ
     (เจ้าของทักมาพร้อมภาพหน้าจอ 2026-08-27 · อาการเดียวกับที่ `<select>` เคยโดน)
   ★ `appearance:none` แล้ววาดใหม่ · แต่ยังเป็น `<input type=checkbox>` จริง
     จึงได้พฤติกรรมคีย์บอร์ด · เว้นวรรคติ๊ก · โฟกัส · ป้ายกำกับ · ตัวอ่านหน้าจอ มาฟรีทั้งหมด
   ⚠ ห้ามเปลี่ยนไปวาดด้วย `<div role=checkbox>` เพื่อให้จัดสไตล์ง่ายขึ้น —
     จะต้องเขียนพฤติกรรมทั้งชุดเองเหมือนที่ `pf-select` ต้องทำ ซึ่งที่นี่ไม่จำเป็นเลย */
.pf-check input[type="checkbox"] {
  appearance: none; -webkit-appearance: none;
  flex: none; width: 18px; height: 18px; margin: 0; cursor: pointer;
  /* ★ จัดให้กึ่งกลางบรรทัดแรกพอดี — ไม่ใช่ชิดบนสุดของกล่องข้อความหลายบรรทัด */
  margin-block-start: calc((var(--pf-lh-body) - 18px) / 2);
  border: 1.5px solid var(--pf-border-strong);
  border-radius: var(--pf-radius-sm);
  background: var(--pf-surface);
  display: grid; place-content: center;
  transition: background var(--pf-duration-standard) var(--pf-ease-out),
              border-color var(--pf-duration-standard) var(--pf-ease-out);
}
/* ★ เครื่องหมายถูกวาดด้วย clip-path — คมทุกความละเอียด และไม่ต้องพึ่งฟอนต์ไอคอน
   ⚠ ใช้ตัวอักษร ✓ แทนไม่ได้ — รูปร่างเปลี่ยนตามฟอนต์ที่เครื่องนั้นมี */
.pf-check input[type="checkbox"]::before {
  content: ""; width: 10px; height: 10px; transform: scale(0);
  background: var(--pf-on-primary);
  clip-path: polygon(14% 44%, 0 65%, 40% 100%, 100% 16%, 84% 0, 39% 70%);
  transition: transform var(--pf-duration-fast) var(--pf-ease-out);
}
.pf-check input[type="checkbox"]:checked {
  background: var(--pf-primary-500); border-color: var(--pf-primary-500);
}
.pf-check input[type="checkbox"]:checked::before { transform: scale(1); }
.pf-check input[type="checkbox"]:hover:not(:disabled) { border-color: var(--pf-primary-500); }
/* ★ วงโฟกัสต้องเห็นทั้งตอนติ๊กและไม่ติ๊ก — offset กันไม่ให้ทับเส้นขอบตัวเอง */
.pf-check input[type="checkbox"]:focus-visible {
  outline: 2px solid var(--pf-primary-500); outline-offset: 2px;
}
.pf-check input[type="checkbox"]:disabled { opacity: .48; cursor: not-allowed; }
.pf-check:has(input:disabled) { cursor: not-allowed; color: var(--pf-text-muted); }
/* ★ ช่องติ๊กที่ยังไม่ได้ตัดสิน (ติ๊กบางส่วน) — ใช้กับหัวตารางที่เลือกทั้งหน้า */
.pf-check input[type="checkbox"]:indeterminate {
  background: var(--pf-primary-500); border-color: var(--pf-primary-500);
}
.pf-check input[type="checkbox"]:indeterminate::before {
  transform: scale(1); clip-path: none; width: 10px; height: 2px; border-radius: 1px;
}

/* สวิตช์ — ใช้ที่หน้าตั้งค่า · ขนาดมาจาก control-sm */
.pf-switch {
  width: 40px; height: 22px; flex: none; border-radius: var(--pf-radius-pill);
  background: var(--pf-gray-300); position: relative;
  transition: background var(--pf-duration-standard) var(--pf-ease-out);
}
/* ★★★★ ปุ่มกลมของสวิตช์ต้องขาวทั้งสองธีม — มันคือ "ของที่วางบนพื้นสี"
   ⚠⚠⚠ เดิมใช้ `--pf-gray-0` ซึ่งแปลว่า "ขาว" ในธีมสว่าง แต่แปลว่า **พื้นการ์ด** ในธีมมืด
     ธีมมืดจึงได้ปุ่มสีเกือบดำนั่งบนรางม่วง — วัดได้ 3.42 : 1 (แก้แล้วเป็น 5.15)
     ผ่านเกณฑ์ WCAG ของ "ส่วนประกอบที่ไม่ใช่ตัวอักษร" (3:1) พอดี **จึงไม่มีเครื่องตรวจไหนร้อง**
     แต่มันผิดเรื่องความหมาย: ปุ่มที่เลื่อนได้ต้องเป็นของที่เด่นที่สุด ไม่ใช่จมหายไปในราง
   ★ ตระกูล P174 เป๊ะ — โทเคนชื่อเดิม ความหมายเปลี่ยนตามธีม
     `--pf-on-primary` คือขาวจริงทั้งสองธีม เพราะมันแปลว่า "สีของสิ่งที่อยู่บนพื้นสีแบรนด์" */
.pf-switch::after {
  content: ""; position: absolute; top: 3px; left: 3px; width: 16px; height: 16px;
  border-radius: 50%; background: var(--pf-on-primary); box-shadow: var(--pf-shadow-1);
  transition: transform var(--pf-duration-standard) var(--pf-ease-out);
}
.pf-switch[aria-checked="true"] { background: var(--pf-primary-500); }
.pf-switch[aria-checked="true"]::after { transform: translateX(18px); }

/* แถบยืนยันติดล่าง สำหรับฟอร์มยาวบนมือถือ (RESPONSIVE.md) */
.pf-sticky-actions {
  position: sticky; bottom: 0; display: flex; gap: var(--pf-space-3);
  padding: var(--pf-space-4); margin: 0 calc(var(--pf-space-5) * -1) calc(var(--pf-space-5) * -1);
  background: var(--pf-surface); border-top: 1px solid var(--pf-border);
  padding-bottom: calc(var(--pf-space-4) + env(safe-area-inset-bottom));
}

/* ═══════════════════════════════════════════════════════════════════════════
   ★★★★ ช่องเลือกที่ออกแบบได้ (pf-select)

   ⚠⚠⚠ ทำไมต้องเขียนเอง ทั้งที่ <select> มีอยู่แล้ว
     **รายการที่กางออกของ <select> จัดสไตล์ไม่ได้เลย** — เบราว์เซอร์วาดด้วย
     คอนโทรลของระบบปฏิบัติการ อยู่นอก DOM ที่ CSS เข้าถึง
     เจ้าของโปรเจคเห็นเอง: กรอบเหลี่ยม พื้นน้ำเงินเข้ม ไม่เข้ากับทั้งหน้า
   ★ <select> ยังใช้ได้อยู่และยังถูกจัดสไตล์ส่วนที่ทำได้ไว้ — เก็บไว้เป็นตัวสำรอง
     แต่หน้าที่อยากได้รายการตามดีไซน์ ให้ใช้ pf-select
   ⚠ ต้องมี role/aria ครบ ไม่งั้นโปรแกรมอ่านหน้าจอจะไม่รู้ว่านี่คือช่องเลือก
   ═══════════════════════════════════════════════════════════════════════════ */
.pf-select { position: relative; }
.pf-select > .pf-field { width: 100%; text-align: left; }
.pf-select-value { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; color: var(--pf-text-primary); }
.pf-select-value[data-empty="1"] { color: var(--pf-text-placeholder); }
.pf-select-caret {
  flex: none; width: 7px; height: 7px; margin-right: 2px;
  border-right: 1.6px solid var(--pf-text-muted);
  border-bottom: 1.6px solid var(--pf-text-muted);
  transform: translateY(-2px) rotate(45deg);
  transition: transform var(--pf-duration-standard) var(--pf-ease-out);
}
.pf-select[data-open="1"] > .pf-field { border-color: var(--pf-primary-500); box-shadow: 0 0 0 2px var(--pf-primary-100); }
.pf-select[data-open="1"] .pf-select-caret { transform: translateY(1px) rotate(-135deg); }

/* ระดับ 2 ตาม DESIGN.md §3 — ตัวควบคุมลอย
   ★★★★ ต้องเป็น `fixed` ไม่ใช่ `absolute`
     ⚠⚠⚠ เกิดจริง: รายการที่กางออกถูก **การ์ดกลืน** — เจ้าของโปรเจคเห็นเองที่ช่อง "20 / หน้า"
       ต้นเหตุ: `.pf-card--flush` มี `overflow:hidden` (จำเป็น ไม่งั้นมุมโค้งของตารางล้น)
       และ `.pf-table-scroll` มี `overflow-x:auto` — ทั้งคู่ตัดของที่ absolute อยู่ข้างใน
     ★ `fixed` หลุดจากกรอบตัดของบรรพบุรุษทุกชั้น · ตำแหน่งคำนวณด้วย JS ตอนเปิด
     ⚠ ห้ามแก้ด้วยการถอด overflow:hidden ออกจากการ์ด — มุมโค้งจะพังทั้งระบบ */
.pf-listbox {
  position: fixed; z-index: 60; top: 0; left: 0;
  max-height: 264px; padding: var(--pf-space-2);
  background: var(--pf-surface); border: 1px solid var(--pf-border);
  border-radius: var(--pf-radius-md); box-shadow: var(--pf-shadow-2);
  display: none;
}
/* ★★★★ กล่องเป็น **คอลัมน์** ไม่ใช่ block (เพิ่มช่องค้นหา 2026-08-27)
   ⚠⚠ `overflow:auto` ต้องอยู่ที่ `.pf-listbox-scroll` ชั้นใน **ไม่ใช่ที่กล่องนอก**
     อยู่ที่กล่องนอกเมื่อไหร่ ช่องค้นหาจะเลื่อนหายไปพร้อมรายการทันทีที่เลื่อนลง
     ซึ่งทำให้ช่องค้นหาไร้ประโยชน์กับรายการยาว ๆ ที่มันมีไว้แก้พอดี
   ★ `placePopover(limitHeight)` ตั้ง max-height ที่กล่องนอก — คอลัมน์จึงบีบชั้นในให้เอง */
.pf-select[data-open="1"] .pf-listbox { display: flex; }
/* ★★★★ ต้องมีทางเปิดกล่องที่ **ไม่พึ่งการเป็นลูกของ `.pf-select`** ด้วย (P211)
   กล่องต้องถูกย้ายไปแขวนที่ `<body>`/`<dialog>` เพื่อหนี `overflow:hidden` ของการ์ด
   ทันทีที่มันย้ายออก กฎบรรทัดบนก็ไม่ match → `.pf-listbox` คงค่า `display:none`
   → รายการไม่โผล่เลยโดยไม่มี error · เหตุผลเดียวกับ `.pf-cal[data-open="1"]` */
.pf-listbox[data-open="1"] { display: flex; flex-direction: column; min-height: 0; }
/* ฉากหลังกันกดทะลุตอนรายการเปิดอยู่ — ★ ต่ำกว่า `.pf-listbox` (60) แต่สูงกว่าเนื้อหาหน้า */
/* ★ ชั้นที่เลื่อนได้จริง — `min-height:0` บังคับ ไม่งั้น flex item ไม่ยอมหดต่ำกว่าเนื้อ */
.pf-listbox-scroll { overflow: auto; min-height: 0; }
/* ★★★ ช่องค้นหาในรายการ — ติดอยู่กับที่ ไม่เลื่อนตามตัวเลือก
   ⚠ ขึ้นเฉพาะรายการที่ยาวจริง (เกินเกณฑ์ใน `ui/select.ts`)
     ใส่ให้ทุกช่องเลือก = ช่องที่มีสองตัวเลือกก็มีช่องค้นหา ซึ่งดูเหมือนระบบไม่ได้คิด */
.pf-listbox-search {
  flex: none; display: flex; align-items: center; gap: var(--pf-space-2);
  padding: 0 var(--pf-space-2) var(--pf-space-2);
  border-bottom: 1px solid var(--pf-border); margin-bottom: var(--pf-space-2);
  color: var(--pf-text-muted);
}
.pf-listbox-search input {
  flex: 1; min-width: 0; border: 0; background: transparent; outline: none;
  font: inherit; color: var(--pf-text-primary);
  font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact);
}
.pf-listbox-search .pf-ico { flex: none; width: 14px; height: 14px; }
/* ★ ค้นแล้วไม่เจอ ต้องบอก ไม่ใช่กล่องเปล่า — กล่องเปล่าอ่านว่า "ระบบพัง" (P6) */
.pf-listbox-empty {
  padding: var(--pf-space-4) var(--pf-space-3); text-align: center;
  color: var(--pf-text-muted);
  font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact);
}
.pf-listbox-scrim { position: fixed; inset: 0; z-index: 59; }
.pf-option {
  display: flex; align-items: center; gap: var(--pf-space-3);
  min-height: var(--pf-control-sm); padding: 0 var(--pf-space-3);
  border-radius: var(--pf-radius-sm); cursor: pointer;
  font-size: var(--pf-size-body); line-height: var(--pf-lh-body); color: var(--pf-text-secondary);
}
.pf-option + .pf-option { margin-top: 2px; }
.pf-option:hover, .pf-option[data-active="1"] { background: var(--pf-gray-50); color: var(--pf-text-primary); }
.pf-option[aria-selected="true"] { background: var(--pf-primary-50); color: var(--pf-primary-ink); font-weight: var(--pf-w-semibold); }

/* ═══════════════════════════════════════════════════════════════════════════
   ช่องเลือกเวลา (`pf-time`) — เจ้าของสั่ง 2026-09-01
     *"ลองออกแบบดีไซน์ของ select เวลา คล้าย ๆ กับ dropdown ของ select"*

   🔴 ทำไมไม่ใช้ `<input type="time">` — เหตุผลเดียวกับที่ไม่ใช้ `<select>`
     คอนโทรลของ OS จัดสไตล์ไม่ได้ · Chrome บน Windows โชว์ AM/PM ตามภาษาเครื่อง
     ทั้งที่ระบบนี้ใช้ 24 ชม. เสมอ · และปุ่มลูกศรจิ๋วของมันกดยากด้วยนิ้วโป้ง

   ★★★★ **สองคอลัมน์** ไม่ใช่รายการเดียวยาว ๆ
     รายการเดียวที่ step 1 นาที = 1,440 ตัวเลือก · และเลือก 08:37 ไม่ได้เลยถ้าไม่อยู่ในก้าว
     ⇒ ชั่วโมง 24 × นาทีตามก้าว — เลือกได้ทุกค่า และเลื่อนสั้นทั้งสองฝั่ง

   ★ ยืมโทนและกลไกจาก `.pf-select` ทั้งชุด — ผู้ใช้เห็นสองช่องนี้ในฟอร์มเดียวกันเสมอ
     (เช่นหน้าจัดกะ: เลือกวันด้วย select แล้วเลือกเวลาด้วยตัวนี้ติดกัน)
   ═══════════════════════════════════════════════════════════════════════════ */
.pf-timepick { position: relative; }
.pf-timepick > .pf-field { cursor: pointer; }
.pf-timepick-value {
  flex: 1; min-width: 0; text-align: start;
  color: var(--pf-text-primary);
  /* ★ ตัวเลขต้องกว้างเท่ากันทุกตัว ไม่งั้นค่าขยับซ้ายขวาตอนเปลี่ยนเวลา */
  font-variant-numeric: tabular-nums;
}
.pf-timepick-value[data-empty="1"] { color: var(--pf-text-placeholder); }
/* ★ ไอคอนนาฬิกาวาดด้วย CSS — ไม่ต้องพึ่งสไปรท์ ช่องนี้ใช้ในหน้าดีไซน์ที่ไม่ได้ฝังสไปรท์ด้วย */
.pf-timepick-icon {
  flex: none; inline-size: 15px; block-size: 15px; border-radius: 50%;
  border: 1.6px solid var(--pf-text-muted); position: relative;
}
.pf-timepick-icon::before, .pf-timepick-icon::after {
  content: ''; position: absolute; inset-block-start: 50%; inset-inline-start: 50%;
  background: var(--pf-text-muted); transform-origin: 0 0;
}
.pf-timepick-icon::before { inline-size: 4px; block-size: 1.4px; transform: rotate(-90deg); }
.pf-timepick-icon::after  { inline-size: 5px; block-size: 1.4px; }
.pf-timepick[data-open="1"] > .pf-field {
  border-color: var(--pf-primary-500); box-shadow: 0 0 0 2px var(--pf-primary-100);
}

/* ── กล่องที่กางออก ─────────────────────────────────────────────────────
   ⚠ `position: fixed` เพราะกล่องถูกย้ายไปแขวนนอก DOM (ดู `ui/popover.ts`) */
.pf-timepick-pop {
  position: fixed; z-index: 60; top: 0; left: 0;
  inline-size: 232px; padding: var(--pf-space-3);
  display: none; flex-direction: column; gap: var(--pf-space-3);
  background: var(--pf-surface); border: 1px solid var(--pf-border);
  border-radius: var(--pf-radius-md); box-shadow: var(--pf-shadow-2);
}
.pf-timepick-pop[data-open="1"] { display: flex; }
/* ★★★ หัวกล่องโชว์ค่าที่กำลังจะได้ **ตัวใหญ่**
   ⚠ ไม่มีหัวนี้ = เลือกชั่วโมงแล้วต้องเดาว่าค่าตอนนี้เป็นอะไร เพราะปุ่มฐานถูกกล่องบังอยู่ */
.pf-timepick-head {
  display: grid; justify-items: center; gap: 2px;
  padding-block-end: var(--pf-space-2);
  border-block-end: 1px solid var(--pf-border);
}
.pf-timepick-big {
  font-size: var(--pf-size-section); font-weight: var(--pf-w-bold);
  font-variant-numeric: tabular-nums; letter-spacing: .02em;
}
.pf-timepick-cols { display: grid; grid-template-columns: 1fr 1fr; gap: var(--pf-space-3); }
.pf-timepick-col { display: grid; gap: var(--pf-space-2); min-inline-size: 0; }
.pf-timepick-colhead {
  text-align: center; font-size: var(--pf-size-caption); color: var(--pf-text-muted);
}
/* ⚠ `overflow` อยู่ที่คอลัมน์ **ไม่ใช่ที่กล่องนอก** — อยู่ที่กล่องนอกแล้วหัวกล่อง
   กับแถวปุ่มจะเลื่อนหายไปพร้อมรายการ (บทเรียนเดียวกับช่องค้นหาของ `.pf-listbox`) */
.pf-timepick-scroll {
  max-block-size: 176px; overflow-y: auto;
  display: grid; gap: 2px; padding-inline-end: 2px;
}
.pf-timepick-cell {
  border: 0; border-radius: var(--pf-radius-sm);
  /* ★ 32px — เล็กกว่าเป้าสัมผัสมาตรฐาน แต่กว้างเต็มคอลัมน์ (~100px) จึงยังกดง่าย
     ⚠ ใช้ 40px เต็มจะเห็นแค่ 4 แถวต่อหน้าจอ แล้วต้องลากหลายรอบกว่าจะถึงค่าที่ต้องการ */
  min-block-size: 32px; padding: 0 var(--pf-space-2);
  background: transparent; color: var(--pf-text-secondary);
  font-family: inherit; font-size: var(--pf-size-body);
  font-variant-numeric: tabular-nums; cursor: pointer;
}
.pf-timepick-cell:hover { background: var(--pf-gray-50); color: var(--pf-text-primary); }
.pf-timepick-cell[data-on="1"] {
  background: var(--pf-primary-50); color: var(--pf-primary-ink);
  font-weight: var(--pf-w-semibold);
}
.pf-timepick-acts { display: flex; gap: var(--pf-space-2); justify-content: flex-end; }
.pf-option .tick { margin-left: auto; opacity: 0; flex: none; }
.pf-option[aria-selected="true"] .tick { opacity: 1; }
.pf-listbox-label {
  padding: var(--pf-space-3) var(--pf-space-3) var(--pf-space-2);
  font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption);
  font-weight: var(--pf-w-semibold); color: var(--pf-text-muted);
}

/* ═══════════════════════════════════════════════════════════════════════════
   ★★★★ ปฏิทิน (pf-datepicker) — ไทย · พ.ศ. · สัปดาห์เริ่มวันอาทิตย์

   ⚠ ช่องวันที่ของเบราว์เซอร์ (input[type=date]) แสดงเป็น ค.ศ. และรูปแบบตามภาษาเครื่อง
     ระบบนี้ใช้ พ.ศ. ทั้งระบบ จึงใช้ของเบราว์เซอร์ไม่ได้ตั้งแต่แรก
   ═══════════════════════════════════════════════════════════════════════════ */
.pf-datepicker { position: relative; }
/* ★ เหตุผลเดียวกับ .pf-listbox — ต้อง fixed ไม่งั้นถูกการ์ดตัด */
.pf-cal {
  position: fixed; z-index: 60; top: 0; left: 0;
  width: 292px; padding: var(--pf-space-4);
  background: var(--pf-surface); border: 1px solid var(--pf-border);
  border-radius: var(--pf-radius-lg); box-shadow: var(--pf-shadow-2);
  display: none;
}
.pf-datepicker[data-open="1"] .pf-cal { display: block; }
/* ★★★★ ต้องมีทางเปิดกล่องที่ **ไม่พึ่งการเป็นลูกของ `.pf-datepicker`** ด้วย (รอบ 283 · P211)
   🔴 ในแอป กล่องปฏิทินถูก `appendChild` ออกไปแขวนที่ `<body>` หรือที่ `<dialog>` ตอนเปิด
     (เหตุผลอยู่หัวไฟล์ `date-picker.ts` — `position:fixed` ตายเมื่อ ancestor มี transform
      และ top layer ของ `<dialog>` อยู่เหนือทุกอย่างที่แขวนที่ body)
     ทันทีที่มันย้ายออก มันก็ **ไม่ใช่ลูกของ `.pf-datepicker`** อีกต่อไป
     → กฎบรรทัดบนไม่ match → `.pf-cal` คงค่า `display:none` → **ปฏิทินไม่โผล่เลย**
   ⚠⚠ อาการจะเป็น "กดแล้วไม่มีอะไรเกิดขึ้น" ไม่ใช่ error — และเทมเพลตก็ดูถูกต้องทุกบรรทัด
   ★ หน้าดีไซน์เป็นภาพนิ่งที่กล่องไม่เคยย้ายไปไหน จึงไม่มีทางเจอปัญหานี้ด้วยตัวเอง */
.pf-cal[data-open="1"] { display: block; }

/* ฉากหลังของปฏิทิน — กดที่ไหนก็ได้นอกกล่องเพื่อปิด
   ⚠ ระบบดีไซน์ไม่มีตัวนี้เพราะหน้าดีไซน์ปิดปฏิทินด้วยการกดปุ่มเท่านั้น
     ของจริงต้องปิดได้ด้วยการกดที่ว่าง ไม่งั้นผู้ใช้ติดอยู่กับปฏิทินที่เปิดค้าง
   ★ ต้องอยู่ z-index ต่ำกว่า `.pf-cal` (60) แต่สูงกว่าเนื้อหาหน้า */
.pf-cal-scrim { position: fixed; inset: 0; z-index: 59; }
/* แถบช่วงสำเร็จรูปด้านบนปฏิทิน — ระบบดีไซน์มีแต่แถบล่าง (`.pf-cal-foot`) ซึ่งมีเส้นคั่นบน
   จึงใช้แทนกันไม่ได้ · ใช้ปุ่ม `.pf-cal-quick` ตัวเดียวกันทั้งสองแถบ */
.pf-cal-presets {
  display: flex; gap: var(--pf-space-2); flex-wrap: wrap;
  margin-bottom: var(--pf-space-4); padding-bottom: var(--pf-space-4);
  border-bottom: 1px solid var(--pf-border);
}
/* ★ ปุ่มช่วงที่กำลังใช้อยู่ต้องเห็นว่าเลือกไว้ — ไม่งั้นกด "7 วัน" แล้วไม่มีอะไรบอกว่าติดแล้ว */
.pf-cal-quick[aria-pressed="true"] {
  background: var(--pf-primary-50); color: var(--pf-primary-ink); font-weight: var(--pf-w-semibold);
}
.pf-datepicker[data-open="1"] > .pf-field { border-color: var(--pf-primary-500); box-shadow: 0 0 0 2px var(--pf-primary-100); }
.pf-cal-head { display: flex; align-items: center; gap: var(--pf-space-3); margin-bottom: var(--pf-space-4); }
.pf-cal-title { flex: 1; text-align: center; font-size: var(--pf-size-card);
  line-height: var(--pf-lh-card); font-weight: var(--pf-w-semibold); }
.pf-cal-nav {
  width: var(--pf-control-sm); height: var(--pf-control-sm); flex: none;
  display: grid; place-items: center; border-radius: var(--pf-radius-sm);
  color: var(--pf-text-secondary);
}
.pf-cal-nav:hover { background: var(--pf-gray-50); color: var(--pf-text-primary); }
/* ═══ หัวปฏิทินที่กดเลือกเดือน/ปีได้ (2026-08-29) ═══════════════════════════
   ★★★★ ลูกศรเดือนละก้าวใช้กับวันเกิดไม่ได้ — คนเกิดปี 2510 ต้องกดถอยหลังหลายร้อยครั้ง
     และค่าที่ค้างมาเป็น ค.ศ. ยิ่งหนัก (2003 → 2546 คือ 543 เดือน)
   ⚠ ต้องดูออกว่ากดได้ — ไม่งั้นไม่มีใครรู้ว่าหัวปฏิทินเป็นปุ่ม (P266 กลับด้าน:
     ของที่กดได้ต้องดูเหมือนกดได้) */
.pf-cal-title { display: flex; align-items: center; justify-content: center; gap: var(--pf-space-2); }
.pf-cal-jump {
  border: 0; background: transparent; cursor: pointer;
  padding: var(--pf-space-1) var(--pf-space-2); border-radius: var(--pf-radius-sm);
  font: inherit; color: inherit;
}
.pf-cal-jump:hover { background: var(--pf-gray-50); }

/* ★ 3 คอลัมน์ → 12 ช่องพอดี 4 แถวทั้งโหมดเดือนและโหมดปี
   ⚠ ความสูงใกล้เคียงตารางวัน กล่องจึงไม่กระโดดตอนสลับโหมด */
.pf-cal-pick { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--pf-space-2); }
.pf-cal-pick-item {
  border: 1px solid transparent; background: transparent; cursor: pointer;
  padding: var(--pf-space-3) var(--pf-space-2); border-radius: var(--pf-radius-sm);
  font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact);
  color: var(--pf-text-primary); font-variant-numeric: tabular-nums;
}
.pf-cal-pick-item:hover:not(:disabled) { background: var(--pf-gray-50); }
.pf-cal-pick-item[data-on="1"] { background: var(--pf-primary-500); color: var(--pf-on-primary); }
/* ⚠ ช่องที่เลือกไม่ได้ต้องจางและกดไม่ติด — และมี title บอกเหตุผลจากฝั่งเทมเพลต */
.pf-cal-pick-item:disabled { opacity: .4; cursor: not-allowed; }

.pf-cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.pf-cal-dow {
  height: 28px; display: grid; place-items: center;
  font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption);
  font-weight: var(--pf-w-semibold); color: var(--pf-text-muted);
}
/* ★ วันอาทิตย์เน้นสีต่าง เพราะปฏิทินไทยอ่านกันแบบนั้น */
.pf-cal-dow:first-child { color: var(--pf-danger-600); }
.pf-day {
  height: 34px; display: grid; place-items: center; border-radius: var(--pf-radius-sm);
  font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact);
  color: var(--pf-text-primary); font-variant-numeric: tabular-nums; cursor: pointer;
  transition: background var(--pf-duration-fast) var(--pf-ease-out);
}
.pf-day:hover { background: var(--pf-gray-50); }
.pf-day[data-out="1"] { color: var(--pf-text-placeholder); }
.pf-day[data-today="1"] { box-shadow: inset 0 0 0 1.5px var(--pf-primary-300); font-weight: var(--pf-w-semibold); }
.pf-day[data-in="1"] { background: var(--pf-primary-50); border-radius: 0; }
.pf-day[data-edge="start"] { background: var(--pf-primary-500); color: var(--pf-on-primary);
  font-weight: var(--pf-w-semibold); border-radius: var(--pf-radius-sm) 0 0 var(--pf-radius-sm); }
.pf-day[data-edge="end"] { background: var(--pf-primary-500); color: var(--pf-on-primary);
  font-weight: var(--pf-w-semibold); border-radius: 0 var(--pf-radius-sm) var(--pf-radius-sm) 0; }
.pf-day[data-edge="only"] { background: var(--pf-primary-500); color: var(--pf-on-primary);
  font-weight: var(--pf-w-semibold); border-radius: var(--pf-radius-sm); }
/* ★★ วันที่เลือกไม่ได้ต้องมีสัญญาณที่ **ไม่ใช่ความจาง** ด้วย (DESIGN.md §10 · ยกมารอบ 283)
     ความจางอย่างเดียวอ่านได้ว่า "วันนอกเดือน" ซึ่งอยู่ในปฏิทินเดียวกันและจางเหมือนกัน
     ขีดฆ่าบอกชัดว่า "มีวันนี้อยู่ แต่เลือกไม่ได้" — ของเดิมในแอปมีมาตลอด ระบบดีไซน์ไม่มี
   ⚠ `pointer-events: none` ใช้กับปุ่มที่ **ไม่ได้** ใส่แอตทริบิวต์ `disabled` เท่านั้น
     (หน้าดีไซน์เป็นแบบนั้น) · ปุ่มที่ `disabled` จริงเบราว์เซอร์กันคลิกให้อยู่แล้ว
     และการทับ `pointer-events` ลงไปอีกจะกินคำอธิบายใน `title` ที่บอกว่า *ทำไม* ถึงกดไม่ได้
     ซึ่งเป็นข้อที่ CLAUDE.md บังคับว่าปุ่มกดไม่ได้ต้องมีเหตุผลกำกับเสมอ */
.pf-day[data-disabled="1"] {
  opacity: .48;
  text-decoration: line-through; text-decoration-thickness: 1px;
}
.pf-day[data-disabled="1"]:not(:disabled) { pointer-events: none; }
.pf-cal-foot {
  display: flex; align-items: center; gap: var(--pf-space-2); flex-wrap: wrap;
  margin-top: var(--pf-space-4); padding-top: var(--pf-space-4);
  border-top: 1px solid var(--pf-border);
}
.pf-cal-quick {
  height: 28px; padding: 0 var(--pf-space-3); border-radius: var(--pf-radius-sm);
  font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption);
  font-weight: var(--pf-w-medium); color: var(--pf-text-secondary);
}
.pf-cal-quick:hover { background: var(--pf-gray-50); color: var(--pf-text-primary); }
.pf-cal-hint { font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption); color: var(--pf-text-muted); }
/* ★ ข้อความสรุปในแถบล่างต้องกินที่ว่างทั้งหมด เพื่อดันปุ่มไปชิดขวา
   ⚠ `min-width: 0` ขาดไม่ได้ — ค่าปริยาย `min-width: auto` ของ flex item ทำให้
     ข้อความยาวดันกล่องกว้างขึ้นแทนที่จะตัดบรรทัด แล้วปฏิทินจะกว้างเกิน 292px */
.pf-cal-foot .pf-cal-hint { flex: 1; min-width: 0; font-variant-numeric: tabular-nums; }
/* ปุ่มย้อนเดือนใช้ไอคอนลูกศรตัวเดียวกับปุ่มถัดไปแต่กลับด้าน (ชุดไอคอนไม่มีลูกศรซ้าย)
   ⚠ ย้ายมาจากบล็อก `styles:` ของคอมโพเนนต์ ซึ่งเป็นแหล่ง CSS ที่สามที่ D42 กำลังไล่ปิด */
.pf-cal-head .pf-cal-nav:first-of-type svg { transform: rotate(180deg); }
/* ═══ ≤560px · ปฏิทินยึดขอบล่างเต็มความกว้าง ══════════════════════════════
   ยกมาจาก `_components.scss` `.dp__pop--float` ตอนขั้น 5.3-จ (D42 · รอบ 283)
   ★ กล่องลอย 292px บนจอ 375px กดยากมาก และตำแหน่งของมันขึ้นกับว่าปุ่มอยู่ตรงไหน
     ยึดขอบล่างแทน = นิ้วโป้งเอื้อมถึงเสมอ และตำแหน่งเดาได้ทุกครั้ง (เหตุผลเดียวกับแผ่นเลื่อนขึ้น)
   ⚠ ต้องเขียน `inset` ทับ `top/left` ที่ JS ตั้งไว้ — `place()` คำนวณตำแหน่งลอยให้เสมอ
     ไม่ว่าจอจะกว้างเท่าไหร่ · CSS จึงต้องชนะในช่วงจอแคบ */
@media (max-width: 560px) {
  .pf-cal {
    inset: auto 0 0 0; width: auto;
    border-radius: var(--pf-radius-lg) var(--pf-radius-lg) 0 0;
  }
}

/* ── Table เพิ่มเติม ── */
.pf-table tbody tr[aria-selected="true"] { background: var(--pf-primary-50); }
.pf-truncate { display: block; max-width: 22ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pf-table td.pf-wrap { white-space: normal; }
/* ★★★ `aria-sort` ต้องอยู่บน `<th>` ไม่ใช่บน `<button>` ข้างใน (แก้รอบ 281 · P208)
     สเปก ARIA อนุญาต `aria-sort` เฉพาะ role `columnheader`/`rowheader` เท่านั้น
     ใส่บนปุ่มแล้ว **โปรแกรมอ่านหน้าจอไม่ประกาศทิศการเรียงเลย** — เงียบสนิท ไม่มี error
   ⚠ ห้ามเขียนกลับเป็น `.pf-sortable[aria-sort=…]` เพราะมันจะ "ดูเหมือนทำงาน"
     (ลูกศรพลิกถูก) ทั้งที่คนที่พึ่งเสียงอ่านไม่ได้ยินอะไรเลย — มีเครื่องตรวจค้ำแล้ว */
th[aria-sort="ascending"] .pf-sortable svg { transform: rotate(180deg); opacity: 1; }
th[aria-sort="descending"] .pf-sortable svg { opacity: 1; }
.pf-cell-sub { display: block; font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption); color: var(--pf-text-muted); }
.pf-neg { color: var(--pf-danger-700); }

/* ── Charts เพิ่มเติม ── */
.pf-bars { display: flex; align-items: flex-end; gap: var(--pf-space-3); height: 132px; }
.pf-bars .b { flex: 1; border-radius: var(--pf-radius-xs) var(--pf-radius-xs) 0 0; background: var(--pf-primary-200); }
.pf-bars .b.on { background: var(--pf-primary-500); }
.pf-bar-labels { display: flex; gap: var(--pf-space-3); margin-top: var(--pf-space-2); }
.pf-bar-labels > span { flex: 1; text-align: center; font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption); color: var(--pf-text-muted); }
.pf-spark { width: 100%; height: 40px; }
.pf-donut-wrap { display: flex; align-items: center; gap: var(--pf-space-6); flex-wrap: wrap; }
.pf-donut-legend { display: grid; gap: var(--pf-space-3); min-width: 0; }
.pf-donut-legend .r {
  display: flex; align-items: center; gap: var(--pf-space-3);
  font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact);
}
.pf-donut-legend .r i { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.pf-donut-legend .r b { margin-left: auto; font-variant-numeric: tabular-nums; }

/* ── Navigation เพิ่มเติม ── */
.pf-breadcrumb { display: flex; align-items: center; gap: var(--pf-space-3); flex-wrap: wrap;
  font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact); color: var(--pf-text-muted); }
.pf-breadcrumb a { color: var(--pf-text-secondary); }
.pf-breadcrumb a:hover { color: var(--pf-primary-ink); }
/* ★★★ แถบแท็บ — **ต้องเลื่อนในตัวเอง ไม่ใช่ดันทั้งหน้า** (D46 ④ · 2026-08-31)
   ⚠⚠ วัดบนมือถือ 375: หน้าคำขอมี 7 แท็บ กว้างรวม 365 ในที่ว่าง 343
     → ทั้งหน้าเลื่อนซ้ายขวาได้ 17px · แท็บสุดท้าย ("ค่าล่วงเวลา") กดไม่ถึง
     และทุกหน้าที่ใช้แถบนี้เจอเหมือนกัน ไม่ใช่เฉพาะหน้าคำขอ
   ★ `flex-shrink: 0` ที่ปุ่ม — ไม่งั้นชื่อแท็บจะถูกบีบจนตัดคำแทนที่จะเลื่อน */
.pf-tabs-line {
  display: flex; gap: var(--pf-space-6); border-bottom: 1px solid var(--pf-border);
  overflow-x: auto; scrollbar-width: none;
}
.pf-tabs-line::-webkit-scrollbar { display: none; }
.pf-tabs-line > button { flex-shrink: 0; }
.pf-tabs-line button {
  padding: var(--pf-space-4) 0; font-size: var(--pf-size-body); line-height: var(--pf-lh-body);
  font-weight: var(--pf-w-medium); color: var(--pf-text-muted); border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  transition: color var(--pf-duration-standard) var(--pf-ease-out),
              border-color var(--pf-duration-standard) var(--pf-ease-out);
}
.pf-tabs-line button[aria-selected="true"] { color: var(--pf-primary-ink); border-bottom-color: var(--pf-primary-500); font-weight: var(--pf-w-semibold); }
.pf-nav-collapsed { width: var(--pf-sidebar-collapsed); }

/* ── สาธิตสถานะที่ปกติเกิดตอนชี้/กด เท่านั้น ── */
.pf-state-demo { pointer-events: none; }
.pf-state-demo.hover.pf-btn--secondary { border-color: var(--pf-gray-400); box-shadow: var(--pf-shadow-1); }
.pf-state-demo.hover.pf-btn--primary { background: var(--pf-primary-600); }
.pf-state-demo.focus { outline: 2px solid var(--pf-primary-500); outline-offset: 2px; }
.pf-state-demo.field-focus { border-color: var(--pf-primary-500); box-shadow: 0 0 0 2px var(--pf-primary-100); }
.pf-state-demo.nav-hover { background: var(--pf-gray-50); color: var(--pf-text-primary); }
.pf-row-demo { background: var(--pf-surface-hover); }

.pf-gallery { display: grid; gap: var(--pf-space-5); }
.pf-gallery-row { display: flex; gap: var(--pf-space-4); flex-wrap: wrap; align-items: center; }
.pf-spec-note { font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact); color: var(--pf-text-muted); }

/* ═══ RESPONSIVE.md ═══ */
/* ★★★★ ต่ำกว่าเดสก์ท็อป **ไม่นับคอลัมน์ ให้วัดจากที่ว่างจริง** (แก้ 2026-09-02)
   🔴 เดิมไล่เป็นขั้น 4 (≤1439) → 3 (≤1023) → 2 (≤767) ซึ่งพังตรงกลางสองช่วง
     วัดจริง: ที่ 1024 แถบข้างยังกางเต็ม 248px ⇒ เหลือ 736 ⇒ สี่คอลัมน์ได้ใบละ **166px**
       ใส่ "฿431,409" คู่กับกล่องไอคอน 40px ไม่ลง — เนื้อหาล้นออกนอกการ์ด 17px
     และที่ 768 (แถบข้างยุบเป็นราง 72) เหลือ 648 ⇒ สามคอลัมน์ได้ 205px · ล้นอีก 40px
   ★★★ ขั้นบันไดตายตัวจะผิดเสมอ เพราะความกว้างที่เหลือ **ไม่ได้ขึ้นกับจออย่างเดียว**
     มันขึ้นกับว่าแถบข้างกางหรือยุบด้วย ซึ่งสลับที่ 1023 ไม่ตรงกับที่ KPI ต้องการ
   ⇒ `auto-fit` + ขั้นต่ำ 220px ตัดสินเองจากที่ว่างจริง — ได้ 4/4/3/2 ตามลำดับข้างบน
   ⚠ 220px คือความกว้างที่วัดแล้วว่าใส่ตัวเลขเงินยาวสุดคู่กับไอคอนได้พอดี ไม่ใช่เลขสวย ๆ
   ⚠⚠ ห้ามเพิ่มกฎ `repeat(N, …)` ของ `.pf-kpi-grid` **หลัง** บล็อกนี้อีก
     ความจำเพาะเท่ากัน ลำดับในไฟล์เป็นตัวตัดสิน แล้วจะกลับไปพังแบบเดิม (P320) */
@media (max-width: 1439px) {
  .pf-kpi-grid:not(.pf-kpi-grid--auto):not(.pf-kpi-grid--compact):not(.pf-kpi-grid--wide):not(.pf-kpi-grid--fill) {
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  }
}
@media (max-width: 1023px) {
  :root { --pf-sidebar-width: var(--pf-sidebar-collapsed); }
  .pf-page { padding: var(--pf-page-padding-tablet); }
  .pf-logo { justify-content: center; padding: 0; }
  .pf-logo-text, .pf-nav-item span:not(.pf-nav-count), .pf-nav-label { display: none; }
  .pf-nav-item { justify-content: center; padding: 0; }
  .pf-nav-count { display: none; }

  /* ═══ ท้ายแถบข้างตอนยุบเป็นราง 72px (แก้ 2026-09-02) ═══════════════════════
     🔴 เดิมซ่อน `.pf-sidebar-foot` ทั้งกล่อง ⇒ **แท็บเล็ต 768–1023 ไม่มีปุ่มออกจากระบบ
       และไม่มีปุ่มสลับธีมเลย** · มือถือไม่เจอเพราะย้ายไปอยู่แถบบน/แถบล่างแล้ว
       ⇒ ช่วงความกว้างนี้จึงเป็นช่วงเดียวที่ผู้ใช้ติดอยู่ในระบบโดยออกไม่ได้
     ★ ราง 72px ใส่ตัวหนังสือไม่ได้จริง — ซ่อน **เฉพาะตัวหนังสือ** เหลือไอคอนที่กดได้
       ⚠ หลอดที่นั่งยังอยู่ (กว้างพอ) พร้อม `title` บอกตัวเลข — สัญญาณไม่หายไปทั้งอัน */
  .pf-sidebar-foot { padding: var(--pf-space-3) var(--pf-space-2); }
  .pf-sidefoot { padding: var(--pf-space-2); gap: var(--pf-space-2); }
  .pf-sidefoot .pf-card-title, .pf-sidefoot .pf-caption,
  .pf-sidefoot .pf-truncate, .pf-sidefoot-sep { display: none; }
  /* ★ ปุ่มกลายเป็นจัตุรัสกึ่งกลาง — เป้าสัมผัสยังเต็มความกว้างราง */
  .pf-sidefoot > .pf-btn { justify-content: center; }
  /* ⚠⚠⚠ ซ่อนที่ `<svg>` ข้างใน **ไม่ใช่ที่ `<pf-icon>`**
     🔴 `icon.ts` ตั้ง `host: { style: 'display:contents' }` ซึ่งเป็น **inline style**
       ⇒ ไม่มี selector ไหนเอาชนะได้เลย (นอกจาก !important) · เขียน `display: none`
         ใส่ตัว host กี่ครั้งก็ไม่มีผล และ **ไม่มีอะไรฟ้อง** เพราะกฎถูกต้องตามไวยากรณ์
     ★ ตัว host เป็น `display: contents` อยู่แล้ว ⇒ กล่องที่วางจริงคือ `<svg>`
       ซ่อนตัวนั้นได้ผลเท่ากันโดยไม่ต้องใช้ !important
     ⚠ เคยเขียนพลาดสองรอบตรงนี้ (`.pf-ico:last-child` แล้ว `pf-icon:last-child`)
       ⇒ ไอคอนกุญแจโผล่ข้างอวตารในราง 40px มาตลอดจนเจ้าของทัก */
  .pf-sidefoot > .pf-btn > pf-icon:last-child > svg { display: none; }

  /* ★★★★ ลำดับต้องเหมือนกันทุกบทบาท (เจ้าของทัก 2026-09-02)
     🔴 เดิม owner ได้ [ธีม · หลอด · โปรไฟล์] ส่วน staff ได้ [โปรไฟล์ · ธีม]
       เพราะ `--slim` ตั้ง order ไว้ แต่กรณีปกติเรียงตาม DOM ⇒ ปุ่มเดียวกันอยู่คนละที่
       ⇒ คนที่สลับบทบาทดู (หรือดูสองเครื่อง) จะหาปุ่มไม่เจอ
     ★ ตรึงเป็น [หลอด] → [โปรไฟล์] → [ธีม] เหมือนกันหมด
     ⚠ ความจำเพาะเท่ากับกฎของ `--slim` (สองคลาส) · บล็อกนี้อยู่หลังกว่าในไฟล์จึงชนะ */
  .pf-sidefoot > .pf-progress { order: 1; }
  .pf-sidefoot > .pf-btn { order: 2; }
  .pf-sidefoot > .pf-sidefoot-head { order: 3; }
  /* ★ ไม่มีชื่อแพ็กให้ชิดซ้ายแล้ว — ปุ่มธีมมากึ่งกลางรางแทนที่จะค้างอยู่ขวา */
  .pf-sidefoot-head { justify-content: center; }
  .pf-sidefoot-head > .pf-btn { margin-inline-start: 0; }
  /* ⚠ รางกว้าง ~40px ใส่สองคอลัมน์ไม่ได้ — กลับเป็นแถวเรียงลงเหมือนกรณีปกติ */
  .pf-sidefoot--slim { grid-template-columns: minmax(0, 1fr); }
  /* ⚠⚠ `min-width: 0` ทั้งสาย **บังคับ** — ราง 72px แคบกว่า min-content ของปุ่ม
     (อวตาร 26 + padding ซ้ายขวา 32 = 58px) ⇒ ปุ่มไม่ยอมหด แล้ว **แถบข้างขึ้นแถบเลื่อน
     แนวนอน** และเนื้อหาล้นออกไป 23px · วัดเจอ 2026-09-02 (ตระกูล P230)
     ★ ตัด padding ของปุ่มทิ้งด้วย — ในรางไม่มีตัวหนังสือให้เว้นระยะอยู่แล้ว */
  .pf-sidefoot, .pf-sidefoot > *, .pf-sidefoot .pf-progress, .pf-sidefoot .track { min-width: 0; }
  .pf-sidefoot > .pf-btn { padding: 0; }
}
/* ★★★ แท็บเล็ตใช้นิ้ว — รางบังคับความกว้างไว้ที่ ~35px แล้ว แต่ **ความสูงยังขยายได้**
   ⚠ ปุ่มออกจากระบบที่กดพลาดบ่อยคือปุ่มที่คนกดค้างไว้จนกดโดนเมนูอื่นแทน
   ⚠ กฎ `pointer: coarse` ที่มีอยู่คุมเฉพาะ `.pf-btn--icon` · สองปุ่มนี้ไม่ใช่คลาสนั้น */
@media (max-width: 1023px) and (pointer: coarse) {
  .pf-sidefoot > .pf-btn { min-block-size: var(--pf-control-lg); }
}
@media (max-width: 767px) {
  .pf-shell { grid-template-columns: minmax(0, 1fr); }
  .pf-sidebar { display: none; }
  .pf-page { padding: var(--pf-page-padding-mobile); gap: var(--pf-space-6); }
  /* ★★★★ มือถือ — **วัดจากที่ว่างจริง ไม่ใช่ตรึงสองคอลัมน์** (เจ้าของทัก 2026-09-02)
     🔴 เดิม `repeat(2, …)` ⇒ ที่จอ 368px การ์ดเหลือใบละ **162px**
       ทั้งที่วัดแล้วการ์ด KPI ต้องการ ~261px ⇒ ป้าย "พนักงานที่ทำงานอยู่" ตัดสองบรรทัด
       และบรรทัดล่างตัดยับ ("ค่าหักรวม ฿400 · คีย์แทน / 0%")
     ★ เจ้าของชี้เองว่า *"บางหน้าก็เป็น 1 grid ถูกต้องแล้ว"* — หน้านั้นใช้ `--compact`
       ซึ่งเป็น auto-fit อยู่แล้ว ⇒ ยุบเหลือคอลัมน์เดียวเองเมื่อที่ไม่พอ
     ⇒ ทำให้กริดฐานเป็น auto-fit เหมือนกัน · 180px = ค่าเดียวกับ `--compact` ที่พิสูจน์แล้วว่าใช้ได้
       368 → 1 คอลัมน์ · 414 → 2 · 767 → 3 (ใบละ 237px ซึ่งกว้างกว่าขั้นต่ำ 220 ของช่วงเดสก์ท็อป)
     ⚠ นี่คือกฎ **ตรึงจำนวนคอลัมน์ตัวสุดท้าย** ของตระกูลนี้ที่เหลืออยู่ — ตอนนี้ไม่เหลือแล้ว */
  .pf-kpi-grid:not(.pf-kpi-grid--auto):not(.pf-kpi-grid--compact):not(.pf-kpi-grid--wide):not(.pf-kpi-grid--fill) {
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  }

  /* ═══ แถบปุ่มบนสุด + แถบตัวกรอง บนมือถือ (เจ้าของทัก 2026-09-02) ═══════════
     🔴 คำทัก: *"พอเป็นมือถือ มันก็ชัดเจนนะครับแต่มันดูรกแปลกๆ"*
       วัดที่ 430px (เนื้อที่ 398): ตัวกรองสี่ตัวกว้าง **321 · 236 · 190 · 275**
       ⇒ สี่แถวซ้อนกันโดยขอบขวาหยักคนละที่ ไม่มีจังหวะสักเส้น — นั่นคือ "รก" ที่ตาเห็น
         (ทุกตัวชิดซ้ายตรงกัน แต่ไม่มีตัวไหนถึงขอบขวา จึงดูเหมือนวางค้างไว้)
     ★★★ แก้ด้วยการให้ **เต็มความกว้างทุกตัว** ⇒ เป็นคอลัมน์เดียวที่ขอบตรงกันทั้งสองข้าง
     ⚠⚠ ความกว้าง (236px · 190px · 214px) มาจาก **inline style** ที่ input ของคอมโพเนนต์เขียนให้
       CSS ธรรมดาชนะไม่ได้ · แต่ `flex-basis` ของ flex item **ชนะ `width`** ตามสเปก
       ⇒ ใช้ `flex: 1 1 100%` แทน `width: … !important` (บทเรียนเดียวกับ P342) */
  .pf-filterbar > * { flex: 1 1 100%; min-inline-size: 0; }
  /* ⚠ ความกว้างของช่องเลือกอยู่ที่ `div` ข้างใน ไม่ใช่ที่ host — ต้องทำให้มันเป็น flex item ด้วย */
  .pf-filterbar pf-select { display: flex; }
  .pf-filterbar pf-select > .pf-select { flex: 1 1 100%; min-inline-size: 0; }
  /* ★ ปุ่มช่วงเวลาโตเติมแถว — แต่ **ห้ามบีบจนคำตัดบรรทัด**
     🔴 เขียน `flex: 1` (= 1 1 0) รอบแรก ⇒ ทุกปุ่มกว้างเท่ากันโดยไม่สนความยาวคำ
       ที่จอ 380px ได้ปุ่มละ ~65px แต่ "เดือนที่แล้ว" ต้องการ ~78px
       ⇒ ตัดเป็น "เดือนที่ / แล้ว" ปุ่มนั้นสูงกว่าเพื่อน แถบทั้งแถบดูเบี้ยว (เจ้าของทัก 2026-09-02)
     ★★ `1 0 auto` = เริ่มจากความกว้างของคำตัวเอง แล้วแบ่งที่ว่างที่เหลือเพิ่มให้เท่า ๆ กัน
       ⇒ ปุ่มยาวได้ที่มากกว่าปุ่มสั้นตามธรรมชาติ · รวมกันแล้วยังเต็มแถวพอดี
       (บทเรียนเดียวกับ `.pf-actions` — ดู P351 · ผมแก้ที่นั่นแล้วลืมแก้ที่นี่)
     ⚠ `white-space: nowrap` เป็นตาข่ายชั้นสอง — จอที่แคบกว่านี้ให้ล้นดีกว่าตัดคำ */
  .pf-filterbar .pf-segment { display: flex; }
  .pf-filterbar .pf-segment button {
    flex: 1 0 auto; white-space: nowrap; padding: 0 var(--pf-space-2);
  }
  /* ★★ "เทียบกับ" ขึ้นบรรทัดบน — ป้ายที่อยู่ซ้ายของช่องทำให้แถวนี้เริ่มไม่ตรงกับแถวอื่น
     ⚠ ยังต้องมีเส้นคั่นอยู่ (มันบอกว่า "คนละกลุ่มกับตัวกรองข้างบน") แต่ย้ายไปอยู่ด้านบน
       เส้นซ้ายไม่มีความหมายแล้วเมื่อทุกอย่างเรียงลงเป็นคอลัมน์เดียว */
  .pf-compare-group {
    flex-direction: column; align-items: stretch; gap: var(--pf-space-2);
    padding: var(--pf-space-3) 0 0; border-left: 0; border-top: 1px solid var(--pf-border);
  }
  /* ★ ปุ่มบนสุดเต็มแถว — ปุ่มไอคอนคงขนาดจัตุรัสเดิม
     ⚠⚠ `flex: 1 0 auto` **ไม่ใช่ `flex: 1`** — ต่างกันที่ `flex-shrink`
       🔴 `flex: 1` (= 1 1 0) บีบปุ่มลงได้ไม่จำกัด · หน้าเงินเดือนมีปุ่มในแถวนี้ **ห้าตัว**
         ⇒ ปุ่มถูกบีบเหลือ 43px แต่ `.pf-btn` ตั้ง `white-space: nowrap` ไว้
         ⇒ ตัวหนังสือทะลุออกนอกปุ่ม แล้วดันทั้งหน้าเลื่อนแนวนอน 13px (วัดเจอ 2026-09-02)
       ★ `1 0 auto` = โตได้เพื่อเติมแถวให้เต็ม แต่ **ไม่ยอมเล็กกว่าตัวหนังสือของตัวเอง**
         แถวไหนใส่ไม่พอก็ตกบรรทัดใหม่ (แถบนี้ `flex-wrap: wrap` อยู่แล้ว) */
  .pf-page-header .sp { display: none; }
  .pf-actions { width: 100%; }
  .pf-actions > .pf-btn:not(.pf-btn--icon) { flex: 1 0 auto; }
  .pf-kpi-grid { gap: var(--pf-grid-gap-mobile); }
  .pf-two-col { grid-template-columns: minmax(0, 1fr) !important; }
}

/* ★★★ `align-items: start` — คอลัมน์สูงไม่เท่ากันได้ (2026-09-02)
   🔴 เจ้าของทัก: "ตอนไม่มีข้อมูล ไอคอนไม่อยู่ตรงกลางการ์ดทำให้ดูแปลก"
     วัดจริง: คอลัมน์ขวามีสองการ์ดซ้อน ⇒ กริดยืดการ์ดซ้ายให้สูงตาม (502px)
     แต่สภาพว่างสูงแค่ 153px ⇒ **เหลือที่ว่างโล่ง ๆ ข้างล่าง 295px**
   ★ ไม่ยืด = การ์ดสูงเท่าเนื้อหาจริง · ปัญหาหายไปที่ต้นเหตุ ไม่ต้องไปจัดกลางทีหลัง
   ⚠ ทางที่ไม่เลือก: จัดกลางแนวตั้งใน `.pf-empty` — ต้องให้ทั้งสายพ่อมีความสูงแน่นอน
     (`.pf-card` เป็น block · `.pf-card-body` ไม่ยืด) ⇒ ต้องแก้ `.pf-card` ทั้งระบบ
     เพื่อแก้อาการที่เกิดเฉพาะตอนว่าง ซึ่งแลกไม่คุ้ม */
.pf-two-col { display: grid; gap: var(--pf-grid-gap); grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); align-items: start; }
@media (max-width: 1200px) { .pf-two-col { grid-template-columns: minmax(0, 1fr); } }

.pf-spec-grid { display: grid; gap: var(--pf-grid-gap); grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.pf-swatch { border: 1px solid var(--pf-border); border-radius: var(--pf-radius-md); overflow: hidden; }
.pf-swatch .c { height: 44px; }
.pf-swatch .n { padding: var(--pf-space-3) var(--pf-space-4); font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption); color: var(--pf-text-muted); background: var(--pf-surface); }

/* ═══════════════════════════════════════════════════════════════════════════
   หน้าเข้าสู่ระบบ / สมัครใช้งาน / เลือกทาง  (ยกมาจากแอป · รอบ 270 · D42 ขั้น 3.1)

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

   โครง:  พื้นหน้า → .pf-auth-shell (การ์ดใบเดียว) → ข้างในมี .pf-auth-stage + .pf-auth-card

   ⚠⚠⚠ รุ่นแรกในแอปทำผิดทรง — เวทีเป็นการ์ดลอยบนพื้นเต็มจอ ฟอร์มเป็นอีกการ์ดหนึ่ง
     เจ้าของทักว่า "พื้นเทาติดขอบบนล่าง เลยดูไม่เหมือนการ์ด · ทรงมันไม่ได้เลย"
     สองสาเหตุที่วัดได้:
       ① ไม่มีอะไรผูกสองฝั่งเข้าด้วยกัน — อ่านไม่ออกว่าเป็นของชิ้นเดียว
       ② พื้นหน้ากับการ์ดขาว สีใกล้กันเกินจนแทบไม่เห็นขอบการ์ด
     ★ แก้: การ์ดใบเดียวครอบทั้งคู่ + พื้นหน้าเข้มขึ้นให้การ์ดลอยออกมาจริง ๆ

   ⚠⚠ ค่าเริ่มต้นคือ **ฟอร์มอย่างเดียว** แล้วค่อยเพิ่มเวทีเมื่อจอกว้างพอ
     ไม่ใช่ทำสองฝั่งก่อนแล้วซ่อนทีหลัง — ทางแรกทำให้มือถือได้หน้าที่เบาที่สุดเสมอ
   ═══════════════════════════════════════════════════════════════════════════ */

/* ⚠ `min-height:100dvh` ไม่ใช่ `100vh` — บนมือถือ `vh` รวมแถบที่เลื่อนหายไปด้วย
     ทำให้ปุ่มล่างสุดถูกดันตกขอบจอ (บั๊กที่เกิดจริงกับเลย์เอาต์เดิม)
   ⚠⚠ พื้นหน้าต้องเป็นพื้นรอง ไม่ใช่พื้นแอป — วัดแล้วรอบ 242 ว่าพื้นแอปกับการ์ดขาว
     ต่างกันแค่ ~7 หน่วยความสว่าง ตาเปล่าอ่านไม่ออกว่ามีการ์ดอยู่ */
.pf-auth {
  min-height: 100dvh; display: grid; place-items: center;
  padding: var(--pf-space-7); background: var(--pf-bg-secondary);
}

/* ★★★ เงาการ์ดใช้เงาระดับ 3 — ระดับ 2 ระยะฟุ้งสั้นเกินกว่าจะอ่านว่า "ลอย"
     ขอบเงายังคม ตาจึงอ่านว่าการ์ดติดพื้น */
.pf-auth-shell {
  width: 100%; max-width: 420px;
  background: var(--pf-surface); border: 1px solid var(--pf-border);
  border-radius: var(--pf-radius-lg); box-shadow: var(--pf-shadow-3);
  padding: var(--pf-space-7);
}
.pf-auth-card { width: 100%; }

/* ═══ รางเลื่อนสลับ เข้าสู่ระบบ ⇄ สมัครใช้งาน ═══
   ★★★★ ทั้งสองฟอร์มอยู่ใน **กริดแถวเดียวกัน** → ความสูงแถว = ฟอร์มที่สูงกว่า
     ⇒ การ์ดสูงเท่ากันทั้งสองโหมด **โดยอัตโนมัติ** ไม่ต้องตั้ง min-height
     ⚠⚠ ตั้ง min-height ตายตัวคือเลขที่จะล้าสมัยทันทีที่เพิ่มช่องกรอก และไม่มีอะไรฟ้อง
       วัดก่อนแก้: 457px ⇄ 631px กระโดด 174px
   ★★★ เลขที่ต้องตรงกัน: `width:200%` คู่กับ `translateX(-50%)`
     transform เป็น % คิดจาก **กล่องของตัวเอง** → -50% ของ 200% = -100% ของช่องที่เห็น
     ⚠ เขียน `translateX(-100%)` จะเลื่อนไปไกลเป็นสองเท่า แล้วจอจะว่างเปล่า
   ⚠ `overflow:hidden` จำเป็น — ไม่มีแล้วฝั่งที่ซ่อนอยู่จะดันให้หน้าเลื่อนแนวนอนได้ */
.pf-auth-swap { overflow: hidden; width: 100%; min-width: 0; }
/* ⚠⚠ `align-items:center` ไม่ใช่ `start` — เจ้าของทักรอบ 250 ว่า "ฟอร์มดูไม่อยู่ตรงกลาง"
   ★ ตั้งแต่การ์ดสูงเท่ากันทั้งสองโหมด ฝั่งเข้าสู่ระบบซึ่งสั้นกว่าจะเหลือที่ว่างข้างล่าง
     `start` ทำให้มันเกาะขอบบนแล้วดูเหมือนวางผิด */
.pf-auth-track {
  display: grid; grid-template-columns: repeat(2, 1fr); width: 200%;
  align-items: center;
  transition: transform var(--pf-duration-standard) var(--pf-ease-out);
}
.pf-auth-swap[data-mode="signup"] .pf-auth-track { transform: translateX(-50%); }
/* ⚠ `min-width:0` บนลูกของกริด — ไม่มีแล้วช่องกรอกจะดันคอลัมน์ให้กว้างเกิน */
.pf-auth-pane { min-width: 0; }

/* ★★★★ กริดต้องอยู่ที่ `<form>` ไม่ใช่ที่กล่องครอบ
   ⚠⚠ วัดแล้วรอบ 243: ใส่ `gap` ไว้ที่กล่องครอบซึ่งมีลูกตัวเดียวคือ `<form>`
     → gap ไม่มีอะไรให้เว้น · และ `<form>` เองเป็น `display:block`
     ผลคือช่องกรอกกับปุ่ม **ติดกันสนิท 0px** และไม่มี error อะไรเลย
   ★ กฎที่ได้: `gap` เว้นระหว่าง **ลูกของกล่องที่ประกาศมัน** เท่านั้น
   ★★★ จังหวะสามระดับ — วัดจริงรอบ 246 แล้วปรับ
     ในช่องเดียวกัน 6px · ระหว่างช่อง 20px · ก่อนปุ่ม 32px (20 + margin 12)
     ⚠ 16px ไม่พอ: คำอธิบายใต้ช่องรหัสผ่านห่างจากของที่มันอธิบาย 6px
       เทียบกับ 16px จากช่องถัดไป = ต่างกันแค่ 2.7 เท่า ตาอ่านว่ามันลอยระหว่างสองช่อง */
.pf-auth-form form { display: grid; gap: var(--pf-space-6); }
.pf-auth-form form .pf-btn { margin-block-start: var(--pf-space-4); }

/* จอแคบ: ไม่มีเวทีเลย — และเทมเพลตก็ไม่สร้าง element ให้ด้วย
   (CSS ตัวนี้เป็นชั้นที่สอง เผื่อ matchMedia ตอบช้ากว่าเฟรมแรก) */
.pf-auth-stage { display: none; }

.pf-auth-brand {
  display: flex; align-items: center; gap: var(--pf-space-3);
  justify-content: center; margin-bottom: var(--pf-space-6);
}
.pf-auth-brand b { font-size: var(--pf-size-page); line-height: var(--pf-lh-page); letter-spacing: -.01em; }
/* ★ ตราโลโก้ — ใส่ width/height ที่ <img> ด้วย เพื่อจองพื้นที่ก่อนรูปมาถึง
     ไม่งั้นชื่อแบรนด์จะกระตุกไปทางซ้ายตอนรูปโหลดเสร็จ */
.pf-auth-mark { width: 40px; height: 40px; flex: none; object-fit: contain; }
/* ★ ทางออกใต้ฟอร์ม (สมัคร / กลับไปเข้าสู่ระบบ) — เด่นกว่าแถวแหล่งข้อมูล
   ⚠ แถวนี้เป็นทางที่ผู้ใช้ต้องใช้จริง ให้ระดับความเด่นสลับกันเมื่อไหร่ คนจะกดผิดแถว */
.pf-auth-alt {
  margin-top: var(--pf-space-6); text-align: center;
  font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact);
  color: var(--pf-text-secondary);
}
.pf-auth-sub {
  color: var(--pf-text-secondary); font-size: var(--pf-size-compact);
  line-height: var(--pf-lh-compact); text-align: center;
  margin: calc(var(--pf-space-3) * -1) 0 var(--pf-space-6);
}
.pf-auth-foot {
  margin-top: var(--pf-space-6); text-align: center;
  color: var(--pf-text-muted); font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact);
}
/* ★ บรรทัดบอกว่า "ตอนนี้เป็นใคร" บนหน้าเลือกทาง (D41) */
.pf-auth-note {
  color: var(--pf-text-secondary); font-size: var(--pf-size-compact);
  line-height: var(--pf-lh-compact); text-align: center; margin: 0 0 var(--pf-space-6);
}
/* ★★ ทางเลือกบนหน้าเลือกทาง — ★ ระยะมาจาก gap ของ grid ไม่ใช่ margin ของลูก
   ⚠ margin ของลูกจะยุบรวมกันเอง แล้วระยะจะไม่ใช่ค่าที่เขียนไว้ */
.pf-auth-choices { display: grid; gap: var(--pf-space-5); }
.pf-choice {
  display: grid; gap: var(--pf-space-3); padding: var(--pf-space-5);
  border: 1px solid var(--pf-border); border-radius: var(--pf-radius-xl);
  background: var(--pf-bg-secondary);
}
.pf-choice-title { font-size: var(--pf-size-body); line-height: var(--pf-lh-body); }
.pf-choice-body {
  margin: 0; color: var(--pf-text-secondary);
  font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact);
}

/* ★★★★ ชื่อแบรนด์ **สองสี** ตามล็อกอัปต้นฉบับที่เจ้าของโปรเจคออกแบบเอง
   People กรมท่าเข้ม · Flow น้ำเงินแบรนด์
   ⚠⚠ ก่อนรอบ 248 เราเรนเดอร์สีเดียวล้วน 5 ที่ โดยไม่มีเครื่องตรวจตัวไหนเห็น
   ⚠ ห้ามใส่ช่องว่างระหว่างสองคำ — ต้นฉบับเขียนติดกันเป็นคำเดียว */
.pf-logo-word { color: var(--pf-text-primary); }
.pf-logo-word span { color: var(--pf-logo-flow); }

@media (min-width: 900px) {
  /* ★★ การ์ดกว้างขึ้นและแบ่งสองคอลัมน์ — ระยะขอบในเท่ากันทุกด้าน
     ⚠ ต้องล้าง justify-items ที่สืบมาจาก place-items:center ของ .pf-auth
       ไม่งั้นลูกไม่ยืดเต็มคอลัมน์ แล้วเวทียุบเหลือ 2×2px (วัดแล้วรอบ 241) */
  .pf-auth-shell {
    max-width: 960px; padding: var(--pf-space-6);
    display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
    gap: var(--pf-space-8); align-items: center; justify-items: stretch;
  }

  /* ═══ เวที — องค์ประกอบเรขาคณิตแบน (ตามภาพอ้างอิงที่เจ้าของส่งมารอบ 250) ═══
     ⚠⚠ เดิมเป็นมาสคอตตัดพื้นหลังวางกลางพื้นสีทึบ + วงเบลอสองวง
       เจ้าของบอกเองว่าไม่จำเป็นต้องมีมาสคอต
     ★ ลวดลายมาจาก SVG ในหน้า ไม่ใช่ไฟล์ภาพ → ไม่มีคำขอไฟล์เพิ่มเลย
       เทียบกับรูปมาสคอตเดิม 53,786 ไบต์ (P191)
     ⚠ มุมโค้งน้อยกว่าการ์ดหนึ่งระดับ — เท่ากันเป๊ะจะดูเหมือนสองขอบทับกัน */
  .pf-auth-stage {
    position: relative; isolation: isolate; overflow: hidden;
    display: grid; align-content: end;
    aspect-ratio: 5/6; border-radius: var(--pf-radius-xl); padding: var(--pf-space-7);
    background-color: var(--pf-pastel-brand);
    background-image: radial-gradient(120% 130% at 12% 8%,
      var(--pf-stage-lit) 0%, var(--pf-pastel-brand) 45%, var(--pf-stage-dim) 100%);
  }

  /* ★ ลวดลายเต็มกรอบ อยู่ใต้ข้อความเสมอ */
  .pf-auth-art { position: absolute; inset: 0; z-index: 0; width: 100%; height: 100%; pointer-events: none; }

  /* ★★★ ความทึบต่างกันสามระดับโดยตั้งใจ — ทรงที่ทึบเท่ากันหมดจะอ่านว่าแบน
     ⚠ ห้ามใช้สีแบรนด์ทึบ 100% เป็นก้อนใหญ่ ตัวอักษรข้างบนจะอ่านไม่ออก */
  .pf-auth-art-a { fill: var(--pf-primary-500); opacity: .16; }
  .pf-auth-art-b { fill: var(--pf-warning-500); opacity: .14; }
  .pf-auth-art-c { fill: var(--pf-primary-500); opacity: .28; }
  .pf-auth-art-line { stroke: var(--pf-primary-ink); stroke-width: 1.6; opacity: .34; stroke-linecap: round; }
  .pf-auth-art-dot { fill: var(--pf-warning-500); opacity: .55; }
  .pf-auth-art-dot--sm { fill: var(--pf-primary-500); opacity: .5; }
  .pf-auth-art-ring { stroke: var(--pf-primary-500); stroke-width: 2; opacity: .3; }
  .pf-auth-art-tri { fill: var(--pf-primary-ink); opacity: .4; }

  /* ★★ ข้อความวางบนพื้นเวทีโดยตรง ไม่ใช่ป้ายลอยทับภาพเหมือนเดิม
     ⚠ ป้ายลอยคือที่ที่คอนทราสต์หลุดง่ายที่สุด เพราะพื้นเปลี่ยนตามภาพข้างล่าง */
  .pf-auth-stage-say { position: relative; z-index: 1; display: grid; gap: var(--pf-space-3); max-width: 30ch; }
  .pf-auth-stage-head {
    font-size: var(--pf-size-page); line-height: var(--pf-lh-page); letter-spacing: -.01em;
    color: var(--pf-text-primary); text-wrap: balance;
  }
  .pf-auth-stage-sub { color: var(--pf-text-secondary); font-size: var(--pf-size-body); line-height: var(--pf-lh-body); }

  /* ★ ฟอร์มชิดซ้ายเมื่อเป็นสองฝั่ง — จัดกลางทำให้ตากระโดดไปมาระหว่างสองคอลัมน์ */
  .pf-auth-card { padding-inline: var(--pf-space-5); }
  .pf-auth-brand { justify-content: flex-start; }
  .pf-auth-sub { text-align: start; }
  .pf-auth-foot { text-align: start; }
}

/* ★★ ลวดลายขยับช้ามาก — ให้ภาพนิ่งดูมีลมหายใจ ไม่ใช่ให้มันเรียกร้องความสนใจ
   ⚠⚠ หน้านี้พนักงานเปิดทุกวัน · อนิเมชันที่สังเกตเห็นได้จะกลายเป็นสิ่งรบกวนในสัปดาห์ที่สอง
   ⚠ ขยับด้วย transform เท่านั้น — ขยับ x/y ของ SVG จะคำนวณเลย์เอาต์ใหม่ทุกเฟรม */
@keyframes pf-auth-sway {
  0%, 100% { transform: translate3d(0, 0, 0); }
  50% { transform: translate3d(0, -8px, 0); }
}
@media (min-width: 900px) {
  .pf-auth-art-a { animation: pf-auth-sway 26s ease-in-out infinite; }
  .pf-auth-art-c { animation: pf-auth-sway 34s ease-in-out infinite reverse; }
  .pf-auth-art-ring { animation: pf-auth-sway 20s ease-in-out infinite; }
}
@media (prefers-reduced-motion: reduce) {
  /* ★ หยุดสนิท ไม่ใช่ช้าลง — กฎเดียวกับหน้าแรก (รอบ 235) */
  .pf-auth-art-a, .pf-auth-art-c, .pf-auth-art-ring { animation: none; }
}
@media (max-width: 520px) {
  .pf-auth { padding: var(--pf-space-5); place-items: start center; padding-top: var(--pf-space-8); }
}

/* ═══════════════════════════════════════════════════════════════════════════
   ไกด์ตั้งค่าองค์กรใหม่ + วงกลมบอกสัดส่วน  (ยกมาจากแอป · รอบ 271 · D42 ขั้น 3.2)

   ★★★★ ไกด์นี้ **หายไปเองเมื่อทำครบ** — ไม่มีปุ่มปิด
     ⚠ ปุ่ม "ไม่ต้องแสดงอีก" = ซ่อนงานที่ยังไม่ได้ทำ แล้วเงินเดือนงวดแรกจะผิดเงียบ ๆ
   ★★ ไม่บล็อกอะไรทั้งสิ้น — ทุกหน้ายังใช้ได้ตามปกติระหว่างที่ยังทำไม่ครบ

   🔴 เดิมไกด์อยู่ **ในหน้าภาพรวม** · วันที่หน้านั้นโหลดไม่สำเร็จ (P195)
     ไกด์หายไปพร้อมกัน ทั้งที่มันคือสิ่งเดียวที่บอกได้ว่าต้องทำอะไรต่อ
     ★ ของที่บอก "ต้องทำอะไรต่อ" ห้ามผูกชะตากับหน้าใดหน้าหนึ่ง → ย้ายขึ้นแถบบน
   ═══════════════════════════════════════════════════════════════════════════ */

.pf-setup-btn { flex: none; gap: var(--pf-space-3); }
/* ⚠ จอแคบไม่มีที่พอสำหรับข้อความ — เหลือแต่วงกลม
   ★ ไม่ซ่อนทั้งปุ่ม เพราะคนที่เข้าครั้งแรกจากมือถือก็ต้องเห็นว่ายังตั้งค่าไม่ครบ
   ⚠⚠ `font-size:0` ซ่อนข้อความแต่ **ไม่ซ่อนจากเครื่องอ่านหน้าจอ** ซึ่งถูกแล้ว
     และวงกลมมี aria-label ของตัวเองอยู่แล้ว */
@media (max-width: 640px) {
  .pf-setup-btn { font-size: 0; padding-inline: var(--pf-space-3); gap: 0; }
}

/* ═══ วงกลมบอกสัดส่วน ═══
   ★★★ เริ่มที่ 12 นาฬิกา หมุนตามเข็ม
     ⚠ ไม่หมุนแกนจะเริ่มที่ 3 นาฬิกา ซึ่งอ่านว่า "ยังไม่เริ่ม" ทั้งที่ทำไปแล้วบางส่วน
   ⚠ `overflow:visible` จำเป็น — เส้นหนา 2.5 บน viewBox 20 จะโดนตัดขอบถ้าไม่ใส่ */
.pf-ring { inline-size: 20px; block-size: 20px; flex: none; overflow: visible; transform: rotate(-90deg); }
.pf-ring circle { fill: none; stroke-width: 2.5; }
/* ★ รางต้องเห็นได้ แต่ไม่แย่งความสนใจ — ใช้เส้นคั่น ไม่ใช่สีหมึก */
.pf-ring-track { stroke: var(--pf-border); }
/* ★★ ส่วนที่เสร็จแล้วเป็นสีแบรนด์ ตามที่เจ้าของโปรเจคสั่ง
   ⚠ ไม่ใช้สีเขียว — เขียวในระบบนี้แปลว่า "สถานะดี" ของข้อมูล ไม่ใช่ "ทำแล้ว"
     ปนกันเมื่อไหร่คนจะอ่านป้ายสถานะผิดทั้งระบบ (กติกาเดียวกับ .pf-setup-mark) */
.pf-ring-fill {
  stroke: var(--pf-primary-500); stroke-linecap: round;
  transition: stroke-dashoffset var(--pf-duration-standard) var(--pf-ease-out);
}
/* ★ เต็มวงแล้วเอาปลายมนออก — ปลายมนที่บรรจบกันเองจะเห็นรอยต่อเป็นติ่ง */
.pf-ring[data-full] .pf-ring-fill { stroke-linecap: butt; }

/* ★★★ แผงลอย — **ไม่ใช่ dialog** โดยตั้งใจ
   ⚠⚠ ทำเป็น modal เมื่อไหร่ = บังคับให้ปิดก่อนถึงจะทำตามที่มันบอกได้ ซึ่งย้อนแย้ง
     ผู้ใช้ต้องกด "ไปทำ" แล้วเดินไปหน้านั้นได้ทันที
   ⚠ z-index ต้องต่ำกว่ากล่องยืนยัน — กล่องยืนยันเป็นของที่ต้องตอบก่อนเสมอ */
/* ★★★★ ย้ายจากมุมขวาบนมาที่ **ข้างแถบข้าง** (2026-09-07)
   เดิมแผงลอยอยู่ใต้ *แถบบน* ซึ่งเป็นที่ของปุ่มไกด์ในเปลือกรุ่นเก่า
   เปลือกที่สร้างใหม่จาก /design ไม่มีแถบบนแล้ว — ปุ่มไปอยู่ท้ายแถบข้าง
   ⚠ ปล่อยตำแหน่งเดิมไว้ = แผงโผล่คนละมุมกับปุ่มที่กด ซึ่งอ่านว่าเป็นของคนละชิ้น */
.pf-setup-pop {
  /* ★★★ 420 → 520px (2026-09-07) — วัดของจริงแล้วคอลัมน์กลางเหลือ **155px**
     ⚠⚠ บรรทัด "ทำไมถึงสำคัญ" จึงตัดเป็น 6 บรรทัดต่อข้อ · แถวสูง 188px
       และทั้งแผงยาว 2,130px ซึ่งไม่มีใครเลื่อนอ่านจนจบ
     ★ แผงย้ายมาอยู่ข้างแถบข้างแล้ว มีที่เหลือเฟือ — ไม่ต้องแคบเหมือนตอนลอยมุมขวาบน */
  position: fixed; z-index: 40; inline-size: min(520px, calc(100vw - var(--pf-space-8)));
  inset-block-end: var(--pf-space-6);
  inset-inline-start: calc(var(--pf-sidebar-width) + var(--pf-space-4));
  max-block-size: min(560px, calc(100dvh - var(--pf-space-8)));
  overflow: auto; box-shadow: var(--pf-shadow-3);
}
@media (max-width: 767px) {
  /* ★ ไม่มีแถบข้างแล้ว — ปุ่มย้ายขึ้นหัวมือถือ แผงจึงต้องเกาะขอบซ้ายของจอแทน
     ⚠ ยังชิดล่างเหมือนเดิม เพราะแถบเครื่องมือของเบราว์เซอร์มือถือกินขอบบนไม่แน่นอน */
  .pf-setup-pop { inset-inline-start: var(--pf-space-5); }
}
@media (max-width: 640px) {
  /* ★ บนมือถือกางเต็มความกว้าง — แผงแคบ ๆ ลอยชิดข้างอ่านไม่ออกบนจอเล็ก */
  .pf-setup-pop { inset-inline: var(--pf-space-5); inline-size: auto; }
}
/* ★★ หัวข้อคั่นสองด่านในแผง — ใช้คลาสคำบรรยายที่มีอยู่แล้ว ไม่เพิ่มคลาสใหม่
   ⚠ ด่านที่สองต้องอ่านออกว่า **ไม่ทำก็ได้** ไม่งั้นมันจะกลายเป็นงานค้างในใจคนอ่าน */
.pf-setup-list + .pf-caption { margin-top: var(--pf-space-6); }
/* ★ ปุ่มปิดชิดขวาในหัวการ์ด — `.pf-setup-count` กิน margin-inline-start:auto ไปแล้ว */
.pf-setup-pop .pf-card-head .pf-btn--icon { margin-inline-start: var(--pf-space-3); }
.pf-setup-count {
  margin-inline-start: auto; color: var(--pf-text-secondary);
  font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact);
}

/* ★ แถบความคืบหน้า — บาง ๆ พอให้เห็น ไม่ใช่พระเอกของการ์ด
   ⚠ ตัวเลข doneCount/total อยู่ข้างบนแล้ว แถบนี้เป็นแค่ตัวช่วยกวาดตา */
.pf-setup-bar {
  block-size: 6px; border-radius: var(--pf-radius-pill);
  background: var(--pf-bg-secondary); overflow: hidden; margin-bottom: var(--pf-space-6);
}
.pf-setup-bar > i {
  display: block; block-size: 100%; background: var(--pf-primary-500);
  border-radius: var(--pf-radius-pill);
  transition: inline-size var(--pf-duration-standard) var(--pf-ease-out);
}

.pf-setup-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--pf-space-2); }
/* ★★★★ สามคอลัมน์ **ความกว้างคงที่สองข้าง** (แก้ 2026-08-27 · เจ้าของทักว่าไม่ตรงแถว)
   ⚠⚠ เดิมเป็น `auto … auto` ซึ่งแปลว่าคอลัมน์ขวากว้างตามเนื้อของแต่ละแถว
     แถวที่มีปุ่มจึงกว้างกว่าแถวที่มีแต่ข้อความ → ขอบขวาของทุกแถวไม่ตรงกันสักแถว
   ★ ซ้ายล็อก 26px (เท่าวงกลม) · ขวาล็อก 84px (พอสำหรับปุ่ม "ไปทำ" และป้ายสั้น)
     ทั้งสองข้างจึงเป็นเส้นตรงเสมอ ไม่ว่าเนื้อในแต่ละแถวจะเป็นอะไร
   ⚠ เดิมขวาล็อก 116px ตั้งแต่ตอนที่ปุ่มมีลูกศรอยู่ข้างใน · ตอนนี้เหลือแต่ข้อความ
     ที่กว้างเกินไปคือที่ที่ยึดมาจากคอลัมน์กลาง ซึ่งเป็นคอลัมน์ที่คนอ่านจริง */
.pf-setup-step {
  display: grid; grid-template-columns: 26px minmax(0, 1fr) 84px;
  gap: var(--pf-space-4); align-items: start;
  /* ★ ระยะในแถว — ทำให้แถบซ้ายของข้อสำคัญมีที่ยืนโดยไม่ต้องดันแถวออกนอกกรอบ */
  padding: var(--pf-space-3) var(--pf-space-3) var(--pf-space-3) var(--pf-space-4);
  border-inline-start: 3px solid transparent;
  border-radius: var(--pf-radius-md);
}
/* ★ ตัวเลขลำดับในวงกลม — เสร็จแล้วเปลี่ยนเป็นเครื่องหมายถูก
   ⚠ ใช้สีพื้นแบรนด์ตอนเสร็จ ไม่ใช่สีเขียว (เหตุผลเดียวกับ .pf-ring-fill) */
.pf-setup-mark {
  inline-size: 26px; block-size: 26px; border-radius: var(--pf-radius-pill);
  display: grid; place-items: center; flex: none;
  font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption);
  font-weight: var(--pf-w-bold); font-variant-numeric: tabular-nums;
  background: var(--pf-bg-secondary); color: var(--pf-text-secondary);
  border: 1px solid var(--pf-border);
}
.pf-setup-step.is-done .pf-setup-mark {
  background: var(--pf-primary-500); color: var(--pf-on-primary); border-color: var(--pf-primary-500);
}
.pf-setup-body { display: grid; gap: 2px; min-width: 0; }
.pf-setup-body > b { font-size: var(--pf-size-body); line-height: var(--pf-lh-body); }
/* ★★ บรรทัดนี้บอก **ผลของการข้าม** ไม่ใช่บอกว่าต้องทำอะไร
   คนอ่านแล้วต้องรู้ว่าทำไมถึงสำคัญ ไม่งั้นเขาจะข้ามข้อที่แพงที่สุด */
.pf-setup-why {
  color: var(--pf-text-secondary);
  font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact);
}
/* ★★★ คอลัมน์ขวามี **ของชิ้นเดียวเสมอ** — ปุ่ม หรือข้อความ ไม่ใช่ทั้งคู่ซ้อนกัน
   ⚠⚠ เดิมแถวที่มีปุ่มจะเอา `.pf-setup-detail` ไปวางใต้ปุ่มด้วย ทำให้แถวนั้นสูงกว่าเพื่อน
     แล้วแถวถัดไปถูกดันลง → "ขนาดไม่เท่ากัน" ที่เจ้าของทัก
   ★ รายละเอียด (เช่น "ขาด 3 คน") ย้ายไปอยู่ใน `.pf-setup-body` ซึ่งเป็นที่ของมันจริง ๆ
     เพราะมันคือ **คำอธิบายของข้อนั้น** ไม่ใช่ **การกระทำ** */
.pf-setup-act { display: grid; justify-items: end; align-content: start; min-height: 26px; }
.pf-setup-act .pf-btn { width: 100%; }
.pf-setup-detail {
  color: var(--pf-text-muted); font-size: var(--pf-size-caption);
  line-height: var(--pf-lh-caption); white-space: nowrap;
  /* ★ จัดให้ตรงกลางแนวตั้งกับวงกลมและหัวข้อ — ไม่งั้นข้อความลอยชิดบนกว่าปุ่มของแถวอื่น */
  display: flex; align-items: center; min-height: 26px;
}
/* ★ ข้อที่ทำเสร็จแล้วจางลง — แต่ **ไม่ซ่อน** เพราะคนอยากเห็นว่าตัวเองมาถึงไหนแล้ว
   ⚠⚠ เดิมซ่อนบรรทัดอธิบายของข้อที่เสร็จ (`display: none`) ผลคือแถวนั้นเตี้ยกว่าเพื่อน 18px
     ทั้งรายการจึงดูขนาดไม่เท่ากัน (เจ้าของทัก 2026-08-27)
   ★ ตอนนี้จางลงอย่างเดียว — ความสูงของทุกแถวเท่ากัน และยังอ่านออกว่าข้อนั้นกันอะไรไว้ */
.pf-setup-step.is-done .pf-setup-body > b { color: var(--pf-text-secondary); }
.pf-setup-step.is-done .pf-setup-why { color: var(--pf-text-muted); }
/* ★★★ ข้อที่ผิดแล้วแก้ย้อนหลังไม่ได้ ต้องอ่านต่างจากข้ออื่น
   ⚠ ใช้ขอบซ้ายไม่ใช่พื้นสีทั้งแถว — พื้นสีทั้งแถวจะแย่งน้ำหนักกับปุ่มในแถวนั้นเอง */
.pf-setup-step.is-critical {
  /* ⚠⚠ เดิมใช้ `margin-inline-start: -space-4` เพื่อดันแถวออกไปรับแถบซ้าย
     ผลคือ **แถวนี้เริ่มไม่ตรงกับแถวอื่น** ซึ่งคือสิ่งที่เจ้าของเห็นว่าเพี้ยน
     ★ ตอนนี้ทุกแถวมี `border-inline-start: 3px solid transparent` อยู่แล้ว
       ข้อสำคัญแค่ **เปลี่ยนสีเส้น** ไม่ขยับตำแหน่งของอะไรเลย */
  border-inline-start-color: var(--pf-primary-500);
  background: var(--pf-surface-subtle);
}
/* ⚠ ข้อที่ระบบยังทำไม่ได้ — จางทั้งแถว แต่ยังอ่านออก · ห้ามซ่อน (ดูหัวข้อ) */
.pf-setup-step.is-blocked .pf-setup-body > b { color: var(--pf-text-muted); }
/* ⚠⚠ จอแคบ: ช่องขวา 116px บีบเนื้อเหลือ ~110px แล้วข้อความห่อจนแถวสูง 290px
     (วัดที่ 375px ก่อนแก้ · เจ้าของทักเรื่องขนาดไม่เท่ากันพอดี)
   ★ ตัดเหลือสองคอลัมน์ แล้วให้ปุ่ม/ข้อความลงบรรทัดใหม่เต็มความกว้างใต้เนื้อ
     ⚠ วงกลมลำดับยังอยู่คอลัมน์ซ้ายเหมือนเดิม — ขอบซ้ายของทุกแถวจึงยังเป็นเส้นตรง */
@media (max-width: 560px) {
  .pf-setup-step { grid-template-columns: 26px minmax(0, 1fr); }
  .pf-setup-act { grid-column: 2; justify-items: stretch; margin-top: var(--pf-space-3); }
  .pf-setup-detail { justify-content: flex-start; min-height: 0; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   ช่องรหัสผ่าน + แถวโควตาวันลา  (ยกมาจากแอป · รอบ 272 · D42 ขั้น 3.3)
   ═══════════════════════════════════════════════════════════════════════════ */

/* ★★ ช่องรหัสผ่านที่มีปุ่มดู/ซ่อน — ปุ่มอยู่ **ในกรอบ** ไม่ใช่ข้าง ๆ
   ⚠⚠ ฉบับในแอปใช้ `position:absolute` + กัน `padding-right` ให้ตัวอักษร
     ที่นี่ **ไม่ต้องทำแบบนั้น** เพราะ `.pf-field` เป็น flex row อยู่แล้ว
     ปุ่มเป็นพี่น้องของ input ได้ตรง ๆ — ไม่มีทางที่ตัวอักษรจะวิ่งไปซ้อนใต้ปุ่ม
   ⚠ ขั้น 5 ต้องเปลี่ยนมาร์กอัปด้วย: `<div class="pf-field pf-pw"><input><button class="pf-pw-eye">`
     ไม่ใช่แค่เปลี่ยนชื่อคลาส — เขียนเตือนไว้ตรงนี้เพราะเป็นกลุ่มเดียวที่โครงต่าง
   ★ พื้นที่กดต้องใหญ่พอสำหรับนิ้ว — ไม่ใช่แค่ขนาดไอคอน */
.pf-pw { gap: 0; }
.pf-pw-eye {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  inline-size: 34px; block-size: 34px; margin-inline-end: -6px;
  background: none; border: 0; border-radius: var(--pf-radius-sm);
  color: var(--pf-text-muted); cursor: pointer;
  transition: color var(--pf-duration-fast) var(--pf-ease-out),
              background var(--pf-duration-fast) var(--pf-ease-out);
}
.pf-pw-eye:hover { color: var(--pf-text-primary); background: var(--pf-bg-secondary); }
.pf-pw-eye[disabled] { opacity: .5; cursor: not-allowed; }

/* ★★★ แถวโควตาวันลา — สามคอลัมน์: กล่องไอคอน · ชื่อ+เงื่อนไข · ตัวเลข
   ⚠ ไม่ใช่เรื่องเงาหรือระยะอย่างเดียว — เป็นเรื่อง **ข้อมูลที่อ่านได้โดยไม่ต้องอ่าน**
     ห้าช่องตัวเลขที่หน้าตาเหมือนกันหมด บังคับให้ตาอ่านตัวหนังสือทุกครั้ง
   ⚠ ช่องตัวเลขแคบโดยตั้งใจ — โควตาเป็นเลข 1–3 หลัก
     ช่องกว้างเต็มแถวทำให้ตาคิดว่ากรอกอะไรก็ได้ */
.pf-quota {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  gap: var(--pf-space-4); align-items: center;
}
.pf-quota-body { display: grid; gap: 2px; min-width: 0; }
.pf-quota-body > label {
  font-size: var(--pf-size-body); line-height: var(--pf-lh-body);
  font-weight: var(--pf-w-semibold); color: var(--pf-text-primary);
}
.pf-quota-num { display: flex; align-items: center; gap: var(--pf-space-3); flex: none; }
.pf-quota-num .pf-field { inline-size: 92px; }
.pf-quota-num .pf-field input { text-align: end; font-variant-numeric: tabular-nums; }
.pf-quota-unit {
  color: var(--pf-text-muted); font-size: var(--pf-size-compact);
  line-height: var(--pf-lh-compact); white-space: nowrap;
}
/* ⚠ จอแคบ: ตัวเลขลงมาอยู่บรรทัดใหม่ใต้ชื่อ ไม่ใช่บีบให้ทั้งสามคอลัมน์อยู่แถวเดียว
   บีบแล้วชื่อประเภทลาจะถูกตัดกลางคำ ซึ่งอ่านไม่ออกว่าลาอะไร */
@media (max-width: 560px) {
  .pf-quota { grid-template-columns: auto minmax(0, 1fr); }
  .pf-quota-num { grid-column: 2; justify-content: start; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   ตัวช่วยเล็ก ๆ ที่ระบบดีไซน์ยังไม่มี  (ยกมาจากแอป · รอบ 273 · D42 ขั้น 3.4)
   ═══════════════════════════════════════════════════════════════════════════ */

/* ★★★★ จังหวะของฟอร์ม — **ที่เดียวทั้งระบบ**
   ⚠⚠ เดิมกฎนี้อยู่ใต้ฟอร์มหน้าเข้าสู่ระบบเท่านั้น → หน้าอื่นในแอปไม่ได้อะไรเลย
     วัดหน้าตั้งค่าแล้วพบว่า **ลูกของการ์ดห่างกัน 0px ทุกคู่** (`display:block` ไม่มี gap
     และลูกไม่มี margin) · ระยะที่เห็นบนจอมาจาก line-height ล้วน ๆ จึงดูไม่เท่ากัน
     — นั่นคือ "ระยะห่างดูไม่เท่ากัน" ที่เจ้าของโปรเจคทัก
   ★ ใช้คลาสนี้กับกล่องที่ห่อฟอร์ม แล้วจะได้จังหวะเดียวกับหน้าเข้าสู่ระบบเป๊ะ */
.pf-form-stack { display: grid; gap: var(--pf-space-6); align-content: start; }
/* ★ ปุ่มแยกออกจากกลุ่มช่องกรอกให้ชัด — มันคือ "การกระทำ" ไม่ใช่ "ช่องอีกช่อง"
   ระยะเท่ากันหมดทำให้ตาอ่านว่าปุ่มเป็นสมาชิกของกลุ่มเดียวกับช่องกรอก */
.pf-form-stack > .pf-btn { margin-block-start: var(--pf-space-4); }
/* ⚠ หัวข้อในกล่องฟอร์มไม่ต้องมี margin ของตัวเอง — gap จัดการให้แล้ว
   ใส่ทั้งสองอย่างเมื่อไหร่ ระยะจะเป็นสองเท่าเฉพาะใต้หัวข้อ ซึ่งอ่านว่า "ไม่เท่ากัน" */
.pf-form-stack > h4 { margin: 0; }

/* แถวปุ่มที่ห่อบรรทัดได้ — ★ ต่างจาก `.pf-actions` ตรงที่ตัวนั้นชิดขวาเสมอ */
.pf-btn-row { display: flex; flex-wrap: wrap; gap: var(--pf-space-4); align-items: center; }

/* ★ ปุ่มไอคอนขนาดเล็ก — ระบบดีไซน์มี `.pf-btn--icon` (40px) แต่ยังไม่มีขนาดเล็ก
   ⚠ 28px ต่ำกว่าเป้าสัมผัส 44px โดยตั้งใจ — ใช้เฉพาะปุ่มปิดในหัวการ์ดบนเดสก์ท็อป
     ห้ามเอาไปใช้เป็นปุ่มหลักบนมือถือ */
.pf-btn--icon-sm { inline-size: var(--pf-control-sm); block-size: var(--pf-control-sm); padding: 0; }

/* ★★ กล่องสรุปเงิน — ใช้ในกล่องยืนยันที่มีผลกับเงิน (กฎเหล็กข้อ 6)
   ★ ตัวเลขต้อง tabular-nums เสมอ ไม่งั้นหลักไม่ตรงกันแล้วอ่านยอดรวมผิด */
.pf-money {
  background: var(--pf-bg-secondary); border: 1px solid var(--pf-border);
  border-radius: var(--pf-radius-md);
  padding: var(--pf-space-4) var(--pf-space-5); margin-top: var(--pf-space-5);
  font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact);
}
/* ⚠⚠ `align-items: center` ไม่ใช่ของประดับ — ค่าปริยายคือ `stretch`
   แถวที่ฝั่งหนึ่งมีของสูงกว่า (ปุ่ม · ป้ายสองบรรทัด) จะยืดอีกฝั่งให้สูงตาม
   แล้ว **ข้อความไปเกาะขอบบน** ส่วนปุ่มอยู่กลาง → ตาอ่านว่า "ไม่ได้อยู่ระดับเดียวกัน"
   วัดจริง (งวดย้อนหลังในหน้าสลิป · 2026-08-27): ทั้งสองฝั่งสูง 60px เท่ากัน
     แต่ข้อความอยู่บนสุด ปุ่มอยู่กลาง — เจ้าของโปรเจคทักมา
   ★ แถวที่มีแต่ข้อความบรรทัดเดียว (ที่ใช้กันอยู่เดิมทุกที่) ไม่ขยับเลย เพราะสูงเท่ากันอยู่แล้ว */
.pf-money .ln { display: flex; align-items: center; justify-content: space-between; gap: var(--pf-space-5); padding: 3px 0; }
.pf-money .ln b { font-variant-numeric: tabular-nums; }
/* ★★★ ป้ายซ้ายห้ามตัดคำ — ตัดแล้วอ่านเป็นคำอื่น ("ชื่อ- / สกุล" · "วันเริ่ม / งาน")
   ⚠ `:not(:last-child)` สำคัญ — บางแถวมีลูกตัวเดียว (ข้อความแจ้งเตือนยาว ๆ)
     ตัวนั้น **ต้องตัดบรรทัดได้** ไม่งั้นมันจะดันการ์ดล้น
   ★ ฝั่งค่ายังตัดบรรทัดได้ตามปกติ แต่ตัดที่ช่องว่างระหว่างคำ ไม่ใช่กลางคำ */
.pf-money .ln > :first-child:not(:last-child) { flex: none; white-space: nowrap; }
.pf-money .ln > :last-child:not(:first-child) { min-inline-size: 0; text-align: end; }
.pf-money .tot {
  border-top: 1px solid var(--pf-border);
  margin-top: var(--pf-space-3); padding-top: var(--pf-space-3);
  font-weight: var(--pf-w-bold);
}

/* ★ ข้อความสำหรับเครื่องอ่านหน้าจอเท่านั้น — ★ ห้ามใช้ `display:none`
   `display:none` ซ่อนจากเครื่องอ่านหน้าจอด้วย ซึ่งตรงข้ามกับที่ต้องการ */
.pf-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

/* ★ ของที่โผล่เฉพาะตอนตารางกลายเป็นการ์ด (≤640px)
   ⚠ หัวตารางถูกซ่อนตอนนั้น → ปุ่มเรียงลำดับหายไปทั้งหมด
     ถ้าไม่มีตัวนี้ ฟีเจอร์เรียงลำดับจะไม่มีอยู่เลยบนมือถือโดยไม่มีอะไรบอก */
.pf-only-cards { display: none; }
@media (max-width: 640px) { .pf-only-cards { display: flex; } }

/* ═══════════════════════════════════════════════════════════════════════════
   เปลือกของ **แอปจริง** (`/app`) — ต่างจากเฟรมสาธิตในแกลเลอรี (2026-08-26)

   ★★★ ทำไมต้องมีบล็อกนี้
     `/design/` เป็นหน้านิ่ง · แถบข้าง กับ เฟรมมือถือ (`pf-m-header` + `pf-bottom-nav`)
     อยู่ **คนละใบ** ในแกลเลอรี จึงไม่เคยต้องสลับกันเอง
     แอปจริงต้องสลับตามความกว้าง — และนั่นคือกฎที่แกลเลอรีไม่มีให้ลอก

   ★ ใช้ `.pf-shell--app` เป็นตัวคุม เพราะ **ความเฉพาะเจาะจง 0,2,0 ชนะ 0,1,0 เสมอ**
     ไม่ต้องพึ่งลำดับในไฟล์ (บทเรียน P204 — ตัวปรับที่ประกาศก่อนกฎฐานถูกทับเงียบ ๆ)

   จุดตัดเดียวกับที่ `pf.css` ใช้อยู่แล้ว
     ≥1024   แถบข้างเต็ม 248px
     768–1023 แถบข้างยุบเหลือไอคอน 72px   (กฎเดิมที่ @media max-width:1023)
     ≤767    แถบข้างหาย → หัวมือถือ 56px + แถบล่าง 5 ช่อง (กฎในบล็อกนี้)
   ═══════════════════════════════════════════════════════════════════════════ */
/* ★ แอปจริงไม่มีแถบสาธิตอยู่เหนือเปลือก — แถบข้างจึงเริ่มที่ขอบบนจอพอดี */
.pf-shell--app { min-height: 100dvh; --pf-shell-top: 0px; }
/* ⚠ ต้องเป็น flex column ไม่ใช่ block — ไม่งั้นแถบล่างลอยตามเนื้อหา ไม่ติดก้นจอ */
.pf-shell--app .pf-main { display: flex; flex-direction: column; min-height: 100dvh; min-width: 0; }
.pf-shell--app .pf-page { flex: 1; }

/* ★ ปริยาย: ของมือถือไม่ขึ้น — แกลเลอรีแสดงมันในเฟรมของตัวเองอยู่แล้ว */
.pf-shell--app .pf-m-header,
.pf-shell--app .pf-bottom-nav { display: none; }

@media (max-width: 767px) {
  /* ⚠ `sticky` ไม่ใช่ `fixed` — `fixed` เลิกอ้างขอบจอทันทีที่ ancestor มี `transform`
     ซึ่งเกิดขึ้นจริงตอนมีอนิเมชัน (บทเรียนเดียวกับที่ทำให้ต้องใช้ `<dialog>` แทน div) */
  .pf-shell--app .pf-m-header { display: flex; position: sticky; top: 0; z-index: 20; }
  .pf-shell--app .pf-bottom-nav { display: grid; position: sticky; bottom: 0; z-index: 20; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   รายการเมนูแบบหน้าเต็ม — หน้า "เพิ่มเติม" บนมือถือ (2026-08-26)

   ★★★ ทำไมไม่ใช้ `.pf-nav-item` ซ้ำ
     `@media (max-width: 1023px)` ซ่อน `.pf-nav-item span` ไว้ เพราะแถบข้างยุบเป็นราง
     เอามาใช้ในหน้าอื่นที่ความกว้างเดียวกัน = **ได้ไอคอนเรียงกันโดยไม่มีชื่อเลย**
     (เกิดขึ้นจริงตอนลองทำเป็นแผ่นเลื่อน — เจ้าของเห็นทันทีว่าอ่านไม่ออก)
   ★ จึงเป็นชิ้นของตัวเอง ไม่ยืมคลาสของแถบข้าง — บทเรียนตระกูล P199/P204
   ═══════════════════════════════════════════════════════════════════════════ */
.pf-menu-list { display: grid; gap: 2px; }
.pf-menu-row {
  display: flex; align-items: center; gap: var(--pf-space-4);
  padding: var(--pf-space-4); border-radius: var(--pf-radius-lg);
  color: var(--pf-text-primary); text-decoration: none; min-height: var(--pf-touch-target);
  transition: background var(--pf-duration-fast) var(--pf-ease-out);
}
.pf-menu-row:hover { background: var(--pf-surface-hover); }
.pf-menu-row:active { background: var(--pf-gray-100); }
.pf-menu-row[aria-current="page"] { background: var(--pf-primary-50); }
/* ★ ป้ายชิ้นนี้ต้องไม่โดนกฎซ่อนของแถบข้าง — เลือกด้วยชื่อของตัวเอง */
.pf-menu-row .t { flex: 1; min-width: 0; }
.pf-menu-row .t b { display: block; font-weight: var(--pf-w-medium); font-size: var(--pf-size-body); line-height: var(--pf-lh-body); }
.pf-menu-row .pf-caption { display: block; }
/* ⚠ ลูกศรต้องเป็นสีจาง — มันเป็นของประดับ ไม่ใช่ข้อมูล */
.pf-menu-row .go { color: var(--pf-text-muted); flex: none; }
.pf-menu-icon { width: 36px; height: 36px; flex: none; border-radius: var(--pf-radius-md); display: grid; place-items: center; }
.pf-menu-icon .pf-ico { width: 20px; height: 20px; }

/* ═══════════════════════════════════════════════════════════════════════════
   ความสมูดของเปลือกแอป (2026-08-26)

   ★ ใช้โทเคนที่มีอยู่แล้วทั้งหมด — `--pf-duration-*` และ `--pf-ease-out`
     ไม่มีตัวเลขใหม่ · ไม่มี easing ใหม่ (สัญญาเดิมของระบบดีไซน์)

   ⚠⚠ ทุกอย่างในบล็อกนี้ต้องถูกปิดได้ด้วย `prefers-reduced-motion`
     คนที่ตั้งค่านั้นไว้มักตั้งเพราะ **เวียนหัวจริง** ไม่ใช่เพราะไม่ชอบ
   ⚠ เคลื่อนที่ด้วย `transform` + `opacity` เท่านั้น — สองอย่างนี้ไม่ทำให้เบราว์เซอร์
     คำนวณเลย์เอาต์ใหม่ · ขยับ `height`/`top` แทนจะกระตุกบนมือถือทันที
   ═══════════════════════════════════════════════════════════════════════════ */
/* ★ หน้าใหม่เลื่อนขึ้นมานิดเดียว 6px — มากกว่านี้จะรู้สึกว่า "รอ" ไม่ใช่ "ลื่น" */
@keyframes pf-page-in { from { opacity: 0; transform: translateY(6px); } }
.pf-shell--app .pf-page {
  animation: pf-page-in var(--pf-duration-emphasis) var(--pf-ease-out) both;
}

/* ★ เมนู — สีและพื้นเปลี่ยนแบบไล่ ไม่กระโดด */
/* 🔴 เคยมี `.pf-icon-btn` อยู่ในรายการนี้ด้วย — **คลาสที่ไม่มีกฎฐานอยู่ที่ไหนเลย**
   มันมีแต่ transition ตัวนี้ · ปุ่มที่ใส่คลาสนั้นจึงเป็นปุ่มเปล่าที่ชุดรีเซ็ตถอดหน้าตาไปหมด
   วัดจริงที่ 375px: 26×16px ไม่มีพื้น ไม่มีขอบ ไม่มีสถานะโฟกัส (ตระกูล P205)
   ★ ถอดออกแล้ว · ที่ที่เคยใช้เปลี่ยนไปใช้ `.pf-btn--icon` ซึ่งมีกฎจริง 40px */
.pf-menu-row, .pf-bottom-nav a {
  transition: background var(--pf-duration-fast) var(--pf-ease-out),
              color var(--pf-duration-fast) var(--pf-ease-out);
}
/* ⚠ ไอคอนในแถบล่างต้องขยับ **หลังจาก** สีเปลี่ยนแล้ว จึงใช้ duration ต่างกันได้
   ★ ย่อ/ขยายแค่ 8% — ใหญ่กว่านี้จะเห็นเป็น "เด้ง" ซึ่งขัดกับ ease-out ที่ไม่มีเด้ง */
.pf-bottom-nav a .pf-ico {
  transition: transform var(--pf-duration-standard) var(--pf-ease-out);
}
.pf-bottom-nav a[aria-current="page"] .pf-ico { transform: scale(1.08); }
.pf-bottom-nav a:active .pf-ico { transform: scale(.92); }

/* ★ แถบข้าง — ตอนยุบ/ขยายตามความกว้างจอ ให้ไหลแทนการกระโดด
   ⚠ `grid-template-columns` เคลื่อนไหวได้เมื่อทั้งสองฝั่งเป็นความยาวที่คำนวณได้
     ซึ่งที่นี่เป็น (248px / 72px) จึงใช้ได้จริง */
.pf-shell--app { transition: grid-template-columns var(--pf-duration-emphasis) var(--pf-ease-out); }

@media (prefers-reduced-motion: reduce) {
  .pf-shell--app .pf-page { animation: none; }
  .pf-shell--app,
  .pf-menu-row, .pf-bottom-nav a, .pf-bottom-nav a .pf-ico { transition: none; }
  .pf-bottom-nav a[aria-current="page"] .pf-ico,
  .pf-bottom-nav a:active .pf-ico { transform: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   การพิมพ์ / บันทึกเป็น PDF  (เจ้าของเคาะทาง ข · 2026-08-27)

   ★★★★ ทำไมไม่เพิ่มไลบรารี PDF
     สลิปที่คนเอาไปยื่นธนาคารคือ PDF ไม่ใช่ CSV — แต่โปรเจคนี้มี dependency แค่สามตัว
     (express · jsonwebtoken · pg) และการเพิ่มตัวที่สี่เป็นการตัดสินใจของเจ้าของ
     ★ เบราว์เซอร์ทุกตัวมี "พิมพ์ → บันทึกเป็น PDF" อยู่แล้ว · ได้ PDF จริงโดยไม่เพิ่มอะไรเลย
       และกฎชุดนี้ใช้ซ้ำได้กับทุกหน้าที่ต้องพิมพ์ (สลิป · รายงาน · ใบลา)

   ⚠⚠⚠ ก่อนรอบนี้ `pf.css` **ไม่มีกฎ `@media print` แม้แต่ข้อเดียว**
     สั่งพิมพ์แล้วจะได้แถบข้าง เมนู ปุ่ม และพื้นเทาติดไปเต็มหน้ากระดาษ

   ★★ สองคลาสที่หน้าจอใช้สั่งได้
     `.pf-no-print`    ของที่ไม่ควรไปอยู่บนกระดาษ (ปุ่ม · ตัวกรอง · รายการย้อนหลัง)
     `.pf-print-only`  ของที่มีเฉพาะตอนพิมพ์ — ★ จำเป็นจริง ๆ ไม่ใช่ของแถม
       เพราะชื่อองค์กรกับชื่อพนักงานอยู่ในแถบข้าง ซึ่งถูกซ่อนตอนพิมพ์
       ไม่มีบล็อกนี้ = กระดาษที่พิมพ์ออกมา **ไม่บอกว่าเป็นสลิปของใคร จากที่ไหน**
   ═══════════════════════════════════════════════════════════════════════════ */

.pf-print-only { display: none; }

@media print {
  /* ★ บังคับพื้นขาวหมึกดำ — ธีมมืดบนกระดาษคือหมึกเต็มหน้าและอ่านไม่ออก
     ⚠ ต้องคลุม `[data-theme="dark"]` ด้วย ไม่งั้นคนที่เปิดธีมมืดอยู่จะพิมพ์ได้หน้าดำ */
  :root, :root[data-theme="dark"] {
    --pf-bg: #FFFFFF; --pf-bg-secondary: #FFFFFF;
    --pf-surface: #FFFFFF; --pf-surface-raised: #FFFFFF; --pf-surface-floating: #FFFFFF;
    --pf-surface-hover: #FFFFFF;
    --pf-text-primary: #101828; --pf-text-secondary: #344054; --pf-text-muted: #475467;
    --pf-border: #D0D5DD; --pf-border-strong: #98A2B3;
  }

  html, body { background: #FFFFFF; }

  /* ── ซ่อนเปลือกแอปทั้งหมด — กระดาษไม่มีเมนูให้กด ── */
  .pf-sidebar, .pf-bottom-nav, .pf-m-header, .pf-setup-pop, .pf-no-print { display: none !important; }
  .pf-print-only { display: revert; }

  /* ⚠ เปลือกเป็นกริดสองคอลัมน์ — ซ่อนแถบข้างเฉย ๆ จะเหลือคอลัมน์ว่างกินครึ่งหน้า */
  .pf-shell, .pf-shell--app { display: block; }
  /* ⚠⚠⚠ ต้องเขียน `.pf-shell--app .pf-main` ให้ **เจาะจงเท่ากฎเดิม** (ตระกูล P204)
     เขียนแค่ `.pf-main` (0,1,0) แพ้ `.pf-shell--app .pf-main` (0,2,0) — วัดแล้วยังได้ `flex`
     ★ ตัวที่เป็นปัญหาจริงคือ `min-height: 100dvh` ที่ติดมากับกฎนั้น:
       บนกระดาษมันบังคับให้เนื้อหาสูงอย่างน้อยหนึ่งจอ → ได้หน้าว่างเพิ่มมาท้ายเอกสาร */
  .pf-main, .pf-shell--app .pf-main { display: block; min-height: 0; }
  .pf-page { padding: 0; gap: var(--pf-space-5); }

  /* ★ เงาไม่มีความหมายบนกระดาษ และเครื่องพิมพ์บางรุ่นเรนเดอร์เป็นแถบเทาสกปรก
     ⚠ ต้องเหลือ **เส้นขอบ** ไว้ ไม่งั้นการ์ดจะกลืนกันหมดจนอ่านไม่ออกว่าตรงไหนจบ */
  .pf-card, .pf-kpi { box-shadow: none !important; border: 1px solid var(--pf-border); }

  /* ★★ ห้ามตัดกลางการ์ด — สลิปที่ถูกตัดครึ่งคนละหน้าอ่านยากมาก
     ⚠ `break-inside` เป็นชื่อมาตรฐาน · `page-break-inside` ไว้เผื่อเบราว์เซอร์เก่า */
  .pf-card, .pf-kpi, .pf-money, .pf-note { page-break-inside: avoid; break-inside: avoid; }

  /* ★ ปุ่มไม่มีความหมายบนกระดาษ — ซ่อนหมด ไม่ใช่พิมพ์เป็นกล่องสีทึบ */
  .pf-btn, .pf-select, .pf-filterbar, .pf-pagination { display: none !important; }

  /* ⚠ ลิงก์ที่พิมพ์ออกมาแล้วเป็นข้อความสีน้ำเงินขีดเส้นใต้ แต่กดไม่ได้ = สับสน */
  a { color: inherit !important; text-decoration: none !important; }

  @page { margin: 14mm; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   คอนโซลผู้ดูแลแพลตฟอร์ม (D35 · D46 ① · 2026-08-31)

   ★★★ มีแค่ **เปลือก** — ทุกอย่างข้างในใช้คลาสร่วมของระบบ (การ์ด · ปุ่ม · ฟอร์ม · แถบเตือน)
     คอนโซลอยู่คนละ origin กับแอปของลูกค้า แต่ **CSS ยังแหล่งเดียวกัน** (D42)
     ⚠ แยก CSS ด้วยจะได้ "แหล่งที่สอง" ซึ่งเป็นสิ่งที่ D42 เกิดมาเพื่อกำจัดพอดี
   ★ สามคลาสนี้แสดงอยู่ที่ `/design/console.html` — ของที่ CSS มีแต่ดีไซน์ไม่แสดง
     คือของที่ทุกคนเดาเอง แล้วเพี้ยนคนละทาง (กฎเหล็กข้อ 1)
   ═══════════════════════════════════════════════════════════════════════════ */
.pf-console { background: var(--pf-bg); }
.pf-console-bar {
  display: flex; align-items: center; gap: var(--pf-space-4);
  padding: var(--pf-space-4) var(--pf-space-6);
  background: var(--pf-surface); border-block-end: 1px solid var(--pf-border);
}
/* ★ แคบกว่าหน้าแอปโดยตั้งใจ — ที่นี่มีของอ่านทีละอย่าง ไม่ใช่ตารางกว้าง ๆ */
.pf-console-main {
  max-inline-size: 780px; margin-inline: auto;
  padding: var(--pf-space-6); display: grid; gap: var(--pf-space-5);
}
.pf-console-hr {
  border: 0; border-block-start: 1px solid var(--pf-border);
  margin-block: var(--pf-space-5);
}

/* ═══════════════════════════════════════════════════════════════════════════
   กรอบกล้องลงทะเบียนใบหน้า (2026-09-01)

   ★★★ มีสองหน้าที่ และทั้งสองอย่างมาจากคำถามของเจ้าของโปรเจคตอนใช้จริง
     ① **บอกว่าให้วางหน้าตรงไหน** — ภาพที่เก็บคือกรอบรอบใบหน้า ไม่ใช่ทั้งเฟรม
        คนที่ไม่รู้จะยืนไกลจนใบหน้าเล็กเกินกว่าจะครอบตัดได้ดี
     ② **บอกว่ากำลังประมวลผลอยู่** — ระหว่างคำนวณ ภาพจากกล้องจะถูกหยุด (pause)
        ⚠⚠ ปล่อยให้ภาพขยับต่อ = ไม่มีสัญญาณว่าระบบรับคำสั่งแล้ว
          เจ้าของทักเองว่า "กล้องมันยังขยับได้อยู่ เลยไม่รู้สึกว่ากำลังประมวล"

   ⚠ กรอบนี้เป็น **ตัวช่วยเล็ง ไม่ใช่ขอบเขตที่ครอบตัดจริง**
     ของจริงครอบรอบ **ใบหน้าที่ตรวจเจอ** + เผื่อ 40% ซึ่งขยับตามคนในภาพ
     ⇒ ห้ามเขียนข้อความว่า "ภาพที่เก็บคือในกรอบนี้" — มันจะไม่จริงเมื่อคนขยับ
   ═══════════════════════════════════════════════════════════════════════════ */
.pf-cam {
  position: relative;
  inline-size: 100%; max-inline-size: 360px;
  border-radius: var(--pf-radius-lg);
  overflow: hidden;
  background: var(--pf-bg-secondary);
}
.pf-cam video {
  display: block; inline-size: 100%;
  /* ★ กล้องที่ขอไว้คือ 480×360 — ตรึงสัดส่วนไว้ กล่องจะได้ไม่กระโดดตอนเฟรมแรกมาถึง
     ⚠ ไม่ตรึง = ความสูงเป็นค่าปริยายของ <video> (150px) จนกว่าภาพจะมา แล้วหน้าเลื่อน */
  aspect-ratio: 4 / 3; object-fit: cover;
  /* ★ กระจกภาพเหมือนกล้องหน้ามือถือ — ไม่งั้นคนขยับผิดข้างทุกครั้ง */
  transform: scaleX(-1);
}
/* ★ กรอบเล็ง — จตุรัสกลางจอ ขนาดพอ ๆ กับที่ครอบตัดจริงเมื่อยืนระยะปกติ */
.pf-cam-guide {
  position: absolute; inset: 0; margin: auto;
  /* ★★★ ขนาดยึดจาก **ความสูง** ไม่ใช่ความกว้าง — ภาพจากกล้องกว้างกว่าสูงเสมอ
     ⚠ ยึดความกว้าง (เช่น 55%) แล้วบังคับให้เป็นจตุรัส = กรอบสูงเกินกล่องแล้วโดนตัดหัวท้าย
       เห็นชัดที่ /design ตอนวิดีโอยังไม่มีภาพ (กล่องเตี้ยกว่าปกติ) */
  block-size: 64%; inline-size: auto; aspect-ratio: 1;
  border: 2px dashed var(--pf-border-strong);
  border-radius: var(--pf-radius-2xl);
  pointer-events: none;
}
/* ★★ ระหว่างประมวลผล — ภาพหยุดนิ่งแล้ว จึงต้องมีป้ายบอกว่าไม่ได้ค้าง */
.pf-cam[data-busy="1"]::after {
  content: 'กำลังประมวลผล…';
  position: absolute; inset-block-end: 0; inset-inline: 0;
  padding: var(--pf-space-3);
  text-align: center;
  font-size: var(--pf-size-caption); font-weight: var(--pf-w-semibold);
  color: var(--pf-scrim-ink); background: var(--pf-scrim-bg);
}
.pf-cam[data-busy="1"] .pf-cam-guide { border-color: var(--pf-primary-500); }
/* ★★★★ ไฟบอกสถานะสด — "ตอนนี้ระบบเห็นหน้าคุณหรือยัง" (2026-09-01)
   🔴 เจ้าของถามว่า "มันตรวจเข้มมากเลยหรอ" หลังเจอ "ไม่พบใบหน้า"
     คำถามนั้นเกิดเพราะ **ไม่มีข้อมูลป้อนกลับเลย** จนกว่าจะกดถ่ายแล้วโดนปฏิเสธ
     ⇒ ของที่ขาดคือ "ไฟเขียวก่อนกด" ไม่ใช่การผ่อนเกณฑ์
   ⚠ คู่สีทั้งสองโทนเป็นคู่ที่ `theme-check` ตรวจอยู่แล้ว (ชิปไอคอนเขียว/เหลือง) */
.pf-cam-live {
  position: absolute; inset-block-start: var(--pf-space-2); inset-inline: var(--pf-space-2);
  margin-inline: auto; inline-size: fit-content;
  padding: 2px var(--pf-space-3); border-radius: var(--pf-radius-pill);
  font-size: var(--pf-size-caption); font-weight: var(--pf-w-semibold);
  pointer-events: none;
}
/* ★★★★ สี่สถานะ ไม่ใช่สอง (2026-09-08) — เดิมมีแค่ 1/0
   🔴 ตอนโหลดตัวประมวลผล (~7.9 MiB) ป้ายเคยขึ้น "ยังไม่พบใบหน้า" ทั้งที่ยังไม่ได้เริ่มดู
     ผู้ใช้จึงไปขยับหน้าหน้ากล้องซ้ำ ๆ ทั้งที่ไม่มีอะไรผิดที่ตัวเขาเลย
   ★ สีสื่อ "ต้องทำอะไรต่อ" ไม่ใช่ระดับความรุนแรง:
     เขียว = กดได้ · เหลือง = ขยับหน้า · เทา = รอเฉย ๆ · แดง = ขยับหน้าไม่ช่วย */
.pf-cam-live[data-on="1"] { background: var(--pf-pastel-green);  color: var(--pf-success-700); }
.pf-cam-live[data-on="0"] { background: var(--pf-pastel-yellow); color: var(--pf-warning-700); }
.pf-cam-live[data-on="wait"] { background: var(--pf-gray-100); color: var(--pf-gray-700); }
.pf-cam-live[data-on="err"] { background: var(--pf-danger-50); color: var(--pf-danger-700); }
/* ★ กรอบเล็งเปลี่ยนสีตามด้วย — คนมองกรอบอยู่แล้ว ไม่ต้องละสายตาไปอ่านป้าย */
.pf-cam:has(.pf-cam-live[data-on="1"]) .pf-cam-guide { border-color: var(--pf-success-500); }

/* ═══════════════════════════════════════════════════════════════════════════
   หน้าลงเวลาของฉัน — ★★★★ หน้าที่คนแตะทุกวัน และมักแตะตอน **ยืนอยู่หน้างาน**
     มือถือ · แสงแดด · มองจอแวบเดียว · บางคนกดขณะสะพายของ

   🔴 ทำไมหน้านี้ถึงต้องมีบล็อกของตัวเอง แทนที่จะเรียงด้วย `.pf-kv` เหมือนหน้าอื่น
     เจ้าของทักเอง (2026-09-01): "หน้าลงเวลามันแปลกและดูยาก ทำให้สับสน"
     ของเดิมเรียงทุกอย่างเป็นแถวป้าย–ค่า ซึ่งอ่านดีตอน **นั่งอ่าน** แต่หน้านี้ไม่มีใครนั่งอ่าน
     ★ สิ่งที่ต้องเห็นภายในหนึ่งวินาที มีแค่สามอย่าง — **ตอนนี้กี่โมง · ฉันสแกนอะไรไปแล้ว ·
       ปุ่มไหนคือปุ่มของฉันตอนนี้** · ที่เหลือเป็นรายละเอียดที่ค่อยอ่านทีหลังได้

   ⚠⚠ ทั้งหมดนี้ยกโครงจาก **ระบบเดิมของเจ้าของ** ที่พนักงานใช้อยู่ทุกวันจริง ๆ
     ไม่ใช่โครงที่คิดใหม่ · ของที่คนคุ้นมือแล้วมีค่ามากกว่าของที่สวยกว่าแต่ต้องเรียนใหม่
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── นาฬิกาใหญ่ ──────────────────────────────────────────────────────────
   ★★★★ เวลาที่แสดงคือ **นาฬิกาของเซิร์ฟเวอร์** ไม่ใช่ของเครื่องผู้ใช้
     หน้านี้เขียนไว้เองว่า "ระบบใช้เวลาของเซิร์ฟเวอร์" — โชว์เวลาเครื่องเมื่อไหร่
     คนที่ตั้งเวลาเครื่องเพี้ยนจะเห็นว่าตัวเองมาทัน ทั้งที่ระบบบันทึกว่าสาย
   ⚠ ไล่เฉดวิ่งระหว่าง primary-700 → primary-500 เท่านั้น
     ทั้งสามเฉดถูกประกาศคู่กับ `--pf-on-primary` ใน `theme-check` แล้ว
     ⇒ เปลี่ยนไปใช้เฉดอื่นเมื่อไหร่ ต้องไปเพิ่มคู่ที่นั่นด้วย ไม่งั้นไม่มีใครวัดให้ */
.pf-timecard {
  display: grid; justify-items: center; gap: var(--pf-space-2);
  padding: var(--pf-space-7) var(--pf-space-5);
  border-radius: var(--pf-radius-lg);
  color: var(--pf-on-primary);
  background: var(--pf-primary-600);
  background-image: linear-gradient(135deg, var(--pf-primary-700), var(--pf-primary-500));
}
.pf-timecard-time {
  /* ★ ใหญ่จริง — ตัวเลขนี้คือสิ่งที่คนมองก่อนอย่างอื่นทั้งหมด
     ⚠ `tabular-nums` บังคับ ไม่งั้นความกว้างขยับทุกวินาที แล้วทั้งการ์ดสั่น */
  font-size: clamp(2.5rem, 12vw, 3.75rem);
  font-weight: var(--pf-w-bold);
  font-variant-numeric: tabular-nums;
  line-height: 1.05; letter-spacing: -0.02em;
}
.pf-timecard-date { font-size: var(--pf-size-body); opacity: 0.92; }

/* ── แถบตัวตน · สแกนล่าสุด ───────────────────────────────────────────────
   ★★ ทำไมต้องมีชื่อตัวเองบนหน้านี้ ทั้งที่ล็อกอินมาแล้ว
     ระบบเดิมมี และมันทำงานจริง — เครื่องที่วางไว้หน้าออฟฟิศมีคนใช้ต่อกันหลายคน
     ⚠ กดผิดบัญชีคนอื่น = เวลาเข้างานของคนสองคนผิดพร้อมกัน และไม่มีใครรู้จนสิ้นเดือน */
.pf-who { display: flex; align-items: center; gap: var(--pf-space-4); flex-wrap: wrap; }
.pf-who-id { display: grid; gap: 2px; min-inline-size: 0; flex: 1; }
.pf-who-name { font-weight: var(--pf-w-semibold); }
/* ★ ชิปสแกนล่าสุด — ชิดขวาบนจอกว้าง · ตกลงมาเองบนมือถือด้วย `flex-wrap` ข้างบน */
.pf-who-last {
  display: grid; gap: 2px; justify-items: end;
  padding: var(--pf-space-2) var(--pf-space-3);
  border-radius: var(--pf-radius-md);
  background: var(--pf-surface-subtle);
}

/* ── แถบ "วันนี้ทำงานไปแล้วเท่าไหร่" ──────────────────────────────────── */
.pf-worked {
  display: flex; align-items: center; justify-content: center; gap: var(--pf-space-2);
  padding: var(--pf-space-3);
  border-radius: var(--pf-radius-md);
  background: var(--pf-success-50); color: var(--pf-success-700);
  font-size: var(--pf-size-body);
}
.pf-worked b { font-variant-numeric: tabular-nums; }

/* ── ปุ่มเข้า/ออก ─────────────────────────────────────────────────────────
   ★★★★ **สองปุ่มคู่กันเสมอ** แม้กดได้ทีละปุ่ม — ตามระบบเดิมที่เจ้าของใช้อยู่
     ⚠⚠ ซ่อนปุ่มที่ยังไม่ถึงคิว = คนอ่านว่า "ระบบไม่มีปุ่มออกงาน" แล้วโทรถาม HR
       ของเดิมที่นี่เคยเป็นปุ่มเดียวที่เปลี่ยนคำบนตัวมันเอง ซึ่งอ่านไม่ออกว่ากำลังจะทำอะไร
   ★ ปุ่มที่ถึงคิวเป็นพื้นแบรนด์ + ป้าย "แนะนำ" · อีกปุ่มเป็นการ์ดขอบธรรมดา
     ⚠ ปุ่มที่กดไม่ได้ **ต้องบอกเหตุผล** ไม่ใช่จางเฉย ๆ (P6) — เทมเพลตใส่ `title` ให้ */
/* ⚠⚠⚠ **ชื่อพวกนี้ยาวกว่าที่ควร เพราะ `.pf-punch` กับ `.pf-clock` มีเจ้าของอยู่แล้ว**
   บรรทัด 921–935 เป็นของเดโม Kiosk ใน `/design/checkin.html` (ปุ่มเดี่ยวเต็มความกว้าง)
   ⚠ รอบแรกผมตั้งชื่อชนกันเป๊ะโดยไม่ได้ grep ก่อน · อาการที่ได้คือ **สีที่มาจากไหนไม่รู้**
     (`.pf-clock-date` ของเก่าตั้ง `color: --pf-text-muted` ⇒ วันที่บนการ์ดสีแบรนด์
      กลายเป็นเทาอ่านไม่ออก) และ `npm run hex/theme/contrast` **จับไม่ได้เลยสักตัว**
      เพราะทั้งสองกฎถูกต้องในตัวเอง ผิดแค่ตอนมาเจอกัน
   ★ ก่อนตั้งชื่อคลาสใหม่ทุกครั้ง: `grep -c "<ชื่อ>" styles/pf.css` ต้องได้ 0 */
.pf-punch-pair { display: grid; grid-template-columns: 1fr 1fr; gap: var(--pf-space-4); }
.pf-punch-tile {
  position: relative;
  display: grid; justify-items: center; gap: var(--pf-space-3);
  /* ★ เว้นหัวไว้ให้ป้าย "แนะนำ" — ไม่เว้นแล้วป้ายทับชิปไอคอนบนจอ 375 */
  padding: var(--pf-space-6) var(--pf-space-3) var(--pf-space-5);
  border: 1px solid var(--pf-border); border-radius: var(--pf-radius-lg);
  background: var(--pf-surface); color: var(--pf-text-primary);
  font-family: inherit; font-size: var(--pf-size-body); font-weight: var(--pf-w-semibold);
  cursor: pointer;
  transition: background var(--pf-duration-standard) var(--pf-ease-out),
              border-color var(--pf-duration-standard) var(--pf-ease-out);
}
.pf-punch-tile:hover:not(:disabled) { background: var(--pf-surface-hover); }
/* ⚠ ไม่เขียน `:focus-visible` เอง — `pf.css` มีกฎกลางที่บรรทัด 446 ให้ทุกอย่างแล้ว
   เขียนซ้ำเมื่อไหร่ = วันที่ใครแก้กฎกลาง ปุ่มนี้จะเป็นปุ่มเดียวที่ไม่เปลี่ยนตาม */
/* ★ ปุ่มที่ถึงคิว — พื้นแบรนด์ · คู่สีนี้ประกาศไว้ที่ `theme-check` แล้ว */
.pf-punch-tile[data-suggest="1"] {
  border-color: var(--pf-primary-600);
  background: var(--pf-primary-500); color: var(--pf-on-primary);
}
.pf-punch-tile[data-suggest="1"]:hover:not(:disabled) { background: var(--pf-primary-600); }
/* ⚠ จางลงแต่ **ยังอ่านออก** — ปุ่มที่หายไปกับปุ่มที่กดไม่ได้ต้องดูต่างกัน */
.pf-punch-tile:disabled { opacity: 0.55; cursor: not-allowed; }
/* ★ ชิปไอคอน — สีความหมายอยู่ที่ชิป ไม่ใช่ที่พื้นปุ่ม
   ⚠⚠ ทำพื้นปุ่มเป็นเขียว/แดงเต็มใบไม่ได้ · ระบบยังไม่มีคู่ `--pf-on-success`
     ที่วัดแล้วผ่านทั้งสองธีม — ใช้คู่ชิปพาสเทลที่ `theme-check` ตรวจอยู่แล้วแทน */
.pf-punch-tile-ic {
  display: grid; place-items: center;
  inline-size: 44px; block-size: 44px; border-radius: var(--pf-radius-md);
}
/* ★★ ตรึงขนาดไอคอนที่ **ชิป** ไม่ใช่ที่ตัวไอคอน — แอปจริงใช้ `<pf-icon>` (คลาส .ic)
   ส่วนแกลเลอรีวาดเส้นซ้ำเป็น `.pf-ico` เพราะหน้าดีไซน์ไม่ได้ฝังสไปรท์
   ⚠ ไม่ตรึงที่นี่ = สองที่ได้ขนาดคนละอย่าง แล้ว "ดีไซน์" กับ "ของจริง" ไม่ตรงกันเงียบ ๆ */
.pf-punch-tile-ic svg { inline-size: 22px; block-size: 22px; }
.pf-punch-tile-ic[data-tone="in"]  { background: var(--pf-pastel-green); color: var(--pf-success-700); }
.pf-punch-tile-ic[data-tone="out"] { background: var(--pf-pastel-pink);  color: var(--pf-danger-700); }
.pf-punch-tile-tag {
  position: absolute; inset-block-start: var(--pf-space-2); inset-inline-end: var(--pf-space-2);
  padding: 2px var(--pf-space-2); border-radius: var(--pf-radius-pill);
  font-size: var(--pf-size-caption); font-weight: var(--pf-w-semibold);
  background: var(--pf-surface); color: var(--pf-primary-ink);
}

/* ═══════════════════════════════════════════════════════════════════════════
   บันทึกการเข้า-ออก — **หนึ่งแถวต่อคนต่อวัน** กางออกดูรูปได้ (053 · 2026-09-01)

   🔴 เจ้าของส่งภาพระบบเดิมมาแล้วบอกว่า
     *"1 คน มันจะเป็น 1 แถว/วัน และค่อยเปิดการ์ดเอาแทนถ้าจะดูรูป"*
   ★★★★ และเขาถูก — รายการดิบเรียงตามเวลาทำให้คนคนเดียวกันกระจายอยู่คนละที่
     HR ที่อยากรู้ว่า "วันนี้เก้าเข้ากี่โมงออกกี่โมง" ต้องไล่หาสองแถวที่ห่างกันหลายสิบบรรทัด

   ⚠ ระบบเดิมของเขามีสี่การ์ด (เข้า · เริ่มพัก · เลิกพัก · ออก) — **ของเรามีสอง**
     เพราะระบบนี้ยังไม่บันทึกเวลาพัก · ทำการ์ดว่างขึ้นมาให้ครบสี่ = โกหกว่ามีฟีเจอร์นั้น
   ═══════════════════════════════════════════════════════════════════════════ */
.pf-daylog { display: grid; gap: var(--pf-space-3); }
.pf-daylog-row {
  border: 1px solid var(--pf-border); border-radius: var(--pf-radius-lg);
  background: var(--pf-surface);
  overflow: hidden;
}
/* ★ ทั้งหัวแถวเป็นปุ่มเปิด/ปิด — เป้าสัมผัสใหญ่กว่าลูกศรเล็ก ๆ มุมขวามาก */
.pf-daylog-head {
  inline-size: 100%; display: flex; align-items: center; gap: var(--pf-space-4);
  padding: var(--pf-space-4) var(--pf-space-5);
  border: 0; background: transparent; color: inherit;
  font-family: inherit; font-size: var(--pf-size-body); text-align: start;
  cursor: pointer;
}
.pf-daylog-head:hover { background: var(--pf-surface-hover); }
.pf-daylog-who { flex: 1; min-inline-size: 0; }
/* ★★ ก้อนเวลา — อยู่ด้วยกันเสมอ จะได้ตกลงมาทั้งก้อนบนจอแคบ ไม่ใช่แตกทีละชิ้น */
.pf-daylog-times { display: flex; align-items: center; gap: var(--pf-space-4); }
.pf-daylog-name { font-weight: var(--pf-w-semibold); }
/* ★ ช่องเวลา — ตรึงความกว้างให้ตรงกันทุกแถว ตาจะกวาดลงมาเป็นคอลัมน์ได้
   ⚠ ไม่ตรึง = เวลาขยับซ้ายขวาตามความยาวชื่อ แล้วอ่านเทียบกันไม่ได้เลย */
.pf-daylog-time { min-inline-size: 76px; text-align: end; }
.pf-daylog-time b { font-variant-numeric: tabular-nums; }
.pf-daylog-net {
  min-inline-size: 84px; text-align: center;
  padding: var(--pf-space-2) var(--pf-space-3); border-radius: var(--pf-radius-pill);
  background: var(--pf-pastel-brand); color: var(--pf-primary-ink);
  font-size: var(--pf-size-caption); font-weight: var(--pf-w-semibold);
  font-variant-numeric: tabular-nums;
}
/* ★★ การ์ดที่กางออกมา — สองใบ (เข้า · ออก) ไม่ใช่สี่ใบแบบระบบเดิม
   ⚠ ระบบนี้ยังไม่บันทึกเวลาพัก · การ์ดว่างสองใบคือการโฆษณาฟีเจอร์ที่ไม่มี */
.pf-daylog-body {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--pf-space-4);
  padding: 0 var(--pf-space-5) var(--pf-space-5);
}
.pf-daylog-card {
  display: grid; justify-items: center; gap: var(--pf-space-3);
  padding: var(--pf-space-4);
  border: 1px solid var(--pf-border); border-radius: var(--pf-radius-lg);
}
/* ★ แถบสีบนหัวการ์ด — เข้า/ออก แยกด้วยสี ตาจับได้ก่อนอ่านตัวอักษร */
.pf-daylog-card[data-tone="in"]  { border-block-start: 3px solid var(--pf-success-500); }
.pf-daylog-card[data-tone="out"] { border-block-start: 3px solid var(--pf-danger-500); }
.pf-daylog-card-title {
  font-size: var(--pf-size-caption); font-weight: var(--pf-w-semibold);
}
.pf-daylog-card[data-tone="in"]  .pf-daylog-card-title { color: var(--pf-success-700); }
.pf-daylog-card[data-tone="out"] .pf-daylog-card-title { color: var(--pf-danger-700); }
/* ✳ 2026-09-02 — เลิกใช้ `<img>` เปล่า ๆ · รูปวางทับอวตารแทน (ดูเหตุผลที่ `.pf-daylog-face`)
   ⚠ คลาสนี้ยังอยู่เพราะหน้าดีไซน์ใช้แสดงตัวอย่าง "ใบที่มีภาพ" · แอปจริงไม่ใช้แล้ว */
.pf-daylog-shot {
  inline-size: 96px; block-size: 96px; border-radius: 50%;
  object-fit: cover; background: var(--pf-bg-secondary);
  border: 2px solid var(--pf-border);
}
/* ★ ไม่มีการสแกนเลย (เช่นยังไม่ออกงาน) — วงกลมว่างที่บอกตรง ๆ ว่าไม่มี
   ⚠ ช่องว่างเปล่า ๆ อ่านว่า "ยังโหลดอยู่" ซึ่งทำให้คนรอของที่ไม่มีวันมา */
.pf-daylog-noshot {
  display: grid; place-items: center;
  inline-size: 96px; block-size: 96px; border-radius: 50%;
  background: var(--pf-surface-subtle); color: var(--pf-text-muted);
  font-size: var(--pf-size-caption); text-align: center;
}
/* ★★★★ **มีการสแกน แต่ไม่มีภาพ** → อวตารตัวอักษรย่อ (เจ้าของสั่ง 2026-09-02)
   ⚠⚠ ต่างจาก `.pf-daylog-noshot` ข้างบนคนละความหมาย และห้ามใช้สลับกัน
     `noshot` = ไม่มีเหตุการณ์เลย · `face` = มีเหตุการณ์ แต่ไม่มีภาพของมัน
   ⚠⚠⚠ **ห้ามเอารูปโปรไฟล์ของพนักงานมาใส่ตรงนี้** — ช่องนี้อยู่ในบริบท
     "หลักฐานว่าใครมาสแกน" · รูปโปรไฟล์จะถูกอ่านว่าเป็นภาพที่ถ่ายตอนสแกน ซึ่งไม่จริง
     ★ ตัวอักษรย่อบอกว่า "คนนี้แหละ แต่ไม่มีภาพของการสแกนนี้" — จริงทั้งสองท่อน */
.pf-daylog-face {
  inline-size: 96px; block-size: 96px;
  font-size: var(--pf-size-section);
  /* ★ เส้นประ — แยกจากอวตารปกติด้วยสายตา ว่านี่คือ "ที่ที่ควรมีภาพแต่ไม่มี"
     ⚠ ใช้เส้นทึบเหมือนอวตารทั่วไป = ดูเหมือนตั้งใจให้เป็นแบบนี้ ซึ่งไม่ใช่ */
  border: 2px dashed var(--pf-border-strong);
}
/* ★★★★ มีภาพ → เส้นทึบ + กดได้
   🔴 บั๊กที่เจ้าของเจอ 2026-09-02: `<img>` เปล่า ๆ ที่โหลดไม่ขึ้น **โชว์ alt text ล้นวงกลม**
     ⇒ ย้ายมาวางรูปทับอวตาร (เทคนิคเดียวกับ `.pf-avatar img` ที่ D30 บังคับไว้)
       รูปโหลดไม่ขึ้น → เบราว์เซอร์ไม่วาด `<img>` → เห็นตัวอักษรย่อที่อยู่ข้างใต้พอดี
   ⚠ `overflow: hidden` มาจาก `.pf-avatar` อยู่แล้ว — ข้อความ alt จึงไม่มีทางล้นออกมาอีก */
.pf-daylog-face[data-shot="1"] {
  border-style: solid; border-color: var(--pf-border);
  cursor: pointer;
}
.pf-daylog-at { font-size: var(--pf-size-section); font-weight: var(--pf-w-bold);
  font-variant-numeric: tabular-nums; }
/* ★ ปุ่มท้ายการ์ด — ชิดขวาเหมือนแถวปุ่มในกล่องซ้อน จะได้อ่านเป็นแบบแผนเดียวกัน */
.pf-daylog-acts {
  display: flex; gap: var(--pf-space-3); justify-content: flex-end; flex-wrap: wrap;
  padding: 0 var(--pf-space-5) var(--pf-space-5);
}

/* ★★★★ จอแคบ — ชื่อขึ้นบรรทัดของตัวเอง แล้วเวลาตกลงมาทั้งแถว
   🔴 ไม่มีกฎนี้ ชื่อไทยยาว ๆ จะถูกบีบจนตัดขึ้นบรรทัดใหม่ **ทีละตัวอักษร**
     (วัดจริงที่ 375px: "กันตพัฒน์ ธนาพิพัฒน์" กลายเป็นห้าบรรทัด)
   ⚠ และป้ายสุทธิจะล้นออกนอกการ์ดไปทางขวา */
@media (max-width: 560px) {
  .pf-daylog-head { flex-wrap: wrap; }
  .pf-daylog-who { flex: 1 1 auto; }
  .pf-daylog-times {
    flex: 1 1 100%; justify-content: space-between;
    gap: var(--pf-space-3);
  }
  /* ★ ชิดซ้ายบนจอแคบ — คอลัมน์ชิดขวาไม่มีความหมายแล้วเมื่อมันขึ้นบรรทัดใหม่ */
  .pf-daylog-time { min-inline-size: 0; text-align: start; }
  .pf-daylog-net { min-inline-size: 0; }
  /* ★★ ลูกศรกาง/พับ ขึ้นไปอยู่กับชื่อ ไม่ใช่ห้อยอยู่บรรทัดล่างคนเดียว
     ⚠ ปล่อยไว้ = มันอยู่ใต้แถวเวลาแบบลอย ๆ ซึ่งอ่านไม่ออกว่าเป็นของแถวไหน */
  .pf-daylog-head > pf-icon { order: 1; margin-inline-start: auto; }
  .pf-daylog-times { order: 2; }
}

/* ★★★ ภาพตอนสแกนในกล่องดูภาพของ HR (053 · 2026-09-01)
   ⚠ จำกัดความสูงไว้ — ภาพจตุรัส 256px ที่ยืดเต็มกล่อง 480px จะเบลอโดยไม่ได้อะไรเพิ่ม
   ★ พื้นเทาไว้ให้เห็นขอบภาพ เวลาภาพมืดหรือพื้นหลังขาว */
.pf-scan-shot {
  display: block; margin-inline: auto;
  inline-size: 100%; max-inline-size: 256px;
  aspect-ratio: 1; object-fit: cover;
  border-radius: var(--pf-radius-lg);
  background: var(--pf-bg-secondary);
}

/* ── ประวัติของวันนี้ ────────────────────────────────────────────────────
   ★ รายการสั้น ๆ ที่ตอบคำถามเดียว: "เมื่อกี้ฉันกดไปแล้วใช่ไหม"
   ⚠ กดซ้ำเพราะไม่แน่ใจ คือที่มาของรายการซ้ำที่ HR ต้องมานั่งลบ */
.pf-punch-log { display: grid; gap: var(--pf-space-2); margin: 0; padding: 0; list-style: none; }
.pf-punch-log li {
  display: flex; align-items: center; gap: var(--pf-space-3);
  padding: var(--pf-space-3);
  border-radius: var(--pf-radius-md);
  background: var(--pf-surface-subtle);
}
.pf-punch-log time { font-weight: var(--pf-w-semibold); font-variant-numeric: tabular-nums; }
/* ★ ป้ายยืนยันใบหน้าไปชิดขวา — เป็นข้อมูลรอง ไม่ใช่หัวเรื่องของแถว */
.pf-punch-log .pf-badge { margin-inline-start: auto; }

/* ★★ มือถือ — ปุ่มยังต้องอยู่คู่กัน แต่บีบระยะลง (จอ 360 กว้างพอสำหรับสองปุ่ม)
   ⚠ ดันเป็นคอลัมน์เดียวเมื่อไหร่ ปุ่ม "ออกงาน" จะตกใต้เส้นพับ แล้วคนหาไม่เจอ */
@media (max-width: 480px) {
  .pf-punch-pair { gap: var(--pf-space-3); }
  .pf-punch-tile { padding: var(--pf-space-4) var(--pf-space-2); }
  .pf-who-last { margin-inline-start: auto; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   รายการขั้นตอนที่ต้องทำให้ครบก่อน (ด่านก่อนลงเวลา · 2026-09-01)

   ★★★ ใช้กับ "ยังตั้งค่าไม่ครบ" ที่ต้องบอกว่า **เหลืออะไร และใครต้องทำ**
     ⚠ ไม่ใช่ progress bar — ขั้นพวกนี้ทำข้ามลำดับได้ และบางขั้นเจ้าตัวทำเองไม่ได้
       แถบความคืบหน้าจะโกหกว่าต้องทำเรียงกันและใกล้เสร็จแล้ว

   ★ `data-state` มาจากสถานะของตรรกะกลาง (`ok` · `warn` · `blocked`) ไม่ใช่จากลำดับข้อ
     ⚠ ผูกสีกับลำดับเมื่อไหร่ ข้อที่ผ่านแล้วจะยังดูเหมือนค้างอยู่
   ═══════════════════════════════════════════════════════════════════════════ */
.pf-steps {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: var(--pf-space-3);
  counter-reset: pf-step;
}
.pf-step {
  position: relative;
  display: flex; align-items: center; gap: var(--pf-space-4);
  padding: var(--pf-space-4);
  padding-inline-start: calc(var(--pf-space-4) + 34px);
  border: 1px solid var(--pf-border);
  border-radius: var(--pf-radius-md);
  background: var(--pf-surface);
  min-inline-size: 0;
}
/* ★★ ตัวเลขลำดับ — ที่นี่ **ลำดับมีความหมายจริง** (ระบุตัวตนได้ → รู้ที่ → รู้เวลา)
   จึงใส่เลขได้โดยไม่ผิดกฎ "อย่าใส่เลขให้ของที่ไม่ได้เรียงกัน" */
.pf-step::before {
  counter-increment: pf-step;
  content: counter(pf-step);
  position: absolute; inset-inline-start: var(--pf-space-4);
  inline-size: 22px; block-size: 22px;
  display: grid; place-items: center;
  border-radius: 50%;
  font-size: var(--pf-size-caption); font-weight: var(--pf-w-semibold);
  /* ⚠ ใช้ `--pf-text-secondary` ไม่ใช่ `--pf-text-muted` — คู่ muted บน gray-100
     ได้ 4.40:1 ในธีมมืด ซึ่งต่ำกว่าเกณฑ์ 4.5 (npm run contrast จับได้)
     ★ ตัวเลขนี้เป็นข้อมูล ไม่ใช่ของตกแต่ง — ต้องอ่านออกทั้งสองธีม */
  background: var(--pf-gray-100); color: var(--pf-text-secondary);
}
.pf-step[data-state="ok"] { border-color: var(--pf-success-100); }
.pf-step[data-state="ok"]::before {
  background: var(--pf-success-50); color: var(--pf-success-700);
}
.pf-step[data-state="warn"] { border-color: var(--pf-warning-100); }
.pf-step[data-state="warn"]::before {
  background: var(--pf-warning-50); color: var(--pf-warning-700);
}
/* ⚠ ขั้นที่ยังกั้นอยู่ต้องเด่นที่สุด — มันคือเหตุผลเดียวที่การ์ดนี้ขึ้นมา */
.pf-step[data-state="blocked"] { border-color: var(--pf-danger-100); }
.pf-step[data-state="blocked"]::before {
  background: var(--pf-danger-50); color: var(--pf-danger-700);
}

/* ═══ ≤560px · ป้ายลงบรรทัดใหม่ ไม่บีบข้อความจนอ่านไม่ออก ═══ */
@media (max-width: 560px) {
  .pf-step { flex-wrap: wrap; row-gap: var(--pf-space-2); }
}

/* ═══════════════════════════════════════════════════════════════════════════
   ผังองค์กร (P17 ② · D46 ③ · 2026-08-31)

   ★★★ เป็น **รายการที่ย่อหน้าตามชั้น** ไม่ใช่กล่องเรียงแนวนอนแบบผังบริษัททั่วไป
     เหตุผลไม่ใช่ความสวย: องค์กรจริงที่วัดมามี 38 คน · ผังแนวนอนที่ระดับนั้น
     กว้างเกินจอมือถือทุกเครื่อง แล้วต้องลากสองแกน ซึ่งอ่านไม่ออกจริง ๆ
     ★ ย่อหน้าตามชั้น = เลื่อนแกนเดียว · อ่านออกตั้งแต่จอ 375px

   ★ ระดับชั้นส่งมาทาง `--pf-depth` (ตัวเลขล้วน) แล้ว CSS คูณระยะเอง
     ⚠ ห้ามส่ง `padding-left: 52px` มาจากหน้าจอ — ระยะย่อหน้าเป็นเรื่องของ CSS
       และมือถือใช้ระยะไม่เท่าเดสก์ท็อป (ดู @media ข้างล่าง)
   ═══════════════════════════════════════════════════════════════════════════ */
.pf-tree { --pf-tree-step: 26px; display: flex; flex-direction: column; }
.pf-tree-row {
  position: relative;
  display: flex; align-items: center; gap: var(--pf-space-4);
  min-inline-size: 0;
  padding: var(--pf-space-3) var(--pf-space-4);
  padding-inline-start: calc(var(--pf-space-4) + var(--pf-depth, 0) * var(--pf-tree-step));
  border-radius: var(--pf-radius-md);
}
.pf-tree-row:hover { background: var(--pf-gray-50); }

/* ★★ เส้นโยงรูปตัว L — บอกว่า "ขึ้นกับคนข้างบน" โดยไม่ต้องเขียนคำว่าขึ้นกับใคร
   ⚠⚠ ผูกกับคลาส `--under` ไม่ใช่กับค่า `--pf-depth` ที่อ่านจากสตริงของ `style`
     ตัวเลือกแบบ `[style*="--pf-depth: 0"]` ขึ้นกับ **วิธีที่เบราว์เซอร์เรียงสตริง**
     ซึ่งไม่มีใครรับประกัน (ช่องว่างหาย = เส้นโผล่ที่คนบนสุด โดยไม่มีอะไรฟ้อง) */
.pf-tree-row--under::before {
  content: ''; position: absolute; pointer-events: none;
  inset-block: 0 50%;
  inset-inline-start: calc(var(--pf-space-4) + (var(--pf-depth, 0) - 1) * var(--pf-tree-step) + 13px);
  inline-size: 12px;
  border-inline-start: 1px solid var(--pf-border);
  border-block-end: 1px solid var(--pf-border);
  border-end-start-radius: var(--pf-radius-xs);
}

/* ═══ กล่องตำแหน่งที่ยังไม่มีคน (D53 ⑧) ══════════════════════════
   ★★ เจ้าของสั่งให้กล่องผู้บริหารสูงสุด **ลอยไว้** แม้ยังไม่มีคน — ตำแหน่งนี้จำเป็นต่อการอ่านผัง
   ⚠⚠ วาดเป็นแถวธรรมดาสีจาง = อ่านว่า "มีคนชื่อว่างเปล่า" · เส้นประคือสิ่งที่อ่านว่า "ยังไม่มีตัวจริง" */
.pf-tree-row--vacant {
  border: 1px dashed var(--pf-border-strong);
  background: var(--pf-gray-50);
}
.pf-tree-row--vacant .pf-avatar {
  background: transparent; color: var(--pf-text-muted);
  border: 1px dashed var(--pf-border-strong);
}
/* ⚠ ชื่อตำแหน่งในกล่องว่างไม่ใช่ชื่อคน — น้ำหนักเท่าชื่อคนจะอ่านสลับกัน */
.pf-tree-row--vacant .pf-person-name { color: var(--pf-text-secondary); font-weight: 500; }

/* ⚠ `.sp` ไม่มีผลเองทั้งไฟล์ — ทุกที่ประกาศเฉพาะในกล่องของตัวเอง (หัวหน้า · หัวการ์ด)
   ไม่ประกาศที่นี่ = ป้ายกับปุ่มบนบรรทัดที่สองของจอแคบไปกองชิดซ้ายใต้ชื่อ */
.pf-tree-row > .sp { flex: 1; }

/* ★★ กลุ่ม "รูป + ชื่อ + ตำแหน่ง" เป็นก้อนเดียว — เพื่อให้ **ทั้งก้อน** ลงบรรทัดใหม่
   พร้อมกันตอนจอแคบ (ดู @media ข้างล่าง) ⚠ ปล่อยให้รูปกับชื่อแยกกันเมื่อไหร่
   รูปจะค้างอยู่บรรทัดบนคนเดียว แล้วอ่านไม่ออกว่ารูปนี้ของใคร */
.pf-tree-who {
  display: flex; align-items: center; gap: var(--pf-space-4);
  flex: 1 1 auto; min-inline-size: 0;
}
/* ⚠ ชื่อในผัง **ห้ามตัดขึ้นบรรทัดใหม่** — ป้ายนับคนกับปุ่มกินความกว้างคงที่
   ชื่อไทยยาว ๆ จะถูกบีบจนเหลือบรรทัดละสองตัวอักษร แถวสูงสามเท่า อ่านไม่ออก */
.pf-tree-who .pf-person-name {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ★ ตำแหน่ง/แผนก — ตัดท้ายเมื่อแคบ **ไม่ซ่อน**
   ⚠ ซ่อนทิ้งที่จอแคบ = มือถืออ่านไม่ออกว่าใครทำอะไร ซึ่งคือคำถามหลักของหน้านี้ */
.pf-tree-meta {
  color: var(--pf-text-muted); font-size: var(--pf-size-caption);
  min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ═══ ≤560px · ย่อระยะย่อหน้าลง — ชั้นที่ 5 ต้องยังเห็นชื่อคน ═══ */
@media (max-width: 560px) {
  .pf-tree { --pf-tree-step: 14px; }
  /* ★★★ จอแคบ: ชื่อกินเต็มบรรทัด · ป้ายนับคนกับปุ่มลงบรรทัดที่สอง
     ⚠ ยัดทุกอย่างในบรรทัดเดียวที่ 375px = ชื่อเหลือราว 100px ซึ่งตัดจนไม่รู้ว่าใคร */
  .pf-tree-row { flex-wrap: wrap; row-gap: var(--pf-space-2); }
  .pf-tree-who { flex-basis: 100%; }
  /* ⚠ เส้นโยงต้องสูงถึงกึ่งกลาง **บรรทัดแรก** ไม่ใช่กึ่งกลางแถวสองบรรทัด */
  .pf-tree-row--under::before { inset-block: 0 auto; block-size: 26px; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   ผังองค์กรแบบ **กล่องกับกิ่ง** (เจ้าของขอ 2026-09-05 · คู่กับ .pf-tree)

   ★★★ ทำไมมีสองแบบ ไม่ใช่แบบเดียว
     `.pf-tree` (ย่อหน้าตามชั้น) อ่านได้บนจอ 375px แม้มีคน 38 คน — แต่ตอบไม่ได้ว่า
     "องค์กรนี้มีกี่สาย" เพราะทุกคนเรียงลงมาเป็นเส้นเดียว
     `.pf-orgtree` ตอบข้อนั้นได้ทันทีด้วยตา แต่กว้างตามจำนวนพี่น้องในชั้นที่กว้างที่สุด
   ⇒ เจ้าของเคาะให้มีปุ่มสลับ · ค่าตั้งต้นเป็นกล่อง เพราะเป็นสิ่งที่คนเปิดหน้านี้มาหา

   ★★★★ เส้นกิ่งมาจาก `::before`/`::after` ของ **ลูก** ไม่ใช่ของพ่อ
     ⚠ วาดจากพ่อ = ต้องรู้ความกว้างรวมของลูกทุกคน ซึ่ง CSS ตอบไม่ได้
     ⇒ ลูกแต่ละคนวาดเส้นตั้งของตัวเอง + ครึ่งเส้นนอน แล้วตัวแรก/ตัวสุดท้ายตัดครึ่งทิ้ง
       (กฎฐานต้องมาก่อนตัวปรับเสมอ ไม่งั้นตัวปรับถูกทับ)

   ⚠ กว้างเกินจอเป็นเรื่องปกติของผังแบบนี้ — **เลื่อนได้** ไม่ใช่บีบให้พอดี
     (เหตุผลเดียวกับตารางพนักงานที่ตรึงคอลัมน์แรกแทนการซ่อนคอลัมน์)
   ═══════════════════════════════════════════════════════════════════════════ */
.pf-orgtree {
  --pf-org-gap: 26px;          /* ระยะระหว่างชั้น = ความยาวเส้นตั้ง */
  --pf-org-box: 168px;
  overflow-x: auto;
  padding: var(--pf-space-4) var(--pf-space-2) var(--pf-space-5);
}
/* ★ ดันให้อยู่กึ่งกลางตอนแคบกว่าจอ แต่ยังเลื่อนได้ตอนกว้างกว่า
   ⚠ `justify-content: center` บนตัวที่ scroll เอง จะตัดฝั่งซ้ายจนเลื่อนไปไม่ถึง */
.pf-orgtree > .pf-orgnode { min-inline-size: max-content; margin-inline: auto; }

.pf-orgnode { display: flex; flex-direction: column; align-items: center; }

.pf-orgkids {
  display: flex; justify-content: center; align-items: flex-start;
  position: relative; margin-block-start: var(--pf-org-gap);
}
/* เส้นตั้งจากกล่องพ่อลงมาหาเส้นนอนของลูก */
.pf-orgkids::before {
  content: ''; position: absolute;
  inset-block-start: calc(var(--pf-org-gap) * -1); inset-inline-start: 50%;
  inline-size: 1px; block-size: var(--pf-org-gap);
  background: var(--pf-border-strong);
}
/* ★ ชั้นบนสุดไม่มีพ่อ — ไม่มีเส้นห้อยลงมาจากที่ว่าง */
.pf-orgkids--top { margin-block-start: 0; }
.pf-orgkids--top::before { content: none; }

.pf-orgkids > .pf-orgnode {
  position: relative;
  padding-block-start: var(--pf-org-gap);
  padding-inline: var(--pf-space-2);
}
/* เส้นตั้งขึ้นไปหาเส้นนอน */
.pf-orgkids > .pf-orgnode::before {
  content: ''; position: absolute;
  inset-block-start: 0; inset-inline-start: 50%;
  inline-size: 1px; block-size: var(--pf-org-gap);
  background: var(--pf-border-strong);
}
/* เส้นนอนเชื่อมพี่น้อง — เต็มความกว้างของตัวเอง แล้วหัวท้ายตัดครึ่ง */
.pf-orgkids > .pf-orgnode::after {
  content: ''; position: absolute;
  inset-block-start: 0; inset-inline: 0;
  block-size: 1px; background: var(--pf-border-strong);
}
.pf-orgkids > .pf-orgnode:first-child::after { inset-inline-start: 50%; }
.pf-orgkids > .pf-orgnode:last-child::after { inset-inline-end: 50%; }
/* ⚠ ลูกคนเดียวไม่มีพี่น้องให้เชื่อม — เหลือเส้นนอนไว้จะเป็นขีดสั้น ๆ ลอยข้างกล่อง */
.pf-orgkids > .pf-orgnode:only-child::after { content: none; }

/* ═══ กล่องหนึ่งคน ═══════════════════════════════════════════════════════ */
.pf-orgbox {
  inline-size: var(--pf-org-box);
  display: flex; flex-direction: column; align-items: center; gap: var(--pf-space-2);
  padding: var(--pf-space-3);
  background: var(--pf-surface);
  border: 1px solid var(--pf-border);
  border-radius: var(--pf-radius-md);
  text-align: center;
}
.pf-orgbox:hover { border-color: var(--pf-border-strong); }
/* ⚠ ชื่อไทยยาวกว่ากล่องได้เสมอ — ตัดท้ายด้วยจุดไข่ปลา ไม่ใช่ดันกล่องให้กว้างไม่เท่ากัน */
.pf-orgbox-name {
  inline-size: 100%; font-weight: var(--pf-w-semibold); font-size: 13px;
  color: var(--pf-text-primary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pf-orgbox-role {
  inline-size: 100%; font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption);
  color: var(--pf-text-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* ★ ป้ายกับปุ่มในกล่อง — ห่อบรรทัดได้ ไม่ดันกล่องให้กว้างขึ้น */
.pf-orgbox-tags {
  display: flex; flex-wrap: wrap; justify-content: center; gap: var(--pf-space-1);
  max-inline-size: 100%;
}
/* ★★ กล่องตำแหน่งที่ยังไม่มีคน — เส้นประเหมือน .pf-tree-row--vacant (ของเดียวกัน คนละมุมมอง) */
.pf-orgbox--vacant {
  border-style: dashed; border-color: var(--pf-border-strong);
  background: var(--pf-gray-50);
}
.pf-orgbox--vacant .pf-orgbox-name { color: var(--pf-text-secondary); font-weight: 500; }

/* ═══ ≤560px · ย่อกล่องลง — ยังเลื่อนแนวนอนได้เหมือนเดิม ═══ */
@media (max-width: 560px) {
  .pf-orgtree { --pf-org-gap: 18px; --pf-org-box: 132px; }
  .pf-orgbox { padding: var(--pf-space-2); }
}


/* ═══════════════════════════════════════════════════════════════════════════
   ★★★★ `hidden` ต้องแปลว่า **ซ่อน** เสมอ — กฎข้อเดียวแทนการปะทีละคลาส

   ⚠⚠⚠ `[hidden]` ของเบราว์เซอร์คือ `display: none` ที่ **ความจำเพาะต่ำที่สุด**
     คลาสไหนก็ตามที่ตั้ง `display:` (เช่น `.pf-btn { display: inline-flex }`)
     จะชนะมันทันที ⇒ **ของที่เขียน `hidden` ไว้ยังโผล่บนจอ**
     ★ อาการคือ "ปุ่มออกจากระบบขึ้นตั้งแต่ยังไม่ได้ล็อกอิน" — เจอจริงที่หน้าคอนโซล
       และไม่มี error ใด ๆ เลย เพราะในสายตา DOM มันถูกซ่อนอยู่แล้ว

   ★★★ ไฟล์นี้เคยปะปัญหานี้มาแล้ว **สามครั้ง** ทีละคลาส
     (`.pf-delta[hidden]` · `.pf-note[hidden]` · `.pf-mascot[hidden]`)
     ⇒ แปลว่ามันจะเกิดครั้งที่สี่แน่ ๆ กับคลาสถัดไปที่ตั้ง `display:`
     ★ รวมเป็นกฎเดียวที่นี่ แล้วลบทั้งสามข้อทิ้ง — ปะทีละจุดคือการรอให้ลืม

   ⚠ ใช้ `!important` โดยตั้งใจ — `hidden` เป็น **สถานะเชิงความหมายของ HTML**
     ไม่ใช่การตกแต่ง · ของที่ประกาศว่าซ่อน ต้องไม่มีสไตล์ไหนมาเถียงได้
   ═══════════════════════════════════════════════════════════════════════════ */
[hidden] { display: none !important; }

/* ═══════════════════════════════════════════════════════════════════════════
   แถวของ "ตัวสร้าง" — รายการช่องกรอกที่ซ้ำกันหลายสิบแถว (D50 ① · 2026-09-03)

   🔴 เจ้าของทัก: *"หัวข้อกับรายละเอียดหัวข้อ เกณฑ์คะแนน มองยากมากครับ ลายตามากเลย"*
     ตอนนั้นหน้าสร้างแบบประเมินวางข้อละก้อนโดย **ไม่มีเส้นแบ่งเลย** และปุ่มลบเป็น
     แถบแดงเต็มความกว้างคั่นอยู่ระหว่างทุกข้อ ⇒ ของที่แดงที่สุดบนหน้าคือปุ่มทำลาย
     และมันกลายเป็นตัวคั่นเดียวที่ตาจับได้ เลยอ่านเหมือนฟอร์มถูกหั่นเป็นท่อน ๆ ด้วยสีแดง

   ★★★ สามอย่างที่บล็อกนี้แก้
     ① **เส้นแบ่งบาง ๆ** ทำหน้าที่คั่น แทนที่จะให้ปุ่มอันตรายมาทำแทน
     ② **เลขลำดับ** ทางซ้าย — 18 ข้อโดยไม่มีเลข คนหาไม่เจอว่าอยู่ข้อไหน
     ③ **ปุ่มลบเป็นไอคอนเงียบ ๆ ชิดขวา** — ยังกดได้เท่าเดิม แต่ไม่แย่งสายตา

   ⚠ ป้ายช่องที่ซ้ำทุกแถวให้ตัดทิ้งแล้วใช้ placeholder แทน (18 แถว = ป้ายเดิม 18 ครั้ง)
     ★ ยกเว้นแถวแรกที่ยังต้องมีหัวคอลัมน์ ไม่งั้นคนไม่รู้ว่าช่องไหนคืออะไร
   ═══════════════════════════════════════════════════════════════════════════ */
.pf-buildrow {
  display: grid; grid-template-columns: 26px minmax(0, 1fr) auto;
  gap: var(--pf-space-4); align-items: start;
  padding-block: var(--pf-space-4);
  border-top: 1px solid var(--pf-border);
}
/* ⚠⚠ เคยเขียน `.pf-buildrow:first-child { border-top: 0 }` ไว้ตรงนี้ **แล้วมันไม่เคยตรงเลย**
     เพราะลูกคนแรกของกล่องหมวดคือ `.pf-buildgroup-head` เสมอ ไม่ใช่แถวข้อ
     ⇒ กฎที่ไม่มีวันตรง = ความเข้าใจผิดที่ฝังอยู่ในไฟล์ · ถอดทิ้ง
   ★ และของจริงดีกว่าที่ตั้งใจไว้ — เส้นบนของแถวแรกทำหน้าที่คั่นหัวหมวดกับข้อพอดี

/* ตัวเลขลำดับ — ★ ทรงเดียวกับ `.pf-setup-mark` โดยตั้งใจ (ของชนิดเดียวกันควรหน้าตาเดียวกัน)
   ⚠ ไม่ reuse คลาสนั้นตรง ๆ เพราะชื่อมันผูกกับ "ขั้นตอนการตั้งค่า" ซึ่งเป็นคนละเรื่อง
     ใช้ข้ามเรื่องเมื่อไหร่ วันที่แก้ของฝั่งโน้นจะพังฝั่งนี้โดยไม่มีใครเดาถูก */
.pf-buildrow-no {
  inline-size: 26px; block-size: 26px; border-radius: var(--pf-radius-pill);
  display: grid; place-items: center; flex: none;
  font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption);
  font-weight: var(--pf-w-bold); font-variant-numeric: tabular-nums;
  background: var(--pf-bg-secondary); color: var(--pf-text-secondary);
  border: 1px solid var(--pf-border);
  /* ★ ดันลงให้ตรงกลางช่องกรอกแถวแรก ไม่ใช่ลอยอยู่เหนือมัน */
  margin-block-start: var(--pf-space-2);
}

/* ★★★ กล่องที่ครอบ "หมวด" — หัวหมวดต้อง **เด่นกว่า** ข้อที่อยู่ข้างใน
   🔴 เจ้าของทักรอบสอง 2026-09-03: *"ยังแอบสับสนหมวดกับรายละเอียด"*
     รอบแรกหัวหมวดเป็นช่องกรอกหน้าตาเดียวกับช่องของข้อ วางเรียงกันในกล่องเดียว
     ⇒ ตาอ่านว่าเป็น "ข้ออีกข้อหนึ่งที่บังเอิญอยู่บนสุด" ไม่ใช่ชื่อของกลุ่ม
   ★★ สามอย่างที่ทำให้แยกออก — **ไม่ได้เพิ่มสีหรือกรอบใหม่สักอัน**
     ① หัวหมวดมีพื้นของตัวเองและกินเต็มความกว้างกล่อง (แถบ ไม่ใช่ช่องลอย)
     ② ตัวหนังสือในหัวหมวดหนาและใหญ่กว่าข้อ
     ③ ข้อทั้งหมดถูกดันเข้ามาข้างใน ⇒ เห็นได้จากระยะว่าอะไรอยู่ใต้อะไร */
.pf-buildgroup {
  border: 1px solid var(--pf-border); border-radius: var(--pf-radius-lg);
  margin-block-end: var(--pf-space-5);
  background: var(--pf-surface);
  /* ★ หัวหมวดต้องชนขอบกล่องพอดี — ไม่งั้นแถบจะลอยอยู่กลางกล่องแล้วไม่อ่านว่าเป็นหัว */
  overflow: hidden;
}
.pf-buildgroup-head {
  display: flex; align-items: center; gap: var(--pf-space-3);
  padding: var(--pf-space-4) var(--pf-space-5);
  background: var(--pf-bg-secondary);
  border-block-end: 1px solid var(--pf-border);
}
/* ★ ช่องชื่อหมวดยังแก้ได้เหมือนเดิม แต่อ่านเป็นหัวข้อ ไม่ใช่ช่องกรอกอีกช่อง */
.pf-buildgroup-head .pf-field input {
  font-size: var(--pf-size-card); line-height: var(--pf-lh-card);
  font-weight: var(--pf-w-bold);
}
/* ⚠ ระยะข้างในต้องมากกว่าหัวหมวด — ความรู้สึก "อยู่ข้างใน" มาจากระยะ ไม่ใช่เส้น */
.pf-buildgroup-body { padding: 0 var(--pf-space-5) var(--pf-space-5); }

/* ═══ ตารางประเมินรายปี — สิบสองเดือนของคนหนึ่งคน (D50 ④ · รอบที่ 3) ═══════
   ★★★★ ทำไมไม่ใช้ `.pf-table` ที่มีอยู่แล้ว
     ตารางต้องการ 12 คอลัมน์ + ชื่อคน ⇒ ที่จอ 1440 ได้ช่องละ ~85px
     ซึ่งใส่ "คะแนน + เกรด" ไม่ลง และบนมือถือโหมดการ์ดของ `.pf-table` จะกาง
     **12 บรรทัดต่อคนหนึ่งคน** ซึ่งอ่านไม่ออกว่าปีนี้เป็นยังไง
   ★ กริดนี้จึงห่อบรรทัดได้เอง: 12 ช่องเรียงเดียวบนจอกว้าง · 6×2 บนแท็บเล็ต · 4×3 บนมือถือ
     ⚠ ห้ามตั้งเป็น `repeat(12, 1fr)` ตายตัว — ช่องจะแคบจนตัวเลขตัดคำที่จอเล็ก */
/* 🔴🔴 **ห้ามใช้ `auto-fit`** — วัดจริงที่ 1280 ได้ 11 คอลัมน์ ⇒ ปีหนึ่งกลายเป็น 11 + 1
     ธ.ค. ตกไปอยู่บรรทัดล่างลำพัง ซึ่งอ่านเหมือนข้อมูลพัง ไม่ใช่เหมือนปฏิทิน
   ★ ปีมี 12 เดือนเสมอ ⇒ ต้องเป็นสี่เหลี่ยมลงตัวเสมอ: 4×3 · 6×2 · 12×1
     (จำนวนที่หาร 12 ลงตัวเท่านั้น — ห้ามใส่ 5 หรือ 8) */
.pf-yeargrid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--pf-space-2);
}
@media (min-width: 641px) { .pf-yeargrid { grid-template-columns: repeat(6, minmax(0, 1fr)); } }
/* ★ 12 ช่องแถวเดียวต้องการเนื้อการ์ด ~1,000px ขึ้นไป — วัดแล้วได้ที่วิวพอร์ต 1400 ขึ้นไป
   ⚠ ต่ำกว่านี้ช่องจะแคบกว่า 74px แล้วคะแนนสองตำแหน่งตัดคำ */
@media (min-width: 1400px) { .pf-yeargrid { grid-template-columns: repeat(12, minmax(0, 1fr)); } }
/* ★★ หนึ่งช่อง = หนึ่งเดือน · เป็น `button` เสมอ ไม่ว่าจะมีใบหรือยัง
   ⚠ ทำช่องว่างเป็น `div` แล้วมีปุ่มแยก = ผู้ใช้ต้องเล็งปุ่มเล็ก ๆ แทนที่จะกดทั้งช่อง */
.pf-month {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px;
  min-block-size: 66px;
  padding: var(--pf-space-2);
  border: 1px solid var(--pf-border);
  border-radius: var(--pf-radius-md);
  background: var(--pf-surface);
  color: var(--pf-text-primary);
  font: inherit;
  cursor: pointer;
  text-align: center;
  transition: border-color var(--pf-duration-standard) var(--pf-ease-out), background var(--pf-duration-standard) var(--pf-ease-out);
}
.pf-month:hover { border-color: var(--pf-primary-500); }
.pf-month:focus-visible { outline: 2px solid var(--pf-primary-500); outline-offset: 2px; }
/* ⚠ กดไม่ได้ต้องดูว่ากดไม่ได้ — ไม่งั้นผู้ใช้จะกดซ้ำแล้วคิดว่าหน้าค้าง */
.pf-month:disabled { cursor: not-allowed; opacity: .55; }
.pf-month:disabled:hover { border-color: var(--pf-border); }

/** ป้ายเดือน — เล็กและจาง เพราะมันคือ **แกน** ไม่ใช่ข้อมูล */
.pf-month-label {
  font-size: var(--pf-size-caption); line-height: 1;
  color: var(--pf-text-muted);
}
/** คะแนนรวม — ★ ตัวเลขทุกที่ต้อง tabular-nums ไม่งั้นเลขในคอลัมน์ไม่ตรงกัน */
.pf-month-score {
  font-size: var(--pf-size-card); line-height: 1.1;
  font-weight: var(--pf-w-bold);
  font-variant-numeric: tabular-nums;
}
.pf-month-grade {
  font-size: var(--pf-size-caption); line-height: 1.1;
  max-inline-size: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ★★★ เดือนที่ยังไม่มีใบ — เส้นประ บอกว่า "ช่องนี้ยังว่าง กดเพื่อเริ่ม"
   ⚠ ใช้สีเทาเฉย ๆ ไม่พอ — เทาอ่านเหมือน "ปิดใช้งาน" ซึ่งตรงข้ามกับความหมาย */
.pf-month--empty {
  border-style: dashed;
  background: var(--pf-bg-secondary);
  color: var(--pf-text-muted);
}
/* ★★ ร่าง — มีเลขแล้วแต่ยังไม่ล็อก · เส้นประเหมือนช่องว่างเพื่อบอกว่า "ยังไม่จบ"
   🔴 ร่างกับส่งผลแล้วต้องแยกออกจากกันด้วยตา ไม่ใช่ต้องอ่านตัวหนังสือ
     เพราะเลขบนใบร่างยังขยับได้ และ **ไม่ถูกนับในค่าเฉลี่ยของปี** */
.pf-month--draft { border-style: dashed; }

/* โทนตามเกรด — ★ มาจาก `gradeTone()` ซึ่งตัดสินด้วย **ลำดับชั้นของเกรดในฟอร์มนั้น**
   ⚠ ไม่ได้ตัดสินจากตัวเลขคะแนน · ฟอร์มคนละใบให้ 75 คนละความหมาย (D50 ①) */
.pf-month--ok   { background: var(--pf-success-50); border-color: var(--pf-success-100); color: var(--pf-success-700); }
.pf-month--info { background: var(--pf-info-50);    border-color: var(--pf-info-100);    color: var(--pf-info-700); }
.pf-month--warn { background: var(--pf-warning-50); border-color: var(--pf-warning-100); color: var(--pf-warning-700); }
.pf-month--red  { background: var(--pf-danger-50);  border-color: var(--pf-danger-100);  color: var(--pf-danger-700); }

/* ═══ ใบประเมินแบบเอกสาร (เจ้าของส่งภาพใบจริงมา 2026-09-03) ════════════════
   ★★★★ ทำไมต้องเป็น **ตาราง** ไม่ใช่การ์ดเรียงกัน
     ใบนี้ไม่ได้มีไว้กรอกอย่างเดียว — มันถูก **พิมพ์ออกมาให้พนักงานเซ็นรับทราบ**
     ⇒ ต้องอ่านได้ว่า "หมวดนี้มีข้ออะไรบ้าง · ข้อนี้ให้อะไรไป · ได้กี่คะแนน"
       ในสายตาเดียว ซึ่งเป็นสิ่งที่ตารางทำได้และการ์ดเรียงกันทำไม่ได้
   ⚠ รอบแรกผมทำเป็นการ์ดต่อหมวด + ปุ่มเรียง — อ่านง่ายตอนกรอก แต่ **ไม่เหมือนเอกสาร**
     เจ้าของส่งภาพของจริงมาเทียบให้ดู · ของจริงชนะ

   ★★★ หัวตารางแสดง **ขั้นของสเกลพร้อมค่า** (ดีมาก 5 · ดี 4 · พอใช้ 3 · ปรับปรุง 2)
     ⇒ คนอ่านรู้ความหมายของช่องกลมทันที โดยไม่ต้องเดาจากตำแหน่ง */
.pf-evalsheet {
  inline-size: 100%;
  border-collapse: collapse;
  font-size: var(--pf-size-body);
}
.pf-evalsheet th, .pf-evalsheet td {
  border: 1px solid var(--pf-border);
  padding: var(--pf-space-3);
  vertical-align: middle;
}
.pf-evalsheet thead th {
  background: var(--pf-primary-500);
  color: var(--pf-on-primary);
  font-weight: var(--pf-w-bold);
  text-align: center;
  line-height: 1.3;
}
/** ค่าของขั้นใต้ชื่อขั้น — ★ เล็กกว่าและจางกว่า ชื่อขั้นคือสิ่งที่คนอ่าน */
.pf-evalsheet-lvl { display: block; font-size: var(--pf-size-caption); opacity: .85; }

/** ช่องหมวด — ★ กินหลายแถวด้วย rowspan · เป็นแกนของตาราง ไม่ใช่ข้อมูล */
.pf-evalsheet-group {
  background: var(--pf-bg-secondary);
  /* 🔴 เคยเป็น `--pf-text-secondary` แล้วเจ้าของทักว่า "สีดรอปกว่าเลยดูแปลก ๆ"
       เขาพูดถูก — ช่องนี้เป็น **หัวของกลุ่มแถว** ไม่ใช่ข้อมูลรอง
       สีจางกว่าเนื้อหาที่มันคุมอยู่ ⇒ อ่านเหมือนถูกปิดใช้งาน ไม่ใช่เหมือนหัวข้อ
     ⚠ ที่เคยจางเพราะไปแก้ให้ผ่านคอนทราสต์ธีมมืด — แก้ผิดตัว
       ตัวที่ควรเปลี่ยนคือ **โทเคน** ไม่ใช่ลดน้ำหนักสายตาของช่องทิ้ง */
  color: var(--pf-text-primary);
  font-weight: var(--pf-w-bold);
  text-align: center;
  inline-size: 150px;
}
/** ★ คอลัมน์ "ด้านการประเมิน" ของตารางหน้าที่ — ใช้สีแบรนด์ตามใบจริง
 *  ⚠ ตารางวินัยไม่ใช้ตัวนี้ · ใบจริงของเจ้าของใช้สองแบบต่างกันจริง ๆ
 *  ⚠⚠ ประกาศ `background` ซ้ำทั้งที่สืบทอดมาจาก `.pf-evalsheet-group` อยู่แล้ว
 *    เพราะ `npm run contrast` จับคู่สีจาก **บล็อกเดียวกัน** เท่านั้น
 *    เขียนแต่ `color` = คู่นี้ไม่ถูกตรวจเลย แล้วเราจะไม่รู้ว่ามันอ่านออกไหมในธีมมืด
 *    ★ ของที่ตรวจไม่ได้ กับของที่ผ่านการตรวจ ต่างกันมาก — ยอมเขียนซ้ำหนึ่งบรรทัด */
.pf-evalsheet-group--cat {
  background: var(--pf-bg-secondary);
  color: var(--pf-primary-ink);
}
/** ช่องคะแนนขวาสุด — ★ ตัวเลขต้องเรียงตรงกันทุกแถว */
.pf-evalsheet-score {
  inline-size: 74px;
  text-align: center;
  font-weight: var(--pf-w-bold);
  font-size: var(--pf-size-card);
  font-variant-numeric: tabular-nums;
  color: var(--pf-success-700);
}
/* ⚠ ยังไม่ได้ให้คะแนน ต้องไม่เป็นสีเดียวกับ "ให้แล้วได้ศูนย์" (ตระกูล P50) */
.pf-evalsheet-score--none { color: var(--pf-text-muted); font-weight: var(--pf-w-regular); }

/** แถวรวมท้ายตาราง */
.pf-evalsheet tfoot td {
  background: var(--pf-bg-secondary);
  color: var(--pf-primary-ink);
  font-weight: var(--pf-w-bold);
}

/* ★★ โหมด "ตัวชี้วัดขั้นบันได" — กล่องเรียงแนวตั้งกินคอลัมน์ตัวเลือกทั้งแถบ
   ⚠ ขั้นของแต่ละข้อไม่ตรงกับหัวตาราง (ข้อนี้มี 3 ขั้น ข้ออื่นมี 4) จึงวางเป็นคอลัมน์ไม่ได้

   🔴🔴 ชื่อคลาสคือ `pf-ladder`/`pf-rung` **ไม่ใช่ `pf-steps`/`pf-step`** — สองชื่อนั้นถูกใช้ไปแล้ว
     โดย "รายการขั้นตอนที่ต้องทำให้ครบก่อน" (ด่านก่อนลงเวลา · บรรทัด ~4400)
     ซึ่งมี `counter-reset: pf-step` + `::before { content: counter(pf-step) }`
     ⇒ รอบแรกผมตั้งชื่อชนกัน ผลคือ **ทุกตัวเลือกบนใบประเมินมีเลข 1 2 3 โผล่ข้างหน้า**
       ทั้งที่ไม่มีใครเขียนเลขนั้นไว้ · เจอเพราะเปิดดูของจริงใน /design/ เท่านั้น
     ⚠ tsc · build · hex · contrast เขียวหมด — ไม่มีเครื่องตรวจตัวไหนจับชื่อคลาสชนกันได้
     ★ ตระกูลเดียวกับ P320/P336 (CSS ที่น้ำหนักเท่ากันชนกัน) แต่คราวนี้ชนกันที่ **ชื่อ** */
.pf-ladder { display: grid; gap: var(--pf-space-2); }
.pf-rung {
  display: flex; align-items: center; justify-content: center; gap: var(--pf-space-2);
  padding: var(--pf-space-2) var(--pf-space-3);
  border: 1px solid var(--pf-border);
  border-radius: var(--pf-radius-md);
  background: var(--pf-surface);
  color: var(--pf-text-primary);
  font: inherit;
  font-size: var(--pf-size-caption);
  cursor: pointer;
  transition: border-color var(--pf-duration-standard) var(--pf-ease-out),
              background var(--pf-duration-standard) var(--pf-ease-out);
}
.pf-rung:hover:not(:disabled) { border-color: var(--pf-primary-500); }
.pf-rung:focus-visible { outline: 2px solid var(--pf-primary-500); outline-offset: 2px; }
.pf-rung:disabled { cursor: not-allowed; opacity: .55; }
/* ★★★ ขั้นที่เลือกอยู่ — ขอบหนาและพื้นอ่อน เหมือนใบจริงที่เจ้าของใช้อยู่
   ⚠ ห้ามใช้พื้นทึบแบบปุ่มหลัก — ในแถบที่มี 4 ขั้น พื้นทึบจะกลบข้อความของขั้นอื่น */
.pf-rung[aria-pressed='true'] {
  border-color: var(--pf-primary-500);
  border-width: 2px;
  background: var(--pf-primary-50);
  color: var(--pf-primary-ink);
  font-weight: var(--pf-w-bold);
}
/** ค่าคะแนนของขั้น — ★ คือ **คะแนนที่จะได้จริง** ไม่ใช่ค่าดิบของสเกล */
.pf-rung-val { font-variant-numeric: tabular-nums; font-weight: var(--pf-w-bold); }

/* ★★ โหมด "ให้เรตตามสเกล" — ช่องกลมหนึ่งช่องต่อหนึ่งคอลัมน์ ตรงกับหัวตาราง
   ⚠ เป็น `button` ไม่ใช่ `<input type=radio>` เพราะต้องกดซ้ำเพื่อ **ล้างคำตอบ** ได้
     radio จริงล้างค่าไม่ได้เมื่อเลือกไปแล้ว ซึ่งทำให้ "ยังไม่ประเมิน" กลับไปไม่ได้ */
.pf-evalsheet-pick { inline-size: 84px; text-align: center; }
.pf-radio {
  inline-size: 20px; block-size: 20px;
  border: 2px solid var(--pf-border-strong);
  border-radius: 50%;
  background: var(--pf-surface);
  padding: 0;
  cursor: pointer;
  transition: border-color var(--pf-duration-standard) var(--pf-ease-out);
}
.pf-radio:hover:not(:disabled) { border-color: var(--pf-primary-500); }
.pf-radio:focus-visible { outline: 2px solid var(--pf-primary-500); outline-offset: 2px; }
.pf-radio:disabled { cursor: not-allowed; opacity: .55; }
.pf-radio[aria-pressed='true'] {
  border-color: var(--pf-primary-500);
  /* ★ จุดตรงกลางทำด้วยเงาใน ไม่ต้องมี element ลูก */
  box-shadow: inset 0 0 0 4px var(--pf-surface), inset 0 0 0 12px var(--pf-primary-500);
}

/* ═══ ตารางวินัย — ดึงจากลงเวลาอัตโนมัติ ═══════════════════════════════════
   ★ เกณฑ์ทุกขั้นแสดงบนใบ เพื่อให้ผู้ถูกประเมินเห็นเองว่าทำไมได้เท่านี้ */
.pf-discsteps { display: flex; flex-wrap: wrap; gap: var(--pf-space-2); }
.pf-discstep {
  padding: 2px var(--pf-space-2);
  border-radius: var(--pf-radius-sm);
  background: var(--pf-gray-100);
  color: var(--pf-text-secondary);
  font-size: var(--pf-size-caption);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* ★★ ขั้นที่ตกอยู่ — ไฮไลต์ให้เห็นว่าคะแนนมาจากขั้นไหน */
.pf-discstep--hit {
  background: var(--pf-primary-50);
  color: var(--pf-primary-ink);
  font-weight: var(--pf-w-bold);
}

/* ═══ กล่องสรุปการคำนวณท้ายใบ ══════════════════════════════════════════════
   ★★★ ต้องโชว์ **การคูณ** ไม่ใช่โชว์แค่ผลลัพธ์
     ผู้ถูกประเมินจะถามว่า "ทำไมได้เท่านี้" และคำตอบต้องอยู่บนใบ ไม่ใช่ในหัวคนคิด */
.pf-calc {
  border: 1px solid var(--pf-border);
  border-radius: var(--pf-radius-lg);
  overflow: hidden;
}
.pf-calc-row {
  display: flex; align-items: center; gap: var(--pf-space-3);
  padding: var(--pf-space-3) var(--pf-space-4);
  border-block-end: 1px solid var(--pf-border-subtle);
}
.pf-calc-row:last-child { border-block-end: 0; }
.pf-calc-row > :first-child { flex: 1; min-inline-size: 0; }
.pf-calc-val { font-variant-numeric: tabular-nums; font-weight: var(--pf-w-bold); }
/** บรรทัดรวม — ★ ใหญ่กว่าและมีพื้น เพราะมันคือคำตอบของทั้งใบ */
.pf-calc-row--total {
  background: var(--pf-bg-secondary);
  font-size: var(--pf-size-card);
}
.pf-calc-row--total .pf-calc-val { color: var(--pf-primary-ink); }

/** แถบเกรดทุกช่วง — ★ ไฮไลต์ช่วงที่ได้ · ช่วงอื่นยังต้องเห็น จะได้รู้ว่าห่างจากช่วงถัดไปแค่ไหน */
.pf-gradebar { display: flex; flex-wrap: wrap; gap: var(--pf-space-2); align-items: center; }
.pf-gradechip {
  padding: 2px var(--pf-space-3);
  border-radius: var(--pf-radius-pill);
  background: var(--pf-gray-100);
  color: var(--pf-text-secondary);
  font-size: var(--pf-size-caption);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.pf-gradechip--now {
  background: var(--pf-primary-500);
  color: var(--pf-on-primary);
  font-weight: var(--pf-w-bold);
}

/* ═══ มือถือ — ตารางกลายเป็นการ์ดต่อข้อ ═══════════════════════════════════
   🔴 ตาราง 4–7 คอลัมน์ที่ 375px อ่านไม่ได้เลย และ **ห้ามใช้วิธีเลื่อนแนวนอน**
     เพราะช่องที่ต้องกดอยู่คนละฝั่งกับข้อความที่บอกว่ากำลังกดให้ข้อไหน
   ★ วิธีเดียวกับ `.pf-table` ของระบบ: ซ่อนหัวตาราง แล้วให้แต่ละแถวเป็นการ์ด
     ⚠ ต่างกันตรงที่ที่นี่ **ยุบช่องหมวดทิ้งไม่ได้** — ต้องย้ายไปเป็นหัวการ์ดแทน */
@media (max-width: 767px) {
  .pf-evalsheet, .pf-evalsheet tbody, .pf-evalsheet tr, .pf-evalsheet td { display: block; }
  .pf-evalsheet thead { display: none; }
  .pf-evalsheet tr { margin-block-end: var(--pf-space-4); }
  .pf-evalsheet td {
    border: 0;
    padding: var(--pf-space-2) 0;
  }
  .pf-evalsheet tbody tr {
    border: 1px solid var(--pf-border);
    border-radius: var(--pf-radius-lg);
    padding: var(--pf-space-4);
  }
  /* ★ หมวดกลายเป็นป้ายเหนือข้อ — ไม่ใช่ช่องที่กินความสูงเต็มการ์ด */
  .pf-evalsheet-group {
    inline-size: auto;
    text-align: start;
    border-radius: var(--pf-radius-sm);
    padding: var(--pf-space-2) var(--pf-space-3) !important;
    margin-block-end: var(--pf-space-2);
    font-size: var(--pf-size-caption);
  }
  /* ★★ ช่องกลมของโหมดเรต ต้องมีป้ายกำกับบนมือถือ — ไม่มีหัวตารางให้เทียบแล้ว
     ⚠ ปล่อยเป็นวงกลมเปล่าสี่วง = เดาไม่ออกว่าวงไหนคือ "ดี" */
  .pf-evalsheet-pick {
    inline-size: auto;
    display: flex !important;
    align-items: center;
    gap: var(--pf-space-3);
  }
  .pf-evalsheet-pick::before {
    content: attr(data-label);
    flex: 1;
    text-align: start;
    color: var(--pf-text-secondary);
    font-size: var(--pf-size-caption);
  }
  /* ★ บนมือถือช่องจำนวนก็ต้องมีป้ายกำกับเหมือนช่องอื่น — ไม่มีหัวตารางให้เทียบแล้ว */
  .pf-evalsheet-amt {
    inline-size: auto;
    display: flex !important;
    align-items: center;
    gap: var(--pf-space-3);
  }
  .pf-evalsheet-amt::before {
    content: attr(data-label);
    flex: 1;
    text-align: start;
    color: var(--pf-text-secondary);
    font-size: var(--pf-size-caption);
  }
  .pf-evalsheet-score {
    inline-size: auto;
    text-align: start;
    border-block-start: 1px solid var(--pf-border-subtle) !important;
    padding-block-start: var(--pf-space-3) !important;
  }
  .pf-evalsheet-score::before {
    content: 'คะแนนข้อนี้ ';
    color: var(--pf-text-secondary);
    font-size: var(--pf-size-caption);
    font-weight: var(--pf-w-regular);
  }
  .pf-evalsheet tfoot td { border-radius: var(--pf-radius-md); padding: var(--pf-space-4) !important; }
}

/* ═══ อ่านง่ายขึ้น — เจ้าของทัก "อ่านยากๆ ยังไงไม่รู้" (2026-09-03) ═════════
   ★★★★ วัดแล้วสาเหตุมีสามอย่าง และไม่ใช่เรื่องสีเลย
     ① **ระยะทางที่ตาต้องเดิน** — คอลัมน์รายละเอียดยืดเต็มที่ว่าง ⇒ ที่จอ 1440
        ข้อความอยู่ซ้ายสุด ช่องกลมอยู่ขวาสุด ห่างกัน ~700px
        ตาอ่านข้อความจบแล้ว **หาแถวของตัวเองไม่เจอ** ตอนกวาดไปทางขวา
     ② **ทุกแถวหน้าตาเหมือนกันหมด** — 18 แถว × วงกลมสี่วง ไม่มีอะไรให้ตายึด
     ③ ช่องคะแนนขวาสุดเป็น "—" ทั้งคอลัมน์ตอนยังไม่ให้คะแนน ⇒ ไม่มีจุดสังเกต
   ★ แก้สามอย่างตามลำดับความแรง: ตรึงความกว้างรายละเอียด · แถบสลับสี · ไฮไลต์แถวที่ชี้อยู่ */

/* 🔴 ตรึงคอลัมน์รายละเอียดไม่ให้ยืดเต็มที่ว่าง — ลดระยะทางที่ตาต้องเดิน
   ⚠ ไม่ใช่ `max-width` — ในตารางต้องใช้ `width` ซึ่ง browser อ่านเป็น "ขนาดที่ต้องการ" */
/* 🔴🔴 ทั้งสามกฎข้างล่างเป็นของ **ตารางเท่านั้น** — บนมือถือแถวกลายเป็นการ์ดไปแล้ว
   ★★★★ เจ้าของทัก 2026-09-09 ว่า "สีเทาไฮไลท์ดูแปลก ๆ" บนมือถือ · วัดแล้วคือตรงนี้
     ① `td` ที่ `display: block` แล้ว **ไม่ได้ต่อกันเป็นแถบ** — มันเป็นก้อนซ้อนกันในการ์ด
        ⇒ พื้นเทาที่เคยเป็น "แถบสลับสีของแถว" กลายเป็นก้อนเทาหลายก้อนขอบไม่เท่ากัน
     ② และเทาขึ้นเฉพาะการ์ดคู่ ⇒ อ่านเหมือน "การ์ดนี้ถูกเลือกอยู่" ทั้งที่ไม่มีอะไรเกิดขึ้น
     ③ `:hover` ไม่มีความหมายบนจอสัมผัสอยู่แล้ว — นิ้วไม่ได้ "ชี้ค้าง"
   ★ เหตุผลเดิมของแถบสลับสีคือ "คอลัมน์อยู่ห่างกัน ตาหาแถวของตัวเองไม่เจอ"
     ซึ่งบนการ์ดไม่มีปัญหานั้นเลย — ทุกอย่างของข้อเดียวกันอยู่ในกรอบเดียวกันแล้ว
   ⇒ ปิดทั้งคู่ที่ต่ำกว่า 768px ไม่ใช่ไปทับด้วยกฎใหม่ให้ซ้อนกันสองชั้น */
@media (min-width: 768px) {
  /* 🔴 ตรึงคอลัมน์รายละเอียดไม่ให้ยืดเต็มที่ว่าง — ลดระยะทางที่ตาต้องเดิน
     ⚠ ไม่ใช่ `max-width` — ในตารางต้องใช้ `width` ซึ่ง browser อ่านเป็น "ขนาดที่ต้องการ"
     ⚠⚠ เคยอยู่นอกบล็อกนี้ ⇒ บนการ์ดกลายเป็นก้อนกว้าง 115px ในการ์ด 277px
       ข้อความตัดบรรทัดเร็วเกินจำเป็น (เจ้าของทัก 2026-09-09)
       ★ ตอนแรกแก้ด้วยการเขียน `inline-size: auto` ทับในบล็อกมือถือ — **ไม่ได้ผล**
         เพราะบล็อกมือถืออยู่ **เหนือ** กฎนี้ในไฟล์ และน้ำหนักตัวเลือกเท่ากัน
         ⇒ กฎที่อยู่ล่างกว่าชนะเสมอ · ทางที่ถูกคือย้ายกฎเข้ามาในนี้ ไม่ใช่ทับมัน */
  .pf-evalsheet-detail { inline-size: 46%; }

  /* ★★★ แถบสลับสี — ตัวช่วยที่แรงที่สุดสำหรับตารางที่คอลัมน์อยู่ห่างกัน
     ⚠ ต้องเป็น `tbody tr` ไม่ใช่ `tr` — ไม่งั้นแถวรวมใน tfoot ก็ถูกสลับด้วย */
  .pf-evalsheet tbody tr:nth-child(even) > td:not(.pf-evalsheet-group) {
    background: var(--pf-bg-secondary);
  }
  /* ★★ ไฮไลต์แถวที่เมาส์ชี้อยู่ — ยืนยันว่า "ช่องกลมที่กำลังจะกด เป็นของข้อนี้"
     ⚠ ต้องเข้มกว่าแถบสลับสี ไม่งั้นแถวคู่จะดูเหมือนถูกชี้อยู่ตลอด */
  .pf-evalsheet tbody tr:hover > td:not(.pf-evalsheet-group) {
    background: var(--pf-primary-50);
  }
}

/** ช่องจำนวนในตารางวินัย — ★ แคบพอที่เลขไม่ลอยห่างจากหัวข้อ */
/* ⚠ วัดแล้ว 128px แคบไป — หน่วย "วัน"/"ครั้ง" ถูกตัดที่ขอบช่อง
   ช่องกรอก 68 + ระยะ + หน่วย ~26 + padding ของ td 24 ⇒ ต้องมี ~150 */
/* ★★ ช่อง "จำนวน" ของตารางวินัย — กล่องกรอกกับหน่วยต้องอยู่กึ่งกลางและมีระยะห่างกัน
   🔴 เดิมไม่มีทั้งสองอย่าง: เนื้อในชิดซ้ายทั้งที่หัวคอลัมน์อยู่กึ่งกลาง (อ่านเหมือนคนละคอลัมน์)
     และหน่วยติดกล่องจนอ่านเป็น "26วัน" (เจ้าของทัก)
   ★ ใช้ flex ที่ตัวช่องเลย ไม่ใช่ `text-align` — ช่องนี้มีสองก้อนที่ต้องจัดคู่กัน
     (กล่องกรอก + หน่วย) ซึ่ง `text-align` จัดระยะระหว่างกันไม่ได้ */
.pf-evalsheet-amt {
  inline-size: 150px;
  white-space: nowrap;
  text-align: center;
}
.pf-evalsheet-amt-box {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--pf-space-2);
}
.pf-evalsheet-amt .pf-field { display: inline-flex; inline-size: 68px; flex: none; }
.pf-evalsheet-amt .pf-field input { text-align: center; }

/* ═══ บรรทัดลงชื่อท้ายใบ ═══════════════════════════════════════════════════
   ★ ทรงตามใบจริง — เส้นลงชื่อ · ชื่อในวงเล็บ · บทบาท · วันที่
   ⚠ ลายเซ็นจริงยังไม่มีในรอบนี้ · ช่องเส้นจึงเขียนตรง ๆ ว่ายังไม่มีลายเซ็น
     🔴 ห้ามวาดเส้นหยึกหยักปลอมไว้ให้ดูเหมือนเซ็นแล้ว — เอกสารนี้ใช้ยืนยันการรับทราบ */
.pf-signrow {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--pf-space-6);
  margin-block-start: var(--pf-space-6);
}
.pf-sign { text-align: center; }
/** เส้นลงชื่อ — ★ สูงพอที่ลายเซ็นจริงจะวางลงได้ในรอบถัดไปโดยไม่ต้องขยับอะไร */
.pf-sign-line {
  display: flex; align-items: flex-end; justify-content: center;
  min-block-size: 56px;
  padding-block-end: var(--pf-space-2);
  border-block-end: 1px solid var(--pf-border-strong);
  margin-block-end: var(--pf-space-2);
}
.pf-sign-none { color: var(--pf-text-placeholder); font-size: var(--pf-size-caption); }
.pf-sign-name { font-weight: var(--pf-w-bold); }
.pf-sign-role { color: var(--pf-text-secondary); font-size: var(--pf-size-caption); }
/* ⚠⚠ ต้องตั้ง `line-height` ด้วย ไม่ใช่ตั้งแต่ `font-size`
   ตั้งแต่ขนาดตัวอักษรอย่างเดียว = ความสูงบรรทัดตกทอดมาจากตัวแม่ (22px) ไม่ใช่ของ caption (19px)
   ⇒ กล่องวันที่สูงกว่าที่ `.pf-sign-meta` จองไว้ 3px แล้วสองฝั่งเหลื่อมกันเท่านั้นพอดี
   ★ วัดจริงที่ 1280: ซ้าย 22px ขวา 19px · ปุ่มห่างกัน 3px (2026-09-09)
   ★ `.pf-caption` ตั้งทั้งคู่มาตั้งแต่แรก — ตัวนี้ลอกมาไม่ครบ */
.pf-sign-date {
  color: var(--pf-text-muted);
  font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption);
}

/* ★★★★ แถวข้อมูลใต้ "ตำแหน่ง" — **จองที่ไว้เสมอ แม้ฝั่งนั้นยังไม่มีอะไรจะบอก**
   (เจ้าของทัก 2026-09-09: "ระดับปุ่มเซ็นชื่อไม่เท่ากัน น่าจะเป็นเพราะวันที่")

   🔴 อาการ: ฝั่งผู้ประเมินมีบรรทัดวันที่ · ฝั่งผู้รับการประเมินไม่มี
     ⇒ ทุกอย่างใต้บรรทัดนั้นของสองฝั่ง **เหลื่อมกันหนึ่งบรรทัดตลอด** ปุ่มเซ็นจึงไม่ตรงกัน
   ★ สองช่องนี้คือ **ช่องเซ็นบนกระดาษที่วางคู่กัน** — ความเท่ากันคือหน้าตาของเอกสาร
     ไม่ใช่เรื่องสวยงามอย่างเดียว · เอกสารที่สองฝั่งไม่ตรงกันอ่านเหมือนพิมพ์ผิด
   ⚠⚠ ทางที่ **ไม่เลือก**: เติมข้อความปลอมให้ฝั่งที่ยังไม่มีวันที่ (เช่นใส่วันที่วันนี้)
     เอกสารนี้ใช้ยืนยันการรับทราบ — วันที่ที่ไม่จริงคือการปลอมเอกสาร
     (เหตุผลเดียวกับที่ `.pf-sign-line` ห้ามวาดเส้นหยึกหยักปลอม)
   ⇒ จองที่ว่างไว้เฉย ๆ ดีกว่า · ว่างแต่ตรง ดีกว่ามีข้อความแต่เหลื่อม */
.pf-sign-meta { min-block-size: var(--pf-lh-caption); }

/* ⚠ มือถือ — สองช่องเรียงกันแล้วเส้นลงชื่อสั้นจนเซ็นไม่ได้ ⇒ ซ้อนกันแทน */
@media (max-width: 560px) {
  .pf-signrow { grid-template-columns: minmax(0, 1fr); gap: var(--pf-space-5); }
}

/* ═══ ช่องเซ็นชื่อ (D50 ④ · รอบที่ 4) ══════════════════════════════════════
   ★★★ ทรงเหมือนช่องเซ็นบนกระดาษ — กรอบว่าง ๆ กับเส้นบรรทัดใต้ลายเซ็น
     ⚠ ห้ามทำให้ดูเหมือนช่องกรอกข้อความ คนจะพยายามพิมพ์ลงไป */
.pf-signpad {
  position: relative;
  border: 1px dashed var(--pf-border-strong);
  border-radius: var(--pf-radius-lg);
  background: var(--pf-surface);
  block-size: 148px;
  overflow: hidden;
}
.pf-signpad-canvas {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  /* 🔴 ขาดบรรทัดนี้ = ลากนิ้วบนมือถือแล้วหน้าเลื่อนแทนที่จะวาด
     แล้วผู้ใช้จะสรุปว่าช่องเซ็นกดไม่ได้ ทั้งที่มันทำงานปกติ */
  touch-action: none;
  cursor: crosshair;
  /* ★★★ สีเส้นที่ JS อ่านไปใช้ — **หมึกบนกระดาษ** ไม่ใช่สีตัวหนังสือของธีม
     🔴 ใช้สีตามธีมเมื่อไหร่ คนที่เซ็นตอนเปิดธีมมืดจะได้เส้นสีอ่อน
       แล้วพอพิมพ์ใบออกมาบนกระดาษขาว ลายเซ็นจะหายไปทั้งอัน */
  background: var(--pf-paper);
  color: var(--pf-ink);
}
/** เส้นบรรทัดใต้ลายเซ็น — ★ ตกแต่ง ไม่ใช่ขอบเขตการวาด */
.pf-signpad-rule {
  position: absolute;
  inset-inline: var(--pf-space-6);
  inset-block-end: 34px;
  border-block-end: 1px solid var(--pf-border);
  pointer-events: none;
}
.pf-signpad-hint {
  position: absolute;
  inset: 0;
  display: flex; align-items: center; justify-content: center;
  color: var(--pf-text-placeholder);
  font-size: var(--pf-size-caption);
  pointer-events: none;
}

/* ═══ ลายเซ็นที่เซ็นแล้ว บนใบ ══════════════════════════════════════════════
   ⚠ ภาพลายเซ็นเป็น PNG พื้นโปร่ง — บนธีมมืดเส้นสีเข้มจะหายไปกับพื้น
     ★ จึงใส่พื้นขาวรองไว้เสมอ เหมือนกระดาษ · เอกสารนี้ถูกพิมพ์ออกมาอยู่แล้ว */
.pf-sign-img {
  display: block;
  max-inline-size: 100%;
  block-size: 56px;
  object-fit: contain;
  object-position: center;
  margin-inline: auto;
  background: var(--pf-paper);
  border-radius: var(--pf-radius-sm);
}
/** ป้ายบอกช่องทางการเซ็น — ★★ สองช่องทางยืนยันได้ไม่เท่ากัน ต้องอ่านออกจากป้าย */
.pf-sign-ch {
  display: inline-flex; align-items: center; gap: var(--pf-space-1);
  font-size: var(--pf-size-caption);
}
.pf-sign-ch--self { color: var(--pf-success-700); }
/* ⚠ ไม่ใช่สีแดง — ไม่ใช่ความผิดพลาด แค่ยืนยันได้น้อยกว่า */
.pf-sign-ch--onsite { color: var(--pf-warning-700); }

/* 🔴 ป้ายช่องทาง กับ ปุ่มถอนลายเซ็น ยืนอยู่บรรทัดเดียวกัน แต่ไม่กึ่งกลางกัน
   ★★★★ เจ้าของทัก 2026-09-09 · สาเหตุคือ **การจัดแนวตั้งโดยปริยายของ inline**
     ทั้งคู่เป็นกล่อง inline (`inline-flex`) ⇒ เบราว์เซอร์จัดด้วย `baseline`
     คือ "ให้เส้นฐานตัวอักษรของสองกล่องอยู่ระดับเดียวกัน"
     ⚠ ปุ่มมี padding + เส้นขอบ ⇒ กล่องมันสูงกว่าตัวหนังสือของป้ายมาก
       พอยึดเส้นฐานตรงกัน **จุดกึ่งกลางของสองกล่องจึงไม่ตรงกัน** — เหลื่อมเท่าครึ่งหนึ่ง
       ของส่วนที่ปุ่มสูงกว่า และมองเห็นชัดที่สุดบนมือถือที่ทั้งสองอยู่ชิดกัน
   ★ `middle` ให้ทั้งคู่ยึด **จุดกึ่งกลางของกล่องตัวเอง** กับหลักเดียวกัน ⇒ ตรงกันจริง
   ⚠ ตีกรอบไว้ใต้ `.pf-sign` เท่านั้น — ห้ามไปแตะ `.pf-btn` ทั้งระบบเพราะปุ่มที่อื่น
     อยู่ใน flex/grid ซึ่ง `vertical-align` ไม่มีผล แต่จะกลายเป็นกฎที่อ่านแล้วเข้าใจผิด */
.pf-sign > .pf-sign-ch,
.pf-sign > .pf-btn { vertical-align: middle; }

/* ═══ ประวัติของใบ ════════════════════════════════════════════════════════
   ★ เพิ่มอย่างเดียว ลบไม่ได้ — หน้าจอจึงไม่มีปุ่มลบสักปุ่ม */
.pf-timeline { display: grid; gap: var(--pf-space-4); }
.pf-tl {
  display: grid;
  grid-template-columns: 10px minmax(0, 1fr);
  gap: var(--pf-space-4);
  align-items: start;
}
.pf-tl-dot {
  inline-size: 10px; block-size: 10px;
  border-radius: 50%;
  background: var(--pf-border-strong);
  margin-block-start: 5px;
}
.pf-tl--signed .pf-tl-dot { background: var(--pf-success-500); }
.pf-tl--undone .pf-tl-dot { background: var(--pf-danger-500); }
.pf-tl-when { color: var(--pf-text-muted); font-size: var(--pf-size-caption); }

/* ═══ ปฏิทินวันหยุดประจำปี (065 · 2026-09-04) ═══════════════════════════════
   ★★★★ เจ้าของส่งภาพหน้าจอของระบบเดิมมาให้ แล้วบอกว่า
     "แยกกลุ่มเดือนและเพิ่มประเภทวันหยุดราชการกับของบริษัทเข้าไป และเพิ่มสีสันหน่อย"

   ★★★ ทำไมไม่ใช้ `pf-table` เหมือนหน้าอื่น
     ปฏิทินวันหยุดถูกอ่านแบบ **กวาดหาเดือน** ไม่ใช่เทียบค่าระหว่างแถว
     ตารางที่มีคอลัมน์ "วันที่" 19 แถวรวด ตาไล่ไม่ออกว่าเดือนไหนมีกี่วัน
     ⇒ จัดกลุ่มตามเดือน + เลขวันตัวใหญ่ = ตอบคำถาม "เดือนหน้าหยุดวันไหน" ได้ในแวบเดียว

   ⚠⚠ **ไม่มีค่าสีดิบสักตัว** — โทนทุกตัวมาจากโทเคน `--pf-*` ที่มีอยู่แล้ว (กฎเหล็กข้อ 1)
     สีของเส้นซ้ายบอก **ประเภท** ไม่ใช่บอกความสวย: ราชการ = น้ำเงินข้อมูล ·
     บริษัท = ม่วง (โทนเดียวกับป้าย `pf-badge--violet`)
   ⚠ สีอย่างเดียวบอกประเภทไม่ได้ — ทุกการ์ดมี **ป้ายที่มีข้อความ** กำกับเสมอ
     (คนตาบอดสีและคนที่พิมพ์หน้านี้ออกมาขาวดำต้องอ่านออกเหมือนกัน) */
.pf-holi-month {
  display: flex; align-items: center; gap: var(--pf-space-3);
  margin: var(--pf-space-6) 0 var(--pf-space-3);
}
.pf-holi-month:first-child { margin-top: 0; }
.pf-holi-month h3 {
  margin: 0; flex: 1; min-width: 0;
  font-size: var(--pf-size-compact); line-height: var(--pf-lh-compact);
  font-weight: var(--pf-w-semibold); color: var(--pf-text-primary);
}
.pf-holi-list { display: grid; gap: var(--pf-space-3); }
/* ★ ชิปสรุปด้านบน — กล่องเดียวเพื่อไม่ให้ `.pf-filterbar > *` บนมือถือดันแต่ละชิปเต็มแถว */
.pf-holi-sum { display: flex; flex-wrap: wrap; gap: var(--pf-space-2); align-items: center; }

/* ★ การ์ดหนึ่งวัน — เส้นสีซ้ายหนา 3px คือตัวบอกประเภทที่กวาดตาเห็นก่อนอ่านตัวอักษร */
.pf-holi {
  /* ★★★ กึ่งกลางแนวตั้ง — กล่องวันที่สูงสองบรรทัด เนื้อหาสูงสามบรรทัด
       ทั้งสองต้องดูอยู่กึ่งกลางของกันและกัน ไม่ใช่ชิดขอบใดขอบหนึ่ง
     🔴 ประวัติของบรรทัดนี้ — **สลับไปมาสองรอบเพราะผมแก้ผิดจุด**
       รอบ ① center + กล่องวันที่ถูกแบนเป็นแถวแนวนอนบนมือถือ
             ⇒ ชื่อวันหยุดลอยเหนือเลขวัน · ผมโทษ center แล้วเปลี่ยนเป็น flex-start
       รอบ ② flex-start ⇒ เลขวันไปติดขอบบนของการ์ด เหลือที่ว่างใต้มันเป็นแถบ
             (เจ้าของทัก: "วันที่กับวันมันติดขอบบนด้านในการ์ดเกินไป")
       ★ ต้นเหตุจริงของรอบ ① คือการแบนกล่องวันที่ ซึ่งถูกเอาออกไปแล้ว
         ⇒ center คือคำตอบที่ถูกมาตั้งแต่แรก · flex-start คือการแก้ผิดจุด
     ⚠ ปุ่มบนมือถืออยู่คนละบรรทัด (width:100%) `align-items` จึงจัดเฉพาะ
       บรรทัดแรก [วันที่][เนื้อหา] ให้กึ่งกลางกัน ซึ่งเป็นสิ่งที่ต้องการพอดี */
  display: flex; align-items: center; gap: var(--pf-space-4);
  /* ★ 12px รอบด้าน — 8px บน-ล่างทำให้กล่องวันที่แทบชนขอบใน (เจ้าของทัก 2026-09-04) */
  padding: var(--pf-space-4);
  border: 1px solid var(--pf-border);
  border-inline-start: 3px solid var(--pf-info-500);
  border-radius: var(--pf-radius-md);
  background: var(--pf-surface);
}
.pf-holi--company { border-inline-start-color: var(--pf-violet-700); }
/* ⚠ วันที่ปิดการจ่ายค่าจ้างรายวันต้องอ่านออกว่า "ต่างจากวันอื่น" ตั้งแต่ยังไม่อ่านป้าย */
.pf-holi--unpaid { border-inline-start-color: var(--pf-gray-400); }

/* ★★★ วันที่ผ่านไปแล้ว — ใช้กับปฏิทินวันเกิด/ครบรอบ (2026-09-04)
   เจ้าของทัก: ปฏิทินทั้งปีที่ทุกแถวหน้าตาเหมือนกัน ตอบไม่ได้ว่าอันไหนต้องเตรียมตัวต่อ
   ⚠⚠ **ไม่ใช้ opacity** — กฎเดียวกับแกนที่ไม่เด่นของกราฟ (บรรทัด ~1584)
     opacity ต่ำ ๆ ทำให้ตัวเลขตกเกณฑ์คอนทราสต์ทั้งที่ยังต้องอ่านออก
     ⇒ เปลี่ยน **สีตัวอักษร** ไปโทนรอง และลดพื้นลงหนึ่งชั้นแทน
   ★ เส้นสีซ้ายยังบอกประเภทเหมือนเดิม — ของที่ผ่านไปแล้วยังต้องรู้ว่ามันคือเรื่องอะไร
   ★ และหน้าจอมีข้อความ "ผ่านไปแล้ว" กำกับด้วยเสมอ — สีอย่างเดียวบอกไม่ได้

   🔴 **วัดสีจริงก่อนเชื่อว่ามันจางลง** — รอบแรกผมใช้ --pf-surface-subtle
     ธีมสว่าง: #FCFCFD บนพื้นการ์ด #FFFFFF ⇒ ต่างกันประมาณ 1% · **มองไม่เห็นเลย**
     (ธีมมืดใช้ได้ปกติ — จึงเป็นข้อผิดพลาดที่เห็นได้ธีมเดียว ซึ่งหลอกกว่าเดิม)
     ★ --pf-bg-secondary เป็นพื้นของ **นอกการ์ด** จึงต่างจากพื้นการ์ดจริงทั้งสองธีม */
.pf-holi--past { background: var(--pf-bg-secondary); }
.pf-holi--past .pf-holi-num,
.pf-holi--past .pf-holi-name { color: var(--pf-text-secondary); }

/* ★★ กล่องวันที่ — เลขวันตัวใหญ่ + ชื่อวันของสัปดาห์ตัวเล็กใต้เลข
   ⚠ ความกว้างคงที่ ไม่งั้นเลข 1 กับ 31 จะทำให้ชื่อวันหยุดไม่ตรงแนวกันทั้งรายการ */
.pf-holi-day {
  /* ★ 64px = พอให้ชื่อวันที่ยาวที่สุด ("พฤหัสบดี") อยู่บรรทัดเดียว
     ⚠ 56px ทำให้มันตัดสองบรรทัด แล้วกล่องวันที่สูงกว่าเนื้อหาข้าง ๆ การ์ดจะดูเอียง */
  flex: none; width: 64px; text-align: center;
  display: grid; gap: 2px;
}
.pf-holi-num {
  font-size: var(--pf-size-page); line-height: 1.1;
  font-weight: var(--pf-w-bold); color: var(--pf-primary-ink);
  font-variant-numeric: tabular-nums;
}
.pf-holi-dow {
  font-size: var(--pf-size-caption); line-height: var(--pf-lh-caption);
  color: var(--pf-text-muted);
}
.pf-holi-main { flex: 1; min-width: 0; display: grid; gap: var(--pf-space-2); }
.pf-holi-name {
  font-size: var(--pf-size-body); line-height: var(--pf-lh-body);
  font-weight: var(--pf-w-semibold); color: var(--pf-text-primary);
}
.pf-holi-tags { display: flex; flex-wrap: wrap; gap: var(--pf-space-2); align-items: center; }
/* ★★★ `flex-wrap: wrap` — **ขาดไปตั้งแต่แรก** (วัดเจอ 2026-09-04)
   หน้าวันหยุดมีปุ่มแค่สามอันจึงไม่เคยล้น · หน้าทรัพย์สินมีแปดอัน
     (จ่าย · รับคืน · ส่งซ่อม · แจ้งหาย · ปลดระวาง · ประวัติ · แก้ไข · ลบ)
   วัดที่จอ 623px: แถวปุ่มกว้าง 632px ดันการ์ดเป็น 660px ⇒ **ทั้งหน้าเลื่อนแนวนอน**
   ⚠ ทางแก้ที่ผิดคือซ่อนปุ่มที่กดไม่ได้ — ปุ่มพวกนั้นคือสิ่งที่บอกว่าต้องทำอะไรก่อน
     ★ ให้ตกบรรทัดแทน · การ์ดสูงขึ้นแต่ไม่มีอะไรหาย และไม่มีอะไรเลื่อนออกนอกจอ */
.pf-holi-acts { flex: none; display: flex; flex-wrap: wrap; gap: var(--pf-space-2); }

/* ★★★ มือถือ — **คงสองคอลัมน์ไว้เหมือนเดิม** ย้ายเฉพาะปุ่มลงไปเต็มแถวล่าง
   🔴 รอบแรกผมทำให้กล่องวันที่กลายเป็นแถวแนวนอน (`display:flex; width:auto`)
     ⇒ เลขวันไปนั่งกลางความสูงของการ์ด ส่วนชื่อวันหยุดลอยอยู่เหนือมัน
       ทั้งการ์ดอ่านแล้วเหมือนของสองชิ้นที่ไม่เกี่ยวกัน (เจ้าของทัก: "ดูไม่สมดุล")
   ★ คอลัมน์วันที่แคบลงและเลขเล็กลง แต่ยังเป็นคอลัมน์ ⇒ ทุกการ์ดเรียงตรงแนวเดียวกัน */
@media (max-width: 640px) {
  .pf-holi { flex-wrap: wrap; gap: var(--pf-space-3); }
  .pf-holi-day { width: 52px; }
  .pf-holi-num { font-size: var(--pf-size-section); }
  /* ★★ ปุ่มลงแถวของตัวเองและ **แบ่งความกว้างกันเต็มแถว**
     ⚠ ชิดขวาอย่างเดียว = เหลือที่ว่างครึ่งแถวทางซ้าย การ์ดดูเอียง (เจ้าของทัก)
     ★ `1 0 auto` = เริ่มจากความกว้างของคำตัวเอง แล้วแบ่งที่เหลือเท่า ๆ กัน
       ⇒ ปุ่มคำยาวได้ที่มากกว่าปุ่มคำสั้นตามธรรมชาติ ไม่มีคำไหนถูกตัดบรรทัด
       (บทเรียนเดียวกับ `.pf-actions` และแถบตัวกรอง — ดู P351) */
  .pf-holi-acts { width: 100%; }
  .pf-holi-acts .pf-btn { flex: 1 0 auto; }
}

/* ═══ คลังคำถามแบบสำรวจ (069) ═══════════════════════════════════════════
   ★★★ หน้านี้เป็น **รายการการ์ดที่แก้ได้ในที่** ไม่ใช่ตารางที่ต้องเปิดโมดัลทีละข้อ
     ⚠ โมดัลทีละข้อ = ตั้งค่ายี่สิบข้อต้องเปิด-ปิดยี่สิบครั้ง และเทียบข้อข้าง ๆ ไม่ได้เลย
       ซึ่งเป็นสิ่งที่คนเขียนชุดคำถามต้องทำตลอดเวลา                                    */
.pf-qbank { display: grid; gap: var(--pf-space-4); }

/* ★ การ์ดหนึ่งข้อ — หัวการ์ดถือรหัส · ป้ายล็อก · ปุ่มลำดับ · สวิตช์เปิดใช้ */
.pf-qitem .pf-card-head { flex-wrap: wrap; gap: var(--pf-space-3); }

/* ★★ ข้อที่ปิดใช้ต้องหรี่ **ทั้งการ์ด** ไม่ใช่หรี่แค่ป้าย
   ⚠ ป้าย "ปิดใช้" อย่างเดียวอ่านยากในรายการยาว — ตาต้องไล่หาป้ายทีละใบ
     (เหตุผลเดียวกับ `.pf-row--off` ของตาราง)
   ★ ไม่ใช้ `display:none` — ข้อที่ปิดยังต้องเห็นและเปิดกลับได้ */
.pf-qitem--off { opacity: .62; }

/* ★★ ปุ่มเลื่อนลำดับต้องอยู่ติดกันเสมอ — หัวการ์ด wrap ได้ และตัวคั่นยืดหยุ่น
   จะดันปุ่มขึ้นไปสุดขวาแถวบน ปุ่มลงตกไปสุดซ้ายแถวล่าง (วัดที่ 375px) */
.pf-qitem-ord { display: inline-flex; gap: var(--pf-space-1); flex: none; }

/* ★ เลขลำดับข้อ — ตัวเลขทุกที่ต้อง tabular-nums (CLAUDE.md) */
.pf-qitem-no {
  min-width: 28px; text-align: center;
  font-size: var(--pf-size-body); line-height: var(--pf-lh-body);
  font-weight: var(--pf-w-bold); color: var(--pf-primary-ink);
  font-variant-numeric: tabular-nums;
}

/* ★★★★ คำเตือนของข้อที่มีคนตอบไปแล้ว — **ไม่ใช่ข้อผิดพลาด**
   🔴 รอบแรกผมล็อกช่องพวกนี้ทิ้งไว้เลย ซึ่งผิด (เจ้าของทัก 2026-09-04)
     แก้ข้อความได้ตามปกติ · นี่แค่บอกผลที่ตามมาให้เห็นก่อนกดบันทึก
   ⚠ จึงเป็น `.pf-caption` โทนปกติ **ไม่ใช่ `.pf-error-text` สีแดง**
     แดง = "คุณทำผิด" ซึ่งไม่จริง และจะทำให้คนไม่กล้าแก้ทั้งที่แก้ได้
   ★ ไอคอนต้องไม่ถูกบีบตอนข้อความตกหลายบรรทัด */
.pf-qitem-warn {
  display: flex; gap: var(--pf-space-2); align-items: flex-start;
  margin: var(--pf-space-3) 0 0;
}
.pf-qitem-warn .pf-ico { flex: none; margin-top: 2px; }

/* ★★ แถบเครื่องมือท้ายรายการ — "เพิ่มข้อ" ซ้าย · "บันทึก" ขวา
   ⚠ ต้อง wrap · จอแคบมีสามปุ่มเรียงกันแล้วดันหน้าเลื่อนแนวนอน (บทเรียน .pf-holi-acts) */
.pf-qbank-acts {
  display: flex; flex-wrap: wrap; gap: var(--pf-space-2); align-items: center;
  margin-top: var(--pf-space-4);
}
@media (max-width: 640px) {
  /* ★ ปุ่มแบ่งความกว้างกันเต็มแถว — `1 0 auto` ให้คำยาวได้ที่มากกว่าคำสั้น (P351) */
  .pf-qbank-acts .pf-btn { flex: 1 0 auto; }
  /* ⚠ ตัวคั่นยืดหยุ่นบนจอแคบจะกินทั้งแถวจนปุ่มบันทึกตกไปบรรทัดของมันเอง */
  .pf-qbank-acts .sp { display: none; }
}
