/*
 * ส่วนเสริมของ Profit Command Center — ต่อยอดจาก `app.min.css` ของเทมเพลต Rizz
 *
 * 🔴 ห้ามแก้ไฟล์ของเทมเพลต (`app.min.css`, `bootstrap.min.css`, `icons.min.css`)
 *    การอัปเดตเทมเพลตครั้งหน้าจะทับทิ้งทั้งหมดโดยไม่มีอะไรฟ้อง
 *    ทุกการปรับแต่งของโปรเจกต์นี้อยู่ในไฟล์นี้ไฟล์เดียว
 *    (กติกาเต็ม: .claude/UX_TEMPLATE.md §3)
 *
 * ⚠️ ใช้ตัวแปรของ Bootstrap (`--bs-*`) เท่านั้น อย่าฝังค่าสีดิบ
 *    มิฉะนั้นโหมดมืด (`html[data-bs-theme=dark]`) จะอ่านไม่ออกในบางการ์ด
 */

/* การ์ด KPI: เส้นบางด้านซ้ายบอกว่าเป็นตัวชี้วัด ไม่ใช่การ์ดเนื้อหาทั่วไป */
.kpi-card {
    border-inline-start: 3px solid var(--bs-primary);
}

.kpi-card [data-kpi-value] {
    font-variant-numeric: tabular-nums;
    /* ตัวเลขต้องไม่ขยับซ้ายขวาตอนอัปเดตสด — ความกว้างของหลักต้องเท่ากัน */
}

/* จังหวะกะพริบสั้น ๆ ตอนตัวเลขเปลี่ยนจากสตรีม เพื่อให้ตาจับได้ว่าอะไรขยับ */
.kpi-card.is-updated {
    animation: kpi-flash 600ms ease-out;
}

@keyframes kpi-flash {
    0%   { background-color: rgba(var(--bs-primary-rgb), 0.12); }
    100% { background-color: transparent; }
}

/*
 * 🔴 เคารพผู้ใช้ที่ตั้งค่าลดการเคลื่อนไหว — ข้อกำหนดการเข้าถึง
 *    การกะพริบเป็นสิ่งกระตุ้นอาการของผู้ใช้บางกลุ่ม และที่นี่มันเป็นแค่การตกแต่ง
 */
@media (prefers-reduced-motion: reduce) {
    .kpi-card.is-updated {
        animation: none;
        outline: 2px solid rgba(var(--bs-primary-rgb), 0.5);
    }
}

.insight-card {
    border-left-width: 4px !important;
    background-color: var(--bs-body-bg);
}

.insight-card dt {
    white-space: nowrap;
}

/* ป้ายสถานะสตรีมบนแถบบน — สีสื่อความหมาย ไม่ใช่การตกแต่ง */
#stream-status[data-state="live"]         { background-color: var(--bs-success-bg-subtle) !important; color: var(--bs-success-text-emphasis) !important; }
#stream-status[data-state="connecting"]   { background-color: var(--bs-warning-bg-subtle) !important; color: var(--bs-warning-text-emphasis) !important; }
#stream-status[data-state="disconnected"] { background-color: var(--bs-danger-bg-subtle)  !important; color: var(--bs-danger-text-emphasis)  !important; }

/* ตารางหนาแน่นแต่ยังอ่านออก — ความสูงแถวขั้นต่ำเพื่อให้แตะบนมือถือได้ */
.table td,
.table th {
    min-height: 2.75rem;
    vertical-align: middle;
}

/* ตัวเลขในตารางเรียงหลักตรงกันเสมอ */
.table .text-end {
    font-variant-numeric: tabular-nums;
}

/*
 * เป้าหมายการแตะขั้นต่ำ 44px
 *
 * ⚠️ **เดิมผูกกับความกว้างจอ (`max-width: 575.98px`) ซึ่งผิด**
 *    แท็บเล็ตกว้าง 768–1024px ก็เป็นอุปกรณ์สัมผัส แต่ไม่เข้าเงื่อนไขนั้น
 *    → ปุ่มในตารางบนแท็บเล็ตเล็กกว่า 44px ทั้งที่ใช้นิ้วกดเหมือนกัน
 *
 * 🔴 `pointer: coarse` ถามสิ่งที่ตรงประเด็นกว่า: **อุปกรณ์ชี้ตำแหน่งหยาบไหม**
 *    (นิ้ว = coarse · เมาส์ = fine) ซึ่งไม่เกี่ยวกับความกว้างจอเลย
 *    คงเงื่อนไขความกว้างไว้ด้วย เผื่อเบราว์เซอร์เก่าที่ไม่รู้จัก `pointer`
 */
@media (pointer: coarse), (max-width: 575.98px) {
    .btn-sm,
    .form-check-input,
    .nav-link {
        min-height: 44px;
        display: inline-flex;
        align-items: center;
    }

    /* ⚠️ checkbox/radio ต้องกว้างด้วย ไม่ใช่แค่สูง — พื้นที่แตะเป็นสี่เหลี่ยม */
    .form-check-input {
        min-width: 1.25rem;
    }

    /* ระยะห่างระหว่างเป้าหมายอย่างน้อย 8px กันกดผิดปุ่ม (กฎ `touch-spacing`) */
    .table td .btn + .btn {
        margin-inline-start: .5rem;
    }
}

/*
 * ลดหน่วงการแตะ ~300ms ที่เบราว์เซอร์มือถือใส่ไว้เพื่อรอ double-tap zoom
 *
 * ⚠️ **ห้ามใช้ `touch-action: none`** — จะปิด pinch-zoom ทั้งหน้า
 *    ซึ่งเป็นเครื่องมือหลักของผู้ใช้สายตาเลือนราง · `manipulation` ปิดเฉพาะ
 *    double-tap zoom แต่ยัง pinch-zoom และเลื่อนได้ตามปกติ (กฎ `tap-delay`)
 */
.btn,
.nav-link,
.form-check-input,
summary {
    touch-action: manipulation;
}

/* ═══════════════════════════════════════════════════════════════════════
   Phase A — สินค้า/ต้นทุน · การเชื่อมร้าน · ค่าธรรมเนียม · ผู้ช่วยตั้งราคา
   ═══════════════════════════════════════════════════════════════════════ */

/*
 * ตัวเลขเงินและเปอร์เซ็นต์ทุกที่ต้องเรียงหลักตรงกัน
 * ⚠️ ไม่ได้เจาะจงแค่ `.table .text-end` เพราะการ์ดผลลัพธ์ของผู้ช่วยตั้งราคา
 *    ก็มีตัวเลขที่ต้องเทียบกันด้วยสายตาเหมือนกัน
 */
.fs-4,
[data-kpi-value],
.table td.text-end,
.table th.text-end {
    font-variant-numeric: tabular-nums;
}

/*
 * 🔴 ลิงก์ข้ามไปเนื้อหาต้องมองเห็นได้จริงตอนโฟกัส
 *    `.visually-hidden-focusable` ของ Bootstrap ทำให้มันโผล่มาก็จริง
 *    แต่ถ้าไม่มีพื้นหลังทึบ มันจะทับข้อความข้างหลังจนอ่านไม่ออก
 */
.visually-hidden-focusable:focus,
.visually-hidden-focusable:focus-within {
    background-color: var(--bs-primary);
    color: var(--bs-white);
    padding: .5rem .75rem;
}

/*
 * 🔴 ห้ามลบเส้นโฟกัส — ผู้ใช้คีย์บอร์ดต้องเห็นว่าตัวเองอยู่ตรงไหน
 *    เทมเพลตบางการ์ดตั้ง `outline: none` ไว้ · ทวงคืนตรงนี้
 */
.form-check-input:focus-visible,
.btn:focus-visible,
a:focus-visible,
summary:focus-visible {
    outline: 2px solid var(--bs-primary);
    outline-offset: 2px;
}

/*
 * ตัวหมุนบอกขั้นของวิซาร์ด — แท็บที่กดไม่ได้ต้องดูต่างจากแท็บที่กดได้
 * ⚠️ ใช้ตัวแปรของธีมเท่านั้น มิฉะนั้นโหมดมืดจะอ่านไม่ออก
 *    (.claude/UX_TEMPLATE.md §4)
 */
#pricing-tabs .nav-link {
    min-height: 44px;
}

/*
 * รายการที่พับไว้ (`<details>`) — ตัวชี้ต้องบอกว่ากดได้
 * ⚠️ `cursor` ถูกตั้ง inline ใน view ด้วยเพราะบางเบราว์เซอร์ยังไม่ให้ค่าเริ่มต้น
 */
details > summary {
    user-select: none;
}

/*
 * การ์ดช่องทางในวิซาร์ด — ช่องที่เลือกไม่ได้ต้องดูจางแต่ยังอ่านออก
 * 🔴 ห้ามใช้ `opacity` ต่ำกว่านี้ · ข้อความที่จางเกินไปไม่ผ่านเกณฑ์คอนทราสต์ 4.5:1
 *    แล้วผู้ใช้จะอ่านไม่ออกว่าทำไมช่องทางนั้นเลือกไม่ได้
 */
.opacity-75 .form-check-label {
    color: var(--bs-body-color);
}

/*
 * ตารางที่มีคอลัมน์เยอะบนจอแคบ — บังคับให้เลื่อนในกล่องของตัวเอง
 * 🔴 หน้าเว็บทั้งหน้าต้องไม่เลื่อนแนวนอน (.claude/UX_TEMPLATE.md §6)
 *
 * ═══ 🔴 ทำไมต้องมี `position: relative` (เพิ่ม 2026-08-22) ═══
 *
 * `overflow-x: auto` **ไม่คุมลูกหลานที่เป็น `position: absolute`** ถ้าตัวกล่อง
 * เองยัง `static` — ลูกกลุ่มนั้นจะใช้ containing block ที่อยู่ **นอกกรอบ**
 * แล้วหลุดออกไปดันความกว้างของทั้งเอกสาร
 *
 * ตัวที่เจอจริง: `.visually-hidden` ของ Bootstrap (`position:absolute !important`)
 * ที่อยู่ในหัวคอลัมน์ขวาสุดของตารางสินค้า → หน้า `/products` เลื่อนแนวนอน 336px
 * บนจอ 390px ทั้งที่ตารางอยู่ในกรอบเลื่อนเรียบร้อย
 *
 * ⚠️ หาตัวการยากผิดปกติ เพราะ `browser-check.mjs` กรอง `.visually-hidden`
 *    ออกจากรายการผู้ต้องสงสัย (ปกติมันคือ false positive) รายงานจึงชี้ไปที่
 *    `<table>` ซึ่งไม่ผิดอะไรเลย
 *
 * ⚠️ ไม่มีผลกับ dropdown ในตาราง — `.dropdown` เป็น `position:relative`
 *    อยู่แล้ว จึงเป็น containing block ของเมนูตัวเองไม่ว่ากรอบนี้จะเป็นอะไร
 */
.table-responsive {
    -webkit-overflow-scrolling: touch;
    position: relative;
}

/* ═══════════════════════════════════════════════════════════════════════
 * ช่อง "ถามเกี่ยวกับข้อเสนอนี้" — หน้า /insights
 *
 * 🔴 ทุกสีมาจากตัวแปรของธีม ห้าม hex ดิบ (design-system MASTER.md §1)
 *    สีที่ฝังตายจะอ่านไม่ออกทันทีที่สลับเป็นโหมดมืด
 * ═══════════════════════════════════════════════════════════════════════ */

/*
 * หัวข้อที่พับได้ — ต้องอ่านออกว่ากดได้โดยไม่ต้องเอาเมาส์ไปชี้
 * ⚠️ พื้นที่กดต้องสูงพอสำหรับนิ้ว (44px) ไม่ใช่แค่ความสูงของตัวอักษร
 */
.insight-ask-summary {
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 0.375rem;
    min-height: 44px;
    color: var(--bs-primary);
    transition: color 0.15s ease-in-out;
}

.insight-ask-summary:hover {
    color: var(--bs-link-hover-color);
}

/*
 * บทสนทนา — จำกัดความสูงแล้วให้เลื่อนในตัวเอง
 * 🔴 ถ้าไม่จำกัด การ์ดที่ถามไปสิบคำถามจะสูงจนกลบข้อเสนอข้ออื่นทั้งหน้า
 *    ซึ่งทำให้ฟีเจอร์ที่ตั้งใจช่วย กลายเป็นตัวบังสิ่งที่ผู้ใช้มาดู
 */
.insight-ask-thread {
    max-height: 26rem;
    overflow-y: auto;
}

.insight-ask-thread:not(:empty) {
    margin-bottom: 0.5rem;
}

.insight-turn + .insight-turn {
    margin-top: 0.75rem;
    padding-top: 0.75rem;
    border-top: 1px dashed var(--bs-border-color);
}

/*
 * คำถามของผู้ใช้ — ต้องแยกออกจากคำตอบด้วยสายตา
 * 🔴 แยกด้วย "สีอย่างเดียว" ไม่ได้ (MASTER.md §1) จึงใช้ตัวหนา + แถบซ้ายคู่กัน
 */
.insight-turn-question {
    font-weight: 600;
    padding-left: 0.5rem;
    border-left: 3px solid var(--bs-primary);
    color: var(--bs-body-color);
}

.insight-turn-answer {
    padding-left: 0.5rem;
    font-size: 0.8125rem;
}

/*
 * ตัวเลขที่กฎใช้ — ต้องเรียงตรงกันเพื่อให้กวาดตาเทียบได้
 * ⚠️ `tabular-nums` จำเป็นทุกที่ที่มีตัวเลขเงิน (MASTER.md §2)
 */
.insight-turn-facts {
    border-left: 2px solid var(--bs-border-color);
    padding-left: 0.5rem;
}

.insight-fact {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 0.5rem;
    padding: 0.125rem 0;
}

.insight-fact-label::after {
    content: ":";
}

.insight-fact-value {
    font-variant-numeric: tabular-nums;
}

/*
 * คำอธิบายว่าทำไมคำตอบนี้ไม่ได้เขียนโดย AI
 * 🔴 เหลือง = "ข้อมูลไม่ครบ / ต้องอ่านก่อนเชื่อ" ตามความหมายของสีในระบบ (MASTER.md §1)
 *    และมีไอคอนกำกับในข้อความอยู่แล้ว จึงไม่ได้สื่อด้วยสีอย่างเดียว
 */
.insight-turn-notice {
    border-left: 3px solid var(--bs-warning);
    padding: 0.25rem 0.5rem;
    background-color: var(--bs-warning-bg-subtle);
    color: var(--bs-body-color);
    border-radius: 0.25rem;
}

/*
 * ปุ่มคำถามสำเร็จรูป — ตัดข้อความไม่ได้ ต้องอ่านครบทั้งประโยค
 * ⚠️ ผู้ใช้เลือกจากข้อความบนปุ่ม ปุ่มที่ถูกตัดกลางประโยคเลือกไม่ถูก
 */
.insight-ask-chip {
    white-space: normal;
    text-align: left;
}

.insight-turn-feedback button {
    text-decoration: underline;
}

/* ═══════════════════════════════════════════════════════════════════════
 * การไฮไลต์รายการที่ถูกลิงก์จากข้อเสนอพามา (หน้า AI Assistant)
 *
 * ⚠️ บล็อก `.insight-evidence*` ถูกลบเมื่อ 2026-08-05 พร้อมกับหัวข้อ
 *    "ตัวเลขที่ใช้และจุดที่ต้องไปแก้" บนการ์ด · ตัวเลขยังแสดงในคำตอบของ
 *    ช่องถามตอบ ซึ่งใช้ `.insight-turn-facts` คนละชุดกัน
 * ═══════════════════════════════════════════════════════════════════════ */

/*
 * แถว/การ์ดที่ถูกลิงก์จากข้อเสนอพามา
 *
 * 🔴 ห้ามสื่อด้วยสีอย่างเดียว (MASTER.md §1) — วิวใส่ไอคอนและข้อความกำกับไว้แล้ว
 *    เส้นซ้ายเป็นตัวช่วยกวาดตาเท่านั้น ไม่ใช่ตัวสื่อความหมายเดียว
 *
 * ⚠️ ไม่ใช้อนิเมชันกะพริบ — ผู้ใช้ที่ตั้ง prefers-reduced-motion ตั้งเพราะมันกระตุ้น
 *    อาการเวียนหัว ไม่ใช่เพราะไม่ชอบ · เส้นนิ่ง ๆ ทำงานได้เหมือนกันและไม่ทำร้ายใคร
 */
.insight-focus {
    box-shadow: inset 0.25rem 0 0 0 var(--bs-primary);
}

/* ⚠️ `scroll-margin-top` กัน fragment เลื่อนไปแล้วโดนแถบบนตรึงบัง */
[id^="channel-"] {
    scroll-margin-top: 5rem;
}

/*
 * ═══ ตัวเลขที่ต้องกวาดตาเทียบกันได้ ═══
 *
 * 🔴 ตัวเลขเงิน/เปอร์เซ็นต์ทุกที่ต้องเป็น `tabular-nums` (MASTER.md §2)
 *    ไม่งั้นคอลัมน์ในตารางไม่ตรงกัน และตัวเลขขยับซ้ายขวาตอนอัปเดตสด
 *
 * ⚠️ เดิมกฎนี้ถูกเขียนซ้ำในทุก selector ที่มีตัวเลข ทำให้หน้าใหม่ที่ลืมเขียน
 *    ได้ฟอนต์แบบสัดส่วนเงียบ ๆ · คลาสเดียวใช้ซ้ำได้ทุกที่
 */
.tabular {
    font-variant-numeric: tabular-nums;
}

/* ═══ หน้า /connections — แถวร้านค้าออนไลน์ (2026-08-05) ═══════════════ */

/**
 * 🔴 สัญญาณ "ต้องจัดการ" เป็น **แถบซ้าย** วาดด้วย box-shadow ไม่ใช่ border
 *
 * เหตุผลที่ไม่ใช่ border (เจอจากการตรวจย้อน 2026-08-05 — เวอร์ชัน border
 * **ไม่เคยวาดอะไรเลย** ทั้งที่เทสต์ผ่าน):
 *
 * 1. Bootstrap ตั้ง `.list-group-flush > .list-group-item { border-width: 0 0 … }`
 *    ที่ specificity (0,2,0) ซึ่ง **ชนะ** `.connection-row { border-left: … }`
 *    ที่ (0,1,0) เสมอ ไม่ว่าไฟล์ไหนโหลดทีหลัง → ความกว้างขอบซ้ายเป็น 0
 *    แล้วกฎที่ตั้งแค่ *สี* ของขอบก็ระบายบนความกว้างศูนย์ = มองไม่เห็น
 * 2. ต่อให้ชนะ การสลับ border 0→4px ตอน JS เปลี่ยน `data-attention`
 *    จะดันเนื้อหาทั้งแถวขยับ 4px ใต้นิ้วผู้ใช้
 *
 * `box-shadow: inset` ไม่มีกฎไหนของ Bootstrap มาแตะ และไม่กินพื้นที่ layout
 *
 * ⚠️ ใช้ data-attribute ตามแบบ `#stream-status[data-state]` ที่มีอยู่แล้วในไฟล์นี้
 *    เพื่อให้ JS เปลี่ยนสถานะได้ด้วยการเขียน attribute เดียว ไม่ต้องยุ่งกับ className
 */
.connection-row {
    transition: background-color .15s ease-in-out, box-shadow .15s ease-in-out;
}

.connection-row[data-attention="true"] {
    box-shadow: inset 4px 0 0 var(--bs-danger);
    background-color: var(--bs-danger-bg-subtle);
}

/**
 * ⚠️ เมนู ⋮ ถือปุ่ม "ยกเลิกการเชื่อมต่อ" ซึ่งกู้คืนไม่ได้
 *    `.dropdown-item` สูงราว 32px — ยังไม่ผ่านเกณฑ์นิ้ว (WCAG 2.2 SC 2.5.8)
 *    และกฎ `pointer: coarse` เดิมในไฟล์นี้ครอบแค่ `.btn-sm` / `.nav-link`
 */
@media (pointer: coarse) {
    .dropdown-item {
        min-height: 44px;
        display: flex;
        align-items: center;
    }
}

/**
 * ═══ ธงชาติของปุ่มสลับภาษา (2026-08-09 รอบ 28) ═══
 *
 * SVG ถูกวาดบนกรอบ 20×14 (`App\Core\Flags::VIEWBOX`) — ตั้งขนาดตายตัวที่นี่
 * ⚠️ **ต้องมี `flex-shrink: 0`** — ธงอยู่ใน flex row ที่มีข้อความยาวกว่า
 *    (ชื่อภาษาบางภาษายาว) ถ้าไม่ล็อก ธงจะถูกบีบจนแบนเป็นเส้น ซึ่งอ่านว่าภาพเสีย
 * ⚠️ ขอบจาง ๆ จำเป็นสำหรับธงที่มีขาวติดขอบ (ญี่ปุ่น/เกาหลี) — ไม่งั้นธงจะดู
 *    เหมือนลอยอยู่บนพื้นขาวโดยไม่มีรูปร่าง
 */
.app-flag {
    width: 20px;
    height: 14px;
    flex-shrink: 0;
    border-radius: 2px;
    box-shadow: 0 0 0 1px rgb(0 0 0 / 12%);
    display: inline-block;
    vertical-align: -2px;
}

/* ภาษาที่ยังไม่มีธงในระบบ — ป้ายรหัสตัวอักษรแทน ไม่ใช่ช่องว่าง */
.app-flag-text {
    width: auto;
    min-width: 20px;
    padding: 0 3px;
    font-size: .625rem;
    line-height: 14px;
    text-align: center;
    font-weight: 600;
    color: var(--bs-secondary-color);
    background: var(--bs-secondary-bg);
}

/**
 * ⚠️ รายการภาษาที่กำลังใช้อยู่ (`.dropdown-item.active`) พื้นเป็นสีน้ำเงินเข้ม
 *    ไอคอนถูกใน Bootstrap ยังเป็น `currentColor` = ขาว ซึ่งถูกแล้ว
 *    แต่เงาขอบธงบนพื้นเข้มมองไม่เห็น → เปลี่ยนเป็นขอบสว่างแทน
 */
.dropdown-item.active .app-flag {
    box-shadow: 0 0 0 1px rgb(255 255 255 / 45%);
}

/**
 * ═══ แผงตัวอย่างการคำนวณของหน้ากติกาค่าธรรมเนียม ═══
 *
 * 🔴 **`position: sticky` ไม่ใช่ `relative`** — บั๊กที่เจอจริง 2026-08-10:
 *    เขียน `class="position-xl-sticky" style="top: 5rem"` ซึ่ง
 *    `position-xl-sticky` **ไม่ใช่คลาสของ Bootstrap 5** (ของจริงคือ `sticky-xl-top`)
 *    คลาสจึงไม่ทำอะไร แต่ `top: 5rem` ยังมีผลกับ `.card` ที่เป็น `position: relative`
 *    → การ์ดถูกดันลง 80px **โดยไม่กันที่ไว้** แล้วทับการ์ดปุ่มด้านล่าง 56px
 *    **ปุ่ม "คำนวณตัวอย่าง" จึงกดไม่ได้เลย** (Playwright จับได้ว่าคลิกไม่โดน)
 *
 *    `relative` เลื่อนภาพแต่ไม่ขยับ layout · `sticky` ยังอยู่ใน flow จึงไม่ทับใคร
 *
 * ⚠️ เฉพาะจอกว้าง (≥1200px) — บนจอแคบคอลัมน์นี้อยู่ใต้ฟอร์มอยู่แล้ว
 *    การทำ sticky จะบังเนื้อหาแทนที่จะช่วย
 * ⚠️ `top` เว้นระยะให้พ้นแถบบนที่ตรึงอยู่ (`.topbar`)
 */
@media (min-width: 1200px) {
    .fee-rule-preview {
        position: sticky;
        top: 5rem;
    }
}

/* ══════════════════════════════════════════════════════════════════════
   แถบบน — กันหน้าเลื่อนแนวนอนบนมือถือ
   ══════════════════════════════════════════════════════════════════════

   🔴 อาการ: ที่ความกว้าง 390px หน้าแดชบอร์ด **เลื่อนไปทางขวาได้**
      ซึ่งบนมือถือคือข้อบกพร่องที่ผู้ใช้เจอทันทีตั้งแต่วินาทีแรก
      (`tools/test-browser.php` เป็นตัวจับ — ไม่มีเทสต์เดิมชุดไหนเห็น
      เพราะทุกชุดตรวจแค่ HTML ที่เรนเดอร์ ไม่ได้วัดขนาดจริง)

   ต้นเหตุ: หัวข้อหน้าใน `.welcome-text` มี `.text-truncate` อยู่แล้ว
   แต่ **ตัดไม่ทำงานเมื่ออยู่ใน flex** เพราะ flex item มีค่าเริ่มต้น
   `min-width: auto` = "ห้ามหดเล็กกว่าเนื้อหาข้างใน" หัวข้อยาวจึงดัน
   ความกว้างของทั้งแถบเกินจอ

   ⚠️ แก้ที่ `min-width` **ไม่ใช่** ซ่อนหัวข้อทิ้งบนจอเล็ก —
      ผู้ใช้มือถือต้องรู้ว่ากำลังดูหน้าไหนอยู่เหมือนกัน
   ⚠️ กลุ่มขวา (ภาษา · บัญชี · สถานะสตรีม) ตั้ง `flex-shrink: 0`
      เพราะเป็นไอคอนที่หดแล้วกดไม่โดน (กฎเป้าสัมผัส 44×44px)
   ────────────────────────────────────────────────────────────────── */
/*
   🔴 วัดจริงที่ 390px (ด้วย Chromium ผ่าน CDP ไม่ใช่การเดา):
      หน้าเลื่อนแนวนอนได้ 27px · `.topbar` ลงเอยที่ `margin-left: 27px`
      ทั้งที่ `left: 0` และ `right: 0`

      ต้นเหตุคือเทมเพลต Rizz ตั้ง `.topbar` เป็น
      `position: fixed; left: 0; right: 0; margin-left: auto;`
      **พร้อมกับ** `width: calc(100% - var(--bs-startbar-width))`
      → เมื่อความกว้างที่คิดได้ไม่พอดีกับช่อง `left/right`
        margin แบบ `auto` จะดันแถบไปทางขวาจนล้นจอ

   ⚠️ **ไม่แก้ไฟล์ของเทมเพลต** (กฎเหล็ก §0.5) — ลบล้างเฉพาะช่วงจอที่
      แถบข้างเป็นแบบเลื่อนทับ (≤1024px) ซึ่งเป็นช่วงที่แถบบนควรเต็มจอพอดี

   🔴 ใช้ `width: auto` แล้วให้ `left/right` เป็นตัวกำหนดความกว้าง
      **อย่าใช้ `overflow-x: hidden` ที่ body แทน** — นั่นคือการซ่อนอาการ
      แถบยังเลยขอบอยู่ เมนูบัญชีจะถูกตัดจนกดไม่ได้ โดยเทสต์กลับเขียว
*/
/*
   ⚠️ **ต้องใช้ `body[data-sidebar-size]` ไม่ใช่ `.topbar` เปล่า ๆ**
      เทมเพลตเขียนกฎไว้ที่ `body[data-sidebar-size=collapsed] .topbar`
      ซึ่งมี specificity สูงกว่า `.topbar` — เขียนสั้นกว่าเมื่อไหร่ กฎนี้แพ้เงียบ ๆ
      (พลาดมาแล้วหนึ่งรอบ: แก้แล้วเทสต์ยังตกเหมือนเดิมทุกประการ)
*/
@media (max-width: 1024px) {
    body[data-sidebar-size] .topbar {
        left: 0;
        right: 0;
        /*
           🔴 ต้องระบุ `100vw` ตรง ๆ — `width: auto` ไม่ได้ผล
              วัดแล้วพบว่า Chromium คิดความกว้างจาก **เนื้อหา** (shrink-to-fit)
              ได้ 417px บนจอ 390px ทั้งที่ `left: 0` และ `right: 0` ครบ
        */
        width: 100vw;
        max-width: 100vw;
        margin-left: 0;
        margin-right: 0;
    }
}

#topbar-custom {
    min-width: 0;
}

#topbar-custom > .topbar-item:first-child {
    min-width: 0;
    flex: 1 1 auto;
}

#topbar-custom > .topbar-item:first-child .welcome-text {
    min-width: 0;
}

#topbar-custom > .topbar-item:last-child {
    flex: 0 0 auto;
}

/* ══════════════════════════════════════════════════════════════════════
   กลุ่มเมนูที่พับได้ + ป้ายกลุ่มในโหมดไอคอนล้วน (2026-08-22 รอบ 47)
   ══════════════════════════════════════════════════════════════════════

   กติกาทั้งสี่ข้อและเหตุผล อยู่ในหัวไฟล์ `app/Views/partials/startbar.php`
   ที่นี่คือ *การบังคับใช้* ข้อ 4 (โหมด rail พับไม่ได้) และรูปลักษณ์ของปุ่ม

   🔴 **ห้ามแก้ `app.min.css`** (กฎเหล็ก §0.5) — ทุกอย่างที่นี่เป็นการ
      *ต่อยอด* selector ของเทมเพลต ไม่ใช่การแก้ไฟล์เทมเพลต
   ────────────────────────────────────────────────────────────────── */

/*
   ป้ายกลุ่มกลายเป็นปุ่ม — ระยะห่างจึงต้องย้ายจาก `<li>` มาไว้ที่ปุ่ม
   มิฉะนั้นพื้นที่กดจะเป็นแค่ตัวหนังสือ ส่วนขอบ 20px รอบ ๆ กดไม่ติด
   ซึ่งอ่านว่า "ปุ่มเสีย" ไม่ใช่ "กดไม่ตรง"

   ⚠️ ต้องเลียน specificity ของเทมเพลต (`.startbar .startbar-menu
      .navbar-nav .menu-label` = 0,4,0) แล้วบวกอีกหนึ่งคลาส ไม่งั้นแพ้
*/
.startbar .startbar-menu .navbar-nav li.menu-label {
    padding: 0;
}

.startbar .startbar-menu .navbar-nav .menu-label .menu-group-toggle {
    /* ปุ่มโปร่งใสที่ "ดูเหมือนป้าย" — สืบทอดตัวอักษรจาก .menu-label ทั้งหมด */
    display: flex;
    align-items: center;
    gap: .5rem;
    width: 100%;
    padding: 14px 12px 14px 20px;
    border: 0;
    background: none;
    color: inherit;
    font: inherit;
    letter-spacing: inherit;
    text-transform: inherit;
    text-align: inherit;
    border-radius: 10px;
    cursor: pointer;
    transition: background-color .15s ease-in-out, color .15s ease-in-out;
}

/*
   ⚠️ hover/focus ต้อง **ไม่ขยับเลย์เอาต์** (MASTER.md §5) — เปลี่ยนแค่สีพื้น
      การใส่ transform: scale ตรงนี้จะทำให้เมนูทั้งคอลัมน์ขยับใต้เมาส์ผู้ใช้
*/
.startbar .startbar-menu .navbar-nav .menu-label .menu-group-toggle:hover,
.startbar .startbar-menu .navbar-nav .menu-label .menu-group-toggle:focus-visible {
    background-color: var(--bs-secondary-bg);
    color: var(--bs-body-color);
}

/*
   🔴 โฟกัสต้องมองเห็นได้ — ผู้ใช้คีย์บอร์ดเดินเมนูนี้ทั้งคอลัมน์
      (กฎ `focus-states` · MASTER.md §6)
*/
.startbar .startbar-menu .navbar-nav .menu-label .menu-group-toggle:focus-visible {
    outline: 2px solid var(--bs-primary);
    outline-offset: -2px;
}

/* ตัวย่อของกลุ่ม (โหมด rail) — เทมเพลตซ่อนไว้ในโหมดปกติอยู่แล้ว */
.startbar .startbar-menu .navbar-nav .menu-label .menu-group-toggle small {
    letter-spacing: 0;
}

/*
   ลูกศรบอกสถานะ — ชี้ลง = กางอยู่ · ชี้ขวา = พับอยู่

   🔴 **ทิศทางลูกศรไม่ใช่ข้อมูลชิ้นเดียวที่บอกสถานะ** — `aria-expanded`
      บนปุ่มคือของจริงสำหรับโปรแกรมอ่านหน้าจอ (Bootstrap เขียนให้เอง)
      · ลูกศรเป็นของคู่กัน ไม่ใช่ของแทน (กฎ "ห้ามสื่อความหมายด้วยภาพอย่างเดียว")
*/
.menu-group-chevron {
    margin-left: auto;
    font-size: 14px;
    line-height: 1;
    transition: transform .2s ease-in-out;
}

.menu-group-toggle[aria-expanded="false"] .menu-group-chevron {
    transform: rotate(-90deg);
}

/*
   ⚠️ ผู้ใช้ที่ตั้งค่าลดการเคลื่อนไหว ต้องได้ลูกศรที่ "อยู่ถูกทิศทันที"
      ไม่ใช่ไม่มีลูกศร — ตัดแค่การหมุน (MASTER.md §4)
   ℹ️ ตัว collapse ของ Bootstrap เคารพค่านี้เองอยู่แล้ว
*/
@media (prefers-reduced-motion: reduce) {
    .menu-group-chevron {
        transition: none;
    }
}

/* รายการในกลุ่ม — `ul` ซ้อนต้องไม่พกระยะห่างของตัวเองมาด้วย */
.startbar .startbar-menu .navbar-nav .menu-group > .nav {
    padding: 0;
    margin: 0;
    list-style: none;
}

/* ══ 🔴 โหมดไอคอนล้วน (rail · ≥1200px) — กติกาข้อ 4 ══════════════════

   `app.js` ของเทมเพลตตั้ง `data-sidebar-size="collapsed"` ให้อัตโนมัติ
   สำหรับจอกว้าง 310–1440px → **จอโน้ตบุ๊กเกือบทุกเครื่องเปิดมาเจอโหมดนี้**

   ที่ความกว้าง 70px ปุ่มพับไม่มีทางสื่อว่ากดแล้วเกิดอะไร
   → บังคับกางทุกกลุ่ม และ `app-nav.js` ปิดปุ่มไปพร้อมกัน
     (CSS ซ่อนอย่างเดียวไม่พอ — ปุ่มที่มองไม่เห็นแต่ยัง `Tab` ไปโดนได้
      คือกับดักของผู้ใช้คีย์บอร์ด)

   🔴 **`!important` จำเป็นตรงนี้** — ต้องชนะ `.collapse:not(.show){display:none}`
      ของ Bootstrap ซึ่งเป็นกฎที่ JS เขียนสถานะลงไป ไม่ใช่กฎที่เราคุมได้
   ⚠️ ครอบทั้งตอน hover และไม่ hover โดยตั้งใจ — ถ้าครอบเฉพาะ `:not(:hover)`
      รายการในกลุ่มที่ผู้ใช้เคยพับไว้จะ "หายไป" ตอนเอาเมาส์ไปวาง
      ซึ่งอ่านว่าเมนูกระพริบ ไม่ใช่ว่าค่าที่จำไว้ทำงาน
   ────────────────────────────────────────────────────────────────── */
@media (min-width: 1200px) {
    body[data-sidebar-size="collapsed"] .startbar .menu-group {
        display: block !important;
        height: auto !important;
        overflow: visible !important;
        visibility: visible !important;
    }

    /* ลูกศรไม่มีความหมายเมื่อพับไม่ได้ — และมันกินความกว้างที่เหลือน้อยอยู่แล้ว */
    body[data-sidebar-size="collapsed"] .startbar:not(:hover) .menu-group-chevron {
        display: none;
    }

    body[data-sidebar-size="collapsed"] .startbar:not(:hover) .menu-label .menu-group-toggle {
        justify-content: center;
        padding: 6px 0;
        cursor: default;
    }

    /*
       ⚠️ ปุ่มที่ปิดอยู่ต้องไม่ดูจางลง — มันยังทำหน้าที่เป็น *ป้ายกลุ่ม* อยู่
          การไล่สีให้จางตามสไตล์ปุ่ม disabled ทั่วไป จะทำให้ตัวย่อของกลุ่ม
          อ่านไม่ออกบนพื้นที่แคบอยู่แล้ว (คอนทราสต์ต่ำกว่า 4.5:1)
    */
    body[data-sidebar-size="collapsed"] .startbar:not(:hover) .menu-label .menu-group-toggle:disabled {
        opacity: 1;
        color: inherit;
        background: none;
    }
}

/* ══════════════════════════════════════════════════════════════════════
   เมนูขวาบน — ตัวควบคุมที่มีข้อความ (2026-08-22 รอบ 47)
   ══════════════════════════════════════════════════════════════════════

   🔴 **`.nav-icon` ของเทมเพลตกว้างตายตัว 48px** และเป็นวงกลม:

       .topbar .topbar-custom .nav-link.nav-icon{width:48px;height:48px;border-radius:50%}

   ปุ่มภาษาและปุ่มบัญชีเคยใช้คลาสนี้ทั้งที่ข้างในมีข้อความ ผลที่วัดได้จริง
   ด้วย Chromium ที่ 1440px คือ **เนื้อหากว้าง 147px อยู่ในกล่อง 48px
   แล้วขอบขวาไปจบที่ 1452px = ชื่อผู้ใช้ถูกตัดหายนอกจอ**
   (เหตุผลเต็มและวิธีวัดซ้ำ อยู่ในหัวไฟล์ `app/Views/partials/topbar.php`)

   `.topbar-pill` = หน้าตาเดียวกับ `.nav-icon` ทุกอย่าง **ยกเว้นความกว้าง**
   ซึ่งเดินตามเนื้อหา และตัดด้วย `…` เมื่อยาวเกินเพดาน
   ────────────────────────────────────────────────────────────────── */

/*
   ⚠️ ต้องมี `.topbar .topbar-custom` นำหน้าให้ specificity เท่ากฎของเทมเพลต
      เขียนสั้นกว่าเมื่อไหร่ กฎนี้แพ้ `.nav-link` ของ Bootstrap เงียบ ๆ
      (บทเรียนเดียวกับบล็อก `body[data-sidebar-size] .topbar` ด้านบนในไฟล์นี้)
*/
.topbar .topbar-custom .nav-link.topbar-pill {
    display: flex;
    align-items: center;
    gap: .5rem;

    /*
       🔴 เป้าสัมผัส 44px (WCAG 2.2 SC 2.5.8) — ปุ่มพวกนี้อยู่มุมจอ
          ซึ่งเป็นตำแหน่งที่นิ้วโป้งเอื้อมถึงยากที่สุดอยู่แล้ว
    */
    min-height: 44px;
    padding: .375rem .75rem;

    /*
       ⚠️ **`min-width: 0` ขาดไม่ได้** — `.text-truncate` ข้างในเป็น flex item
          ซึ่งมีค่าเริ่มต้น `min-width: auto` = "ห้ามหดเล็กกว่าเนื้อหา"
          ตัวตัดจึงไม่ทำงาน แล้วข้อความดันความกว้างของทั้งแถบจนล้น
          (บั๊กตระกูลเดียวกับ `.welcome-text` ที่แก้ไปแล้วในไฟล์นี้)
    */
    min-width: 0;
    max-width: 14rem;

    border-radius: 999px;
    background-color: var(--bs-topbar-bg);
    box-shadow: 0 3px 16px rgb(142 134 171 / 5%);
    color: var(--bs-body-color);
    transition: background-color .15s ease-in-out, box-shadow .15s ease-in-out;
}

.topbar .topbar-custom .nav-link.topbar-pill:hover {
    background-color: var(--bs-secondary-bg);
}

/* 🔴 โฟกัสต้องมองเห็นได้ — เมนูเหล่านี้เข้าถึงด้วยคีย์บอร์ดล้วนได้ */
.topbar .topbar-custom .nav-link.topbar-pill:focus-visible {
    outline: 2px solid var(--bs-primary);
    outline-offset: 2px;
}

/* ตัวตัดข้อความจะทำงานได้ ตัวมันเองก็ต้องยอมหดด้วย ไม่ใช่แค่พ่อของมัน */
.topbar-pill-label {
    min-width: 0;
}

/*
   ═══ ตัวย่อชื่อแทนรูปโปรไฟล์ ═══

   🔴 **ไม่ใช้รูปจากผู้ให้บริการ** — เป็น URL ข้ามโดเมนซึ่ง CSP
      (`img-src 'self' data:`) บล็อกทุกครั้ง เหตุผลเต็มอยู่ใน `topbar.php`

   ⚠️ ใช้โทเคนที่ **มีค่าทั้งสองธีม** (`--bs-primary-bg-subtle` /
      `--bs-primary-text-emphasis` ของ Bootstrap 5.3) ไม่ใช่สีตายตัว
      · ตรวจแล้วว่า `bootstrap.min.css` นิยามไว้ครบทั้งใน `:root`
        และใน `[data-bs-theme=dark]`
*/
.topbar-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    background-color: var(--bs-primary-bg-subtle);
    color: var(--bs-primary-text-emphasis);
    font-size: .8125rem;
    font-weight: 600;
    line-height: 1;
}

.topbar-avatar-lg {
    width: 40px;
    height: 40px;
    font-size: 1rem;
}

/* ── เมนูบัญชี ─────────────────────────────────────────────────────── */

/*
   ⚠️ เพดานความกว้าง — อีเมลยาว ๆ ทำให้เมนูกว้างจนล้นขอบขวาของจอ
      แล้ว Bootstrap จะดีดเมนูไปทางซ้ายจนหลุดจากปุ่มที่กด
*/
.topbar-account-menu {
    min-width: 15rem;
    max-width: 18rem;
}

.topbar-account-head {
    display: flex;
    align-items: center;
    gap: .625rem;

    /*
       ⚠️ `.dropdown-header` ของ Bootstrap เป็นตัวเล็กสีจาง — ที่นี่มันคือ
          *ตัวตนของผู้ใช้* ซึ่งต้องอ่านออกชัด ๆ (คอนทราสต์ ≥4.5:1)
    */
    color: var(--bs-body-color);
    font-size: .875rem;
    padding-top: .625rem;
    padding-bottom: .625rem;
}

/* ตัดข้อความยาวได้ ต้องยอมหดก่อน (เหตุผลเดียวกับ `.topbar-pill`) */
.topbar-account-id {
    min-width: 0;
}

.topbar-account-shop {
    /*
       🔴 แถบสีด้านซ้ายบอกว่า "นี่คือของที่ต้องตรวจ" โดยไม่ต้องอ่านก่อน —
          ร้านที่ผูกผิดคือความเสียหายที่กู้คืนไม่ได้ (กฎเหล็ก §0.8)
       ⚠️ `box-shadow: inset` ไม่กินพื้นที่ layout จึงไม่ดันข้อความขยับ
          (เหตุผลเดียวกับ `.connection-row` ด้านบนในไฟล์นี้)
    */
    box-shadow: inset 3px 0 0 var(--bs-primary);
    display: block;
}

/* ── ปุ่มสลับธีม ───────────────────────────────────────────────────── */

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

   ⚠️ ไอคอนไม่ใช่ข้อมูลชิ้นเดียวที่บอกสถานะ — `aria-pressed` บนปุ่มคือของจริง
      สำหรับโปรแกรมอ่านหน้าจอ และ `aria-label` บอกว่าปุ่มนี้ทำอะไรเสมอ
*/
[data-theme-icon-light] {
    display: none;
}

html[data-bs-theme="dark"] [data-theme-icon-dark] {
    display: none;
}

html[data-bs-theme="dark"] [data-theme-icon-light] {
    display: inline-block;
}

/* ══════════════════════════════════════════════════════════════════════
   คอนทราสต์ของเมนูข้าง — วัดจริงแล้วตกสองจุด (2026-08-22 รอบ 47)
   ══════════════════════════════════════════════════════════════════════

   วัดด้วย Chromium จริง (ประกอบสีที่มี alpha แล้ว) ทั้งสองธีม:

   | ข้อความ | สว่าง | มืด | เกณฑ์ |
   |---|---|---|---|
   | ป้ายกลุ่ม (11px)      | **3.97** ✗ | **4.04** ✗ | 4.5 |
   | เมนูที่ **กำลังเปิดอยู่** | **2.28** ✗ | 7.02 ✓ | 4.5 |
   | เมนูปกติ              | 18.27 ✓ | 6.75 ✓ | |
   | ชื่อผู้ใช้บนแถบบน       | 13.63 ✓ | 12.14 ✓ | |
   | ตัวย่อชื่อในวงกลม       | 8.91 ✓  | 6.63 ✓  | |

   🔴 **จุดที่แย่ที่สุดคือ 2.28 ของ "หน้าที่กำลังเปิดอยู่"** — เทมเพลตตั้ง
      `.nav-link.active{color:#22c55e}` ซึ่งเป็นเขียวสดบนพื้นขาว
      · ข้อความที่บอกว่า *"คุณอยู่ตรงนี้"* คือข้อความที่ต้องอ่านง่ายที่สุดในเมนู
        แต่กลับเป็นตัวที่อ่านยากที่สุด
      · ทั้งสองจุดเป็นของเดิม **แต่รอบนี้กลายเป็นความรับผิดชอบของเรา**
        เพราะป้ายกลุ่มถูกทำเป็นปุ่ม และโหมดมืดเพิ่งจะเข้าถึงได้เป็นครั้งแรก

   ⚠️ **ห้ามแก้ด้วยการใส่ hex** (MASTER.md §1) — สีที่ฝังตายจะอ่านไม่ออก
      ทันทีที่สลับธีม ซึ่งคือปัญหาที่กำลังแก้อยู่พอดี
      → ใช้ `--bs-primary-text-emphasis` ของ Bootstrap 5.3 ซึ่งมีค่าคนละตัว
        ในแต่ละธีม (`#0e4f26` สว่าง · `#7adc9e` มืด) ตรวจแล้วว่านิยามครบทั้งคู่
   ────────────────────────────────────────────────────────────────── */

/*
   ⚠️ ต้องเลียน specificity ของเทมเพลตให้ครบ —
      `.startbar .startbar-menu .navbar-nav .nav-item .nav-link.active` (0,6,0)
      เขียนสั้นกว่านี้แล้วแพ้เงียบ ๆ
   ⚠️ ครอบ `:hover` ด้วย เพราะเทมเพลตให้สีเดียวกันทั้งสองสถานะ
      ถ้าแก้แต่ `.active` เมาส์ผ่านเมื่อไหร่ตัวอักษรจะจางลงกว่าเดิม
*/
.startbar .startbar-menu .navbar-nav .nav-item .nav-link.active,
.startbar .startbar-menu .navbar-nav .nav-item .nav-link:hover {
    color: var(--bs-primary-text-emphasis);
}

/*
   ℹ️ **แถบสีด้านซ้ายของเมนูที่เปิดอยู่ ยังเป็นเขียวสดของเทมเพลตตามเดิม**
      โดยตั้งใจ — มันเป็น *เครื่องหมาย* ไม่ใช่ข้อความ และตอนนี้ทำหน้าที่เป็นสีเน้น
      คู่กับตัวอักษรสีเข้ม · สถานะ "หน้าปัจจุบัน" ยังมีตัวบอกครบสามทาง
      (สี · แถบ · `aria-current="page"`) จึงไม่ใช่การสื่อด้วยสีอย่างเดียว
*/

/*
   ป้ายกลุ่ม — `--bs-menu-label-color` ของเทมเพลตเป็น `#717fa7` ทั้งสองธีม
   ซึ่งตกเกณฑ์ทั้งคู่ (3.97 / 4.04)

   🔴 **บรรทัด `color` สองบรรทัดคือ fallback ที่ตั้งใจ ไม่ใช่ของซ้ำ**
      เบราว์เซอร์ที่ไม่รู้จัก `color-mix()` จะข้ามบรรทัดที่สองแล้วใช้บรรทัดแรก
      = สีตัวอักษรปกติ (คอนทราสต์สูงกว่าเกณฑ์แน่นอน) · ลบบรรทัดแรกทิ้งเมื่อไหร่
      เบราว์เซอร์เก่าจะตกกลับไปใช้ `#717fa7` ที่ตกเกณฑ์อยู่แล้ว

   ⚠️ ผสมกับ **สีพื้น** ไม่ใช่กับ `transparent` — ผสมกับ transparent จะได้
      ตัวอักษรโปร่งแสง ซึ่งคอนทราสต์เปลี่ยนไปตามสิ่งที่อยู่ข้างหลัง
      และคำนวณล่วงหน้าไม่ได้ · ผสมกับพื้นได้สีทึบที่วัดค่าได้แน่นอน
*/
.startbar .startbar-menu .navbar-nav .menu-label .menu-group-toggle {
    color: var(--bs-body-color);
    color: color-mix(in srgb, var(--bs-body-color) 72%, var(--bs-body-bg));
}

/*
   คำบรรยายใต้ชื่อหน้า (แถบบน) — วัดได้ **2.49** ในโหมดสว่าง (เกณฑ์ 4.5)

   `.text-muted` ของเทมเพลตคือ `--bs-secondary-color` = `#96a0b5`
   ซึ่งบนพื้นขาวอ่านได้ยากที่ขนาด 13px

   ⚠️ **แก้เฉพาะจุดนี้ ไม่แตะ `.text-muted` ทั้งระบบ** — คลาสนั้นถูกใช้
      หลายร้อยจุดทั่วทุกหน้า การเปลี่ยนค่ากลางในรอบที่ตรวจแค่เมนู
      คือการเปลี่ยนหน้าตาทั้งแอปโดยไม่ได้ดูผลทุกหน้า
   📌 **บันทึกไว้เป็นงานค้าง**: `.text-muted` ที่ 2.49 มีอยู่อีกหลายหน้า
      ต้องมีรอบที่ไล่ตรวจทั้งระบบพร้อมกัน ไม่ใช่แก้ทีละจุดจนสีไม่ตรงกัน
*/
/*
   🔴 **ไม่ใช้คลาส `.text-muted` แล้ว และไม่สู้ด้วย `!important`**
      `.text-muted` ของ Bootstrap เป็น utility ที่ประกาศ `!important` มาเอง
      → การเขียนทับต้องใส่ `!important` ตาม ซึ่งเริ่มสงคราม specificity
        ที่คนแก้คนถัดไปต้องสู้ต่ออีก
      → ใช้คลาสของเราเองบน `<p>` ตัวนั้นแทน (`topbar-subtitle`) จบในที่เดียว
*/
.topbar .welcome-text p.topbar-subtitle {
    color: var(--bs-body-color);
    color: color-mix(in srgb, var(--bs-body-color) 68%, var(--bs-body-bg));
}

/* ══════════════════════════════════════════════════════════════════════
   โปรไฟล์ส่วนตัว — รูปตัวอย่างและรูปบนแถบบน (2026-08-22 รอบ 48)
   ══════════════════════════════════════════════════════════════════════ */

/*
   🔴 **กำหนดขนาดตายตัวทั้งกล่องที่มีรูปและกล่องที่ยังว่าง**

   ถ้าที่ว่างไม่มีขนาด พออัปโหลดรูปเสร็จแล้วเลย์เอาต์จะกระตุกทั้งการ์ด
   (CLS · กฎ `reserve-space`) · และ `<img>` ที่ยังโหลดไม่เสร็จก็ทำแบบเดียวกัน
   ถ้าไม่มี width/height

   ⚠️ `object-fit: cover` จำเป็นสำหรับรูปโปรไฟล์ที่ถูกครอบเป็นจัตุรัสมาแล้ว
      ส่วนโลโก้ถูกย่อแบบคงสัดส่วน จึงใช้ `contain` เพื่อไม่ให้ถูกครอบซ้ำ
*/
.profile-image-preview {
    width: 88px;
    height: 88px;
    flex-shrink: 0;
    object-fit: cover;
    background-color: var(--bs-secondary-bg);
    border: 1px solid var(--bs-border-color);
}

.profile-image-preview.rounded {
    object-fit: contain;
}

/*
   กล่องว่าง — ตัวย่อชื่อหรือไอคอน แทนที่จะเป็นช่องว่างเปล่า
   ⚠️ ช่องว่างเปล่าอ่านว่า "รูปโหลดไม่ขึ้น" ไม่ใช่ "ยังไม่ได้อัปโหลด"
*/
.profile-image-empty {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 1.75rem;
    font-weight: 600;
    color: var(--bs-secondary-color);
}

/*
   ═══ รูปจริงบนแถบบน — แทนวงกลมตัวย่อเมื่อผู้ใช้อัปโหลดแล้ว ═══

   ⚠️ ใช้คลาสเดียวกับวงกลมตัวย่อ (`.topbar-avatar`) เพื่อให้ขนาดและตำแหน่ง
      ตรงกันเป๊ะ ๆ · ถ้าประกาศขนาดแยกกัน วันหนึ่งสองอันจะไม่เท่ากัน
      แล้วแถบบนจะขยับตอนผู้ใช้อัปโหลดรูปครั้งแรก
*/
img.topbar-avatar {
    object-fit: cover;

    /*
       ⚠️ ทับ `background-color` ของวงกลมตัวย่อ — รูปที่มีพื้นโปร่งใส
          จะเห็นสีเขียวจาง ๆ ทะลุออกมา ซึ่งดูเหมือนรูปเพี้ยน
    */
    background-color: var(--bs-secondary-bg);
}

/* ══════════════════════════════════════════════════════════════════════
   🔴 ชื่อหน้าหายไปทั้งหมดบนจอ ≤1024px (พบและแก้ 2026-08-22 รอบ 48)
   ══════════════════════════════════════════════════════════════════════

   เทมเพลต Rizz เขียนไว้ว่า:

       @media (max-width:1024px){ … .welcome-text{display:none} }

   ผลคือ **ทุกจอที่แคบกว่า 1025px ไม่มีชื่อหน้าเลย** — มือถือ · แท็บเล็ต ·
   และโน้ตบุ๊กจอ 1024px

   ═══ ทำไมถึงแพงกว่าที่เห็น ═══

   1. 🔴 **`display:none` ตัด element ออกจาก accessibility tree ด้วย**
      ไม่ใช่แค่ซ่อนจากสายตา · ผู้ใช้โปรแกรมอ่านหน้าจอบนมือถือจึงได้หน้าที่
      **ไม่มี `<h1>` เลยสักตัว** ซึ่งเป็นสิ่งที่ `partials/topbar.php` เขียน
      กำกับไว้ตรง ๆ ว่าขาดไม่ได้:
      *"โปรแกรมอ่านหน้าจอใช้ `<h1>` ตอบคำถาม 'ตอนนี้อยู่หน้าอะไร'"*
   2. 🔴 **ขัดกับกฎที่ไฟล์นี้เขียนไว้เองด้านบน**:
      *"⚠️ แก้ที่ `min-width` **ไม่ใช่** ซ่อนหัวข้อทิ้งบนจอเล็ก —
        ผู้ใช้มือถือต้องรู้ว่ากำลังดูหน้าไหนอยู่เหมือนกัน"*
      กฎถูกเขียนไว้แล้ว แต่ไม่มีใครรู้ว่าเทมเพลตซ่อนทิ้งไปก่อนหน้านั้นแล้ว

   ⚠️ **ไม่มีชุดเทสต์ไหนเห็น** — `test-browser.php` ตรวจที่ 390px และ 1440px
      แต่ด่าน "มีเนื้อหาจริง" ดูทั้งหน้า ไม่ได้ถามหา `<h1>` โดยเฉพาะ
      · เจอจากการ **ดูภาพหน้าจอมือถือของหน้าโปรไฟล์** แล้วสังเกตว่า
        แถบบนมีแต่ไอคอน (ตระกูลเดียวกับแถบ "AI พามา" ที่เจอตอนรอบ 42)

   🔴 **ห้ามแก้ `app.min.css`** (กฎเหล็ก §0.5) — ลบล้างจากที่นี่แทน
   ────────────────────────────────────────────────────────────────── */
@media (max-width: 1024px) {
    .welcome-text {
        display: inline-block;
    }

    /*
       ⚠️ **ซ่อนเฉพาะคำบรรยาย ไม่ใช่ชื่อหน้า** บนจอที่แคบที่สุด
          ที่ 390px เหลือพื้นที่ให้ข้อความราว 110px หลังหักไอคอนขวาสี่ตัว
          — พอสำหรับชื่อหน้าที่ตัดด้วย `…` แต่ไม่พอสำหรับสองบรรทัด
          และชื่อหน้าคือบรรทัดที่ตอบว่า "อยู่หน้าไหน" ส่วนคำบรรยายเป็นของเสริม
    */
    .welcome-text p {
        display: none;
    }
}

@media (min-width: 576px) and (max-width: 1024px) {
    .welcome-text p {
        display: block;
    }
}

/* ══════════════════════════════════════════════════════════════════════
   จอแคบ — ชื่อหน้าต้องได้ที่ยืนจริง ไม่ใช่แค่ "ไม่ถูกซ่อน"
   ══════════════════════════════════════════════════════════════════════

   วัดจริงที่ 390px บนแดชบอร์ด **หลัง** แก้ `display:none` ด้านบนแล้ว:

       nav 366px = ปุ่มเมนู 52 + ระยะห่าง 32 + กลุ่มขวา 271  →  เหลือให้ชื่อ ~11px
       (กลุ่มขวา = ป้ายสตรีม 65 · ปุ่มธีม 56 · ภาษา 52 · บัญชี 62 + ช่องไฟ)

   ชื่อหน้าจึงยังหดเหลือ 0 แม้จะไม่ถูก `display:none` แล้ว
   → **การเลิกซ่อนอย่างเดียวไม่พอ ต้องคืนพื้นที่ให้ด้วย**

   ⚠️ **ไม่ลดขนาดต่ำกว่า 44px** — เป้าสัมผัสตามเกณฑ์นิ้ว (WCAG 2.2 SC 2.5.8)
      ยังต้องผ่านทุกปุ่ม · ที่ลดคือ *ระยะห่างส่วนเกิน* ไม่ใช่ตัวปุ่ม
   ────────────────────────────────────────────────────────────────── */
@media (max-width: 575.98px) {
    /*
       🔴 `flex: 1 1 auto` ไม่ใช่แค่ `min-width: 0`
          `min-width: 0` คือ "อนุญาตให้หดได้" · `flex-grow` คือ "ขอพื้นที่ที่เหลือ"
          มีแต่ข้อแรก ชื่อหน้าจึงยอมหดจนเป็นศูนย์โดยไม่เคยเรียกร้องอะไรเลย
    */
    #topbar-custom > .topbar-item:first-child .welcome-text {
        flex: 1 1 auto;
        min-width: 3rem;
    }

    /* ระยะห่างซ้าย-ขวาของชื่อหน้า — `mx-3` (32px) กินไปเกือบเท่าตัวชื่อเอง */
    #topbar-custom > .topbar-item:first-child .welcome-text.mx-3 {
        margin-left: .5rem !important;
        margin-right: .5rem !important;
    }

    .topbar .topbar-custom .nav-link.nav-icon {
        width: 44px;
        height: 44px;
    }

    .topbar .topbar-custom .nav-link.topbar-pill {
        padding-left: .5rem;
        padding-right: .5rem;
    }

    /*
       ═══ ป้ายสถานะสตรีม — เหลือแต่ไอคอน แต่ **ยังได้ยินอยู่** ═══

       🔴 ใช้ `.visually-hidden` ไม่ใช่ `display:none`
          ป้ายนี้มี `aria-live="polite"` เพื่อบอกว่าสตรีมหลุด (DASHBOARD.md §4)
          `display:none` ตัดข้อความออกจาก accessibility tree = คำเตือนที่
          ผู้ใช้โปรแกรมอ่านหน้าจอไม่มีวันได้ยิน ทั้งที่นั่นคือกลุ่มที่พึ่งมันที่สุด

       ⚠️ **`position: relative` บนตัวป้ายขาดไม่ได้** — `.visually-hidden`
          ของ Bootstrap เป็น `position: absolute` · ถ้ากรอบที่ครอบมันเป็น
          `static` มันจะไปยึดกับ element อื่นที่อยู่ไกลออกไป แล้วดันหน้า
          ให้เลื่อนแนวนอน (บั๊กนี้เคยเกิดจริงและกินไป 336px — CLAUDE.md rev.35)
    */
    #stream-status {
        position: relative;
    }

    #stream-status > span.align-middle {
        position: absolute !important;
        width: 1px !important;
        height: 1px !important;
        padding: 0 !important;
        margin: -1px !important;
        overflow: hidden !important;
        clip: rect(0, 0, 0, 0) !important;
        white-space: nowrap !important;
        border: 0 !important;
    }
}
