/* Panyagraph Design System R6 — "Red Spine" · LOCKED · see design.md
 * Promoted from base-r6.css to primary on 2026-08-01 — this is now the one
 * true base.css. axon.html (admin) and /tools/capstone-debrief still load
 * base-legacy.css (pre-R6) — see the note in tokens.css.
 * Requires tokens.css + fonts.css loaded first.
 */

/* ============================== Reset / base ============================== */

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
/* ริ้วยื่นออกนอกขอบจอโดยตั้งใจ — clip กันแถบเลื่อนแนวนอนโผล่ */
html, body { overflow-x: clip; }

body {
  margin: 0;
  background: var(--color-paper);
  color: var(--color-ink);
  font-family: var(--font-sans);
  font-size: var(--text-body);
  font-weight: var(--weight-body);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, p, ul, ol, figure { margin: 0; }
ul, ol { padding: 0; list-style: none; }
img { max-width: 100%; display: block; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
a { color: inherit; text-decoration: none; }
input { font: inherit; color: inherit; }

:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
.color-surface-inverse :focus-visible,
[data-surface="inverse"] :focus-visible {
  outline-color: var(--color-focus-inverse);
}

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

.mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.small { font-size: var(--text-sm); }
.muted { color: var(--color-ink-soft); }

.wrap {
  width: 100%;
  max-width: 42rem;
  margin: 0 auto;
  padding: var(--space-8) var(--space-4) var(--space-12);
}
/* หน้ารายงาน (dashboard กว้าง: radar chart, heatmap ตาราง) ใส่คลาสนี้เพิ่มเอง —
   42rem (672px) ของ .wrap ปกติออกแบบไว้สำหรับคอลัมน์อ่านคำถามทีละข้อ แคบเกินไป
   สำหรับ layout กว้างแบบนี้บนจอคอมพิวเตอร์ */
.wrap.wrap--wide { max-width: 74rem; }

@media (max-width: 30rem) {
  .wrap { padding: var(--space-6) var(--space-4) var(--space-10); }
}

/* ============================== Motion ============================== */

@keyframes r6-enter {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}

.enter {
  animation: r6-enter var(--dur-med) var(--ease-out) both;
}
.enter--1 { animation-delay: 0ms; }
.enter--2 { animation-delay: 60ms; }
.enter--3 { animation-delay: 120ms; }
.enter--4 { animation-delay: 180ms; }

.reveal {
  opacity: 1;
  transform: none;
}
.reveal.is-armed {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity var(--dur-med) var(--ease-out), transform var(--dur-med) var(--ease-out);
}
.reveal.is-armed.is-in {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .enter, .reveal, .reveal.is-armed {
    animation: none !important;
    transition: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
}

/* ============================== Ribbon ==============================
   ริ้วสีพาดทแยง — องค์ประกอบตกแต่งหลักของ R6 (DESIGN-R6.md §3b)

   สืบเทคนิคที่ทำให้ R5.1 "มีมิติจริง" มาทั้งชุด เพราะบทเรียนนั้นยังถูก:
     1) แต่ละเส้นเอียงคนละองศา จึงตัดกันเกิดโซนทับ ไม่ใช่แผ่นแบนแผ่นเดียว
     2) ไล่สีคนละลำดับ/คนละทิศ โซนทับได้โทนใหม่ ไม่ใช่สีเดิมเข้มขึ้น
     3) เบลอไล่ระดับ (78→18→0px) = depth of field
     4) ชั้นกลาง multiply + เส้นไฮไลต์ plus-lighter ภายใน isolate

   ต่างจาก R5 สองเรื่องที่เป็นตัวตนของ R6:
     · สีอยู่ในตระกูลแดงล้วน (plum→deep→core→light) สร้างมิติด้วยช่วงความสว่าง
       ไม่ใช่ไล่ข้ามวรรณะ ฟ้า→ม่วง→แดง→ส้ม แบบเดิมที่ทำให้แดงเป็นแค่ 1 ใน 4 สี
     · ใบมีดหน้าสุดมี "เส้นรูล" พาดในตัว (--a::after) องศาเดียวกับใบมีด —
       ผูกริ้วเข้ากับ spine ซึ่งเป็นลายเซ็นของระบบ (แดง = โครงสร้าง ไม่ใช่แค่แสง)
   ==================================================================== */

.ribbon {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
  /* จำกัด blend ให้เกิดเฉพาะระหว่างเส้นในกลุ่ม ไม่ไปคูณกับพื้นขาวของหน้า */
  isolation: isolate;
  /* ยึดมุมขวาบน จางหมดก่อนถึงคอลัมน์ข้อความ — ฝั่งข้อความต้องเป็นกระดาษขาว
     คอนทราสต์จึงผ่าน AA (บทเรียน R5.1: halo เบลอกว้างแผ่มาย้อมพื้นได้ง่ายมาก) */
  -webkit-mask-image: radial-gradient(102% 116% at 100% 6%, #000 22%, rgba(0,0,0,.8) 38%, transparent 60%);
  mask-image: radial-gradient(102% 116% at 100% 6%, #000 22%, rgba(0,0,0,.8) 38%, transparent 60%);
}

.ribbon__group {
  position: absolute;
  top: -30%;
  right: -20%;
  width: 66%;
  height: 166%;
  animation: ribbon-drift 44s ease-in-out infinite alternate;
}

.ribbon__strip {
  position: absolute;
  top: 0;
  left: 50%;
  height: 100%;
  /* ใบมีดโค้ง — สี่มุมคนละรัศมี ปลายจึงเรียวไม่เท่ากันสองข้าง ไม่ใช่แคปซูลทู่ */
  border-radius: 60% 40% 54% 46% / 42% 36% 64% 58%;
  overflow: hidden;
  will-change: transform;
}

/* ชั้นหลังสุด — แสงฟุ้งกว้าง เบลอหนัก เป็น "ระยะไกล" ของภาพ */
.ribbon__strip--halo {
  left: 54%;
  width: 46%;
  filter: blur(78px);
  opacity: .36;
  background: linear-gradient(168deg,
    var(--ribbon-plum) 0%,
    var(--ribbon-deep) 40%,
    var(--ribbon-core) 74%,
    var(--ribbon-light) 100%);
  animation: r6-strip-halo 46s ease-in-out infinite alternate;
}

/* ชั้นกลาง — ไล่สีย้อนทางแล้ว multiply ตรงที่พาดทับชั้นหน้าจึงได้โทนลึกกว่าเดิม */
.ribbon__strip--mid {
  left: 63%;
  width: 19%;
  filter: blur(14px);
  opacity: .62;
  mix-blend-mode: multiply;
  background: linear-gradient(186deg,
    var(--ribbon-light) 0%,
    var(--ribbon-core) 34%,
    var(--ribbon-deep) 72%,
    var(--ribbon-plum) 100%);
  animation: r6-strip-mid 38s ease-in-out infinite alternate;
}

/* ชั้นหน้า — ไม่เบลอเลย นี่คือเส้นที่ทำให้ริ้ว "คม" (เทคนิคยังอยู่ครบ)
   opacity ลดลงจากรอบก่อน (ไม่ใส่ = เต็ม 1) ตามฟีดแบ็กรอบสาม "ยังโฉบเฉี่ยว
   sport ไป" — ใบมีดยังคมเท่าเดิม แค่เบาลง ไม่ใช่สีทึบเต็มที่ตัดกับพื้นขาว */
.ribbon__strip--a {
  left: 45%;
  width: 13%;
  opacity: .8;
  background: linear-gradient(176deg,
    var(--ribbon-deep) 0%,
    var(--ribbon-core) 42%,
    var(--ribbon-light) 78%,
    var(--ribbon-core) 100%);
  animation: r6-strip-a 32s ease-in-out infinite alternate;
}

/* เส้นรูลในใบมีด — ของใหม่ของ R6 ไม่มีใน R5
   ริ้วไม่ได้เป็นแค่ก้อนแสง แต่มีโครงเส้นอยู่ข้างใน สอดคล้องกับ spine
   ที่บอกว่าแดงในระบบนี้ = โครงสร้าง · บางมากและ opacity ต่ำ ระยะไกลอ่านเป็น
   ผิวสัมผัส ระยะใกล้ถึงเห็นว่าเป็นเส้น */
.ribbon__strip--a::after {
  content: "";
  position: absolute;
  inset: -20%;
  background: repeating-linear-gradient(96deg,
    rgba(255, 250, 248, .11) 0 1px,
    transparent 1px 11px);
  mix-blend-mode: screen;
  opacity: .32;
}

/* เส้นสว่างบาง — เอียงตัดคนละทางกับทุกเส้น ทำให้ก้อนสีไม่ตันเป็นแผ่นเดียว
   เดิมเป็น #ffffff + plus-lighter ซึ่งให้ประกายแบบแสงสะท้อนบนโลหะเคลือบเงา
   (specular) — นั่นคือภาษาของสีรถ ไม่ใช่ของงานพิมพ์ · เปลี่ยนเป็นโทนอุ่นซีด
   เบลอกว้างขึ้นและหรี่ลง ให้อ่านเป็น "แสงนุ่ม" แทน "ประกายวาว" */
.ribbon__strip--b {
  left: 66%;
  width: 6%;
  filter: blur(9px);
  opacity: .32;
  mix-blend-mode: screen;
  background: linear-gradient(180deg,
    transparent 0%,
    var(--ribbon-light) 26%,
    #f7cfc4 50%,
    var(--ribbon-light) 74%,
    transparent 100%);
  animation: r6-strip-b 34s ease-in-out infinite alternate;
}

/* สีไหลไปตามแนวยาว — เฉพาะสองชั้นนุ่ม ชั้นคมปล่อยนิ่งไม่งั้นเห็นรอยต่อวิ่ง */
.ribbon__strip--halo::after,
.ribbon__strip--mid::after {
  content: "";
  position: absolute;
  inset: -40% -10%;
  background: inherit;
  animation: r6-ribbon-flow 36s linear infinite;
}
.ribbon__strip--mid::after { animation-duration: 44s; animation-direction: reverse; }

@keyframes r6-ribbon-flow {
  0%   { transform: translateY(0); }
  100% { transform: translateY(18%); }
}

/* แต่ละเส้นแกว่งองศาของตัวเองคนละช่วง มุมตัดจึงเปลี่ยนไปเรื่อยๆ
   (ต้องเขียน transform เต็มค่าทุก keyframe เพราะ transform ถูกแทนที่ทั้งชุด) */
/* องศาถูกลดความชันลงจากรอบแรก (-32/-14/-25/-44 → -21/-9/-16/-28)
   ความชันมากๆ อ่านเป็น "ความเร็ว" ทันที ซึ่งเป็นอีกครึ่งหนึ่งของปัญหา
   โฆษณารถยนต์ · ยังต้องต่างกันระหว่างเส้นอยู่ดีเพื่อให้ตัดกันเกิดโซนทับ
   แค่ทั้งชุดตั้งตรงขึ้น อ่านเป็นแถบกระดาษวางซ้อนมากกว่าเส้นพุ่ง */
@keyframes r6-strip-halo {
  0%   { transform: translateX(-50%) rotate(-21deg) scale(1.25); }
  100% { transform: translateX(-50%) rotate(-17deg) scale(1.36); }
}
@keyframes r6-strip-mid {
  0%   { transform: translateX(-50%) rotate(-9deg); }
  100% { transform: translateX(-45%) rotate(-13deg); }
}
@keyframes r6-strip-a {
  0%   { transform: translateX(-50%) rotate(-16deg); }
  100% { transform: translateX(-55%) rotate(-13deg); }
}
@keyframes r6-strip-b {
  0%   { transform: translateX(-50%) rotate(-28deg); }
  100% { transform: translateX(-42%) rotate(-23deg); }
}

@keyframes ribbon-drift {
  0%   { transform: translate3d(0, 0, 0) rotate(0deg); }
  100% { transform: translate3d(-3%, 3%, 0) rotate(4deg); }
}

/* Parallax ตอนไถ — CSS ล้วน ไม่มี JS ผูก scroll listener
   เบราว์เซอร์ที่ไม่รองรับก็แค่ไม่มี parallax ริ้วยังอยู่ครบ */
@supports (animation-timeline: scroll()) {
  .ribbon--hero {
    animation: r6-ribbon-parallax linear both;
    animation-timeline: scroll();
    animation-range: 0 100vh;
  }
}
@keyframes r6-ribbon-parallax {
  to { transform: translate3d(0, 14%, 0) scale(1.1) rotate(3deg); }
}

/* ขนาดย่อ — แถบบางพาดมุมขวาบน ใช้กับ login ผู้เรียน / หน้า 404
   กลุ่มเล็กลงและดันเข้ามาในจอมากกว่าเดิม เพื่อให้เห็น "ปลายโค้ง" ของริ้ว
   ไม่ใช่ด้านตรงของมันถูกขอบจอตัดจนกลายเป็นลิ่มสามเหลี่ยม */
.ribbon--corner .ribbon__group {
  top: -46%;
  right: -12%;
  width: 44%;
  height: 108%;
}

/* หน้าเหล่านี้เป็นหน้า utility ที่มีฟอร์มเป็นพระเอก ริ้วต้องเป็นฉากหลัง
   ไม่ใช่คู่แข่ง — ลบคมของใบมีดลงนิดหนึ่งและหรี่เส้นรูลในตัวลง */
.ribbon--corner .ribbon__strip--a { filter: blur(7px); }
.ribbon--corner .ribbon__strip--a::after { opacity: .3; }
.ribbon--corner .ribbon__strip--halo { opacity: .42; }

/* ริ้วในแถบเข้ม — ของที่โผล่มาตอนไถลงมา ไม่ใช่แถบดำเปล่าๆ
   ไม่ใช้ blend mode ที่นี่: mask สร้าง stacking context ซึ่งตัดการ blend กับ
   พื้นเข้มอยู่แล้ว · สีสดเบลอหนัก opacity ต่ำ บนพื้นดำก็อ่านเป็นแสงเรืองอยู่แล้ว
   วางกองที่มุมล่างขวา ห่างจากคอลัมน์ข้อความ คอนทราสต์ตัวอักษรจึงไม่ตก */
.ribbon--band {
  -webkit-mask-image: radial-gradient(74% 86% at 104% 114%, #000 8%, transparent 58%);
  mask-image: radial-gradient(74% 86% at 104% 114%, #000 8%, transparent 58%);
}
.ribbon--band .ribbon__group {
  top: auto;
  bottom: -66%;
  right: -12%;
  width: 56%;
  height: 142%;
}
.ribbon--band .ribbon__strip {
  mix-blend-mode: normal;
  opacity: .62;
  filter: blur(44px);
}
.ribbon--band .ribbon__strip--a::after { display: none; }
.ribbon--band .ribbon__strip--b { opacity: .34; filter: blur(24px); }

/* มือถือ: ริ้วถอยขึ้นไปอยู่แถบบน ไม่ทับคอลัมน์ข้อความ */
/* มือถือ: ริ้วถอยขึ้นไปกองบนสุดและจางหมดก่อนถึงบรรทัดแรกของข้อความ
   จอแคบไม่มีคอลัมน์ว่างด้านข้างให้ริ้วอยู่แบบเดสก์ท็อป ถ้าปล่อยให้จางลงมาถึง
   eyebrow (แดง --color-primary ตัวเล็ก) มันจะไปนั่งบนพื้นอมชมพูแทนที่จะเป็น
   ขาวล้วน แล้วคอนทราสต์ตกโดยที่ตาเปล่าดูไม่ค่อยออก */
@media (max-width: 860px) {
  .ribbon {
    -webkit-mask-image: linear-gradient(196deg, #000 14%, transparent 38%);
    mask-image: linear-gradient(196deg, #000 14%, transparent 38%);
  }
  .ribbon__group { top: -66%; right: -32%; width: 104%; height: 150%; }
  .ribbon--band { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .ribbon--hero,
  .ribbon__group,
  .ribbon__strip,
  .ribbon__strip::after { animation: none; }
  /* ไม่มี animation แล้วต้องตั้งองศาเอง ไม่งั้นทุกเส้นกลับไปซ้อนกันที่ 0deg */
  .ribbon__strip--halo { transform: translateX(-50%) rotate(-21deg) scale(1.25); }
  .ribbon__strip--mid  { transform: translateX(-50%) rotate(-9deg); }
  .ribbon__strip--a    { transform: translateX(-50%) rotate(-16deg); }
  .ribbon__strip--b    { transform: translateX(-50%) rotate(-28deg); }
}

/* หน้าที่ใช้ริ้วแบบลอยทั้งหน้า (learner login, 404) — ปกติซ่อน เปิดเฉพาะจอที่ต้องการ */
.pageribbon.ribbon { position: fixed; }

/* ============================== Buttons ============================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  height: 2.75rem;
  padding: 0 var(--space-5);
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  font-size: var(--text-body);
  font-weight: var(--weight-strong);
  background: var(--color-surface-sunken);
  color: var(--color-ink);
  transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
  white-space: nowrap;
}
.btn:hover { background: var(--graphite-100); }
.btn:disabled { opacity: .5; cursor: not-allowed; }
.btn:disabled:hover { background: var(--color-surface-sunken); }
/* Results dashboard R5 (A0): a disabled .btn--primary otherwise just fades the
   brand red to 50% opacity — still reads as a muted red button, not clearly
   "can't click this". Scoped to the two specific Publish buttons named in the
   brief (Publish Class / Publish Industry) via this explicit class, not a
   sitewide override of .btn:disabled — reuses the same neutral surface/ink
   tokens .badge--waiting already uses for "not ready yet". */
.btn--disabled-quiet:disabled { background: var(--color-surface-sunken); color: var(--color-ink-soft); border-color: var(--color-rule); opacity: 1; }
.btn--disabled-quiet:disabled:hover { background: var(--color-surface-sunken); }

.btn--primary {
  background: var(--color-primary);
  color: var(--color-on-primary);
}
.btn--primary:hover { background: var(--color-primary-hover); }
.btn--primary:active { background: var(--color-primary-active); }
.btn--primary:disabled:hover { background: var(--color-primary); }

.btn--secondary {
  background: transparent;
  border-color: var(--color-rule);
  color: var(--color-ink);
}
.btn--secondary:hover { background: var(--color-surface-sunken); }

.btn--ghost {
  background: transparent;
  color: var(--color-ink-soft);
  height: auto;
  padding: var(--space-1) var(--space-2);
}
.btn--ghost:hover { color: var(--color-ink); background: transparent; }

.btn--link {
  height: auto;
  padding: 0;
  background: none;
  color: var(--color-ink-soft);
  font-weight: var(--weight-label);
  font-size: var(--text-sm);
}
.btn--link:hover { color: var(--color-ink); }

/* SPEC_FEEDBACK.md §3 E1 — ตัวเลือกในการ์ด feedback ต่อยอดจาก .btn--link เดิม
   (เก็บสี/ไม่มีพื้นหลัง/ไม่มีเส้นขอบไว้ทั้งหมด — ยังเบากว่า .btn--secondary/
   .btn--primary เห็นชัด) แต่คืนพื้นที่กดให้เต็มแถว >= 44px (WCAG 2.5.5) แทนที่จะ
   กว้างแค่เท่าตัวอักษรแบบ .btn--link ดิบ — override เฉพาะ 4 พร็อพเพอร์ตี้ ไม่ใช่
   component ใหม่ */
.btn--link--option {
  height: 2.75rem;              /* 44px — คืนความสูงมาตรฐานของ .btn ฐาน */
  width: 100%;                  /* เต็มความกว้างการ์ด แทนที่จะกว้างเท่าตัวอักษร */
  padding: 0 var(--space-3);    /* 12px ซ้ายขวา */
  justify-content: flex-start;  /* ข้อความชิดซ้าย (ฐาน .btn เป็น center) */
  text-align: left;
  /* J1 root-cause fix (2026-09-02, Ray วัดจริงเจอสถานะ "เลือกอยู่" ไม่ขึ้นสี) —
     .btn ตั้ง transition: background/border-color/color 150ms ไว้ (base.css
     ราวบรรทัด 371) ตอน toggle .is-selected ด้วย classList.toggle() ทาง JS
     เบราว์เซอร์สร้าง CSSTransition ให้ แต่ animation timeline ของปุ่มไม่เดิน
     (currentTime ค้างที่ 0 ตลอดไป — พิสูจน์ด้วย getAnimations()+finish() แล้ว
     สีที่ถูกต้องโผล่ทันที) ผลคือสีเก่า (fill:backwards) ค้างแสดงตลอดไป ไม่ settle
     เอง — ไม่ใช่ปัญหา specificity/cascade (.is-selected specificity สูงสุดจริง
     ไม่มี rule ไหนทับ, ยืนยันแล้วด้วย insertRule ทดสอบสด) ทางแก้ที่ไม่พึ่ง
     !important และไม่กระทบความสูง 44px: ตัด transition ออกจากปุ่มตัวเลือกกลุ่มนี้
     โดยเฉพาะ ให้เปลี่ยนสีทันทีไม่มี animation คั่นกลาง */
  transition: none;
}

/* ฟีดแบคเจ้าของ 2026-09-03 (มือถือ): ปุ่มสถานะข้อความยาว เช่น "View report — opens
   once your instructor publishes it" ล้นออกนอกจอแทนที่จะขึ้นบรรทัดใหม่ — .btn ตั้ง
   white-space:nowrap + height คงที่ไว้ (บรรทัดบน) ปุ่มเต็มความกว้าง (--block) จึงควร
   ยอมให้ข้อความยาวขึ้นหลายบรรทัดได้แทน ไม่ใช่ดันล้นจอ */
.btn--block {
  width: 100%;
  white-space: normal;
  height: auto;
  min-height: 2.75rem;
  padding-top: var(--space-2);
  padding-bottom: var(--space-2);
  line-height: 1.3;
  text-align: center;
}

/* F1 (design-learner-prom/REVIEW.md) — ปุ่มรองที่ต้องอธิบายตัวเองยาวเกินบรรทัด
   เดียว: เดิม .btn บังคับ white-space:nowrap เสมอ ทำให้ข้อความยาวถูกตัด
   (scrollWidth > clientWidth ที่ 375) modifier นี้ให้ตัดบรรทัดได้แทน + เผื่อที่
   ให้บรรทัดอธิบายรอง (.btn__why) อยู่ในปุ่มเดียวกัน */
.btn--wrap {
  white-space: normal;
  line-height: 1.3;
  height: auto;
  padding-top: var(--space-3);
  padding-bottom: var(--space-3);
  display: grid;
  justify-items: center;
  gap: 2px;
}
.btn__why {
  display: block;
  font-size: var(--text-xs);
  font-weight: var(--weight-body);
  color: var(--color-ink-soft);
  margin-top: 2px;
}

.btn__arrow {
  display: inline-block;
  transition: transform var(--dur-fast) var(--ease-out);
}
.btn:hover .btn__arrow, .btn:focus-visible .btn__arrow {
  transform: translateX(3px);
}

/* ไอคอนส่ง — เลิกใช้ .btn__arrow (สัญลักษณ์ "ไปข้างหน้า" ของทั้งระบบ) กับปุ่มส่ง
   ข้อความเพราะไม่ใช่การนำทาง (ทิศทาง design-director, 21-interaction-visual-
   direction.md §3) ไม่มี hover-slide เหมือน .btn__arrow โดยตั้งใจ */
.btn__send-icon {
  display: inline-block;
  vertical-align: -2px;
}

.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  color: var(--color-primary);
  font-weight: var(--weight-strong);
}
.link-arrow:hover { color: var(--color-primary-hover); }

.backlink {
  color: var(--color-ink-soft);
  font-size: var(--text-sm);
  font-weight: var(--weight-label);
  margin-bottom: var(--space-4);
  display: inline-block;
}
.backlink:hover { color: var(--color-ink); }

/* ============================== Cards ============================== */

.card {
  background: var(--color-surface);
  border: 1px solid var(--color-rule);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  padding: var(--space-6);
}

/* ===== SPEC_ENTRY_UX.md §3.1 — สองการ์ดทางเข้า (จาก design-entry/entry-ux-mockup.html
   เป๊ะ, ห้ามเปลี่ยนค่า token) =====

   อัปเดต 2026-09-16: ค่า token ยังเป็นของ mockup เหมือนเดิม แต่ **กลไก** เปลี่ยนแล้ว —
   เส้นคาดซ้ายกับน้ำหนักหัวการ์ดไม่ได้ผูกกับ id ตายตัวอีกต่อไป ย้ายไปผูกกับคลาส
   .card--primary ที่ JS เติมตามการ์ดที่เป็นทางหลักของคลาสนั้น (ดูบล็อกคอมเมนต์ที่
   .card--primary ด้านล่าง และ SPEC_ENTRY_UX_V2_2026-09-16.md §5/§6 B7)
   ⇒ mockup ยังเป็นความจริงเรื่องค่า ไม่ใช่เรื่องกลไก */

.screen-h1 {
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  font-size: 1.35rem;
  margin: 0 0 var(--space-4);
}

.stackgap { display: flex; flex-direction: column; gap: var(--space-4); }

.card--selected { border-left: 2px solid transparent; padding-left: calc(var(--space-6) - 2px); }
.card--neutral { border-left: 2px solid transparent; }

.card--folded {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-5) var(--space-6);
  cursor: pointer;
}
button.card--folded {
  width: 100%;
  color: inherit;
  font: inherit;
  text-align: start;
}
.card--folded .card-eyebrow { margin-bottom: var(--space-1); }
.card--folded .card-sub { margin: 0; }
.card-folded__arrow {
  flex-shrink: 0;
  font-size: var(--text-lg);
  color: var(--color-ink-faint);
  line-height: 1;
}
.card-eyebrow {
  font-size: var(--text-sm);
  font-weight: var(--weight-strong);
  margin-bottom: var(--space-3);
  display: flex; align-items: center; gap: var(--space-2);
}
.card-sub { font-size: var(--text-xs); color: var(--color-ink-soft); margin: -.5rem 0 var(--space-4); }

/* fix 2026-09-16 รอบ 2 (SPEC_ENTRY_UX_V2_2026-09-16.md §6 B7): เดิม (รอบแรก, PR #4)
   ผูกเส้นคาดซ้ายกับ `#card-code.card--selected` — แปลว่าเส้นคาดคือตัวบ่งชี้ "การ์ด
   รหัสกำลังคลี่อยู่" ไม่ใช่ "การ์ดรหัสคือทางที่แนะนำ" ผิดเจตนาเดิมของ mockup ทันทีที่
   ระบบมีแนวคิด "การ์ดไหนขึ้นก่อน" จาก entry_card_order/entry_primary (§5) เพราะ
   ถ้าแอดมินตั้งให้การ์ดอีเมลเป็นทางหลัก แต่ผู้เรียนดันกดคลี่การ์ดรหัสดู เส้นคาดก็จะ
   ย้ายไปอยู่ที่การ์ดรหัสผิดที่ทันที ทั้งที่ยังไม่ได้เปลี่ยนทางแนะนำจริง

   ย้ายเส้นคาดไปเป็น `.card--primary` ที่ quiz.js/program.js เติม/ถอดเองตาม
   `state.entryPrimary` (ดู cardCodeFoldedHtml/cardCodeOpenHtml/cardEmailFoldedHtml/
   cardEmailOpenHtml) มีผลทั้งตอนพับและคลี่เท่ากัน ไม่ผูกกับ id หรือสถานะเปิด/พับอีก
   ต่อไป — `.card--selected`/`.card--neutral` เหลือหน้าที่แค่จองความกว้าง border
   2px ไว้เท่ากันทุกสถานะ (กันคอนเทนต์ขยับตอนสลับสี) ไม่ใช่คนกำหนดสีอีกต่อไป */
.card--primary { border-left-color: var(--color-signature); }

/* fix 2026-09-16 รอบ 3 (ผลตรวจรอบสอง ข้อ D, design-director + ux-critic ยกตรงกัน):
   เดิม `#card-code .card-eyebrow { font-size: var(--text-md) }` และ
   `#card-email .card-eyebrow { color: var(--color-ink-soft) }` ยังผูกกับ id ตายตัว
   จากสมัยที่การ์ดรหัสเป็น "ทางหลัก" เสมอ — ทันทีที่เส้นคาดย้ายไป `.card--primary`
   ตามลำดับที่สลับได้แล้ว (บล็อกด้านบน) น้ำหนักตัวอักษรกับเส้นคาดจะสวนทางกันได้:
   คลาสที่การ์ดบัญชีเป็นทางหลัก จะเห็นเส้นแดงอยู่ใบบน แต่ตัวอักษรใหญ่กว่า/เข้มกว่า
   (ปกติของ #card-code) ไปอยู่ใบล่างแทน — ย้ายน้ำหนัก+สีไปผูกกับ `.card--primary`
   เหมือนเส้นคาดด้วย: การ์ดที่เป็นทางหลัก (ไม่ว่าจะเป็นรหัสหรือบัญชี) ได้ตัวอักษร
   ใหญ่กว่า การ์ดรองได้สีจาง — ยังคง scope ด้วย id `#card-code`/`#card-email` ไว้
   (ไม่ใช้ `.card` เปล่าๆ) กัน `.card-eyebrow` ของจอไม่เกี่ยวข้อง (เช่น "Check your
   inbox" ใน showCheckInbox ซึ่งไม่เคยมี `.card--primary`) โดนอานิสงส์ตามไปด้วย */
#card-code.card--primary .card-eyebrow,
#card-email.card--primary .card-eyebrow {
  font-size: var(--text-md);
}
#card-code:not(.card--primary) .card-eyebrow,
#card-email:not(.card--primary) .card-eyebrow {
  color: var(--color-ink-soft);
}

.segtabs {
  display: flex;
  background: var(--color-surface-sunken);
  border-radius: var(--radius-sm);
  padding: 3px;
  margin-bottom: var(--space-5);
  gap: 3px;
}
.segtabs__tab {
  flex: 1;
  border: 0;
  text-align: center;
  font-size: var(--text-sm);
  font-weight: var(--weight-strong);
  padding: var(--space-2) 0;
  border-radius: 3px;
  color: var(--color-ink-soft);
  cursor: pointer;
  font-family: inherit;
}
.segtabs__tab--active {
  background: var(--surface-0);
  color: var(--color-ink);
  box-shadow: var(--shadow-sm);
}

.steplist { margin: var(--space-4) 0; padding: 0; list-style: none; counter-reset: step; }
.steplist li {
  counter-increment: step;
  position: relative;
  padding-left: 1.75rem;
  font-size: var(--text-sm);
  color: var(--color-ink-soft);
  margin-bottom: var(--space-2);
}
.steplist li::before {
  content: counter(step);
  position: absolute; left: 0; top: 0;
  width: 1.25rem; height: 1.25rem;
  border-radius: var(--radius-pill);
  background: var(--color-surface-sunken);
  color: var(--color-ink);
  font-family: var(--font-mono);
  font-size: .6875rem;
  font-weight: 600;
  display: flex; align-items: center; justify-content: center;
}

/* ============================== Badges & status ============================== */

.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: .1875rem .5rem;
  border-radius: var(--radius-sm);
  font-size: var(--text-xs);
  font-weight: var(--weight-strong);
  text-transform: uppercase;
  letter-spacing: .02em;
  font-family: var(--font-mono);
}
.badge::before { content: ""; width: 6px; height: 6px; border-radius: var(--radius-pill); flex: none; }

.badge--open      { background: color-mix(in srgb, var(--color-signature) 8%, white); color: var(--color-primary-active); }
.badge--open::before      { background: var(--color-signature); }
.badge--submitted { background: color-mix(in srgb, var(--color-ok) 8%, white); color: var(--color-ok); }
.badge--submitted::before { background: var(--color-ok); }
.badge--results   { background: var(--color-primary); color: var(--color-on-primary); }
.badge--results::before { background: var(--color-on-primary); }
.badge--locked    { background: var(--color-surface-sunken); color: var(--color-ink-soft); }
.badge--locked::before    { background: var(--color-ink-faint); }
.badge--waiting   { background: var(--color-surface-sunken); color: var(--color-ink-soft); }
.badge--waiting::before   { background: var(--color-ink-faint); }

.statusdot {
  display: inline-block;
  width: 8px; height: 8px;
  border-radius: var(--radius-pill);
  background: var(--color-signature);
  flex: none;
}

/* ============================== HR dashboard (Part K4) ============================== */
/* อ่านอย่างเดียวทั้งหน้า — ไม่มี interactive control ใดๆ นอกจาก <details> เปิด-ปิดรายชื่อ */

.tags { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-2); }
.tag {
  padding: .1875rem var(--space-3);
  border-radius: var(--radius-pill);
  border: 1px solid var(--color-rule);
  font-size: var(--text-xs);
  color: var(--color-ink-soft);
}

.statbox { text-align: left; }
.statbox__value {
  font-size: var(--text-2xl, 1.75rem);
  font-weight: var(--weight-strong);
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
.statbox__label { margin-top: .125rem; }

.hrsummary {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-6);
}
.hrsummary__modules { min-width: 0; }

.hrround__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
}
.hrround__title { margin: .125rem 0 0; font-size: var(--text-lg, 1.125rem); }

.hrcohort {
  margin-top: var(--space-5);
  padding-top: var(--space-5);
  border-top: 1px solid var(--color-rule);
}
.hrcohort__stats { display: flex; gap: var(--space-8); margin-bottom: var(--space-5); }

.hrdist {
  display: flex;
  align-items: flex-end;
  gap: var(--space-3);
  height: 5.5rem;
  margin-bottom: var(--space-5);
}
.hrdist__col { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; height: 100%; }
.hrdist__bar {
  width: 100%;
  max-width: 2.25rem;
  min-height: 2px;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  background: var(--color-signature);
  opacity: .8;
}
.hrdist__count { font-size: var(--text-xs); margin-top: var(--space-1); }
.hrdist__label { margin-top: 1px; }

.hrdomains { display: flex; flex-direction: column; gap: var(--space-2); }
.hrdomains__row { display: grid; grid-template-columns: 8rem 1fr 2.5rem; align-items: center; gap: var(--space-3); }
.hrdomains__name { font-size: var(--text-sm); color: var(--color-ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hrdomains__track { height: 6px; border-radius: var(--radius-pill); background: var(--color-surface-sunken); overflow: hidden; }
.hrdomains__fill { height: 100%; border-radius: inherit; background: var(--color-signature); }
.hrdomains__pct { font-size: var(--text-xs); text-align: right; }

.hrroster { margin-top: var(--space-4); }
.hrroster > summary { cursor: pointer; color: var(--color-ink-soft); }
.hrroster__list { margin-top: var(--space-3); display: flex; flex-direction: column; gap: 1px; background: var(--color-rule); border-radius: var(--radius-sm); overflow: hidden; }
.hrroster__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  background: var(--color-surface);
}
.hrroster__name { margin: 0; font-size: var(--text-sm); }
.hrroster__aside { display: flex; align-items: center; gap: var(--space-3); flex: none; }
.hrroster__pct { font-size: var(--text-sm); font-weight: var(--weight-strong); color: var(--color-ink); }

@media (max-width: 640px) {
  .hrcohort__stats { gap: var(--space-5); }
  .hrdomains__row { grid-template-columns: 5.5rem 1fr 2.25rem; }
}

/* ============================== Pre/Post comparison — SHARED renderer (K5-H3 v2) ==============================
 * markup มาจาก prepost-render.js เดียวกันเป๊ะทั้ง HR (ที่นี่) และ admin
 * (report.css) — เจ้าของสั่งตรงๆ "report ของหน้า admin และ hr report กรณีที่
 * เป็น pre post ควรจะเหมือนกันทุกอย่าง" คลาส .pp-* ชื่อเดียวกันทั้งสองไฟล์
 * แค่ค่า token ต่างกันตามระบบสีของแต่ละหน้า (ที่นี่ = R6)
 *
 * โทนสี: After = --color-signature (แดง CONC — สีเดียวกับที่ .hrdomains__fill/
 * .hrdist__bar ใช้อยู่แล้วในหน้านี้ทั้งหน้า, ตรงตาม design.md §2.1), Before =
 * เทาเข้ม (graphite, ค่าอ้างอิง) · delta +/- ยังใช้ --color-ok/--color-err
 * เขียว/แดงเหมือนเดิมทั้งระบบ */
.pp-report { margin: 0 0 var(--space-6); }
.pp-muted { color: var(--color-ink-faint); }
.pp-h3 { margin: var(--space-5) 0 var(--space-2); font-size: var(--text-lg, 1.125rem); font-weight: var(--weight-strong); }
.pp-note { margin: var(--space-2) 0 0; font-size: var(--text-xs); color: var(--color-ink-soft); }
.pp-section { margin-top: var(--space-4); }
.pp-delta--ok  { color: var(--color-ok); font-weight: var(--weight-strong); }
.pp-delta--err { color: var(--color-err); font-weight: var(--weight-strong); }

.pp-topbar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding-bottom: var(--space-3);
  margin-bottom: var(--space-2);
  border-bottom: 1px solid var(--color-rule);
}
.pp-logo { display: block; height: 28px; width: auto; object-fit: contain; }
.pp-topbar__t1 { margin: 0; font-weight: var(--weight-strong); font-size: var(--text-lg, 1.125rem); }
.pp-topbar__t2 { margin: .125rem 0 0; font-size: var(--text-xs); color: var(--color-ink-soft); }

.pp-hero {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--space-4);
  margin: var(--space-4) 0;
}
.pp-hero__card {
  border: 1px solid var(--color-rule);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  background: var(--color-surface-sunken);
}
.pp-hero__card--after {
  background: color-mix(in srgb, var(--color-signature) 8%, var(--color-surface-sunken));
  border-color: color-mix(in srgb, var(--color-signature) 30%, transparent);
}
.pp-hero__label { margin: 0 0 .375rem; font-size: var(--text-sm); font-weight: var(--weight-strong); color: var(--color-ink-soft); }
.pp-hero__num { margin: 0; font-size: 2.25rem; font-weight: var(--weight-strong); font-variant-numeric: tabular-nums; line-height: 1; }
.pp-hero__card--after .pp-hero__num { color: var(--color-signature); }
.pp-hero__unit { font-size: 1.25rem; font-weight: 500; color: var(--color-ink-faint); }
.pp-hero__arrow { display: flex; flex-direction: column; align-items: center; gap: .25rem; }
.pp-hero__arrowicon { font-size: 1.5rem; color: var(--color-ink-faint); }

.pp-legend { display: flex; gap: var(--space-4); margin: var(--space-2) 0; }
.pp-legend__item { display: inline-flex; align-items: center; gap: .375rem; font-size: var(--text-xs); color: var(--color-ink-soft); }
.pp-legend__swatch { display: inline-block; width: 10px; height: 10px; border-radius: 3px; }
.pp-legend__swatch--before { background: var(--color-ink-faint); }
.pp-legend__swatch--after  { background: var(--color-signature); }

.pp-dist-svg { display: block; width: 100%; max-width: 640px; height: auto; }
.pp-dist-before { fill: var(--color-ink-faint); }
.pp-dist-after  { fill: var(--color-signature); }
.pp-dist-n { font-size: 11px; font-weight: var(--weight-strong); fill: var(--color-ink-soft); }
.pp-dist-x { font-size: 10px; fill: var(--color-ink-soft); }
.pp-dist-axis { stroke: var(--color-rule); }

.pp-topics { display: flex; flex-direction: column; gap: var(--space-3); }
.pp-topic { border: 1px solid var(--color-rule); border-radius: var(--radius-md); padding: var(--space-3); }
.pp-topic__head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: var(--space-2); }
.pp-topic__name { font-weight: var(--weight-strong); font-size: var(--text-sm); }
.pp-topic__track { height: 6px; border-radius: var(--radius-pill); background: var(--color-surface-sunken); overflow: hidden; margin-bottom: 3px; }
.pp-topic__track:last-of-type { margin-bottom: 0; }
.pp-topic__fill { height: 100%; border-radius: inherit; }
.pp-topic__fill--before { background: var(--color-ink-faint); }
.pp-topic__fill--after  { background: var(--color-signature); }

.pp-tablewrap { overflow-x: auto; }
.pp-table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); margin-top: var(--space-2); }
.pp-table th { text-align: left; padding: .4375rem .5625rem; font-weight: var(--weight-strong); font-size: var(--text-xs); color: var(--color-ink-soft); border-bottom: 1px solid var(--color-rule); }
.pp-table td { padding: .4375rem .5625rem; border-bottom: 1px solid var(--color-rule); }
.pp-table tbody tr:nth-child(even) { background: var(--color-surface-sunken); }
.pp-num { text-align: right; font-variant-numeric: tabular-nums; }

@media (max-width: 640px) {
  .pp-hero { grid-template-columns: 1fr; }
  .pp-hero__arrow { flex-direction: row; justify-content: center; }
}

/* ============================== The spine ============================== */
/* Signature pattern (DESIGN-R6.md §3): a persistent red rule that list-
   structured screens sit against — never per-row color, always one
   continuous structural line. Dropping it silently reverts red to "accent
   only" — every spine-eligible screen this run keeps it. */

.spinelist {
  position: relative;
  padding-left: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: 1px;
  background: var(--color-rule);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.spinelist::before {
  content: "";
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 2px;
  background: var(--color-signature);
}

.spinerow {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: var(--space-4);
  width: 100%;
  padding: var(--space-5) var(--space-5) var(--space-5) var(--space-3);
  background: var(--color-surface);
  text-align: start;
  transition: background var(--dur-fast) var(--ease-out);
}
a.spinerow, button.spinerow { cursor: pointer; }
a.spinerow:hover, button.spinerow:hover { background: var(--color-surface-sunken); }
.spinerow--disabled { color: var(--color-ink-faint); cursor: default; }
.spinerow--disabled:hover { background: var(--color-surface); }

.spinerow__title {
  font-size: var(--text-md);
  font-weight: var(--weight-strong);
  color: var(--color-ink);
  margin: 0 0 .25rem;
}
.spinerow--disabled .spinerow__title { color: var(--color-ink-faint); }

.spinerow__note {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-sm);
  color: var(--color-ink-soft);
  max-width: 60ch;
}

.spinerow__aside {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* F3 (design-learner-prom/REVIEW.md) — แถวทั้งแถวกดได้แต่ไม่มีอะไรบอก: ลูกศร
   ท้ายแถวตัวเดียวกับ .btn__arrow ของระบบเอง ใส่เฉพาะแถวที่กดได้จริง
   (ไม่ใส่ใน .spinerow--disabled) · .rowhint คือบรรทัดคำสั่งสั้นเหนือรายการ
   ครั้งเดียวต่อจอ ไม่ใช่ต่อแถว */
.spinerow__go { color: var(--color-ink-faint); font-size: var(--text-lg); line-height: 1; }
.spinerow:hover .spinerow__go { color: var(--color-primary); }

.rowhint { font-size: var(--text-xs); color: var(--color-ink-soft); margin: 0 0 var(--space-2); }

@media (max-width: 30rem) {
  .spinerow { grid-template-columns: 1fr; row-gap: var(--space-2); }
  .spinerow__aside { justify-self: start; }
}

/* ── Learner class list — surface parity (BRIEF_SURFACE_PARITY.md §3, §6) ──
   ไม่มี hardcode สี ไม่มี token ใหม่ ใช้เฉพาะ --color-* / --space-* / --text-* /
   --font-* เดิม · ทุกอย่างเป็น modifier บน .spinerow / .spinerow__note ที่มีอยู่
   แล้ว ไม่สร้างคลาสคู่ขนาน (structural thesis ของบรีฟ: ต่างด้วย modifier บน
   ของชิ้นเดิม ไม่ใช่คลาสคนละชุด — กันไม่ให้ซ้ำรอย P2 ที่สันแดงถูกนิยามสองที่) */

/* .spinerow เดิมเป็น grid 2 ช่อง (ด้านบน) โดยบรรทัดรองถูกห่อไว้ในลูกตัวแรก
   พอยุบเป็นคอลัมน์เดียวบนมือถือ ลำดับจึงบังคับไม่ได้ · modifier นี้แบนลูกทั้งสาม
   ออกมาเป็นลูกตรงของ .spinerow แล้ววางด้วย grid-template-areas จึงสั่งลำดับ
   บนมือถือแยกจากเดสก์ท็อปได้: ชื่อคลาส → ความคืบหน้า → โปรแกรม/รหัส
   (ลำดับตาม BRIEF_SURFACE_PARITY.md §6) */
.spinerow--meta {
  grid-template-columns: 1fr auto;
  grid-template-areas:
    "title aside"
    "note  aside";
  align-items: start;
  row-gap: .25rem;
}
.spinerow--meta > .spinerow__title { grid-area: title; margin: 0; }
.spinerow--meta > .spinerow__aside { grid-area: aside; align-self: center; }
.spinerow--meta > .spinerow__note  { grid-area: note; }

@media (max-width: 30rem) {
  .spinerow--meta {
    grid-template-columns: 1fr;
    grid-template-areas: "title" "aside" "note";
    row-gap: var(--space-2);
  }
  .spinerow--meta > .spinerow__aside { justify-self: start; align-self: start; }
  /* แถวที่ไม่มี badge (เช่น Learning Materials) เหลือแต่ลูกศร › ตัวเดียวใน aside
     ถ้าปล่อยชิดซ้ายมันจะลอยเดี่ยวใต้ข้อความ (design-director 2026-09-03) — ดันไปขวา */
  .spinerow__aside:has(> .spinerow__go:only-child) { justify-self: end; }
}

/* บรรทัดรองของแถวคลาส — ใช้ .spinerow__note ตัวเดิมข้างบน ต่อด้วย modifier ตัวนี้
   เพราะบรรทัดนี้เป็นร้อยแก้ว (ชื่อโปรแกรม) ปนรหัส ไม่ใช่ตัวเลขล้วนแบบที่
   .spinerow__note ตั้งใจไว้เดิม · ตัวหนังสือกลับเป็นฟอนต์เนื้อความ เหลือ mono
   เฉพาะรหัสคลาสที่เป็นรหัสจริง (design.md §3: mono สำหรับรหัส/ตัวเลข) */
.spinerow__note--prose { font-family: var(--font-sans); }
.spinerow__note--prose code {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-ink-soft);
}
.spinerow__note--prose .sep { color: var(--color-ink-faint); }

/* ความคืบหน้า = จำนวนรอบที่ส่งแล้ว (เจ้าของตัดสิน 2026-09-02 — ไม่แสดงคะแนน)
   ตัวเลขล้วน ไม่มีแถบ ไม่มีวงแหวน: รอบแรกลองใส่แถบแบ่งช่วงเล็ก ๆ แล้วบนจอจริง
   มันอ่านเป็นขีดแดงหลงทาง ไม่ได้อ่านเป็นความคืบหน้า · mono + tabular-nums
   ทำให้เลขทุกแถวตรงหลักกัน กวาดสายตาลงในแนวตั้งได้โดยไม่ต้องมีกราฟิกช่วย */
.rounds {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-sm);
  color: var(--color-ink-soft);
  white-space: nowrap;
}

/* ============================== Forms ============================== */

.field { margin-bottom: var(--space-5); }
.field__label {
  display: block;
  font-size: var(--text-sm);
  font-weight: var(--weight-label);
  color: var(--color-ink-soft);
  margin-bottom: var(--space-2);
}
/* แก้ตามผลตรวจรอบสาม (visual-critic VIS-001) — --color-ink-faint (graphite-400,
   3.74:1 บน white ตาม tokens.css:42) ทำเครื่องหมายไว้แล้วว่า "faint/disabled/
   placeholder text only" ไม่ใช่โทเค็นสำหรับข้อความเนื้อหาจริงที่ต้องอ่านออก —
   .field__hint คือบรรทัดเดียวบนจอทางเข้าที่บอกว่าไปเอารหัสมาจากไหน วัดได้แค่
   3.24-3.31:1 ที่ขนาดจริง ต้อง ≥4.5:1 — เปลี่ยนไปใช้ --color-ink-soft (graphite-600,
   6.58:1) ซึ่งเป็นโทเค็นที่มีอยู่แล้วในระบบ ไม่ได้ตั้งค่าสีใหม่ */
.field__hint {
  font-size: var(--text-xs);
  color: var(--color-ink-soft);
  margin-top: var(--space-2);
}

.input {
  width: 100%;
  height: 2.75rem;
  padding: 0 var(--space-4);
  background: var(--color-surface);
  border: 1px solid var(--color-rule);
  border-radius: var(--radius-sm);
  color: var(--color-ink);
}
/* แก้ตามผลตรวจรอบสาม (visual-critic VIS-001) — วัดได้ 3.66:1 ที่ขนาดจริงในช่อง
   กรอกรหัสของจอทางเข้า (B1's ตัวอย่าง "เช่น 10001" ฯลฯ) --color-ink-faint เป็น
   โทเค็นเดียวกับ .field__hint ด้านบนที่พึ่งแก้ไป (ดูเหตุผลเต็มที่นั่น) — เปลี่ยนไปใช้
   --color-ink-soft (6.58:1) ที่มีอยู่แล้วเหมือนกัน แม้ปกติ placeholder จะจางกว่า
   ข้อความจริงได้ตามธรรมเนียม UI แต่ที่นี่ placeholder เป็นตัวอย่างรูปแบบข้อมูลที่
   ต้องพิมพ์ (ไม่ใช่แค่ ghost text ตกแต่ง) จึงต้องอ่านออกจริงเท่าเนื้อหาอื่น */
.input::placeholder { color: var(--color-ink-soft); }
.input:disabled { opacity: .6; }

.msg {
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  margin-bottom: var(--space-4);
}
.msg--err  { background: color-mix(in srgb, var(--color-err) 10%, white); color: var(--color-err); }
.msg--info { background: var(--color-surface-sunken); color: var(--color-ink-soft); }

/* ============================== Modal ============================== */

.modal-backdrop {
  position: fixed; inset: 0;
  background: var(--scrim);
  display: flex; align-items: center; justify-content: center;
  padding: var(--space-4);
  z-index: 50;
}
/* Author-stylesheet `display: flex` above beats the UA stylesheet's
   `[hidden] { display: none }` in the cascade (author always wins over
   UA regardless of selector specificity) — restate it explicitly or the
   modal renders open by default. Caught by screenshot, not by reading
   the CSS. */
.modal-backdrop[hidden] { display: none; }
.modal {
  width: 100%;
  max-width: 26rem;
  background: var(--color-surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}
.modal--sm { max-width: 22rem; }
/* Results dashboard R5 (A0): named token for the 32rem width the Publish Class
   modal already used as an inline style — same value, now a reusable class
   instead of ad hoc sizing, no new visual size introduced. */
.modal--md { max-width: 32rem; }
/* กล่องที่เนื้อในสูงเกินจอได้ (เช่น Account settings ที่มี 3 ฟอร์มซ้อนกัน ดู
   public/assets/me.js) — หัวกล่องอยู่กับที่ ให้เฉพาะ body เลื่อน ไม่งั้นหัวข้อกับ
   ปุ่มปิดหลุดขอบจอบนโน้ตบุ๊กจอเตี้ยจนกดปิดไม่ได้ */
.modal--tall {
  display: flex;
  flex-direction: column;
  max-height: calc(100vh - var(--space-8));
}
.modal--tall .modal__body { overflow-y: auto; }
.modal__head {
  display: flex; align-items: center;
  padding: var(--space-5) var(--space-5) 0;
}
.modal__title { font-size: var(--text-lg); font-weight: var(--weight-strong); }
.modal__close { margin-left: auto; color: var(--color-ink-soft); font-size: var(--text-lg); line-height: 1; }
.modal__close:hover { color: var(--color-ink); }
.modal__body { padding: var(--space-5); }
.modal__foot {
  display: flex; justify-content: flex-end; gap: var(--space-3);
  padding: 0 var(--space-5) var(--space-5);
}

/* หลายฟอร์มในกล่องเดียว — คั่นด้วยเส้นแทนที่จะซ้อนการ์ดในการ์ด (ดู me.js
   settingsFormsHtml) */
.settings-group h3 { margin: 0 0 .75rem; font-size: var(--text-md); }
.settings-group + .settings-group {
  margin-top: var(--space-6);
  padding-top: var(--space-6);
  border-top: 1px solid var(--color-rule);
}

/* ============================== Page furniture ============================== */

.eyebrow {
  font-size: var(--text-sm);
  font-weight: var(--weight-strong);
  color: var(--color-primary);
  text-transform: uppercase;
  letter-spacing: .06em;
  margin-bottom: var(--space-2);
}
.hero { margin-bottom: var(--space-8); }
/* หัวข้อใหญ่ใช้ serif — เซริฟที่ขนาดนี้ไม่ต้องการ tracking ติดลบ
   (ระยะห่างมันถูกออกแบบมาแล้วสำหรับงานพิมพ์ บีบเพิ่มจะทึบ) */
.hero h1 {
  font-family: var(--font-display);
  font-size: var(--text-display);
  font-weight: var(--weight-display);
  line-height: 1.2;
  margin-bottom: var(--space-2);
  text-wrap: balance;
}
/* ชื่อคลาสพิมพ์เองโดยแอดมิน มักผสมอังกฤษ+ไทยในสตริงเดียว (เช่น "... รุ่น 2") —
   เดิม Playfair Display ไม่มี glyph ไทยเลย ตัวไทยเลยหลุดไป fallback เป็น
   Noto Serif Thai กลางประโยค ดูเป็นรอยต่อ 2 ฟอนต์ชัดเจนตอนขนาดใหญ่แบบนี้
   จึง override เป็น --font-sans ล้วนไว้ก่อน (รอบ 7)

   รอบ 8 (2026-08-11): --font-display เลิกใช้ Noto Serif Thai แล้ว ไทยตกไป
   Noto Sans Thai ตัวเดียวกับ --font-sans อยู่แล้ว (ดู tokens.css, design.md
   §10 Amendment 9) — override นี้จึงกลายเป็น no-op (สองตัวแปรให้ผลไทยเท่ากัน
   ต่างกันแค่ Latin: --font-sans ไม่มี Playfair) ลบทิ้ง กลับไปใช้ .hero h1
   ปกติ ผลคือ Latin ในชื่อคลาสที่ผสมอังกฤษ/ไทยกลับมาเป็น Playfair Display
   เหมือน .hero h1 อื่น ส่วนไทยยังเป็น Noto Sans Thai เหมือนเดิม ไม่มีรอยต่อ
   2 ฟอนต์ต่างจากเดิม เพราะแค่ font-family เท่านั้นที่เปลี่ยน */
.lede { color: var(--color-ink-soft); font-size: var(--text-md); }

.page-title {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: var(--weight-display);
  margin-bottom: var(--space-5);
}

/* F6 (design-learner-prom/REVIEW.md) — หัวจอแบบเครื่องมือ: ชื่อ + บรรทัดสรุป
   ตัวเลขจริงจากฐานข้อมูล (mono) + ปุ่มหลักปุ่มเดียวที่เป็น "สิ่งที่ต้องทำต่อ"
   แทนที่ .page-title เดี่ยว ๆ เฉพาะจอที่มีสิ่งที่ต้องทำต่อให้ชี้ (เช่นรายการรอบ) */
.next {
  display: grid;
  gap: var(--space-2);
  padding: var(--space-4) 0 var(--space-5);
}
.next__title {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: var(--weight-display);
  line-height: 1.2;
  margin: 0;
}
/* N4 (Ray, revision round 3): กติกาเดียวกับ A1/A2 ที่ .classhome ปิดไปแล้ว — ".next"
 * ใช้เฉพาะในสคริปต์ผู้เรียน (quiz.js เท่านั้น ไม่มีสคริปต์อื่นอ้างถึง .next__facts เลย —
 * ตรวจแล้วด้วย grep) แก้ตรงนี้จึงไม่มีความเสี่ยงรั่วออกนอก learner surface เหมือน
 * .spinerow ที่ใช้ร่วมกันหลายสคริปต์ ประโยคสรุปเป็น sans เสมอ ตัวเลขห่อ numSpan() แยก
 * เป็นจุด ๆ จาก quiz.js */
.next__facts {
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  color: var(--color-ink-soft);
  margin: 0;
}
.next .btn { margin-top: var(--space-2); }

.stack { display: flex; flex-direction: column; gap: var(--space-3); }

/* ซ่อมของเดิม (visual-critic VIS-002, commit ก่อน PR นี้ 4ee2abf/cab3049 —
   ไม่ใช่ของที่ PR นี้ทำพัง เจ้าของสั่งรวมมาแก้รอบนี้): .card ไม่มี margin ของตัวเอง
   เลย (ดู .card ด้านบน) และ #app (.wrap) เป็น block flow ธรรมดา ไม่มี gap คั่นระหว่าง
   ลูกเหมือน .stack — เวลา .card ตามด้วย .stack ทันที (showConfirm/showPasswordSetup/
   showPasswordPrompt) ขอบล่างการ์ดจึงชนหัวปุ่มพอดี (0px) ทั้งที่ปุ่มสองใบใน .stack
   เองห่างกัน 12px (var(--space-3)) — เกิด "ลิ่มขาว" ที่มุมโค้งล่างของการ์ดทับบน
   พื้นแดงของปุ่มด้วย เพราะไม่มีระยะห่างให้มุมโค้งได้อยู่บนพื้นหลังหน้าปกติเลย
   ใช้ token เดียวกับระยะปุ่ม→ปุ่มใน .stack เอง (var(--space-3)) ให้ระยะการ์ด→ปุ่ม
   เท่ากันอย่างน้อย ไม่ต้องคิดค่าใหม่ */
.card + .stack { margin-top: var(--space-3); }

/* ============================== Nav shells ============================== */

.homenav, .learnernav {
  position: sticky;
  top: 0;
  z-index: 30;
  height: 68px;
  background: color-mix(in srgb, var(--color-paper) 88%, transparent);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--color-rule);
}
.homenav__inner, .learnernav__inner {
  max-width: 1080px;
  height: 100%;
  margin: 0 auto;
  padding: 0 var(--space-4);
  display: flex;
  align-items: center;
  gap: var(--space-6);
}
.homenav__brand, .learnernav__home {
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  font-size: var(--text-md);
  letter-spacing: var(--tracking-latin);
}
/* ทั้งสองใช้ --font-sans (Noto Sans Thai) อยู่แล้วผ่าน inheritance เหมือนกัน — ต่างกันแค่
   น้ำหนัก (700 ทั่วระบบ vs 600 เฉพาะหน้าแรกตาม R7) ทำให้ตัวหนังสือคำเดียวกันดู
   ไม่เหมือนกันเวลาเทียบสองหน้า ให้ learnernav ใช้น้ำหนักเดียวกับหน้าแรกด้วย */
.learnernav__home { font-weight: var(--home-weight-display); }
.homenav__tabs { display: flex; gap: var(--space-6); margin-right: auto; margin-left: var(--space-8); }
.hometab { color: var(--color-ink-soft); font-size: var(--text-sm); font-weight: var(--weight-label); }
.hometab:hover { color: var(--color-ink); }
.homenav__enter { margin-left: auto; }
/* ซ่อนปุ่ม nav ไว้ก่อน โชว์เฉพาะหลังเลื่อนผ่านปุ่มเดียวกันใน hero ไปแล้ว
   (home.js ใส่ .is-armed/.is-visible ให้ — ไม่มี JS ก็ยังเห็นปุ่มปกติ ไม่มีทาง
   หายไปเฉยๆ) กันปุ่ม "Enter class" ซ้ำสองจุดบนจอเดียว */
.homenav__enter.is-armed {
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-med) var(--ease-out);
}
.homenav__enter.is-armed.is-visible {
  opacity: 1;
  pointer-events: auto;
}
@media (prefers-reduced-motion: reduce) {
  .homenav__enter.is-armed { transition: none; }
}

/* F4 (design-learner-prom/REVIEW.md) — เดิมมีปุ่ม "‹ Class home" ที่มุมซ้ายบน
   (.learnernav__back) ซ้ำกับทางกลับที่อยู่ในเนื้อจอเองอยู่แล้ว (← Class home /
   ← All quizzes) และบน Class home เองปุ่มนั้นชี้กลับหาตัวเอง — ตัดปุ่มนี้ออก
   ทั้งหมด (ดู renderNav ใน quiz.js) เหลือ wordmark เป็นชื่อแบรนด์เฉยๆ ไม่ใช่
   ลิงก์ (กันปัญหาก่อน 2026-08-24 ที่ wordmark เคยลิงก์ออกจากคลาสไปหน้าแรกเว็บ
   โดยไม่มีใครรู้ตัว) ทางกลับที่แท้จริงมีทางเดียวคือของในเนื้อจอ */
.learnernav__home--static { color: var(--color-ink-soft); }

.learnernav__logos { display: flex; align-items: center; gap: var(--space-3); margin-left: var(--space-4); }
.learnernav__logo { height: 40px; width: auto; }
.learnernav__sep { width: 1px; height: 20px; background: var(--color-rule); }

.learnernav__student {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--space-4);
}
.screen-language-toggle {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 1px;
  padding: 2px;
  border: 1px solid var(--color-rule);
  border-radius: var(--radius-sm);
  background: var(--color-surface-sunken);
  white-space: nowrap;
}
.screen-language-toggle + .learnernav__student { margin-left: 0; }
.screen-language-toggle__button {
  padding: .25rem .55rem;
  border-radius: calc(var(--radius-sm) - 2px);
  color: var(--color-ink-soft);
  font-size: var(--text-xs);
  font-weight: var(--weight-label);
}
.screen-language-toggle__button:hover { color: var(--color-ink); }
.screen-language-toggle__button.is-active {
  background: var(--color-surface);
  color: var(--color-ink);
  box-shadow: var(--shadow-sm);
}
.learnernav__meta { text-align: right; line-height: 1.3; }
.learnernav__class { font-size: var(--text-xs); color: var(--color-ink-soft); margin: 0; }
.learnernav__name { font-size: var(--text-sm); font-weight: var(--weight-strong); margin: 0; }

.classswitch { position: relative; }
.classswitch__toggle { font-size: var(--text-sm); }
.classswitch__menu {
  position: absolute; right: 0; top: calc(100% + var(--space-2));
  background: var(--color-surface);
  border: 1px solid var(--color-rule);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  min-width: 12rem;
  padding: var(--space-2);
  opacity: 0; visibility: hidden; transform: translateY(-4px);
  transition: opacity var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}
.classswitch__menu.is-open { opacity: 1; visibility: visible; transform: none; }
.classswitch__item {
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  cursor: pointer;
}
.classswitch__item:hover { background: var(--color-surface-sunken); }

/* SPEC_LEARNER_TABBAR.md §4.2 — ซ่อนโดยปริยายทุกความกว้าง เปิดเฉพาะ ≤34rem ใน
   media query ด้านล่าง (bugfix ระหว่างทดสอบจริง: <nav> เปล่าไม่มี display พิเศษ
   อะไรมาก่อน — ถ้าไม่ตั้ง none ไว้ตรงนี้ ที่ >34rem จะ render เป็น block ว่างๆ
   ท้ายหน้าแทนที่จะหายไปเลย) */
.tabbar { display: none; }
/* Contact ทางเดียวบนมือถือ — homenav__tabs (มีแท็บ Contact อยู่ในนั้น) ถูกซ่อน
   ที่ breakpoint เดียวกัน (กฎด้านล่าง) ปุ่มนี้ผูก media query เดียวกันเป๊ะๆ จึงไม่มี
   ทางเห็นซ้อนกับแท็บ Contact ที่ความกว้างใด (โจทย์เจ้าของ 2026-09-03, พอร์ตจาก
   design-entry/home-mockup.html) */
.homenav__mobile-contact { display: none; margin-left: auto; height: 2.25rem; padding: 0 var(--space-3); font-size: var(--text-sm); }

@media (max-width: 34rem) {
  .homenav__tabs { display: none; }
  .homenav__mobile-contact { display: inline-flex; }
  .learnernav__logos { display: none; }

  /* แถบบนของผู้เรียนบนมือถือ — ของในแถบรวมกันกว้างเกิน 375px แถบสูงคงที่ 68px
     ตัวหนังสือจึงตกบรรทัดซ้อนกันจนอ่านไม่ออก บีบให้เหลือบรรทัดเดียวเสมอด้วย
     สามอย่าง โดยไม่ตัดของที่จำเป็นทิ้ง:
       1) ซ่อน "ชื่อผู้เรียน" — คนใช้รู้อยู่แล้วว่าตัวเองเป็นใคร และยังดูได้ที่
          Account settings · บรรทัดบริบท (ชื่อคลาส/โปรแกรม) เก็บไว้เพราะเป็น
          สิ่งเดียวที่บอกว่าตอนนี้อยู่ในคลาสไหน
       2) บรรทัดบริบทตัดท้ายด้วย ellipsis ไม่ตกบรรทัด (ชื่อคลาสยาวได้ถึง ~80
          ตัวอักษร — ดู BRIEF_SURFACE_PARITY.md ข้อ 5)
       3) ปุ่มลิงก์ห้ามตัดคำ และลดช่องไฟระหว่างชิ้นลง

     F4 (2026-09-03): ปุ่ม "‹ Class home" (.learnernav__back) ที่เคยกิน ~95px
     (ปุ่ม 79 + margin-right space-4) ถูกตัดออกจากแถบทั้งหมด (ดู renderNav ใน
     quiz.js) — เพิ่ม max-width ของชื่อคลาสให้ใช้พื้นที่ที่ว่างลง วัดจริงแล้วว่า
     ไม่ล้นที่ 320/375 ก่อน merge (design-learner-prom checklist) */
  .learnernav__inner { gap: var(--space-3); }
  .learnernav__student { gap: var(--space-3); min-width: 0; }
  .learnernav__meta { min-width: 0; }
  .learnernav__name { display: none; }
  .learnernav__class {
    max-width: 13rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .learnernav .btn--link { white-space: nowrap; }

  /* SPEC_LEARNER_TABBAR.md §4.2 — แถบเมนูล่างผู้เรียนแทนที่ลิงก์เดิมของ
     .learnernav ที่ ≤34rem (ที่ 768/1280 .learnernav คงทุกอย่างเดิม ไม่แตะ)
     ตัดสินใจเอง: ซ่อนแค่ .classswitch__toggle (ปุ่มที่มองเห็น) ไม่ใช่
     .classswitch ทั้งก้อน — แท็บ Classes ของนักเรียน roster-only (tabbar.js
     ผ่าน quiz.js) ยังต้องเปิด #classswitch-menu เดิมได้ (SPEC §2.1 ข้อ ก,
     §4.3) ถ้าซ่อนทั้ง .classswitch เมนูจะหายไปจาก DOM tree ที่ render ได้ */
  .classswitch__toggle, .learnernav__back, .learnernav__account, #logout, #settings-open { display: none; }

  .tabbar {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 30; /* เท่า .learnernav (base.css:1108) */
    height: calc(var(--tabbar-h) + env(safe-area-inset-bottom));
    padding-bottom: env(safe-area-inset-bottom);
    display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
    background: var(--color-surface);
    border-top: 1px solid var(--color-rule);
  }
  .tabbar a, .tabbar button {
    display: grid; place-items: center; gap: 2px;
    min-height: 48px; font-size: var(--text-xs); color: var(--color-ink-soft);
    text-decoration: none; touch-action: manipulation;
    background: none; border: 0; font-family: inherit; cursor: pointer;
  }
  .tabbar [aria-current="page"] { color: var(--color-ink); font-weight: var(--weight-strong); }
  .tabbar svg { width: 20px; height: 20px; stroke: currentColor; fill: none; stroke-width: 1.75; }
  .tabbar [aria-current="page"] svg { stroke: var(--color-ink); }

  /* หลังออกจากระบบ/ยังไม่ล็อกอิน แถบว่าง ห้ามเหลือเป็นกล่องเปล่าคาดจอ (ux-critic N1) */
  .tabbar:empty { display: none; }

  /* เว้นที่ให้แถบเฉพาะตอนแถบมีของจริง — ยังไม่ล็อกอิน (.tabbar ว่างและซ่อน) ไม่ต้องเว้น (ux-critic 2026-09-03) */
  body:has(.tabbar:not(:empty)) .wrap {
    padding-bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom) + var(--space-5));
  }

  /* เปิดถ้าเจ้าของสั่งซ่อนตอนสอบ: */
  /* body[data-screen="quiz"] .tabbar { display: none; } */
}

/* .homenav/.learnernav ทั้งคู่ sticky top:0 สูง 68px (base.css:1108-1112) — ลิงก์ในหน้าเดียวกัน
   ที่กระโดดไปหา section[id] (เช่นแท็บ nav หน้าแรก) จะพาหัวข้อไปโผล่ใต้แถบพอดี บัคนี้มีอยู่แล้ว
   ในหน้าจริงก่อนรอบนี้ (โจทย์เจ้าของ 2026-09-03 พอร์ตจาก dev/home-v2.html สั่งแก้พร้อมกัน) — ตั้ง
   ทั่วไปไม่ผูก body.homepage เพราะบัคเดียวกันเกิดได้กับ section[id] อื่นที่อยู่หลัง sticky nav */
section[id] { scroll-margin-top: 6rem; }

/* ============================== Homepage hero + sections (R7 overhaul) ==============================
   Full rebuild, harvard.edu DNA (design-pilot/harvard-dna/dna.json) — owner-
   approved anti-goals: no ribbon, no bordered 3-card grid, no small red
   uppercase caption labels. Red demoted to a homepage-local accent (CTA +
   tiny kicker text only) via --home-accent*, everything else via --home-*
   tokens from tokens.css. `body.homepage` scopes the paper/ink swap so nav/
   nothing outside index.html is affected — see tokens.css comment. */

body.homepage { background: var(--home-paper); color: var(--home-ink); }

.homenav { background: color-mix(in srgb, var(--home-paper) 88%, transparent); border-bottom-color: var(--home-line); }
.homenav .homenav__brand { font-weight: var(--home-weight-display); }
.homenav .hometab:hover { color: var(--home-ink); }

/* ---- Placeholder media block — labeled, not disguised as a finished asset.
   Real photography is a separate, explicitly-approved download; see R7
   RESULT.md. Diagonal charcoal→paper gradient reads as "reserved for an
   image" rather than a broken/empty box. */
.mediaplaceholder {
  position: relative;
  overflow: hidden;
  background: linear-gradient(155deg, var(--home-charcoal) 0%, var(--home-charcoal) 40%, color-mix(in srgb, var(--home-charcoal) 25%, var(--home-paper)) 100%);
  display: flex;
  align-items: flex-end;
}
.mediaplaceholder__tag {
  margin: var(--space-3);
  padding: .3em .6em;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: rgba(255,255,255,.72);
  background: rgba(255,255,255,.1);
  border: 1px solid rgba(255,255,255,.22);
  border-radius: var(--radius-sm);
}

.homehero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  min-height: 62vh;
  display: flex;
  align-items: flex-end;
}
.homehero__media { position: absolute; inset: 0; z-index: -1; }
.homehero__media .mediaplaceholder { position: absolute; inset: 0; }
.homehero__media img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.homehero__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(0deg, rgba(10,10,11,.86) 0%, rgba(10,10,11,.5) 45%, rgba(10,10,11,.08) 75%);
}
.homehero__inner {
  position: relative;
  width: 100%;
  max-width: 1080px;
  margin: 0 auto;
  padding: var(--space-12) var(--space-4);
}
.homehero__copy { max-width: 42rem; }
.homehero__kicker {
  font-size: var(--text-sm);
  color: rgba(255,255,255,.78);
  margin-bottom: var(--space-3);
}
.homehero__word {
  font-family: var(--font-display);
  font-size: var(--text-hero);
  font-weight: var(--home-weight-display);
  color: #fff;
  line-height: 1.08;
  margin-bottom: var(--space-5);
  text-wrap: balance;
}
.homehero__reg {
  font-size: 0.4em;
  vertical-align: super;
  font-weight: var(--weight-strong);
}
.homehero__slogan {
  font-size: var(--text-lg);
  color: rgba(255,255,255,.86);
  line-height: 1.5;
  margin-bottom: var(--space-2);
}
.homehero__slogan-sub {
  font-size: var(--text-sm);
  color: rgba(255,255,255,.72);
  line-height: 1.5;
  margin-bottom: var(--space-8);
}
/* wrap: hero มี 3 ปุ่มแล้ว (Enter a class / Register / Try the demo) — จอแคบ
   ต้องขึ้นบรรทัดใหม่แทนที่จะล้นออกนอกจอ (.btn ตั้ง white-space:nowrap ไว้ ปุ่มจึงย่อเองไม่ได้) */
.homehero__actions { display: flex; flex-wrap: wrap; gap: var(--space-3); }
/* .btn--secondary's global treatment (dark ink text, --color-rule border) reads on
   light pages only — on the hero's dark photo it would be invisible. Scoped override,
   white-outline ghost, for the "Try the demo" CTA that sits here. Does not touch
   .btn--secondary anywhere else (e.g. the "No, go back" confirm-step button below). */
.homehero__actions .btn--secondary {
  border-color: rgba(255,255,255,.4);
  color: #fff;
}
.homehero__actions .btn--secondary:hover { background: rgba(255,255,255,.12); border-color: rgba(255,255,255,.6); }

/* ---- Demo CTA busy state (hero button + Tools card link) — .disabled doesn't exist
   on <a>, so a class hook covers both element types the same way. */
.js-demo-login.is-busy { pointer-events: none; opacity: .5; }

@media (max-width: 860px) {
  .homehero { min-height: 78vh; align-items: flex-end; }
  .homehero__copy { max-width: none; }
}

.homeband {
  padding: var(--space-12) var(--space-4);
  border-top: 1px solid var(--home-line);
}
.homeband__inner { max-width: 1080px; margin: 0 auto; }
.homeband--inverse {
  background: var(--home-charcoal);
  border-top: none;
  color: #fff;
}
.homeband--muted { background: var(--home-paper-raised); }

.homeband__eyebrow {
  font-family: var(--font-display);
  font-size: var(--text-display);
  font-weight: var(--home-weight-display);
  line-height: 1.15;
  color: var(--home-ink);
  max-width: 20ch;
  margin-bottom: var(--space-8);
  text-wrap: balance;
}
.homeband--inverse .homeband__eyebrow { color: #fff; }
.homeband--inverse .homeband__lede { color: rgba(255,255,255,.72); }

.homeband__lede { color: var(--home-ink-soft); font-size: var(--text-md); max-width: 48ch; }

.steps {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-8);
}
.step__num {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: var(--home-weight-display);
  /* conc-600, not --home-accent (conc-700) — checked by hand: conc-700 on
     --home-charcoal measures 3.03:1 (borderline, since 600-weight text
     isn't safely "bold" for the WCAG large-text 3:1 exception); conc-600
     measures 4.03:1 here, real margin. Matches tokens.css's own primitive
     rule: conc-600 is for large display text >=24px, exactly this case. */
  color: var(--color-signature);
  margin-bottom: var(--space-2);
}
.step__title {
  font-size: var(--text-md);
  font-weight: var(--weight-strong);
  color: #fff;
  margin-bottom: var(--space-2);
}
.step__body { color: rgba(255,255,255,.72); font-size: var(--text-sm); }

/* ---- Tools / For organisations — media+text list (harvard.edu's
   b-media-text pattern), replacing the bordered 3-card grid. NOT .modcard —
   that class is shared with Class Home's Learning Materials list
   (quiz.js), which this round does not touch. */
.homefeature-list { display: flex; flex-direction: column; gap: var(--space-10); }
.homefeature {
  display: grid;
  grid-template-columns: minmax(0, 18rem) 1fr;
  gap: var(--space-8);
  align-items: center;
}
.homefeature__media { aspect-ratio: 4 / 3; border-radius: var(--radius-md); overflow: hidden; }
.homefeature__media img { display: block; width: 100%; height: 100%; object-fit: cover; }
.homefeature__title { font-size: var(--text-lg); font-weight: var(--weight-strong); margin-bottom: var(--space-2); }
.homefeature__body { color: var(--home-ink-soft); font-size: var(--text-md); max-width: 42ch; }
.homefeature__body + .link-arrow { display: inline-block; margin-top: var(--space-3); }

@media (max-width: 48rem) {
  .steps { grid-template-columns: 1fr; }
  .homefeature { grid-template-columns: 1fr; }
  .homefeature__media { aspect-ratio: 16 / 9; }
}

.homefoot {
  border-top: 1px solid var(--home-line);
  padding: var(--space-8) var(--space-4);
}
.homefoot__inner {
  max-width: 1080px;
  margin: 0 auto;
  display: flex;
  justify-content: space-between;
  align-items: center;
  color: var(--home-ink-soft);
  font-size: var(--text-sm);
  flex-wrap: wrap;
  gap: var(--space-3);
}

/* ---- 6-section port from dev/home-v2.html (HOME_CONTENT_PLAN, owner-approved
   2026-09-03) — everything below built from the same --home-*/--space-*/--text-*/
   --radius-*/--font-mono tokens already in the cascade, no new colors. */

/* ---- How it works — 4-step grid. Default .steps above stays 3-col (used
   nowhere else right now, but left general on purpose); this page's "How it
   works" has 4 steps, so a modifier adds the 4th column, same breakpoint
   collapse as the base rule at 48rem. */
.steps--four { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 48rem) { .steps--four { grid-template-columns: 1fr; } }

/* ---- For instructors — Results tab diagram. Schematic, not a screenshot;
   labeled honestly via .evidencecap below it. Bars use --home-ink per the
   mockup (not --home-accent) — this is a neutral data illustration, not a CTA. */
.illustboard {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-8);
  margin-top: var(--space-8);
}
@media (max-width: 48rem) { .illustboard { grid-template-columns: 1fr; } }
.illustboard__panel {
  background: var(--home-paper-raised);
  border: 1px solid var(--home-line);
  border-radius: var(--radius-md);
  padding: var(--space-5);
}
.illustboard__head {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--home-ink-soft);
  margin-bottom: var(--space-4);
}
.illustboard__row { display: flex; align-items: center; gap: var(--space-3); margin-bottom: var(--space-3); }
.illustboard__row:last-child { margin-bottom: 0; }
.illustboard__name { flex: 0 0 6.25rem; font-family: var(--font-mono); font-size: var(--text-xs); color: var(--home-ink); }
.illustboard__track { flex: 1; height: 10px; border-radius: var(--radius-pill); background: var(--home-line); overflow: hidden; }
.illustboard__fill { display: block; height: 100%; border-radius: inherit; background: var(--home-ink); }
.illustboard__value { flex: 0 0 2.75rem; text-align: right; font-family: var(--font-mono); font-size: var(--text-xs); color: var(--home-ink); }

/* ---- small caption/label under illustrative evidence blocks (Results
   diagram + real screens) — same visual job as .mediaplaceholder__tag above,
   redrawn locally since these sit on light paper, not the charcoal hero. */
.evidencecap {
  margin-top: var(--space-4);
  display: inline-block;
  padding: .3em .6em;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--home-ink-soft);
  background: var(--home-paper-raised);
  border: 1px solid var(--home-line);
  border-radius: var(--radius-sm);
}

.programlist {
  margin-top: var(--space-6);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--home-ink-soft);
  max-width: 62ch;
}

/* ---- For learners — 3 real demo-class screens (report / home / list).
   Desktop: centered row, report frame wider so it reads as the lead image.
   Mobile: horizontal scroll-snap so all 3 stay legible instead of squeezing
   into one row; peek of the next card + an edge fade signal there's more.
   .homeband--muted (the section this sits in) has --home-paper-raised as its
   background, so the fade gradient targets that color, not --home-paper. */
.phonepair-wrap { position: relative; margin-top: var(--space-8); }
.phonepair {
  display: flex;
  gap: var(--space-8);
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
}
.phoneframe {
  width: 13.5rem;
  flex: 0 0 auto;
  border: 1px solid var(--home-line);
  border-radius: var(--radius-lg);
  background: var(--home-paper-raised);
  box-shadow: var(--shadow-md);
  overflow: hidden;
}
.phoneframe img { display: block; width: 100%; height: auto; }
.phoneframe--lead { width: 17.5rem; }

@media (max-width: 48rem) {
  .phonepair-wrap::before,
  .phonepair-wrap::after {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    width: var(--space-8);
    z-index: 1;
    pointer-events: none;
  }
  .phonepair-wrap::before { left: 0; background: linear-gradient(90deg, var(--home-paper-raised) 0%, transparent 100%); }
  .phonepair-wrap::after { right: 0; background: linear-gradient(270deg, var(--home-paper-raised) 0%, transparent 100%); }
  .phonepair {
    flex-wrap: nowrap;
    justify-content: flex-start;
    gap: var(--space-4);
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    padding: 0 var(--space-4);
    margin: 0 calc(var(--space-4) * -1);
    -webkit-overflow-scrolling: touch;
  }
  .phoneframe { width: 62%; scroll-snap-align: center; }
  .phoneframe--lead { width: 72%; }
}

.materialsline {
  margin-top: var(--space-6);
  padding-top: var(--space-6);
  border-top: 1px solid var(--home-line);
  color: var(--home-ink-soft);
  font-size: var(--text-sm);
}

/* ---- bump the tap target of plain text links that carry a call to action
   (hero, organisations, footer contact) to >=44px tall without changing how
   they look: vertical padding only, no box/background. */
.link-arrow--tap { padding: .75rem 0; }

/* ============================== Entry code form / confirm ============================== */

.codeform { display: flex; flex-direction: column; gap: var(--space-3); }
/* เดียวกับ .modal-backdrop[hidden] ด้านบน — display:flex ในคลาสนี้ทับ UA [hidden]
   ต้องประกาศ [hidden] ซ้ำเองไม่งั้น form.hidden=true ทาง JS จะไม่ซ่อนอะไรเลย */
.codeform[hidden] { display: none; }
/* ขั้นยืนยัน (heroconfirm) โผล่มาแล้ว — ไม่ซ่อนฟอร์มด้านบนทั้งหมด (แบบเดิม) แต่กดให้จาง
   ลงแทน ให้เห็น hierarchy ว่าขั้นด้านล่างคือขั้นที่ต้องทำต่อ ส่วนด้านบนทำเสร็จไปแล้ว */
.codeform.is-locked { opacity: .4; pointer-events: none; }
.codeform__input { }
.codeform__go { position: relative; }
.codeform__spinner { display: none; }
.is-busy .codeform__spinner {
  display: inline-block;
  width: 14px; height: 14px;
  border: 2px solid color-mix(in srgb, var(--color-on-primary) 40%, transparent);
  border-top-color: var(--color-on-primary);
  border-radius: var(--radius-pill);
  animation: r6-spin .6s linear infinite;
}
.is-busy .codeform__label { opacity: .6; }
@keyframes r6-spin { to { transform: rotate(360deg); } }
.codeform__error { color: var(--color-err); font-size: var(--text-sm); min-height: 1.2em; margin: var(--space-2) 0 0; }

.confirm-name {
  font-size: var(--text-lg);
  font-weight: var(--weight-strong);
  margin-top: var(--space-1);
}

.heroconfirm__prompt { font-weight: var(--weight-strong); margin-bottom: var(--space-2); }
.heroconfirm__emp { color: var(--color-ink-soft); font-size: var(--text-sm); font-family: var(--font-mono); }
.heroconfirm__name { font-size: var(--text-lg); font-weight: var(--weight-strong); margin: var(--space-1) 0 var(--space-5); }
.heroconfirm__btns { display: flex; flex-direction: column; gap: var(--space-3); }

/* ============================== Quiz flow ============================== */

.pagehead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-5);
}
.pagehead .backlink { margin-bottom: 0; }

.progress { display: flex; align-items: center; gap: var(--space-3); margin-bottom: var(--space-6); }
.progress__track {
  flex: 1;
  height: 4px;
  background: var(--color-rule);
  border-radius: var(--radius-pill);
  overflow: hidden;
}
.progress__bar {
  height: 100%;
  width: 100%;
  background: var(--color-signature);
  transform-origin: left center;
  transform: scaleX(0);
  transition: transform var(--dur-med) var(--ease-out);
}
.progress__label {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-sm);
  color: var(--color-ink-soft);
  flex: none;
}

.qhead { display: flex; align-items: baseline; gap: var(--space-3); margin-bottom: var(--space-3); }
.qnum {
  font-family: var(--font-mono);
  font-size: var(--text-xl);
  font-weight: var(--weight-strong);
  color: var(--color-signature);
}
.qhead__domain {
  font-size: var(--text-xs);
  font-weight: var(--weight-label);
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--color-ink-soft);
}

/* R1-C: สลับภาษาข้อสอบ — เกาะขวาสุดของ qhead ให้เลขข้ออยู่ซ้ายเหมือนเดิม
   ตัวเล็กและเงียบ ไม่ควรแย่งความสนใจไปจากโจทย์ */
.langtoggle {
  margin-inline-start: auto;
  align-self: center; /* qhead จัด baseline — ปุ่มเล็กจะห้อยต่ำผิดตาถ้าไปเกาะ baseline ของ qnum */
  display: flex;
  gap: 1px;
  padding: 2px;
  border: 1px solid var(--color-rule);
  border-radius: var(--radius-sm);
  background: var(--color-surface-sunken);
}
.langtoggle__btn {
  padding: .25rem .6rem;
  border-radius: calc(var(--radius-sm) - 2px);
  font-size: var(--text-xs);
  font-weight: var(--weight-label);
  color: var(--color-ink-soft);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.langtoggle__btn:hover { color: var(--color-ink); }
.langtoggle__btn.is-active {
  background: var(--color-surface);
  color: var(--color-ink);
  box-shadow: var(--shadow-sm);
}

.qcard { margin-bottom: var(--space-5); }
.qcard__stem { font-size: var(--text-md); line-height: 1.5; margin-bottom: var(--space-5); white-space: pre-line; }
.qcard__stem-image {
  display: block;
  max-width: 100%;
  max-height: 22rem;
  object-fit: contain;
  border-radius: var(--radius-sm);
  margin: calc(-1 * var(--space-3)) 0 var(--space-5);
}

.choices { display: flex; flex-direction: column; gap: var(--space-3); }
.choice {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--color-rule);
  border-radius: var(--radius-sm);
  text-align: start;
  transition: border-color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.choice:hover { background: var(--color-surface-sunken); }
.choice__body { display: flex; flex-direction: column; gap: var(--space-2); flex: 1; min-width: 0; }
.choice__image {
  display: block;
  max-width: 100%;
  max-height: 10rem;
  object-fit: contain;
  border-radius: var(--radius-sm);
}
.choice__key {
  flex: none;
  width: 1.75rem; height: 1.75rem;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--radius-sm);
  background: var(--color-surface-sunken);
  font-family: var(--font-mono);
  font-weight: var(--weight-strong);
  font-size: var(--text-sm);
}
.choice--selected {
  border: 2px solid var(--color-primary);
  padding: calc(var(--space-4) - 1px);
  background: var(--color-surface);
}
.choice--selected .choice__key {
  background: var(--color-primary);
  color: var(--color-on-primary);
}

/* R11-A: หน้าเฉลย+คำอธิบายหลังส่ง — ใช้โครง .choice เดิม (อ่านง่ายเหมือนหน้าทำข้อสอบ)
   แค่ระบายสีนิ่งๆ แทนที่จะรับคลิก */
.choice--static { cursor: default; }
.choice--static:hover { background: var(--color-surface); }
.choice--correct {
  border: 2px solid var(--color-ok);
  padding: calc(var(--space-4) - 1px);
  background: color-mix(in srgb, var(--color-ok) 8%, white);
}
.choice--correct .choice__key { background: var(--color-ok); color: var(--color-on-primary); }
.choice--wrong {
  border: 2px solid var(--color-err);
  padding: calc(var(--space-4) - 1px);
  background: color-mix(in srgb, var(--color-err) 8%, white);
}
.choice--wrong .choice__key { background: var(--color-err); color: var(--color-on-primary); }
.choice__tag {
  margin-left: auto;
  flex: none;
  font-size: var(--text-sm);
  font-weight: var(--weight-strong);
}
.answerkey__item { margin-bottom: var(--space-8); }
/* R17: จอเฉลยมีได้ถึง 3 การ์ด (explanation/misconception/class reference) — แต่ละ
   การ์ดมีป้ายกำกับ + จุดสีบอกความหมาย เลียนแบบ .badge::before ด้านบน (dot indicator)
   แทนเส้นขอบสี ให้ตรงกับภาษาภาพเดิมของระบบ ใช้ token สีเดิมล้วน ไม่เพิ่มสีใหม่ */
.answerkey__notes { margin-top: var(--space-4); display: flex; flex-direction: column; gap: var(--space-3); }
.answerkey__note {
  padding: var(--space-4);
  border-radius: var(--radius-sm);
  background: var(--color-surface-sunken);
  font-size: var(--text-sm);
  line-height: 1.5;
  /* คำอธิบายเฉลยมีหัวข้อย่อยและ bullet คั่นด้วยขึ้นบรรทัด — ถ้าไม่ตั้ง pre-line
     เบราว์เซอร์จะยุบทุกบรรทัดรวมเป็นย่อหน้าเดียวจนอ่านไม่ออก (บั๊กเดียวกับที่เคย
     เกิดกับ .qcard__stem) · renderer ใช้ esc() ล้วน ไม่ได้แปลง \n เป็น <br> */
  white-space: pre-line;
}
.answerkey__note p { margin: 0; }
.answerkey__note-label {
  display: flex;
  align-items: center;
  gap: .4rem;
  font-size: var(--text-xs);
  font-weight: var(--weight-strong);
  text-transform: uppercase;
  letter-spacing: .02em;
  color: var(--color-ink-faint);
  margin-bottom: var(--space-1);
}
.answerkey__note-label::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: var(--radius-pill);
  flex: none;
  background: var(--color-ink-faint);
}
.answerkey__note--ok .answerkey__note-label::before { background: var(--color-ok); }
.answerkey__note--warn .answerkey__note-label::before { background: var(--color-warn); }
.answerkey__summary { display: flex; align-items: baseline; gap: var(--space-2); margin-bottom: var(--space-2); }
.answerkey__score { font-size: var(--text-lg); font-weight: var(--weight-strong); }

.savehint {
  font-size: var(--text-sm);
  color: var(--color-ink-faint);
  margin: var(--space-4) 0;
  min-height: 1.2em;
}
.savehint--ok { color: var(--color-ok); }
.savehint--err { color: var(--color-err); }

.qnav { display: flex; gap: var(--space-3); }
.qnav .btn { flex: 1; }

.legend { display: flex; gap: var(--space-5); font-size: var(--text-sm); color: var(--color-ink-soft); margin-bottom: var(--space-4); }
.legend span { display: flex; align-items: center; gap: var(--space-2); }
.legend i { width: 10px; height: 10px; border-radius: var(--radius-sm); display: inline-block; }
.legend i.is-done { background: var(--color-ok); }
.legend i.is-blank { background: var(--graphite-200); }

.reviewgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(2.75rem, 1fr));
  gap: var(--space-2);
  margin-bottom: var(--space-6);
}
.reviewcell {
  aspect-ratio: 1;
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-weight: var(--weight-strong);
  font-size: var(--text-sm);
  display: flex; align-items: center; justify-content: center;
}
.reviewcell--done { background: color-mix(in srgb, var(--color-ok) 8%, white); color: var(--color-ok); }
/* Border-only, not a tinted fill: a signature-red wash here would land
   within ~1 dE of the msg--err tint used on this same screen (measured:
   f5e9ec vs fde8e9) — exactly the "failed vs brand red" collision
   DESIGN-R6.md §2 rules out. Rule/outline reads as "needs attention"
   without borrowing the error-message's own visual language. */
.reviewcell--blank { background: var(--color-surface); color: var(--color-primary-active); border: 2px solid var(--color-signature); }

/* การ์ด Submitted + ปุ่มใต้การ์ด — คุมความกว้างของตัวเองไว้ที่ขนาดจอปกติ (.wrap
   42rem) เพราะจอนี้ต่อรายงานท้ายจอได้ (ดู showDone ใน quiz.js) ซึ่งเติม
   .wrap--wide ให้ทั้งหน้า — ถ้าไม่คุมไว้ การ์ดยืนยันจะโดนดึงกว้างตามไปด้วย */
.donetop { max-width: 42rem; margin-inline: auto; }

/* SPEC_FEEDBACK.md §3 E2 — การ์ด feedback ใต้ #done-report ต้องคุมความกว้างของ
   ตัวเองไว้เหมือน .donetop ด้านบน (ค่าเดียวกัน 42rem) เพราะ showDone() เติม
   .wrap--wide ให้ทั้งหน้าเมื่อรายงานโหลดสำเร็จ — ถ้าไม่คุมไว้ ปุ่มตัวเลือกจะยืด
   จนอ่านไม่สบาย ใช้ค่าคงที่นี้เสมอไม่ว่า wrap--wide จะติดอยู่หรือไม่ */
#feedback-card > .card,
#feedback-entry .card { max-width: 42rem; margin-inline: auto; }
/* J3 (รอบ 7) — เจ้าของขอ "อีกหน่อย" จากระยะเดิม (--space-8, 32px) ขยับขึ้น 1 ขั้น
   token เป็น --space-10 (40px) เฉพาะการ์ดในจอ Submitted เท่านั้น — ไม่แตะ
   #feedback-entry (บรรทัดเบาทางเข้าที่สองใช้ margin-top ของตัวเองแยกต่างหาก,
   quiz.js, ไม่เกี่ยวกับ J3) */
#feedback-card > .card { margin-top: var(--space-10); }

/* J2 (รอบ 7) — การ์ด feedback เล็กลง: ลด padding ของ .card เฉพาะการ์ดนี้ (ห้ามแตะ
   .card เปล่าเพราะกระทบทั้งแอป) และลด gap "ชั้นนอก" (ระหว่าง header/คำเชิญ-คำถาม/
   บล็อกตัวเลือก/ปุ่ม Next) — gap "ชั้นใน" ระหว่างปุ่มตัวเลือกแต่ละแถวยังคง
   var(--space-3) เดิมเป๊ะ (ห้ามลด, กัน fat-finger overlap ตาม E1) ไม่ถูกแตะโดย
   selector นี้เพราะเป็น gap ของ container คนละชั้น (ใส่ inline ใน quiz.js) */
#feedback-card .card,
#feedback-entry .card { padding: var(--space-4); }
#feedback-card .fb-outer,
#feedback-entry .fb-outer { gap: var(--space-2); }

/* J1 — สถานะ "เลือกอยู่" ของตัวเลือก multi-select ต่อยอดจาก .btn--link--option (E1)
   ยืมแค่ "ภาษาสี" จาก .choice--selected (border-color: var(--color-primary)) ไม่เอา
   โครง/padding ทั้งก้อน กันการ์ดสูงขึ้นอีกโดยไม่จำเป็น (ขัดกับ J2) — border ยังเป็น
   1px เท่าเดิม (แค่เปลี่ยนสี) จึงไม่ขยับความสูง 44px เลย
   บั๊กที่ Ray เจอ (2026-09-02): เดิมเขียน font-weight: var(--weight-label) —
   .btn--link เองก็ใช้ --weight-label (500) อยู่แล้ว บรรทัดนี้จึงเป็นค่าเดิมซ้ำ
   ไม่ทำอะไรเลยไม่ว่า CSS จะติดหรือไม่ติด แก้เป็น --weight-strong (600) ให้เป็น
   สัญญาณตัวหนาที่ต่างจริงตามที่สเปกตั้งใจ (border-color + ตัวหนา + ✓ = 3 ชั้น) */
.btn--link--option.is-selected {
  border-color: var(--color-primary);
  color: var(--color-ink);
  font-weight: var(--weight-strong);
}

/* VIS-005: กล่องติ๊กของ multi-select (Feedback) เห็นกล่องชัดเจนทุกบรรทัด */
.fb-check {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.125rem;
  height: 1.125rem;
  border: 1.5px solid var(--color-rule, #cbd5e1);
  border-radius: var(--radius-sm, 4px);
  margin-right: var(--space-3, 0.75rem);
  flex-shrink: 0;
  vertical-align: middle;
  background: var(--color-surface, #fff);
  color: #fff;
  font-size: 0.8125rem;
  font-weight: var(--weight-strong, 600);
  line-height: 1;
}
.btn--link--option.is-selected .fb-check {
  border-color: var(--color-primary);
  background: var(--color-primary);
  color: #fff;
}

.done { text-align: center; padding: var(--space-10) var(--space-6); margin-bottom: var(--space-6); }
.done__mark {
  width: 3rem; height: 3rem;
  margin: 0 auto var(--space-4);
  border-radius: var(--radius-pill);
  background: var(--color-ok);
  color: white;
  display: flex; align-items: center; justify-content: center;
  font-size: var(--text-lg);
}
.done__mark--neutral {
  background: var(--color-surface-sunken);
  color: var(--color-ink);
  border: 1.5px solid var(--color-rule);
}
.done h2 { font-family: var(--font-display); font-size: var(--text-xl); font-weight: var(--weight-display); margin-bottom: var(--space-3); }
.done p { color: var(--color-ink-soft); margin-bottom: var(--space-1); }

.omfit-courses { display: flex; flex-direction: column; gap: var(--space-2); margin-top: var(--space-2); }
.omfit-courses li {
  position: relative;
  padding-left: var(--space-4);
  color: var(--color-ink-soft);
  overflow-wrap: break-word;
}
.omfit-courses li::before {
  content: '';
  position: absolute;
  left: 0;
  top: .55em;
  width: .375rem;
  height: .375rem;
  border-radius: var(--radius-pill);
  background: var(--color-signature);
}

/* ============================== "Are You OM?" persona report ==============================
   BRIEF_OMFIT_REPORT.md §3/§6 — read as a document (a letter), not a stack of cards: no
   .card wrapper, no borders around the whole thing. Persona name is the only element on
   this screen that gets a motion moment on load (`.enter` on `.omreport__persona` only) —
   everything else renders still on purpose (brief: "จังหวะเด่นจังหวะเดียว"). Thin `--color-rule`
   dividers stand in for card edges without ever exceeding 1px or using a brand color there.
   All muted text below uses `--color-ink-soft` (6.58:1), never `--color-ink-faint` (3.74:1,
   fails AA at body/small sizes per its own token comment in tokens.css) — this report has no
   text large enough to fall under the 3:1 "large text" allowance. */
.omreport { max-width: 34rem; }

/* colophon — วันที่ส่ง/ชื่อรอบ อยู่ "ท้าย" เอกสารเสมอ ไม่ใช่เหนือ h1 (ห้ามเป็น
   eyebrow ตาม §6 ข้อ 1) วางต่อจาก .omreport__footer โดยตรง ไม่มีเส้นคั่นซ้ำ */
.omreport__meta {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-ink-soft);
  margin: var(--space-2) 0 0;
}

.omreport__persona {
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  font-size: clamp(1.5rem, 1rem + 3vw, 2.25rem);
  line-height: 1.2;               /* design.md §3 — Latin display floor, never below this */
  color: var(--color-ink);
  overflow-wrap: break-word;      /* safety net for the 30-char hybrid name at 360px */
  margin: var(--space-4) 0 var(--space-4);
}

.omreport__lede {
  font-size: var(--text-md);
  line-height: 1.6;
  color: var(--color-ink-soft);
  margin-bottom: var(--space-4);
}

/* โปรไฟล์ 4 ด้าน (2026-09-02) — แท่งแนวนอนยาว→สั้น สีเดียว (--color-signature)
   ตัดสินแล้วห้ามเปลี่ยน (Ray brief): ห้าม 4 สีแยกด้าน, ห้าม donut/pie/ring,
   ห้าม animation บนแท่ง (จังหวะเด่นจังหวะเดียวของจอนี้คือชื่อ persona เท่านั้น)
   อัตลักษณ์แต่ละด้านมาจากป้ายชื่อ ไม่ใช่สี ⇒ ไม่มี legend */
.omreport__lenses {
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-rule);
  margin-bottom: var(--space-4);
}
.omlenses { display: flex; flex-direction: column; gap: var(--space-3); }
.omlens__row { min-width: 0; }
.omlens__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-1);
}
.omlens__name {
  font-size: var(--text-sm);
  font-weight: var(--weight-strong);
  color: var(--color-ink);
  overflow-wrap: break-word;   /* ชื่อด้านยาวสุด ~15 ตัวอักษร แต่กันไว้ */
}
.omlens__value {
  flex: none;
  display: flex;
  align-items: baseline;
  gap: var(--space-1);
  font-size: var(--text-sm);
  color: var(--color-ink);
  white-space: nowrap;
}
.omlens__picks { font-size: var(--text-xs); color: var(--color-ink-soft); }
.omlens__picks::before { content: '\00b7\0020'; }  /* "· " กันเลขคะแนนกับจำนวนครั้งอ่านติดกัน (เช่น "7 3 picks") */

/* คำอธิบาย "ด้าน" เฉพาะด้านคะแนนสูงสุด (2026-09-02, คำสั่ง Ray) — ตั้งใจให้อ่านต่าง
   จาก .omreport__lede ที่อยู่เหนือขึ้นไปทั้งเนื้อหา (lede = "ชอบอะไร", บรรทัดนี้ =
   "เริ่มมองปัญหาจากตรงไหน") และตำแหน่ง/รูปลักษณ์: อยู่ "ใน" แถวของด้านนั้นโดยตรง
   ใต้แท่งกราฟ ไม่ใช่ลอยเดี่ยว ๆ ใต้ชื่อ persona แบบ lede, ใช้ italic (lede ไม่ italic)
   และ text-sm (lede เป็น text-md) — ระยะ/น้ำหนัก/ตำแหน่งบอกว่าสองก้อนนี้ทำหน้าที่
   ต่างกัน ไม่ใช่พูดซ้ำ */
.omlens__desc {
  margin: var(--space-2) 0 0;
  font-size: var(--text-sm);
  font-style: italic;
  line-height: 1.6;
  color: var(--color-ink-soft);
}

/* เส้นฐานจาง (track) ทำให้แถวที่ได้ 0 คะแนนยังเห็นเป็นแถว ไม่ใช่ที่ว่างเปล่า
   — "ด้านนี้คุณไม่ได้เลือกเลย" คือข้อมูล ห้ามซ่อน (brief §…ห้ามซ่อนแถวคะแนน 0) */
.omlens__track {
  height: 8px;
  border-radius: var(--radius-sm);
  background: var(--color-surface-sunken);
  overflow: hidden;
}
.omlens__bar {
  height: 100%;
  /* 0% ต้องเป็น 0px จริง ไม่ใช่แท่งจิ๋วหลอกตา — แถวที่ได้ 0 คะแนนอาศัย
     .omlens__track (เส้นฐานจาง) กับชื่อ/ตัวเลข "0" ยืนยันว่าเป็นแถวจริงแทน */
  border-radius: var(--radius-sm);
  background: var(--color-signature);
  /* บาร์นี้เป็นเนื้อหาของรายงาน ไม่ใช่ UI ตกแต่ง — ต้องติดกระดาษตอนพิมพ์/PDF
     แม้เบราว์เซอร์จะปิด "print backgrounds" เป็นค่าเริ่มต้นก็ตาม */
  print-color-adjust: exact;
  -webkit-print-color-adjust: exact;
}

.omreport__band {
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-rule);
  margin-bottom: var(--space-4);
}
.omreport__band-label {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: var(--weight-strong);
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--color-ink-soft);
  margin: 0 0 var(--space-2);
}
.omreport__band-text {
  line-height: 1.6;
  color: var(--color-ink-soft);
  margin: 0 0 var(--space-2);
}
.omreport__score {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-ink-soft);
  margin: 0;
}

.omreport__section-title {
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  font-size: var(--text-md);
  margin: 0 0 var(--space-2);
}
.omreport__courses {
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-rule);
  margin-bottom: var(--space-4);
}

.omreport__footer {
  font-size: var(--text-xs);
  font-style: italic;
  line-height: 1.5;
  color: var(--color-ink-soft);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-rule);
  margin: 0;
}

.omreport__actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-top: var(--space-8);
  max-width: 34rem;
}

/* ============================== Learning materials embed ============================== */

.material { margin-bottom: var(--space-6); }
.material__head {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: var(--space-3);
}
.material__title { font-size: var(--text-md); font-weight: var(--weight-strong); }
.material__open { font-size: var(--text-sm); color: var(--color-primary); font-weight: var(--weight-label); }
.material__frame {
  border-radius: var(--radius-md);
  overflow: hidden;
  border: 1px solid var(--color-rule);
  aspect-ratio: 4 / 3;
}
.material__frame--video { aspect-ratio: 16 / 9; }
.material__frame iframe { width: 100%; height: 100%; border: 0; }
.material__image { border-radius: var(--radius-md); border: 1px solid var(--color-rule); }

/* ============================== Learner class home (Course Editions B) ==============================
 * SPEC_LEARNER_CLASS_UI_2026-09-16.md §4/§5/§11 + SPEC_LEARNER_AESTHETIC_2026-09-16.md §4.2 (L04)
 *
 * Scope: ทุกอย่างในบล็อกนี้ขึ้นต้นด้วย .classhome เท่านั้น — ไม่แตะ .wrap/.next/.card/
 * .spinerow ของจอสอบ/รายงาน/login (base มี .wrap max-width 42rem ใช้ทุกจอ; .classhome
 * override เฉพาะตอนที่ #app มี class นี้ ซึ่ง quiz.js เติมให้เฉพาะ showClassHome()
 * เท่านั้น — ดู render()/showClassHome() comment)
 *
 * ไม่มี token ใหม่ระดับ global — ตัวแปร local ด้านล่างเป็น alias เฉพาะ scope นี้
 * (SPEC §5 "ถ้า scale นี้ไม่มี token เดิมให้เสนอ local alias ใน spec/implementation
 * ไม่เปลี่ยน global scale เอง")
 */
.classhome {
  --classhome-h1: clamp(1.75rem, 1.45rem + 1.4vw, 2.5rem); /* 28px มือถือ → 40px desktop, §5 */
  max-width: 70rem; /* 1120px */
}

.classhome__header {
  display: flex;
  align-items: stretch;
  justify-content: space-between;
  gap: var(--space-6);
  margin-bottom: var(--space-8);
  min-height: 200px;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--color-surface-sunken);
}
.classhome__header:has(+ .assessment-navigation) {
  margin-bottom: var(--space-4);
}
.classhome__headertext {
  flex: 1 1 auto;
  min-width: 0;
  padding: var(--space-6) var(--space-6) var(--space-6) 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-2);
}
.classhome__context { font-size: var(--text-sm); color: var(--color-ink-soft); }
.classhome__context a { color: var(--color-primary); }
.classhome__context a:hover { text-decoration: underline; }
.classhome__title {
  font-family: var(--font-display);
  font-size: var(--classhome-h1);
  font-weight: var(--weight-display);
  line-height: 1.2;
  color: var(--color-ink);
  /* §4.2/§16.1 — ชื่อยาวต้อง wrap เต็ม ไม่ ellipsis ไม่ตัด ไม่กำหนด height ตายตัว */
  overflow-wrap: anywhere;
}
.classhome__identity { font-size: var(--text-md); color: var(--color-ink-soft); }

/* Artwork — decorative only, CSS background (ไม่ใช่ <img>) กันปัญหา alt/broken-image
 * (§4.4 D09 fallback) desktop ≤35% ความกว้าง header, mobile เป็น band สั้น */
.classhome__art {
  flex: 0 0 35%;
  max-width: 35%;
  background-image: url('/assets/img/learner-header-b-desktop-v1.svg');
  background-size: cover;
  background-position: center;
  pointer-events: none;
}

.classhome__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-6);
  align-items: start;
}

.classhome__main { display: flex; flex-direction: column; gap: var(--space-8); min-width: 0; }
.classhome__support { display: flex; flex-direction: column; gap: var(--space-6); min-width: 0; }

/* Next-action panel — §5 "sunken surface, radius 8px, padding 24/20px" ไม่มี artwork
 * ซ้ำในพาเนลนี้ (aesthetic spec) น้ำหนักมาจาก layout/typography เท่านั้น */
.classhome__next {
  background: var(--color-surface-sunken);
  border-radius: var(--radius-md);
  padding: var(--space-6);
  min-width: 0;
}
.classhome__nextkicker {
  font-size: var(--text-body);
  font-weight: var(--weight-label);
  color: var(--color-ink-soft);
  margin-bottom: var(--space-1);
}
.classhome__nexttitle {
  font-family: var(--font-sans); /* §5: heading ไทย/activity title ใช้ sans ไม่ใช่ serif ทุกหัว */
  font-size: var(--text-xl);
  font-weight: var(--weight-strong);
  color: var(--color-ink);
  margin-bottom: var(--space-2);
}
/* A1 (design-director, revision round 1): ข้อความจริงที่ใส่ในนี้เป็นประโยค ("Answered 4
 * of 12 questions") ไม่ใช่เลขล้วน — sans เป็นค่าเริ่มต้นเสมอ (ขัด SPEC_LEARNER_CLASS_UI
 * §5 ถ้าเป็น mono) ตัวเลขในประโยคห่อด้วย .mono แยกเป็นจุด ๆ จาก quiz.js (numSpan()) */
.classhome__nextfacts {
  font-family: var(--font-sans);
  font-size: var(--text-body);
  color: var(--color-ink-soft);
  margin-bottom: var(--space-4);
}
.classhome__next .btn { min-height: 44px; }
.classhome__next--empty,
.classhome__next--error {
  background: var(--color-surface);
  border: 1px solid var(--color-rule);
}
.classhome__next--empty .classhome__nextkicker,
.classhome__next--error .classhome__nextkicker {
  font-weight: var(--weight-body);
  margin-bottom: var(--space-2);
}
.classhome__next--empty .classhome__nextfacts { margin-bottom: 0; }
.classhome__next--error .btn { margin-top: var(--space-2); }

.classhome__sectionheading {
  font-family: var(--font-sans);
  font-size: var(--text-lg);
  font-weight: var(--weight-strong);
  color: var(--color-ink);
  margin-bottom: var(--space-4);
}

.classhome__rounds { min-width: 0; }
.classhome__rounds .spinelist { margin: 0; }
/* A2 (design-director, revision round 1) — ยังไม่รื้อ: .classhome ยังได้กติกานี้เหมือน
 * เดิมทุกประการ (เป็น descendant ของ body.learnerapp อยู่แล้ว)
 *
 * N4 (Ray, revision round 3): เดิม scope แค่ ".classhome" — จอ "#/list" เดิม (ยังใช้
 * จริงเมื่อคลาสหลายวิชา ดู quizNote()) ใช้ .spinerow__note ตัวเดียวกันแต่ยังไม่ได้แก้
 * ("4 questions · tap to start" ยังเป็น mono) ยกขึ้นเป็น body.learnerapp เหมือนที่ทำกับ
 * B1/G2 (contrast) แล้ว — กติกาเดียวกันเป๊ะ ไม่ต้องมี 2 selector คู่กัน ไม่รั่วออกนอก
 * learner surface (body.learnerapp มาจาก quiz.js เท่านั้น) */
body.learnerapp .spinerow__note { font-family: var(--font-sans); font-variant-numeric: normal; }

/* Q2 (visual-critic VIS-008, revision round 4): ".btn:disabled { opacity:.5 }" ทั่วเว็บ
 * ลดความเข้มข้อความทุกอย่างในปุ่มลงครึ่งหนึ่งรวมถึง .btn__why (คำอธิบายว่าทำไมกดไม่ได้ —
 * ข้อความที่ผู้เรียน "ต้องอ่าน" ไม่ใช่ label ปุ่มปิดที่ไม่มีใครสนใจตัวมันเอง) ผลคือ
 * ink (18.40:1) เหลือ ~3.45:1 และ ink-soft (6.58:1) เหลือ ~2.22:1 — คำนวณตรงกับค่าที่
 * visual-critic วัดได้จาก "View report"/"Opens when your instructor publishes it."
 * เป๊ะ (opacity ผสมกับพื้นขาวได้สีเดียวกัน) — B1 ที่แก้ไปก่อนหน้าครอบแค่
 * .spinerow--disabled ไม่ได้ครอบปุ่ม .btn:disabled เลย จึงเป็นจุดที่ "เหลือ" ตามที่ Ray
 * สั่งให้ไล่หา ปิด opacity dimming เฉพาะ scope ผู้เรียน (ปุ่ม disabled ชั่วคราวระหว่างส่ง
 * ฟอร์ม เช่น "Submitting…" ไม่ได้รับผลกระทบเชิงลบ — สีตัวหนังสือเดิมของปุ่มนั้นอ่านออก
 * อยู่แล้วโดยไม่ต้องพึ่ง opacity) ไม่แตะ .btn:disabled ตัวกลางที่ admin/me/program ใช้ */
body.learnerapp .btn:disabled { opacity: 1; }

/* disabledBtnHtml() (quiz.js) ฝัง inline style สีเอง (--color-ink-faint, 3.74:1) แยกจาก
 * ปัญหา opacity ข้างบน — แก้ token ที่ต้นทาง JS แล้ว (--color-ink-soft) ไม่ต้องมี CSS
 * override ซ้อนที่นี่ */

/* V1 (visual-critic VIS-008-b, revision round 5): ".savehint" (ข้อความใต้ตัวเลือกจอทำ
 * ข้อสอบ "Choose an answer — it's saved automatically.") ใช้ --color-ink-faint (3.74:1)
 * ทั่วเว็บเช่นกัน — ข้อความนี้ผู้เรียน "ต้องอ่าน" (บอกว่าจะเกิดอะไรถ้ายังไม่กด ไม่ใช่ label
 * ปุ่มปิด/placeholder) จึงเข้าเกณฑ์เดียวกับ B1/Q2 ข้างบน — override เป็น --color-ink-soft
 * (6.58:1) เฉพาะ scope ผู้เรียน
 *
 * บั๊กที่ Ray จับได้ (revision round 5, ตรวจซ้ำ): comment เดิมตรงนี้เขียนว่า "สถานะ ok/err
 * ใช้สีเขียว/แดงของตัวเองอยู่แล้วไม่กระทบ" — ผิด เพราะ `body.learnerapp .savehint`
 * มี specificity (0,2,1) สูงกว่า `.savehint--ok`/`.savehint--err` ที่ (0,1,0) เดิม (บรรทัด
 * 1856-1857) ไม่ว่าลำดับในไฟล์จะเป็นยังไง กฎสีเทานี้ชนะเสมอ ทำให้ "Saved" (เขียว) และ
 * "Couldn't save…" (แดง) กลายเป็นสีเทาเดียวกันหมด ผู้เรียนแยกสถานะบันทึกไม่ออก — แก้ด้วย
 * `:not()` กันไม่ให้ selector นี้จับ element ที่มีคลาสสถานะเลย (ไม่ใช่แข่ง specificity) */
body.learnerapp .savehint:not(.savehint--ok):not(.savehint--err) { color: var(--color-ink-soft); }

/* B1 (visual-critic VIS-001, revision round 1): แถวที่ไม่ใช่ปุ่ม (not_open/locked/
 * submitted ไม่มีเฉลย) ใช้ --color-ink-faint ทั่วเว็บ (3.74:1 — token เขียนคอมเมนต์ไว้เอง
 * ว่า "faint/disabled/placeholder text only" ไม่ใช่ข้อความที่ต้องอ่าน) แถวรอบใน Class Home
 * เป็นข้อมูลจริง (ชื่อรอบ + สถานะ) ที่ผู้เรียนต้องอ่าน ไม่ใช่ label ปุ่มปิดที่ไม่มีใครต้อง
 * อ่านตัวมันเอง
 *
 * G2 (Ray, revision round 2): เดิม override แค่ ".classhome" (Class Home อย่างเดียว) —
 * ผู้เรียนคลาสหลายวิชายังเห็นจอ #/list เดิม (แถวรอบเต็มรูปแบบ ไม่ใช่ inline บน Class
 * Home) ซึ่งใช้ .spinerow--disabled ตัวเดียวกันแต่ยังไม่ได้แก้ — ยกขึ้นเป็น
 * "body.learnerapp" (ทุกจอที่ quiz.js วาด: login/list/materials/quiz/answers/results)
 * ให้ใช้กติกาเดียวกันทั้งชุด ".classhome" เป็น descendant ของ body.learnerapp อยู่แล้ว
 * จึงยังได้ผลเดิมโดยไม่ต้องมี 2 selector คู่กัน · body.learnerapp มาจาก quiz.js เท่านั้น
 * (`document.body.classList.add('learnerapp')`) — me.js/program.js/admin.js ไม่เรียก
 * เส้นนี้ จึง**ไม่แก้** .spinerow--disabled ของจอ My classes/Program overview/Admin
 * ที่ใช้ class เดียวกันแต่คนละสคริปต์ (กันข้อ "ระวังอย่าให้รั่วออกนอก learner surface") */
body.learnerapp .spinerow--disabled,
body.learnerapp .spinerow--disabled .spinerow__title {
  color: var(--color-ink-soft);
}

.classhome__support-card {
  padding: var(--space-5);
  background: var(--color-surface);
  border: 1px solid var(--color-rule);
  border-radius: var(--radius-md);
  min-width: 0;
}
.classhome__supportheading {
  font-family: var(--font-sans);
  font-size: var(--text-md);
  font-weight: var(--weight-strong);
  margin-bottom: var(--space-2);
}
.classhome__supportbody { font-size: var(--text-body); color: var(--color-ink-soft); }
.classhome__support-card .btn--link { margin-top: var(--space-2); display: inline-flex; align-items: center; gap: var(--space-1); }

/* Loading skeleton — §11 ตำแหน่งเดิมของ header/action/list, ไม่ fake ตัวเลข */
.classhome__skeleton { display: flex; flex-direction: column; gap: var(--space-6); }
.classhome__skeleton-header,
.classhome__skeleton-next,
.classhome__skeleton-row {
  border-radius: var(--radius-md);
  background: var(--color-surface-sunken);
  animation: classhome-pulse 1.4s ease-in-out infinite;
}
.classhome__skeleton-header { height: 200px; }
.classhome__skeleton-next { height: 140px; }
.classhome__skeleton-row { height: 64px; }
@keyframes classhome-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: .6; }
}
@media (prefers-reduced-motion: reduce) {
  .classhome__skeleton-header,
  .classhome__skeleton-next,
  .classhome__skeleton-row { animation: none; }
}

/* Desktop ≥1024px — content cards 2-column balanced grid, gap 32px */
@media (min-width: 64rem) {
  .classhome__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: start; gap: var(--space-8); }
  .classhome__grid--single { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .classhome__grid--single .classhome__main { max-width: 100%; }
}

/* ==========================================================================
   Class Home Layout (Program-style: 2/3 Main list + 1/3 Recommended feed & results)
   ========================================================================== */

.classhome-layout {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  grid-template-areas:
    "main recommended"
    "main results";
  gap: var(--space-8);
  align-items: start;
}

.classhome-main {
  grid-area: main;
  min-width: 0;
}

.classhome-main__title {
  margin: 0 0 var(--space-3);
  font-family: var(--font-display);
  font-size: var(--text-lg);
  line-height: 1.4;
  color: var(--color-ink);
}

.classhome-round-list {
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--color-rule);
}

.classhome-round-row {
  display: grid;
  grid-template-columns: 2.5rem minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-4);
  min-width: 0;
  min-height: 5.5rem;
  padding: var(--space-4) 0;
  border-bottom: 1px solid var(--color-rule);
  color: var(--color-ink);
  text-decoration: none;
  background: transparent;
  border-left: none;
  border-right: none;
  border-top: none;
  width: 100%;
  text-align: left;
  cursor: pointer;
}

.classhome-round-row:hover:not(.classhome-round-row--disabled) {
  background: var(--color-surface-sunken);
}

.classhome-round-row:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 4px;
}

.classhome-round-row__index {
  align-self: start;
  padding-top: .2rem;
  color: var(--color-primary);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
  font-weight: var(--weight-strong);
}

.classhome-round-row__body {
  display: grid;
  gap: var(--space-1);
  min-width: 0;
}

.classhome-round-row__name {
  min-width: 0;
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: var(--weight-strong);
  line-height: 1.35;
  overflow-wrap: anywhere;
}

.classhome-round-row__facts {
  color: var(--color-ink-soft);
  font-size: var(--text-sm);
  line-height: 1.55;
  overflow-wrap: anywhere;
}

.classhome-round-row__action {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  min-height: 44px;
  color: var(--color-primary);
  font-size: var(--text-sm);
  font-weight: var(--weight-strong);
  white-space: nowrap;
}

.classhome-round-row--disabled {
  cursor: default;
  opacity: 0.8;
}
.classhome-round-row--disabled .classhome-round-row__name {
  color: var(--color-ink);
}
.classhome-round-row--disabled .classhome-round-row__facts {
  color: var(--color-ink-soft);
}
.classhome-round-row__status {
  display: inline-flex;
  align-items: center;
  font-size: var(--text-sm);
  color: var(--color-ink-soft);
  white-space: nowrap;
}
.classhome-round-row__status--submitted {
  color: var(--color-ink-soft);
}
.classhome-round-row__status--locked {
  color: var(--color-ink-faint);
}

/* Multi-subject grouping */
.classhome-subject-group + .classhome-subject-group {
  margin-top: var(--space-6);
}
.classhome-subject-title {
  font-family: var(--font-display);
  font-size: var(--text-md);
  color: var(--color-ink);
  margin-bottom: var(--space-2);
}

/* Right column: สิ่งที่ควรทำตอนนี้ */
.classhome-recommended {
  grid-area: recommended;
  min-width: 0;
  padding: var(--space-5);
  border-top: 2px solid var(--color-ink);
  background: var(--color-surface-sunken);
}
.classhome-recommended__title {
  margin: 0 0 var(--space-2);
  font-family: var(--font-display);
  font-size: var(--text-md);
  line-height: 1.35;
  color: var(--color-ink);
}
.classhome-recommended__kicker {
  font-size: var(--text-xs);
  font-weight: var(--weight-strong);
  color: var(--color-primary);
  margin: 0 0 var(--space-1);
  letter-spacing: 0.02em;
}
.classhome-recommended__heading {
  font-family: var(--font-sans);
  font-size: var(--text-xl);
  font-weight: var(--weight-strong);
  line-height: 1.3;
  margin: 0 0 var(--space-2);
  color: var(--color-ink);
}
.classhome-recommended__desc {
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  line-height: 1.55;
  color: var(--color-ink-soft);
  margin: 0 0 var(--space-4);
}

/* Right column: ผลประเมินของฉัน */
.classhome-results {
  grid-area: results;
  min-width: 0;
}
.classhome-results-card {
  padding: var(--space-5);
  background: var(--color-surface);
  border: 1px solid var(--color-rule);
  border-radius: var(--radius-md);
  min-width: 0;
}
.classhome-results-card__title {
  font-family: var(--font-display);
  font-size: var(--text-md);
  font-weight: var(--weight-strong);
  margin: 0 0 var(--space-2);
  color: var(--color-ink);
}
.classhome-results-card__desc {
  font-size: var(--text-sm);
  line-height: 1.55;
  color: var(--color-ink-soft);
  margin: 0 0 var(--space-3);
}
.classhome-results-card .btn--secondary {
  min-height: 44px;
}

/* Empty state */
.classhome-empty {
  display: grid;
  gap: var(--space-2);
  padding: var(--space-5) 0;
  border-top: 1px solid var(--color-rule);
  border-bottom: 1px solid var(--color-rule);
}
.classhome-empty p { margin: 0; }

/* Mobile responsive: ≤48rem (768px) */
@media (max-width: 48rem) {
  .classhome-layout {
    display: flex;
    flex-direction: column;
    gap: var(--space-6);
  }
  .classhome-round-row {
    grid-template-columns: 2rem minmax(0, 1fr);
    align-items: start;
    gap: .75rem;
    min-height: 0;
    padding: 1rem 0;
  }
  .classhome-round-row__action,
  .classhome-round-row__status {
    grid-column: 2;
  }
  .classhome-round-row__name {
    font-size: 1.1rem;
  }
}

/* B3: การ์ดสนับสนุนที่ไหลอยู่ใน main (ไม่ได้เป็น sidebar) ไม่ต้อง margin เพิ่มเอง —
 * เป็นลูกของ .classhome__main (flex column gap: var(--space-8) อยู่แล้ว) */
.classhome__support--inline { margin: 0; }

/* Mobile 360–430px (§4.2) — single column, artwork ย่อเป็น band สั้น */
@media (max-width: 46.24rem) {
  .classhome__header {
    flex-direction: column;
    min-height: 0;
  }
  .classhome__headertext { padding: var(--space-5) var(--space-5) 0; order: 1; }
  .classhome__art {
    order: 2;
    flex: none;
    max-width: 100%;
    width: 100%;
    height: 64px;
    /* G4 (Ray, revision round 2): asset นี้มีแถบฐานแดงที่ต้อง "เต็มความกว้างเสมอ" — ล็อก
       ไว้ที่ต้นไฟล์ SVG เอง (learner-header-b-mobile-v2.svg) ไม่ใช่ที่นี่ อ่านคอมเมนต์
       ในไฟล์นั้นก่อนแก้ */
    background-image: url('/assets/img/learner-header-b-mobile-v2.svg');
  }
  .classhome__next { padding: var(--space-5); }
  .classhome__next .btn { min-height: 48px; }
}

/* B2 (visual-critic VIS-002, revision round 1): ≤30rem, .spinerow ทั่วเว็บ (base.css
 * ต้นไฟล์) ยุบเป็น grid-template-columns:1fr แถวเดียว ทำให้ลูกศร › (อยู่ใน .spinerow__aside)
 * ตกไปอยู่บรรทัดใหม่ใต้ชื่อรอบ — ถูกสำหรับจอ list เดิมที่มี badge ให้โชว์คู่ลูกศร แต่ Class
 * Home ไม่มี badge ในแถว (ลูกศรลอยเดี่ยว ไม่อยู่แนวเดียวกับอะไร) ยังทำให้แถวที่กดได้สูงกว่า
 * แถวที่กดไม่ได้ (ไม่มีลูกศรจึงเหลือแถวเดียว) — override เฉพาะ .classhome ให้กลับเป็นสอง
 * คอลัมน์ ลูกศรอยู่บรรทัดเดียวกับชื่อรอบเสมอ กึ่งกลางแนวตั้ง ความสูงแถวจึงเท่ากันไม่ว่าจะ
 * กดได้หรือไม่ */
@media (max-width: 30rem) {
  .classhome .spinerow {
    grid-template-columns: 1fr auto;
    row-gap: 0;
    align-items: center;
  }
  .classhome .spinerow__aside { justify-self: end; align-self: center; }
}

/* Assessment preparation uses the existing learner typography and palette. */
.assessment-preparation {
  margin-block: var(--space-4) var(--space-6);
  max-width: 90ch;
  color: var(--graphite-900);
}
.assessment-preparation h3 { font-size: 1rem; margin-bottom: var(--space-2); }
.assessment-preparation ul { padding-inline-start: 1.25rem; }
.assessment-preparation li { margin-bottom: var(--space-2); line-height: 1.6; }
.assessment-preparation p { margin-top: var(--space-3); }
.assessment-instructions { margin-top: var(--space-3); }
.assessment-instructions summary { cursor: pointer; color: var(--color-primary); font-size: var(--text-sm); }
.assessment-instructions .assessment-preparation { border: 0; margin-bottom: 0; padding-top: 0; }
.classhome__header--assessment {
  min-height: 200px;
  margin-bottom: var(--space-8);
  border-radius: var(--radius-md);
  background: var(--color-surface-sunken);
}
.classhome__header--assessment .classhome__headertext { padding: var(--space-6); }
.classhome__header--assessment .classhome__art { display: block; flex-basis: 30%; max-width: 30%; }
.assessment-summary { color: var(--color-ink-soft); font-size: var(--text-sm); }
.assessment-summary > span[aria-hidden] { margin-inline: var(--space-2); color: var(--color-rule); }
.assessment-focus {
  background: var(--color-surface-sunken);
  border: 0;
  border-radius: var(--radius-md);
  padding: var(--space-6);
}
.assessment-focus .classhome__nextkicker { color: var(--color-ink-soft); font-size: var(--text-body); }
.assessment-focus .classhome__nexttitle { font-size: var(--text-xl); }
.assessment-focus .btn--block { width: 100%; min-width: 0; min-height: 48px; }
.assessment-focus .assessment-preparation {
  padding-top: 0;
  border-top: 0;
  margin-top: var(--space-4);
}
.classswitch__item > button { width: 100%; text-align: start; }
.classhome:has(.classhome__header--assessment) { max-width: 70rem; }
.classhome:has(.classhome__header--assessment) .classhome__title {
  font-size: clamp(2.25rem, 3vw, 2.5rem);
}
.classhome:has(.classhome__header--assessment) .classhome__main { gap: var(--space-8); }
.classhome:has(.classhome__header--assessment) .classhome__support-card {
  border: 0;
  border-top: 1px solid var(--color-rule);
  border-radius: 0;
  background: transparent;
  padding: var(--space-5) 0 0;
  box-shadow: none;
}
.classhome:has(.classhome__header--assessment) .enter { animation: none; }
.classhome:has(.classhome__header--assessment) {
  --edition-gutter: clamp(1.5rem, 4vw, 3.75rem);
  max-width: 70rem;
  margin-inline: auto;
  padding-inline: var(--edition-gutter);
}
.classhome:has(.classhome__header--assessment) .classhome__header--assessment {
  width: 100%;
  min-height: 14.7rem;
  margin-inline: 0;
  margin-bottom: var(--space-8);
  border-radius: 0;
}
.classhome:has(.classhome__header--assessment) .classhome__headertext {
  padding-inline-start: var(--edition-gutter);
  padding-inline-end: var(--space-8);
}
.classhome:has(.classhome__header--assessment) .classhome__art {
  flex-basis: 30%;
  max-width: 30%;
}
.classhome:has(.classhome__header--assessment) .classhome__grid {
  max-width: 70rem;
  margin-inline: auto;
}
.learnerapp:has(#app.classhome) .learnernav { height: 76px; }
.learnerapp:has(#app.classhome) .learnernav__inner {
  max-width: 70rem;
  padding-inline: clamp(1.5rem, 4vw, 3.75rem);
}
.learnerapp:has(#app.classhome) .learnernav__home { font-size: 1.75rem; }
.classhome:has(.classhome__header--assessment) .classhome__nexttitle {
  font-size: 1.5rem;
}
.portal-art { height: 64px; margin-bottom: var(--space-5); background: var(--color-surface-sunken) url('/assets/img/learner-header-b-mobile-v2.svg') center / cover no-repeat; }
dialog.modal { border: 1px solid var(--color-rule); padding: 0; color: var(--color-ink); max-width: min(32rem, calc(100vw - 2rem)); }
dialog.modal::backdrop { background: rgb(0 0 0 / .35); }
.classhome:has(.classhome__header--assessment) .assessment-focus .btn--block { width: auto; min-width: 15rem; max-width: 100%; }
@media (max-width: 767px) {
  .classhome:has(.classhome__header--assessment) .assessment-focus .btn--block { width: 100%; min-width: 0; }
}
.assessment-edition {
  flex: 0 0 96px;
  width: 96px;
  height: 64px;
  border-radius: var(--radius-sm);
  background: var(--color-surface-sunken) url('/assets/img/learner-header-b-desktop-v1.svg') center / cover;
  pointer-events: none;
}
.assessment-edition--1 { background-position: left; }
.assessment-edition--2 { background-position: right; }
.classhome__rounds:has(.assessment-edition) .spinerow { display: flex; align-items: center; gap: var(--space-4); }
.classhome__rounds:has(.assessment-edition) .spinerow__id { flex: 1; min-width: 0; }

/* Entry and class choice use the same restrained Course Editions rhythm as Class Home:
   context first, then a single clear next decision. */
.entryhome__context {
  max-width: 34rem;
  margin: var(--space-3) 0 0;
  color: var(--color-ink-soft);
}
.entryhome .hero::after {
  content: '';
  display: block;
  width: 3rem;
  height: 2px;
  margin-top: var(--space-5);
  background: var(--color-signature);
}
.classswitch__item--all {
  margin-bottom: var(--space-2);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--color-rule);
}
.classchooser {
  max-width: 70rem;
  padding-top: var(--space-8);
}
.classchooser__loading { min-height: 14rem; }
.classchooser__header {
  display: flex;
  min-height: 15rem;
  margin-bottom: var(--space-8);
  background: var(--color-surface-sunken);
  overflow: hidden;
}
.classchooser__headertext {
  flex: 1;
  padding: clamp(var(--space-6), 5vw, var(--space-9));
}
.classchooser__title {
  max-width: 34rem;
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(2rem, 4vw, 3rem);
  font-weight: var(--weight-display);
  line-height: 1.1;
}
/* Spec B: one editorial shell for account, programme and classroom surfaces.
   Assessment content stays quiet: no decorative art behind questions/results. */
.learner-edition .learnernav { background: var(--color-paper); backdrop-filter: none; }
.learner-edition .learnernav__inner { max-width: 70rem; }
.learner-edition .wrap:has(> .edition-cover) { max-width: 70rem; }
.edition-cover {
  display: flex;
  min-height: 13rem;
  margin-bottom: 2rem;
  background: var(--color-surface-sunken);
  overflow: hidden;
}
.edition-cover__text { flex: 1; min-width: 0; padding: 2rem; align-self: center; }
.edition-cover h1 { font-family: var(--font-display); font-size: clamp(1.75rem, 3vw, 2.75rem); line-height: 1.25; margin: .6rem 0; }
.edition-cover .lede { margin: .75rem 0 0; font-size: 1rem; line-height: 1.75; }
.edition-cover__art { flex: 0 0 30%; background: url('/assets/img/learner-header-b-desktop-v1.svg') center / cover no-repeat; }
.learnerportal #login-app {
  max-width: 70rem;
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
  gap: 0 3rem;
  align-items: start;
  padding-top: 3rem;
}
.learnerportal .login-intro { grid-column: 1; grid-row: 1 / span 2; }
.edition-cover--login { grid-column: 1; grid-row: 1; min-height: 22rem; margin: 0; flex-direction: column; }
.edition-cover--login .edition-cover__text { width: 100%; padding: 2.5rem; }
.edition-cover--login .edition-cover__art { flex: none; width: 100%; height: 6rem; background-image: url('/assets/img/learner-header-b-mobile-v2.svg'); }
.learnerportal #class-link-notice { grid-column: 1; grid-row: 2; padding: 1.5rem 0; margin: 0; border: 0; background: transparent; color: var(--color-ink-soft); line-height: 1.8; }
.learnerportal #login-section, .learnerportal #register-section { grid-column: 2; grid-row: 1 / span 2; }
.portal-heading { font-size: 1.5rem; margin: 0 0 1.5rem; }
.learnerportal .card { padding: 1.75rem; box-shadow: none; border: 1px solid var(--color-rule); border-radius: 8px; }
.learnerportal .segtabs { margin-bottom: 1.75rem; }
.learnerportal .segtabs__tab { font: inherit; text-align: center; }
.learnerportal button.segtabs__tab { cursor: pointer; }
.learner-edition .input { min-height: 48px; border-radius: 4px; }
.learner-edition .spinerow { box-shadow: none; }
/* Owner revision: remove the wide grey gutter throughout learner lists.
   Keep only hairline row separators; account home uses individual tiles. */
.learner-edition .spinelist { padding-left: 0; border-radius: 0; background: transparent; gap: 0; }
.learner-edition .spinelist::before { content: none; }
.learner-edition .spinelist > .spinerow { border-bottom: 1px solid var(--color-rule); padding-inline: 0; }
.class-group { margin: 0 0 2rem; }
.assessment-navigation { display: flex; gap: 1.75rem; overflow-x: auto; border-bottom: 1px solid var(--color-rule); margin-bottom: 1.5rem; }
.assessment-navigation a { white-space: nowrap; padding: .85rem .1rem; color: var(--color-ink-soft); border-bottom: 2px solid transparent; }
.assessment-navigation a[aria-current="page"] { color: var(--color-ink); border-bottom-color: var(--color-ink); font-weight: var(--weight-strong); }
.assessment-navigation a:hover { color: var(--color-ink); background: var(--color-surface-sunken); }
.assessment-row-action { display: inline-flex; align-items: center; justify-content: center; min-height: 44px; min-width: 6rem; padding: .5rem 1rem; border: 1px solid var(--color-ink-soft); border-radius: 4px; color: var(--color-ink); font-size: var(--text-sm); font-weight: var(--weight-strong); }
.classhome__round:hover .assessment-row-action { background: var(--color-ink); color: var(--color-paper); }
.recent-documents { display: grid; }
.recent-document { display: flex; align-items: center; justify-content: space-between; gap: 1rem; min-height: 52px; padding: .75rem 0; border-bottom: 1px solid var(--color-rule); }
.recent-document span:first-child { text-decoration: underline; text-underline-offset: 4px; }
.recent-document:hover { background: var(--color-surface-sunken); }
.recent-result-action { margin-top: 1rem; }
.class-group__title { font-size: 1rem; font-weight: var(--weight-strong); margin: 0 0 1rem; }
.class-tiles { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.25rem; }
.learner-edition .class-tile {
  display: flex; flex-direction: column; align-items: stretch; gap: .75rem;
  text-align: left;
  min-height: 13rem; padding: 1.5rem; border: 1px solid var(--color-rule); border-radius: 8px;
}
.class-tile__top, .class-tile__foot { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.class-tile__top { margin-bottom: .5rem; }
.learner-edition .class-tile .spinerow__title { font-size: 1.35rem; margin: 0; }
.class-tile__foot { margin-top: auto; padding-top: 1.25rem; border-top: 1px solid var(--color-rule); }
.class-tile__open { font-size: var(--text-sm); color: var(--color-primary); font-weight: var(--weight-strong); }
.learner-edition button.class-tile:hover { background: var(--color-surface-sunken); border-color: var(--color-ink-soft); }
@media (max-width: 46.24rem) { .class-tiles { grid-template-columns: 1fr; } }
.learner-edition .program-navigation { margin: 0 0 2rem; }
.learner-edition .program-navigation a { min-height: 44px; display: inline-flex; align-items: center; }
.learner-edition .program-page { min-width: 0; }
.learner-edition .program-overview-layout {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  grid-template-areas: "main feed";
  gap: var(--space-8);
  align-items: start;
}
.learner-edition .program-overview-main { min-width: 0; grid-area: main; }
.learner-edition .program-overview-main__title {
  margin: 0 0 var(--space-3);
  font-family: var(--font-display);
  font-size: var(--text-lg);
  line-height: 1.4;
}
.learner-edition .program-class-list {
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--color-rule);
}
.learner-edition .program-class-row {
  display: grid;
  grid-template-columns: 2.5rem minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-4);
  min-width: 0;
  min-height: 6.5rem;
  padding: var(--space-5) 0;
  border-bottom: 1px solid var(--color-rule);
  color: var(--color-ink);
  text-decoration: none;
}
/* เจ้าของสั่ง 2026-09-19 — หลังเอาแถบเมนูล่างออก เส้นใต้แถวสุดท้ายโผล่มา
   ลอยๆ (เดิมถูกแถบล่างบัง) อ่านเหมือนรายการยังไม่จบ · ขอบบนของรายการ
   ยังมี border-top ที่ .program-class-list จึงยังมีกรอบครบ ไม่กระทบเส้นระหว่างแถว */
@media (max-width: 34rem) {
  .learner-edition .program-class-list .program-class-row:last-child { border-bottom: 0; }
}
.learner-edition .program-class-row:hover { background: var(--color-surface-sunken); }
.learner-edition .program-class-row:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 4px;
}
.learner-edition .program-class-row__index {
  align-self: start;
  padding-top: .2rem;
  color: var(--color-primary);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
}
.learner-edition .program-class-row__body { display: grid; gap: var(--space-2); min-width: 0; }
.learner-edition .program-class-row__name {
  min-width: 0;
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: var(--weight-strong);
  line-height: 1.35;
  overflow-wrap: anywhere;
}
.learner-edition .program-class-row__facts {
  color: var(--color-ink-soft);
  font-size: var(--text-sm);
  line-height: 1.55;
  overflow-wrap: anywhere;
}
.learner-edition .program-class-row__open {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  min-height: 44px;
  color: var(--color-primary);
  font-size: var(--text-sm);
  font-weight: var(--weight-strong);
  white-space: nowrap;
}
.learner-edition .program-class-row.is-loading,
.learner-edition .program-action-summary__primary.is-loading { opacity: .55; pointer-events: none; }
.learner-edition .program-classes-empty {
  display: grid;
  gap: var(--space-2);
  padding: var(--space-5) 0;
  border-top: 1px solid var(--color-rule);
  border-bottom: 1px solid var(--color-rule);
}
.learner-edition .program-classes-empty p { margin: 0; }
.learner-edition .program-classes-empty .small { color: var(--color-ink-soft); }
.learner-edition .program-action-feed {
  grid-area: feed;
  min-width: 0;
  padding: var(--space-3);
  border-top: 2px solid var(--color-ink);
  background: var(--color-surface-sunken);
}
.learner-edition .program-action-feed h2 {
  margin: 0 0 var(--space-2);
  font-family: var(--font-display);
  font-size: var(--text-md);
  line-height: 1.35;
}
.learner-edition .program-action-summary {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.learner-edition .program-action-summary__block {
  display: grid;
  gap: var(--space-1);
  min-width: 0;
}
.learner-edition .program-action-summary__block + .program-action-summary__block {
  padding-top: var(--space-1);
  border-top: 1px solid var(--color-rule);
}
.learner-edition .program-action-summary__eyebrow {
  display: flex;
  align-items: center;
  gap: .35rem;
  margin: 0;
  color: var(--color-primary);
  font-size: var(--text-xs);
  font-weight: var(--weight-strong);
}
.learner-edition .program-action-summary__icon {
  flex: none;
  width: 16px;
  height: 16px;
}
.learner-edition .program-action-summary__chevron { transform-origin: 50% 50%; transition: transform .15s ease; }
.learner-edition .program-action-summary__details[open] > summary .program-action-summary__chevron { transform: rotate(90deg); }
.learner-edition .program-action-summary__next h3,
.learner-edition .program-action-summary__time,
.learner-edition .program-action-summary__topic,
.learner-edition .program-action-summary__venue,
.learner-edition .program-action-summary__work-status { margin: 0; }
.learner-edition .program-action-summary__when {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.learner-edition .program-action-summary__next h3 {
  font-family: var(--font-display);
  font-size: var(--text-md);
  line-height: 1.45;
  overflow-wrap: anywhere;
}
.learner-edition .program-action-summary__time {
  color: var(--color-ink);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
}
.learner-edition .program-action-summary__topic {
  display: grid;
  gap: .1rem;
  line-height: 1.4;
}
.learner-edition .program-action-summary__topic strong { font-size: var(--text-md); }
.learner-edition .program-action-summary__topic span {
  color: var(--color-ink-soft);
  font-size: var(--text-xs);
  overflow-wrap: anywhere;
}
.learner-edition .program-action-summary__venue {
  color: var(--color-ink-soft);
  font-size: var(--text-sm);
  line-height: 1.45;
  overflow-wrap: anywhere;
}
.learner-edition .program-action-summary__prep {
  margin: 0;
  color: var(--color-ink-soft);
  font-size: var(--text-xs);
  line-height: 1.45;
  overflow-wrap: anywhere;
}
.learner-edition .program-action-summary__prep span {
  color: var(--color-ink);
  font-weight: var(--weight-strong);
}
.learner-edition .program-action-summary__work-status {
  color: var(--color-ink);
  font-size: var(--text-sm);
  font-weight: var(--weight-strong);
  line-height: 1.5;
  overflow-wrap: anywhere;
}
.learner-edition .program-action-summary__work-note {
  margin: 0;
  color: var(--color-ink-soft);
  font-size: var(--text-sm);
  line-height: 1.5;
  overflow-wrap: anywhere;
}
.learner-edition .program-action-summary__primary {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--color-primary);
  color: var(--color-on-primary);
  font-size: var(--text-sm);
  font-weight: var(--weight-strong);
  text-decoration: none;
  text-align: center;
}
.learner-edition .program-action-summary__primary:hover { background: var(--color-primary-hover); }
.learner-edition .program-action-summary__primary:focus-visible,
.learner-edition .program-action-summary__details > summary:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 3px;
}
.learner-edition .program-action-summary__details { border-top: 1px solid var(--color-rule); }
.learner-edition .program-action-summary__details > summary {
  display: flex;
  align-items: center;
  gap: .4rem;
  min-height: 44px;
  width: 100%;
  cursor: pointer;
  color: var(--color-ink);
  font-size: var(--text-sm);
  font-weight: var(--weight-strong);
  text-decoration: none;
  list-style: none;
}
.learner-edition .program-action-summary__details > summary::-webkit-details-marker { display: none; }
.learner-edition .program-action-summary__details-body {
  display: grid;
  gap: var(--space-4);
  padding-top: var(--space-3);
}
.learner-edition .program-action-summary__detail-section {
  display: grid;
  gap: var(--space-2);
  min-width: 0;
}
.learner-edition .program-action-summary__detail-section + .program-action-summary__detail-section {
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-rule);
}
.learner-edition .program-action-summary__detail-section h3,
.learner-edition .program-action-summary__detail-section p,
.learner-edition .program-action-summary__detail-note,
.learner-edition .program-action-summary__detail-empty { margin: 0; }
.learner-edition .program-action-summary__detail-note {
  color: var(--color-ink-soft);
  font-size: var(--text-xs);
  line-height: 1.5;
  overflow-wrap: anywhere;
}
.learner-edition .program-action-summary__detail-section .program-action-summary__detail-warning {
  margin: 0;
  color: var(--color-ink);
  font-size: var(--text-sm);
  line-height: 1.5;
  overflow-wrap: anywhere;
}
.learner-edition .program-action-summary__detail-section h3 {
  font-size: var(--text-sm);
  font-weight: var(--weight-strong);
}
.learner-edition .program-action-summary__detail-section p,
.learner-edition .program-action-summary__detail-empty {
  color: var(--color-ink-soft);
  font-size: var(--text-sm);
  line-height: 1.5;
  overflow-wrap: anywhere;
}
.learner-edition .program-action-summary__work-list {
  display: grid;
  gap: var(--space-3);
}
.learner-edition .program-action-summary__work-row {
  display: grid;
  gap: var(--space-1);
  min-width: 0;
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-rule);
}
.learner-edition .program-action-summary__work-title,
.learner-edition .program-action-summary__work-meta,
.learner-edition .program-action-summary__work-state,
.learner-edition .program-action-summary__work-evidence { margin: 0; }
.learner-edition .program-action-summary__work-title {
  font-size: var(--text-sm);
  line-height: 1.45;
  overflow-wrap: anywhere;
}
.learner-edition .program-action-summary__work-meta,
.learner-edition .program-action-summary__work-state,
.learner-edition .program-action-summary__work-evidence {
  color: var(--color-ink-soft);
  font-size: var(--text-xs);
  line-height: 1.45;
  overflow-wrap: anywhere;
}
.learner-edition .program-action-summary__work-state { color: var(--color-ink); }
.learner-edition .program-action-summary__work-evidence { color: var(--color-ink-soft); }
.learner-edition .program-action-summary__details[open] > summary { margin-bottom: 0; }
.learner-edition .program-page__title {
  margin: 0 0 1.25rem;
  font-size: 1.5rem;
  line-height: 1.35;
}
.learner-edition .program-page__title:focus { outline: 2px solid var(--color-primary); outline-offset: 4px; }
.learner-edition .program-page__lede { margin: 0 0 1.5rem; color: var(--color-ink-soft); }
.learner-edition .program-material-groups__list {
  display: flex;
  flex-direction: column;
  gap: 2rem;
}
.learner-edition .program-material-group { min-width: 0; }
.learner-edition .program-material-group__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: .75rem;
}
.learner-edition .program-material-group__title {
  min-width: 0;
  font-size: 1.1rem;
  font-weight: var(--weight-strong);
  line-height: 1.4;
  margin: 0;
  overflow-wrap: anywhere;
}
.learner-edition .program-material-group__count { flex: 0 0 auto; white-space: nowrap; }
.learner-edition .program-material-group__items {
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--color-rule);
}
.learner-edition .program-material-group__items .material {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  min-width: 0;
  min-height: 44px;
  margin: 0;
  padding: .75rem 0;
  border-bottom: 1px solid var(--color-rule);
  text-decoration: none;
}
.learner-edition .program-material-group__items .material:hover { background: var(--color-surface-sunken); }
.learner-edition .program-material-group__items .material__title { min-width: 0; overflow-wrap: anywhere; }
.learner-edition .program-material-group__items .material__open {
  flex: 0 0 auto;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
}
.learner-edition .program-material-groups__empty,
.learner-edition .program-schedule__empty {
  min-height: 9rem;
  display: grid;
  place-items: center;
  color: var(--color-ink-soft);
  border: 1px solid var(--color-rule);
  border-radius: 8px;
  background: var(--color-surface);
  box-shadow: none;
}
.learner-edition .program-schedule-content { display: flex; flex-direction: column; gap: 2.75rem; }
.learner-edition .program-schedule-phases h3,
.learner-edition .program-schedule-onsite h3,
.learner-edition .program-schedule-showcase h3 {
  margin: 0 0 1rem;
  font-size: 1.15rem;
  line-height: 1.4;
}
.learner-edition .program-schedule-timeline {
  display: grid;
  gap: 1rem;
  list-style: none;
  margin: 0;
  padding: 0;
}
.learner-edition .program-schedule-phase {
  display: grid;
  grid-template-columns: 3rem minmax(0, 1fr);
  gap: 1rem;
  min-width: 0;
  padding: 1.25rem 0;
  border-top: 1px solid var(--color-rule);
}
.learner-edition .program-schedule-phase:first-child { border-top: 2px solid var(--color-ink); }
.learner-edition .program-schedule-phase__index {
  padding-top: .15rem;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--color-ink-soft);
}
.learner-edition .program-schedule-phase__body { min-width: 0; }
.learner-edition .program-schedule-phase h4 { margin: 0; font-size: 1.1rem; line-height: 1.45; }
.learner-edition .program-schedule-phase p { margin: .25rem 0 1rem; color: var(--color-ink-soft); line-height: 1.5; }
.learner-edition .program-schedule-deliveries {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  gap: .75rem 1.25rem;
}
.learner-edition .program-schedule-delivery {
  display: grid;
  gap: .2rem;
  min-width: 0;
  padding-left: .75rem;
  border-left: 2px solid var(--color-ink);
}
.learner-edition .program-schedule-delivery strong { font-size: var(--text-sm); }
.learner-edition .program-schedule-delivery span { line-height: 1.5; }
.learner-edition .program-schedule-delivery small { color: var(--color-ink-soft); }
.learner-edition .program-schedule-calendar {
  border-top: 2px solid var(--color-ink);
  padding-top: 1rem;
}
.learner-edition .program-schedule-calendar__head {
  display: flex;
  align-items: end;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1rem;
}
.learner-edition .program-schedule-calendar__head h3 { margin: .2rem 0 0; }
.learner-edition .program-schedule-calendar__months {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1rem;
}
.learner-edition .program-schedule-month {
  min-width: 0;
  padding: 1rem;
  border: 1px solid var(--color-rule);
  border-radius: 8px;
}
.learner-edition .program-schedule-month h4 { margin: 0 0 .75rem; font-size: 1rem; }
.learner-edition .program-schedule-calendar__weekdays,
.learner-edition .program-schedule-calendar__grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: .2rem;
  text-align: center;
}
.learner-edition .program-schedule-calendar__weekdays {
  margin-bottom: .2rem;
  color: var(--color-ink-soft);
  font-size: var(--text-xs);
}
.learner-edition .program-schedule-calendar__date,
.learner-edition .program-schedule-calendar__blank {
  display: grid;
  min-height: 2.5rem;
  place-items: center;
}
.learner-edition .program-schedule-calendar__date {
  color: var(--color-ink-faint);
  font-size: var(--text-sm);
}
.learner-edition button.program-schedule-calendar__date--event {
  min-width: 0;
  padding: 0;
  border: 1px solid var(--color-primary);
  border-radius: 4px;
  background: var(--color-paper);
  color: var(--color-ink);
  font: inherit;
  font-weight: var(--weight-strong);
  cursor: pointer;
}
.learner-edition button.program-schedule-calendar__date--event:hover { background: var(--color-surface-sunken); }
.learner-edition button.program-schedule-calendar__date--event:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.learner-edition button.program-schedule-calendar__date--event.is-selected {
  border: 2px solid var(--color-ink);
  background: var(--color-ink);
  color: var(--color-paper);
}
.learner-edition .program-schedule-topic { overflow-wrap: anywhere; }
.learner-edition .program-schedule-topic__en,
.learner-edition .program-schedule-topic__th { display: block; }
.learner-edition .program-schedule-topic__th { margin-top: .25rem; color: var(--color-ink-soft); }
.learner-edition .program-schedule-days { display: grid; gap: 1rem; }
.learner-edition .program-schedule-day {
  min-width: 0;
  padding: 1rem 0 0;
  border-top: 2px solid var(--color-ink);
  scroll-margin-top: 1rem;
}
.learner-edition .program-schedule-day:focus { outline: 2px solid var(--color-primary); outline-offset: 4px; }
.learner-edition .program-schedule-day__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: .75rem;
}
.learner-edition .program-schedule-day h4 { margin: 0; font-size: 1rem; }
.learner-edition .program-schedule-day__room { margin: 0; color: var(--color-ink-soft); font-size: var(--text-sm); }
.learner-edition .program-schedule-day__sessions { border-top: 1px solid var(--color-rule); }
.learner-edition .program-schedule-session {
  display: grid;
  grid-template-columns: 8rem minmax(0, 1fr);
  gap: 1rem;
  padding: .85rem 0;
  border-bottom: 1px solid var(--color-rule);
}
.learner-edition .program-schedule-session time { white-space: nowrap; font-variant-numeric: tabular-nums; }
.learner-edition .program-schedule-showcase { border-top: 2px solid var(--color-ink); padding-top: 1rem; }
.learner-edition .program-schedule-showcase__body { display: grid; grid-template-columns: auto auto 1fr; gap: .35rem 1rem; align-items: baseline; }
.learner-edition .program-schedule-showcase__body p { grid-column: 1 / -1; margin: .5rem 0 0; }
@media (max-width: 46.24rem) {
  .learner-edition .program-overview-layout {
    grid-template-columns: 1fr;
    grid-template-areas: "feed" "main";
    gap: 2rem;
  }
  .learner-edition .program-class-row {
    grid-template-columns: 2rem minmax(0, 1fr);
    align-items: start;
    gap: .75rem;
    min-height: 0;
    padding-block: 1rem;
  }
  .learner-edition .program-class-row__open { grid-column: 2; }
  .learner-edition .program-class-row__name { font-size: 1.1rem; }
  .learner-edition .program-navigation { margin-bottom: 1.5rem; }
  .learner-edition .program-page__title { margin-bottom: var(--space-2); }
  .learner-edition .program-action-feed h2 { margin-bottom: var(--space-2); }
  .learner-edition .program-action-summary { gap: var(--space-1); }
  .learner-edition .program-action-summary__block { gap: var(--space-1); }
  .learner-edition .program-action-summary__block + .program-action-summary__block { padding-top: var(--space-2); }
  .learner-edition .program-navigation a { flex: 1 1 0; justify-content: center; }
  .learner-edition .program-material-group__head { align-items: flex-start; flex-direction: column; gap: .25rem; }
  .learner-edition .program-material-group__items .material { align-items: flex-start; flex-direction: column; gap: .25rem; }
  .learner-edition .program-material-group__items .material__open { min-height: 44px; }
  .learner-edition .program-material-groups__empty,
  .learner-edition .program-schedule__empty { min-height: 7rem; }
  .learner-edition .program-schedule-calendar__head { align-items: flex-start; flex-direction: column; }
  .learner-edition .program-schedule-calendar__months { grid-template-columns: 1fr; }
  .learner-edition .program-schedule-phase { grid-template-columns: 2.25rem minmax(0, 1fr); gap: .75rem; }
  .learner-edition .program-schedule-deliveries { grid-template-columns: 1fr; }
  .learner-edition .program-schedule-day__head { align-items: flex-start; flex-direction: column; gap: .25rem; }
  .learner-edition .program-schedule-session { display: grid; grid-template-columns: 6.6rem minmax(0, 1fr); gap: .65rem; padding: .75rem 0; border-top: 1px solid var(--color-rule); }
  .learner-edition .program-schedule-showcase__body { grid-template-columns: 1fr; gap: .25rem; }
}
.learner-edition .spinerow__title { line-height: 1.5; }
.learner-edition .spinerow__note { font-family: var(--font-sans); line-height: 1.65; }
.learner-edition .qcard { box-shadow: none; border-radius: 8px; }
.learner-edition .material { box-shadow: none; border-radius: 8px; }
.learner-edition #material-search { max-width: 32rem; margin-bottom: .75rem; }
.learner-edition #material-search-status { color: var(--color-ink-soft); font-size: var(--text-sm); margin: .75rem 0; }
.learner-edition .page-title { line-height: 1.35; }
.learner-edition .wrap:has(#mtl-report-status) { max-width: 70rem; }
.learner-edition #mtl-report-status, .learner-edition .wrap:has(#mtl-report-status) #continue-list {
  width: fit-content; max-width: 100%; align-self: flex-start;
}
.learner-edition #done-report > .card { box-shadow: none; border: 1px solid var(--color-rule); border-radius: 8px; }
.learner-auth-companion main::before {
  content: ''; display: block; height: 64px; margin-bottom: 2rem;
  background: var(--color-surface-sunken) url('/assets/img/learner-header-b-mobile-v2.svg') center / cover no-repeat;
}
.learner-auth-companion .card { border: 1px solid var(--color-rule); border-radius: 8px; box-shadow: none; }
/* Entry, identity confirmation, password setup and profile gates share B.
   The legacy page-ribbon toggle now marks this shell, never shows a gradient. */
.learner-edition .learner-entry-b { max-width: 52rem; padding-top: 2rem; }
.learner-entry-b > .hero {
  position: relative; background: var(--color-surface-sunken);
  padding: 2rem 30% 2rem 2rem; margin: 0 0 2rem;
  min-height: 12rem; display: flex; flex-direction: column; justify-content: center;
}
.learner-entry-b > .hero::after {
  content: ''; position: absolute; inset: 0 0 0 auto; width: 26%; height: auto;
  margin: 0; background: url('/assets/img/learner-header-b-desktop-v1.svg') center / cover no-repeat;
}
.learner-entry-b > .hero h1, .learner-entry-b .loginhero__title { font-size: clamp(1.75rem, 3vw, 2.5rem); line-height: 1.3; }
.learner-entry-b .screen-h1 { font-family: var(--font-sans); font-size: 1.25rem; margin-bottom: 1.25rem; }
.learner-entry-b .card { border: 1px solid var(--color-rule); border-radius: 8px; box-shadow: none; }
.learner-entry-b .card--selected, .learner-entry-b .card--neutral { border-left-width: 1px; padding-left: var(--space-6); }
.learner-entry-b .card--primary { border-left-color: var(--color-rule); }
.learner-entry-b .card--folded { min-height: 96px; }
.learner-entry-b .card-folded__arrow { border: 1px solid var(--color-rule); border-radius: 4px; min-width: 40px; min-height: 40px; display: grid; place-items: center; }
.learner-entry-b button.card--folded:hover { border-color: var(--color-ink-soft); background: var(--color-surface-sunken); }
.learner-edition .settings-group { border-bottom: 1px solid var(--color-rule); padding-bottom: 1.5rem; margin-bottom: 1.5rem; }
.learner-edition .settings-group h3 { font-size: 1.125rem; margin-bottom: .75rem; }
.learner-edition #settings-backdrop .modal { border: 1px solid var(--color-rule); border-radius: 8px; }
.learner-edition #settings-backdrop .modal__head { border-top: 4px solid var(--color-ink); background: var(--color-surface-sunken); }
@media (max-width: 46.24rem) {
  .learner-entry-b > .hero { padding: 1.5rem 1.5rem 5.5rem; min-height: 0; }
  .learner-entry-b > .hero::after { inset: auto 0 0; width: 100%; height: 64px; background-image: url('/assets/img/learner-header-b-mobile-v2.svg'); }
  .learner-entry-b .card--selected, .learner-entry-b .card--neutral { padding-left: 1.25rem; }
}
@media (max-width: 46.24rem) {
  .edition-cover { flex-direction: column; min-height: 0; margin-bottom: 1.5rem; }
  .edition-cover__text, .edition-cover--login .edition-cover__text { padding: 1.5rem; }
  .edition-cover__art, .edition-cover--login .edition-cover__art { flex: none; height: 64px; background-image: url('/assets/img/learner-header-b-mobile-v2.svg'); }
  .learnerportal #login-app { display: block; padding-top: 1.5rem; }
  .learnerportal #class-link-notice { padding: 1.25rem 0; font-size: var(--text-sm); }
  .learnerportal .card { padding: 1.25rem; }
  .portal-heading { font-size: 1.25rem; margin-bottom: 1rem; }
}
/* หน้าภาพรวมโปรแกรมเท่านั้น — hero ที่ตามด้วยแท็บ .program-navigation ซึ่ง
   render จาก program.js เพียงที่เดียว บีบระยะบน-ล่างของ hero บนมือถือ
   ให้ปุ่มหลักของแถบ awareness อยู่เหนือแถบเมนูล่างตั้งแต่เปิดหน้า
   ไม่แตะ .edition-cover ที่ใช้ร่วมกับ login / activate / me */
@media (max-width: 46.24rem) {
  .edition-cover:has(+ .program-navigation) { margin-bottom: var(--space-4); }
  .edition-cover:has(+ .program-navigation) .edition-cover__text { padding-block: var(--space-4); }
}

/* หน้าภาพรวมโปรแกรมเท่านั้น — เจ้าของตัดสิน 2026-09-19: หน้านี้ไม่มีแถบเมนูล่าง
   (program.js renderTabbar ส่ง tabs: null) ⇒ ปุ่มออกจากระบบต้องกลับมาอยู่แถบบน
   เหมือน PC · base.css:1323 ซ่อน #logout ทุกหน้าที่ ≤34rem เพราะเดิมย้ายไปอยู่แถบล่าง
   — กฎนั้นไม่แตะ (app.html/me.html ยังมีแถบล่างตามเดิม) แต่ override เฉพาะหน้านี้
   ตัวผูก: body.learnerprogram — program.js ติดให้ตั้งแต่ตอนโหลดสคริปต์
   (แบบเดียวกับ body.learnerapp ของ quiz.js:48) ⇒ มีครบทุกจอรวมจอ error และจอ login
   เจตนา: ห้ามกลับไปใช้ :has() หรือโครงของจอใดจอหนึ่ง — จอ error ไม่มี .program-navigation
   และไม่มีแถบล่างแล้ว กฎไม่ติดตรงนั้น = หน้าที่ออกจากระบบไม่ได้ (เจ้าของสั่งปิด 2026-09-19)
   พื้นที่กด ≥44 CSS px ตามเกณฑ์เดียวกับ .program-navigation a (base.css:2708) */
@media (max-width: 34rem) {
  body.learnerprogram #logout {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding-inline: var(--space-2);
  }

  /* เจ้าของสั่ง 2026-09-19 — ชื่อโปรแกรมบนแถบบนซ้ำกับหัวเรื่องใหญ่
     (.edition-cover h1) ที่อยู่ต่ำลงมาแค่ ~140px · บนมือถือที่มีบัญชี (has_account)
     ลิงก์ "โปรแกรมของฉัน" + Log out เบียดจนชื่อโปรแกรมเหลือ 53px ถูกตัดเป็น "QA Lea…"
     ⇒ ซ่อนเชิงภาพ เฉพาะหน้าโปรแกรม เฉพาะมือถือ · ห้ามเอาออกจาก DOM — โปรแกรม
     อ่านหน้าจอยังต้องอ่านเจอ · ชุดคุณสมบัติข้างล่างคือของ .sr-only (base.css:43-47) ทุกบรรทัด
     คัดมาใช้ซ้ำ ไม่ได้คิดขึ้นใหม่ — ติด class .sr-only ที่ HTML ไม่ได้ เพราะจะพาไปซ่อน
     บน 1280 ด้วย ซึ่งเจ้าของสั่งห้ามขยับ · ผูกที่ .learnernav__meta ไม่ใช่ลูกข้างใน
     เพื่อไม่ให้เหลือกล่องเปล่ากิน gap ของ .learnernav__student

     ผูกกับ :has(.edition-cover) ด้วย (เจ้าของสั่ง 2026-09-19 รอบที่สอง) — .edition-cover
     คือกล่องหัวเรื่องใหญ่ที่พิมพ์ชื่อโปรแกรมซ้ำ จึงเป็นเงื่อนไขที่ตรงกับเหตุผลของการซ่อนจริงๆ
     (ไม่ใช้ .program-navigation ซึ่งเป็นแถบแท็บ ไม่ใช่ตัวซ้ำ) · จอ error ไม่มี .edition-cover
     ⇒ ชื่อโปรแกรมกลับมาโชว์ — จอนั้นไม่มีหัวเรื่องใหญ่ให้พึ่ง ถ้าซ่อนด้วยจะไม่เหลืออะไรบอกโปรแกรมเลย
     ใช้ :has() ตรงนี้ได้ เพราะเบราว์เซอร์เก่าที่ไม่รู้จัก :has() จะทิ้งกฎทั้งข้อ = "ชื่อโชว์"
     ซึ่งไม่เสียหาย · ต่างจาก #logout ที่ผลของการทิ้งกฎคือ "ออกจากระบบไม่ได้" — ตัวนั้น
     จึงต้องคงใช้ class จาก JS ห้ามเปลี่ยนกลับไปใช้ :has() เด็ดขาด */
  body.learnerprogram:has(.edition-cover) .learnernav__meta {
    position: absolute; width: 1px; height: 1px;
    padding: 0; margin: -1px; overflow: hidden;
    clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
  }
}
.classchooser__intro {
  max-width: 35rem;
  margin: var(--space-4) 0 0;
  color: var(--color-ink-soft);
}
.classchooser__art {
  flex: 0 0 30%;
  background: var(--color-surface-sunken) url('/assets/img/learner-header-b-desktop-v1.svg') center / cover no-repeat;
}
.classchooser__list {
  border-top: 2px solid var(--color-signature);
}
.classchooser__row {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  min-height: 6rem;
  padding: var(--space-5) var(--space-4);
  border: 0;
  border-bottom: 1px solid var(--color-rule);
  background: transparent;
  color: var(--color-ink);
  font: inherit;
  text-align: start;
}
button.classchooser__row { cursor: pointer; }
button.classchooser__row:hover { background: var(--color-surface-sunken); }
.classchooser__name { display: block; font-size: var(--text-lg); font-weight: var(--weight-strong); }
.classchooser__status { display: block; margin-top: var(--space-1); color: var(--color-ink-soft); font-size: var(--text-sm); }
.classchooser__current { color: var(--color-ink-soft); font-size: var(--text-sm); }
.classchooser__arrow { color: var(--color-primary); font-size: var(--text-xl); }
.classchooser__back { margin-top: var(--space-6); }
.classchooser__empty { padding-block: var(--space-8); }
@media (max-width: 46.24rem) {
  .classchooser { padding-top: var(--space-5); }
  .classchooser__header { min-height: 0; margin-bottom: var(--space-6); }
  .classchooser__headertext { padding: var(--space-6) var(--space-5); }
  .classchooser__art { flex-basis: 100%; height: 64px; background-image: url('/assets/img/learner-header-b-mobile-v2.svg'); }
  .classchooser__header { flex-direction: column; }
  .classchooser__row { min-height: 5.5rem; padding-inline: 0; }
  .classchooser__name { font-size: var(--text-md); }
}
@media (max-width: 46.24rem) {
  .classhome__header--assessment { min-height: 0; }
  .classhome__header--assessment { margin-bottom: var(--space-4); }
  .classhome:has(.classhome__header--assessment) { --edition-gutter: 1.5rem; }
  .classhome:has(.classhome__header--assessment) .classhome__header--assessment {
    min-height: 0;
    margin-bottom: var(--space-5);
  }
  .classhome__header--assessment .classhome__headertext { padding: var(--space-5) var(--edition-gutter) var(--space-3); }
  .classhome__header--assessment .classhome__art { height: 64px; flex: none; max-width: 100%; }
  .classhome:has(.classhome__header--assessment) .classhome__title { font-size: clamp(1.75rem, 7vw, 2rem); }
  .assessment-focus { padding: var(--space-5); }
  .assessment-focus .btn--block { width: 100%; min-width: 0; }
  .assessment-edition { flex-basis: 56px; width: 56px; height: 40px; }
}

/* ==============================================================
 * เพิ่มสำหรับหน้า Instructor ของ Demo · รออนุมัติเป็น component ถาวร
 * Demo Instructor dashboard — SPEC 30/31 (2026-09-20)
 * Additive only: brand-new classes for public/demo-instructor.html.
 * No rule above this block is edited. Scope: instructor demo dashboard only.
 * ============================================================== */
.card__title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  font-size: var(--text-lg);
  font-weight: var(--weight-strong);
  color: var(--color-ink);
  margin: 0 0 var(--space-4) 0;
}
.card__subtitle {
  font-size: var(--text-sm);
  font-weight: var(--weight-body);
  color: var(--color-ink-soft);
  margin: 0 0 var(--space-4) 0;
  line-height: 1.5;
}
/* 2026-09-20 (Ray, close-out round 3, item 16): removed .msg--warn,
 * .msg--ok and .badge--pill-demo entirely — after item 4's fix (next-actions
 * cards no longer use .msg--ok; see .card--accent-ok below), nothing in this
 * file called any of the three. Ray's direction doc now has a standing rule
 * against leaving unused components in this shared file "just in case" —
 * add them back when a real caller needs them, with that caller's actual
 * requirements in hand, not speculatively. (.msg--err/.msg--info above are
 * untouched and still have real callers elsewhere in the app.)
 *
 * item 4 (Ray, close-out round 3 — Ray's own tie-break of two conflicting
 * review gates): the "ควรทำอะไรต่อ" cards used to fill an entire .msg--ok
 * green card — the owner's direction explicitly bans coloring a whole card
 * just because it's "a recommendation" (direction §4). Replaced with the
 * plain .card surface plus this 4px left accent only. */
.card--accent-ok { border-left: 4px solid var(--color-ok); }

/* VIS-A03 close-out: was plain .msg--info, whose background (--color-surface-
 * sunken) is the exact same token as .classhome__header's background right
 * above it — the band read as a stray leftover fragment of the header, not
 * its own thing. Give it the surface color + a border instead, everything
 * else (padding, text color) still comes from .msg/.msg--info. */
.sample-class-banner { background: var(--color-surface); border: 1px solid var(--color-rule); }

.obs-card {
  border: 1px solid var(--color-rule);
  border-radius: var(--radius-sm);
  padding: var(--space-4);
  margin-bottom: var(--space-3);
}
.obs-card:last-child { margin-bottom: 0; }
.obs-card__line { font-size: var(--text-sm); line-height: 1.5; margin: 0 0 var(--space-2) 0; }
.obs-card__line:last-child { margin-bottom: 0; }
.obs-card__label { font-weight: var(--weight-strong); color: var(--color-ink); }
/* VIS-A04 close-out: the rubric-distinction line carries the level contrast
 * (bold "ระดับ N" spans, ink color) — the two hedged lines below it read
 * quieter (ink-soft) so a 3-second scan lands on the level tags first. */
.obs-card__line--primary { color: var(--color-ink); font-weight: var(--weight-body); }
.obs-card__line--primary strong { font-weight: var(--weight-strong); }
.obs-card__line--secondary { color: var(--color-ink-soft); }

.evidence-block {
  border: 1px solid var(--color-rule);
  border-radius: var(--radius-sm);
  padding: var(--space-4);
  margin-bottom: var(--space-4);
}
.evidence-block:last-child { margin-bottom: 0; }
.evidence-block__crit {
  padding: var(--space-2) 0;
  border-top: 1px dashed var(--color-rule);
  font-size: var(--text-sm);
  line-height: 1.5;
}
.evidence-block__crit:first-of-type { border-top: none; padding-top: 0; }

.demo-instr-bar {
  display: flex;
  height: 1.25rem;
  border-radius: var(--radius-pill);
  overflow: hidden;
  margin: var(--space-4) 0 var(--space-2) 0;
  background: var(--color-surface-sunken);
}
.demo-instr-bar__seg { height: 100%; }
.demo-instr-legend { display: flex; flex-wrap: wrap; gap: var(--space-4); font-size: var(--text-sm); margin-top: var(--space-2); }
.demo-instr-legend__item { display: flex; align-items: center; gap: var(--space-1); }
.demo-instr-legend__dot { width: .65rem; height: .65rem; border-radius: 50%; flex: none; }

/* [data-surface="inverse"] only had a :focus-visible outline color before this
 * change — .btn/.btn--secondary had no dark-surface variant yet, so the demo
 * banner (dark background) needed one to use the system button at all. New
 * selectors only; .btn/.btn--secondary's own light-surface rules above are
 * untouched. */
[data-surface="inverse"] .btn {
  background: var(--graphite-700);
  color: var(--surface-0);
}
[data-surface="inverse"] .btn:hover { background: var(--graphite-600); }
[data-surface="inverse"] .btn--secondary {
  background: transparent;
  border-color: var(--color-rule-inverse);
  color: var(--surface-0);
}
[data-surface="inverse"] .btn--secondary:hover { background: var(--graphite-700); }

/* UX-023 (Ray, close-out round 2): the generic `section[id] { scroll-margin-
 * top: 6rem }` rule (above, untouched) doesn't apply here — the click targets
 * on the Instructor page are `<div id="evidence-N">`, not `<section>`, so
 * they landed with their heading hidden under the sticky demo banner +
 * .learnernav stack. Also that stack got taller once .learnernav was added
 * (VIS-001), so 6rem is no longer enough room even for `#next-actions`,
 * which the more specific ID selector below overrides without touching the
 * shared `section[id]` rule. */
.evidence-block[id], #next-actions { scroll-margin-top: 10rem; }

/* VIS-004 close-out: on the real Learner Class Home, the header artwork band
 * does not render at mobile widths for this class (verified against a live
 * screenshot — .classhome__art's own mobile rule above, untouched, still
 * applies to every other classhome screen). This selector is scoped to only
 * #instr-header (this file's own header, given that id above) so it changes
 * nothing about .classhome__art anywhere else. */
@media (max-width: 46.24rem) {
  #instr-header .classhome__art { display: none; }
}
