/* ============================================================================
   JSOOR Tech · Repair — UI LAYER (single source of truth for components)
   Requires brand/jsoor-tech.css (tokens). Prefix: js-  ·  Root gets .js-scope

   CONFORMANCE CONTRACT — every component in this file satisfies all five:
     K1 tokens      no colour/space/radius/shadow literal (white ink on charcoal
                    and alpha overlays on charcoal are the only allowed literals)
     K2 elevation   a real shadow from the 3-step scale
     K3 reflection  an inset top highlight so the surface catches light
     K4 micro       hover (fine pointers) + press + focus-visible, all tokenised
     K5 a11y        >=44px target where pressable, AA contrast, reduced-motion safe
   ========================================================================= */

.js-scope { --s1: 4px; --s2: 8px; --s3: 12px; --s4: 18px; --s5: 26px;
  --refl: inset 0 1px 0 oklch(1 0 0 / .55);          /* light-surface reflection */
  --refl-dark: inset 0 1px 0 oklch(1 0 0 / .08);      /* on charcoal */
  --press: scale(.97); }
.dark .js-scope, .js-scope.dark { --refl: inset 0 1px 0 oklch(1 0 0 / .06); }

/* -- The `hidden` attribute actually hides ------------------------------------
   The UA stylesheet's `[hidden] { display: none }` sits in a LOWER cascade
   origin than this file, so ANY author rule that sets `display` silently beats
   it. Every component below sets one — so `el.hidden = true` on a .js-chip or a
   .js-btn did nothing at all, and gated controls stayed on screen. Found on the
   first deploy, where an app that had reached the venue displayed "Offline".
   Gating is the method's sanctioned way to withhold a control, which makes this
   a correctness rule rather than a nicety. -------------------------------- */
[hidden] { display: none !important; }

/* -- K1-K5 base: anything pressable inherits the interaction contract -- */
.js-btn, .js-iconbtn, .js-tab, .js-row, .js-tile, .js-pager button, .js-chip--act, .js-seg-opt {
  font-family: inherit; cursor: pointer;
  transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease),
              color var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              transform var(--dur-press) var(--ease-out);
}
.js-scope :focus-visible { outline: none; box-shadow: 0 0 0 2px var(--card), 0 0 0 4px var(--accent) !important; border-radius: var(--r-sm); }
.js-scope ::selection { background: var(--accent-soft); color: var(--accent-fg); }

/* -- C01 surfaces -- */
.js-card { background: var(--card); border: 1px solid var(--border); border-radius: var(--r-card); box-shadow: var(--sh-1), var(--refl); }
.js-card--raised { box-shadow: var(--sh-2), var(--refl); }
.js-card--float { box-shadow: var(--sh-3), var(--refl); }
.js-panel { display: flex; flex-direction: column; min-height: 0; overflow: hidden; }
.js-sunken { background: var(--sunken); border: 1px solid var(--border); border-radius: var(--r-md); box-shadow: inset 0 1px 2px oklch(0 0 0 / .04); }

/* -- C02 slab -- */
.js-slab { position: relative; overflow: hidden; border-radius: var(--r-card); background: var(--char-900); color: #fff; padding: 20px 24px 18px; box-shadow: var(--sh-3), var(--refl-dark); }
.js-slab::before { content: ""; position: absolute; inset: auto -8% -70% 44%; height: 130%; background: radial-gradient(closest-side, var(--accent), transparent 70%); opacity: .2; pointer-events: none; }
.js-slab::after { content: ""; position: absolute; inset: 0 0 auto 0; height: 40%; background: linear-gradient(oklch(1 0 0 / .05), transparent); pointer-events: none; }
.dark .js-slab { background: var(--card); border: 1px solid var(--border); }
.js-slab > * { position: relative; }
.js-slab-label { font-family: var(--font-mono); font-size: var(--t-meta); letter-spacing: .12em; text-transform: uppercase; color: oklch(1 0 0 / .55); display: flex; align-items: center; gap: var(--s2); }
.dark .js-slab-label { color: var(--muted); }
.js-slab-label .live { width: 7px; height: 7px; border-radius: 50%; background: var(--ok-dot); box-shadow: 0 0 0 3px oklch(.6 .1 186 / .25); }
.js-figure { margin-top: 7px; font-size: clamp(2.5rem, 4vw, 3.5rem); font-weight: 600; letter-spacing: -.045em; line-height: .95; color: #fff; display: flex; align-items: baseline; gap: 10px; font-variant-numeric: tabular-nums; }
.dark .js-figure { color: var(--ink); }
.js-figure .cur { font-size: .875rem; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: oklch(1 0 0 / .5); }
.dark .js-figure .cur { color: var(--muted); }

.js-eyebrow { font-family: var(--font-mono); font-size: .68rem; font-weight: 500; letter-spacing: .12em; text-transform: uppercase; color: var(--muted); }
.js-h { font-size: var(--t-h2); font-weight: 600; letter-spacing: -.015em; color: var(--ink); }

/* -- C03 buttons -- */
.js-btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--s2); min-height: 44px; padding: 0 16px; border-radius: var(--r-btn); border: 1px solid var(--border-strong); background: var(--card); color: var(--ink); font-size: .9375rem; font-weight: 600; white-space: nowrap; box-shadow: var(--sh-1), var(--refl); }
.js-btn--primary { background: var(--primary); color: var(--primary-fg); border-color: var(--primary); box-shadow: var(--sh-2), var(--refl-dark); }
.js-btn--ghost { border-color: transparent; background: transparent; color: var(--muted); box-shadow: none; }
.js-btn--danger { color: var(--bad-fg); border-color: var(--bad-bd); background: var(--bad-bg); }
.js-btn--sm { min-height: 36px; padding: 0 12px; font-size: var(--t-body); border-radius: var(--r-sm); }
@media (hover: hover) and (pointer: fine) {
  .js-btn:hover:not(:disabled) { border-color: var(--accent); box-shadow: var(--sh-2), var(--refl); }
  .js-btn--primary:hover:not(:disabled) { background: var(--primary-hover); border-color: var(--primary-hover); box-shadow: var(--sh-3), var(--refl-dark); }
  .js-btn--ghost:hover:not(:disabled) { color: var(--ink); background: var(--sunken); box-shadow: var(--sh-1), var(--refl); }
}
.js-btn:active:not(:disabled) { transform: var(--press); box-shadow: var(--sh-1), var(--refl); }
.js-btn:disabled { opacity: .45; cursor: default; transform: none; box-shadow: none; }

/* -- C04 icon button -- */
.js-iconbtn { position: relative; width: 44px; height: 44px; display: grid; place-items: center; border-radius: var(--r-sm); border: 1px solid var(--border); background: var(--card); color: var(--ink-2); box-shadow: var(--sh-1), var(--refl); }
.js-iconbtn--sm { width: 40px; height: 40px; }
@media (hover: hover) and (pointer: fine) { .js-iconbtn:hover { border-color: var(--accent); color: var(--ink); box-shadow: var(--sh-2), var(--refl); } }
.js-iconbtn:active { transform: scale(.96); box-shadow: var(--sh-1), var(--refl); }
.js-iconbtn .ping { position: absolute; top: 9px; inset-inline-end: 10px; width: 6px; height: 6px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 0 2px var(--card); }

/* -- C05 state chips -- */
.js-chip { display: inline-flex; align-items: center; gap: 6px; height: 22px; padding: 0 9px; border-radius: var(--r-pill); font-size: var(--t-meta); font-weight: 600; white-space: nowrap; border: 1px solid var(--neutral-bd); background: var(--neutral-bg); color: var(--neutral-fg); box-shadow: var(--refl); }
.js-chip .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--neutral-dot); flex: none; box-shadow: 0 0 0 2px color-mix(in oklab, currentColor 18%, transparent); }
.js-chip--ok { background: var(--ok-bg); border-color: var(--ok-bd); color: var(--ok-fg); } .js-chip--ok .dot { background: var(--ok-dot); }
.js-chip--info { background: var(--info-bg); border-color: var(--info-bd); color: var(--info-fg); } .js-chip--info .dot { background: var(--info-dot); }
.js-chip--warn { background: var(--warn-bg); border-color: var(--warn-bd); color: var(--warn-fg); } .js-chip--warn .dot { background: var(--warn-dot); }
.js-chip--bad { background: var(--bad-bg); border-color: var(--bad-bd); color: var(--bad-fg); } .js-chip--bad .dot { background: var(--bad-dot); }
.js-chip--frozen { background: var(--frozen-bg); border-color: var(--frozen-bd); color: var(--frozen-fg); } .js-chip--frozen .dot { background: var(--frozen-dot); }
.js-chip--guest { background: var(--guest-bg); border-color: var(--guest-bd); color: var(--guest-fg); } .js-chip--guest .dot { background: var(--guest-dot); }
.js-chip--accent { background: var(--accent-soft); border-color: var(--accent-soft-bd); color: var(--accent-fg); } .js-chip--accent .dot { background: var(--accent); }
.js-chip--mono { font-family: var(--font-mono); font-weight: 500; font-size: .66rem; }
.js-chip--act { min-height: 32px; padding: 0 12px; }
@media (hover: hover) and (pointer: fine) { .js-chip--act:hover { border-color: var(--accent); box-shadow: var(--sh-1), var(--refl); } }
.js-chip--act:active { transform: scale(.96); }

/* -- C06 tabs / segmented -- */
.js-tabs { display: inline-flex; gap: 2px; padding: 3px; background: var(--sunken); border: 1px solid var(--border); border-radius: var(--r-pill); box-shadow: inset 0 1px 2px oklch(0 0 0 / .04); }
.js-tab { display: inline-flex; align-items: center; gap: var(--s2); min-height: 34px; padding: 0 13px; border: 0; background: transparent; border-radius: var(--r-pill); color: var(--muted); font-size: .875rem; font-weight: 500; white-space: nowrap; }
.js-tab span { font-family: var(--font-mono); font-size: .7rem; color: var(--faint); }
.js-tab.is-on { background: var(--card); color: var(--ink); font-weight: 600; box-shadow: var(--sh-1), var(--refl); }
.js-tab.is-on span { color: var(--accent-fg); }
@media (hover: hover) and (pointer: fine) { .js-tab:not(.is-on):hover { color: var(--ink); background: color-mix(in oklab, var(--card) 60%, transparent); } }
.js-tab:active { transform: scale(.98); }
.js-seg { display: grid; grid-auto-flow: column; gap: 6px; padding: 4px; background: var(--sunken); border: 1px solid var(--border); border-radius: var(--r-pill); box-shadow: inset 0 1px 2px oklch(0 0 0 / .04); }
.js-seg-opt { position: relative; display: grid; place-items: center; min-height: 40px; border-radius: var(--r-pill); font-size: .9375rem; font-weight: 600; color: var(--muted); }
.js-seg-opt input { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
.js-seg-opt:has(input:checked) { background: var(--card); color: var(--ink); box-shadow: var(--sh-1), var(--refl); }
.js-seg-opt--ok:has(input:checked) { color: var(--ok-fg); } .js-seg-opt--bad:has(input:checked) { color: var(--bad-fg); }
.js-seg-opt:active { transform: scale(.98); }

/* -- C07 pager -- */
.js-pager { display: inline-flex; align-items: center; gap: var(--s1); }
.js-pager button { width: 34px; height: 34px; display: grid; place-items: center; border-radius: var(--r-sm); border: 1px solid var(--border); background: var(--card); color: var(--ink-2); box-shadow: var(--sh-1), var(--refl); }
.js-pager button:disabled { opacity: .4; cursor: default; box-shadow: none; }
@media (hover: hover) and (pointer: fine) { .js-pager button:not(:disabled):hover { border-color: var(--accent); color: var(--ink); box-shadow: var(--sh-2), var(--refl); } }
.js-pager button:not(:disabled):active { transform: scale(.94); }
.js-pager > span { font-family: var(--font-mono); font-size: var(--t-meta); color: var(--muted); min-width: 32px; text-align: center; }

/* -- C08 table head + rows -- */
.js-thead { display: grid; align-items: center; gap: var(--s3); padding: 8px 18px; background: var(--sunken); border-bottom: 1px solid var(--border); font-family: var(--font-mono); font-size: .64rem; letter-spacing: .11em; text-transform: uppercase; color: var(--muted); box-shadow: var(--refl); }
.js-rows { flex: 1; min-height: 0; display: grid; grid-auto-rows: minmax(44px, 1fr); overflow: hidden; }
.js-row { position: relative; display: grid; align-items: center; gap: var(--s3); padding: 0 18px; min-height: 44px; border: 0; border-top: 1px solid var(--hairline); background: transparent; text-align: start; color: var(--ink); }
.js-row:first-child { border-top: 0; }
.js-row::before { content: ""; position: absolute; inset-block: 0; inset-inline-start: 0; width: 3px; background: var(--accent); opacity: 0; transition: opacity var(--dur) var(--ease); }
@media (hover: hover) and (pointer: fine) {
  .js-row:hover { background: var(--sunken); }
  .js-row:hover::before { opacity: 1; }
  .js-row:hover .js-go { color: var(--accent-fg); transform: translateX(3px); }
  .js-row:hover .js-progress i { background: var(--accent); }
}
.js-row:active { background: var(--sunken); transform: scale(.999); }
.js-row:focus-visible { box-shadow: inset 0 0 0 2px var(--accent) !important; border-radius: 2px; }
.js-name b { display: block; font-size: .9375rem; font-weight: 600; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.js-name span { display: block; font-size: var(--t-meta); color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.js-num { text-align: end; font-variant-numeric: tabular-nums; font-size: .9375rem; white-space: nowrap; }
.js-num--quiet { color: var(--muted); font-weight: 500; }
.js-num--lead { font-weight: 600; color: var(--ink); letter-spacing: -.01em; }
.js-num--bad { color: var(--bad-fg); font-weight: 600; }
.js-num--ok { color: var(--ok-fg); font-weight: 600; }
.js-go { color: var(--faint); display: inline-grid; place-items: center; transition: transform var(--dur) var(--ease-out), color var(--dur) var(--ease); }
[dir="rtl"] .js-go { transform: scaleX(-1); }

/* -- C09 progress -- */
.js-progress { height: 5px; border-radius: var(--r-pill); background: var(--sunken); border: 1px solid var(--border); overflow: hidden; box-shadow: inset 0 1px 1px oklch(0 0 0 / .05); }
.js-progress i { display: block; height: 100%; background: var(--ok-dot); box-shadow: var(--refl); transition: background-color var(--dur) var(--ease), width var(--dur-pop) var(--ease-out); }

/* -- C10 tile -- */
.js-tilegrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(168px, 1fr)); grid-auto-rows: minmax(112px, max-content); gap: var(--s3); align-content: start; }
.js-tile { position: relative; display: flex; flex-direction: column; min-height: 112px; padding: 12px 13px 11px; border-radius: 14px; background: var(--card); border: 1px solid var(--border); box-shadow: var(--sh-1), var(--refl); text-align: start; color: var(--ink); overflow: hidden; }
.js-tile::after { content: ""; position: absolute; inset-block: 0; inset-inline-start: 0; width: 3px; background: var(--accent); opacity: 0; transition: opacity var(--dur) var(--ease); }
@media (hover: hover) and (pointer: fine) { .js-tile:hover:not(.is-out) { border-color: var(--border-strong); box-shadow: var(--sh-2), var(--refl); transform: translateY(-1px); } .js-tile:hover::after { opacity: 1; } }
.js-tile:active:not(.is-out) { transform: scale(.985); box-shadow: var(--sh-1), var(--refl); }
.js-tile-head { flex: none; display: flex; align-items: center; justify-content: space-between; gap: var(--s2); min-width: 0; }
.js-tile-name { margin-top: 7px; font-size: .92rem; font-weight: 600; line-height: 1.25; letter-spacing: -.01em; overflow-wrap: anywhere; }
.js-tile-foot { margin-top: auto; padding-top: 10px; display: flex; align-items: flex-end; justify-content: space-between; gap: var(--s2); }
.js-price { font-weight: 700; font-size: 1.2rem; letter-spacing: -.025em; line-height: 1; font-variant-numeric: tabular-nums; }
.js-price small { font-size: .6rem; font-weight: 600; color: var(--muted); margin-inline-start: 3px; letter-spacing: .04em; }
.js-tile.is-out { cursor: default; background: repeating-linear-gradient(135deg, var(--card) 0 7px, var(--sunken) 7px 14px); box-shadow: none; }
.js-tile.is-out .js-tile-name, .js-tile.is-out .js-price { color: var(--muted); }

/* -- C11 fields -- */
.js-field { display: flex; flex-direction: column; gap: 6px; }
.js-label { font-family: var(--font-mono); font-size: .66rem; letter-spacing: .11em; text-transform: uppercase; color: var(--muted); }
.js-hint { font-size: var(--t-meta); color: var(--faint); }
.js-input { width: 100%; min-height: 46px; padding: 0 13px; font-family: inherit; font-size: 1rem; color: var(--ink); background: var(--sunken); border: 1.5px solid var(--border-strong); border-radius: var(--r-input); box-shadow: inset 0 1px 2px oklch(0 0 0 / .05); transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease), background-color var(--dur) var(--ease); }
.js-input::placeholder { color: var(--faint); }
@media (hover: hover) and (pointer: fine) { .js-input:hover:not(:focus) { border-color: var(--muted); } }
.js-input:focus { outline: none; border-color: var(--accent); background: var(--card); box-shadow: 0 0 0 3px var(--ring), var(--refl) !important; }
select.js-input { appearance: none; background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%), linear-gradient(135deg, var(--muted) 50%, transparent 50%); background-position: calc(100% - 18px) 21px, calc(100% - 13px) 21px; background-size: 5px 5px; background-repeat: no-repeat; padding-inline-end: 34px; }
.js-amount { position: relative; display: block; }
.js-amount .js-input { padding-inline-end: 48px; font-family: var(--font-mono); font-weight: 600; font-size: 1.125rem; text-align: end; }
.js-amount small { position: absolute; inset-inline-end: 13px; inset-block-start: 50%; transform: translateY(-50%); font-size: .7rem; font-weight: 600; letter-spacing: .06em; color: var(--faint); pointer-events: none; }

/* -- C12 dialog -- */
.js-dialog { position: fixed; inset: 0; z-index: 900; display: grid; place-items: center; padding: 24px; }
.js-dialog-scrim { position: absolute; inset: 0; background: color-mix(in oklab, var(--char-950) 55%, transparent); opacity: 0; transition: opacity var(--dur-pop) var(--ease-out); }
.js-dialog.is-on .js-dialog-scrim { opacity: 1; }
.js-dialog-panel { position: relative; width: min(520px, 100%); max-height: calc(100vh - 48px); display: flex; flex-direction: column; background: var(--card); border: 1px solid var(--border-strong); border-radius: var(--r-card); box-shadow: var(--sh-3), var(--refl); opacity: 0; transform: scale(.97); transition: opacity var(--dur-pop) var(--ease-out), transform var(--dur-pop) var(--ease-out); }
.js-dialog.is-on .js-dialog-panel { opacity: 1; transform: none; }

/* -- C13 toast -- */
.js-toast { position: fixed; inset-block-end: 26px; inset-inline-start: 50%; z-index: 950; display: inline-flex; align-items: center; gap: var(--s2); padding: 12px 20px; border-radius: var(--r-pill); background: var(--primary); color: var(--primary-fg); font-size: .9375rem; font-weight: 600; box-shadow: var(--sh-3), var(--refl-dark); opacity: 0; transform: translateX(-50%) translateY(12px); transition: opacity 220ms var(--ease-out), transform 220ms var(--ease-out); pointer-events: none; }
.js-toast.is-on { opacity: 1; transform: translateX(-50%); }

/* -- C14 empty -- */
.js-empty { display: grid; place-items: center; align-content: center; gap: 5px; padding: 22px; text-align: center; color: var(--muted); font-size: .9375rem; }
.js-empty b { color: var(--ink); font-weight: 600; } .js-empty span { font-size: var(--t-body); color: var(--faint); }

/* -- C15 avatar / count -- */
.js-avatar { width: 34px; height: 34px; flex: none; display: grid; place-items: center; border-radius: 50%; background: var(--primary); color: var(--primary-fg); font-weight: 600; font-size: var(--t-meta); box-shadow: var(--sh-1), var(--refl-dark); }
.js-count { font-family: var(--font-mono); font-size: .7rem; font-weight: 500; color: var(--muted); background: var(--sunken); border: 1px solid var(--border); border-radius: var(--r-pill); padding: 2px 9px; box-shadow: var(--refl); }
.js-count--accent { color: var(--accent-fg); background: var(--accent-soft); border-color: var(--accent-soft-bd); }

/* ============================================================================
   C16 SHEET — A MODAL THAT IS A PAGE                          (contract M1-M7)
   ----------------------------------------------------------------------------
   Ruled 2026-09-12: every page is an app and every modal is a page in that app.
   The measured failure is that modals get drawn big and empty and their edge
   cases get invented by whoever hits them first. C12 `js-dialog` stays for a
   small acknowledgement; anything that asks a question, takes a number or
   writes a record is this.

   The M clauses are in RAWASI-DESIGN-DOCTRINE.md 11. CSS can only carry four of
   them — M1 (room for a sentence), M2 (a state block that cannot collapse),
   M4 (a visible guard against two primaries) and M5 (a labelled dismiss, and no
   slot for an icon-only close). M3, M6 and M7 are the author's, and no rule here
   can fake them.

   THE LAYER MUST LIVE INSIDE THE `js-scope` ROOT. The focus ring is written as
   `.js-scope :focus-visible`, so a sheet appended to <body> outside the scope
   loses every focus ring in it and fails K5 while looking correct.

   Z-LADDER, stated once so nobody guesses: peek 800, sheet 850, dialog 900,
   toast 950. The toast is above the sheet on purpose — a sheet commits, and the
   report of what was recorded has to be readable over it.
   ========================================================================= */

.js-sheet-layer { position: fixed; inset: 0; z-index: 850; display: none; }
.js-sheet-layer.is-on { display: block; }
.js-sheet-scrim { position: absolute; inset: 0; background: color-mix(in oklab, var(--char-950) 62%, transparent); opacity: 0; transition: opacity var(--dur-drawer) var(--ease-out); }
.js-sheet-layer.is-on .js-sheet-scrim { opacity: 1; }

/* Over the surface, never instead of it: the scrim dims but does not replace,
   because a machine that stops answering while an order is being rung still has
   to be findable. And the panel is `block-size: auto` with a CAP, not a fixed
   `inset-block` — pinning the top and the bottom is exactly how a three-line
   confirmation renders in a 92vh-tall empty box, which is the defect this
   component was written to end. */
.js-sheet {
  position: absolute; inset-inline-start: 50%; inset-block-start: 50%;
  inline-size: min(940px, 94vw);
  block-size: auto; max-block-size: min(88vh, 900px);
  display: flex; flex-direction: column;
  background: var(--card); border: 1px solid var(--border-strong);
  border-radius: var(--r-card); box-shadow: var(--sh-3), var(--refl);
  overflow: hidden; opacity: 0;
  transform: translate(-50%, -50%) translateY(10px) scale(.99);
  transition: opacity var(--dur-drawer) var(--ease-out), transform var(--dur-drawer) var(--ease-out);
}
.js-sheet-layer.is-on .js-sheet { opacity: 1; transform: translate(-50%, -50%); }
/* A step that genuinely needs the full height (a counting sheet, a grid of
   items) opts in. The default does not, and that asymmetry is the point. */
.js-sheet--tall { block-size: min(88vh, 900px); }

/* -- head: M1 has to fit ---------------------------------------------------
   No clamp, no ellipsis, no nowrap. A one-line title box is what makes an
   author write "Details" instead of "There is less than we expected" — the
   container silently sets the copy budget. 46ch keeps the sentence readable
   instead of running the full 940px.
   Every selector here is a CLASS. `.js-sheet-head span { display: block }` is
   the shape of a live defect on the vendored screens: a class-plus-type rule
   beats a bare class, so a `js-chip` dropped into a sheet head stacks
   vertically and nobody can see why. Type selectors are banned in this block. */
.js-sheet-head { flex: none; display: flex; align-items: flex-start; gap: var(--s-3); padding: 14px 18px; border-block-end: 1px solid var(--border); }
.js-sheet-head-txt { min-inline-size: 0; }
.js-sheet-title { display: block; font-size: var(--t-h2); font-weight: 600; color: var(--ink); letter-spacing: -.01em; line-height: var(--lh-tight); max-inline-size: 46ch; text-wrap: pretty; }
.js-sheet-sub { display: block; margin-block-start: 2px; font-family: var(--font-mono); font-size: var(--t-meta); color: var(--muted); }
/* The step counter earns its place: a modal is a page, and a page in a flow
   says which page it is. "Step 1 of 3", not a progress bar with no numbers. */
.js-sheet-step { flex: none; margin-inline-start: auto; font-family: var(--font-mono); font-size: var(--t-meta); letter-spacing: .1em; text-transform: uppercase; color: var(--faint); }

/* -- body: clips, never scrolls -------------------------------------------
   Law 1 applies INSIDE a modal. `overflow: hidden` is a tripwire, not an
   oversight: content past the cap is visibly clipped so the author pages or
   tabs it, where a scrollbar would have absorbed the problem and shipped it. */
.js-sheet-body { flex: 1; min-block-size: 0; overflow: hidden; padding: var(--s-4) 18px; }
.js-sheet-body--flush { padding: 0; }

/* -- M2: the state family, and a floor under it ---------------------------
   Loading, empty, error and conflict all render here. `min-block-size` is the
   floor that stops a one-line state from collapsing the panel to a letterbox,
   and `max-inline-size` keeps the sentence readable — a state is a SENTENCE
   saying what is true and what happens next, never a spinner on its own.
   Tone comes from the seven-tone chips (C05); this block adds no colour, so a
   state cannot be styled locally into an eighth tone. */
.js-sheet-state { display: grid; place-items: center; align-content: center; gap: var(--s-2); min-block-size: 168px; padding: var(--s-5) var(--s-4); text-align: center; }
.js-sheet-state-lead { font-size: var(--t-body-lg); font-weight: 600; color: var(--ink); }
.js-sheet-state-why { max-inline-size: 52ch; margin: 0; font-size: var(--t-body); line-height: var(--lh-body); color: var(--muted); }

/* -- M2 loading, stated: the actions go inert and the cursor says why. No
   skeleton, no shimmer — a fake of the content is a claim about data we do not
   have yet, and an action bar that stays live through it is how double-submits
   happen. */
.js-sheet.is-busy .js-sheet-act { pointer-events: none; cursor: progress; }
.js-sheet.is-busy .js-sheet-body { cursor: progress; }

/* -- the action bar: M4 and M5 --------------------------------------------
   `--plate` is a vertical token; the family falls back to `--sunken` so this
   works with or without a vertical delta loaded. */
.js-sheet-act { flex: none; display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); padding: 13px 18px; border-block-start: 1px solid var(--border); background: var(--plate, var(--sunken)); }
/* M5: the dismiss is a LABELLED button at the end of the bar, not a × in the
   header. The contract has no header-close slot at all, because an icon cannot
   state a consequence and "Cancel" describes the gesture rather than the
   result. It sits last so the eye reaches the outcome before the exit. */
.js-sheet-dismiss { margin-inline-start: auto; }
/* The `why` line under the buttons: one sentence saying what the primary
   actually does, or why it is withheld. It spans the bar so it is never read as
   a caption on the last button. */
.js-sheet-why { flex-basis: 100%; margin: 0; font-size: var(--t-meta); line-height: var(--lh-snug); color: var(--muted); }

/* -- M4 GUARD, visible in the browser ------------------------------------
   A second primary in the action bar is a contract breach that reads as design
   emphasis, which is why it survives review. It is now impossible to miss: the
   offending button is ringed in the danger tone wherever it renders. Cheaper
   than a test, and it fires in the one place the author is already looking. */
.js-sheet-act .js-btn--primary ~ .js-btn--primary {
  outline: 2px dashed var(--bad-fg); outline-offset: 2px;
}

/* -- K5 reduced motion -- */
@media (prefers-reduced-motion: reduce) {
  .js-scope *, .js-dialog *, .js-sheet-layer *, .js-toast { animation: none !important; transition-property: opacity, color, background-color, border-color, box-shadow !important; }
  .js-dialog-panel { transform: none !important; }
  /* The sheet is CENTRED by a transform, so it cannot simply drop it:
     zeroing `transform` here would park the panel's own top-left corner at
     50%/50%, i.e. half off the bottom-right of the viewport. The resting
     position is restored; only the entrance offset and the scale go. */
  .js-sheet, .js-sheet-layer.is-on .js-sheet { transform: translate(-50%, -50%) !important; }
  .js-sheet, .js-sheet-scrim { transition: none !important; }
  .js-toast { transform: translateX(-50%) !important; }
  .js-btn:active, .js-tile:active, .js-row:active, .js-iconbtn:active, .js-tab:active, .js-pager button:active { transform: none !important; }
}

/* ============================================================================
   PATTERN A/B SCAFFOLD — the layout grammar, added in consolidation.
   These are not new components. They are the three boxes every surface uses,
   so a new build does not reinvent "slab -> board -> rail" in local CSS.
   ========================================================================= */
.js-surface { height: 100dvh; display: flex; flex-direction: column; overflow: hidden; padding-block-start: var(--jshell-h, 0px); }
.js-stage { flex: 1; min-height: 0; display: grid; gap: var(--s-4); padding: var(--s-4); grid-template-columns: minmax(0, 1fr) 320px; grid-template-rows: auto minmax(0, 1fr); grid-template-areas: "slab rail" "board rail"; }
.js-stage > .js-slab { grid-area: slab; }
.js-stage > .js-board { grid-area: board; min-height: 0; }
.js-stage > .js-rail { grid-area: rail; min-height: 0; display: flex; flex-direction: column; gap: var(--s-4); }
.js-stage--noRail { grid-template-columns: minmax(0, 1fr); grid-template-areas: "slab" "board"; }
.js-stage--noSlab { grid-template-rows: minmax(0, 1fr); grid-template-areas: "board rail"; }
/* -- Law 1 HOLDS WHERE IT IS TRUE, and stops where it becomes data loss ----
   This block used to read `.js-stage > .js-rail { display: none }` below
   1080px. Measured on the gaming counter: that removed the whole "Needs you"
   feed, the legend, the waiting queue and the "Seat Ibrahim at PC-08" button —
   with no replacement anywhere on the screen and no route to any of them. The
   doctrine's own words are "a capability with no control is NOT shipped.
   Gating is allowed; hiding is banned", so a breakpoint that deletes four
   controls to preserve a layout guarantee had the priority backwards.

   THE RAIL MOVES. It never goes. Below the design width the stage stops
   promising one viewport: it stacks slab / board / rail in DOM order and
   becomes the scroll container. "Never scrolls" is a guarantee at the design
   size and a data-loss mechanism below it, so it stops applying there.

   Two axes, because the failure has two. WIDTH takes the rail out of the
   second column; HEIGHT alone does not — at 1600x860 the board still wants
   two columns, it just no longer fits vertically, so only the row sizing and
   the scroll change and the rail stays where it is. Measured cut-offs on the
   counter: intact at >=1181 x >=1000, content lost below either. */
@media (max-width: 1180px) {
  .js-stage {
    display: flex; flex-direction: column; overflow-y: auto; overscroll-behavior: contain;
  }
  .js-stage > .js-slab, .js-stage > .js-board, .js-stage > .js-rail {
    flex: none; min-height: auto;
  }
}
@media (min-width: 1181px) and (max-height: 999px) {
  .js-stage { grid-template-rows: auto auto; overflow-y: auto; overscroll-behavior: contain; }
  .js-stage--noSlab { grid-template-rows: auto; }
  .js-stage > .js-board, .js-stage > .js-rail { min-height: auto; }
}
