/**
 * แม่แบบ Capsule — สไตล์ชีต
 *
 * คัดมาจาก mockup ที่อนุมัติแล้ว (specs/assets/2026-08-02-shell-capsule-mockup.html,
 * บล็อก TOKENS ถึงท้าย media query) แบบไม่ปรับระยะหรือสีระหว่างแปลง ยกเว้นห้าจุดที่
 * ระบุไว้ในบรีฟ Task 5 ข้อ 4:
 *
 * 1. คำนำหน้าคลาสทุกตัวเปลี่ยนจาก .sh- เป็น .rxsu-cap- (รวม .shell เปลือยที่ไม่มีขีด
 *    กลาง ซึ่งเปลี่ยนเป็น .rxsu-cap-shell ด้วยเหตุผลเดียวกัน - ชื่อคลาสตรงตัวเกินไปที่จะ
 *    ปล่อยชนกับอะไรก็ได้บนหน้า)
 * 2. :root{...} ของ mockup ย้ายมาอยู่ใต้ body class .rxsu-shell-capsule แทน เพราะหน้า
 *    เดียวกันยังมีสไตล์ของธีม 1 อยู่ด้วย - ตัวแปรจะต้อง scope ตามแม่แบบที่ active จริง
 *    (ชื่อตัวแปร --sh-* เองไม่เปลี่ยน มีแต่คลาสที่ห่อมันเปลี่ยน)
 * 3. ตัด .doc/.toner/.caption ทั้งหมด - เป็น CSS ของหน้าโชว์ mockup เอง ไม่ใช่ของเปลือก
 * 4. แถบยูทิลิตี้ต่อกับตัวเลือกสีแถบที่มีอยู่แล้ว (ดูหมายเหตุเหนือ --sh-strip ด้านล่าง)
 * 5. ไม่มีบล็อกโทนอื่น ๆ ([data-tone="olive|teal|light"] ของ mockup) เพราะไม่มีอะไรใน
 *    รุ่นนี้เลือกมันได้ - เก็บไว้ที่ mockup เป็นหลักฐานสถาปัตยกรรม token เท่านั้น
 *
 * ข้อ 6 (ช่องค้นหาที่ mockup ไม่มี) อยู่ท้ายไฟล์ เป็นสไตล์ใหม่ล้วน ไม่ใช่การแปลง
 *
 * Fix round 1 (หลัง review): เพิ่ม .rxsu-cap-skip (ก่อนหน้านี้ไม่มีสไตล์เลยจึงโผล่เป็น
 * ตัวหนังสือธรรมดาอยู่เหนือ header ทุกหน้า) และ .rxsu-cap-brand__fallback (โลโก้ว่าง =
 * ลิงก์ขนาดศูนย์) ตัด .rxsu-cap-body (สวอตช์โชว์ของหน้า mockup เอง หมวดเดียวกับ
 * .doc/.toner/.caption ที่ข้อ 3 ให้ตัดไปแล้ว) และตัดตัวแปร --sh-page ที่ไม่มีกฎไหนใช้เลย
 * (ยังไม่เคยผูก body{background} เพราะ a{color:inherit} ของ mockup จะทับสไตล์ลิงก์ของ
 * เนื้อหาจริงทั้งหน้า - ดูหมายเหตุใน header.php)
 *
 * Task 6 (footer): เช่นเดียวกับข้อ 4 ข้างต้น แถบล่างสุดของ footer
 * (.rxsu-cap-foot__bottom) ต่อกับตัวเลือกสีแถบเดียวกัน - ดูหมายเหตุเหนือ --sh-foot-bottom
 * ด้านล่าง สองจุดที่ตั้งใจแตกต่างจาก mockup นอกเหนือจากนั้น (grid ของ .rxsu-cap-foot__inner
 * เหลือ 3 คอลัมน์ไม่เท่ากันแทนที่จะเป็น 4 คอลัมน์เท่ากันบางส่วน และคลาสใหม่
 * .rxsu-cap-foot__tagline/.rxsu-cap-foot__line ที่ไม่มีต้นแบบใน mockup) อธิบายไว้ที่ตัวกฎ
 * เอง ไม่ใช่ตรงนี้ ดูเหตุผลเต็มใน templates/shell/capsule/parts/footer.php
 *
 * Task 8 (burger + drawer มือถือ): ไม่มีต้นแบบใน mockup เลย (mockup ไม่มีสถานะมือถือ)
 * สไตล์ทั้งบล็อก ".rxsu-cap-burger"/".rxsu-cap-drawer" ท้ายไฟล์นี้จึงเป็นของใหม่ล้วน
 * ไม่ใช่การแปลง ดูเหตุผลเชิงโครงสร้าง ("กางอยู่โดยปริยาย พับด้วยสคริปต์") ที่ตัวบล็อกเอง
 * และในหมายเหตุหัวไฟล์ templates/shell/capsule/parts/header.php
 *
 * 0.27.0 (แก้เจ้าของเว็บไม่ชอบ header เขียวเข้มที่ 0.26.x ส่งไป): tests/shell-capsule-
 * contrast.php วัดจากไฟล์โลโก้จริง (silapakorn_logo.png, attachment 1310) แล้วพบว่ามัน
 * เป็น **หมึกเข้มบนพื้นโปร่ง** ไม่ใช่ไฟล์กลับสี - บน #123A2E ส่วนที่มืดที่สุดของตราวัดได้แค่
 * 1.92:1 ตราคณะเลยจมหายบนพื้นเข้ม ทางแก้เดียวที่ไม่ต้องรอไฟล์โลโก้ใหม่จากคณะคือทำให้แถว
 * brand row *สว่าง* แทน - ดู includes/class-rxsu-shell-tone.php สำหรับตัวเลขที่วัดมาและ
 * เหตุผลเต็ม ค่าเริ่มต้นของ .rxsu-shell-capsule ด้านล่างนี้จึงยกมาจาก mockup ที่อนุมัติแล้ว
 * บล็อก [data-tone="light"] (--sh-brand/--sh-brand-ink/--sh-rail/--sh-lift/--sh-texture)
 * โดยตรง ยกเว้น --sh-lead กับ --sh-foot ที่ตั้งใจ "ไม่" ยกมา (mockup เองก็ไม่แตะสองตัวนี้ใน
 * บล็อก light เหมือนกัน) เพราะกฎออกแบบที่แม่แบบนี้สืบทอดมาบอกว่าเขียวเข้มเป็น "จุดเน้น" ไม่ใช่
 * "พื้น" - ปรากฏได้ในที่จำกัด: การ์ดนำของเมกะเมนู (--sh-lead) กับตัวเนื้อ footer (--sh-foot)
 *
 * ตั้งใจไม่ยกมาทั้งบล็อก: mockup ออกแบบมาให้พลิกทั้งหน้ารวมถึงแถบยูทิลิตี้พร้อมกัน
 * (--sh-strip ของ mockup กลายเป็นสีอ่อนในบล็อก light ด้วย) แต่ --sh-strip ของไฟล์นี้ผูกกับ
 * ตัวเลือกสีแถบของผู้ดูแลอยู่แล้ว (var(--rxsu-bar-surface,...) - ดูหมายเหตุเหนือ --sh-strip
 * ด้านล่าง) เป็นคนละคำถามกับโทนของเปลือกนี้เลย จึงไม่แตะ --sh-strip/--sh-strip-ink ที่นี่
 * ด้วยเหตุผลเดียวกัน --sh-mint/--sh-mint-ink และ --sh-cream/--sh-cream-ink ก็ไม่ยกมาจาก
 * บล็อก light เช่นกัน: ทั้งคู่เป็นคู่พื้น/หมึกที่ใช้ซ้ำทั้งบนพื้นเข้ม (การ์ดนำ, ปุ่ม footer,
 * เบอร์โทร footer, แถบยูทิลิตี้) และพื้นสว่าง (badge บนแถว brand) พร้อมกัน mockup พลิกทั้งหน้า
 * จึงไม่ชนปัญหานี้ แต่เปลือกนี้พื้นเข้มยังอยู่ - ค่าที่ mockup ให้ (มืดลง สำหรับตัวหนังสือ) จะทำ
 * เบอร์โทร/ปุ่ม CTA บนพื้นเข้มจมหายทันที ค่าทั้งสองคู่นี้จึงคงเดิม (สว่าง สำหรับพื้นเข้ม)
 *
 * ผลข้างเคียงหนึ่งจุดจากการเปลี่ยน --sh-brand: .rxsu-cap-drawer เคยใช้ --sh-brand/
 * --sh-brand-ink ตัวเดียวกับแถว brand (เพราะทั้งคู่เคยเป็น "การ์ดเขียวเข้ม" แบบเดียวกัน) พอ
 * --sh-brand สว่างขึ้น drawer จะสว่างตามไปด้วยทั้งที่ไม่มีโลโก้อยู่ข้างในให้ต้องขาว ทำให้
 * ตัวหนังสือกลุ่มย่อย (--sh-cream, สว่าง) และเส้นคั่น (--sh-hairline, สว่างโปร่งแสง) ที่ปรับ
 * มาให้อ่านบนพื้นเข้มกลายเป็นเกือบมองไม่เห็นบนพื้นสว่าง แก้โดยตัด drawer ออกจาก --sh-brand
 * แล้วผูกกับ --sh-lead แทน (เนื้อหาข้างในไม่มีอะไรต้องแก้เลย - เข้ากับกฎ "จุดเน้นเข้ม" เดียวกับ
 * การ์ดนำเมกะเมนู, มือถือได้เมนูแบบเดียวกับที่เดสก์ท็อปได้ในการ์ดนำ) burger/nav-rail-hover/
 * search-input-focus ที่นั่งอยู่บนแถว brand โดยตรงยังเป็น literal สีขาวโปร่งแสงเดิมจากยุคพื้น
 * เข้ม - ปรับเป็นสีเข้มโปร่งแสงแทนที่ตัวรูปกฎเอง (ไม่ใช่ผ่านตัวแปรที่ยังต้องใช้พื้นเข้มที่อื่น)
 * ดูหมายเหตุเหนือกฎแต่ละจุด
 *
 * 0.28.0 (สองข้อแก้จากเจ้าของเว็บ หลังดู 0.27.0 บน production):
 *
 * ข้อ 1 - header เลิกเป็นแคปซูลลอย: --sh-inset (ระยะร่นจากขอบจอ), --sh-radius-card (มุมโค้ง)
 * และ --sh-lift (เงาลอย) ทั้งสามลงเป็น 0/none พร้อมกันในบล็อกโทเค็นด้านล่าง - เก็บชื่อตัวแปรไว้
 * ทั้งสามตัว ไม่ลบทิ้ง (แบบเดียวกับ --sh-texture:0 ที่ยังเป็นตัวแปรมีชีวิตแม้ค่าจะปิดอยู่) เผื่อ
 * แม่แบบในอนาคตอยากได้การ์ดโค้งกลับมาก็แก้ค่าเดียวจบ ไม่ต้องตามหากฎที่ใช้มันทุกจุด · การ์ด
 * header (.rxsu-cap-header) ไม่มี overflow:hidden อีกต่อไป - ค่านั้นมีไว้ตัดมุมโค้งของการ์ดโดย
 * เฉพาะ (ดู task-5-report.md ของแผน 2026-08-02-shell-template-capsule) พอมุมโค้งหายไป มันไม่มี
 * อะไรให้ตัดอีกแล้ว และกลับกลายเป็นความเสี่ยงเฉย ๆ : รุ่นก่อนเคยมีเหตุที่ปุ่มค้นหาหลุดขอบการ์ด
 * ที่ความกว้าง 761-1023px แล้วถูก overflow:hidden ตัดหายไปทั้งปุ่มแบบเงียบ ๆ (แก้แล้วด้วยการ
 * ย้ายจุดพับ nav rail มาที่ 1023px ใน Task 8 fix round 1) - เก็บ overflow:hidden ไว้ต่อไปจะทำให้
 * บั๊กคล้ายกันในอนาคตกลายเป็น "หายเงียบ ๆ" อีกครั้งแทนที่จะเป็น scroll แนวนอนที่อย่างน้อยยังเห็น
 * ได้ ตัด overflow:hidden ทิ้งจึงถูกต้องกว่าการคงไว้ "เผื่อไว้" · เมกะเมนูกับ drawer ยังคงเป็นพี่
 * น้องของ <header> เหมือนเดิมทุกจุด (ดูเหตุผลเดิมในหัวไฟล์ templates/shell/capsule/parts/
 * header.php) - เหตุผลเดิมที่ทำให้ต้องวางแบบนั้น (grid ของแผงกว้างกว่าราง จะดันโครง <li> พัง)
 * ไม่เกี่ยวกับ overflow:hidden เลย เป็นคนละเหตุผลกัน จึงไม่แตะ header.php ในรอบนี้ - นี่เป็นการ
 * แก้รูปทรงล้วน ๆ ที่ทำจบในสไตล์ชีตไฟล์เดียว
 *
 * ข้อ 2 - footer เลิกเป็นพื้นเขียวเข้ม กลายเป็นพื้นสว่าง ตามหน้าตาที่มีอยู่แล้วจริงในปลั๊กอินนี้
 * เอง: includes/class-rxsu-shell-renderer.php + assets/shell-footer.css (ฟุตเตอร์อีกตัวหนึ่ง
 * ที่แสดงแทน Elementor footer เมื่อ source=plugin - เจ้าของเว็บชี้ตรงไปที่ตัวอย่างนี้ในหน้าจอ
 * "ตัวอย่างก่อนสลับ" ของแอดมิน) --sh-foot/-ink/-head/-circle ทั้งสี่ตัวด้านล่างเปลี่ยนเป็นค่า
 * สว่าง คงที่ทุกโทน Shell_Tone (light/sage/olive) ไม่ต่างจากเดิมอีกต่อไป - ตัดออกจากทั้งสอง
 * บล็อก [data-rxsu-tone] ด้านล่างด้วยเหตุผลเดียวกัน (footer อ้างอิงหน้าตาของ Shell_Renderer ซึ่ง
 * เป็นดีไซน์ตายตัวหนึ่งเดียว ไม่ใช่ดีไซน์ที่พลิกตามโทนของแถว brand) --sh-lead ข้างบนยังคงแปรผัน
 * ตามโทนเหมือนเดิม (การ์ดนำเมกะเมนู/drawer ยังเป็น "จุดเน้นเข้ม" อยู่ ไม่ใช่ส่วนที่เจ้าของเว็บชี้)
 * โทเค็นใหม่ --sh-foot-muted/--sh-foot-tagline/--sh-foot-phone/--sh-foot-hairline เพิ่มเข้ามา
 * เพราะสีเดิมของ footer ทุกตัวถูกปรับจูนไว้สำหรับพื้นเข้ม (บางจุดใช้ opacity ลดหมึกเข้มลงแทนที่
 * จะมีสีของตัวเอง) ย้ายพื้นเป็นสว่างแล้วต้องแทนที่ทุกตัว ไม่ใช่แค่ตัวพื้นหลัง - ดูหมายเหตุเหนือ
 * แต่ละกฎด้านล่างว่าทำไมค่าที่เลือกถึงเป็นค่านั้น
 *
 * 0.29.0 (สามข้อจากรีวิวของเจ้าของเว็บหลังดู 0.28.0 บน production -
 * .superpowers/sdd/2026-08-02-shell-capsule-polish/task-1-brief.md):
 *
 * ข้อ 1 - header กลับมามีรูปทรงแคปซูล (มุมโค้ง + เงาลอย) แต่ยังคงเต็มความกว้างจอเหมือนเดิม
 * สองเรื่องนี้ไม่ขัดกัน: 0.28.0 ตัด --sh-inset/--sh-radius-card/--sh-lift ลงเป็น 0/none พร้อม
 * กันทั้งสามตัวเพราะตีความว่าเจ้าของเว็บอยากได้ทรงเหลี่ยมเต็มจอ แต่คำพูดจริงคือ "เอารูป capsule
 * เต็มความกว้าง ดูสวยงามกว่า" - อยากได้ทั้งความกว้างเต็มจอ *และ* รูปทรงแคปซูลไปพร้อมกัน ไม่ใช่
 * เลือกอย่างใดอย่างหนึ่ง --sh-inset ยังคงเป็น 0 เหมือนเดิม (ไม่มีระยะร่นจากขอบจอ) ส่วน
 * --sh-radius-card/--sh-lift กลับมามีค่าอีกครั้ง (ดูหมายเหตุเหนือทั้งสองตัวด้านล่างสำหรับค่าใหม่
 * และเหตุผลเต็ม) แต่ย้ายจาก .rxsu-cap-header ไปที่ .rxsu-cap-brand-row แทน - องค์ประกอบที่ระบาย
 * พื้นหลังจริงและเป็นขอบล่างสุดที่มองเห็นของการ์ด (แถบยูทิลิตี้เหนือมันไม่มีมุมโค้งเลย จึงไม่มีขอบ
 * อิสระด้านบนให้ต้องตัดด้วย) มุมโค้งจึงเป็นรูปทรงที่ถูกวาดจริงบนพื้นหลัง ไม่ใช่การ clip ภาพสี่เหลี่ยม
 * ด้วย overflow:hidden แบบเดิมก่อน 0.28.0 - ความเสี่ยงที่ 0.28.0 ตั้งใจตัด overflow:hidden ทิ้งไป
 * (ดูหมายเหตุเหนือ .rxsu-cap-header ด้านล่าง) จึงยังไม่ถูกนำกลับมาเลย .rxsu-cap-shell เองก็ได้
 * breakout แบบเดียวกับที่ .rxsu-cap-foot ได้ไปแล้วที่ 0.27.0 (เป็นลูกโดยตรงของ <body> เหมือนกัน -
 * ดูหมายเหตุเหนือ .rxsu-cap-foot ด้านล่าง) พร้อมกับ position:relative และ z-index:900 ใหม่ เพื่อ
 * รองรับข้อ 3
 *
 * ข้อ 2 - สี hover/open/current ของราง nav ออกแบบใหม่ทั้งชุด (โทเค็นใหม่สี่ตัว --sh-nav-hover/-ink,
 * --sh-nav-open/-ink แทนที่ rgba(18,58,46,.10) ตอน hover และ --sh-mint ตอนเปิด/current ของ 0.27.0)
 * ตามคำเจ้าของเว็บ "สี hover ไม่สวยเลย ออกแบบให้สวยกว่านี้" ปุ่มที่เปิดอยู่ตอนนี้ใช้สีเขียวเข้ม
 * เดียวกับการ์ดนำของแผงที่มันเปิด (--sh-lead) ให้ปุ่มกับแผงอ่านเป็นวัตถุเดียวกัน และมี
 * :focus-visible ริงเดียวทั้งเปลือกแทน outline ของเบราว์เซอร์ ซึ่งเป็นเส้นแปลกที่เจ้าของเว็บเห็นใน
 * ภาพหน้าจอ 0.28.0 บนปุ่มพื้นเขียวเข้ม
 *
 * ข้อ 3 - แผงเมกะเมนู (.rxsu-cap-panel) เปลี่ยนจากอยู่ใน flow ปกติ (margin-top ดันความสูงของ
 * .rxsu-cap-shell และทุกอย่างใต้เปลือกลงเมื่อเปิด) เป็น position:absolute วางซ้อนทับเนื้อหาแทน -
 * "เมนูแสดงควร overlay วางเหนือไม่ใช่ ดัน content" ทำได้เพราะ .rxsu-cap-shell เป็น
 * position:relative แล้วจากข้อ 1 และแผงยังเป็นพี่น้องของ <header> เหมือนเดิมทุกจุด (เหตุผล
 * โครงสร้างเดิมไม่เปลี่ยน - ดูหมายเหตุหัวไฟล์ templates/shell/capsule/parts/header.php) ไม่มีการ
 * แก้ JavaScript เลยในรอบนี้ - assets/shell/capsule.js ปิดแผงเมื่อคลิกนอกหรือกด Escape อยู่แล้ว
 *
 * 0.29.0 T2 (อีกสองข้อจากรีวิวชุดเดียวกัน - task-2-brief.md) ทั้งคู่เป็นอาการเดียวกันคือ
 * "เปลือกนี้ไม่ได้อยู่คนเดียวบนหน้า":
 *
 * ข้อ 4 - "ตัวอักษรในรูปที่ 7 มันถูกกลืนหายไป": หัวข้อการ์ดนำของเมกะเมนูจมหายบนหน้าข่าว
 * เพราะ .rxsu-cap-panel__lead ตั้ง color:#fff ที่ตัวมันเอง ส่วน .rxsu-cap-panel__title เป็น
 * <h2> ที่ไม่มีสีของตัวเองเลย อาศัยการสืบทอดอย่างเดียว - The7 ประกาศสีหัวข้อไว้ที่ตัว h1..h6
 * โดยตรง และการประกาศที่ตัว element ชนะสีที่สืบทอดมาจากบรรพบุรุษเสมอ ไม่ว่ากฎของบรรพบุรุษ
 * นั้นจะ specific แค่ไหน (บั๊กตัวเดียวกับที่ 0.26.1 แก้ให้ <a> ไปแล้ว แต่ตอนนั้นไม่ได้ครอบ
 * หัวข้อกับย่อหน้า) ทางแก้คือบล็อก TEXT COLOUR ใต้บล็อก LINKS ด้านล่าง และตามด้วยการยก
 * component ทุกตัวที่ตั้งใจมีสีของตัวเองขึ้นเป็นสองคลาส - ดูรายการเต็มและตัวเลข specificity
 * ที่คำนวณไว้ทีละกฎใน .superpowers/sdd/2026-08-02-shell-capsule-polish/task-2-report.md
 *
 * ข้อ 5 - "พบมีส่วนเกินในหน้าอื่น ๆ เช่น ข่าวสารตามรูปที่ 6": หน้าข่าวแสดง header ของ The7
 * เองและ widget footer ของ The7 เองเพิ่มมาจากของเรา ต้นเหตุคือ Theme_Runtime::
 * suppress_elementor_header() คืน template id 0 ให้ทั้งสองตำแหน่ง ซึ่งทำให้
 * elementor_theme_do_location() คืน false - และ false คือสัญญาณที่ The7 รอเพื่อวาดเปลือกของ
 * ตัวเองแทน การปิดตำแหน่งของ Elementor ยังจำเป็นอยู่ (ไม่งั้น header Elementor จริงของคณะจะ
 * โผล่มาด้วย) แต่ยังไม่พอ - ดูบล็อก "THE THEME'S OWN SHELL" ท้ายหมวด SHELL — header
 *
 * 0.29.0 fix round 2 (five-lens adversarial sweep over tasks 1-4, colour half).
 * ห้าข้อ ทุกข้อมีตัวเลขกำกับที่ tests/shell-capsule-contrast.php คำนวณซ้ำได้เอง:
 *
 * ข้อ 6 (สำคัญที่สุด - เป็นข้อที่เจ้าของเว็บขอให้แก้ตั้งแต่แรก) - hover ของราง nav มองแทบ
 * ไม่ออก ข้อ 2 ข้างบนเปลี่ยนสี hover เป็น --sh-nav-hover #EAF1EC แล้ววัดเทียบ
 * --sh-nav-hover-ink อย่างเดียว ซึ่งเป็นการวัด "หมึกบนชิป" ไม่ใช่ "ชิปบนพื้นที่มันไปวางอยู่"
 * ลิงก์ในรางไม่ได้นั่งบน --sh-brand แต่นั่งบน *รางไม่โปร่งใสอีกต่อไป* คือ --sh-rail
 * rgba(18,58,46,.06) ผสมลงบน --sh-brand แล้ว = (240.78,243.18,242.46) บนโทนปกติ ซึ่ง
 * #EAF1EC ต่างจากมันแค่ 1.03:1 (sage 1.06:1, olive 1.06:1) - อ่อนกว่า hover เดิมของ 0.27.0
 * (rgba(18,58,46,.10) = 1.08:1) ที่มันมาแทนเสียอีก พื้นอ่อนบนรางที่เกือบขาวไปไม่ถึง 3:1
 * ด้วยการระบายสีอย่างเดียวไม่ว่าจะเลือกเฉดไหน สถานะจึงต้องมีองค์ประกอบที่วัดได้จริง:
 * เพิ่มเส้นใต้ inset 2px สีเดียวกับ --sh-nav-open (เขียวเข้มของการ์ดนำ) วัดได้ 11.29:1
 * บนโทนปกติ 10.37:1 บน sage และ 10.31:1 บน olive เทียบกับรางที่ผสมแล้ว - ดูหมายเหตุเหนือ
 * กฎ :hover เองสำหรับเหตุผลที่เลือกเส้นใต้แทนวงแหวนรอบชิป
 *
 * ข้อ 7 - ลิงก์ในแถบลิขสิทธิ์ท้าย footer (.rxsu-cap-foot__bottom a) opacity .88 ทำให้
 * --sh-foot-bottom-ink จางลงจนตก AA สองโทน: 'logo-teal' 4.43:1 และ 'olive-mid' 4.28:1
 * (ต้องการ 4.5:1 ที่ 13.5px/400) ส่วน <span> ลิขสิทธิ์ข้าง ๆ ที่ทึบเต็มร้อยผ่านสบาย
 * (5.24:1 / 4.98:1) คือครึ่งที่คลิกได้เท่านั้นที่ตก .94 (ค่าเดียวกับ
 * .rxsu-cap-utility__link พี่น้องของมันที่รอดมาแล้วบนชุดโทนเดียวกัน) ให้ 4.62:1 เป็นค่าต่ำสุด
 *
 * ข้อ 8 - ปุ่มค้นหา background:#fff บน --sh-brand #FFFFFF คือสีเดียวกันเป๊ะ ขอบจึงเป็น
 * ขอบเขตเดียวที่มี และ rgba(18,58,46,.14) ผสมแล้ว = 1.29:1 ต่ำกว่า 3:1 ที่โครงการนี้
 * บังคับกับขอบเขตของ UI ปรับเป็น .6 -> 3.76:1 (default) 3.44:1 (sage) 3.42:1 (olive)
 * (fix round 4 corrected the olive figure: it computes to 3.4249, i.e. 3.42, not
 * the 3.43 rounds 2 and 3 wrote here and at the rule itself. Same pass, same
 * conclusion - recorded because a number that cannot be reproduced is a number
 * the next reader has to re-derive to trust.)
 *
 * ข้อ 9 - .rxsu-cap-skip อยู่นอกทั้งสอง root: header.php วาดมันก่อน <div class="rxsu-cap-shell">
 * เปิด มันจึงเป็นพี่น้องของเปลือก ไม่ใช่ลูกหลานของ .rxsu-cap-shell หรือ .rxsu-cap-foot -
 * ทั้งบล็อก LINKS และ TEXT COLOUR เอื้อมไม่ถึง บนหน้าจริง a{} ของ The7 จึงขีดเส้นใต้ให้
 * และ a:focus{color} ของ The7 (0,1,1) ชนะสีของกฎเดิม (0,1,0) พอดี - ซึ่งเป็นสถานะเดียว
 * ที่ลิงก์นี้มองเห็นได้เลย ดูบล็อกของมันเองด้านล่าง
 *
 * ข้อ 10 - .rxsu-cap-search__input เป็นสีข้อความคลาสเดียวที่เหลืออยู่ในทั้งสอง root และ
 * เป็น element เดียวที่ผู้อ่านพิมพ์ลงไป เพิ่ม `input` เข้าบล็อก TEXT COLOUR แล้วยกกฎนี้เป็น
 * สองคลาส - ทำทั้งสองอย่าง ไม่ใช่อย่างใดอย่างหนึ่ง ดูเหตุผลที่ตัวกฎเอง
 *
 * 0.29.0 fix round 4 (third adversarial sweep, colour + state half). Seven
 * defects in this file plus one in its test; each is argued at the rule it
 * changes, and every number below is recomputed by
 * tests/shell-capsule-contrast.php rather than typed there:
 *
 * ข้อ 11 (CRITICAL) - every shell link repaints to The7's blue on HOVER. The
 * LINKS reset below is (0,1,1), an exact tie with The7's own a:hover, and
 * capsule.css prints first so The7 wins every tie. This is the 0.26.1 defect
 * returning in the one state that block never reasoned about. Fixed by a state
 * rule at (0,1,2) - above The7, below every component that means to keep a
 * colour - see the LINKS block's own note for why that gap can only be reached
 * with :where(). The skip link, a third root, gets :active/:visited added to
 * the states its own block already restates.
 *
 * ข้อ 12 - the mega panel's link hover was 1.09:1 with no second indicator: the
 * owner's original "สี hover ไม่สวยเลย" complaint, unfixed in the place holding
 * most of the menu's links (fix round 2 hardened only the nav rail). Same
 * answer in the same visual language - the wash plus a 2px inset rule in
 * --sh-nav-open, 12.57:1 on the panel ground.
 *
 * ข้อ 13 - the expanded search FIELD's border was 2.01:1, the identical defect
 * fix round 2 fixed on the search BUTTON 36 lines away and left on the control
 * the reader types into. .35 -> .6, 3.76 / 3.44 / 3.42:1.
 *
 * ข้อ 14 - three footer anchors (phone, email, CTA) had no focus indicator at
 * all. Not a weak one: The7's `a:focus{outline:0}` at (0,1,1) removes the UA
 * default and this file declared no outline under .rxsu-cap-foot. The footer
 * now carries the same deliberate :focus-visible treatment as .rxsu-cap-shell
 * (4.94:1 on the light ground, --sh-cream on the dark copyright strip), and
 * the comment that asserted the UA default was there is corrected in place.
 *
 * ข้อ 15 - the search field's ::placeholder was still The7's, because a
 * pseudo-element takes no colour from `color:inherit` on its originating
 * element. Declared in both the standard and -webkit- spellings, 4.53:1.
 *
 * ข้อ 16 - the burger had no boundary (1.27:1) and below the rail breakpoint it
 * is the only route into the menu. An inset 1px ring at the same .6 tint the
 * two search controls use - inset rather than a border so the 46px the row
 * arithmetic in three media blocks assumes does not move.
 *
 * ข้อ 17 - .rxsu-cap-foot__cta filled with --sh-mint on the light footer at
 * 1.96:1, the exact ratio the token block already records as why the phone
 * number stopped using --sh-mint on this same surface. Fixed the same way.
 * Latent (contact.url is unset on production) but it would render the day an
 * operator fills the field.
 *
 * The eighth defect is in the test, not here: the hover-perceptibility check
 * read only the trailing colour term of a :hover box-shadow, so `0 0 0 0` -
 * an indicator that paints nothing - measured 11.29:1. It now requires
 * non-zero painted extent. */

/* 0.29.0: full-bleed breakout, same treatment .rxsu-cap-foot already got at
   0.27.0 (see that rule's own comment further down this file) and for the
   identical reason - .rxsu-cap-shell is a direct child of <body> too, and
   --sh-inset:0 alone only removes this file's own inset, not any padding or
   centred max-width a real page's own container still supplies. margin-left/
   right: calc(50% - 50vw) is the standard breakout: it reaches past that
   ancestor without needing to know its exact width. Fix round 1 (0.29.0 T1
   review) corrected an overstatement just below - see .rxsu-cap-foot's own
   comment further down this file for the exact arithmetic; the short version
   is that this overshoots the visible viewport by half the scrollbar's own
   width, not by zero, but still by half of what width:100vw alone would (no
   compensating negative margin at all). .rxsu-cap-foot has shipped the
   identical technique on this exact property since 0.27.0 with no reported
   scrollbar regression, so this introduces no new one.
   position:relative + z-index:900: this element is now the positioning
   context for the mega panel (.rxsu-cap-panel, position:absolute - see that
   rule further down) - without it, the panel's position:absolute would resolve
   against a more distant ancestor instead of sitting flush under this header.
   The z-index is what then lifts that panel above page content printed below
   the shell rather than sliding behind it.

   0.32.1: that value was 900, and "page content in normal flow carries no
   z-index of its own to contest it" - the sentence that used to stand here -
   was wrong about this plugin's own pages. assets/degree-program-public.css
   gives the degree programme's sticky section tabs `position:sticky;
   z-index:900`, the identical value, twice (the base rule and the >=60rem
   override). Equal z-index resolves by document order, and every page's
   content lives inside The7's #page, which print_header() emits the shell
   BEFORE - so the tab bar won, and the owner reported it painting straight
   through an open mega panel on the หลักสูตรเภสัชศาสตรบัณฑิต page.
   Raising the shell is the correct half of the fix rather than lowering the
   tabs: the shell is site chrome and must outrank anything a page draws,
   whichever page it is. 9990 clears every z-index this plugin's own
   stylesheets assign (tests/shell-capsule-contrast.php parses them all and
   asserts this rule outranks the highest, so the next page component to
   claim a big number fails the suite instead of the header), while staying
   under WordPress's own admin bar at 99999 - an editor previewing the site
   must not have the shell paint over their toolbar. .rxsu-cap-skip is a
   sibling printed BEFORE this element and has to stay reachable above it,
   so it moves in step; see its own rule. */
.rxsu-cap-shell{
  position:relative;z-index:9990;
  padding:0 var(--sh-inset);
  margin-left:calc(50% - 50vw);margin-right:calc(50% - 50vw);
}

/* ══════════════════════════════════════════════════════════════════════════
   LINKS — base reset, both shell roots (0.26.1 fix)

   The approved mockup carried its own `a{color:inherit;text-decoration:none}`,
   but it sat at the page level, outside the shell block - and was correctly
   stripped as mockup chrome when this file was extracted (see the docblock
   above, item 3's neighbour on --sh-page). Nothing replaced it. On the harness
   page, which loads no other stylesheet, that absence was invisible: with no
   competing `a` rule at all, the browser had nothing to fall back to but the
   inherited colour and the UA default of no underline-until-hover this file
   already draws. On a real page The7 ships its own `a{color:...;
   text-decoration:underline}` - a rule declared directly on the element, which
   wins over a colour merely inherited from a styled ancestor (.rxsu-cap-utility,
   .rxsu-cap-foot, .rxsu-cap-drawer, .rxsu-cap-brand-row all set `color` on
   themselves, not on the anchors inside them) regardless of that rule's own
   specificity. Measured on production 2026-08-02: every shell link underlined,
   several rendering The7's link blue instead of the shell's own ink - the
   handful of links that already state their own `color` directly on the
   anchor (.rxsu-cap-nav__link, .rxsu-cap-foot__phone) already won that fight
   against The7 and were only ever wrong on the underline there.

   Scoped to both shell roots - .rxsu-cap-shell (header, utility strip, nav
   rail, mega panel, mobile drawer) and .rxsu-cap-foot (footer: a separate
   root, never a descendant of .rxsu-cap-shell - see templates/shell/capsule/
   parts/footer.php, printed on its own hook) - rather than repeated at the
   page level like the mockup, so it cannot leak into page content the way
   item 3 above already flagged as unsafe.

   `color:inherit` hands colour back to each component rule below - most
   already say `color:inherit` or a token explicitly. `text-decoration:none`
   is the property nothing here ever restated once the mockup's page-level
   rule was gone. But this reset is itself a same-file, later, direct rule on
   the anchor (one class plus the `a` type = (0,1,1)), so it competes on
   ordinary specificity with every other rule in this file the same way it
   competes with The7 - including .rxsu-cap-foot__phone's own one-class rule,
   which already beat The7's lower-specificity rule but would have quietly
   lost to *this* one and been re-broken by the very fix meant to save it.
   Five components named in the 0.26.1 report deliberately want a colour
   other than inherit: the mint phone, the mint CTA, the cream pill, the
   active nav pill, and the unreachable-today "ดูหลักสูตรทั้งหมด →"
   more-link. Auditing every `color:` declaration in this file against both
   roots' specificity turned up a sixth in the same shape that the report's
   live measurement could not have caught, because it does not render on
   production yet (.rxsu-cap-foot__cta - contact.url is still unset, see
   footer.php's docblock). All but the active nav pill (already safe: its
   `[aria-current]` attribute makes it two classes on its own) are bumped to
   two classes below so each keeps outranking this reset rather than
   depending on source order (the same reasoning as the three-class rule on
   .rxsu-cap-js .rxsu-cap-drawer.is-open further down this file).
   tests/shell-capsule-contrast.php's check 6 pins the reset itself under
   both roots.

   0.29.0 fix round 4 (defect 1, CRITICAL - the 0.26.1 defect returning in the
   hover state). Everything above reasons about The7's `a{...}` at (0,0,1),
   which this reset outranks. It never reasoned about The7's STATE rules, and
   those are a different fight: dt-custom.css ships
   `a:hover{color:var(--the7-links-hover-color,var(--the7-links-color))}` at
   (0,1,1) - the identical specificity to this reset's own
   `.rxsu-cap-shell a` (one class plus the `a` type). An exact tie is decided
   by source order, capsule.css is enqueued at wp_enqueue_scripts priority 5
   and therefore prints BEFORE The7's stylesheets, so The7 wins every tie.
   Measured consequence: every shell anchor whose colour comes only from this
   reset repainted #1ebbf0 the moment a reader pointed at it - the utility
   strip's links, the drawer's whole menu, the mega panel's link columns, the
   footer's email line and copyright links, the four social marks' text
   fallback. The resting state was fixed at 0.26.1; the hover state was never
   looked at.
   The state rule below is the fix, and its specificity is the whole design:
   (0,1,2) - one class-column entry (the state pseudo-class) and two
   type-column entries (`body` and `a`). It therefore
     · BEATS The7's a:hover/a:focus at (0,1,1) without !important and without
       depending on source order, and
     · LOSES to every two-class component rule at (0,2,0) or (0,2,1) that
       deliberately declares a colour of its own - the cream pill, the mint
       CTA, the accent more-link, the mint phone, the footer CTA, the nav
       rail's own hover ink, the language switch's current chip, the mega
       panel's current link and TikTok's white mark. Those all keep painting
       exactly what they paint today; a rule at .rxsu-cap-shell a:hover, the
       obvious spelling, would be (0,2,1) and would have flattened all nine of
       them to `inherit` on hover - white ink on a mint pill, dark ink on a
       green CTA.
   Nothing between (0,1,1) and (0,2,0) can be reached with the shell root
   spelled as a plain class, since the class column is already spent on the
   state pseudo-class. :where() is what frees it: its argument contributes
   zero specificity by definition, so the root still SCOPES the rule (this
   never leaks to page content, the same constraint the reset itself is
   scoped under) while contributing nothing to the arithmetic, and `body` -
   an element that is always there and always an ancestor - supplies the
   second type-column entry. The eight selectors are written out one root at
   a time rather than as :where(.rxsu-cap-shell,.rxsu-cap-foot): a comma
   inside a functional pseudo-class is not a selector-list boundary, and the
   rule-splitting in tests/shell-capsule-contrast.php (and in
   tests/shell-capsule-js.js) splits on commas.
   text-decoration is restated for the same reason it is in the reset: The7
   declares `a{text-decoration:underline}` on the element, and nothing
   guarantees a theme leaves the state alone. The two places in this file
   that deliberately DO underline on hover (.rxsu-cap-foot li a:hover and
   .rxsu-cap-foot__bottom a:hover, (0,2,2) and (0,2,1)) both outrank this
   rule and are unaffected - verified numerically, not assumed, in check 14.
   The skip link is outside both roots and gets the same treatment in its own
   block above; check 14 pins the whole specificity ordering.
   ══════════════════════════════════════════════════════════════════════════ */
.rxsu-cap-shell a,
.rxsu-cap-foot a{color:inherit;text-decoration:none}
body:where(.rxsu-shell-capsule) :where(.rxsu-cap-shell) a:hover,
body:where(.rxsu-shell-capsule) :where(.rxsu-cap-shell) a:focus,
body:where(.rxsu-shell-capsule) :where(.rxsu-cap-shell) a:active,
body:where(.rxsu-shell-capsule) :where(.rxsu-cap-shell) a:visited,
body:where(.rxsu-shell-capsule) :where(.rxsu-cap-foot) a:hover,
body:where(.rxsu-shell-capsule) :where(.rxsu-cap-foot) a:focus,
body:where(.rxsu-shell-capsule) :where(.rxsu-cap-foot) a:active,
body:where(.rxsu-shell-capsule) :where(.rxsu-cap-foot) a:visited{color:inherit;text-decoration:none}

/* ══════════════════════════════════════════════════════════════════════════
   TEXT COLOUR — the same defect the LINKS block above documents, for every
   other element that carries text. (0.29.0 T2)

   The7 declares heading colours directly on h1..h6. A declaration on the
   element beats a colour merely inherited from a styled ancestor, whatever
   the ancestor rule's own specificity - so .rxsu-cap-panel__lead{color:#fff}
   did not reach its own <h2>, and on the news page (measured 2026-08-02) the
   mega panel's lead heading rendered The7's dark ink on the lead card's dark
   green and disappeared. The homepage did not show it, which is exactly why
   it survived three releases: the harness page loads no other stylesheet and
   the homepage's own kit happens not to set it.

   Rooted at both shell roots and scoped to the tags the shell actually
   renders, so it cannot reach page content. Placed here, before every
   component rule, and every component that wants a colour of its own is
   bumped to two classes below - the same discipline the LINKS block uses,
   for the same reason: source order is not a specificity argument.

   `button` is named under .rxsu-cap-shell only: footer.php renders no button
   at all (its CTA and its social links are anchors, already covered by the
   LINKS reset above), so naming one there would reset an element that does
   not exist. No <div> is named under either root, which is why
   .rxsu-cap-utility and .rxsu-cap-foot__bottom - the two <div>s in this file
   that set a colour - keep their one-class rules untouched; adding `div` here
   later means revisiting both, and tests/shell-capsule-contrast.php's check 9
   pins them so that is a deliberate act rather than an accident.

   0.29.0 fix round 2 (defect 10): `input` joins the list, also under
   .rxsu-cap-shell only - header.php renders exactly one (the search field,
   <input class="rxsu-cap-search__input" type="search">) and footer.php renders
   no form at all. It was the last text-bearing tag the shell prints that this
   reset did not name, and the one element a visitor types their own words
   into: The7 styles form controls directly on the element the same way it
   styles headings, so an <input> with no rule of its own is an <input> The7
   owns. Both halves of the fix were applied rather than either alone (the
   sweep offered a choice): naming the tag here is what protects the *next*
   input somebody adds, and bumping .rxsu-cap-search__input to two classes
   below - it declares #0F3A30, a colour it means to keep - is what stops this
   very reset from flattening the only input that exists today. Doing just the
   bump would leave a future field to The7; doing just the reset would silently
   grey out the search field's own ink the moment this line shipped.
   ══════════════════════════════════════════════════════════════════════════ */
.rxsu-cap-shell h1,.rxsu-cap-shell h2,.rxsu-cap-shell h3,.rxsu-cap-shell h4,
.rxsu-cap-shell p,.rxsu-cap-shell span,.rxsu-cap-shell li,.rxsu-cap-shell button,
.rxsu-cap-shell input,
.rxsu-cap-foot h1,.rxsu-cap-foot h2,.rxsu-cap-foot h3,.rxsu-cap-foot h4,
.rxsu-cap-foot p,.rxsu-cap-foot span,.rxsu-cap-foot li{color:inherit}

/* ══════════════════════════════════════════════════════════════════════════
   ลิงก์ข้าม — mockup ไม่มีสถานะนี้เลยเพราะเป็นภาพนิ่ง ไม่มีคีย์บอร์ดมากด Tab จึงไม่มี
   CSS ต้นแบบให้แปลง สไตล์ทั้งบล็อกนี้จึงเป็นของใหม่ล้วน: ซ่อนอยู่นอกจอจนกว่าจะโฟกัส
   ด้วยคีย์บอร์ด แล้วเลื่อนเข้ามาในจอ - ไม่ใช่ opacity:0 เพราะแบบนั้นยังกินพื้นที่และยัง
   ถูกอ่านเป็นข้อความว่างที่มองไม่เห็นอยู่เหนือ header ตามที่ review รอบแรกจับได้

   0.29.0 fix round 2 (defect 9) — this link is a third root, and always was.
   header.php prints <a class="rxsu-cap-skip"> BEFORE <div class="rxsu-cap-shell">
   opens, so it is the shell's SIBLING and a descendant of neither
   .rxsu-cap-shell nor .rxsu-cap-foot. Every reset this file has added since
   0.26.1 is rooted at one of those two, so none of them ever reached it:
     · the LINKS reset (`.rxsu-cap-shell a,.rxsu-cap-foot a`) never applied, so
       nothing here restated `text-decoration:none` and The7's own
       `a{text-decoration:underline}` - a declaration on the element - drew the
       underline the LINKS block exists to remove;
     · the rule below used to be a bare `.rxsu-cap-skip` at (0,1,0), which does
       outrank The7's `a{color}` at (0,0,1) but LOSES to The7's `a:focus{color}`
       and `a:hover{color}` at (0,1,1) - and :focus is the only state in which
       this link is ever visible at all, so the losing case was the only case;
     · `.rxsu-cap-shell :focus-visible` (the one shared ring, further down)
       could not reach it either, leaving the UA default outline this release
       replaced everywhere else.
   All three are fixed by rooting the block at .rxsu-shell-capsule - a body
   class, and the skip link is a body descendant, so it reaches where the two
   shell roots cannot. (0,2,0) for the resting rule and (0,3,0) for the state
   rules, which beats The7's (0,1,1) on both without needing !important.
   The state rules restate the resting colours rather than saying `color:inherit`
   because there is nothing sane to inherit from here: the parent is <body>.

   outline-offset is NEGATIVE, unlike the shell's own ring. This link sits at
   top:0 left:0 of the initial containing block, i.e. laid over the utility
   strip, whose surface is the operator's dark bar tone. A ring drawn 2px
   OUTSIDE the link would land dark green (--sh-nav-open) on that dark strip
   and be no ring at all; drawn inside, its backdrop is the link's own
   --sh-brand, the identical pair check 8 already measures for the shell ring
   (12.57:1 default, 11.52:1 sage, 11.45:1 olive - all far over the 3:1 floor).
   ══════════════════════════════════════════════════════════════════════════ */
.rxsu-shell-capsule .rxsu-cap-skip{
  /* 0.32.1: 1000 -> 9995, moved in step with .rxsu-cap-shell's own raise to
   * 9990 (see that rule for why it had to go up). This link is a SIBLING
   * printed before the shell, so at equal or lower z-index the shell would
   * paint over it and the first thing a keyboard user reaches on every page
   * would be invisible - the exact regression the raise would otherwise have
   * introduced. Stays 5 under the shell's own headroom and far under the
   * admin bar's 99999. */
  position:absolute;top:0;left:0;z-index:9995;
  transform:translateY(-120%);
  /* 0.27.0: color tracks --sh-brand-ink now, not a hardcoded #fff - the
   * skip link shares the brand row's own surface, and a light brand row
   * with white text would be invisible white-on-white. */
  background:var(--sh-brand,#FFFFFF);color:var(--sh-brand-ink,#1D2B24);
  text-decoration:none;
  padding:12px 22px;border-radius:0 0 14px 0;
  font-weight:700;font-size:14px;
  transition:transform .15s ease;
}
/* 0.29.0 fix round 4 (defect 1): :active and :visited join the list. The
   three states above were the ones defect 9 named, because :focus is the only
   state in which this link is visible at all - but a link that has been
   followed is :visited for the rest of the session, and the pointer states
   overlap with it, so The7's `a:visited`/`a:active` would own the colour in
   exactly the moment the link IS on screen. Same states, same declarations,
   as the shell-wide state rule under the LINKS block below; this block has to
   state them separately because the skip link is a sibling of both shell
   roots and nothing rooted at either can reach it. (0,3,0) here, so it stays
   far above The7's (0,1,1) with room to spare. */
.rxsu-shell-capsule .rxsu-cap-skip:hover,
.rxsu-shell-capsule .rxsu-cap-skip:focus,
.rxsu-shell-capsule .rxsu-cap-skip:focus-visible,
.rxsu-shell-capsule .rxsu-cap-skip:active,
.rxsu-shell-capsule .rxsu-cap-skip:visited{
  background:var(--sh-brand,#FFFFFF);color:var(--sh-brand-ink,#1D2B24);
  text-decoration:none;
}
.rxsu-shell-capsule .rxsu-cap-skip:focus{transform:translateY(0)}
.rxsu-shell-capsule .rxsu-cap-skip:focus-visible{
  outline:2px solid var(--sh-nav-open,#123A2E);outline-offset:-2px;
}

/* ══════════════════════════════════════════════════════════════════════════
   TOKENS — ย้ายจาก :root มาอยู่ใต้ body class ของแม่แบบนี้โดยเฉพาะ (ดูข้อ 2 ด้านบน)
   ══════════════════════════════════════════════════════════════════════════ */
.rxsu-shell-capsule{
  /* การ์ด header: แถบยูทิลิตี้บนแถว brand */
  /* แถบยูทิลิตี้และแถบลิขสิทธิ์ท้าย footer อ่านค่าจากตัวเลือกสีแถบเดียวกับที่หน้าจอ
   * "เปลือกเว็บไซต์" แท็บ Header คุมอยู่แล้ว (shell_bar_tone) ผ่าน
   * Header_Menu_Runtime::bar_tone_css() ซึ่งปล่อย --rxsu-bar-surface และ
   * --rxsu-bar-ink ออกมาทุกหน้าอยู่แล้ว ไม่ใช่ค่าคงที่ - นี่คือสิ่งเดียวที่ทำให้ผู้ดูแล
   * ปรับสีแถบของเปลือกใหม่ได้ตั้งแต่รุ่นแรกโดยไม่ต้องมีตัวควบคุมใหม่เลย ค่า fallback
   * คือค่าที่ live อยู่จริงบน production ณ 2026-08-02 (โทน sage:
   * includes/class-rxsu-home-page-schema.php::bar_tones()['sage']) เผื่อกรณีที่
   * bar_tone_css() ยังไม่ได้พิมพ์ตัวแปรออกมา (เช่น ค่า tone ที่เก็บไว้ไม่อยู่ในชุดปิด) */
  --sh-strip: var(--rxsu-bar-surface, #5B6752);
  --sh-strip-ink: var(--rxsu-bar-ink, #FBFCF9);
  /* 0.27.0: light by default - the faculty seal is dark ink on transparency,
   * and only a light brand row keeps its darkest strokes above 3:1 (measured
   * in Shell_Tone::LOGO_INK_DARKEST). See this file's own docblock above. */
  --sh-brand:#FFFFFF;          /* brand row */
  --sh-brand-ink:#1D2B24;

  /* Action + identity
   * 0.27.0: kept exactly as before, not lifted from the mockup's light block.
   * Both pairs render on the header's always-dark utility strip AND on the
   * always-dark lead card/footer (phone, CTA, social hover) at the same
   * time as on the now-light brand row - a self-contained bg/ink pill that
   * already reads fine as a colour accent regardless of what sits behind
   * it. The mockup's light-tone values are tuned for text, not an accent
   * chip, and would sink the footer phone number to unreadable on its own
   * dark background. See this file's own docblock above. */
  --sh-mint:#35C79B;           /* active pill, phone, CTA */
  --sh-mint-ink:#04281F;
  --sh-cream:#DEE1B0;          /* E-donation pill, dash accent - dark-surface only */
  --sh-cream-ink:#33361A;
  --sh-olive:#A9AE4E;

  /* Mega panel */
  --sh-panel:#FFFFFF;
  --sh-panel-ink:#22302A;
  --sh-panel-muted:#6A7A70;
  --sh-panel-active:#E7F6EF;
  /* The lead card, the drawer (via .rxsu-cap-drawer below - decoupled from
   * --sh-brand for exactly this reason) and the footer just below are the
   * "small number of deliberate places" dark green still anchors. */
  --sh-lead:#123A2E;
  /* 0.69.0 - scrim behind the mobile drawer. Not a new colour: 18,58,46 is the rgb of
   * --sh-lead on the line above, which this file already spells as a literal in three
   * places at three opacities (--sh-rail .06, the burger's ground .13, the burger's
   * inset edge .6). It has to be a *token* rather than a fourth literal because it is a
   * surface that must move with Shell_Tone exactly as --sh-lead does - see both
   * [data-rxsu-tone] blocks below. .55 sits between the two scrims this plugin already
   * ships: 42% in assets/global-header-unity.css:1238 (the owned menu layer) and .92 in
   * assets/lightbox.css (which deliberately swallows the page). The drawer wants the
   * middle: enough to stop you reading the page behind it, not so much that the page
   * stops existing. */
  --sh-scrim:rgba(18,58,46,.55);

  /* Footer - 0.28.0: light ground now, matching includes/class-rxsu-shell-renderer.php's
   * own footer (assets/shell-footer.css) - the "real renderer" the owner pointed at in
   * the admin screen's own preview panel. Constant across every Shell_Tone from here on
   * (removed from both [data-rxsu-tone] override blocks below); --sh-lead just above is
   * the one dark anchor still varying per tone. var(--rxsu-home-soft, #F3F6EF) is the
   * exact pattern shell-footer.css itself uses for its ground, not a fabricated new
   * fallback - assets/managed-shell.css and assets/home-page.css both already declare
   * --rxsu-home-soft:#F3F6EF on :root, so this renders identically to them wherever
   * either has loaded, and the literal fallback is the same #F3F6EF either way when
   * neither has. */
  --sh-foot: var(--rxsu-home-soft, #F3F6EF);
  --sh-foot-ink:#2C3A2B;       /* shell-footer.css's own body ink */
  --sh-foot-head:#2C3A2B;      /* same as ink - shell-footer.css does not give headings a distinct accent hue on the light ground */
  --sh-foot-circle:#FBFCF9;    /* social icon/fallback circle - shell-footer.css's own near-white */
  /* New tokens: the footer's own text roles this file did not previously need, because
   * every one of them either assumed a dark ground (a literal white-ish colour, or
   * opacity dimming a light ink toward a dark background) or borrowed --sh-mint as a
   * bare text colour rather than a self-contained bg/ink chip - see the notes on each
   * consumer further down this file for why each one specifically breaks on a light
   * ground and what replaces it. */
  --sh-foot-muted:#5F6B5C;     /* address/email - shell-footer.css's own muted paragraph colour */
  --sh-foot-tagline:#007C72;   /* shell-footer.css's own tagline colour, literal-for-literal */
  --sh-foot-phone:#00795D;     /* not --sh-mint (1.96:1 here, see below) - reuses the literal this file already trusts for an accent link on a light surface (.rxsu-cap-group__more, on the white mega-panel) */
  --sh-foot-hairline:#D3DDCF;  /* shell-footer.css's own hairline - concrete, not translucent white; --sh-hairline below stays reserved for the still-dark drawer */
  /* Task 6: the copyright strip reads the same bar-tone choice as the utility
   * strip at the top of the header (--sh-strip above) - one decision since
   * 0.25.12, and the "เปลือกเว็บไซต์" admin screen tells the operator exactly
   * that. A constant here would make that description a lie the moment capsule
   * shipped. Same fallback value as --sh-strip's sibling for the same reason:
   * it is the tone live on production on 2026-08-02, not a made-up default. */
  --sh-foot-bottom: var(--rxsu-bar-surface, #0B2E26);
  /* Task 7 (tests/shell-capsule-contrast.php) found that only the background
   * above had been wired to the operator's choice - the text was still
   * inheriting the constant --sh-foot-ink from .rxsu-cap-foot, unchanged by
   * whichever tone was selected. Two of the eight selectable tones fail AA
   * against that constant: logo-teal measured 4.25:1 and olive-mid 4.04:1,
   * both below the 4.5:1 floor, on the strip carrying the copyright and
   * privacy-policy links. This is the same bug class the old shell shipped
   * once already (see tests/shell-bar-contrast.php's note on 0.25.14 and
   * --rxsu-footer-on-primary) - a surface reads the shared tone but its text
   * does not, so the pairing that actually renders was never the one that got
   * checked. The fallback stays #DCE7E1, so an install with no tone chosen
   * yet renders exactly what it rendered before this fix. */
  --sh-foot-bottom-ink: var(--rxsu-bar-ink, #DCE7E1);

  /* Shape + rhythm */
  /* 0.28.0 dropped this to 0 - the owner asked for a full-width, square
     header, not a floating capsule - kept as a live token even at 0 (not
     deleted), same as --sh-texture:0 below, so a future skin could reintroduce
     a rounded card by changing one value instead of hunting down every
     consumer. 0.29.0: that "future skin" is this release - the owner's actual
     request was full width *and* a capsule shape together ("เอารูป capsule
     เต็มความกว้าง ดูสวยงามกว่า" - see this file's own 0.29.0 docblock note
     above), not a trade-off between the two. Bottom-only radius, not all four
     corners: the shape now paints on .rxsu-cap-brand-row (see that rule below),
     whose only free edge is the bottom one - the utility strip above it has no
     rounded corner of its own to match. */
  --sh-radius-card:0 0 22px 22px;
  --sh-radius-panel:24px;
  --sh-radius-lead:18px;
  --sh-inset:0;                /* 0.28.0: no inset from the page edges either - see --sh-radius-card just above */
  /* 0.27.0: dark-tinted now, not white - the nav rail track sits directly on
   * the (now light) brand row and a white overlay on a white surface does
   * not render. --sh-hairline stays untouched: its two consumers
   * (.rxsu-cap-foot__bottom's border and, since the drawer/brand decoupling
   * below, .rxsu-cap-drawer__top's border) are both still dark surfaces. */
  --sh-rail:rgba(18,58,46,.06);
  /* 0.29.0: nav-rail hover/open states, redesigned from scratch per the
     owner's "สี hover ไม่สวยเลย" - replaces the flat rgba(18,58,46,.10) hover
     and the --sh-mint open/current treatment 0.27.0 used, both restated
     directly on the consumer rules below rather than through a token at the
     time. See the rules under "brand row" further down for the fuller account
     of why each specific value was chosen and what it replaces.
     0.29.0 fix round 2 (defect 6): --sh-nav-hover is now only HALF the hover
     state and no longer carries it on its own - the wash measures 1.03:1
     against the composited rail track it lands on, and cannot do better while
     staying a light wash. The state's contrast comes from a 2px inset bottom
     rule in --sh-nav-open declared on the :hover rule itself, not from a fifth
     token: the whole point of that indicator is that it is the open state's
     own colour, so giving it a name of its own would let the two drift apart. */
  --sh-nav-hover:#EAF1EC;      /* soft sage wash — half of the rail's hover, the half with no contrast of its own */
  --sh-nav-hover-ink:#123A2E;
  --sh-nav-open:#123A2E;       /* an open trigger takes the lead card's own green, so the button and the panel it opened read as one object */
  --sh-nav-open-ink:#FFFFFF;
  --sh-hairline:rgba(255,255,255,.14);
  --sh-nav-size:1.0625rem;     /* = menu_density "โปร่ง", the live setting */
  /* 0.29.0 fix round 1 (five-lens sweep, defect 1): the brand mark's size is a
     token now, because it is the one thing in the brand row that can give way
     when the row runs out of width. It used to be a literal `height:52px` on
     the <img> with no max-width at all, which made the logo's full rendered
     width (284.7px on the real file, attachment 1310, intrinsic 2557x467) the
     hard floor of the whole row - see the note over .rxsu-cap-brand img below
     for what that cost on a phone. The two narrow tiers at the end of this
     file step it down (44px, then 36px) rather than restating a literal
     height in three places. */
  --sh-logo-h:52px;
  /* 0.29.0 fix round 5 (defect 1): --sh-logo-h says how tall the mark WANTS to
     be; this says how small it is allowed to get before the row has to admit
     it is out of room. Expressed as a fraction of --sh-logo-h rather than a
     px floor of its own, so the three tiers keep their one source of truth:
     the floor is .72 x 52 = 37.4px at full size, .72 x 44 = 31.7 compact,
     .72 x 36 = 25.9 on a phone.

     Fix round 1 gave the mark constraint-based sizing with no lower bound at
     all, which turned every remaining source of pressure into a silent
     squeeze: chrome-headless-shell measured the mark at 41.6x7.6 (21% of the
     36px this file intends) with documentElement.scrollWidth == clientWidth,
     i.e. the page reported itself as fitting perfectly while the faculty's
     logo had become a dash. Eighty-four of the 228 configurations in fix
     round 5's matrix rendered under 60% of the intended height and not one of
     them overflowed. The floor converts that failure mode into the other one:
     past this point the row stops giving and the page gets a horizontal
     scrollbar instead - a signal an operator can see and act on, which a 4px
     logo is not.

     .72 rather than a rounder number: it is the largest floor that still
     never binds in any configuration this site can actually reach at rest
     (the tightest is 320px, where the mark settles at 92% on its own), so the
     floor only ever engages in the configurations the fix round 5 matrix
     names as pathological - a seven-item menu on a narrow desktop, or a rail
     that has outgrown its tier. See .rxsu-cap-brand--mark below for how the
     fraction becomes a width, and why the aspect ratio it needs comes from
     the template rather than from this file. */
  --sh-logo-min:.72;
  /* 0.28.0 dropped this to none - flush with the page, matching the footer's
     own full-bleed treatment from 0.27.0. 0.29.0: restored alongside
     --sh-radius-card above, and reassigned together with it to whichever
     element actually paints the rounded shape (.rxsu-cap-brand-row now, not
     .rxsu-cap-header - see that rule below) so the shadow follows the rounded
     bottom edge instead of implying a square card sits behind it. */
  --sh-lift:0 16px 34px -26px rgba(6,32,24,.45);
  --sh-texture:0;              /* diagonal stripe strength - off on a light header */
}

/* ══════════════════════════════════════════════════════════════════════════
   TONE OVERRIDES — Shell_Tone::SAGE / Shell_Tone::OLIVE (0.27.0)

   Only the header's brand row and the dark anchors (lead card, drawer,
   footer) vary per tone - the same set the docblock above explains for the
   default. --sh-strip/--sh-strip-ink (operator's own bar-tone pick),
   --sh-mint/--sh-cream and their inks (self-contained accent pairs already
   safe on any surface - see the comments above them) and --sh-rail/
   --sh-hairline/--sh-lift/--sh-texture (translucent overlays that do not
   need to track the tone's exact hue) stay the shared defaults for every
   tone, so they are not restated here.

   Every --sh-brand below clears 3:1 against the logo's own darkest ink
   (Shell_Tone::LOGO_INK_DARKEST) - tests/shell-capsule-contrast.php checks
   both blocks by name, the same rule check 5 applies to the default above.

   The attribute lands on <html> (document.documentElement), printed by
   Theme_Runtime::print_capsule_boot_class() on wp_head - see that method's
   docblock for why it cannot be the body tag or a footer-enqueued script.
   .rxsu-shell-capsule itself is a body class, so the selector below reaches
   across that ancestor gap rather than expecting the attribute on the same
   element - a rule declared this way still outranks the plain
   .rxsu-shell-capsule block above on specificity alone (0,2,0 vs 0,1,0),
   regardless of source order.

   0.28.0: --sh-foot/--sh-foot-ink/--sh-foot-head/--sh-foot-circle used to be
   restated here alongside --sh-lead, back when the footer was one more dark
   anchor that moved with the tone. The footer is a single fixed light design
   now (see the token block's own 0.28.0 note above), so these two blocks only
   still override --sh-brand/-ink (the header) and --sh-lead (the mega-panel
   lead card and, via the decoupling noted above, the mobile drawer) - the
   parts that are still deliberately dark and still vary by tone.
   tests/shell-capsule-contrast.php's check 5 asserts these four tokens are
   absent from both blocks below, so nobody re-couples the footer to the tone
   picker by habit. */
[data-rxsu-tone="sage"] .rxsu-shell-capsule{
  --sh-brand:#F3F6EF; --sh-brand-ink:#1F2B22;
  --sh-lead:#22352A;
  /* 0.69.0 - the rgb of this tone's own --sh-lead directly above, at the same .55.
   * Left out, the scrim would stay the default tone's green on a sage site. */
  --sh-scrim:rgba(34,53,42,.55);
}
[data-rxsu-tone="olive"] .rxsu-shell-capsule{
  --sh-brand:#F6F5E7; --sh-brand-ink:#2A2A12;
  --sh-lead:#333819;
  --sh-scrim:rgba(51,56,25,.55);
}

/* ══════════════════════════════════════════════════════════════════════════
   SHELL — header
   ══════════════════════════════════════════════════════════════════════════ */
/* 0.28.0: overflow:hidden removed - it existed solely to clip this card's
   rounded corners (see the token block's 0.28.0 note above), and with
   --sh-radius-card at 0 there is nothing left for it to clip. Kept, it would
   only be a hazard: a prior release already lost the search button to it
   silently at 761-1023px before the nav-rail breakpoint was corrected to
   1023px (Task 8 fix round 1). Without it, any future overflow at least
   shows up as a visible horizontal scrollbar instead of vanishing content.
   0.29.0: --sh-radius-card/--sh-lift are live again (see the token block's
   0.29.0 note above), but that rounded shape now paints on .rxsu-cap-brand-row
   below, not here - .rxsu-cap-header itself carries neither border-radius nor
   box-shadow any more, so it still has nothing of its own for overflow:hidden
   to protect and the 0.28.0 reasoning above stands unchanged. position:relative
   stays for the texture pseudo-element just below (position:absolute;inset:0
   relative to this element) - it is not what positions the mega panel or the
   drawer: those are this element's siblings inside .rxsu-cap-shell, not its
   descendants (see header.php's own docblock for why), and .rxsu-cap-shell
   itself took over that positioning-context role at 0.29.0 (see its own rule
   near the top of this file). */
.rxsu-cap-header{position:relative}

/* The diagonal texture. One gradient, no image, and it disappears entirely on
   the light tone because --sh-texture goes to 0. */
.rxsu-cap-header::before,.rxsu-cap-foot::before{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:repeating-linear-gradient(115deg,rgba(255,255,255,var(--sh-texture)) 0 2px,transparent 2px 9px);
}

/* ── utility strip ─────────────────────────────────────────────────────── */
/* 0.29.0 fix round 1 (five-lens sweep, defect 5): flex-wrap:wrap, and the gap
   split into a row gap and a column gap.
   The strip is one nowrap flex row of two children - the motto and the
   actions - and neither can shrink below its own min-content width. On a
   phone the actions win that fight (they are a pill, plus the TH/EN switch,
   both effectively rigid) and the motto is what collapses. Measured against
   the real tagline "เรียนรู้ไปด้วยกัน เติบโตไปด้วยกัน": at 320px the motto
   box was 71.8px wide and 136px tall - eight lines of two or three syllables -
   and the strip grew from 54px to 158px, nearly three times its height, spent
   on a sliver nobody can read comfortably. At 360px it was 79.3x102 (six
   lines, strip 124px) and even at 480px it was still 199.3x34 (two lines).

   Wrapping rather than hiding the motto below a breakpoint, which was the
   other option: the tagline is the operator's own copy, entered in the
   "เปลือกเว็บไซต์" screen, and a phone is where most of this site's readers
   see the header at all - dropping it there means most readers never see it.
   Wrapped, it keeps a full line of its own (216.7x17 at every width where it
   wraps, i.e. one line, the same box it has on a desktop) and the actions take
   the next one. Measured strip height: 320px 158 -> 81, 360px 124 -> 81, 390px
   73 -> 81, 414px and 480px 70 -> 81, and 54px unchanged at 600px and up where
   nothing wraps at all. The 11px the strip gains at 390-480 is the honest cost
   of this choice and is bought back several times over at 320-360; what it
   buys at every one of those widths is one readable line instead of two to
   eight unreadable ones. The horizontal gap is unchanged at 24px, so no wide
   layout moves; 10px is the new row gap, which only ever applies once a wrap
   has happened.
   The narrow padding lives in the same @media (max-width:480px) block as the
   brand row's, at the end of this file. */
.rxsu-cap-utility{
  position:relative;background:var(--sh-strip);color:var(--sh-strip-ink);
  display:flex;align-items:center;justify-content:space-between;
  flex-wrap:wrap;gap:10px 24px;
  padding:11px 26px;font-size:13.5px;
}
.rxsu-cap-motto{display:flex;align-items:center;gap:11px;margin:0;font-weight:500}
.rxsu-cap-dash{width:26px;height:7px;border-radius:999px;background:var(--sh-cream);opacity:.85;flex:none}
.rxsu-cap-utility__actions{display:flex;align-items:center;gap:26px}
.rxsu-cap-utility__link{opacity:.94;padding:4px 0;border-bottom:1px solid transparent}
.rxsu-cap-utility__link:hover{border-bottom-color:currentColor}
/* Two classes (ancestor + own), not one: the emphasised pill sits on cream, not
   the strip's own ink, and the base link reset above (.rxsu-cap-shell a, one
   class + the `a` type) would otherwise win that fight and flatten it back to
   inherit - 0.26.1 fix, see the LINKS docblock near the top of this file. */
.rxsu-cap-utility__actions .rxsu-cap-pill{
  display:inline-flex;align-items:center;gap:7px;
  background:var(--sh-cream);color:var(--sh-cream-ink);
  padding:7px 16px;border-radius:999px;font-weight:700;font-size:13px;
}
/* The language switch's track darkens the strip instead of lightening it.
   Fix round 1 (0.29.0 T4 review): it was rgba(255,255,255,.12), which raised
   the ground under the *non-current* label - the one this task resurrects on
   every English page - while that label's own ink stayed --sh-strip-ink
   dimmed to opacity .8. Two alphas working against each other: composited,
   the pair measured 3.11:1 on the operator-selectable 'olive-mid' tone and
   3.26:1 on 'logo-teal', against a 4.5:1 floor (12.5px/700 is not WCAG large
   text - large needs 18.66px bold). No tone in the closed set of eight
   cleared it; the worst thing to read on the strip was the half of the switch
   the reader is looking for. The aria-current half was never affected - it
   paints its own opaque --sh-mint/--sh-mint-ink pair.

   Darkening rather than lightening is the fix because the ink is light: every
   bar tone ships #FBFCF9 on a dark surface, so any white wash under it eats
   contrast and any black wash adds it. At rgba(0,0,0,.24) the same .8 ink
   measures 5.47:1 on the tightest tone and 9.53:1 on the darkest, the track
   is as visible against the strip as the old white one was (1.21-1.49:1
   either way - it is decoration, not a control boundary), and the mint
   current-pill gains a stronger edge against it (3.57:1 at worst, over the
   3:1 UI floor) instead of a weaker one. The de-emphasis the .8 was there for
   is unchanged. tests/shell-capsule-contrast.php check 11 composites both
   alphas over all eight tones - the pair was invisible to that suite until
   then, which is why a shipped 3.11:1 sat under 218 green assertions. */
.rxsu-cap-lang{display:inline-flex;background:rgba(0,0,0,.24);border-radius:999px;padding:3px}
.rxsu-cap-lang a{padding:5px 14px;border-radius:999px;font-size:12.5px;font-weight:700;opacity:.8}
.rxsu-cap-lang a[aria-current]{background:var(--sh-mint);color:var(--sh-mint-ink);opacity:1}

/* ── brand row ─────────────────────────────────────────────────────────── */
/* 0.29.0: border-radius/box-shadow moved here from .rxsu-cap-header (see that
   rule's own 0.29.0 note above). This row is the element that actually paints
   the header card's visible bottom edge - the utility strip above it has no
   rounded corner of its own - so the shape and the lift shadow that follows
   its silhouette both belong on the element doing the painting, not on an
   ancestor that would need overflow:hidden reintroduced just to clip to a
   shape it never draws itself. */
.rxsu-cap-brand-row{
  position:relative;background:var(--sh-brand);color:var(--sh-brand-ink);
  display:flex;align-items:center;gap:28px;padding:18px 22px 18px 26px;
  border-radius:var(--sh-radius-card);box-shadow:var(--sh-lift);
  /* Fix round 1 (0.29.0 T1 review): pairs with the corner-squaring rule further
     down (search ":has(.rxsu-cap-panel:not([hidden]))") so the bottom corners
     ease to square while a panel opens instead of snapping. */
  transition:border-radius .16s ease;
}
/* 0.29.0 fix round 1 (five-lens sweep, defect 1 - CRITICAL, measured in
   chrome-headless-shell against this exact stylesheet and the real six-item
   menu):
   `height:52px;width:auto` with no max-width made the mark 284.7px wide and
   completely rigid, and .rxsu-cap-brand is a flex item whose default
   min-width:auto takes its automatic minimum size from exactly that. With
   .rxsu-cap-search and .rxsu-cap-burger both flex:none, nothing in the row
   could give: the row needed 26 + 284.7 + 28 + 48 + 28 + 46 + 22 = 483px
   before the nav rail is even considered, so every viewport under that
   overflowed. Measured at 320px: scrollWidth 461 against clientWidth 320, the
   burger's box at x=414.7..460.7 - 141px past the right edge, and the search
   button clipped with it. Under the rail breakpoint the burger is the ONLY
   way into the site menu, and this site's traffic is mostly phones.

   Two declarations do the work, and both are needed:
   · min-width:0 on the link, so the flex item is allowed to be narrower than
     its content's own minimum size at all;
   · width:auto + height:auto + max-width:100% + max-height on the <img>, so
     the mark is sized by *constraints* rather than by a fixed height. With
     both dimensions auto, CSS 2.1 §10.4's constraint-violation table applies
     max-height and max-width proportionally, so the intrinsic 2557:467 ratio
     survives every clamp - measured 5.4754 at all fifteen widths, identical
     to the file's own naturalWidth/naturalHeight. `height:52px;max-width:100%`
     (the obvious one-line fix) would have kept the height while squeezing the
     width and rendered a squashed logo instead; object-fit:contain would have
     preserved the paint but left a 52px-tall box that no longer matches what
     is drawn in it.
   The mark therefore shrinks only when the row genuinely has no room left,
   and is the only thing in the row that does - the burger and the search
   button keep their full 46px targets at every width. */
.rxsu-cap-brand{min-width:0}
/* 0.29.0 fix round 5 (defect 1 - the other half of the rule above, and the
   correction to it).
   min-width:0 alone says "this item may shrink to nothing", and fix round 1
   read that as safe because the only pressure it had measured was the resting
   row. Three further sources of pressure were then reproduced in
   chrome-headless-shell, all three with scrollWidth == clientWidth, i.e. with
   the page insisting nothing was wrong:
     (a) focusing the search field, which used to add a rigid 190px + 8px to
         the row (fixed independently - the field is out of flow now, see the
         ค้นหา block at the end of this file);
     (b) a larger browser default font size, which grows the rail (rem) while
         the tier boundaries stayed px (fixed independently - the three
         queries are em now, see the compact-rail block below);
     (c) a seventh top-level menu item, which is the operator's own setting
         and cannot be designed away at all.
   (c) is why this rule exists rather than only (a) and (b): no amount of
   correcting the tiers removes the case where the rail simply wants more
   width than the viewport has, and the question is only what gives. Without a
   floor the answer was always the logo, silently and without limit.

   Applied through a marker class rather than to .rxsu-cap-brand itself
   because the floor is meaningless - and harmful on a phone - for the text
   fallback the link renders when no logo has been uploaded yet
   (.rxsu-cap-brand__fallback). header.php adds this class only on the branch
   that actually emitted an <img>. Written as two classes (0,2,0) so it beats
   the min-width:0 above on specificity rather than on source order, which a
   later reshuffle of this file could undo silently.

   --sh-logo-aspect is the mark's own intrinsic ratio and cannot be derived
   here: CSS can floor a height, but flooring the HEIGHT of a constraint-sized
   replaced element is exactly what fix round 1 established you must not do
   (CSS 2.1 §10.4's "w > max-width and h < min-height" cell resolves to
   w = max-width, h = min-height - a squashed logo, which is why that round
   chose max-height + max-width in the first place). A width floor keeps the
   ratio intact, and a width floor needs the ratio. So header.php prints it
   from the attachment's own registered dimensions, and the fallback here is
   the production file's (attachment 1310, 2557x467 = 5.4754) so that a page
   rendered without the custom property - an older cached template, a logo
   whose metadata WordPress cannot read - still gets a floor rather than none.
   A wrong-but-present aspect only mis-sizes the floor; a missing one removes
   it. */
.rxsu-cap-brand.rxsu-cap-brand--mark{
  min-width:calc(var(--sh-logo-h) * var(--sh-logo-aspect, 5.4754) * var(--sh-logo-min));
}
.rxsu-cap-brand img{display:block;width:auto;height:auto;max-width:100%;max-height:var(--sh-logo-h)}
/* โลโก้ยังไม่ได้ตั้งค่าไว้เสมอไป (ก่อนผู้ดูแลอัปโหลดในหน้าจอเปลือกเว็บไซต์) - ไม่มีสไตล์
   ต้นแบบจาก mockup เพราะภาพนิ่งมีโลโก้เสมอ ไม่มีเนื้อหาให้กันลิงก์ยุบเหลือ 0 ทำให้ตัวหนังสือ
   สำรองสูงเท่าที่โลโก้เคยกิน (52px) เพื่อไม่ให้แถว brand สูงกระตุกตอนอัปโหลดโลโก้ทีหลัง
   0.29.0 fix round 1: height -> min-height, and the literal 52px -> the same
   --sh-logo-h the mark now reads. height was safe only while the row could
   never be too narrow for this string; now that the row shrinks (see the two
   rules above), a fixed height would let the wrapped fallback text spill out
   of its own box. min-height keeps the anti-jump guarantee this rule exists
   for and lets the box grow instead. */
.rxsu-cap-brand__fallback{display:flex;align-items:center;min-height:var(--sh-logo-h);font-weight:800;font-size:15px;letter-spacing:.01em}
.rxsu-cap-nav{margin-inline-start:auto}
.rxsu-cap-nav__rail{
  display:flex;align-items:center;gap:4px;
  background:var(--sh-rail);border-radius:999px;padding:6px;
  list-style:none;margin:0;
}
/* 0.27.0 had this sitting directly on the (now light) brand row as a flat
 * rgba(18,58,46,.10) hover with no token of its own, and --sh-mint for both
 * aria-current and an open trigger - a "stop being actively wrong on a light
 * surface" fix at the time, not a considered hover/open/current system.
 * 0.29.0 (owner: "สี hover ไม่สวยเลย ออกแบบให้สวยกว่านี้"): replaced with the
 * four dedicated tokens declared above (--sh-nav-hover/-ink, --sh-nav-open/
 * -ink). --sh-mint is no longer used on this rail at all: with an open
 * trigger now taking the dark --sh-nav-open (the lead card's own green, so
 * the trigger and the panel it opens read as one object), a bright mint
 * aria-current chip on the same rail would compete with it for the same
 * "something is different here" signal rather than reading as a distinct
 * state - so aria-current moves to a quiet white chip with a hairline
 * instead (see its own comment just below). */
.rxsu-cap-nav__link,.rxsu-cap-nav__trigger{
  display:inline-flex;align-items:center;gap:7px;
  font:inherit;font-size:var(--sh-nav-size);font-weight:600;
  color:inherit;background:none;border:0;cursor:pointer;
  padding:10px 17px;border-radius:999px;white-space:nowrap;
  transition:background-color .16s ease,color .16s ease,box-shadow .16s ease;
}
/* 0.29.0 fix round 2 (five-lens sweep, defect 6 - the owner's original
   complaint, still unfixed after 0.29.0's first attempt at it).
   The wash alone was imperceptible, and measuring it never showed that because
   the only assertion covering this state measured --sh-nav-hover-ink against
   --sh-nav-hover: the chip's own text against the chip. What a reader actually
   sees appear is the CHIP against the surface the chip appears ON, and these
   links do not sit on --sh-brand - they sit on the rail track,
   .rxsu-cap-nav__rail{background:var(--sh-rail)}, i.e. rgba(18,58,46,.06)
   already composited over --sh-brand:
     default (240.78,243.18,242.46)   sage (229.50,234.72,227.42)
     olive   (232.32,233.78,219.90)
   --sh-nav-hover #EAF1EC against those is 1.03:1, 1.06:1 and 1.06:1 - weaker
   than the flat rgba(18,58,46,.10) hover of 0.27.0 that it replaced (1.08:1
   composited the same way), i.e. this release made the thing the owner asked
   to fix slightly worse while reporting a green suite.

   No fill can fix that. A light wash on a near-white track is bounded above by
   the track's own luminance: to reach 3:1 by fill alone the chip would have to
   go dark, which is the open state, or bright, which is the teal the owner
   already rejected. So the state is carried by an element with real contrast
   instead, and the wash stays as the soft, refined half of it.

   The indicator is a 2px inset bottom rule in --sh-nav-open - the lead card's
   own dark green, the same colour the trigger fills with when the panel
   actually opens. Measured against the composited track above: 11.29:1 on the
   default tone, 10.37:1 on sage, 10.31:1 on olive (and 10.95:1 against the
   chip's own wash, so it reads as an edge from both sides). Chosen over the
   full inset ring the sweep also offered because .rxsu-cap-nav__link[aria-current]
   just below already IS a ringed chip: two ring states on one rail would be
   two spellings of the same signal. An underline that is the open state's own
   colour also says what hovering a trigger is about to do - hover previews the
   colour, opening commits the whole chip to it.
   inset, not a border: a real border-bottom would add 2px to the control's
   height and shift every sibling in the rail on hover. box-shadow paints
   inside the existing box and is already in this rule pair's transition list. */
.rxsu-cap-nav__link:hover,.rxsu-cap-nav__trigger:hover{
  background:var(--sh-nav-hover);color:var(--sh-nav-hover-ink);
  box-shadow:inset 0 -2px 0 0 var(--sh-nav-open);
}
/* "you are here" — a quiet white chip with a hairline, not a filled accent: it
   has to coexist on the same rail as an open trigger without competing with it.
   Nothing in header.php sets aria-current on a nav link today (Theme_Menu_Walker's
   tree carries no "current page" flag yet), so this rule is not live on any real
   page yet - still worth being correct, since it is one attribute away from
   shipping. Fix round 1 (0.29.0 T1 review): the original .22 alpha hairline was
   not a boundary at all against the rail track it sits on top of - 1.51:1,
   under WCAG 1.4.11's 3:1 floor for a UI-component boundary. Strengthened to
   .65, which clears 3:1 against the composited rail track (--sh-rail over
   --sh-brand) on all three tones - default 3.89:1, sage 3.57:1, olive 3.55:1 -
   see tests/shell-capsule-contrast.php's check 8 for the measured boundary,
   not the chip's text. */
.rxsu-cap-nav__link[aria-current]{
  background:#FFFFFF;color:var(--sh-nav-open);
  box-shadow:inset 0 0 0 1.5px rgba(18,58,46,.65);
}
/* 0.29.0 fix round 2: the current-page chip and the hover state now both use
   box-shadow, and both selectors are (0,2,0) - an exact specificity tie the
   later rule (this one) wins outright, which would have left the current page's
   own link with no hover feedback at all. Restated so it keeps its ring AND
   gains the hover rule, stacked in one declaration because box-shadow does not
   accumulate across rules. */
.rxsu-cap-nav__link[aria-current]:hover{
  background:#FFFFFF;color:var(--sh-nav-open);
  box-shadow:inset 0 0 0 1.5px rgba(18,58,46,.65),inset 0 -2px 0 0 var(--sh-nav-open);
}
.rxsu-cap-nav__trigger[aria-expanded="true"]{
  background:var(--sh-nav-open);color:var(--sh-nav-open-ink);
}
/* Restated on purpose: without this, hovering an already-open trigger would
   flip it to the pale --sh-nav-hover wash mid-interaction - the "ugly hover"
   complaint reappearing in a different place.
   0.29.0 fix round 2: box-shadow:none joins it. This selector is (0,3,0) and
   beats the hover rule above, but only for the properties it actually names -
   without this line the hover's new inset bottom rule would still paint, a
   dark green rule on the dark green chip it is the same colour as. Invisible
   rather than wrong, but it would leave a phantom declaration nobody could see
   to check. */
.rxsu-cap-nav__trigger[aria-expanded="true"]:hover{
  background:var(--sh-nav-open);color:var(--sh-nav-open-ink);box-shadow:none;
}
.rxsu-cap-nav__trigger[aria-expanded="true"] .rxsu-cap-chev{
  transform:rotate(-135deg) translate(-2px,-2px);
}
.rxsu-cap-chev{
  width:9px;height:9px;border-right:2px solid currentColor;border-bottom:2px solid currentColor;
  transform:rotate(45deg) translate(-2px,-2px);transition:transform .16s ease;
}

/* One focus treatment for every control in the shell. The UA default ring on a
   filled dark-green pill is what the owner saw as a stray outline in the 0.28.0
   screenshots; :focus-visible keeps it off mouse clicks and on for keyboards.
   Fix round 1 (0.29.0 T1 review): border-radius:999px removed. Modern browsers
   already conform an outline to the element's own border-radius on their own,
   so the declaration bought nothing - and at this rule's (0,2,0) specificity it
   beat two other controls' own shapes: a focused .rxsu-cap-drawer__top (0,1,0)
   had its straight border-bottom separator forced to arc upward ~20px at both
   ends, and a focused .rxsu-cap-group a (0,1,1) mega-panel link - a rectangular
   block, not a pill - got a stadium ring instead of one following its own
   corners. */
.rxsu-cap-shell :focus-visible{outline:2px solid var(--sh-nav-open);outline-offset:2px}
/* The utility strip, the drawer, and the mega panel's lead card are all still
   dark surfaces (see the token block's own docblock on why --sh-strip/
   --sh-lead stay dark anchors) - --sh-nav-open, dark green, would not read
   against any of the three, so all three override to --sh-cream instead, the
   light accent already tuned for dark grounds elsewhere in this file.
   Fix round 1 (0.29.0 T1 review): .rxsu-cap-panel__lead was missing from this
   list. --sh-nav-open is literally --sh-lead's own value on the default tone -
   a ring in the surface's own colour is not a ring at all (1.00:1 default,
   1.04:1 sage, 1.03:1 olive - see tests/shell-capsule-contrast.php's check 8),
   so a keyboard user tabbing onto the panel's "Overview" CTA got no visible
   ring whatsoever: a regression against the UA default ring this rule exists
   to replace. */
.rxsu-cap-utility :focus-visible,.rxsu-cap-drawer :focus-visible,.rxsu-cap-panel__lead :focus-visible{outline-color:var(--sh-cream)}

/* ══════════════════════════════════════════════════════════════════════════
   THE THEME'S OWN SHELL, when the plugin is drawing one (0.29.0 T2)

   Theme_Runtime::suppress_elementor_header() returns template id 0 for the
   header and footer locations, which is how a page tells Elementor it has
   none. What that actually does is make elementor_theme_do_location() return
   false - and false is precisely the signal The7 waits for before rendering
   its *own* header and its own widget footer instead. Measured on production
   2026-08-02: on /main/news/ the reader got the capsule header, then The7's
   demo header under it, then the page, then The7's dark widget footer, then
   the capsule footer. Suppressing the Elementor location is still right (the
   faculty's real Elementor header would otherwise render as well); it is
   just not the whole job.

   Selectors come from the measured document, not from The7's class names,
   which are not public API: The7 puts <div id="page"> directly in <body> and
   renders both the header location and <footer id="footer"> as direct
   children of it, while print_header()/print_footer() run on wp_body_open/
   wp_footer and therefore land outside #page entirely - .rxsu-cap-shell
   before it, .rxsu-cap-foot after it. So "a header or footer inside #page" is
   never ours and always theirs. The :not() on the body-level footer rule is
   belt and braces for a theme that puts its footer directly in <body>.

   Scoped under .rxsu-shell-capsule, a body class Shell_Template::
   body_classes() only ever emits when the plugin owns the shell - so a site
   on the default Elementor template is untouched, which is the whole
   verification story for this release.
   0.32.0 T2 amends the sentence above rather than deleting it: that class is
   now ALSO emitted when only the footer switch is on (Shell_Schema::source()
   = plugin with the header still Elementor's), because every other thing this
   stylesheet does - the tokens, the resets, the state rules that outrank The7
   - is exactly as necessary for a capsule footer on an Elementor page as for
   one under a capsule header. A site on both defaults is still untouched:
   neither class is emitted there and this file is not even enqueued.
   What that broadening would have broken, if this block had been left as one
   rule, is the header. On the footer-only path The7's header is the page's
   real header - the operator kept it deliberately - and every selector below
   except the two footer ones exists to hide a header. So the block is split
   in two by the second class Shell_Template::body_classes() emits on that
   path, .rxsu-shell-capsule-footer:
     · the HEADER half (The7's fallback header, its top-bar, its mobile
       header, its sticky clone, its side-header layouts and its page-title/
       breadcrumb band) is exempted with :not(.rxsu-shell-capsule-footer) -
       none of it materialises anyway, since it is the suppressed Elementor
       HEADER location that summons it, and that location is untouched on this
       path;
     · the FOOTER half stays live on both paths, and has to: suppressing the
       Elementor FOOTER location is what the footer switch does, and the
       paragraph at the top of this block is the measured consequence - The7
       answers that by rendering its own widget footer. Hiding it is what
       keeps exactly one footer on the page in all four switch combinations.
   :not() rather than a positive "the plugin drew the header" class so that a
   live capsule site's body classes do not move at all in this release; the
   header rules go from (0,1,x) to (0,2,x), which changes nothing that
   `display:none!important` was not already winning.

   This hides rather than unhooks, and 0.29.0 fix round 3 corrects what that
   was previously said to cost. Two releases carried a sentence taken from the
   plan brief - that the hidden markup "leaves a second (hidden)
   banner/contentinfo landmark in the accessibility tree". That is false, and
   worth correcting rather than deleting, because the false version overstates
   the harm and would justify a riskier fix than this problem deserves.
   display:none generates no box, and an element that generates no box (with
   its whole subtree) is not exposed to assistive technology in any engine -
   this is exactly the mechanism the shell relies on elsewhere in the plugin
   (see the note in tests/shell-manager-render.php). A screen reader on a
   capsule page therefore finds one banner and one contentinfo, and both are
   ours.
   What the hide DOES still cost is real but smaller: The7 has already built
   that header and footer server-side - menu queries, widgets, the lot - and
   the markup is serialised into every response before the browser throws the
   boxes away. Bytes and server work, not a duplicated landmark. Removing it
   properly needs a theme-level hook that The7 does not document; that is a
   separate release, not a reason to leave two visible headers on the page.

   0.31.0 T4: position-based selection was not the whole job either. Owner's
   screenshots of the live preview: the news archive and the "การประชุม/อบรม/
   สัมนา" page still showed The7's top-bar (demo phone "011 322 44 56",
   "Monday - Friday") plus The7's own menu row with the "7" logo, UNDER the
   capsule header. On those templates The7's fallback header nests deeper than
   a direct child of #page, so `#page > header` never reached it. It cannot be
   scraped and fixed by position: measured 2026-08-02, the markers
   'masthead'/'top-bar'/'dt-mobile-header'/'phantom' appear in ZERO anonymous
   /main/news/ responses - the fallback only materialises when the capsule
   suppresses the Elementor header location, i.e. exactly and only on the
   pages this stylesheet loads on. So the second half of the rule selects by
   The7's own namespace instead, at ANY depth: #masthead/.masthead (the
   fallback header, whichever form a given The7 template gives it - id and
   class asserted separately in tests/shell-capsule-contrast.php check 15),
   #top-bar (the demo phone strip; the ID only - PhotoSwipe's .pswp__top-bar
   CLASS is a different element inside the lightbox and must keep rendering,
   which check 15 pins as a negative), #phantom (The7's sticky clone of the
   header), .dt-mobile-header/.mobile-header-bar (the mobile header and its
   bar), .mobile-sticky-header-overlay (the scrim behind it), and .side-header
   (The7's left/right header layouts). None of these can ever match capsule
   markup: every class the capsule parts emit is rxsu-cap-* (grepped
   templates/shell/capsule/parts/*.php for all eight markers on 2026-08-03 -
   zero hits), so descendant scope under the body class is safe at any depth.
   Not a get_header()/by-id template leak: all four managed templates already
   gate their Elementor header/footer fetches on Theme_Runtime::owns_shell()
   (the managed-homepage.php Critical-1 fix, 0.26.0), and templates/ contains
   no get_header() call at all - this is The7's own fallback, reachable only
   by CSS.

   0.32.0 T1: the same fallback brings a second band with it. Owner's
   screenshots of ข่าวสารและกิจกรรม, การประชุม/อบรม/สัมนา and a research-centre
   page: under the capsule header, a grey strip repeating the page title and a
   "Home / <page>" breadcrumb, with the plugin's or Elementor's own heading
   directly below it. That is The7's page-title band (presscore's title area),
   and it materialises under exactly the same condition as the header
   fallback: on the ANONYMOUS /main/news/ HTML the band does not exist at all
   - the only renderings of the page title are the header nav item and an
   Elementor h6 inside the listing, and there is no breadcrumb markup on the
   page. Re-measured 2026-08-03 over the 21 captured anonymous production
   responses (the personnel-baseline-2026-07-21 capture set, raw public HTML):
   parsing every class= and id= attribute for exact tokens, ZERO of the ten
   markers this rule names appear in any of them. So it cannot be scraped and
   suppressed by position or by content either; like the header it is
   suppressed by The7's own namespace, at any depth, in this same rule:
   #page-title/.page-title (the band itself, id and class form), .page-title-bg
   (its background layer), #fancy-header/.fancy-header (The7's alternate,
   image-backed form of the same band), .title-bar (the compact variant), and
   .breadcrumbs/.dt-breadcrumbs (the "Home / <page>" trail, which The7 emits
   both bare and under its dt- prefix - the prefix itself is the one part of
   this family the repo can confirm, from tests/fixtures/*.html's
   dt-nav-menu-horizontal and the site's own Customizer CSS .side-header rule;
   the rest are defensive, which is safe precisely because the grep recorded
   below says they can reach nothing else).
   Nothing but The7 can be reached by these. Every class and id the plugin
   emits was grepped on 2026-08-03 across templates/ and assets/ AND across
   includes/ (which is where the plugin's own archive/degree/personnel
   renderers live, not templates/): the strings page-title, breadcrumbs,
   dt-breadcrumbs, fancy-header, page-title-bg and title-bar appear on exactly
   six lines in the whole plugin, all six in assets/personnel-public.css and
   assets/executive-public.css, and all six as the CUSTOM PROPERTY name
   --rxsu-type-page-title / --rxsu-personnel-type-page-title, which is not a
   class or an id and cannot be selected by any of these rules. The plugin's
   archive renderer draws its heading as
   `<div class="rxsu-archive__head"><h2>…</h2><hr></div>` (includes/class-rxsu-
   archive-renderer.php) and ships NO breadcrumb of its own; personnel uses
   .rxsu-personnel-public__header h1; so the heading the owner sees below the
   band survives, and it is the only one left. Two substring traps are pinned
   as negatives in check 15 rather than left to care: Elementor's live page
   heading on these very pages carries .elementor-page-title (measured in the
   captures, on a theme-post-title widget) and its breadcrumb widget carries
   .elementor-widget-breadcrumbs - both contain a marker as a SUBSTRING, and a
   `[class*="page-title"]` or `[class*="breadcrumb"]` would blank the real page
   heading. Class TOKENS only, exactly as #top-bar was kept off
   PhotoSwipe's .pswp__top-bar.
   ══════════════════════════════════════════════════════════════════════════ */
.rxsu-shell-capsule:not(.rxsu-shell-capsule-footer) #page > header,
body.rxsu-shell-capsule:not(.rxsu-shell-capsule-footer) > header,
.rxsu-shell-capsule:not(.rxsu-shell-capsule-footer) #masthead,
.rxsu-shell-capsule:not(.rxsu-shell-capsule-footer) .masthead,
.rxsu-shell-capsule:not(.rxsu-shell-capsule-footer) #top-bar,
.rxsu-shell-capsule:not(.rxsu-shell-capsule-footer) #phantom,
.rxsu-shell-capsule:not(.rxsu-shell-capsule-footer) .dt-mobile-header,
.rxsu-shell-capsule:not(.rxsu-shell-capsule-footer) .mobile-header-bar,
.rxsu-shell-capsule:not(.rxsu-shell-capsule-footer) .mobile-sticky-header-overlay,
.rxsu-shell-capsule:not(.rxsu-shell-capsule-footer) .side-header,
.rxsu-shell-capsule:not(.rxsu-shell-capsule-footer) #page-title,
.rxsu-shell-capsule:not(.rxsu-shell-capsule-footer) .page-title,
.rxsu-shell-capsule:not(.rxsu-shell-capsule-footer) .page-title-bg,
.rxsu-shell-capsule:not(.rxsu-shell-capsule-footer) #fancy-header,
.rxsu-shell-capsule:not(.rxsu-shell-capsule-footer) .fancy-header,
.rxsu-shell-capsule:not(.rxsu-shell-capsule-footer) .title-bar,
.rxsu-shell-capsule:not(.rxsu-shell-capsule-footer) .breadcrumbs,
.rxsu-shell-capsule:not(.rxsu-shell-capsule-footer) .dt-breadcrumbs{display:none!important}
/* The footer half of the same block - live on both paths, see the split
   explained at the end of the docblock above. */
.rxsu-shell-capsule #page > footer,
body.rxsu-shell-capsule > footer:not(.rxsu-cap-foot){display:none!important}

/* ── mega panel ────────────────────────────────────────────────────────── */
/* 0.29.0 (owner: "เมนูแสดงควร overlay วางเหนือไม่ใช่ ดัน content"): position
   changed from normal flow (margin-top:10px, which pushed .rxsu-cap-shell's
   own height - and by extension every page element printed after the shell -
   down by the panel's full height for as long as it was open) to
   position:absolute. .rxsu-cap-shell is already position:relative (see its
   own rule near the top of this file) and the panel is already its sibling,
   not header's descendant (structural reason unrelated to this change and
   untouched - see templates/shell/capsule/parts/header.php's own docblock),
   so top:100%;left:var(--sh-inset);right:var(--sh-inset) lands the panel
   exactly under the header without needing to know the header's height as a
   literal number. An absolutely positioned element contributes nothing to its
   containing block's own height, so opening the panel can no longer push
   .rxsu-cap-shell taller or move anything below it - the drawer, the shell's
   only other child, is already display:none at every width once
   .rxsu-cap-js lands (see that rule further down), so it never competes for
   this same space either.
   margin-top:10px is gone - the panel now hangs directly off the header's
   bottom edge, so only the bottom corners are rounded (the top edge is no
   longer a free edge to round). z-index:1 here, combined with
   .rxsu-cap-shell's own z-index:900, is what lifts the panel above ordinary
   Elementor page content instead of sliding behind it, since that content
   sits in normal document flow below the shell with no z-index of its own to
   contest it. No JavaScript change was needed for this: capsule.js already
   closes an open panel on outside-click and on Escape regardless of how it is
   positioned.
   Fix round 1 (0.29.0 T1 review): left/right corrected from the literal 0 the
   original brief gave to var(--sh-inset). position:absolute resolves left/
   right against .rxsu-cap-shell's padding box, not its border box, so a
   literal 0 quietly assumed --sh-inset stays 0 forever - the day a future
   release gives --sh-inset a real value again (it is a live token for exactly
   that reason, see the token block's own notes), the panel would silently
   become 2×inset wider than the header it hangs from with nothing here to
   catch it. var(--sh-inset) tracks whatever the header itself is inset by,
   today and later, with no visual change at --sh-inset:0. */
.rxsu-cap-panel{
  position:absolute;top:100%;left:var(--sh-inset);right:var(--sh-inset);z-index:1;
  background:var(--sh-panel);color:var(--sh-panel-ink);
  border-radius:0 0 var(--sh-radius-panel) var(--sh-radius-panel);
  overflow:hidden;
  box-shadow:0 26px 60px -34px rgba(6,32,24,.5);
  border-top:3px solid var(--sh-cream);
}
/* Fix round 1 (0.29.0 T1 review): Step 1 rounded the brand row's bottom
   corners (--sh-radius-card); Step 3's square top corners on the panel above
   were reasoned for a straight edge to meet, which stopped being true the
   moment the brand row grew a rounded one. With a panel open, the mismatch
   left a ~22px wedge of page background showing at each end - masked on the
   default tone only because --sh-panel and --sh-brand are both near-white
   there, a real visible notch on sage/olive. Squares the brand row's own
   bottom corners for as long as a panel is open (paired with the transition
   declared on .rxsu-cap-brand-row itself, so it eases rather than snaps),
   so its edge is straight exactly when the panel's edge is straight.
   :has() is what lets a selector reach "some .rxsu-cap-panel inside this
   shell is currently not hidden" at all - on a browser without :has() support
   this rule simply never matches, so the fallback is today's already-shipped
   notch, not anything broken. */
.rxsu-cap-shell:has(.rxsu-cap-panel:not([hidden])) .rxsu-cap-brand-row{
  border-bottom-left-radius:0;border-bottom-right-radius:0;
}
.rxsu-cap-panel__inner{display:grid;grid-template-columns:minmax(240px,300px) 1fr;gap:34px;padding:28px}
.rxsu-cap-panel__lead{
  background:var(--sh-lead);color:#fff;border-radius:var(--sh-radius-lead);
  padding:26px;display:flex;flex-direction:column;gap:14px;align-self:start;
}
.rxsu-cap-panel__lead .rxsu-cap-dash{margin-bottom:2px}
.rxsu-cap-panel__title{margin:0;font-size:26px;font-weight:800}
.rxsu-cap-panel__desc{margin:0;font-size:14.5px;line-height:1.75;opacity:.86}
/* Two classes, same reason as .rxsu-cap-utility__actions .rxsu-cap-pill above:
   the mint CTA needs --sh-mint-ink, not the lead card's own inherited white -
   0.26.1 fix. */
.rxsu-cap-panel__lead .rxsu-cap-cta{
  margin-top:6px;align-self:start;display:inline-flex;align-items:center;gap:9px;
  background:var(--sh-mint);color:var(--sh-mint-ink);
  padding:11px 20px;border-radius:999px;font-weight:700;font-size:14.5px;
}
.rxsu-cap-panel__cols{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:26px}
/* 0.31.0 T2 — a leaf-only panel spreads its links across the whole panel.
   A node whose children are all leaves (no groups - ศูนย์วิจัย on production:
   four research centres) used to render as ONE "ลิงก์ด่วน" section occupying
   one of __cols' four tracks, leaving three quarters of the panel empty - the
   owner's screenshot. header.php drops the section wrapper in that case and
   puts .rxsu-cap-group on the <ul> itself (so every row keeps the
   .rxsu-cap-group a padding/hover/current treatment above with no new rules),
   plus this modifier, which spans it across __cols and lays the rows out in a
   grid of its own.
   Grid, NOT column-count, three reasons recorded:
     · geometry: repeat(4)/26px here and repeat(2) in the 1180px media block
       below are the exact tracks __cols draws for grouped panels, so both
       panel shapes keep one visual rhythm instead of column-count's own
       balancing arithmetic inventing a second one;
     · fragmentation: column-count fragments boxes, so a row's hover chip
       (padding + radius + the inset bottom-rule indicator, see
       .rxsu-cap-group a:hover below) can be sliced across two columns at a
       fragment boundary unless every <li> also carries break-inside:avoid -
       a second rule that can silently go missing later;
     · keyboard order: the <li>s stay in document order (header.php emits
       them in menu order, asserted by tests/shell-capsule-render.php), and
       grid-auto-flow's default `row` paints that order left-to-right,
       top-to-bottom - Tab moves the way the eye reads, with no `order` or
       dense packing anywhere to split the two apart.
   (0,2,0) with the ancestor class, same discipline as every bumped rule in
   this file - and the ancestor also keeps the modifier meaningless anywhere
   outside a panel's column area. */
.rxsu-cap-panel__cols .rxsu-cap-group--spread{
  grid-column:1/-1;
  list-style:none;margin:0;padding:0;
  display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:3px 26px;
}
/* Two classes (ancestor + own), same discipline as the pill/CTA/more-link in
   this file: this is an <h3> and the 0.29.0 TEXT COLOUR reset near the top of
   the file names `.rxsu-cap-shell h3` - one class plus a type, (0,1,1), which
   outranks this rule's own (0,1,0) and would have flattened the olive heading
   to the panel's body ink. Declarations unchanged; only the selector moved. */
.rxsu-cap-panel .rxsu-cap-group__title{
  margin:0 0 10px;padding-bottom:9px;border-bottom:1.5px solid var(--sh-olive);
  font-size:13.5px;font-weight:700;color:#4E5A22;display:flex;gap:7px;align-items:baseline;
}
/* Same bump, same reason - this one is a <span>, which the reset also names. */
.rxsu-cap-panel .rxsu-cap-group__count{color:var(--sh-panel-muted);font-weight:600}
.rxsu-cap-group ul{list-style:none;margin:0;padding:0;display:grid;gap:3px}
.rxsu-cap-group a{display:block;padding:9px 11px;border-radius:10px;font-size:14.5px;line-height:1.55}
/* 0.29.0 fix round 4 (defect 2) - the owner's original complaint ("สี hover
   ไม่สวยเลย ออกแบบให้สวยกว่านี้"), in the place that holds most of the menu's
   links. Fix round 2 hardened the nav rail and stopped there; this panel is
   where the rail's six triggers actually lead, and it carries every leaf link
   in the tree.
   #F2F6F3 on the panel's own --sh-panel #FFFFFF measures 1.09:1 - the same
   shape of defect the rail had (a light wash on a near-white ground, bounded
   above by that ground's luminance) and the same answer, deliberately in the
   same visual language: the wash stays as the soft half, and the state is
   carried by a 2px inset bottom rule in --sh-nav-open, the mega panel's own
   lead-card green. Measured against the panel ground it appears on: 12.57:1
   (and 11.53:1 against the hover wash itself, so it reads as an edge from
   both sides), against a 3:1 UI-state floor.
   No [aria-current] counterpart is needed here, unlike the rail's, and the
   difference is worth recording: .rxsu-cap-nav__link[aria-current] declares
   its own box-shadow (a ring), so the rail's hover box-shadow and its
   current-chip box-shadow were one property two rules were fighting over at
   an exact (0,2,0) tie. The panel's current link
   (.rxsu-cap-group a[aria-current], (0,2,1)) declares background, color and
   font-weight and NO box-shadow, and the cascade runs per property: on the
   current link, its background wins over this rule's wash and this rule's
   indicator is uncontested. Hovering "you are here" therefore keeps the
   --sh-panel-active chip and gains the same bottom rule (11.27:1 against that
   chip). Adding a box-shadow to the current link later means adding the
   restating rule the rail needed - check 14 measures both grounds so the
   number is there either way.
   inset, not a border, for the reason the rail's own note gives: a real
   border-bottom would add 2px to every hovered row and shift the column. */
.rxsu-cap-group a:hover{background:#F2F6F3;box-shadow:inset 0 -2px 0 0 var(--sh-nav-open)}
.rxsu-cap-group a[aria-current]{background:var(--sh-panel-active);color:#0B5E44;font-weight:600}
/* 0.31.0 T2 — the one-line description under a panel link, the hint The7's
   original menu draws under each research centre and this panel used to drop
   (production fills the menu-item description field; the walker has always
   carried it for children too - see Theme_Menu_Walker::items()). A <span>
   INSIDE the <a>, so the whole two-line row is one link target like The7's,
   and the hover/current chip above covers both lines with no extra rule;
   display:block is what makes it the second line.
   Two classes, (0,2,0), the same discipline as __count above: this is a
   <span> and the 0.29.0 TEXT COLOUR reset near the top of this file names
   `.rxsu-cap-shell span` at (0,1,1). Declared on the span itself, it is also
   untouched by every anchor-level state rule - The7's a:hover (0,1,1) and
   this file's own (0,1,2) state layer both set colour on the <a>, and a
   declaration on the element always beats a colour merely inherited from it.
   --sh-panel-muted on --sh-panel measures 4.54:1 - AA for the 12.5px/400
   this line is set in (tests/shell-capsule-contrast.php check 17 computes
   the pair rather than trusting this comment). */
.rxsu-cap-panel .rxsu-cap-group__desc{
  display:block;margin-top:2px;
  font-size:12.5px;font-weight:400;line-height:1.6;
  color:var(--sh-panel-muted);
}
/* The two grounds a hovered or current row actually paints under that line
   are NOT --sh-panel: the hover wash #F2F6F3 sinks --sh-panel-muted to
   4.16:1 and the current chip --sh-panel-active to 4.07:1 - both under the
   4.5:1 this size of text needs, the exact "background moved, foreground
   didn't" defect this test file's own docblock opens with. Same ink family
   one step darker: #57665D measures 5.56:1 on the wash, 5.44:1 on the chip
   (and 6.06:1 on the plain panel, so a row that is current-but-not-hovered
   is covered by the same declaration). (0,3,1) on both selectors, so the
   state strictly outranks the (0,2,0) resting rule above rather than tying
   with it - check 17 measures both grounds and pins the canary that the
   resting muted ink still fails on them, so nobody folds this back into one
   rule believing the panel pair covered it. */
.rxsu-cap-group a:hover .rxsu-cap-group__desc,
.rxsu-cap-group a[aria-current] .rxsu-cap-group__desc{color:#57665D}
/* Two classes for the same reason as the pill and CTA above, even though this
   class is not emitted by header.php today (task-5-report.md: no field in
   Theme_Menu_Walker's group shape holds a "more" URL yet) - the colour stays
   correct for the day a data source exists, instead of silently depending on
   source order against the base link reset. */
.rxsu-cap-panel .rxsu-cap-group__more{margin-top:12px;display:inline-flex;gap:7px;font-weight:700;font-size:14px;color:#00795D}

/* ══════════════════════════════════════════════════════════════════════════
   SHELL — footer (Task 6: templates/shell/capsule/parts/footer.php)

   0.27.0: full width, not a floating card. .rxsu-cap-foot never carried its
   own side padding or max-width - the 40px inset the owner's screenshot
   showed came from the page's own container (the same one .rxsu-cap-shell
   deliberately opts out of with its own padding:0 var(--sh-inset)), which
   print_footer() has no reason to also sit inside once it is printed on
   wp_footer, past the point any such container closes on a real page. The
   card look was entirely border-radius + box-shadow (both removed below) on
   top of that inherited inset - removing only those two would still have
   left the footer sitting inside whatever inset its container supplies.
   margin-left/right: calc(50% - 50vw) is the standard breakout: it reaches
   past any ancestor's padding or centred max-width without depending on
   knowing that ancestor's exact width. width stays auto (the default for a
   block element) rather than being set explicitly, so the margins alone
   determine the span. Fix round 1 (0.29.0 T1 review) corrected an
   overstatement in this paragraph: each margin resolves to -S/2 (S = the
   scrollbar's own width), so the used width is exactly 100vw and the right
   edge lands at clientWidth + S/2 - half a scrollbar's width past the
   visible viewport, not zero as this comment previously claimed. That is
   still only half of what width:100vw alone would overshoot by with no
   compensating negative margin, and this exact technique on this exact
   property has shipped on this element since 0.27.0 with no reported
   scrollbar regression, so it is not a new risk - .rxsu-cap-shell's own
   breakout (added at 0.29.0, see its own comment near the top of this file)
   uses the identical calc() for the identical reason. The header keeps its
   current geometry in this pass - this touches only .rxsu-cap-foot, a
   separate root footer.php has always printed on its own hook, never nested
   inside .rxsu-cap-shell. */
.rxsu-cap-foot{
  position:relative;margin-top:14px;margin-left:calc(50% - 50vw);margin-right:calc(50% - 50vw);
  overflow:hidden;background:var(--sh-foot);color:var(--sh-foot-ink);
}
.rxsu-cap-foot__edge{height:4px;background:linear-gradient(90deg,var(--sh-mint),var(--sh-olive) 55%,transparent)}
/* 0.29.0 fix round 4 (defect 4): one deliberate focus treatment for the whole
   footer, the counterpart of `.rxsu-cap-shell :focus-visible` above.
   Until this release the footer had exactly one focus indicator - the social
   marks' box-shadow ring (see its own note further down) - and three anchors
   with none at all: .rxsu-cap-foot__phone, the <a> inside .rxsu-cap-foot__line
   (the email address), and .rxsu-cap-foot__cta. The note over the social ring
   said the UA default was what the other footer anchors got, and that premise
   was false with The7 on the page: main.min.css ships `a:focus{outline:0}` at
   (0,1,1), capsule.css declared no outline anywhere under .rxsu-cap-foot, and
   nothing else in either file restores one. A keyboard user tabbing through
   the footer got no indicator whatsoever - not a weak one, none. That note is
   corrected where it stands rather than deleted.
   (0,2,0) beats The7's (0,1,1) on specificity, so it does not depend on load
   order the way the LINKS reset was found to (capsule.css prints first at
   wp_enqueue_scripts priority 5). :focus-visible, not :focus, for the reason
   the shell's own ring gives: the owner saw the UA ring on mouse clicks in the
   0.28.0 screenshots.
   var(--sh-foot-phone) #00795D is reused rather than a new accent invented -
   the same token the social ring already measures, 4.94:1 against the light
   footer ground --sh-foot and 5.24:1 against the --sh-foot-circle the social
   marks sit on, both over the 3:1 floor SC 1.4.11 puts on a focus indicator.
   On the social anchors this outline and their existing 3px box-shadow ring
   are the same colour and adjacent bands (shadow 0-3px out, outline 2-4px out
   from the border box), so they read as one slightly thicker ring rather than
   two competing ones; the shadow is left in place because it is what still
   rings the mark when a page's own stylesheet suppresses outlines. */
.rxsu-cap-foot :focus-visible{outline:2px solid var(--sh-foot-phone);outline-offset:2px}
/* The copyright strip is the footer's one dark surface - it reads the
   operator's own bar tone (--sh-foot-bottom = var(--rxsu-bar-surface)), and
   all eight tones in Home_Page_Schema::bar_tones() are dark. #00795D on the
   fallback #0B2E26 measures 2.72:1, so the ring above would fail on exactly
   the strip carrying the privacy-policy link. --sh-cream is the light accent
   this file already keeps for dark grounds and the same override the utility
   strip and the drawer take on the header side; check 8 already measures it
   against all eight tones, which is the identical set this surface can be.
   Three classes, not the two the header's equivalent override uses: at (0,2,0)
   it would tie with the rule above and win on source order alone, and this
   file's own standing rule is that source order is not a specificity
   argument. */
.rxsu-cap-foot .rxsu-cap-foot__bottom :focus-visible{outline-color:var(--sh-cream)}
/* mockup's four columns were 1.5fr 1fr 1fr 1fr; the brand/contact split (see
 * footer.php's docblock) drops it to three, and the brand column keeps its
 * larger share because the neighbouring contact column now carries the full
 * postal address on its own. minmax(0, …) on every track, not a bare fr, so a
 * long unbroken string in that address cannot force its column past its share
 * and push the grid wider than the card - the same horizontal-overflow class
 * of bug the header's tests already guard against. */
.rxsu-cap-foot__inner{position:relative;display:grid;grid-template-columns:minmax(0,1.4fr) minmax(0,1fr) minmax(0,1fr);gap:34px;padding:40px 34px 30px}
/* 0.31.0 T1: the mockup's link columns ("การศึกษา" / "วิจัยและบริการ") are
 * operator content now, so the grid has to hold 4 and 5 columns as well as
 * the 3 above. footer.php appends the matching modifier ONLY when at least
 * one column has a renderable link - a footer with both columns empty emits
 * the bare class and the three-column rule above stays byte-for-byte in
 * charge, which is what keeps the default-template preview identical until
 * the operator fills something in.
 * The 4-column track list is the mockup's own line (1.5fr 1fr 1fr 1fr, its
 * .sh-foot__inner) with the minmax(0,…) wrap the 3-column rule already
 * explains; 5 columns has no mockup to copy, so the brand track keeps the
 * 1.4fr the 3-column rule chose and every link/contact/social track stays an
 * equal 1fr. Both modifiers sit at (0,1,0), the same specificity as the base
 * rule and as the two narrow-viewport overrides further down
 * (`.rxsu-cap-foot__inner{grid-template-columns:1fr 1fr}` at max-width:1180px
 * and `…:1fr` below the burger breakpoint) - and they are declared HERE,
 * before both, so those later rules keep winning inside their media windows
 * exactly the way they already win over the base rule: 4- and 5-column
 * footers wrap to two columns and then stack to one at the same widths the
 * 3-column footer always has. */
.rxsu-cap-foot__inner--cols-4{grid-template-columns:minmax(0,1.5fr) minmax(0,1fr) minmax(0,1fr) minmax(0,1fr)}
.rxsu-cap-foot__inner--cols-5{grid-template-columns:minmax(0,1.4fr) minmax(0,1fr) minmax(0,1fr) minmax(0,1fr) minmax(0,1fr)}
.rxsu-cap-foot img{display:block;height:48px;width:auto;margin-bottom:16px}
/* 0.28.0: was opacity:.9 dimming the inherited (then near-white) ink toward
   the dark ground - opacity dims toward whatever is behind an element, so on
   the new light ground the same rule would have dimmed dark ink toward white
   instead, which is not the same correction and was never measured for it.
   --sh-foot-tagline is shell-footer.css's own literal teal-and-bold
   treatment for this exact role, carried over rather than re-decided.
   0.29.0 T2: two classes now. This is a <p>, and the TEXT COLOUR reset near
   the top of this file names `.rxsu-cap-foot p` at (0,1,1) - one class plus a
   type - which outranks a bare .rxsu-cap-foot__tagline (0,1,0). Unbumped, the
   teal tagline would silently become the footer's own body ink. Declarations
   unchanged; only the selector moved. */
.rxsu-cap-foot .rxsu-cap-foot__tagline{margin:0;font-size:14px;font-weight:600;line-height:1.7;color:var(--sh-foot-tagline);max-width:32ch}
/* 0.28.0: opacity:.9 replaced with --sh-foot-muted, the same reasoning as the
   tagline just above - shell-footer.css's own concrete muted-paragraph
   colour for address/contact copy, not a translucent dim of the main ink.
   0.29.0 T2: two classes, same <p>-versus-reset arithmetic as the tagline
   just above. Declarations unchanged. */
.rxsu-cap-foot .rxsu-cap-foot__addr{margin:0 0 16px;font-size:14px;line-height:1.85;color:var(--sh-foot-muted);max-width:34ch}
/* Two classes, same reason as the pill/CTA/more-link above: .rxsu-cap-foot__phone
   on its own is one class, which the base link reset's `.rxsu-cap-foot a` (one
   class plus the `a` type) would outrank and flatten back to the footer's own
   ink - even though this element's colour already read correctly on the live
   0.26.0 page (it declares its own `color` directly on the anchor, which beat
   The7's rule there), it would have been a new regression introduced by this
   very fix without this bump.
   0.28.0: --sh-mint replaced with --sh-foot-phone. --sh-mint is a
   self-contained bg/ink accent pair everywhere else it is used in this file
   (a pill, a CTA button) - safe on any surface because both halves travel
   together. Here it was used as a bare text colour sitting directly on the
   footer's own background, which is a different thing: measured against the
   new light --sh-foot it is 1.96:1, far under AA, and was never a chip to
   begin with. --sh-foot-phone reuses the literal #00795D this file already
   trusts for an accent link directly on a light surface
   (.rxsu-cap-group__more, on the white mega-panel) rather than inventing a
   fourth accent green - see the token block's own note. */
.rxsu-cap-foot .rxsu-cap-foot__phone{font-size:19px;font-weight:800;color:var(--sh-foot-phone);letter-spacing:.01em}
/* Email (schema field the mockup never sampled - it only showed address and
   phone - so there is no .sh-foot__* precedent to carry over verbatim). Sized
   like the address/phone lines it sits beside rather than the bold phone
   treatment, since it is a second, quieter way to reach the same office.
   0.28.0: opacity:.9 replaced with --sh-foot-muted, same reasoning as the
   address paragraph above.
   0.29.0 T2: two classes, same <p>-versus-reset arithmetic as the two
   paragraphs above. The <a> inside it is a separate question already settled
   by the LINKS reset (color:inherit), which now inherits this rule's muted
   colour rather than the footer's body ink - unchanged behaviour, since that
   was already true before the reset existed. Declarations unchanged. */
.rxsu-cap-foot .rxsu-cap-foot__line{margin:10px 0 0;font-size:14px;line-height:1.6;color:var(--sh-foot-muted)}
/* 0.28.0: --sh-foot-head is now the same literal as --sh-foot-ink (see the
   token block) rather than a distinct accent hue - shell-footer.css does not
   give its headings a separate colour on the light ground either, so this
   follows that decision instead of inventing a new accent that was never
   part of the reference design. The token stays a separate declaration
   (not simply `color:inherit`) so a future release can still give headings
   their own colour again by editing one value.
   0.29.0 T2: bumped from `.rxsu-cap-foot h3` to a three-compound selector.
   The TEXT COLOUR reset near the top of this file names `.rxsu-cap-foot h3`
   itself, which is the identical (0,1,1) - a tie, decided by source order,
   and the reset is deliberately first. .rxsu-cap-foot__inner is the wrapper
   footer.php already renders around both column headings (the copyright strip
   below it, .rxsu-cap-foot__bottom, has no <h3> at all), so this reaches
   exactly the same two elements at (0,2,1) without adding a class to the
   markup. Declarations unchanged. */
.rxsu-cap-foot .rxsu-cap-foot__inner h3{margin:0 0 14px;font-size:13.5px;font-weight:700;color:var(--sh-foot-head);letter-spacing:.02em}
/* NOTHING RENDERS THESE THREE RULES TODAY. Checked in 0.29.0 fix round 3 at a
   reviewer's prompt: templates/shell/capsule/parts/footer.php emits no <ul>
   and no <li> anywhere - the contact column is <p>s, the social row is a
   <div>, and the bottom strip's links are bare <a>s inside a <nav>. So is the
   `li` in the TEXT COLOUR reset near the top of this file. They are kept
   rather than deleted for two reasons, but a reader should not mistake them
   for load-bearing:
     1. The mockup's footer link columns are the markup these were written
        for. The Footer tab holds one flat `links` list today and the template
        prints it as bare anchors in the bottom strip; the day those become
        columns, the styling should be waiting rather than rediscovered.
     2. `.rxsu-cap-foot li a{opacity:.92}` is one of the entries the opacity
        census in tests/shell-capsule-contrast.php measures composited against
        the footer ground. Deleting the rule deletes a measured 14.5px text
        contrast from the suite; the rule costs nothing and the measurement is
        the thing that would have to be re-earned.
   If a future release decides these columns are never coming, delete the
   rules AND the matching row in that census in the same commit.
   0.31.0 T1: the columns came. Reason 1 above is now the fact rather than
   the bet - footer.php renders each filled column as <section><h3><ul><li><a>
   (Shell_Schema's columns[], edited on the Footer tab), so these three rules
   and the `li` in the TEXT COLOUR reset are load-bearing today. The census
   row stays exactly as it was; the paragraph above is kept as the record of
   why the rules were already here waiting. */
.rxsu-cap-foot ul{list-style:none;margin:0;padding:0;display:grid;gap:11px;font-size:14.5px}
.rxsu-cap-foot li a{opacity:.92}
.rxsu-cap-foot li a:hover,.rxsu-cap-foot li a:focus-visible{text-decoration:underline;text-underline-offset:4px}
.rxsu-cap-social{display:flex;gap:9px;margin-bottom:18px;flex-wrap:wrap}
/* 0.28.0: padding:8px added (was none) so an <img> icon (see footer.php) does
   not sit edge-to-edge inside the 44px circle - the same proportions
   shell-footer.css's own .rxsu-shell-social uses. display swaps from
   grid/place-items to inline-flex/align-items/justify-content, which centres
   either an <img> or the .rxsu-cap-social__text fallback identically; the
   text's own font-size/weight/letter-spacing move onto that fallback class
   below, since they have no meaning on an <img>.

   0.29.0 T3 (owner: "footer icon ไม่อยู่อย่างสมดุลในวงกลม ตามรูปที่ 5"):
   padding tightened 8px -> 5px, and the mark itself clipped to a circle by the
   `a img` rule below. The five networks' source files are not the same shape -
   Facebook and YouTube are circular badges drawn on a square transparent
   canvas, LINE and Instagram are square badges, TikTok is a bare white glyph
   on transparency. object-fit:contain fits each one to the square content box,
   so a square badge reaches corner to corner (diagonal ~48px of visible ink
   across a 34px box) while a circular badge only reaches edge to edge (34px):
   the square ones read visibly larger and their corners crowd the ring. contain
   alone cannot fix that, because it is doing exactly what it is asked to -
   preserve each source's own aspect and fill the box. Clipping the <img> to
   50% makes every network render as a circle of one diameter (34px) concentric
   inside the 44px wrapper, whatever shape its source file happens to be: a
   square badge loses its corners, a circular badge is unchanged, and TikTok's
   glyph loses only transparent canvas. Known tradeoff this buys: a *non-square*
   source (a wordmark, say) would be letterboxed by contain and then have its
   ends clipped by the radius. Every network Social_Schema whitelists ships a
   square-canvas badge, and the 34px box is square, so nothing on production
   hits that - but an operator uploading a wide custom icon_url would, and this
   is the rule to revisit if that ever happens.

   The wrapper keeps padding, rather than dropping to 0 and letting the badge
   be the circle: the 5px ring of --sh-foot-circle is what still separates a
   dark badge (TikTok's #111, LINE's green) from the light footer ground, and
   it is what the :focus-visible ring below sits outside of. 5px was chosen
   over 8px because the mark, once clipped, no longer has corners that could
   collide with the wrapper's own edge - the geometry that made 8px necessary
   in 0.28.0 is the geometry this release removed. */
.rxsu-cap-social a{
  width:44px;height:44px;padding:5px;border-radius:50%;background:var(--sh-foot-circle);
  display:inline-flex;align-items:center;justify-content:center;
  transition:transform 180ms cubic-bezier(.16,1,.3,1),box-shadow 180ms ease;
}
/* margin:0 is the load-bearing declaration in this rule, not housekeeping.
   `.rxsu-cap-foot img` above - the BRAND LOGO's rule, (0,1,1) - reaches every
   <img> in the footer, this one included, and it carries margin-bottom:16px.
   This rule is (0,1,2), so it wins on display/width/height, but a property it
   never mentions is not contested at all: without margin:0 the mark keeps that
   16px. Inside align-items:center the item's MARGIN box is what gets centred,
   so a 34px mark with a 50px margin box in a 34px line box resolves to -8px -
   the mark rides 8px above the ring's centre and, since the ring only has 5px
   of padding, its top edge lands 3px OUTSIDE the 44px wrapper, leaving a thick
   crescent of --sh-foot-circle underneath (worst on TikTok's #111 disc).
   Measured in chrome-headless-shell against footer.php's real markup with
   The7's global `*{box-sizing:border-box}`: dy = -8.00px for all five networks
   without this declaration, dy = 0.00px with it. That is the owner's exact
   complaint - the mark is not off-centre because of its shape, it is
   off-centre because of an inherited-by-cascade margin - and it is why the
   reference design (assets/shell-footer.css, .rxsu-shell-social img) needs no
   such reset: that stylesheet has no footer-wide <img> rule to leak from.
   Removing margin:0 must fail check 10's resolved-margin loop in
   tests/shell-capsule-contrast.php, which walks every rule in this file that
   can match this <img> rather than reading this rule alone. */
.rxsu-cap-social a img{display:block;width:100%;height:100%;object-fit:contain;border-radius:50%;margin:0}
/* Untouched by the clip above: the two-letter fallback is a <span>, not an
   <img>, so it has no box to round and nothing to lose to the radius. It only
   gains room - the content box widened 28px -> 34px with the padding change,
   and two 12px/700 glyphs measure ~16px, so it centres with more clearance
   than before rather than less. Its colour is not declared here on purpose:
   the anchor already carries one (var(--sh-foot-ink) inherited from
   .rxsu-cap-foot for four networks, #fff from .is-tiktok for the fifth) and
   the TEXT COLOUR reset near the top of this file names `.rxsu-cap-foot span`
   at (0,1,1) with color:inherit, which is what stops The7 from declaring a
   colour on the <span> directly and beating the anchor's. Measured against
   the grounds those two land on: #2C3A2B on --sh-foot-circle #FBFCF9 is
   11.67:1, #fff on #111 is 18.88:1 - both clear AA for 12px bold, at rest and
   (since 0.29.0 T3) on hover too, because hover no longer repaints either. */
.rxsu-cap-social__text{font-size:12px;font-weight:700;letter-spacing:.02em}
/* Same correction as shell-footer.css carries, for the same measured reason:
   TikTok's mark is drawn white and disappears on the near-white
   --sh-foot-circle ground (and, before this release, on plain text).
   0.28.0 placed this before the :hover rule that used to follow it so that
   equal specificity resolved to source order - TikTok staying dark at rest
   (where the owner's screenshot was taken) and lifting to the shared mint
   hover like every other network. That :hover rule is gone in 0.29.0 T3 (see
   below), so this rule now has no same-specificity competitor at all and
   #111 is TikTok's ground in every state. The ordering note is kept rather
   than deleted because it records why this line sits where it does; source
   order here is no longer load-bearing, and a future release adding another
   (0,2,1) rule for these anchors has to re-read it. */
.rxsu-cap-social a.is-tiktok{background:#111;color:#fff}
/* 0.29.0 T3, second half of the same complaint: the hover used to be
   `background:var(--sh-mint);color:var(--sh-mint-ink)`, which only recoloured
   the 5px (then 8px) ring *behind* an opaque badge and read as a stray teal
   blob around the hovered YouTube mark in the owner's screenshot. It was also
   a measured contrast defect on one network: an <img> does not respond to
   `color`, so on TikTok the hover repainted the ground from #111 to --sh-mint
   #35C79B and left the white glyph on it at 2.14:1 - under the 3:1 a graphical
   object needs. Lifting instead is what the reference design this footer was
   aligned to in 0.28.0 already does (assets/shell-footer.css,
   .rxsu-shell-social:hover) and it touches no colour pair at all, so every
   ratio recorded above holds in every state.

   The focus ring is a new declaration, not a moved one: .rxsu-cap-foot is a
   separate shell root and is NOT a descendant of .rxsu-cap-shell (footer.php
   prints on wp_footer, the header on wp_body_open - see the LINKS docblock),
   so the shell's own `.rxsu-cap-shell :focus-visible` outline never reached
   these anchors.
   0.29.0 fix round 4 (defect 4) corrects what this note said next. It claimed
   the UA default outline was what a keyboard user got on the other footer
   anchors, so this ring was an upgrade on a working baseline. That premise is
   false with The7 loaded: main.min.css declares `a:focus{outline:0}` at
   (0,1,1), and capsule.css declared no outline anywhere under .rxsu-cap-foot -
   so .rxsu-cap-foot__phone, the email <a> inside .rxsu-cap-foot__line and
   .rxsu-cap-foot__cta had NO focus indicator at all, and this box-shadow was
   the footer's only one. The correction matters beyond bookkeeping: the false
   version made three missing indicators look like three weak ones, which is
   why two review rounds passed over them. The footer-wide :focus-visible ring
   added near the top of this section is the fix; everything measured below
   about this box-shadow is unchanged by it.
   var(--sh-foot-phone) rather than the rgba(0,121,93,.35) the task brief
   proposed: 0,121,93 IS #00795D, i.e. this token, but a spread shadow is
   painted outside the border box, so its backdrop is the footer ground and at
   35% alpha it composites to #9ECABC - 1.65:1 against that ground and 1.75:1
   against the --sh-foot-circle wrapper it hugs. Not a ring, and far under the
   3:1 SC 1.4.11 asks of a focus indicator. (It clears 3:1 on TikTok's #111
   alone, at 10.46:1, which is the trap: sampling one network would have
   passed it.) Solid, the same colour measures 4.94:1 against --sh-foot,
   5.24:1 against --sh-foot-circle and 3.50:1 against #111 - clear on all
   three adjacent surfaces. The token is reused rather than a fifth accent
   green invented, the same discipline its own note in the token block
   records. */
.rxsu-cap-social a:hover{transform:translateY(-2px)}
.rxsu-cap-social a:focus-visible{box-shadow:0 0 0 3px var(--sh-foot-phone)}
/* Two classes, same reason as the header's mint CTA and pill above. Not one of
   the five named in the 0.26.1 report (contact.url is still empty on
   production, per footer.php's docblock, so this button was not on the live
   page to measure) but the same mint-on-mint-ink pattern, so it gets the same
   fix now rather than shipping the identical regression the day an operator
   fills in the field.
   0.29.0 fix round 4 (defect 7): --sh-mint replaced with --sh-foot-phone, and
   the ink with white. This is the same correction 0.28.0 already made to the
   phone number eleven rules above, for the same measured reason and on the
   same surface - the token block's own note records --sh-mint as 1.96:1
   against the light --sh-foot, which is exactly what this button's FILL
   measured. A filled button whose fill is 1.96:1 against the ground has no
   boundary at all (it declares no border), under the 3:1 SC 1.4.11 asks of a
   UI component's boundary; the phone number failed the text floor with the
   same colour on the same ground, and one fix answers both because both are
   "--sh-mint used as a bare colour on a light surface" rather than as the
   self-contained bg/ink chip it is everywhere else in this file.
   Latent today - footer.php only prints this anchor when contact.url is set,
   and it is unset on production - so this is the one defect in this round
   that no reader has seen. It is fixed on the same round anyway: the day an
   operator fills that field is not the day to discover it.
   #00795D on --sh-foot #F3F6EF measures 4.94:1 for the boundary, and #FFFFFF
   on #00795D measures 5.39:1 for the label (14.5px/700 needs 4.5:1). The ink
   cannot stay --sh-mint-ink: #04281F on #00795D is 2.93:1, dark on dark. The
   literal #FFFFFF rather than a token because no white token in this file
   belongs to the footer - --sh-nav-open-ink is the nav rail's, and borrowing
   it would tie the footer's CTA to a header decision. */
.rxsu-cap-foot .rxsu-cap-foot__cta{
  display:inline-flex;align-items:center;background:var(--sh-foot-phone);color:#FFFFFF;
  padding:11px 24px;border-radius:999px;font-weight:700;font-size:14.5px;
}
/* 0.28.0: border-top reads --sh-foot-hairline, not --sh-hairline. The two
   used to be the same token because both surfaces on either side of it were
   dark (translucent white reads fine on a dark ground); the ground above
   this border is light now while the strip below it keeps reading the
   operator's own bar-tone choice (still var(--sh-foot-bottom), still
   untouched by this release) - all eight of those tones remain dark
   surfaces (Home_Page_Schema::bar_tones()), so the light footer body above
   this line and the dark strip below it already read as two distinct
   surfaces on their own; the hairline is reinforcement, not the only signal.
   A translucent white line would have gone invisible against the light
   ground above it either way, so it is replaced with shell-footer.css's own
   concrete #D3DDCF instead - --sh-hairline itself is untouched and still
   correctly serves .rxsu-cap-drawer__top below, which stays dark. */
.rxsu-cap-foot__bottom{
  position:relative;background:var(--sh-foot-bottom);color:var(--sh-foot-bottom-ink);
  display:flex;align-items:center;justify-content:space-between;gap:20px;flex-wrap:wrap;
  padding:16px 34px;font-size:13.5px;
  border-top:1px solid var(--sh-foot-hairline);
}
.rxsu-cap-foot__bottom nav{display:flex;gap:26px;flex-wrap:wrap}
/* 0.29.0 fix round 2 (defect 7): .88 -> .94. This strip's ink is
   --sh-foot-bottom-ink = var(--rxsu-bar-ink), on --sh-foot-bottom =
   var(--rxsu-bar-surface), so the pair moves with the operator's bar tone -
   Task 7 of 0.28.0 fixed exactly that one layer up, by wiring the ink to the
   tone at all. What it did not do is composite the alpha sitting on top of the
   result: at .88 the links measured 4.43:1 on 'logo-teal' and 4.28:1 on
   'olive-mid', under the 4.5:1 that 13.5px/400 needs, while the unlinked
   copyright <span> beside them - full opacity, same ink, same surface - passed
   at 5.24:1 and 4.98:1. Only the clickable half failed, and the number the
   admin screen prints beside each tone swatch is the span's.
   .94 is not a fresh guess: it is what .rxsu-cap-utility__link already carries
   on the identical tone set (the utility strip reads the same two variables),
   where it survives at 4.82:1 / 4.62:1. Same de-emphasis, same eight tones,
   one value. Worst case across all eight plus the var() fallback: 4.62:1.
   tests/shell-capsule-contrast.php check 11 now composites every opacity in
   either shell root, not only the utility strip's - the scoping that let this
   one through. */
.rxsu-cap-foot__bottom a{opacity:.94}
.rxsu-cap-foot__bottom a:hover,.rxsu-cap-foot__bottom a:focus-visible{text-decoration:underline;text-underline-offset:4px}

/* ── narrow screens: the rail wraps, the panel stacks ──────────────────── */
@media (max-width:1180px){
  .rxsu-cap-panel__inner{grid-template-columns:1fr}
  .rxsu-cap-panel__cols{grid-template-columns:repeat(2,minmax(0,1fr))}
  /* 0.31.0 T2: the spread list tracks __cols' own step-down - same width,
     same two columns, one rhythm (see the modifier's docblock above). */
  .rxsu-cap-panel__cols .rxsu-cap-group--spread{grid-template-columns:repeat(2,minmax(0,1fr))}
  .rxsu-cap-foot__inner{grid-template-columns:1fr 1fr}
}

/* ── the compact rail: 1088px to 1256px ───────────────────────────────────
   0.29.0 fix round 1 (five-lens sweep, defect 2 - CRITICAL). Everything in
   this block exists to buy the rail room, and 1256px is not a round number:
   it is one pixel under the measured width at which the row fits with the
   shipped (full-size) metrics.

   The arithmetic that forced this, measured in chrome-headless-shell against
   this stylesheet and the real six-item menu (เกี่ยวกับ 105.9, การศึกษา 117.4,
   ข่าวสารและกิจกรรม 187.4, ศูนย์วิจัย 109.6, บริการวิชาการ 150.3, หน่วยงาน 117.6
   - a 820.2px rail, reproduced to 0.1px on the harness): the rail is a nowrap
   flex row of white-space:nowrap links with min-width:auto, so it cannot
   shrink at all, and with the full-size logo the brand row needs
   26 + 284.7 + 28 + 820.2 + 28 + 48 + 22 = 1257px. The old 1023px breakpoint
   was therefore ~230px too low for the menu this site actually has: measured
   overflow was 211px at 1024, 135px at 1100, 35px at 1200, and the search
   button's right edge sat at a fixed 1234.9px, off-screen at every width from
   1024 to 1234.

   Two levers were available - shrink what the row needs, or move the
   breakpoint - and the answer is whichever the measurements support. Both, as
   it turns out: moving the breakpoint alone would have put it at
   1256px and handed the burger to every tablet in landscape (1024, 1112,
   1180, 1194) even though those widths have room for a slightly tighter rail.
   This block is that tighter rail. Nothing here changes a colour, a state or
   a shape - only size and spacing - so every contrast measurement in
   tests/shell-capsule-contrast.php still describes what renders here:
     · --sh-logo-h 52px -> 44px          (mark 284.7 -> 240.9, saves 43.8)
     · --sh-nav-size 1.0625rem -> 1rem   (17px -> 16px, the six labels
                                          488.2 -> 459.5, saves 28.7)
     · nav link/trigger padding 17 -> 12 (saves 6 x 10 = 60)
     · rail gap 4 -> 3, padding 6 -> 5   (saves 5 + 2)
     · brand-row gap 28 -> 20            (saves 2 x 8 = 16, two gaps: the
                                          burger is display:none up here)
     · brand-row padding 26/22 -> 18/16  (saves 14)
   Measured result: the row needs 1088px, so the rail is kept down to 1088 and
   the breakpoint below is 1087. It is deliberately capped at 1256px rather
   than applied everywhere: at 1257px and up the owner-approved full-size
   header fits on its own, and this release does not change how it looks
   there.

   0.29.0 fix round 5 (defect 1b): the ceiling is 78.5em, not 1256px, and the
   two queries below it moved the same way (67.9375em for 1087px, 30em for
   480px). Every number in the arithmetic above is unchanged - at the browser
   default of 16px, 78.5em IS 1256px, and the whole matrix at 16px root
   measures identically before and after.
   What the unit buys is the case that arithmetic never covered. --sh-nav-size
   is in rem, so the six labels grow with the reader's own default font size,
   while a px breakpoint does not: at Chrome's "Very large" (24px) the rail
   wants half as much width again, and the tier that is supposed to rescue it
   never arrives. Measured in chrome-headless-shell with Page.setFontSizes
   (which moves the default font size, the way the browser's own preference
   does - not html{font-size}, which would move rem and leave a media query's
   em exactly where it was): at 1100px and 24px root the mark rendered
   24.3x4.4px. A four-pixel-tall logo, and a four-pixel-tall hit target for the
   only link that reaches the home page.
   A media query's em resolves against the INITIAL font size - the browser's
   default, not any font-size a theme sets on <html> - which is precisely the
   quantity that moves the rail here, so the tiers now arrive in proportion:
   at 24px root the compact rail starts at 1884px and the rail is dropped for
   the burger below 1631px. The one case em does not cover is a theme that
   sets an absolute html{font-size}, which would move rem without moving the
   media query; that is what the mark's floor above is for, and why both
   mechanisms ship rather than either alone. */
@media (max-width:78.5em){
  .rxsu-shell-capsule{--sh-logo-h:44px;--sh-nav-size:1rem}
  .rxsu-cap-brand-row{gap:20px;padding:14px 16px 14px 18px}
  .rxsu-cap-nav__rail{gap:3px;padding:5px}
  .rxsu-cap-nav__link,.rxsu-cap-nav__trigger{padding:9px 12px}
}

@media (max-width:67.9375em){
  /* 0.28.0: this used to narrow --sh-inset to 14px and --sh-radius-card to
   * 20px here, on the theory that a floating card wants a smaller margin and
   * a tighter corner on a phone. Both tokens are 0 at every width now (see
   * the token block above), so a narrow-viewport override of either would
   * quietly reintroduce the inset/corner this release removes, exactly on
   * the 390px width the owner's report asks to be checked. */
  /* Fix round 1: moved from 760px to 1023px. Measured in a real browser across
   * nine widths (see task-8-report.md): the header card's brand row (brand +
   * nav rail + search) does not shrink, and the card clips it with
   * overflow:hidden - the rail fits from 1024px, and below that the search
   * button is clipped out of existence entirely, sometimes along with part of
   * the rail. Below 1024px the rail has to go, or a reader gets a header with
   * a sliced-off nav and no search - so this rule and #rxsu-cap-drawer's
   * threshold below (and capsule.js's matchMedia string) all read the exact
   * same 1023px, and a test asserts they cannot drift apart.
   *
   * 0.29.0 fix round 1 (five-lens sweep, defect 2): 1023 -> 1087, and the
   * "rail fits from 1024px" sentence above was never true of this site's real
   * menu. That measurement was taken against a placeholder rail; with the six
   * top-level items production actually has, the rail is 820.2px and the row
   * needs 1257px at full size, so the rail was overflowing by 211px at the
   * very width the old number declared a clean fit - and the search button,
   * pinned at 1234.9px, was off-screen from 1024 all the way to 1234. The new
   * number is measured the same way but against the real menu and against the
   * compact metrics in the @media (max-width:1256px) block above: the row
   * needs exactly 1088px there, so the rail survives down to 1088 and this
   * block starts at 1087. The three places that must agree (this query,
   * #rxsu-cap-drawer's query below, capsule.js's matchMedia string) all read
   * 1087, and tests/shell-capsule-js.js still asserts they cannot drift.
   *
   * 0.29.0 fix round 5 (defect 1b): 1087px -> 67.9375em, which is the same
   * 1087px at the 16px browser default and a proportionally larger number for
   * a reader who has set a larger one - the full reasoning is in the
   * compact-rail block above, on the query that moved with it. All three
   * places still have to agree and still do; they agree on 67.9375em now, and
   * the drift test in tests/shell-capsule-js.js reads the unit as well as the
   * number so that a px/em mismatch between the CSS and the matchMedia string
   * fails there rather than on a reader's screen.
   *
   * The narrow brand-row metrics below are defect 1's other half. With the
   * rail gone the row is brand + search + burger, which needs
   * 14 + 240.9 + 14 + 48 + 14 + 46 + 14 = 391px at --sh-logo-h:44px; under
   * that width the mark's own max-width:100% keeps shrinking it (ratio
   * preserved - see .rxsu-cap-brand img) instead of pushing the burger out of
   * the viewport, which is what used to happen.
   *
   * .rxsu-cap-panel is defect 3's CSS belt. The mega panel is a sibling of
   * <header>, not a descendant of .rxsu-cap-nav, so hiding the nav does
   * nothing to a panel that is already open when the viewport crosses this
   * breakpoint: measured against the pre-fix files at 1440 -> 900, the panel
   * stayed hidden=false, display:block, the full 900px wide (465.8px tall on
   * the harness, 548px on the real menu per the sweep), absolutely positioned
   * over the page - while the rail holding its trigger had gone display:none,
   * so the trigger was neither visible nor clickable and still reported
   * aria-expanded="true". capsule.js closes it on the crossing now (see its
   * own note), and this rule is the belt: no trigger is reachable below this
   * width, so no panel has any business painting here whatever its `hidden`
   * attribute happens to say. */
  .rxsu-cap-nav,.rxsu-cap-utility__actions .rxsu-cap-utility__link{display:none}
  .rxsu-cap-panel{display:none}
  .rxsu-cap-panel__cols,.rxsu-cap-foot__inner{grid-template-columns:1fr}
  .rxsu-cap-brand-row{gap:14px;padding:12px 14px}
  /* 0.29.0 final verification (responsive lens): .rxsu-cap-nav's
     margin-inline-start:auto is the brand row's ONLY right-alignment
     mechanism, and the display:none above removes it together with the rail -
     leaving the search button and burger packed against the logo on the left
     and a void on the right that grows with the viewport (measured 56.9px at
     390px up to 698.1px at 1087px, and further still on larger root font
     sizes, while the utility strip above stays flush to both edges). Every
     overflow- and containment-based check stayed green through two
     adversarial rounds because nothing overflows - the row is just
     misaligned. Hand the auto margin to the first control after the rail, so
     the trailing controls keep hugging the right edge exactly as they do
     when the rail is present. */
  .rxsu-cap-search{margin-inline-start:auto}
}

/* ── phones: 30em and under (480px at the 16px browser default) ───────────
   0.29.0 fix round 1 (five-lens sweep, defect 1). The second step of the same
   staircase. Fix round 5 changed the unit, not the number, for the reason the
   compact-rail block above gives in full. At 480px the narrow tier above still fits with room to spare, but
   at 320-414 it does not: the fixed chrome (search 48 + burger 46 = 94px plus
   padding and gaps) leaves the mark less and less room, and every pixel handed
   back to it is a pixel the mark keeps. 12/10 padding and a 10px gap free 20px
   against the tier above, and --sh-logo-h:36px stops the mark from being the
   tallest thing in a row whose other two controls are 46px.
   Measured at 320px: the row needs 12 + logo + 10 + 48 + 10 + 46 + 12, so the
   mark's own max-width:100% settles it at 182x33.3 (ratio 5.475, unchanged)
   and nothing overflows. */
@media (max-width:30em){
  .rxsu-shell-capsule{--sh-logo-h:36px}
  .rxsu-cap-brand-row{gap:10px;padding:10px 12px}
  /* Alignment, not overflow control, and measured before being claimed as
     either: with flex-wrap in place the strip is 81px tall at 320px whether
     this padding is 26px or 14px, because the wrapped motto (216.7px) fits
     inside 320-52 either way. What 26px does do is leave the strip inset
     twice as far from the screen edge as the brand row directly beneath it,
     which after that row dropped to 12px reads as a misalignment rather than
     a rhythm. 14px lines the two up. */
  .rxsu-cap-utility{padding:11px 14px}
  /* The last 2px of defect 1, found by measuring the row in its *focused*
     state rather than at rest: .rxsu-cap-search is flex:none and the expanded
     field is a flat 190px, so at 320px the row needs 322px even after the mark
     has been squeezed to zero - a 2px overflow that only appears while someone
     is typing, which is exactly the kind of state a resting-state screenshot
     never shows. Clamped to 42vw below 452px (190/0.42), the row fits with the
     mark still visible; at 480px, 42vw is 201.6 so min() picks the unchanged
     190px and nothing about this tier's wider end moves. The field keeps its
     own font-size, so this costs characters visible at once, not legibility.
     Rooted at .rxsu-cap-shell for specificity, not written bare: the rule it
     overrides (.rxsu-cap-search:focus-within .rxsu-cap-search__input) is
     (0,3,0) and lives in the ค้นหา block at the very end of this file, i.e.
     *after* this media query - a bare copy here would be an exact
     specificity tie decided by source order, and would lose. Measured: with
     the bare selector the field stayed 190px and 320px still overflowed by
     2px; at (0,4,0) it clamps and the overflow is 0. Same discipline as the
     two-class bumps the LINKS and TEXT COLOUR blocks document.

     0.29.0 fix round 5 (defect 1a): the rule is gone, and every sentence above
     it is kept because the measurement is still true of the file it described
     - it just no longer describes this one. The premise of the whole
     paragraph is "the expanded field is a flat 190px [of rigid demand] in a
     flex:none item", and that premise is what fix round 5 removed: the field
     is absolutely positioned now (see the ค้นหา block at the end of this
     file), so it adds nothing to the row at any width and there is no 2px to
     clamp away. What survives of the problem is only the left-edge case - a
     right-anchored overlay wider than the space beside it runs off screen -
     and that belongs on the rule that opens the field, not in a width tier:
     it is the same hazard at 320px whatever the tier, and expressing it as
     `min(190px, calc(100vw - 96px))` there says so in one place. Deleting a
     tier override rather than adding one is also the only version of this
     that reduces the number of rules a future reader has to hold at once. */
}

/* ── drawer มือถือ (Task 8) ──────────────────────────────────────────────
   Fix round 1: the base rule below used to be display:none, with a single
   media query turning it on at <=760px - which made "no JavaScript still
   reaches every link" true on phones and false everywhere else. Without a
   script, at 1440px the rail still shows inert <button>s for every item that
   has a mega panel (their hidden panels never open without JS) and the
   drawer was display:none at that width too - a whole section's subtree,
   landing page included, unreachable. Now the drawer is the no-JS fallback
   at every width: display:block by default, and JavaScript is what hides it
   again once it is known to be running (`.rxsu-cap-js`). See
   templates/shell/capsule/parts/header.php's docblock and
   .superpowers/sdd/2026-08-02-shell-template-capsule/task-8-brief.md for the
   full "open by default, JS folds it" reasoning this extends to every width.

   Fix round 2: round 1 left a second, narrower flash. Below 1024px it used
   to re-open the drawer with a plain `.rxsu-cap-js .rxsu-cap-drawer{display:
   block}` inside the media query, keyed on nothing but the class and the
   width - not on whether the drawer was actually meant to be open. The
   `rxsu-cap-js` class lands at wp_head, before the browser has even parsed
   the drawer's markup, but the `hidden` attribute that was supposed to close
   it is only set once capsule.js runs in the footer - after the whole
   document, drawer included, has already been parsed and painted. So on
   every phone load the full menu rendered open first and collapsed visibly
   once the footer script caught up: the exact flash the wp_head class exists
   to prevent, on the majority of this site's traffic.

   The fix inverts which state needs a class. "Open" is now something the
   script has to assert explicitly (`.is-open`, only inside the narrow media
   query, so it can never open on a desktop) rather than something that
   happens unless the script has already run to prevent it. With no
   `.is-open` class, `.rxsu-cap-js .rxsu-cap-drawer{display:none}` below
   applies at every width from the very first paint - the wp_head class
   alone is now sufficient, and the drawer is never visible before the fold
   has "happened", because there is no separate fold step to race. */
.rxsu-cap-burger { display: none; }
/* 0.27.0: decoupled from --sh-brand/--sh-brand-ink, which the drawer used to
 * share with the brand row simply because both were "the dark card". The
 * brand row is light now (the logo needs it to be); the drawer has no logo
 * inside it and nothing forcing it to follow. Left dark, tied to --sh-lead
 * instead - the same "deliberate anchor" the mega-menu's lead card uses, and
 * the mobile equivalent of it. This is also what keeps .rxsu-cap-drawer__group
 * (color:var(--sh-cream), a light accent tuned for dark surfaces) and
 * .rxsu-cap-drawer__top's border (var(--sh-hairline), a light translucent
 * line also tuned for dark surfaces) correct without touching either token -
 * both already assumed a dark drawer, and now they have one again. */
.rxsu-cap-drawer { display: block; padding: 8px 18px 22px; background: var(--sh-lead); color: #fff; }
/* Once JavaScript is confirmed running, hide the drawer regardless of width
   or the `hidden` attribute, unless it is also explicitly marked `.is-open`
   (see the media query below) - this has to sit outside the media query, or
   a wide viewport with JS running would still show the always-open drawer
   underneath the rail. */
.rxsu-cap-js .rxsu-cap-drawer { display: none; }

/* ── scrim behind the drawer (0.69.0) ──────────────────────────────────────
 * Not in the markup: capsule.js builds it at runtime and appends it to
 * .rxsu-cap-shell. A server-printed scrim would be an opaque sheet with
 * nothing able to dismiss it for a reader without JavaScript - the same shape
 * as "a button that does nothing when pressed", the 0.25.18 lesson this
 * file's burger rule already obeys. It also means header.php is not touched
 * at all, so tests/shell-capsule-render.php's 361 assertions stay true.
 *
 * The selector deliberately carries no `.rxsu-cap-drawer` token anywhere. The
 * cascade resolver in tests/shell-capsule-js.js collects every rule holding
 * that token together with a display:none|block declaration and computes the
 * drawer's seven pinned states from them; a scrim rule written as
 * `.rxsu-cap-drawer.is-open + .rxsu-cap-scrim{display:block}` would be counted
 * as a drawer display rule when it is nothing of the sort. The scrim's own
 * .is-open is toggled by the same function that toggles the drawer's, rather
 * than being tied to it with a combinator.
 *
 * opacity/visibility rather than display, because both transition directly
 * (visibility is delayed to the end on the way out so the fade finishes before
 * the element stops taking hits). Both selectors are registered in
 * tests/shell-capsule-contrast.php's opacity table - every opacity declared in
 * this file must be. */
.rxsu-cap-scrim{
  position:fixed;inset:0;z-index:2;
  background:var(--sh-scrim);
  opacity:0;visibility:hidden;
  /* Dragging on the scrim has nothing to scroll, and on iOS an untyped drag
   * there rubber-bands the whole viewport behind it. Costs nothing here. */
  touch-action:none;
  transition:opacity .18s ease,visibility 0s linear .18s;
}
.rxsu-cap-js .rxsu-cap-scrim.is-open{
  opacity:1;visibility:visible;
  transition:opacity .18s ease;
}

/* ── page scroll lock while the drawer is open (0.69.0) ────────────────────
 * The class goes on <html>: capsule.js already owns documentElement and has
 * never touched document.body, and keeping it that way means there are not two
 * places competing for the same element.
 *
 * !important, and the reason is measurement rather than habit. The first pass
 * of this rule left it off, on the grounds that a scan of all 53 readable
 * stylesheets on the live page finds no rule setting overflow on html or body
 * and no inline style on either. Both of those are true, and the conclusion
 * drawn from them was wrong. Tested on the page itself:
 *   nothing applied                  -> computed overflow: clip visible
 *   inline overflow:hidden           -> computed overflow: clip visible
 *   inline overflow:hidden !important -> computed overflow: hidden hidden
 * An inline declaration at normal priority losing means the winning one is
 * !important from somewhere the stylesheet scan cannot see, so a plain
 * declaration in this file - which is enqueued at priority 5 and therefore
 * printed before the theme's sheets - never had a chance. Without the flag the
 * page keeps scrolling underneath the open sheet, which is the whole defect.
 *
 * scrollbar-gutter is declared unconditionally, NOT inside the lock. The
 * drawer's breakpoint reaches 1087px, which includes desktop windows on
 * Windows/Linux where the scrollbar takes real width; adding the gutter at
 * lock time would move the whole page sideways at the exact moment the design
 * exists to stop the page moving. Reserving it always costs the same handful
 * of pixels in both states, so nothing shifts.
 *
 * The name is rxsu-cap-lock, not rxsu-cap-drawer-lock, for the same reason as
 * the scrim above: the drawer's token must not appear in rules that are not
 * the drawer's. */
.rxsu-cap-js{scrollbar-gutter:stable}
.rxsu-cap-js.rxsu-cap-lock,
.rxsu-cap-js.rxsu-cap-lock body{
  overflow:hidden !important;overscroll-behavior:none;
}

@media (max-width: 67.9375em) {
  /* Same rail-fits measurement as the "narrow screens" query above - kept in
   * step with it and with capsule.js's matchMedia string on purpose
   * (tests/shell-capsule-js.js asserts all three agree): a mismatch would
   * show the burger at a width where the script refuses to fold the drawer,
   * or hide the drawer at a width where the rail has already been hidden.
   * 0.29.0 fix round 1 (five-lens sweep, defect 2): 1023 -> 1087, measured
   * against the real six-item menu and the compact metrics in the
   * @media (max-width:1256px) block - the full account is on the other
   * query, above.
   * 0.29.0 fix round 5 (defect 1b): 1087px -> 67.9375em, the same number at
   * the 16px browser default and a larger one for a reader who has changed
   * it - again, the full account is on the compact-rail query above. This is
   * the query that shows the burger, so it is the one that must never be
   * left behind: at a large default font size the rail stops fitting long
   * before 1087px, and if this query alone had stayed in px the reader would
   * have had no rail (hidden by the query above, which did move) and no
   * burger either. */

  /* fix round 2: three classes, so this always outranks the unconditional
   * .rxsu-cap-js .rxsu-cap-drawer{display:none} above (two classes) on
   * specificity alone - not on source order, which a later reshuffle of
   * this file could silently undo. The burger sets this class explicitly on
   * open, alongside `hidden` and aria-expanded (see capsule.js) - there is
   * no width- or class-only path back to `block` any more. */
  .rxsu-cap-js .rxsu-cap-drawer.is-open { display: block; }

  /* ── the drawer as a floating sheet (0.69.0) ────────────────────────────
   * Measured before this existed, at 375x812 with the drawer open: the drawer
   * had no `position` rule anywhere in this file, so it was `static`, stood
   * 2523px tall (3.1 screens) and pushed <main> down to y=2647. There was no
   * scrim, no scroll lock, and the only control that closes it scrolled off
   * the top within the first 120px because .rxsu-cap-header is not sticky.
   *
   * `display` is deliberately NOT restated here. The rule above owns it, and
   * the resolver in tests/shell-capsule-js.js reads the literal word
   * (none|block) to compute the drawer's seven pinned states - writing flex or
   * grid here would leave the test's model and the truth disagreeing with
   * nothing going red. This rule contributes geometry only.
   *
   * THREE classes, and the third is the point. `.is-sheet` is added by
   * capsule.js only after the sheet's furniture (close button, scrim,
   * accordion) has actually been built. If that build throws, the class never
   * lands and the drawer falls back to exactly today's behaviour - static, in
   * the document flow, all 41 links reachable, burger still opening and
   * closing it. Without this gate a mid-build exception would leave an opaque
   * fixed sheet with no close button and no way out, which is worse than the
   * bug being fixed. `.is-open` alone must never mean "floating sheet".
   *
   * Everything here is behind `.rxsu-cap-js` as well, so a reader without
   * JavaScript never meets any of it: their drawer stays static and full
   * height. Making this unconditional would clip every link below the fold for
   * them permanently - the failure this file's own header note exists to
   * prevent. */
  .rxsu-cap-js .rxsu-cap-drawer.is-open.is-sheet{
    /* top/bottom rather than height:100vh - Safari iOS grows and shrinks its
     * toolbars while scrolling, and 100vh stays taller than the real viewport
     * the whole time. */
    position:fixed;z-index:3;top:0;right:0;bottom:0;left:auto;
    /* 88vw = 330px at 375px, leaving 45px of scrim - above the 44px this same
     * round sets as the floor for every tap target in the sheet, since tapping
     * the scrim is one of the four ways out. Tablets stop at 380px so the
     * sheet never grows wide enough to read as a new page. */
    width:min(88vw,380px);
    /* Side padding moves to __list below so the sticky head can span the full
     * width. The 72px at the bottom clears the theme's floating chat button:
     * its wrapper is position:fixed at z-index 99999 (measured), an order of
     * magnitude above .rxsu-cap-shell's 9990, and it is not part of this
     * plugin - capsule.js hides it while the sheet is open, and this reserve
     * is the second belt for the case where that element is not found. */
    padding:0 0 calc(72px + env(safe-area-inset-bottom,0px));
    /* Scrolls inside itself instead of making the whole page scroll.
     * overscroll-behavior:contain stops the scroll chaining at the sheet edge -
     * the belt to the scroll lock's braces. */
    overflow-y:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;
    scrollbar-gutter:stable;
    /* Not var(--sh-lift): that token is 0 16px 34px -26px, whose -26px spread
     * pulls the shadow box 26px inward while the 34px blur only reaches ~17px
     * back out, so its left edge stops 9px INSIDE the sheet and is painted over
     * by the sheet's own opaque ground. It was tuned for a card with air around
     * it. A sheet flush to the screen edge needs a horizontal throw. */
    box-shadow:-18px 0 40px -12px rgba(6,32,24,.45);
    transform:translateX(100%);
    transition:transform .2s ease;
  }
  /* Four classes. capsule.js adds .is-shown two animation frames after display
   * changed from none, because a transition has no starting value to run from
   * if both happen in one style recalculation. Declares no display, so the
   * resolver skips the whole rule. */
  .rxsu-cap-js .rxsu-cap-drawer.is-open.is-sheet.is-shown{transform:none}

  /* 0.69.1 - WordPress prints its admin bar as position:fixed, top:0, at
   * z-index 99999. A sheet pinned to top:0 slides underneath it, and what the
   * bar covers is the sticky head - which is where the close button lives, the
   * sheet's primary way out. Reported from a real tablet screenshot with the
   * cross clipped in half.
   *
   * (0,4,1) against the geometry rule's (0,4,0), so it wins on specificity
   * rather than on being later in the file.
   *
   * The variable is WordPress's own and tracks its two heights (32px at 783px
   * and up, 46px below). The fallback is the LARGER of the two on purpose: on
   * a WordPress old enough to lack the variable, being 14px too low leaves a
   * thin strip of scrim above the sheet, while being 14px too high puts the
   * close button back under the bar - the defect this rule exists for. An
   * untidy gap is a better failure than an unreachable control. */
  body.admin-bar .rxsu-cap-drawer.is-open.is-sheet{
    top: var(--wp-admin--admin-bar--height, 46px);
  }

  /* Side padding for the list, only while it is a sheet; the right side allows
   * for a landscape notch. max(constant, env(...)) is the shape this plugin
   * already uses in assets/lightbox.css. */
  .rxsu-cap-js .rxsu-cap-drawer.is-open.is-sheet .rxsu-cap-drawer__list{
    padding:0 max(18px,env(safe-area-inset-right)) 8px 18px;
  }

  .rxsu-cap-js .rxsu-cap-burger {
    display: grid; place-items: center; width: 46px; height: 46px; flex: none;
    border: 0; border-radius: 50%;
    /* 0.27.0: dark-tinted, not white - sits on the (now light) brand row,
     * same reasoning as --sh-rail above; the bars themselves stay visible
     * either way via currentColor from the brand row's own ink. */
    background: rgba(18,58,46,.13); color: inherit; cursor: pointer;
    /* 0.29.0 fix round 4 (defect 6): the disc had no edge. rgba(18,58,46,.13)
     * composited over --sh-brand is (224.19,229.39,227.83) on the default
     * tone - 1.27:1 against the brand row it sits on, and the same order of
     * nothing on sage and olive. Below this breakpoint the burger is the ONLY
     * route into the site menu (the rail is display:none and this site's
     * traffic is mostly phones), so its 3:1 UI boundary is not a nicety.
     * Same 18,58,46 tint at the same .6 the search button and the search
     * field's focus border now carry, so all three controls on the brand row
     * are drawn in one hand: composited over the disc it lands on, the edge
     * measures 3.43 / 3.31 / 3.31:1 against that disc inside it and
     * 4.35 / 4.19 / 4.18:1 against --sh-brand on default/sage/olive outside it.
     * An INSET box-shadow, not the `border` those two use, and the reason is
     * measurement rather than taste: three separate arithmetics in this file
     * (the 1256px compact rail, the 1087px narrow row, the 480px phone tier)
     * are written against a 46px burger, while the search button is written
     * into the same sums as 48px precisely because it carries a 1px border on
     * each side. A border here would silently move all three by 2px. An inset
     * shadow paints over the element's own background inside the border box,
     * so the geometry every one of those sums assumes is unchanged - and its
     * backdrop is the disc, which is what the composite above measures. */
    box-shadow: inset 0 0 0 1px rgba(18,58,46,.6);
  }
  /* Redundant with the rule above once `.is-open` and `hidden` are kept in
   * lockstep by capsule.js (which they always are), but kept as a second,
   * independent guard: if capsule.css ever fails to load while capsule.js
   * still runs, the `hidden` attribute is the only thing left that closes
   * the drawer, via the browser's own built-in styling rather than anything
   * authored here - and if `.is-open` and `hidden` were ever both present at
   * once (a bug state), this rule's equal specificity but later position
   * makes "closed" the side that wins, not "open". */
  .rxsu-cap-js .rxsu-cap-drawer[hidden] { display: none; }
}

.rxsu-cap-drawer__list, .rxsu-cap-drawer__sub { list-style: none; margin: 0; padding: 0; }
/* 0.69.1 - display:block -> flex, plus a 52px floor.
   Two defects came out of the missing flex, both visible on the live tablet
   drawer and both from this one declaration:
   1. .rxsu-cap-chev is a <span> that draws its arrow out of a right and bottom
      border on a 9x9 box, and declares no display of its own. Inside a block
      parent it stays `inline`, where width and height do not apply - so the box
      collapses to the line box and only the right border survives, painting a
      2px vertical bar next to the label. The sibling branch never showed it
      because .rxsu-cap-drawer__toggle is display:grid, which blockifies the
      span. Fixing it here rather than on .rxsu-cap-chev keeps the shared token
      untouched for the desktop rail that already renders it correctly.
   2. justify-content:space-between on .rxsu-cap-drawer__top-toggle did nothing
      on a block box, so the chevron sat against the label instead of at the
      far edge of the row.
   Every other declaration is unchanged, border-bottom included: the no-JS
   drawer has no .rxsu-cap-drawer__row wrapper to carry the divider for it. */
.rxsu-cap-drawer__top { display: flex; align-items: center; min-height: 52px; padding: 13px 0; font-size: 1.0625rem; font-weight: 700; border-bottom: 1px solid var(--sh-hairline); }
/* Two classes: this is a <p> (header.php renders the group label as one) and
   the 0.29.0 TEXT COLOUR reset names `.rxsu-cap-shell p` at (0,1,1), which
   outranks this rule's own (0,1,0). Left unbumped, the cream label would have
   fallen back to the drawer's own white and stopped reading as a group
   heading distinct from the links under it. Declarations unchanged. */
.rxsu-cap-drawer .rxsu-cap-drawer__group { margin: 14px 0 6px; font-size: 12.5px; font-weight: 700; color: var(--sh-cream); }
/* 0.69.0 - display:block -> flex plus a 44px floor. Measured on the live page
   before the change: 28 of the 36 sub-links stood below 44px, the shortest at
   42px, because this rule sets no min-height and the theme leaves line-height
   at `normal`. opacity:.92 keeps its exact value - it is the entry
   tests/shell-capsule-contrast.php measures white-on---sh-lead at 4.5:1
   through, and removing it fails that test as a stale entry. */
.rxsu-cap-drawer__sub a { display: flex; align-items: center; min-height: 44px; padding: 9px 0 9px 14px; font-size: 15px; opacity: .92; }
/* Tap targets that share an edge read as one target. 2px is enough to separate
   them without changing the rhythm of the list. */
.rxsu-cap-drawer__sub li + li { margin-top: 2px; }
.rxsu-cap-drawer__utility { display: flex; align-items: center; min-height: 48px; padding: 12px 0; font-weight: 600; }
.rxsu-cap-burger span { display: block; width: 20px; height: 2px; background: currentColor; box-shadow: 0 -6px 0 currentColor, 0 6px 0 currentColor; }

/* ══════════════════════════════════════════════════════════════════════════
   ลิ้นชักแบบแผ่นลอย — ของที่ capsule.js ประกอบขึ้น (0.69.0)
   ทุกอย่างในบล็อกนี้ไม่มีอยู่ในมาร์กอัปของ header.php เลย มันถูกสร้างตอนรันไทม์
   จึงไม่มีอะไรให้ผู้อ่านที่ไม่มี JavaScript เห็นหรือกดพลาด
   ══════════════════════════════════════════════════════════════════════════ */

/* Sticky at the top of the sheet, so the way out is reachable at every scroll
   position. The burger it replaces is a child of the brand row on a header
   that is not sticky, and scrolled away within the first 120px of 2523px.
   56px is a value chosen here, not borrowed: the owned-menu head in
   global-header-unity.css is 64px, which is right for a full-width panel but
   too heavy on a 330px sheet where it is pure chrome. The 44px close button
   inside it IS that file's number. */
.rxsu-cap-drawer__head{
  position:sticky;top:0;z-index:1;
  display:flex;align-items:center;justify-content:space-between;gap:12px;
  min-height:56px;
  padding:max(6px,env(safe-area-inset-top)) max(12px,env(safe-area-inset-right)) 6px 18px;
  background:var(--sh-lead);
  border-bottom:1px solid var(--sh-hairline);
}
/* Two classes for the same reason as .rxsu-cap-drawer__group above: this is a
   <p> and the TEXT COLOUR reset names `.rxsu-cap-shell p` at (0,1,1). At
   (0,2,0) it also outranks the LINKS state rules at (0,1,2), so it does not
   need an entry in that test's $nonAnchorColourRules. --sh-cream on --sh-lead
   is a pair this file already measures. */
.rxsu-cap-drawer .rxsu-cap-drawer__title{
  margin:0;font-size:12.5px;font-weight:700;letter-spacing:.04em;color:var(--sh-cream);
}
/* Declares no `color` - `.rxsu-cap-shell button{color:inherit}` gives it the
   sheet's white, so no new colour pair appears.
   The ring is rgba(255,255,255,.45), not --sh-hairline (.14). Composited on
   --sh-lead the hairline measures 1.56:1, while this measures 3.85:1 against
   the same ground. That 3:1 floor is the one the burger's own comment argues
   for at length on the grounds that below this breakpoint it is the only way
   into the menu - this button is now the only way out, by the same logic.
   An inset shadow rather than a border, again like the burger, so the 44px
   box stays 44px instead of quietly becoming 46px. */
.rxsu-cap-drawer__close{
  width:44px;height:44px;flex:none;display:grid;place-items:center;
  border:0;border-radius:50%;background:transparent;cursor:pointer;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.45);
}
.rxsu-cap-drawer__close span{
  position:relative;display:block;width:18px;height:2px;background:currentColor;
  transform:rotate(45deg);
}
.rxsu-cap-drawer__close span::after{
  content:"";position:absolute;inset:0;background:currentColor;transform:rotate(-90deg);
}

/* Top row: link on the left, 48px expand button on the right. 48px is the
   toggle width global-header-unity.css already uses for the same job. The rule
   below moves the divider onto the row so it spans the button column too. */
.rxsu-cap-drawer__row{
  display:grid;grid-template-columns:minmax(0,1fr) 48px;align-items:center;
  border-bottom:1px solid var(--sh-hairline);
}
.rxsu-cap-drawer__row .rxsu-cap-drawer__top{border-bottom:0}
/* When the heading has no destination capsule.js replaces the <p> with a button
   that is the whole row, and a one-child grid would otherwise leave it in
   column 1 only: `width:100%` would mean 100% of the 1fr track, the chevron
   would sit ~48px left of where the other rows put theirs, and the 48px column
   beside it would be dead space that looks tappable and is not. Spanning both
   columns puts every chevron on one vertical line. */
.rxsu-cap-drawer__row > .rxsu-cap-drawer__top-toggle{grid-column:1 / -1}
/* font-family, never the `font` shorthand: the shorthand would reset the
   font-size and weight that .rxsu-cap-drawer__top sets on this same element. */
.rxsu-cap-drawer__top-toggle{
  width:100%;justify-content:space-between;gap:12px;padding-inline-end:14px;
  background:none;border:0;font-family:inherit;text-align:start;cursor:pointer;
}
.rxsu-cap-drawer__toggle{
  width:48px;min-height:52px;height:100%;display:grid;place-items:center;
  background:none;border:0;padding:0;cursor:pointer;
}
/* Reuses the file's own .rxsu-cap-chev and rotates from the attribute rather
   than a new class - the pattern .rxsu-cap-nav__trigger[aria-expanded] already
   uses. Declares transform, not display, so the resolver ignores it. */
.rxsu-cap-drawer [aria-expanded="true"] .rxsu-cap-chev{
  transform:rotate(-135deg) translate(-2px,-2px);
}
.rxsu-cap-drawer__section{padding-bottom:6px}

/* Current page: a cream bar down the left edge rather than a colour change.
   Declaring `color` here would pull the rule into two separate gates in
   tests/shell-capsule-contrast.php without telling the reader anything the bar
   does not already say. */
.rxsu-cap-drawer__top[aria-current],
.rxsu-cap-drawer__top-toggle[aria-current],
.rxsu-cap-drawer__sub a[aria-current]{
  box-shadow:inset 3px 0 0 var(--sh-cream);font-weight:700;
}

/* The theme's floating chat button is position:fixed at z-index 99999, ten
   times .rxsu-cap-shell's 9990, so it floats over both the scrim and the sheet.
   It is not part of this plugin (grep of the whole repo returns nothing), so
   its own z-index cannot be reached from here - it gets hidden for as long as
   the sheet is open instead. The selector lists the wrapper this site actually
   renders; a miss is harmless because the sheet also reserves 72px at its
   bottom edge for exactly this. */
.rxsu-cap-lock .rxsu-chat-wrapper,
.rxsu-cap-lock .rxsu-fab{visibility:hidden}

/* ══════════════════════════════════════════════════════════════════════════
   ค้นหา — mockup มีแค่ปุ่มวงกลมประดับ ไม่มีฟอร์ม สไตล์ทั้งหมดในบล็อกนี้จึงเป็นของใหม่
   ล้วน (บรีฟ Task 5 ข้อ 4.6) ไม่ใช่การแปลงจาก .sh-search: header.php ยกระดับปุ่มนั้น
   เป็นฟอร์มค้นหาจริงที่ submit ได้โดยไม่ต้องมี JavaScript (สเปกข้อ 3.8) — ช่องข้อความ
   ซ่อนอยู่จนกว่าจะโฟกัส แล้วกางออก ต้องกางด้วย :focus-within เพื่อให้ผู้ใช้คีย์บอร์ด
   แตะ Tab ไปถึงแล้วเห็นช่องก่อนพิมพ์ ไม่ใช่กางเฉพาะตอนคลิกด้วยเมาส์เท่านั้น
   ══════════════════════════════════════════════════════════════════════════ */
.rxsu-cap-search{position:relative;display:flex;align-items:center;flex:none}
.rxsu-cap-search__label{
  position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
  clip:rect(0,0,0,0);white-space:nowrap;border:0;
}
/* 0.29.0 fix round 2 (defect 10): two classes now, and `input` was added to the
   TEXT COLOUR reset near the top of this file at the same time - see that
   block's own note for why both halves, not either. This rule was the last
   single-class text colour left in either shell root: at (0,1,0) it now loses
   to the reset's (0,1,1), and #0F3A30 is a colour it means to keep (12.59:1 on
   the field's own white background). The one element on this shell a visitor
   types their own words into is not the one to leave to source order. */
/* 0.29.0 fix round 5 (defect 1a): position:absolute, and it is the whole fix
   for the worst of the three squeeze triggers.
   The field used to be an in-flow flex item inside .rxsu-cap-search, which is
   flex:none - so the 190px the focused state adds (plus its 8px margin) was
   198px of rigid new demand appearing in the brand row the instant a visitor
   put the caret in the box, in a row where the rail cannot shrink and the
   burger and the button are flex:none. The only item able to absorb it was the
   mark. Measured in chrome-headless-shell against the pre-fix file, six-item
   menu, 16px root: at 1280px (the commonest desktop width) the mark went from
   284.6x52.0 at rest to 111.8x20.4 focused - 39% of the height this tier
   intends - and at 1257px, one pixel ABOVE the compact tier's ceiling, to
   88.8x16.2, so a viewport one pixel wider rendered a smaller logo. Every one
   of those measurements had scrollWidth == clientWidth: nothing on the page
   said a thing.
   An expanding field is a transient overlay, not a new column, so it should
   not be in the flow at all. Out of flow it adds no demand to the row, the
   row's geometry is identical focused and at rest at every width, and the
   staircase of tier metrics further down this file - all of it written against
   the resting row - is true of the focused row too for the first time.

   inset-inline-end:100% anchors the field's trailing edge to the leading edge
   of .rxsu-cap-search (which is position:relative already, and whose whole
   width is the 48px button), so the field opens leftwards over the rail with
   the button still in place at its end - the same picture as before, drawn
   without moving anything. The block axis needs no offset: an absolutely
   positioned child of a flex container takes its static position from the
   container as though it were the sole flex item, so align-items:center still
   centres it (CSS Flexbox §4.1).
   margin-inline-end:8px is kept and still reads as the gap: on an absolutely
   positioned box the trailing margin adds to the inset, so it pushes the field
   8px further from the button exactly as it used to push the button 8px away
   from the field. */
.rxsu-cap-search .rxsu-cap-search__input{
  position:absolute;inset-inline-end:100%;z-index:2;
  width:0;min-width:0;padding:0;margin-inline-end:0;opacity:0;
  border:1px solid transparent;border-radius:999px;
  background:#fff;color:#0F3A30;font:inherit;font-size:14px;line-height:1;
  transition:width .18s ease,opacity .18s ease,padding .18s ease,margin .18s ease;
}
.rxsu-cap-search:focus-within .rxsu-cap-search__input{
  /* The clamp replaces the @media (max-width:480px) override this file used to
     carry (see that block's own note, kept and amended). It is no longer about
     overflow - out of flow, the field cannot overflow the row - but about the
     one thing absolute positioning does NOT solve: a right-anchored box wider
     than the space to its left runs off the LEFT edge of the viewport, where
     scrollWidth cannot see it (inline-start overflow is not scrollable
     overflow in LTR) and the visitor simply loses the first characters they
     type. 96px is the row's own trailing chrome at the phone tier - 12px
     padding + 48px button + 8px gap, plus 28px of visible page - so the field
     stops shrinking exactly when it would start disappearing. It binds only
     under 286px of viewport; at 320px it computes to 224px and min() keeps the
     designed 190px, so nothing in the measured matrix moves. */
  width:min(190px,calc(100vw - 96px));padding:12px 16px;opacity:1;margin-inline-end:8px;
  /* 0.27.0: dark-tinted, not white - this sits on the (now light) brand row;
   * a near-white outline on a near-white surface no longer reads.
   * 0.29.0 fix round 4 (defect 3): .35 -> .6, the identical correction defect 8
   * made 36 lines below on this control's own sibling, the search BUTTON, and
   * left undone on the one control the reader types into. The arithmetic is
   * the same one that rule's note spells out: background-clip is border-box,
   * so the border composites over the field's own #fff fill, and at .35 that
   * landed on (172.05,186.10,181.85) - 2.01:1 against the fill inside it and
   * 2.01 / 1.84 / 1.83:1 against --sh-brand on the default/sage/olive tones
   * outside it. Under the 3:1 WCAG 1.4.11 asks of a control's boundary, and
   * this field has no other boundary at all: white fill on a white brand row.
   * .6 composites to (112.80,136.80,129.60) and measures 3.76:1 against the
   * fill and 3.76 / 3.44 / 3.42:1 against the three brand rows - the same
   * numbers the button carries, which is the point: two halves of one control
   * should not be drawn with two different edges. */
  border-color:rgba(18,58,46,.6);
}
/* 0.29.0 fix round 4 (defect 5): the placeholder is still The7's, and the
   TEXT COLOUR reset cannot reach it.
   A pseudo-element does not take `color` from `color:inherit` declared on its
   originating element - `inherit` there resolves against the input's PARENT,
   and the pseudo-element then has no colour rule of its own at all. So Chrome
   matches the only rule that does name it: The7's
   `input::-webkit-input-placeholder{color:var(--the7-input-color);opacity:var(
   --the7-form-placeholder-opacity,0.5)}` - a theme colour at half opacity over
   this field's own white fill, i.e. a value this project has never measured
   and cannot bound, since --the7-input-color is whatever the active The7 skin
   sets. Defect 10 bumped the input itself to two classes and added `input` to
   the reset; neither reaches this.
   Declared in both spellings, in two SEPARATE rules on purpose: a selector
   list is discarded whole by any engine that does not recognise one of its
   selectors, so pairing the standard `::placeholder` with the -webkit- form in
   one comma list would lose both in the browser that only understands one.
   `opacity:1` is not housekeeping either - it is what undoes The7's own 0.5,
   which would otherwise re-dim whatever colour is set here.
   var(--sh-panel-muted) rather than a fifth grey-green: it is this file's
   existing "muted text on a white surface" role (the mega panel's item
   counts, on --sh-panel #FFFFFF, the same white this field fills with).
   Measured against that fill: 4.54:1, over the 4.5:1 floor placeholder text
   is held to as text. That is a narrower margin than this file usually likes,
   and it is deliberate: the token is shared, so check 14 measures the token
   rather than a copy, and darkening the panel's muted role can only move this
   number up. If a future release lightens it, the suite fails here first. */
.rxsu-cap-search .rxsu-cap-search__input::placeholder{
  color:var(--sh-panel-muted);opacity:1;
}
.rxsu-cap-search .rxsu-cap-search__input::-webkit-input-placeholder{
  color:var(--sh-panel-muted);opacity:1;
}
/* 0.27.0: the always-visible white circle used to stand out against a dark
 * brand row on colour alone. On the now-light row it needs its own hairline
 * to still read as a distinct button rather than blending into the header -
 * a dark tint, not --sh-hairline (that token stays tuned for the drawer/
 * footer's dark surfaces, not this light one - see the notes above it).
 * 0.29.0 T2: two classes now. This is a <button>, and the TEXT COLOUR reset
 * near the top of this file names `.rxsu-cap-shell button` at (0,1,1), which
 * outranks a bare .rxsu-cap-search__button (0,1,0). The task brief's bump list
 * missed this one; it is the only <button> in the shell that states a colour
 * of its own (the nav trigger and the burger both already resolve at (0,2,0)),
 * and the colour is load-bearing: the magnifier <svg> inside it strokes with
 * currentColor, so unbumped the icon would quietly repaint in the brand row's
 * inherited ink instead of this button's own #0F3A30. Still legible either
 * way on the white circle, which is exactly why it would not have been
 * noticed. Declarations unchanged; only the selector moved.
 * 0.29.0 fix round 2 (defect 8): the hairline the 0.27.0 note above introduced
 * was never strong enough to be one. background:#fff on --sh-brand #FFFFFF is
 * literally the same colour, so this border is not reinforcement of a fill
 * contrast the way .rxsu-cap-social's ring is - it is the ONLY thing telling a
 * reader a button is there. At rgba(18,58,46,.14) it composites over the
 * button's own white fill (background-clip is border-box, so the fill is what
 * is behind the border, not the brand row) to (221.82,227.42,225.74): 1.29:1
 * against the fill inside it and 1.29:1 / 1.19:1 / 1.18:1 against --sh-brand
 * on the default/sage/olive tones outside it. WCAG 1.4.11 asks 3:1 of a
 * control's boundary and this project asks it of every UI boundary.
 * .6 is the first alpha that clears it on all three tones with room to spare
 * rather than on the wire: composited (112.80,136.80,129.60), measuring
 * 3.76:1 against the white fill and 3.76:1 / 3.44:1 / 3.42:1 against
 * --sh-brand. (Fix round 4: the olive figure was written 3.43 here and in this
 * file's own docblock; it computes to 3.4249. Corrected rather than left, since
 * the whole point of these notes is that a reader can recompute them.) (.55 was measured first and rejected - 3.01:1 on sage and
 * 2.99:1 on olive, i.e. it fails outright on one of the three.) Still a 1px
 * translucent tint of the same 18,58,46 this file uses for every dark hairline
 * on the light brand row, so the button reads as drawn in the same hand as the
 * rail track and the burger, only visible. */
.rxsu-cap-search .rxsu-cap-search__button{
  flex:none;width:46px;height:46px;border-radius:50%;border:1px solid rgba(18,58,46,.6);cursor:pointer;
  background:#fff;color:#0F3A30;display:grid;place-items:center;
}
.rxsu-cap-search__button svg{width:19px;height:19px}

/* 0.29.0: the nav rail's new hover/open transitions and the chevron's rotation
   both animate purely for polish, not to carry information a static state
   change would not - reduced-motion visitors get the same end states
   instantly instead. Fix round 1 (0.29.0 T1 review) added .rxsu-cap-brand-row
   to this list alongside its new corner-squaring transition, for the same
   reason.
   0.29.0 T3 added the footer's social links and their hover lift. Kept in
   this one block rather than a second @media with the identical condition
   next to the social rules themselves: two blocks with the same query in one
   file is a merge hazard, and there is no ordering subtlety to justify it -
   nothing between the social rules and here re-declares transition or
   transform on those anchors, so this later block wins outright.
   Both halves are needed, unlike the nav rail's: transition:none alone would
   still leave the -2px lift, applied instantly. assets/shell-footer.css's own
   reduced-motion block zeroes the same two properties for the same element,
   and the :focus-visible ring below is untouched here - it is a static
   box-shadow, so a keyboard user keeps a full-strength focus indicator with
   motion off. */
@media (prefers-reduced-motion:reduce){
  .rxsu-cap-nav__link,.rxsu-cap-nav__trigger,.rxsu-cap-chev,.rxsu-cap-brand-row{transition:none}
  .rxsu-cap-social a{transition:none}
  .rxsu-cap-social a:hover{transform:none}
  /* 0.69.0 - the drawer and its scrim gained transitions for the first time,
     so they sign in here as the comment above this block requires.
     The selectors are written long on purpose. A media query adds no
     specificity, so the shorthand form `.rxsu-cap-drawer,.rxsu-cap-scrim` at
     (0,1,0) would lose outright to the rules it means to cancel - the sheet's
     own transition is declared at (0,4,0) and the scrim's open state at
     (0,3,0) - and the scrim would keep fading in over 180ms for exactly the
     readers who asked it not to. Each selector below matches or beats the one
     it overrides. The nav-rail line above works because its source rules are
     single-class too; copying its shape without copying that condition is the
     trap. None of these declare display, so the cascade resolver in
     tests/shell-capsule-js.js still ignores them all. */
  .rxsu-cap-scrim,
  .rxsu-cap-js .rxsu-cap-scrim.is-open,
  .rxsu-cap-js .rxsu-cap-drawer.is-open.is-sheet{transition:none}
  /* transition:none alone would leave the sheet parked at translateX(100%) and
     snap it in on the next frame - motion that is still motion. */
  .rxsu-cap-js .rxsu-cap-drawer.is-open.is-sheet{transform:none}
}
