/**
 * app.css -- everything Arsha does not already cover.
 * Deliberately reuses Arsha's tokens (--heading-color, --accent-color) so the
 * signed-in area and the public site read as one product.
 */

:root {
  --app-sidebar-width: 264px;
  --app-sidebar-bg: #37517e;
  --app-sidebar-fg: rgba(255, 255, 255, 0.78);
  --app-sidebar-fg-active: #ffffff;
  --app-surface: #ffffff;
  --app-bg: #f5f6f8;
  --app-border: #e5e9f0;
  --app-muted: #7a869a;
  --app-success: #2e9e6b;
  --app-warning: #d99a2b;
  --app-danger: #d9534f;
}

.brand-accent { color: var(--accent-color); }

/* ==================================================================
   Public site adjustments
   ================================================================== */
.auth-page .main { background: var(--app-bg); }

.auth-wrap {
  min-height: calc(100vh - 90px);
  display: flex;
  align-items: center;
  padding: 48px 0;
}

.auth-card {
  background: var(--app-surface);
  border: 1px solid var(--app-border);
  border-radius: 14px;
  padding: 36px;
  box-shadow: 0 10px 40px rgba(55, 81, 126, 0.08);
}

.auth-card h2 {
  color: var(--heading-color);
  font-size: 26px;
  margin-bottom: 6px;
}

.auth-card .auth-lead { color: var(--app-muted); margin-bottom: 26px; }

.auth-aside {
  color: var(--heading-color);
  padding: 20px 40px 20px 0;
}

.auth-aside h3 { font-size: 30px; line-height: 1.25; }

.auth-aside ul { list-style: none; padding: 0; margin-top: 24px; }

.auth-aside li {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  margin-bottom: 16px;
  color: var(--default-color);
}

.auth-aside li i { color: var(--accent-color); font-size: 20px; line-height: 1.4; }

.demo-accounts {
  background: #eef4fb;
  border: 1px dashed #b9d4ea;
  border-radius: 10px;
  padding: 16px 18px;
  font-size: 13px;
  margin-top: 26px;
}

.demo-accounts table { width: 100%; margin-bottom: 6px; }
.demo-accounts td { padding: 3px 8px 3px 0; }
.demo-accounts code { color: var(--heading-color); background: transparent; padding: 0; }

.code-input {
  font-size: 30px;
  letter-spacing: 14px;
  text-align: center;
  font-weight: 600;
  padding: 12px;
}

/* ==================================================================
   Signed-in shell
   ================================================================== */
.app-body {
  background: var(--app-bg);
  font-family: var(--default-font);
  color: var(--default-color);
}

/**
 * align-items: flex-start is the load-bearing bit.
 *
 * With the default `stretch`, .app-main is stretched to the flex line's
 * height -- which the sidebar pins at 100vh -- and any content taller than
 * the viewport spills out below the footer instead of pushing it down.
 * flex-start lets .app-main size to its own content, so the shell grows and
 * the footer always lands underneath. The sidebar keeps its own 100vh and
 * sticks as you scroll.
 */
.app-shell {
  display: flex;
  min-height: 100vh;
  align-items: flex-start;
}

.app-sidebar {
  width: var(--app-sidebar-width);
  flex: 0 0 var(--app-sidebar-width);
  background: var(--app-sidebar-bg);
  display: flex;
  flex-direction: column;
  padding: 22px 0 14px;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
  /*
   * ==================================================================
   * WHY THIS NUMBER IS HERE AND NOT ON THE FLYOUT
   * ==================================================================
   *
   * Collapsed to icons, a submenu opens beside the icon as a fixed
   * flyout with z-index: 1200 -- and it still appeared UNDERNEATH page
   * content, so hovering across the gap to reach it lost the hover and
   * the menu vanished before it could be clicked.
   *
   * The flyout's number was never the problem. `position: sticky`
   * ALWAYS creates a stacking context, whatever its z-index. So the
   * sidebar is a sealed context, and 1200 only ranks the flyout against
   * its siblings INSIDE the sidebar. Ranked against the page it counts
   * for nothing.
   *
   * With z-index: auto, the sidebar took its place among positioned
   * elements in DOM order -- and it is the FIRST thing in .app-shell, so
   * every positioned thing after it won. That is nearly everything:
   * Bootstrap gives `.input-group > .form-control` position: relative,
   * which is enough. A plain scan box beat a menu.
   *
   * Raising the whole context fixes every case at once. 1050 sits above
   * page content and Bootstrap's sticky layer (1020) and below a modal
   * (1055) -- a dialog must still cover the menu, or its backdrop leaves
   * a live, clickable sidebar poking through.
   */
  z-index: 1050;
}

.app-brand {
  color: #fff;
  font-family: var(--heading-font);
  font-weight: 700;
  font-size: 21px;
  text-decoration: none;
  padding: 0 22px 18px;
  display: block;
}

.app-brand:hover { color: #fff; }

.app-scope {
  margin: 0 14px 18px;
  padding: 10px 14px;
  background: rgba(255, 255, 255, 0.08);
  border-radius: 8px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.app-scope-label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 1.1px;
  color: rgba(255, 255, 255, 0.55);
}

.app-scope-value { color: #fff; font-size: 13px; font-weight: 600; }

.app-nav { flex: 1; }

.app-nav-link {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 11px 22px;
  color: var(--app-sidebar-fg);
  text-decoration: none;
  font-size: 14.5px;
  transition: background 0.2s, color 0.2s;
  border-left: 3px solid transparent;
}

.app-nav-link:hover {
  color: var(--app-sidebar-fg-active);
  background: rgba(255, 255, 255, 0.06);
}

.app-nav-link.active {
  color: var(--app-sidebar-fg-active);
  background: rgba(71, 178, 228, 0.16);
  border-left-color: var(--accent-color);
}

.app-nav-link i:first-child { font-size: 17px; width: 20px; }

.app-nav-toggle[aria-expanded="true"] .bi-chevron-down { transform: rotate(180deg); }
.app-nav-toggle .bi-chevron-down { transition: transform 0.2s; font-size: 12px; }

.app-subnav { padding: 2px 0 6px; }

.app-subnav-link {
  display: block;
  padding: 8px 22px 8px 54px;
  color: rgba(255, 255, 255, 0.62);
  font-size: 13.5px;
  text-decoration: none;
}

.app-subnav-link:hover { color: #fff; background: rgba(255, 255, 255, 0.05); }

/*
 * The page you are on.
 *
 * Colour and weight alone were easy to miss in a list this long -- a
 * slightly brighter line among thirty. The bar and the lit background
 * make it findable without reading, which is the point of marking it at
 * all.
 */
.app-subnav-link.active {
  color: #fff;
  font-weight: 600;
  background: rgba(255, 255, 255, 0.09);
  box-shadow: inset 3px 0 0 var(--accent-color);
}

/*
 * A group label inside a submenu. Not a link and not tabbable: it is a
 * signpost, and landing on one with the keyboard would be a dead stop.
 */
.app-subnav-heading {
  padding: 14px 22px 5px 40px;
  margin-top: 6px;
  font-size: 11.5px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.78);
  font-weight: 700;
  pointer-events: none;
  border-top: 1px solid rgba(255, 255, 255, 0.12);
}
.app-subnav-heading:first-child { border-top: none; margin-top: 0; padding-top: 6px; }

.app-sidebar-foot { border-top: 1px solid rgba(255, 255, 255, 0.1); padding-top: 10px; }

.app-main {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  min-height: 100vh;   /* at least a screen, grows with content */
  /* NO align-self here. In a row flex container the cross axis is
     VERTICAL, so align-self: stretch would stretch this to the flex line's
     height -- which the 100vh sidebar sets -- and content taller than the
     viewport would spill past the footer again. Width comes from
     flex: 1 1 auto on the main axis, which is unaffected. */
}

.app-topbar {
  background: var(--app-surface);
  border-bottom: 1px solid var(--app-border);
  padding: 16px 28px;
  display: flex;
  align-items: center;
  gap: 16px;
}

.app-topbar-title h1 {
  font-size: 21px;
  margin: 0;
  color: var(--heading-color);
  font-family: var(--heading-font);
  font-weight: 600;
}

.app-topbar-title p { margin: 2px 0 0; font-size: 13.5px; color: var(--app-muted); }

.app-topbar-user { margin-left: auto; }

.app-avatar-btn { display: flex; align-items: center; gap: 10px; font-size: 14px; }
.app-avatar-btn:focus { box-shadow: none; }

.app-avatar {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--accent-color);
  color: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  font-weight: 700;
}

/**
 * flex-basis MUST be auto here.
 *
 * `flex: 1` is shorthand for `flex: 1 1 0%` -- a zero basis that may shrink.
 * With that, .app-main's natural height is only the topbar plus the footer,
 * the shell settles at exactly 100vh, and any page taller than the viewport
 * overflows past the footer instead of pushing it down. The visible symptom
 * is the footer bisecting a tall column.
 */
.app-content {
  padding: 26px 28px 40px;
  flex: 1 0 auto;
  min-height: 0;
}

.app-footer {
  padding: 16px 28px;
  /*
   * ROOM FOR THE HELP BUTTON (v1036). The support assistant's "?" is
   * fixed at bottom 20px / right 20px and 52px wide, and this row is
   * space-between -- so its LAST item, the build number and the way in
   * to the change log, sat exactly underneath it. David could not click
   * the version and asked where the history had gone; it was there,
   * under a button. 20 + 52 + 20.
   */
  padding-right: 92px;
  border-top: 1px solid var(--app-border);
  display: flex;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  font-size: 13px;
  color: var(--app-muted);
  background: var(--app-surface);
  /* Keeps the footer at the bottom on short pages without pinning it. */
  margin-top: auto;
  flex-shrink: 0;
}

/*
 * ABOVE EVERYTHING, INCLUDING THE MENU.
 *
 * This was z-index 1030 while the mobile sidebar is 1040, so the menu
 * covered the one strip of the page saying "you are signed in as
 * somebody else". Reported as the banner sitting under the menu.
 *
 * 1050 puts it over both the sidebar (1040) and the sticky topbar. The
 * banner is the highest-consequence thing on the screen: everything a
 * person does while it is showing happens on another member's record.
 */
.impersonation-banner {
  background: #b35309;
  color: #fff;
  padding: 9px 18px;
  font-size: 14px;
  position: sticky;
  top: 0;
  z-index: 1050;
}

/*
 * AND THE SIDEBAR STARTS BELOW IT.
 *
 * .app-sidebar is `sticky; top: 0; height: 100vh`, so it pins to the
 * top of the VIEWPORT regardless of the banner sitting above it in the
 * flow -- the two then occupy the same strip and one hides the other.
 *
 * The offset is a variable set on <body> only while impersonating, so
 * the normal case is untouched and there is no magic number to keep in
 * step with the banner's padding.
 */
body.is-impersonating .app-sidebar {
  top: var(--impersonation-h, 0);
  height: calc(100vh - var(--impersonation-h, 0px));
}

/* ==================================================================
   Cards, stats, tables
   ================================================================== */
.p2m-card {
  background: var(--app-surface);
  border: 1px solid var(--app-border);
  border-radius: 12px;
  padding: 22px;
  /*
   * NO percentage height here -- not `height: 100%`, not `min-height: 100%`.
   *
   * A percentage resolves against the parent's height, but inside a
   * Bootstrap .row the column's height comes from its content, which is
   * this card. Each feeds the other, and the browser resolves it by
   * double-counting: a column two cards tall reported exactly twice its
   * own height and painted straight through the footer.
   *
   * Where cards in a row should line up, the markup asks for it with
   * Bootstrap's own .h-100 -- which works because it is applied to the
   * column, not the card.
   */
}

.p2m-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 16px;
}

.p2m-card-head h2,
.p2m-card-head h3 {
  font-size: 16px;
  margin: 0;
  color: var(--heading-color);
  font-family: var(--heading-font);
  font-weight: 600;
}

.stat-card { display: flex; align-items: center; gap: 16px; }

.stat-icon {
  width: 46px;
  height: 46px;
  border-radius: 10px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 21px;
  background: rgba(71, 178, 228, 0.12);
  color: var(--accent-color);
  flex: 0 0 46px;
}

.stat-value {
  font-size: 27px;
  font-weight: 700;
  color: var(--heading-color);
  line-height: 1.1;
  font-family: var(--heading-font);
}

.stat-label { font-size: 13px; color: var(--app-muted); }

.p2m-badge {
  display: inline-block;
  padding: 3px 10px;
  border-radius: 30px;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.2px;
}

.p2m-badge-success { background: rgba(46, 158, 107, 0.13); color: var(--app-success); }
.p2m-badge-warning { background: rgba(217, 154, 43, 0.15); color: var(--app-warning); }
.p2m-badge-danger  { background: rgba(217, 83, 79, 0.13);  color: var(--app-danger); }
.p2m-badge-muted   { background: #eef0f4; color: var(--app-muted); }
.p2m-badge-info    { background: rgba(71, 178, 228, 0.14); color: #2f8fbf; }

table.dataTable thead th {
  background: #f7f9fc;
  color: var(--heading-color);
  font-size: 12.5px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  border-bottom: 1px solid var(--app-border) !important;
}

table.dataTable tbody td { font-size: 14px; vertical-align: middle; }

/* ------------------------------------------------------------------
   DataTables buttons (Columns / Excel / Print)
   ------------------------------------------------------------------
   These sit inside .app-content, where the inherited text color can end
   up white -- giving white-on-white buttons that only become readable on
   hover. Rather than rely on Bootstrap's --bs-btn-* cascade surviving
   that, every color here is stated outright.
   ------------------------------------------------------------------ */
div.dt-buttons {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 12px;
}

div.dt-buttons .btn,
div.dt-buttons button.dt-button,
div.dt-buttons .dt-button {
  font-size: 13px;
  line-height: 1.4;
  border-radius: 6px;
  padding: 6px 12px;
  background: #ffffff !important;
  background-image: none !important;
  border: 1px solid var(--app-border) !important;
  color: var(--heading-color) !important;
  box-shadow: none !important;
  text-shadow: none !important;
  opacity: 1 !important;
}

div.dt-buttons .btn:hover,
div.dt-buttons button.dt-button:hover,
div.dt-buttons .dt-button:hover {
  background: var(--heading-color) !important;
  border-color: var(--heading-color) !important;
  color: #ffffff !important;
}

div.dt-buttons .btn:focus-visible,
div.dt-buttons button.dt-button:focus-visible {
  outline: 2px solid var(--accent-color);
  outline-offset: 2px;
}

div.dt-buttons .btn i,
div.dt-buttons .dt-button i {
  margin-right: 5px;
  color: inherit !important;
}

/* The column-visibility dropdown inherits the same problem. */
div.dt-button-collection {
  background: #ffffff;
  border: 1px solid var(--app-border);
  border-radius: 8px;
  box-shadow: 0 8px 28px rgba(55, 81, 126, 0.12);
  padding: 6px;
}

div.dt-button-collection .dt-button {
  display: block;
  width: 100%;
  text-align: left;
  border: 0 !important;
  background: transparent !important;
  color: var(--heading-color) !important;
}

div.dt-button-collection .dt-button:hover {
  background: #eef4fb !important;
  color: var(--heading-color) !important;
}

div.dt-button-collection .dt-button.active,
div.dt-button-collection .dt-button.dt-button-active {
  background: #eef4fb !important;
  font-weight: 600;
}

div.dt-button-collection .dt-button.active::before,
div.dt-button-collection .dt-button.dt-button-active::before {
  content: "\2713";
  margin-right: 8px;
  color: var(--app-success);
}

.dt-container .form-control, .dt-container .form-select { font-size: 14px; }

.scope-note {
  background: #eef4fb;
  border-left: 3px solid var(--accent-color);
  padding: 10px 14px;
  border-radius: 0 8px 8px 0;
  font-size: 13.5px;
  color: var(--heading-color);
  margin-bottom: 20px;
}

/* ==================================================================
   The renewal offer at checkout

   Tinted so the offer reads as an offer rather than as the fine print
   under the total. Built on --accent-color and the --app-* tokens, NOT
   a colour of its own: this is a multi-tenant platform, and a panel
   painted USA Karate's blue is a bug for the dojo whose accent is
   something else. tier_level shipped NOT NULL DEFAULT 'L1' once for
   exactly this reason.

   WHAT THIS DELIBERATELY DOES NOT DO: look chosen. The unticked state
   is an offer, not an error and not a default -- no red, no warning
   tint, nothing that reads as something left undone. v513 decided the
   box is not pre-ticked because affirmative consent is what makes a
   recurring charge defensible, and styling that implies it was already
   agreed to gives away the same thing the tick was protecting.
   ================================================================== */

.renew-offer {
  background: #eef4fb;
  border: 1px solid var(--app-border);
  border-left: 3px solid var(--accent-color);
  border-radius: 0 8px 8px 0;
  /*
   * PADDING-LEFT IS NOT 14px, and this is the whole reason the rule is
   * written out longhand instead of `padding: 12px 14px`.
   *
   * Bootstrap gives .form-check `padding-left: 1.5em` and floats
   * .form-check-input at `margin-left: -1.5em` -- the input lives in
   * that reserved gutter. Setting padding-left to 14px keeps the gutter
   * width but not the reservation, so the checkbox is dragged 1.5em to
   * the LEFT of the content box and renders outside the border this
   * rule just drew.
   *
   * calc() keeps the gutter AND the inset. Anything that changes this
   * padding has to keep the `+ 1.5em`.
   */
  padding: 12px 14px 12px calc(14px + 1.5em);
  margin-top: 16px;
  transition: background-color .15s ease, border-color .15s ease;
}

.renew-offer .form-check-label { cursor: pointer; }

.renew-offer .renew-terms {
  font-weight: 600;
  color: var(--heading-color);
}

/* The stop-any-time line. Present at the same size as the commitment it
   qualifies -- a cancellation right set smaller than the charge it
   applies to is the shape regulators call buried. */
.renew-offer .renew-out {
  display: block;
  margin-top: 2px;
  color: var(--app-muted);
}

/* ARMED. Confirmation that the server accepted it, which is a real
   question here: the tick is armed by a second request, and a box that
   looks ticked while that request is in flight is the screen claiming
   something Stripe has not agreed to yet. Applied by the success
   handler, never by :checked. */
.renew-offer.is-armed {
  background: #eefaf4;
  border-left-color: var(--app-success);
}

.renew-offer.is-armed .renew-armed-note { display: block; }
.renew-offer .renew-armed-note {
  display: none;
  margin-top: 6px;
  font-size: 12.5px;
  color: var(--app-success);
}

/* In flight. Dimmed rather than spinner'd: it is one request and it is
   usually over before anyone reads this rule. */
.renew-offer.is-working { opacity: .6; }

@media (prefers-reduced-motion: reduce) {
  .renew-offer { transition: none; }
}

.perm-grid { column-count: 2; column-gap: 26px; }
.perm-grid .form-check { break-inside: avoid; }

.empty-state {
  text-align: center;
  padding: 46px 20px;
  color: var(--app-muted);
}

.empty-state i { font-size: 38px; color: #c6cedd; display: block; margin-bottom: 12px; }

/* ==================================================================
   Responsive
   ================================================================== */
@media (max-width: 991.98px) {
  .app-sidebar {
    position: fixed;
    left: -100%;
    z-index: 1040;
    transition: left 0.25s ease;
    box-shadow: 0 0 40px rgba(0, 0, 0, 0.25);
  }
  .app-sidebar.open { left: 0; }
  .app-content { padding: 20px 16px 32px; }
  .app-topbar { padding: 12px 16px; }
  .perm-grid { column-count: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .app-sidebar, .app-nav-link, .app-nav-toggle .bi-chevron-down { transition: none; }
}

/* ==================================================================
   Environment badge -- absent in production
   ================================================================== */
.env-badge {
  display: inline-block;
  margin-left: 10px;
  padding: 2px 9px;
  border-radius: 4px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1px;
  vertical-align: middle;
  font-family: var(--default-font);
}

.env-dev     { background: #2e9e6b; color: #fff; }
.env-staging { background: #d99a2b; color: #fff; }
.env-test    { background: #6c757d; color: #fff; }


.audit-row-impersonated { background: rgba(179, 83, 9, 0.05) !important; }


/* ==================================================================
   Public header legibility
   ------------------------------------------------------------------
   Arsha makes the index header fully transparent with white text, on the
   assumption it always sits over the dark hero. Anything that shifts the
   page down -- a PHP notice printed before <!DOCTYPE>, a cookie bar, an
   admin banner -- puts white text on a white background and the brand
   disappears until you scroll. Give it its own background so legibility
   never depends on what happens to be behind it.
   ================================================================== */
.index-page .header {
  --background-color: rgba(55, 81, 126, 0.92);
  --heading-color: #ffffff;
  --nav-color: #ffffff;
  backdrop-filter: blur(6px);
}

.index-page.scrolled .header {
  --background-color: rgba(40, 58, 90, 0.97);
}

/* The wordmark should never inherit a color that can vanish. */
.header .logo h1.sitename {
  color: #ffffff;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
}

.header .logo h1.sitename .brand-accent { color: var(--accent-color); }


/* ==================================================================
   Shell overflow safety
   ------------------------------------------------------------------
   Belt and braces for the footer-bisecting-a-column bug: whatever the
   flex context does, content inside .app-content establishes its own
   block formatting context and cannot escape the box it is drawn in.
   ================================================================== */
.app-content { display: flow-root; }
.app-content > .row { margin-bottom: 0; }
.app-content::after { content: ""; display: table; clear: both; }


/* Cards asked to match their neighbour do it through the column, so the
   card itself never needs a height of its own. */
.app-content .row > [class*="col-"] { display: flex; flex-direction: column; }
.app-content .row > [class*="col-"] > .p2m-card.h-100 { flex: 1 1 auto; }


/* ==================================================================
   Federation sign-in background
   ------------------------------------------------------------------
   The image and its darkening gradient are set inline, because they come
   from the database. What lives here is everything that must hold true
   whatever image was uploaded: white text, a card that stays legible, and
   a fallback color behind the image so the page never flashes white
   while a large photograph loads.
   ================================================================== */
.auth-wrap-bg { background-color: #0c1220; }

/*
 * The intro text sits ON the photograph, and white-on-photo is only
 * readable where the photo happens to be dark. It never reliably is:
 * a federation uploads a bright dojo shot and three paragraphs vanish.
 *
 * So the text gets its own panel -- dark, translucent, blurred behind --
 * which keeps the image visible while giving every line a guaranteed
 * contrast floor no matter what was uploaded.
 */
/* Kept for any auth page that still carries an intro block. The sign-in
   page no longer does -- copy over a photograph competed with the form and
   obscured the image it was sitting on. */
.auth-wrap-bg .auth-aside {
  background: rgba(10, 16, 28, .62);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  border: 1px solid rgba(255, 255, 255, .12);
  border-radius: 14px;
  padding: 30px 32px;
}
.auth-wrap-bg .auth-aside,
.auth-wrap-bg .auth-aside h1,
.auth-wrap-bg .auth-aside h2,
.auth-wrap-bg .auth-aside p,
.auth-wrap-bg .auth-aside li,
.auth-wrap-bg .auth-aside span,
.auth-wrap-bg .auth-aside strong { color: #fff; }
.auth-wrap-bg .auth-aside p,
.auth-wrap-bg .auth-aside li { color: rgba(255, 255, 255, .92); }
.auth-wrap-bg .auth-aside .bi { color: var(--accent-color, #47b2e4); }

.auth-wrap-bg .auth-card {
  background: #fff;
  box-shadow: 0 18px 50px rgba(0, 0, 0, .45);
}

@media (max-width: 767px) {
  /* On a phone the intro sits above the form and the photo is mostly
     hidden behind both. Tighter padding keeps the form above the fold. */
  .auth-wrap-bg .auth-aside { padding: 20px 22px; }
}


/* ==================================================================
   Collapsible form sections
   ------------------------------------------------------------------
   Native <details>, deliberately. Bootstrap tabs rendered as a stack of
   plain links here because the template restyles .nav-link, and every
   pane showed at once -- worse than the stacked cards they replaced.
   Nothing can override an element the browser draws itself.
   ================================================================== */
.p2m-sections details {
  border-top: 1px solid var(--app-border, #e6e9ef);
}
.p2m-sections details:last-of-type { border-bottom: 1px solid var(--app-border, #e6e9ef); }

.p2m-sections summary {
  cursor: pointer;
  padding: 11px 2px;
  font-weight: 600;
  font-size: 15px;
  color: var(--heading-color, #37517e);
  list-style: none;
  display: flex;
  align-items: center;
  gap: 8px;
  user-select: none;
}
.p2m-sections summary::-webkit-details-marker { display: none; }

/* A chevron that turns, so open and closed are obvious at a glance. */
.p2m-sections summary::before {
  content: '';
  width: 7px; height: 7px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg);
  transition: transform .15s ease;
  margin-left: 3px;
  opacity: .55;
}
.p2m-sections details[open] > summary::before { transform: rotate(45deg); }

.p2m-sections summary:hover { color: var(--accent-color, #47b2e4); }

/* The count of options, when there are any. */
.p2m-sections summary span {
  font-weight: 400;
  font-size: 13px;
  color: var(--text-muted, #6c757d);
}

.p2m-sections details > div { padding-bottom: 14px; }

@media (max-width: 991px) {
  /* On a phone every section starts closed, so the form is a short list
     of headings rather than a page of fields to scroll past. */
  .p2m-sections summary { padding: 13px 2px; }
}


/* ==================================================================
   Figure lists beside a chart
   ------------------------------------------------------------------
   A plain two-column table stretches to its longest label, so a row
   reading "Club Participant" ends up with its number a hand's width
   away while "L3 - Nationally Competitive Athlete Membership" sits
   snug. Fixing the layout and truncating the label keeps every number
   in the same place, close to the text it belongs to.
   ================================================================== */
.p2m-figures {
  width: 100%;
  table-layout: fixed;
  margin-bottom: 0;
}
.p2m-figures td { padding: 4px 0; vertical-align: baseline; border: 0; }

.p2m-figures .fig-label {
  font-size: 13px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;   /* the name gives way, not the number */
}

.p2m-figures .fig-value {
  width: 68px;
  text-align: right;
  font-variant-numeric: tabular-nums;   /* digits line up column-wise */
  font-size: 13px;
  font-weight: 600;
  padding-left: 10px;
}

/* A second small figure, where a row has one (states in a region). */
.p2m-figures .fig-note {
  width: 54px;
  text-align: right;
  font-size: 12px;
  color: var(--text-muted, #6c757d);
  padding-left: 8px;
}

.p2m-figures tr + tr td { border-top: 1px solid var(--app-border, #eef1f5); }

.p2m-figures thead td, .p2m-figures thead th {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-muted, #6c757d);
  font-weight: 600;
  padding-bottom: 6px;
}

/* ==================================================================
   Dashboard analytics sections
   ------------------------------------------------------------------
   The grid experiment is gone; collapsible sections do the job.
   Their styling already lives further up this file -- a drawn chevron
   rather than a text character, so it matches the weight of the
   heading beside it at any size. A second block here overrode it with
   a › and looked like a different application.
   ================================================================== */


/* ==================================================================
   A sidebar collapsed to icons
   ------------------------------------------------------------------
   The labels are hidden, not removed: a screen reader still reads them,
   and hovering shows the label as a tooltip. Icons alone are a memory
   test, and the tooltip is what stops it being one.
   ================================================================== */
:root { --app-sidebar-narrow: 68px; }

.app-sidebar.narrow {
  width: var(--app-sidebar-narrow);
  flex: 0 0 var(--app-sidebar-narrow);
  /*
   * Still scrolls. An earlier attempt let the column overflow so the
   * flyouts could escape, which stopped a long menu scrolling at all.
   * The flyouts are positioned against the VIEWPORT instead, so they
   * are not inside anything that can clip them.
   */
}

/* Anything that is only a word disappears. */
.app-sidebar.narrow .app-nav-link > span,
.app-sidebar.narrow .app-scope,
.app-sidebar.narrow .app-brand .brand-accent + *,
.app-sidebar.narrow .app-nav-toggle .bi-chevron-down {
  display: none;
}

.app-sidebar.narrow .app-brand {
  font-size: 0;                 /* the wordmark goes */
  text-align: center;
  padding: 14px 0;
}
.app-sidebar.narrow .app-brand::before {
  content: 'P2M';
  font-size: 15px;
  font-weight: 700;
  color: #fff;
}

.app-sidebar.narrow .app-nav-link {
  justify-content: center;
  padding: 11px 0;
  position: relative;
}

.app-sidebar.narrow .app-nav-link > .bi:first-child {
  font-size: 18px;
  margin: 0;
}

/*
 * A submenu cannot open downwards in a 68px column, so it opens beside
 * the icon on hover. Collapsing a group to nothing would strand every
 * page underneath it.
 */
.app-sidebar.narrow .collapse,
.app-sidebar.narrow .collapsing { display: none !important; }

.app-sidebar.narrow li { position: relative; }

/*
 * Opened and placed by script, against the viewport.
 *
 * A group near the bottom of the menu has nothing below it to open
 * into, so the script also flips it upwards and keeps it on screen --
 * which is why this cannot be a :hover rule.
 */
.app-sidebar.narrow .collapse.flyout {
  display: block !important;
  position: fixed;
  min-width: 210px;
  max-height: 70vh;
  overflow-y: auto;
  background: var(--app-sidebar-bg, #37517e);
  border-radius: 0 6px 6px 0;
  box-shadow: 0 8px 24px rgba(0,0,0,.28);
  padding: 6px 0;
  z-index: 1200;
}

/* The label as a tooltip, for the items that have no submenu. */
.app-sidebar.narrow .app-nav-link[data-label]:hover::after {
  content: attr(data-label);
  /* Fixed, so a tooltip on the last item is not clipped by the scroll. */
  position: fixed;
  left: calc(var(--app-sidebar-narrow) - 6px);
  margin-top: -13px;
  background: #1d2b45;
  color: #fff;
  font-size: 12px;
  white-space: nowrap;
  padding: 5px 9px;
  border-radius: 4px;
  box-shadow: 0 4px 14px rgba(0,0,0,.25);
  z-index: 1300;
  pointer-events: none;
}

.app-sidebar.narrow .app-collapse-toggle .bi { transform: rotate(180deg); }

/*
 * Below this the sidebar is a drawer that slides away entirely, so
 * collapsing it is a setting with nothing to do.
 */
@media (max-width: 991px) {
  .app-sidebar.narrow {
    width: var(--app-sidebar-width);
    flex: 0 0 var(--app-sidebar-width);
  }
  .app-sidebar.narrow .app-nav-link > span,
  .app-sidebar.narrow .app-scope { display: revert; }
  .app-sidebar.narrow .app-brand { font-size: revert; text-align: revert; }
  .app-sidebar.narrow .app-brand::before { content: none; }
}


/* ==================================================================
   A face beside the page title
   ------------------------------------------------------------------
   Only on pages about one person. Circular because a square photo of a
   face beside a heading reads as an icon; a circle reads as a person.
   ================================================================== */
.app-topbar-avatar {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  overflow: hidden;
  margin-right: 12px;
  border: 2px solid #fff;
  box-shadow: 0 1px 4px rgba(20,40,60,.18);
  display: block;
  cursor: zoom-in;
  transition: transform .12s ease;
}

.app-topbar-avatar:hover { transform: scale(1.06); }

.app-topbar-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;   /* a face, not a squashed rectangle */
  display: block;
}

/* The larger one on the edit form, same behavior. */
.p2m-photo-zoom { cursor: zoom-in; }

/* ------------------------------------------------------------------
   Full size, over the page
   ------------------------------------------------------------------
   A new browser tab would show the raw file on its own, with no way
   back except the back button. An overlay keeps the person where they
   were.
   ------------------------------------------------------------------ */
.p2m-lightbox {
  position: fixed;
  inset: 0;
  background: rgba(12, 20, 32, .88);
  z-index: 2000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  cursor: zoom-out;
}

.p2m-lightbox img {
  max-width: 100%;
  max-height: 100%;
  border-radius: 8px;
  box-shadow: 0 12px 48px rgba(0,0,0,.5);
}

.p2m-lightbox-close {
  position: absolute;
  top: 14px;
  right: 18px;
  color: #fff;
  font-size: 30px;
  line-height: 1;
  background: none;
  border: 0;
  opacity: .75;
}

.p2m-lightbox-close:hover { opacity: 1; }

@media (max-width: 767px) {
  .app-topbar-avatar { width: 36px; height: 36px; margin-right: 8px; }
}


/* ==================================================================
   Select2, wearing the app's clothes
   ------------------------------------------------------------------
   The default skin is its own era. These few rules make a typable
   dropdown sit at the same height and weight as a plain one, so a form
   with both does not look like two forms.
   ================================================================== */
.select2-container--default .select2-selection--single {
  height: calc(1.5em + 0.75rem + 2px);
  border: 1px solid var(--app-border, #ced4da);
  border-radius: 0.375rem;
}

.select2-container--default .select2-selection--single .select2-selection__rendered {
  line-height: calc(1.5em + 0.75rem);
  padding-left: 0.75rem;
  color: var(--default-color, #212529);
}

.select2-container--default .select2-selection--single .select2-selection__arrow {
  height: calc(1.5em + 0.75rem);
}

/* Focus ring matching Bootstrap's, so tabbing through a form is even. */
.select2-container--default.select2-container--focus .select2-selection--single,
.select2-container--default.select2-container--open .select2-selection--single {
  border-color: #86b7fe;
  box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, .25);
}

.select2-container--default .select2-results__option--highlighted[aria-selected] {
  background-color: var(--accent-color, #47b2e4);
}

/* v1091: the MULTI select had no rule here at all, so the tag picker and
   every filter dropdown since v1090 drew with no border -- the field was
   an unmarked strip of pills. Same border, radius and focus ring as the
   single; pills in the app's accent so a chosen value is a chosen value. */
.select2-container--default .select2-selection--multiple {
  min-height: calc(1.5em + 0.75rem + 2px);
  border: 1px solid var(--app-border, #ced4da);
  border-radius: 0.375rem;
  padding: 0.125rem 0.375rem;
  background-color: var(--card-bg, #fff);
}
.select2-container--default .select2-selection--multiple .select2-selection__rendered {
  padding: 0;
  display: block;
}
.select2-container--default .select2-selection--multiple .select2-selection__choice {
  background-color: var(--accent-color, #47b2e4);
  border: 0;
  border-radius: 0.25rem;
  color: #fff;
  font-size: 0.85em;
  margin: 0.125rem 0.25rem 0.125rem 0;
  padding: 0.05rem 0.4rem 0.05rem 0.2rem;
}
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
  color: #fff;
  margin-right: 0.25rem;
  border: 0;
  background: transparent;
}
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover {
  color: #fff;
  background: rgba(0, 0, 0, .15);
}
/* v1179: one class more specific than Select2's own
   `.select2-container .select2-search--inline .select2-search__field`
   (16px, margin-top 5px), which is loaded AFTER this file and was winning --
   so the placeholder in an empty multi-select ("Everybody — or pick a tag"
   on the Diversity page) sat low and was clipped at the bottom. */
.select2-container.select2-container--default .select2-search--inline .select2-search__field {
  margin-top: 0.25rem;
  font-size: 0.875rem;
  line-height: 1.5;
  height: 1.5em;
}
.select2-container--default.select2-container--focus .select2-selection--multiple,
.select2-container--default.select2-container--open .select2-selection--multiple {
  border-color: #86b7fe;
  box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, .25);
}
.select2-container--default .select2-selection--multiple .select2-selection__clear {
  margin-top: 0.15rem;
}

/*
 * REMOVED AT v815, and recorded rather than silently dropped.
 *
 * There was a rule here lifting Select2 above the old dialog library's
 * container, written as a SIBLING selector because that library
 * appended its popup to <body> and Select2's container landed next to
 * it rather than inside it.
 *
 * Every dialog is a Bootstrap .modal now, and app.js sets
 * dropdownParent to the enclosing .modal -- so the dropdown is rendered
 * inside the dialog's own stacking context and there is nothing to lift
 * it above. A rule matching a container that no longer exists is dead
 * weight that reads as load-bearing.
 *
 * The lesson it carried is still true and is kept below: this must stay
 * SCOPED. As a blanket rule it put EVERY Select2 on the page at 2100 --
 * including the check-in screen's, which then sat on top of the
 * sidebar's flyout menu and ate the hover on the way to it. A control
 * in an ordinary form needs no z-index at all.
 */

/* ---------------------------------------------------------------------
   Club tiers -- Bronze, Silver, Gold.

   Metal rather than flat colour, because that is what the words mean and
   a flat brown badge reads as a status warning rather than as a
   distinction. Each is a two-stop gradient with a matching border, which
   is enough to look struck rather than printed without any images.

   Deliberately restrained: this appears on a public directory beside a
   club's name, and a badge that outshines the name it is attached to is
   working against the page. Gold gets a slight glow, the other two do
   not -- the difference between the tiers should be visible at a glance
   without any of them shouting.

   Contrast checked against the pale fills rather than the gradient
   midpoint: silver is the risky one, and #4a4a4a on #f4f4f4 clears AA.
   --------------------------------------------------------------------- */
.club-tier {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 11px;
  border-radius: 999px;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  white-space: nowrap;
  border: 1px solid transparent;
  vertical-align: middle;
}

.club-tier i { font-size: 11px; }

.club-tier-bronze {
  background: linear-gradient(135deg, #f7e2cf 0%, #e6c19a 100%);
  border-color: #c8965f;
  color: #6b3f18;
}

.club-tier-silver {
  background: linear-gradient(135deg, #f6f7f8 0%, #d4d9de 100%);
  border-color: #adb5bd;
  color: #454b52;
}

.club-tier-gold {
  background: linear-gradient(135deg, #fdf3cf 0%, #f0d477 100%);
  border-color: #d4af37;
  color: #6b5310;
  /* The only tier with a glow. Gold is the top of three, and one of them
     being obviously different is the whole point of a ladder. */
  box-shadow: 0 0 0 1px rgba(212, 175, 55, .18), 0 1px 4px rgba(212, 175, 55, .28);
}

/* Dark surfaces: the pale fills vanish, so the badge becomes an outline
   with the metal kept in the text and border. */
@media (prefers-color-scheme: dark) {
  .club-tier-bronze { background: rgba(200, 150, 95, .16);  color: #e0b183; }
  .club-tier-silver { background: rgba(173, 181, 189, .16); color: #d7dce1; }
  .club-tier-gold   { background: rgba(212, 175, 55, .16);  color: #f0d477; }
}

/* ---------------------------------------------------------------------
   Class times on a directory listing.

   A <details> rather than always-open, because a club running fifteen
   classes a week would otherwise push every other club off the screen --
   and somebody scanning a directory is comparing clubs before they are
   reading any one of them.
   --------------------------------------------------------------------- */
.club-schedule summary {
  cursor: pointer;
  color: var(--accent-color, #47b2e4);
  list-style: none;
  user-select: none;
}
.club-schedule summary::-webkit-details-marker { display: none; }
.club-schedule summary::after {
  content: '\25be';
  display: inline-block;
  margin-left: 4px;
  transition: transform .15s ease;
}
.club-schedule[open] summary::after { transform: rotate(180deg); }


/* ------------------------------------------------------------------
   Select2, the parts the block above does not cover.

   That block was already correct -- height, line height and padding all
   matching a plain control. A second block added later duplicated it
   with DIFFERENT numbers, and being later it won, which is what put the
   text high in the field.

   Removed. What follows is only what was genuinely missing.
   ------------------------------------------------------------------ */

/* Full width inside a form column, rather than shrinking to content. */
.select2-container { width: 100% !important; }

/*
 * The clear button.
 *
 * Select2 floats it right, where it lands under the arrow and the two
 * overlap -- an x and a chevron sharing eight pixels, which is what the
 * register's member picker was showing. Placed to the left of the arrow
 * instead, with a target big enough to hit on a tablet at the door.
 */
.select2-container--default .select2-selection--single { position: relative; }

.select2-container--default .select2-selection--single .select2-selection__rendered {
  padding-right: 2.75rem;
}

.select2-container--default .select2-selection--single .select2-selection__clear {
  position: absolute;
  right: 1.75rem;
  top: 50%;
  transform: translateY(-50%);
  margin: 0;
  padding: 0 .25rem;
  font-size: 1.1rem;
  line-height: 1;
  color: #6c757d;
  cursor: pointer;
}
.select2-container--default .select2-selection--single .select2-selection__clear:hover {
  color: #dc3545;
}

/*
 * A dropdown behind a backdrop cannot be clicked, which looks like the
 * control being broken -- but it does not need a global 1200 to avoid
 * that any more. app.js sets dropdownParent to the enclosing .modal, so
 * a dropdown inside a dialog is rendered inside that dialog's stacking
 * context and cannot fall behind it.
 *
 * 1035 keeps it above ordinary page content and BELOW the sidebar
 * (1050). It matters on the check-in screen, where the dropdown is open
 * more or less permanently: at 1200 it covered the flyout menu, which is
 * the same bug as the rule above.
 *
 * A Select2 built by hand inside a modal, without dropdownParent, would
 * need its own rule. There are none today.
 */
.select2-container--open /* ==================================================================
   Toasts -- OPAQUE.

   Reported from the live site: they look bad because you can read the
   page through them.

   Nothing here was overriding Bootstrap, so every toast was inheriting
   its default:

       --bs-toast-bg: rgba(var(--bs-body-bg-rgb), 0.85)

   85% over a white card is nearly invisible as an effect and reads as
   a rendering fault; over the roster's striped table it is worse,
   because the stripes show through the text and the eye tracks the
   ruling instead of the words. The border is worse again --
   --bs-border-color-translucent is rgba(0,0,0,.175), so the EDGE of
   the notice is the thing that fades out first and it stops looking
   like an object at all.

   A notice that interrupts you to say a payment failed has no business
   being decorative. Solid surface, solid border, and a shadow doing
   the work the transparency was pretending to do.

   Set as the Bootstrap variables rather than as background/border
   declarations, so .toast-header and anything else keyed to the same
   tokens follows without a second rule.
   ================================================================== */
.toast {
  --bs-toast-bg: var(--app-surface);
  --bs-toast-border-color: var(--app-border);
  --bs-toast-header-bg: var(--app-surface);
  --bs-toast-header-border-color: var(--app-border);
  --bs-toast-box-shadow: 0 8px 24px rgba(16, 24, 40, .16);

  /*
   * SAID TWICE, ON PURPOSE (v821).
   *
   * The variables above are the tidy way and they were not enough --
   * reported still see-through from the login page after v816 shipped.
   * They rely on three things all holding at once: that Bootstrap is
   * 5.3 or newer (the --bs-toast-* variables do not exist before that,
   * and header.php can fall back to a CDN copy this project does not
   * pin), that app.css wins the cascade against a stylesheet loaded
   * after it, and that --app-surface resolved.
   *
   * A literal background-color needs none of those to be true. The
   * variable line stays because it keeps .toast-header in step; this
   * line is what makes the box solid.
   */
  background-color: #ffffff;
  border-color: var(--app-border, #e5e9f0);
}

/*
 * AND THE ELEMENT ITSELF IS NOT HALF THERE.
 *
 * The other way a toast reads as transparent has nothing to do with its
 * background: `.fade` animates OPACITY, and Bootstrap sets
 * `.toast.showing { opacity: 0 }` on the way in. Anything that leaves
 * `showing` on -- or any other library on the public pages animating
 * opacity, and the Arsha template ships AOS, which does exactly that --
 * makes the whole box translucent, text and all.
 *
 * Stated rather than assumed, because the two causes look identical on
 * a screenshot and I could not tell them apart from one.
 */
.toast.show { opacity: 1; }

/*
 * The timer line reads as part of the notice, not as a shadow under it.
 * It is drawn at opacity .35 of currentColor by app.js; against an
 * opaque background that is a legible grey, which it was not against a
 * transparent one.
 */
.toast .p2m-toast-timer { opacity: .35; }

.select2-dropdown { z-index: 1035; }

.select2-dropdown {
  border-color: var(--app-border, #ced4da);
  border-radius: 0.375rem;
  box-shadow: 0 6px 18px rgba(0, 0, 0, .08);
}

.select2-container--default .select2-results__option--highlighted[aria-selected],
.select2-container--default .select2-results__option--highlighted {
  background-color: var(--accent-color, #47b2e4);
}

.select2-search--dropdown .select2-search__field {
  border: 1px solid var(--app-border, #ced4da);
  border-radius: 0.25rem;
  padding: 0.375rem 0.5rem;
}

/* Inside an input-group, where Select2 would otherwise ignore its
   sibling button. */
.input-group > .select2-container { flex: 1 1 auto; width: 1% !important; }
.input-group > .select2-container .select2-selection--single {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}


/* ---------------------------------------------------------------------
   w-sm-auto -- full width on a phone, natural width from small up.

   Bootstrap 5 has w-100 and w-auto but no responsive variants of either,
   so a button that should fill the row on a phone and sit inline on a
   laptop has no class for it. Written here rather than worked around
   with a wrapper div, because the pattern appears wherever a list row
   has an action on it.
   --------------------------------------------------------------------- */
@media (min-width: 576px) {
  .w-sm-auto { width: auto !important; }
}

/* ==================================================================
   THE OPEN MOBILE MENU HAS TO OUTRANK THE PAGE
   ==================================================================

   #header carries sticky-top (Bootstrap: position: sticky) and
   main.css then sets .header { z-index: 997 }. A positioned element
   with a z-index CREATES A STACKING CONTEXT, so everything inside the
   header is sealed into the 997 slot -- including the open menu, which
   is position: fixed; inset: 0 and looks like it should cover the
   viewport.

   It does cover the viewport. It just does so at 997, and anything on
   the page above that paints straight over the top of it. main.css has
   two: .scroll-top at 99999 and #preloader at 999999. The nav's own
   z-index of 9997 is meaningless outside its own context and reads as
   though it settles the matter, which is why this is easy to stare
   past.

   Fixed by raising the header's context above the page rather than by
   raising the nav inside it -- the nav's number was never the problem.
   ================================================================== */

.header {
  /* Above page content and Bootstrap's own layers, below a modal. */
  z-index: 1055;
}

.scroll-top {
  /* Was 99999, outside the header's context, so a 44px button sat on
     top of a full-screen menu. It belongs under the header. */
  z-index: 990;
}

/* And it must not be clipped or shifted by an ancestor. A transform,
   filter or contain on any ancestor turns position: fixed into
   position: absolute relative to that ancestor -- the menu then opens
   inside the header box and looks like it is hiding behind the page.
   AOS puts transforms on [data-aos] elements, so this is one careless
   attribute away at all times. */
.mobile-nav-active #header,
.mobile-nav-active #header .container,
.mobile-nav-active #header .container-fluid {
  transform: none !important;
  filter: none !important;
}

/* ------------------------------------------------------------------
   THE OPEN MENU, STATED IN FULL RATHER THAN INHERITED

   main.css already says .mobile-nav-active .navmenu { position: fixed;
   inset: 0 } and the browser agrees it applies -- and the box still
   comes out 79px tall, sitting in the header instead of over the page.
   The list inside it is then absolutely positioned at inset 60/20/20/20
   of a 79px box, which is the thin white strip that looks like a
   collapsed menu.

   Rather than keep chasing why the template's rule does not take, every
   property the open menu needs is restated here with enough specificity
   to win outright, and the size is given explicitly instead of being
   left to stretch from inset. An overlay that must work on a phone is
   not the place for a clever shorthand.
   ------------------------------------------------------------------ */
@media (max-width: 1199px) {

  /* ----------------------------------------------------------------
     IT OPENS, RATHER THAN APPEARING -- AND THE BUTTON STAYS PUT

     display: none -> block cannot be animated, so the template's
     transition on the list had nothing to act on: the panel was simply
     there, and what the eye caught was the browser painting it.

     The fix has to work around where this template puts the toggle.
     Arsha normally has the hamburger as a SIBLING of the nav; here it is
     INSIDE it. So hiding the nav when closed -- the obvious way to make
     it transitionable -- takes the hamburger with it, and the menu
     becomes unopenable. It did, in the last build.

     So the nav is always present and always visible, and it is the
     BACKDROP and the PANEL that animate. When closed the nav is a
     transparent, non-interactive sheet, which is why pointer-events
     matters as much as opacity: a full-screen invisible layer that still
     swallowed taps would break every link on the page.
     ---------------------------------------------------------------- */
  nav#navmenu {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: 1100;
    overflow: hidden;

    /* Closed: see-through and untouchable. */
    background: rgba(24, 31, 44, 0);
    pointer-events: none;
    transition: background-color .22s ease;
  }

  body.mobile-nav-active nav#navmenu {
    background: rgba(24, 31, 44, .92);
    pointer-events: auto;
  }

  /* The hamburger is a child of that sheet, so it has to opt back in to
     taking taps -- and it must never inherit the closed state's hiding,
     which is exactly what went wrong before. */
  nav#navmenu .mobile-nav-toggle {
    position: fixed;
    top: 8px;
    right: 8px;
    z-index: 1101;
    pointer-events: auto;
    cursor: pointer;
    font-size: 26px;
    line-height: 1;
    color: var(--nav-color, #fff);

    /*
     * A 44px target with its own size, not the icon's.
     *
     * The button is an <i> whose whole size comes from an icon font. If
     * that font is slow, blocked or missing the element is 0x0 -- still
     * "visible" to CSS, and impossible to tap. Giving it real dimensions
     * means the hamburger works even on the render before the font
     * arrives, and 44px is the smallest target worth offering a thumb.
     */
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
  }

  /*
   * And the wordmark gets out of its way.
   *
   * "PATH2MEMBERSHIP" at full size runs the width of a phone and passed
   * underneath the button, so the tap landed on the logo link and went
   * to the home page instead of opening the menu. Shrunk, and stopped
   * short of the button rather than merely drawn below it.
   */
  .header .logo h1.sitename {
    font-size: 20px;
    max-width: calc(100vw - 132px);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /*
   * And Sign in stops short of the hamburger.
   *
   * The toggle is position: fixed over the top-right corner, so it is
   * out of the flow and nothing reserves room for it -- the pill ran
   * straight underneath, and a tap near the right edge hit whichever
   * happened to be on top. main.css gives this button a 30px left
   * margin and no right margin, which is fine on a desktop where there
   * is no hamburger at all.
   */
  .header .btn-getstarted,
  .header .btn-getstarted:focus {
    font-size: 13px;
    padding: 6px 14px;
    margin: 0 52px 0 8px;
  }

  nav#navmenu > ul {
    display: block;
    position: absolute;
    top: 64px;
    left: 16px;
    right: 16px;
    bottom: 16px;
    height: auto;
    padding: 8px 0;
    margin: 0;
    border-radius: 8px;
    background: var(--nav-mobile-background-color, #fff);
    overflow-y: auto;
    box-shadow: 0 10px 40px rgba(0, 0, 0, .35);

    /* Dropped from behind the header, which is where the eye already is
       after tapping the bar. visibility, not display, so it can move. */
    visibility: hidden;
    opacity: 0;
    transform: translateY(-12px);
    pointer-events: none;
    transition: transform .24s cubic-bezier(.2, .7, .3, 1),
                opacity .2s ease,
                visibility 0s linear .24s;
  }

  body.mobile-nav-active nav#navmenu > ul {
    visibility: visible;
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
    transition: transform .24s cubic-bezier(.2, .7, .3, 1),
                opacity .2s ease,
                visibility 0s;
  }

  /* Somebody who has asked for less motion gets none of it. */
  @media (prefers-reduced-motion: reduce) {
    nav#navmenu,
    nav#navmenu > ul {
      transition: none;
    }
  }

  /* Open, the X sits where the hamburger was and the Sign in pill goes
     away -- the call to action is in the menu anyway, and a button
     under a full-screen overlay is only there to be tapped by mistake. */
  body.mobile-nav-active nav#navmenu .mobile-nav-toggle {
    color: #fff;
  }

  body.mobile-nav-active .btn-getstarted {
    visibility: hidden;
  }

  /* ----------------------------------------------------------------
     THE ACTUAL CAUSE

     .index-page .header sets backdrop-filter: blur(6px), added here to
     keep the brand legible over the hero image.

     backdrop-filter makes an element a CONTAINING BLOCK FOR FIXED
     DESCENDANTS -- the same rule as transform, and far less well known.
     So the open menu, which is position: fixed; inset: 0, was being laid
     out against the header instead of the viewport: 79px tall, sitting
     in the header. Its list, absolutely positioned at inset 60/20/20/20
     of a 79px box, came out as a 16px white strip.

     Every z-index in the stylesheet was irrelevant. Nothing was on top
     of the menu; the menu was the size of the header.

     Removed only while the menu is open. Blurring behind a header whose
     background is already 92% opaque buys almost nothing, and it buys
     nothing at all when a full-screen overlay is covering the page.
     ---------------------------------------------------------------- */
  body.mobile-nav-active .header,
  body.mobile-nav-active.index-page .header {
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;

    /*
     * AND NOT TRANSITIONED THERE.
     *
     * main.css sets `transition: all 0.5s` on the header, so removing
     * the blur ANIMATED it -- and half a second after the tap the
     * computed value was still blur(0.0000000226px). Any non-none
     * backdrop-filter creates the containing block, so for that half
     * second the menu stayed the size of the header and then snapped
     * open when the transition finally landed.
     *
     * That is the delayed flash: not a slow animation, a layout that
     * could not settle until an unrelated transition finished.
     */
    transition: none !important;
  }
}

/* =====================================================================
   PRINT: the application chrome comes off, everywhere.
   =====================================================================

   .app-topbar, NOT .app-header. There is no .app-header in this
   codebase and never has been -- app_header.php renders .app-shell,
   .app-sidebar, .app-topbar, .app-body, .app-main, .app-content. Four
   pages carried a private @media print block hiding ".app-header", so
   the sidebar vanished and the topbar printed at the head of every
   sheet: the page title, the hamburger and the signed-in user's name
   above a grading paper.

   Found by printing one. cards.php and dues_board.php have had it since
   they were written; grading_sheet.php and grading_exam.php inherited
   it by copy.

   Here rather than in each page, because that is how one wrong selector
   became four.
   ===================================================================== */
@media print {
  .app-sidebar,
  .app-topbar,
  .app-sidebar-foot,
  .p2m-sidebar,
  nav,
  footer,
  .d-print-none          { display: none !important; }

  /* The content sits in a flex shell offset by the sidebar. With the
     sidebar gone the offset is a blank left margin on every page. */
  .app-shell,
  .app-body,
  .app-main,
  .app-content           { display: block !important;
                           margin: 0 !important;
                           padding: 0 !important;
                           width: auto !important; }

  .p2m-card              { border: 0 !important;
                           box-shadow: none !important;
                           padding: 0 !important; }

  .container-fluid       { padding: 0 !important; }

  a                      { text-decoration: none !important; color: inherit !important; }
}

/* ==================================================================
   The workflow stepper (my_workflow.php)

   BRANDED WITHOUT ANY PER-FEDERATION CODE. --heading-color and
   --accent-color are written into :root by tenant_theme_style(), so
   these rules pick up USA Karate's colours on their host and WV
   Karate's on theirs. The fallbacks are Arsha's defaults, for platform
   pages that have no tenant.

   NOT A BOOTSTRAP COMPONENT. Bootstrap 5 has no stepper, and the
   nav-pills/progress combinations that get used as one are keyboard
   traps -- a pill that is not a link cannot be tabbed to, and a
   <progress> announces a number rather than a position in a list.
   This is an <ol> of links, which is what it actually is.
   ================================================================== */

.p2m-steps {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  list-style: none;
  margin: 0;
  padding: 0;
  counter-reset: p2m-step;
}

.p2m-step {
  position: relative;
  flex: 1 1 0;
  text-align: center;
  min-width: 0;          /* long phase names must not stretch the row */
}

/* The connecting rail.
   Drawn as a ::before on each step but the first, from the left edge to
   the centre, so it always meets the dot wherever the row wraps or
   however wide the step is. A single absolutely-positioned line across
   the whole row breaks the moment one label is longer than the others. */
.p2m-step + .p2m-step::before {
  content: "";
  position: absolute;
  top: 18px;
  right: 50%;
  left: -50%;
  height: 2px;
  background: var(--app-border, #e5e9f0);
}

/* A completed step pulls its incoming rail with it, so the coloured
   run reads as "how far you have got" at a glance. */
.p2m-step.is-done::before,
.p2m-step.is-current::before { background: var(--app-success, #2e9e6b); }

.p2m-step-link {
  position: relative;
  z-index: 1;
  display: block;
  text-decoration: none;
  color: var(--app-muted, #7a869a);
  padding: 0 .25rem;
}

.p2m-step-dot {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  margin: 0 auto .5rem;
  border-radius: 50%;
  background: #eef0f4;
  border: 2px solid var(--app-border, #e5e9f0);
  color: var(--app-muted, #7a869a);
  font-weight: 700;
  font-size: 15px;
  line-height: 1;
  transition: transform .12s ease, box-shadow .12s ease;
}

.p2m-step-name {
  display: block;
  font-size: 13px;
  line-height: 1.25;
  overflow-wrap: anywhere;
}

/* --- done ------------------------------------------------------- */
.p2m-step.is-done .p2m-step-dot {
  background: var(--app-success, #2e9e6b);
  border-color: var(--app-success, #2e9e6b);
  color: #fff;
}
.p2m-step.is-done .p2m-step-name { color: var(--app-success, #2e9e6b); }

/* --- where you are now ------------------------------------------- */
.p2m-step.is-current .p2m-step-dot {
  background: var(--accent-color, #47b2e4);
  border-color: var(--accent-color, #47b2e4);
  color: #fff;
  box-shadow: 0 0 0 4px rgba(71, 178, 228, .22);
}
.p2m-step.is-current .p2m-step-name {
  color: var(--heading-color, #37517e);
  font-weight: 700;
}

/* --- open but not next, and not open yet -------------------------- */
.p2m-step.is-open .p2m-step-dot {
  border-color: var(--accent-color, #47b2e4);
  color: var(--heading-color, #37517e);
}

/* --- not approved -------------------------------------------------
   Deliberately NOT a filled dot like is-done. A refusal is the end of
   the run, not a step through it, and a solid coloured circle in the
   same visual family as the completed ones reads as progress at a
   glance -- which is the exact misreading this whole feature exists to
   prevent. An outlined red dot stops the eye instead of carrying it
   forward, and the incoming rail is left grey because nothing was
   completed by being refused. */
.p2m-step.is-declined .p2m-step-dot {
  border-color: var(--app-danger, #d9534f);
  color: var(--app-danger, #d9534f);
  background: #fff;
}
.p2m-step.is-declined .p2m-step-name {
  color: var(--app-danger, #d9534f);
  font-weight: 700;
}

.p2m-step-link:hover .p2m-step-dot { transform: translateY(-1px); }
.p2m-step-link:hover .p2m-step-name { text-decoration: underline; }

/* Keyboard focus must be visible on the DOT, not on the whole cell --
   an outline around a flex child that is mostly whitespace reads as a
   box floating next to the control. */
.p2m-step-link:focus-visible { outline: none; }
.p2m-step-link:focus-visible .p2m-step-dot {
  outline: 3px solid var(--accent-color, #47b2e4);
  outline-offset: 2px;
}

.p2m-steps-next { border-top: 1px solid var(--app-border, #e5e9f0); }

/* Narrow screens: the labels are what break first, so they go and the
   dots stay. A row of numbered circles still says "2 of 5"; a row of
   wrapped three-line labels says nothing and is 200px tall. The current
   step keeps its label, because that is the one worth the space. */
@media (max-width: 575.98px) {
  .p2m-step-dot { width: 32px; height: 32px; font-size: 14px; }
  .p2m-step + .p2m-step::before { top: 15px; }
  .p2m-step-name { display: none; }
  .p2m-step.is-current .p2m-step-name { display: block; font-size: 12px; }
}

/* Anchor jumps must not land the card under the sticky header. */
[id^="phase-"] { scroll-margin-top: 84px; }

/* Required fields say so (v1043): a red mark after the label, and a red
   field with "Required" under it when an empty one refused a save. */
.p2m-req { color: var(--bs-danger, #dc3545); margin-left: 0.2em; font-weight: 600; }
.p2m-req-msg { font-size: 0.85em; }

/*
 * "412 Records" above the search box on every data table (v1106).
 * Right-aligned over the search field it describes; semibold so it reads
 * as the answer, not a caption.
 */
.p2m-record-count {
  font-size: .9rem;
  font-weight: 600;
  color: var(--bs-body-color);
  text-align: right;
  white-space: nowrap;
}

/* =====================================================================
 * v1122 -- the International Official path: Step 2's callout points DOWN
 * at the SafeSport card (the first card of #complianceStrip, left
 * column), and the card it points at is ringed. No script positions it:
 * the callout sits directly above the strip, so the arrow's left offset
 * lands on the first card at every width.
 * ===================================================================== */
.p2m-official-callout {
  position: relative;
  background: var(--accent-color, #0d6efd);
  color: #fff;
  border-radius: 10px;
  padding: 12px 16px;
  margin-bottom: 18px !important;
  max-width: 640px;
  box-shadow: 0 6px 18px rgba(0,0,0,.15);
  animation: p2m-official-nudge 1.6s ease-in-out 3;
}
.p2m-official-callout::after {
  content: "";
  position: absolute;
  left: 36px;
  bottom: -14px;
  border-left: 14px solid transparent;
  border-right: 14px solid transparent;
  border-top: 14px solid var(--accent-color, #0d6efd);
}
.p2m-official-callout.no-arrow::after { display: none; }
.p2m-official-target > .p2m-card {
  outline: 3px solid var(--accent-color, #0d6efd);
  outline-offset: 2px;
}
@keyframes p2m-official-nudge {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(4px); }
}
@media (prefers-reduced-motion: reduce) {
  .p2m-official-callout { animation: none; }
}

/* v1124: the member number on a membership card; highlighted ONCE for an
   international official (they need it to register on Tournament in a Box). */
.p2m-member-number { letter-spacing: .03em; color: var(--heading-color); }
.p2m-highlight-once {
  padding: 1px 6px; border-radius: 6px;
  animation: p2m-highlight 1.4s ease-in-out 3;
  background: rgba(13, 110, 253, .12);
  box-shadow: 0 0 0 2px var(--accent-color, #0d6efd);
}
@keyframes p2m-highlight {
  0%, 100% { background: rgba(13, 110, 253, .12); }
  50%      { background: rgba(13, 110, 253, .35); }
}
@media (prefers-reduced-motion: reduce) { .p2m-highlight-once { animation: none; } }

/* v1125: "Setting up your SafeSport training" -- a full-page wait while the
   SafeSport link runs (about ten seconds), so nobody navigates away. */
.p2m-busy {
  position: fixed; inset: 0; z-index: 2000; display: none;
  align-items: center; justify-content: center;
  background: rgba(15, 23, 42, .45);
}
.p2m-busy-box {
  background: var(--surface-color, #fff); color: var(--default-color, #212529);
  border-radius: 12px; padding: 28px 32px; max-width: 360px; margin: 16px;
  text-align: center; box-shadow: 0 10px 30px rgba(0,0,0,.25);
}

/* ==================================================================
   Managed help articles (v1213, inc/kb_docs.php kb_doc_render()).
   Numbered steps with their pictures, figures with captions, call-outs,
   the platform's and the federation's notes -- and a print view that is
   the article alone, pictures never split across a page.
   ================================================================== */
.kb-article h3 { font-size: 1.15rem; margin: 1.4rem 0 .5rem; }
.kb-steps { counter-reset: kbstep; list-style: none; padding-left: 0; }
.kb-steps > li { counter-increment: kbstep; position: relative; padding-left: 2.4rem; margin: .9rem 0; }
.kb-steps > li::before { content: counter(kbstep); position: absolute; left: 0; top: -.05rem; width: 1.75rem; height: 1.75rem;
  border-radius: 50%; background: var(--accent-color, #47b2e4); color: #fff; font-weight: 700; font-size: .9rem;
  display: flex; align-items: center; justify-content: center; }
.kb-fig { margin: .8rem 0 1.1rem; text-align: center; }
.kb-fig img { max-width: 100%; height: auto; border: 1px solid #d6dde7; border-radius: 8px; box-shadow: 0 2px 10px rgba(30,50,80,.08); }
.kb-fig figcaption { font-size: .85rem; color: #66758a; font-style: italic; margin-top: .35rem; }
.kb-callout { border-radius: 8px; padding: .65rem .9rem; margin: .9rem 0; font-size: .95rem; }
.kb-tip  { background: #eaf8f0; border-left: 4px solid #2e9e5b; }
.kb-note { background: #eef4fb; border-left: 4px solid var(--heading-color, #37517e); }
.kb-warn { background: #fdeeee; border-left: 4px solid #c8202e; }
.kb-editorial { border-top: 1px solid #e3e8ef; margin-top: 1.4rem; padding-top: 1rem; }
.kb-fednote { background: #fff8e6; border: 1px solid #f1dfa8; border-radius: 8px; padding: .8rem 1rem; margin-top: 1.2rem; }
.kb-fednote-head { font-weight: 700; font-size: .85rem; text-transform: uppercase; letter-spacing: .04em; color: #8a6d1d; margin-bottom: .3rem; }
.kb-related { margin-top: 1.4rem; }
@media print {
  .article-page #header, .article-page #footer, .article-page .page-title, .article-page .breadcrumbs,
  .article-page .btn, .article-page .cookie-notice { display: none !important; }
  .article-page .section { padding: 0 !important; }
  .article-page .col-lg-8 { width: 100% !important; max-width: 100% !important; flex: 0 0 100% !important; }
  .kb-fig, .kb-steps > li, .kb-callout { break-inside: avoid; }
  .kb-fig img { max-height: 7.5in; box-shadow: none; }
  .kb-steps > li::before { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
}

/* v1214: the help center -- "On this page", "Was this helpful?", the "?" help panel, tiles. */
.kb-toc { border: 1px solid #e3e8ef; border-radius: 8px; padding: .6rem 1rem; margin-bottom: 1rem; background: #fafbfd; }
.kb-toc-head { font-weight: 700; font-size: .8rem; text-transform: uppercase; letter-spacing: .05em; color: #66758a; }
.kb-toc ul { margin: .3rem 0 0; padding-left: 1.1rem; }
.kb-feedback { border-top: 1px solid #e3e8ef; margin-top: 1.4rem; padding-top: 1rem; }
.kb-hero { background: linear-gradient(135deg, var(--heading-color, #37517e), var(--accent-color, #47b2e4)); color: #fff; border-radius: 12px; padding: 1.6rem 1.6rem 1.4rem; margin-bottom: 1.2rem; }
.kb-hero h2 { color: #fff; font-size: 1.5rem; margin: 0 0 .8rem; }
.kb-hero .form-control { border: 0; padding: .7rem 1rem; }
.kb-tile { display: block; border: 1px solid #e3e8ef; border-radius: 10px; padding: .9rem 1rem; height: 100%; text-decoration: none; color: inherit; background: #fff; transition: box-shadow .15s; }
.kb-tile:hover { box-shadow: 0 4px 14px rgba(30,50,80,.10); color: inherit; }
.kb-tile i { font-size: 1.4rem; color: var(--accent-color, #47b2e4); }
.kb-tile .kb-tile-name { font-weight: 600; margin-top: .3rem; }
.kb-help-btn { white-space: nowrap; }

/* v1216: help fitted to the reader's federation -- live sections and the federation picker */
.kb-live { margin: .6rem 0 1rem; }
.kb-live-table { max-width: 640px; }
.kb-live-table th { font-size: .8rem; text-transform: uppercase; letter-spacing: .03em; color: #5d6b80; }
.kb-live-list li { margin: .2rem 0; }
.kb-pickfed { border: 1px solid #d6e2f0; background: #f5f9fd; border-radius: 10px; padding: 1rem 1.2rem; margin: .6rem 0 1.2rem; }
