/* ============================================================
   HotelOS DS — COMPONENTS layer (.hos-*)
   ------------------------------------------------------------
   Layer 3 of 3 (base -> tokens -> components). Loads after os_ds
   and the page sheets, so a page can lean on these.

   THE RULE: a class belongs here ONLY if it encodes something
   about RESORTS — room status, rosters, manifests, transport
   runs, folios. Everything generic resolves from os-ds. If the
   gem lacks a generic primitive, contribute it UP and delete the
   local copy. A divergence from a .dv-* component is DRIFT until
   someone can name why it should differ; the burden of proof is
   on staying local, not on migrating.

   Why this matters beyond tidiness: a local component reads
   local tokens, so this layer breaking takes the component with
   it. A gem component reads gem tokens and survives. That is not
   hypothetical — a stray comment-close (star-slash) in tokens.css blanked 21 tokens on
   2026-07-20 and every .ao-eyebrow lost its type, while .dv-*
   elements on the same page rendered perfectly.

   IN PROGRESS: this file is being drained. Retired so far:
   .ao-tab -> .page-tab, .ao-eyebrow -> .os-eyebrow (396 sites).
   Next: the card family, chip/tag/pill (settle as ONE component
   before promoting), then the generic primitives upstream.
   Remaining resort-domain classes get the .hos-* prefix at the
   end. Plan + evidence: tasks/hotelos-ds-plan.md,
   tasks/ao-star-audit.md.
   ============================================================ */

/* ─── Card ─────────────────────────────────────────────────────
   DiveOS idiom: the --shadow-card 1px ring IS the border, so no
   separate border declaration (avoids a doubled hairline). */
/* .os-card retired -> os-ds .os-card (1px border + overflow:hidden + radius).
   Two arena-only modifiers survive as local hos- bridges (the gem card has no
   equivalent), so the base adopts the gem with zero break at their callsites: */
.hos-card--pad { padding: 15px; }  /* opt-in root padding (gem puts padding on __body) */
/* Attention ring (e.g. a delayed movement) — amber instead of hairline. */
.hos-card--warn { box-shadow: 0 1px 2px rgba(17, 24, 39, 0.04), 0 0 0 1px var(--warning); }

/* ─── RETIRED: .hos-chipin (Type chip) → os-ds `.status-pill` ───────────────────
   .hos-chipin / --sm / --transfer / --excursion / --dive / --private / --input now
   resolve from the gem's .status-pill (mapping in the note at the retired
   .ao-tag below). Only the chip-INPUT widget scaffolding survives here — it is
   layout plumbing (a flex box + a remove button), not a pill. */

/* Multi-item list input (dietary/allergies) — JS enhances a textarea into removable
   chips (progressive enhancement; the textarea is the no-JS fallback). */
.hos-chipbox { display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
  padding: 6px; border: 1px solid var(--border-strong); border-radius: var(--radius-md);
  background: var(--surface); }
.hos-chipbox:focus-within { border-color: var(--accent); }
.hos-chipbox input { flex: 1; min-width: 90px; border: 0; outline: 0; background: transparent;
  padding: 4px 6px; font-size: var(--text-sm); color: var(--ink); }
/* The chips + remove control are now the gem's `.os-chip.os-chip--sm` + `.os-chip__x`
   (v1.1.0); only the field wrapper `.hos-chipbox` remains local (no gem chip-input
   container exists). Retired: .hos-chipin-x -> .os-chip__x. */

/* ─── Capacity bar ─────────────────────────────────────────── */
.hos-cap { display: inline-flex; align-items: center; gap: 8px; flex: none; }
.hos-cap__track {
  width: 64px;
  height: 6px;
  border-radius: 3px;
  background: var(--slate-100);
  overflow: hidden;
}
.hos-cap__fill { height: 100%; border-radius: 3px; background: var(--accent); }
.hos-cap__num {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--muted);
  white-space: nowrap;
}
.hos-cap--warn .hos-cap__fill { background: var(--warning); }
.hos-cap--full .hos-cap__fill { background: var(--danger); }
.hos-cap--full .hos-cap__num  { color: var(--danger); }

/* ─── Avatar retired onto the gem's .os-avatar (v1.1.0). ─────── */

/* ─── RETIRED: .ao-tag / .ao-pill / .hos-chipin → os-ds `.status-pill` ──────────
     .ao-tag--alert     -> .status-pill--cancelled    .ao-tag--warn  -> .status-pill--briefing
     .ao-tag--accent    -> .status-pill--accent       .ao-tag--muted -> .status-pill--muted
     .ao-pill--ok       -> .status-pill--confirmed    .ao-pill--pending -> .status-pill--pending
     .ao-pill__dot      -> .status-pill__dot
     .hos-chipin--dive     -> .status-pill--confirmed    .hos-chipin--excursion -> .status-pill--briefing
     .hos-chipin--transfer -> .status-pill--in_water     .hos-chipin--private   -> .status-pill--returning
     .hos-chipin--input    -> .status-pill--accent       .hos-chipin--sm        -> .status-pill--sm
   Three local families that were one component under three names, now the gem's
   .status-pill. NOTE one behavioural diff to watch: .ao-pill carried `flex: none`
   and .status-pill does not — a pill in a tight flex row can now shrink; add
   `flex: none` at the callsite if needed. The chip-input widget scaffolding
   (.hos-chipbox / .hos-chipin-x) survives above; it is plumbing, not a pill. */

/* ─── Section divider line (period headers, group separators) ─ */

/* ─── Status dot (fleet ticker) ────────────────────────────── */

/* .ao-pill / --ok / --pending / __dot retired above onto the gem's .status-pill. */

/* ─── Square icon tile (booking-source mark, portal glyph) ───── */
.hos-icon-tile {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-md);
  flex: none;
}

/* ─── Inset panel (surface-inset rounded block) ──────────────── */
.hos-inset { background: var(--slate-50); border-radius: var(--radius-md); }

/* .ao-fineprint + .ao-fineprint--dim RETIRED 2026-07-20 -> os-ds .os-meta--mono.
   Zero visual change, provable by resolving the tokens:
     .ao-fineprint       --text-xs + --text-muted -> var(--muted) -> slate-500
     .os-meta--mono (gem)      --text-xs + --muted      ->                 slate-500
     .ao-fineprint--dim  --muted-2                ->                 slate-500
   This theme deliberately collapses --muted and --muted-2 onto slate-500 (see
   tokens.css), so --dim was a no-op in this product: it is dropped, not
   translated into a gem modifier. */

/* ─── Form/card display heading (Display font, xl) ──────────────── */
.hos-form-title { font-family: var(--font-display); font-size: var(--text-xl); font-weight: 600; }

/* ─── Pax mark (mono adults + children) ──────────────────────── */
.hos-pax {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
}

/* ─── Task checklist (Tasks tab) ─────────────────────────────── */
.hos-fo-task {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  text-align: left;
  padding: 9px 2px;
  border: none;
  border-bottom: 1px solid var(--hairline);
  background: none;
  cursor: pointer;
  font-family: var(--font-sans);
}
.hos-fo-task:hover .hos-fo-task__label { color: var(--ink); }
.hos-fo-task__box {
  width: 18px;
  height: 18px;
  flex: none;
  border-radius: var(--radius-pill);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-2xs);
  border: 2px solid var(--hairline-strong);
  color: var(--status-confirmed-fg);
}
.hos-fo-task__label { font-size: var(--text-sm); color: var(--slate-700); }
.hos-fo-task--done .hos-fo-task__box { background: var(--status-confirmed-bg); border: none; }
.hos-fo-task--done .hos-fo-task__label { color: var(--muted-2); text-decoration: line-through; }
.hos-fo-task-add {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 9px 2px;
  color: var(--muted-2);
  font-size: var(--text-sm);
  cursor: pointer;
}
.hos-fo-task-add:hover { color: var(--accent); }

/* ─── Journey timeline (Arrival / In-house / Departure) ──────── */

/* ─── Link-color reset guards ──────────────────────────────────
   The arena_ds.css base reset `.hos-ds a { color: var(--accent) }`
   has specificity (0,1,1) and outranks single-class component colours on
   <a> elements — turning primary-button labels accent-on-accent (invisible)
   and tinting inactive sidebar links. Re-assert intended colours at higher
   specificity for the link-based components. */
.hos-ds a.os-btn--primary,
.hos-ds a.os-btn--primary:hover { color: #fff; }
.hos-ds a.os-btn--secondary { color: var(--ink); }
.hos-ds a.os-btn--danger    { color: var(--cancelled); }
.hos-ds a.os-btn--icon      { color: var(--muted); }
.hos-ds a.os-fab { color: var(--on-accent); }  /* the FAB is an <a>; beat .hos-ds a's accent link colour so the glyph is white on the teal fill (os-ds v1.23.0) */
.hos-ds a.os-sidebar__link { color: var(--ink); }
.hos-ds .os-sidebar__item--active a.os-sidebar__link { color: var(--accent); }
.hos-ds a.hos-sidebar__brand-link { color: inherit; }
/* tasks/071 — the crew Add-walk-on + rep departures-queue actions were local
   button lookalikes needing their own link-colour override. They now carry
   `.os-btn os-btn--primary|--secondary`, so the variant rules above (183–186)
   colour them; the overrides that lived here are deleted, not translated. */

/* ── Folio money summary (tasks/011 PR-4; PR-8 reuses on the drawer footer).
   Label/value rows for Total · Paid · Balance — money always .os-mono. ── */
.hos-panel-title { font-family: var(--font-display); font-size: var(--text-lg); font-weight: 600; line-height: 1.2; }
.hos-money-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; font-size: var(--text-sm); }
.hos-money-row__label { color: var(--text-muted); }
.hos-money-row--strong { font-weight: 700; }
.hos-money--due { color: var(--status-cancelled-fg); }
.hos-money--settled { color: var(--status-confirmed-fg); }

/* ── Card flight-status flag (Delayed / Cancelled) — tasks/011 PR-7 ── */
.hos-flight-flag { font-size: var(--text-2xs); font-weight: 700; }
.hos-flight-flag--delayed { color: var(--status-briefing-fg); }
.hos-flight-flag--cancelled { color: var(--status-cancelled-fg); }

/* ── tasks/155 — "Set the month" standing off-day picker (.hos-daypick + .hos-daylist).
      HotelOS-domain, not a gem gap: os-ds documents .os-segmented as 2–4 MUTUALLY-EXCLUSIVE
      options and explicitly says a 6+ set stops being readable, and .os-choicecard is the
      icon+label tile for bigger sets. A 7-day MULTI-select on one compact track is neither, so
      it lives here. Geometry deliberately matches the gem's .os-fitting-size-pill (38×28, 6px
      radius, accent fill when on) so the two read as one family.

      Markup is a REAL checkbox + label — no JS at all. The input is visually hidden but stays in
      the tab order and focusable, so :checked drives the fill and :focus-visible drives the ring.
      That is why there is no .is-on state class here: the DOM already holds the state. ── */
/* Page measure. Deliberately NOT `.hos-formpage`: that class is pinned to the gem's 640px FORM
   measure and its own comment says it must not drift off 640 — so this page gets its own wrapper
   rather than a modifier that would erode that rule.
   Why 880 and not one of the two standard measures: the screen is a 27×7 roster TABLE wearing a
   form's clothes. At the 640 form measure the pill track is cramped; at the 1280 view measure the
   "this month" count strands itself a screen-width away from the pills and the eye has to travel
   the whole row to connect name → days → count. 880 keeps all three in one glance. */
.hos-daylist-page { display: flex; flex-direction: column; gap: var(--space-5); max-width: 880px; margin-inline: auto; }

/* Pills share the track equally and grow into it — 7 equal columns rather than 7 natural-width
   boxes, so the day headers above stay aligned to them at every width. */
.hos-daypick { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; border: 0; padding: 0; margin: 0; min-inline-size: 0; }
.hos-daypick__in { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.hos-daypick__pill { display: inline-flex; align-items: center; justify-content: center; min-width: 38px; min-height: 28px; padding: 2px 8px; border: 1px solid var(--hairline); border-radius: var(--radius-sm); background: var(--surface); color: var(--muted-2); font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: 700; cursor: pointer; transition: background-color 100ms ease, color 100ms ease, border-color 100ms ease; }
.hos-daypick__pill--wk { background: var(--canvas); }
.hos-daypick__pill:hover { border-color: var(--accent); color: var(--accent); }
.hos-daypick__in:checked + .hos-daypick__pill { background: var(--accent); border-color: var(--accent); color: var(--surface); }
.hos-daypick__in:focus-visible + .hos-daypick__pill { outline: 2px solid var(--accent); outline-offset: 2px; }

.hos-daylist { border-top: 1px solid var(--hairline-2); }
/* The name column is CAPPED, not 1fr. With 1fr the label absorbed every pixel of extra page
   width (420px of an 880px row) while the pill track stayed at its natural 290 — pushing name and
   days apart, which is the exact eye-travel the 880 measure was chosen to avoid. Cap the label and
   let the pills take the slack: the extra width goes to the thing you actually aim at. */
.hos-daylist__head,
.hos-daylist__row { display: grid; grid-template-columns: minmax(140px, 240px) 1fr 96px; align-items: center; gap: 16px; padding: 8px 4px; border-bottom: 1px solid var(--hairline-2); }
.hos-daylist__head { font-size: var(--text-2xs); font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--muted-2); }
.hos-daylist__row:hover { background: var(--surface-alt); }
/* Same 7-column grid as .hos-daypick so each header sits exactly over its pill. */
.hos-daylist__dows { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }
.hos-daylist__hd { display: inline-flex; align-items: center; justify-content: center; font-family: var(--font-mono); }
.hos-daylist__hd--wk { color: var(--muted-2); }
.hos-daylist__group { padding: 7px 4px; background: var(--canvas); font-size: var(--text-2xs); font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--muted-2); border-bottom: 1px solid var(--hairline-2); }
.hos-daylist__who { display: flex; align-items: center; gap: 9px; min-width: 0; }
.hos-daylist__names { display: flex; flex-direction: column; min-width: 0; }
.hos-daylist__name { font-size: var(--text-sm); font-weight: 600; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Already off for a real reason. Apply SKIPS these dates, so without this line a weekday you
   ticked would appear to have been ignored. */
.hos-daylist__leave { font-size: var(--text-2xs); color: var(--muted-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hos-daylist__av { flex: none; display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; border-radius: 50%; font-size: var(--text-2xs); font-weight: 700; }
.hos-daylist__av--skip { background: var(--accent-soft); color: var(--accent); }
.hos-daylist__av--crew { background: var(--surface-alt); color: var(--muted-2); }
.hos-daylist__ch { display: flex; align-items: baseline; justify-content: flex-end; gap: 4px; text-align: right; font-family: var(--font-mono); font-size: var(--text-xs); color: var(--muted-2); }
.hos-daylist__ch > span { display: inline-flex; align-items: baseline; gap: 4px; }
.hos-daylist__n { font-size: var(--text-sm); font-weight: 700; color: var(--ink); }
/* The unit is what stops a bare "4" from being a riddle. Muted + small so the NUMBER still reads
   first when you scan the column. */
.hos-daylist__unit { font-size: var(--text-2xs); color: var(--muted-2); }
/* Unsaved work in this row, spelled out as before → after rather than one ambiguous number.
   `was` stays muted (it is history), `will` takes the accent (it is what Apply commits). */
.hos-daylist__was { font-size: var(--text-xs); color: var(--muted-2); }
.hos-daylist__arrow { font-size: var(--text-2xs); color: var(--muted-2); }
.hos-daylist__will { font-size: var(--text-sm); font-weight: 700; color: var(--accent); }
.hos-daylist__none { font-size: var(--text-2xs); color: var(--muted-2); }
.hos-daylist__bar { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; padding: 14px 0 0; margin-top: 14px; border-top: 1px solid var(--hairline); }
.hos-daylist__note { margin: 0; font-size: var(--text-xs); color: var(--muted-2); }
.hos-daylist__actions { display: flex; align-items: center; gap: 8px; }
.hos-daylist__clear { margin: 0; }
/* "Start over" sits on the canvas BELOW the form card, not inside it — button_to renders its own
   <form>, and a nested form is invalid HTML the browser reparents (it hijacked the Apply submit). */
.hos-daylist__reset { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin-top: 16px; padding: 0 4px; }

@media (max-width: 720px) {
  .hos-daylist__head { display: none; } /* the pills carry their own letters */
  .hos-daylist__row { grid-template-columns: 1fr auto; row-gap: 10px; padding: 12px 4px; }
  /* Explicit placement, not source order: the count must sit BESIDE the name on line 1, otherwise
     auto-placement pushes it below the full-width pill track and it orphans on a third line. */
  .hos-daylist__ch { grid-column: 2; grid-row: 1; }
  .hos-daypick { grid-column: 1 / -1; grid-row: 2; }
  .hos-daypick__pill { min-height: 36px; } /* tablet/phone hit target */
}

/* ── tasks/155 phase B — paint mode on the month grid. The grid is read-only by default (a cell
      links to that day); this bar turns it into an editable surface, which is a deliberate
      reversal of tasks/029's forms-first rule for THIS one case: bulk day-off entry is a
      different job from filing one dated leave with a note, and the form stays for the latter. ── */
.hos-paintbar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding: 10px 12px; border-bottom: 1px solid var(--hairline); background: var(--surface-alt); }
.hos-paintbar__brushes { display: flex; gap: 6px; flex-wrap: wrap; }
.hos-paintbar__hint { font-size: var(--text-2xs); color: var(--muted-2); margin-left: auto; }
.hos-brush { display: inline-flex; align-items: center; gap: 7px; height: 30px; padding: 0 11px; border: 1px solid var(--hairline); border-radius: var(--radius-sm); background: var(--surface); color: var(--ink); font-size: var(--text-xs); font-weight: 600; cursor: pointer; }
.hos-brush:hover { border-color: var(--accent); }
.hos-brush[aria-pressed="true"] { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); background: var(--accent-soft); color: var(--accent); }
/* The swatch is a 20px TILE carrying the kind's icon on the SAME amber the painted cell gets, so
   the bar doubles as the legend. Colour says "this person is off" — the question you scan for;
   the ICON says why — the question you only ask about one day. A 12px flat colour chip with no
   shape was indistinguishable across the brushes, and per-kind colours were four pale washes that
   nobody could tell apart (and which the weekend tint out-ranked anyway). */
.hos-brush__sw { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; border-radius: var(--radius-sm); background: var(--status-briefing-bg); border: 1px solid var(--status-briefing-border); color: var(--status-briefing-fg); }
/* Sick and training keep their own colours, matching their cells. */
.hos-brush--sick .hos-brush__sw { background: var(--status-cancelled-bg); border-color: var(--status-cancelled-border); color: var(--status-cancelled-fg); }
.hos-brush--training .hos-brush__sw { background: var(--status-in-water-bg); border-color: var(--status-in-water-border); color: var(--status-in-water-fg); }

/* Per-KIND cell colour. Before this every painted cell used one colour and a bare letter
   (D/A/S/T), so the brush swatches advertised a distinction the grid did not actually render.
   Each kind now gets its own tint + icon, matching its brush exactly. */
.hos-roster-ic { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; }
/* ONE amber for every kind — the icon carries the type. See .hos-brush__sw above for why.
   THREE classes deliberately: `.hos-roster-c.hos-roster-wk` (the weekend tint) is 0,2,0 and is
   declared further down this file, so a 0,1,0 or even 0,2,0 rule LOSES to it — leave falling on a
   Saturday rendered plain slate and the colouring silently broke on 2 days in 7. Measured, not
   guessed: the cell computed rgb(248,250,252) instead of the amber. A day with leave on it must
   out-rank "this is a weekend". */
/* NOT scoped to paint mode: the read view and the edit view must speak the same language for the
   same fact. A letter in read mode and an icon in edit mode meant switching modes changed what
   leave looked like — and the letter collided with the BOAT letters in neighbouring cells. */
.hos-roster .hos-roster-c.hos-roster-c--k-day_off,
.hos-roster .hos-roster-c.hos-roster-c--k-annual {
  background: var(--status-briefing-bg);
  color: var(--status-briefing-fg);
}
/* Two exceptions to the single amber, both for scanning: sick is the day somebody did not turn
   up (unplanned), training is a commitment elsewhere. Amber = off as planned. */
.hos-roster .hos-roster-c.hos-roster-c--k-sick {
  background: var(--status-cancelled-bg);
  color: var(--status-cancelled-fg);
}
.hos-roster .hos-roster-c.hos-roster-c--k-training {
  background: var(--status-in-water-bg);
  color: var(--status-in-water-fg);
}

/* In paint mode the cursor and the disabled text selection are what make a drag feel like a
   stroke rather than a botched text highlight. */
.hos-roster--painting { user-select: none; }
.hos-roster--painting .hos-roster-c { cursor: cell; }
/* A cell covered by a MULTI-DAY leave is inert: shredding an approved holiday in half is a
   destructive edit, and a grid stroke is far too casual a gesture for it. */
.hos-roster--painting .hos-roster-c--locked { cursor: not-allowed; opacity: .55; }

/* ── tasks/027 — monthly roster grid. A genuinely tabular VIEW → a real <table> inside .os-card,
      horizontally scrollable (class-based per the DiveOS gate). Cell = state fill + boat letter (D2);
      the bottom row = uncovered-boat count on a heat tint (D4). Sticky name column + day headers. ── */
.hos-roster-card { padding: 0; overflow: hidden; }
.hos-roster-scroll { overflow-x: auto; }
.hos-roster { border-collapse: separate; border-spacing: 0; width: max-content; min-width: 100%; font-size: var(--text-2xs); }
.hos-roster-name { position: sticky; left: 0; z-index: 2; background: var(--surface); text-align: left; font-weight: 600; color: var(--ink); font-size: var(--text-sm); padding: 6px 12px; min-width: 150px; max-width: 180px; border-right: 1px solid var(--hairline); border-bottom: 1px solid var(--hairline-2); }
.hos-roster-corner { z-index: 3; top: 0; }
.hos-roster-dh { position: sticky; top: 0; z-index: 1; background: var(--surface); color: var(--text-muted); font-weight: 600; text-align: center; padding: 5px 0; min-width: 30px; width: 30px; line-height: 1.15; border-bottom: 1px solid var(--hairline); }
.hos-roster-dow { display: block; font-size: var(--text-2xs); color: var(--muted-2); text-transform: uppercase; }
.hos-roster-lvrow { font-size: var(--text-sm); }
/* tasks/155 — the leave panel, split by what you DO with each kind. Absences keep a row with a
   delete; routine days off collapse to one count per person, because listing them individually
   printed ~200 near-identical lines a month and buried the rows that actually need managing. */
.hos-lvlist { display: flex; flex-direction: column; gap: 7px; }
.hos-lvrow { display: flex; align-items: center; gap: 10px; }
.hos-lvrow__who { min-width: 130px; }
.hos-lvrow__span { flex: 1; }
.hos-lvrow__form { margin: 0; }
.hos-lvrow__x { width: 24px; height: 24px; display: inline-flex; align-items: center; justify-content: center; border: 1px solid var(--hairline); border-radius: var(--radius-sm); background: var(--surface); color: var(--muted-2); }
/* Grouped BY PERSON, one row each: name · their absences · their day-off count. Splitting it into
   an "absences" list and a separate "days off" list meant somebody with both — an annual leave AND
   routine days off in the same month — appeared twice, in two places, and you had to assemble
   their month yourself. One row per person is the whole picture. */
.hos-lvperson { display: flex; align-items: flex-start; gap: 12px; padding: 7px 0; border-bottom: 1px solid var(--hairline-2); font-size: var(--text-sm); }
.hos-lvperson:last-child { border-bottom: 0; }
.hos-lvperson__who { flex: none; width: 150px; }
.hos-lvperson__items { flex: 1; display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.hos-lvperson__off { flex: none; text-align: right; min-width: 84px; font-family: var(--font-mono); font-size: var(--text-xs); color: var(--muted-2); }
.hos-lvitem { display: flex; align-items: center; gap: 8px; }

/* Add-leave drawer: the chassis is .hos-odrawer and the inner layout is the GEM's
   .os-side-panel (header / scrolling body / pinned .os-side-panel__action-bar), same as
   front_office/_occupant_drawer. Nothing local is needed for the shell — only the form grid. */
/* Sticky error flash. Geometry deliberately matches the gem's .toast-stack.tr (fixed, top-right,
   360px, 16px inset) so a notice and an error land in the same place — but this one has no
   dismissal timer. See flash_alert_controller.js for why errors must not auto-dismiss. */
/* z-index is ONE ABOVE the toast stack, deliberately. The gem's .toast-stack.tr is
   the identical box (fixed, top/right 16px, width 360px, z-index var(--z-toast)) and
   is appended to <body> AFTER the layout, so at equal z-index the toast paints on top.
   On a response carrying both a notice and an alert that hid the refusal under a
   disposable success for 4s. The error is the message that matters, so it wins. */
/* `.hos-flashnote` is the SUCCESS twin: same box, same stickiness, accent tone instead
   of the cancelled ramp. It exists because some notices are not disposable — "A reset
   link has been sent to your email" is the whole outcome of the action, and the gem's
   4s toast erases it (the same WCAG 2.2.1 argument that took errors off the toast).
   Opt in per surface with `render "shared/flash", sticky_notice: true`; everywhere
   else a notice stays a toast, because "Days off updated" genuinely is disposable. */
.hos-flasherr, .hos-flashnote { position: fixed; top: 16px; right: 16px; z-index: calc(var(--z-toast) + 1); display: flex; align-items: flex-start; gap: 11px; width: 360px; max-width: calc(100vw - 32px); padding: 12px 14px; border-radius: var(--radius-md); background: var(--surface); color: var(--ink); font-size: var(--text-sm); box-shadow: var(--shadow-lg, 0 10px 30px rgba(17, 24, 39, 0.16)); }
.hos-flasherr  { border: 1px solid var(--status-cancelled-border); border-left: 4px solid var(--status-cancelled-fg); }
.hos-flashnote { border: 1px solid var(--accent-border); border-left: 4px solid var(--accent); }
.hos-flasherr__ic { flex: none; margin-top: 1px; color: var(--status-cancelled-fg); }
.hos-flashnote__ic { flex: none; margin-top: 1px; color: var(--accent); }
.hos-flasherr__msg, .hos-flashnote__msg { flex: 1; min-width: 0; }
.hos-flasherr__x, .hos-flashnote__x { flex: none; padding: 2px; margin: -2px -2px 0 0; border: 0; border-radius: var(--radius-sm); background: none; color: var(--muted-2); cursor: pointer; line-height: 0; }
.hos-flasherr__x:hover, .hos-flashnote__x:hover { background: var(--surface-alt); color: var(--ink); }

.hos-leaveform { display: grid; gap: 14px; }
.hos-leaveform__dates { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }

@media (max-width: 720px) {
  .hos-lvperson { flex-wrap: wrap; }
  .hos-lvperson__who { width: auto; flex: 1; }
  .hos-lvperson__items { flex: 1 1 100%; order: 3; }
}
.hos-roster-lvspan { color: var(--slate-700); }
.hos-run-time { font-family: var(--font-mono); font-size: var(--text-sm); font-weight: 700; color: var(--ink); } /* a HH:MM run time on the transfers board */
.hos-roster-grouphd { position: sticky; left: 0; text-align: left; font-size: var(--text-2xs); font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--muted-2); background: var(--slate-50); padding: 5px 12px; }
.hos-roster-dh.hos-roster-wk, .hos-roster-c.hos-roster-wk, .hos-roster-cov.hos-roster-wk { background: var(--slate-50); }
.hos-roster-dh.hos-roster-today { color: var(--accent); box-shadow: inset 0 -2px 0 var(--accent); }
.hos-roster-c.hos-roster-today, .hos-roster-cov.hos-roster-today { box-shadow: inset 1px 0 0 var(--accent-soft), inset -1px 0 0 var(--accent-soft); }
.hos-roster-c { padding: 0; text-align: center; height: 30px; border-bottom: 1px solid var(--hairline-2); border-right: 1px solid var(--hairline-2); }
.hos-roster-link { display: flex; align-items: center; justify-content: center; gap: 1px; width: 100%; height: 30px; text-decoration: none; }
/* Boat INITIALS in the cell (tasks/155). Mono so two characters always occupy the same width in a
   30px cell; the "+" flags a second boat that did not fit (the tooltip names them all). */
.hos-roster-bi { font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: 700; line-height: 1; letter-spacing: -0.02em; }
.hos-roster-bi--skip { color: var(--accent); }
.hos-roster-bi--crew { color: var(--muted-2); }
.hos-roster-bi__more { opacity: .6; }

.hos-roster-c--skip { background: var(--accent-soft); }
.hos-roster-c--crew { background: var(--slate-100); }
.hos-roster-c--leave { background: var(--status-briefing-bg); }
.hos-roster-c--ovr { box-shadow: inset 0 0 0 2px var(--accent); }
.hos-roster-lv { font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: 700; color: var(--status-briefing-fg); }
.hos-roster-covhd { border-top: 2px solid var(--hairline); }
.hos-roster-cov { text-align: center; font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: 700; height: 26px; color: var(--muted-2); border-right: 1px solid var(--hairline-2); border-top: 2px solid var(--hairline); }
.hos-roster-cov--1 { background: var(--status-briefing-bg); color: var(--status-briefing-fg); }
.hos-roster-cov--2 { background: var(--status-cancelled-bg); color: var(--status-cancelled-fg); }

/* ── Drawer folio-footer figures (M20): money is ALWAYS Geist Mono tabular ── */
/* .hos-money-figure retired onto gem `.os-figure` (+--cancelled/--confirmed,
   v1.1.0). os-figure is size-neutral; arena's prominent-money treatment
   (text-lg/600/ls-tight) survives as this thin emphasis modifier, layered on
   .os-figure — additive, not a gem shadow. */
.hos-figure-strong { font-size: var(--text-lg); font-weight: 600; letter-spacing: var(--ls-tight); }

/* ── Statement line amount (guest portal folio) ── */
.st-line-amount { font-size: var(--text-sm); }

/* ── Transport vessel cards + drawer (tasks/023) — semantic classes so the new
      fleet component is class-based, not inline token-styles (DiveOS gate). ── */
.hos-icon-badge { display:inline-flex; align-items:center; justify-content:center; flex:none; border-radius:var(--radius-md); background:var(--accent-soft); color:var(--accent); }
.hos-icon-badge--lg { border-radius:var(--radius-lg); }
.hos-card-title { font-size:var(--text-md); font-weight:600; color:var(--ink); }
.hos-row-name { font-size:var(--text-sm); font-weight:600; color:var(--ink); } /* a list-row name (manifest heads/party header) */
.hos-muted { font-size:var(--text-sm); color:var(--text-muted); }
.hos-muted--xs { font-size:var(--text-xs); }
.hos-strong-ink { color:var(--ink); } /* inline emphasis inside a muted run */

/* ── Excursion trip roster (#19) ─────────────────────────────────────── */
.hos-roster-count { font-size:var(--text-lg); color:var(--ink); }
.hos-row-name__count { color:var(--muted-2); font-weight:400; } /* party-size ×N */
/* excursion roster legacy-party + headcount now render as flat divider rows in
   components/excursion_roster.css (.hos-exc-legacyrow / .hos-exc-hcrow) — the
   bordered card-in-card variants were retired 2026-08-02 (read-surface rule:
   rows + hairline, never a bordered sub-panel). */
.hos-form-error { padding:10px 12px; border-radius:var(--radius); background:var(--danger-bg); color:var(--danger); font-size:var(--text-sm); border:1px solid var(--danger-border); }

/* ── Segmented pill-group (link-based) ──────────────────────────────────
   Gray backing tray for a row of mutually-exclusive link pills (view/shift
   switchers). The DiveOS .os-segment is input-based; this is the link variant
   used by the boards (tasks/009 #12 first classed use — the older inline
   copies are baselined debt to migrate opportunistically). */
/* .hos-seg / .hos-seg-btn retired onto gem `.os-segmented` (__option +
   __option--active, v1.1.0). .hos-segtog (package-builder JS toggle) is a
   distinct arena-unique family and stays local. */

/* ── Agent portal chrome (tasks/031 P1 · conformance sweep tasks/071) ───────
   The trade portal is an internal-grade tool, so it reads like the staff app:
   its page band, cards, tables, stat cards, callouts, labels, chips, uploads
   and buttons ALL come from the gem now. What is left below is the residue the
   gem has no component for, carrying the `hos-` prefix (see CLAUDE.md).

   The bare `.hos-agp-*` text utilities survive ONLY because callsites OUTSIDE this
   bucket still use them — settings/agent_{allotments,rates,users,agents},
   crew_portal/fuel, guest_requests. Drain them in those buckets, then delete
   these four rules and the family is gone. */
.hos-agp-muted { color: var(--text-muted); }
.hos-agp-xs { font-size: var(--text-xs); }
.hos-agp-sec { font-size: var(--text-xs); text-transform: uppercase; letter-spacing: .07em; color: var(--text-muted); }
.hos-agp-mono { font-family: var(--font-mono); }
.hos-auth-canvas { background: radial-gradient(130% 120% at 50% 0%, var(--slate-50), var(--canvas)); }
.hos-brandline { font-family: var(--font-display); font-size: var(--text-base); letter-spacing: -0.01em; color: var(--ink); }
.hos-brandline b { font-weight: 600; }

/* ---- QA registry checklist (tasks/033, /admin — System Health & QA) ---- */
.hos-qa-kpi { font-family: var(--font-display); font-size: var(--text-xl); font-weight: 600; color: var(--ink); }
.hos-qa-kpi--dim { color: var(--muted); }
.hos-qa-kpi-sub { font-size: var(--text-sm); font-weight: 500; color: var(--muted); }
.hos-qa-key { font-family: var(--font-mono); font-size: var(--text-2xs); color: var(--muted); }
.hos-qa-ref { font-family: var(--font-mono); font-size: var(--text-2xs); color: var(--muted-2); }
.hos-qa-title { font-weight: 600; color: var(--ink); }
.hos-qa-title--done { text-decoration: line-through; color: var(--success); }
.hos-qa-desc { font-size: var(--text-sm); color: var(--muted); }
.hos-qa-body { font-size: var(--text-sm); color: var(--slate-700); }
.hos-qa-strong { font-size: var(--text-sm); color: var(--ink); }
.hos-qa-note { font-size: var(--text-2xs); color: var(--muted-2); }
.hos-qa-note--alert { color: var(--danger); }
.hos-qa-expand { font-size: var(--text-2xs); color: var(--accent); cursor: pointer; }
.hos-qa-row { display: flex; align-items: flex-start; gap: 14px; padding: 13px 18px; border-bottom: 1px solid var(--hairline); }
.hos-qa-row--done { background: var(--success-bg); }
.hos-qa-state { display: inline-flex; width: 26px; height: 26px; align-items: center; justify-content: center;
  border-radius: var(--radius-sm); border: 1.5px solid var(--hairline-strong); background: transparent; color: var(--muted); }
button.hos-qa-state { cursor: pointer; }
.hos-qa-state--empty { color: transparent; }
.hos-qa-state--completed { background: var(--status-confirmed-bg); border-color: var(--success-border); color: var(--status-confirmed-fg); }
.hos-qa-state--failed { background: var(--status-cancelled-bg); border-color: var(--danger-border); color: var(--status-cancelled-fg); }
.hos-qa-state--skipped { background: var(--slate-300); border-color: var(--slate-300); color: var(--slate-700); }
/* Block remaining bar (tasks/031 design review P5) — dashboard blocks + Book rail. */
.hos-blockbar { height: 7px; border-radius: var(--radius-pill); background: var(--slate-100); overflow: hidden; }
.hos-blockbar__fill { height: 100%; background: var(--accent); border-radius: var(--radius-pill); }

/* Agent-portal composition (approved Stitch reference, 2026-07-07). Only the
   two page grids remain local — everything they hold is a gem component. */
.hos-agp-grid { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 16px; align-items: start; }
@media (max-width: 1100px) { .hos-agp-grid { grid-template-columns: 1fr; }}
.hos-agp-grid--book { grid-template-columns: minmax(0, 1fr) 340px; gap: 18px; }
@media (max-width: 1000px) { .hos-agp-grid--book { grid-template-columns: 1fr; }}
.hos-agp-grid--ledger { grid-template-columns: 360px minmax(0, 1fr); }
.hos-agp-3col { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; }
@media (max-width: 760px) { .hos-agp-3col { grid-template-columns: 1fr; }}
.hos-agp-col { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
.hos-agp-col--tight { gap: 9px; }
.hos-agp-rail { position: sticky; top: 16px; }
.hos-agp-stack { margin-bottom: 16px; }
.hos-agp-centered { text-align: center; }
.hos-agp-rowactions { white-space: nowrap; }
.hos-agp-inlineform { display: inline; margin: 0; }
/* A rail action is the rail's whole width — the .os-form-actions row still owns
   placement (rule 9), this only stretches its single button. */
.hos-agp-actions--block { margin-top: 12px; }
.hos-agp-actions--block > * { flex: 1; }

/* Booking-summary rail internals. */
.hos-agp-railtitle { margin: 0 0 6px; }
.hos-agp-railmeta { margin: 0 0 12px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.hos-agp-sumline { display: flex; justify-content: space-between; margin-bottom: 4px; }
.hos-agp-sumline--total { font-weight: 650; border-top: 1px solid var(--hairline); padding-top: 6px; margin-bottom: 12px; }
/* The cancel card is a deliberate danger zone, set apart from the amend grid. */
.hos-agp-danger { margin-top: 20px; border-color: var(--danger-border); }
.hos-agp-formcol { max-width: 520px; gap: 12px; }

/* Rooming — occupant identity grid, add-guest row, portal-link copy row. */
.hos-agp-2col { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.hos-agp-2col__full { grid-column: 1 / -1; }
.hos-agp-addicon { color: var(--accent); margin-bottom: 6px; }
.hos-agp-addrow { display: flex; gap: 8px; flex-wrap: wrap; align-items: end; justify-content: center; text-align: left; }
.hos-agp-addrow__name { min-width: 180px; }
.hos-agp-copyrow { display: flex; gap: 6px; align-items: center; margin-bottom: 12px; }
.hos-agp-copyrow .os-input { flex: 1; min-width: 0; }
.hos-agp-ocrstrip { margin-top: 4px; }
.hos-agp-ocrstrip__act { display: inline-flex; align-items: center; gap: 8px; }
.hos-agp-ocrstrip__act .os-file-field { max-width: 200px; }
.hos-agp-templaterow { margin: 10px 0 0; }

/* Rates + statements. */
.hos-agp-narrow { max-width: 720px; }
.hos-agp-lockline { display: flex; align-items: center; gap: 6px; margin: 0; }
.hos-agp-footnote { margin: 12px 0 0; }
.hos-agp-flush { padding-left: 0; padding-right: 0; }
.hos-agp-rowpad { padding: 0 20px; }
.hos-agp-ledgerrow { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 10px 20px; border-bottom: 1px solid var(--hairline-2); }
.hos-agp-ledgerrow:last-child { border-bottom: 0; }
.hos-agp-ledgerrow__end { margin-left: auto; text-align: right; }
.hos-agp-pager { display: flex; gap: 8px; margin-top: 12px; }
.hos-agp-pager__end { margin-left: auto; }

/* ── Agent settlements (STAFF side: /agents/:id/settlements, production) ────
   A centred single-column reading width; everything inside is a gem
   component (header zone, table-wrap, empty state). */
/* tasks/073 A3/A5 — `gap: var(--gutter)` stacked these sections 48px apart:
   `--gutter` is the shell's horizontal page INSET, not a stack rhythm. The extra
   `padding: var(--space-5)` also re-padded a column the shell already insets,
   which is what pushed it off the centre the `margin-inline` was asking for. */
.hos-agst { display: flex; flex-direction: column; gap: var(--space-5); max-width: 960px; margin-inline: auto; }
.hos-agst--narrow { max-width: 860px; }
.hos-agst--wide { max-width: 1000px; }
/* The gem's header zone is title-cluster vs actions; the cluster has to claim
   the free space or a long title wraps into a narrow column beside the form. */
.hos-agst-head { flex: 1; min-width: 0; }
.hos-agst-toolbar { display: flex; flex-wrap: wrap; gap: 20px; align-items: flex-end; }
.hos-agst-form { display: flex; gap: 8px; align-items: end; }
.hos-agst-form--end { margin-left: auto; }
.hos-agst-form label { display: flex; flex-direction: column; gap: 4px; }
.hos-agst-total { background: var(--slate-50); font-weight: 600; color: var(--ink); }

/* Allotment-block row: identity · remaining-nights bar · Book. */
.hos-agp-blockrow { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.hos-agp-blockrow__id { min-width: 150px; }
.hos-agp-blockrow__bar { flex: 1; min-width: 180px; }
.hos-agp-blockrow__legend { display: flex; justify-content: space-between; margin-bottom: 4px; }

/* The guest's portal PIN, set out to be read aloud across a desk. No gem
   equivalent (a large tracked one-time code is a HotelOS-domain thing). */
.hos-agp-pinbox { background: var(--accent-soft, var(--slate-50)); border-radius: var(--radius-md); padding: 10px; text-align: center; font-family: var(--font-mono); font-size: var(--text-xl); font-weight: 650; letter-spacing: .3em; }

/* Quantity stepper (− n +) now uses the gem's `.os-qty`. The local
   `.hos-agp-stepper` duplicated it (rubric 14) and its "GEM GAP: os-ds has no
   numeric stepper" note was stale — os_ds/components/qty.css ships it. */

/* Passport-OCR strip (agent rooming + crew fuel receipts). */
.hos-agp-ocrstrip { display: flex; align-items: center; gap: 10px; background: var(--accent-soft); border-radius: var(--radius-md); padding: 10px 14px; font-size: var(--text-xs); }
.hos-agp-ocrstrip .hos-agp-meta { margin-left: auto; font-weight: 500; }

/* An unnamed (TBA) guest reads as a placeholder, not a name. */
.hos-agp-tba { font-style: italic; color: var(--text-muted); }
/* A dashed card = "nothing here yet, add one". Extension of the gem's .os-card.
   GEM GAP: propose `.hos-card--dashed` upstream. */
.hos-card--dashed { border-style: dashed; }

/* The field-only .hom-* moved to hotelos_mobile.css (tasks/159). .hom-fseg,
   .hom-pinbrand STAY (pinbrand already lives only in hotelos_mobile.css) — they're used
   outside the field layout (boarding, transport board, the shared arena_mark) which
   don't load hotelos_mobile.css.
   .hom-flash* REMOVED: the field portal now renders shared/_flash (os-ds toast + sticky
   .hos-flasherr), and the fuel inline error uses .hos-inlinealert. No refs remain. */

.hom-fseg { display: flex; gap: 5px; overflow-x: auto; }
.hom-fseg button { flex: none; min-height: 40px; padding: 6px 12px; border-radius: 999px;
  border: 1px solid var(--hairline); background: var(--surface); color: var(--text-muted);
  font-size: var(--text-xs); font-weight: 600; white-space: nowrap; display: inline-flex;
  align-items: center; gap: 5px; }
.hom-fseg button.on { background: var(--accent); border-color: var(--accent); color: var(--surface); }
.hom-fseg button .n { font-family: var(--font-mono); font-weight: 700; }

/* hos-bp-movepick — one candidate run in the boarding board's "move party" picker.
   A row, not a card: it is a list of choices, and a card per option would be the
   stack-of-cards pattern DESIGN.md rejects. Disabled when the party does not fit,
   because a party that half-moves is worse than one that cannot. */
.hos-bp-movepick { display: flex; align-items: center; gap: 10px; width: 100%;
  min-height: 44px; padding: 9px 11px; border: 1px solid var(--hairline-2);
  border-radius: var(--radius-md); background: var(--surface); cursor: pointer;
  text-align: left; font-family: var(--font-sans); }
.hos-bp-movepick:hover { background: var(--surface-alt); border-color: var(--hairline); }
/* A tablet at a jetty is still keyboard-reachable, and these are buttons in a
   modal — the one place focus most needs to be visible. */
.hos-bp-movepick:focus-visible { outline: 2px solid var(--focus-ring-color);
  outline-offset: 2px; box-shadow: var(--shadow-focus); }
/* Dimmed with TOKENS, never `opacity`. Opacity composites the whole subtree, so
   a child can never paint above its parent's alpha — `opacity: 1` on the seat
   count was a no-op, and the one element that explains why the row is unusable
   rendered at half strength on a sunlit tablet. Recolour the parts that should
   recede and leave the count at full contrast. */
.hos-bp-movepick.is-full { background: var(--surface-alt); border-color: var(--hairline-2);
  cursor: not-allowed; }
.hos-bp-movepick.is-full .hos-bp-movepick__time,
.hos-bp-movepick.is-full .hos-bp-movepick__boat { color: var(--muted); }
/* The reason the row is still shown is the seat count — so dimming the row must
   not dim the one thing that justifies keeping it. */
.hos-bp-movepick.is-full .hos-bp-movepick__seats { color: var(--danger); font-weight: 600; }
.hos-bp-movepick__time { font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-weight: 700; font-size: var(--text-sm); color: var(--ink); }
.hos-bp-movepick__boat { flex: 1; min-width: 0; font-size: var(--text-sm); color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The decision datum — the count a rep weighs against the party size — so it
   gets mono + tabular-nums like the departure time beside it (DESIGN.md makes
   that mandatory for anything scanned in a column) rather than the smallest,
   lightest text in the row. */
.hos-bp-movepick__seats { font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: var(--text-sm); color: var(--muted); white-space: nowrap; }


/* Ext-ferry / external-transfer state pills + a faded row for a walk-on handed
   to another resort's boat (it isn't riding an Arena boat). */
.hos-status-pill--extferry, .status-pill--extferry { background: var(--info-bg); color: var(--info); }
/* Neutral pill — the gem ships no gray status-pill, but the field portals map a
   `draft`/unknown Trip state onto it (transport_helper#trip_status_pill). Without
   this rule those badges fall through to the base .status-pill (transparent) and
   render colourless — the exact defect the mapping exists to prevent. */
/* Neutral chip (draft trips, Duty crew role). White fill + hairline border so the
   slate-500 label clears WCAG AA 4.5:1 (it's ~4.3:1 on the slate-100 fill) while
   staying on semantic tokens only. */
.os-status-pill--muted, .status-pill--muted { background: var(--surface); color: var(--muted-2); border-color: var(--hairline); }

/* ─── Room-status pill (HotelOS domain) ───────────────────────────────────
   A `.hos-*` EXTENSION of the gem's `.os-status-pill`, not a second pill: it
   only repaints the fill/ink from the `--st-*` room-status family (tokens.css),
   which the gem has no notion of (housekeeping/occupancy is hotel-only).
   Replaces a per-callsite inline `style="color:var(--st-…)"` in
   SettingsHelper#room_status_pill / FrontOfficeHelper.
   The base carries what every variant shares (the gem's pill border is dropped
   because the --st-* fills are opaque); each variant only repaints. */
.hos-room-pill           { border-color: transparent; }
.hos-room-pill--vacant   { color: var(--st-vacant);   background: var(--st-vacant-bg); }
.hos-room-pill--occupied { color: var(--st-occupied); background: var(--st-occupied-bg); }
.hos-room-pill--reserved { color: var(--st-reserved); background: var(--st-reserved-bg); }
.hos-room-pill--dueout   { color: var(--st-dueout);   background: var(--st-dueout-bg); }
.hos-room-pill--dirty    { color: var(--st-dirty);    background: var(--st-dirty-bg); }
.hos-room-pill--blocked  { color: var(--st-blocked);  background: var(--st-blocked-bg); }


/* ── hos-run — the #22 Excursion Run full-screen tablet surface ──────────────
   No sidebar: a focused single-trip state machine the crew drives on the water.
   Token-driven + class-based (hallmark-clean). */
/* THE SHELL is the scroller (DESIGN.md run archetype, measured against DiveOS
   dive_trips/:id/run): a FULL-WIDTH full-height flex column whose only scroll is
   on itself, so the header scrolls away, the .os-stickybar stepper holds at top:0,
   the body scrolls, and .hos-wz-foot pins to the floor (margin-top:auto fills a
   short step). `100dvh` with a `100vh` fallback keeps it exact on mobile chrome.
   FULL-BLEED BANDS: the header, stepper and footer span the whole pane (they are
   flex items, stretched); only the CONTENT re-insets to a centred column (the
   stepper to 760, the body to 1040) — matching the DiveOS run, whose bands are
   edge-to-edge while the stage is capped and centred. */
.hos-run { background: var(--canvas);
  height: 100vh; height: 100dvh;
  display: flex; flex-direction: column; overflow-y: auto; }
/* Header band — full-bleed; trip identity + the safety exit. SCROLLS AWAY above
   the held stepper (not sticky); a bottom hairline separates it as chrome. */
.hos-run__header { display: flex; align-items: center; justify-content: flex-start;
  gap: 16px; flex-wrap: wrap; padding: 18px 28px; background: var(--canvas);
  border-bottom: 1px solid var(--hairline); }
/* Back link (DiveOS focus-bar ‹ Plan) — quiet, at the far left. */
.hos-run__back { display: inline-flex; align-items: center; gap: 6px; flex: 0 0 auto;
  padding: 6px 10px; border-radius: var(--radius-md); text-decoration: none;
  font-size: var(--text-xs); font-weight: 600; color: var(--muted); }
.hos-run__back:hover { background: var(--surface-alt); color: var(--ink); }
.hos-run__ident { min-width: 0; flex: 1 1 auto; }
/* Title · status pill · meta all inline on ONE horizontal row (wraps on narrow
   widths); the meta reads as a muted continuation after the chip. */
.hos-run__idrow { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 10px; min-width: 0; }
.hos-run__title { font-family: var(--font-display); font-size: var(--text-xl);
  font-weight: 600; color: var(--ink); line-height: 1.15; }
.hos-run__sub { font-size: var(--text-sm); color: var(--muted); }
.hos-run__chip { display: inline-flex; align-items: center; gap: 6px; flex: 0 0 auto;
  padding: 3px 10px; border-radius: var(--radius-pill); border: 1px solid var(--hairline);
  background: var(--surface-alt); color: var(--muted);
  font-size: var(--text-xs); font-weight: 600; }
/* Emergency — danger button at the far right of the header (NOT the footer). */
.hos-run__emg { margin-left: auto; flex: 0 0 auto; display: inline-flex; align-items: center;
  gap: 6px; padding: 8px 13px; border-radius: var(--radius-md); cursor: pointer;
  border: 1px solid var(--danger-border); background: var(--danger-bg); color: var(--danger);
  font: inherit; font-size: var(--text-xs); font-weight: 600; }
.hos-run__emg:hover { border-color: var(--danger); }
/* Held stepper — a FULL-BLEED band (the gem's .os-stickybar owns sticky/top:0/
   canvas/hairline); its steps re-inset to a 760 centred column (DiveOS run-stepper
   inner cap). */
.hos-run > .os-stickybar { padding: 14px 32px 16px; margin-top: 0; }
.hos-run__steps { max-width: 760px; margin: 0 auto; }
/* Body — the scrolling region between the held stepper and the pinned footer;
   content re-insets to a centred 1040 column (bands stay full-bleed). Bottom
   padding clears the sticky footer on a tall step. */
.hos-run__body { width: 100%; max-width: 1040px; margin: 0 auto;
  padding: 24px 32px 120px; display: flex; flex-direction: column; gap: 18px; }
/* Pin the (already sticky-bottom) wizard footer to the floor of the full-height
   shell so it lands on the viewport bottom even when the step is short. Full-bleed
   band, matched to the DiveOS run-actionbar (14/40, ~71px tall). */
.hos-run > .hos-wz-foot { margin-top: auto; }
.hos-run > .hos-wz-foot .hos-wz-foot-inner { padding: 14px 40px; }
.hos-run__ladder { display: flex; gap: 6px; flex-wrap: wrap; }
.hos-run__rung { display: flex; align-items: center; gap: 6px; padding: 6px 12px;
  border-radius: var(--radius-pill); border: 1px solid var(--hairline);
  background: var(--surface); font-size: var(--text-sm); color: var(--muted); }
.hos-run__rung--active { border-color: var(--accent-border); background: var(--accent-soft);
  color: var(--accent-strong); font-weight: 600; }
.hos-run__rung--done { background: var(--status-confirmed-bg); color: var(--status-confirmed-fg);
  border-color: var(--status-confirmed-border); }
/* The current step panel — big tap targets. Padding matched to the DiveOS run
   card (22/24/24). */
.hos-run__panel { padding: 22px 24px 24px; }
/* Pre-departure checks — tickable inline, a responsive chip grid mirroring the
   DiveOS run SAFETY CHECKS (auto-fill columns ~220px, 12px gap, 44px chips). */
.hos-run__checks { margin: 0 0 4px; }
.hos-run__checks-head { display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-bottom: 10px; }
.hos-run__checks-gate { display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--text-xs); font-weight: 600; color: var(--briefing); }
/* Single-column vertical stack (DiveOS SAFETY CHECKS): 8px row gap, chips sized
   to their content and left-aligned. */
.hos-run__checkgrid { display: grid; grid-template-columns: 1fr; gap: 8px; }
.hos-run__check { margin: 0; }
.hos-run__check-lab { display: inline-flex; align-items: center; gap: 10px; width: fit-content;
  max-width: 100%; min-height: 44px; padding: 0 14px; border: 1px solid var(--hairline);
  border-radius: var(--radius-md); background: var(--surface-alt); cursor: pointer; }
.hos-run__check-lab:has(.os-field-check__input:checked) { background: var(--accent-soft);
  border-color: var(--accent-border); }
.hos-run__check--ro { display: inline-flex; align-items: center; justify-content: space-between; gap: 10px;
  width: fit-content; max-width: 100%; min-height: 44px; padding: 0 14px;
  border: 1px solid var(--hairline); border-radius: var(--radius-md); background: var(--surface-alt); }
.hos-run__check-name { font-size: var(--text-sm); color: var(--ink); }
.hos-run__check-req { color: var(--accent); margin-left: 2px; }
.hos-run__checks-more { margin-top: 12px; }
.hos-run__checks.is-blocked .hos-run__checks-head { color: var(--ink); }
/* Save + Add item + full-checklist row beneath the stack (left-aligned). */
.hos-run__checkactions { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 10px; margin-top: 16px; }
.hos-run__additem { min-width: 0; }
.hos-run__additem-toggle { cursor: pointer; list-style: none; }
.hos-run__additem-toggle::-webkit-details-marker { display: none; }
.hos-run__additem[open] .hos-run__additem-toggle { margin-bottom: 12px; }
.hos-run__additem-form { display: flex; flex-direction: column; gap: 12px; padding: 14px;
  border: 1px solid var(--hairline); border-radius: var(--radius-lg); background: var(--surface-alt);
  max-width: 420px; }
.hos-run__step-title { font-family: var(--font-display); font-size: var(--text-lg);
  font-weight: 600; color: var(--ink); margin-bottom: 4px; }
.hos-run__step-hint { font-size: var(--text-sm); color: var(--muted); margin-bottom: 16px; }
.hos-run__party { display: flex; align-items: center; gap: 12px; padding: 12px 14px;
  border: 1px solid var(--hairline); border-radius: var(--radius-md); background: var(--surface); }
.hos-run__party + .hos-run__party { margin-top: 8px; }
.hos-run__blocked { padding: 14px; border-radius: var(--radius-md);
  background: var(--danger-bg); border: 1px solid var(--danger-border); color: var(--danger);
  font-size: var(--text-sm); }
.hos-run__gate-item { font-size: var(--text-sm); color: var(--ink); padding: 4px 0; }
/* Live check-in counter (the redesign's "N of M aboard" affordance) + the
   auto-advance note shown when the last guest checking in rolls the ladder to
   Depart. Both are token-driven; the note sits on --accent with --surface text
   (the same on-accent pairing the stepper tick uses). */
.hos-run__count { display: flex; align-items: center; gap: 10px; margin-bottom: 14px;
  font-size: var(--text-sm); color: var(--secondary); }
.hos-run__count b { font-size: var(--text-md); color: var(--ink); }
.hos-run__track { flex: 1; max-width: 180px; height: 6px; border-radius: var(--radius-pill);
  background: var(--surface-alt); overflow: hidden; }
.hos-run__fill { height: 100%; background: var(--accent); border-radius: var(--radius-pill); }
.hos-run__note { display: flex; align-items: center; gap: 10px; margin-bottom: 14px;
  padding: 11px 13px; border-radius: var(--radius-md); background: var(--accent);
  color: var(--surface); font-size: var(--text-sm); font-weight: 600; }
.hos-run__note small { display: block; margin-top: 1px; font-weight: 500; font-size: var(--text-xs); opacity: 0.85; }

/* ── hos-stepc — connected numbered-circle stepper (Excursion Run) ────────────
   Arena-local port of the os-ds stepper_horizontal, redrawn in plain classes
   with teal tokens (the gem partial's Tailwind arbitrary values don't compile in
   arena's build, and its focus ring is hardcoded DiveOS blue). Geometry copied
   verbatim from the DiveOS run.css: 32px nodes, a 2px spine behind them
   (top:16px, left:calc(50%+16px)→right:calc(-50%+16px)), 6px dot→label gap.
   done = filled accent + check · current = ring · upcoming = grey number. */
.hos-stepc { display: flex; align-items: flex-start; gap: 0; padding: 8px 0; }
.hos-stepc__step { position: relative; flex: 1; display: flex; flex-direction: column;
  align-items: center; gap: 6px; }
.hos-stepc__dot { position: relative; z-index: 1; width: 32px; height: 32px;
  border-radius: 50%; display: flex; align-items: center; justify-content: center;
  border: 2px solid var(--hairline); background: var(--surface); color: var(--muted);
  font-weight: 600; font-size: var(--text-xs); }
.hos-stepc__label { color: var(--muted); }
.hos-stepc__line { position: absolute; top: 16px; left: calc(50% + 16px);
  right: calc(-50% + 16px); height: 2px; background: var(--hairline); }
.hos-stepc__step--done .hos-stepc__dot { background: var(--accent); border-color: var(--accent); color: var(--surface); }
.hos-stepc__step--done .hos-stepc__label { color: var(--accent); }
.hos-stepc__step--done .hos-stepc__line { background: var(--accent); }
.hos-stepc__step--current .hos-stepc__dot { background: var(--accent-soft);
  border-color: var(--accent); color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.hos-stepc__step--current .hos-stepc__label { color: var(--accent); font-weight: 700; }

/* ── Run step-header + titled sibling cards (DiveOS run-pagehead / run-pcards /
   run-cardhead lift). Step-header = an accent-soft icon tile + title + subtitle
   on the canvas; sections are their own os-cards titled by an in-card eyebrow
   (top-only padding → no divider under the label). Values copied from run.css. */
.hos-run__pagehead { display: flex; align-items: center; gap: 11px; margin-bottom: 16px; }
.hos-run__pagehead-ic { width: 34px; height: 34px; border-radius: 9px; flex: 0 0 auto;
  background: var(--accent-soft); color: var(--accent); display: grid; place-items: center; }
.hos-run__pagehead-ic svg { display: block; }
.hos-run__pagehead h2 { margin: 0; }
.hos-run__pagehead p { margin: 2px 0 0; }
.hos-run__pcards { display: flex; flex-direction: column; gap: 16px; }
.hos-run__cardhead { display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; padding: 20px var(--space-6) 0; }
/* Headcount is a 1–2 digit number — cap it narrow instead of filling the card. */
.hos-run__hc { max-width: 8rem; }

/* .hos-callout--attention — arena-only callout variant. The os-ds gem ships
   .os-callout (+ --briefing) but NOT --attention, so this modifier is kept
   app-side (relocated here when the forked components/callout.css was dropped
   for the gem, MR !122). Attention = an actionable warning (e.g. an unresolved
   arrival blocker), amber (--warning-*), distinguished from --briefing (a
   passive note) by a left accent bar. Used by front_office/_details_overview. */
.hos-callout--attention {
  background: var(--warning-bg);
  border-color: var(--warning-border);
  border-left: 3px solid var(--warning);
  color: var(--warning);
}
.hos-callout--attention .os-callout__icon,
.hos-callout--attention .os-callout__eyebrow,
.hos-callout--attention .os-callout__title {
  color: var(--warning);
}

/* #30 Green Tax report — small reused layout utilities (KPI grid, inline filter form,
   empty state). Token-driven; no raw colour/hex. */
/* tasks/073 A5 — `gap: var(--gutter)` was a category error: `--gutter` is the
   shell's horizontal page INSET (48px in the staff pane), not a grid rhythm, so
   this KPI grid rendered 48px apart. The card-grid gap is 16px — the value
   DiveOS uses for its own card grid (`.ds-grid`, admin/design_system) and one
   step up from the gem's tighter `.os-stat-card-grid` 12px. */
.hos-kpi-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 16px;
  margin-bottom: 18px;
}
/* .hos-inline-form retired onto gem `.os-inline` (v1.1.0); callsites use
   equal-height controls, so the gem's align-items:center is visually equal. */
.hos-mb-10 {
  margin-bottom: 10px;
}
/* .hos-emptystate retired onto the gem's `.os-empty-state` (+ `__message` /
   `__description`) — the gem ships the full component; the local rule was a
   duplicate of the concept under a different name. */

/* tasks/161 T11 — a FOLDED subsection: a second subject living on another
   subject's page (Certifications under Crew, Vendors under Fleet). It needs the
   separation of a page break without the weight of a card-in-a-card, so it is a
   heading + rule's worth of air and nothing else. */
/* tasks/161 T11 — an outsource vendor as a compact reference card on the Fleet
   lens. A phone-book entry, not a record you act on: name, contact, and whether
   it is still in service. Editing stays on the settings page. */
.hos-vendor-row { flex-wrap: wrap; }
.hos-vendor { padding: var(--space-3) var(--space-4); min-width: 200px; }
.hos-vendor__name { font-size: var(--text-sm); font-weight: 600; color: var(--ink); }

.hos-foldsection { margin-top: var(--space-6); }
.hos-foldsection > * + * { margin-top: var(--space-3); }

/* ── tasks/161 T11/T15 (design 2A) — the COMPACT empty state ─────────────────
   A modifier on the gem's `.os-empty-state`, not a new family.

   Why it exists: folding Certifications into Crew and Vendors into Fleet means
   those sections are always PRESENT, empty or not. The gem's empty state is a
   40px-padded centred block designed to own a whole page when a list is empty —
   correct there, wrong for a subsection, where it becomes a permanent "you have
   not done this" card on a page you visit for another reason entirely. Three of
   those on one screen and the page reads as a nag rather than a record.

   Compact turns it into a quiet add row: one line, left-aligned, hairline
   above, the action inline. It states the section is empty and offers the fix
   without claiming the vertical space of a real emptiness.

   ⚠️ NAMED FOR CONTRIBUTION. Everything below is expressed as a modifier of the
   gem's component with no local base, so lifting it into os-ds is a rename of
   `.hos-empty-state--compact` → `.os-empty-state--compact` and nothing else.
   R4A decision (2026-08-17): ship it here now, raise it with the os-ds owner
   after. If you are reading this and it is still `.hos-*`, that conversation
   has not happened — DiveOS has the same subsection problem. */
.os-empty-state.hos-empty-state--compact,
.os-empty-state.hos-empty-state--compact.os-card {
  text-align: left;
  padding: var(--space-3) 0 0;
  border-top: 1px solid var(--hairline);
  border-radius: 0;
  background: none;
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.hos-empty-state--compact .os-empty-state__message {
  font-size: var(--text-xs);
  margin: 0;
}
/* The gem promotes __message to a bold title when a __description follows.
   A compact row has no description, but callers copy markup — keep it quiet. */
.hos-empty-state--compact .os-empty-state__message,
.hos-empty-state--compact:has(.os-empty-state__description) .os-empty-state__message {
  font-weight: 400;
  color: var(--muted);
}
.hos-empty-state--compact .os-empty-state__description { display: none; }
.hos-empty-state--compact .os-empty-state__actions { margin: 0; }
.hos-empty-state--compact .os-empty-state__action { font-size: var(--text-xs); }

/* ── Moved out of arena_ds.css, 2026-07-19 (os-ds consolidation) ─────────────
   These are COMPONENTS, not a base layer, so they belong here with the rest of
   the .ao-* family rather than in the pre-gem base file. Safe to load after
   os_ds: every selector below is .ao-*-namespaced, so there is no gem rule to
   collide with (unlike the bare-element resets, which must stay before os_ds —
   `.hos-ds h1` and the gem's `.os-page-band h1` are both specificity (0,1,1),
   so moving them after os_ds would flip every page-band heading's line-height
   and tracking). See tasks/ao-star-audit.md. */

/* Typography roles */
/* .ao-eyebrow RETIRED 2026-07-20 -> os-ds .os-eyebrow (396 callsites migrated).
   It was a re-implementation of a component the gem already ships, and the only
   differences were drift from the original DiveOS port, not decisions:
     weight   700   -> 600     (--fw-bold vs the gem's literal)
     tracking 0.06  -> 0.08em  (--ls-wider vs the gem's literal)
     colour   identical (--text-muted and --muted both resolve to slate-500)
   Keeping it local is what made a typo in this file's token block able to blank
   every eyebrow in the app: .ao-eyebrow read three arena-only tokens, whereas
   .os-eyebrow reads only gem tokens (--text-2xs, --muted) plus literals, so it
   renders correctly even if this layer breaks entirely. Generic primitives
   belong to the base; only resort-domain components belong here. */
.hos-tnum { font-variant-numeric: tabular-nums; }

/* Interaction states */
.hos-btn:not(:disabled):hover { filter: brightness(0.96); }
.hos-btn:not(:disabled):active { transform: translateY(0.5px); filter: brightness(0.92); }
.hos-btn:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
/* --hover-warm / --canvas, not the raw slate-100 / slate-50 these files used to
   carry: identical colours (hotelos_theme maps them 1:1), but semantic — the
   hallmark SemanticOnly rule rightly flags raw primitives in a component sheet
   (it was only tolerated while these lived in the base file). */
/* .hos-icon-btn retired onto gem `.os-btn.os-btn--icon` (v1.1.0); the box was
   always inline-styled at callsites, so only these state rules lived here. */
/* hos-card--interactive: arena's hover LIFT (translateY). Kept local — the gem
   os-card--interactive uses shadow+bg with no transform, a different feel. */
.hos-card--interactive { cursor: pointer; transition: box-shadow var(--dur-fast) var(--ease-standard), transform var(--dur-fast) var(--ease-standard); }
.hos-card--interactive:hover { box-shadow: var(--shadow-md); transform: translateY(-1px); }
.hos-card--interactive:active { transform: translateY(0); box-shadow: var(--shadow-sm); }
.hos-field:focus-within { border-color: var(--accent); box-shadow: var(--shadow-focus); }
.hos-field:hover:not(:focus-within) { border-color: var(--hairline-strong); }
select.hos-field:focus, input.hos-field:focus { border-color: var(--accent); box-shadow: var(--shadow-focus); outline: none; }

/* os-ds select/popover popup stacking. The gem hosts its open menu on <body> at
   z-index 30 so it escapes overflow:hidden ancestors, and only lifts it into the
   browser top layer when the trigger sits inside a native dialog[open]. This app
   builds its modals as plain fixed scrims/drawers, NOT <dialog> — .hos-odrawer is
   z-index 60, .hos-boat-scrim 40 — so at z-30 the menu renders BEHIND them and a
   dropdown inside a drawer/modal reads as "won't open" (transport assign-boat,
   front-office popovers, etc.). Lift it above the whole overlay stack, staying
   below toasts (z-index 100000). Same specificity as the gem rule, loaded later,
   so it wins without !important.
   os-ds v1.22.3: the searchable custom-select menu was renamed .os-pop →
   .os-select__menu (it collided with the popover.css .os-pop and lost its
   scroll). Both families still body-host at z-30, so both need the lift — the
   .os-select__menu selector is what keeps in-drawer searchable selects from
   regressing behind the drawer after the bump; .os-pop stays for the (unrenamed)
   multiselect/filter/calendar popover. */
.os-pop,
.os-select__menu { z-index: 1000; }

/* os-ds os-select popup scroll. In the flattened os_ds.css bundle TWO different
   components declare `.os-pop`: dv_select.css (the searchable select menu) sets
   `max-height:320px; overflow-y:auto`, but popover.css's generic `.os-pop`
   (a separate multiselect popover) loads LATER with `overflow:hidden` and no
   max-height, at equal specificity — so it wins and the select menu can no
   longer scroll (a long boat/captain list overflows the viewport, uncut). The
   dv_select controller relocates the menu to <body> and gives its list container
   no class, so the gem's `.os-pop__list` scroll rule can't rescue it either.
   Restore scroll on the select listbox specifically until the gem de-collides
   the two `.os-pop` families. */
.os-pop[role="listbox"] { max-height: 320px; overflow-y: auto; }

/* Slide-in drawer chassis */
/* tasks/128 — occupant Identity "passport data-page": the portrait crop sits LEFT and the
   particulars line up in two columns to its right (Full name spans both). The crop loads
   into a lazy turbo-frame (only on drawer open). No crop → the plain .os-field-grid stays. */
.hos-idcard { grid-template-columns: 120px 1fr 1fr; align-items: start; }
.hos-idcard > .os-field-grid__field--span-2 { grid-column: 2 / -1; }
.hos-idcard__photo-col { grid-column: 1; grid-row: 1 / span 3; display: flex; flex-direction: column;
  gap: 6px; align-items: center; }
.hos-idcard__frame { display: block; width: 100%; }
.hos-idcard__photo { display: block; width: 118px; height: 150px; object-fit: cover;
  border: 1px solid var(--hairline); border-radius: var(--radius-md); background: var(--surface-alt); }
.hos-idcard__note { font-size: var(--text-xs); text-align: center; margin: 0; }
@media (max-width: 560px) {
  .hos-idcard { grid-template-columns: 1fr 1fr; }
  .hos-idcard__photo-col { grid-column: 1 / -1; grid-row: auto; flex-direction: row; align-items: flex-start; }
}

.hos-odrawer { position: fixed; inset: 0; z-index: 60; visibility: hidden; }
.hos-odrawer[data-open="true"] { visibility: visible; }
.hos-odrawer-scrim { position: absolute; inset: 0; background: rgba(13, 27, 42, 0.42); opacity: 0; transition: opacity var(--dur-base) var(--ease-standard); }
.hos-odrawer[data-open="true"] .hos-odrawer-scrim { opacity: 1; }
.hos-odrawer-panel {
  position: absolute; top: 0; right: 0; height: 100%; width: min(560px, 94vw); background: var(--canvas);
  box-shadow: var(--shadow-lg); display: flex; flex-direction: column; overflow: hidden;
  transform: translateX(100%); transition: transform var(--dur-base) var(--ease-standard);
}
.hos-odrawer[data-open="true"] .hos-odrawer-panel { transform: translateX(0); }

/* ── tasks/073 A1 — THE HOTELOS DRAWER PATTERN ────────────────────────────
   One drawer anatomy for the whole app. The reference implementation is the
   front-office reservation drawer (`front_office/_drawer`) + the journey-edit
   drawer (`front_office/_details_overview`); everything else converges on it.

     .hos-odrawer                        chassis (fixed overlay, data-open)
       .hos-odrawer-scrim                click-to-close scrim
       .hos-odrawer-panel .os-side-panel    the pane + the GEM's side-panel chrome
         .os-side-panel__header             [icon tile][identity][close]
         .hos-drawer-tabs                OPTIONAL full-bleed tab band
         .os-side-panel__body               scrolling content
           .hos-secblock                 a section: head on the canvas + card
             .hos-sectitle               eyebrow head (__ico/__eye/__metric, --warn)
             .hos-seccard                the section's content card (--warn/--tap)
           .os-callout                   the gem's callout — the ONLY callout
         .os-side-panel__action-bar         pinned actions
       (fields: the gem's .os-field-grid / .os-field / .os-label — never a
        hand-rolled `style="display:grid…"` with placeholder-only inputs)

   `.hos-sectitle` is the DENSE drawer-scoped section head; the gem's
   `.os-section-head` is the larger CANVAS head (promoted from `.rd-sectitle`).
   Two roles, two scales — see tasks/073 for the upstream proposal.

   FULL-BLEED CONTRACT. A band inside a drawer spans the PANE, never the
   viewport — same intent as the shell's `--bleed-*` (diveos_shell.css), but
   expressed off the panel's own gutter rather than a container query: the panel
   is a FIXED-WIDTH pane, so the gutter is known, and `container-type` here would
   make the panel the containing block for the `position: fixed` boat-picker
   scrim and trap it inside the drawer. Never `vw` — the panel is not the
   viewport. A band butts the band above it; only its CONTENT re-insets. */
.hos-odrawer-panel { --hos-drawer-gutter: 24px; }

/* The drawer BODY is the canvas the section cards sit on — the gem's
   `.os-side-panel__body` inherits `.os-side-panel`'s --surface, which would make a
   white `.hos-seccard` invisible on it. Header and action bar stay on --surface
   so the pane still reads as chrome / content / chrome. Matches the reference
   drawer, whose body inherits the panel's --canvas. */
.hos-odrawer .os-side-panel__body { background: var(--canvas); }

/* Section stack for a drawer body — the `.hos-journey` rhythm (reference
   drawer) named for reuse: one section per section, generous gap. READ tabs
   stack `.hos-secblock` (head on the canvas); FORM tabs stack the gem's
   `.os-form-section` (head inside the card) — see DESIGN.md. The gem section
   ships its own 18px bottom margin, which would DOUBLE the container gap, so
   zero it here: the flex gap owns the rhythm. */
.hos-drawer-body { display: flex; flex-direction: column; gap: var(--space-5); }
.hos-drawer-body > .os-form-section { margin-bottom: 0; }
/* Same double-gap fix for the workpage content column (arrival wizard steps),
   which is a gapped flex column of its own. */
.os-workpage__main > .os-form-section { margin-bottom: 0; }
/* A form-section body whose contents are a STACK of blocks (callout, toggle,
   field grid) rather than one grid — the `.hos-seccard--stack` rhythm, on the
   gem component. */
.hos-form-section__body--stack { display: flex; flex-direction: column; gap: var(--space-3); }
/* Repeated-row list inside a section card (companions, line items …). */
.hos-drawer-rows { display: flex; flex-direction: column; gap: var(--space-3); }
.hos-drawer-addrow { margin-top: var(--space-3); }
/* A row of actions closing out a section card / field grid. */
.hos-btnrow { display: flex; gap: var(--space-2); flex-wrap: wrap; align-items: center; }
/* A companion is a FIELDSET (legend = "Guest N", sr-only): a field grid — the lead
   drawer's person fields — plus its remove button, which is itself an `.os-field` with a
   blank label so it aligns with the name input by construction (no pixel offset). */
.hos-companion-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--space-2); align-items: start;
  min-width: 0; margin: 0; padding: 0 0 var(--space-3); border: 0; border-bottom: 1px solid var(--hairline); }
.hos-companion-row:last-child { padding-bottom: 0; border-bottom: 0; }
.hos-companion-row__drop { align-items: center; }

/* Full-bleed tab band for a drawer whose tabs live INSIDE `.os-side-panel__body`
   (the gem's `__body` is padded 20px 24px). The rule reaches both panel edges
   and butts the header above it; the tabs re-inset to the drawer gutter. */
.hos-drawer-tabs {
  margin-inline: calc(-1 * var(--hos-drawer-gutter, 24px));
  padding-inline: var(--hos-drawer-gutter, 24px);
  margin-bottom: var(--space-4);
  border-bottom: 1px solid var(--hairline);
}
/* Butt the band against the header by REMOVING the body's top padding, not by
   pulling the band up with a negative margin: content above a scroll box's
   padding edge is unreachable overflow, and scroll anchoring was landing the
   drawer already scrolled past its own tabs. */
.hos-odrawer .os-side-panel__body:has(> .hos-drawer-tabs) { padding-top: 0; }

/* The gem stacks free-standing `.os-field`s with a 22px sibling margin; inside a
   field GRID the gap already does that spacing, so zero it (scoped override). */
.os-field-grid > .os-field + .os-field { margin-top: 0; }

/* #59 — the page-header location/date line. App-specific: the os-ds gem ships
   os-page-head__sub (the subtitle) but not __loc, and arena.html.erb's header puts
   a muted "map-pin Maafushi · <date>" line in os-page-head__actions. Mirrors the
   __sub muted treatment, inline with its icon. */
.hos-page-head__loc {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--text-sm);
  color: var(--muted);
  white-space: nowrap;
}
/* Phones (≤640px): drop the resort-context chip, mirroring os-ds dropping
   .os-page-head__sub. It's a secondary date/property label — on the compact
   single-row phone header it reads as a stray subtitle, so it goes with the sub. */
@media (max-width: 640px) {
  .hos-page-head__loc { display: none; }
}

/* Sticky title bar is MOBILE-ONLY (os-ds v1.22.4 pins .os-page-head at ≤640px).
   On DESKTOP the title bar scrolls; instead, a long table keeps its HEAD reachable
   by pinning the header row — see .os-table sticky-thead below. */

/* ═══════════════════════════════════════════════════════════════════════════
   Date-scope filter  ·  .hos-datescope  (tasks/152)
   ───────────────────────────────────────────────────────────────────────────
   The canonical HotelOS date-range/period filter: a 34px pill (KEY + "%-d %b →
   %-d %b" value) that opens a preset menu + start→end range calendar. Promoted
   from the Front-office board's inline `.hos-datepill`/`.hos-cal` (fo_board.css)
   into a reusable component. FO still runs its own copy (strangler — see
   tasks/ FO-migration follow-up); this is the shared version the rest of the app
   consumes via `shared/_date_scope`. Driven by the `date-scope` Stimulus
   controller. Two modes: RANGE (default) and `--single` (one date, `<input>`).

   The pill is a 34px control (`--radius-md`) — the gem has no 34px button step,
   so a matching secondary header action uses `.hos-headbtn` (below). On phones
   the popover becomes a bottom SHEET so the calendar never overflows, and the
   header stays ONE row (extra facets collapse into the filter drawer, never a
   second row). ═══════════════════════════════════════════════════════════ */
.hos-datescope { position: relative; flex: none; }
.hos-datescope__pill {
  display: inline-flex; align-items: center; gap: 6px; height: 34px; padding: 0 12px;
  border: 1px solid var(--hairline); border-radius: var(--radius-md);
  background: var(--surface); flex: none; list-style: none; cursor: pointer;
}
.hos-datescope__pill::-webkit-details-marker { display: none; }
.hos-datescope__k {
  font-size: var(--text-2xs); font-weight: var(--fw-bold);
  letter-spacing: .04em; text-transform: uppercase; color: var(--muted-2);
}
.hos-datescope__v { font-size: var(--text-sm); font-weight: var(--fw-semibold); color: var(--ink); white-space: nowrap; }
/* --single mode: the value IS a native date input, borderless inside the pill. */
.hos-datescope__input {
  border: none; background: transparent; outline: none;
  font-family: var(--font-sans); font-size: var(--text-sm); font-weight: var(--fw-semibold);
  color: var(--ink); cursor: pointer; margin-left: 2px;
}
.hos-datescope__pop {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 40;
  display: flex; background: var(--surface);
  border: 1px solid var(--hairline); border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg); overflow: hidden;
}
.hos-datescope__presets {
  display: flex; flex-direction: column; gap: 2px; padding: 10px;
  border-right: 1px solid var(--hairline-2); min-width: 140px;
}
.hos-datescope__preset {
  display: block; width: 100%; text-align: left; padding: 8px 12px;
  border: none; background: transparent; cursor: pointer;
  font-family: var(--font-sans); font-size: var(--text-sm); color: var(--text-secondary);
  border-radius: var(--radius-sm);
}
.hos-datescope__preset:hover { background: var(--surface-alt); }
.hos-datescope__preset--clear { color: var(--text-muted); display: flex; align-items: center; gap: 6px; margin-top: 4px; }
.hos-datescope__cal { padding: 12px 14px; width: 296px; }
.hos-datescope__nav { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.hos-datescope__title { font-family: var(--font-display); font-size: var(--text-md); font-weight: var(--fw-semibold); color: var(--ink); }
.hos-datescope__wdays { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; margin-bottom: 4px; }
.hos-datescope__wday { text-align: center; font-size: var(--text-2xs); font-weight: var(--fw-bold); color: var(--muted-2); }
.hos-datescope__grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.hos-datescope__day {
  height: 32px; border: none; border-radius: var(--radius-sm); cursor: pointer;
  font-family: var(--font-mono); font-size: var(--text-sm); background: transparent; color: var(--ink);
}
.hos-datescope__day--in { background: var(--accent-soft); }
.hos-datescope__day--sel { background: var(--accent); color: var(--on-accent); }
.hos-datescope__day--today { box-shadow: inset 0 0 0 1px var(--accent); }
.hos-datescope__hint { margin-top: 8px; font-size: var(--text-2xs); color: var(--muted-2); text-align: right; }

/* Phones (≤640px): the popover becomes a bottom SHEET so the calendar never
   overflows the viewport, and touch targets grow. Scrim + grab handle. */
@media (max-width: 640px) {
  .hos-datescope[open]::before {
    content: ""; position: fixed; inset: 0; z-index: 59;
    background: color-mix(in srgb, var(--ink) 45%, transparent);
  }
  .hos-datescope[open] .hos-datescope__pop {
    position: fixed; left: 0; right: 0; bottom: 0; top: auto;
    width: auto; max-width: none; flex-direction: column; gap: 6px;
    border-radius: 18px 18px 0 0; box-shadow: var(--shadow-lg);
    z-index: 60; max-height: 86svh; max-height: 86vh; overflow: auto;
    padding: 10px 14px calc(16px + env(safe-area-inset-bottom));
  }
  .hos-datescope[open] .hos-datescope__pop::before {
    content: ""; display: block; width: 40px; height: 4px; border-radius: var(--radius-pill);
    background: var(--hairline-strong); margin: 2px auto 8px;
  }
  .hos-datescope__presets { flex-direction: row; flex-wrap: wrap; gap: 6px; border-right: 0; padding: 6px 4px; min-width: 0; }
  .hos-datescope__cal { width: 100%; }
  .hos-datescope__day { height: 40px; }
  /* Compact the pill so the header stays ONE row: drop the scope-word key (the
     "%-d %b → %-d %b" value is self-explanatory), leaving room for a trailing
     action icon beside it. */
  .hos-datescope__k { display: none; }
  /* os-ds (v1.22.x) stretches ANY input-bearing header form to a full-width row,
     assuming a search field. Two arena cases are NOT search and must stay inline
     (so they collapse to a glyph beside the other icon actions, keeping the header
     to one row):
       • the date-scope pill's form (hidden from/to, or a single date input);
       • a button_to / submit_tag ACTION form — only a hidden CSRF token + a submit
         button, no visible field (e.g. Excursions "Auto-assign").
     A real search form (a visible, non-submit text input) is untouched — it still
     gets the full row. */
  .os-page-head__actions > form:is(:has(.hos-datescope), :not(:has(input:not([type="hidden"]):not([type="submit"])))) {
    flex: 0 0 auto; width: auto; min-width: 0;
  }
}

/* .hos-headbtn — a secondary header ACTION sized to the 34px control family
   (date pill / filter button). The gem's button steps are 36/32/22, so a header
   action that must align to the 34px filter row uses this, NOT `.os-btn--sm`.
   On phones it collapses to a 34px glyph (parity with os-ds collapsing `.os-btn`
   in the actions row), keeping the header to one line. */
.hos-headbtn {
  display: inline-flex; align-items: center; gap: 7px; height: 34px; padding: 0 12px;
  border: 1px solid var(--hairline); border-radius: var(--radius-md);
  background: var(--surface); color: var(--ink);
  font-size: var(--text-sm); font-weight: var(--fw-medium); text-decoration: none; cursor: pointer;
}
.hos-headbtn:hover { border-color: var(--hairline-strong); }
.hos-headbtn svg, .hos-headbtn [data-lucide] { color: var(--muted-2); flex: none; }
@media (max-width: 640px) {
  .hos-headbtn { width: 34px; padding: 0; gap: 0; justify-content: center; font-size: 0; }
  .hos-headbtn svg, .hos-headbtn [data-lucide] { font-size: initial; }
}

/* tasks/069 S1 — the settings hub page (DP1). */
.hos-hub-head { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }
.hos-hub-head h1 { font-family: var(--font-display); font-size: var(--text-xl); font-weight: 600; }
.hos-hub-head p, .hos-hub-sub { font-size: var(--text-xs); color: var(--muted); }
.hos-hub-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px; }
.hos-hub-card { padding: 16px; display: flex; flex-direction: column; gap: 6px; color: inherit; text-decoration: none; }
.hos-hub-card--pending { opacity: .65; }

/* tasks/069 S9 (DP2 pkg-a) — the package itinerary composer. */
.hos-pkgb-grid { display: grid; grid-template-columns: 1fr 320px; gap: var(--space-5); align-items: start; max-width: 1080px; }
.hos-pkgb-rail { position: sticky; top: var(--space-5); display: flex; flex-direction: column; gap: var(--space-3); }
.hos-pkgb-dayband { background: var(--surface-alt); padding: 8px 14px; border-bottom: 1px solid var(--hairline-2); }
.hos-pkgb-row { display: flex; align-items: center; gap: 10px; padding: 8px 14px; border-bottom: 1px solid var(--hairline-2); }
.hos-pkgb-row .os-mono { margin-left: auto; }
.hos-pkgb-empty { padding: 10px 14px; color: var(--muted); }
.hos-pkgb-addrow { display: flex; gap: 10px; align-items: center; padding: 12px 14px; flex-wrap: wrap; }
@media (max-width: 767px) {
  /* DP7: the rail drops below the compose column on mobile. */
  .hos-pkgb-grid { grid-template-columns: 1fr; }
  .hos-pkgb-rail { position: static; }}

/* tasks/069 S10 — drawer head/body (the gem ships the chassis + panel, not
   the two inner bands every drawer hand-rolled inline until now). */
/* tasks/073 A1 — these are the LIGHT drawer bands (a `<strong>` title, no icon
   tile) and they are the SAME drawer as `.os-side-panel__header/__body`, just a
   simpler head. Their geometry is now aligned to the gem's — 22px/24px header,
   24px body gutter, canvas body — so a drawer looks identical whichever band
   vocabulary its view happens to use. It previously ran a 18px gutter, which is
   why the two families never lined up.
   UPSTREAM: this is really `.os-side-panel__header--plain`; see the promotion
   list in tasks/073. */
.hos-odrawer-head { flex: none; display: flex; align-items: center; gap: 12px; padding: 22px var(--hos-drawer-gutter, 24px) 16px;
  background: var(--surface); border-bottom: 1px solid var(--hairline-2); }
.hos-odrawer-head strong { flex: 1; font-family: var(--font-display); font-size: var(--text-lg); color: var(--ink); }
/* `position: relative` is load-bearing, not cosmetic. This body scrolls inside
   a `position: fixed` wrapper (`.hos-odrawer`, above), so without it an
   absolutely positioned descendant — every visually-hidden radio/checkbox is
   one — resolves against the fixed wrapper instead of this scroller. It then
   does not scroll with the content, and focusing it makes the browser
   scroll-into-view the FIXED ancestor, yanking the whole drawer off-screen.
   os-ds hit exactly this on `.side-panel__body` and fixed it there
   (os_ds/patterns.css); these sibling scroll bodies were missed. See TODOS.md
   item 2 for the containers still unguarded. */
.hos-odrawer-body { flex: 1; overflow-y: auto; padding: 20px var(--hos-drawer-gutter, 24px); background: var(--canvas); position: relative; }

/* tasks/069 S10 (DP2 prog-a) — the weekly-program calendar grid. */
.hos-prog-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 1px;
  background: var(--hairline-2); border: 1px solid var(--hairline-2); border-radius: var(--radius); overflow: hidden; }
.hos-prog-col { background: var(--canvas); display: flex; flex-direction: column; min-height: 190px; }
.hos-prog-colhead { background: var(--surface-alt); border: 0; border-bottom: 1px solid var(--hairline-2);
  padding: 9px 12px; font-size: var(--text-sm); font-weight: 600; color: var(--ink);
  display: flex; align-items: center; justify-content: center; gap: 6px; width: 100%; cursor: default; }
.hos-prog-count { display: none; }
.hos-prog-slots { display: flex; flex-direction: column; gap: 6px; padding: 10px; flex: 1; }
.hos-prog-chip { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; padding: 7px 10px; border: 1px solid var(--hairline);
  border-radius: var(--radius-md); background: var(--surface); color: var(--ink);
  font-size: var(--text-sm); text-decoration: none; min-height: 36px; }
.hos-prog-chip:hover { border-color: var(--accent); }
.hos-prog-chip--off { opacity: 0.55; }
.hos-prog-time { color: var(--muted); font-size: var(--text-xs); font-family: var(--font-mono); }
.hos-prog-name { min-width: 0; line-height: 1.2; white-space: normal; font-weight: 600; }
.hos-prog-empty { color: var(--muted); padding: 4px 2px; }
.hos-prog-add { margin-top: auto; text-align: center; color: var(--muted); text-decoration: none;
  border: 1px dashed var(--hairline); border-radius: var(--radius-sm); padding: 5px; font-size: var(--text-sm); }
.hos-prog-add:hover { color: var(--accent); border-color: var(--accent); }

@media (max-width: 767px) {
  /* DP7: the grid becomes a day accordion — headers tap to expand, chips
     go full-width, 44px targets. */
  .hos-prog-grid { grid-template-columns: 1fr; }
  .hos-prog-col { min-height: 0; }
  .hos-prog-colhead { justify-content: space-between; cursor: pointer; min-height: 44px; }
  .hos-prog-count { display: inline-flex; color: var(--muted); font-weight: 400; }
  .hos-prog-slots { display: none; }
  .hos-prog-col--open .hos-prog-slots { display: flex; }
  .hos-prog-chip, .hos-prog-add { min-height: 44px; align-items: center; justify-content: flex-start; }}

/* tasks/069 S12 (DP4) — the public waiver page. Mobile-first: one column,
   large type, a single 48px action. */
.hos-wv-page { min-height: 100vh; display: flex; align-items: flex-start; justify-content: center;
  padding: var(--space-5) var(--space-4); background: var(--canvas); }
.hos-wv-card { width: 100%; max-width: 520px; background: var(--surface); border: 1px solid var(--hairline);
  border-radius: var(--radius-lg); padding: var(--space-5); display: flex; flex-direction: column; gap: var(--space-3); }
.hos-wv-card h1 { font-family: var(--font-display); font-size: var(--text-2xl); color: var(--ink); margin: 0; }
.hos-wv-card--done { text-align: center; align-items: center; }
.hos-wv-tick { width: 52px; height: 52px; border-radius: var(--radius-pill); background: var(--accent-soft);
  color: var(--accent); display: inline-flex; align-items: center; justify-content: center; }
.hos-wv-context { color: var(--muted); font-size: var(--text-sm); margin: 0; }
.hos-wv-for { color: var(--ink); font-size: var(--text-sm); margin: 0; }
.hos-wv-text { border-top: 1px solid var(--hairline-2); border-bottom: 1px solid var(--hairline-2);
  padding: var(--space-4) 0; color: var(--ink); font-size: var(--text-sm); line-height: 1.65; }
.hos-wv-error { color: var(--danger); font-size: var(--text-sm); margin: 0; }
.hos-wv-form { display: flex; flex-direction: column; gap: var(--space-2); }
.hos-wv-label { font-size: var(--text-sm); color: var(--ink); font-weight: 600; }
.hos-wv-input { min-height: 48px; font-size: var(--text-base); }
/* .hos-wv-submit retired -> `.os-btn--touch` (48px) at the callsite. */
.hos-wv-fine { color: var(--muted-2); font-size: var(--text-xs); margin: 0; }
/* S12 — the roster's copy-to-share waiver link. */
.hos-wv-share { width: 190px; font-size: var(--text-2xs); font-family: var(--font-mono); }

/* Clickable-card affordance — .hos-hub-card is a link (settings hub + the
   read-first excursion board); a hover lift/accent border signals it opens. */
.hos-hub-card { transition: border-color var(--dur-fast, .12s) var(--ease-standard), transform var(--dur-fast, .12s) var(--ease-standard); }
a.hos-hub-card:hover { border-color: var(--accent); transform: translateY(-1px); }
.hos-noshow { text-decoration: line-through; color: var(--muted-2); }

/* tasks/072 MR-3 — kiosk "Scan passport to autofill" affordance in each person block.
   The native file input is .sr-only; the <label> is the visible ghost button. */
.hos-kiosk-scan { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
  margin-bottom: var(--space-3); }
.hos-kiosk-scan__status { margin: 0; }
.hos-kiosk-scan__status[data-state="ok"] { color: var(--accent); }
.hos-kiosk-scan__status[data-state="miss"] { color: var(--muted); }

/* tasks/072 MR-4 — the reconciliation queue rows (staff match backlog). */
.hos-recon-list { display: flex; flex-direction: column; gap: var(--space-3); }
.hos-recon-row__match { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
  margin-top: var(--space-2); padding-top: var(--space-2); border-top: 1px solid var(--hairline); }
.hos-recon-cand { display: inline-flex; align-items: center; gap: var(--space-2); }
.hos-recon-signals { display: inline-flex; gap: var(--space-2); color: var(--muted); }
.hos-recon-sig { color: var(--accent); }

/* tasks/072 MR-4 — the match screen (two-column decide surface) + PIN hand-off. */
.hos-recon-row__cta { margin-left: auto; }
.hos-recon-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr); gap: var(--space-4); align-items: start; }
@media (max-width: 720px) { .hos-recon-grid { grid-template-columns: 1fr; } }
.hos-recon-person { padding: var(--space-2) 0; border-bottom: 1px solid var(--hairline); }
.hos-recon-person:last-child { border-bottom: 0; }
.hos-recon-candidates { display: flex; flex-direction: column; gap: var(--space-3); }
.hos-recon-cand--top { border-color: var(--accent); box-shadow: var(--shadow-sm); }
.hos-recon-confirm { margin: var(--space-2) 0 0; }
/* Match screen — the all-arrivals browse list under the ranked candidates. */
.hos-recon-facts { line-height: 1.6; }
.hos-recon-browse__head { display: flex; align-items: baseline; gap: var(--space-2); margin-top: var(--space-2); }
.hos-recon-browse__search { display: flex; gap: var(--space-2); align-items: center; margin-bottom: var(--space-3); }
.hos-recon-browse__search .os-input { flex: 1; min-width: 0; }
.hos-recon-arrivals { display: flex; flex-direction: column; }
.hos-recon-arrival { display: flex; gap: var(--space-3); align-items: flex-start; padding: var(--space-3) 0; border-top: 1px solid var(--hairline); }
.hos-recon-arrival__cta { flex: none; margin-top: var(--space-1); }
.hos-recon-done { text-align: center; display: flex; flex-direction: column; align-items: center; gap: var(--space-3); padding: var(--space-5); }
.hos-recon-done__tick { width: 52px; height: 52px; border-radius: var(--radius-pill); background: var(--accent-soft);
  color: var(--accent); display: inline-flex; align-items: center; justify-content: center; }
.hos-recon-done__actions { display: flex; gap: var(--space-2); flex-wrap: wrap; justify-content: center; }
.hos-recon-pin { font-family: var(--font-mono); font-size: var(--text-3xl); letter-spacing: 0.2em; color: var(--ink); margin: var(--space-1) 0; }

.hos-recon-undo { margin-top: var(--space-2); color: var(--muted); }

/* tasks/072 — the kiosk intake is a STANDALONE public page (not the guest-portal
   shell), so it has no bottom tab bar. Override the arrival-page footer, which docks
   at bottom:74px to clear that tab bar — here it must sit at the true bottom, and the
   page reserves space so the sticky Submit bar never overlaps the last field. */
.hos-kiosk-intake { padding-bottom: 88px; }
.hos-kiosk-intake .os-workpage__footer { bottom: 0; }
@media (max-width: 720px) {
  .hos-kiosk-intake .os-workpage__footer { bottom: 0; }
}

/* tasks/075 — group intake: one section per self-declared room. Solo bookings show no
   room chrome (the head is hidden until a 2nd room is added). Each grouped room reads as
   a titled block so the rep can see which guests belong where. */
.hos-kiosk-room + .hos-kiosk-room { border-top: 1px solid var(--hairline); }
.hos-kiosk-room__head { display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-2); padding: var(--space-3) var(--space-4) 0; }
.hos-kiosk-room__head[hidden] { display: none; } /* author display:flex would else override [hidden] */
.hos-kiosk-room__head .os-eyebrow { margin: 0; }
.hos-kiosk-addroom { display: flex; flex-direction: column; gap: var(--space-1);
  border-top: 1px dashed var(--hairline); }
.hos-kiosk-addroom .os-meta { margin: 0; }

/* tasks/075 — collapsed person/room summary: a filled block folds to one tappable row
   ("Ann Shuman · Maldivian" / "Room 2 · Ann, Bob") with an Edit affordance. Keeps the
   form scannable once a group of several rooms is filled. */
.hos-kiosk-summary { display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-2); min-height: 44px; margin: var(--space-2) var(--space-4);
  width: calc(100% - 2 * var(--space-4)); padding: var(--space-2) var(--space-3); text-align: left;
  border: 1px solid var(--hairline); border-radius: var(--radius-md); background: var(--surface-alt);
  color: var(--ink); font: inherit; font-size: var(--text-sm); cursor: pointer; }
.hos-kiosk-summary[hidden] { display: none; } /* author display:flex would else override [hidden] */
.hos-kiosk-summary::after { content: "Edit"; flex: none; color: var(--accent);
  font-family: var(--font-mono); font-size: var(--text-xs); }
.hos-kiosk-summary:hover, .hos-kiosk-summary:focus-visible { border-color: var(--accent); }
.hos-kiosk-summary--room { font-weight: 600; background: var(--surface); }

/* tasks/073 — the kiosk two-path landing (portal vs complete-here). */
.hos-kiosk-paths { display: flex; flex-direction: column; gap: var(--space-3); }
.hos-kiosk-path { text-decoration: none; color: inherit; display: block; }
.hos-kiosk-path--primary { border-color: var(--accent); }
a.hos-kiosk-path:hover { border-color: var(--accent); }
.hos-kiosk-path .os-card__body { display: flex; flex-direction: column; gap: var(--space-2); align-items: flex-start; }
.hos-kiosk-path__icon { width: 44px; height: 44px; border-radius: var(--radius-md); background: var(--accent-soft);
  color: var(--accent); display: inline-flex; align-items: center; justify-content: center; }
.hos-kiosk-path__icon--muted { background: var(--surface-alt); color: var(--muted); }

/* tasks/073 — kiosk departure flight + transfer slot picker. */
.hos-kiosk-depart { border-top: 1px solid var(--hairline); }
.hos-kiosk-flight { margin: var(--space-3) 0; }
.hos-kiosk-transfer { display: flex; flex-direction: column; gap: var(--space-2); }
.hos-radio { display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-sm); color: var(--ink); min-height: 44px; }
.hos-kiosk-slots { margin: 0 0 var(--space-2) var(--space-4); }
.hos-kiosk-slotgrid { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-1); }
.hos-slot { display: inline-flex; align-items: center; gap: 6px; padding: 8px 12px; border: 1px solid var(--hairline);
  border-radius: var(--radius-md); min-height: 44px; font-family: var(--font-mono); }
.hos-slot--suggested { border-color: var(--accent); background: var(--accent-soft); }

/* tasks/073 — per-adult inline signature pad. */
.hos-kiosk-sign { margin-top: var(--space-3); padding-top: var(--space-3); border-top: 1px dashed var(--hairline);
  display: flex; flex-direction: column; gap: var(--space-2); align-items: flex-start; }
.hos-sigpad { width: 100%; max-width: 340px; height: 130px; border: 1px solid var(--hairline);
  border-radius: var(--radius-md); background: var(--surface); touch-action: none; }

.hos-recon-person__tags { display: inline-flex; gap: var(--space-2); align-items: center; }
.hos-recon-person__facts, .hos-recon-booking { margin-top: var(--space-2); }
.hos-recon-booking { padding-bottom: var(--space-2); border-bottom: 1px solid var(--hairline); }

/* tasks/073 — the standalone kiosk isn't in the guest-portal shell, so the arrival
   page's mobile margin-top:-40px (which tucks content under the shell's sticky top bar)
   instead pulls the sand hero up over the brand header. Neutralize it here. The
   compound selector outweighs `.hos-arrival-page` regardless of load order. */
@media (max-width: 720px) {
  .hos-arrival-page.hos-kiosk-intake { margin-top: 0; }
}

/* tasks/072 MR-4 — reconciliation queue TABLE (scannable at a 90-arrival wave) + the
   full-width match grid. Both replace the mis-applied guest .os-workpage rail. */
.hos-recon-tablecard { padding: 0; overflow-x: auto; }
.hos-recon-table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.hos-recon-table th { text-align: left; padding: 11px 16px; color: var(--muted); font-weight: 600;
  font-size: var(--text-xs); letter-spacing: 0.04em; text-transform: uppercase; border-bottom: 1px solid var(--hairline); white-space: nowrap; }
.hos-recon-table td { padding: 12px 16px; border-bottom: 1px solid var(--hairline-2); vertical-align: middle; }
.hos-recon-table tbody tr:last-child td { border-bottom: 0; }
.hos-recon-table tbody tr:hover { background: var(--surface-alt); }
.hos-recon-tr--confident { box-shadow: inset 3px 0 0 var(--accent); }
.hos-recon-name { font-weight: 600; color: var(--ink); }
.hos-recon-table__act { text-align: right; white-space: nowrap; }
.hos-recon-match { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr); gap: var(--space-4); align-items: start; }
@media (max-width: 900px) { .hos-recon-match { grid-template-columns: 1fr; } }

/* ── .hos-cardlink — a card-foot action, EXTENDING the gem's .os-link ───────
   tasks/073 A1. The foot action used to be `.os-btn--ghost.os-btn--sm`, whose
   12px of horizontal padding pushed the label inside the card's content right
   edge — so the one element the eye checks alignment on was the one element
   that did not line up with the figures above it. `.os-link` is padding-0 and
   therefore lands its text ON that edge; this modifier only raises it from the
   link scale (--text-xs, for a "View all 12" suffix in a list) to the card's
   own body size, because a card's single action is not incidental text.
   Composes on top of the gem class, never redeclares it. */
.hos-cardlink {
  font-size: var(--text-sm);
  font-weight: 600;
  gap: 6px;
}

/* The no-JS select paint that used to live here is GONE (2026-07-28).
   os-ds v1.17.0 makes `.os-field-select` painted BY DEFAULT — appearance,
   chevron, and the padding-right the glyph needs — so every consumer gets it
   with or without JavaScript, and the gem exposes `--os-select-chevron` for a
   product that wants a different glyph. The local copy, its private
   `--hos-chevron-glyph`, and the `(hover:hover)` gate are all retired; the gem
   deliberately does NOT gate by pointer type, because `appearance: none`
   changes only the closed state and gating would strand touch-capable laptops
   on the UA chevron. The `os-field-select` shadow-allowlist entry went with it. */

/* ── .hos-parity-more — a collapsed .os-form-section (tasks/143) ─────────────
   Renders an .os-form-section as a <details>: the <summary> IS the section
   header; hide the native disclosure marker and rotate the chevron on open.
   Mirrors .hos-share-card__disclosure. Keeps the eZee-parity long tail
   (address/company/CRM dates) out of the default capture flow. */
.hos-parity-more > summary { cursor: pointer; list-style: none; }
.hos-parity-more > summary::-webkit-details-marker { display: none; }
.hos-parity-more > summary .os-form-section__icon { transition: transform 0.15s ease; }
.hos-parity-more[open] > summary .os-form-section__icon { transform: rotate(90deg); }

/* tasks/079: shared caption for the Settings → Integrations last-sync summary lines
   (arrivals + BookingList) and the area meta — replaces byte-identical inline
   token-styles that hallmark flagged. */
.hos-integ-sync-meta { font-size: var(--text-2xs); color: var(--muted-2); }

/* ── INTERIM: os-ds page-head mobile `button_to` mis-stack ───────────────────
   os-ds gem bug (v1.18.2). At ≤640px, os_ds.css gives a header SEARCH field its
   own full-width row via `.os-page-head__actions:has(input)` and
   `.os-page-head__actions > form:has(input)`. But a Rails `button_to` renders a
   `<form>` carrying a hidden CSRF `<input>`, so those selectors ALSO catch every
   POST action in the band — forcing each `button_to` (e.g. the excursion board's
   Auto-assign) onto its own full-width row instead of sitting as a 40px icon
   button beside the link actions.

   Correct fix is upstream — narrow the gem selectors to text/search inputs
   (`tasks/075-os-ds-page-head-mobile-proposal.md`). Until the gem ships it, this
   loads after os_ds.css and re-narrows here: restore the wrapped-row default for
   any actions/form that has NO genuine text/search field. A real header search
   (with a text/search input) is untouched, so it still takes its full row.
   DELETE this block once the gem carries the fix. */
@media (max-width: 640px) {
  .os-page-head__actions:has(input):not(:has(.os-hsearch)):not(:has(input:is([type=search], [type=text]))) {
    flex: 0 0 auto;
    justify-content: flex-end;
  }
  .os-page-head__actions > form:has(input):not(:has(input:is([type=search], [type=text]))) {
    flex: 0 0 auto;
    min-width: 0;
  }
}

/* ── Crew page (Transport → Crew) ─────────────────────────────────────────
   The three-tab crew surface. The exceptions summary reuses the gem's
   .os-stat-card (fleet-at-a-glance pattern): a neutral card where ONLY the
   number takes colour, exactly as fleet's .hos-fl-kpi--alert recolours the
   value. A flagged crew card (an expired licence) gets a danger border. */
.hos-crew-kpis {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin-top: var(--space-4);
  margin-bottom: var(--space-6);
}
@media (max-width: 900px) { .hos-crew-kpis { grid-template-columns: 1fr; } }
.hos-crew-kpi--danger .os-stat-card-value { color: var(--status-cancelled-fg); }
.hos-crew-kpi--warn   .os-stat-card-value { color: var(--status-briefing-fg); }
.hos-crewcard--flag { border-color: var(--status-cancelled-border); }
.hos-crew-kpi__cta { color: var(--accent); font-weight: 500; }

/* Crew card internals (Crew tab) — class-based so no token-governed inline styles. */
.hos-crewcard__body   { display: flex; flex-direction: column; gap: var(--space-3); }
.hos-crewcard__id     { display: flex; gap: var(--space-3); align-items: flex-start; }
.hos-crewcard__idmain { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.hos-crewcard__name   { font-weight: 600; font-size: var(--text-sm); color: var(--ink); line-height: 1.2; }
.hos-crewcard__phone  { font-size: var(--text-2xs); color: var(--muted-2); }
.hos-crewcard__pills  { margin-top: var(--space-1); display: flex; gap: 6px; flex-wrap: wrap; }
.hos-crewcard__status { display: flex; gap: 6px; flex-wrap: wrap; }
.hos-crewcard__leave  { display: flex; justify-content: space-between; gap: var(--space-2);
  align-items: baseline; font-size: var(--text-xs); background: var(--surface-alt);
  border-radius: var(--radius); padding: var(--space-2) var(--space-3); }
.hos-crewcard__leave-k { color: var(--muted); }
.hos-crewcard__leave-v { color: var(--ink); text-align: right; }
.hos-crewcard__link    { font-size: var(--text-xs); }

/* Leave tab rows */
.hos-crewleave__body { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3) var(--space-4); }
.hos-crewleave__who  { min-width: 150px; }
.hos-crewleave__name { font-weight: 600; font-size: var(--text-sm); color: var(--ink); }
.hos-crewleave__meta { font-size: var(--text-2xs); color: var(--muted); }
.hos-crewleave__list { flex: 1; min-width: 200px; display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.hos-crewleave__none { font-size: var(--text-xs); color: var(--muted); }
.hos-crewleave__chip { display: inline-flex; align-items: center; gap: 6px; }

/* The crew tabs band carries only tabs (its action lives in the header band), so
   the gem's `.os-page-tools > .os-page-tabs { margin-block: -12px }` would collapse
   it to bare tab height (25px). Pin the same min-height as the transport board's
   .hos-boardband — control height (32px) + the band's own 2x12px padding. */
.os-page-tools.hos-crewband { min-height: calc(var(--space-8) + var(--space-6)); }

/* Crew/cert TABLE layout (the ?layout=table comparison against the cards). A roster
   of homogeneous rows scans better as a table than as N cards; DESIGN.md sanctions a
   table for "genuinely tabular data, inside an .os-card". */
.hos-crewrow__id    { display: flex; align-items: center; gap: var(--space-3); }
.hos-crewrow__name  { font-weight: 500; color: var(--ink); }
.hos-crewrow__phone { font-size: var(--text-2xs); color: var(--muted-2); }
.hos-crewrow__leave-none { color: var(--muted-2); }
.hos-crewrow__actions { display: inline-flex; gap: var(--space-2); justify-content: flex-end; }
.hos-crewrow--flag > td { background: var(--danger-bg); }

/* The gem's .os-table-wrap thead th sets type but no vertical padding, so the header
   row collapses against the first data row. Give the crew/cert data tables a padded
   header (scoped, so other app tables keep their current header rhythm). */
.hos-datatable thead th { padding-top: var(--space-3); padding-bottom: var(--space-3); }
/* Band search — sized so it sits neatly right of the tabs, not stretched. */
.hos-crew-search { flex: 0 1 260px; }

/* Certifications tab — split into Crew + Boats sub-sections (a cert belongs to a
   person OR a boat). Canvas-title heading above each table card (DESIGN.md: a read
   surface's section header goes on the canvas above the card). */
.hos-cert-section + .hos-cert-section { margin-top: var(--space-6); }
.hos-cert-section > h2 { margin-bottom: var(--space-3); }
.hos-cert-none { color: var(--muted); }

/* .os-empty-state sets display:flex, which overrides the [hidden] UA rule — so a
   filter's hidden empty-state stayed visible. The attribute selector out-specifies
   the base rule and restores hide-on-hidden. */
.os-card.os-empty-state[hidden] { display: none; }

/* Crew-page CRUD drawers — the form grid inside the .os-side-panel body. The chassis
   (.hos-odrawer / -scrim / -panel) and the panel regions (.os-side-panel__header /
   __body / __action-bar) already ship in the gem; only the field grid is local. */
.hos-drawerform { display: grid; gap: 14px; }
.hos-drawerform__row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.hos-drawerform__check { display: flex; align-items: center; gap: 7px; }

/* ─── Crew-portal access (settings/fleet portal drawer + full page) ─────────
   State bodies shared by settings/fleet/_portal_access_body. Cards ride the gem's
   .os-card (+ .hos-card--pad); these classes are layout + domain accents only.
   Copied-state feedback is CSS keyed off [data-copied] set by enroll_link. */
.hos-portal-id { display: flex; align-items: center; gap: 11px; padding: 14px 16px; }
.hos-portal-id__who { min-width: 0; flex: 1; }
.hos-portal-id__name { font-size: var(--text-sm); font-weight: 600; color: var(--ink); }
.hos-portal-id__status { display: flex; flex-direction: column; align-items: flex-end; gap: 5px; }
.hos-callout--warning { background: var(--warning-bg); border-color: var(--warning-border); color: var(--warning); }
.hos-portal-qr { display: flex; flex-direction: column; align-items: center; gap: 10px; text-align: center; }
.hos-portal-qr p { margin: 0; }
.hos-portal-sep { height: 1px; background: var(--hairline); margin: 16px 0; }
.hos-portal-steps { list-style: none; display: flex; flex-direction: column; gap: 10px; margin: 0; padding: 0; }
.hos-portal-step { display: flex; gap: 10px; align-items: flex-start; }
.hos-portal-step p { margin: 0; font-size: var(--text-sm); line-height: 1.5; color: var(--ink); }
.hos-portal-step__num {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 20px; height: 20px; border-radius: var(--radius-pill);
  background: var(--accent-soft); color: var(--accent);
  font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: 600;
}
.hos-portal-eyebrow {
  margin: 0 0 8px; font-size: var(--text-2xs); font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.08em; color: var(--muted);
}
.hos-portal-copy {
  display: flex; align-items: center; gap: 8px; width: 100%; padding: 10px 12px;
  border: 1px solid var(--hairline); border-radius: var(--radius-md);
  background: var(--canvas); cursor: pointer; text-align: left; font-family: inherit;
  transition: border-color 150ms ease, background 150ms ease;
}
.hos-portal-copy:hover { border-color: var(--accent-border); }
.hos-portal-copy__icon { display: inline-flex; flex: none; color: var(--muted); }
.hos-portal-copy__icon--ok { display: none; color: var(--success); }
.hos-portal-copy[data-copied] { border-color: var(--success-border); background: var(--success-bg); }
.hos-portal-copy[data-copied] .hos-portal-copy__icon--link { display: none; }
.hos-portal-copy[data-copied] .hos-portal-copy__icon--ok { display: inline-flex; }
.hos-portal-copy__url {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-family: var(--font-mono); font-size: var(--text-xs); color: var(--ink);
}
.hos-portal-copy__hint {
  flex: none; display: inline-flex; align-items: center; gap: 4px;
  font-size: var(--text-xs); font-weight: 600; color: var(--accent);
}
.hos-portal-copy[data-copied] .hos-portal-copy__hint { color: var(--success); }
.hos-portal-sharebar { display: flex; gap: 8px; margin-top: 10px; }
.hos-portal-share { flex: 1; }
.hos-portal-note { display: flex; gap: 12px; align-items: flex-start; }
.hos-portal-note__icon {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 38px; height: 38px; border-radius: var(--radius-md);
  background: var(--surface-alt); color: var(--muted);
}
.hos-portal-note__icon--accent { background: var(--accent-soft); color: var(--accent); }
.hos-portal-note__title { margin: 0; font-size: var(--text-sm); font-weight: 600; color: var(--ink); }
.hos-portal-note__body { margin: 3px 0 0; font-size: var(--text-sm); line-height: 1.5; color: var(--muted); }
.hos-portal-inactive { margin: 0; }

/* ─── Chip (status / selectable) ──────────────────────────────────────
   Relocated from components/package_builder.css (#204): the package-builder
   wizard is retired, but its base .hos-chip is the shared status chip used
   across the spa / watersports / excursion drawers (+ their booking forms),
   paired with the .hos-tone--* triad (fo_board.css) for colour. The .tk /
   .is-on sub-rules are the selectable-filter variant; moved verbatim so the
   component definition stays whole. */
.hos-chip {
  display: inline-flex; align-items: center; gap: 8px; padding: 8px 13px;
  font: inherit; font-size: var(--text-sm); font-weight: 500; color: var(--text-secondary);
  background: var(--surface); border: 1px solid var(--hairline); border-radius: var(--radius-pill);
  cursor: pointer; transition: border-color var(--t-fast) ease, background var(--t-fast) ease, color var(--t-fast) ease;
}
.hos-chip:hover { border-color: var(--accent-border); }
.hos-chip .tk {
  width: 15px; height: 15px; border-radius: var(--radius-pill); flex-shrink: 0;
  border: 1.5px solid var(--hairline-strong); display: flex; align-items: center; justify-content: center; color: transparent;
}
.hos-chip.is-on { color: var(--accent); background: var(--accent-soft); border-color: var(--accent); font-weight: 600; }
.hos-chip.is-on .tk { background: var(--accent); border-color: var(--accent); color: var(--surface); }

/* ─── Empty / no-results state ────────────────────────────────────────
   Relocated from components/package_builder.css (#204). Used by the
   reconciliation index/show + admin design-system arrivals empty state. */
.hos-empty { display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 56px 20px; text-align: center; }
.hos-empty .t { font-size: var(--text-sm); font-weight: 600; color: var(--text-secondary); }
.hos-empty .s { font-size: var(--text-sm); color: var(--muted); }
