/*
  ALPHA-06 bartender ticket UI component styles.

  Built entirely on design/tokens.css and the active design/themes/*.css
  file (see index.html's <link> order and design/README.md's "How
  engineers consume this in ALPHA-06"). Every color, spacing, radius,
  shadow, and touch-target value below is a var(--token-name); nothing
  here is a literal hex, pixel, or millisecond copied out of a mockup, per
  design/README.md's instruction. Class names follow
  design/mockups/ring.html and design/mockups/liquor-sheet.html where a
  screen has an authoritative mockup, so this file reads as an
  implementation of those mockups rather than a new layout language.

  Touch-first per design/principles.md: nothing tappable is smaller than
  --touch-target-min (44px); Prepared and Pay use the larger tiers. No
  hover-only affordance carries meaning; :hover rules below are a bonus
  for a mouse-driven review pass, never required to operate the screen.
*/

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
  height: 100%;
  background: var(--color-bg);
  color: var(--color-text-primary);
  font-family: var(--font-family-base);
  font-size: var(--font-size-base);
  line-height: var(--line-height-normal);
  -webkit-font-smoothing: antialiased;
}

button,
input,
select {
  font-family: inherit;
  font-size: inherit;
  /* Contrast defect fix, owner review 2026-08-22 (Amendment 2026-08-22,
     item 1): form controls do not inherit `color` from their ancestors
     by default in the UA stylesheet the way a <div> or <span> does; a
     <button> in particular renders with the platform's `buttontext`
     system color unless told otherwise. .tile already carried its own
     `color: inherit;` (ALPHA-06) for exactly this reason, but that fix
     was never generalized, so every OTHER button built afterward
     (.tab-card in ALPHA-07, .liquor-row in ALPHA-06/10, admin rows, and
     every <input>/<select>) silently fell back to that near-black
     system default. On every light theme this happened to look correct
     by accident (near-black text on a light surface reads fine), which
     is exactly why the theme verification pass never caught it: nothing
     LOOKED broken until Charcoal, the one dark theme, rendered the same
     near-black default against a near-black background. Setting
     `color: inherit` here once, at the element level, makes every
     current and future form control resolve to the active theme's
     `--color-text-primary` the same way ordinary text already does,
     instead of depending on each new component remembering to repeat
     `.tile`'s fix by hand. */
  color: inherit;
}

[hidden] {
  display: none !important;
}

/* ---------- Buttons, shared ---------- */

.btn-primary,
.btn-secondary,
.btn-pay,
.btn-add {
  border-radius: var(--radius-md);
  font-weight: var(--font-weight-semibold);
  border: 1px solid transparent;
  cursor: pointer;
}

.btn-primary {
  height: var(--touch-target-comfortable);
  background: var(--color-accent-strong);
  color: var(--color-text-on-accent);
  font-size: var(--font-size-base);
  box-shadow: var(--shadow-2);
  border: none;
}
/* Design principle 3, "blocked is a state you see before you touch it":
   the same visually-blocked treatment .btn-pay:disabled already
   establishes, extended to every plain .btn-primary a screen can disable
   (e.g. the Counts review's Close count button with nothing counted
   yet), so a disabled primary action never LOOKS fully live. */
.btn-primary:disabled {
  background: var(--color-surface-sunken);
  color: var(--color-text-tertiary);
  box-shadow: none;
}

.btn-secondary {
  height: var(--touch-target-comfortable);
  background: var(--color-surface);
  border: 1px solid var(--color-border-strong);
  color: var(--color-text-primary);
  font-size: var(--font-size-base);
  padding: 0 var(--space-4);
}

.btn-compact {
  padding: 0 var(--space-4);
}

.btn-full {
  width: 100%;
}

.btn-icon {
  width: var(--touch-target-min);
  height: var(--touch-target-min);
  border-radius: var(--radius-full);
  border: none;
  background: transparent;
  color: var(--color-text-secondary);
  font-size: var(--font-size-base);
  cursor: pointer;
}
.btn-icon:hover {
  background: var(--color-surface-sunken);
}

button:disabled {
  cursor: not-allowed;
}

button:focus-visible,
input:focus-visible,
select:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
}

/* ---------- Centered-card screens (auth track, track/auth Slice 1) ----
   Five screens share this one centered-card layout: terminal setup (the
   one-time server/tenant connection step), the user-tile picker, the PIN
   pad, the manager-approval approver picker, and the payment-terminal
   picker. index.html's own header comment has the full walk order; this
   replaced the old single #login-screen the pre-auth stub used.

   Gate finding 6, 2026-08-27: #approval-picker-screen and
   #terminal-picker-screen were both built on the same .login-card markup
   as the three original screens but were never added to this selector,
   so each rendered as a bare card jammed into the top-left of an
   otherwise empty page (measured at 1280x800: getBoundingClientRect()
   height 355 against a full-viewport page, no centering, no padding).
   Anything added here must be added to this list; e2e/
   manager-approval.spec.ts asserts the resulting geometry rather than
   only toBeVisible(), which cannot see the difference. */

#terminal-setup-screen,
#user-picker-screen,
#pin-entry-screen,
#approval-picker-screen,
#terminal-picker-screen {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-6);
}

.login-card {
  width: 100%;
  max-width: 420px;
  background: var(--color-surface);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-3);
  padding: var(--space-8);
}

.login-title {
  font-size: var(--font-size-xl);
  font-weight: var(--font-weight-bold);
  margin: 0 0 var(--space-1);
}

.login-subtitle {
  color: var(--color-text-secondary);
  margin: 0 0 var(--space-6);
}

.field-label {
  display: block;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-text-secondary);
  margin: var(--space-4) 0 var(--space-1);
}

.field-input {
  width: 100%;
  height: var(--touch-target-comfortable);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border-strong);
  background: var(--color-surface-sunken);
  padding: 0 var(--space-4);
  color: var(--color-text-primary);
}

.login-submit {
  width: 100%;
  margin-top: var(--space-6);
}

.login-note {
  margin-top: var(--space-5);
  font-size: var(--font-size-xs);
  color: var(--color-text-tertiary);
  line-height: var(--line-height-relaxed);
}

/* ---------- User-tile picker ----------
   One large touch tile per active employee (CLAUDE.md touch-only rule:
   this is the only "typing" a shared terminal ever needs, and it is
   none). Widens .login-card slightly over the plain 420px login-card
   default so 2-3 tiles per row is comfortable on a landscape tablet. */
.user-picker-card {
  max-width: 640px;
}
.user-tile-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: var(--space-3);
  margin: var(--space-2) 0 var(--space-5);
}
.user-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--touch-target-primary);
  padding: var(--space-5) var(--space-3);
  border-radius: var(--radius-lg);
  border: 1px solid var(--color-border-strong);
  background: var(--color-surface-sunken);
  color: var(--color-text-primary);
  cursor: pointer;
  text-align: center;
}
.user-tile:hover {
  border-color: var(--color-accent-subtle-border);
  background: var(--color-accent-subtle);
}
.user-tile:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
}
.user-tile-name {
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight);
}
.user-tile-role-badge {
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  color: var(--color-text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.user-picker-change-terminal {
  margin-top: var(--space-5);
  width: 100%;
}

/* ---------- PIN pad ----------
   One reusable pad (main.ts's openPinEntry) driving both login and the
   three-step change-PIN flow. Every digit is --touch-target-primary
   (64px), the largest of the three touch-target tiers: this is the
   highest-frequency touch target in the whole app, entered many times a
   shift on a shared terminal with no keyboard fallback (CLAUDE.md
   touch-only rule). 3 columns, matching the DOM order already in
   index.html (1-2-3 / 4-5-6 / 7-8-9 / clear-0-backspace). */
.pin-pad-card {
  width: 100%;
  max-width: 420px;
  background: var(--color-surface);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-3);
  padding: var(--space-8);
  text-align: center;
}
.pin-entry-title {
  font-size: var(--font-size-xl);
  font-weight: var(--font-weight-bold);
  margin: 0 0 var(--space-1);
}
.pin-entry-subtitle {
  color: var(--color-text-secondary);
  margin: 0 0 var(--space-5);
}
/* Dots, never digits (this is a shared terminal); large and legible from
   arm's length, per the work item's own "not a phone" instruction. */
.pin-entry-display {
  min-height: var(--touch-target-comfortable);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-size-2xl);
  letter-spacing: var(--space-3);
  color: var(--color-text-primary);
  margin-bottom: var(--space-4);
}
.pin-entry-error {
  color: var(--color-danger-strong);
  background: var(--color-danger-subtle);
  border: 1px solid var(--color-danger-subtle-border);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  font-size: var(--font-size-sm);
  line-height: var(--line-height-normal);
  margin: 0 0 var(--space-4);
  text-align: left;
}
.pin-pad-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--touch-target-gap-min);
  margin-bottom: var(--space-5);
}
.pin-pad-digit {
  min-height: var(--touch-target-primary);
  border-radius: var(--radius-lg);
  border: 1px solid var(--color-border-strong);
  background: var(--color-surface-sunken);
  color: var(--color-text-primary);
  font-size: var(--font-size-xl);
  font-weight: var(--font-weight-semibold);
  cursor: pointer;
}
.pin-pad-digit:hover:not(:disabled),
.pin-pad-digit:active:not(:disabled) {
  border-color: var(--color-accent-subtle-border);
  background: var(--color-accent-subtle);
  /* Owner finding 2026-08-23: the light accent-subtle fill kept the
     digit's normal text color, which on Charcoal is light-on-light and
     unreadable mid-tap. Pair with accent-strong, the trio every theme
     already contrast-verifies (see .tile-kind-badge). */
  color: var(--color-accent-strong);
}
.pin-pad-digit:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.pin-pad-clear,
.pin-pad-backspace {
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-medium);
  color: var(--color-text-secondary);
}
.pin-pad-actions {
  display: flex;
  gap: var(--space-3);
}
.pin-pad-actions .btn-secondary,
.pin-pad-actions .btn-primary {
  flex: 1;
}

/* ---------- My Profile ---------- */
.profile-section {
  margin: 0 0 var(--space-6);
}

/* ---------- Shared centered confirm dialog ----------
   Distinct from the bottom-sheet .scrim/.sheet family above: a small,
   centered dialog for a single short message plus 1-2 actions (the
   one-time temporary-PIN reveal, index.html's #pin-reveal-scrim; any
   future caller with the same "one message, no form" shape). Built on
   the same tokens as .admin-confirm-sheet/.admin-confirm-body/
   .admin-confirm-actions one screen over, just centered instead of
   docked to the bottom edge. */
.confirm-scrim {
  position: fixed;
  inset: 0;
  background: var(--color-scrim);
  z-index: var(--z-scrim);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-6);
}
.confirm-dialog {
  width: 100%;
  max-width: 420px;
  background: var(--color-surface);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-3);
  padding: var(--space-6);
}
.confirm-title {
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-bold);
  margin: 0 0 var(--space-3);
}
.confirm-message {
  margin: 0 0 var(--space-4);
  color: var(--color-text-secondary);
  font-size: var(--font-size-base);
  line-height: var(--line-height-normal);
}
.confirm-actions {
  display: flex;
  gap: var(--space-3);
}
.confirm-actions .btn-primary,
.confirm-actions .btn-secondary {
  flex: 1;
}
.pin-reveal-value {
  font-size: var(--font-size-2xl);
  font-weight: var(--font-weight-bold);
  letter-spacing: var(--space-2);
  text-align: center;
  color: var(--color-text-primary);
  background: var(--color-surface-sunken);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  margin: 0 0 var(--space-5);
}

/* ---------- Open Tabs (ALPHA-07 landing screen) ----------
   .topbar and its children are shared with the ring screen below (see
   "App shell / top bar"); this section only adds what is unique to Open
   Tabs. Modeled on design/mockups/tabs.html: single flowing column,
   max-width container, no fixed-height app shell, so it uses ordinary
   page scroll at every supported width (1280x800, 1024x768, and phone)
   with no bespoke breakpoint of its own. */

#open-tabs-screen {
  min-height: 100vh;
  background: var(--color-bg);
}

/* Live-verification finding, ALPHA-08, at 1024x768, ORIGINALLY: Open
   Tabs' own then-per-screen topbar-right grew from one theme control at
   ALPHA-07 to six items (theme label, selector, employee badge, Closeout
   Summary, Manager Settings, Log out) as ALPHA-08 and ALPHA-10 each added
   a button here without re-checking this screen's own fit at the
   narrower supported tablet width, leaving topbar-left (flex-shrink: 1,
   no floor) crushed to near zero width so the brand name and location
   visually overlapped the theme selector instead of being pushed aside
   or wrapped.

   UX17/UX18 (docs/build-briefs/UX17-persistent-navigation.md,
   UX18-manager-surfaces-to-admin.md) collapsed nine per-screen topbars
   into the ONE shared #app-topbar and moved Closeout Summary/Manager
   Exceptions off it entirely, so topbar-right now carries at most two
   direct children (the one screen-specific secondary-action button,
   #topbar-secondary-action, plus the account-menu trigger) on EVERY
   screen, never six. That real narrowing is what makes generalizing this
   guard from "Open Tabs only" to every screen the shared bar now serves
   safe rather than reckless: the ORIGINAL reason ring was excluded (its
   own fixed-height ticket rail had no spare row to give up if the topbar
   wrapped, see .app's own comment above) no longer competes with a row
   this bar is now unlikely to ever need. Kept as a width-keyed
   breakpoint rather than an unconditional wrap for the same
   content-driven reasoning as before: flex-wrap's own one-line-fits test
   measures the FULL, un-truncated content, so an unconditional wrap
   would still wrap at 1280x800 where nowrap-plus-shrink already fits.
   1150px sits comfortably between the two supported tablet widths. */
@media (max-width: 1150px) {
  #app-topbar,
  #app-topbar .topbar-right {
    flex-wrap: wrap;
    row-gap: var(--space-2);
  }
}

.tabs-page {
  max-width: 880px;
  margin: 0 auto;
  padding: var(--space-6) var(--space-5) var(--space-20);
}

.tabs-page-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-5);
}
.tabs-page-title {
  font-size: var(--font-size-xl);
  font-weight: var(--font-weight-bold);
  margin: 0;
}

.new-ticket-row {
  display: flex;
  gap: var(--space-3);
  margin-bottom: var(--space-6);
}
.new-ticket-name-input {
  flex: 1;
}
.btn-new-ticket {
  height: var(--touch-target-primary);
  padding: 0 var(--space-8);
  border-radius: var(--radius-md);
  border: none;
  background: var(--color-accent-strong);
  color: var(--color-text-on-accent);
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-bold);
  box-shadow: var(--shadow-2);
  flex-shrink: 0;
  cursor: pointer;
}

.tab-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.tabs-empty {
  padding: var(--space-8) var(--space-5);
  text-align: center;
  color: var(--color-text-secondary);
}
.tabs-loading {
  padding: var(--space-8) var(--space-5);
  text-align: center;
  color: var(--color-text-tertiary);
}

.tab-card {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  width: 100%;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-1);
  padding: var(--space-5);
  min-height: var(--touch-target-primary);
  text-align: left;
  cursor: pointer;
}
.tab-card:disabled {
  cursor: not-allowed;
  opacity: 0.6;
}

.tab-main {
  flex: 1;
  min-width: 0;
}
.tab-name-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.tab-name {
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-primary);
}
.badge-aging {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  color: var(--color-warning-strong);
  background: var(--color-warning-subtle);
  border: 1px solid var(--color-warning-subtle-border);
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
}
/* Sixteenth amendment item 2: a Paid badge on a fully-paid open tab
   (balance brought to zero by a payment, tab still open). Mirrors
   .badge-aging exactly, swapped to the existing green success tokens
   rather than a new color, per the tokens-only theming rule. */
.badge-paid {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  color: var(--color-success-strong);
  background: var(--color-success-subtle);
  border: 1px solid var(--color-success-subtle-border);
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
}
.tab-meta {
  margin-top: var(--space-1);
  font-size: var(--font-size-sm);
  color: var(--color-text-tertiary);
}

.tab-figures {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  flex-shrink: 0;
}
.figure {
  text-align: right;
}
.figure-value {
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-bold);
  color: var(--color-text-primary);
}
.figure-label {
  font-size: var(--font-size-xs);
  color: var(--color-text-tertiary);
}

.chevron {
  flex-shrink: 0;
  width: var(--touch-target-min);
  height: var(--touch-target-min);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-text-tertiary);
  font-size: var(--font-size-lg);
}

.recently-closed-section {
  margin-top: var(--space-8);
}
.recently-closed-title {
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-bold);
  margin: 0 0 var(--space-1);
}
.recently-closed-note {
  font-size: var(--font-size-sm);
  color: var(--color-text-tertiary);
  margin: 0 0 var(--space-4);
}
.btn-reopen {
  height: var(--touch-target-comfortable);
  padding: 0 var(--space-5);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border-strong);
  background: var(--color-surface);
  color: var(--color-text-primary);
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  flex-shrink: 0;
  cursor: pointer;
}

@media (max-width: 480px) {
  .tab-card {
    flex-wrap: wrap;
  }
  .tab-figures {
    width: 100%;
    justify-content: space-between;
  }
  .new-ticket-row {
    flex-direction: column;
  }
  .btn-new-ticket {
    width: 100%;
  }
}

/* ---------- App shell / top bar ---------- */

/* UX17 (docs/build-briefs/UX17-persistent-navigation.md): the persistent
   topbar (#app-topbar) moved OUTSIDE every screen `<section>`, including
   #ring-screen's own `.app` wrapper it used to sit inside as `.app`'s
   first flex child. `.app` used to be `height: 100vh` for exactly that
   reason (see the removed comment this replaces): with the topbar gone
   from inside it, height:100vh here would double-count the topbar's own
   height against the viewport. #app-shell (new, wraps the topbar and
   every screen section as siblings, directly under <body>) is now the
   ONE viewport-height flex column; `.app` fills whatever height
   #ring-screen's own flex-item allocation leaves it (height: 100%),
   preserving exactly the same "a definite, bounded height for .body's
   flex:1 to fill" property the old height:100vh gave it, just measured
   one level up. */
#app-shell {
  display: flex;
  flex-direction: column;
  height: 100%;
  /* Matches html/body's own height:100% above; min-height keeps the app
     usable on a viewport shorter than 720px without ever collapsing
     below it (moved here from .app's own former rule, unchanged). */
  min-height: 720px;
  overflow-y: auto;
}
/* Every screen section is a plain, naturally-sized flex item by default
   (flex-shrink: 0 so none of them is ever squeezed below its own content
   -- #app-shell's own overflow-y: auto is what turns a taller-than-
   viewport screen into a scrollable region, exactly matching the plain
   page-scroll behavior every screen already had before #app-shell
   existed). #ring-screen overrides this below: it is the one screen with
   its own bounded-height, internally-scrolling layout (.app/.body). */
#app-shell > section {
  flex: 0 0 auto;
}
#ring-screen:not([hidden]) {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}

.app {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
}

.topbar {
  /* UX15-F1: the header is the account dropdown's containing block, so
     the menu can sit outside .topbar-right's overflow lane below and
     still hang from the header's bottom edge. */
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-5);
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
  flex-shrink: 0;
  /* Design gate finding, ALPHA-06: at 1024x768 this used to wrap to two
     rows, eating ~40-56px the ticket rail needed. The real trigger is
     unbounded content (a long tenant id or employee id can outgrow the
     row on its own), not the viewport width, so the fix is nowrap plus
     truncating the two open-ended text fields (.location, .employee-name
     below) rather than a width-keyed breakpoint hack. Restored to wrap at
     phone/tablet-portrait width in the existing max-width:900px block
     below, where .body already stacks into a single column and a second
     topbar row is not competing with the ticket rail for height. */
  flex-wrap: nowrap;
}
.topbar-left {
  display: flex;
  align-items: baseline;
  gap: var(--space-4);
  min-width: 0;
  flex-shrink: 1;
  /* UX15: a defensive floor, not the load-bearing fix (that is
     .topbar-right's own shrink/scroll change above). If this side is
     ever squeezed below its own children's combined size in some future
     layout this file has not anticipated, clip the excess here rather
     than let it render outside this box and under .topbar-right's own
     controls, matching the reasoning `.topbar-right`'s own comment
     states in full. */
  overflow: hidden;
}
.brand {
  font-weight: var(--font-weight-bold);
  font-size: var(--font-size-lg);
  letter-spacing: -0.01em;
  flex-shrink: 0;
}
.location {
  color: var(--color-text-tertiary);
  font-size: var(--font-size-sm);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.topbar-right {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: nowrap;
  /* UX15 (docs/build-briefs/UX15-floor-topbar-overflow.md): this used to
     be `flex-shrink: 0`, which refuses to give up any of its own width no
     matter how many buttons it holds. With .topbar-left's own
     `flex-shrink: 1; min-width: 0` (kept, still needed so .location's
     ellipsis can absorb a long tenant id per the ALPHA-06 comment above),
     a stubborn flex-shrink:0 here forced the ENTIRE deficit onto
     .topbar-left, past the point its own content (the home icon, the
     brand name) could still fit, and that content then rendered outside
     .topbar-left's own shrunk box and under .topbar-right's buttons,
     which is exactly the click-interception defect this item fixes.
     Allowing this side to shrink too lets the flex algorithm split the
     deficit by each side's own size (mostly landing here, since this side
     is normally the larger of the two), and overflow-x below turns
     whatever still does not fit into a horizontal pan inside this row's
     own lane instead of spilling onto .topbar-left. This is what makes
     the row incapable of overlapping regardless of how many buttons a
     later change adds here: the two sides can only ever sum to the
     container's own width, never more. */
  flex-shrink: 1;
  min-width: 0;
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
}
/* UX15: every direct child (each button, and the account-menu trigger)
   keeps its own natural, single-line size no matter how small this row's
   own box gets squeezed to above. Without this, a plain flex child's
   default `flex-shrink: 1` lets its TEXT wrap to two or three lines
   before the row above finishes giving up its own width, and the wrapped
   lines then get clipped by this row's `overflow-y: hidden` (observed
   directly while building this fix: "Record Outside Order" wrapped to
   three lines and its bottom line was cut off). Reserving each child's
   own size and letting the row's `overflow-x: auto` above carry whatever
   does not fit keeps every label on one line and every button fully
   readable, at the cost of a horizontal pan for the buttons furthest
   right when many are visible at once (rare after this item's own
   consolidation of the three purchasing entry points into one). */
.topbar-right > * {
  flex-shrink: 0;
}
/* Same visible-scrollbar treatment .ticket-lines already uses (that
   rule's own comment: overflow:auto alone renders no reliably visible
   scrollbar on every platform), oriented horizontally for this row. */
.topbar-right::-webkit-scrollbar {
  height: 6px;
}
.topbar-right::-webkit-scrollbar-track {
  background: transparent;
}
.topbar-right::-webkit-scrollbar-thumb {
  background: var(--color-border-strong);
  border-radius: var(--radius-full);
}
.theme-switcher-label {
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
}
.theme-switcher {
  height: var(--touch-target-min);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border-strong);
  background: var(--color-surface);
  color: var(--color-text-primary);
  padding: 0 var(--space-3);
}
.employee {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
  min-width: 0;
}
.employee-name {
  max-width: 160px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.employee-badge {
  min-width: 32px;
  height: 32px;
  padding: 0 var(--space-1);
  border-radius: var(--radius-full);
  background: var(--color-accent-subtle);
  color: var(--color-accent-strong);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: var(--font-weight-semibold);
  font-size: var(--font-size-xs);
}

/* ---------- Sixteenth amendment item 1: topbar redesign ----------
   A Home icon button sits immediately right of the company name
   (.topbar-left), and the logged-in user's own name chip is always
   pinned to the top-right corner (.topbar-right), tapping it opens a
   touch-friendly dropdown containing, in order, Theme, Settings
   (rights-holders only), and Log out. Replaces the old standalone Theme
   <select> and Settings/Log out buttons that used to sit loose in the
   bar. One consistent shape, duplicated per screen in index.html (this
   codebase has no shared-component layer), so one set of classes here
   styles all five instances identically across every screen and theme. */
.btn-home-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--touch-target-min);
  height: var(--touch-target-min);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border-strong);
  background: var(--color-surface);
  color: var(--color-text-primary);
  flex-shrink: 0;
  align-self: center;
}
.btn-home-icon:active {
  background: var(--color-surface-sunken);
}
.account-menu {
  position: relative;
  flex-shrink: 0;
}
/* The tappable name chip itself: same visual weight as the old .employee
   span it replaces (reuses .employee/.employee-badge/.employee-name
   inside it unchanged), just now a real <button> that opens the
   dropdown, with a caret marking it as interactive. */
.account-trigger {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  height: var(--touch-target-min);
  padding: 0 var(--space-3);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border-strong);
  background: var(--color-surface);
  color: var(--color-text-primary);
}
.account-trigger[aria-expanded="true"] {
  border-color: var(--color-accent-strong);
}
.account-trigger-caret {
  font-size: var(--font-size-xs);
  color: var(--color-text-tertiary);
}
/* Absolutely positioned under the trigger rather than a full scrim
   sheet: this is a small menu, not a form, and the touch-only rule only
   requires it be dismissible by tapping outside (main.ts's outside-tap
   and Escape handling), never that it block the rest of the screen. */
.account-dropdown {
  position: absolute;
  top: calc(100% + var(--space-2));
  /* UX15-F1: positioned against .topbar (position: relative above), not
     .account-menu, because .topbar-right's overflow-y: hidden (UX15)
     clipped a menu positioned inside it to a one-row-high box. Aligned
     with the header's own right padding. */
  right: var(--space-5);
  z-index: var(--z-ticket-rail);
  min-width: 220px;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-2);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border-strong);
  background: var(--color-surface);
  box-shadow: var(--shadow-3);
}
.account-dropdown-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-2);
  min-height: var(--touch-target-min);
}
.account-dropdown-label {
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
}
.account-dropdown-theme .theme-switcher {
  flex: 1;
  min-width: 0;
}
.account-dropdown-button {
  display: flex;
  align-items: center;
  width: 100%;
  min-height: var(--touch-target-min);
  padding: var(--space-2) var(--space-2);
  border-radius: var(--radius-sm);
  border: none;
  background: transparent;
  color: var(--color-text-primary);
  font-size: var(--font-size-sm);
  text-align: left;
}
.account-dropdown-button:active {
  background: var(--color-surface-sunken);
}

/* ---------- Body: menu + ticket rail ---------- */

.body {
  display: flex;
  flex: 1;
  min-height: 0;
}

.menu-pane {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
}

/* ALPHA-10 item 1, corrected during the design/QA gate re-review
   (2026-08-20): a fixed 27-entry strip (A-Z plus "#") does not fit one
   row at either supported tablet width (a 44px target per letter needs
   ~1341px; only 880px is available at 1280x800 and 624px at 1024x768,
   both measured live against #alphabet-strip.clientWidth), and the
   original fix left the overflow as a horizontal scroll with zero
   affordance, so roughly half the alphabet was reachable only by a
   bartender who happened to guess to swipe sideways on what reads as a
   static row of buttons.

   Fixed by wrapping to two rows (flex-wrap) instead of scrolling one.
   44px stays the real touch-target floor (tokens.css: "nothing tappable
   is smaller than 44px") and is never shrunk to force a fit. What gives
   instead is this strip's own horizontal padding and inter-letter gap,
   both dropped to 0: at 1024x768, 14 letters at 44px with any gap or
   padding at all no longer fits inside 624px (14 * 44px = 616px alone,
   leaving 8px total to split across 13 gaps and 2 sides), so both had to
   go to zero to reach exactly two rows rather than three. Adjacent
   letters keep a visible 1px seam from each button's own border (below)
   colliding with its neighbor's, which reads as a grid divider rather
   than a gap; row-to-row spacing is a separate, small var(--space-1) row
   gap, kept deliberately minimal so the extra row costs as little
   vertical space as possible against the menu grid's own dominant share
   of the screen. Verified live (Chromium) at both supported widths: two
   full rows, no horizontal scrollbar, every letter including "Z" and "#"
   fully visible with no scrolling. */
.alphabet-strip {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) 0;
  /* 2px sides, not the space-5 every other row in this pane uses: the
     narrowest supported width has essentially zero slack left once 14
     letters at the 44px floor are laid out with no inter-letter gap (see
     this rule's own header comment), so this strip alone gives up its
     edge inset rather than the letters giving up their touch-target
     size. */
  padding: var(--space-3) 2px var(--space-2);
  flex-shrink: 0;
  background: var(--color-bg);
}
.alphabet-letter {
  flex-shrink: 0;
  width: var(--touch-target-min);
  height: var(--touch-target-min);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-md);
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-secondary);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  cursor: pointer;
}
/* A letter with no drinks stays in the strip, muted and unclickable
   (:disabled), rather than the strip reflowing around only letters
   currently in use; see main.ts's renderAlphabetStrip. */
.alphabet-letter:disabled,
.alphabet-letter.inactive {
  opacity: 0.35;
  cursor: not-allowed;
  color: var(--color-text-tertiary);
}

.menu-scroll {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-2) var(--space-5) var(--space-8);
}

.menu-empty {
  color: var(--color-text-secondary);
  padding: var(--space-6) 0;
}

.menu-section {
  margin-bottom: var(--space-8);
  scroll-margin-top: var(--space-4);
}
.menu-section-title {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-tertiary);
  margin: 0 0 var(--space-3);
}

.tile-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: var(--space-3);
}

.tile {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-1);
  padding: var(--space-4);
  min-height: 96px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--space-3);
  text-align: left;
  cursor: pointer;
  color: inherit;
  width: 100%;
}
.tile:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}
.tile-name {
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight);
}
.tile-price {
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
}
.tile-price.variable {
  /* Amendment 2026-08-22 (item 2): --color-accent-strong is now the
     DEEP fill/pill-text color (paired with a light --color-accent-subtle
     background or with --color-text-on-accent on a solid fill; see
     design/README.md "Distinctness from the fixed semantic hues,
     Charcoal's inverted pairing"). This is the one spot that colors text
     directly on the plain tile surface with no subtle-background pill
     underneath it, so it reads the lighter --color-accent (base) instead,
     the variant every theme keeps legible as bare text on its own
     surface. */
  color: var(--color-accent);
  font-weight: var(--font-weight-medium);
}

/* Inventory track (track/inventory) Slice 3, Amendment 2026-08-22
   (twelfth): "type-appropriate tiles" for a pour/draft-beer/packaged menu
   tile, alongside a cocktail tile's unchanged plain look. Reuses
   .line-badge-substitution's exact accent-subtle/accent-strong/accent-
   subtle-border trio (design/tokens.css, verified-contrast in every
   theme) rather than inventing a second accent pairing: this is the same
   "an intended, ordinary distinction, not a problem" case that badge's
   own comment already argues for. */
.tile-kind-badge {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  line-height: var(--line-height-tight);
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
  color: var(--color-accent-strong);
  background: var(--color-accent-subtle);
  border: 1px solid var(--color-accent-subtle-border);
}

/* ---------- Resizable ticket rail (Amendment 2026-08-22, sixth, item 1) ----------
   The drag handle sits as a DOM sibling BETWEEN .menu-pane and
   .ticket-rail (index.html), not inside either, so it can own the visual
   boundary line between them (moved here from .ticket-rail's own former
   border-left, now removed below to avoid a doubled seam) while its own
   width stays fixed regardless of how wide the rail itself is dragged. */
.rail-resize-handle {
  flex-shrink: 0;
  /* Comfortable touch target, not the 1px seam a plain border would give:
     the item's own words are "a comfortable touch target (a visible
     handle, not a 1-pixel edge)." 24px is this product's own documented
     hit-area floor for a narrow drag affordance (see the grip bar below
     for the actually-visible part); still short of touch-target-min
     (44px) because a full 44px-wide vertical strip would eat meaningfully
     into the menu grid's own tight width budget at 1024x768 for a control
     that is dragged, not tapped, and drag targets tolerate a narrower hit
     area than a tap target does (there is no adjacent-target misfire risk
     the way a 44px tap-target floor exists to prevent). */
  width: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: col-resize;
  /* Stops the browser's own touch-scroll/pan gesture from competing with
     a touch drag that starts on this element - without this, a touch drag
     on a touch device scrolls the page instead of resizing the rail. */
  touch-action: none;
  border-left: 1px solid var(--color-border);
  background: var(--color-surface);
}
/* The actual visible grip: a short rounded bar centered in the handle's
   own full-column-height hit area, distinguishing "there is something to
   drag here" from a plain divider line. Runs the full column height via
   the handle's own flex-stretch (a sibling flex item of .menu-pane and
   .ticket-rail inside .body, which does not override the default
   align-items: stretch), never just a corner or edge. */
.rail-resize-handle::before {
  content: "";
  width: 4px;
  height: 48px;
  border-radius: var(--radius-full);
  background: var(--color-border-strong);
}
.rail-resize-handle:hover::before,
.rail-resize-handle.dragging::before {
  background: var(--color-accent);
}
/* Below the tablet-landscape primary target, .body stacks the rail under
   the menu instead of beside it (see the @media (max-width: 900px) rule
   further down); a horizontal drag handle has no meaning once the two
   panes are no longer side by side, so it is hidden rather than left
   present and inert. */
@media (max-width: 900px) {
  .rail-resize-handle {
    display: none;
  }
}

/* ---------- Ticket rail ---------- */

.ticket-rail {
  /* Default width; src/railResize.ts overwrites this with an inline
     style on load (the device's stored width, or this same 400px default
     when nothing is stored yet - see RAIL_WIDTH_DEFAULT_PX). Kept in sync
     with that constant deliberately, as the pre-JS-paint fallback. */
  width: 400px;
  flex-shrink: 0;
  background: var(--color-surface);
  display: flex;
  flex-direction: column;
  min-height: 0;
  /* SIG3 (docs/backlog.md; docs/build-briefs/SIG3-ticket-rail-overflow.md).
     Every child of this column except #ticket-lines is flex-shrink: 0
     (the header, the two conditional banners, the guest row,
     #ticket-totals, #ticket-actions), and #ticket-lines itself already
     has its own tuned min-height floor (see that rule's own comment,
     which records two earlier rounds of this exact defect class: ALPHA-12's
     guest row and the Discounts slice's #discount-row, both fixed by
     reclaiming space from #ticket-lines). #ticket-actions is different in
     shape from either of those: it is a variable, open-ended stack
     (Payments and Payment link are permission-gated; the no-tip button is
     state-gated; a future action could add another row), not one new
     roughly-fixed-height row, so there is no fixed amount of space left to
     reclaim from #ticket-lines without repeating the brittle hand-tuned
     pixel arithmetic that rule's own history already argues against.

     Before this rule the rail had no overflow property (the CSS default,
     visible), so once every child's combined height exceeded the rail's
     own fixed outer height (stretched to .body's own height by the
     layout above it, unaffected by this rule), the excess rendered PAST
     the rail's own bottom edge. #debug-panel sits in normal flow
     immediately after .body, exactly where the rail's box ends, so the
     overflow painted directly under it and, being earlier in the DOM,
     underneath its opaque background - confirmed live at 1024x768 with a
     Comp applied (a six-action #ticket-actions: Prepare All, Close
     Ticket, Pay, No tip close ticket, Payments, Payment link):
     #no-tip-button sat exactly where #debug-panel now painted, and
     elementFromPoint at the button's own center resolved to #debug-panel,
     not the button, which is what made the tap genuinely refused rather
     than merely off-screen. Reproduced at 1280x800 too, not only
     1024x768: this six-action case does not fit inside either supported
     tablet height, unlike the three-discount-line case #ticket-lines'
     own budget was tuned against.

     `overflow-y: auto` makes the rail scroll ITS OWN overflow instead of
     spilling past its box, with no change to the rail's own outer height
     and no new pixel constant to keep in sync with whatever
     #ticket-actions grows to next. On every ticket state this suite's
     other geometry tests already exercise (e.g. e2e/discounts.spec.ts's
     three-discount-line case), content already fit inside the rail's box
     before this change and still does: this rule never engages there,
     scrollTop stays 0, and the rendered layout is unchanged. It only
     takes effect once content genuinely cannot fit.

     Scoped to the rail as a whole rather than to #ticket-actions alone,
     deliberately: a #ticket-actions-only bounded scroll region would need
     its own max-height computed from every OTHER sibling's real height
     (the header, both conditional banners, the guest row, #ticket-lines'
     own floor, #ticket-totals) to know how much room is left over, which
     is the same brittle arithmetic already rejected above. Scrolling the
     column as one unit needs none of that.

     TOUCH-ONLY (CLAUDE.md): native overflow scroll, no hover and no
     keyboard involved. A touch pan starting inside #ticket-lines
     continues to scroll that inner region first (its own nested
     overflow-y: auto, unchanged by this rule); only once it reaches its
     own limit does further panning move this outer rail, ordinary nested-
     scroll browser behavior that needs no code here.

     AFFORDANCE: the same permanent (not overlay-on-hover) thin
     track-and-thumb #ticket-lines already carries below is reused here
     rather than inventing a new pattern, so a bartender sees a scrollbar
     - a shorter thumb signaling more content below - the instant the
     rail's real content does not fit, exactly the cue #ticket-lines has
     always given for a long drink list. */
  overflow-y: auto;
  scrollbar-gutter: stable;
}
.ticket-rail::-webkit-scrollbar {
  width: 8px;
}
.ticket-rail::-webkit-scrollbar-track {
  background: transparent;
}
.ticket-rail::-webkit-scrollbar-thumb {
  background: var(--color-border-strong);
  border-radius: var(--radius-full);
}
/* UX13 (design/ux13-ticket-rail-rationale.md, DEC-053's "DENSITY: compact
   chrome" scale, design/tokens.css). The header used to be a title row,
   then a "N drinks" subtitle row, then a separate guest-actions row:
   three rows of vertical budget for information that fits on one. This
   is now one --chrome-row-height row, costing nothing beyond what a
   header chip already needs at that height. */
.ticket-header {
  padding: 0 var(--space-4);
  border-bottom: 1px solid var(--color-border);
  flex-shrink: 0;
}
.ticket-header-row {
  height: var(--chrome-row-height);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--chrome-gap-tight);
}
/* The ticket's own name and status, sharing one truncating line
   (mirroring design/mockups/ticket-rail-redesign-blocked.html's own
   .ticket-id). #ticket-sub carries the name (or "Ticket" for an unnamed
   walk-in); #order-status/.ticket-age carries status, unchanged text
   logic either way. */
.ticket-id {
  min-width: 0;
  flex-shrink: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--font-size-chrome);
  font-weight: var(--font-weight-bold);
  color: var(--color-text-primary);
}
.ticket-age {
  font-weight: var(--font-weight-regular);
  color: var(--color-text-tertiary);
}
/* Payments-track Slice 4 (DEC-002). Mirrors .badge-aging's own
   inline-pill shape (border, subtle background, small bold text), the
   warning palette rather than accent: a hold is money set aside on a
   guest's card, worth calling out visually the same way an aging tab
   already is, not a neutral status line. */
.ticket-hold-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-top: var(--space-1);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  color: var(--color-warning-strong);
  background: var(--color-warning-subtle);
  border: 1px solid var(--color-warning-subtle-border);
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
  width: fit-content;
}

.no-order-panel {
  padding: var(--space-6) var(--space-5);
  text-align: center;
  color: var(--color-text-secondary);
}
.no-order-panel p {
  margin: 0 0 var(--space-4);
}

/* UX13 (design/ux13-ticket-rail-rationale.md). The old min-height calc
   here (git history) modeled a "two lines plus a peek" FLOOR, tuned
   against the six-action chrome stack this item removes and against a
   second, tighter tablet height DEC-051/VP1-F1 has since retired (the
   floor is designed and tested at FLOOR_DEVICE.viewport alone). That
   floor is gone outright, not raised: with every sibling chrome element
   compressed to its own real minimum (the header row, the totals block,
   the action row), flex: 1 alone gives this region whatever is left,
   which is what harness/e2e/ux13-ticket-rail.spec.ts measures directly
   against the real rendered rail rather than a hand-tuned pixel formula. */
.ticket-lines {
  flex: 1;
  overflow-y: auto;
  padding: 0 var(--space-5);
  scrollbar-gutter: stable;
}
/* Visible scroll affordance: overflow-y:auto alone renders no reliably
   visible scrollbar on every platform (macOS Chromium's default is a
   thin overlay that only appears while actively scrolling, easy to miss
   under bar lighting). This keeps a thin permanent track+thumb instead,
   Chromium/WebKit only per the browser support this product targets
   (browser-only, touch tablet; no Firefox requirement in
   docs/solution-architecture.md). */
.ticket-lines::-webkit-scrollbar {
  width: 8px;
}
.ticket-lines::-webkit-scrollbar-track {
  background: transparent;
}
.ticket-lines::-webkit-scrollbar-thumb {
  background: var(--color-border-strong);
  border-radius: var(--radius-full);
}

.line {
  /* Compact two-row line, design gate fix (2026-08-20): was a single
     flex row with name/liquor/price stacked three-deep in .line-info
     beside the Prepared pill, which let a real long liquor name (up to
     ~57 characters, fixtures/inventory-snapshot.json) wrap onto two or
     three lines on its own and push measured line height to 121-166px,
     leaving barely more than one line visible in the rail at either
     1280x800 or 1024x768. Name and price now share a row (.line-top)
     and the liquor detail is a single truncated row below it, so a real
     line measures well under 96px; see .ticket-lines's min-height
     comment for the retuned figure this was measured against live. */
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--color-border);
}
.line:last-child {
  border-bottom: none;
}
.line-info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.line-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  min-width: 0;
}
.line-name {
  /* UI-batch gate fix (drink-name touch recovery): now a flex wrapper,
     the same shape .line-detail below already has, holding one
     truncating child (.line-name-text) instead of being the truncating
     element itself. main.ts's attachTruncatedTextTapToggle needs a
     wrapper to toggle the shared .line-detail-expanded class on,
     independently of the text span it measures for overflow - the same
     reason .line-detail split from its own text during the ALPHA-10
     re-review. Still .line-top's flex item; sizing is unchanged, only
     moved onto this rule from the old single-element version. */
  flex-shrink: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 2px;
}
/* Expanded state, same shared class and reasoning as
   .line-detail.line-detail-expanded below: align-items relaxes from
   center to flex-start once the name wraps onto multiple lines, so a
   long drink name never gets vertically centered against .line-top's
   own price/paid-badge siblings while wrapped. */
.line-name.line-detail-expanded {
  align-items: flex-start;
}
.line-name-text {
  min-width: 0;
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  /* Touch-only audit fix, same reasoning as .line-detail-text below:
     tapping this text is the real, touch-first recovery path for a
     truncated drink name now (the title attribute is a non-touch
     fallback only). cursor: pointer is cosmetic (this product is
     touch-only); :active is the real, always-on tap feedback. */
  cursor: pointer;
}
.line-name-text:active {
  opacity: 0.65;
}
.line-name.line-detail-expanded .line-name-text {
  overflow: visible;
  text-overflow: clip;
  white-space: normal;
  word-break: break-word;
}
.line-detail {
  /* Design gate fix, ALPHA-10 re-review (2026-08-20): this row now holds
     up to two children, a truncating text span (the liquor name) and, for
     a substituted line, a non-shrinking badge (see .line-badge below). It
     used to be a single block element whose own overflow/ellipsis rules
     applied to ALL of its text content, including an appended
     "· Substituted" suffix, which is exactly what let a long liquor name
     truncate the marker away; see main.ts's renderTicketLines for the
     full history. The overflow/ellipsis behavior itself moved onto
     .line-detail-text so it applies only to the liquor name now, never to
     the badge. */
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}
.line-detail-text {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  /* Touch-only audit fix (ALPHA-09 release evidence, category 2,
     main.ts:1790 and main.ts:1814): a truncated title-only tooltip is
     never touch-reachable, and live inspection found the title
     attribute empty regardless. main.ts's attachTruncatedTextTapToggle
     makes this row itself the recovery path: tapping it wraps to the
     full text (see .line-detail-expanded below), tapping again
     collapses it back. cursor: pointer is cosmetic (this product is
     touch-only), :active below is the real, always-on tap feedback. */
  cursor: pointer;
}
.line-detail-text:active {
  opacity: 0.65;
}
/* Expanded state, toggled on the row wrapper (.line-detail, shared by
   both the normal liquor-detail row and .line-warning) rather than on
   .line-detail-text itself, so align-items can relax from center to
   flex-start: once the text wraps onto multiple lines, centering would
   vertically split a non-shrinking sibling badge (.line-badge) or notch
   it off-center against the now-taller text block. Untouched
   (untruncated) lines never gain this class (attachTruncatedTextTapToggle
   is a no-op when textEl.scrollWidth <= textEl.clientWidth), so the
   compact two-row layout and Review 5's line-height fixes are unchanged
   for every line that was never truncated in the first place. */
.line-detail.line-detail-expanded {
  align-items: flex-start;
}
.line-detail.line-detail-expanded .line-detail-text {
  overflow: visible;
  text-overflow: clip;
  white-space: normal;
  word-break: break-word;
}
/* UX13 (design/ux13-ticket-rail-rationale.md "Problem 2"): the tap-to-
   expand path above (attachTruncatedTextTapToggle) had no VISIBLE sign it
   exists (cursor: pointer alone, invisible with no hover on a
   touchscreen). This chevron (main.ts's appendTruncationChevron) is
   always in the DOM but hidden by default, shown only once
   refreshLineTruncationChevrons marks its own row genuinely truncated
   (scrollWidth > clientWidth), and rotated while the row is expanded, the
   same shape design/mockups/ticket-rail-redesign-blocked.html's own
   .trunc-chevron establishes. Purely visual: the tap target stays the
   text span itself, unchanged. */
.trunc-chevron {
  display: none;
  flex-shrink: 0;
  font-size: 12px;
  font-weight: var(--font-weight-bold);
  line-height: 1;
  color: var(--color-accent-strong);
  transform: translateY(1px);
  transition: transform var(--motion-fast) var(--motion-easing-standard);
}
.line-name.is-truncated .trunc-chevron,
.line-detail.is-truncated .trunc-chevron {
  display: inline-block;
}
.line-name.line-detail-expanded .trunc-chevron,
.line-detail.line-detail-expanded .trunc-chevron {
  transform: rotate(180deg) translateY(-1px);
}
.line-price {
  flex-shrink: 0;
  font-size: var(--font-size-sm);
  color: var(--color-text-tertiary);
  white-space: nowrap;
}
/* ALPHA-07: a resumed line whose liquor selection could not be
   reconstructed (see main.ts's TicketLineState.priceable comment). Rare;
   warning-colored so it reads as "needs attention", not as a normal
   price. */
.line-price.unavailable {
  color: var(--color-warning-strong);
  font-weight: var(--font-weight-semibold);
}
/* Design gate fix (2026-08-20): the persistent "needs a manager" note
   that replaces .line-detail on an unpriceable line (renderTicketLines).
   Shares .line-detail's layout (one truncated row) but reads as a warning
   rather than incidental detail text, so the state is visible on the line
   itself for as long as the line is on screen, not only in a toast.
   Targets .line-detail-text (the child span), not .line-detail itself,
   now that .line-detail is a flex wrapper rather than the text element. */
.line-warning .line-detail-text {
  color: var(--color-warning-strong);
  font-weight: var(--font-weight-semibold);
}
/* ALPHA-10 item 2, corrected during the design gate re-review
   (2026-08-20): "Substituted lines show it plainly on the ticket rail"
   (work item instruction), now a standalone badge rather than text
   appended to the truncating liquor-name row (main.ts's renderTicketLines
   carries the full history of why). flex-shrink: 0 keeps the badge's
   full width guaranteed regardless of how much the liquor name beside it
   has to truncate; .line-detail-text is the only thing that ever gives up
   width. Distinguished by shape (a bordered, filled pill) and its own
   literal text ("Substituted"), not by color alone, so it reads
   correctly in every theme including colorblind and high-contrast, where
   .line-badge-substitution's accent-subtle/accent-strong/accent-subtle-
   border trio (design/tokens.css) is still a verified-contrast pair (see
   each theme file's own contrast-ratio comment) even though the hue
   itself differs theme to theme. Accent-colored rather than
   warning-colored: a substitution is an intended bartender action, not a
   problem needing attention the way an unpriceable line is. */
.line-badge {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-bold);
  line-height: var(--line-height-tight);
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
}
.line-badge-substitution {
  color: var(--color-accent-strong);
  background: var(--color-accent-subtle);
  border: 1px solid var(--color-accent-subtle-border);
}
/* .line-badge-paid (the separate "Paid" pill this file used to define
   here, ALPHA-08) is gone as of Amendment 2026-08-22 (sixth), item 2: the
   signal it carried now lives on the line's own .state-btn instead (see
   .state-btn.paid below), one control per line rather than two stacked
   side by side. */
/* Amendment 2026-08-22 (item 3): the per-line guest badge (.line-badge-
   guest, info-toned) is gone. A guest's drinks are now grouped under
   their own .line-section-header (below) instead, so which guest a line
   belongs to is conveyed by the section it sits in rather than a badge
   competing with the drink name for the line's own limited width. */
.line-section-header {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-bold);
  color: var(--color-text-secondary);
  padding: var(--space-2) 0 var(--space-1);
  margin-top: var(--space-2);
  border-top: 1px solid var(--color-border);
}
.line-section-header:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}
/* Amendment 2026-08-22 (fifth), item 3: a section whose every line is
   already covered sinks to the bottom of the rail and is "visibly marked
   paid" (the item's own wording). Text-suffixed by main.ts's
   buildLineSectionHeader (", paid"), never color alone, matching this
   file's own touch-only/colorblind rule; the color here reuses the same
   fixed success pairing .line-badge-paid above already established for
   "this is a positive, paid state." Font-size/weight/padding are
   untouched from the plain .line-section-header above, so this adds no
   height of its own. */
.line-section-header-paid {
  color: var(--color-success-strong);
}

.state-btn {
  /* Design gate fix (2026-08-20): dropped from touch-target-comfortable
     (56px) to touch-target-min (44px, still >= the 44px hard floor) and
     from a fixed 148px to a content-sized min-width, to give the
     compacted .line-info more of the ticket rail's 400px width for real
     drink/liquor names instead of forcing them to wrap. */
  flex-shrink: 0;
  height: var(--touch-target-min);
  min-width: 112px;
  padding: 0 var(--space-3);
  border-radius: var(--radius-md);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  border: 1px solid transparent;
  cursor: pointer;
}
.state-btn .dot-icon {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.state-btn.not-prepared {
  background: var(--color-warning-subtle);
  color: var(--color-warning-strong);
  border-color: var(--color-warning-subtle-border);
}
.state-btn.not-prepared .dot-icon {
  border: 2px solid var(--color-warning-strong);
}
.state-btn.prepared {
  background: var(--color-success-subtle);
  color: var(--color-success-strong);
  border-color: var(--color-success-subtle-border);
  cursor: default;
}
.state-btn.prepared .dot-icon {
  background: var(--color-success-strong);
  /* text-on-dark, not text-on-accent: this background is the FIXED
     semantic success-strong color (design/tokens.css), always dark, not
     this theme's own accent-strong. text-on-accent is tuned per-theme to
     pair with THAT theme's accent-strong specifically (see
     design/README.md) and on Charcoal is a dark color, since Charcoal's
     own accent-strong is light; paired with the fixed dark success-strong
     it renders near-invisible dark-on-dark. text-on-dark is defined on
     every theme to be safely light against a dark, saturated background,
     which is what this fixed chip icon always is. Design/QA gate finding,
     2026-08-21: found while verifying the new Charcoal accent red does
     not read as an error state; this bug predates that change (same root
     cause affected the danger/success toast and the warning/info icons
     below, fixed alongside it). */
  color: var(--color-text-on-dark);
  font-size: 12px;
  font-weight: var(--font-weight-bold);
}
/* Amendment 2026-08-22 (sixth), item 2: the terminal Paid state, replacing
   the separate .line-badge-paid pill that used to render beside this
   button (see that class's own now-historical comment above). Deliberately
   a SOLID success fill rather than reusing .state-btn.prepared's subtle
   tint: the two are adjacent states a bartender scans past quickly, and a
   light-green-on-light-green pair (subtle Prepared next to a
   hypothetically subtle Paid) would read as one state twice rather than a
   clear progression. color-success-strong/text-on-dark is the same
   verified-contrast fixed pair .toast.toast-success and this same rule's
   own .dot-icon above already use for "a solid, always-dark, saturated
   background," so this stays legible in every theme including Charcoal
   without a new pairing to verify. */
.state-btn.paid {
  background: var(--color-success-strong);
  color: var(--color-text-on-dark);
  border-color: var(--color-success-strong);
  cursor: default;
}
.state-btn.paid .dot-icon {
  background: var(--color-text-on-dark);
  color: var(--color-success-strong);
  font-size: 12px;
  font-weight: var(--font-weight-bold);
}

.ticket-totals {
  padding: var(--chrome-gap-hairline) var(--space-4) 0;
  border-top: 1px solid var(--color-border);
  flex-shrink: 0;
}
.totals-row {
  display: flex;
  justify-content: space-between;
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
  padding: var(--space-1) 0;
}
/* UX13: Subtotal and Tax fold behind a tap on this row (design/mockups/
   ticket-rail-redesign-blocked.html's identical shape). A plain,
   non-focusable div with a click listener, the same convention
   .line-name/.line-detail's own tap-to-expand rows already use elsewhere
   in this file, not a <button>: this is the ticket rail's own money
   figure, always visible, only its OWN breakdown toggles. Total itself
   is never hidden. */
.totals-row-grand {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  cursor: pointer;
  font-size: var(--font-size-xl);
  line-height: var(--line-height-chrome);
  font-weight: var(--font-weight-bold);
  color: var(--color-text-primary);
}
.totals-row-grand:active {
  opacity: 0.8;
}
.totals-label-row {
  display: flex;
  align-items: center;
  gap: 2px;
}
.totals-chevron {
  flex-shrink: 0;
  font-size: 12px;
  font-weight: var(--font-weight-bold);
  color: var(--color-accent-strong);
  transform: translateY(1px);
  transition: transform var(--motion-fast) var(--motion-easing-standard);
}
.totals-row-grand.is-expanded .totals-chevron {
  transform: rotate(180deg) translateY(-1px);
}
.totals-compact {
  display: none;
  padding-top: var(--chrome-gap-hairline);
  font-size: var(--font-size-chrome);
  line-height: var(--line-height-chrome);
  color: var(--color-text-tertiary);
}
.totals-row-grand.is-expanded + .totals-compact {
  display: block;
}
.totals-compact-sep {
  margin: 0 2px;
}

/* Sixteenth amendment item 2: the "All paid. Close this ticket?" one-tap
   prompt. Success tokens (this is a good-news state), touch-first row
   with both controls at the >=44px hard floor (CLAUDE.md's touch-only
   rule), matching .zero-due-prompt-actions below. */
/* ALPHA-12 vertical-space-budget class of bug (see .ticket-lines' own
   comment above for the full history): design-gate finding, this rule's
   own first version used justify-content:space-between with flex-wrap,
   which wraps to two full rows the moment the text and both buttons
   cannot share one line, roughly DOUBLING this element's height
   (measured 127px on a 400px-wide rail at 1024x768 vs. an intended ~55px)
   and pushing #ticket-actions/#close-ticket down far enough to overlap
   #debug-panel below the ticket rail - the exact `#debug-panel
   intercepts pointer events` failure mode this codebase has hit before.
   Fixed the way that history's own fix reads: never let this text force
   a second row. nowrap plus a truncating text column (flex-shrink,
   min-width:0, ellipsis) keeps the whole banner pinned to one row's
   height always, at any rail width; the two buttons never shrink or
   wrap. Compact padding/gap on top of that, since two buttons already
   at the 44px touch-target floor set the real height floor regardless. */
.zero-due-prompt {
  margin: 0 var(--space-5) var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  background: var(--color-success-subtle);
  border: 1px solid var(--color-success-subtle-border);
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: nowrap;
}
.zero-due-prompt-text {
  color: var(--color-success-strong);
  font-weight: var(--font-weight-semibold);
  font-size: var(--font-size-sm);
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.zero-due-prompt-actions {
  display: flex;
  gap: var(--space-2);
  flex-shrink: 0;
}
.zero-due-prompt-actions button {
  height: var(--touch-target-min);
  padding: 0 var(--space-3);
}

.ticket-actions {
  /* UX13: Prepare All and Pay now share ONE row (.action-row-primary
     below) instead of Pay owning a full-width row under a separate
     Prepare-All/Close-Ticket pair. Close Ticket is gone outright (see
     index.html's own comment on this block). No gap here: the real
     production topbar (unlike the isolated mockup's own simplified one)
     leaves this rail less room than the mockup could assume, and
     harness/e2e/ux13-ticket-rail.spec.ts measures every pixel of it, so a
     conditional child (#pay-block-reason, #no-tip-button, #pay-status)
     sits directly against whatever is above it; each already carries its
     own background/border-left so the boundary stays readable without a
     gap spending height this rail does not have. */
  padding: 0 var(--space-4) 0;
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
  border-top: 1px solid var(--color-border);
}
/* Sharing one row instead of two stacked rows is a LAYOUT change, not a
   touch-target change: neither button's own height moved (Prepare All is
   --touch-target-min, Pay is --touch-target-primary, both unchanged
   below). Stacked, the pair costs 44 + --chrome-gap-target + 64 = 116px;
   side by side they cost max(44, 64) = 64px, since --chrome-gap-target
   (8px) is spent horizontally between them instead of vertically between
   rows -- design/ux13-ticket-rail-rationale.md's own single largest
   density saving. This is the one place this rail departs from
   design/principles.md rule 2 ("Pay is a single full-width bar... nothing
   else competes with it for size, color, or position"): Pay still
   dominates by size (64px vs 44px) and by color (solid accent fill vs
   Prepare All's outline below), so the hierarchy rule 2 asks for is still
   visible at a glance; only "alone on its own row" is given up. */
.action-row-primary {
  display: flex;
  align-items: center;
  gap: var(--chrome-gap-target);
}
.btn-prepare-all {
  flex: 1;
  height: var(--touch-target-min);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  border: 1px solid var(--color-border-strong);
  color: var(--color-text-primary);
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  cursor: pointer;
}
.btn-prepare-all:disabled {
  background: var(--color-surface-sunken);
  color: var(--color-text-tertiary);
  border-color: var(--color-border);
  cursor: default;
}
/* .action-row-primary's own two-thirds share; .btn-pay's base rule below
   (height, color, shape) is shared with every other Pay-styled button in
   this file (#zero-due-close, the pay-confirm sheet's own confirm
   button), so the flex ratio stays scoped to this row rather than
   applied to .btn-pay generally. */
.action-row-primary .btn-pay {
  flex: 2;
}

.btn-pay {
  height: var(--touch-target-primary);
  border-radius: var(--radius-md);
  border: none;
  background: var(--color-accent-strong);
  color: var(--color-text-on-accent);
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-bold);
  box-shadow: var(--shadow-2);
}
.btn-pay:disabled {
  background: var(--color-surface-sunken);
  color: var(--color-text-tertiary);
  box-shadow: none;
}
/* Found by this task's own live Charcoal sweep (Amendment 2026-08-22),
   not by the owner's report: this read the FIXED --color-warning-strong
   as bare text with no background at all, the inverse of design/README.md's
   own rule ("never pair a semantic color with a component's own surface
   or text tokens") from the other direction, a fixed color against a
   THEME surface rather than a theme color against a fixed surface. On
   every light theme this happened to stay legible by accident
   (warning-strong is dark, light surfaces are light); measured at 2.58:1
   against Charcoal's own dark surface, well under the 4.5:1 floor. Given
   its own self-contained warning-subtle background, the same pairing
   every other warning chip in this file already uses
   (design/README.md's checked 5.4:1 ratio), so it now reads correctly
   regardless of theme surface, not just by accident on light ones. */
/* UX13: compressed from a padded, bordered banner to a left-accented
   line (design/mockups/ticket-rail-redesign-blocked.html's own
   .pay-block-reason), roughly a quarter of the previous box's height.
   Still color + shape together (design/README.md's own "never color
   alone" rule: a colored left bar, not color alone) and still its own
   row; not a tappable control, so it is not held to the 44px/8px rules
   the buttons above it are. */
.pay-block-reason {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-size-chrome);
  line-height: var(--line-height-chrome);
  font-weight: var(--font-weight-semibold);
  color: var(--color-warning-strong);
  background: var(--color-warning-subtle);
  border-left: 3px solid var(--color-warning-strong);
  padding: var(--chrome-gap-hairline) var(--space-2) var(--chrome-gap-hairline) var(--space-3);
}
.pay-status {
  text-align: center;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  padding: var(--space-2);
  border-radius: var(--radius-md);
}
.pay-status.success {
  background: var(--color-success-subtle);
  color: var(--color-success-strong);
}
.pay-status.error {
  background: var(--color-danger-subtle);
  color: var(--color-danger-strong);
}

/* Design/QA gate finding F: per-maker tip split, shown right after a
   payment with a tip actually captures. Plain theme-native surface (not a
   fixed semantic background), since this is informational, not a state
   signal - ordinary --color-text-primary/--color-surface is correct here,
   unlike the warn-/status- banners' fixed-background pairs above. */
.pay-tip-split {
  margin-top: var(--space-2);
  padding: var(--space-3);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border);
  background: var(--color-surface);
}
.pay-tip-split-title {
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  margin-bottom: var(--space-2);
}
.pay-tip-split-row {
  display: flex;
  justify-content: space-between;
  font-size: var(--font-size-sm);
  color: var(--color-text-primary);
  padding: var(--space-1) 0;
}

/* ---------- Debug / session panel (staff/admin only, never shown to a
   customer). Carries the provisional-tax label per
   docs/decision-proposals/provisional-pricing-config.md wherever a rate
   (not just a dollar amount) is displayed. ---------- */

.debug-panel {
  border-top: 1px solid var(--color-border);
  background: var(--color-surface-sunken);
  padding: var(--space-2) var(--space-5);
  font-size: var(--font-size-xs);
  color: var(--color-text-secondary);
  flex-shrink: 0;
}
.debug-panel summary {
  cursor: pointer;
  padding: var(--space-2) 0;
}
.debug-panel dl {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--space-1) var(--space-3);
  margin: 0 0 var(--space-3);
}
.debug-panel dt {
  color: var(--color-text-tertiary);
}
.debug-panel dd {
  margin: 0;
  font-family: ui-monospace, monospace;
}

/* ---------- Liquor sheet ---------- */

.scrim {
  position: fixed;
  inset: 0;
  background: var(--color-scrim);
  z-index: var(--z-scrim);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: 0;
}
.sheet {
  background: var(--color-surface);
  border-radius: var(--radius-xl) var(--radius-xl) 0 0;
  box-shadow: var(--shadow-3);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 640px;
  max-height: min(720px, 92vh);
  z-index: var(--z-sheet);
}
.sheet-header {
  padding: var(--space-5) var(--space-5) var(--space-4);
  border-bottom: 1px solid var(--color-border);
  flex-shrink: 0;
}
.sheet-title-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
}
.sheet-title {
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-bold);
}
.sheet-subtitle {
  margin-top: var(--space-1);
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
}
.required-flag {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  margin-top: var(--space-3);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  color: var(--color-warning-strong);
  background: var(--color-warning-subtle);
  border: 1px solid var(--color-warning-subtle-border);
  padding: 3px var(--space-2);
  border-radius: var(--radius-sm);
}

.search-row {
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--color-border);
  flex-shrink: 0;
}
.search-input {
  width: 100%;
  height: var(--touch-target-comfortable);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border-strong);
  background: var(--color-surface-sunken);
  padding: 0 var(--space-4);
  color: var(--color-text-primary);
}
.search-input::placeholder {
  color: var(--color-text-tertiary);
}

.liquor-list {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-2) var(--space-3) var(--space-4);
}
.liquor-empty {
  padding: var(--space-6) var(--space-3);
  color: var(--color-text-secondary);
  text-align: center;
}
.liquor-group-title {
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-tertiary);
  padding: var(--space-3) var(--space-2) var(--space-2);
}
.liquor-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  min-height: var(--touch-target-comfortable);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  border: 1px solid transparent;
  background: transparent;
  cursor: pointer;
  text-align: left;
}
.liquor-row + .liquor-row {
  margin-top: 2px;
}
.liquor-row.selected {
  background: var(--color-accent-subtle);
  border-color: var(--color-accent-subtle-border);
}
/* Amendment 2026-08-22 (item 2) fallout, caught in this task's own live
   Charcoal sweep, not by the owner's report: .liquor-name/.liquor-price/
   .liquor-type normally read the ambient --color-text-primary/secondary/
   tertiary tokens, which is correct against the row's usual transparent
   background. Once selected, the row's OWN background switches to
   --color-accent-subtle, and every theme's accent-subtle is now light
   (this item made Charcoal's light too, matching the other three), while
   Charcoal's own text-primary/secondary/tertiary stay light-on-dark
   colors. Before this item, Charcoal's accent-subtle was dark, so the
   ambient light text colors happened to still read correctly against it;
   flipping it light for the badge/pill fix elsewhere in this same item
   silently broke this specific spot the same way the item's own Finding 1
   broke text-on-button, just newly introduced rather than inherited. On
   every light theme this was already accidentally fine (text-primary is
   already dark there), which is exactly why nothing else in this sweep
   caught it. Fixed the same way every other accent-subtle pairing in this
   file already is: explicit, self-contained accent-strong text, dark and
   readable on the now-uniformly-light accent-subtle on every theme,
   never left to inherit whatever the ambient (theme-relative) text color
   happens to be. */
.liquor-row.selected .liquor-name,
.liquor-row.selected .liquor-price,
.liquor-row.selected .liquor-type {
  color: var(--color-accent-strong);
}
.liquor-check {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: 2px solid var(--color-border-strong);
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  color: transparent;
}
.liquor-row.selected .liquor-check {
  background: var(--color-accent-strong);
  border-color: var(--color-accent-strong);
  color: var(--color-text-on-accent);
}
.liquor-name-block {
  flex: 1;
  min-width: 0;
}
.liquor-name {
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight);
  color: var(--color-text-primary);
}
.liquor-type {
  font-size: var(--font-size-xs);
  color: var(--color-text-tertiary);
}
.liquor-price {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-text-secondary);
  flex-shrink: 0;
}

/* ALPHA-10 item 2, 2026-08-20 amendment: the explicit "More spirits"
   expansion into every active liquor. Deliberately plainer than
   .btn-add/.btn-primary (secondary-styled, full-width, sits inside the
   scrollable sheet body rather than the footer) so tapping it reads as
   "look further," not as the drink's own default confirm action. */
.btn-more-spirits {
  width: 100%;
  min-height: var(--touch-target-comfortable);
  margin-top: var(--space-2);
  padding: 0 var(--space-4);
  border-radius: var(--radius-md);
  border: 1px dashed var(--color-border-strong);
  background: transparent;
  color: var(--color-text-secondary);
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-medium);
  cursor: pointer;
}
/* Replaces .btn-more-spirits once tapped (main.ts's handleMoreSpiritsClick
   hides one and shows the other): a persistent, plain-language reminder
   that the sheet is now showing every active liquor, not just this
   drink's usual categories, for as long as the sheet stays open. */
.substitution-note {
  margin: var(--space-2) var(--space-3) 0;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  background: var(--color-warning-subtle);
  border: 1px solid var(--color-warning-subtle-border);
  color: var(--color-warning-strong);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
}

/* ---------- MyTime track (track/mytime) Slice 2: Add Guest client
   search (index.html's #add-guest-sheet), amendment 2026-08-23
   (nineteenth). Result rows mirror .liquor-row's own shape (same
   min-height, same selected/tap styling family) so the search-and-tap
   feel is consistent with the liquor picker's already-shipped, already
   touch-tested pattern rather than inventing a second one. ---------- */
.add-guest-freshness {
  margin-top: var(--space-2);
  font-size: var(--font-size-xs);
  color: var(--color-text-tertiary);
}
.add-guest-freshness.stale {
  color: var(--color-warning-strong);
}
.add-guest-results {
  max-height: 320px;
  overflow-y: auto;
  padding: var(--space-2) var(--space-3) 0;
}
.add-guest-result-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  min-height: var(--touch-target-comfortable);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  border: 1px solid transparent;
  background: transparent;
  cursor: pointer;
  text-align: left;
}
.add-guest-result-row + .add-guest-result-row {
  margin-top: 2px;
}
.add-guest-result-row:active {
  background: var(--color-accent-subtle);
  border-color: var(--color-accent-subtle-border);
}
.add-guest-result-name-block {
  flex: 1;
  min-width: 0;
}
.add-guest-result-name {
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight);
  color: var(--color-text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.add-guest-result-phone {
  font-size: var(--font-size-xs);
  color: var(--color-text-tertiary);
}
.add-guest-result-badges {
  display: flex;
  gap: var(--space-1);
  flex-shrink: 0;
}
.add-guest-badge {
  display: inline-flex;
  align-items: center;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  line-height: var(--line-height-tight);
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
}
.add-guest-badge-member {
  color: var(--color-accent-strong);
  background: var(--color-accent-subtle);
  border: 1px solid var(--color-accent-subtle-border);
}
.add-guest-badge-provisional {
  color: var(--color-warning-strong);
  background: var(--color-warning-subtle);
  border: 1px solid var(--color-warning-subtle-border);
}
.add-guest-results-empty {
  padding: var(--space-4) var(--space-3);
  color: var(--color-text-secondary);
  text-align: center;
  font-size: var(--font-size-sm);
}
.add-guest-results-hint {
  padding: var(--space-4) var(--space-3);
  color: var(--color-text-tertiary);
  text-align: center;
  font-size: var(--font-size-sm);
}
/* Mirrors .btn-more-spirits's own "plainer, dashed, full-width, lives in
   the scrollable body" shape, for the identical reason: this is a
   secondary path (offered only once search has already come up empty),
   not the sheet's default confirm action. */
.btn-new-client {
  width: 100%;
  min-height: var(--touch-target-comfortable);
  margin: var(--space-2) var(--space-3) 0;
  padding: 0 var(--space-4);
  border-radius: var(--radius-md);
  border: 1px dashed var(--color-border-strong);
  background: transparent;
  color: var(--color-text-secondary);
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-medium);
  cursor: pointer;
}
.add-guest-new-client-help {
  margin: var(--space-4) var(--space-5) var(--space-2);
  color: var(--color-text-secondary);
  font-size: var(--font-size-sm);
}
.add-guest-divider {
  margin: var(--space-4) var(--space-5) 0;
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border);
  color: var(--color-text-tertiary);
  font-size: var(--font-size-xs);
  text-align: center;
}

.sheet-footer {
  padding: var(--space-4) var(--space-5) var(--space-5);
  border-top: 1px solid var(--color-border);
  flex-shrink: 0;
  background: var(--color-surface);
}
.price-preview {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: var(--space-3);
}
.price-preview-label {
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
}
.price-preview-value {
  font-size: var(--font-size-2xl);
  font-weight: var(--font-weight-bold);
}
.price-preview-value.placeholder {
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-medium);
  color: var(--color-text-tertiary);
}
.price-breakdown {
  font-size: var(--font-size-xs);
  color: var(--color-text-tertiary);
  margin-bottom: var(--space-3);
  min-height: 1em;
}
.btn-add {
  width: 100%;
  height: var(--touch-target-primary);
  border-radius: var(--radius-md);
  border: none;
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-bold);
}
.btn-add.enabled {
  background: var(--color-accent-strong);
  color: var(--color-text-on-accent);
  box-shadow: var(--shadow-2);
}
.btn-add.disabled {
  background: var(--color-surface-sunken);
  color: var(--color-text-tertiary);
}

/* ---------- Pay confirm sheet (ALPHA-07) ----------
   Shares .scrim/.sheet/.sheet-header/.sheet-title-row/.sheet-title/
   .sheet-footer with the liquor sheet above; only what is unique to this
   sheet is defined here. */

.simulated-badge {
  /* Design gate fix (2026-08-20, finding 4): bumped from font-size-xs/
     font-weight-semibold and 3px/space-2 padding, which read as a quiet
     footnote easy to miss under bar lighting. "No card charged" is also
     now duplicated onto .pay-confirm-note below the Confirm button, so a
     bartender does not depend on noticing this badge alone. */
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  margin-top: var(--space-3);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-bold);
  color: var(--color-info);
  background: var(--color-info-subtle);
  border: 1px solid var(--color-info-subtle-border);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-sm);
}

.pay-confirm-note {
  text-align: center;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  color: var(--color-info);
  margin: 0 var(--space-5) var(--space-4);
}

.pay-confirm-amount {
  text-align: center;
  padding: var(--space-6) var(--space-5) var(--space-2);
}
.pay-confirm-amount-label {
  display: block;
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
}
.pay-confirm-amount-value {
  display: block;
  font-size: var(--font-size-3xl);
  font-weight: var(--font-weight-bold);
  line-height: var(--line-height-tight);
  margin-top: var(--space-1);
}

.pay-confirm-totals {
  padding: 0 var(--space-5) var(--space-4);
  border-top: none;
}

/* Design/QA gate finding D: what Pay is about to charge, listed by name,
   distinct from the totals block below it. Theme-native surface, not a
   fixed semantic background: this is informational, not a state signal. */
.pay-confirm-charging-now {
  margin: 0 var(--space-5) var(--space-4);
  padding: var(--space-3);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border);
  background: var(--color-surface-sunken);
}
.pay-confirm-charging-now-title {
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  margin-bottom: var(--space-2);
}
.pay-confirm-charging-now-row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-2);
  font-size: var(--font-size-sm);
  color: var(--color-text-primary);
  padding: var(--space-1) 0;
}
/* Touch-only product rule: no title-attribute-only recovery for hidden
   text (there is no hover on a touchscreen to reveal a tooltip). Wraps
   instead of truncating; this list is short (a handful of rows at most)
   and each row has room to grow, unlike the ticket rail's own tightly
   height-budgeted line layout. */
.pay-confirm-charging-now-row span:first-child {
  word-break: break-word;
}

.pay-confirm-actions {
  display: flex;
  gap: var(--space-3);
}
.pay-confirm-actions .btn-secondary {
  flex: 1;
}
.pay-confirm-actions .btn-pay {
  flex: 2;
}

/* ---------- Toast ----------
   Design gate fix (2026-08-20, finding 2 non-blocking correction): .toast
   used to be unconditionally styled danger-red, so a plainly good outcome
   ("Ticket closed.") read as an error. Base positioning/shape lives here;
   color comes from one of the three variant classes below, which main.ts's
   showToast always applies explicitly (see its own comment). */

.toast {
  position: fixed;
  left: 50%;
  bottom: var(--space-6);
  transform: translateX(-50%);
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-3);
  z-index: var(--z-toast);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  max-width: 90vw;
}
/* text-on-dark, not text-on-accent, on both variants below: the
   background is a FIXED semantic *-strong color (design/tokens.css),
   always dark, never this theme's own accent-strong. Design/QA gate
   finding, 2026-08-21, found while verifying the new Charcoal accent red
   against toasts (Amendment 2026-08-21, eighth/ninth): on Charcoal,
   text-on-accent is dark (tuned for Charcoal's own light accent-strong),
   which against the fixed dark danger-strong/success-strong background
   rendered functionally invisible text, roughly 1.9:1. text-on-dark is
   defined on every theme to be safely light against a dark, saturated
   background, which is what a fixed *-strong color always is; swapping
   to it fixes Charcoal (now ~8:1) with no change on any other theme
   (text-on-accent was already near-white there, so the two tokens were
   already equal in value, just not in meaning). */
.toast.toast-danger {
  background: var(--color-danger-strong);
  color: var(--color-text-on-dark);
}
.toast.toast-success {
  background: var(--color-success-strong);
  color: var(--color-text-on-dark);
}
/* Neutral: --color-text-primary/--color-surface are always a verified
   high-contrast pair in every theme (design/tokens.css), so swapping them
   (dark-on-light in a light theme, light-on-dark in a dark theme) gives a
   readable neutral chip without a fourth theme-independent color pair to
   maintain. Used for a heads-up that degrades gracefully rather than an
   error (see loadCheapestLiquorPrices's toast call). */
.toast.toast-neutral {
  background: var(--color-text-primary);
  color: var(--color-surface);
}

/* ---------- Responsive: tablet portrait and phone width ----------
   Tablet landscape (1280x800, 1024x768) is the primary supported class
   and uses the two-column layout above unchanged. Below 900px the ticket
   rail stacks under the menu instead of beside it, per the ALPHA-06
   instruction to stay usable at phone width even though phone is not a
   primary target (design/README.md: "Breakpoints below tablet width" are
   an explicit gap in the design mockups; this is a functional fallback,
   not a designed phone experience). */
@media (max-width: 900px) {
  .body {
    flex-direction: column;
  }
  .ticket-rail {
    /* !important, deliberately: src/railResize.ts applies the resized/
       persisted rail width as an INLINE style (railEl.style.width), which
       normally beats any stylesheet rule regardless of media query or
       selector specificity - only an !important stylesheet declaration
       out-ranks a plain inline one. Below this breakpoint .body is a
       single stacked column (above) and .rail-resize-handle is hidden
       (its own rule below), so a leftover wide inline width from a
       previous, wider viewport must not force this narrow layout back
       into a fixed-width side column. */
    width: auto !important;
    border-top: 1px solid var(--color-border);
  }
  .menu-pane {
    min-height: 45vh;
  }
  /* Below the tablet-landscape primary target, .body is already a single
     stacked column (above), so a second topbar row is not competing with
     the ticket rail for height the way it was at 1024x768; wrapping is
     an acceptable fallback here, same as pre-fix behavior. */
  .topbar {
    flex-wrap: wrap;
  }
  .topbar-right {
    flex-wrap: wrap;
  }
}

/* ---------- ALPHA-10 item 3: manager settings screen ---------- */

.admin-page-subtitle {
  margin: 0 0 var(--space-5);
  color: var(--color-text-secondary);
  font-size: var(--font-size-base);
}

.admin-drink-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.admin-drink-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  width: 100%;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5);
  min-height: var(--touch-target-comfortable);
}

.admin-drink-info {
  min-width: 0;
  flex: 1;
}
.admin-drink-name {
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight);
}
.admin-drink-category {
  margin-top: 2px;
  font-size: var(--font-size-sm);
  color: var(--color-text-tertiary);
}

/* Amendment 2026-08-22 (tenth): bulk drink operations. */

.admin-panel-header-actions {
  display: flex;
  gap: var(--space-3);
  flex-shrink: 0;
}

/* The per-row selection checkbox, multiselect mode only. Sized to the
   same --touch-target-min floor .ack-checkbox already establishes for a
   plain (non-toggle-switch) checkbox in this codebase. */
.admin-drink-select-checkbox {
  width: var(--touch-target-min);
  height: var(--touch-target-min);
  flex-shrink: 0;
  accent-color: var(--color-accent-strong);
}

.admin-select-all-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--touch-target-comfortable);
  padding: var(--space-2) var(--space-1);
  margin-bottom: var(--space-3);
  cursor: pointer;
}
.admin-select-all-checkbox {
  width: var(--touch-target-min);
  height: var(--touch-target-min);
  flex-shrink: 0;
  accent-color: var(--color-accent-strong);
}

/* Sticky footer bar, the same visual weight as the pay-confirm sheet's
   own footer: stays reachable without scrolling back up to find it after
   selecting drinks lower in a long list. */
.admin-bulk-action-bar {
  position: sticky;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  margin-top: var(--space-4);
  padding: var(--space-4) var(--space-5);
  background: var(--color-surface);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-2);
}
.admin-bulk-action-buttons {
  display: flex;
  gap: var(--space-3);
  flex-wrap: wrap;
}
#admin-drink-bulk-count {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-secondary);
}

/* Plain-language progress and per-drink failure summary, persistent (not
   a toast) so a manager can review what happened after a bulk apply. */
.admin-bulk-result {
  margin-top: var(--space-4);
  padding: var(--space-4) var(--space-5);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
}
.admin-bulk-failure-list {
  margin: var(--space-2) 0 0;
  padding-left: var(--space-5);
  font-size: var(--font-size-sm);
  color: var(--color-danger-strong);
}
.admin-bulk-failure-list li {
  margin-bottom: var(--space-1);
}

/* Plain toggle switch: a real checkbox input (screen-reader and keyboard
   accessible, aria-label set per row in main.ts) visually hidden behind a
   track/status pair, the whole label a single >=44px touch target. Never
   a bare unlabeled checkbox, per this screen's plain-language, 8th-grade
   staff-facing wording standard applied to a manager surface. */
.admin-toggle-label {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-shrink: 0;
  min-height: var(--touch-target-min);
  cursor: pointer;
}
.admin-toggle-input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
.admin-toggle-track {
  position: relative;
  width: 48px;
  height: 28px;
  border-radius: var(--radius-full);
  background: var(--color-border-strong);
  transition: background-color 0.15s ease;
  flex-shrink: 0;
}
.admin-toggle-track::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--color-surface);
  box-shadow: var(--shadow-1);
  transition: transform 0.15s ease;
}
.admin-toggle-input:checked ~ .admin-toggle-track {
  background: var(--color-success-strong);
}
.admin-toggle-input:checked ~ .admin-toggle-track::after {
  transform: translateX(20px);
}
.admin-toggle-input:focus-visible ~ .admin-toggle-track {
  /* Amendment 2026-08-22 (item 2) fallout, same class of bug as the
     .liquor-row.selected fix above: this was the one focus outline in
     the file built on --color-accent-strong directly instead of the
     shared --color-focus-ring convention every other :focus-visible rule
     here already uses (see the button/input/select rule near the top of
     this file and the two other :focus-visible rules below). That was
     already an inconsistency before this item; it stayed invisible only
     because the old Charcoal accent-strong happened to be light. Now
     that accent-strong is dark on every theme, this outline needs the
     token actually meant to survive landing on any surface. Switched to
     match the established convention rather than special-cased. */
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}
.admin-toggle-input:disabled ~ .admin-toggle-track {
  opacity: 0.5;
}
.admin-toggle-status {
  min-width: 56px;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-secondary);
}

/* =======================================================================
   ALPHA-11: manager console expansion (Drinks/Bottles/Location settings
   sections, add/edit forms). Every rule below is tokens-only, same
   discipline as every rule above; touch targets never drop below
   --touch-target-min.
   ======================================================================= */

.admin-panel-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}
.admin-panel-header .admin-page-subtitle {
  margin: 0;
  flex: 1;
}

/* Settings rebuild (docs/backlog.md SET1/SET2/SET3), replacing the old
   five-tab .admin-section-nav row: a left rail of ten grouped areas plus
   a search box, so the operator sees only the group they picked instead
   of scrolling one long flat panel. Admin surface, so a keyboard and
   pointer are assumed (CLAUDE.md's scoped touch-only rule); the rail
   collapses to a horizontal scrolling strip below 900px only so it still
   works on a narrower laptop window, not for touch. */
.settings-shell {
  display: flex;
  align-items: flex-start;
  gap: var(--space-6);
}
.settings-rail {
  flex: 0 0 240px;
  position: sticky;
  top: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  background: var(--color-surface-sunken);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
}
.settings-search-wrap {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.settings-search-input {
  min-height: var(--touch-target-comfortable);
  padding: 0 var(--space-4);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border-strong);
  background: var(--color-surface);
  color: var(--color-text-primary);
  font-size: var(--font-size-base);
}
.settings-search-input:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}
.settings-search-count {
  margin: 0;
  padding: 0 var(--space-2);
  font-size: var(--font-size-xs);
  color: var(--color-text-tertiary);
  min-height: 1em;
}
.settings-rail-groups {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.settings-rail-item {
  min-height: var(--touch-target-comfortable);
  padding: 0 var(--space-4);
  border-radius: var(--radius-md);
  border: 1px solid transparent;
  background: transparent;
  color: var(--color-text-secondary);
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  text-align: left;
  cursor: pointer;
}
.settings-rail-item[aria-selected="true"] {
  background: var(--color-accent-strong);
  border-color: var(--color-accent-strong);
  color: var(--color-text-on-accent);
}
.settings-groups {
  flex: 1;
  min-width: 0;
}

/* UX12 (DEC-052 ruling (2)): a jump control at the top of any settings
   group panel long enough to need it, built by admin.ts's
   ensureSettingsSectionJump from that panel's own subsection headings.
   Sticky the same way .settings-rail beside it already is (that sibling
   proves the technique holds against this page's own document-level
   scroll, so this reuses it rather than inventing a floating widget). */
.admin-section-jump {
  position: sticky;
  top: var(--space-4);
  z-index: var(--z-sticky);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-1);
}
.admin-section-jump-label {
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-tertiary);
  flex-shrink: 0;
}
.admin-section-jump-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.admin-section-jump-button {
  min-height: var(--touch-target-comfortable);
  padding: 0 var(--space-3);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border);
  background: var(--color-surface-sunken);
  color: var(--color-text-primary);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  cursor: pointer;
}
.admin-section-jump-button:hover,
.admin-section-jump-button:focus-visible {
  border-color: var(--color-accent-strong);
}
.admin-section-jump-button:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}

/* SET2: the live search-results overlay, shown in place of the active
   group panel whenever the search box is non-empty. */
.settings-search-results {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
/* SET2: while a search is active, every group panel underneath the
   results overlay is hidden (the overlay's own [hidden] toggle handles
   showing/hiding itself; this class only ever hides its SIBLINGS). */
.settings-groups.settings-search-active > *:not(.settings-search-results) {
  display: none !important;
}
.settings-search-result {
  display: block;
  width: 100%;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text-primary);
  text-align: left;
  cursor: pointer;
}
.settings-search-result:hover,
.settings-search-result:focus-visible {
  border-color: var(--color-accent-strong);
}
.settings-search-result-group {
  display: inline-block;
  margin-bottom: var(--space-1);
  padding: 2px var(--space-2);
  border-radius: var(--radius-full);
  background: var(--color-accent-subtle);
  border: 1px solid var(--color-accent-subtle-border);
  color: var(--color-text-secondary);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
}
.settings-search-result-label {
  display: block;
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
}
.settings-search-result-description {
  margin: var(--space-1) 0 0;
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
}
.settings-search-empty {
  margin: 0;
  padding: var(--space-4);
  color: var(--color-text-tertiary);
}
/* A field flashed into view from a search result, briefly highlighted so
   the operator's eye finds it against the rest of the group panel. */
.settings-search-target-flash {
  outline: 2px solid var(--color-accent-strong);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

@media (max-width: 900px) {
  .settings-shell {
    flex-direction: column;
  }
  .settings-rail {
    flex: 1 1 auto;
    position: static;
    width: 100%;
  }
  .settings-rail-groups {
    flex-direction: row;
    flex-wrap: wrap;
  }
  .settings-rail-item {
    flex: 1 1 auto;
  }
}

.admin-search-row {
  padding: 0;
  border-bottom: none;
  margin-bottom: var(--space-4);
}

/* Owner alpha-review feedback (Amendment 2026-08-21, fourth): shelf-tier
   and liquor-category filter chips above the Bottles list. Each group is
   its own single-select radio row (native radio inputs sharing one
   `name`, same visually-hidden-input-plus-label shape as
   .admin-chip-select's checkbox pickers above; :checked styling is
   identical since it keys off the sibling selector, not the input type),
   so tapping a chip in one group never disturbs the other group's
   selection, and "All" is always one tap away rather than a picker to
   reopen. */
.admin-bottle-filters {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}
.admin-filter-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.admin-filter-label {
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
.admin-filter-chip-select {
  margin-bottom: 0;
}

/* DSC22 (DEC-097): the Discounts panel's own Sort/Type/State chip groups,
   the same column-of-groups layout as .admin-bottle-filters above, given
   its own class rather than sharing that one so a future change to either
   list's own filter row does not silently move the other. */
.admin-discount-filters {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.admin-bottle-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
/* TF2, owner testing feedback 2026-08-28: the Settings inventory list now
   puts every disabled item in its own group at the bottom, under a
   heading. Rendered only when BOTH groups have something in them
   (admin.ts's renderBottleList), so a location with nothing disabled sees
   the flat list exactly as before.

   Deliberately quiet: a heading, not a banner. This is an ordering aid on
   an admin screen, and the row's own "Inactive" status pill is still what
   states a given item's state. */
.admin-bottle-group-heading {
  margin: var(--space-3) 0 0;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-bold);
  color: var(--color-text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
.admin-bottle-group-heading:first-child {
  margin-top: 0;
}
.admin-bottle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  width: 100%;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5);
  min-height: var(--touch-target-comfortable);
  text-align: left;
  cursor: pointer;
}
.admin-bottle-info {
  min-width: 0;
  flex: 1;
}
.admin-bottle-name {
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight);
}
/* Owner alpha-review feedback: a real location's bottle names run long
   (a seeded real name is up to ~67 characters), and the row now also
   carries a shelf-tier chip and an active/inactive status badge, so a
   long name is clipped to one line with an ellipsis rather than wrapping
   the row tall. This is NOT a title-attribute-only recovery (CLAUDE.md's
   touch-only rule forbids that): tapping the row (its existing, already
   touch-reachable action) opens the edit sheet, whose title
   ("Edit {full name}") and pre-filled name field both show the complete,
   untruncated name, so the real recovery path is a normal tap, not a
   hidden hover/title affordance. */
.admin-bottle-name-text {
  display: block;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.admin-bottle-detail {
  margin-top: 2px;
  font-size: var(--font-size-sm);
  color: var(--color-text-tertiary);
}
/* The bottle's current shelf tier, per owner alpha-review feedback:
   the tenant's own configured tier name (never the raw "tier_1" key),
   or plainly "No shelf" when unassigned. A compact badge, not plain
   text, so it reads as a status at a glance the same way the Active/
   Inactive badge does. */
.admin-bottle-shelf-row {
  margin-top: var(--space-1);
}
.admin-bottle-shelf-chip {
  display: inline-flex;
  align-items: center;
  padding: 2px var(--space-2);
  border-radius: var(--radius-full);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  background: var(--color-accent-subtle);
  color: var(--color-accent-strong);
  border: 1px solid var(--color-accent-subtle-border);
}
.admin-bottle-shelf-chip.unassigned {
  background: var(--color-surface-sunken);
  color: var(--color-text-tertiary);
  border-color: var(--color-border);
}
.admin-bottle-status {
  flex-shrink: 0;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-secondary);
}
.admin-bottle-status.inactive {
  color: var(--color-warning-strong);
}

/* Tapping a Drinks/Bottles row opens the edit sheet; the row itself is
   already >=44px tall (admin-drink-row/.admin-bottle-row min-height), so
   no extra affordance element is required for the touch target, matching
   the ring screen's own tappable-row precedent (.liquor-row). */
.admin-drink-row,
.admin-bottle-row {
  cursor: pointer;
}
.admin-drink-row:focus-visible,
.admin-bottle-row:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
}

/* A field's own toggle switch that is a form field, not a list-row
   action: same visual language as .admin-toggle-* above, spaced like a
   field. */
.admin-field-toggle {
  margin: var(--space-4) 0 var(--space-1);
}

.admin-field-help {
  margin: var(--space-1) 0 var(--space-3);
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
  line-height: var(--line-height-normal);
}

/* Amendment 2026-08-22 (second), settings input formatting: the visible
   unit affordance ("%" for a percent field, "$" for a dollars field) so
   the human-readable unit is evident without relying on the label alone.
   A plain wrapper + absolutely positioned mark, matching this file's
   other affix-free inputs' own precedent of using padding rather than a
   bespoke input component; the input keeps using .field-input's real
   styling untouched, this only reserves room for the mark and positions
   it. aria-hidden on the mark: the field's own <label> ("Tax rate, in
   percent" / the shelf-tier row's "price in dollars" aria-label) already
   carries the unit for assistive tech, so the visual mark would be
   redundant noise read twice. Originally settings-only; a UI-batch gate
   fix (exactness rule extension) carried the same "$" affix onto the
   drink build-charge and bottle sell-price catalog fields
   (index.html/admin.ts), whose own labels already say "in dollars" too,
   so the aria-hidden reasoning above still holds there. */
.field-input-affix {
  position: relative;
  display: block;
}
.field-input-affix-mark {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  color: var(--color-text-tertiary);
  font-size: var(--font-size-base);
  pointer-events: none;
}
.field-input-affix--suffix .field-input {
  padding-right: calc(var(--space-4) + 1.5ch);
}
.field-input-affix--suffix .field-input-affix-mark {
  right: var(--space-4);
}
.field-input-affix--prefix .field-input {
  padding-left: calc(var(--space-4) + 1ch);
}
.field-input-affix--prefix .field-input-affix-mark {
  left: var(--space-4);
}

.provisional-flag {
  display: inline-flex;
  align-items: center;
  margin-left: var(--space-2);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  color: var(--color-warning-strong);
  background: var(--color-warning-subtle);
  border: 1px solid var(--color-warning-subtle-border);
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
  vertical-align: middle;
}

/* Multi-select "chip" pickers (liquor-slot categories, pour ounce
   options): each chip is its own >=44px touch target, a real checkbox
   under the hood (screen-reader and keyboard accessible) rather than a
   div with a click handler, mirroring .admin-toggle-input's
   visually-hidden-input pattern. */
.admin-chip-select {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}
.admin-chip-option {
  display: inline-flex;
}
.admin-chip-option-input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
.admin-chip-option-label {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target-min);
  padding: 0 var(--space-4);
  border-radius: var(--radius-full);
  border: 1px solid var(--color-border-strong);
  background: var(--color-surface);
  color: var(--color-text-secondary);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  cursor: pointer;
}
.admin-chip-option-input:checked ~ .admin-chip-option-label {
  background: var(--color-accent-subtle);
  border-color: var(--color-accent-subtle-border);
  color: var(--color-accent-strong);
  font-weight: var(--font-weight-semibold);
}
.admin-chip-option-input:focus-visible ~ .admin-chip-option-label {
  outline: none;
  box-shadow: var(--shadow-focus);
}

/* Read-only chip row (configured tip presets, shown with a remove
   action): visually similar to the picker chips above but each carries
   its own X touch target rather than being itself a toggle. */
.admin-chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}
.admin-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--touch-target-min);
  padding: 0 var(--space-2) 0 var(--space-4);
  border-radius: var(--radius-full);
  border: 1px solid var(--color-accent-subtle-border);
  background: var(--color-accent-subtle);
  color: var(--color-accent-strong);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
}
.admin-chip-remove {
  width: var(--touch-target-min);
  height: var(--touch-target-min);
  border-radius: 50%;
  border: none;
  background: transparent;
  color: inherit;
  font-size: var(--font-size-base);
  cursor: pointer;
  flex-shrink: 0;
}
.admin-add-chip-row {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  margin-bottom: var(--space-4);
}
.admin-add-chip-row .field-input {
  flex: 1;
}

/* Payments-track Slice 3: Settings > Payments > Terminals. Same visual
   language as .admin-drink-row/.admin-toggle-* (a plain list), a list
   rather than a div so screen readers announce "N terminals". Amendment
   2026-08-22 (ninth) added the per-row rename affordance below; the row
   itself is still not a single tap target the way a drink/bottle row is
   (the toggle and the rename button are each their own explicit control,
   since a bare row tap here would be ambiguous between "edit" and
   "toggle active" the way it never is for a drink/bottle row, which has
   only one edit destination). */
.admin-terminal-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
  list-style: none;
  padding: 0;
}
.admin-terminal-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  width: 100%;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5);
  min-height: var(--touch-target-comfortable);
}
.admin-terminal-info {
  min-width: 0;
  flex: 1;
}
.admin-terminal-name-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.admin-terminal-name {
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Amendment 2026-08-22 (ninth): the rename pencil. Sized by .btn-icon's
   own --touch-target-min floor; margin-left: auto is deliberately NOT
   used here (the button sits directly after the name, not pinned to the
   row's far edge, since .admin-terminal-provider below already occupies
   the same visual column the Active toggle uses on the row's far side). */
.admin-terminal-rename-button {
  flex-shrink: 0;
  font-size: var(--font-size-md);
}
.admin-terminal-rename-button:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.admin-terminal-provider {
  margin-top: 2px;
  font-size: var(--font-size-sm);
  color: var(--color-text-tertiary);
}

/* M0: Settings > Void reasons. Same row language as
   .admin-terminal-list above (a plain list of rows, each with an editable
   name and an Active toggle on the far side); a separate class rather
   than a reuse of the terminal one because the two lists are unrelated
   and a shared name would make either one risky to restyle. */
.admin-void-reason-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
  list-style: none;
  padding: 0;
}
.admin-void-reason-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  width: 100%;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5);
  min-height: var(--touch-target-comfortable);
}
.admin-void-reason-fields {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
  flex: 1;
}
.admin-void-reason-name {
  flex: 1;
  min-width: 0;
}
.admin-void-reason-shipped {
  font-size: var(--font-size-sm);
  color: var(--color-text-tertiary);
  white-space: nowrap;
}

/* Discounts, slice A: Settings > Discounts. Same row language as
   .admin-void-reason-row above, with more columns because a discount
   carries more than a label. An ADMIN surface, so a dense row with a
   select and typed value boxes is legitimate here (product-owner ruling
   2026-08-27); the floor never sees this layout. Wraps rather than
   overflowing, so a narrower laptop window stacks the columns instead of
   scrolling the page sideways. */
.admin-discount-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
  list-style: none;
  padding: 0;
}
.admin-discount-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3) var(--space-4);
  width: 100%;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5);
  min-height: var(--touch-target-comfortable);
}
.admin-discount-name {
  flex: 1 1 12rem;
  min-width: 8rem;
}
.admin-discount-type {
  flex: 0 1 12rem;
}
.admin-discount-value-wrap {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}
.admin-discount-value-unit {
  color: var(--color-text-tertiary);
  font-size: var(--font-size-sm);
  min-width: 1ch;
}
.admin-discount-value {
  width: 6rem;
}
.admin-discount-flags {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}
.admin-discount-check {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
  white-space: nowrap;
}
/* Adversarial-gate fix, 2026-08-29 (docs/backlog.md CL5): the visible
   consequence text for closes_ticket, ALWAYS on screen and never a hover
   tooltip -- this console runs on a tablet, which has no hover. Forces
   onto its own line under the checkbox via flex-basis while the
   checkbox's own label text stays on one line (the parent's nowrap). */
.admin-discount-check-hint {
  flex-basis: 100%;
  width: 100%;
  white-space: normal;
  font-size: var(--font-size-xs);
  color: var(--color-warning-strong);
}
/* Settings rebuild SET1 hard requirement 2: member_daily_comp is given a
   home but stays READ ONLY (src/domain/discounts.ts's own comment on
   why); plain text in a pill, never a checkbox, so nobody mistakes it
   for editable. closes_ticket moved to an editable checkbox above,
   2026-08-29 (docs/backlog.md CL5): no CHECK constraint ever referenced
   it, unlike member_daily_comp. */
.admin-discount-readonly-flag {
  display: inline-flex;
  align-items: center;
  padding: 2px var(--space-2);
  border-radius: var(--radius-full);
  background: var(--color-surface-sunken);
  border: 1px solid var(--color-border);
  font-size: var(--font-size-xs);
  color: var(--color-text-tertiary);
  white-space: nowrap;
}
.admin-discount-add {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}
.admin-discount-add-name {
  flex: 1 1 12rem;
  min-width: 8rem;
}
.admin-discount-add-type {
  flex: 0 1 12rem;
}
.admin-discount-add-value {
  width: 6rem;
}

/* Discounts, slice A: the ONE summed Discount row in the ticket's totals
   block (owner ruling 2026-08-28, "the total discount should be a line
   item at the bottom of the ticket").

   TF1, owner testing feedback the same day: this used to be a <button>,
   because tapping it was the floor's only route back to a mis-applied
   discount. Nothing on screen said it was tappable and the owner did not
   find it ("there is no way to remove a discount if you apply the wrong
   one"), so removal moved onto an explicit Remove control on each
   discount's own row (.discount-line below) and this reverted to a plain
   total.

   UX13: the touch-target-min height is gone. It dated from when this row
   was still a <button>, TF1 already removed that, and DEC-053's density
   scale gives a non-interactive chrome row (nothing here is tappable) no
   44px floor to hold onto; docs/build-briefs/UX13-ticket-rail.md records
   why this row stays always-visible rather than folding behind the Total
   tap the way Subtotal/Tax do (ten already-passing e2e assertions depend
   on its visibility the instant a discount is applied). */
.totals-row-discount {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: var(--space-1) 0;
  font-size: var(--font-size-sm);
  color: var(--color-success-strong);
}

/* TF1: the per-discount rows, at the bottom of the scrollable ticket and
   directly above the totals block. See ui/index.html's #discount-lines
   comment for why they live in the scrolling region rather than in
   #ticket-totals (measured: 17px of free fixed height at 1024x768, and a
   variable number of rows at the 44px touch floor to place in it).

   Not a card or a panel: these read as a continuation of the ticket's own
   line list, so they reuse .line-section-header for their heading and
   .line's own row rhythm. */
.discount-lines {
  display: block;
}
.discount-line {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--color-border);
}
.discount-line:last-child {
  border-bottom: none;
}
.discount-line-info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.discount-line-name {
  font-size: var(--font-size-md);
  line-height: var(--line-height-tight);
  color: var(--color-text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.discount-line-detail {
  font-size: var(--font-size-sm);
  line-height: var(--line-height-normal);
  color: var(--color-text-tertiary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.discount-line-amount {
  flex-shrink: 0;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-success-strong);
  white-space: nowrap;
}
/* The removal affordance the owner could not find, made explicit and
   per-row. A real touch target (the 44px hard floor, CLAUDE.md's
   touch-only rule) and a word rather than an icon, because "Remove" is
   the thing that was missing and an icon would be one more glyph to
   decode mid-service. */
.btn-discount-remove {
  flex-shrink: 0;
  min-height: var(--touch-target-min);
  min-width: var(--touch-target-min);
  padding: 0 var(--space-3);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border-strong);
  background: var(--color-surface);
  color: var(--color-text-secondary);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  cursor: pointer;
}
.btn-discount-remove:active {
  background: var(--color-surface-sunken);
}
.btn-discount-remove:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}
/* DSC2: a derived member benefit has no application behind it, so it
   carries this word where a removable discount carries its Remove button.
   Deliberately NOT a disabled-looking button: a control that cannot act
   reads as a broken control, and the bartender needs to know this row is
   automatic rather than that it is stuck. Sized to the same 44px row
   rhythm so the two kinds of row line up. */
.discount-line-badge {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target-min);
  padding: 0 var(--space-3);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-tertiary);
}

/* Auth track (track/auth) Slice 1: Settings > Users. Same visual
   language as .admin-terminal-list/.admin-drink-row above (a plain list
   of rows, each with its own info block and action buttons); the role
   picker in the Add User row reuses .admin-chip-select/.admin-chip-
   option-* as-is (admin.ts's renderChipRadioGroup), no new chip CSS
   needed. */
.admin-add-user-row {
  flex-wrap: wrap;
}
.admin-add-user-row .field-input {
  min-width: 200px;
}
.admin-user-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.admin-user-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  width: 100%;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5);
  min-height: var(--touch-target-comfortable);
}
.admin-user-info {
  min-width: 0;
  flex: 1;
}
.admin-user-name {
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight);
}
.admin-user-badges {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-2);
}
.admin-user-badge {
  display: inline-flex;
  align-items: center;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-secondary);
  background: var(--color-surface-sunken);
  border: 1px solid var(--color-border-strong);
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
}
.admin-user-badge-active {
  color: var(--color-success-strong);
  background: var(--color-success-subtle);
  border-color: var(--color-success-subtle-border);
}
.admin-user-badge-inactive {
  color: var(--color-text-tertiary);
}
.admin-user-badge-locked {
  color: var(--color-danger-strong);
  background: var(--color-danger-subtle);
  border-color: var(--color-danger-subtle-border);
}
.admin-user-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  flex-shrink: 0;
}

.admin-component-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}
.admin-component-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-sunken);
}
.admin-component-row .field-input {
  height: var(--touch-target-min);
}
.admin-component-name-input {
  flex: 2 1 160px;
  min-width: 120px;
}
.admin-component-quantity-input {
  flex: 1 1 80px;
  min-width: 70px;
}
.admin-component-unit-input {
  flex: 1 1 80px;
  min-width: 70px;
}
.admin-component-liquor-slot-label {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--touch-target-min);
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
  cursor: pointer;
}
.admin-component-remove {
  width: var(--touch-target-min);
  height: var(--touch-target-min);
  border-radius: 50%;
  border: none;
  background: transparent;
  color: var(--color-danger);
  font-size: var(--font-size-lg);
  cursor: pointer;
  flex-shrink: 0;
  margin-left: auto;
}

/* Generic scrollable sheet body: the liquor sheet's own middle region is
   named .liquor-list because it is ONLY ever a liquor list; the add/edit
   forms below need the identical layout role (flex:1, internal scroll,
   capped by the shared .sheet's own max-height) for an ordinary form, so
   this is the general-purpose name that role should have had. Capped
   height plus internal scroll, never bottom-pinned inputs, is this
   codebase's own established answer to the on-screen keyboard appearing
   (design/README.md; ALPHA-08 checkpoint notes). */
.sheet-body {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-4) var(--space-5);
}
/* Design/QA gate CRITICAL correction, 2026-08-21: .pay-confirm-body reuses
   .sheet-body's flex:1/overflow-y:auto scrolling behavior (index.html's own
   comment on the wrapper has the full reproduction) but overrides its
   default padding to zero, the same way .admin-confirm-body below
   overrides it too: every child inside already carries its own left/
   right/top/bottom spacing (var(--space-5) throughout), sized on the
   assumption of being a direct child of .sheet, not of an already-padded
   .sheet-body. Without this override every child would get DOUBLED
   horizontal padding. Declared HERE, after .sheet-body's own rule (not
   grouped with the rest of the pay-confirm-sheet's styles earlier in this
   file): both are single-class selectors of equal specificity, so CSS
   resolves the conflicting `padding` declaration by source order, and an
   override placed BEFORE the rule it means to override loses the cascade
   silently, with no error - a real mistake this correction's own first
   draft made and caught only by a negative control that unexpectedly
   still passed (see docs/review-record.md's Review 10 for the full
   story). .admin-confirm-body's own identical override, immediately
   below, has always been correctly placed after .sheet-body for the same
   reason. */
.pay-confirm-body {
  padding: 0;
}
/* Deliberately NOT display:flex. An earlier draft made this a flex
   column (to zero out the first field-label's own top margin below);
   that turned every direct child, including buttons like #admin-drink-
   add-component, into a flex item whose default flex-shrink:1 let the
   browser shrink it below its own explicit height (measured live at
   1024x768: the Add component button collapsed to ~24px, well under
   --touch-target-min) whenever the sheet's already-scrollable content
   exceeded the available height, since a shrinkable flex item is
   resized before an overflow:auto container actually scrolls. Plain
   block flow has no such failure mode: every child keeps its own CSS
   height, and .sheet-body's own overflow-y:auto (a sibling class on the
   same element) scrolls the overflow exactly as intended. */
.admin-form-body .field-label:first-child {
  margin-top: 0;
}

.admin-form-footer {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.admin-form-error {
  color: var(--color-danger-strong);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  min-height: 1.2em;
}

/* ---------- Destructive-deactivation confirm sheet (Review 9 correction,
   both gates, Medium) ----------
   Shared by the drinks list toggle and the bottle form's Active toggle.
   Built entirely on the same .scrim/.sheet/.sheet-header/.sheet-title
   tokens every other admin sheet already uses, per design/README.md; the
   only new classes are the body/actions layout below. */
.admin-confirm-sheet {
  max-width: 480px;
}
.admin-confirm-body {
  padding: var(--space-2) var(--space-5) var(--space-4);
}
.admin-confirm-body p {
  margin: 0;
  color: var(--color-text-primary);
  font-size: var(--font-size-base);
  line-height: var(--line-height-normal);
}
.admin-confirm-actions {
  display: flex;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5) var(--space-5);
  border-top: 1px solid var(--color-border);
}
.admin-confirm-actions .btn-secondary,
.admin-confirm-actions .btn-danger {
  flex: 1;
}

.admin-settings-save-row {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-top: var(--space-6);
}
.admin-settings-save-status {
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
  font-weight: var(--font-weight-medium);
}

.admin-shelf-tier-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  margin-bottom: var(--space-2);
  background: var(--color-surface);
}
.admin-shelf-tier-key {
  min-width: 64px;
  font-size: var(--font-size-sm);
  color: var(--color-text-tertiary);
  font-weight: var(--font-weight-semibold);
}
.admin-shelf-tier-row .field-input {
  height: var(--touch-target-min);
}
.admin-shelf-tier-name-input {
  flex: 2 1 160px;
  min-width: 120px;
}
/* The price input's own flex sizing moves to its .field-input-affix
   wrapper (the actual flex child of .admin-shelf-tier-row now that the
   "$" mark sits alongside it, Amendment 2026-08-22 second); the input
   itself stays width:100% of that wrapper via .field-input's own rule. */
.admin-shelf-tier-row .field-input-affix--prefix {
  flex: 1 1 120px;
  min-width: 100px;
}

/* Inventory track Slice 3: the item editor's "Size" field pair (a plain
   quantity plus its free-text unit, e.g. 750 + "ml"), same two-input flex
   row shape as .admin-shelf-tier-row above without that row's bordered
   card chrome, since this pair is a single field's own two parts rather
   than a repeating list. */
.admin-size-row {
  display: flex;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}
.admin-size-row .field-input {
  height: var(--touch-target-min);
}
.admin-size-row #admin-bottle-size-value {
  flex: 1 1 120px;
  min-width: 100px;
}
.admin-size-row #admin-bottle-size-unit {
  flex: 1 1 100px;
  min-width: 80px;
}

/* TF3 (docs/backlog.md, owner ruling 2026-08-28): Par level and the
   read-only on-hand figure sit side by side, so the reorder decision (par
   6, on hand 2, order 4) reads at a glance. */
.admin-par-onhand-row {
  display: flex;
  gap: var(--space-3);
}
.admin-par-onhand-col {
  flex: 1 1 0;
  min-width: 0;
}
/* Deliberately styled like a field-input (same box, same height) while
   being a <p>, never an <input>: see index.html's own comment on this
   element for why that is load-bearing, not cosmetic. */
.admin-onhand-value {
  display: flex;
  align-items: center;
  margin: 0;
  color: var(--color-text-primary);
  font-weight: var(--font-weight-medium);
}
.admin-onhand-value--unavailable {
  color: var(--color-text-tertiary);
  font-weight: var(--font-weight-regular);
  font-style: italic;
}

/* =======================================================================
   ALPHA-08: line-edit control, tip section, pay-warning banner, closeout
   summary. Tokens-only throughout, same as every rule above.
   ======================================================================= */

/* Small icon-only button, sibling of .state-btn in a rendered ticket
   line's row (renderTicketLines). Same touch-target-min floor as
   .btn-icon; kept icon-only (not a text button) specifically to protect
   the compact two-row line's tight width budget (.line's own comment
   above documents how sensitive that budget already is). Hidden entirely
   once the order has a captured payment this session (state.orderPaid),
   matching src/domain/lineEditing.ts's own "blocked after any captured
   payment" gate: an edit control a tap could not act on would be worse
   than no control at all. */
.btn-line-edit {
  flex-shrink: 0;
  width: var(--touch-target-min);
  height: var(--touch-target-min);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border-strong);
  background: var(--color-surface);
  color: var(--color-text-secondary);
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-bold);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.btn-line-edit:hover {
  background: var(--color-surface-sunken);
}

/* Amendment 2026-08-22 (fifth), item 2: the prominent, always-visible
   quick action on an unassigned line (main.ts's buildTicketLineRow,
   promptAssignGuest), distinct from the "..." edit-sheet control above
   which every line (assigned or not) already offers. A real touch
   target of its own (touch-target-min floor, same as every other
   tappable control in this file), rendered as its own row in the line's
   info column rather than squeezed into .line-top, so it never competes
   with the drink name/price for that row's own tight width budget
   (.line's own comment documents how sensitive that budget already is).
   Accent-colored (an intended, available action, not a warning), never
   color alone since the text itself says exactly what it does. */
.line-assign-guest-button {
  align-self: flex-start;
  height: var(--touch-target-min);
  padding: 0 var(--space-3);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-accent-subtle-border);
  background: var(--color-accent-subtle);
  color: var(--color-accent-strong);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
}
.line-assign-guest-button:active {
  opacity: 0.8;
}

.line-edit-actions {
  padding: var(--space-4) var(--space-5) var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.btn-danger {
  width: 100%;
  height: var(--touch-target-comfortable);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-danger-subtle-border);
  background: var(--color-danger-subtle);
  color: var(--color-danger-strong);
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  cursor: pointer;
}
.btn-danger:disabled {
  background: var(--color-surface-sunken);
  color: var(--color-text-tertiary);
  border-color: var(--color-border);
  cursor: default;
}

/* ---------- Tips (ALPHA-08) ----------
   Sits inside the pay-confirm sheet, between totals and the pay-warning
   banner. Presets are rendered from config (main.ts's loadTipOptions),
   never hardcoded percentages; see docs/decision-proposals/provisional-
   tips-config.md rule 3. */
.tip-section {
  padding: 0 var(--space-5) var(--space-4);
}
.tip-section-label {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-secondary);
  margin-bottom: var(--space-2);
}
.tip-preset-row {
  display: flex;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}
.btn-tip-preset {
  flex: 1;
  min-height: var(--touch-target-comfortable);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border-strong);
  background: var(--color-surface);
  color: var(--color-text-primary);
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  line-height: var(--line-height-tight);
}
.btn-tip-preset .tip-preset-amount {
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  color: var(--color-text-tertiary);
}
.btn-tip-preset.selected {
  background: var(--color-accent-subtle);
  border-color: var(--color-accent-strong);
  color: var(--color-accent-strong);
}
.btn-tip-preset.selected .tip-preset-amount {
  color: var(--color-accent-strong);
}
.tip-custom-row {
  display: flex;
  gap: var(--space-2);
}
.tip-custom-input {
  flex: 1;
  min-width: 0;
}
#tip-none-button.selected,
#tip-custom-apply.selected {
  background: var(--color-accent-subtle);
  border-color: var(--color-accent-strong);
  color: var(--color-accent-strong);
}

/* ---------- Pay warning (ALPHA-08, product owner ruling 2026-08-20) ----------
   Mirrors design/mockups/pay-warning.html: amber, never danger red (a
   guest may legitimately be starting a new round); the banner stays on
   screen through both the unacknowledged and acknowledged states, never
   disappearing once shown. */
.warn-banner {
  margin: 0 var(--space-5) var(--space-4);
  background: var(--color-warning-subtle);
  border: 1px solid var(--color-warning-subtle-border);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
}
.warn-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.warn-icon {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--color-warning-strong);
  /* text-on-dark, not text-on-accent: see .toast.toast-danger's comment
     above; same fixed-strong-color-as-background bug, same fix. */
  color: var(--color-text-on-dark);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-bold);
  flex-shrink: 0;
}
.warn-title {
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-bold);
  color: var(--color-warning-strong);
}
/* Design/QA gate finding E, CRITICAL: both of these used to read
   --color-text-primary, a THEME-layer token, against
   --color-warning-subtle, a FIXED semantic background. On Charcoal (the
   one dark theme) --color-text-primary is a LIGHT color, landing at
   1.0:1 contrast against this light amber background: functionally
   invisible. design/README.md's own rule ("never pair a semantic color
   with a component's own surface or text tokens") says exactly why this
   was wrong: a fixed semantic background must pair only with its own
   fixed semantic text color. --color-warning-strong already IS that
   partner (.warn-title, just below the CSS that reads this comment, has
   always used it) and is already verified at 5.4:1 against
   --color-warning-subtle in design/README.md's own checked-pairs table,
   independent of which theme surface happens to be behind the banner. */
.warn-prior {
  margin-top: var(--space-2);
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-bold);
  color: var(--color-warning-strong);
}
.warn-body {
  margin-top: var(--space-1);
  font-size: var(--font-size-sm);
  color: var(--color-warning-strong);
}

/* Design/QA gate finding C: a distinct DANGER-colored variant (never the
   amber above, which is deliberately non-alarming for "you might be
   double-charging a guest on purpose") for "we could not even check
   whether this tab was already paid." This is a hard block, not a
   caution: the fixed danger semantic pair, same rule as .warn-prior/
   .warn-body above (a fixed subtle background pairs only with its own
   fixed strong text token, never a theme token). */
.warn-banner-danger {
  background: var(--color-danger-subtle);
  border-color: var(--color-danger-subtle-border);
}
.warn-icon-danger {
  background: var(--color-danger-strong);
}
.warn-title-danger {
  color: var(--color-danger-strong);
}
.warn-body-danger {
  color: var(--color-danger-strong);
}

/* Acknowledge control: unchecked by default, never pre-checked (design/
   principles.md rule 4, "no soft default"). Confirm stays disabled until
   this is explicitly checked (main.ts's updatePayConfirmButtonState).
   Design/QA gate finding I: now a <label> (index.html), so the whole row
   is the click target, not just the checkbox square; `cursor: pointer`
   makes that discoverable. */
.ack-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--touch-target-comfortable);
  padding: var(--space-3) var(--space-4);
  margin: 0 var(--space-5) var(--space-4);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border-strong);
  background: var(--color-surface);
  cursor: pointer;
}
/* Design/QA gate finding I: was 24x24px, below the 44px touch-target
   floor this codebase holds everywhere else (--touch-target-min). Bumped
   to the floor itself; the row's own 56px --touch-target-comfortable
   min-height already had headroom for it. */
.ack-checkbox {
  width: var(--touch-target-min);
  height: var(--touch-target-min);
  flex-shrink: 0;
  accent-color: var(--color-accent-strong);
}
.ack-label {
  font-size: var(--font-size-sm);
  color: var(--color-text-primary);
}

/* Amendment 2026-08-22 (fifth), item 1: the neutral, non-alarming
   counterpart to .warn-banner above (index.html's #pay-warning-note),
   shown instead of it for a captured payment that is NOT a reopened-tab
   surprise (an ordinary sequential multi-guest payment). Info-colored, no
   checkbox: this states a fact, it does not ask for a decision. Same
   fixed-semantic-token pairing (own strong/subtle text-and-background
   pair, never a theme token) every other banner in this file uses; see
   .warn-prior/.warn-body's own comment for why. */
.pay-info-banner {
  margin: 0 var(--space-5) var(--space-4);
  background: var(--color-info-subtle);
  border: 1px solid var(--color-info-subtle-border);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-info);
}

/* ---------- Closeout summary (ALPHA-08) ----------
   Mirrors design/mockups/closeout-summary.html: an info-colored "not
   finalized" banner (never success, never a lock), figure cards, and two
   named warning-colored unresolved lists rather than one vague exceptions
   count. */
.closeout-page {
  max-width: 1024px;
}
.status-banner {
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
  background: var(--color-info-subtle);
  border: 1px solid var(--color-info-subtle-border);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  margin-bottom: var(--space-6);
}
.status-icon {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--color-info);
  /* text-on-dark, not text-on-accent: see .toast.toast-danger's comment
     above; same fixed-strong-color-as-background bug, same fix. */
  color: var(--color-text-on-dark);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-bold);
  flex-shrink: 0;
}
.status-title {
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-bold);
  color: var(--color-info);
}
/* Design/QA gate finding E: same fixed-background/theme-text mismatch as
   .warn-prior/.warn-body above, on the closeout "not finalized" banner's
   fixed --color-info-subtle background. --color-info is already this
   banner's own verified text partner (.status-title and .status-icon,
   just above, already use it) at 6.0:1 against --color-info-subtle per
   design/README.md's checked-pairs table. */
.status-body {
  font-size: var(--font-size-sm);
  color: var(--color-info);
  margin-top: var(--space-1);
  max-width: 62ch;
}

/* CL6: the finalized banner reuses the .status-banner layout with the
   fixed success pair (own strong text on own subtle background), the
   same rule every other success surface follows. */
.status-banner-success {
  background: var(--color-success-subtle);
  border-color: var(--color-success-subtle-border);
}
.status-banner-success .status-icon {
  background: var(--color-success-strong);
}
.status-banner-success .status-title,
.status-banner-success .status-body {
  color: var(--color-success-strong);
}

/* CL6: day navigation arrows and the finalize affordances. */
.closeout-day-nav {
  display: flex;
  gap: var(--space-3);
  margin-top: var(--space-3);
}
#closeout-finalize-button {
  margin-top: var(--space-3);
}
.closeout-view-note {
  margin-bottom: var(--space-3);
}
.closeout-hold-at-risk {
  color: var(--color-danger-strong);
  font-weight: var(--font-weight-bold);
}

/* Design/QA gate finding H: freshness row (as-of timestamp + manual
   refresh) and the stale-figures note, both on the live closeout summary.
   .closeout-stale-note uses the fixed warning pair (own strong text on
   own subtle background, per the same finding-E rule the pay-warning
   banner follows), since "this number may be old" is a real caution, not
   decoration. */
.closeout-freshness {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-2);
}
.closeout-last-updated {
  font-size: var(--font-size-xs);
  color: var(--color-text-secondary);
}
.closeout-stale-note {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-warning-strong);
  background: var(--color-warning-subtle);
  border: 1px solid var(--color-warning-subtle-border);
  border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-3);
  margin-bottom: var(--space-4);
}
.section-title,
.admin-subsection-title {
  /* UX12: admin.ts's ensureSettingsSectionJump scrolls a settings
     subsection's own heading to the TOP of the viewport (block: "start"),
     and the sticky jump control it built sits at that same top edge.
     Without this, the heading would land partly behind that control
     instead of below it. Sized to clear .admin-section-jump's own top
     offset plus its comfortable-touch-target height plus a little air. */
  scroll-margin-top: calc(var(--space-4) + var(--touch-target-comfortable) + var(--space-4));
}
.section-title {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-tertiary);
  margin: var(--space-8) 0 var(--space-3);
}
.section-title:first-of-type {
  margin-top: 0;
}
.closeout-subsection-title {
  margin-top: 0;
}
.section-count {
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  color: var(--color-warning-strong);
  background: var(--color-warning-subtle);
  border: 1px solid var(--color-warning-subtle-border);
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
  text-transform: none;
  letter-spacing: normal;
}
.figure-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--space-3);
}
.figure-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-1);
  padding: var(--space-4);
}
.figure-card-label {
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
}
.figure-card-value {
  font-size: var(--font-size-2xl);
  font-weight: var(--font-weight-bold);
  margin-top: var(--space-1);
}
.figure-card-note {
  font-size: var(--font-size-xs);
  color: var(--color-text-tertiary);
  margin-top: var(--space-2);
}
.badge-simulated {
  display: inline-flex;
  align-items: center;
  margin-top: var(--space-2);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  color: var(--color-info);
  background: var(--color-info-subtle);
  border: 1px solid var(--color-info-subtle-border);
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
}
.closeout-two-col {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-6);
}
@media (max-width: 860px) {
  .closeout-two-col {
    grid-template-columns: 1fr;
  }
}
/* CL7: four unresolved-state subsections (failed payments, pending
   payments, terminal registration, MyTime sync). auto-fit rather than a
   fixed column count so it wraps on its own at narrower widths, matching
   .figure-grid's own responsive approach. */
.closeout-three-col {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--space-6);
}
.list-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-1);
  overflow: hidden;
}
.list-card-empty {
  padding: var(--space-5);
  color: var(--color-text-tertiary);
  font-size: var(--font-size-sm);
  text-align: center;
}
.activity-row,
.unresolved-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--color-border);
}
.activity-row:last-child,
.unresolved-row:last-child {
  border-bottom: none;
}
.activity-name,
.unresolved-name {
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
}
.activity-detail,
.unresolved-detail {
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
  margin-top: 2px;
}
.activity-tag {
  flex-shrink: 0;
  white-space: nowrap;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  padding: 3px var(--space-2);
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  color: var(--color-warning-strong);
  background: var(--color-warning-subtle);
  border-color: var(--color-warning-subtle-border);
}
.unresolved-amount {
  text-align: right;
  flex-shrink: 0;
}
.unresolved-amount-value {
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-bold);
}
.unresolved-amount-detail {
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
  margin-top: 2px;
}
.flagged-reason {
  font-size: var(--font-size-sm);
  color: var(--color-text-primary);
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-warning-subtle-border);
}

/* ---------- ALPHA-12 multi-guest tickets and split payments ---------- */

/* Amendment 2026-08-22 (item 3): the old chips-row-across-the-top design
   is gone. Add Guest is now one small, single control inside the ticket
   rail; guests themselves render as section headers inside #ticket-lines
   (see .line-section-header below and main.ts's renderTicketLines), not
   as chips here. Hidden entirely while state.orderId is undefined; once a
   ticket is loaded, always visible, whether or not any guest has been
   named yet, so adding the first guest is still one tap. */
/* UX13: moved from its own full-width row below the header into the
   header row's own trailing chip group (index.html's own comment on
   #add-guest-row). flex-shrink: 0 so this chip group never yields its own
   width to .ticket-id beside it: .ticket-id already truncates (its own
   overflow/ellipsis rule), which is the right element to give ground,
   since a chip's own label being clipped would hide what it does. */
.add-guest-row {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  gap: var(--chrome-gap-tight);
}
.btn-add-guest {
  height: var(--chrome-row-height);
  white-space: nowrap;
  padding: 0 var(--space-3);
  border-radius: var(--radius-full);
  border: 1px dashed var(--color-border-strong);
  background: var(--color-surface);
  color: var(--color-accent);
  font-size: var(--font-size-chrome);
  font-weight: var(--font-weight-semibold);
  display: inline-flex;
  align-items: center;
  cursor: pointer;
}

/* ALPHA-12: which of the three pay modes a Confirm applies to, shown
   above the amount only for "pay my drinks" and equal-split (see
   index.html's own comment on #pay-confirm-scope). */
.pay-confirm-scope {
  padding: 0 var(--space-5);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-secondary);
}

/* Payments-track Slice 3: "Pay with" (Use saved card), same row-is-the-
   whole-touch-target shape .pay-confirm-scope's siblings use, tappable
   like .choice-option (opens the reusable choice sheet, see main.ts's
   openPayWithCardChoice). */
.pay-confirm-card-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: calc(100% - var(--space-5) * 2);
  margin: var(--space-2) var(--space-5) 0;
  min-height: var(--touch-target-min);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text-primary);
  cursor: pointer;
}
.pay-confirm-card-label {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-secondary);
}
.pay-confirm-card-value {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
}
.pay-confirm-manage-cards-link {
  display: block;
  margin: var(--space-2) var(--space-5) 0;
  background: none;
  border: none;
  padding: 0;
  color: var(--color-accent);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  text-align: left;
  cursor: pointer;
  min-height: var(--touch-target-min);
}

/* Payments-track Slice 3: card-on-file consent screen. */
.card-consent-guest {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-secondary);
  margin: 0 0 var(--space-2);
}
.card-consent-body {
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
  margin: 0 0 var(--space-4);
}

/* SIG1 (docs/build-briefs/SIG1-signature-and-receipt.md): the touch-drawn
   signature canvas and DEC-016's printed/emailed/none receipt choice. */
.signature-instructions {
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
  margin: 0 0 var(--space-3);
}
.signature-canvas {
  width: 100%;
  height: 240px;
  touch-action: none;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}
.signature-actions {
  display: flex;
  gap: var(--space-3);
  margin-top: var(--space-3);
}
.signature-actions .btn-secondary {
  flex: 1;
  min-height: var(--touch-target-min);
}
.signature-actions .btn-pay {
  flex: 2;
  min-height: var(--touch-target-min);
}
.receipt-choice-actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.receipt-choice-actions .btn-secondary {
  min-height: var(--touch-target-min);
}
.receipt-email-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-top: var(--space-3);
}
.receipt-email-input {
  min-height: var(--touch-target-min);
  font-size: var(--font-size-md);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}

/* Payments-track Slice 3: the saved-cards management list. Same row shape
   as .admin-terminal-row (a plain list, one action per row), reused here
   on the ring screen's own sheet system rather than the admin console's
   classes since the two screens never share styling state. */
.saved-card-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.saved-card-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  width: 100%;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5);
  min-height: var(--touch-target-comfortable);
}
.saved-card-info {
  min-width: 0;
  flex: 1;
}
.saved-card-name {
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight);
}
.saved-card-guest {
  margin-top: 2px;
  font-size: var(--font-size-sm);
  color: var(--color-text-tertiary);
}
/* The in-row remove confirm (main.ts's renderSavedCardRemoveConfirm):
   stacked, not the parent row's own side-by-side layout, so the message
   and both buttons each get full width rather than competing for the
   same row at touch-target width. */
.saved-card-row-confirm {
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-3);
}
.saved-card-row-confirm .saved-card-info {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
}

/* A plain labeled wrapper around one text input inside a sheet, matching
   .search-row's own padding/border rhythm without borrowing its
   search-specific name. */
.field-row {
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--color-border);
  flex-shrink: 0;
}

/* ---------- Reusable choice sheet (main.ts's openChoiceSheet) ----------
   One sheet, four callers: the pay-mode picker, the "pay my drinks" guest
   picker, the add-drink guest picker, and the equal-split progress list
   (a mix of tappable pending shares, rendered as .choice-option buttons,
   and plain non-interactive "Collected" rows, rendered as
   .choice-option-done divs so a completed share is never mistaken for a
   still-tappable one). */
.choice-note {
  margin: 0 var(--space-5) var(--space-3);
  padding: var(--space-3);
  border-radius: var(--radius-md);
  background: var(--color-surface-sunken);
  color: var(--color-text-secondary);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
}
.choice-list {
  overflow-y: auto;
  padding: 0 var(--space-5) var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.choice-option {
  min-height: var(--touch-target-comfortable);
  width: 100%;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border-strong);
  background: var(--color-surface);
  color: var(--color-text-primary);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 2px;
  text-align: left;
  cursor: pointer;
}
.choice-option-label {
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
}
.choice-option-sublabel {
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
}
/* A collected share (or, as of Amendment 2026-08-22 seventh, a guest with
   no outstanding balance in the "pay my drinks" picker): same shape as a
   tappable option so the list reads consistently, but never a <button>
   and visually marked done via the same success token pairing
   .state-btn.paid uses, never color alone (the sublabel's own literal
   "Collected"/"Paid"/"No drinks yet" text carries the state too). */
.choice-option-done {
  min-height: var(--touch-target-comfortable);
  width: 100%;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-success-subtle-border);
  background: var(--color-success-subtle);
  color: var(--color-success-strong);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 2px;
}
.choice-option-done .choice-option-sublabel {
  color: var(--color-success-strong);
}
/* Architect-gate CRITICAL correction ("abandoned split deadlock"),
   2026-08-21: the split-progress sheet's own "Cancel split" row, the exit
   an active plan never had before this fix. Same fixed semantic
   background/text token pairing .choice-option-done already establishes
   for "success," here for "danger" instead: never a component-surface/
   text-token pairing (design/README.md's own checked-pairs rule), and
   distinguished from an ordinary tappable share by more than color alone
   (its own literal "Cancel split" label plus a distinct sublabel, matching
   .choice-option-done's identical "never color alone" precedent). */
.choice-option-danger {
  border-color: var(--color-danger-subtle-border);
  background: var(--color-danger-subtle);
  color: var(--color-danger-strong);
}
.choice-option-danger .choice-option-sublabel {
  color: var(--color-danger-strong);
}
/* M4's refund picker rows reuse .choice-option and toggle .selected, the
   same accent-subtle pairing .liquor-row.selected established, with the
   same explicit accent-strong text (never the ambient theme text color)
   and never color alone: main.ts swaps the selected row's sublabel to a
   literal "Refunding ..." string alongside the visual change. */
.choice-option.selected {
  background: var(--color-accent-subtle);
  border-color: var(--color-accent-subtle-border);
}
.choice-option.selected .choice-option-label,
.choice-option.selected .choice-option-sublabel {
  color: var(--color-accent-strong);
}
/* The refund sheet's derived total: the sum of the selected rows' own
   server-supplied figures, restated where the confirm tap happens. */
.refund-total-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--space-2) var(--space-1);
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-primary);
}

/* PL1: the payment-link sheet's status line and its selectable url. A
   plain, touch-selectable block of text (a long-press on the floor's
   tablet selects it for copy) is sufficient this slice; no QR code. */
.payment-link-status {
  padding: var(--space-2) var(--space-1);
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-primary);
}
.payment-link-url {
  padding: var(--space-3) var(--space-2);
  margin: var(--space-1) 0 var(--space-3);
  border-radius: var(--radius-md);
  background: var(--color-surface-sunken);
  color: var(--color-accent-strong);
  font-size: var(--font-size-sm);
  word-break: break-all;
  user-select: text;
  -webkit-user-select: text;
}

/* ---------- Equal-split setup: share-count stepper ---------- */
.split-setup-body {
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
}
.split-setup-label {
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-primary);
}
.split-setup-stepper {
  display: flex;
  align-items: center;
  gap: var(--space-5);
}
.btn-stepper {
  width: var(--touch-target-comfortable);
  height: var(--touch-target-comfortable);
  border-radius: var(--radius-full);
  border: 1px solid var(--color-border-strong);
  background: var(--color-surface);
  color: var(--color-text-primary);
  font-size: var(--font-size-xl);
  font-weight: var(--font-weight-bold);
  cursor: pointer;
}
.split-setup-count {
  min-width: 56px;
  text-align: center;
  font-size: var(--font-size-2xl);
  font-weight: var(--font-weight-bold);
}
.split-setup-preview {
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
  text-align: center;
  min-height: 1.5em;
}

@media (max-width: 480px) {
  .tile-grid {
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  }
  .topbar {
    padding: var(--space-3);
  }
  .ticket-header,
  .ticket-lines,
  .ticket-totals,
  .ticket-actions {
    padding-left: var(--space-3);
    padding-right: var(--space-3);
  }
}

/* ---------- Inventory track (track/inventory) Slice 2: Counts ----------
   The Settings > Counts panel (section list, start/resume, history) and
   the count-session sheet (walk + review) and history-detail sheet. Built
   entirely on the same .scrim, .sheet, .admin-form-body, and
   .admin-chip-select classes every other admin sheet in this file
   already uses; nothing below introduces a new visual language. */

.admin-count-section-chip {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target-min);
  padding: 0 var(--space-4);
  border-radius: var(--radius-full);
  border: 1px solid var(--color-accent-subtle-border);
  background: var(--color-accent-subtle);
  color: var(--color-accent-strong);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  cursor: pointer;
}
.admin-count-section-chip.inactive {
  border-color: var(--color-border);
  background: var(--color-surface-sunken);
  color: var(--color-text-tertiary);
  font-weight: var(--font-weight-medium);
}

.admin-count-history-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}
.admin-count-history-row {
  display: flex;
  align-items: center;
  min-height: var(--touch-target-comfortable);
  padding: 0 var(--space-4);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text-primary);
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-medium);
  text-align: left;
  cursor: pointer;
}

/* T14 (docs/backlog.md): the MyTime ambiguous-reconciliation queue. */
.admin-reconciliation-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-top: var(--space-3);
}
.admin-reconciliation-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border);
  background: var(--color-surface);
}
.admin-reconciliation-row-header {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.admin-reconciliation-guest-name {
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-primary);
}
.admin-reconciliation-reason {
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
}
.admin-reconciliation-matches {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* Count-session sheet: progress header. */
.admin-count-progress {
  margin: var(--space-2) 0 0;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-secondary);
}
.admin-count-progress-track {
  margin-top: var(--space-2);
  height: 6px;
  border-radius: var(--radius-full);
  background: var(--color-surface-sunken);
  overflow: hidden;
}
.admin-count-progress-fill {
  height: 100%;
  width: 0%;
  border-radius: var(--radius-full);
  background: var(--color-accent);
  transition: width var(--motion-base) var(--motion-easing-standard);
}

/* INV2 (docs/backlog.md, PRD P0.7): assigned counter, reassign, and
   blind/visible mode, in the shared sheet header. */
.admin-count-governance-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-2);
  flex-wrap: wrap;
}
.admin-count-assigned-counter {
  margin: 0;
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
  flex: 1 1 auto;
}
#admin-count-reassign-panel {
  margin-top: var(--space-2);
}

/* INV2: the tap-a-tenth partial-bottle control, Backbar's own named
   precedent for this exact interaction. */
.admin-count-bottle-stepper {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: var(--space-2) 0;
}
#admin-count-bottle-stepper-value {
  min-width: 2.5rem;
  text-align: center;
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-semibold);
}

/* Count-session sheet: the walk view's one-product-at-a-time card. Large,
   numeric-pad-friendly entry per design/principles.md's "words read in
   under two seconds" and CLAUDE.md's touch-only inputmode rule: the
   quantity field is the biggest, most prominent control on this card. */
.admin-count-product-card {
  margin-top: var(--space-4);
  padding: var(--space-5);
  border-radius: var(--radius-lg);
  border: 1px solid var(--color-border);
  background: var(--color-surface-sunken);
}
.admin-count-product-name {
  display: block;
  font-size: var(--font-size-xl);
  font-weight: var(--font-weight-bold);
  color: var(--color-text-primary);
}
.admin-count-product-status {
  margin: var(--space-1) 0 var(--space-4);
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
}
.admin-count-quantity-input {
  width: 100%;
  height: var(--touch-target-primary);
  margin-bottom: var(--space-3);
  border-radius: var(--radius-lg);
  border: 1px solid var(--color-border-strong);
  background: var(--color-surface);
  color: var(--color-text-primary);
  font-size: var(--font-size-2xl);
  font-weight: var(--font-weight-bold);
  text-align: center;
}
.admin-count-quantity-input:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
}

.admin-count-footer-row {
  display: flex;
  gap: var(--space-3);
}
.admin-count-footer-row .btn-primary,
.admin-count-footer-row .btn-secondary {
  flex: 1;
}
.admin-count-review-link {
  margin-top: var(--space-2);
}

/* Review and history-detail: the shared grouped variance list. */
.admin-count-review-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-2);
}
.admin-count-review-section-heading {
  margin: var(--space-4) 0 var(--space-1);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-tertiary);
}
.admin-count-review-section-heading:first-child {
  margin-top: 0;
}
.admin-count-review-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border);
  background: var(--color-surface);
}
.admin-count-review-row-name {
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-primary);
}
.admin-count-review-row-detail {
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
}

@media (max-width: 900px) {
  .admin-count-product-card {
    padding: var(--space-4);
  }
  .admin-count-quantity-input {
    height: var(--touch-target-comfortable);
    font-size: var(--font-size-xl);
  }
}

/* ---------- U1/U3: the three-tab Edit User modal ----------
   An ADMIN surface, run from a laptop per CLAUDE.md's scoped touch-only
   rule (owner ruling 2026-08-27), so a dense grid and hover affordances
   are legitimate here. Every control is still a real, tappable target at
   the same sizes the rest of this stylesheet uses, because the same
   screen has to stay usable on a tablet's software keyboard. */
.admin-user-edit-sheet {
  max-width: 760px;
  max-height: min(860px, 94vh);
}
.admin-user-edit-tabs {
  margin-top: var(--space-4);
  margin-bottom: 0;
}
.admin-user-edit-footer {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.admin-user-edit-footer-actions {
  margin-left: auto;
  display: flex;
  gap: var(--space-3);
}
.admin-user-active-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--touch-target-comfortable);
  cursor: pointer;
}
.admin-user-active-toggle-label {
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
}
.admin-user-active-note {
  font-size: var(--font-size-xs);
  color: var(--color-text-tertiary);
  max-width: 22em;
}
.admin-user-location-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.admin-user-location-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--touch-target-comfortable);
  padding: 0 var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-sunken);
}
.admin-user-location-name {
  font-weight: var(--font-weight-semibold);
}

.admin-permission-toolbar {
  display: flex;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
  flex-wrap: wrap;
}
.admin-permission-grid {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.admin-permission-empty {
  color: var(--color-text-secondary);
  font-size: var(--font-size-sm);
}
.admin-permission-group {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.admin-permission-group-header {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  min-height: var(--touch-target-comfortable);
  padding: var(--space-2) var(--space-3);
  border: none;
  background: var(--color-surface-sunken);
  color: var(--color-text-primary);
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-bold);
  text-align: left;
  cursor: pointer;
}
.admin-permission-group-caret {
  width: 1em;
  flex-shrink: 0;
  color: var(--color-text-secondary);
}
.admin-permission-group-count {
  margin-left: auto;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  color: var(--color-text-secondary);
}
/* The GROUP rollup. Deliberately a separate control from the leaf
   three-way picker below: this one is a read-only summary of the
   children ("all the same" or "mixed"), never something you can set.
   Conflating the two is the main design risk U3 names. */
.admin-permission-rollup {
  padding: 0 var(--space-2);
  border-radius: var(--radius-full);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  border: 1px solid var(--color-border-strong);
  color: var(--color-text-secondary);
  background: var(--color-surface);
  white-space: nowrap;
}
.admin-permission-rollup-mixed {
  border-color: var(--color-accent-strong);
  color: var(--color-accent-strong);
}
.admin-permission-rows {
  display: flex;
  flex-direction: column;
}
.admin-permission-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3);
  border-top: 1px solid var(--color-border);
  flex-wrap: wrap;
}
.admin-permission-row-main {
  flex: 1 1 20em;
  min-width: 0;
}
.admin-permission-row-label {
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
}
.admin-permission-row-id {
  font-size: var(--font-size-xs);
  color: var(--color-text-tertiary);
  word-break: break-all;
}
.admin-permission-row-controls {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}
/* The source badge U3 calls "the one place not to copy the reference":
   without it an Admin cannot tell whether access came from a role or was
   set on this one person. */
.admin-permission-source {
  padding: 0 var(--space-2);
  border-radius: var(--radius-full);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  border: 1px solid var(--color-border-strong);
  color: var(--color-text-secondary);
  white-space: nowrap;
}
.admin-permission-source-individual {
  border-color: var(--color-accent-strong);
  color: var(--color-accent-strong);
}
.admin-permission-planned {
  font-size: var(--font-size-xs);
  color: var(--color-text-tertiary);
}
.admin-permission-capped {
  font-size: var(--font-size-xs);
  color: var(--color-text-tertiary);
  max-width: 20em;
}

/* DSC5: the discount attribute-scope editor.
   FOUR separately bordered, separately headed groups on their own row
   below the discount's own fields. The separation is not decoration: "what
   you stock" and "how it is sold" share two of their values and differ on
   the rest, so a merged list would look almost right and price the wrong
   lines. Each group keeps its own box and its own legend at every width. */
.admin-discount-scope {
  flex: 1 1 100%;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  width: 100%;
}
.admin-discount-scope-summary {
  flex: 1 1 100%;
  margin: 0;
  color: var(--color-text-tertiary);
  font-size: var(--font-size-sm);
}
.admin-discount-scope-group {
  flex: 1 1 12rem;
  min-width: 10rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-3) var(--space-3);
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.admin-discount-scope-group legend {
  padding: 0 var(--space-2);
  color: var(--color-text-secondary);
  font-weight: 600;
  font-size: var(--font-size-sm);
}
.admin-discount-scope-hint {
  margin: 0 0 var(--space-1);
  color: var(--color-text-tertiary);
  font-size: var(--font-size-xs);
}

/* RT1: the role-template list in People and access. Rows are real touch
   targets at the --touch-target-comfortable tier, the same tier every
   other tappable list row in Settings uses, so e2e/usability.spec.ts's
   44px sweep passes for the same reason the rest of the panel does
   rather than by coincidence. */
.admin-role-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}

.admin-role-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  width: 100%;
  min-height: var(--touch-target-comfortable);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text-primary);
  font-size: var(--font-size-base);
  text-align: left;
  cursor: pointer;
}

/* RT2: the row splits into an open-the-editor button plus a switch-off
   control, so the row itself is no longer the button. The open half
   carries the touch-target floor, since that is what a finger lands on. */
.admin-role-row-open {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex: 1;
  min-height: var(--touch-target-comfortable);
  padding: 0;
  border: none;
  background: none;
  color: inherit;
  font-size: inherit;
  text-align: left;
  cursor: pointer;
}

/* A switched-off role stays visible and readable rather than vanishing,
   the same retire-never-hide treatment the deactivated-item lists use:
   an Admin has to be able to find it to switch it back on. */
.admin-role-row-retired .admin-role-row-name {
  color: var(--color-text-secondary);
}

.admin-role-row:hover {
  border-color: var(--color-border-strong);
}

.admin-role-row-name {
  font-weight: var(--font-weight-medium);
}

.admin-role-row-count {
  color: var(--color-text-secondary);
  font-size: var(--font-size-sm);
}

/* AU8 (docs/backlog.md): the manager-exceptions queue's floor-facing
   surface. Open Tabs' own badge count, the ticket rail's own banner, and
   the shared global sheet's rows and inline resolve panel. */
.badge-exception-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  margin-left: var(--space-2);
  padding: 0 6px;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  color: var(--color-warning-strong);
  background: var(--color-warning-subtle);
  border: 1px solid var(--color-warning-subtle-border);
  border-radius: var(--radius-full);
}

.ticket-manager-exception-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin: 0 0 var(--space-3);
  padding: var(--space-3);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-warning-subtle-border);
  background: var(--color-warning-subtle);
}
.ticket-manager-exception-text {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-warning-strong);
}

.manager-exceptions-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.manager-exception-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border);
  background: var(--color-surface);
}
.manager-exception-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.manager-exception-tab-name {
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-primary);
}
.manager-exception-amount {
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  color: var(--color-warning-strong);
}
.manager-exception-kind {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-text-primary);
}
.manager-exception-help {
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
}
.manager-exception-age {
  font-size: var(--font-size-xs);
  color: var(--color-text-tertiary);
}
.manager-exception-resolve-panel {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-2);
  padding-top: var(--space-2);
  border-top: 1px solid var(--color-border);
}
.manager-exception-note-input {
  width: 100%;
  min-height: var(--touch-target-primary);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border-strong);
  background: var(--color-surface);
  color: var(--color-text-primary);
  font-size: var(--font-size-sm);
}
.manager-exception-resolve-actions {
  display: flex;
  gap: var(--space-2);
}
.manager-exception-resolve-actions .btn-primary,
.manager-exception-resolve-actions .btn-secondary {
  flex: 1;
}

/* PO6 Slice 1 (docs/adr/ADR-003-purchasing-and-receiving-surfaces.md;
   docs/build-briefs/PO6-S1-order-list-and-detail.md). Read-only purchase
   order list and detail, mirroring .admin-user-row/.admin-user-list's own
   row-per-record shape rather than a <table>, matching every other list
   in this codebase (see admin.ts's own vendor/user lists). */
.purchasing-order-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.purchasing-order-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
  width: 100%;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5);
  min-height: var(--touch-target-comfortable);
}
.purchasing-order-vendor {
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-semibold);
  flex: 2;
  min-width: 160px;
}
.purchasing-order-status {
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
  flex: 1;
  min-width: 140px;
}
.purchasing-order-line-count,
.purchasing-order-estimated-total {
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
  flex: 1;
  min-width: 100px;
}
.purchasing-order-detail-summary {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-4);
  margin: var(--space-4) 0;
}
.purchasing-order-detail-summary dt {
  font-size: var(--font-size-xs);
  color: var(--color-text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
.purchasing-order-detail-summary dd {
  margin: 0;
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-semibold);
}
.purchasing-status-history-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.purchasing-status-history-row {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
}
.purchasing-status-history-headline {
  font-weight: var(--font-weight-semibold);
}
.purchasing-status-history-reason,
.purchasing-status-history-exception-reason,
.purchasing-status-history-resolution {
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
}
.purchasing-status-history-variance {
  margin: var(--space-2) 0 0;
  padding-left: var(--space-4);
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
}

/* PO6-S2-F1: the order detail's own line list, mirroring
   .purchasing-order-row's shape one level down. */
.purchasing-order-line-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.purchasing-order-line-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
  width: 100%;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
}
.purchasing-order-line-product {
  font-weight: var(--font-weight-semibold);
  flex: 2;
  min-width: 160px;
}
.purchasing-order-line-quantity,
.purchasing-order-line-unit-cost,
.purchasing-order-line-extended-cost {
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
  flex: 1;
  min-width: 100px;
}

/* PO6 Slice 5 (docs/adr/ADR-003-purchasing-and-receiving-surfaces.md,
   Decision 4 slice 5; docs/build-briefs/PO6-S5-corrections-and-
   exceptions.md). Each order line's own nested "Receipts" list, and the
   inline per-receipt correction form. */
.purchasing-order-line-receipts {
  flex-basis: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-2);
  padding-top: var(--space-2);
  border-top: 1px solid var(--color-border);
}
.purchasing-order-line-receipt-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  font-size: var(--font-size-sm);
}
.purchasing-order-line-receipt-summary {
  color: var(--color-text-secondary);
  flex: 1;
  min-width: 160px;
}
.purchasing-order-line-receipt-correction-form {
  flex-basis: 100%;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-2);
}
.purchasing-order-line-receipt-correction-quantity-input,
.purchasing-order-line-receipt-correction-cost-input {
  width: 110px;
  flex: 0 0 auto;
}
.purchasing-order-line-receipt-correction-reason-input {
  flex: 1 1 200px;
}
.purchasing-order-line-receipt-correction-error,
.purchasing-order-line-receipt-correction-status {
  flex-basis: 100%;
  font-size: var(--font-size-sm);
  color: var(--color-danger-strong);
}
.purchasing-order-line-receipt-correction-approval {
  flex-basis: 100%;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

/* PO6 Slice 2 (docs/adr/ADR-003-purchasing-and-receiving-surfaces.md;
   docs/build-briefs/PO6-S2-cart-and-placement.md). The cart screen:
   proposals, product search results, and cart lines grouped by vendor.
   Mirrors .purchasing-order-list/.purchasing-order-row's own row-per-
   record shape. */
.purchasing-cart-proposal-list,
.purchasing-cart-product-results,
.purchasing-cart-vendor-groups,
.purchasing-cart-placed-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}
.purchasing-cart-proposal-row,
.purchasing-cart-product-result-row,
.purchasing-cart-placed-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
  width: 100%;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-3) var(--space-4);
  min-height: var(--touch-target-comfortable);
}
.purchasing-cart-proposal-product,
.purchasing-cart-product-result-name,
.purchasing-cart-placed-vendor {
  font-weight: var(--font-weight-semibold);
  flex: 2;
  min-width: 160px;
}
.purchasing-cart-proposal-sources,
.purchasing-cart-proposal-onhand,
.purchasing-cart-proposal-par,
.purchasing-cart-proposal-suggested,
.purchasing-cart-placed-status,
.purchasing-cart-placed-total {
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
  flex: 1;
  min-width: 100px;
}

/* UX16 (DEC-058) criterion 2: the "Add all below par to cart" button
   sits beside the "Draft proposals" heading rather than below it, the
   same header/action row shape .admin-panel-header already uses. */
.purchasing-cart-proposals-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
}
.purchasing-cart-proposals-header .section-title {
  margin: 0;
}

/* One card per vendor, so the grouping placement will produce -- one
   purchase order per card -- is visible before any of them is placed
   (ADR-003 Decision 4, Slice 2). */
.purchasing-cart-vendor-group {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5);
}
.purchasing-cart-vendor-group-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}
.purchasing-cart-vendor-group-name {
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-semibold);
}
.purchasing-cart-vendor-group-status {
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
}
.purchasing-cart-vendor-group-status--blocked {
  color: var(--color-danger-strong);
  font-weight: var(--font-weight-bold);
}
.purchasing-cart-vendor-group-lines {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}
.purchasing-cart-line-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-3);
}
.purchasing-cart-line-product {
  font-weight: var(--font-weight-semibold);
  flex: 2;
  min-width: 160px;
}
.purchasing-cart-line-quantity-input {
  width: 90px;
  flex: 0 0 auto;
}
.purchasing-cart-line-unit-cost {
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
  flex: 1;
  min-width: 100px;
}
.purchasing-cart-line-blockers {
  flex-basis: 100%;
  font-size: var(--font-size-sm);
  color: var(--color-danger-strong);
}

/* PO6 Slice 4 (docs/adr/ADR-003-purchasing-and-receiving-surfaces.md;
   docs/build-briefs/PO6-S4-receiving.md). The receiving screen. FLOOR
   RULE (DEC-053: chrome compressed first, content last): one compact
   banner line each for an open exception or a fully-received order, and
   every line's own entry row is a single wrapping flex row rather than a
   grid of labeled fields, so a small tablet viewport spends its height on
   the lines themselves. */
.purchasing-receiving-exception {
  color: var(--color-warning-strong);
  background: var(--color-warning-subtle);
  border: 1px solid var(--color-warning-subtle-border);
  border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-4);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
}
.purchasing-receiving-fully-received {
  color: var(--color-success-strong);
  background: var(--color-success-subtle);
  border: 1px solid var(--color-success-subtle-border);
  border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-4);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
}
.purchasing-receiving-line-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.purchasing-receiving-line-row {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5);
}
.purchasing-receiving-line-header {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-3);
  margin-bottom: var(--space-2);
}
.purchasing-receiving-line-product {
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-semibold);
}
.purchasing-receiving-line-quantities,
.purchasing-receiving-line-receipt-count {
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
}
.purchasing-receiving-line-closed {
  color: var(--color-success-strong);
  background: var(--color-success-subtle);
  border: 1px solid var(--color-success-subtle-border);
  border-radius: var(--radius-sm);
  padding: 2px var(--space-2);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
}
.purchasing-receiving-line-entry {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-2);
}
.purchasing-receiving-line-quantity-input {
  width: 110px;
  flex: 0 0 auto;
}
.purchasing-receiving-line-cost-input {
  width: 110px;
  flex: 0 0 auto;
}
.purchasing-receiving-line-error {
  flex-basis: 100%;
  font-size: var(--font-size-sm);
  color: var(--color-danger-strong);
}

/* PO6 Slice 6 (docs/adr/ADR-003-purchasing-and-receiving-surfaces.md;
   docs/build-briefs/PO6-S6-outside-system-orders.md). Recording an order
   placed outside the product. FLOOR RULE like .purchasing-receiving-*
   above: comfortable touch targets throughout, the entry panel a single
   wrapping flex row rather than a labeled-field grid. */
.purchasing-outside-order-product-results,
.purchasing-outside-order-recorded-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}
.purchasing-outside-order-product-result-row,
.purchasing-outside-order-recorded-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
  width: 100%;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-3) var(--space-4);
  min-height: var(--touch-target-comfortable);
}
.purchasing-outside-order-product-result-name,
.purchasing-outside-order-recorded-product {
  font-weight: var(--font-weight-semibold);
  flex: 2;
  min-width: 160px;
}
.purchasing-outside-order-recorded-quantity,
.purchasing-outside-order-recorded-cost,
.purchasing-outside-order-recorded-reference {
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
  flex: 1;
  min-width: 100px;
}
.purchasing-outside-order-entry {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5);
  margin-bottom: var(--space-5);
}
.purchasing-outside-order-entry-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}
.purchasing-outside-order-entry-product {
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-semibold);
}
.purchasing-outside-order-entry > .field-input,
.purchasing-outside-order-entry > .admin-chip-select {
  margin-top: var(--space-3);
}
.purchasing-outside-order-quantity-input,
.purchasing-outside-order-cost-input {
  width: 140px;
}
.purchasing-outside-order-entry-error {
  font-size: var(--font-size-sm);
  color: var(--color-danger-strong);
  margin-top: var(--space-2);
}
#purchasing-outside-order-submit {
  margin-top: var(--space-3);
}
