/* =============================================================================
   RefReflect theme — the design-token layer.

   WHY THIS FILE EXISTS
   --------------------
   Every page in this app is built from the same Tabler component vocabulary
   (~640 `.card`, ~520 `.card-header`, Tabler buttons/tables/badges throughout).
   That means the look of all 650+ templates is decided by Tabler's CSS
   variables — so retheming the app is a matter of redefining those variables
   once, here, rather than editing templates.

   THE RULE: colour, radius, shadow, spacing and type belong to a token.
   Do NOT add a hex colour to a template. If you need a shade that isn't here,
   add it here as a token and use the token. The app previously accumulated 249
   distinct hard-coded hex colours across templates (three different design
   systems' palettes mixed together) — that is what this file exists to end.

   LOAD ORDER: Tabler first, then this file. Overrides below rely on that.

   Light mode only, deliberately. The tokens are structured
   so a dark palette can be added later as a `[data-bs-theme="dark"]` block
   without restating any component rule.
   ============================================================================= */

:root {
  /* ---------------------------------------------------------------------
     BRAND SEEDS — the four colours everything else is derived from.

     These come from AppSettings (see config.models: brand_primary_color and
     friends) and are emitted as :root overrides by
     templates/includes/_brand_theme_vars.html, which loads AFTER this file.
     The values below are the fallbacks — they must stay in step with the
     model defaults, because they are what renders when a page omits that
     include or the settings row is unavailable.

     Four, not forty: the ramp below is computed from them, so a tenant can
     change the brand without being able to produce a scale whose steps
     disagree with each other.
     --------------------------------------------------------------------- */
  /* RefReflect Blue — the default theme (config.models.REFREFLECT_BLUE_THEME).
     Football Australia's green and gold is a saved theme; tenants pick or
     save themes under Settings -> Branding & theme. */
  --rr-brand-primary-seed: #206bc4;       /* fills, navbar, heroes */
  --rr-brand-primary-dark-seed: #1a56a0;  /* gradient ends, hover states */
  --rr-brand-primary-mid-seed: #2f76cc;   /* links and figures on white */
  --rr-brand-accent-seed: #ff922b;        /* highlights and active marks */

  /* ---------------------------------------------------------------------
     PRIMARY RAMP — the product accent, derived from the seeds above.

     The three steps that ARE a seed read it directly, with the literal as a
     `var()` fallback. The in-between tints are literals here and are
     recomputed from the seed inside the `@supports` block further down —
     NOT with a second declaration beside the literal. That shortcut does not
     work for a custom property: its value is not validated at parse time, so
     an unsupported `color-mix()` is stored happily and only collapses when
     `var()` resolves it, taking the property to unset rather than falling
     back to the literal above it. `@supports` gates the whole block instead,
     which is the only form that actually degrades.

     Monotonic light→dark, and note where the seeds land: 500 is the mid
     seed, 600 the primary, 700 the primary-dark. That ordering matters
     because the component rules below address the ramp by number.
     --------------------------------------------------------------------- */
  --rr-primary-50:  #f3f7fc;
  --rr-primary-100: #e6eff9;
  --rr-primary-200: #cddef3;
  --rr-primary-300: #acc8eb;
  --rr-primary-400: #78a6de;
  --rr-primary-500: var(--rr-brand-primary-mid-seed, #2f76cc);
  --rr-primary-600: var(--rr-brand-primary-seed, #206bc4);
  --rr-primary-700: var(--rr-brand-primary-dark-seed, #1a56a0);
  --rr-primary-800: #174c8d;
  --rr-primary-900: #144078;

  /* ---------------------------------------------------------------------
     LEGACY BLUE ALIASES — deprecated, kept so the retheme actually lands.

     ~155 references to `--rr-blue-*` are spread across ~30 templates. Left
     pointing at blue they would have survived the brand change as blue
     islands on green pages; repointing them here retheme them all without a
     sweep, and without a template needing to know the brand moved.

     DO NOT use `--rr-blue-*` in new work — the name now lies about its
     value. Use `--rr-primary-*`. These aliases exist to be deleted once the
     remaining call sites are migrated.
     --------------------------------------------------------------------- */
  --rr-blue-50:  var(--rr-primary-50);
  --rr-blue-100: var(--rr-primary-100);
  --rr-blue-200: var(--rr-primary-200);
  --rr-blue-300: var(--rr-primary-300);
  --rr-blue-400: var(--rr-primary-400);
  --rr-blue-500: var(--rr-primary-500);
  --rr-blue-600: var(--rr-primary-600);
  --rr-blue-700: var(--rr-primary-700);
  --rr-blue-800: var(--rr-primary-800);
  --rr-blue-900: var(--rr-primary-900);

  /* ---------------------------------------------------------------------
     ACCENT — the gold. It is a colour for DARK surfaces: at 4.5:1 the raw
     seed fails against white, so text-on-white uses `--rr-accent-ink` and
     the seed itself is reserved for fills, marks and type on the primary.
     --------------------------------------------------------------------- */
  --rr-accent: var(--rr-brand-accent-seed, #ff922b);
  /* Darkened enough to read as text on white. */
  --rr-accent-ink: #8c5018;
  /* The soft fill behind accent badges ("47 awaiting"). */
  --rr-accent-soft: #ffeedd;
  --rr-accent-border: #ffcea0;
  /* Text and icons sitting ON gold — the deep brand green, not black. */
  --rr-on-accent: #123c70;
  /* Text sitting ON any primary surface. */
  --rr-on-primary: #ffffff;
  /* Accent-coloured TEXT on a primary surface (the active menu item, a
     hero's eyebrow). The accent itself only where it holds 4.5:1 there;
     otherwise the primary's ink. The default brand's orange on blue is
     2.4:1, so this default is the ink. The tenant's value is computed in
     core/branding_colours.accent_text and emitted by
     includes/_brand_theme_vars.html. */
  --rr-accent-text: var(--rr-on-primary);

  /* PANEL HEADER — the full-bleed hero block on the finance and performance
     pages (`.appointment-info`, `.report-header` and friends), and the match
     and next-match heroes on the redesigned dashboards.

     Was a violet gradient, the app's one departure from the brand ramp. The
     redesign carries the brand green through every hero, and a violet panel
     in the middle of it read as a leftover from the old theme.
     It is a gradient rather than a flat fill for the same reason as before —
     a flat fill lost the depth these panels had. All 26 sites point at this
     token, so the treatment is still a one-line change here. */
  --rr-panel-header-bg: linear-gradient(135deg,
      var(--rr-brand-primary-seed, #206bc4) 0%,
      var(--rr-brand-primary-dark-seed, #1a56a0) 100%);

  /* ---------------------------------------------------------------------
     NEUTRALS — one cool-grey ramp. Replaces the Bootstrap-4 greys
     (#6c757d, #dee2e6, #e9ecef), the Tailwind greys (#6b7280, #374151,
     #1f2937) and the ad-hoc ones (#e0e0e0, #fefefe) that were all in use
     on different pages at the same time.
     --------------------------------------------------------------------- */
  --rr-grey-25:  #fbfcfd;
  --rr-grey-50:  #f7f8fa;
  --rr-grey-100: #eff1f5;
  --rr-grey-200: #e4e8ee;
  --rr-grey-300: #cfd6e0;
  --rr-grey-400: #9aa5b5;
  --rr-grey-500: #6b7889;
  --rr-grey-600: #52606f;
  --rr-grey-700: #3d4855;
  --rr-grey-800: #2a323c;
  --rr-grey-900: #182433;

  /* ---------------------------------------------------------------------
     SEMANTIC STATUS — muted, modern versions of the traffic-light colours.
     The old #28a745 / #dc3545 / #ffc107 (Bootstrap 4) read as loud and dated
     next to everything else; these keep the same meanings at lower chroma.
     `warning` doubles as the "outstanding / needs attention" colour, matching
     how amber is already used in the dashboards.
     --------------------------------------------------------------------- */
  --rr-success: #1a7f56;
  --rr-success-bg: #e8f6f0;
  /* Completes the -bg/-border set. Warning and danger already had a hairline
     distinct from their fill (a border mapped onto -bg is invisible against
     its own box); success did not, so `bg-green-lt` had no border to take. */
  --rr-success-border: #b8ddcb;
  /* The LIGHT end of a success gradient. Several cards run
     `linear-gradient(--rr-success, <a mint literal>)`; without a lighter step
     to point the second stop at, sweeping that literal onto --rr-success
     makes the gradient flat. */
  --rr-success-light: #2fa678;
  --rr-warning: #b5730c;
  --rr-warning-bg: #fdf4e3;
  /* The hairline that pairs with the amber fill. Distinct on purpose: the fill
     and the border are two different jobs, and mapping both onto -bg (which the
     first colour sweep did) leaves a border that is invisible against its own
     box. */
  --rr-warning-border: #f0d9ab;
  --rr-danger:  #c8352f;
  --rr-danger-bg: #fdecea;
  --rr-danger-border: #f5c4c1;
  /* One rung between danger and warning, for the 5-point performance ramp —
     red / orange / amber / teal / green. Without it steps 2 and 3 of that
     scale are the same colour and the scale reads as 4 steps. */
  --rr-orange: #c0561f;
  /* Orange's own fill and hairline, so `bg-orange-lt` has somewhere to point
     that is not warning's amber — the whole reason --rr-orange exists. */
  --rr-orange-bg: #fbeee7;
  --rr-orange-border: #f2d6c6;
  /* INFO stays blue, and is now its OWN colour rather than an alias of the
     product accent.

     It used to be `--rr-blue-600` — the same value as the primary — which was
     harmless while the brand was blue. With a green brand that alias would
     have made every info alert and badge green, i.e. indistinguishable from
     success, and `--rr-info` is a semantic status colour whose whole job is to
     read as "not success, not a warning". Hence a literal: this one is
     deliberately NOT derived from the brand seeds, and should not be. */
  --rr-info:     #1f6fb2;
  --rr-info-bg:  #eef5fb;
  --rr-info-ink: #17558a;
  --rr-info-border: #c3dcf0;

  /* ---------------------------------------------------------------------
     ELEVATION — layered, low-opacity shadows. Tabler's default single
     `0 .5rem 1rem rgba(0,0,0,.15)` is the harsh drop-shadow that dates the
     UI most visibly; real depth comes from a tight contact shadow plus a
     wider ambient one.
     --------------------------------------------------------------------- */
  --rr-shadow-xs: 0 1px 2px rgba(24, 36, 51, 0.05);
  --rr-shadow-sm: 0 1px 2px rgba(24, 36, 51, 0.06), 0 1px 3px rgba(24, 36, 51, 0.04);
  --rr-shadow-md: 0 2px 4px rgba(24, 36, 51, 0.05), 0 4px 12px rgba(24, 36, 51, 0.07);
  --rr-shadow-lg: 0 4px 8px rgba(24, 36, 51, 0.05), 0 12px 28px rgba(24, 36, 51, 0.10);
  /* The focus ring. A literal rgba() would pin it to the retired blue and
     leave every focused control ringed in a colour the brand no longer uses.
     Mixed from the primary in the @supports block below; this value is the
     default brand's, so the ring stays visible — the part that actually
     matters for accessibility — either way. */
  --rr-ring: 0 0 0 3px rgba(32, 107, 196, 0.22);

  /* RADIUS — the single loudest "modern vs 2019" signal. Tabler ships 4px. */
  --rr-radius-sm: 6px;
  --rr-radius:    8px;
  --rr-radius-lg: 12px;
  --rr-radius-xl: 16px;

  /* ---------------------------------------------------------------------
     ORGANISATION BRAND COLOURS

     These are Football Australia's own palette, used on the media and analytics
     pages for charts and hero panels — NOT the product UI colours above. They
     were hardcoded across five templates; centralised here so they can be
     changed in one place.

     Note for white-labelling: the app resolves names and logos through
     `AppSettings.organization_name` / `ORGANIZATION_LOGO_URL`, but these colours
     are still fixed. Making them configurable is a separate change; this at
     least puts them in one place to change rather than five.
     --------------------------------------------------------------------- */
  --rr-brand-green: #00843D;
  --rr-brand-green-dark: #006430;
  --rr-brand-gold: #FFD100;
  --rr-brand-navy: #002B5C;

  /* ---------------------------------------------------------------------
     CATEGORICAL SERIES SCALE — for chart series, and ONLY for chart series.

     A categorical scale has one job the semantic tokens cannot do: every
     entry must be told apart from every other entry, including by a reader
     with colour-vision deficiency and including at the 2px width of a line
     on a trend chart. That is why this is a scale of DIFFERENT HUES rather
     than steps of the brand ramp — ten shades of green is not a legend.

     Ordering matters. The first four carry the most series on the most-read
     charts, so they lead with the brand and then take the widest hue and
     lightness separations available; the tail is for charts with a series
     per official, where being distinguishable at all is the whole
     requirement. They are picked from the same families the semantic tokens
     use, so a chart sits beside a status badge without arguing with it.

     HOW TO USE THEM. Canvas has NO CSS cascade: Chart.js hands
     borderColor/backgroundColor straight to the 2D context, and a raw
     `var(--rr-…)` string there is silently ignored — the slice paints black.
     Read them through the `rrToken()` helper in the base templates, which
     resolves the computed value:

         borderColor: rrToken('--rr-series-2')

     Do not put these on an element's CSS; the semantic tokens are for that.
     --------------------------------------------------------------------- */
  --rr-series-1: #0d6a49;   /* brand green   */
  --rr-series-2: #d99000;   /* gold, darkened to read on white */
  --rr-series-3: #1f6fb2;   /* info blue     */
  --rr-series-4: #c8352f;   /* danger red    */
  --rr-series-5: #7048e8;   /* violet        */
  --rr-series-6: #0ca678;   /* mint          */
  --rr-series-7: #e8590c;   /* orange        */
  --rr-series-8: #0b7285;   /* deep teal     */
  --rr-series-9: #ae3ec9;   /* magenta       */
  --rr-series-10: #495057;  /* graphite      */

  /* SPACING RHYTHM */
  --rr-space-1: 0.25rem;
  --rr-space-2: 0.5rem;
  --rr-space-3: 0.75rem;
  --rr-space-4: 1rem;
  --rr-space-5: 1.5rem;
  --rr-space-6: 2rem;

  /* =====================================================================
     TABLER VARIABLE MAPPING
     Everything above is wired into Tabler here. This is the part that
     retheme every template at once.
     ===================================================================== */
  --tblr-primary: var(--rr-primary-600);
  /* Tabler's <code>/<pre> and any page rule reading its variable take the
     design's mono face. */
  --tblr-font-monospace: var(--rr-font-mono);
  /* Tabler builds rgba() shadows and tints from the channel triplet, and it
     cannot be derived from a custom property — a `rgb(var(--x))` there would
     need the components, not a hex. This is the DEFAULT primary's channels;
     includes/_brand_theme_vars.html overrides it with the tenant's, computed
     server-side (core/branding_colours.rgb_triplet). */
  --tblr-primary-rgb: 32, 107, 196;
  --tblr-blue: var(--rr-primary-600);
  /* Links take the MID seed, not 700. The ramp is monotonic, so 700 is darker
     than the 600 used for fills — on this brand that is a near-black green
     that reads as body copy rather than as a link. The mid seed is the
     design's link colour and the one step that holds 4.5:1 on white while
     still looking like an accent. */
  --tblr-link-color: var(--rr-primary-500);
  --tblr-link-hover-color: var(--rr-primary-700);

  --tblr-body-bg: var(--rr-grey-50);
  --tblr-body-color: var(--rr-grey-900);
  --tblr-bg-surface: #ffffff;
  --tblr-bg-surface-secondary: var(--rr-grey-50);
  --tblr-bg-surface-tertiary: var(--rr-grey-100);
  --tblr-border-color: var(--rr-grey-200);
  --tblr-border-color-translucent: var(--rr-grey-200);
  --tblr-secondary: var(--rr-grey-500);
  --tblr-muted: var(--rr-grey-500);

  --tblr-success: var(--rr-success);
  --tblr-warning: var(--rr-warning);
  --tblr-danger: var(--rr-danger);
  --tblr-info: var(--rr-info);

  --tblr-border-radius: var(--rr-radius);
  --tblr-border-radius-sm: var(--rr-radius-sm);
  --tblr-border-radius-lg: var(--rr-radius-lg);
  --tblr-border-radius-xl: var(--rr-radius-xl);

  --tblr-box-shadow: var(--rr-shadow-md);
  --tblr-box-shadow-sm: var(--rr-shadow-sm);
  --tblr-box-shadow-lg: var(--rr-shadow-lg);

  /* TYPE — 0.875rem base was cramped; 0.9375rem with a looser leading reads
     as considered rather than dense, without reflowing any layout. */
  --tblr-body-font-size: 0.9375rem;
  --tblr-body-line-height: 1.55;
  /* Tabler sets h5 at 12px and h6 at 10px — below body text. The templates
     were written against Bootstrap, where h6 is 16px, and use h5/h6 as the
     title of a list item or a form section; at Tabler's sizes a match name
     rendered smaller than the date line under it. Keep them under the 16px
     h3/card title, and above body-small. */
  --tblr-font-size-h4: 0.9375rem;
  --tblr-font-size-h5: 0.875rem;
  --tblr-font-size-h6: 0.8125rem;
  --tblr-font-sans-serif: 'InterVariable', 'Inter var', 'Inter', -apple-system,
    BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;

  /* DISPLAY + MONO — the two faces the redesign adds alongside Inter.
     Archivo carries big headings and KPI numerals; JetBrains Mono the
     figures that are compared down a column. Applied through `.rr-display`
     and `.rr-mono` — do not name either family in a template. Both fall back
     to the sans stack, so a page that loads without the webfonts (they come
     from Google Fonts in the base templates) still sets in Inter rather than
     in a default serif. */
  --rr-font-display: 'Archivo', var(--tblr-font-sans-serif);
  --rr-font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo,
    Consolas, monospace;
}

/* =============================================================================
   DERIVED TINTS — recomputed from the brand seeds where `color-mix()` exists.

   Gated on @supports rather than declared next to the literals in :root,
   because a custom property accepts any value at parse time: an unsupported
   `color-mix()` would be stored, then collapse to unset when `var()` resolved
   it, rather than falling back to the literal. This block is the only form
   that degrades cleanly — without it the tints stay at the default theme's
   values, which is a correct-looking page rather than a broken one.

   The seeds are overridden by a later <style> block (the AppSettings
   include), and that is fine: `var()` resolves after the whole cascade, so
   these mixes pick up the tenant's values, not the fallbacks above.
   ============================================================================= */
@supports (color: color-mix(in srgb, red 50%, blue)) {
  :root {
    --rr-primary-50:  color-mix(in srgb, var(--rr-brand-primary-mid-seed) 6%,  #ffffff);
    --rr-primary-100: color-mix(in srgb, var(--rr-brand-primary-mid-seed) 12%, #ffffff);
    --rr-primary-200: color-mix(in srgb, var(--rr-brand-primary-mid-seed) 24%, #ffffff);
    --rr-primary-300: color-mix(in srgb, var(--rr-brand-primary-mid-seed) 40%, #ffffff);
    --rr-primary-400: color-mix(in srgb, var(--rr-brand-primary-mid-seed) 65%, #ffffff);
    --rr-primary-800: color-mix(in srgb, var(--rr-brand-primary-dark-seed) 88%, #000000);
    --rr-primary-900: color-mix(in srgb, var(--rr-brand-primary-dark-seed) 75%, #000000);

    --rr-accent-ink:    color-mix(in srgb, var(--rr-brand-accent-seed) 55%, #000000);
    --rr-accent-soft:   color-mix(in srgb, var(--rr-brand-accent-seed) 16%, #ffffff);
    --rr-accent-border: color-mix(in srgb, var(--rr-brand-accent-seed) 45%, #ffffff);
    /* From the DARK seed: a mid-bright primary (RefReflect's blue) darkened
       8% was ~2.7:1 on its accent. 70% of the dark seed holds 4.5:1+ on a
       light accent for FA's green and RefReflect's blue alike. */
    --rr-on-accent:     color-mix(in srgb, var(--rr-brand-primary-dark-seed) 70%, #000000);

    --rr-ring: 0 0 0 3px color-mix(in srgb, var(--rr-brand-primary-seed) 22%, transparent);
  }
}

/* =============================================================================
   BASE
   ============================================================================= */
body {
  font-family: var(--tblr-font-sans-serif);
  font-size: var(--tblr-body-font-size);
  line-height: var(--tblr-body-line-height);
  color: var(--rr-grey-900);
  background-color: var(--rr-grey-50);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Optical sizing + tighter tracking on headings is most of what makes type
   look "designed" rather than default. */
/* Deliberately no `color` here. Headings already inherit --rr-grey-900 from
   `body`, so setting it again buys nothing on an ordinary page — and it breaks
   every heading sitting inside a coloured surface. The panel headers set
   `color: white` on the block; a forced dark heading here won that cascade and
   rendered the title in near-black on the violet gradient while the paragraph
   beside it stayed white. Let headings inherit their surface's colour. */
h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6,
.page-title, .card-title {
  font-weight: 600;
  letter-spacing: -0.011em;
}

.page-title {
  font-size: 1.375rem;
  letter-spacing: -0.018em;
}

/* Tabular figures everywhere numbers are compared down a column — money,
   counts, dates in tables. Proportional digits make columns look ragged. */
.table td, .table th,
.text-tabular, .card-title .h1, .h1.mb-3 {
  font-variant-numeric: tabular-nums;
}

/* Tabler colours a link from `--tblr-link-color-rgb`, an "r, g, b" triple,
   not from `--tblr-link-color` — so setting only the latter (above) left every
   plain link in the app Tabler blue. The brand seeds are hex, set per tenant,
   and CSS cannot turn a hex variable into a triple; so colour links from the
   hex variable directly. Hover mirrors Tabler's own rule: it swaps the
   VARIABLE, so a link that takes its colour from a class (a button, a nav
   link, a white link on the navbar) keeps that colour on hover. */
a {
  color: var(--tblr-link-color);
  text-decoration: none;
  transition: color 0.12s ease;
}
a:hover {
  --tblr-link-color: var(--tblr-link-hover-color);
}

/* Tabler also hard-codes its default blue (#206bc4) in a few rules that come
   AFTER its variable-driven ones, so no variable reaches them: a
   `.border-primary` card, every `.btn-link`, the active list-group item's
   rule and the loader all stayed blue on a green brand. */
.border-primary { border-color: var(--rr-primary-600) !important; }
.btn-link { color: var(--tblr-link-color); }
.list-group-item.active { border-left-color: var(--rr-primary-600); }
.loader { color: var(--rr-primary-600); }

/* =============================================================================
   FOCUS — one visible, consistent ring. Accessibility and polish at once.
   ============================================================================= */
:focus-visible,
.btn:focus-visible,
.form-control:focus,
.form-select:focus,
.form-check-input:focus,
.rr-check-grid input[type="checkbox"]:focus {
  outline: none;
  box-shadow: var(--rr-ring);
  border-color: var(--rr-primary-400);
}

/* =============================================================================
   CHECK GRID — a table of hundreds of rows of bare checkboxes (the panels
   matrix, the official roles grid). `.rr-check-grid` on the <table> gives every
   checkbox in it Tabler's `.form-check-input` look, so no cell repeats the
   class. These are that class's final declarations for a checkbox, Tabler's
   later overrides applied (background-size, margin-top, a ticked box's
   `border: none`); focus is the ring above. A page's own margin rule for its
   grid's boxes still wins: it comes after this file.
   ============================================================================= */
.rr-check-grid input[type="checkbox"] {
  --tblr-form-check-bg: var(--tblr-bg-forms);
  width: 1rem;
  height: 1rem;
  margin-top: 0.125rem;
  vertical-align: top;
  background-color: var(--tblr-form-check-bg);
  background-image: var(--tblr-form-check-bg-image);
  background-repeat: no-repeat;
  background-position: center;
  background-size: 1rem;
  border: var(--tblr-border-width) var(--tblr-border-style) var(--tblr-border-color-translucent);
  border-radius: var(--tblr-border-radius);
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  -webkit-print-color-adjust: exact;
  color-adjust: exact;
  print-color-adjust: exact;
}
.rr-check-grid input[type="checkbox"]:active {
  filter: brightness(90%);
}
.rr-check-grid input[type="checkbox"]:checked {
  --tblr-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' width='16' height='16'%3e%3cpath fill='none' stroke='%23ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8.5l2.5 2.5l5.5 -5.5'/%3e%3c/svg%3e");
  background-color: var(--tblr-primary);
  border: none;
}

/* =============================================================================
   CARDS — hairline border, minimal shadow. The flat-with-a-hairline card is
   the current idiom; a heavy drop shadow on every card is what made the app
   read as an old Bootstrap admin.
   ============================================================================= */
.card {
  border: 1px solid var(--rr-grey-200);
  border-radius: var(--rr-radius-lg);
  box-shadow: var(--rr-shadow-xs);
  background-color: #ffffff;
}

.card-header {
  background-color: transparent;
  border-bottom: 1px solid var(--rr-grey-100);
  padding: var(--rr-space-4) var(--rr-space-5);
  min-height: auto;
}

.card-title {
  font-size: 0.9375rem;
  font-weight: 600;
  margin-bottom: 0;
}

.card-body {
  padding: var(--rr-space-5);
}

.card-footer {
  background-color: var(--rr-grey-25);
  border-top: 1px solid var(--rr-grey-100);
}

/* Interactive cards (dashboard tiles, tool links) get a real hover state
   rather than sitting inert. */
.card-link-hover,
a.card,
.card.card-link {
  transition: box-shadow 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
}
a.card:hover,
.card.card-link:hover,
.card-link-hover:hover {
  box-shadow: var(--rr-shadow-md);
  border-color: var(--rr-grey-300);
  transform: translateY(-1px);
}

/* =============================================================================
   BUTTONS
   ============================================================================= */
.btn {
  border-radius: var(--rr-radius-sm);
  font-weight: 500;
  padding: 0.4375rem 0.875rem;
  transition: background-color 0.12s ease, border-color 0.12s ease,
              box-shadow 0.12s ease, color 0.12s ease;
}

.btn-sm {
  border-radius: var(--rr-radius-sm);
  padding: 0.3125rem 0.625rem;
  font-size: 0.8125rem;
}

.btn-lg { border-radius: var(--rr-radius); padding: 0.625rem 1.25rem; }

.btn-primary {
  background-color: var(--rr-primary-600);
  border-color: var(--rr-primary-600);
  box-shadow: var(--rr-shadow-xs);
}
.btn-primary:hover,
.btn-primary:focus {
  background-color: var(--rr-primary-700);
  border-color: var(--rr-primary-700);
}

.btn-outline-primary { color: var(--rr-primary-700); border-color: var(--rr-primary-300); }
.btn-outline-primary:hover { background-color: var(--rr-primary-600); border-color: var(--rr-primary-600); }

/* The most-used button in the app (239 uses) — it was reading as a disabled
   grey box. Now a proper neutral secondary. */
.btn-outline-secondary {
  color: var(--rr-grey-700);
  border-color: var(--rr-grey-300);
  background-color: #ffffff;
}
.btn-outline-secondary:hover {
  background-color: var(--rr-grey-100);
  border-color: var(--rr-grey-400);
  color: var(--rr-grey-900);
}

.btn-success { background-color: var(--rr-success); border-color: var(--rr-success); }
.btn-danger  { background-color: var(--rr-danger);  border-color: var(--rr-danger); }
.btn-warning { background-color: var(--rr-warning); border-color: var(--rr-warning); color: #fff; }

/* =============================================================================
   FORMS
   ============================================================================= */
.form-control, .form-select {
  border-radius: var(--rr-radius-sm);
  border-color: var(--rr-grey-300);
  padding: 0.4375rem 0.75rem;
  font-size: 0.9375rem;
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
/* The shorthand above also took away the right padding Tabler leaves for a
   select's chevron, so a long option ran underneath the arrow. */
.form-select { padding-right: 2.25rem; }
/* Tabler's small sizes have the same specificity as the rule above, which
   loads later, so every -sm control rendered at full size. (Phones keep the
   16px / 44px floors further down: those are !important and media-scoped.) */
.form-control-sm, .form-select-sm {
  padding-top: 0.25rem; padding-bottom: 0.25rem; padding-left: 0.5rem;
  font-size: 0.8125rem;
}
.form-control-sm { padding-right: 0.5rem; }
.form-select-sm { padding-right: 2rem; }
.form-control::placeholder { color: var(--rr-grey-400); }
.form-label {
  font-weight: 500;
  font-size: 0.8125rem;
  color: var(--rr-grey-700);
  margin-bottom: 0.3125rem;
}
.form-hint, .form-text { color: var(--rr-grey-500); font-size: 0.8125rem; }

/* -----------------------------------------------------------------------------
   PHANTOM FORM CLASSES

   `match_reports/forms.py` styles its widgets with `form-input` (28 uses),
   `form-checkbox` (5) and `form-textarea` (3). None of those are Tabler or
   Bootstrap classes, and none are defined anywhere in the project — so 36 form
   controls, all of them on the match report and incident report forms, render
   completely unstyled: no width, no border, no font size.

   On a phone that is not cosmetic. An unstyled <textarea> falls back to
   `cols`-based sizing and runs past the viewport: this is what put the incident
   report's `incident_description` field off-screen (x29..399 in a 390px window)
   and gave that page 9px of horizontal scroll.

   Aliased here rather than renamed in forms.py: one change fixes all 36, costs
   no Python, and still works if the names come back. Nothing in the templates,
   CSS or JS selects on them, so there is nothing to break.
   ----------------------------------------------------------------------------- */
.form-input,
.form-textarea {
  display: block;
  width: 100%;
  max-width: 100%;
  padding: 0.4375rem 0.75rem;
  font-size: 0.9375rem;
  line-height: var(--tblr-body-line-height);
  color: var(--rr-grey-900);
  background-color: #ffffff;
  border: 1px solid var(--rr-grey-300);
  border-radius: var(--rr-radius-sm);
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.form-input::placeholder,
.form-textarea::placeholder { color: var(--rr-grey-400); }
.form-input:focus,
.form-textarea:focus {
  outline: none;
  border-color: var(--rr-primary-400);
  box-shadow: var(--rr-ring);
}

.form-checkbox {
  width: 1.0625rem;
  height: 1.0625rem;
  margin-right: 0.375rem;
  border: 1px solid var(--rr-grey-300);
  border-radius: 3px;
  vertical-align: middle;
  accent-color: var(--rr-primary-600);
}

/* Structural backstop: NO form control may exceed its container, whatever class
   it carries — or doesn't.

   Every rule above is class-based, so a control with no class at all gets none
   of them and falls back to browser-default sizing. That is a real overflow
   source, not a hypothetical: a bare `<select>` on Bulk Reschedule measured
   485px inside a 390px viewport, and an unclassed `<input>` did the same on
   Merit List Create. `cols` and `size` are character counts, not widths.

   Checkboxes, radios and range sliders are excluded — they are fixed-size
   controls and capping them does nothing useful. */
textarea,
select,
input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]):not([type="range"]) {
  max-width: 100%;
}

/* -----------------------------------------------------------------------------
   SCROLL AFFORDANCE

   A horizontally scrollable table looks identical to one that simply ends. That
   is the whole reason hidden action columns read as missing rather than as
   "swipe for more" — and it is the remaining cost on Smart Appointments, which
   is ~99% a desktop surface but should still be readable on a phone.

   `responsive-layout.js` adds `.is-scrollable` to any scroller with content off
   its right edge, and removes it once you reach the end. The fade is drawn on
   the container, not the table, so it never moves with the content.
   ----------------------------------------------------------------------------- */
.table-responsive.is-scrollable,
.planner-grid.is-scrollable {
  position: relative;
}
.table-responsive.is-scrollable::after,
.planner-grid.is-scrollable::after {
  content: '';
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 40px;
  pointer-events: none;
  background: linear-gradient(to right, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.92));
}

/* -----------------------------------------------------------------------------
   SCROLL RAIL

   A scroll box as tall as its table draws its sideways scroll bar under the last
   row, which can be several screens down. `data-scroll-rail` on the box (opt-in;
   static/core/js/scroll_rail.js) adds a second bar right after it that sticks to
   the bottom of the window while the table is in view, and moves with the box.

   Desktop only. The box's own bar is hidden only while the rail shows
   (`.rr-rail-on`, set by the script in the same step), so if the script does
   not run the box keeps its native bar.

   Tabler sets `scrollbar-color` on every element (`*`) to a 16%-alpha thumb on a
   transparent track. Chromium ignores `::-webkit-scrollbar` on any element whose
   scrollbar-color or scrollbar-width is not `auto`, so the rail puts both back to
   `auto` first; otherwise it would get the same faint bar it exists to replace.
   Firefox has no `::-webkit-scrollbar` and takes `scrollbar-color` instead.

   Stacking: above the grid's frozen cells (z-index 2), below pop-overs such as
   the crew picker (1050), the fixed sidebar and modals.
   ----------------------------------------------------------------------------- */
.rr-scroll-rail {
  position: sticky;
  bottom: 0;
  z-index: 5;
  overflow-x: auto;
  overflow-y: hidden;
  min-height: 14px;
  background: var(--tblr-bg-surface);
  border-top: 1px solid var(--tblr-border-color);
  scrollbar-width: auto;
  scrollbar-color: auto;
}
.rr-scroll-rail[hidden] { display: none; }
.rr-scroll-rail-inner { height: 1px; }
.rr-scroll-rail::-webkit-scrollbar { height: 14px; }
.rr-scroll-rail::-webkit-scrollbar-track { background: var(--rr-grey-100); }
.rr-scroll-rail::-webkit-scrollbar-thumb {
  background-color: var(--rr-grey-500);
  border: 3px solid var(--rr-grey-100);
  border-radius: 7px;
  box-shadow: none;
}
.rr-scroll-rail:hover::-webkit-scrollbar-thumb { background-color: var(--rr-grey-600); box-shadow: none; }
@supports not selector(::-webkit-scrollbar) {
  .rr-scroll-rail { scrollbar-color: var(--rr-grey-500) var(--rr-grey-100); }
}
@media (min-width: 768px) {
  [data-scroll-rail].rr-rail-on { scrollbar-width: none; }
  [data-scroll-rail].rr-rail-on::-webkit-scrollbar { display: none; }
}
@media (max-width: 767.98px) {
  .rr-scroll-rail { display: none !important; }
}
@media print {
  .rr-scroll-rail { display: none !important; }
}

/* -----------------------------------------------------------------------------
   STICKY TABLE

   `.rr-sticky-table` on a table's scroll box (the `.table-responsive` div, not
   the <table>) keeps the column headings on screen while the rows scroll.

   A scroll box is what `position: sticky` pins to, so the box is bounded to
   what is left of the window under the site header and scrolls inside it. The
   <thead> is the sticky unit, not each <th>: once a filter row sits under the
   labels, per-cell `top: 0` stacks the two rows on one line and hides the
   labels. `border-collapse: separate` stops scrolled rows showing through the
   seam between the two head rows.

   Frozen columns: `.rr-sticky-col` on every cell of the column (header and
   body) pins it left; `--rr-sticky-left` is its offset, the sum of the widths
   of any frozen columns before it. `.rr-sticky-edge` draws the freeze line on
   the last one. A frozen header cell sits in both sticky axes, so it outranks
   the rest of the head or it slides under it on a diagonal scroll.

   Stacking inside the box: frozen body cells 1, head 3, frozen corner 4.
   The scroll rail (5) sits above all of them.

   Desktop only. Below 768px a stacked table hides its <thead> and the page owns
   vertical scrolling, so the box is unbounded and nothing sticks.
   Override the space left below the box per page with
   `--rr-sticky-table-offset` on the box.
   ----------------------------------------------------------------------------- */
:root {
  --rr-sticky-table-offset: 5rem;
  --rr-sticky-left: 0;
}
@media (min-width: 768px) {
  .rr-sticky-table {
    max-height: max(20rem, calc(100vh - var(--rr-header-h, 3.5rem) - var(--rr-sticky-table-offset)));
    overflow: auto;
  }
  .rr-sticky-table > .table {
    border-collapse: separate;
    border-spacing: 0;
  }
  .rr-sticky-table > .table > thead {
    position: sticky;
    top: 0;
    z-index: 3;
    box-shadow: 0 1px 0 var(--rr-grey-200);
  }
  .rr-sticky-table > .table > thead > tr > th {
    background-color: var(--rr-grey-50);
  }
  .rr-sticky-table .rr-sticky-col {
    position: sticky;
    left: var(--rr-sticky-left);
    z-index: 1;
    background-color: var(--tblr-bg-surface);
  }
  .rr-sticky-table > .table > thead .rr-sticky-col {
    z-index: 4;
    background-color: var(--rr-grey-50);
  }
  .rr-sticky-table > .table > thead:has(> tr:first-child > th.rr-sticky-col:first-child) > tr.data-table-filter-row > th:first-child {
    position: sticky;
    left: 0;
    z-index: 4;
  }
  .rr-sticky-table .rr-sticky-edge {
    box-shadow: inset -1px 0 0 var(--rr-grey-200);
  }
  .rr-sticky-table > .table-hover > tbody > tr:hover > .rr-sticky-col {
    background-color: var(--rr-grey-25);
  }
}
@media print {
  .rr-sticky-table { max-height: none; overflow: visible; }
  .rr-sticky-table > .table > thead,
  .rr-sticky-table .rr-sticky-col { position: static; }
}

/* =============================================================================
   TABLES — lighter rules, a quieter header, a real hover row.
   ============================================================================= */
.table {
  --tblr-table-border-color: var(--rr-grey-100);
  margin-bottom: 0;
}
.table thead th {
  background-color: var(--rr-grey-50);
  border-bottom: 1px solid var(--rr-grey-200);
  color: var(--rr-grey-600);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  padding: 0.6875rem 0.75rem;
  white-space: nowrap;
}
.table tbody td {
  padding: 0.6875rem 0.75rem;
  border-bottom: 1px solid var(--rr-grey-100);
  vertical-align: middle;
}
.table tbody tr:last-child td { border-bottom: 0; }
.table-hover tbody tr { transition: background-color 0.1s ease; }
.table-hover tbody tr:hover { background-color: var(--rr-grey-25); }

/* A table that fills a card should meet its rounded corners cleanly. */
.card > .table-responsive:first-child .table thead th:first-child,
.card > .table:first-child thead th:first-child { border-top-left-radius: var(--rr-radius-lg); }
.card > .table-responsive:first-child .table thead th:last-child,
.card > .table:first-child thead th:last-child { border-top-right-radius: var(--rr-radius-lg); }

/* =============================================================================
   BADGES — soft, legible, pill-shaped.
   ============================================================================= */
.badge {
  border-radius: 999px;
  font-weight: 600;
  font-size: 0.75rem;
  padding: 0.25rem 0.5625rem;
  letter-spacing: 0.005em;
}
.badge.bg-success, .badge.bg-green  { background-color: var(--rr-success-bg) !important; color: var(--rr-success) !important; }
.badge.bg-danger,  .badge.bg-red    { background-color: var(--rr-danger-bg)  !important; color: var(--rr-danger)  !important; }
.badge.bg-warning, .badge.bg-yellow { background-color: var(--rr-warning-bg) !important; color: var(--rr-warning) !important; }
.badge.bg-info,    .badge.bg-azure  { background-color: var(--rr-info-bg)    !important; color: var(--rr-info-ink) !important; }
.badge.bg-primary, .badge.bg-blue   { background-color: var(--rr-primary-50)  !important; color: var(--rr-primary-700) !important; }
/* ...unless the template asks for white text on it: that is the solid pill
   (development's interaction and official pages), not the soft chip. */
.badge.bg-primary.text-white, .badge.bg-blue.text-white {
  background-color: var(--rr-primary-600) !important; color: var(--rr-on-primary) !important;
}
.badge.bg-secondary                 { background-color: var(--rr-grey-100)   !important; color: var(--rr-grey-700) !important; }

/* =============================================================================
   TABLER HUE UTILITIES — the single biggest off-brand surface in the app.

   Templates carry 715 uses of Tabler's soft-tint utilities (`bg-blue-lt`,
   `bg-azure-lt`, `bg-purple-lt` and friends) and this file used to contain
   ZERO `-lt` selectors, so every one of them still painted the pre-green
   theme: 148 `bg-blue-lt`, 61 `bg-azure-lt`, 52 `bg-purple-lt` across the
   list and detail pages. The badge rules above only ever reached
   `.badge.bg-blue`, never the `-lt` variant and never a non-badge element.

   `bg-primary-lt` was the worst of them, because it was HALF themed: this
   file sets `--tblr-primary-rgb` to the brand green, which Tabler uses for
   the TEXT, while the BACKGROUND comes from `--tblr-primary-lt-rgb`, which
   nothing redefined — green text on pale blue.

   Overriding the utility classes directly, rather than redefining Tabler's
   `--tblr-<hue>-lt-rgb` triplets, is deliberate: the triplets want comma
   channel lists, which cannot be derived from a hex custom property without
   a build step, so they would have to be frozen literals that stop following
   the tenant's brand. These rules use the tokens, so they do follow it.
   `!important` is not a choice — Tabler's own utilities carry it.

   WHAT MAPS WHERE. The hues that merely meant "the accent" in the old blue
   theme collapse onto the brand; the ones carrying a SEMANTIC meaning keep a
   distinct one, because a page that paints success, warning and danger the
   same colour has lost the thing the colour was for:

     blue / indigo / primary   -> brand primary     (they were the accent)
     azure / cyan / info       -> info blue         (matches .badge.bg-azure)
     purple / grape / pink     -> accent gold       (violet is what the panel
                                                     header was retired FROM;
                                                     gold keeps a second
                                                     distinguishable category)
     green / teal / lime       -> success
     yellow / warning          -> warning
     orange                    -> orange (a distinct rung, see --rr-orange)
     red / danger              -> danger
     secondary / muted / dark / light / gray -> neutral
   ============================================================================= */

/* --- soft tints (`-lt`) --------------------------------------------------- */
.bg-blue-lt, .bg-indigo-lt, .bg-primary-lt {
  background-color: var(--rr-primary-50) !important;
  color: var(--rr-primary-700) !important;
  border-color: var(--rr-primary-100) !important;
}
/* A tinted box that also asks for .border-primary keeps a real edge — the
   pale -lt border above would otherwise win (it is !important and later). */
.bg-primary-lt.border-primary, .bg-blue-lt.border-primary { border-color: var(--rr-primary-500) !important; }
.bg-azure-lt, .bg-cyan-lt, .bg-info-lt {
  background-color: var(--rr-info-bg) !important;
  color: var(--rr-info-ink) !important;
  border-color: var(--rr-info-border) !important;
}
.bg-purple-lt, .bg-grape-lt, .bg-pink-lt {
  background-color: var(--rr-accent-soft) !important;
  color: var(--rr-accent-ink) !important;
  border-color: var(--rr-accent-border) !important;
}
.bg-green-lt, .bg-teal-lt, .bg-lime-lt, .bg-success-lt {
  background-color: var(--rr-success-bg) !important;
  color: var(--rr-success) !important;
  border-color: var(--rr-success-border) !important;
}
.bg-yellow-lt, .bg-warning-lt {
  background-color: var(--rr-warning-bg) !important;
  color: var(--rr-warning) !important;
  border-color: var(--rr-warning-border) !important;
}
.bg-orange-lt {
  background-color: var(--rr-orange-bg) !important;
  color: var(--rr-orange) !important;
  border-color: var(--rr-orange-border) !important;
}
.bg-red-lt, .bg-danger-lt {
  background-color: var(--rr-danger-bg) !important;
  color: var(--rr-danger) !important;
  border-color: var(--rr-danger-border) !important;
}
.bg-secondary-lt, .bg-muted-lt, .bg-dark-lt, .bg-light-lt, .bg-gray-lt {
  background-color: var(--rr-grey-100) !important;
  color: var(--rr-grey-700) !important;
  border-color: var(--rr-grey-200) !important;
}

/* --- solid hues on NON-badge elements ------------------------------------
   Avatars, dots and chips use `bg-blue` / `bg-azure` directly, which the
   badge-scoped rules above never reached. Scoped away from `.badge` so those
   keep their soft treatment. */
:not(.badge).bg-blue, :not(.badge).bg-indigo {
  background-color: var(--rr-primary-600) !important;
  color: var(--rr-on-primary) !important;
}
:not(.badge).bg-azure, :not(.badge).bg-cyan {
  background-color: var(--rr-info) !important;
  color: #ffffff !important;
}
:not(.badge).bg-purple, :not(.badge).bg-grape, :not(.badge).bg-pink {
  background-color: var(--rr-accent) !important;
  color: var(--rr-on-accent) !important;
}
:not(.badge).bg-green, :not(.badge).bg-teal, :not(.badge).bg-lime {
  background-color: var(--rr-success) !important;
  color: #ffffff !important;
}
:not(.badge).bg-yellow { background-color: var(--rr-warning) !important; color: #ffffff !important; }
:not(.badge).bg-orange { background-color: var(--rr-orange) !important; color: #ffffff !important; }
:not(.badge).bg-red    { background-color: var(--rr-danger) !important; color: #ffffff !important; }

/* --- hue text utilities --------------------------------------------------- */
.text-blue, .text-indigo { color: var(--rr-primary-600) !important; }
.text-azure, .text-cyan  { color: var(--rr-info-ink) !important; }
.text-purple, .text-grape, .text-pink { color: var(--rr-accent-ink) !important; }
.text-green, .text-teal, .text-lime   { color: var(--rr-success) !important; }
.text-yellow { color: var(--rr-warning) !important; }
.text-orange { color: var(--rr-orange) !important; }
.text-red    { color: var(--rr-danger) !important; }

/* Tabler also ships `text-<hue>-fg` for text ON a solid fill of that hue. */
.text-blue-fg, .text-indigo-fg, .text-primary-fg { color: var(--rr-on-primary) !important; }
.text-purple-fg, .text-pink-fg { color: var(--rr-on-accent) !important; }
.text-azure-fg, .text-green-fg, .text-yellow-fg,
.text-orange-fg, .text-red-fg { color: #ffffff !important; }

/* Tabler's own channel triplets, for the internals that still read them
   (ribbons, progress bars, status dots). The DEFAULT brand's channels; blue
   and purple (the primary and the accent) are overridden per tenant by
   includes/_brand_theme_vars.html, for the reason given above. */
:root {
  --tblr-blue-rgb: 32, 107, 196;
  --tblr-azure-rgb: 31, 111, 178;
  --tblr-green-rgb: 26, 127, 86;
  --tblr-red-rgb: 200, 53, 47;
  --tblr-yellow-rgb: 181, 115, 12;
  --tblr-orange-rgb: 192, 86, 31;
  --tblr-purple-rgb: 255, 146, 43;
}

/* Notification counts stay loud — a soft pill would lose the badge's job. */
.nav-link .badge.bg-red,
.navbar .badge.bg-red {
  background-color: var(--rr-danger) !important;
  color: #ffffff !important;
}

/* The avatar's profile-setup dot (core.context_processors._nav_onboarding).
   Not a .badge: the soft badge tints above are for text, and a dot has none,
   so a tint all but vanishes on the chip. Amber is "outstanding" (a required
   step is left); grey is "only optional steps left". The ring is the page
   surface, so the dot reads as sitting on the chip's edge in both themes. */
.rr-avatar-dot {
  position: absolute;
  top: -2px;
  right: -2px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  outline: 2px solid var(--tblr-bg-surface, #fff);
}
.rr-avatar-dot-warning   { background-color: var(--rr-warning); }
.rr-avatar-dot-secondary { background-color: var(--rr-grey-400); }

/* The dashboard switcher's "not this one" marker (_dashboard_switch_row.html):
   a ~3px dot centred in the 20px icon column, as the inline SVG drew it.
   Tabler 2.47 has no glyph that small, so it is point-filled (a disc 10/24 of
   the em) at 7px, on a 20px line so it sits where the check mark does. */
.ti.rr-switch-dot {
  font-size: .4375rem;
  line-height: 1.25rem;
}

/* The avatar menu's folded Dashboards row (layout_navbar.html). The summary
   is a dropdown-item, so it hovers and focuses like its neighbours; closed, it
   names the dashboard you are on, and a chevron says it opens. The name
   truncates rather than widening the menu at 390px. */
.rr-dash-fold > summary { list-style: none; cursor: pointer; }
.rr-dash-fold > summary::-webkit-details-marker { display: none; }
.rr-dash-fold-title { flex: 1 0 auto; }
.rr-dash-fold-current {
  flex: 0 1 auto;
  min-width: 0;
  max-width: 11rem;
  margin-left: 0.75rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rr-dash-fold > summary::after {
  content: "\203A";
  margin-left: 0.5rem;
  font-size: 1rem;
  line-height: 1;
  color: var(--rr-grey-500);
  transition: transform 0.12s ease;
}
.rr-dash-fold[open] > summary::after { transform: rotate(90deg); }
.rr-dash-fold[open] > summary { font-weight: 600; }

/* =============================================================================
   ALERTS
   ============================================================================= */
.alert {
  border-radius: var(--rr-radius);
  border-width: 1px;
  padding: var(--rr-space-3) var(--rr-space-4);
  box-shadow: none;
}
/* Tabler keeps room on the right of a dismissible alert for its × (padding-right:
   3rem). The shorthand above comes later and took it back, so a long message ran
   to the × and under it. Same selector Tabler uses, restored after the shorthand. */
.alert-dismissible { padding-right: 3rem; }
.alert-success { background-color: var(--rr-success-bg); border-color: #bfe3d3; color: #125c3e; }
.alert-danger  { background-color: var(--rr-danger-bg);  border-color: var(--rr-danger-border); color: #92251f; }
.alert-warning { background-color: var(--rr-warning-bg); border-color: var(--rr-warning-border); color: #85550a; }
.alert-info    { background-color: var(--rr-info-bg);    border-color: var(--rr-info-border); color: var(--rr-info-ink); }
/* Tabler beta19 colours .alert-light text with --tblr-light (#fcfdfe) on a #fff alert:
   unreadable. --tblr-alert-color too, because .alert-title / .alert-icon read it, not color. */
.alert-light   { background-color: var(--rr-grey-25); border-color: var(--rr-grey-200); color: var(--rr-grey-800); --tblr-alert-color: var(--rr-grey-800); }

/* =============================================================================
   NAVBAR + PAGE CHROME
   ============================================================================= */
.navbar {
  background-color: #ffffff;
  border-bottom: 1px solid var(--rr-grey-200);
}
.navbar-sticky .navbar { box-shadow: var(--rr-shadow-xs); }

.navbar .nav-link {
  border-radius: var(--rr-radius-sm);
  font-weight: 500;
  color: var(--rr-grey-600);
  transition: background-color 0.12s ease, color 0.12s ease;
}
.navbar .nav-link:hover { background-color: var(--rr-grey-100); color: var(--rr-grey-900); }
.navbar .nav-link.active,
.navbar .nav-item.active > .nav-link {
  background-color: var(--rr-primary-50);
  color: var(--rr-primary-700);
}

/* -----------------------------------------------------------------------------
   THE MENU ROW — brand-filled, with a gold active marker.

   Two bars stack at the top of every page: the brand row (logo, inbox,
   settings, user menu) and this sticky menu row. Only the menu row takes the
   brand fill — the brand row stays white because the organisation logo is a
   full-colour mark on transparent and reverses badly on a dark ground.

   Applied via `.rr-navbar-menu` on the row in includes/layout_navbar.html,
   which replaced a `bg-white` utility. It had to be a class swap: `.bg-white`
   is `!important` in Bootstrap, so no amount of specificity here would have
   beaten it.

   The nav ITEMS are untouched — same links, same order, same registry
   (core/navigation.py). This is colour only.
   -------------------------------------------------------------------------- */
.rr-navbar-menu {
  background: linear-gradient(180deg,
      var(--rr-primary-600) 0%, var(--rr-primary-700) 100%);
  border-bottom-color: var(--rr-primary-800) !important;
}
/* On a phone the row's only child is Bootstrap's `.navbar-collapse`, which is
   `display: none` until the burger is tapped — so the row rendered as its own
   vertical padding and nothing else. That was invisible while the row was
   white; once it took the brand fill it became a 65px green stripe under the
   header with no content in it. Drop the row's padding at this width and let
   the menu supply its own when it opens. */
@media (max-width: 767.98px) {
  /* `min-height` as well as the padding: Tabler floors every `.navbar` at
     56px, which is what was actually holding the empty band open — measured,
     because zeroing the padding alone left it at 56px exactly. Nothing is
     lost by removing the floor here; when the menu opens its own content
     sets the height (measured 320px). */
  .rr-navbar-menu {
    padding-top: 0;
    padding-bottom: 0;
    min-height: 0;
  }
  .rr-navbar-menu .navbar-collapse.show { padding-block: var(--rr-space-2); }

  /* The "logged in as" block only appears inside the open mobile menu, where
     it inherits body ink — near-black on the brand fill. It reads as dark
     text on a dark panel, which is the one thing a retint must not leave
     behind. Anything else the menu grows gets the same treatment by being a
     descendant, rather than each block naming its own colour. */
  .rr-navbar-menu .navbar-collapse {
    color: rgba(255, 255, 255, 0.92);
  }
  .rr-navbar-menu .navbar-collapse .text-muted {
    color: rgba(255, 255, 255, 0.68) !important;
  }
  .rr-navbar-menu .navbar-collapse .border-bottom {
    border-bottom-color: rgba(255, 255, 255, 0.18) !important;
  }
}

.rr-navbar-menu .nav-link {
  color: rgba(255, 255, 255, 0.82);
  font-weight: 500;
}
.rr-navbar-menu .nav-link:hover,
.rr-navbar-menu .nav-link:focus {
  background-color: rgba(255, 255, 255, 0.10);
  color: #ffffff;
}
/* Gold, not a lighter green: on a green bar the active item needs a different
   hue to register at a glance, and the accent is what the design uses. The
   left border is the marker — a full gold fill on a wide nav item shouts. */
.rr-navbar-menu .nav-link.active,
.rr-navbar-menu .nav-item.active > .nav-link,
.rr-navbar-menu .nav-link.show {
  background-color: rgba(255, 255, 255, 0.14);
  /* The accent as text only where it reads on the bar; --rr-accent-text
     falls back to the bar's ink when it doesn't (orange on RefReflect Blue
     is 2.4:1). The border keeps the accent as the marker either way. */
  color: var(--rr-accent-text, var(--rr-accent));
  box-shadow: inset 2px 0 0 0 var(--rr-accent);
}
/* Tabler draws the dropdown caret with a border triangle in `currentColor`,
   so it follows the link colour without help — but the toggle's ::after is
   set to the grey ramp further up. Reset it on this bar. */
.rr-navbar-menu .dropdown-toggle::after { border-top-color: currentColor; }

/* The hamburger. Bootstrap's icon is a background-image with a hardcoded dark
   stroke, invisible on the brand fill; the filter is cheaper and more robust
   than restating the SVG data URI. */
.rr-navbar-menu .navbar-toggler { border-color: rgba(255, 255, 255, 0.3); }
.rr-navbar-menu .navbar-toggler-icon { filter: invert(1) brightness(2); }

/* The collapsed mobile menu is the SAME element, so it keeps the brand fill —
   which is correct, but the links inside need breathing room once they stack. */
@media (max-width: 767.98px) {
  .rr-navbar-menu .navbar-collapse { padding-bottom: var(--rr-space-2); }
  .rr-navbar-menu .nav-link { padding: 0.5rem 0.625rem; }
  /* Inline (dropend) submenus render on the bar itself rather than in a
     popover, so their items are on green and must invert too. */
  .rr-navbar-menu .dropdown-menu {
    background-color: transparent;
    border: 0;
    box-shadow: none;
  }
  .rr-navbar-menu .dropdown-menu .dropdown-item {
    color: rgba(255, 255, 255, 0.82);
  }
  .rr-navbar-menu .dropdown-menu .dropdown-item:hover,
  .rr-navbar-menu .dropdown-menu .dropdown-item:focus {
    background-color: rgba(255, 255, 255, 0.10);
    color: #ffffff;
  }
  .rr-navbar-menu .dropdown-header { color: rgba(255, 255, 255, 0.55); }
}

/* Desktop dropdowns stay light — they are popovers over the page, not part of
   the bar, and a dark menu would be the only one in the app. The mobile rules
   above are scoped to the breakpoint so they cannot leak up here. */
@media (min-width: 768px) {
  .rr-navbar-menu .dropdown-menu {
    background-color: #ffffff;
    border: 1px solid var(--rr-grey-200);
    box-shadow: var(--rr-shadow-lg);
  }
  .rr-navbar-menu .dropdown-menu .dropdown-item { color: var(--rr-grey-700); }
  /* The inline competition fly-outs sit INSIDE that white menu (position:
     static, see dashboard_base.html), so they must not inherit a dark scheme. */
  .rr-navbar-menu .dropdown-menu .dropdown-menu {
    background-color: transparent;
    border: 0;
    box-shadow: none;
  }
}

/* -----------------------------------------------------------------------------
   NAVBAR DROPDOWNS — two panes, by how often a page is used.

   core/navigation.py gives every link a frequency and derives its tier; the
   template renders the tiers (includes/navbar_navigation_v2.html):
     .rr-mega-primary    the round's work: icon, label, one-line description,
                         chips for the ways into the same page
     .rr-mega-secondary  occasional work: compact rows on a tinted pane, unlabelled
     .rr-mega-foot       "⚙ Appointing setup · 7" — the seasonal configuration
                         the dropdown no longer carries, on the Setup page
   Descriptions are one line with an ellipsis on desktop so the tallest menu
   (Performance, 8 + 11 rows) fits 1440x900 without scrolling inside itself.
   On a phone the panes stack inside the brand-filled burger and every row
   inverts, like the rest of the mobile menu above.
   -------------------------------------------------------------------------- */
.rr-mega { padding: 0 !important; overflow: hidden; }
.rr-mega-kicker {
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--rr-grey-500);
  padding: var(--rr-space-2) 0.5rem 0.25rem;
}
.rr-mega-primary > .rr-mega-kicker:first-child { padding-top: 0; }
.rr-mega-link {
  display: flex;
  gap: 0.65rem;
  align-items: flex-start;
  padding: 0.375rem 0.5rem;
  border-radius: var(--rr-radius-sm);
  color: inherit;
  text-decoration: none;
}
.rr-mega-link:hover,
.rr-mega-link:focus-visible { background-color: var(--rr-primary-50); text-decoration: none; }
.rr-mega-item.active > .rr-mega-link { background-color: var(--rr-primary-50); }
.rr-mega-ico {
  flex: 0 0 1.85rem;
  width: 1.85rem;
  height: 1.85rem;
  border-radius: var(--rr-radius-sm);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background-color: var(--rr-primary-50);
  color: var(--rr-primary-700);
  font-size: 1.05rem;
}
.rr-mega-text { min-width: 0; display: flex; flex-direction: column; }
.rr-mega-label {
  font-weight: 600;
  font-size: 0.875rem;
  line-height: 1.25;
  color: var(--rr-grey-900);
}
.rr-mega-desc {
  font-size: 0.75rem;
  line-height: 1.3;
  color: var(--rr-grey-500);
  margin-top: 1px;
}
.rr-mega-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  /* Under the label, not under the icon. */
  padding: 0 0.5rem 0.3rem calc(0.5rem + 1.85rem + 0.65rem);
}
.rr-mega-chip {
  font-size: 0.6875rem;
  line-height: 1;
  padding: 0.25rem 0.5rem;
  border: 1px solid var(--rr-grey-200);
  border-radius: 999px;
  color: var(--rr-grey-600);
  background-color: #ffffff;
  white-space: nowrap;
  text-decoration: none;
}
.rr-mega-chip:hover,
.rr-mega-chip:focus-visible {
  border-color: var(--rr-primary-300);
  background-color: var(--rr-primary-50);
  color: var(--rr-primary-700);
  text-decoration: none;
}
.rr-mega-chip.active { border-color: var(--rr-primary-400); color: var(--rr-primary-700); }
/* Amber means "outstanding", as it does everywhere else in the app. */
.rr-mega-due {
  font-size: 0.625rem;
  font-weight: 600;
  color: var(--rr-warning);
  background-color: var(--rr-warning-bg);
  border-radius: 999px;
  padding: 0.1rem 0.45rem;
  vertical-align: 1px;
}
.rr-mega-promoted .rr-mega-ico { background-color: var(--rr-warning-bg); color: var(--rr-warning); }
.rr-mega-less-group + .rr-mega-less-group { margin-top: 0.35rem; }
.rr-mega-secondary .rr-mega-kicker { font-size: 0.625rem; padding: 0.1rem 0.5rem; }
.rr-mega-secondary > .rr-mega-less-group:first-child .rr-mega-kicker:first-child { padding-top: 0; }
.rr-mega-less-link {
  display: block;
  padding: 0.18rem 0.5rem;
  font-size: 0.8125rem;
  color: var(--rr-grey-700);
  border-radius: var(--rr-radius-sm);
  text-decoration: none;
  cursor: pointer;
}
.rr-mega-less-link:hover,
.rr-mega-less-link:focus-visible {
  background-color: var(--rr-grey-100);
  color: var(--rr-grey-900);
  text-decoration: none;
}
.rr-mega-less-link.active { color: var(--rr-primary-700); font-weight: 600; }
.rr-mega-expander > summary { list-style: none; display: flex; align-items: center; gap: 0.35rem; }
.rr-mega-expander > summary::-webkit-details-marker { display: none; }
.rr-mega-expander > summary::after {
  content: "\203A";
  font-size: 1rem;
  line-height: 1;
  transition: transform 0.12s ease;
}
.rr-mega-expander[open] > summary::after { transform: rotate(90deg); }
/* A right-pane row's own sub-pages, on a small line under it. */
.rr-mega-less-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0 0.6rem;
  padding: 0 0.5rem 0.2rem 1.1rem;
  font-size: 0.75rem;
}
.rr-mega-less-chips a { color: var(--rr-grey-500); text-decoration: none; }
.rr-mega-less-chips a:hover,
.rr-mega-less-chips a:focus-visible { color: var(--rr-primary-700); text-decoration: underline; }
.rr-mega-less-chips a.active { color: var(--rr-primary-700); font-weight: 600; }
.rr-mega-sub { padding-left: 1.1rem; }
.rr-mega-foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--rr-space-3);
  padding: 0.55rem 1.1rem;
  border-top: 1px solid var(--rr-grey-100);
  background-color: var(--rr-grey-50);
  font-size: 0.8125rem;
}
.rr-mega-foot a {
  color: var(--rr-grey-600);
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  text-decoration: none;
}
.rr-mega-foot a:hover { color: var(--rr-primary-700); }
.rr-mega-foot-note { color: var(--rr-grey-400); }
/* Something unread inside a closed menu (Team Chat). */
.rr-nav-dot {
  display: inline-block;
  width: 0.45rem;
  height: 0.45rem;
  margin-left: 0.3rem;
  border-radius: 50%;
  background-color: var(--rr-accent);
  vertical-align: middle;
}

@media (min-width: 768px) {
  .rr-navbar-menu .rr-mega {
    min-width: 40rem;
    max-width: min(46rem, calc(100vw - 1rem));
    border-radius: var(--rr-radius-lg);
  }
  .rr-navbar-menu .rr-mega.rr-mega-narrow { min-width: 22rem; max-width: 26rem; }
  /* A wide menu opened from the right of the bar anchors to its toggle's
     right edge instead of running off the screen (script in the navbar). */
  .rr-navbar-menu .rr-mega.rr-mega-end { left: auto !important; right: 0 !important; }
  .rr-mega-body { display: flex; align-items: stretch; }
  .rr-mega-primary {
    flex: 1.6 1 0;
    min-width: 0;
    padding: var(--rr-space-3) var(--rr-space-2) var(--rr-space-2);
  }
  .rr-mega-secondary {
    flex: 1 1 0;
    min-width: 0;
    padding: var(--rr-space-3) var(--rr-space-3) var(--rr-space-2);
    background-color: var(--rr-grey-25);
    border-left: 1px solid var(--rr-grey-100);
  }
  .rr-mega-desc { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
}

@media (max-width: 767.98px) {
  .rr-mega-body { display: block; }
  .rr-mega-primary,
  .rr-mega-secondary { padding: 0 0 var(--rr-space-1); }
  .rr-mega-secondary { background: transparent; border: 0; }
  .rr-mega-link { padding: 0.55rem 0.5rem; min-height: 44px; }
  .rr-mega-link:hover,
  .rr-mega-link:focus-visible { background-color: rgba(255, 255, 255, 0.08); }
  .rr-mega-item.active > .rr-mega-link { background-color: rgba(255, 255, 255, 0.14); }
  .rr-mega-label { color: inherit; }
  .rr-mega-desc { color: currentColor; opacity: 0.7; }
  .rr-mega-ico { background-color: rgba(255, 255, 255, 0.10); color: inherit; }
  .rr-mega-kicker { color: currentColor; opacity: 0.6; }
  .rr-mega-chip {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: 0 0.75rem;
    font-size: 0.8125rem;
    background: transparent;
    color: inherit;
    border-color: rgba(255, 255, 255, 0.3);
  }
  .rr-mega-chip:hover,
  .rr-mega-chip:focus-visible { background-color: rgba(255, 255, 255, 0.10); color: #ffffff; border-color: rgba(255, 255, 255, 0.5); }
  /* On a phone the panes stack, so a hairline marks where the second begins. */
  .rr-mega-secondary { border-top: 1px solid rgba(255, 255, 255, 0.14); padding-top: var(--rr-space-2); }
  .rr-mega-less-link {
    display: flex;
    align-items: center;
    min-height: 44px;
    padding: 0.5rem;
    font-size: 0.9375rem;
    color: inherit;
  }
  .rr-mega-less-link:hover,
  .rr-mega-less-link:focus-visible { background-color: rgba(255, 255, 255, 0.08); color: #ffffff; }
  .rr-mega-less-link.active { color: var(--rr-accent-text, #ffffff); }
  .rr-mega-less-chips { gap: 0 var(--rr-space-2); padding-left: 1rem; font-size: 0.875rem; }
  .rr-mega-less-chips a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    color: inherit;
    opacity: 0.75;
  }
  .rr-mega-less-chips a:hover,
  .rr-mega-less-chips a:focus-visible { color: #ffffff; opacity: 1; }
  .rr-mega-foot { background: transparent; border-top: 0; padding: 0 0.5rem var(--rr-space-2); }
  .rr-mega-foot a { color: inherit; opacity: 0.8; min-height: 44px; }
  .rr-mega-foot-note { color: inherit; opacity: 0.5; }
}

/* The top of the phone menu: search, then the pages most people came for. */
.rr-nav-phone-top { padding: 0 0.25rem var(--rr-space-3); }
/* Tabler's `.form-control` is `color: inherit`, and this box sits inside the
   phone menu, whose ink is white for the brand fill (see `.rr-navbar-menu
   .navbar-collapse` above) — so what you typed was white on the white field.
   The field is a light surface, so it takes body ink back. The whole block is
   `d-md-none`, so the desktop header search is not reached by this. */
.rr-nav-phone-top .form-control {
  font-size: 16px;
  min-height: 44px;
  color: var(--rr-grey-900);
}
.rr-nav-phone-top .form-control::placeholder { color: var(--rr-grey-500); }
.rr-nav-phone-results {
  display: none;
  margin-top: var(--rr-space-2);
  padding: var(--rr-space-1);
  max-height: 50vh;
  overflow-y: auto;
  background-color: #ffffff;
  border-radius: var(--rr-radius);
}
.rr-nav-phone-results.show { display: block; }
.rr-nav-phone-results .dropdown-item {
  display: flex;
  align-items: center;
  min-height: 44px;
  white-space: normal;
  color: var(--rr-grey-700);
}
.rr-nav-phone-results .dropdown-header { color: var(--rr-grey-500); }
.rr-nav-phone-results .dropdown-item-text { color: var(--rr-grey-500); }
.rr-nav-tiles { display: flex; flex-wrap: wrap; gap: var(--rr-space-2); margin-top: var(--rr-space-3); }
.rr-nav-tile {
  flex: 0 0 calc(50% - var(--rr-space-2) / 2);
  min-width: 0;
  min-height: 64px;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  align-items: flex-start;
  padding: 0.7rem 0.75rem;
  border-radius: var(--rr-radius);
  background-color: rgba(255, 255, 255, 0.08);
  color: inherit;
  font-weight: 600;
  font-size: 0.875rem;
  text-decoration: none;
}
.rr-nav-tile:hover,
.rr-nav-tile:focus-visible { background-color: rgba(255, 255, 255, 0.14); color: #ffffff; text-decoration: none; }
.rr-nav-tile i { font-size: 1.2rem; opacity: 0.85; }

/* -----------------------------------------------------------------------------
   HEADER — the bar at the very top (includes/layout_navbar.html), in both
   layouts.

   White, so the organisation's full-colour logo sits on the ground it was
   drawn for; a brand-filled header with the logo on a white plate was tried
   first and turned down. The height is kept tight: a 2.5rem logo instead of
   3rem takes the header from
   73px to 57px (65px to 57px on a phone), and with the sidebar there is no
   menu row under it either.
   -------------------------------------------------------------------------- */
:root {
  --rr-header-h: 3.5rem;
  --rr-sidebar-w: 15.5rem;
  --rr-sidebar-rail-w: 4.25rem;
}
/* `--rr-header-h` is where the sidebar hangs from, and sidebar.js sets it to
   the header's measured height — so the header's own floor must not read it,
   or a header that grew once (a wrap, then the window widened) could never
   shrink back. */
.navbar.rr-header {
  min-height: 3.5rem;
  padding-block: 0.25rem;
}
/* Out-ranks dashboard_base.html's `.navbar-brand-image` (3rem from md up).
   Tabler pads `.navbar-brand` vertically, which on its own made the bar 65px;
   the bar's padding is enough. */
.rr-header .navbar-brand { padding-block: 0; }
.rr-header .navbar-brand-image { height: 2.5rem; width: auto; }
@media (max-width: 767.98px) {
  .rr-header .navbar-brand-image { height: 2.25rem; }
}

/* -----------------------------------------------------------------------------
   SIDEBAR — the menus down the left (includes/navbar_sidebar.html), for
   whoever core.nav_layout gives the sidebar. Same registry, same tiers, same
   gold active marker as the menu row; only the shape changes.

     popped out   15.5rem: every menu's name, the one you are in open under it
     popped in    the rail, 4.25rem of icons; a menu flies out beside it
     phone        a drawer over the page, opened from the header's button

   The sidebar is fixed below the header, and everything else in `.page`
   moves over by its width (`--rr-sidebar-w`, which the rail narrows) — so no
   page template changes, and a page's own sticky strips still stick.
   -------------------------------------------------------------------------- */
.rr-layout-sidebar .rr-header-sticky { z-index: 1030; }
.rr-header-start { display: flex; align-items: center; gap: var(--rr-space-2); min-width: 0; }
.rr-sidebar-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  padding: 0;
  border: 0;
  border-radius: var(--rr-radius-sm);
  background: transparent;
  color: var(--rr-grey-600);
  font-size: 1.35rem;
}
/* The same hover as the header's other icon buttons (`.navbar .nav-link`). */
.rr-sidebar-toggle:hover,
.rr-sidebar-toggle:focus-visible { background-color: var(--rr-grey-100); color: var(--rr-grey-900); }
.rr-sidebar-toggle i { display: none; }

.rr-sidebar {
  position: fixed;
  top: var(--rr-header-h);
  bottom: 0;
  left: 0;
  z-index: 1025;
  width: var(--rr-sidebar-w);
  display: flex;
  flex-direction: column;
  background: linear-gradient(180deg, var(--rr-primary-700) 0%, var(--rr-primary-800) 100%);
  border-right: 1px solid var(--rr-primary-800);
  color: rgba(255, 255, 255, 0.88);
}
.rr-sidebar-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-x: hidden;
  overflow-y: auto;
  padding: var(--rr-space-2);
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.25) transparent;
}
.rr-sidebar-muted { color: rgba(255, 255, 255, 0.68); }
.rr-side-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.rr-side-link {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.65rem;
  width: 100%;
  min-height: 2.5rem;
  padding: 0.4rem 0.6rem;
  border-radius: var(--rr-radius-sm);
  color: rgba(255, 255, 255, 0.82);
  font-size: 0.875rem;
  font-weight: 500;
  text-decoration: none;
  cursor: pointer;
  list-style: none;
}
.rr-side-link::-webkit-details-marker { display: none; }
.rr-side-link:hover,
.rr-side-link:focus-visible { background-color: rgba(255, 255, 255, 0.10); color: #ffffff; text-decoration: none; }
/* The gold marker the menu row uses, on the left edge. */
.rr-side-link.active {
  background-color: rgba(255, 255, 255, 0.14);
  color: var(--rr-accent-text, var(--rr-accent));
  box-shadow: inset 2px 0 0 0 var(--rr-accent);
}
.rr-side-ico {
  flex: 0 0 1.5rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 1.25rem;
}
.rr-side-ico .nav-link-icon { width: auto; height: auto; margin: 0; color: inherit; opacity: 1; }
.rr-side-initial { font-weight: 700; font-size: 0.95rem; }
.rr-side-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rr-side-caret::before {
  content: "\203A";
  display: inline-block;
  font-size: 1.1rem;
  line-height: 1;
  opacity: 0.7;
  transition: transform 0.12s ease;
}
.rr-side-group[open] > summary .rr-side-caret::before { transform: rotate(90deg); }
.rr-side-group > summary .rr-nav-dot { margin-left: 0; }

/* A menu's contents, popped out: indented under its name. */
.rr-side-panel { padding: 0.15rem 0 0.4rem 0.6rem; }
.rr-side-panel-title { display: none; }
.rr-side-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 2rem;
  padding: 0.3rem 0.5rem;
  border-radius: var(--rr-radius-sm);
  color: rgba(255, 255, 255, 0.8);
  font-size: 0.8125rem;
  line-height: 1.25;
  text-decoration: none;
}
.rr-side-row i { flex: 0 0 auto; font-size: 1rem; opacity: 0.75; }
.rr-side-row-label { flex: 1 1 auto; min-width: 0; }
.rr-side-row:hover,
.rr-side-row:focus-visible { background-color: rgba(255, 255, 255, 0.08); color: #ffffff; text-decoration: none; }
.rr-side-row.active {
  background-color: rgba(255, 255, 255, 0.12);
  color: var(--rr-accent-text, #ffffff);
  font-weight: 600;
}
.rr-side-row.active i { opacity: 1; }
.rr-side-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0 0.6rem;
  padding: 0 0.5rem 0.25rem 2rem;
  font-size: 0.75rem;
}
.rr-side-chips a { color: rgba(255, 255, 255, 0.6); text-decoration: none; }
.rr-side-chips a:hover,
.rr-side-chips a:focus-visible { color: #ffffff; text-decoration: underline; }
.rr-side-chips a.active { color: var(--rr-accent-text, #ffffff); font-weight: 600; }
/* The dropdown's right pane: its stage headings and compact rows, after a
   hairline. The row and heading markup is the dropdown's own. */
.rr-side-more {
  margin-top: 0.35rem;
  padding-top: 0.35rem;
  border-top: 1px solid rgba(255, 255, 255, 0.12);
}
.rr-sidebar .rr-mega-kicker {
  color: rgba(255, 255, 255, 0.5);
  font-size: 0.625rem;
  padding: 0.45rem 0.5rem 0.2rem;
}
.rr-sidebar .rr-mega-less-link {
  color: rgba(255, 255, 255, 0.75);
  font-size: 0.8125rem;
  padding: 0.25rem 0.5rem;
}
/* The competition list's levels, which the padding above flattened: a
   competition's Current Season / Historical / Counts sat flush with it. */
.rr-sidebar .rr-mega-sub { padding-left: 1.1rem; }
.rr-sidebar .rr-mega-less-link:hover,
.rr-sidebar .rr-mega-less-link:focus-visible { background-color: rgba(255, 255, 255, 0.08); color: #ffffff; }
.rr-sidebar .rr-mega-less-link.active { color: var(--rr-accent-text, #ffffff); font-weight: 600; }
.rr-sidebar .rr-mega-less-chips a { color: rgba(255, 255, 255, 0.55); }
/* A section's less-often rows under its own main rows (one heading per
   section, core.navigation._side_halves): lined up with the main rows'
   labels, which sit after a 1rem icon and a 0.5rem gap. */
.rr-side-section-more .rr-mega-less-link { padding-left: 2rem; }
.rr-side-section-more .rr-mega-less-chips { padding-left: 2.5rem; }
.rr-sidebar .rr-mega-less-chips a:hover,
.rr-sidebar .rr-mega-less-chips a:focus-visible { color: #ffffff; }
.rr-sidebar .rr-mega-less-chips a.active { color: var(--rr-accent-text, #ffffff); }
.rr-side-setup {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: 0.35rem;
  padding: 0.35rem 0.5rem;
  border-radius: var(--rr-radius-sm);
  color: rgba(255, 255, 255, 0.6);
  font-size: 0.75rem;
  text-decoration: none;
}
.rr-side-setup:hover,
.rr-side-setup:focus-visible { background-color: rgba(255, 255, 255, 0.08); color: #ffffff; text-decoration: none; }

.rr-sidebar-backdrop[hidden] { display: none; }

@media (min-width: 768px) {
  /* Everything in the page but the header and the sidebar moves over. */
  .rr-layout-sidebar .page > :not(.rr-header-sticky):not(.rr-sidebar):not(.rr-sidebar-backdrop) {
    margin-left: var(--rr-sidebar-w);
  }
  /* A full-width strip beside the sidebar would run past the window by its width. */
  .rr-layout-sidebar .page > .container-fluid {
    width: calc(100% - var(--rr-sidebar-w));
  }
  .rr-layout-sidebar.rr-sidebar-rail { --rr-sidebar-w: var(--rr-sidebar-rail-w); }
  .rr-sidebar-backdrop { display: none !important; }

  .rr-sidebar-toggle .rr-when-open { display: inline; }
  .rr-sidebar-rail .rr-sidebar-toggle .rr-when-open { display: none; }
  .rr-sidebar-rail .rr-sidebar-toggle .rr-when-rail { display: inline; }

  /* Popped in: icons only, and a menu flies out beside the rail. The fly-out
     is fixed, not absolute, so the rail's scroller cannot clip it; sidebar.js
     sets its `top` level with the icon and keeps it on screen. */
  .rr-sidebar-rail .rr-sidebar-scroll { padding: var(--rr-space-2) 0.45rem; }
  .rr-sidebar-rail .rr-side-link { justify-content: center; padding-inline: 0; }
  .rr-sidebar-rail .rr-side-label,
  .rr-sidebar-rail .rr-side-caret { display: none; }
  .rr-sidebar-rail .rr-side-group > summary .rr-nav-dot {
    position: absolute;
    top: 0.4rem;
    right: 0.5rem;
  }
  .rr-sidebar-rail .rr-side-group[open] > .rr-side-panel {
    position: fixed;
    top: var(--rr-header-h);
    left: calc(var(--rr-sidebar-w) + 0.35rem);
    width: 18rem;
    max-height: calc(100vh - 1rem);
    overflow-y: auto;
    padding: var(--rr-space-2);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: var(--rr-radius-lg);
    background-color: var(--rr-primary-700);
    box-shadow: var(--rr-shadow-lg);
  }
  .rr-sidebar-rail .rr-side-panel-title {
    display: block;
    margin-bottom: 0.35rem;
    padding: 0.3rem 0.5rem 0.5rem;
    border-bottom: 1px solid rgba(255, 255, 255, 0.12);
    color: #ffffff;
    font-size: 0.875rem;
    font-weight: 700;
  }
}

@media (max-width: 767.98px) {
  /* A drawer under the header, over the page, closed until the button opens
     it. `visibility` as well as the slide, so the links of a closed drawer
     are out of the tab order. */
  .rr-sidebar {
    width: min(20rem, 86vw);
    transform: translateX(-100%);
    visibility: hidden;
    transition: transform 0.2s ease, visibility 0.2s;
  }
  .rr-sidebar-open .rr-sidebar { transform: none; visibility: visible; box-shadow: var(--rr-shadow-lg); }
  .rr-sidebar-backdrop {
    position: fixed;
    inset: var(--rr-header-h) 0 0 0;
    z-index: 1024;
    background-color: rgba(10, 20, 30, 0.45);
  }
  .rr-sidebar-toggle { width: 44px; height: 44px; }
  .rr-sidebar-toggle .rr-when-phone { display: inline; }
  .rr-sidebar-phone-top { padding: 0 0.25rem var(--rr-space-3); }
  .rr-sidebar-who {
    padding: 0.25rem 0.25rem var(--rr-space-2);
    margin-bottom: var(--rr-space-2);
    border-bottom: 1px solid rgba(255, 255, 255, 0.14);
  }
  /* 44px tap targets, as everywhere on a phone. */
  .rr-side-link,
  .rr-side-row,
  .rr-side-setup { min-height: 44px; }
  .rr-side-chips { gap: 0 var(--rr-space-2); padding-left: 1.75rem; font-size: 0.875rem; }
  .rr-side-chips a { display: inline-flex; align-items: center; min-height: 44px; }
}
@media (prefers-reduced-motion: reduce) {
  .rr-sidebar { transition: none; }
}
@media print {
  .rr-layout-sidebar .page > * { margin-left: 0 !important; }
}

/* The avatar menu's "Menu layout" choice: two buttons, the one that is on
   pressed. */
.rr-layout-switch { padding: 0.35rem var(--tblr-dropdown-item-padding-x, 0.75rem) 0.5rem; }
.rr-layout-switch-label {
  margin-bottom: 0.35rem;
  color: var(--rr-grey-500);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.rr-layout-switch-options { display: flex; gap: 0.35rem; }
.rr-layout-option {
  flex: 1 1 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  min-height: 2.25rem;
  padding: 0.25rem 0.5rem;
  border: 1px solid var(--rr-grey-200);
  border-radius: var(--rr-radius-sm);
  background-color: #ffffff;
  color: var(--rr-grey-700);
  font-size: 0.8125rem;
  font-weight: 500;
}
.rr-layout-option:hover,
.rr-layout-option:focus-visible {
  border-color: var(--rr-primary-300);
  background-color: var(--rr-primary-50);
  color: var(--rr-primary-700);
}
.rr-layout-option.active {
  border-color: var(--rr-primary-600);
  background-color: var(--rr-primary-50);
  color: var(--rr-primary-700);
  font-weight: 600;
}

/* -----------------------------------------------------------------------------
   SETUP PAGE (templates/settings/dashboard.html) — a sticky section index
   beside the blocks on a wide screen, a wrapping chip row above them on a
   phone. Flex, not the grid the rest of the page uses for cards: the index is
   a fixed-width rail, not a grid cell.
   -------------------------------------------------------------------------- */
.rr-setup-intro { max-width: 65ch; margin-bottom: var(--rr-space-4); }
/* Quick links above the page (Users first): a wrapping row of buttons. */
.rr-setup-quick { display: flex; flex-wrap: wrap; gap: var(--rr-space-2); margin-bottom: var(--rr-space-3); }
@media (max-width: 991.98px) { .rr-setup-quick .btn { min-height: 44px; } }
.rr-setup { display: flex; gap: var(--rr-space-5); align-items: flex-start; }
.rr-setup-main { flex: 1 1 auto; min-width: 0; }
.rr-setup-side {
  flex: 0 0 14rem;
  position: sticky;
  top: 5rem;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--rr-space-2);
  background-color: #ffffff;
  border: 1px solid var(--rr-grey-200);
  border-radius: var(--rr-radius-lg);
}
.rr-setup-side-link {
  display: flex;
  justify-content: space-between;
  gap: var(--rr-space-2);
  padding: 0.4rem 0.65rem;
  border-radius: var(--rr-radius-sm);
  font-size: 0.8125rem;
  color: var(--rr-grey-700);
  text-decoration: none;
}
.rr-setup-side-link:hover { background-color: var(--rr-primary-50); color: var(--rr-primary-700); text-decoration: none; }
.rr-setup-side-count { color: var(--rr-grey-400); font-variant-numeric: tabular-nums; }
.rr-setup-block { margin-bottom: var(--rr-space-5); scroll-margin-top: 5rem; }
.rr-setup-steps { list-style: none; margin: 0; padding: 0; }
.rr-setup-step {
  display: flex;
  gap: var(--rr-space-3);
  align-items: center;
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--rr-grey-100);
}
.rr-setup-step:last-child { border-bottom: 0; }
.rr-setup-step-n {
  flex: 0 0 1.6rem;
  height: 1.6rem;
  border-radius: 50%;
  border: 1px solid var(--rr-grey-300);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75rem;
  color: var(--rr-grey-600);
  font-variant-numeric: tabular-nums;
}
.rr-setup-step-body { min-width: 0; display: flex; flex-wrap: wrap; gap: 0.25rem var(--rr-space-3); align-items: baseline; }
.rr-setup-step-label { font-weight: 600; font-size: 0.875rem; color: var(--rr-grey-900); text-decoration: none; }
a.rr-setup-step-label:hover { color: var(--rr-primary-700); text-decoration: none; }
.rr-setup-step-desc { font-size: 0.8125rem; color: var(--rr-grey-500); }
.rr-setup-step-links { display: flex; flex-wrap: wrap; gap: 0.25rem var(--rr-space-3); font-size: 0.8125rem; }
.rr-setup-cards { display: flex; flex-wrap: wrap; gap: var(--rr-space-3); }
.rr-setup-card { flex: 0 0 calc((100% - 2 * var(--rr-space-3)) / 3); min-width: 0; margin: 0; }
.rr-setup-card .card-body { display: flex; flex-direction: column; gap: 0.25rem; }
.rr-setup-card-title { font-weight: 600; font-size: 0.9375rem; color: var(--rr-grey-900); text-decoration: none; }
.rr-setup-card-title:hover { color: var(--rr-primary-700); text-decoration: none; }
.rr-setup-card:hover { border-color: var(--rr-primary-200); }
.rr-setup-card-desc { margin: 0; font-size: 0.8125rem; line-height: 1.4; color: var(--rr-grey-600); }
/* Sub-pages sit above the stretched card link so each stays its own target. */
.rr-setup-card-sub {
  position: relative;
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  gap: 0.2rem 0.75rem;
  margin-top: 0.25rem;
  font-size: 0.8125rem;
}
.rr-setup-card-freq {
  margin-top: auto;
  padding-top: 0.35rem;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--rr-grey-400);
}
@media (max-width: 1199.98px) {
  .rr-setup-card { flex-basis: calc((100% - var(--rr-space-3)) / 2); }
}
@media (max-width: 575.98px) {
  .rr-setup-card { flex-basis: 100%; }
}
@media (max-width: 991.98px) {
  .rr-setup { flex-direction: column; gap: var(--rr-space-3); }
  .rr-setup-side {
    position: static;
    flex: 0 0 auto;
    flex-direction: row;
    flex-wrap: wrap;
    gap: var(--rr-space-1);
    padding: 0;
    background: transparent;
    border: 0;
  }
  .rr-setup-side-link {
    min-height: 44px;
    align-items: center;
    padding: 0 0.75rem;
    border: 1px solid var(--rr-grey-200);
    border-radius: 999px;
    background-color: #ffffff;
  }
  .rr-setup-step { flex-wrap: wrap; }
  .rr-setup-step-links a,
  .rr-setup-card-sub a { display: inline-flex; align-items: center; min-height: 44px; }
}

/* Header bell: the count sits on the icon rather than beside it. */
.rr-bell .nav-link { position: relative; }
.rr-bell-count {
  position: absolute;
  top: 0.1rem;
  right: -0.1rem;
  font-size: 0.625rem;
  padding: 0.15rem 0.35rem;
}
.rr-bell-menu { min-width: 17rem; }

/* Avatar menu "Report a problem": dims while Sentry's form is loading. */
.rr-feedback-loading { opacity: 0.5; cursor: progress; }

.dropdown-menu {
  border-radius: var(--rr-radius);
  border: 1px solid var(--rr-grey-200);
  box-shadow: var(--rr-shadow-lg);
  padding: var(--rr-space-2);
}
.dropdown-item {
  border-radius: var(--rr-radius-sm);
  padding: 0.4375rem 0.625rem;
  color: var(--rr-grey-700);
}
.dropdown-item:hover, .dropdown-item:focus { background-color: var(--rr-grey-100); color: var(--rr-grey-900); }
.dropdown-item.active { background-color: var(--rr-primary-50); color: var(--rr-primary-700); }
.dropdown-header {
  font-size: 0.6875rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--rr-grey-400);
  padding: var(--rr-space-2) 0.625rem var(--rr-space-1);
}

.page-header {
  padding-top: var(--rr-space-5);
  padding-bottom: var(--rr-space-3);
}
/* The frame's optional lines around the title ({% block page_pretitle %} and
   {% block page_subtitle %} in core/dashboard_base.html). Empty unless a page
   fills them, and an empty one takes no space. A page whose header is a title
   plus one line of explanation uses these instead of drawing its own header. */
.page-header .page-pretitle:empty,
.page-header .page-subtitle:empty { display: none; }
.page-header .page-pretitle {
  font-size: 0.6875rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--rr-grey-500); margin-bottom: 0.125rem;
}
.page-header .page-subtitle {
  font-size: 0.875rem; color: var(--rr-grey-500);
  margin-top: 0.25rem; max-width: 72ch;
}
.page-body { padding-top: var(--rr-space-4); }

.footer {
  border-top: 1px solid var(--rr-grey-200);
  color: var(--rr-grey-500);
  font-size: 0.8125rem;
  margin-top: var(--rr-space-6);
}

/* =============================================================================
   PANEL HEADER

   The full-bleed hero block on finance and performance pages. Its background is
   `--rr-panel-header-bg` (see the token for why it is a violet gradient rather
   than the brand blue) so all 26 sites change together.

   Use this class on new panels rather than restating the gradient inline — that
   is exactly how the app ended up with 175 copies of it to find.
   ============================================================================= */
.rr-panel-header {
  background: var(--rr-panel-header-bg);
  color: #ffffff;
  border-radius: var(--rr-radius-lg) var(--rr-radius-lg) 0 0;
}
.rr-panel-header h1, .rr-panel-header h2, .rr-panel-header h3,
.rr-panel-header h4, .rr-panel-header h5,
.rr-panel-header .card-title { color: #ffffff; }

/* =============================================================================
   SHARED COMPONENT CLASSES
   Used by templates/includes/_empty_state.html, _stat_tile.html and
   _filter_bar.html so pages stop inventing their own. The page header itself
   is not a partial: dashboard_base.html already owns it as a `page_header`
   block, and only two templates override it.
   ============================================================================= */

/* Empty state — a page with no rows should explain itself and offer the
   action that fixes it, not render a blank card. */
.rr-empty {
  text-align: center;
  padding: var(--rr-space-6) var(--rr-space-4);
  color: var(--rr-grey-500);
}
.rr-empty-icon {
  width: 48px; height: 48px;
  margin: 0 auto var(--rr-space-4);
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background-color: var(--rr-grey-100);
  color: var(--rr-grey-400);
}
.rr-empty-title { font-size: 0.9375rem; font-weight: 600; color: var(--rr-grey-800); margin-bottom: var(--rr-space-1); }
.rr-empty-text  { font-size: 0.875rem; max-width: 24rem; margin: 0 auto var(--rr-space-4); }

/* An empty state INSIDE a scrolling table.

   It sits in a `<td colspan="N">`, and on a phone that cell is as wide as the
   whole table — 500-800px of columns in a 364px scroller — so the message
   centred itself across columns that are off-screen: Fitness Testing's "No
   upcoming sessions." and its "Create one" button landed 75-230px right of
   centre, partly past the edge. Pin it to what the scroller shows instead:
   sticky at left:0 keeps it in view if the table is swiped sideways, and the
   width is the scroller's own. Only a container query can name that (`cqi`), so
   a scroller that holds an empty state becomes a size container — just that one,
   so no other table's layout changes. The cell's side padding goes too,
   otherwise the message starts 12-20px in and ends past the edge.
   `white-space: normal` because several page-local table styles (the role,
   panel-matrix and date-of-birth tables) set `nowrap` on every td, which the
   message inherits and which would run a long line off the right edge.
   `100cqi` is only used where `:has()` exists, because without it no container
   is made and `cqi` would resolve against the whole viewport (Firefox 110-120
   knows container units but not `:has()`). Everywhere else the viewport-based
   width stays: close, and always inside the screen.
   (A `.table-stack-mobile` table is a block below 768px, so the cell is the
   scroller's width less the row's 1rem side padding, and `min()` picks the
   cell.) */
.table-responsive:has(td[colspan] > .rr-empty) {
  container-type: inline-size;
}
.table-responsive td[colspan]:has(> .rr-empty) {
  padding-left: 0;
  padding-right: 0;
}
.table-responsive td[colspan] > .rr-empty {
  position: sticky;
  left: 0;
  width: min(100%, calc(100vw - 3rem));
  white-space: normal;
}
@supports selector(:has(*)) {
  .table-responsive td[colspan] > .rr-empty {
    width: min(100%, 100cqi);
  }
}

/* Stat tile — the dashboard number block. */
.rr-stat { display: flex; flex-direction: column; gap: var(--rr-space-1); }
.rr-stat-label {
  font-size: 0.6875rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--rr-grey-500);
}
.rr-stat-value {
  font-size: 1.75rem; font-weight: 650; line-height: 1.1;
  color: var(--rr-grey-900); font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}
.rr-stat-meta { font-size: 0.8125rem; color: var(--rr-grey-500); }

/* Filter bar — the row of selects above a list page. */
.rr-filter-bar {
  display: flex; flex-wrap: wrap; gap: var(--rr-space-3);
  align-items: flex-end;
  padding: var(--rr-space-4);
  background-color: #ffffff;
  border: 1px solid var(--rr-grey-200);
  border-radius: var(--rr-radius-lg);
  margin-bottom: var(--rr-space-4);
}
.rr-filter-field { display: flex; flex-direction: column; gap: 0.25rem; min-width: 10rem; flex: 1 1 10rem; }
.rr-filter-actions { display: flex; gap: var(--rr-space-2); flex: 0 0 auto; }

/* Section heading between cards. */
.rr-section-title {
  font-size: 0.6875rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--rr-grey-500);
  margin: var(--rr-space-5) 0 var(--rr-space-3);
}
/* Leading a page it sits under the header's own spacing, like any other
   first block — the section gap is for separating sections, not for
   separating the first one from the title. */
.rr-section-title:first-child { margin-top: 0; }

/* =============================================================================
   TYPE ROLES — the two faces the redesign adds, as classes.

   A template says `.rr-display` or `.rr-mono`; it never names Archivo or
   JetBrains Mono, for the same reason it never writes a hex colour. Both
   families are tokens (`--rr-font-display` / `--rr-font-mono`).
   ============================================================================= */
.rr-display {
  font-family: var(--rr-font-display);
  font-weight: 700;
  letter-spacing: -0.015em;
}
/* Big numerals — KPI values, hero scores. Tabular so a changing figure does
   not shift the layout under it. */
.rr-display-num {
  font-family: var(--rr-font-display);
  font-weight: 800;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  line-height: 1.05;
}
.rr-mono {
  font-family: var(--rr-font-mono);
  font-variant-numeric: tabular-nums;
}

/* =============================================================================
   WIDGET LAYER — the dashboard vocabulary from the approved 2a/2b/2c designs.

   These are the pieces the three redesigned dashboards are built from. They
   live here rather than in per-page <style> blocks so the next dashboard gets
   them for free, and so the brand change above reaches them.

   Everything sizes in rem and every grid has a mobile collapse: the pages
   these serve are checked at 390 x 844 like any other.
   ============================================================================= */

/* ---- The widget grid -------------------------------------------------------
   RATIO COLUMNS, not track columns. `.rr-w-N` is a 12ths RATIO, not a fixed
   track, and the line is a wrapping flex row rather than a 12-column grid.

   WHY IT IS NOT A CSS GRID ANY MORE
   Widgets here are conditionally rendered — a flag is off, a queryset is
   empty, the user is a guest — so which widgets appear on a page varies. With
   fixed 12-column tracks there is NO assignment of spans that tiles for every
   subset: whatever is left over at the end takes a whole row to say one
   thing. Measured before this change: the referee dashboard ended on a row at
   32.6% fill and match details had rows at 83.2% and 66.3%.

   With `flex-grow: N` a line's widths are a function only of the items
   ACTUALLY on that line, so a line whose spans total S renders as an
   S-column layout at full width, in the designed ratio. The grid never needs
   to know which widgets rendered.

   Rows that already tiled are untouched: when a line's spans total 12 its
   free space is exactly zero, so the flex basis IS the old track width to the
   pixel. Only short lines change, and they change from "ragged" to "full".

   The trade, stated plainly: vertical seams on a partial line no longer align
   with the seams of a full line above it. That is unavoidable — you cannot
   both fill a 10-of-12 line and keep its seams on the 12-column rhythm. A
   filled line beats an aligned-but-ragged one on a dashboard.

   Collapses to halves on tablets and one column on phones. */
.rr-grid {
  --rr-grid-gap: var(--rr-space-4);
  display: flex;
  flex-wrap: wrap;
  gap: var(--rr-grid-gap);
  align-items: stretch;
}

/* basis = N/12 of the line, minus this item's share of the gap; grow = N so
   leftover space on a short line is handed out in the same ratio.

   THE SCALE IS COMPLETE, 1 TO 12, deliberately. It used to start at 3, and
   the classes it did not define were not an error anyone could see: a card
   marked `.rr-w-2` matched no rule, fell back to `flex: 0 1 auto`, and sized
   itself from its own text — so a line of six of them came out 291px short
   of its container and read as a ragged right margin rather than as a missing
   CSS rule. A ratio vocabulary with holes in it is worse than no vocabulary,
   because the hole is invisible at the call site. */
.rr-w-1  { flex: 1 1 calc(8.3333%  - var(--rr-grid-gap)); min-width: 0; }
.rr-w-2  { flex: 2 1 calc(16.6667% - var(--rr-grid-gap)); min-width: 0; }
.rr-w-3  { flex: 3 1 calc(25%      - var(--rr-grid-gap)); min-width: 0; }
.rr-w-4  { flex: 4 1 calc(33.3333% - var(--rr-grid-gap)); min-width: 0; }
.rr-w-5  { flex: 5 1 calc(41.6667% - var(--rr-grid-gap)); min-width: 0; }
.rr-w-6  { flex: 6 1 calc(50%      - var(--rr-grid-gap)); min-width: 0; }
.rr-w-7  { flex: 7 1 calc(58.3333% - var(--rr-grid-gap)); min-width: 0; }
.rr-w-8  { flex: 8 1 calc(66.6667% - var(--rr-grid-gap)); min-width: 0; }
.rr-w-9  { flex: 9 1 calc(75%      - var(--rr-grid-gap)); min-width: 0; }
.rr-w-10 { flex: 10 1 calc(83.3333% - var(--rr-grid-gap)); min-width: 0; }
.rr-w-11 { flex: 11 1 calc(91.6667% - var(--rr-grid-gap)); min-width: 0; }
.rr-w-12 { flex: 12 1 100%; min-width: 0; }

@media (max-width: 1199.98px) {
  /* The narrow ratios pair up rather than going full width — four `.rr-w-2`
     tiles on one line become two lines of two, not four full-width bars. */
  .rr-w-1, .rr-w-2, .rr-w-3 { flex-basis: calc(50% - var(--rr-grid-gap)); }
  .rr-w-4, .rr-w-5, .rr-w-6, .rr-w-7,
  .rr-w-8, .rr-w-9, .rr-w-10, .rr-w-11 { flex-basis: 100%; }
}
@media (max-width: 767.98px) {
  .rr-grid { --rr-grid-gap: var(--rr-space-3); }
  .rr-grid > * { flex-basis: 100%; }
}

/* Panels that set `height: 100%` for the old grid must not keep it here: in a
   flex line `align-items: stretch` already sizes them, and a percentage
   height against a stretched (auto) line resolves to the content height, so
   the panel stops stretching and its row-mate towers over it. Measured: a
   hero/KPI pair came out 420.6px vs 254.7px with the height, 300.6 vs 300.6
   without it. */
.rr-grid > .rr-hero,
.rr-grid > .rr-accent-panel,
.rr-grid > .rr-kpi { height: auto; }

/* A grid child that is a plain Tabler card. `.card` has `margin-bottom` from
   Tabler's own rules, which in a flex line shows up as a gap under one card
   and not its neighbour. The line's `gap` already provides the spacing. */
.rr-grid > .card { margin-bottom: 0; }

/* A LAZY SLOT — a grid child that holds a card it does not own, because the
   card arrives later over AJAX and replaces whatever placeholder was there.
   The slot takes the ratio class; this makes the card inside it fill the slot
   so a lazy panel lines up with the panels beside it both before and after
   its content lands. */
/* A STACK — a grid child that holds SEVERAL cards, one above the other, and
   takes a ratio for the pair. The alternative, a nested `.row`, lays its
   children out side by side inside the third of the width they were given. */
.rr-stack { display: flex; flex-direction: column; gap: var(--rr-space-4); }
.rr-stack > .card { margin-bottom: 0; }

.rr-slot { display: flex; flex-direction: column; }
.rr-slot > .card,
.rr-slot > .row,
.rr-slot > .rr-grid { flex: 1 1 auto; margin-bottom: 0; }

/* VERTICAL SLACK — who absorbs the difference when row-mates differ in height.
   Three policies, chosen per widget, because there is no single right answer:

   1. (default) stretch. Right when the card's content fills it.
   2. `.rr-fill-centre` — stretch the card, centre its content in the space.
      For a widget whose content is small and self-contained (a radial, a
      donut, a single figure): the card matches its neighbour so the layout
      stays even, and the content sits in the middle rather than stranded at
      the top under a field of white.
   3. `.rr-elastic` — stretch the card and let a designated child absorb the
      slack by scrolling. For LIST panels, where "more room" genuinely means
      "show more rows". Pair with `.rr-scroll-list` on the child that grows.

   `.rr-fit` (align-self: start) is deliberately gone. It did not remove the
   whitespace, it only chose which side of the card it landed on — and on the
   referee dashboard it put a 318px hole in the middle of the page. */
.rr-fill-centre > .card-body {
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.rr-elastic > .card-body {
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.rr-scroll-list {
  flex: 1 1 0;
  min-height: 10rem;
  overflow-y: auto;
  overscroll-behavior: contain;
}
/* A scroller is a keyboard-reachable region; give it a visible focus ring
   rather than relying on the browser's default on a div. */
.rr-scroll-list:focus-visible { outline: none; box-shadow: var(--rr-ring); }
@media (max-width: 767.98px) {
  /* Stacked, nothing is taller than anything else, so a scroller inside a
     card would trap the page scroll for no benefit. */
  .rr-scroll-list { overflow-y: visible; min-height: 0; flex: 0 1 auto; }
}

/* ---- KPI tile --------------------------------------------------------------
   Label, big number, sub-label and a sparkline, in a card. */
.rr-kpi {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
  /* Most KPI tiles are <a>, and an <a> with no colour of its own inherits the
     link blue. Every named part below sets its own ink, but anything a
     template puts in a tile directly would come out blue otherwise. */
  color: var(--rr-grey-900);
  padding: var(--rr-space-4);
  background-color: #ffffff;
  border: 1px solid var(--rr-grey-200);
  border-radius: var(--rr-radius-lg);
  box-shadow: var(--rr-shadow-xs);
  height: 100%;
}
.rr-kpi-top { display: flex; align-items: center; justify-content: space-between; gap: var(--rr-space-2); }
.rr-kpi-label {
  font-size: 0.65625rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--rr-grey-500);
}
.rr-kpi-value { font-size: 2rem; color: var(--rr-grey-900); }
.rr-kpi-bottom {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: var(--rr-space-2); margin-top: auto;
}
.rr-kpi-sub { font-size: 0.71875rem; color: var(--rr-grey-500); }

/* The value slot when the value is a SHORT STRING rather than a figure — a
   fixture, a date, a name. `.rr-kpi-value` is sized for a numeral; a fixture
   set at 2rem wraps to three lines and stops looking like a tile. Same slot,
   same weight, prose size. */
.rr-kpi-lead {
  font-size: 0.9375rem; font-weight: 600; line-height: 1.35;
  color: var(--rr-grey-900);
}

/* Delta chip — the up/down pill beside a KPI label. */
.rr-delta {
  font-size: 0.65625rem; font-weight: 600;
  padding: 0.1875rem 0.375rem; border-radius: var(--rr-radius-sm);
  white-space: nowrap;
}
.rr-delta-up   { background-color: var(--rr-success-bg); color: var(--rr-success); }
.rr-delta-down { background-color: var(--rr-danger-bg);  color: var(--rr-danger); }
.rr-delta-flat { background-color: var(--rr-grey-100);   color: var(--rr-grey-600); }

/* ---- Filled KPI tile -------------------------------------------------------
   A tile whose whole surface carries a status colour (yellow cards, red
   cards). The caller supplies the fill as a theme token; everything else has
   to step out of its way — the white background, the grey label and the
   grey-900 numeral would each fight it.

   Two ink policies, because the fills differ in luminance: `.rr-kpi-on-dark`
   for a fill dark enough to need white text, and the default for a light one,
   which keeps the near-black ink and would be unreadable reversed. */
.rr-kpi-filled {
  background-color: transparent;          /* the inline fill is the surface */
  border-color: transparent;
  /* White by default, because every status token a caller can pass here
     (--rr-warning, --rr-danger, --rr-success) is an ink colour chosen to be
     legible ON white and is therefore dark. `color: inherit` was the first
     attempt and came out LINK BLUE on a gold tile: these tiles are anchors,
     and inherit on an <a> with no colour set resolves to the link colour. */
  color: #ffffff;
  /* A fixed scrim over whatever fill the caller passed. The status tokens are
     chosen for legibility as INK on white, so as a surface they sit in the
     mid-tones: white on --rr-warning measures 3.87:1, which clears AA for the
     32px numeral and fails it for the 10px label. The tile cannot darken a
     colour it is handed as an inline style, but it can lay a constant scrim
     over it — 14% black takes --rr-warning to 4.9:1 and leaves the hue
     recognisable. Applied as an inset shadow so it covers the padding box
     without another element. */
  box-shadow: inset 0 0 0 100vmax rgba(0, 0, 0, 0.14), var(--rr-shadow-xs);
}
.rr-kpi-filled .rr-kpi-label,
.rr-kpi-filled .rr-kpi-sub { color: inherit; opacity: 0.88; }
.rr-kpi-filled .rr-kpi-value,
.rr-kpi-filled .rr-kpi-lead { color: inherit; }

/* For a genuinely PALE fill, where white would vanish. Nothing passes one
   today; it exists so a caller that does has somewhere to say so rather than
   reaching for an inline colour. */
.rr-kpi-on-light { color: var(--rr-grey-900); }

/* On a filled tile a tinted delta chip reads as a second, unrelated status.
   A translucent overlay in the tile's own ink stays legible on either fill
   without introducing a colour that is not already there. */
.rr-delta-on-fill {
  background-color: rgba(255, 255, 255, 0.28);
  color: inherit;
}
.rr-kpi-on-light .rr-delta-on-fill {
  background-color: rgba(0, 0, 0, 0.12);
}

/* ---- Sparkline -------------------------------------------------------------
   Bars are sized by an inline `height` percentage — the one place a template
   writes a value, because it IS the datum. Colour still comes from here. */
.rr-spark { display: flex; align-items: flex-end; gap: 2px; height: 1.25rem; }
.rr-spark > span {
  width: 4px; border-radius: 2px; flex: 0 0 auto;
  background-color: var(--rr-primary-300);
  min-height: 2px;
}
.rr-spark-primary > span { background-color: var(--rr-primary-400); }
.rr-spark-accent  > span { background-color: var(--rr-accent-border); }

/* ---- Hero panel ------------------------------------------------------------
   The full-bleed brand block: next match, match details, milestones. Shares
   --rr-panel-header-bg with the finance/performance panels so the two never
   drift apart. The glow is a child so it can sit under the content without
   a stacking-context fight. */
.rr-hero {
  position: relative;
  overflow: hidden;
  border-radius: var(--rr-radius-lg);
  padding: var(--rr-space-5);
  background: var(--rr-panel-header-bg);
  color: var(--rr-on-primary);
  height: 100%;
}
/* Ambient light from the top-right, NOT a glowing dot.

   This was a 200px circle at 20% gold tucked into the corner. On a
   1400px-wide panel that reads as a smudge or a rendering artefact rather
   than as lighting — it has a visible edge and no relationship to anything
   else in the panel. A wide, low-opacity ellipse anchored off the corner
   falls off before it reaches any content, so it lifts the surface without
   ever announcing itself. */
.rr-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(58% 130% at 88% -10%,
      color-mix(in srgb, var(--rr-accent) 12%, transparent) 0%,
      color-mix(in srgb, var(--rr-accent) 4%, transparent) 38%,
      transparent 68%);
}
/* Browsers without color-mix get no wash rather than a black block —
   `radial-gradient` with an invalid colour drops the whole declaration. */
@supports not (color: color-mix(in srgb, red 50%, blue)) {
  .rr-hero::after { background: none; }
}
.rr-hero > * { position: relative; }
.rr-hero-eyebrow {
  font-size: 0.65625rem; font-weight: 600; letter-spacing: 0.07em;
  text-transform: uppercase; color: var(--rr-accent-text, var(--rr-accent));
}
.rr-hero-muted { color: color-mix(in srgb, var(--rr-on-primary) 72%, transparent); }
@supports not (color: color-mix(in srgb, red 50%, blue)) {
  .rr-hero-muted { color: rgba(255, 255, 255, 0.72); }
}
/* Pill on a hero — "In 3 days", "Confirmed". */
.rr-hero-chip {
  display: inline-flex; align-items: center; gap: 0.375rem;
  font-size: 0.65625rem; font-weight: 600;
  padding: 0.25rem 0.625rem; border-radius: 999px;
  background-color: rgba(255, 255, 255, 0.14);
  color: var(--rr-on-primary);
}
/* The hero's three bands: eyebrow row, the match itself, then a meta strip.

   The panel is wide, and its content used to sit in two clumps against the
   left and right edges with a large dead gap between them. Splitting it into
   bands gives every line a reason to be where it is: the top row balances
   the eyebrow against the status chip, the main row sets the teams and the
   kickoff on one baseline, and the meta strip collects the things that were
   previously floating on their own. */
.rr-hero-top {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--rr-space-3); flex-wrap: wrap;
  margin-bottom: var(--rr-space-4);
}
.rr-hero-main {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: var(--rr-space-5); flex-wrap: wrap;
}
/* The fixture. Baseline-aligned so the two club names and the "v" sit on one
   line however long the names are. */
.rr-hero-fixture {
  display: flex; align-items: baseline; gap: 0.75rem;
  flex-wrap: wrap; min-width: 0;
}
.rr-hero-team {
  font-family: var(--rr-font-display);
  font-weight: 700; font-size: 1.5rem; letter-spacing: -0.015em;
  line-height: 1.15;
}
.rr-hero-v {
  font-family: var(--rr-font-display);
  font-weight: 600; font-size: 1rem; color: var(--rr-accent-text, var(--rr-accent));
  flex: 0 0 auto;
}
/* Kickoff block, right-aligned against the fixture. */
.rr-hero-when { text-align: right; flex: 0 0 auto; }
.rr-hero-time {
  font-family: var(--rr-font-display);
  font-weight: 800; font-size: 1.625rem; letter-spacing: -0.02em;
  line-height: 1.1; font-variant-numeric: tabular-nums;
}
/* The strip along the bottom: role, venue, anything else one line each.
   A hairline rather than a gap, so it reads as part of the panel instead of
   as a pill that floated loose. */
.rr-hero-meta {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 0.5rem 1.25rem;
  margin-top: var(--rr-space-4);
  padding-top: var(--rr-space-3);
  border-top: 1px solid rgba(255, 255, 255, 0.14);
  font-size: 0.8125rem;
}
.rr-hero-meta-item { display: inline-flex; align-items: center; gap: 0.4375rem; }
.rr-hero-meta-item i { color: var(--rr-accent-text, var(--rr-accent)); font-size: 0.9375rem; }

/* ---- Actions on a hero -----------------------------------------------------
   The round widget carries the release workflow's own destinations, which
   used to be a row of seven identical outline buttons above the grid. Here
   they sit on the panel they belong to, and exactly one of them — the action
   that moves the round forward — is filled. Seven equal buttons said nothing
   about which one mattered. */
.rr-hero-action {
  display: inline-flex; align-items: center; gap: 0.4375rem;
  font-size: 0.8125rem; font-weight: 600;
  padding: 0.4375rem 0.75rem;
  border-radius: var(--rr-radius-sm);
  color: var(--rr-on-primary);
  background-color: rgba(255, 255, 255, 0.12);
  text-decoration: none;
  transition: background-color 0.15s ease;
}
.rr-hero-action:hover,
.rr-hero-action:focus-visible {
  background-color: rgba(255, 255, 255, 0.2);
  color: var(--rr-on-primary);
  /* Out-ranks `a:hover` but never said so for the underline, which then fell
     through from Tabler's link rule and underlined a pill button on hover. */
  text-decoration: none;
}
.rr-hero-action i { color: var(--rr-accent-text, var(--rr-accent)); font-size: 0.9375rem; }
.rr-hero-action-primary {
  background-color: var(--rr-accent);
  color: var(--rr-on-accent);
}
.rr-hero-action-primary:hover,
.rr-hero-action-primary:focus-visible {
  background-color: var(--rr-accent);
  color: var(--rr-on-accent);
  filter: brightness(1.07);
}
/* On the filled action the gold icon would vanish into the gold fill. */
.rr-hero-action-primary i { color: var(--rr-on-accent); }

/* ---- The referee hero's "new appointment" state ---------------------------
   widgets/next_match_hero.html. The same panel as "Next confirmed match", so
   the page keeps one hero, marked by an accent stripe down its leading edge:
   this one is waiting on the official. The action is a full touch target
   (44px) because it is the one thing on the page they came to do. */
.rr-hero-new {
  box-shadow: inset 4px 0 0 var(--rr-accent);
  /* The attention band deep-links to #new-appointment; without this the
     sticky top bar covers the eyebrow and the deadline chip on arrival. */
  scroll-margin-top: 5rem;
}
.rr-hero-new-actions {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--rr-space-2) var(--rr-space-3);
  margin-top: var(--rr-space-4);
}
.rr-hero-new-actions .rr-hero-action {
  min-height: 44px;
  padding: 0.5rem 1.125rem;
  font-size: 0.9375rem;
}
.rr-hero-new-confirmed {
  display: block;
  margin-top: var(--rr-space-3);
  padding-top: var(--rr-space-3);
  border-top: 1px solid rgba(255, 255, 255, 0.14);
  font-size: 0.8125rem;
  text-decoration: none;
  overflow-wrap: anywhere;
}
.rr-hero-new-confirmed:hover,
.rr-hero-new-confirmed:focus-visible { color: var(--rr-on-primary); text-decoration: none; }
@media (max-width: 575.98px) {
  .rr-hero-new-actions .rr-hero-action { flex: 1 1 100%; justify-content: center; }
}

/* ---- Round progress bar ----------------------------------------------------
   One round's appointments by state: out the door, ready to go, still being
   drafted. Two filled segments on a track rather than three, because "still
   drafting" is the REMAINDER — drawing it as its own segment invites a
   rounding gap between the three widths that reads as a fourth state. */
.rr-round-bar {
  display: flex;
  height: 0.5rem;
  margin-top: var(--rr-space-4);
  border-radius: 999px;
  overflow: hidden;
  background-color: rgba(255, 255, 255, 0.16);
}
.rr-round-seg { display: block; height: 100%; }
.rr-round-seg-out { background-color: var(--rr-accent); }
.rr-round-seg-ready { background-color: rgba(255, 255, 255, 0.55); }
.rr-round-legend {
  display: flex; flex-wrap: wrap; gap: 0.375rem 1.125rem;
  margin-top: var(--rr-space-2);
  font-size: 0.78125rem;
  color: color-mix(in srgb, var(--rr-on-primary) 76%, transparent);
}
@supports not (color: color-mix(in srgb, red 50%, blue)) {
  .rr-round-legend { color: rgba(255, 255, 255, 0.76); }
}
.rr-round-legend b { color: var(--rr-on-primary); font-weight: 700; }

/* ---- Quick links -----------------------------------------------------------
   A quiet strip of secondary destinations, under a label. Deliberately NOT
   buttons: as seven `btn-outline-primary` above the widget grid they carried
   the same visual weight as the page's one real action, so the row said
   nothing about which of them mattered. */
.rr-quicklinks {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 0.375rem 0.5rem;
  margin-bottom: var(--rr-space-4);
}
.rr-quicklinks-label {
  font-size: 0.65625rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--rr-grey-500);
  margin-right: 0.25rem;
}
.rr-quicklink {
  display: inline-flex; align-items: center; gap: 0.375rem;
  font-size: 0.8125rem; font-weight: 500;
  padding: 0.3125rem 0.625rem;
  border-radius: var(--rr-radius-sm);
  color: var(--rr-grey-700);
  background-color: var(--rr-grey-100);
  text-decoration: none;
  transition: background-color 0.15s ease, color 0.15s ease;
}
.rr-quicklink:hover,
.rr-quicklink:focus-visible {
  background-color: var(--rr-primary-50);
  color: var(--rr-primary-700);
}
.rr-quicklink i { font-size: 0.875rem; color: var(--rr-grey-500); }
.rr-quicklink:hover i, .rr-quicklink:focus-visible i { color: var(--rr-primary-600); }

@media (max-width: 575.98px) {
  .rr-hero-main { align-items: flex-start; }
  .rr-hero-when { text-align: left; }
  .rr-hero-team { font-size: 1.25rem; }
  .rr-hero-time { font-size: 1.375rem; }
}

.rr-hero-chip-accent {
  background-color: var(--rr-accent);
  color: var(--rr-on-accent);
}

/* ---- Accent panel — the milestones block: brand fill, gold eyebrow. ------- */
.rr-accent-panel {
  position: relative; overflow: hidden;
  border-radius: var(--rr-radius-lg);
  padding: var(--rr-space-4);
  background: var(--rr-panel-header-bg);
  color: var(--rr-on-primary);
  height: 100%;
}

/* ---- Radial progress ------------------------------------------------------
   An SVG donut with the figure centred in it. The dash length is the datum
   and is set inline; the geometry and colours are here. */
.rr-radial { position: relative; width: 8.125rem; height: 8.125rem; }
.rr-radial svg { width: 100%; height: 100%; transform: rotate(-90deg); }
.rr-radial-track { fill: none; stroke: var(--rr-grey-100); }
.rr-radial-fill  { fill: none; stroke: var(--rr-primary-500); stroke-linecap: round; }
.rr-radial-fill-accent { stroke: var(--rr-accent); }
/* On a hero/accent panel the track has to read against a dark fill. */
.rr-accent-panel .rr-radial-track,
.rr-hero .rr-radial-track { stroke: rgba(255, 255, 255, 0.16); }
.rr-radial-centre {
  position: absolute; inset: 0;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  text-align: center;
}

/* ---- Heatmap — officials x days availability grid. ----------------------

   A <table> (2026-09-27). It was a flex grid of divs, 1,840 of them on
   production data, each carrying the official's name in its tooltip and a
   visually-hidden label — most of the manager dashboard's weight. Now a
   `<th scope="row">` names the official once per row (which is also how a
   screen reader knows whose cell it is) and a cell is one short `<td>`: a
   state class and a tooltip.

   Border-spacing is the flex grid's gap — 0.5rem between columns, 0.375rem
   between rows — but unlike a gap it also pads the table's OUTER edge. The
   negative margins take the left and top of that back out, and the scroller's
   negative right margin (below) makes room for the right, so every cell sits
   where the flex grid put it. `table-layout: fixed` gives the ten day columns
   one width each, as `flex: 1 1 0` did; the name column takes its width from
   the first row's label cell. 35rem = the old 34rem floor plus 1rem of outer
   spacing: below it the grid scrolls sideways instead of squeezing. */
.rr-heatmap {
  border-collapse: separate;
  border-spacing: 0.5rem 0.375rem;
  table-layout: fixed;
  width: max(calc(100% + 0.5rem), 35rem);
  margin: -0.375rem 0 0 -0.5rem;
}
.rr-heatmap th,
.rr-heatmap td { padding: 0; }
.rr-heatmap-label,
.rr-heatmap th[scope="row"] {
  width: 7rem;
  text-align: left;
  font-size: 0.78125rem; font-weight: 600; color: var(--rr-grey-900);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* A day cell. Its state class (`c-a` …, below) gives the colour and draws the
   glyph; the `title` is its tooltip and, because the glyph has empty alt text,
   what a screen reader announces. */
.rr-heatmap tbody td {
  height: 1.875rem;
  border-radius: var(--rr-radius-sm);
  text-align: center;
  vertical-align: middle;
  font-size: 0.75rem; font-weight: 700;
}
/* The date box inside each day heading (`.rr-heatmap-date` below does most of
   the work; this gives it the cells' rounding, for today's tint). */
.rr-heatmap-cell {
  flex: 1 1 0; min-width: 0; height: 1.875rem;
  border-radius: var(--rr-radius-sm);
  display: grid; place-items: center;
  font-size: 0.75rem; font-weight: 700;
  position: relative;
}
/* The four states. Named for what they MEAN — a template must not pick the
   colour, only the state. `.rr-cell-*` is the long form (legend swatches, the
   referee's month calendar); `c-*` is the heatmap cell's short form, written
   1,840 times a page, so the bytes matter. One rule each, so they cannot drift. */
.rr-cell-appointed,   .rr-heatmap .c-a { background-color: var(--rr-primary-500); color: #ffffff; }
.rr-cell-available,   .rr-heatmap .c-v { background-color: var(--rr-primary-100); color: var(--rr-primary-700); }
.rr-cell-tentative,   .rr-heatmap .c-t { background-color: var(--rr-warning-bg);  color: var(--rr-warning); }
.rr-cell-unavailable, .rr-heatmap .c-u { background-color: var(--rr-danger-bg);   color: var(--rr-danger); }
.rr-cell-empty,       .rr-heatmap .c-e { background-color: var(--rr-grey-50);     color: var(--rr-grey-400); }
/* Available for local games only (a "travel only" block-out, 2026-10-04).
   The second selector outranks Tabler's table-cell background, which
   otherwise paints a calendar <td> white. */
.rr-cell-local,
.table > :not(caption) > * > .rr-cell-local { background-color: var(--rr-warning-bg); color: var(--rr-warning); }
/* Each state's glyph, as the cell showed it before (A, tick, half circle,
   cross; nothing for "no data"). Generated content with EMPTY alt text (the
   `/ ""`), so it is seen but not read: the cell's accessible name falls back to
   its `title` — "Available", or the reason for a block — which is what the
   visually-hidden label used to say. The first declaration is the fallback for
   a browser without alt text on `content`, which drops the second. */
.rr-heatmap .c-a::before { content: "A"; content: "A" / ""; }
.rr-heatmap .c-v::before { content: "\2713"; content: "\2713" / ""; }
.rr-heatmap .c-t::before { content: "\25D1"; content: "\25D1" / ""; }
.rr-heatmap .c-u::before { content: "\2715"; content: "\2715" / ""; }

/* The grid scrolls rather than showing only what fits. A manager works a
   panel at a time, so the useful thing is every official grouped, not the six
   busiest — and 60 rows do not fit a dashboard card.

   `max-height` in rem rather than a row count: the row height is the one
   thing here that is already a token, and a viewport-relative height would
   make the card's size depend on the window rather than on its content. */
.rr-heatmap-scroll {
  max-height: 24rem;
  overflow-y: auto;
  /* Ten day columns have a floor below which a cell cannot hold its glyph.
     A narrow card scrolls sideways INSIDE itself rather than squeezing the
     cells or pushing the page wider. */
  overflow-x: auto;
  /* The sticky header below resolves against THIS box, so the scroller has
     to be the positioning context as well as the scroll container. */
  position: relative;
  /* Room for the table's right-hand border-spacing (see .rr-heatmap): the
     scroller reaches 0.5rem into the card's padding, so the last day column
     still ends where the card's content does and nothing overflows. */
  margin-right: -0.5rem;
}
/* The date row stays put while the body moves: a grid of ticks and crosses
   means nothing once its column headings have scrolled away. Sticky on the
   CELLS, not the row, which is the form every engine honours. */
.rr-heatmap thead th,
.rr-heatmap thead td {
  position: sticky;
  top: 0;
  z-index: 2;
  /* An opaque ground, or the rows scroll visibly through the headings — and
     a shadow the width of the spacing to the right and below, which would
     otherwise show them passing behind. */
  background-color: #ffffff;
  box-shadow: 0.5rem 0 0 #ffffff, 0 0.375rem 0 #ffffff, 0.5rem 0.375rem 0 #ffffff;
  /* Top-aligned, not centred: a match day carries a fixture-count badge and
     is taller, and centring put its day name higher than its neighbours'. */
  vertical-align: top;
}
.rr-heatmap-date {
  background: transparent;
  font-weight: 600;
  font-size: 0.6875rem;
  color: var(--rr-grey-500);
  /* Day name over day number — ten columns cannot each carry "26 Sep". */
  height: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.0625rem;
  line-height: 1.1;
  padding: 0.125rem 0;
}
.rr-heatmap-dow { font-size: 0.5625rem; text-transform: uppercase; letter-spacing: 0.04em; }
.rr-heatmap-dom { font-size: 0.8125rem; color: var(--rr-grey-900); font-variant-numeric: tabular-nums; }
.rr-heatmap-date-weekend .rr-heatmap-dom { color: var(--rr-primary-700); }
/* Today, so the grid reads as "from here" rather than as a list of dates. */
.rr-heatmap-date-today { background-color: var(--rr-primary-50); }
.rr-heatmap-date-today .rr-heatmap-dow { color: var(--rr-primary-700); }
/* How many fixtures fall on the day. The ten days include ones with no match
   on them — this is what tells the match days apart without hiding the rest. */
.rr-heatmap-fixtures {
  font-size: 0.5625rem; font-weight: 700;
  min-width: 1rem; padding: 0 0.25rem; border-radius: 999px;
  background-color: var(--rr-primary-500); color: #ffffff;
}

/* A panel's name, as a divider inside the grid — the same reading the manager
   availability overview gives with its section rows.

   NOT sticky, though the first attempt made it so. Several sticky elements
   sharing one `top` do not push each other out of the way: each sticks
   independently, so NRA's heading was still pinned at the top while FARA's
   rows scrolled past under it, and the pile grew with every panel. Only the
   date row is pinned; the dividers scroll with the rows they label, which is
   what the overview page does too. */
.rr-heatmap-section {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.3125rem 0.5rem;
  margin-top: 0.25rem;
  border-radius: var(--rr-radius-sm);
  background-color: var(--rr-grey-100);
}
.rr-heatmap-section-name {
  font-size: 0.6875rem; font-weight: 700;
  letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--rr-grey-700);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rr-heatmap-section-count {
  font-size: 0.625rem; font-weight: 700;
  padding: 0.0625rem 0.375rem; border-radius: 999px;
  background-color: #ffffff;
  color: var(--rr-grey-600);
  flex: 0 0 auto;
}

/* Names stay put while the days scroll sideways. On a phone the ten days are
   wider than the card; without this, scrolling to Friday scrolled every name
   and panel off the left edge and left a grid of ticks attached to no one.
   Opaque ground, and a shadow the width of the row gap, so the cells pass
   UNDER the name rather than showing through it or the gap beside it. */
.rr-heatmap-scroll .rr-heatmap th[scope="row"],
.rr-heatmap-scroll .rr-heatmap-label {
  position: sticky;
  left: 0;
  z-index: 1;
  background-color: #ffffff;
  box-shadow: 0.5rem 0 0 #ffffff;
  /* As tall as the cells it covers: centred text alone is a line shorter,
     and the cells scrolling under it showed above and below the name. */
  line-height: 1.875rem;
}
/* The corner is pinned both ways, so it sits over the days (sideways) and the
   names (downwards), and covers the spacing on both of its open sides. */
.rr-heatmap-scroll thead .rr-heatmap-label {
  z-index: 3;
  box-shadow: 0.5rem 0 0 #ffffff, 0 0.375rem 0 #ffffff, 0.5rem 0.375rem 0 #ffffff;
}
/* The panel's name and count ride along the same way, inside their band. */
.rr-heatmap-section-key {
  position: sticky;
  left: 0.5rem;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
}

/* Legend for the heatmap and the availability calendar. */
.rr-legend { display: flex; flex-wrap: wrap; gap: 0.75rem; font-size: 0.6875rem; color: var(--rr-grey-600); }
.rr-legend-item { display: flex; align-items: center; gap: 0.3125rem; }
.rr-legend-swatch { width: 0.6875rem; height: 0.6875rem; border-radius: 3px; flex: 0 0 auto; }

/* On a phone the heatmap is the one widget that cannot usefully collapse —
   ten days in 390px is a 25px cell. It scrolls instead of squeezing (the
   34rem floor above); here it only gets shorter and its names narrower. */
@media (max-width: 767.98px) {
  .rr-heatmap-scroll { max-height: 20rem; }
  .rr-heatmap-label,
  .rr-heatmap th[scope="row"] { width: 5.5rem; }
}

/* The heatmap's lazy placeholder (widgets/availability_outlook_slot.html) is
   the loaded card's height, so the widgets below it do not jump when a slow
   fetch lands. The loaded card is the scroller's max-height above plus about
   150px of header, padding and the overview link under it: measured at
   production shape, 533px at 1440 and 470px at 390. The placeholder used to
   be 302px / 138px. Keep these in step with the two max-heights. */
.rr-heatmap-placeholder { min-height: calc(24rem + 149px); }
@media (max-width: 767.98px) {
  .rr-heatmap-placeholder { min-height: calc(20rem + 150px); }
}

/* ---- Availability calendar — a month of day cells. ---------------------- */
.rr-cal { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 0.3125rem; }
.rr-cal-dow {
  text-align: center; font-size: 0.625rem; font-weight: 600;
  color: var(--rr-grey-400);
}
.rr-cal-day {
  aspect-ratio: 1;
  border-radius: var(--rr-radius-sm);
  display: grid; place-items: center;
  font-size: 0.6875rem; font-weight: 600;
  /* Same reason as .rr-heatmap-cell — these carry a visually-hidden label. */
  position: relative;
}

/* ---- Load bar — the official workload / fairness row. ------------------- */
.rr-load-row {
  display: flex; align-items: center; gap: 0.875rem;
  padding: 0.5rem 0;
  border-top: 1px solid var(--rr-grey-100);
}
.rr-load-name { width: 9.375rem; flex: 0 0 auto; display: flex; align-items: center; gap: 0.5625rem; min-width: 0; }
.rr-load-track {
  flex: 1 1 auto; height: 0.625rem; border-radius: 999px;
  background-color: var(--rr-grey-100); overflow: hidden; min-width: 3rem;
}
.rr-load-fill {
  height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, var(--rr-primary-400), var(--rr-primary-600));
}
@media (max-width: 767.98px) {
  .rr-load-row { flex-wrap: wrap; }
  .rr-load-name { width: 100%; }
  .rr-load-track { flex: 1 1 8rem; }
}

/* ---- Avatar initials ---------------------------------------------------- */
.rr-avatar {
  display: grid; place-items: center; flex: 0 0 auto;
  border-radius: 50%;
  width: 1.625rem; height: 1.625rem;
  font-family: var(--rr-font-display);
  font-weight: 700; font-size: 0.625rem;
  background-color: var(--rr-primary-100);
  color: var(--rr-primary-700);
}
.rr-avatar-lg { width: 2.125rem; height: 2.125rem; font-size: 0.75rem; }
.rr-avatar-accent { background-color: var(--rr-accent); color: var(--rr-on-accent); }
.rr-avatar-onhero { background-color: rgba(255, 255, 255, 0.14); color: var(--rr-on-primary); }

/* ---- Tile list — the compact "row per thing" list inside a widget. ------ */
.rr-tile-list { display: flex; flex-direction: column; }
.rr-tile-row {
  display: flex; align-items: center; gap: 0.625rem;
  padding: 0.5rem 0;
  border-top: 1px solid var(--rr-grey-100);
  min-width: 0;
}
.rr-tile-row:first-child { border-top: 0; }
/* A tile row that is a link. The list-group-item-action rows these replaced
   signalled it with a background; tile rows have no side padding, so a fill
   would butt against the text — the title takes the link colour instead. */
a.rr-tile-row { color: inherit; text-decoration: none; }
a.rr-tile-row:hover .rr-tile-title,
a.rr-tile-row:focus-visible .rr-tile-title { color: var(--rr-primary-600); text-decoration: underline; }
a.rr-tile-row:focus-visible { outline: none; box-shadow: var(--rr-ring); border-radius: var(--rr-radius-sm); }
.rr-tile-main { flex: 1 1 auto; min-width: 0; }
.rr-tile-title {
  font-size: 0.78125rem; font-weight: 600; color: var(--rr-grey-900);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rr-tile-meta { font-size: 0.6875rem; color: var(--rr-grey-500); }
/* A "view all" link inside a meta line. Without this it takes Bootstrap's
   link colour — the one blue thing on a card whose every other colour is a
   brand token. */
.rr-tile-meta a {
  color: var(--rr-primary-600);
  font-weight: 600;
  text-decoration: none;
}
.rr-tile-meta a:hover, .rr-tile-meta a:focus-visible {
  color: var(--rr-primary-700);
  text-decoration: underline;
}
/* The status stripe down the left of a fixture row. */
.rr-tile-stripe { width: 0.375rem; height: 2.25rem; border-radius: 3px; flex: 0 0 auto; opacity: 0.85; }

/* ---- Segmented control — the 7d / 14d / 30d / Season switch. ------------ */
.rr-segmented {
  display: inline-flex; gap: 2px; padding: 3px;
  background-color: var(--rr-grey-100);
  border-radius: var(--rr-radius);
}
.rr-segmented > .rr-seg {
  border: 0; cursor: pointer;
  font-size: 0.71875rem; font-weight: 600;
  padding: 0.3125rem 0.75rem;
  border-radius: var(--rr-radius-sm);
  background-color: transparent;
  color: var(--rr-grey-600);
  transition: background-color 0.12s ease, color 0.12s ease;
}
.rr-segmented > .rr-seg:hover { color: var(--rr-grey-900); }
.rr-segmented > .rr-seg.active {
  background-color: #ffffff;
  color: var(--rr-primary-600);
  box-shadow: var(--rr-shadow-xs);
}

/* ---- Save-on-change status ----------------------------------------------
   The line where core/js/autosave.js writes "Saved" (or why it didn't).
   Holds its line while empty, so the confirmation appearing — and fading
   four seconds later — does not move the text beneath the control twice. */
.rr-autosave-status { min-height: 1.25rem; }

/* ---- Placeholder widget -------------------------------------------------
   For the panels the design specifies but the data does not exist for yet —
   weather, kit clash, form/head-to-head. Deliberately legible as "not wired":
   a dashed border and a corner tag, so nobody mistakes sample figures for
   this match's. Remove the class, not the card, when the data lands. */
.rr-placeholder { position: relative; border-style: dashed !important; }
.rr-placeholder::before {
  content: "Preview";
  position: absolute; top: 0.5rem; right: 0.5rem;
  font-size: 0.5625rem; font-weight: 700;
  letter-spacing: 0.06em; text-transform: uppercase;
  padding: 0.125rem 0.375rem; border-radius: var(--rr-radius-sm);
  background-color: var(--rr-grey-100);
  color: var(--rr-grey-500);
  z-index: 1;
}
/* On a brand-filled panel the tag has to invert or it disappears. */
.rr-hero.rr-placeholder::before,
.rr-accent-panel.rr-placeholder::before {
  background-color: rgba(255, 255, 255, 0.16);
  color: var(--rr-on-primary);
}
.rr-placeholder-note {
  font-size: 0.6875rem;
  color: var(--rr-grey-500);
  margin-top: var(--rr-space-3);
}

/* A preview panel that ends up alone on a line grows to the full width, and a
   dashed "not wired up yet" card is then the widest thing on the page —
   exactly the wrong emphasis for the one panel that has no data in it. Laying
   its body out as a wrapping row fixes the emphasis without fighting the
   fill: at narrow widths it stays a stacked card, and once there is room the
   icon, the label and the note settle onto one line and it reads as a
   footnote strip. `flex-basis: 22rem` on the note is what triggers the
   change — below that it wraps to its own line, above it sits inline. */
.rr-placeholder > .card-body {
  display: flex;
  /* `row` STATED, not inherited. Most preview cards also carry
     `.rr-fill-centre`, whose `> .card-body` rule sets `flex-direction: column`
     at the same specificity and earlier in the file — so the direction stuck
     at column and the note's `flex-basis: 22rem` below became 22rem of
     HEIGHT. Every preview card on the match page rendered 505px tall with a
     field of white under three lines of text. */
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--rr-space-3) var(--rr-space-4);
}
.rr-placeholder > .card-body > .rr-placeholder-note {
  margin-top: 0;
  flex: 1 1 22rem;
  min-width: 0;
}

/* =============================================================================
   MOBILE
   Phone parity is a first-class requirement, not a fallback. Everything below
   768px is treated as the primary layout for that width, not a squeezed
   desktop.
   ============================================================================= */
@media (max-width: 767.98px) {
  /* Tap targets. Apple/Google both put the floor at 44px; Tabler's default
     .btn-sm is ~28px, and the app is full of them. */
  /* `display` is only changed on things that lay out their own contents.
     An <input>/<select> renders its value through browser-internal boxes, and
     making one a flex container is a known way to break <select> rendering in
     some engines — those get the height and nothing else. */
  .btn, .nav-link, .page-link {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }
  .dropdown-item { min-height: 44px; display: flex; align-items: center; }

  /* Pagination wraps instead of running off the screen.
     `.pagination` is a flex row with Bootstrap's default `nowrap`, and
     django_tables2/tabler.html renders EVERY page number — its ELLIPSIS branch
     is dead, because a plain `page_range` never yields one (that needs
     `get_elided_page_range`). At FA's 282 appointments that is 19 links plus
     prev/next, which pushed /appointments/all/ 215px past a 390px viewport.

     The demo fixture has 141 rows, so it renders 10 links, fits in 316px, and
     the whole-app sweep read this page as clean — the bug only exists at
     production row counts. Measured on the real element grown to 19 pages:
     215px of page overflow before this rule, 0 after. */
  .pagination {
    flex-wrap: wrap;
    row-gap: var(--rr-space-1);
    justify-content: center;
  }

  /* ...and the buttons a page styled itself, which never carry `.btn`:
     `.btn-remove` on the merit-list and weighting builders measured 28px,
     `.filter-btn` on the report card 38px. Height only — a native <button>
     already centres its own label, and forcing a display type on one is how
     you break a button the page laid out itself. `.btn-close` keeps its own
     size; it is a fixed glyph with its own hit-area convention. */
  button:not(.btn):not(.btn-close) { min-height: 44px; }
  .form-control, .form-select, .form-input { min-height: 44px; }
  /* A segment can be a link or a label as well as a button. */
  .rr-segmented > .rr-seg { min-height: 44px; display: inline-flex; align-items: center; }
  .btn-sm { min-height: 40px; }

  /* Reclaim horizontal space — on a 375px screen the desktop gutters cost
     ~12% of the viewport. */
  .container-fluid, .container-xl { padding-left: var(--rr-space-3); padding-right: var(--rr-space-3); }
  .card-body   { padding: var(--rr-space-4); }
  .card-header {
    /* Tabler's .card-header-tabs / -pills pull themselves out of the header by
       these variables (1rem / 1.5rem on .card), meant to cancel the padding
       exactly. Shrinking only the padding left every tab strip on a phone ~7px
       past the card's left edge and 3px above its top (W4). Keep them equal.
       Set here, not on .card, so .card-footer (which reads them too) is untouched. */
    --tblr-card-cap-padding-y: var(--rr-space-3);
    --tblr-card-cap-padding-x: var(--rr-space-4);
    padding: var(--tblr-card-cap-padding-y) var(--tblr-card-cap-padding-x);
  }
  .page-header { padding-top: var(--rr-space-4); padding-bottom: var(--rr-space-2); }
  .page-title  { font-size: 1.1875rem; }

  /* Tabler makes `.page-title` a flex container, so a subtitle marked
     `d-block` becomes a flex SIBLING rather than a new line — on the report
     forms that left "Incident Report" squeezed into two words on the left with
     the fixture crammed beside it. Let the title wrap and give the subtitle its
     own full-width row. */
  .page-title { flex-wrap: wrap; }
  .page-title > small,
  .page-title > .d-block {
    flex: 1 0 100%;
    font-size: 0.8125rem;
    font-weight: 400;
    line-height: 1.4;
    margin-top: 0.125rem;
  }

  /* Cards go edge-to-edge and lose their side borders — the standard mobile
     list idiom. Keeps content width instead of spending it on chrome. */
  .card { border-radius: var(--rr-radius); }

  /* A row of buttons in a page header stacks and fills, instead of
     overflowing off the right edge. */
  .page-header .btn-list { width: 100%; display: flex; flex-wrap: wrap; gap: var(--rr-space-2); }
  .page-header .btn-list > .btn { flex: 1 1 auto; justify-content: center; }

  /* `.btn-group` pins its buttons to a single line by design, so a five-button
     filter group measures ~818px and drags the whole page into a horizontal
     scroll on a 390px screen — the single most common "the mobile site is
     broken" symptom, and it was happening on the manager dashboard. Let the
     group wrap; each button then needs its own radius back, since the group's
     square-inner-corners rule assumes one unbroken row. */
  .btn-group:not(.btn-group-vertical) {
    flex-wrap: wrap;
    gap: var(--rr-space-2);
  }
  .btn-group:not(.btn-group-vertical) > .btn,
  .btn-group:not(.btn-group-vertical) > .btn-check + .btn {
    border-radius: var(--rr-radius-sm) !important;
    margin-left: 0 !important;
    flex: 0 1 auto;
  }

  /* Same class of bug, one level up: any flex row of controls that was laid
     out for desktop should wrap rather than push the page sideways. */
  .card-actions { flex-wrap: wrap; }

  /* Tabler's `.card-header` is a flex row with `.card-actions` pushed to the
     right. At 390px there is no room for both, so the actions crowd the title
     and read as floating on top of it — the role tab group on the competition
     statistics page sat over "Appointment Counts by Role". Let the header wrap
     and give the actions their own full-width row underneath. */
  .card-header {
    flex-wrap: wrap;
    /* Enough that a descender ("pp", "y") clears the actions row below. */
    row-gap: var(--rr-space-3);
  }
  .card-header > .card-actions {
    /* Tabler sets `margin-top: -8px` to pull actions up onto the title's line.
       Once the header wraps, that exactly cancels the `row-gap` above and the
       actions sit flush against the title — which is what made the role tabs
       look like they were covering "Appointment Counts by Role". */
    margin-top: 0;
  }

  /* Only a WIDE action group takes a row of its own: a tab strip, or two or
     more controls. A lone button — "Clear" on the appointment-list filter card
     — still belongs on the title's line, and forcing it down left it stranded
     and left-aligned under "Filters". `.nav` is matched as a descendant
     because the tab strips wrap their <ul> in a .nav-tabs-container. */
  .card-header > .card-actions:has(> * + *),
  .card-header > .card-actions:has(.nav) {
    margin-left: 0;
    width: 100%;
  }

  /* ...and the general case. Bootstrap's `.d-flex` defaults to `nowrap`, so any
     desktop-authored row of controls drags the page sideways on a phone — a row
     of four action buttons on Manage Expense Claims measured 260px past the
     viewport. Wrapping is the only sane default at this width; a row that
     genuinely must stay on one line says so with `.flex-nowrap` and is honoured
     (those get a scroller instead, below). */
  .d-flex:not(.flex-nowrap):not(.flex-column):not(.flex-md-column) {
    flex-wrap: wrap;
  }

  /* A sub-navigation IS a row that must stay on one line — wrapping a tab bar
     onto two rows looks broken. Give it a horizontal scroller instead, which is
     what `competition_stats_subnav.html` already does and the other four
     subnavs (performance, appointments, equipment, flights) did not. The merit
     list overflowed 184px purely because of this. */
  .navbar-nav.flex-row.flex-nowrap {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
  }
  .navbar-nav.flex-row.flex-nowrap > .nav-item { flex: 0 0 auto; }

  /* Modals become sheets. */
  .modal-dialog { margin: 0; align-items: flex-end; min-height: 100%; }
  .modal-content { border-radius: var(--rr-radius-lg) var(--rr-radius-lg) 0 0; }

  /* Forms: 16px minimum stops iOS Safari zooming the viewport on focus —
     the single most common "the mobile site is broken" report. The phantom
     classes above need it just as much; the incident report's description
     textarea was the field that triggered it.

     Matched on the ELEMENT, not just the class, because a sweep found the floor
     missing three different ways: bulk-reschedule uses bare `<select>`/`<input>`
     with no `.form-control` at all; the report card's own `<style>` sets
     `.filter-group input { font-size: 0.9rem }`, which outranks a single-class
     selector and is loaded after this file; and any field can carry an inline
     font-size (the Smart Appointments note fields once did). `!important` is
     what beats the last two — this is a floor, not a preference, and a page
     that wants smaller text on a phone is asking for the viewport to jump on
     focus. */
  .form-control, .form-select, .form-input, .form-textarea,
  textarea,
  select,
  input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]):not([type="range"]):not([type="file"]):not([type="color"]):not([type="submit"]):not([type="button"]):not([type="image"]) {
    font-size: 16px !important;
  }

  /* The menu button is the one control on every page, and it was 32px — under
     the 44px the rest of the app is held to. Tabler sizes it from padding
     alone, so give it a floor and centre the icon inside it. */
  .navbar-toggler {
    min-height: 44px;
    min-width: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }

  /* Its neighbours in the header — the bell, "?", Setup and the avatar — are
     icon-only on a phone, and Tabler sized them from the icon: 28x36. Same
     floor. */
  header.navbar .navbar-nav.flex-row > .nav-item > .nav-link {
    min-height: 44px;
    min-width: 44px;
    justify-content: center;
  }

  /* The burger's top-level rows (Home, Appointing, ...) were 39px. Four
     classes deep so it outranks Tabler's two-class `.navbar-nav .nav-link`
     whichever stylesheet loads last. */
  .rr-navbar-menu .navbar-nav-v2 > .nav-item > .nav-link { min-height: 44px; }

  /* ---------------------------------------------------------------------
     THE FLEX MIN-WIDTH TRAP

     A flex item defaults to `min-width: auto`, which refuses to shrink below
     its content's min-content width. One long unbroken string — a filename
     like `demo_venue_briefing_ref_only.pdf`, an email, a venue name — therefore
     pushes its whole row past the viewport instead of wrapping, and the page
     gains a horizontal scroll. This is what still overhung Match Documents by
     56px after the table fix: the row was a `.d-flex`, not a table, so nothing
     in the table pass touched it.

     `min-width: 0` only *permits* shrinking; an item that must keep its size
     still says so with `flex-shrink: 0` or a width, and those still win. The
     exclusions below are the things that must never be squeezed narrower than
     their label: buttons, badges, avatars and icons.
     --------------------------------------------------------------------- */
  .d-flex > *:not(.btn):not(.badge):not(.avatar):not(svg):not(img),
  .list-group-item > *:not(.btn):not(.badge):not(.avatar):not(svg):not(img) {
    min-width: 0;
  }

  /* Shrinking is only half of it — the string still has to be allowed to
     break. Scoped to the content containers so it can't affect nav or
     controls.

     `anywhere`, not `break-word`, because only `anywhere` also lowers the
     element's MIN-CONTENT width — which is what lets a flex row carrying a long
     filename shrink instead of pushing the page sideways.

     That same property is exactly wrong inside a table. A table sizes its
     columns from their min-content width, so `anywhere` tells the browser every
     column may shrink to one character: a 10-column appointments table then
     compresses to fit 390px instead of overflowing into its `.table-responsive`
     scroller, and every header renders one letter per line ("D/a/t/e"). It also
     breaks ordinary names mid-word ("Adam Fieldin / g").

     So table cells opt back out. They keep normal wrapping, the table keeps its
     natural width, and the scroller does its job. (This was first patched for
     one card — #officials-activity-content — before it was clear the whole app
     had it.) */
  .list-group-item, .card-body, .card-header, .rr-empty {
    overflow-wrap: anywhere;
  }

  /* Table cells opt back out of the rule above.
     Scoped to `th`/`td` rather than `.table th` because plenty of tables here
     carry their own class instead (`.merit-table`, `.comparison-table`, the
     KMI and parking grids) and shredded just the same. A table cell should
     never break mid-word by default, whatever class its table has. */
  th, td {
    overflow-wrap: normal;
    word-break: normal;
  }

  /* Not wrapping mid-word still leaves the column only as wide as its longest
     word, so "Ashley Beecham (190)" comes out as nine stacked fragments in an
     80px column. `responsive-layout.js` measures what the table would take
     unwrapped and gives it that as a floor; this is the class it toggles for
     one frame to take that measurement. Never leave it on an element. */
  .rr-measure-nowrap th, .rr-measure-nowrap td {
    white-space: nowrap !important;
  }

  /* The flip side: a badge or button excluded from `min-width: 0` above still
     has flex-shrink: 1, so it gets squeezed and CLIPS its own label —
     "DEMO Venue Briefing" rendering as "DEMO Venue Briefi". Now that the text
     beside it can shrink, these can hold their natural width instead; a label
     too long even for that wraps to a second line rather than losing
     characters. */
  .d-flex > .badge,
  .list-group-item .badge,
  .d-flex > .btn,
  .list-group-item .btn {
    flex-shrink: 0;
  }
  .badge {
    white-space: normal;
  }

  /* Match Documents rows: the category badge sits BESIDE the filename, which
     leaves the filename ~230px and forces long ones to break mid-word
     ("demo_travel_itinerar / y_m1.pdf"). Stack the badge above instead — it is
     a tag, not a column — and the filename gets the full width it needs. */
  .doc-row .doc-main {
    flex-direction: column;
    align-items: flex-start !important;
    gap: 0.375rem !important;
    width: 100%;
  }
  .doc-row .doc-main > .badge {
    align-self: flex-start !important;
  }
  .doc-row .doc-meta { width: 100%; }

  /* A row that pairs content with an action (Download, View, Respond) puts the
     action on its own full-width line rather than fighting the text for room. */
  .list-group-item.d-flex.justify-content-between {
    flex-wrap: wrap;
  }
  .list-group-item.d-flex.justify-content-between > *:last-child:has(.btn) {
    width: 100%;
  }
  .list-group-item.d-flex.justify-content-between > *:last-child:has(.btn) .btn {
    width: 100%;
    justify-content: center;
  }

  /* Stat tiles shrink so two fit per row rather than one. */
  .rr-stat-value { font-size: 1.5rem; }

  /* ---------------------------------------------------------------------
     THE MANAGER DASHBOARD'S LENGTH

     The dashboard is 9,541px tall on a phone, and it is worth being precise
     about why: it is NOT a layout problem. Measured at 390px, zero rows sit
     side-by-side — it is already a single column. One card,
     "Officials Last Activity", accounts for 5,565px of it, or 58%, because it
     renders every official across five role tables: 468 rows.

     Those tables are already sorted with the most relevant rows first (longest
     since a game), so the top of each list is the part a manager actually wants.
     Capping each into a scrollable panel keeps every row reachable, keeps the
     column sorting working, and takes roughly 4,000px off the page without
     touching the desktop layout at all.
     --------------------------------------------------------------------- */
  #officials-activity-content .table-responsive {
    max-height: 320px;
    overflow-y: auto;
  }
  /* The role heading must stay put while its own list scrolls under it. */
  #officials-activity-content thead th {
    position: sticky;
    top: 0;
    z-index: 1;
  }

  /* These panels scroll in both directions, so a name takes the width it needs
     rather than wrapping to two lines. (The `overflow-wrap: normal` part is now
     handled for every table above; the `nowrap` is specific to this card.) */
  #officials-activity-content td,
  #officials-activity-content th {
    white-space: nowrap;
  }

  .rr-filter-bar { padding: var(--rr-space-3); gap: var(--rr-space-2); }
  .rr-filter-field { flex: 1 1 100%; }
  .rr-filter-actions { flex: 1 1 100%; }
  .rr-filter-actions .btn { flex: 1 1 auto; justify-content: center; }
}

/* Tablet: two-up stat tiles rather than four cramped ones. */
@media (min-width: 768px) and (max-width: 991.98px) {
  .rr-stat-value { font-size: 1.625rem; }
}

/* =============================================================================
   PRINT — several pages are printed (team sheets, match documents, exports).
   ============================================================================= */
@media print {
  .card { box-shadow: none; border: 1px solid var(--rr-grey-300); }
  .table thead th { background-color: var(--rr-grey-50) !important; -webkit-print-color-adjust: exact; }
}

/* Respect the OS setting — several of the hover transforms above are motion. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  a.card:hover, .card.card-link:hover, .card-link-hover:hover { transform: none; }
}

/* =============================================================================
   PUBLIC FRAME — login, password, errors, public forms

   The pages that extend core/base.html rather than dashboard_base.html: the
   login family (registration/*, userAccounts/*password*), the 4xx/5xx pages,
   public feedback and survey forms, legal pages. They are what a person sees
   BEFORE the app, so they carry the tenant's brand the same way the app does —
   every colour here is a ramp token, so a tenant's AppSettings colours reach
   them through _brand_theme_vars.html with no per-page work.

   See docs/DESIGN_SYSTEM.md §12. The vocabulary, outermost first:

     .rr-public          the page region (neutral background, centred column)
     .rr-public-wide     lets a `.container-tight` column hold a side-by-side split
     .rr-public-split    brand panel + card as ONE elevated unit
     .rr-brand-panel     the tenant's gradient panel: logo or name, accent rule, a line
     .rr-public-card     the white form card on a public page
     .rr-public-footer   the "Powered by RefReflect" sign-off
     .rr-error           an error page: big status numeral, sentence, action

   A login-family page is therefore:

     <div class="rr-public">
       <div class="container container-tight rr-public-wide">
         <div class="rr-public-split">
           <div class="rr-brand-panel">
             <h1 class="rr-brand-panel-title">
               <span class="rr-brand-panel-logo"><img …></span>   (or .rr-brand-panel-name)
               <span class="rr-brand-panel-rule" aria-hidden="true"></span>
               <span class="rr-brand-panel-line">Referee Management System</span>
             </h1>
           </div>
           <div class="card rr-public-card"><div class="card-body">…</div></div>
         </div>
         <div class="rr-public-footer">…</div>
       </div>
     </div>

   registration/_login_card.html is the reference implementation.
   ============================================================================= */

/* .rr-public — THE PAGE REGION. Put it on the outermost element of
   {% block content %}. It paints the neutral page background (the same token
   as <body>, so it has no visible edge inside base.html's container) and
   stacks its children in a column that is vertically centred in the viewport
   from 992px, where a short form would otherwise hang at the top of a tall
   empty page. On a phone it is plain flow: the keyboard, not the layout,
   decides what is on screen there. */
.rr-public {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding-block: var(--rr-space-5);
  background-color: var(--tblr-body-bg);
}
@media (min-width: 992px) {
  .rr-public {
    /* 12rem ≈ base.html's navbar plus .page-body's margins, so the region
       fills the viewport without making the page scroll. */
    min-height: calc(100vh - 12rem);
    min-height: calc(100dvh - 12rem);
    padding-block: var(--rr-space-6);
  }
}

/* .rr-public-wide — a MODIFIER for `.container.container-tight`. Below 992px
   the column stays Tabler's 30rem, which is right for a card with the brand
   panel above it; from 992px it widens to hold the panel BESIDE the card.
   (The login page keeps `container-tight` rather than swapping it for a new
   class because gallery/tests.py tells the plain layout from the photo one by
   that class.) */
@media (min-width: 992px) {
  .container-tight.rr-public-wide { max-width: 58rem; }
}

/* .rr-public-split — BRAND PANEL + CARD AS ONE UNIT. Direct children: one
   `.rr-brand-panel` then one `.card.rr-public-card`. The unit owns the
   rounding and the elevation and clips its children to them, so the two read
   as one object: panel ABOVE the card below 992px, BESIDE it from 992px.

   .rr-public-split-stacked keeps the panel above the card at every width — for
   a unit that sits in a narrow column whatever the viewport (the photo login's
   ~380px rail), and for a public form whose card wants the tenant's name as a
   header band. */
.rr-public-split {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border-radius: var(--rr-radius-xl);
  background-color: var(--tblr-bg-surface);
  box-shadow: var(--rr-shadow-lg);
}
.rr-public-split > .rr-brand-panel {
  flex: 0 0 auto;
  border-radius: 0;
}
/* Three classes on purpose: gallery/login_photo.css gives `.login-rail .card`
   its own drop shadow on a phone, and loads after this file. Inside the unit
   the card must stay flush — the unit carries the shadow. */
.rr-public-split > .card.rr-public-card {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}
@media (min-width: 992px) {
  .rr-public-split:not(.rr-public-split-stacked) { flex-direction: row; }
  .rr-public-split:not(.rr-public-split-stacked) > .rr-brand-panel {
    flex: 0 0 42%;
    padding: var(--rr-space-6) 2.5rem;
  }
  .rr-public-split:not(.rr-public-split-stacked) .rr-brand-panel-name { font-size: 2.25rem; }
  .rr-public-split:not(.rr-public-split-stacked) .rr-brand-panel-logo img { max-height: 3.5rem; }
}

/* .rr-brand-panel — THE TENANT'S PANEL. The primary → primary-dark gradient
   is `--rr-panel-header-bg`, the one token `.rr-hero` and the finance and
   performance panel headers already share, so the login panel follows any
   retune of the brand panels instead of drifting from them. It adds the same
   off-corner accent wash as `.rr-hero`. It holds an
   `h1.rr-brand-panel-title` of three parts, top to bottom:

     .rr-brand-panel-logo   the tenant's uploaded logo (ORGANIZATION_LOGO_URL)
                            on a white plate — an uploaded logo is usually drawn
                            for a white page, and the plate keeps a dark one
                            legible on the brand fill;
     .rr-brand-panel-name   …or, with no logo, ORGANIZATION_NAME in the display
                            face;
     .rr-brand-panel-rule   the thin accent (gold) rule — decoration, so
                            aria-hidden;
     .rr-brand-panel-line   one short line under it.

   Standalone (not in a split) it is a rounded band in its own right. It must
   hold no form controls: scripts and automation take the page's first form,
   first submit and first tooltip to be the login form's. */
.rr-brand-panel {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: var(--rr-space-5);
  border-radius: var(--rr-radius-lg);
  background: var(--rr-panel-header-bg);
  color: var(--rr-on-primary);
}
.rr-brand-panel::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(75% 120% at 100% 0%,
      color-mix(in srgb, var(--rr-accent) 12%, transparent) 0%,
      color-mix(in srgb, var(--rr-accent) 4%, transparent) 38%,
      transparent 68%);
}
@supports not (color: color-mix(in srgb, red 50%, blue)) {
  .rr-brand-panel::after { background: none; }
}
/* An <h1>, because on the login page the tenant IS the page's title; reset so
   the heading scale and margins do not fight the panel's own sizes. */
.rr-brand-panel-title {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--rr-space-3);
  min-width: 0;
  margin: 0;
  font-size: 1rem;
  font-weight: 400;
  letter-spacing: 0;
  color: inherit;
}
.rr-brand-panel-logo {
  display: inline-block;
  max-width: 100%;
  padding: var(--rr-space-2) var(--rr-space-3);
  line-height: 0;
  border-radius: var(--rr-radius);
  background-color: var(--tblr-bg-surface);
  box-shadow: var(--rr-shadow-sm);
}
.rr-brand-panel-logo img {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 2.75rem;
}
.rr-brand-panel-name {
  display: block;
  max-width: 100%;
  font-family: var(--rr-font-display);
  font-size: 1.625rem;
  font-weight: 800;
  line-height: 1.08;
  letter-spacing: -0.02em;
  overflow-wrap: anywhere;
  color: var(--rr-on-primary);
}
.rr-brand-panel-rule {
  display: block;
  width: 2.5rem;
  height: 3px;
  border-radius: 999px;
  background-color: var(--rr-accent);
}
/* Where color-mix is unsupported the declaration is invalid at computed-value
   time and `color` falls back to inheriting — the panel's on-primary white. */
.rr-brand-panel-line {
  display: block;
  font-size: 0.9375rem;
  font-weight: 500;
  line-height: 1.4;
  color: color-mix(in srgb, var(--rr-on-primary) 80%, transparent);
}

/* .rr-public-card — THE PUBLIC FORM CARD. Goes on a `.card`: a softer, more
   generous card than the app's hairline one, because on a public page it is
   the only object on the screen. Width comes from the column it sits in
   (`container-tight`, `col-lg-6`…), never from the card.

     .rr-public-card-title  the card's heading ("Login to your account")
     .rr-public-aux         a secondary link row under the submit ("Forgot
                            your password?") — padded to a usable tap target
     .rr-public-eye         the show-password toggle inside an input-group

   Controls and full-width buttons get the 44px tap-target floor at every
   width, not only on phones: a sign-in form is used on touch laptops too. */
.rr-public-card {
  border-radius: var(--rr-radius-xl);
  box-shadow: var(--rr-shadow-md);
}
.rr-public-card > .card-body { padding: var(--rr-space-6); }
@media (max-width: 575.98px) {
  .rr-public-card > .card-body { padding: var(--rr-space-5); }
}
.rr-public-card-title {
  margin: 0 0 var(--rr-space-5);
  font-family: var(--rr-font-display);
  font-size: 1.375rem;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.015em;
  color: var(--rr-grey-900);
}
.rr-public-card .form-control,
.rr-public-card .form-select { min-height: 2.75rem; }
.rr-public-card .form-footer { margin-top: var(--rr-space-5); }
.rr-public-card .btn.w-100 { min-height: 2.75rem; }
.rr-public-card .form-footer .btn { font-weight: 600; }
.rr-public-card .input-group-flat .input-group-text { padding-block: 0; }
/* Tabler draws a flat input-group's focus state in its own hard-coded blue (a
   `rgba(32,107,196,.25)` ring and a `#90b5e2 !important` border) that no
   variable reaches, so the password field — the one control every user
   touches — lit up blue on a green brand. Scoped to the public card here;
   `!important` only because Tabler's border rule carries it. */
.rr-public-card .input-group-flat:focus-within {
  box-shadow: var(--rr-ring);
  border-radius: var(--rr-radius-sm);
}
.rr-public-card .input-group-flat:focus-within .form-control,
.rr-public-card .input-group-flat:focus-within .input-group-text {
  border-color: var(--rr-primary-400) !important;
}
.rr-public-eye {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.5rem;
  min-height: 2.5rem;
}
.rr-public-aux a {
  display: inline-block;
  padding: var(--rr-space-2) var(--rr-space-1);
}

/* .rr-public-messages — flash messages above a public card on a page that has
   no base template to render them (the photo login). A light shadow so an
   alert still reads as a surface when it sits over a photograph. */
.rr-public-messages .alert { box-shadow: var(--rr-shadow-sm); }

/* .rr-public-footer — THE "POWERED BY" SIGN-OFF under a public card:
   `.rr-public-footer-label` ("Powered by") over `.rr-public-footer-logo`
   (the RefReflect mark, replacing an inline height/max-width). */
.rr-public-footer {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--rr-space-1);
  margin-top: var(--rr-space-5);
  text-align: center;
  color: var(--rr-grey-500);
}
.rr-public-footer-label {
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.rr-public-footer-logo {
  display: block;
  width: auto;
  height: 1.75rem;
  max-width: 7.5rem;
  object-fit: contain;
}

/* .rr-error — AN ERROR PAGE (4xx/5xx): the empty-state pattern, centred and
   scaled up. Inside `.rr-public`:

     <div class="rr-error">
       <div class="rr-error-code rr-display-num">404</div>   the status, large
       <h1 class="rr-error-title">Page not found</h1>  optional short heading
       <p class="rr-error-text">One sentence.</p>
       <div class="rr-error-actions"><a class="btn btn-primary">…</a></div>
       <p class="rr-error-note">If you believe this is an error…</p>  optional
     </div>

   THE NUMERAL NEEDS `.rr-error-code`. DESIGN_SYSTEM.md §12 says "the status
   as a large `.rr-display-num`", but `.rr-display-num` alone only sets the
   face, weight and tabular figures: the size, the brand-primary colour and
   the accent rule under it all live on `.rr-error-code`. Put both classes on
   it as above (`.rr-error-code` alone is also complete: it repeats those
   three properties, and wins on line-height and tracking because it comes
   later). The accent rule ties the page to the login screen's brand panel. */
.rr-error {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  max-width: 36rem;
  margin-inline: auto;
  padding: var(--rr-space-6) var(--rr-space-4);
  text-align: center;
}
.rr-error-code {
  font-family: var(--rr-font-display);
  font-size: clamp(4.5rem, 3rem + 8vw, 7.5rem);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.04em;
  font-variant-numeric: tabular-nums;
  color: var(--rr-primary-600);
}
.rr-error-code::after {
  content: "";
  display: block;
  width: 3rem;
  height: 4px;
  margin: var(--rr-space-4) auto 0;
  border-radius: 999px;
  background-color: var(--rr-accent);
}
.rr-error-title {
  margin: var(--rr-space-5) 0 var(--rr-space-2);
  font-size: 1.25rem;
  font-weight: 600;
  color: var(--rr-grey-900);
}
.rr-error-text {
  max-width: 30rem;
  margin: 0 0 var(--rr-space-5);
  color: var(--rr-grey-600);
}
.rr-error-code + .rr-error-text { margin-top: var(--rr-space-5); }
.rr-error-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--rr-space-2);
}
.rr-error-note {
  margin: var(--rr-space-5) 0 0;
  font-size: 0.8125rem;
  color: var(--rr-grey-500);
}

/* =============================================================================
   THEME PICKER — Settings -> Branding & theme (templates/settings/branding.html)

   Each theme card and the live preview set their own colours as --tp-*
   custom properties in a style attribute: --tp-primary, --tp-dark, --tp-mid,
   --tp-accent, --tp-accent-text, --tp-on-primary, --tp-on-accent, --tp-tint.
   --tp-accent-text is the accent when it reads on the primary, otherwise the
   primary's ink (core/branding_colours.accent_text). These rules paint
   only from those, never from the live brand tokens, so a card shows ITS
   theme whatever theme the page itself is in.
   ============================================================================= */
.rr-theme-card { overflow: hidden; }
.rr-theme-card.is-active { box-shadow: 0 0 0 2px var(--tp-mid), var(--rr-shadow-sm); }
.rr-theme-sample {
  position: relative;
  display: flex; align-items: flex-end; justify-content: space-between; gap: 0.75rem;
  min-height: 5.5rem; padding: 0.875rem 1rem 1.125rem;
  background: linear-gradient(135deg, var(--tp-primary) 0%, var(--tp-dark) 100%);
  color: var(--tp-on-primary);
}
.rr-theme-sample::after {
  /* The accent rule the real hero and email header carry. */
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 4px;
  background: var(--tp-accent);
}
.rr-theme-sample-title {
  font-family: var(--rr-font-display); font-weight: 800; font-size: 1.125rem;
  letter-spacing: -0.01em;
}
.rr-theme-sample-chip {
  display: inline-block; flex: 0 0 auto;
  padding: 0.25rem 0.625rem; border-radius: var(--rr-radius-sm);
  font-size: 0.75rem; font-weight: 700;
  background: var(--tp-accent); color: var(--tp-on-accent);
}
.rr-theme-swatches { display: flex; align-items: center; gap: 0.375rem; }
.rr-theme-swatch {
  width: 1.375rem; height: 1.375rem; border-radius: 50%;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.12);
}
.rr-theme-input .form-control-color { flex: 0 0 3rem; padding: 0.25rem; }

/* Live preview: a navbar strip, a hero, a link, a button and a badge. */
.rr-theme-live-nav {
  display: flex; align-items: center; gap: 0.25rem; flex-wrap: wrap;
  padding: 0.5rem 0.75rem; margin-bottom: 0.75rem; border-radius: var(--rr-radius-sm);
  background: var(--tp-primary); color: var(--tp-on-primary);
  font-size: 0.8125rem;
}
.rr-theme-live-brand { font-weight: 800; margin-right: auto; }
.rr-theme-live-link { padding: 0.125rem 0.5rem; border-radius: var(--rr-radius-sm); opacity: 0.85; }
.rr-theme-live-link.is-active {
  opacity: 1; box-shadow: inset 0 0 0 1px var(--tp-accent);
  color: var(--tp-accent-text, var(--tp-accent));
}
.rr-theme-live-hero {
  position: relative; padding: 0.875rem 1rem 1.125rem; margin-bottom: 0.875rem;
  border-radius: var(--rr-radius-sm); overflow: hidden;
  background: linear-gradient(135deg, var(--tp-primary) 0%, var(--tp-dark) 100%);
  color: var(--tp-on-primary);
}
.rr-theme-live-hero::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 4px;
  background: var(--tp-accent);
}
.rr-theme-live-eyebrow {
  font-size: 0.6875rem; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--tp-accent-text, var(--tp-accent));
  margin-bottom: 0.25rem;
}
.rr-theme-live-title {
  font-family: var(--rr-font-display); font-weight: 800; font-size: 1.25rem;
  margin-bottom: 0.625rem;
}
.rr-theme-live-a { color: var(--tp-mid); font-weight: 600; }
.rr-theme-live-btn {
  display: inline-block; padding: 0.375rem 0.875rem; border-radius: var(--rr-radius-sm);
  font-weight: 600; font-size: 0.875rem;
  background: var(--tp-primary); color: var(--tp-on-primary);
}
.rr-theme-live-badge {
  display: inline-block; padding: 0.125rem 0.5rem; border-radius: 999px;
  font-size: 0.75rem; font-weight: 700;
  background: var(--tp-tint); color: var(--tp-mid);
}

/* =============================================================================
   INSTALL AS AN APP — the install banner (includes/_app_install_banner.html)
   and the Get the App page (core/app/install.html). Both are driven by
   static/core/js/installable_app.js.

   The banner floats at the bottom of the screen: full width above a phone's
   home indicator, a card in the bottom-right corner on a computer. It sits
   under the header (1030), the phone menu drawer (1025) and every modal, so
   nothing it covers is ever unreachable — and it is dismissible.
   ============================================================================= */
.rr-app-banner {
  position: fixed;
  z-index: 1020;
  left: var(--rr-space-3);
  right: var(--rr-space-3);
  bottom: calc(var(--rr-space-3) + env(safe-area-inset-bottom, 0px));
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--rr-space-2) var(--rr-space-3);
  align-items: center;
  padding: var(--rr-space-3) var(--rr-space-4);
  background: var(--tblr-bg-surface);
  border: 1px solid var(--rr-grey-200);
  border-radius: var(--rr-radius-lg);
  box-shadow: var(--rr-shadow-lg);
}
.rr-app-banner[hidden] { display: none; }
.rr-app-banner-icon {
  width: 48px;
  height: 48px;
  border-radius: var(--rr-radius);
  flex: none;
}
.rr-app-banner-title { font-weight: 700; color: var(--rr-grey-900); line-height: 1.3; }
.rr-app-banner-body { font-size: 0.875rem; color: var(--rr-grey-600); line-height: 1.4; }
.rr-app-banner-actions {
  grid-column: 1 / -1;
  display: flex;
  gap: var(--rr-space-2);
  justify-content: flex-end;
}
.rr-app-banner-actions .btn[hidden] { display: none; }
/* The No thanks form is only there to carry its post; its button lines up
   with the others as if the form were not there. */
.rr-app-banner-actions form { display: contents; }
@media (max-width: 767.98px) {
  .rr-app-banner-actions .btn { flex: 1 1 0; min-height: 44px; }
}
@media (min-width: 768px) {
  .rr-app-banner {
    left: auto;
    right: var(--rr-space-4);
    bottom: var(--rr-space-4);
    width: 24rem;
  }
}
@media print {
  .rr-app-banner { display: none !important; }
}

/* Get the App ---------------------------------------------------------------- */
/* One readable column, as wide as the steps need: a line of instructions
   across a 1440px screen is hard to follow. Full width on a phone. */
.rr-app-page { max-width: 52rem; }
.rr-app-hero .card-body {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--rr-space-3) var(--rr-space-4);
  align-items: center;
}
.rr-app-hero-icon {
  width: 88px;
  height: 88px;
  border-radius: var(--rr-radius-xl);
  box-shadow: var(--rr-shadow-sm);
}
.rr-app-hero-name {
  margin: 0 0 var(--rr-space-1);
  font-family: var(--rr-font-display);
  font-weight: 800;
  font-size: 1.375rem;
}
.rr-app-hero-status {
  grid-column: 1 / -1;
  padding-top: var(--rr-space-3);
  border-top: 1px solid var(--rr-grey-100);
}
.rr-app-hero-status [data-app-state] {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--rr-space-1) var(--rr-space-3);
}
.rr-app-hero-status [data-app-state][hidden] { display: none; }
.rr-app-hero-done { font-weight: 600; color: var(--rr-success); }
@media (max-width: 575.98px) {
  .rr-app-hero-icon { width: 64px; height: 64px; border-radius: var(--rr-radius-lg); }
  .rr-app-hero-status .btn { width: 100%; min-height: 44px; }
}

.rr-app-guides { display: flex; flex-direction: column; gap: var(--rr-space-3); }
.rr-app-guide { margin: 0; }
.rr-app-guide > summary {
  display: flex;
  align-items: center;
  gap: var(--rr-space-3);
  min-height: 44px;
  cursor: pointer;
  list-style: none;
}
.rr-app-guide > summary::-webkit-details-marker { display: none; }
.rr-app-guide > summary::after {
  content: '';
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  margin-left: var(--rr-space-2);
  border-right: 2px solid var(--rr-grey-500);
  border-bottom: 2px solid var(--rr-grey-500);
  transform: rotate(45deg);
  transition: transform 0.15s ease;
}
.rr-app-guide[open] > summary::after { transform: rotate(225deg); }
.rr-app-guide:not([open]) > summary { border-bottom: 0; }
.rr-app-guide-icon { font-size: 1.375rem; color: var(--rr-primary-600); }
.rr-app-guide-title { font-weight: 600; flex: 1 1 auto; min-width: 0; }
.rr-app-steps { padding-left: 1.25rem; margin-bottom: var(--rr-space-3); }
.rr-app-steps li { margin-bottom: var(--rr-space-2); line-height: 1.5; }
.rr-app-steps .ti { font-size: 1.1em; vertical-align: -0.15em; color: var(--rr-primary-600); }
.rr-app-facts { padding-left: 1.25rem; margin: 0; }
.rr-app-facts li { margin-bottom: var(--rr-space-2); line-height: 1.5; }
.rr-app-facts li:last-child { margin-bottom: 0; }
