/* ============================================================================
   app.css -- the booking platform's one design system.

   Every screen this platform serves (the public booking page, the operator
   console, sign-in, invite accept/invalid, change password) loads this file
   and only this file for styling. No page keeps its own ad-hoc <style>
   block of rules this file could hold instead -- see the task report.

   LIGHT BY DEFAULT, client's colours by default (AOC.md's own rule, carried
   over on purpose): the tokens below are the same light neutral palette and
   proportions as aoc/aoc.css's own :root -- the operator console look Greg
   has already shown clients and that they signed off. This file is not a
   new look; it is that look, written for a plain HTML/CSS/JS stack with no
   build step instead of AOC's single scoped module.

   WHITE-LABEL, IN FOUR VARIABLES: a tenant's own brand colour is the ONE
   variable --brand. Every other brand-flavoured token below (the darker
   fill used behind white button text, the pale "ghost" tint behind a badge)
   is DERIVED from --brand with color-mix(), so setting one custom property
   re-themes every button, focus accent and badge without a second ruleset
   anywhere -- "a few variables, not a fork" (the task brief's own words).
   A page sets it by overriding --brand on :root, e.g.
     document.documentElement.style.setProperty('--brand', '#FF5A1F');
   See public/book.js and public/ops.js's own BRAND_COLOUR map for the one
   real example wired up (Predator's own accent) and the task report for
   why that map is a stand-in for a `brand.colour` column that does not
   exist in this database yet -- adding one is outside this task's hard
   constraint of no new migration beyond the owner-invite rule.

   CONTRAST: every text/background pairing below was checked against WCAG
   2.1's 4.5:1 (normal text) / 3:1 (large text, 18px+/14px+bold, and
   non-text UI like focus rings and borders) by computing relative
   luminance from the actual rendered colours, not by eyeballing hex codes
   -- see the task report for the script and its output. --brand itself is
   NEVER used as a background behind light text (its contrast depends on
   whatever hue a future tenant picks, and some bright, light accent colours
   -- Predator's own orange among them -- fail 4.5:1 as a direct fill).
   --brand-strong, a color-mix()'d darkening of --brand, is what fills a
   primary button or a selected pill, and is dark enough that white text on
   it clears 4.5:1 for both the default red and Predator's orange, checked
   by the same script. --brand itself is reserved for borders, links, focus
   accents and dark-text-on-light "ghost" badges, where the direct colour
   is what a brand override should actually change.
   ========================================================================= */

:root {
  color-scheme: light;

  /* ---------------------------------------------------------- brand (1 var) */
  --brand: #C0271F;                                            /* tenant override point */
  --brand-strong: color-mix(in srgb, var(--brand) 68%, black); /* fills behind white text */
  --brand-strong-hover: color-mix(in srgb, var(--brand) 58%, black);
  --brand-ghost: color-mix(in srgb, var(--brand) 11%, white);  /* tint behind brand text */
  --on-brand: #FFFFFF;

  /* -------------------------------------------------------------- neutrals */
  --bg: #F3F4F3;
  --surface: #FFFFFF;
  --surface-2: #ECEEEC;
  --sunk: #E7E9E7;
  --line: #DEE1DE;
  --line-soft: #EBEDEB;
  --line-strong: #B9BEB9;
  --text: #11151C;
  --text-2: #4B5259;
  --text-3: #6B727A;

  /* --------------------------------------------------------------- semantic */
  --go: #1E7A43;      --go-ghost: #DFF0E6;
  --warn: #8A5806;    --warn-ghost: #FBEEDA;
  --stop: #AA2318;    --stop-ghost: #FBE4E1;
  --info: #205080;    --info-ghost: #E2ECF6;
  --on-semantic: #FFFFFF;

  /* ------------------------------------------------------------------ focus
     A fixed, brand-independent colour, not derived from --brand: a focus
     ring has to stay visible no matter which hue a tenant picks, including
     a tenant colour that is itself pale or low-contrast. The white gap
     (box-shadow's first layer) is what makes it read on a brand-coloured
     button too, light or dark. */
  --focus: #154BD6;

  /* ---------------------------------------------------------- type + font */
  /* AOC's own faces (aoc/aoc.css --aoc-ui, --aoc-mono): CONTRACT.md §2 rule 1 */
  --font-ui: "Barlow Semi Condensed", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  --text-xs: 12px;
  --text-sm: 13.5px;
  --text-base: 16px;
  --text-md: 17px;
  --text-lg: 20px;
  --text-xl: 24px;
  --text-2xl: 30px;

  /* -------------------------------------------------------- spacing scale */
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px;
  --sp-5: 20px; --sp-6: 24px; --sp-7: 32px; --sp-8: 40px; --sp-9: 48px;

  /* ------------------------------------------------------------------ radii */
  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 16px;
  --radius-pill: 999px;

  /* ----------------------------------------------------------------- shadow */
  --shadow-sm: 0 1px 2px rgba(17,21,28,.07);
  --shadow-md: 0 1px 2px rgba(17,21,28,.06), 0 10px 24px -14px rgba(17,21,28,.38);
  --shadow-lg: 0 4px 14px rgba(17,21,28,.10), 0 28px 56px -28px rgba(17,21,28,.45);

  --transition: 150ms ease;
  --shell-sidebar-w: 236px;
  --shell-topbar-h: 58px;
}

/* ============================================================================
   RESET + BASE
   ========================================================================= */

*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
html {
  overflow-x: hidden;
  max-width: 100%;
  scroll-padding-top: var(--sp-4);
  -webkit-text-size-adjust: 100%;
}
body {
  overflow-x: hidden;
  max-width: 100%;
  min-height: 100vh;
  font-family: var(--font-ui);
  font-size: var(--text-base);
  line-height: 1.5;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4 { margin: 0 0 var(--sp-2); font-weight: 700; line-height: 1.1; letter-spacing: .01em; text-transform: uppercase; color: var(--text); }
h1 { font-size: var(--text-xl); }
h2 { font-size: var(--text-lg); }
h3 { font-size: var(--text-md); }
h4 { font-size: var(--text-base); }
p { margin: 0 0 var(--sp-2); }
a { color: var(--brand-strong); text-decoration: underline; text-decoration-color: color-mix(in srgb, var(--brand-strong) 40%, transparent); text-underline-offset: 2px; }
a:hover { text-decoration-color: currentColor; }
ul, ol { margin: 0; padding-left: 1.2em; }
img, svg { max-width: 100%; display: block; }
[hidden] { display: none !important; }
small, .muted { color: var(--text-2); }
.tiny { font-size: var(--text-xs); color: var(--text-3); }
.mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
code { font-family: var(--font-mono); background: var(--surface-2); padding: 1px 5px; border-radius: 4px; font-size: .92em; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.skip-link {
  position: absolute; left: var(--sp-3); top: -48px; z-index: 300;
  background: var(--text); color: #fff; padding: var(--sp-2) var(--sp-3);
  border-radius: var(--radius-sm); transition: top var(--transition);
}
.skip-link:focus-visible { top: var(--sp-3); }

button, input, select, textarea { font: inherit; color: inherit; }

/* Every interactive element gets the SAME visible focus treatment,
   regardless of what it sits on top of (a plain surface or a brand-filled
   button) -- the inner ring always touches the element; the outer "gap"
   borrows the local background via a solid colour close to it so the ring
   reads as a halo rather than a hard double outline. */
:focus { outline: none; }
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible,
textarea:focus-visible, [tabindex]:focus-visible, summary:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--surface), 0 0 0 4.5px var(--focus);
  border-radius: var(--radius-sm);
}
.btn:focus-visible, .pill:focus-visible, .mb-pill:focus-visible { border-radius: var(--radius-md); }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}

/* ============================================================================
   LAYOUT PRIMITIVES
   ========================================================================= */

.stack { display: flex; flex-direction: column; gap: var(--sp-4); }
.cluster { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }
.page { max-width: 640px; margin: 0 auto; padding: var(--sp-4) var(--sp-4) var(--sp-9); }
.page-narrow { max-width: 420px; margin: 0 auto; padding: var(--sp-6) var(--sp-4) var(--sp-9); }

/* ----------------------------------------------------------- page header
   "a page header pattern" (task brief, design-system component #1): a
   title, optional supporting text, optional trailing actions, used the
   same way on every screen that has one. */
.page-header {
  display: flex; flex-wrap: wrap; gap: var(--sp-3); align-items: flex-end;
  justify-content: space-between; margin-bottom: var(--sp-5);
}
.page-header h1 { margin: 0; }
.page-header .page-header-sub { color: var(--text-2); font-size: var(--text-sm); margin-top: 2px; }
.page-header-actions { display: flex; gap: var(--sp-2); flex-wrap: wrap; }

/* ============================================================================
   BUTTONS
   buttons (primary, secondary, danger, disabled) -- one base class plus
   modifiers. Existing screens keep every original id/class (what the test
   suite and the server's own front-end-rule gate key off); .btn and its
   modifiers are added alongside them in the markup, purely additive.
   ========================================================================= */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  min-height: 44px; padding: 0 var(--sp-4);
  font-size: var(--text-base); font-weight: 600; line-height: 1.2;
  border-radius: var(--radius-md); border: 1.5px solid transparent;
  cursor: pointer; white-space: nowrap; text-decoration: none;
  transition: background-color var(--transition), border-color var(--transition), opacity var(--transition), transform var(--transition);
}
.btn:active { transform: translateY(1px); }
.btn-block { display: flex; width: 100%; }
.btn-sm { min-height: 36px; padding: 0 var(--sp-3); font-size: var(--text-sm); }

.btn-primary { background: var(--brand-strong); border-color: var(--brand-strong); color: var(--on-brand); }
.btn-primary:hover { background: var(--brand-strong-hover); border-color: var(--brand-strong-hover); }
/* .provider-btn is an <a>, inline by default -- min-height has no effect
   on an inline element, so it gets one self-contained rule (not .btn,
   a class it does not carry) rather than splitting colour and box
   properties across two rules, which is exactly how border-color was
   lost to a later shorthand `border:` once before. Every value matches
   .btn-primary / .auth-method form button[type="submit"] exactly, which
   is what test/password-auth.test.mjs's "visually equal peers" check
   (acceptance 8) verifies by comparing the three cards' CONTROLS'
   computed styles, not by reading these rules. */
.provider-btn {
  display: flex; align-items: center; justify-content: center; gap: var(--sp-2);
  min-height: 44px; padding: 0 var(--sp-4); width: 100%;
  border-radius: var(--radius-md); border: 1.5px solid var(--brand-strong);
  background: var(--brand-strong); color: var(--on-brand);
  font-size: var(--text-base); font-weight: 600; text-decoration: none; text-align: center; cursor: pointer;
}
.provider-btn:hover { background: var(--brand-strong-hover); border-color: var(--brand-strong-hover); }

.btn-secondary {
  background: var(--surface); border-color: var(--line-strong); color: var(--text);
}
.btn-secondary:hover { background: var(--surface-2); }

.btn-danger {
  background: var(--surface); border-color: var(--stop); color: var(--stop);
}
.btn-danger:hover { background: var(--stop-ghost); }

.btn-ghost { background: transparent; border-color: transparent; color: var(--text-2); }
.btn-ghost:hover { background: var(--surface-2); color: var(--text); }

.btn[disabled], .btn[aria-disabled="true"] { opacity: .5; cursor: not-allowed; pointer-events: none; }

/* Concrete, pre-existing selectors get the same treatment directly, as a
   defensive second layer -- see the comment above: belt and braces if a
   button is ever missed when adding the .btn classes themselves. */
#confirm-btn, #mb-confirm-btn, #invite-submit-btn { background: var(--brand-strong); border: 1.5px solid var(--brand-strong); color: var(--on-brand); }
#confirm-btn:hover, #mb-confirm-btn:hover, #invite-submit-btn:hover { background: var(--brand-strong-hover); }
#confirm-btn[disabled], #mb-confirm-btn[disabled], #invite-submit-btn[disabled] { background: var(--line-strong); border-color: var(--line-strong); opacity: .7; cursor: not-allowed; }
.cancel-btn, .revoke-btn, .checkin-all-btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px; padding: 0 var(--sp-3); border-radius: var(--radius-md);
  font-size: var(--text-sm); font-weight: 600; cursor: pointer;
}
.cancel-btn, .revoke-btn { background: var(--surface); border: 1.5px solid var(--stop); color: var(--stop); }
.cancel-btn:hover, .revoke-btn:hover { background: var(--stop-ghost); }
.cancel-btn[disabled], .revoke-btn[disabled] { opacity: .5; cursor: default; border-color: var(--line-strong); color: var(--text-3); }
.checkin-all-btn { background: var(--surface); border: 1.5px solid var(--go); color: var(--go); }
.checkin-all-btn:hover { background: var(--go-ghost); }
.checkin-all-btn[disabled] { opacity: .6; cursor: default; border-color: var(--line-strong); color: var(--text-3); }

/* ------------------------------------------------------------- pill choices
   The booking flow's choice controls (date, offering, session type,
   activity): a bigger tap target than an ordinary button, built to carry
   a server-given reason when disabled. */
button.pill, button.mb-pill {
  display: inline-flex; flex-direction: column; align-items: flex-start; gap: 2px;
  min-height: 48px; padding: var(--sp-2) var(--sp-4);
  border-radius: var(--radius-md); border: 1.5px solid var(--line-strong);
  background: var(--surface); color: var(--text);
  font-size: var(--text-base); text-align: left; max-width: 100%;
  white-space: normal; word-break: break-word; cursor: pointer;
  transition: background-color var(--transition), border-color var(--transition), color var(--transition);
}
button.pill:hover, button.mb-pill:hover { border-color: var(--brand-strong); }
button.pill[aria-pressed="true"], button.mb-pill[aria-pressed="true"] {
  background: var(--brand-strong); border-color: var(--brand-strong); color: var(--on-brand);
}
button.pill[disabled], button.mb-pill[disabled] {
  background: var(--surface-2); border-color: var(--line); color: var(--text-3); cursor: not-allowed;
}
.pill-reason, .mb-pill-reason { display: block; font-size: var(--text-xs); color: var(--text-3); font-weight: 500; }
button.pill[aria-pressed="true"] .pill-reason, button.mb-pill[aria-pressed="true"] .mb-pill-reason { color: color-mix(in srgb, var(--on-brand) 80%, transparent); }
.pill-list, .mb-pill-list { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

/* -------------------------------------------------------------- stepper
   The party-size +/- control: 44px square targets either side of the
   number, same shape on the public page and the manual-booking panel. */
.stepper { display: flex; align-items: center; gap: var(--sp-3); }
.stepper button {
  width: 44px; height: 44px; flex: none; border-radius: var(--radius-md);
  border: 1.5px solid var(--line-strong); background: var(--surface);
  font-size: 1.4rem; line-height: 1; cursor: pointer; color: var(--text);
}
.stepper button:hover:not([disabled]) { border-color: var(--brand-strong); color: var(--brand-strong); }
.stepper button[disabled] { opacity: .4; cursor: not-allowed; }
#party-input, #mb-party-input {
  width: 92px; min-height: 44px; text-align: center; font-size: var(--text-lg);
  font-family: var(--font-mono); font-weight: 600;
  border: 1.5px solid var(--line-strong); border-radius: var(--radius-md); padding: 0 var(--sp-2);
  background: var(--surface); color: var(--text);
}

/* ============================================================================
   FORM FIELDS -- labels tied to inputs, inline validation, select
   ========================================================================= */

label { display: block; margin: 0 0 var(--sp-3); font-size: var(--text-sm); font-weight: 600; color: var(--text-2); }
label input, label select, label textarea { margin-top: var(--sp-1); font-weight: 400; }

input[type="text"], input[type="email"], input[type="password"], input[type="tel"], input[type="url"],
input[type="number"], input[type="date"], input[type="search"], select, textarea {
  display: block; width: 100%; min-height: 44px; padding: var(--sp-2) var(--sp-3);
  border: 1.5px solid var(--line-strong); border-radius: var(--radius-md);
  background: var(--surface); color: var(--text); font-size: var(--text-base);
  transition: border-color var(--transition);
}
input::placeholder, textarea::placeholder { color: var(--text-3); opacity: 1; }
input:hover, select:hover, textarea:hover { border-color: var(--text-3); }
input[disabled], select[disabled], textarea[disabled] { background: var(--surface-2); color: var(--text-3); cursor: not-allowed; }

select {
  appearance: none;
  background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='%234B5259' d='M5.5 7.5l4.5 4.5 4.5-4.5' stroke='%234B5259' stroke-width='1.6' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3e%3c/svg%3e");
  background-repeat: no-repeat; background-position: right 12px center; background-size: 16px;
  padding-right: var(--sp-8);
}

.field-error {
  display: flex; gap: var(--sp-2); align-items: flex-start;
  font-size: var(--text-sm); color: var(--stop); margin: var(--sp-2) 0 0;
}
.password-hint { font-size: var(--text-xs); color: var(--text-2); margin: 0 0 var(--sp-2); }
.password-hint.ok { color: var(--go); }
.missing-line { font-size: var(--text-sm); color: var(--stop); margin: var(--sp-2) 0 0; }

/* ----------------------------------------------------- sign-in / invite
   "Three options, all equal, none preferred in the layout" (the invite
   and sign-in pages' own standing rule, PLATFORM.md): every .auth-method
   card -- Google, Microsoft, email-and-password -- shares one heading
   style and one primary-control style. Nothing below singles any one of
   them out; a card for a provider missing its credentials gets the same
   card, just an explanatory line instead of a button (see src/auth.mjs). */
.auth-page { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: var(--sp-6) var(--sp-4); }
.auth-page-inner { width: min(720px, 100%); }
.auth-page-inner.notice-width { width: min(480px, 100%); }
.auth-page-mark { text-align: center; margin-bottom: var(--sp-6); color: var(--text-2); font-weight: 700; letter-spacing: .02em; }
.auth-page h1 { text-align: center; }
.auth-page > .auth-page-inner > p.muted { text-align: center; max-width: 54ch; margin: 0 auto var(--sp-5); }
.auth-methods { display: flex; flex-wrap: wrap; gap: var(--sp-4); margin: var(--sp-5) 0; align-items: stretch; }
.auth-method h2 {
  font-size: var(--text-xs); text-transform: uppercase; letter-spacing: .05em;
  color: var(--text-3); margin: 0 0 var(--sp-3); font-weight: 700;
}
.auth-method { flex: 1 1 230px; min-width: 230px; }
.auth-method .provider-btn { margin: 0 0 auto; }
.auth-method form { margin-top: 0; display: flex; flex-direction: column; flex: 1; }
.auth-method form button[type="submit"] {
  display: block; width: 100%; min-height: 44px; margin-top: auto; padding: 0 var(--sp-4);
  border-radius: var(--radius-md); border: 1.5px solid var(--brand-strong);
  background: var(--brand-strong); color: var(--on-brand);
  font-size: var(--text-base); font-weight: 600; cursor: pointer;
}
.auth-method form button[type="submit"]:hover { background: var(--brand-strong-hover); border-color: var(--brand-strong-hover); }
.auth-method form button[type="submit"][disabled] { opacity: .6; cursor: default; }

/* ============================================================================
   CARD
   ========================================================================= */

.card, .session-block, #manual-booking-panel, #staff-panel, #result, #mb-result,
.auth-method, #price-box, #mb-price-box, .summary-tile, .notice, .error-notice,
.invite-info, .empty-state {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}
.card { padding: var(--sp-5); }
.session-block, #manual-booking-panel, #staff-panel, #result, #mb-result { padding: var(--sp-4) var(--sp-5); margin-bottom: var(--sp-4); }
.auth-method { padding: var(--sp-5); display: flex; flex-direction: column; }
#price-box, #mb-price-box { padding: var(--sp-4) var(--sp-5); background: var(--brand-ghost); border-color: color-mix(in srgb, var(--brand) 30%, var(--line)); }
.summary-tile { padding: var(--sp-3) var(--sp-4); }
.notice, .invite-info { padding: var(--sp-3) var(--sp-4); background: var(--info-ghost); border-color: color-mix(in srgb, var(--info) 35%, var(--line)); font-size: var(--text-sm); }
.notice code { background: color-mix(in srgb, var(--info) 14%, white); padding: 1px 5px; border-radius: 4px; word-break: break-all; }
/* Two different kinds of "something is not right", on purpose: a REFUSAL
   (why a selection can't go ahead yet -- a normal, expected part of the
   flow, same family as a disabled pill's own reason text) stays calm and
   amber; a genuine FAILURE to reach or load from the server is the one
   that gets the more alarming red -- "honest states" distinguishes them
   rather than making every message look like the same emergency. Neither
   changes when or why a message appears, only its colour. */
.refusals, .mb-refusals, #refusals-box, #mb-refusals-box, #submit-error, #mb-submit-error, #invite-error {
  padding: var(--sp-3) var(--sp-4); background: var(--warn-ghost);
  border: 1px solid color-mix(in srgb, var(--warn) 35%, var(--line)); border-radius: var(--radius-md);
  color: color-mix(in srgb, var(--warn) 85%, black); font-size: var(--text-sm);
}
.refusals ul, .mb-refusals ul, #refusals-box ul, #mb-refusals-box ul { margin: var(--sp-2) 0 0; padding-left: 1.2em; }

.error-notice, #load-error, #mb-load-error, #invitations-load-error {
  padding: var(--sp-3) var(--sp-4); background: var(--stop-ghost);
  border: 1px solid color-mix(in srgb, var(--stop) 35%, var(--line)); border-radius: var(--radius-md);
  color: color-mix(in srgb, var(--stop) 85%, black); font-size: var(--text-sm);
}
#invite-result { background: var(--go-ghost); border-color: color-mix(in srgb, var(--go) 35%, var(--line)); color: color-mix(in srgb, var(--go) 85%, black); padding: var(--sp-3) var(--sp-4); border-radius: var(--radius-md); font-size: var(--text-sm); }
.accept-url-box { font-size: var(--text-xs); word-break: break-all; background: var(--surface); padding: var(--sp-2) var(--sp-3); border-radius: var(--radius-sm); margin-top: var(--sp-2); border: 1px solid var(--line); }

/* ============================================================================
   TABLE
   ========================================================================= */

table.table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
table.table th, table.table td {
  text-align: left; padding: var(--sp-3) var(--sp-3); border-bottom: 1px solid var(--line-soft); vertical-align: middle;
}
table.table th {
  font-size: var(--text-xs); letter-spacing: .03em; text-transform: uppercase;
  color: var(--text-3); font-weight: 600; background: var(--surface-2);
}
table.table tr:last-child td { border-bottom: 0; }
.table-scroll { overflow-x: auto; border-radius: var(--radius-md); border: 1px solid var(--line); }
.table-scroll table.table { border: 0; }

@media (max-width: 640px) {
  /* Stack to labelled rows instead of scrolling sideways -- nothing is
     ever past the edge of a phone (AOC's own proven pattern, aoc.css). */
  table.table.stacks thead { display: none; }
  table.table.stacks, table.table.stacks tbody { display: block; width: 100%; }
  table.table.stacks tr { display: block; padding: var(--sp-2) 0; border-bottom: 1px solid var(--line); }
  table.table.stacks td {
    display: flex; justify-content: space-between; gap: var(--sp-3); border: 0; padding: 3px 0; text-align: right;
  }
  table.table.stacks td::before {
    content: attr(data-l); font-size: var(--text-xs); letter-spacing: .03em; text-transform: uppercase;
    color: var(--text-3); text-align: left;
  }
}

/* ============================================================================
   BADGE / PILL (status)
   ========================================================================= */

.badge {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: var(--text-xs); font-weight: 600; letter-spacing: .02em;
  padding: 3px 9px; border-radius: var(--radius-pill);
  background: var(--surface-2); color: var(--text-2); border: 1px solid var(--line-strong);
  white-space: nowrap; line-height: 1.5;
}
/* var(--brand-strong), not a bespoke color-mix: a fresh contrast sweep for
   this task (against Predator's own real, brighter orange, not just the
   default red) found the badge's PREVIOUS "80%, black" mix at 4.10:1,
   below the required 4.5:1 -- too light for this specific tenant's
   accent, a pre-existing gap the design-system task's own check never
   happened to exercise for this exact pairing. --brand-strong is the
   SAME derived token .shell-nav a[aria-current="page"] already uses
   against this identical --brand-ghost background (measured 5.33:1 for
   both the default red and Predator's orange), so this reuses an
   already-proven ratio instead of a second, independently-tuned one. */
.badge.group { background: var(--brand-ghost); color: var(--brand-strong); border-color: color-mix(in srgb, var(--brand) 40%, var(--line-strong)); }
.badge.manual { background: var(--info-ghost); color: color-mix(in srgb, var(--info) 85%, black); border-color: color-mix(in srgb, var(--info) 40%, var(--line-strong)); }
.badge.cancelled, .badge.revoked, .badge.damaged { background: var(--stop-ghost); color: color-mix(in srgb, var(--stop) 85%, black); border-color: color-mix(in srgb, var(--stop) 40%, var(--line-strong)); }
.badge.pending, .badge.in-repair { background: var(--warn-ghost); color: color-mix(in srgb, var(--warn) 85%, black); border-color: color-mix(in srgb, var(--warn) 40%, var(--line-strong)); }
.badge.accepted, .badge.available { background: var(--go-ghost); color: color-mix(in srgb, var(--go) 85%, black); border-color: color-mix(in srgb, var(--go) 40%, var(--line-strong)); }
/* Rental inventory task: .badge.damaged/.badge.in-repair/.badge.available
   are plain aliases of the three pairings directly above (retired reuses
   .badge.closed, defined further down near the calendar's own badges) --
   same colours, already measured at 4.5:1+ against both the default red
   and Predator's own brighter orange (this file's header, and this
   block's own comment two pairings up) -- addressed by the vocabulary a
   unit's condition actually uses, not borrowed class names whose own
   names mean something else on this screen. */

/* ---------------------------------------------------- booking reference
   The one number a customer or an operator actually needs to keep --
   shown big, mono, high-contrast, impossible to miss or mistype off the
   screen. Used identically by the public result panel and the manual-
   booking result panel. */
.result-reference {
  font-family: var(--font-mono); font-size: var(--text-xl); font-weight: 700; letter-spacing: .03em;
  background: var(--go-ghost); color: color-mix(in srgb, var(--go) 85%, black);
  border-radius: var(--radius-md); padding: var(--sp-4); text-align: center; word-break: break-all;
}

/* ============================================================================
   MODAL -- built and ready; not wired to a live control on these screens.
   Every existing panel this task restyles (manual booking, staff &
   invitations) already opens and closes as an inline, in-page section, and
   keeping that -- rather than promoting it to a true overlay dialog with
   its own focus trap and backdrop-dismiss behaviour -- is a deliberate
   choice to restyle without changing behaviour (the task brief's own
   instruction). The next screen that genuinely needs a dialog (a
   confirmation, a settings editor) uses this.
   ========================================================================= */

.modal-backdrop {
  position: fixed; inset: 0; z-index: 250; background: rgba(17,21,28,.55);
  display: none; align-items: center; justify-content: center; padding: var(--sp-4);
}
.modal-backdrop.open { display: flex; }
.modal {
  background: var(--surface); border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
  width: min(560px, 100%); max-height: 88vh; display: flex; flex-direction: column; overflow: hidden;
}
.modal-header { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); padding: var(--sp-4) var(--sp-5); border-bottom: 1px solid var(--line); }
.modal-body { padding: var(--sp-5); overflow-y: auto; }
.modal-footer { display: flex; justify-content: flex-end; gap: var(--sp-2); padding: var(--sp-4) var(--sp-5); border-top: 1px solid var(--line); }

/* ============================================================================
   TOAST -- built and ready; see the comment above .modal-backdrop. The
   booking and check-in flows already surface every success/failure inline
   (the result panel, the refusal box) and the cancel/revoke prompts are
   native browser dialogs the test suite drives directly (window.prompt /
   window.confirm) -- swapping those for a toast would change the actual
   interaction, not just its appearance, so they are left exactly as they
   were. This lands ready for the first screen that needs a transient,
   non-blocking notice without one.
   ========================================================================= */

.toast {
  position: fixed; left: 50%; bottom: var(--sp-5); transform: translateX(-50%) translateY(8px);
  z-index: 290; background: var(--text); color: #fff; font-size: var(--text-sm);
  padding: var(--sp-3) var(--sp-4); border-radius: var(--radius-md); border-left: 4px solid var(--brand);
  box-shadow: var(--shadow-lg); max-width: min(92vw, 480px); opacity: 0; pointer-events: none;
  transition: opacity var(--transition), transform var(--transition);
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); pointer-events: auto; }

/* ============================================================================
   TABS -- built and ready; no current screen has tabbed content (per the
   platform's own standing rule, a nav item or a control is not drawn for
   something that is not built -- the same discipline extends to a
   component). Ready for Settings, when that screen exists for real.
   ========================================================================= */

.tabs { display: flex; gap: var(--sp-1); border-bottom: 1px solid var(--line); margin-bottom: var(--sp-4); }
.tab {
  padding: var(--sp-2) var(--sp-3); border: 0; background: none; cursor: pointer;
  font-weight: 600; font-size: var(--text-sm); color: var(--text-2);
  border-bottom: 2.5px solid transparent; margin-bottom: -1px;
}
.tab[aria-selected="true"] { color: var(--brand-strong); border-bottom-color: var(--brand-strong); }

/* ============================================================================
   EMPTY STATE / LOADING SKELETON
   ========================================================================= */

.empty-state {
  padding: var(--sp-7) var(--sp-5); text-align: center; color: var(--text-2);
}
.empty-state .empty-icon {
  width: 44px; height: 44px; margin: 0 auto var(--sp-3); border-radius: 50%;
  background: var(--surface-2); display: flex; align-items: center; justify-content: center;
  color: var(--text-3);
}
.empty-state h3 { color: var(--text); margin-bottom: var(--sp-1); }
.empty-state p { margin: 0; font-size: var(--text-sm); }

@keyframes skeleton-shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
.skeleton {
  background: linear-gradient(100deg, var(--surface-2) 28%, var(--line-soft) 48%, var(--surface-2) 68%);
  background-size: 250% 100%; animation: skeleton-shimmer 1.4s ease-in-out infinite;
  border-radius: var(--radius-sm); color: transparent;
}
.skeleton-stack { display: flex; flex-direction: column; gap: var(--sp-3); }
.skeleton-line { height: 16px; }
.skeleton-pills { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.skeleton-pill { height: 48px; width: 150px; border-radius: var(--radius-md); }
@media (prefers-reduced-motion: reduce) { .skeleton { animation: none; } }

/* ============================================================================
   CHECKOUT PROGRESS (public booking page)
   ========================================================================= */

.steps-progress { display: flex; gap: var(--sp-2); list-style: none; padding: 0; margin: 0 0 var(--sp-5); }
.steps-progress li {
  flex: 1; display: flex; flex-direction: column; gap: var(--sp-1); min-width: 0;
}
.steps-progress .dot {
  height: 4px; border-radius: var(--radius-pill); background: var(--line-strong);
  transition: background-color var(--transition);
}
.steps-progress li.done .dot { background: var(--go); }
.steps-progress li.current .dot { background: var(--brand-strong); }
.steps-progress .step-label {
  font-size: var(--text-xs); color: var(--text-3); font-weight: 600; letter-spacing: .02em;
}
.steps-progress li.current .step-label { color: var(--text); }
.steps-progress li.done .step-label { color: var(--go); }

/* ============================================================================
   APP SHELL (operator console) -- left sidebar, top bar, main content.
   Collapses to a horizontal nav row on a tablet, then a compact top-only
   bar on a phone (same proven pattern as AOC's own .aoc-desk, no extra
   script needed to show/hide a drawer -- fewer moving parts to keep
   keyboard-operable and fewer places a 390px layout can overflow).
   ========================================================================= */

.shell { display: flex; min-height: 100vh; align-items: stretch; background: var(--bg); }
.shell-sidebar {
  width: var(--shell-sidebar-w); flex: none; background: var(--surface);
  border-right: 1px solid var(--line); display: flex; flex-direction: column;
  padding: var(--sp-4) var(--sp-3);
}
.shell-brand { display: flex; align-items: center; gap: var(--sp-2); padding: 0 var(--sp-2) var(--sp-5); min-width: 0; }
.shell-brand .shell-logo {
  width: 32px; height: 32px; flex: none; border-radius: var(--radius-sm);
  background: var(--brand-strong); color: var(--on-brand); display: flex; align-items: center; justify-content: center;
  font-weight: 700; font-size: var(--text-sm);
}
.shell-brand b { font-size: var(--text-sm); line-height: 1.2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.shell-nav { display: flex; flex-direction: column; gap: 2px; }
.shell-nav a, .shell-nav button {
  display: flex; align-items: center; gap: var(--sp-2); min-height: 44px; padding: 0 var(--sp-3);
  border-radius: var(--radius-md); color: var(--text-2); text-decoration: none; font-weight: 600; font-size: var(--text-sm);
  border: 0; background: none; width: 100%; text-align: left; cursor: pointer;
}
.shell-nav a:hover, .shell-nav button:hover { background: var(--surface-2); color: var(--text); }
.shell-nav a[aria-current="page"] { background: var(--brand-ghost); color: var(--brand-strong); }
.shell-nav button[aria-expanded="true"] { background: var(--surface-2); color: var(--text); }
.shell-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.shell-topbar {
  display: flex; align-items: center; gap: var(--sp-3); min-height: var(--shell-topbar-h);
  padding: 0 var(--sp-5); background: var(--surface); border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 20; flex-wrap: wrap;
}
.shell-topbar-tenant { font-weight: 700; font-size: var(--text-base); color: var(--text); }
.shell-topbar-spacer { flex: 1 1 auto; min-width: 0; }
.shell-content { flex: 1; padding: var(--sp-5); max-width: 980px; width: 100%; margin: 0 auto; }

/* ---------------------------------------------------------- user menu */
.user-menu { position: relative; }
.user-menu-btn {
  display: flex; align-items: center; gap: var(--sp-2); min-height: 44px; padding: var(--sp-1) var(--sp-2);
  border-radius: var(--radius-md); border: 1px solid transparent; background: none; cursor: pointer; color: var(--text);
}
.user-menu-btn:hover { background: var(--surface-2); }
.user-avatar {
  width: 30px; height: 30px; border-radius: 50%; background: var(--brand-strong); color: var(--on-brand);
  display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: var(--text-xs); flex: none;
}
.user-menu-name { font-size: var(--text-sm); font-weight: 600; line-height: 1.15; text-align: left; }
.user-menu-role { font-size: var(--text-xs); color: var(--text-3); text-align: left; display: block; text-transform: capitalize; }
.user-menu-caret { color: var(--text-3); transition: transform var(--transition); }
.user-menu-btn[aria-expanded="true"] .user-menu-caret { transform: rotate(180deg); }
.user-menu-panel {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 30; min-width: 220px;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg); padding: var(--sp-2); display: none;
}
.user-menu-panel.open { display: block; }
.user-menu-panel a, .user-menu-panel button {
  display: flex; width: 100%; align-items: center; min-height: 40px; padding: 0 var(--sp-2);
  border-radius: var(--radius-sm); border: 0; background: none; text-align: left; cursor: pointer;
  font-size: var(--text-sm); color: var(--text); text-decoration: none;
}
.user-menu-panel a:hover, .user-menu-panel button:hover { background: var(--surface-2); }
.user-menu-panel .signout-item { color: var(--stop); }
#signout-form { margin: 0; }
#signout-btn { width: 100%; }

/* --------------------------------------------------------- tablet (<=900px) */
@media (max-width: 900px) {
  .shell { flex-direction: column; }
  .shell-sidebar {
    width: auto; flex-direction: row; align-items: center; padding: var(--sp-2) var(--sp-3);
    border-right: 0; border-bottom: 1px solid var(--line); overflow-x: auto;
  }
  .shell-brand { padding: 0 var(--sp-3) 0 0; border-right: 1px solid var(--line); margin-right: var(--sp-2); flex: none; }
  .shell-nav { flex-direction: row; }
  .shell-nav a { white-space: nowrap; }
  .shell-content { padding: var(--sp-4); }
}

/* ============================================================================
   DIARY -- the calendar, the cross-date bookings list, and blocking a day
   or a session (the diary task). New components, added here rather than
   as page-specific CSS in calendar.html/bookings.html -- the same
   discipline the design-system task set, and the reason two of these
   (.list-row, .meter) are deliberately NOT a retrofit of ops.html's
   existing .booking-card/.booking-head (page-specific, untouched, and
   load-bearing for 120 existing tests' selectors): a genuinely new second
   use of "a row in a list" is what promotes a component to this file: the
   FIRST use stayed where it was.
   ========================================================================= */

/* ---------------------------------------------------------------- filters
   A wrapping row of labelled fields -- the bookings list's date range,
   session, status, source and search controls. ops.html's own .controls
   (Today's plainer date+search pair) is left exactly as it was. */
.filters { display: flex; flex-wrap: wrap; gap: var(--sp-3); align-items: flex-end; margin-bottom: var(--sp-4); }
.filters > div { flex: 1 1 160px; min-width: 0; }
.filters label { margin-bottom: var(--sp-1); }
.filters .filters-search { flex: 2 1 240px; }

/* ------------------------------------------------------------------ meter
   The glanceable fill indicator (task brief: "not a number buried in
   text"): a thin bar plus a short mono count/fraction beside it, never a
   number alone in a sentence. Colour escalates with how full it is;
   .meter itself carries no text colour contrast obligation (it is a
   non-text UI element -- WCAG 3:1 against its surface, which --sunk
   already clears against both --surface and --bg). When the total is
   unknown (CAPACITY_NOT_SET -- genuinely true for every Predator slot
   today, src/rules.mjs's own honesty rule), there is no bar at all: see
   .meter-unknown below, the same "report unknown, do not invent infinite"
   discipline applied to a pixel instead of a refusal code. */
.meter-row { display: flex; align-items: center; gap: var(--sp-2); }
.meter { flex: 1; height: 6px; border-radius: var(--radius-pill); background: var(--sunk); overflow: hidden; }
.meter .meter-fill { height: 100%; border-radius: inherit; background: var(--go); transition: width var(--transition); }
.meter.is-near .meter-fill { background: var(--warn); }
.meter.is-full .meter-fill { background: var(--stop); }
.meter-count { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: var(--text-xs); color: var(--text-2); white-space: nowrap; }
.meter-unknown { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-2); white-space: nowrap; }

/* ------------------------------------------------------------- badge, more
   Three more day/session states, same family as .badge.cancelled etc. */
.badge.event { background: var(--brand-ghost); color: var(--brand-strong); border-color: color-mix(in srgb, var(--brand) 40%, var(--line-strong)); }
.badge.closed { background: var(--surface-2); color: var(--text-2); border-color: var(--line-strong); }
.badge.blocked { background: var(--stop-ghost); color: color-mix(in srgb, var(--stop) 85%, black); border-color: color-mix(in srgb, var(--stop) 40%, var(--line-strong)); }

/* ----------------------------------------------------------------- calendar
   The month grid. ".cal-day" is a <button> (real tap target, real
   keyboard focus, for free from this file's global :focus-visible rule)
   -- 7 equal columns at every width down to 390px (see the RESPONSIVE
   section for the narrow-width type-size tweak; the grid shape itself
   does not change, which is the actual hard part the task brief names:
   solved by never trying to fit a label at 390px that only fits at 1440,
   rather than by a second, different layout). */
.cal-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--sp-3); margin-bottom: var(--sp-4); }
.cal-head h2 { margin: 0; min-width: 11ch; }
.cal-nav { display: flex; gap: var(--sp-2); }
.cal-weekdays, .cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: var(--sp-2); }
.cal-weekdays { margin-bottom: var(--sp-2); }
.cal-weekdays span { text-align: center; font-size: var(--text-xs); font-weight: 600; color: var(--text-2); text-transform: uppercase; letter-spacing: .03em; }
.cal-day {
  display: flex; flex-direction: column; align-items: stretch; gap: 4px;
  min-height: 72px; padding: var(--sp-2); border-radius: var(--radius-md);
  border: 1.5px solid var(--line); background: var(--surface); color: var(--text);
  cursor: pointer; text-align: left; font: inherit;
  transition: border-color var(--transition), background-color var(--transition);
}
.cal-day:hover { border-color: var(--brand-strong); }
.cal-day:disabled { cursor: default; opacity: .55; }
.cal-day:disabled:hover { border-color: var(--line); }
.cal-day .cal-daynum { font-weight: 700; font-size: var(--text-sm); font-family: var(--font-mono); }
.cal-day .cal-badges { display: flex; flex-wrap: wrap; gap: 3px; }
.cal-day .cal-badges .badge { padding: 1px 6px; font-size: 10px; }
.cal-day.is-outside-month { background: var(--bg); color: var(--text-3); }
.cal-day.is-outside-month .cal-daynum { color: var(--text-3); }
.cal-day.is-today { border-color: var(--brand-strong); border-width: 2px; box-shadow: inset 0 0 0 1px var(--brand-strong); }
.cal-day.is-closed { background: var(--surface-2); }
.cal-day.is-blocked { background: var(--stop-ghost); border-color: color-mix(in srgb, var(--stop) 35%, var(--line)); }
.cal-legend { display: flex; flex-wrap: wrap; gap: var(--sp-4); margin-top: var(--sp-5); font-size: var(--text-sm); color: var(--text-2); }
.cal-legend-item { display: flex; align-items: center; gap: var(--sp-2); }
.cal-legend-swatch { width: 14px; height: 14px; border-radius: 4px; flex: none; border: 1px solid var(--line-strong); }
.cal-legend-swatch.runs { background: var(--go-ghost); border-color: var(--go); }
.cal-legend-swatch.closed { background: var(--surface-2); border-color: var(--line-strong); }
.cal-legend-swatch.blocked { background: var(--stop-ghost); border-color: var(--stop); }
.cal-legend-swatch.event { background: var(--brand-ghost); border-color: var(--brand); }

/* ------------------------------------------------------------- list rows
   The bookings list's one row -- see this section's own header for why
   this is a new component rather than a reuse of ops.html's .booking-card. */
.list-row { border-top: 1px solid var(--line-soft); padding: var(--sp-3) 0; }
.list-row:first-child { border-top: 0; }
.list-row-head { display: flex; flex-wrap: wrap; justify-content: space-between; gap: var(--sp-2); align-items: flex-start; }
.list-row-title { font-weight: 700; word-break: break-word; }
.list-row-ref { font-family: var(--font-mono); font-weight: 700; letter-spacing: .02em; }
.list-row-meta { font-size: var(--text-sm); color: var(--text-2); margin-top: 2px; }
.list-row-actions { display: flex; gap: var(--sp-2); flex-wrap: wrap; }
.list-row.is-cancelled { opacity: .68; }
.list-row.is-cancelled .list-row-ref { text-decoration: line-through; }
.list-row-detail { margin-top: var(--sp-3); padding-top: var(--sp-3); border-top: 1px dashed var(--line); font-size: var(--text-sm); }
.list-row-detail dl { display: grid; grid-template-columns: max-content 1fr; gap: var(--sp-1) var(--sp-3); margin: 0; }
.list-row-detail dt { color: var(--text-3); font-weight: 600; }
.list-row-detail dd { margin: 0; }

/* --------------------------------------------------------------- pagination */
.pagination { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--sp-3); margin-top: var(--sp-5); }
.pagination-info { font-size: var(--text-sm); color: var(--text-2); }
.pagination-controls { display: flex; gap: var(--sp-2); }

/* -------------------------------------------------------- affected list
   Inside the block-confirmation modal: the bookings a block would affect,
   named, not just counted (task brief: "show exactly who is affected"). */
.affected-list { max-height: 260px; overflow-y: auto; border: 1px solid var(--line); border-radius: var(--radius-md); margin: var(--sp-3) 0; }
.affected-row { padding: var(--sp-2) var(--sp-3); border-bottom: 1px solid var(--line-soft); font-size: var(--text-sm); }
.affected-row:last-child { border-bottom: 0; }
.affected-row .ref { font-family: var(--font-mono); font-weight: 700; }

/* ============================================================================
   RESPONSIVE: the booking page and the ops console, down to 390px. Nothing
   below this line may introduce horizontal scroll -- see the task report
   for how that was measured (document.documentElement.scrollWidth vs
   clientWidth, at 390px, on every screen).
   ========================================================================= */

@media (max-width: 640px) {
  .page, .page-narrow { padding-left: var(--sp-3); padding-right: var(--sp-3); }
  .shell-content { padding: var(--sp-3); }
  .shell-topbar { padding: 0 var(--sp-3); }
  h1 { font-size: var(--text-lg); }
  .page-header { flex-direction: column; align-items: stretch; }
  .user-menu-name, .user-menu-role { max-width: 120px; overflow: hidden; text-overflow: ellipsis; }

  /* The calendar's own hard part (task brief, verbatim): "a month grid on
     a phone... solve it properly, do not just let it squash." The GRID
     SHAPE never changes (still 7 equal columns, still one <button> per
     day, still real 44px-capable tap targets) -- only type size, gap and
     padding shrink, and the fraction count drops to a bare number so it
     never has to wrap inside a ~46px-wide cell. */
  .cal-grid, .cal-weekdays { gap: 4px; }
  .cal-day { min-height: 56px; padding: 4px; gap: 2px; }
  .cal-day .cal-daynum { font-size: var(--text-xs); }
  .cal-day .cal-badges .badge { font-size: 9px; padding: 0 4px; }
  .meter-count, .meter-unknown { font-size: 10px; }
  .filters { flex-direction: column; align-items: stretch; }
  /* flex-basis is read along the MAIN axis -- 160px meant "160px wide" in
     the row layout above; once .filters switches to flex-direction:
     column here, the exact same "flex: 1 1 160px" would be read as
     "160px tall" instead and stretch every field to that height, leaving
     large empty gaps under each short one. Caught from the rendered
     390px screenshot (acceptance 8), not from the no-horizontal-scroll
     check alone, which this bug does not trip. */
  .filters > div, .filters .filters-search { flex: none; }
  .list-row-detail dl { grid-template-columns: 1fr; }
}

/* ---- Customer email: sender identity controls (public/sender-card.js) ----
   Shared by the onboarding step and the Settings screen. The status block is
   deliberately plain: it states what customers see TODAY, and a badge whose
   colour carries no claim the words do not also make. */
.sender-card { margin-bottom: var(--sp-5); }
.sender-card h2 { margin-top: 0; }
.sender-preview { border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface-2); padding: var(--sp-3) var(--sp-4); margin-bottom: var(--sp-4); }
.sender-preview-label { margin: 0 0 var(--sp-1); text-transform: uppercase; letter-spacing: .06em; }
.sender-from { font-weight: 700; margin: 0; word-break: break-word; }
.sender-replies { margin: var(--sp-1) 0 var(--sp-3); word-break: break-word; }
.sender-status .badge { margin-bottom: var(--sp-2); }
.sender-status-line { margin: var(--sp-1) 0 0; font-size: var(--text-sm); }
.warn-text { color: color-mix(in srgb, var(--warn) 85%, black); }
.sender-own { margin: 0 0 var(--sp-3); padding: 0 var(--sp-4) var(--sp-3); border: 1px solid var(--line); border-radius: var(--radius-md); }
.sender-own summary { cursor: pointer; font-weight: 600; min-height: 44px; display: flex; align-items: center; }
.sender-feedback { margin: 0; color: var(--text-2); font-size: var(--text-sm); }

/* ---- Customer email: the delivery record in the booking detail (public/bookings.js) ---- */
.email-record { margin: var(--sp-3) 0 0; padding-top: var(--sp-3); border-top: 1px solid var(--line); }
.email-record h4 { margin: 0 0 var(--sp-2); font-size: var(--text-sm); text-transform: uppercase; letter-spacing: .06em; color: var(--text-2); }
.email-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--sp-2) var(--sp-3); align-items: start; padding: var(--sp-2) 0; border-bottom: 1px solid var(--line); }
.email-row:last-child { border-bottom: 0; }
.email-row-title { font-weight: 600; }
.email-row-meta { font-size: var(--text-sm); color: var(--text-2); word-break: break-word; }
.email-row-meta.email-row-problem { color: color-mix(in srgb, var(--stop) 85%, black); }
.email-row-actions { display: flex; gap: var(--sp-2); flex-wrap: wrap; justify-content: flex-end; }
.email-row-error { grid-column: 1 / -1; margin: 0; }
.email-body-view { grid-column: 1 / -1; margin: var(--sp-2) 0 0; padding: var(--sp-3); background: var(--surface-2); border-radius: var(--radius-md); font: var(--text-sm) / 1.5 var(--font-mono); white-space: pre-wrap; word-break: break-word; max-height: 260px; overflow: auto; }
@media (max-width: 520px) {
  .email-row { grid-template-columns: 1fr; }
  .email-row-actions { justify-content: flex-start; }
}

/* ---- the platform front door (GET /) --------------------------------------
   Lives here rather than inline in src/server.mjs: a bare number in a server
   file is indistinguishable from a copied business rule, and test/bookings
   acceptance #10 rightly refuses one. Styles belong in the design system
   anyway. */
.root-main { max-width: 40rem; margin: 0 auto; padding: 2rem 1rem 4rem; }
.root-list { list-style: none; padding: 0; margin: 0 0 2rem; }
.root-list li { padding: 0.75rem 0; border-bottom: 1px solid var(--line); }
.root-list .bookline { font-weight: 600; }
.root-list .muted, .root-main > .muted { color: var(--text-2); font-size: 0.9rem; }

/* every button a finger can find: 44px (milestone 6, the admin screens at 390) */
.shell-content .btn { min-height: 44px; }
