/* @cascade-order: 0 — do not reorder. Split 1/10 of the former
   monolithic style.css (see docs/ui-pattern-audit.md and ADR-0095-style-css-split-into-ten-contiguous-files).
   Originally a contiguous slice of style.css in @cascade-order. That
   ordering is still enforced (tests/unit/css-split-structure.test.js);
   byte-for-byte reconstruction is NOT enforced and must not be relied on
   — the split files have been edited independently since. Earlier headers
   cited tests/unit/css-split-equality.test.js, which does not exist.
   Contains: Shell chrome, app layout/sidebar, schedule sidebar, main content cards, the member table. What auth.html needs (the :root shell/motion tokens are in the first ~120 lines) plus the primary staff-facing layout.
   Original line range: 1-2143. */


/* The band that HOLDS the search object is `.app-main-search` (v3.css). The
   asymmetric 26px/16px inset, the --band-h height and the elevated ground are
   ITS properties and are declared there.

   `.main-search-bar` used to re-declare all six of them here, one rule above
   `.search-bar` — which re-declared every one of those six again, at equal
   specificity and later in the file, so the whole rule was inert. That is the
   leftover from the split the index.html comment describes: the strip and the
   object were once one element wearing both names.

   What is left is the universal bar's INSTANCE of the shared field object
   (ADR-0128). `.search-bar` was a parallel copy of `.attendance-search` — same
   flex row, same elevated ground, same radius, same focus ring, a scope chip
   under a second name — differing only in the two things below, and both of
   those are about the BAND rather than about the field:

   - It takes the band's control height, so it lines up with the day-nav
     buttons across the seam. The check-in fields size to their own content.
   - It carries the search-method chips INSIDE the bordered shape, so it needs
     a right inset the check-in fields (whose last child is the 28px clear
     button) do not.

   Everything else — the border, the ring, the glyph, the input, the scope chip
   — is now the one field's, which is what makes ADR-0094's three-jobs table
   literal: Look up, Pick and Filter are one object and the chip says which.

   No `align-items` here. This file loads BEFORE v3.css, so at equal
   specificity the field's own `stretch` wins anything written here — and
   `stretch` is what the field wants: it is what carries the scope chip's tint
   to the field's top and bottom edges instead of leaving it floating in the
   middle. The method chips centre themselves inside their own slot. */
.main-search-bar {
  height: var(--ctl-h);
  box-sizing: border-box;
  padding-right: 4px;
}

/* Tabs that are not the check-in card keep their own internal padding. */
/* The record dims while the check-in dropdown is open. The panel is an OVERLAY
   over the list, not a block in it, so the list has to say it is not what you
   are aiming at -- app-shell.html's openDrop() adds this to the page body and
   closeDrop() removes it. */
.dimmed {
  opacity: 0.45;
  filter: saturate(0.7);
  pointer-events: none;
}

/* The design's `.page` has NO padding: header, notice, toolbar and body are
   full-bleed bands and the 14px inset lives on the pane internals. A tab that
   composes the V3 card anatomy must be flush, or its header stops being the
   card's top band and becomes a strip floating inside it. The class and camp
   check-in tabs compose .class-checkin-layout; #tab-search's .hdrD IS the
   card's top band. The rest keep an inset until they are ported. */
.main-content-body > .tab-content:not(:has(.hdrD)):not(:has(> .class-checkin-header)):not(#tab-class-checkin):not(#tab-camp-checkin):not(#tab-today):not(#tab-more) {
  padding: var(--space-4);
}

.main-search-bar .search-controls {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
}

/* Base search button style */
/* Filter controls are V3 .chip: quiet by default, and an ACTIVE one lifts to
   the elevated surface with a real border rather than shouting in colour —
   the applied filter is a fact, not an alert. */

/* Every control in the band is now one fixed 28px height, so the clear
   button no longer needs a hand-tuned padding to line up with the ▾ ones. */

/* The `.search-dropdown` / `.search-dropdown-menu` / `.dropdown-item` family is
   DELETED (ADR-0120, census §A2). It was the band's Memberships and Passes
   dropdowns, whose markup went with ADR-0075 when filtering moved onto the
   Members card; the rules outlived their last emitter by a year. Re-checked
   across `public/js`, `public/index.html` and every other page before deletion:
   zero. Its `.open` rule was also the origin of the "1000/1001" tier the
   check-in popover's comment cited as precedent — a tier whose only remaining
   citation was a dead rule, which is why the tier is tokens now. */

.header-actions {
  display: flex;
  gap: var(--space-2);
}

.header-action-btn {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: var(--radius-md);
  color: #fff;
  cursor: pointer;
  transition: all 0.15s ease;
}

.header-action-btn:hover {
  background: rgba(255, 255, 255, 0.2);
  border-color: rgba(255, 255, 255, 0.3);
}

.header-action-btn.active {
  background: var(--color-accent);
  border-color: var(--color-accent);
}

.header-action-btn .action-icon {
  font-size: 1.25rem;
}

.header-action-btn .action-badge {
  position: absolute;
  top: -4px;
  right: -4px;
  min-width: 18px;
  height: 18px;
  padding: 0 4px;
  background: var(--color-danger);
  border-radius: 9px;
  color: #fff;
  font-size: 0.65rem;
  font-weight: var(--font-bold);
  display: flex;
  align-items: center;
  justify-content: center;
}

.header-action-btn .action-badge.hidden {
  display: none;
}

/* Online Orders / Camps pill buttons */
button.header-action-btn.header-action-text,
button.header-action-btn.header-action-text.active {
  width: auto;
  height: 28px;
  padding: 0 var(--space-3);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  font-weight: var(--font-semibold);
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid transparent;
  color: rgba(255, 255, 255, 0.7);
}

button.header-action-btn.header-action-text:hover {
  background: rgba(255, 255, 255, 0.15);
  color: #fff;
}

button.header-action-btn.header-action-text.has-orders,
button.header-action-btn.header-action-text.has-orders.active {
  background: var(--color-accent);
  color: #fff;
}

.header-user {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  color: rgba(255, 255, 255, 0.8);
  font-size: var(--text-sm);
}

/* The callout pinned beside the health dot when a fault has a fix (ADR-0114).
 * It IS a notification card (buildNotificationCard, two-line variant), so the
 * shape, tier dot, full tier border, text, action and dismiss all come from
 * that family; this block adds only the anchored placement and the pointer.
 * left/top/--arrow-left are set by core/system-health-callout.js. The card
 * drops BELOW the System button in the title bar (ADR-0158), pointer on top. */
.sys-health-callout {
  position: fixed;
  z-index: 330; /* above the shell panels and the date picker (320), below .modal-overlay (10000) */
  width: 380px;
  max-width: calc(100vw - 2 * var(--space-4));
}
.sys-health-callout.is-anchor-hidden {
  display: none;
}
/* The pointer: a rotated square that inherits the card's surface and tier
 * border colour, so it reads as part of the card in every tier and theme. */
.sys-health-callout::before {
  content: '';
  position: absolute;
  top: -6px;
  left: var(--arrow-left, 50%);
  width: 10px;
  height: 10px;
  background: inherit;
  border-style: solid none none solid;
  border-width: 1px;
  border-color: inherit;
  transform: translateX(-50%) rotate(45deg);
}
/* Flipped above the button (a viewport too short below it): the pointer moves
   to the card's bottom edge, as anchored-placement reports in arrowSide. */
.sys-health-callout[data-arrow-side="bottom"]::before {
  top: auto;
  bottom: -6px;
  border-style: none solid solid none;
}

/* ========================================================================
 * Release notes (foundation/release-notes.js, ADR-0192): one release under its label, as the update banner's What's
 * new window and System info's Earlier releases view both draw it. The System info window itself is css/system-info.css.
 * ======================================================================== */
.release-notes-list {
  margin: var(--space-3) 0 0;
  padding-left: var(--space-5);
  text-align: left;
  font-size: var(--text-base);
  line-height: 1.45;
  color: var(--color-text);
}
.release-notes-list li + li {
  margin-top: var(--space-1);
}
.release-notes-release {
  font-size: var(--text-sm);
  font-weight: var(--font-semibold);
  color: var(--color-text);
}
.release-notes-entry + .release-notes-entry {
  margin-top: var(--space-3);
}
.release-notes-entry > .release-notes-list {
  margin-top: var(--space-1);
}

/* ==========================================================================
   SCHEDULE SIDEBAR (Column 2 - Always Visible)
   ========================================================================== */
/* NO right edge, and no shadow. The content card's own outline
   (.main-content-body::before, inset -1px past the seam) is the ONLY
   vertical line here — that is what lets the selected card fuse into it
   as one stroke instead of reading as a card parked next to a wall. See
   "SELECTED DESTINATION — the fused tab". Adding a border-right back
   re-breaks the fusion; it is not a missing boundary. */
.schedule-sidebar {
  min-width: 220px;
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
  /* A grid item's automatic minimum is its content size (see .app-container.shell
     above) — without this the day block + card list would overflow the bounded
     row instead of shrinking to it. The sidebar itself stays unclipped; the
     scrolling happens one level in, on .schedule-scroll, so the fixed day block
     above it never moves. */
  min-height: 0;
  overflow: visible;
  /* The sidebar is the selected card's stacking context, so THIS is the layer
     that must clear the content card's outline (`.page::before`, one tier above
     --z-chrome) for the tab to open into the card. At 101 it tied the outline
     once the outline rose above the pinned header (4988d6c1), and the card,
     later in the DOM, won the tie: its left edge drew through the open side.
     Two tiers above the chrome: above the outline, below anchored surfaces. */
  z-index: calc(var(--z-chrome) + 2);
}

/* Date Navigation */
/* ==========================================================================
   SCHEDULE DAY BLOCK — V3 (design-system/schedule-sidebar.html)

   The day caption and the visitors card, one group above the list. Replaces
   three stacked bands: the 48px date nav, the full-width mode band and the
   separate visitors card. 126px of chrome became 85px.
   ========================================================================== */
/* The day block is a fixed BAND (--band-h), so the sidebar's first row lines
   up with the content card's header across the seam. app-shell.html pins it
   at 57px with an asymmetric inset — 16px on the right where it meets the
   seam, 12px on the left. */
.schedule-day-block {
  height: var(--band-h);
  box-sizing: border-box;
  display: flex;
  align-items: center;
}

.schedule-day-block .schedule-day-caption {
  height: var(--ctl-h);
  width: 100%;
  box-sizing: border-box;
  margin-bottom: 0;
}

.schedule-day-block .schedule-day-btn {
  height: var(--ctl-h);
}

/* The label takes the middle of the band and centres, so the date sits on the
   sidebar's optical centre regardless of how many controls flank it. */
.schedule-day-block .schedule-day-label {
  flex: 1;
  justify-content: center;
  font-size: var(--text-md);
}

/* The date is a VALUE, not a section label, so it is set at the same 12px as
   the visitors card's label below it and reads identically in both places. */
.schedule-day-label {
  font-family: inherit;
}

/* The date slot IS the return to today. On today there is nowhere to return
   to, so it is inert text (a span). Off today it becomes a button and says
   the action in words instead of naming the day, because the visitors card
   below then carries the viewed date: the day is never unnamed, never named
   twice, and the slot never moves. */

/* A control that does not apply goes inert, never absent. A control that
   appears shifts everything beside it under a hand that is already moving. */

/* Two distinct icons (calendar vs list) already say which view is on — no
   background fill needed on top of that. */

/* Visitors card. A destination the main panel can show, not a caption, so it
   takes the same selection as any row — including the 16px overhang past the
   seam. Bleed is horizontal, so sitting above the block's hairline changes
   nothing and the rule stays one rule: whatever is selected floats the seam. */
.schedule-visitors {
  font-family: inherit;
}

.schedule-visitors-label i {
  flex-shrink: 0;
}

/* Same fused-tab contract as a class card: fill + 1px border, no ring. */
/* ============================================================
   Schedule Date Picker Popover
   ============================================================ */
.schedule-date-picker-popover {
  position: fixed;
  z-index: var(--z-anchored, 1000);
  animation: date-picker-in 0.12s ease;
}
.schedule-date-picker-popover[hidden] {
  display: none;
}
@keyframes date-picker-in {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: translateY(0); }
}
#date-picker-month-label {
  font-size: var(--text-sm);
  font-weight: var(--font-semibold);
  color: var(--color-text);
  flex: 1;
  text-align: center;
}
.date-picker-cell.is-selected:hover {
  background: var(--color-accent);
}
/* `.date-picker-footer` is owned by v3.css: a right-aligned row, no divider.
   The rule here added a border-top and padding that v3.css does not declare,
   so they leaked through and drew a rule across the popover's foot. */
.date-picker-today-btn {
  appearance: none;
  background: none;
  border: none;
  font-family: inherit;
  font-size: var(--text-xs);
  font-weight: var(--font-semibold);
  color: var(--color-accent);
  cursor: pointer;
  padding: 4px 12px;
  border-radius: 6px;
  transition: background 0.1s ease;
}
.date-picker-today-btn:hover {
  background: color-mix(in srgb, var(--color-accent) 12%, var(--color-bg-elevated));
}


/* No right padding: a selected card reaches the seam with width:100% (the
   fused tab in v3.css), so the column's right inset lives on the CARD
   (.std-sidebar .schedule-class-item margin-right), never on this wrapper. */
/* A bare wrapper. It carries NO inset: every card in the column sits at the
   scroll's own 12px, which is the design's single left edge (left=12, w=192 for
   every card). This used to add margin-left + padding, so the all-day cards sat
   20px further in and 20px narrower than the timed ones below them -- four
   different left edges in one column. The class stays: every card slot in the
   column (the all-day camp cards, the timed list's wrappers) names its wrapper
   with it. */
.schedule-card-container {
  min-width: 0;
}

/* The sidebar's class list scrolls. The design is silent on this — its mock is
   short enough never to overflow — so the behaviour is production-owned, but it
   hangs off the design's own selector rather than a parallel class, and it adds
   NO padding or margin (the column is inset once, by .schedule-scroll). */
.schedule-sidebar .schedule-list {
  flex: 1;
  overflow-y: auto;
  min-height: 0;
}


/* ==========================================================================
   MEMBER LIST / TABLE
   ========================================================================== */
.member-table {
  /* The design's own value. Under `auto` the browser sizes columns by CONTENT,
     so one long name or one wide badge set renegotiated every other column and
     the header stopped predicting the row. `fixed` makes the widths below the
     whole story. */
  table-layout: fixed;
}

/* COLUMN WIDTHS — app-shell.html, keyed by CLASS rather than by the reference's
   `nth-child`. Production carries one column the reference does not: the
   tablet-only Access cell. A positional selector counts it, which is why the
   design's block could not be applied as written and why the table had drifted
   to its own numbers. Every column now has one name, worn by its <th> and its
   <td> alike, so position is no longer load-bearing.
   Order: Name(1) Memberships(2) Passes(3) Notes(4) Access(5) actions(6). */
/* Fixed px, not a percentage — same reasoning the actions column below
   already uses, and the same bug: `table-layout: fixed` sizes columns from
   the header row's `width` ALONE, so `min-width` on the cells is silently
   ignored (verified live: adding it changed nothing).

   Size a badge column to the widest badge it really renders, MEASURED, never
   to the sum of the segment floors. The floors (v3.css: label 90px, count
   55px, action 42px) are minimums: a segment grows to fit its words, and
   `.pass-badge` clips its own overflow, so a column sized to the floors cuts
   off the action segment, which is the part staff click. That is how the old
   210px clipped "START": it was summed from a count floor of 48px that has
   since become 55px, and from the action's floor rather than its words.

   Memberships: only membership badges carry an action (Start, Renew,
   Resume). Measured across every configured membership type, the widest is
   "Resume": 209px at 100% text size, 217px at 120%, the largest step
   ADR-0081 allows (type scales, the px floors do not). 217px + 28px of cell
   padding = 245px. Passes carry no action segment, so 210px still fits. */
.member-table .member-th-memberships,
.member-table .member-cell-memberships { width: 245px; }
.member-table .member-th-passes,
.member-table .member-cell-passes { width: 210px; }
/* Notes is a fixed 220px: one line of an important note (an allergy, a pickup
   rule), ended by .notes-preview-important's ellipsis; the hover preview shows
   it whole. It used to be `auto` and split the
   remainder with Name half each (2026-10-16), so Name lost what Notes gained:
   178px at a 1280 window, 78px at 1081, and names truncated. Name owns the
   remainder alone; when the table cannot give Name its floor beside this
   column, the column goes (NAME FIRST, below) and the note moves under the
   name. */
.member-table .member-th-notes,
.member-table .member-cell-notes { width: 220px; }
/* The same note under the name, drawn only where the Notes column is hidden
   (NAME FIRST below shows it). */
.member-table .member-name-note { display: none; }
/* Fixed px, not a percentage: View + the overflow button + padding measure
   ~130px, and a percentage dips below that at narrow card widths and spills the
   button past the card edge. The three empty spare <th>s this replaced reserved
   175px between them. */
.member-table .member-th-actions,
.member-table .member-cell-action-edit {
  width: 132px;
  text-align: right;
  padding-right: var(--space-3);
  white-space: nowrap;
}
/* Name is the one column with no width: it absorbs the remainder. No
   `min-width` either — under fixed layout that would come out of the columns
   the design pinned. Its floor is kept by NAME FIRST below instead. */
.member-table .member-th-name,
.member-table .member-cell-name { width: auto; }

/* NAME FIRST. A truncated name is worse than a note moved under it, so the Notes
   column exists only while the table can give Name 320px beside it: a long name
   (186px at 100% text) plus the check-in icon, the status icons and the cell
   padding. The other columns are fixed: 245 + 210 + 220 + 132 + 320 = 1127px.
   Below that the Notes column collapses to zero (the same collapse the Access
   column uses on desktop, for the same reason) and the note is drawn under the
   name, where it stays readable.

   Keyed to the TABLE's width, not the window's: the window minus the nav rail
   and the shell's gutters is what the table gets, and a media query would carry
   that subtraction as a magic number. The container is the table's parent (a
   table cannot be its own query container). `container-type` adds style and
   inline-size containment only, so the rows' position:fixed menus still place
   against the viewport. The phone draws cards, not this table, so it is not a
   container there. */
html:not([data-mode="mobile"]) :has(> .member-table) {
  container: member-table / inline-size;
}
@container member-table (width < 1127px) {
  .member-table .member-cell-notes,
  .member-table .member-th-notes {
    width: 0;
    padding: 0;
    border: 0;
    overflow: hidden;
    visibility: hidden;
  }
  .member-table .member-name-note { display: block; margin-top: var(--space-1); white-space: normal; }
}

/* One line, truncated — never wrapped. A wrapped name changes the row's height,
   which breaks the alignment the badge floors exist to hold. */
.member-table .member-cell-name .member-name {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* .member-actions wraps the waiver + schedule icons inside the table row's
   name wrapper; transparent (display:contents) so the icons stay inline
   beside the name. The phone chrome renders its own row markup (see
   mobile-search.js) rather than promoting this wrapper, so this rule
   now serves the desktop/tablet table only. */
.member-actions {
  display: contents;
}

/* Combined access column — narrow tables only (under 907px, below).

   On desktop it COLLAPSES TO ZERO WIDTH rather than `display: none`, and the
   difference is not cosmetic. Under `table-layout: fixed` a `display: none`
   cell still counts as a column and still claims a share of the table's free
   space; the space is then simply lost, because the cell paints nothing.
   Name was the only other `auto` column, so the split was even: Name took 226px
   of the 452 available, every member name truncated at half its proper width
   ("Bristol, Liana, Tate / Miln..."), and 226px of table sat empty to the right
   of the actions column. With the column collapsed to a real zero, Name takes
   the whole 451px remainder and the columns fill the card.

   This is the same shape as the incident recorded in the tablet block below —
   "A column is hidden or it is not; it is never both" — one level up: hiding a
   column with `display` does not remove it from a fixed table's column set. */
/* Scoped through `.member-table` so it outranks `.member-table th` (0,1,1),
   which would otherwise put its 14px cell padding back and leave the collapsed
   column 28px wide. */
.member-table .member-cell-access,
.member-table .member-th-access {
  width: 0;
  padding: 0;
  border: 0;
  overflow: hidden;
  visibility: hidden;
}

/* Narrow: Name / Access / Overflow ; stack memberships+passes in one column.
   NAME FIRST again, one step down: the wide layout needs 245 + 210 + 132 = 587px
   of fixed columns plus Name's 320px floor, so it holds while the table is at
   least 907px wide. It used to switch at a 1080px WINDOW, which left a table
   743-842px wide (windows 1081-1180, an iPad held sideways) carrying every
   column and Name 156-255px. Same container as NAME FIRST, same reason. */
@container member-table (width < 907px) {
  /* Memberships and Passes give way to the combined Access column (Notes is
     already gone at this width: NAME FIRST above). The
     actions column is NOT hidden here: it stays, narrowed to its `...` control.
     It used to be hidden and then re-shown thirty lines further down, which
     under `table-layout: fixed` left a visible column whose header cell was
     display:none -- and the fixed algorithm reads its widths from the first
     row, so the column fell through to "auto" and took 240px of a 674px table
     instead of 50. A column is hidden or it is not; it is never both. */
  /* And they collapse to zero, for the same reason the Access column does on
     desktop, at three times the cost: three hidden columns split the free space
     four ways with Name, so Name measured 109px of a 682px table and every name
     read as "Li, Ev...". Zeroed, Name takes the whole 437px remainder. */
  .member-table .member-cell-memberships,
  .member-table .member-th-memberships,
  .member-table .member-cell-passes,
  .member-table .member-th-passes {
    width: 0;
    padding: 0;
    border: 0;
    overflow: hidden;
    visibility: hidden;
  }

  /* Show combined access column on tablet. Everything the desktop collapse
     zeroed has to come back, not just the width. */
  .member-table .member-cell-access {
    padding: 8px 14px;
    border-bottom: 1px solid var(--color-border);
    overflow: visible;
    visibility: visible;
  }

  /* Memberships and passes stack in this cell here, so it must fit the same
     widest membership badge as the desktop Memberships column: 245px (see
     COLUMN WIDTHS above). At 195px a "Resume" badge lost its action. */
  .member-table th.member-th-access {
    width: 245px;
    padding: 8px 14px;
    border-bottom: 1px solid var(--color-border);
    overflow: visible;
    visibility: visible;
  }

  /* The action cell IS the overflow column at this width: one `...` control,
     no View button. Both halves of the column take the width, so the fixed
     layout can read it off the header row.

     It KEEPS the table's own 14px cell gutter. `padding-right: 0` used to sit
     here, which left the `...` about 3px off the card's right edge while every
     other cell in the row held its gutter — the row read as bleeding past the
     card on that side. The width grows to cover the control plus both gutters
     (30 + 14 + 14) so the fixed layout still has a real number to read. */
  .member-table .member-th-actions,
  .member-table .member-cell-action-edit {
    width: 58px;
    text-align: right;
  }

  /* Scoped to .member-row, NOT the bare cell: a group header row's action
     cell holds ONE plain Edit button (no overflow trigger at all, since a
     `...` whose only item duplicated the visible button was dead weight) and
     must stay reachable at every width. This rule only ever collapses the
     View+overflow segmented pair down to the trigger. */
  .member-table .member-row .member-cell-action-edit .btn:not(.member-overflow-trigger) {
    display: none;
  }

  /* The trigger is now the ONLY visible control in the group (View is
     hidden above), so both its outer corners are outer corners. .btn-group
     .btn:first-child owns the left pair but never matches it here — View is
     still its DOM predecessor, just invisible — so the left pair is claimed
     explicitly instead of staying stuck at the base .btn-group .btn's square
     0. The right pair already rounds correctly via :last-of-type. */
  .member-table .member-row .member-cell-action-edit .member-overflow-trigger {
    border-top-left-radius: var(--radius-sm);
    border-bottom-left-radius: var(--radius-sm);
  }

  /* Name column takes remaining space on tablet */
  .member-table .member-th-name,
  .member-table .member-cell-name { width: auto; }

  /* Hide dash placeholders in combined cell */
  .member-table .member-cell-access > .text-muted {
    display: none;
  }
}

/* The row hover itself moved to v3.css's `.data-table` base (2026-09-12,
   admin-ui PR2) — `.member-table` carries `data-table` in its markup now, so
   this table's hover comes from there, not from a rule declared here. That
   base rule also excludes .group-header-row (cleanup 2026-09-13), so this
   file no longer needs its own hover rule at all. */

/* Name column - flex layout for icon(s) + name/email */

.member-name {
  text-overflow: ellipsis;
}

/* v3.css owns this element. The only declaration that was ever ours is the
   margin -- and since v3 is silent about it, it survived and pushed the contact
   line 6px off the name above it. */
.member-contact {
  margin-left: 0;
}

.member-contact:empty {
  display: none;
}

/* Pass Badges - Two-tone pill design: [Label | Count]
   Structure: <span class="pass-badge TYPE"><span class="badge-label">Text</span><span class="badge-count">Value</span></span>
*/
.pass-badges {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* For badges directly in table cells (without wrapper) */
.member-table td > .pass-badge {
  margin-right: 6px;
  margin-bottom: 2px;
}

.member-table td > .pass-badge:last-child {
  margin-right: 0;
  margin-bottom: 0;
}

/* Badge container */
/* Badge container.

   ONE render path: renderBadge() puts the resolved hex on the inline
   --badge-color var and the CSS builds the whole pill from it. Category
   classes below are the static-markup mirror of the same value.

   The V3 shape is a TINTED label + a SOLID count, divided by a border
   derived from the category color — not a solid fill with dark overlays.
   The label carries the type, the count carries the number, and the
   division between them is what makes a column of badges scannable. */

/* Clickable membership badges */

/* Inline label icons: shared group pass, and payer-funded visit (ADR-0066). */

/* Left section - the label */

/* A label-only pill (the add-on fee chip) has no count segment to carry the
   solid fill, so the label takes it. --badge-text-color is the admin's
   per-type override; white is the derived default. */

/* Right section - the count */

/* CSS arrow replacing → for consistent cross-platform rendering */
.count-arrow,
.pass-badge .count-arrow {
  font-size: 0.8em;
  margin: 0 1px;
  opacity: 0.9;
}

/* Count-change states. Fixed hues with white text: they must read the same
   on every category, so they deliberately do NOT derive from --bcat. */

/* Action button within badge */
.pass-badge .badge-action {
  cursor: pointer;
}

/* Standalone group pass USE buttons (for member rows) - matches .badge-action style */
/* Sits beside pass badges in a group member row, so it takes the same floors:
   22px tall (v3.css:197 pins that to .pass-badge's height so nothing in a row
   reads shorter) and .badge-action's 42px min-width. */
.group-pass-use-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  height: 22px;
  min-width: 42px;
  padding: 0 8px;
  border-radius: var(--radius-sm);
  font-size: 0.7143rem;
  letter-spacing: 0.02em;
  font-weight: 600;
  color: #fff;
  cursor: pointer;
  transition: background-color 0.15s ease;
  margin-right: 2px;
  /* Dark overlay to match .badge-action */
  box-shadow: inset 0 0 0 100px rgba(0, 0, 0, 0.35);
}

.group-pass-use-btn:hover {
  box-shadow: inset 0 0 0 100px rgba(0, 0, 0, 0.5);
}

.group-pass-use-btn:active {
  box-shadow: inset 0 0 0 100px rgba(0, 0, 0, 0.6);
}

.group-pass-use-btn i {
  font-size: 0.7rem;
}

/* Category vars — the static-markup mirror of the hexes the live access
   config supplies. A type's own color field is the rendering authority;
   these classes exist so hand-written markup gets the same result. */

.pass-badge.classes,
.pass-badge.membership {
  --bcat: var(--color-classes);
}

/* Tag memberships — identity markers, always win badge priority */

/* Drop-in carries no color of its own: renderBadge colors it by the class's
   access category (class drop-in = classes blue, athletic = gold, day pass =
   open-gym green). This static class defaults to the common class drop-in;
   BADGE_COLORS.dropin #00897b is only the Square Unmapped/Unknown fallback. */

.pass-badge.multi-pass {
  --bcat: #6b7280;
}

/* State overlays — these REPLACE the type color (STATE_COLORS) */

/* Athletic: gold needs dark ink — but ONLY on the type fill. A state fill
   (increased, decreased, depleted, zero) paints its own dark hue where that
   ink would be unreadable, so those keep white. Declared BEFORE the state
   block wins on equal specificity, which is why the count-state rules above
   sit earlier in the file. */

/* A fee count keeps white ink: it sits on its own neutral fill, not the gold. */

/* Dark mode: lift the label tint off the darker elevated surface and pull
   the ink toward white, or a 75%-toward-near-white text lands unreadable. */

/* Generic faded state overlay - the live badge resolver emits this class
   for >14-day-expired memberships regardless of type color. Stacks on top
   of any background set via --badge-color (the only live-rendering path)
   or via a legacy color class (only used by static demo HTML). */

/* ==========================================================================
   Badge Animation States
   For dynamic count updates (5→4 transitions, etc.)
   ========================================================================== */

/* Transitioning state - disable clicks, keep layout stable */
.pass-badge.badge-transitioning {
  pointer-events: none;
}

/* Badge count transition container - horizontal wheel effect */
.badge-count-inner {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 1px;
  overflow: hidden;
  white-space: nowrap;
}

/* Individual animated elements within badge count */
.badge-count-inner > span {
  display: inline-block;
  transition: transform 0.35s ease-out, opacity 0.35s ease-out;
}

/* Hidden state - off screen to the right */
.badge-count-inner .hidden-right {
  opacity: 0;
  transform: translateX(12px);
}

/* Shift left to make room for incoming elements */
.badge-count-inner .shift-left {
  animation: wheelShiftLeft 0.35s ease-out forwards;
}

/* Slide in from right */
.badge-count-inner .slide-in-right {
  animation: wheelSlideInRight var(--anim-slide-duration) var(--anim-easing) forwards;
}

/* Slide out to left */
.badge-count-inner .slide-out-left {
  animation: wheelSlideOutLeft var(--anim-slide-duration) var(--anim-easing) forwards;
}

/* Slide out to right (for undo) */
.badge-count-inner .slide-out-right {
  animation: wheelSlideOutRight var(--anim-slide-duration) var(--anim-easing) forwards;
}

/* Slide to center after others leave */
.badge-count-inner .slide-to-center {
  animation: wheelSlideToCenter var(--anim-center-duration) var(--anim-center-easing) forwards;
  animation-delay: var(--anim-center-delay);
}

/* Note: Action button stays visible during transition to prevent flickering.
   Only hide it explicitly when count reaches 0 (done in JS). */

/* Error state - brief red flash */
.pass-badge.badge-error .badge-count {
  animation: badge-error-flash 0.4s ease-out;
  background-color: var(--color-danger, #ef4444) !important;
}

/* Pending state - click registered, server request in flight.
   Disables the action button and shows a subtle spinner so staff know the
   click was accepted. */
/* In-flight loading state - SHARED with runPendingAction() in utils-manager.js.
   The base .is-pending rule (further down) sets opacity / pointer-events.
   These rules add the element-specific overrides: a spinner overlay on the
   action button slot, and a slightly heavier opacity for badge-style elements.
   .pass-badge / .group-pass-use-btn / .member-schedule-btn never set a
   background-color in this state, so the type color (--badge-color) remains
   visible underneath. */
.pass-badge.is-pending .badge-action,
.pass-badge.is-pending .group-pass-use-btn,
/* A clickable badge with no action segment (whole-badge check-in, GH #38)
   has no .badge-action slot to host the spinner — the solid count segment
   hosts it instead, same treatment. */
.pass-badge.is-pending:not(:has(.badge-action)) .badge-count,
.group-pass-use-btn.is-pending,
.member-schedule-btn.is-pending {
  pointer-events: none;
  position: relative;
  color: transparent !important;
  text-shadow: none !important;
}
/* Hide the calendar icon on member-schedule-btn while pending so only the
   spinner is visible. The ::after spinner is positioned over the space. */
.member-schedule-btn.is-pending > i {
  visibility: hidden;
}
.pass-badge.is-pending .badge-action::after,
.pass-badge.is-pending .group-pass-use-btn::after,
.pass-badge.is-pending:not(:has(.badge-action)) .badge-count::after,
.group-pass-use-btn.is-pending::after,
.member-schedule-btn.is-pending::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 10px;
  height: 10px;
  margin: -5px 0 0 -5px;
  border: 1.5px solid rgba(255, 255, 255, 0.4);
  border-top-color: #fff;
  border-radius: 50%;
  animation: badge-pending-spin 0.7s linear infinite;
}
/* On the schedule button in its default (unchecked) state the background is
   gray, so tint the spinner to the button's accent color for visibility. */
.member-schedule-btn.is-pending:not(.checked-in)::after {
  border-color: rgba(0, 0, 0, 0.15);
  border-top-color: var(--color-classes, var(--color-accent));
}

/* Ack state - brief success pulse after server accepts, before count animates */
.pass-badge.badge-ack .badge-count,
.member-schedule-btn.badge-ack {
  animation: badge-ack-flash 0.18s ease-out;
}

@keyframes badge-pending-spin {
  to { transform: rotate(360deg); }
}

/* Optimistic-UI boundary convention (D4). Add `.is-pending` to a button/row
 * while an async write is in flight. `runPendingAction()` in utils-manager.js
 * adds/removes the class around the await. */
.is-pending {
  pointer-events: none;
  opacity: 0.55;
  cursor: progress;
  transition: opacity 120ms ease-out;
}
.is-pending[data-pending-spinner]::after {
  content: "";
  display: inline-block;
  width: 0.9em;
  height: 0.9em;
  margin-left: 0.4em;
  vertical-align: -0.1em;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: badge-pending-spin 0.7s linear infinite;
}

/* Inset shadow keeps the success pulse INSIDE the count element so it
   doesn't bleed across the label boundary. */
@keyframes badge-ack-flash {
  0% { box-shadow: inset 0 0 0 6px color-mix(in srgb, var(--color-success) 55%, transparent); }
  100% { box-shadow: inset 0 0 0 0 transparent; }
}

/* Keyframes - Horizontal wheel effect */
@keyframes wheelSlideInRight {
  0% {
    opacity: 0;
    transform: translateX(12px);
  }
  100% {
    opacity: 1;
    transform: translateX(0);
  }
}

@keyframes wheelSlideOutLeft {
  0% {
    opacity: 1;
    transform: translateX(0);
  }
  100% {
    opacity: 0;
    transform: translateX(-12px);
  }
}

@keyframes wheelSlideOutRight {
  0% {
    opacity: 1;
    transform: translateX(0);
  }
  100% {
    opacity: 0;
    transform: translateX(12px);
  }
}

@keyframes wheelShiftLeft {
  0% {
    transform: translateX(0);
  }
  100% {
    transform: translateX(-4px);
  }
}

@keyframes wheelSlideToCenter {
  0% {
    transform: translateX(8px);
  }
  100% {
    transform: translateX(0);
  }
}

@keyframes badge-error-flash {
  0%, 50% { background-color: var(--color-danger, #ef4444); }
  100% { background-color: rgba(0, 0, 0, 0.2); }
}

/* Horizontal Carousel Animation */
.carousel-track {
  display: flex;
  gap: 0;
  transition: transform 0.5s ease-out;
}

.carousel-item {
  flex-shrink: 0;
  width: 100%;
  text-align: center;
}

.carousel-track.slide-left {
  transform: translateX(-100%);
}

.carousel-track.slide-right-anim {
  transform: translateX(0);
}

.member-notes {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  font-style: italic;
  max-width: 200px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Action link (Resume, Start) */
.action-link {
  font-size: var(--text-xs);
  color: var(--color-accent);
  text-decoration: none;
  margin-left: var(--space-2);
}

.action-link:hover {
  text-decoration: underline;
}

/* .btn-group lives entirely in v3.css now (APP SHELL / shared components) —
   this copy was a full duplicate, still on the `:last-child` bug v3.css's own
   comment documents (the overflow MENU div is the actual last child, so the
   last BUTTON lost its right radius). One owner. */

