/* ==========================================================================
   .bp-card — P1.F2.T3
   ©2026 Vexstorm Studios – All Rights Reserved

   The general-purpose panel: a dashboard tile, a record summary, a form
   section, the container a chart and its exact-value table share.

   Variants   --elevated  --interactive  --compact  --flush
   Elements   __header  __title  __subtitle  __actions  __body  __footer  __metric
   States     :hover  :active  :focus-visible  (interactive variant only)

   The card's edge is border-subtle rather than border-default on purpose: a
   card is a grouping, not a control, so WCAG 1.4.11's 3:1 boundary requirement
   does not apply to it. Anything inside the card that IS a control carries
   border-default and is asserted at 3:1 against every surface, including this
   one.
   ========================================================================== */

.bp-card {
  display: flex;
  flex-direction: column;
  min-width: 0;
  border: var(--bp-border-width-hairline) solid var(--bp-color-border-subtle);
  border-radius: var(--bp-radius-lg);
  background-color: var(--bp-color-surface-raised);
  color: var(--bp-color-text-primary);
  /* The application header is `position: sticky`, so a card the browser scrolls to on its own —
     a fragment link from a section index, or a restored position after a POST — would otherwise
     arrive underneath it, with its own title the part that is hidden. The same reasoning and the
     same token as `.bp-filter-panel`, applied here because any card can be a fragment target. */
  scroll-margin-block-start: var(--bp-space-header-height);
}

/* --------------------------------------------------------------------------
   Elements
   -------------------------------------------------------------------------- */

.bp-card__header {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--bp-space-3);
  padding: var(--bp-space-5) var(--bp-space-6);
  border-bottom: var(--bp-border-width-hairline) solid var(--bp-color-border-subtle);
}

.bp-card__title {
  min-width: 0;
  color: var(--bp-color-text-primary);
  font-size: var(--bp-text-lg);
  font-weight: var(--bp-weight-bold);
  line-height: var(--bp-leading-tight);
  overflow-wrap: break-word;
}

.bp-card__subtitle {
  margin-top: var(--bp-space-1);
  color: var(--bp-color-text-muted);
  font-size: var(--bp-text-sm);
  line-height: var(--bp-leading-normal);
}

.bp-card__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bp-space-2);
}

.bp-card__body {
  padding: var(--bp-space-6);
  min-width: 0;
  /* Fills the remaining height so a row of cards has aligned footers. */
  flex: 1 1 auto;
}

.bp-card__footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--bp-space-3);
  padding: var(--bp-space-4) var(--bp-space-6);
  border-top: var(--bp-border-width-hairline) solid var(--bp-color-border-subtle);
  background-color: var(--bp-color-surface-sunken);
  border-end-start-radius: var(--bp-radius-lg);
  border-end-end-radius: var(--bp-radius-lg);
  color: var(--bp-color-text-muted);
  font-size: var(--bp-text-sm);
}

/* A single headline figure. Tabular figures, because these are compared across
   a row of tiles, and a figure is never rendered without the label that says
   what it counts (UX principle: honesty of data). */
.bp-card__metric {
  display: flex;
  flex-direction: column;
  gap: var(--bp-space-1);
}

.bp-card__metric-value {
  color: var(--bp-color-text-primary);
  font-size: var(--bp-text-3xl);
  font-weight: var(--bp-weight-bold);
  line-height: var(--bp-leading-tight);
  font-variant-numeric: tabular-nums;
}

.bp-card__metric-label {
  color: var(--bp-color-text-secondary);
  font-size: var(--bp-text-sm);
}

/* A row of figures rather than a column of them. Five metrics stacked one per
   line is five screenfuls of nothing on a phone and a mostly empty card on a
   desktop, and the whole point of a figure is that it is compared with the
   ones beside it. auto-fit with a minimum means no breakpoint: it is one
   column at 320px and as many as fit after that. */
.bp-card__metrics {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(var(--bp-space-metric-min), 1fr));
  gap: var(--bp-space-5);
  min-width: 0;
}

/* --------------------------------------------------------------------------
   Variants
   -------------------------------------------------------------------------- */

.bp-card--elevated {
  border-color: transparent;
  box-shadow: var(--bp-shadow-md);
}

.bp-card--compact .bp-card__header {
  padding: var(--bp-space-3) var(--bp-space-4);
}

.bp-card--compact .bp-card__body {
  padding: var(--bp-space-4);
}

.bp-card--compact .bp-card__footer {
  padding: var(--bp-space-3) var(--bp-space-4);
}

/* For a card whose body is a full-bleed table or chart. */
.bp-card--flush .bp-card__body {
  padding: 0;
}

/* A card that is itself a link or button. The whole card is one target, so it
   must carry one accessible name — the template puts the link around the
   title and stretches its hit area, rather than nesting controls. */
.bp-card--interactive {
  cursor: pointer;
  transition:
    border-color var(--bp-motion-duration-fast) var(--bp-motion-ease-standard),
    background-color var(--bp-motion-duration-fast) var(--bp-motion-ease-standard);
}

.bp-card--interactive:hover {
  border-color: var(--bp-color-border-default);
  background-color: var(--bp-color-surface-sunken);
}

.bp-card--interactive:active {
  background-color: var(--bp-color-accent-subtle);
}

.bp-card--interactive:focus-within {
  /* The ring belongs to the control inside; the card echoes it so the
     relationship between the two is visible. */
  border-color: var(--bp-color-border-focus);
}

.bp-card--interactive:focus-visible {
  position: relative;
  z-index: var(--bp-z-dropdown);
}

/* --------------------------------------------------------------------------
   Disabled — a card representing an inert record (a soft-deleted observation,
   a superseded report). Marked with aria-disabled in the template, never by
   removing it from the DOM: a record the user can no longer act on is still a
   record they must be able to read.
   -------------------------------------------------------------------------- */

.bp-card[aria-disabled="true"] {
  opacity: var(--bp-opacity-disabled);
  cursor: not-allowed;
}

/* --------------------------------------------------------------------------
   Collapsible — a card whose body folds away, for a screen that is a stack of
   sections rather than one report
   --------------------------------------------------------------------------

   The Configuration screen carries one section per register group and every
   one of them was open at once, which is a page of scrolling to reach a
   setting you already knew the name of. Health is the same shape and is read
   when something is wrong, which is the worst moment to be scrolling.

   PROGRESSIVE ENHANCEMENT, IN THE DIRECTION THAT KEEPS THE PAGE USABLE. The
   server ships every section EXPANDED with `aria-expanded="true"`, and
   `section-panel.js` collapses them on load — the same direction as `nav.js`
   and `filter-panel.js`. With scripting off the reader gets every section open
   rather than every section sealed, so nothing is unreachable and no control
   is hidden behind a button that does nothing.

   THE REVEAL is `grid-template-rows: 1fr -> 0fr`, the technique
   `.bp-filter-panel` documents: the only one that animates to the content's
   real height, and one that still snaps correctly where a browser declines to
   interpolate it. The collapsed region is `visibility: hidden` as well as
   zero-height, because a zero-height `overflow: hidden` region still holds
   every control inside it in the tab order — which is worse than no disclosure
   at all.
   -------------------------------------------------------------------------- */

/* The header becomes the control. Aligned centre rather than to the top,
   because a toggle beside a two-line subtitle otherwise floats at its top. */
.bp-card--collapsible > .bp-card__header {
  align-items: center;
}

/* A button that spans the header, so the whole strip is the hit area rather
   than a small chevron at one end. It carries no button chrome: the card's own
   border is the boundary, and a bordered control inside a bordered header
   reads as two boxes. */
.bp-card__toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--bp-space-3);
  width: 100%;
  min-height: var(--bp-space-target-min);
  padding: 0;
  border: 0;
  border-radius: var(--bp-radius-sm);
  background: none;
  color: inherit;
  font: inherit;
  text-align: start;
  cursor: pointer;
}

.bp-card__toggle:focus-visible {
  outline: var(--bp-border-width-thick) solid var(--bp-color-border-focus);
  outline-offset: var(--bp-space-1);
}

/* Rotates to point at the state the card is IN, not the action. The word in
   the button's accessible name carries the action; this is reinforcement. */
.bp-card__toggle-icon {
  width: var(--bp-space-icon);
  height: var(--bp-space-icon);
  flex: none;
  color: var(--bp-color-text-secondary);
  transition: transform var(--bp-motion-duration-base)
    var(--bp-motion-ease-standard);
}

.bp-card__toggle[aria-expanded="true"] .bp-card__toggle-icon {
  transform: rotate(180deg);
}

.bp-card__region {
  display: grid;
  grid-template-rows: 1fr;
  transition: grid-template-rows var(--bp-motion-duration-base)
    var(--bp-motion-ease-standard);
}

/* `min-height: 0` is what lets the row actually reach zero — a grid item's
   automatic minimum size is its content, so without it the region collapses to
   the height of its contents instead of to nothing. */
.bp-card__region-inner {
  min-height: 0;
  overflow: hidden;
  visibility: visible;
  transition-property: visibility;
}

.bp-card__region.bp-is-collapsed {
  grid-template-rows: 0fr;
}

.bp-card__region.bp-is-collapsed .bp-card__region-inner {
  visibility: hidden;
  transition-delay: var(--bp-motion-duration-base);
}

/* A collapsed card's header no longer sits above anything, so its dividing
   rule is a line under nothing. */
.bp-card--collapsible:has(.bp-card__region.bp-is-collapsed) > .bp-card__header {
  border-bottom-color: transparent;
}
