/* ============================================================
   ORDER HUB — application stylesheet (M5' shell + component inventory)

   Binding rules this file obeys, all from doc 08:
   · §2.2  ห้าม hex literal นอก tokens.css — every colour below is a var().
           Raw pixels survive only where they are structure rather than scale
           (hairline borders, a 34px avatar box); every size that means
           "spacing" or "type" comes from a token.
   · §2.2  ห้าม opacity ทำ "สีอ่อน" — each tint has its own token, because the
           same opacity over two grounds gives two contrasts and silently
           regresses a11y.
   · §7.2  ห้าม letter-spacing / text-transform กับข้อความไทย. `letter-spacing`
           appears exactly where the text is Latin uppercase and nowhere else.
   · §7.1  ห้าม outline:none ทุกกรณี; touch targets ≥ var(--tap-min).
   · §7.1  reset rules are wrapped in :where() so their specificity is 0 and a
           component class always wins — the un-wrapped `.app button{color:inherit}`
           is what once painted dark ink on the navy sidebar at 1.1:1.
   ============================================================ */

*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }

body.oh {
  font-family: var(--font-ui);
  font-size: var(--fs-body);
  line-height: var(--lh-body);       /* 1.65 — สระบน/ล่าง + วรรณยุกต์ ต้องไม่ชนบรรทัด */
  color: var(--c-text);
  background: var(--c-bg);
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
}

.oh :where(button, input, select, textarea) { font: inherit; color: inherit; }
.oh :where(h1, h2, h3, p, dl, dd, figure, ul, ol) { margin: 0; }
.oh :where(ul, ol) { padding: 0; list-style: none; }
.oh :where(a) { color: var(--c-link); text-decoration: none; }
.oh :where(a:visited) { color: var(--c-link-visited); }
.oh :where(a:hover) { text-decoration: underline; }
.oh svg { display: block; }

/* ห้าม outline:none ทุกกรณี (§7.1 ข้อ 3) */
.oh :focus-visible {
  outline: 2px solid var(--c-primary);
  outline-offset: 2px;
  border-radius: var(--r-sm);
  box-shadow: var(--sh-focus);
}
/* บนพื้น navy ใช้ ring สีขาว มิฉะนั้น focus หายไปในพื้นหลัง */
.sidebar :focus-visible, .tabbar :focus-visible { outline-color: var(--c-sidebar-ink); }

.icon { stroke: currentColor; fill: none; flex: none; }
.mono { font-family: var(--font-mono); font-size: .92em; }
.tnum { font-variant-numeric: tabular-nums; }
.muted { color: var(--c-text-muted); }
.up { color: var(--c-success-ink); }
.down { color: var(--c-danger-ink); }
.nowrap { white-space: nowrap; }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}
/* ไทยไม่มีช่องว่างระหว่างคำ — break-all ตัดผิดที่แล้วความหมายเปลี่ยน (§7.2 ข้อ 7) */
.oh { overflow-wrap: break-word; word-break: normal; }
.prose { max-inline-size: 68ch; }

/* doc 08 §2.3 defines a `--fs-label` level for Latin uppercase labels, and this
   file deliberately ships **no class for it**. Every label on these screens is
   Thai, where §7.2 ข้อ 2–3 forbid both letter-spacing and uppercase; a ready-made
   ".label-latin" would sit in the stylesheet waiting for someone to reach for it
   on a Thai string, and `text-transform: uppercase` on Thai is a silent no-op
   that looks correct in review. The two places letter-spacing survives below are
   the wordmark (ORDER HUB) and the one-time credential (ASCII + digits) — both
   spelled out, neither reusable. Add the class with the string it is for if a
   genuinely Latin label ever appears.

   ============================================================
   Cards
   ============================================================ */
.card {
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-sm);
  min-inline-size: 0;               /* grid items default to min-content (§8C) */
}
.card.narrow { max-inline-size: 620px; }
.card-head {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-4) var(--card-pad) var(--sp-3);
}
.card-title { font-size: var(--fs-h3); line-height: var(--lh-h3); font-weight: var(--fw-semibold); }
.card-note { color: var(--c-text-muted); font-size: var(--fs-xs); line-height: var(--lh-xs); }
.card-head .spacer, .card-note.spacer { margin-inline-start: auto; }
.card-body { padding: 0 var(--card-pad) var(--card-pad); display: flex; flex-direction: column; gap: var(--sp-4); }
.card-body.flush { padding: 0; gap: 0; }
.section-label { font-size: var(--fs-xs); line-height: var(--lh-xs); font-weight: var(--fw-bold); color: var(--c-text-muted); }

/* ============================================================
   Buttons
   ============================================================ */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  min-height: var(--tap-min); padding: var(--sp-2) var(--sp-4);
  border: 1px solid var(--c-border-strong); border-radius: var(--r-md);
  background: var(--c-surface); color: var(--c-text);
  font-size: var(--fs-sm); line-height: var(--lh-sm); font-weight: var(--fw-semibold);
  cursor: pointer; box-shadow: var(--sh-xs); text-decoration: none;
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.btn:hover { background: var(--c-surface-sunken); text-decoration: none; }
.btn-primary {
  background: var(--c-primary); border-color: var(--c-primary); color: var(--c-on-primary);
  box-shadow: var(--sh-sm);
}
.btn-primary:hover { background: var(--c-primary-hover); border-color: var(--c-primary-hover); color: var(--c-on-primary); }
.btn-danger { color: var(--c-danger-ink); border-color: var(--c-danger-tint); }
.btn-danger:hover { background: var(--c-danger-tint); }
.btn-ghost { background: none; border-color: transparent; box-shadow: none; }
.btn-block { inline-size: 100%; }
.btn-sm { padding: var(--sp-1) var(--sp-3); font-size: var(--fs-xs); line-height: var(--lh-xs); }
/* ปุ่มเล็กยังต้องแตะได้: ลดแค่ตัวอักษร/ระยะแนวนอน ไม่ลด hit area */
.btn-44 { min-inline-size: var(--tap-min); min-block-size: var(--tap-min); }
.btn:disabled, .btn[disabled] { opacity: .5; cursor: not-allowed; box-shadow: none; }
/* §4.16: ความกว้างปุ่มต้องไม่กระโดดตอนเปลี่ยนเป็นสถานะโหลด */
.btn-submit { min-inline-size: 11rem; }
.actions { display: flex; flex-wrap: wrap; gap: var(--sp-3); }
.link-more {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  min-height: var(--tap-min);
  font-size: var(--fs-xs); line-height: var(--lh-xs); font-weight: var(--fw-semibold);
  color: var(--c-link); background: none; border: 0; cursor: pointer; padding: 0;
}
.icon-btn {
  position: relative; display: grid; place-items: center;
  inline-size: var(--tap-min); block-size: var(--tap-min);
  border-radius: var(--r-md); background: var(--c-surface);
  border: 1px solid var(--c-border); cursor: pointer;
  color: var(--c-text-muted); box-shadow: var(--sh-xs);
}
.icon-btn:hover { color: var(--c-text); border-color: var(--c-border-strong); }
.dot-badge {
  position: absolute; inset-block-start: -2px; inset-inline-end: -2px;
  min-inline-size: 20px; padding: 0 var(--sp-1); border-radius: var(--r-pill);
  background: var(--c-danger); color: var(--c-on-primary);
  font-size: var(--fs-xs); line-height: 1.6; font-weight: var(--fw-bold);
  border: 2px solid var(--c-surface); text-align: center;
}

/* ============================================================
   Forms — §4.9
   ============================================================ */
.stack { display: flex; flex-direction: column; gap: var(--sp-4); }
.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-4) var(--sp-5); }
.form-grid .full { grid-column: 1 / -1; }
.field { display: flex; flex-direction: column; gap: var(--sp-2); min-inline-size: 0; }
.label { font-size: var(--fs-sm); line-height: var(--lh-sm); font-weight: var(--fw-semibold); }
.label .req { color: var(--c-danger); }
.hint { color: var(--c-text-muted); font-size: var(--fs-xs); line-height: var(--lh-xs); max-inline-size: 68ch; }

.input, .select, .textarea {
  inline-size: 100%; min-block-size: var(--tap-min);
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--c-border-strong); border-radius: var(--r-md);
  background: var(--c-surface); color: var(--c-text);
  font-size: var(--fs-body); line-height: var(--lh-sm);
}
.textarea { min-block-size: 5rem; resize: vertical; }
.input::placeholder, .textarea::placeholder { color: var(--c-text-subtle); }
.input:focus, .select:focus, .textarea:focus { border-color: var(--c-primary); }
.input[aria-invalid="true"], .textarea[aria-invalid="true"], .select[aria-invalid="true"] {
  border-color: var(--c-danger); box-shadow: var(--sh-focus-danger);
}
.input.mono-input { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.input-wrap { display: flex; gap: var(--sp-2); align-items: stretch; }
.input-wrap .input { flex: 1; min-inline-size: 0; }

.field-error {
  display: flex; gap: var(--sp-2); align-items: flex-start;
  color: var(--c-danger-ink); font-size: var(--fs-xs); line-height: var(--lh-xs);
  font-weight: var(--fw-semibold);
}
.field-error a { color: var(--c-danger-ink); text-decoration: underline; font-weight: var(--fw-bold); }

/* §4.9 paste hygiene — การตัดค่าต้องมองเห็นและเลิกทำได้ ห้ามแปลงเงียบ */
.hygiene-note {
  display: flex; gap: var(--sp-3); align-items: center; flex-wrap: wrap;
  font-size: var(--fs-xs); line-height: var(--lh-xs); color: var(--c-warning-ink);
  background: var(--c-warning-tint); border: 1px solid var(--c-warning);
  border-radius: var(--r-sm); padding: var(--sp-2) var(--sp-3);
}
.hygiene-note .btn { min-height: var(--tap-min); }
.hygiene-note .was {
  font-family: var(--font-mono); background: var(--c-surface);
  border: 1px solid var(--c-warning); border-radius: var(--r-sm); padding: 0 var(--sp-1);
}
/* โหมด masked ของกลุ่มฟิลด์ลูกค้า — ต่างทางสายตาด้วย ไม่ใช่ต่างแค่ข้อความ */
.masked-mode {
  background: var(--c-surface-sunken); border: 1px dashed var(--c-border-strong);
  border-radius: var(--r-md); padding: var(--sp-4);
  display: flex; flex-direction: column; gap: var(--sp-4);
}
.masked-warning {
  color: var(--c-warning-ink); font-size: var(--fs-sm); line-height: var(--lh-sm);
  font-weight: var(--fw-semibold); max-inline-size: 68ch;
}

.sticky-bar {
  position: sticky; inset-block-end: 0; z-index: var(--z-sticky);
  background: var(--c-surface); border-block-start: 1px solid var(--c-border);
  padding: var(--sp-3) var(--content-pad);
  margin: 0 calc(var(--content-pad) * -1) calc(var(--content-pad) * -1);
  display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap;
  box-shadow: var(--sh-md);
}
.sticky-bar .grow { margin-inline-start: auto; display: flex; gap: var(--sp-3); flex-wrap: wrap; }

/* ============================================================
   Order form — §4.10 multi-item rows, §4.11 product picker
   ============================================================ */
/* §6.4 item 2: the platform-order-number cell sits beside the platform select.
   When the chosen platform issues no number the slot is EMPTY and collapses, so
   the date field moves up next to the select rather than sitting under a hole.
   The field is absent from the DOM in that case, not merely invisible — hiding
   it with CSS would leave the value in the form and in the submitted body. */
.pono-slot { display: contents; }
.pono-slot.is-empty { display: none; }

.item-rows { display: flex; flex-direction: column; gap: var(--sp-3); }
.item-row {
  border: 1px solid var(--c-border); border-radius: var(--r-lg);
  background: var(--c-surface-sunken);
  padding: var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-3);
  min-inline-size: 0;
}
/* §4.10 states: a row with its own error carries a left rule, not only red text */
.item-row.has-error { border-color: var(--c-danger); box-shadow: inset 3px 0 0 0 var(--c-danger); }
.item-head { display: flex; align-items: center; gap: var(--sp-3); }
.item-idx {
  font-size: var(--fs-xs); line-height: var(--lh-xs);
  font-weight: var(--fw-bold); color: var(--c-text-muted);
}
.item-remove { margin-inline-start: auto; }
.item-grid { display: grid; grid-template-columns: 200px minmax(0, 1fr); gap: var(--sp-3); }
.item-chosen {
  display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap;
  background: var(--c-surface); border: 1px solid var(--c-border-strong);
  border-radius: var(--r-md); padding: var(--sp-2) var(--sp-3);
}

/* Quantity stepper — both buttons are 44px, per §3.3 item 1 */
.stepper {
  display: flex; align-items: center; background: var(--c-surface);
  border: 1px solid var(--c-border-strong); border-radius: var(--r-md);
  overflow: hidden; min-block-size: var(--tap-min);
}
.stepper button {
  inline-size: var(--tap-min); min-block-size: var(--tap-min);
  border: 0; background: var(--c-surface); color: var(--c-text-muted);
  display: grid; place-items: center; cursor: pointer;
}
.stepper button:hover { background: var(--c-surface-sunken); color: var(--c-text); }
.stepper button:disabled { cursor: not-allowed; opacity: .5; }
.stepper input {
  inline-size: 100%; min-inline-size: 0; border: 0; text-align: center;
  background: var(--c-surface); color: var(--c-text);
  font-variant-numeric: tabular-nums; font-weight: var(--fw-semibold);
}

/* Option chips. `has-effect` is the REMOVE_JOB tint — but the words "ตัดงานผลิต"
   inside the chip are what carries the meaning (§2.2, §7.1 item 2). */
.opts-block { display: flex; flex-direction: column; gap: var(--sp-2); min-inline-size: 0; }
.opts { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.opt {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  min-block-size: var(--tap-min); padding: var(--sp-2) var(--sp-4);
  border: 1px solid var(--c-border-strong); border-radius: var(--r-pill);
  background: var(--c-surface); font-size: var(--fs-sm); line-height: var(--lh-sm);
  cursor: pointer;
}
.opt:hover { border-color: var(--c-primary); }
.opt input { inline-size: 16px; block-size: 16px; margin: 0; accent-color: var(--c-primary); }
/* CR-006 R-3: the per-order value of a PARAMETER option, inline in its chip. */
.opt input.opt-param {
  inline-size: 11ch; min-block-size: 28px; padding: 0 var(--sp-2); margin: 0;
  border: 1px solid var(--c-border-strong); border-radius: var(--r-sm);
  background: var(--c-bg); font-size: var(--fs-sm);
}
.opt.has-effect { border-color: var(--c-warning); background: var(--c-warning-tint); color: var(--c-warning-ink); }
.removal-note {
  display: flex; gap: var(--sp-2); align-items: flex-start;
  color: var(--c-warning-ink); background: var(--c-warning-tint);
  border: 1px solid var(--c-warning); border-radius: var(--r-sm);
  padding: var(--sp-2) var(--sp-3);
  font-size: var(--fs-sm); line-height: var(--lh-sm); max-inline-size: 68ch;
}

/* Product picker — the results list is server-rendered on every keystroke */
.picker { display: flex; gap: var(--sp-2); align-items: center; }
.picker .input { flex: 1; min-inline-size: 0; }
.picker-results:empty { display: none; }
.picker-list {
  border: 1px solid var(--c-border-strong); border-radius: var(--r-md);
  background: var(--c-surface); overflow: hidden;
  box-shadow: var(--sh-sm); max-block-size: 20rem; overflow-y: auto;
}
.picker-list > * + * { border-block-start: 1px solid var(--c-border); }
.picker-hit {
  inline-size: 100%; display: flex; gap: var(--sp-3); align-items: center; flex-wrap: wrap;
  min-block-size: var(--tap-min); padding: var(--sp-2) var(--sp-4);
  background: none; border: 0; text-align: start; cursor: pointer;
  font-size: var(--fs-sm); line-height: var(--lh-sm); color: var(--c-text);
}
button.picker-hit:hover { background: var(--c-primary-tint-weak); }
.picker-hit.is-blocked { color: var(--c-text-muted); cursor: not-allowed; }
.picker-code { font-family: var(--font-mono); font-weight: var(--fw-semibold); flex: none; }
.picker-name { flex: 1; min-inline-size: 0; }
.picker-empty {
  padding: var(--sp-3) var(--sp-4); color: var(--c-text-muted);
  font-size: var(--fs-sm); line-height: var(--lh-body);
  border: 1px dashed var(--c-border-strong); border-radius: var(--r-md);
  display: flex; flex-direction: column; gap: var(--sp-1);
}
.free-text summary {
  cursor: pointer; color: var(--c-link); font-size: var(--fs-sm); line-height: var(--lh-sm);
  min-block-size: var(--tap-min); display: flex; align-items: center;
}
.free-text > .field { margin-block-start: var(--sp-2); }
.hygiene-note[hidden] { display: none; }

/* ============================================================
   Helper / banner — §4.17
   ============================================================ */
.helper, .banner {
  display: flex; gap: var(--sp-3); align-items: flex-start;
  padding: var(--sp-3) var(--sp-4); border-radius: var(--r-md);
  font-size: var(--fs-sm); line-height: var(--lh-sm);
  border: 1px solid transparent;
}
.helper-blue,  .banner-blue  { background: var(--c-primary-tint-weak); color: var(--c-info-ink);    border-color: var(--c-primary-tint); }
.helper-amber, .banner-amber { background: var(--c-warning-tint);      color: var(--c-warning-ink); border-color: var(--c-warning); }
.helper-red,   .banner-red   { background: var(--c-danger-tint);       color: var(--c-danger-ink);  border-color: var(--c-danger); }
.helper-green, .banner-green { background: var(--c-success-tint);      color: var(--c-success-ink); border-color: var(--c-success); }
.helper .icon, .banner-icon { flex: none; margin-block-start: 2px; }
.banner-body { flex: 1; min-inline-size: 0; display: flex; flex-direction: column; gap: var(--sp-1); }
.banner-title { font-weight: var(--fw-bold); }
.banner-text { max-inline-size: 68ch; }
.banner-rid { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--c-text-muted); flex: none; }
.lockout { font-weight: var(--fw-semibold); }

/* ============================================================
   Chips & pills — §2.4 / §2.5 / §4.3 / §4.4
   ============================================================ */
.chip {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  padding: 2px var(--sp-3); border-radius: var(--r-pill);
  font-size: var(--fs-xs); line-height: var(--lh-xs); font-weight: var(--fw-semibold);
  white-space: nowrap;
}
.chip .pdot { inline-size: 8px; block-size: 8px; border-radius: var(--r-pill); flex: none; }
.p-FACEBOOK { background: var(--pf-facebook-bg); color: var(--pf-facebook-ink); }
.p-FACEBOOK .pdot { background: var(--pf-facebook-dot); }
.p-SHOPEE   { background: var(--pf-shopee-bg);   color: var(--pf-shopee-ink); }
.p-SHOPEE .pdot   { background: var(--pf-shopee-dot); }
.p-LAZADA   { background: var(--pf-lazada-bg);   color: var(--pf-lazada-ink); }
.p-LAZADA .pdot   { background: var(--pf-lazada-dot); }
.p-TIKTOK   { background: var(--pf-tiktok-bg);   color: var(--pf-tiktok-ink); }
.p-TIKTOK .pdot   { background: var(--pf-tiktok-dot); }
.p-LINE     { background: var(--pf-line-bg);     color: var(--pf-line-ink); }
.p-LINE .pdot     { background: var(--pf-line-dot); }
/* แพลตฟอร์มที่ยังไม่มี mapping ต้องไม่พังหน้า (§2.5) */
.p-UNKNOWN  { background: var(--c-surface-sunken); color: var(--c-text-muted); }
.p-UNKNOWN .pdot  { background: var(--c-text-subtle); }

/* padding 4px แนวตั้ง + line-height 1.55 กันวรรณยุกต์ไทยโดนตัดหัว */
.pill {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  padding: var(--sp-1) var(--sp-3); border-radius: var(--r-pill);
  font-size: var(--fs-xs); line-height: var(--lh-xs); font-weight: var(--fw-bold);
  white-space: nowrap; border: 1px solid transparent;
}
.pill::before {
  content: ""; inline-size: 6px; block-size: 6px; border-radius: var(--r-pill);
  background: currentColor; flex: none;
}
/* order status — 7 ค่า canonical (§2.4) */
.s-RECEIVED      { background: var(--st-received-bg);     color: var(--st-received-ink);     border-color: var(--st-received-bd); }
.s-NEEDS_REVIEW  { background: var(--st-needsreview-bg);  color: var(--st-needsreview-ink);  border-color: var(--st-needsreview-bd); }
.s-CONFIRMED     { background: var(--st-confirmed-bg);    color: var(--st-confirmed-ink);    border-color: var(--st-confirmed-bd); }
.s-IN_PRODUCTION { background: var(--st-inproduction-bg); color: var(--st-inproduction-ink); border-color: var(--st-inproduction-bd); }
.s-READY_TO_SHIP { background: var(--st-readytoship-bg);  color: var(--st-readytoship-ink);  border-color: var(--st-readytoship-bd); }
.s-SHIPPED       { background: var(--st-shipped-bg);      color: var(--st-shipped-ink);      border-color: var(--st-shipped-bd); }
.s-CANCELLED     { background: var(--st-cancelled-bg);    color: var(--st-cancelled-ink);    border-color: var(--st-cancelled-bd); }
/* job status — 9 ค่า; prefix j- แยกจาก order เพราะ CANCELLED มีสองความหมายคนละสี */
.j-WAITING          { background: var(--job-waiting-bg);    color: var(--job-waiting-ink); }
.j-READY            { background: var(--job-ready-bg);      color: var(--job-ready-ink); }
.j-ASSIGNED         { background: var(--job-assigned-bg);   color: var(--job-assigned-ink); }
.j-IN_PROGRESS      { background: var(--job-inprogress-bg); color: var(--job-inprogress-ink); }
.j-BLOCKED          { background: var(--job-blocked-bg);    color: var(--job-blocked-ink); }
.j-CANCEL_REQUESTED { background: var(--job-cancelreq-bg);  color: var(--job-cancelreq-ink); }
.j-COMPLETED        { background: var(--job-completed-bg);  color: var(--job-completed-ink); }
.j-FAILED           { background: var(--job-failed-bg);     color: var(--job-failed-ink); }
.j-CANCELLED        { background: var(--job-cancelled-bg);  color: var(--job-cancelled-ink); }
.pill-note { color: var(--c-text-muted); font-size: var(--fs-xs); line-height: var(--lh-xs); }

/* §4.3/§6.5 masked-PII chip — ข้อความ ไม่ใช่สีอย่างเดียว */
.chip-masked {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  padding: 2px var(--sp-3); border-radius: var(--r-pill);
  font-size: var(--fs-xs); line-height: var(--lh-xs); font-weight: var(--fw-semibold);
  white-space: nowrap;
  background: var(--c-warning-tint); color: var(--c-warning-ink);
  border: 1px solid var(--st-needsreview-bd);
}
.tag {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  padding: 2px var(--sp-2); border-radius: var(--r-sm);
  font-size: var(--fs-xs); line-height: var(--lh-xs); font-weight: var(--fw-semibold);
  background: var(--st-received-bg); color: var(--st-received-ink);
}
.tag-warn  { background: var(--c-warning-tint); color: var(--c-warning-ink); }
.tag-blue  { background: var(--c-primary-tint); color: var(--c-info-ink); }
.tag-red   { background: var(--c-danger-tint);  color: var(--c-danger-ink); }
.tag-green { background: var(--c-success-tint); color: var(--c-success-ink); }
/* บรรทัดที่สองในเซลล์ "ช่องทาง" — เลขคำสั่งซื้อ ไม่ใช่คอลัมน์ที่ 8 (§4.3) */
.sub-ref {
  display: block; font-family: var(--font-mono);
  font-size: var(--fs-xs); line-height: var(--lh-xs); color: var(--c-text-muted);
  max-inline-size: 14ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  margin-block-start: 2px;
}

/* ============================================================
   KPI card — §4.1
   ============================================================ */
.kpi-row { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--sp-5); }
.kpi {
  padding: var(--sp-5); display: flex; gap: var(--sp-3); align-items: flex-start;
  min-inline-size: 0; text-align: start; color: var(--c-text);
}
a.kpi:hover { text-decoration: none; border-color: var(--c-border-strong); }
.kpi-main { flex: 1; min-inline-size: 0; display: flex; flex-direction: column; gap: var(--sp-1); }
.kpi-label { color: var(--c-text-muted); font-size: var(--fs-xs); line-height: var(--lh-xs); font-weight: var(--fw-semibold); }
.kpi-num { font-size: var(--fs-display); line-height: var(--lh-display); font-weight: var(--fw-bold); }
.kpi-delta { display: flex; align-items: center; gap: var(--sp-1); flex-wrap: wrap; font-size: var(--fs-xs); line-height: var(--lh-xs); }
.chip-icon { inline-size: 44px; block-size: 44px; border-radius: var(--r-md); display: grid; place-items: center; flex: none; }
.t-blue   { background: var(--c-primary-tint);  color: var(--c-info-ink); }
.t-green  { background: var(--c-success-tint);  color: var(--c-success-ink); }
.t-violet { background: var(--c-violet-tint);   color: var(--c-violet-ink); }
.t-amber  { background: var(--c-warning-tint);  color: var(--c-warning-ink); }
.t-red    { background: var(--c-danger-tint);   color: var(--c-danger-ink); }
.t-teal   { background: var(--c-teal-tint);     color: var(--c-teal-ink); }
.t-slate  { background: var(--st-received-bg);  color: var(--st-received-ink); }

/* ============================================================
   Table — §4.3. `.tbl` is the canonical class; `.table` is the M3' alias so
   the staff page keeps working without a second rule set to drift from.
   ============================================================ */
/* กว้างเกินจอ → scroll ในกล่องตัวเอง ไม่ใช่ scroll ของ body (§3.3 ข้อ 3) */
.table-scroll { overflow-x: auto; }
.tbl, .table {
  inline-size: 100%; border-collapse: collapse;
  font-size: var(--fs-sm); line-height: var(--lh-sm);   /* cell ห้ามต่ำกว่า 1.5 */
}
.tbl th, .table th, .tbl td, .table td {
  text-align: start; padding: var(--sp-3) var(--card-pad);
  border-block-end: 1px solid var(--c-border); vertical-align: top;
}
.tbl thead th, .table thead th {
  background: var(--c-surface-sunken); color: var(--c-text-muted);
  font-size: var(--fs-xs); line-height: var(--lh-xs); font-weight: var(--fw-bold);
  white-space: nowrap;
}
.tbl tbody tr:hover, .table tbody tr:hover { background: var(--c-primary-tint-weak); }
.tbl tbody tr:last-child td, .table tbody tr:last-child td { border-block-end: 0; }
/* §4.3 states: NEEDS_REVIEW gets a left rule; a cancelled row goes quiet */
.tbl tr.row-review, .table tr.row-review { box-shadow: inset 3px 0 0 0 var(--c-warning); }
.tbl tr.row-cancelled td, .table tr.row-cancelled td { color: var(--c-text-muted); }
.tbl tr.row-cancelled .ordno, .table tr.row-cancelled .ordno { text-decoration: line-through; }
.ordno { font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--c-link); font-weight: var(--fw-semibold); }
.num { text-align: end; font-variant-numeric: tabular-nums; }
.row-actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

/* ============================================================
   Panel / list rows — §4.5, §4.7
   ============================================================ */
.rowlist { display: flex; flex-direction: column; }
.rowlist > * + * { border-block-start: 1px solid var(--c-border); }
.listrow { display: flex; gap: var(--sp-3); padding: var(--sp-3) var(--card-pad); align-items: flex-start; }
.listrow-body { flex: 1; min-inline-size: 0; display: flex; flex-direction: column; gap: var(--sp-1); }
.listrow-main { font-weight: var(--fw-semibold); }
.listrow-sub, .listrow-time { color: var(--c-text-muted); font-size: var(--fs-xs); line-height: var(--lh-xs); }
.listrow-time { flex: none; white-space: nowrap; }
.sev { inline-size: 9px; block-size: 9px; border-radius: var(--r-pill); margin-block-start: 6px; flex: none; }
.sev-high { background: var(--c-danger); } .sev-med { background: var(--c-warning); } .sev-info { background: var(--c-primary); }
.qa-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); }
.qa {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3); border-radius: var(--r-md);
  border: 1px solid var(--c-border); background: var(--c-surface);
  color: var(--c-text); cursor: pointer; text-align: start;
  font-size: var(--fs-sm); line-height: var(--lh-sm); font-weight: var(--fw-semibold);
  min-block-size: 56px;
}
.qa:hover { border-color: var(--c-border-strong); background: var(--c-surface-sunken); text-decoration: none; }
.qa .qa-text { flex: 1; min-inline-size: 0; }
.qa .chev { color: var(--c-border-strong); }

/* ============================================================
   Empty state — §4.15
   ============================================================ */
.empty-state {
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-3);
  padding: var(--sp-8) var(--sp-4); text-align: center;
  color: var(--c-text-muted);
  background: var(--c-surface-sunken); border: 1px dashed var(--c-border-strong);
  border-radius: var(--r-md);
}
.empty-icon { color: var(--c-text-subtle); }
.empty-title { color: var(--c-text); font-size: var(--fs-h3); line-height: var(--lh-h3); font-weight: var(--fw-semibold); }
.empty-note { max-inline-size: 60ch; font-size: var(--fs-sm); line-height: var(--lh-body); }

/* ============================================================
   Loading — §4.16
   ============================================================ */
.skeleton { display: flex; flex-direction: column; gap: var(--sp-2); }
.skeleton-line {
  display: block; block-size: 14px; border-radius: var(--r-sm);
  background: var(--c-surface-sunken);
  animation: oh-shimmer var(--dur-slow) var(--ease) infinite alternate;
}
.skeleton-line:last-child { inline-size: 60%; }
@keyframes oh-shimmer { from { opacity: .55; } to { opacity: 1; } }

/* htmx toggles .htmx-request on the indicator (and on hx-indicator targets) */
.htmx-indicator { display: none; }
.htmx-request .htmx-indicator, .htmx-indicator.htmx-request { display: inline-flex; }
.spinner { align-items: center; gap: var(--sp-2); }
.spinner-dot {
  inline-size: 16px; block-size: 16px; border-radius: var(--r-pill);
  border: 2px solid var(--c-border-strong); border-block-start-color: var(--c-primary);
  animation: oh-spin 700ms linear infinite;
}
@keyframes oh-spin { to { transform: rotate(360deg); } }
.btn-spinner { display: none; }
.btn.htmx-request .btn-spinner {
  display: block; inline-size: 16px; block-size: 16px; border-radius: var(--r-pill);
  border: 2px solid currentColor; border-block-start-color: transparent;
  animation: oh-spin 700ms linear infinite;
}
.btn.htmx-request { opacity: .75; cursor: progress; }

/* navigation-level progress bar under the topbar */
.progress-bar {
  position: fixed; inset-block-start: 0; inset-inline: 0; block-size: 2px;
  background: var(--c-primary); transform: scaleX(0); transform-origin: left;
  transition: transform var(--dur-slow) var(--ease), opacity var(--dur-fast) var(--ease);
  z-index: var(--z-toast); opacity: 0;
}
.progress-bar.busy { transform: scaleX(.8); opacity: 1; }

/* ============================================================
   Toast — §4.14
   ============================================================ */
.toast-root {
  position: fixed; inset-block-start: var(--sp-4); inset-inline-end: var(--sp-4);
  z-index: var(--z-toast); display: flex; flex-direction: column; gap: var(--sp-2);
  max-inline-size: min(380px, calc(100vw - var(--sp-8)));
}
.toast {
  display: flex; gap: var(--sp-3); align-items: flex-start;
  padding: var(--sp-3) var(--sp-4); border-radius: var(--r-lg);
  background: var(--c-surface); border: 1px solid var(--c-border-strong);
  box-shadow: var(--sh-lg); font-size: var(--fs-sm); line-height: var(--lh-sm);
}
.toast-success { border-inline-start: 4px solid var(--c-success); }
.toast-error   { border-inline-start: 4px solid var(--c-danger); }
.toast-warning { border-inline-start: 4px solid var(--c-warning); }
.toast-info    { border-inline-start: 4px solid var(--c-primary); }
.toast-body { flex: 1; min-inline-size: 0; }
.toast-icon { flex: none; margin-block-start: 2px; }
.toast-close {
  flex: none; display: grid; place-items: center;
  inline-size: var(--tap-min); block-size: var(--tap-min);
  margin: calc(var(--sp-2) * -1); background: none; border: 0;
  color: var(--c-text-muted); cursor: pointer; border-radius: var(--r-sm);
}

/* ============================================================
   Offline banner — §3.4. z-offline sits above everything, deliberately: it is
   the one message that must never be covered, because covering it is how a
   submit that cannot work looks like one that can.
   ============================================================ */
.offline-banner {
  position: fixed; inset-inline: 0; inset-block-start: 0; z-index: var(--z-offline);
  display: flex; align-items: center; justify-content: center; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-4);
  background: var(--c-warning-tint); color: var(--c-warning-ink);
  border-block-end: 1px solid var(--c-warning);
  font-size: var(--fs-sm); line-height: var(--lh-sm); font-weight: var(--fw-semibold);
  padding-block-start: calc(var(--sp-2) + env(safe-area-inset-top));
}
.offline-banner[hidden] { display: none; }
.offline-note { font-weight: var(--fw-regular); }

/* ============================================================
   App shell — §3.1
   ============================================================ */
.sidebar {
  position: fixed; inset-block: 0; inset-inline-start: 0;
  inline-size: var(--sidebar-w); background: var(--c-sidebar);
  display: flex; flex-direction: column; z-index: var(--z-drawer);
}
.brand { display: flex; gap: var(--sp-3); align-items: center; padding: var(--sp-5) var(--sp-4) var(--sp-4); }
.brand-mark {
  inline-size: 38px; block-size: 38px; border-radius: var(--r-lg);
  display: grid; place-items: center; flex: none;
  background: var(--c-primary); color: var(--c-on-primary); box-shadow: var(--sh-sm);
}
.brand-name {
  display: block; color: var(--c-sidebar-ink); font-size: var(--fs-sm); line-height: 1.3;
  font-weight: var(--fw-bold); letter-spacing: .06em;   /* Latin only */
}
.brand-sub { display: block; color: var(--c-sidebar-muted); font-size: var(--fs-xs); line-height: var(--lh-xs); }

.nav { flex: 1; overflow-y: auto; padding: var(--sp-1) var(--sp-3) var(--sp-4); }
/* ป้ายหมวดภาษาไทย: ห้าม letter-spacing / uppercase (§7.2 ข้อ 2–3) */
.nav-label {
  /* 500, not 400: light-on-dark renders optically thinner than dark-on-light,
     and Thai carries meaning in hairline tone marks that vanish first. */
  color: var(--c-sidebar-section); font-size: var(--fs-xs); line-height: var(--lh-xs);
  font-weight: var(--fw-medium);
  font-weight: var(--fw-bold); padding: var(--sp-4) var(--sp-3) var(--sp-2);
}
/* Same reasoning as .nav-label — every small light-on-dark string in the
   sidebar gets the extra weight, not just the section headings. */
.nav-soon, .brand-sub, .user-role, .user-action { font-weight: var(--fw-medium); }

.nav-item {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3); min-block-size: var(--tap-min);
  border-radius: var(--r-md); color: var(--c-sidebar-muted);
  font-size: var(--fs-sm); line-height: 1.5;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.nav-item:hover { background: var(--c-sidebar-2); color: var(--c-sidebar-ink); text-decoration: none; }
.nav-item[aria-current="page"] {
  background: var(--c-primary); color: var(--c-on-primary); font-weight: var(--fw-semibold);
}
/* หน้าที่ยังไม่เปิดในรุ่นนี้: บอกตรง ๆ ดีกว่าลิงก์ที่พาไป 404 */
.nav-item.is-soon { color: var(--c-sidebar-section); cursor: not-allowed; }
.nav-item.is-soon:hover { background: none; color: var(--c-sidebar-section); }
.nav-text { flex: 1; min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nav-soon { font-size: var(--fs-xs); line-height: var(--lh-xs); flex: none; }
.nav-badge {
  flex: none; padding: 0 var(--sp-2); border-radius: var(--r-pill);
  font-size: var(--fs-xs); line-height: 1.6; font-weight: var(--fw-bold);
}
.b-amber { background: var(--st-needsreview-bg); color: var(--st-needsreview-ink); }
.b-blue  { background: var(--c-primary-hover);   color: var(--c-on-primary); }
.b-green { background: var(--c-success-ink);     color: var(--c-on-primary); }

.user-card {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4); background: var(--c-sidebar-2);
  border-block-start: 1px solid var(--c-sidebar-section);
}
.avatar {
  inline-size: 34px; block-size: 34px; border-radius: var(--r-pill); flex: none;
  display: grid; place-items: center;
  background: var(--c-primary); color: var(--c-on-primary);
  font-weight: var(--fw-bold); font-size: var(--fs-xs);
}
.user-main { min-inline-size: 0; }
.user-name {
  display: block; color: var(--c-sidebar-ink); font-size: var(--fs-sm); line-height: 1.5;
  font-weight: var(--fw-semibold); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.user-role { display: block; color: var(--c-sidebar-muted); font-size: var(--fs-xs); line-height: var(--lh-xs); }
.user-actions { display: flex; background: var(--c-sidebar-2); }
.user-actions form { flex: 1; display: flex; }
.user-action {
  flex: 1; display: flex; align-items: center; justify-content: center; gap: var(--sp-2);
  min-block-size: var(--tap-min); padding: var(--sp-2);
  background: none; border: 0; cursor: pointer;
  color: var(--c-sidebar-muted); font-size: var(--fs-xs); line-height: var(--lh-xs);
}
.user-action:hover { color: var(--c-sidebar-ink); background: var(--c-sidebar); text-decoration: none; }

.main { margin-inline-start: var(--sidebar-w); min-block-size: 100vh; display: flex; flex-direction: column; }
.topbar {
  position: sticky; inset-block-start: 0; z-index: var(--z-sticky);
  min-block-size: var(--topbar-h); padding: var(--sp-4) var(--content-pad);
  background: var(--c-surface); border-block-end: 1px solid var(--c-border);
  display: flex; align-items: flex-start; gap: var(--sp-4); flex-wrap: wrap;
}
.page-title { font-size: var(--fs-h1); line-height: var(--lh-h1); font-weight: var(--fw-bold); }
.page-sub { color: var(--c-text-muted); font-size: var(--fs-sm); line-height: var(--lh-sm); }
.topbar-actions { margin-inline-start: auto; display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }

/* §3.1/§6.1 identity banner — "เข้าสู่ระบบเป็น …" plus a way out, on every page.
   It is a <form> POST to /auth/logout, not a link: switching user is a state
   change and must carry the CSRF token like every other one. */
.identity {
  display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap;
  padding: var(--sp-1) var(--sp-3); border-radius: var(--r-pill);
  background: var(--c-surface-sunken); border: 1px solid var(--c-border);
  font-size: var(--fs-xs); line-height: var(--lh-xs); color: var(--c-text-muted);
}
.identity-name { color: var(--c-text); font-weight: var(--fw-semibold); }
.identity form { display: flex; }
.identity-switch {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  min-block-size: var(--tap-min); padding: 0 var(--sp-2);
  background: none; border: 0; cursor: pointer;
  color: var(--c-link); font-size: var(--fs-xs); line-height: var(--lh-xs); font-weight: var(--fw-semibold);
}

.content {
  flex: 1; padding: var(--content-pad);
  inline-size: 100%; max-inline-size: var(--content-max); margin-inline: auto;
  display: flex; flex-direction: column; gap: var(--sp-5);
}
/* §3.1 content grid. `span-3` is the KPI card's own width in that section
   ("KPI card = span 3, 4 ใบต่อแถว") and is defined here even though `.kpi-row`
   gives the dashboard its four-up row without the 12-column grid: a page builder
   who reads §3.1 and writes `class="card kpi span-3"` inside `.grid-12` must get
   the width the spec promises, not an auto column. The collapse selector below
   already matches `span-3`, so leaving the class undefined was half a mechanism. */
.grid-12 { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: var(--sp-5); }
.span-3 { grid-column: span 3; } .span-4 { grid-column: span 4; } .span-5 { grid-column: span 5; }
.span-7 { grid-column: span 7; } .span-8 { grid-column: span 8; } .span-12 { grid-column: span 12; }
.footer {
  text-align: center; color: var(--c-text-muted);
  font-size: var(--fs-xs); line-height: var(--lh-xs); padding: var(--sp-4) 0 var(--sp-6);
}

.scrim { position: fixed; inset: 0; background: var(--c-overlay); z-index: var(--z-backdrop); }
#modal-root:empty { display: none; }
/* Alpine hides x-cloak elements until it has booted; without this rule the
   drawer scrim would flash across the page on every load. */
[x-cloak] { display: none !important; }

/* ============================================================
   Key/value list, secret block (M3' staff pages)
   ============================================================ */
.kv {
  display: grid; grid-template-columns: minmax(9rem, max-content) 1fr;
  gap: var(--sp-2) var(--sp-4); align-items: baseline;
}
.kv dt { color: var(--c-text-muted); font-size: var(--fs-sm); line-height: var(--lh-sm); }
.kv dd { margin: 0; display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.secret {
  padding: var(--sp-2) var(--sp-3); border-radius: var(--r-sm);
  background: var(--c-surface-sunken); border: 1px solid var(--c-border-strong);
  font-size: var(--fs-body); letter-spacing: .04em;   /* Latin/digits only */
}
.credential { border-color: var(--c-warning); }
.pill-ok   { background: var(--c-success-tint); color: var(--c-success-ink); border-color: var(--st-shipped-bd); }
.pill-off  { background: var(--st-cancelled-bg); color: var(--st-cancelled-ink); border-color: var(--st-cancelled-bd); }
.pill-warn { background: var(--st-needsreview-bg); color: var(--st-needsreview-ink); border-color: var(--st-needsreview-bd); }

/* ============================================================
   Login / error / offline page (centred single card)
   ============================================================ */
.auth-page { display: block; }
.auth-wrap { min-block-size: 100vh; display: grid; place-items: center; padding: var(--sp-6) var(--sp-4); }
.auth-card {
  inline-size: 100%; max-inline-size: 400px; padding: var(--card-pad);
  display: flex; flex-direction: column; gap: var(--sp-4);
}
.auth-card.wide { max-inline-size: 560px; }
.auth-brand { display: flex; align-items: center; gap: var(--sp-3); }
.auth-brand-text { display: flex; flex-direction: column; }
.auth-brand-name {
  font-size: var(--fs-h3); line-height: var(--lh-h3); font-weight: var(--fw-bold);
  letter-spacing: .06em;   /* Latin uppercase only */
}
.auth-brand-sub { color: var(--c-text-muted); font-size: var(--fs-xs); line-height: var(--lh-xs); }
.auth-title { font-size: var(--fs-h2); line-height: var(--lh-h2); font-weight: var(--fw-bold); }
.auth-lead { color: var(--c-text-muted); font-size: var(--fs-sm); line-height: var(--lh-sm); }
.auth-form { display: flex; flex-direction: column; gap: var(--sp-4); }
.auth-foot { color: var(--c-text-muted); font-size: var(--fs-xs); line-height: var(--lh-xs); }
.error-mark {
  inline-size: 44px; block-size: 44px; border-radius: var(--r-pill);
  display: grid; place-items: center;
  background: var(--c-danger-tint); color: var(--c-danger-ink);
}
.detail-list { margin: 0; padding-inline-start: var(--sp-5); color: var(--c-text-muted); font-size: var(--fs-sm); }

/* /privacy and /terms. Wider and left-aligned than the auth cards they borrow
   their shell from: those hold one short message, these hold a document
   somebody may actually have to read on a phone. */
.legal-page { max-inline-size: 640px; text-align: start; }
.legal-page .auth-lead { color: var(--c-text); }
.legal-heading {
  font-size: var(--fs-h3); line-height: var(--lh-h3); font-weight: var(--fw-semibold);
  margin-block-start: var(--sp-2);
}

/* ============================================================
   Bottom tab bar + drawer (mobile) — §3.3 ข้อ 6
   ============================================================ */
.tabbar { display: none; }
.burger { display: none; }

/* ---------- Tablet and below: collapse the §3.1 grid (§3.2) ----------
   The boundary is 1023px because §3.2 freezes three breakpoints (768 / 1024 /
   1440-via-`--content-max`) and closes with a binding rule: "ใช้ media query กับ
   breakpoint 3 ค่านี้เท่านั้น ห้ามงอก breakpoint เฉพาะกิจต่อ component". This block
   used to sit at 1180px, which is that forbidden ad-hoc value and — worse than
   untidy — it overrode §3.1 for every desktop between 1024 and 1180: a viewport
   the spec says gets four KPI cards and a 7/5 chart row got two and a stacked
   one instead. The two declarations below are exactly what the tablet row of the
   §3.2 table asks for ("KPI = 2×2", "chart row = 1 คอลัมน์ซ้อน").
   `tests/test_web_shell.py::test_the_stylesheet_uses_only_the_frozen_breakpoints`
   pins the value so it cannot drift back. */
@media (max-width: 1023px) {
  .kpi-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid-12 > [class^="span-"] { grid-column: 1 / -1; }
}

/* ---------- Tablet: icon rail (§3.2) ---------- */
@media (max-width: 1023px) and (min-width: 768px) {
  .sidebar { inline-size: var(--sidebar-rail-w); }
  .main { margin-inline-start: var(--sidebar-rail-w); }
  .brand-name, .brand-sub, .nav-text, .nav-label, .nav-badge, .nav-soon, .user-main { display: none; }
  .brand, .nav-item, .user-card { justify-content: center; }
  .user-action span { display: none; }
  /* Side by side in a 72px rail the two actions are 40px and 32px wide, which
     fails the 44px floor of §7.1 ข้อ 8 — measured, not guessed. Stacking gives
     each one the full rail width without shrinking the icon. */
  .user-actions { flex-direction: column; }
  .user-actions form { inline-size: 100%; }
}

/* ---------- Mobile: off-canvas drawer + bottom tab bar (§3.2/§3.3) ---------- */
@media (max-width: 767px) {
  .sidebar {
    transform: translateX(-100%);
    transition: transform var(--dur-base) var(--ease);
    box-shadow: var(--sh-lg); inline-size: min(280px, 84vw);
  }
  .sidebar.open { transform: none; }
  .main { margin-inline-start: 0; padding-block-end: calc(var(--tabbar-h) + env(safe-area-inset-bottom)); }
  .topbar { padding: var(--sp-3) var(--card-pad-sm); gap: var(--sp-2); }
  .page-title { font-size: var(--fs-h2); line-height: var(--lh-h2); }
  .topbar-actions { margin-inline-start: 0; inline-size: 100%; }
  .burger { display: grid; }
  .content { padding: var(--card-pad-sm); }
  .card-head { padding: var(--card-pad-sm) var(--card-pad-sm) var(--sp-2); }
  .card-body { padding: 0 var(--card-pad-sm) var(--card-pad-sm); }
  .kpi-row { grid-template-columns: 1fr 1fr; gap: var(--sp-3); }
  .kpi { padding: var(--sp-3); }
  .kpi-num { font-size: var(--fs-h1); }
  .chip-icon { inline-size: 34px; block-size: 34px; }
  .form-grid, .qa-grid { grid-template-columns: minmax(0, 1fr); }
  .kv { grid-template-columns: 1fr; gap: var(--sp-1); }
  .kv dt { margin-block-start: var(--sp-3); }
  .toast-root { inset-inline: var(--sp-3); max-inline-size: none; }
  .sticky-bar { margin: 0 calc(var(--card-pad-sm) * -1) calc(var(--card-pad-sm) * -1); padding: var(--sp-3) var(--card-pad-sm); }
  .sticky-bar .grow { margin-inline-start: 0; inline-size: 100%; }
  .sticky-bar .btn { flex: 1; }

  /* §3.3 ข้อ 4: the sticky action bar sits above the bottom tab bar so
     "ยืนยันสั่งผลิต" is reachable without scrolling to the end of a long form. */
  .sticky-bar { inset-block-end: calc(var(--tabbar-h) + env(safe-area-inset-bottom)); }
  .item-row { padding: var(--sp-3); }
  .item-grid { grid-template-columns: minmax(0, 1fr); }
  .opt { inline-size: 100%; }

  /* §4.9: 16px บนมือถือกัน iOS zoom เข้าเอง */
  .input, .select, .textarea { font-size: 16px; }
  .stepper input { font-size: 16px; }

  .tabbar {
    display: grid; grid-template-columns: repeat(5, 1fr);
    position: fixed; inset-inline: 0; inset-block-end: 0; z-index: var(--z-sticky);
    background: var(--c-surface); border-block-start: 1px solid var(--c-border);
    padding-block-end: env(safe-area-inset-bottom);
  }
  .tabbar a, .tabbar button {
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
    min-block-size: var(--tabbar-h); padding: var(--sp-1);
    border: 0; background: none; cursor: pointer;
    color: var(--c-text-muted); font-size: var(--fs-xs); line-height: 1.3;
  }
  .tabbar a:hover { text-decoration: none; }
  .tabbar a[aria-current="page"] { color: var(--c-primary); font-weight: var(--fw-bold); }
  .tabbar .tab-primary { color: var(--c-primary); font-weight: var(--fw-bold); }
  .tabbar .is-soon { color: var(--c-text-subtle); }

  /* §3.3 ข้อ 2: a table becomes cards — not a sideways scroll. Labels come from
     data-label so the <th scope="col"> stays in the DOM for assistive tech. */
  .tbl thead, .table thead { display: none; }
  .tbl, .table, .tbl tbody, .table tbody, .tbl tr, .table tr,
  .tbl th, .table th, .tbl td, .table td { display: block; inline-size: 100%; }
  .tbl tr, .table tr {
    border: 1px solid var(--c-border); border-radius: var(--r-md);
    padding: var(--sp-3); margin-block-end: var(--sp-3); background: var(--c-surface);
  }
  .tbl th, .table th, .tbl td, .table td { border-block-end: 0; padding: var(--sp-1) 0; }
  .tbl th[scope="row"], .table th[scope="row"] { font-size: var(--fs-body); font-weight: var(--fw-bold); }
  .tbl td[data-label], .table td[data-label] { display: flex; gap: var(--sp-3); justify-content: space-between; }
  .tbl td[data-label]::before, .table td[data-label]::before {
    content: attr(data-label);
    color: var(--c-text-muted); font-weight: var(--fw-semibold); flex: none;
  }
  .tbl td.num, .table td.num { text-align: start; }
  .row-actions { margin-block-start: var(--sp-2); }
  .sub-ref { max-inline-size: none; }
}

/* ============================================================
   Order list (doc 08 §6.3) and order detail (§6.5)
   Tokens only — no hex, no raw px outside the token scale.
   ============================================================ */

/* ---------- tab shortcuts (§6.3) ---------- */
.tabs { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.tab {
  display: inline-flex; align-items: center; min-block-size: var(--tap-min);
  padding: var(--sp-2) var(--sp-4); border-radius: var(--r-pill);
  border: 1px solid var(--c-border); background: var(--c-surface);
  color: var(--c-text); font-size: var(--fs-sm); line-height: var(--lh-sm);
  font-weight: var(--fw-semibold);
}
.tab:hover { background: var(--c-primary-tint-weak); text-decoration: none; }
.tab.is-active {
  background: var(--c-primary); border-color: var(--c-primary); color: var(--c-on-primary);
}

/* ---------- filter bar ---------- */
.filters { display: flex; flex-direction: column; gap: var(--sp-4); }
.filter-row {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-4);
}
.filter-statuses { border: 0; padding: 0; margin: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
.chip-choices { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.chip-choice {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  min-block-size: var(--tap-min); padding-inline: var(--sp-1); cursor: pointer;
}
.chip-choice input { inline-size: 18px; block-size: 18px; accent-color: var(--c-primary); }
.chip-choice input:focus-visible + .pill { box-shadow: var(--sh-focus); }

/* An order number is one token to compare against a marketplace screen; wrapped
   across two lines (which is what the narrow first column did at 768px) it stops
   being scannable. It never needs `break-all` — it has no spaces to begin with. */
.ordno { white-space: nowrap; }
.result-line { font-size: var(--fs-sm); line-height: var(--lh-body); }
.pager { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.cust-name { display: block; }
.cust-phone { display: block; color: var(--c-text-muted); font-size: var(--fs-xs); line-height: var(--lh-xs); }
.pad-row { padding: var(--sp-3) var(--card-pad); }

/* ---------- detail header ---------- */
.order-head .card-body { padding-block-start: var(--card-pad); }
.order-ident { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.order-number {
  font-size: var(--fs-h2); line-height: var(--lh-h2); font-weight: var(--fw-bold);
  font-family: var(--font-mono);
}
.ref-block {
  display: inline-flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
  background: var(--c-surface-sunken); border: 1px solid var(--c-border);
  border-radius: var(--r-md); padding: var(--sp-1) var(--sp-3);
}
.ref-label { color: var(--c-text-muted); font-size: var(--fs-xs); line-height: var(--lh-xs); }
.ref-value { font-variant-numeric: tabular-nums; font-weight: var(--fw-semibold); }
.order-meta { margin-block-start: var(--sp-2); }

/* ---------- command panels (§6.5) ---------- */
.cmd-panel {
  border: 1px solid var(--c-border-strong); border-radius: var(--r-md);
  background: var(--c-surface);
}
.cmd-summary {
  display: flex; align-items: center; gap: var(--sp-2);
  min-block-size: var(--tap-min); padding: var(--sp-2) var(--sp-4);
  font-weight: var(--fw-semibold); cursor: pointer; border-radius: var(--r-md);
}
.cmd-summary:hover { background: var(--c-surface-sunken); }
.cmd-panel[open] .cmd-summary { border-block-end: 1px solid var(--c-border); }
.cmd-body {
  padding: var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-4);
}
.cmd-what { font-size: var(--fs-sm); line-height: var(--lh-body); max-inline-size: 68ch; }
.cmd-panel.banner { display: flex; }

.dup-panel { display: flex; flex-direction: column; gap: var(--sp-2); }
.dup-list { display: flex; flex-wrap: wrap; gap: var(--sp-3); }

/* ---------- payment strip (§4.6) ---------- */
.pay-strip {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-3);
  background: var(--c-surface-sunken); border-radius: var(--r-md); padding: var(--sp-3);
}
.pay-label { display: block; color: var(--c-text-muted); font-size: var(--fs-xs); line-height: var(--lh-xs); }
.pay-value { display: block; font-weight: var(--fw-semibold); }

/* ============================================================
   CR-005 paste-prefill — the box, the queue, and the amber notes
   ============================================================ */
.paste-box .paste-input { min-block-size: 6rem; font-family: var(--font-mono); font-size: var(--fs-sm); }
.paste-box .actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3); margin-block-start: var(--sp-3); }

/* Confidence (P-7): on a paste-filled form, a box the server filled reads green;
   an empty box stays neutral (white). "Guessed" is not a per-input state — it is
   an amber note above the form — so it never tints an input here. aria-invalid
   still wins, so a 422 error border is never hidden by the green accent. */
.is-prefilled .input:not(:placeholder-shown):not([aria-invalid="true"]),
.is-prefilled .textarea:not(:placeholder-shown):not([aria-invalid="true"]) {
  border-color: var(--c-success);
  box-shadow: inset 3px 0 0 0 var(--c-success);
}

.save-gate-note {
  display: flex; align-items: center; gap: var(--sp-2);
  color: var(--c-warning-ink); font-weight: var(--fw-semibold);
}

.paste-queue-list { display: flex; flex-direction: column; gap: var(--sp-1); margin-block-start: var(--sp-2); }
.paste-queue-list .q-item {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-2); border-radius: var(--r-sm);
}
.paste-queue-list .q-item.is-current { background: var(--c-primary-tint-weak); font-weight: var(--fw-semibold); }
.paste-queue-list .q-index {
  flex: none; inline-size: 1.5rem; block-size: 1.5rem; border-radius: var(--r-pill);
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--c-surface-sunken); font-size: var(--fs-xs);
}
.paste-queue-list .q-label { min-inline-size: 0; }
.paste-queue-list .q-ref { color: var(--c-text-muted); }

.paste-collision-choices { display: flex; flex-wrap: wrap; gap: var(--sp-3); margin-block-start: var(--sp-2); }
.paste-collision-choices .inline-form { display: inline; margin: 0; }

.paste-note-list { display: flex; flex-direction: column; gap: var(--sp-1); margin-block-start: var(--sp-2); }
.paste-note-list li { max-inline-size: 68ch; }

@media (max-width: 1023px) {
  .filter-row { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}

@media (max-width: 767px) {
  .filter-row { grid-template-columns: minmax(0, 1fr); }
  .pay-strip { grid-template-columns: minmax(0, 1fr); }
  .tabs { overflow-x: auto; flex-wrap: nowrap; padding-block-end: var(--sp-1); }
  .tab { flex: none; }
  .order-number { font-size: var(--fs-h3); }
  /* The mobile card of a row keeps the customer's two lines readable rather than
     pushing the phone onto the label's row (§3.3 ข้อ 2). */
  .tbl td[data-label] .cust-name, .tbl td[data-label] .cust-phone { text-align: end; }
}

/* §7.1 ข้อ 10 — reduced motion kills every animation, not only transitions */
@media (prefers-reduced-motion: reduce) {
  .oh *, .oh *::before, .oh *::after {
    animation-duration: .001ms !important; animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}
